From 1ac80bda6f5526181ed76b08787fc3055f660bd3 Mon Sep 17 00:00:00 2001 From: Rutimka <48045755+Rutimka@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:37:30 +0100 Subject: [PATCH] fix(codex): resolve JSON serialization and logging exception propagation in codex_llm (#724) Port fixes from #461 (claude_code_llm) to codex_llm: - Replace json.dumps(result) with result.model_dump_json() for Pydantic models to fix TypeError during consolidation - Wrap record_llm_call tracing block in try/except so logging failures never propagate to retry handler Co-authored-by: Marco Rutsch Co-authored-by: Claude Sonnet 4.6 --- .../engine/providers/codex_llm.py | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/hindsight-api-slim/hindsight_api/engine/providers/codex_llm.py b/hindsight-api-slim/hindsight_api/engine/providers/codex_llm.py index 04e25710..bbaddd3d 100644 --- a/hindsight-api-slim/hindsight_api/engine/providers/codex_llm.py +++ b/hindsight-api-slim/hindsight_api/engine/providers/codex_llm.py @@ -263,24 +263,27 @@ class CodexLLM(LLMInterface): ) # Record trace span - from hindsight_api.tracing import get_span_recorder + try: + from hindsight_api.tracing import get_span_recorder - # Estimate tokens for tracing - estimated_input = sum(len(m.get("content", "")) for m in messages) // 4 - estimated_output = len(content) // 4 - span_recorder = get_span_recorder() - span_recorder.record_llm_call( - provider=self.provider, - model=self.model, - scope=scope, - messages=messages, - response_content=result if isinstance(result, str) else json.dumps(result), - input_tokens=estimated_input, - output_tokens=estimated_output, - duration=duration, - finish_reason=None, - error=None, - ) + # Estimate tokens for tracing + estimated_input = sum(len(m.get("content", "")) for m in messages) // 4 + estimated_output = len(content) // 4 + span_recorder = get_span_recorder() + span_recorder.record_llm_call( + provider=self.provider, + model=self.model, + scope=scope, + messages=messages, + response_content=result if isinstance(result, str) else result.model_dump_json(), + input_tokens=estimated_input, + output_tokens=estimated_output, + duration=duration, + finish_reason=None, + error=None, + ) + except Exception: + pass # logging failure must never affect the operation if return_usage: # Codex doesn't provide token counts, estimate based on content @@ -526,26 +529,31 @@ class CodexLLM(LLMInterface): ) # Record OpenTelemetry span - from hindsight_api.tracing import get_span_recorder + try: + from hindsight_api.tracing import get_span_recorder - span_recorder = get_span_recorder() - # Convert LLMToolCall objects to dicts for span recording - tool_calls_dict = ( - [{"id": tc.id, "name": tc.name, "arguments": tc.arguments} for tc in tool_calls] if tool_calls else None - ) - span_recorder.record_llm_call( - provider=self.provider, - model=self.model, - scope=scope, - messages=messages, - response_content=content, - input_tokens=0, # Codex doesn't provide token counts - output_tokens=0, - duration=duration, - finish_reason="tool_calls" if tool_calls else "stop", - error=None, - tool_calls=tool_calls_dict, - ) + span_recorder = get_span_recorder() + # Convert LLMToolCall objects to dicts for span recording + tool_calls_dict = ( + [{"id": tc.id, "name": tc.name, "arguments": tc.arguments} for tc in tool_calls] + if tool_calls + else None + ) + span_recorder.record_llm_call( + provider=self.provider, + model=self.model, + scope=scope, + messages=messages, + response_content=content, + input_tokens=0, # Codex doesn't provide token counts + output_tokens=0, + duration=duration, + finish_reason="tool_calls" if tool_calls else "stop", + error=None, + tool_calls=tool_calls_dict, + ) + except Exception: + pass # logging failure must never affect the operation return LLMToolCallResult( content=content,