Skip to content

Throw ToolExecutionStopped from stopped HTTP responses - #390

Merged
mattt merged 10 commits into
mainfrom
mattt/http-tool-stop
Nov 5, 2026
Merged

mattt merged 10 commits into
mainfrom
mattt/http-tool-stop

Conversation

@mattt

@mattt mattt commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

This PR makes the HTTP models end a stopped structured response the same way as MLX and llama.cpp. When a tool execution delegate returns .stop and the response type has required properties, the OpenAI, Open Responses, Anthropic, and Gemini models threw a decoding error, and Ollama crashed on a force cast for any type other than String.

Each model's respond stop path now uses the shared stoppedResponse helper, which moves to its own file, and stoppedSnapshot throws LanguageModelSession.ToolExecutionStopped with the provider name and the stopped tool calls. In both paths, a String response ends with an empty string, and other types end with the first of {}, null, or [] that they decode from; a stopped number or Boolean response throws, because zero or false would look like the model's answer.

A stream that throws this error can end without a snapshot, so the error also carries the usage of the stream's requests. The session counts that usage against what the snapshots reported, so each request counts once. After a model handoff, the error includes the entries and usage of the rounds before the handoff.

mattt added 7 commits November 4, 2026 13:57
A session with tools that asked MLXLanguageModel or LlamaLanguageModel
for a Generable type went straight to constrained JSON generation,
so the model never called a tool.
Run the tool loop for every response type,
then generate the structured content from the tool calls and outputs.

Resolves #388.
The OpenAI, Open Responses, Anthropic, Gemini, and Ollama models now end
a stopped response with the shared stoppedResponse helper, and their
streams throw ToolExecutionStopped from stoppedSnapshot, when the response
type can't be empty. Previously they threw a decoding error, and Ollama
crashed on a force cast for any type other than String.
A stopped respond call and a stopped stream now end with the same empty
values: an empty string for String, and otherwise an empty object, null,
or an empty array. A stopped number or Boolean response throws
ToolExecutionStopped in both paths, because zero or false would look like
the model's answer.
Base automatically changed from mattt/local-tools-structured-output to main November 5, 2026 11:38
# Conflicts:
#	Sources/AnyLanguageModel/ToolExecution.swift
#	Tests/AnyLanguageModelTests/LlamaLanguageModelTests.swift
#	Tests/AnyLanguageModelTests/LocalGenerationUsageTests.swift
#	Tests/AnyLanguageModelTests/MLXLanguageModelTests.swift
@mattt
mattt marked this pull request as ready for review November 5, 2026 19:22
@mattt
mattt requested a balanced review from Copilot November 5, 2026 19:22

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

mattt added 2 commits November 5, 2026 14:58
A stopped number or Boolean stream throws ToolExecutionStopped and can end
without a snapshot, and the session counts streamed usage only from snapshots.
The error now carries the requests' usage, and the session counts it against
what the stream's snapshots reported, so each request counts once.

A handoff to another model adds the entries and usage of the rounds before it
to the error.
@mattt
mattt merged commit 0827229 into main Nov 5, 2026
12 checks passed
@mattt
mattt deleted the mattt/http-tool-stop branch November 5, 2026 23:20
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