Skip to content

fix(mistral): aggregate streamed tool call deltas by index - #4594

Open
BlueX888 wants to merge 1 commit into
strands-agents:mainfrom
BlueX888:fix/prep-mistral-stream-tool-args-id-keyed
Open

BlueX888 wants to merge 1 commit into
strands-agents:mainfrom
BlueX888:fix/prep-mistral-stream-tool-args-id-keyed

Conversation

@BlueX888

Copy link
Copy Markdown
Contributor

Description

MistralModel.stream aggregated streamed tool-call deltas in a dict keyed by tool_call.id (strands-py/src/strands/models/mistral.py:538 before this change). Mistral sends a tool call's id and name only on the first delta; continuation deltas carry only the index and an arguments fragment, and the mistralai SDK fills the missing id with its default "null" (ToolCall.id: Optional[str] = "null" in mistralai 2.x). Every continuation fragment therefore landed in a spurious "null" bucket:

  • the toolUse block carrying the real id was emitted with only the first delta's empty arguments, so the event loop parsed its input as {} (strands/event_loop/streaming.py:301-302) and the tool executed without its arguments
  • a second block with name="" / toolUseId="null" carried the fragments and was dropped as an incomplete tool use (strands/event_loop/streaming.py:318)
  • with parallel tool calls, fragments from different tools merged into that single "null" bucket

Keying the dict by tool_call.index keeps each tool's fragments under the delta that carries its id and name, matching the OpenAI (openai.py:780), LiteLLM (litellm.py:518), SageMaker (sagemaker.py:475), and Writer (writer.py:441) providers.

Related Issues

None — no existing issue found for this bug.

Documentation PR

Not needed — no public API change; internal aggregation fix only.

Type of Change

Bug fix

Testing

Two regression tests in tests/strands/models/test_mistral.py stream (1) a single fragmented tool call and (2) two parallel tool calls, with continuation deltas carrying id="null" as the mistralai SDK fills in. Both fail on the previous id-keyed aggregation and pass with this change.

Run from strands-py/ (project venv, Python 3.12):

  • .venv/bin/python -m pytest tests/strands/models/test_mistral.py -k fragmented -q → 2 passed, 59 deselected (on unpatched main: 2 failed, 59 deselected)
  • .venv/bin/python -m pytest tests/strands/models/test_mistral.py -q → 61 passed
  • .venv/bin/python -m pytest tests/ -q → 6973 passed, 31 skipped
  • .venv/bin/ruff format --check → 727 files already formatted
  • .venv/bin/ruff check → All checks passed!
  • .venv/bin/mypy ./src ./tests_typing → blocked locally by a pre-existing environment mismatch: the venv's installed smithy_* packages use Python 3.12-only syntax while the repo pins python_version = "3.10", and the failure is identical on unmodified main. With --python-version 3.12 it completes over 327 files with only a pre-existing unused-ignore in _exception_notes.py (an artifact of overriding the target version); this change adds no mypy errors.

hatch run prepare itself could not run in this environment (hatch env creation fails building a native dependency), so the constituent checks above were run directly.

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have reviewed and understand every line of code in this PR, including any generated by AI tools, and I can explain why it works
  • My change is focused and reasonably small; I have split unrelated work into separate PRs
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Mistral streams a tool call's id and name only on the first delta;
continuation deltas carry only the index and an arguments fragment, so
the mistralai SDK fills their id with its default "null". Keying the
aggregation dict by id therefore landed every continuation fragment in
a spurious "null" bucket: the toolUse block carrying the real id was
emitted with an empty input, and the event loop dropped the spurious
block as incomplete. With parallel tool calls, fragments of different
tools merged into that single bucket.

Key the deltas by index instead, matching the OpenAI, LiteLLM, SageMaker
and Writer providers.
@BlueX888
BlueX888 requested a review from a team as a code owner September 25, 2026 07:41
@BlueX888
BlueX888 requested a review from poshinchen September 25, 2026 07:41
@github-actions github-actions Bot added area-model Related to models or model providers bug Something isn't working python Pull requests that update python code labels Sep 25, 2026

This branch has not been deployed

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

Labels

area-model Related to models or model providers bug Something isn't working python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant