diff --git a/pydantic_ai_slim/pydantic_ai/models/instrumented.py b/pydantic_ai_slim/pydantic_ai/models/instrumented.py index 3587eaa8ff..b5beae9b57 100644 --- a/pydantic_ai_slim/pydantic_ai/models/instrumented.py +++ b/pydantic_ai_slim/pydantic_ai/models/instrumented.py @@ -112,7 +112,7 @@ async def request( model_settings: ModelSettings | None, model_request_parameters: ModelRequestParameters, ) -> tuple[ModelResponse, Usage]: - with self._instrument(messages, model_settings) as finish: + with self._instrument(messages, model_settings, model_request_parameters) as finish: response, usage = await super().request(messages, model_settings, model_request_parameters) finish(response, usage) return response, usage @@ -124,7 +124,7 @@ async def request_stream( model_settings: ModelSettings | None, model_request_parameters: ModelRequestParameters, ) -> AsyncIterator[StreamedResponse]: - with self._instrument(messages, model_settings) as finish: + with self._instrument(messages, model_settings, model_request_parameters) as finish: response_stream: StreamedResponse | None = None try: async with super().request_stream( @@ -140,6 +140,7 @@ def _instrument( self, messages: list[ModelMessage], model_settings: ModelSettings | None, + model_request_parameters: ModelRequestParameters, ) -> Iterator[Callable[[ModelResponse, Usage], None]]: operation = 'chat' span_name = f'{operation} {self.model_name}' @@ -149,6 +150,13 @@ def _instrument( attributes: dict[str, AttributeValue] = { 'gen_ai.operation.name': operation, **self.model_attributes(self.wrapped), + 'model_request_parameters': json.dumps(InstrumentedModel.serialize_any(model_request_parameters)), + 'logfire.json_schema': json.dumps( + { + 'type': 'object', + 'properties': {'model_request_parameters': {'type': 'object'}}, + } + ), } if model_settings: @@ -201,7 +209,10 @@ def _emit_events(self, span: Span, events: list[Event]) -> None: 'logfire.json_schema': json.dumps( { 'type': 'object', - 'properties': {attr_name: {'type': 'array'}}, + 'properties': { + attr_name: {'type': 'array'}, + 'model_request_parameters': {'type': 'object'}, + }, } ), } diff --git a/tests/models/test_fallback.py b/tests/models/test_fallback.py index d22d9737af..b0381e315f 100644 --- a/tests/models/test_fallback.py +++ b/tests/models/test_fallback.py @@ -136,6 +136,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None: 'end_time': 5000000000, 'attributes': { 'gen_ai.operation.name': 'chat', + 'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}', 'logfire.span_type': 'span', 'logfire.msg': 'chat fallback:function:failure_response:,function:success_response:', 'gen_ai.usage.input_tokens': 51, @@ -160,7 +161,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None: }, ] ), - 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}', + 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, { @@ -233,6 +234,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None 'end_time': 5000000000, 'attributes': { 'gen_ai.operation.name': 'chat', + 'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}', 'logfire.span_type': 'span', 'logfire.msg': 'chat fallback:function::failure_response_stream,function::success_response_stream', 'gen_ai.system': 'function', @@ -241,7 +243,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None 'gen_ai.usage.output_tokens': 2, 'gen_ai.response.model': 'function::success_response_stream', 'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "hello world"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', - 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}', + 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, { diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 85cb18b752..dc813b212d 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -152,6 +152,8 @@ async def test_instrumented_model(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, + 'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}', + 'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', 'logfire.span_type': 'span', @@ -374,6 +376,8 @@ async def test_instrumented_model_stream(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, + 'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}', + 'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', 'logfire.span_type': 'span', @@ -457,6 +461,8 @@ async def test_instrumented_model_stream_break(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, + 'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}', + 'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', 'logfire.span_type': 'span', @@ -559,6 +565,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, + 'model_request_parameters': '{"function_tools": [], "allow_text_result": true, "result_tools": []}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', 'logfire.span_type': 'span', @@ -652,7 +659,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire): ] ) ), - 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}', + 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, ] diff --git a/tests/test_logfire.py b/tests/test_logfire.py index ed72235123..7b0226f940 100644 --- a/tests/test_logfire.py +++ b/tests/test_logfire.py @@ -167,7 +167,7 @@ async def my_ret(x: int) -> str: ) chat_span_attributes = summary.attributes[2] if instrument is True or instrument.event_mode == 'attributes': - attribute_mode_attributes = {k: chat_span_attributes.pop(k) for k in ['events', 'logfire.json_schema']} + attribute_mode_attributes = {k: chat_span_attributes.pop(k) for k in ['events']} assert attribute_mode_attributes == snapshot( { 'events': IsJson( @@ -198,7 +198,6 @@ async def my_ret(x: int) -> str: ] ) ), - 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}}}', } ) @@ -207,6 +206,28 @@ async def my_ret(x: int) -> str: 'gen_ai.operation.name': 'chat', 'gen_ai.system': 'test', 'gen_ai.request.model': 'test', + 'model_request_parameters': IsJson( + snapshot( + { + 'function_tools': [ + { + 'name': 'my_ret', + 'description': '', + 'parameters_json_schema': { + 'additionalProperties': False, + 'properties': {'x': {'type': 'integer'}}, + 'required': ['x'], + 'type': 'object', + }, + 'outer_typed_dict_key': None, + } + ], + 'allow_text_result': True, + 'result_tools': [], + } + ) + ), + 'logfire.json_schema': IsJson(), 'logfire.span_type': 'span', 'logfire.msg': 'chat test', 'gen_ai.response.model': 'test',