pub trait ToolHandler:
ToolMeta
+ Send
+ Sync {
type Input: DeserializeOwned + JsonSchema + Send;
type Output: Serialize;
// Required method
fn call<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ToolCtx,
input: Self::Input,
) -> Pin<Box<dyn Future<Output = Result<ToolOutcome<Self::Output>, HandlerError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
// Provided method
fn input_schema() -> Value
where Self: Sized { ... }
}Expand description
The behavior half of the tool contract: typed input, typed output, and the
async call that turns one into the other.
A type implements this by hand (the macro only generates its
ToolMeta supertrait; see the derive seam).
The associated types carry the bounds the rest of the system needs:
Input: DeserializeOwnedso the type-erased layer can turn the model’s JSON into it, andInput: JsonSchemaso a schema can be generated to hand the model in the first place.Output: Serializeso the type-erased layer can turn the handler’s result back into JSON for the event log.
The Send/Sync bounds and the Send bound on Input are what let a
handler be wrapped as a DynTool and dispatched from an
async runtime that may move the work across threads.
call returns a ToolOutcome, not a bare Output: a human-in-the-loop
tool may return ToolOutcome::Suspend to park the run. Its error type is
HandlerError, the tool’s own failure; a schema mismatch is impossible
here because call only ever receives an already-deserialized Input.
Required Associated Types§
Sourcetype Input: DeserializeOwned + JsonSchema + Send
type Input: DeserializeOwned + JsonSchema + Send
The typed input the model must supply. DeserializeOwned drives erased
dispatch; JsonSchema drives the schema handed to the model.
Required Methods§
Sourcefn call<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ToolCtx,
input: Self::Input,
) -> Pin<Box<dyn Future<Output = Result<ToolOutcome<Self::Output>, HandlerError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn call<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ToolCtx,
input: Self::Input,
) -> Pin<Box<dyn Future<Output = Result<ToolOutcome<Self::Output>, HandlerError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Runs the tool for one attempt.
ctx carries the idempotency key for this attempt (see ToolCtx).
The input is already validated and typed. Return
ToolOutcome::Output on success, ToolOutcome::Suspend to park the
run for a human, or Err(HandlerError) on a genuine failure.
Provided Methods§
Sourcefn input_schema() -> Valuewhere
Self: Sized,
fn input_schema() -> Valuewhere
Self: Sized,
The JSON Schema for Input, generated by schemars.
This is a provided method: it lives on the handler trait because it
needs the Input type, and no tool should override it. The runtime
hands this schema to the model so the model knows how to shape its tool
call. It is surfaced without erasure here and, after wrapping, through
DynTool::input_schema.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".