From cc3b0f13a6faf6c49d146e59e84bb7eb5ae00caa Mon Sep 17 00:00:00 2001 From: Kian Jones <11655409+kianjones9@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:28:49 -0800 Subject: [PATCH] fix: remove duplicate provider trace logging in LettaAgent (#9276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provider traces were being created twice per step: 1. Via `request_async_with_telemetry` / `log_provider_trace_async` in LLMClient 2. Via direct `create_provider_trace_async` calls in LettaAgent This caused duplicate records in provider_trace_metadata (Postgres) and llm_traces (ClickHouse) for every agent step. Removed the redundant direct calls since telemetry is now centralized in the LLM client layer. 🤖 Generated with [Letta Code](https://letta.com) Co-authored-by: Letta --- letta/agents/letta_agent.py | 75 ------------------------------------- 1 file changed, 75 deletions(-) diff --git a/letta/agents/letta_agent.py b/letta/agents/letta_agent.py index 06ca2822..e60fda2d 100644 --- a/letta/agents/letta_agent.py +++ b/letta/agents/letta_agent.py @@ -49,7 +49,6 @@ from letta.schemas.openai.chat_completion_response import ( UsageStatisticsCompletionTokenDetails, UsageStatisticsPromptTokenDetails, ) -from letta.schemas.provider_trace import ProviderTrace from letta.schemas.step import StepProgression from letta.schemas.step_metrics import StepMetrics from letta.schemas.tool_execution_result import ToolExecutionResult @@ -409,25 +408,6 @@ class LettaAgent(BaseAgent): agent_step_span.add_event(name="step_ms", attributes={"duration_ms": ns_to_ms(step_ns)}) agent_step_span.end() - # Log LLM Trace - if settings.track_provider_trace: - await self.telemetry_manager.create_provider_trace_async( - actor=self.actor, - provider_trace=ProviderTrace( - request_json=request_data, - response_json=response_data, - step_id=step_id, - agent_id=self.agent_id, - agent_tags=agent_state.tags, - run_id=self.current_run_id, - call_type=LLMCallType.agent_step, - org_id=self.actor.organization_id, - user_id=self.actor.id, - llm_config=self.agent_state.llm_config.model_dump() if self.agent_state.llm_config else None, - ), - ) - step_progression = StepProgression.LOGGED_TRACE - # stream step # TODO: improve TTFT filter_user_messages = [m for m in persisted_messages if m.role != "user"] @@ -763,25 +743,6 @@ class LettaAgent(BaseAgent): agent_step_span.add_event(name="step_ms", attributes={"duration_ms": ns_to_ms(step_ns)}) agent_step_span.end() - # Log LLM Trace - if settings.track_provider_trace: - await self.telemetry_manager.create_provider_trace_async( - actor=self.actor, - provider_trace=ProviderTrace( - request_json=request_data, - response_json=response_data, - step_id=step_id, - agent_id=self.agent_id, - agent_tags=agent_state.tags, - run_id=self.current_run_id, - call_type=LLMCallType.agent_step, - org_id=self.actor.organization_id, - user_id=self.actor.id, - llm_config=self.agent_state.llm_config.model_dump() if self.agent_state.llm_config else None, - ), - ) - step_progression = StepProgression.LOGGED_TRACE - MetricRegistry().step_execution_time_ms_histogram.record(get_utc_timestamp_ns() - step_start, get_ctx_attributes()) step_progression = StepProgression.FINISHED @@ -1224,42 +1185,6 @@ class LettaAgent(BaseAgent): # TODO (cliandy): the stream POST request span has ended at this point, we should tie this to the stream # log_event("agent.stream.llm_response.processed") # [4^] - # Log LLM Trace - # We are piecing together the streamed response here. - # Content here does not match the actual response schema as streams come in chunks. - if settings.track_provider_trace: - await self.telemetry_manager.create_provider_trace_async( - actor=self.actor, - provider_trace=ProviderTrace( - request_json=request_data, - response_json={ - "content": { - "tool_call": tool_call.model_dump_json(), - "reasoning": [content.model_dump_json() for content in reasoning_content], - }, - "id": interface.message_id, - "model": interface.model, - "role": "assistant", - # "stop_reason": "", - # "stop_sequence": None, - "type": "message", - "usage": { - "input_tokens": usage.prompt_tokens, - "output_tokens": usage.completion_tokens, - }, - }, - step_id=step_id, - agent_id=self.agent_id, - agent_tags=agent_state.tags, - run_id=self.current_run_id, - call_type=LLMCallType.agent_step, - org_id=self.actor.organization_id, - user_id=self.actor.id, - llm_config=self.agent_state.llm_config.model_dump() if self.agent_state.llm_config else None, - ), - ) - step_progression = StepProgression.LOGGED_TRACE - if persisted_messages[-1].role != "approval": # yields tool response as this is handled from Letta and not the response from the LLM provider tool_return = [msg for msg in persisted_messages if msg.role == "tool"][-1].to_letta_messages()[0]