Skip to content

Cannot use instrumentation with TestModel returning complex types #3145

@phemmer

Description

@phemmer

Initial Checks

Description

While writing some tests, I ran across an issue where having instrumentation enabled while trying to use TestModel causes exceptions.

Example Code

@pytest.mark.asyncio
async def test_bug(capfire: CaptureLogfire):
    class Response(BaseModel):
        foo: str = Field(description="foo")
        bar: int = Field(description="bar")

    model = TestModel(custom_output_args=Response(foo="FOO", bar=1))
    model = InstrumentedModel(model)
    agent = Agent(model)
    result = await agent.run("...", output_type=Response)
    assert result.output == Response(foo="FOO", bar=1)
../../../.local/lib/python3.13/site-packages/pydantic_ai/agent/abstract.py:235: in run
    async for node in agent_run:
../../../.local/lib/python3.13/site-packages/pydantic_ai/run.py:150: in __anext__
    next_node = await self._graph_run.__anext__()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.local/lib/python3.13/site-packages/pydantic_graph/graph.py:758: in __anext__
    return await self.next(self._next_node)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.local/lib/python3.13/site-packages/pydantic_graph/graph.py:731: in next
    self._next_node = await node.run(ctx)
                      ^^^^^^^^^^^^^^^^^^^
../../../.local/lib/python3.13/site-packages/pydantic_ai/_agent_graph.py:397: in run
    return await self._make_request(ctx)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.local/lib/python3.13/site-packages/pydantic_ai/_agent_graph.py:439: in _make_request
    model_response = await ctx.deps.model.request(message_history, model_settings, model_request_parameters)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.local/lib/python3.13/site-packages/pydantic_ai/models/instrumented.py:363: in request
    finish(response)
../../../.local/lib/python3.13/site-packages/pydantic_ai/models/instrumented.py:446: in finish
    self.instrumentation_settings.handle_messages(messages, response, system, span)
../../../.local/lib/python3.13/site-packages/pydantic_ai/models/instrumented.py:258: in handle_messages
    output_messages = self.messages_to_otel_messages([response])
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.local/lib/python3.13/site-packages/pydantic_ai/models/instrumented.py:232: in messages_to_otel_messages
    otel_message = _otel_messages.OutputMessage(role='assistant', parts=message.otel_message_parts(self))
                                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.local/lib/python3.13/site-packages/pydantic_ai/messages.py:1327: in otel_message_parts
    call_part['arguments'] = {k: InstrumentedModel.serialize_any(v) for k, v in part.args.items()}
                                                                                ^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Response(foo='FOO', bar=1), item = 'items'

    def __getattr__(self, item: str) -> Any:
        private_attributes = object.__getattribute__(self, '__private_attributes__')
        if item in private_attributes:
            attribute = private_attributes[item]
            if hasattr(attribute, '__get__'):
                return attribute.__get__(self, type(self))  # type: ignore
    
            try:
                # Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items
                return self.__pydantic_private__[item]  # type: ignore
            except KeyError as exc:
                raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') from exc
        else:
            # `__pydantic_extra__` can fail to be set if the model is not yet fully initialized.
            # See `BaseModel.__repr_args__` for more details
            try:
                pydantic_extra = object.__getattribute__(self, '__pydantic_extra__')
            except AttributeError:
                pydantic_extra = None
    
            if pydantic_extra:
                try:
                    return pydantic_extra[item]
                except KeyError as exc:
                    raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') from exc
            else:
                if hasattr(self.__class__, item):
                    return super().__getattribute__(item)  # Raises AttributeError if appropriate
                else:
                    # this is the current error
>                   raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')
E                   AttributeError: 'Response' object has no attribute 'items'

../../../.local/lib/python3.13/site-packages/pydantic/main.py:991: AttributeError

Python, Pydantic AI & LLM client version

Python 3.13.3
pydantic-ai 1.0.17

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions