Skip to content

Opt-in tool_call_id injection into tool execution - #366

Open
ypicard wants to merge 1 commit into
mainfrom
add-optional-tool_call_id-injection-to
Open

Opt-in tool_call_id injection into tool execution#366
ypicard wants to merge 1 commit into
mainfrom
add-optional-tool_call_id-injection-to

Conversation

@ypicard

@ypicard ypicard commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

A tool that indexes documents needs to know which tool call it is serving, so a UI can show which search found which papers. Aviary knows that ID but never hands it to the tool. Environments work around this by scanning the whole corpus after every step and diffing it against the previous one.

A tool function can now declare tool_call_id: str and get the ID of the call it is running:

async def paper_search(query: str, state: MyEnvState, tool_call_id: str) -> str:
    """Search for papers.

    Args:
        query: Search query.
    """
    state.candidates[tool_call_id] = await index(query)

This works like state. Declare the parameter to opt in. Leave it out and nothing changes.

Tool.from_function skips it when building the JSON schema, right next to the state skip, so the model never sees it and cannot send it. If a model sends one anyway, the injected ID wins.

_exec_tool_call already runs once per call, so concurrent calls each get their own ID. No globals. argref_by_name passes the value through instead of treating it as a reference key.

Pairs with #349, which made the ID settable on ToolCall.from_name.

@ypicard
ypicard force-pushed the add-optional-tool_call_id-injection-to branch from 8aa8216 to 86018ab Compare August 28, 2026 16:59
@ypicard
ypicard marked this pull request as ready for review August 28, 2026 18:10
Copilot AI lite review requested due to automatic review settings August 28, 2026 18:10
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. enhancement New feature or request labels Aug 28, 2026

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.

Pull request overview

This PR adds an opt-in mechanism for tool functions to receive the current ToolCall.id via a tool_call_id: str parameter, enabling tools (e.g., indexing/search tools) to key work/results to the specific tool call that produced them while keeping the parameter hidden from the LLM-facing schema.

Changes:

  • Add tool_call_id injection in Environment.exec_tool_calls() when the tool function declares it.
  • Hide tool_call_id from Tool.from_function JSON schema generation and skip it in argref_by_name reference handling.
  • Add tests and documentation describing the new injected parameter behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_tools.py Adds coverage for schema hiding, injection into async/sync/state/method tools, concurrency behavior, and spoofing override.
src/aviary/tools/base.py Updates Tool.from_function to exclude tool_call_id from the LLM-facing schema (like state).
src/aviary/tools/argref.py Ensures tool_call_id is not treated as an argref key when using argref_by_name.
src/aviary/env.py Injects the tool call ID into tool execution when the signature declares tool_call_id.
README.md Documents the new optional tool_call_id: str injected parameter for tools.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/aviary/env.py
A tool function may now declare a tool_call_id: str parameter, and
exec_tool_calls passes the ID of the call it is executing, mirroring how state
is injected. Environments that attribute per-call work, such as recording which
documents a search call indexed, can write it while the tool runs instead of
reconstructing the mapping from the ToolRequestMessage afterwards.

from_function omits the parameter from the JSON schema, so the model never sees
it and cannot emit it. Injection happens per call inside _exec_tool_call, so
concurrent calls each receive their own ID.
@ypicard
ypicard force-pushed the add-optional-tool_call_id-injection-to branch from 86018ab to dc45734 Compare August 28, 2026 20:57
Comment thread src/aviary/env.py
Comment on lines +242 to +243
# optional in the function signature: state is dropped when undeclared,
# tool_call_id is injected when declared. Tool.from_function keeps both

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not sure I appreciate the difference: state is dropped but tool call id is injected? Do they not behave the same?

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

Labels

enhancement New feature or request size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants