Skip to content

Conversation

@timvw
Copy link

@timvw timvw commented Dec 5, 2025

Summary

Adds defensive handling and additional test coverage for empty tool call function names in SSE streaming, complementing the root cause fix in PR #7594.

Context

PR #7594 already fixed the root cause by using get_or_insert_with() to prevent overwriting tool names. This PR builds on that fix by adding:

  1. Defensive layer at emission point with debug logging
  2. Additional test coverage for edge cases

Issues Addressed

Changes in This PR

1. Enhanced Documentation (lines 164-166)

Adds comment explaining the root cause and linking to issue #7579:

// Only update name if non-empty to prevent subsequent deltas
// from overwriting the initial name with empty strings.
// See: https://github.com/openai/codex/issues/7579

2. Defensive Layer at Emission Point (lines 228-251)

Even though the root cause is fixed, this adds defensive handling to skip any tool calls that somehow end up with empty/missing names, with detailed debug logging for troubleshooting:

match state.name {
    Some(name) if !name.is_empty() => { /* emit tool call */ }
    Some(name) if name.is_empty() => {
        debug!("Skipping tool call with empty name: call_id={}, arguments={}", ...);
    }
    None => {
        debug!("Skipping tool call with missing name: call_id={}, arguments={}", ...);
    }
}

This helps diagnose issues by logging:

  • call_id of problematic tool calls
  • arguments that were received
  • Whether name was empty string vs completely missing

3. Additional Test Coverage

Adds skips_tool_calls_with_empty_or_missing_names test to verify the defensive layer works correctly when tool calls have empty or missing names.

Test Results

All tests passing (7/7):

  • emits_multiple_tool_calls
  • concatenates_tool_call_arguments_across_deltas
  • emits_tool_calls_even_when_content_and_reasoning_present
  • drops_partial_tool_calls_on_stop_finish_reason
  • preserves_tool_call_name_when_empty_deltas_arrive (from PR fix: sse for chat #7594)
  • preserves_name_when_subsequent_deltas_have_empty_names (this PR)
  • skips_tool_calls_with_empty_or_missing_names (this PR)

Value Add

While PR #7594 fixed the root cause, this PR provides:

  1. Defense in depth - Additional safety layer if the root cause fix is bypassed
  2. Better observability - Debug logging helps diagnose streaming issues in production
  3. Comprehensive testing - Edge case coverage for empty/missing names
  4. Documentation - Links to GitHub issues explaining the problem

Testing

cargo test --package codex-api --lib sse::chat::tests

@github-actions
Copy link

github-actions bot commented Dec 5, 2025

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@timvw
Copy link
Author

timvw commented Dec 5, 2025

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Dec 5, 2025
timvw added 2 commits December 5, 2025 10:45
This commit improves handling of malformed tool calls in SSE streams:

1. Defensive fix: Skip tool calls that have empty or missing names to
   prevent API errors downstream
2. Debug logging: Add detailed logging when skipping tool calls to help
   diagnose root cause (model issue vs streaming issue)
3. Test coverage: Add test case to verify empty/missing names are
   properly filtered out

The issue can occur when:
- The SSE stream from the API is incomplete or corrupted
- The model sends tool call deltas without including the name field
- Tool call streaming completes with finish_reason="tool_calls" but
  some tool calls never received their name in any delta

The debug logs will show:
- call_id of the problematic tool call
- arguments that were received (if any)
- whether the name was empty string or completely missing

This helps identify if the issue is:
1. A model bug (consistently missing names for certain patterns)
2. A streaming issue (names lost during transmission)
3. A timing issue (finish_reason sent before all deltas arrived)
This commit addresses the root cause identified in GitHub issues:
- openai#7094: Empty function name cause Azure to reject the request
- openai#7517: Tool call streaming broken in 0.64.0 with local LLM providers
- openai#7579: Regression: Tool Name Lost in Streaming Chat Completions

Root Cause (Issue openai#7579):
The SSE streaming parser was unconditionally overwriting tool call names
with values from subsequent deltas. Many providers (Azure OpenAI, LM Studio,
etc.) send the tool name in the first delta, then send empty strings in
subsequent deltas containing only arguments.

Before this fix:
1. First delta: name="my_tool", arguments="{"
2. Second delta: name="", arguments="foo}"
3. Result: name gets overwritten with "" -> causes API errors

The Fix (Two Layers):

1. ROOT CAUSE FIX (lines 165-170):
   Don't overwrite existing tool call names with empty strings during
   delta parsing. Only update the name if the new value is non-empty.
   This preserves the name from the first delta.

2. DEFENSIVE FIX (lines 228-251):
   Even with the root cause fixed, we add defensive handling at the
   emission point to skip any tool calls that somehow end up with
   empty/missing names, with debug logging to help diagnose issues.

Test Coverage:
- preserves_name_when_subsequent_deltas_have_empty_names: Tests the
  root cause scenario (name in first delta, empty in subsequent)
- skips_tool_calls_with_empty_or_missing_names: Tests the defensive
  layer for edge cases

This fixes compatibility with:
- Azure OpenAI (via LiteLLM)
- LM Studio
- Other OpenAI-compatible local providers
@timvw timvw force-pushed the fix/empty-tool-call-names branch from e614f9b to 211b5fc Compare December 5, 2025 09:46
@etraut-openai etraut-openai added the chat-endpoint Bugs or PRs related to the chat/completions endpoint (wire API) label Dec 5, 2025
@etraut-openai
Copy link
Collaborator

@codex review

@chatgpt-codex-connector
Copy link
Contributor

Codex Review: Didn't find any major issues. You're on a roll.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@etraut-openai
Copy link
Collaborator

Thanks for the PR, but I think we'll pass on this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chat-endpoint Bugs or PRs related to the chat/completions endpoint (wire API)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants