Skip to content

fix: handle array content and normalize tool_calls from providers#24

Merged
monatis merged 2 commits intoaltaidevorg:mainfrom
efecnc:fix/handle-array-content-from-providers
May 6, 2026
Merged

fix: handle array content and normalize tool_calls from providers#24
monatis merged 2 commits intoaltaidevorg:mainfrom
efecnc:fix/handle-array-content-from-providers

Conversation

@efecnc
Copy link
Copy Markdown
Contributor

@efecnc efecnc commented May 6, 2026

Summary

  • Fixes NoContent error when providers (Gemini, OpenRouter) return content as an array of parts instead of a plain string
  • Fixes silent loss of tool_calls when arguments is returned as a JSON object instead of a JSON-encoded string (Gemini)
  • Adds warn-level logging when tool_calls parsing fails instead of silently dropping

Root Cause

  1. Array content: content_val.as_str() returns None for arrays → NoContent error → entire response (including tool_calls) is lost
  2. Tool_calls parse: serde_json::from_value(...).ok() silently converts parse errors to None → model's tool call intent is completely lost → empty responses or re-prompt loops

Test plan

  • Verify cargo check passes
  • Test with Gemini via OpenAI-compatibility endpoint (returns array content)
  • Test with DeepSeek / OpenAI (should continue working as before)
  • Verify tool calls work across all providers

Two related response parsing fixes:

1. Content as array: Some providers (Gemini OpenAI-compat, OpenRouter)
   return `content` as an array of parts instead of a string. Previously
   this raised NoContent error, losing the entire response including any
   tool_calls. Now extracts text from array parts gracefully.

2. Silent tool_calls loss: When `arguments` is returned as a JSON object
   (Gemini) instead of a JSON-encoded string, deserialization failed
   silently via `.ok()`, dropping all tool call intent. Now normalizes
   object arguments to strings before parsing, and logs parse failures
   at warn level instead of swallowing them.
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the LLMClient in src/utils.rs to improve compatibility with various LLM providers by handling content returned as an array of parts and normalizing tool call arguments from JSON objects or arrays into strings. Feedback suggests returning an error instead of an empty string when encountering unexpected content types to prevent silent failures and recommends using the warn! macro directly for consistency with the rest of the codebase.

Comment thread src/utils.rs Outdated
.join("")
} else {
content_val.as_str().ok_or(LLMError::NoContent)?.to_string()
"".to_string()
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.

medium

The catch-all else block now returns an empty string, which silently ignores malformed content (e.g., if the provider returns an object or a number). The previous implementation correctly errored out in these cases using LLMError::NoContent. It is better to preserve this error for truly unexpected types to help with debugging and ensure the response follows the expected schema.

Suggested change
"".to_string()
return Err(LLMError::NoContent);

Comment thread src/utils.rs Outdated
match serde_json::from_value::<Vec<ToolCallRequest>>(tc_json) {
Ok(calls) => Some(calls),
Err(e) => {
log::warn!(
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.

medium

Use the imported warn! macro instead of the fully qualified log::warn! for consistency with how debug! and info! are used elsewhere in the file.

Suggested change
log::warn!(
warn!(

@efecnc
Copy link
Copy Markdown
Contributor Author

efecnc commented May 6, 2026

Fixes applied:

  • Catch-all else now returns LLMError::NoContent instead of silently swallowing errors with empty string
  • log::warn! replaced with imported warn! macro for consistency
  • Array content parsing and tool_calls normalization preserved

Ready for re-review.

@monatis monatis merged commit e043873 into altaidevorg:main May 6, 2026
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.

2 participants