Skip to content

Fix Kimi tool-call payload normalization for reasoning_content - #1467

Merged
luispater merged 1 commit into
router-for-me:devfrom
dusty-du:fix/kimi-toolcall-reasoning-content
Feb 7, 2026
Merged

Fix Kimi tool-call payload normalization for reasoning_content#1467
luispater merged 1 commit into
router-for-me:devfrom
dusty-du:fix/kimi-toolcall-reasoning-content

Conversation

@dusty-du

@dusty-du dusty-du commented Feb 6, 2026

Copy link
Copy Markdown

Follow-up to: https://github.com/router-for-me/CLIProxyAPIPlus/pull/182#issuecomment-3861001374

Problem

Kimi rejects chat completion requests when an assistant message that contains tool_calls is missing a non-empty reasoning_content field. We also saw tool message linkage issues when only call_id is present.

Changes

  • Normalize assistant tool-call messages to ensure reasoning_content is present and non-empty
  • Reuse latest prior assistant reasoning_content when available
  • Fallback to assistant content text when prior reasoning is unavailable
  • Fallback to a non-empty placeholder when both are missing
  • Normalize tool message linkage by filling tool_call_id from call_id
  • Infer tool_call_id from a single unambiguous pending assistant tool call

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @dusty-du, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves critical issues within the Kimi executor related to chat completion requests. It introduces robust normalization logic to ensure that assistant messages containing tool calls always have a valid reasoning_content field and that tool messages are correctly linked by their IDs. These changes prevent Kimi from rejecting requests due to missing or malformed fields and improve the overall reliability of tool integration.

Highlights

  • Assistant Message Normalization: Assistant tool-call messages are now normalized to ensure the reasoning_content field is always present and non-empty, addressing Kimi's rejection issues.
  • Reasoning Content Fallback Logic: The system reuses the latest prior assistant reasoning_content if available, falls back to the assistant's content text if prior reasoning is absent, and uses a non-empty placeholder if both are missing.
  • Tool Message Linkage Normalization: Tool message linkage is improved by filling the tool_call_id from the call_id when tool_call_id is missing.
  • Tool Call ID Inference: The tool_call_id can now be inferred from a single unambiguous pending assistant tool call, enhancing tool message consistency.
  • Unit Test Coverage: New unit tests have been added specifically for the reasoning and tool call normalization paths within the Kimi executor to validate the changes.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • internal/runtime/executor/kimi_executor.go
    • Added import for github.com/tidwall/gjson to facilitate JSON parsing.
    • Integrated normalizeKimiToolMessageLinks function into the Execute and ExecuteStream methods to apply normalization to the request body before sending.
    • Implemented normalizeKimiToolMessageLinks function, which handles the logic for ensuring reasoning_content is present and non-empty, and for normalizing tool_call_id fields.
    • Added fallbackAssistantReasoning helper function to determine the appropriate reasoning_content when it's missing or empty.
  • internal/runtime/executor/kimi_executor_test.go
    • Added a new test file containing comprehensive unit tests for the normalizeKimiToolMessageLinks function.
    • Tests cover scenarios such as call_id fallback, single pending ID inference, handling ambiguous missing IDs, preserving existing IDs, inheriting previous reasoning, inserting fallback reasoning, using content as reasoning fallback, replacing empty reasoning, preserving existing reasoning, and combined ID and reasoning repairs.
Activity
  • The author has implemented new normalization logic for Kimi tool-call payloads.
  • New unit tests have been added to validate the correctness of the reasoning_content and tool_call_id normalization.
  • The author has confirmed that all unit tests pass locally (go test ./internal/runtime/executor -count=1 passes).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces normalization logic for Kimi tool-call payloads to address issues with reasoning_content and tool_call_id linkage. The changes are well-structured and include comprehensive unit tests covering various scenarios. The core logic in normalizeKimiToolMessageLinks correctly handles the described normalization cases.

My review includes a couple of suggestions for performance improvements in the new normalization function. Specifically, I've pointed out potential optimizations regarding repeated JSON modifications within a loop and a more efficient slice removal technique. These are not critical but could improve performance for payloads with many messages.

Comment on lines +321 to +329
removePending := func(id string) {
for idx := range pending {
if pending[idx] != id {
continue
}
pending = append(pending[:idx], pending[idx+1:]...)
return
}
}

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 current implementation of removePending uses append to remove an element from the slice, which is an O(N) operation as it may require shifting all subsequent elements. Since the order of elements in the pending slice does not appear to be significant for the logic, you can achieve the same result with an O(1) operation by swapping the element to be removed with the last element and then shrinking the slice. This is more efficient, especially if the pending slice could grow.

_FUNC_START_removePending := func(id string) {
		for i, pID := range pending {
			if pID == id {
				// Swap with the last element and shrink the slice for O(1) removal.
				pending[i] = pending[len(pending)-1]
				pending = pending[:len(pending)-1]
				return
			}
		}
	}_FUNC_END_

Comment on lines +354 to +358
next, err := sjson.SetBytes(out, path, reasoningText)
if err != nil {
return body, fmt.Errorf("kimi executor: failed to set assistant reasoning_content: %w", err)
}
out = next

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

Each call to sjson.SetBytes inside this loop can cause a full copy and reallocation of the JSON byte slice out. If a payload has many messages that need patching, this could become a performance bottleneck. A more performant approach would be to unmarshal the messages array into a Go slice of maps (e.g., []map[string]any), modify this slice in memory, and then marshal it back to JSON. Finally, you can replace the original messages array in the payload with a single sjson.SetBytes call outside the loop.

@vleeuwenmenno

vleeuwenmenno commented Feb 6, 2026

Copy link
Copy Markdown

I assume this is the PR you'd like to have tested, I tried it with Zed again and it seems like the issue presists:

{
  "auth_index": "xxx",
  "created_at": "2026-02-07T05:23:09.476694972+08:00",
  "disabled": false,
  "id": "kimi-xxx.json",
  "label": "kimi",
  "last_refresh": "2026-02-07T05:33:14.695613115+08:00",
  "modtime": "2026-02-07T05:33:14.694458133+08:00",
  "name": "kimi-xxx.json",
  "path": "/root/.cli-proxy-api/kimi-xxx.json",
  "provider": "kimi",
  "runtime_only": false,
  "size": 1079,
  "source": "file",
  "status": "error",
  "status_message": "{\"error\":{\"message\":\"tool_call_id  is not found\",\"type\":\"invalid_request_error\"}}",
  "type": "kimi",
  "unavailable": true,
  "updated_at": "2026-02-07T05:33:45.065423754+08:00"
}

My zed config:

"language_models": {
    "openai_compatible": {
    "CLIProxyAPI": {
      "api_url": "http://mennos-server:8317/v1",
      "available_models": [
        {
          "capabilities": {
            "chat_completions": false,
            "images": true,
            "parallel_tool_calls": true,
            "prompt_cache_key": true,
            "tools": true
          },
          "max_completion_tokens": 65536,
          "max_output_tokens": 65536,
          "max_tokens": 262144,
          "name": "kimi-k2.5"
        }
      ]
    }
  }
}

EDIT:
I also tried deleting it from the config and re-adding it and re-authenticate but this seems to have no effect on it.

@vleeuwenmenno

Copy link
Copy Markdown

Status update: I can confirm the issue only presists for Zed (zed.dev) and in OpenCode with Oh My Opencode it works now!
image
image

@dusty-du

dusty-du commented Feb 6, 2026

Copy link
Copy Markdown
Author

I assume this is the PR you'd like to have tested, I tried it with Zed again and it seems like the issue presists:

{
  "auth_index": "xxx",
  "created_at": "2026-02-07T05:23:09.476694972+08:00",
  "disabled": false,
  "id": "kimi-xxx.json",
  "label": "kimi",
  "last_refresh": "2026-02-07T05:33:14.695613115+08:00",
  "modtime": "2026-02-07T05:33:14.694458133+08:00",
  "name": "kimi-xxx.json",
  "path": "/root/.cli-proxy-api/kimi-xxx.json",
  "provider": "kimi",
  "runtime_only": false,
  "size": 1079,
  "source": "file",
  "status": "error",
  "status_message": "{\"error\":{\"message\":\"tool_call_id  is not found\",\"type\":\"invalid_request_error\"}}",
  "type": "kimi",
  "unavailable": true,
  "updated_at": "2026-02-07T05:33:45.065423754+08:00"
}

My zed config:

"language_models": {
    "openai_compatible": {
    "CLIProxyAPI": {
      "api_url": "http://mennos-server:8317/v1",
      "available_models": [
        {
          "capabilities": {
            "chat_completions": false,
            "images": true,
            "parallel_tool_calls": true,
            "prompt_cache_key": true,
            "tools": true
          },
          "max_completion_tokens": 65536,
          "max_output_tokens": 65536,
          "max_tokens": 262144,
          "name": "kimi-k2.5"
        }
      ]
    }
  }
}

EDIT: I also tried deleting it from the config and re-adding it and re-authenticate but this seems to have no effect on it.

Please edit your Zed config for this model, chat_completions should be set to true, and prompt_cache_key should be set to false. Thanks for testing, let me know if any issues arise.

@vleeuwenmenno

Copy link
Copy Markdown

That did it! Thanks for the comment!

EDIT: I also tried deleting it from the config and re-adding it and re-authenticate but this seems to have no effect on it.

Please edit your Zed config for this model, chat_completions should be set to true, and prompt_cache_key should be set to false. Thanks for testing, let me know if any issues arise.

@dusty-du

dusty-du commented Feb 6, 2026

Copy link
Copy Markdown
Author

PR is complete and ready for your review @luispater

@luispater
luispater changed the base branch from main to dev February 7, 2026 01:34
@luispater
luispater merged commit 7e9d0db into router-for-me:dev Feb 7, 2026
2 checks passed
CreatorMetaSky pushed a commit to AIxSpace/CLIProxyAPI that referenced this pull request Feb 11, 2026
…-reasoning-content

Fix Kimi tool-call payload normalization for reasoning_content
AoaoMH pushed a commit to AoaoMH/CLIProxyAPI-Aoao that referenced this pull request Mar 3, 2026
…-reasoning-content

Fix Kimi tool-call payload normalization for reasoning_content
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.

3 participants