Skip to content

fix(llmobs): preserve openai assistant content when tool_calls are present#17713

Open
jessicagamio wants to merge 1 commit intoDataDog:mainfrom
jessicagamio:fix-MLOS-605-extract-tool-call
Open

fix(llmobs): preserve openai assistant content when tool_calls are present#17713
jessicagamio wants to merge 1 commit intoDataDog:mainfrom
jessicagamio:fix-MLOS-605-extract-tool-call

Conversation

@jessicagamio
Copy link
Copy Markdown
Contributor

Summary

  • Fix MLOB-7340 (escalation: MLOS-605): OpenAI's LLMObs integration was unconditionally clearing content on any message carrying tool_calls, silently dropping legitimate assistant prose from replayed conversation history.
  • Gate the content = "" clear so it only fires when capture_plain_text_tool_usage actually appended a ReAct-parsed tool call to the list (ReAct content is a stringified duplicate of the structured call; native OpenAI content is independent prose and must be preserved).
  • Normalize None content to "" before stringifying — avoids leaking the literal string "None" into the span on OpenAI responses where tool_calls is set and content=None (previously masked by the unconditional clear).

Test plan

  • riot -v run -s -p 3.12 '^openai$' --no-cov -k test_chat_completion_tool_call_preserves_assistant_content or test_chat_completion_react_style_content_still_deduplicates — both new tests pass across all 4 openai riot configs (latest, <2.0.0, ~=1.76.2, ==1.66.0).
  • riot -v run -s -p 3.12 '^openai$' --no-cov tests/contrib/openai/test_openai_llmobs.py — full file passes (47 passed, 13 skipped) across all 4 openai configs. No regressions to test_chat_completion_tool_call_with_follow_up, which continues to cover the tool_calls-only and tool_results-restructured paths.
  • hatch run lint:style — PASS.
  • hatch run lint:typingSuccess: no issues found in 1013 source files.
  • Customer verified the fix resolves MLOS-605 against their reproduction.

…esent

OpenAI's native function-calling schema allows an assistant message to carry
both prose `content` and structured `tool_calls` on the same message. The
openai LLMObs integration was unconditionally clearing `content` whenever
tool_calls were extracted, dropping legitimate assistant narration from
replayed history. Only clear `content` when it was the source of the
tool_calls (ReAct-style agents), leaving native tool_calls untouched.

Also normalize `None` content to `""` before stringifying so that OpenAI
responses with `content=None` + `tool_calls` no longer leak the literal
string `"None"` into the span (previously masked by the unconditional clear).

MLOB-7340

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant