@dataclass(init=False)classInstrumentationSettings:"""Options for instrumenting models and agents with OpenTelemetry. Used in: - `Agent(instrument=...)` - [`Agent.instrument_all()`][pydantic_ai.agent.Agent.instrument_all] - [`InstrumentedModel`][pydantic_ai.models.instrumented.InstrumentedModel] See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info. """tracer:Tracer=field(repr=False)event_logger:EventLogger=field(repr=False)event_mode:Literal['attributes','logs']='attributes'def__init__(self,*,event_mode:Literal['attributes','logs']='attributes',tracer_provider:TracerProvider|None=None,event_logger_provider:EventLoggerProvider|None=None,):"""Create instrumentation options. Args: event_mode: The mode for emitting events. If `'attributes'`, events are attached to the span as attributes. If `'logs'`, events are emitted as OpenTelemetry log-based events. tracer_provider: The OpenTelemetry tracer provider to use. If not provided, the global tracer provider is used. Calling `logfire.configure()` sets the global tracer provider, so most users don't need this. event_logger_provider: The OpenTelemetry event logger provider to use. If not provided, the global event logger provider is used. Calling `logfire.configure()` sets the global event logger provider, so most users don't need this. This is only used if `event_mode='logs'`. """frompydantic_aiimport__version__tracer_provider=tracer_providerorget_tracer_provider()event_logger_provider=event_logger_providerorget_event_logger_provider()self.tracer=tracer_provider.get_tracer('pydantic-ai',__version__)self.event_logger=event_logger_provider.get_event_logger('pydantic-ai',__version__)self.event_mode=event_mode
The mode for emitting events. If 'attributes', events are attached to the span as attributes.
If 'logs', events are emitted as OpenTelemetry log-based events.
'attributes'
tracer_provider
TracerProvider | None
The OpenTelemetry tracer provider to use.
If not provided, the global tracer provider is used.
Calling logfire.configure() sets the global tracer provider, so most users don't need this.
None
event_logger_provider
EventLoggerProvider | None
The OpenTelemetry event logger provider to use.
If not provided, the global event logger provider is used.
Calling logfire.configure() sets the global event logger provider, so most users don't need this.
This is only used if event_mode='logs'.
None
Source code in pydantic_ai_slim/pydantic_ai/models/instrumented.py
def__init__(self,*,event_mode:Literal['attributes','logs']='attributes',tracer_provider:TracerProvider|None=None,event_logger_provider:EventLoggerProvider|None=None,):"""Create instrumentation options. Args: event_mode: The mode for emitting events. If `'attributes'`, events are attached to the span as attributes. If `'logs'`, events are emitted as OpenTelemetry log-based events. tracer_provider: The OpenTelemetry tracer provider to use. If not provided, the global tracer provider is used. Calling `logfire.configure()` sets the global tracer provider, so most users don't need this. event_logger_provider: The OpenTelemetry event logger provider to use. If not provided, the global event logger provider is used. Calling `logfire.configure()` sets the global event logger provider, so most users don't need this. This is only used if `event_mode='logs'`. """frompydantic_aiimport__version__tracer_provider=tracer_providerorget_tracer_provider()event_logger_provider=event_logger_providerorget_event_logger_provider()self.tracer=tracer_provider.get_tracer('pydantic-ai',__version__)self.event_logger=event_logger_provider.get_event_logger('pydantic-ai',__version__)self.event_mode=event_mode
@dataclassclassInstrumentedModel(WrapperModel):"""Model which wraps another model so that requests are instrumented with OpenTelemetry. See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info. """settings:InstrumentationSettings"""Configuration for instrumenting requests."""def__init__(self,wrapped:Model|KnownModelName,options:InstrumentationSettings|None=None,)->None:super().__init__(wrapped)self.settings=optionsorInstrumentationSettings()asyncdefrequest(self,messages:list[ModelMessage],model_settings:ModelSettings|None,model_request_parameters:ModelRequestParameters,)->tuple[ModelResponse,Usage]:withself._instrument(messages,model_settings,model_request_parameters)asfinish:response,usage=awaitsuper().request(messages,model_settings,model_request_parameters)finish(response,usage)returnresponse,usage@asynccontextmanagerasyncdefrequest_stream(self,messages:list[ModelMessage],model_settings:ModelSettings|None,model_request_parameters:ModelRequestParameters,)->AsyncIterator[StreamedResponse]:withself._instrument(messages,model_settings,model_request_parameters)asfinish:response_stream:StreamedResponse|None=Nonetry:asyncwithsuper().request_stream(messages,model_settings,model_request_parameters)asresponse_stream:yieldresponse_streamfinally:ifresponse_stream:finish(response_stream.get(),response_stream.usage())@contextmanagerdef_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}'# TODO Missing attributes:# - error.type: unclear if we should do something here or just always rely on span exceptions# - gen_ai.request.stop_sequences/top_k: model_settings doesn't include theseattributes: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'}},}),}ifmodel_settings:forkeyinMODEL_SETTING_ATTRIBUTES:ifisinstance(value:=model_settings.get(key),(float,int)):attributes[f'gen_ai.request.{key}']=valuewithself.settings.tracer.start_as_current_span(span_name,attributes=attributes)asspan:deffinish(response:ModelResponse,usage:Usage):ifnotspan.is_recording():returnevents=self.messages_to_otel_events(messages)foreventinself.messages_to_otel_events([response]):events.append(Event('gen_ai.choice',body={# TODO finish_reason'index':0,'message':event.body,},))new_attributes:dict[str,AttributeValue]=usage.opentelemetry_attributes()# type: ignoreattributes.update(getattr(span,'attributes',{}))request_model=attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]new_attributes['gen_ai.response.model']=response.model_nameorrequest_modelspan.set_attributes(new_attributes)span.update_name(f'{operation}{request_model}')foreventinevents:event.attributes={GEN_AI_SYSTEM_ATTRIBUTE:attributes[GEN_AI_SYSTEM_ATTRIBUTE],**(event.attributesor{}),}self._emit_events(span,events)yieldfinishdef_emit_events(self,span:Span,events:list[Event])->None:ifself.settings.event_mode=='logs':foreventinevents:self.settings.event_logger.emit(event)else:attr_name='events'span.set_attributes({attr_name:json.dumps([self.event_to_dict(event)foreventinevents]),'logfire.json_schema':json.dumps({'type':'object','properties':{attr_name:{'type':'array'},'model_request_parameters':{'type':'object'},},}),})@staticmethoddefmodel_attributes(model:Model):attributes:dict[str,AttributeValue]={GEN_AI_SYSTEM_ATTRIBUTE:model.system,GEN_AI_REQUEST_MODEL_ATTRIBUTE:model.model_name,}ifbase_url:=model.base_url:try:parsed=urlparse(base_url)exceptException:# pragma: no coverpasselse:ifparsed.hostname:attributes['server.address']=parsed.hostnameifparsed.port:attributes['server.port']=parsed.portreturnattributes@staticmethoddefevent_to_dict(event:Event)->dict[str,Any]:ifnotevent.body:body={}elifisinstance(event.body,Mapping):body=event.body# type: ignoreelse:body={'body':event.body}return{**body,**(event.attributesor{})}@staticmethoddefmessages_to_otel_events(messages:list[ModelMessage])->list[Event]:events:list[Event]=[]formessage_index,messageinenumerate(messages):message_events:list[Event]=[]ifisinstance(message,ModelRequest):forpartinmessage.parts:ifhasattr(part,'otel_event'):message_events.append(part.otel_event())elifisinstance(message,ModelResponse):message_events=message.otel_events()foreventinmessage_events:event.attributes={'gen_ai.message.index':message_index,**(event.attributesor{}),}events.extend(message_events)foreventinevents:event.body=InstrumentedModel.serialize_any(event.body)returnevents@staticmethoddefserialize_any(value:Any)->str:try:returnANY_ADAPTER.dump_python(value,mode='json')exceptException:try:returnstr(value)exceptExceptionase:returnf'Unable to serialize: {e}'