Skip to content

fix: preserve native tool call ID in multi-turn tool calling#32768

Merged
youkaichao merged 19 commits into
vllm-project:mainfrom
wangln19:fix/preserve-tool-call-id
Jan 27, 2026
Merged

fix: preserve native tool call ID in multi-turn tool calling#32768
youkaichao merged 19 commits into
vllm-project:mainfrom
wangln19:fix/preserve-tool-call-id

Conversation

@wangln19

@wangln19 wangln19 commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

In multi-turn tool calling scenarios, models like Kimi K2 generate tool calls with specific ID formats (e.g., 'functions.get_weather:0'). The model expects to see these IDs in subsequent tool results to match them correctly.

Previously, _parse_tool_calls_from_content() was discarding the native tool call ID parsed by the tool parser, and the serving layer was generating random IDs instead. This broke multi-turn tool calling for models that rely on consistent tool call IDs.

This fix:

  1. Add optional 'id' field to FunctionCall class
  2. Preserve native ID in _parse_tool_calls_from_content()
  3. Use preserved ID in chat_completion_full_generator()
  4. Add .strip() to clean whitespace in Kimi K2 tool parser

Purpose

Test Plan

Test Result


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.
  • (Optional) Release notes update. If your change is user facing, please update the release notes draft in the Google Doc.

@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

The pull request successfully addresses the issue of preserving native tool call IDs in multi-turn tool calling scenarios, particularly for models like Kimi K2. The addition of the id field to FunctionCall and its preservation in _parse_tool_calls_from_content() are crucial improvements. The .strip() calls in kimi_k2_tool_parser.py also contribute to cleaner data handling. However, there's a potential issue in chat_completion_full_generator() where make_tool_call_id() might not generate IDs in the expected format for Kimi K2 models if the original tc.id is None.

Comment thread vllm/entrypoints/openai/chat_completion/serving.py Outdated
Comment thread vllm/entrypoints/openai/chat_completion/serving.py Outdated
@wangln19

Copy link
Copy Markdown
Contributor Author

Related to: #32504 #32216 #30238 #29596

Comment thread vllm/entrypoints/openai/engine/protocol.py Outdated
@wangln19 wangln19 force-pushed the fix/preserve-tool-call-id branch from 2ba5175 to c21c48b Compare January 21, 2026 09:33
@mergify

mergify Bot commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

@wangln19 wangln19 force-pushed the fix/preserve-tool-call-id branch from c21c48b to f5c8c24 Compare January 21, 2026 09:42
@mergify

mergify Bot commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

@wangln19 wangln19 force-pushed the fix/preserve-tool-call-id branch from f5c8c24 to 268ff59 Compare January 21, 2026 10:02
@mergify

mergify Bot commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

@wangln19

Copy link
Copy Markdown
Contributor Author

Note: The mypy error at line 1736 is a pre-existing issue in the main branch (line 1712 in main). This PR does not introduce any new type errors.

@daniel-salib daniel-salib 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.

thanks for the fix! I tried the fix locally and it fixes the issue I've been having with Kimi k2 tool parsing

elif (
request.tool_choice
and type(request.tool_choice) is ChatCompletionNamedToolChoiceParam
):
assert tool_calls is not None and len(tool_calls) > 0
tool_call_class_items = []
for tc in tool_calls:
tool_call_class_items.append(

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.

would we need to apply the same pattern in responses/serving.py to support responses API?

tool_call_class_items.append(
tool_call_class(
id=tc.id
if tc.id

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.

if tc.id is an empty string do we still want o call make_tool_call_id?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, applying the same pattern to responses/serving.py makes sense for consistency. And the if tc.id check handles empty strings correctly by falling back to make_tool_call_id.

@mergify

mergify Bot commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

@qandrew qandrew 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.

thanks for putting this together! can you write a unit test to preserve behavior?

@wangln19

wangln19 commented Jan 22, 2026

Copy link
Copy Markdown
Contributor Author

thanks for putting this together! can you write a unit test to preserve behavior?

I considered adding unit tests but realized the cost-benefit doesn't favor it here:
To properly test the serving layer, I would need to either:
Extract the loop logic into a separate function and refactor 3 places in serving.py to use it or mock the entire serving layer with complex setup.

@mergify

mergify Bot commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

@wangln19 wangln19 force-pushed the fix/preserve-tool-call-id branch from 7009a7a to 17c8fb7 Compare January 22, 2026 10:42
@mergify

mergify Bot commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

@wangln19 wangln19 force-pushed the fix/preserve-tool-call-id branch from 17c8fb7 to b3435a8 Compare January 22, 2026 11:04
@mergify

mergify Bot commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

1 similar comment
@mergify

mergify Bot commented Jan 25, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

@ywang96 ywang96 added the ready ONLY add when PR is ready to merge/full CI is needed label Jan 25, 2026
Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
@wangln19 wangln19 force-pushed the fix/preserve-tool-call-id branch from b1cab5a to 5508de7 Compare January 26, 2026 15:55
@mergify

mergify Bot commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Hi @wangln19, the pre-commit checks have failed. Please run:

uv pip install pre-commit
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy or markdownlint failing?
mypy and markdownlint are run differently in CI. If the failure is related to either of these checks, please use the following commands to run them locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10
# For markdownlint
pre-commit run --hook-stage manual markdownlint

wangln19 and others added 6 commits January 27, 2026 00:10
Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Co-authored-by: Isotr0py <2037008807@qq.com>
Signed-off-by: Roger Wang <hey@rogerw.io>
Signed-off-by: Roger Wang <hey@rogerw.io>
@ywang96

ywang96 commented Jan 26, 2026

Copy link
Copy Markdown
Member

@cursor review

Comment thread vllm/entrypoints/openai/responses/serving.py Outdated
tool_call_class_items.append(
tool_call_class(id=generated_id, function=tc)
)
history_tool_call_cnt += 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Double-counting causes non-sequential tool call indices

Medium Severity

The tool call index calculation uses history_tool_call_cnt + idx where idx comes from enumerate(), but history_tool_call_cnt is also incremented inside the loop. This causes indices to skip values. For example, with 3 tool calls starting at history count 5, the indices would be 5, 7, 9 instead of the expected 5, 6, 7. For Kimi K2, this produces IDs like functions.get_weather:5, functions.get_weather:7, functions.get_weather:9 breaking the sequential indexing the model expects.

Additional Locations (2)

Fix in Cursor Fix in Web

ywang96 and others added 3 commits January 26, 2026 16:22
Signed-off-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Isotr0py <2037008807@qq.com>
Signed-off-by: Roger Wang <hey@rogerw.io>
@ywang96

ywang96 commented Jan 27, 2026

Copy link
Copy Markdown
Member

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@youkaichao youkaichao merged commit 2d70534 into vllm-project:main Jan 27, 2026
46 of 49 checks passed
apd10 pushed a commit to apd10/vllm that referenced this pull request Jan 31, 2026
…oject#32768)

Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Signed-off-by: wangln19 <96399074+wangln19@users.noreply.github.com>
Signed-off-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Isotr0py <2037008807@qq.com>
bbrowning added a commit to bbrowning/vllm that referenced this pull request Mar 31, 2026
Audited recent tool parser bug-fix PRs and found that several
landed without corresponding test coverage. Added unit tests
for each fix to prevent regressions.

- Mistral: fast detokenization text detection (PR vllm-project#37209)
- Qwen3Coder: malformed XML crash, anyOf double-encoding,
  speculative decode streaming (PRs vllm-project#36774, vllm-project#36032, vllm-project#35615)
- DeepSeekV32: delimiter preservation with fast detokenization,
  skip_special_tokens adjustment (PR vllm-project#33964)
- GLM-4 MoE: zero-argument tool calls, transformers 5.x delimiter
  handling, Unicode character preservation (PRs vllm-project#32321, vllm-project#31622, vllm-project#30920)
- MiniMax M2: anyOf nullable parameter handling for non-null and
  null values (PR vllm-project#32342)
- Step3p5: MTP-style variable-chunk and multi-token streaming
  (PR vllm-project#33690)
- Kimi K2: native tool call ID extraction and multi-turn ID
  continuity (PR vllm-project#32768)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Ben Browning <bbrownin@redhat.com>
mystous pushed a commit to mystous/vllm_hybrid that referenced this pull request May 10, 2026
…oject#32768)

Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Signed-off-by: wangln19 <96399074+wangln19@users.noreply.github.com>
Signed-off-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Isotr0py <2037008807@qq.com>
my-other-github-account pushed a commit to my-other-github-account/vllm that referenced this pull request May 15, 2026
…oject#32768)

Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Signed-off-by: wangln19 <96399074+wangln19@users.noreply.github.com>
Signed-off-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Isotr0py <2037008807@qq.com>
my-other-github-account pushed a commit to my-other-github-account/vllm that referenced this pull request May 15, 2026
…oject#32768)

Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Signed-off-by: wangln19 <96399074+wangln19@users.noreply.github.com>
Signed-off-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Isotr0py <2037008807@qq.com>
0826joyce pushed a commit to 0826joyce/vllm-serving-optimization that referenced this pull request May 19, 2026
…oject#32768)

Signed-off-by: wanglinian <wanglinian@stu.pku.edu.cn>
Signed-off-by: wangln19 <96399074+wangln19@users.noreply.github.com>
Signed-off-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Isotr0py <2037008807@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants