-
Notifications
You must be signed in to change notification settings - Fork 0
fix(tools): broad tool calling across every in-process engine #879
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ttupper92618
wants to merge
29
commits into
dev
Choose a base branch
from
fix/tool-calling-model-truth
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
f372c3d
wip: broad tool-call dialects
ttupper92618 29e6b3f
fix(tools): parse the unmarked tool-call dialect and stop at the mess…
ttupper92618 caddc69
fix(tools): open on every marker a family uses and drop calls to tool…
ttupper92618 1953eac
fix(tools): apply the offered-tools rule to the in-process llama.cpp …
ttupper92618 90bb990
fix(tools): recognize tool-call markers split across streamed chunks
ttupper92618 7dacb2c
fix(tools): honor tool_choice on the in-process engines
ttupper92618 226ae12
fix(tools): parse for tool calls only when the request offered tools
ttupper92618 2c668fd
docs(tools): describe what a forced tool name that matches nothing ac…
ttupper92618 2c1f5f7
fix(tools): do not let reasoning settle whether a message is a tool call
ttupper92618 b409833
fix(tools): keep scanning for a call after ordinary text has been rel…
ttupper92618 7d99130
docs(tools): describe the rolling scan and the anchored unmarked dialect
ttupper92618 6217702
fix(tools): filter llama.cpp's own structured calls against the offer…
ttupper92618 ccfcff3
fix(tools): keep an answer when a native call names no offered tool, …
ttupper92618 14dc884
fix(tools): locate the closing marker rather than requiring it at the…
ttupper92618 f5ebaed
fix(tools): accept an unmarked call the model keeps writing after
ttupper92618 089ece6
fix(tools): return the tail of a dropped block to the scan, and corre…
ttupper92618 e416080
fix(tools): coalesce a message's tool calls so parallel calls survive
ttupper92618 ddef441
fix(tools): finish the message after a block the caller cannot run
ttupper92618 9d904c1
refactor(tools): route every close-site exit through one terminal han…
ttupper92618 2a82b9e
fix(tools): hold the same rules when a block is closed by the end of …
ttupper92618 aabbdf2
fix(tools): withhold the finish reason when earlier calls still have …
ttupper92618 a2b9858
docs(tools): scope the shared dialect reader to the paths that actual…
ttupper92618 52de05b
test(tools): sweep the parser's invariants across every split point
ttupper92618 1010951
fix(tools): apply the offered-tools rule to gpt-oss and DeepSeek too
ttupper92618 37191a5
fix(tools): let the real terminal chunk carry the stream's end
ttupper92618 9bb0cda
fix(tools): separate rejected calls and keep their accounting
ttupper92618 251c727
test(cards): bundled cards for one base model must agree about tools
ttupper92618 80bf27c
fix(tools): strip dialect markers from a block handed back as content
ttupper92618 97df300
fix(tools): recognize a tool block even when the request offered no t…
ttupper92618 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| """Coverage for applying ``tool_choice`` before dispatch. | ||
|
|
||
| Only the served engines forward ``tool_choice`` to a server that acts on it, so | ||
| without this resolution an in-process engine answers a ``"none"`` request with | ||
| a tool call. That was observed live: a Llama model on the MLX engine returned | ||
| `get_weather` on all four attempts of a `"none"` request that asked for the | ||
| tool by name. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| from skulk.api.adapters.chat_completions import resolve_tool_choice | ||
|
|
||
| WEATHER: dict[str, Any] = { | ||
| "type": "function", | ||
| "function": {"name": "get_weather", "parameters": {"type": "object"}}, | ||
| } | ||
| TIME: dict[str, Any] = { | ||
| "type": "function", | ||
| "function": {"name": "get_time", "parameters": {"type": "object"}}, | ||
| } | ||
| BOTH = [WEATHER, TIME] | ||
|
|
||
|
|
||
| def names(tools: list[dict[str, Any]] | None) -> list[str]: | ||
| return [] if tools is None else [tool["function"]["name"] for tool in tools] | ||
|
|
||
|
|
||
| class TestNone: | ||
| def test_none_removes_the_tools_entirely(self) -> None: | ||
| tools, choice = resolve_tool_choice(BOTH, "none") | ||
| assert tools is None | ||
| assert choice is None | ||
|
|
||
| def test_none_without_tools_is_a_no_op(self) -> None: | ||
| assert resolve_tool_choice(None, "none") == (None, "none") | ||
|
|
||
|
|
||
| class TestNamedFunction: | ||
| def test_a_named_function_narrows_the_offered_tools(self) -> None: | ||
| tools, choice = resolve_tool_choice( | ||
| BOTH, {"type": "function", "function": {"name": "get_time"}} | ||
| ) | ||
| assert names(tools) == ["get_time"] | ||
| # The choice still travels, so a served engine enforces it server-side. | ||
| assert choice == {"type": "function", "function": {"name": "get_time"}} | ||
|
|
||
| def test_a_name_matching_nothing_is_left_for_the_engine_to_report(self) -> None: | ||
| # Silently sending no tools would turn the caller's mistake into a | ||
| # confusing prose answer instead of an error. | ||
| tools, _ = resolve_tool_choice( | ||
| BOTH, {"type": "function", "function": {"name": "nope"}} | ||
| ) | ||
| assert names(tools) == ["get_weather", "get_time"] | ||
|
|
||
| def test_a_malformed_choice_object_passes_through(self) -> None: | ||
| tools, choice = resolve_tool_choice(BOTH, {"type": "function"}) | ||
| assert names(tools) == ["get_weather", "get_time"] | ||
| assert choice == {"type": "function"} | ||
|
|
||
|
|
||
| class TestPassThrough: | ||
| def test_auto_is_untouched(self) -> None: | ||
| assert resolve_tool_choice(BOTH, "auto") == (BOTH, "auto") | ||
|
|
||
| def test_required_is_untouched(self) -> None: | ||
| # In-process there is no constrained decoding to force a call, so this | ||
| # stays a best-effort instruction rather than being reinterpreted here. | ||
| assert resolve_tool_choice(BOTH, "required") == (BOTH, "required") | ||
|
|
||
| def test_an_absent_choice_is_untouched(self) -> None: | ||
| assert resolve_tool_choice(BOTH, None) == (BOTH, None) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.