Skip to content

fix(llm): tolerate list-shaped delta content in strip_thinking_tokens#6324

Open
KSerProject wants to merge 1 commit into
livekit:mainfrom
KSerProject:fix-strip-thinking-tokens-list-content
Open

fix(llm): tolerate list-shaped delta content in strip_thinking_tokens#6324
KSerProject wants to merge 1 commit into
livekit:mainfrom
KSerProject:fix-strip-thinking-tokens-list-content

Conversation

@KSerProject

Copy link
Copy Markdown

OpenAI-compatible providers (e.g. Mistral) may stream delta.content as a list of typed content parts instead of a plain string. strip_thinking_tokens assumed str and crashed with AttributeError, which surfaces as a retryable APIConnectionError mid-stream and makes a FallbackAdapter silently switch the whole session to its fallback leg.

This flattens the text parts before the tag scan; plain strings and None are untouched.

Fixes #6323

OpenAI-compatible providers (e.g. Mistral) may stream delta.content as
a list of typed content parts. strip_thinking_tokens assumed str and
crashed with AttributeError ('list' object has no attribute 'find'),
which surfaces as a retryable APIConnectionError mid-stream and makes a
FallbackAdapter silently switch the whole session to its fallback leg.
Flatten text parts before the tag scan.
@KSerProject KSerProject requested a review from a team as a code owner July 5, 2026 15:01
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +612 to +618
if isinstance(content, list):
# OpenAI-compatible providers (e.g. Mistral) may stream delta.content as a
# list of typed content parts instead of a plain string.
content = (
"".join(part.get("text", "") for part in content if isinstance(part, dict))
or None
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Joining list parts may increase likelihood of thinking token leakage

The thinking token state machine in strip_thinking_tokens processes only one tag transition per call β€” it finds either <think> or </think> but not both. When content was always a plain string from streaming, each chunk was typically small enough that both tags rarely appeared in the same chunk. With the new list joining (livekit-agents/livekit/agents/llm/utils.py:616), multiple content parts are concatenated into a single string, making it more likely that both <think> and </think> appear in one call. In that case, only the first tag is processed: if <think>reasoning</think>actual arrives as a single joined string, the function sets the thinking flag and returns "reasoning</think>actual", leaking the model's internal reasoning to the user. This is a pre-existing design limitation, but the list-joining makes it a more plausible scenario.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

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.

strip_thinking_tokens crashes on list-shaped streaming delta content (AttributeError: 'list' object has no attribute 'find')

2 participants