1313from openai .types .chat import ChatCompletion , ChatCompletionMessage , ChatCompletionMessageParam
1414from openai .types .chat .chat_completion import Choice as ChatCompletionChoice
1515from pydantic import TypeAdapter
16- from pydantic_ai import Agent
16+ from pydantic_ai import Agent , ModelSettings
1717from pydantic_ai ._utils import generate_tool_call_id
1818from pydantic_ai .messages import ModelMessage
1919from pydantic_ai .messages import (
@@ -46,7 +46,6 @@ def __call__(self, rows: list[EvaluationRow], config: RolloutProcessorConfig) ->
4646 """Create agent rollout tasks and return them for external handling."""
4747
4848 semaphore = config .semaphore
49-
5049 agent = self ._setup_agent (config )
5150
5251 async def process_row (row : EvaluationRow ) -> EvaluationRow :
@@ -70,7 +69,10 @@ async def process_row(row: EvaluationRow) -> EvaluationRow:
7069 row .tools = tools
7170
7271 model_messages = [self .convert_ep_message_to_pyd_message (m , row ) for m in row .messages ]
73- response = await agent .run (message_history = model_messages , usage_limits = config .kwargs .get ("usage_limits" ))
72+ settings = self .construct_model_settings (agent , row )
73+ response = await agent .run (
74+ message_history = model_messages , usage_limits = config .kwargs .get ("usage_limits" ), model_settings = settings
75+ )
7476 row .messages = await self .convert_pyd_message_to_ep_message (response .all_messages ())
7577
7678 # TODO: pydantic ai accumulates usage info across all models in multi-agent setup, so this simple tracking doesn't work for cost. to discuss with @dphuang2 when he's back.
@@ -98,6 +100,24 @@ async def convert_pyd_message_to_ep_message(self, messages: list[ModelMessage])
98100 oai_messages : list [ChatCompletionMessageParam ] = await self ._util ._map_messages (messages )
99101 return [Message (** m ) for m in oai_messages ] # pyright: ignore[reportArgumentType]
100102
103+ def construct_model_settings (self , agent : Agent , row : EvaluationRow ) -> ModelSettings :
104+ model = agent .model
105+ if model and not isinstance (model , str ) and model .settings :
106+ # We must copy model settings to avoid concurrency issues by modifying the same object in-place
107+ settings = model .settings .copy ()
108+ if settings is None :
109+ settings = ModelSettings ()
110+ settings ["extra_body" ] = settings .get ("extra_body" , {})
111+ extra_body = settings ["extra_body" ]
112+ if isinstance (extra_body , dict ):
113+ extra_body ["metadata" ] = settings .get ("metadata" , {})
114+ extra_body ["metadata" ]["row_id" ] = row .input_metadata .row_id
115+ extra_body ["metadata" ]["invocation_id" ] = row .execution_metadata .invocation_id
116+ extra_body ["metadata" ]["rollout_id" ] = row .execution_metadata .rollout_id
117+ extra_body ["metadata" ]["run_id" ] = row .execution_metadata .run_id
118+ extra_body ["metadata" ]["experiment_id" ] = row .execution_metadata .experiment_id
119+ return settings
120+
101121 def convert_ep_message_to_pyd_message (self , message : Message , row : EvaluationRow ) -> ModelMessage :
102122 if message .role == "assistant" :
103123 type_adapter = TypeAdapter (ChatCompletionMessage )
0 commit comments