项目文件夹

文件
Asim Aslam ce0741a80c Implement tool-execution wrappers and restructure ToolHandler (#2970)
* feat(agent): tool-execution wrappers via WrapTool

Restructure ai.ToolHandler to the structured, ctx-carrying shape that
mirrors a go-micro RPC handler:

    func(ctx context.Context, call ai.ToolCall) ai.ToolResult

This reuses the existing ToolCall (with its correlation ID) and
ToolResult types instead of the flat (name, input)->(any, string)
signature, and adds ToolCall.Scan for typed argument access.

Add ai.ToolWrapper and the agent option WrapTool / micro.AgentWrapTool —
the tool-side analogue of client.CallWrapper and server.HandlerWrapper.
Reframe the built-in guardrails (MaxSteps, LoopLimit, ApproveTool) as
composed wrappers around a base handler; developer wrappers compose
outermost, so they observe every call and result, including refusals.

Update all provider call sites, the MCP server and chat handlers, the
integration harnesses, and docs to the new signature.

* examples: add agent-wrap-tool showing AgentWrapTool

A runnable example of tool-execution middleware: an observe wrapper that
times calls and records per-tool metrics (correlated by call ID), and a
retry wrapper that recovers a flaky service call before the model sees
it. Demonstrates outermost-first composition and the wrapper/guardrail
interaction (retries are seen by loop detection).

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-16 17:26:30 +01:00

64 行
2.1 KiB
Markdown

# Agent Tool Wrappers
Middleware around an agent's tool execution, the same way
`client.CallWrapper` and `server.HandlerWrapper` wrap RPCs.
Every tool call an agent makes runs through `ai.ToolHandler`:
```go
type ToolHandler func(ctx context.Context, call ai.ToolCall) ai.ToolResult
type ToolWrapper func(ai.ToolHandler) ai.ToolHandler
```
`WrapTool` (exposed as `micro.AgentWrapTool`) registers a wrapper: it
takes the next handler and returns a new one. Code before `next(...)`
runs before the tool, code after runs after. That single seam covers
the whole lifecycle — before/after hooks, timing, metrics, retries,
inspecting results.
## What this example does
One flaky `weather` service and one agent with two wrappers:
- **observe** — times every call and records a per-tool count, logging
the correlation ID (`call.ID`) carried through from the provider. It
observes; it changes nothing.
- **retry** — re-runs a call whose result is an error, up to three
attempts. The weather service fails the first time it's hit and
succeeds after, so retry turns a transient failure into a success the
model never sees.
Wrappers compose **outermost-first**: `observe` is registered first, so
it wraps `retry` and sees one logical call even when retry runs the tool
twice.
```go
micro.NewAgent("forecaster",
micro.AgentServices("weather"),
micro.AgentProvider(provider),
micro.AgentAPIKey(apiKey),
micro.AgentWrapTool(m.observe, retry(3)),
)
```
## Wrappers vs. guardrails
Developer wrappers run **outside** the built-in guardrails (`MaxSteps`,
`LoopLimit`, `ApproveTool`), so they see every call and its result —
including a guardrail's refusal. The flip side: a retry wrapper's
`next` is the full guardrail stack, so each retry is also counted by
loop detection. Keep `LoopLimit` at or above your retry count, or set
`AgentLoopLimit(0)` when a wrapper owns the repetition.
See the [Agent Guardrails guide](../../internal/website/docs/guides/agent-guardrails.md)
for the full picture.
## Run
Needs an LLM provider key:
```bash
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...
go run main.go
```