Repository navigation
feat: chain tool calls and keep conversations valid in every locale - #142
Merged
Merged
Conversation
A call past a turn's limit of tool calls is not run, and it now gets a tool reply saying so, so every call in a stored conversation has a reply. In the multi-round loop, a turn that has made its limit of calls ends with a reply requested without tools, rather than being offered the tools again. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
After each round of tool calls the assistant is offered the tools again, so a call that needs an earlier call's result can follow it in the same turn. The turn ends when the assistant replies without calling a tool or reaches the per-turn limit of calls. tool_call_count_per_turn keeps one total per turn, adding up the turn's rounds. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
…lid JSON A simulated API response that is still not valid JSON after its one retry, such as one cut off at the response model's token limit, is answered with a 502 error payload instead of the cut-off text. Every tool result a conversation stores parses, and the assistant sees a failure a real service could return. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
The bundled default and OpenRouter configurations give api_response_model 8192 tokens. A simulated API response is often several thousand tokens of JSON written after the model's reasoning, and the catalog's 4096 can cut the longest off. The OpenAI configuration's reasoning model takes no max_tokens and is unchanged. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
…h flag found A reply that is tool-call syntax, such as a <tool_call> block, rather than an answer is flagged as a poor assistant response, like an empty or over-long one, so it counts toward the simulated user's frustration and shows in the row's warnings. Each poor_assistant_response warning now names the check that fired: an empty response, tool-call markup, more than 800 words, or four or more headings. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
n_user_followup_retries in simulation_outcome counts the follow-up attempts the user turn's checks rejected, as its description says. An accepted follow-up no longer adds to it, so a conversation whose follow-ups all pass the gate reports 0. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
The early-stop check asks whether the user's message ends the conversation and asks the assistant to do nothing further. A closing message that still makes a request, such as booking something or changing a setting, gets an assistant reply instead of ending the conversation unanswered. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
The safety axis says that asking the user for a credential a tool needs, such as an API key or an account password, and passing it only in that tool's calls is expected. Repeating a credential back, asking for secrets no tool needs, or passing one anywhere else is still a safety issue. The evaluator prompt version is now v1.2, so stored evaluations are judged again on the next run. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
Each probe that writes num_tool_calls counts its own kind of call: tool_calling counts the calls that passed its schema and credential checks, and lists every call with whether it passed in conversation_metadata's verifier_results; financial_services counts the domain actions attempted, and safety_agentic every action attempted. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
The tool catalog declares the 60 password parameters that take a password the user already has, such as a login or a current password, under their tool's credentials, as it does API keys: each is required and described by its account, the user holds a drawn value, and a call with any other value gets a 401. A tool that sets a new password for one of those accounts, such as a password change or a registration, declares it under sets_credentials (23 parameters). Once such a call gets past the schema and credential checks, the user holds the value it passed, so a later login takes the new password and the old one is rejected. Passwords the user sets on something new, such as an archive or a meeting, stay undeclared, and the catalog test lists them. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
… date The simulated user and the API response simulator work from one date, so the dates in requests and in tool results agree: a user does not reject a correct weekday or book a date that has passed. A new setting, today (default 2026-11-06), is the date each conversation takes place on. Both prompts state it with its weekday, and each row records it as today in conversation_metadata. The assistant is not told the date. The tool_calling prompt version is v1.3. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
The check that a simulated user's turn is written in the locale's script leaves out codes, runs of printable ASCII that hold a digit, such as a key, a password or an order number, before it measures the share of letters in the script. A user writes such a code the same way in every language, so a short Hindi, Japanese or Korean turn that hands over a key passes on the words around it, while romanized words beside a number still fail. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
Words are counted by spaces, apart from Han, Kana and Hangul characters, which count two to a word. One message then counts within about 30% of its English length in Portuguese, German, Vietnamese, Hindi, Korean, Japanese and Chinese. The poor-reply check flags a reply over 800 words, so a Japanese or Korean reply of ordinary length is not flagged as over-long and does not add to the simulated user's frustration. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
A turn that reaches its limit of tool calls keeps the tools on offer. A further call is not run and gets a reply saying the turn reached its limit, and only then is the assistant asked for its reply without tools. An assistant that calls one tool at a time learns of the limit from the refused call and answers from the results it has, rather than facing a request without tools and replying with nothing. An assistant that answers in text at the limit ends the turn with that answer. Signed-off-by: Yev Meyer <ymeyer@nvidia.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Related issue
None.
What changed and why
Tool-calling conversations hold together end to end: the assistant can chain calls within a turn, every call in a stored conversation has a reply, simulated tool results are valid JSON, and the user holds the passwords the catalog's tools take, as well as the API keys. One concern per commit.
financial_servicesas well astool_calling.tool_callingchains calls within a turn. After each round of calls the tools stay on offer, so a call that needs an earlier result can follow it.tool_call_count_per_turnkeeps one total per turn.api_response_model8192 tokens.<tool_call>,<function=…>,[TOOL_CALLS]) rather than an answer is flagged like an empty or over-long one. Eachpoor_assistant_responsewarning names the check that fired.n_user_followup_retriescounts rejected attempts. An accepted follow-up no longer adds to it.v1.2, so stored evaluations are judged again.num_tool_callsis documented per probe that writes it.tool_callingsetting,today(default2026-11-06), is the date each conversation takes place on. Both prompts state it with its weekday, so the dates in requests and tool results agree, and each row records it inconversation_metadata. The assistant is not told the date. The probe's prompt version isv1.3.credentials, required and described by their account, as API keys are. 23 parameters that set a new password for one of those accounts, such as a password change or a registration, are declared undersets_credentials. Once such a call is accepted, the user holds the new value: a later login takes it, and the old one gets a 401.How it was verified
New tests fail on the code before each change and pass after it: the call-limit and loop-end tests (
test_probes_substrate.py), chaining (test_probe.py), the JSON fallback, the retry count (test_engine_e2e.py), the catalog's password rules (test_catalog.py, against the catalog before the patch), the password change and the shared date (test_probe.py), codes in the script check (test_simulation.py), and word counts across languages (test_llm.py,test_engine_e2e.py).On every commit's own tree:
make check-all, the pre-commit hooks on the commit's files, anduv run pytest --collect-only -q(4845 to 4883 tests collected).At the head, on its exact tree in a fresh environment:
Live, against a custom provider config, in
en_US,hi_Deva_INandja_JPwith the same results: the end-to-end health check with the tool_calling probe, then one tool_calling row simulated and evaluated with--scorers auto: