symfonic.agent.backend.contributed_tools¶
contributed_tools ¶
Bind capability-contributed tool descriptions at the native composition root.
bind_contributed_tool ¶
One contributed tool, in the shape the runtime binds.
A :class:DelegationToolSpec — anything carrying name, description
and a coroutine — becomes a StructuredTool. Anything else is
returned untouched, because a capability that already offers a runtime tool
has nothing for this function to do and re-wrapping one would destroy it.
"Anything else" includes an already bound runtime tool, and the check for
one has to come first. A StructuredTool carries a callable
coroutine too, so a guard that asked only about that would rebuild it
from StructuredTool.from_function(coroutine=...) — which discards the
args_schema the binder that produced it derived and re-infers one from
the wrapper's signature. That is exactly what happened to a
ToolContribution bound by
:func:~symfonic.agent.cutover.extension_tools.bind_extension_tool: a tool
declaring query was re-advertised as taking a single free-form
arguments object, and every call the model made arrived at
ToolContribution.invoke wrapped in an extra dict level. See
:func:_already_bound.
parameters is read, and read here rather than nowhere. The runtime
infers the argument schema from the coroutine, so a spec whose declared
parameter list disagreed with the coroutine it carries would bind the
coroutine's signature and say nothing -- the tool would work, under a
description of itself that was false, and the field would be documentation
nobody could rely on. So the declaration is held against the signature and
a divergence is a ConfigurationError raised at fold time, at the
composition root, before any turn is admitted. A spec declaring no
parameters declares nothing to check and is left alone.
The tool-library import stays local, so importing this binder alone does not load runtime tool types. Both native composition and compatibility composition use this implementation.
Source code in src/symfonic/agent/backend/contributed_tools.py
bind_contributed_tools ¶
Bind every (capability, tool) pair the fold produced.
The pairing survives the binding. merge_capability_tools reports a name
collision by capability, and a composition root that flattened the pairs
here would turn "delegation contributed a tool named 'run_agent', which is
already registered by a tool passed to Agent(tools=[...])" into "two tools
called run_agent" — true, and useless to whoever has to fix it.