This page describes the callback system used across the project to intercept,
observe, and customize model inference, tool invocation, and agent execution.
The callback system comes in three categories:
ModelCallbacks
ToolCallbacks
AgentCallbacks
Each category provides a Before and an After callback. A Before callback can
short-circuit the default execution by returning a non-nil custom response.
ModelCallbacks
BeforeModelCallback: Runs before a model inference.
AfterModelCallback: Runs after the model finishes (or per streaming phase).
modelCallbacks:=model.NewCallbacks().// Before: respond to a special prompt to skip the real model call.RegisterBeforeModel(func(ctxcontext.Context,req*model.Request)(*model.Response,error){iflen(req.Messages)>0&&strings.Contains(req.Messages[len(req.Messages)-1].Content,"/ping"){return&model.Response{Choices:[]model.Choice{{Message:model.Message{Role:model.RoleAssistant,Content:"pong"}}}},nil}returnnil,nil}).// After: annotate successful responses, keep errors untouched.RegisterAfterModel(func(ctxcontext.Context,req*model.Request,resp*model.Response,runErrerror)(*model.Response,error){ifrunErr!=nil||resp==nil||len(resp.Choices)==0{returnresp,runErr}c:=resp.Choices[0]c.Message.Content=c.Message.Content+"\n\n-- answered by callback"resp.Choices[0]=creturnresp,nil})
ToolCallbacks
BeforeToolCallback: Runs before each tool invocation.
AfterToolCallback: Runs after each tool invocation.
// Before: can short-circuit with a custom result and can mutate arguments via pointer.typeBeforeToolCallbackfunc(ctxcontext.Context,toolNamestring,toolDeclaration*tool.Declaration,jsonArgs*[]byte,// pointer: mutations are visible to the caller)(any,error)// After: can override the result.typeAfterToolCallbackfunc(ctxcontext.Context,toolNamestring,toolDeclaration*tool.Declaration,jsonArgs[]byte,resultany,runErrerror,)(any,error)
Argument mutation (important):
jsonArgs is passed as a pointer (*[]byte) to BeforeToolCallback.
The callback may replace the slice (e.g., *jsonArgs = newBytes).
The mutated arguments will be used for:
The actual tool execution.
Telemetry traces and graph events (emitToolStartEvent/emitToolCompleteEvent).
Short-circuiting:
If BeforeToolCallback returns a non-nil custom result, the tool is skipped
and that result is used directly.
toolCallbacks:=tool.NewCallbacks().RegisterBeforeTool(func(ctxcontext.Context,toolNamestring,d*tool.Declaration,jsonArgs*[]byte)(any,error){ifjsonArgs!=nil&&toolName=="calculator"{// Enrich arguments.original:=string(*jsonArgs)enriched:=[]byte(fmt.Sprintf(`{"original":%s,"ts":%d}`,original,time.Now().Unix()))*jsonArgs=enriched}returnnil,nil}).RegisterAfterTool(func(ctxcontext.Context,toolNamestring,d*tool.Declaration,args[]byte,resultany,runErrerror)(any,error){ifrunErr!=nil{returnnil,runErr}ifs,ok:=result.(string);ok{returns+"\n-- post processed by tool callback",nil}returnresult,nil})
Telemetry and events:
Modified arguments are propagated to:
TraceToolCall telemetry attributes.
Graph events emitted by emitToolStartEvent and emitToolCompleteEvent.
agentCallbacks:=agent.NewCallbacks().// Before: if the user message contains /abort, return a fixed response and skip the rest.RegisterBeforeAgent(func(ctxcontext.Context,inv*agent.Invocation)(*model.Response,error){ifinv!=nil&&strings.Contains(inv.GetUserMessageContent(),"/abort"){return&model.Response{Choices:[]model.Choice{{Message:model.Message{Role:model.RoleAssistant,Content:"aborted by callback"}}}},nil}returnnil,nil}).// After: append a footer to successful responses.RegisterAfterAgent(func(ctxcontext.Context,inv*agent.Invocation,runErrerror)(*model.Response,error){ifrunErr!=nil{returnnil,runErr}ifinv==nil||inv.Response==nil||len(inv.Response.Choices)==0{returnnil,nil}c:=inv.Response.Choices[0]c.Message.Content=c.Message.Content+"\n\n-- handled by agent callback"inv.Response.Choices[0]=creturninv.Response,nil})
Access Invocation in Callbacks
Callbacks can access the current agent invocation via context to correlate
events, add tracing, or implement per-invocation logic.
_=model.NewCallbacks().RegisterBeforeModel(func(ctxcontext.Context,req*model.Request)(*model.Response,error){fmt.Printf("Global BeforeModel: %d messages.\n",len(req.Messages))returnnil,nil}).RegisterAfterModel(func(ctxcontext.Context,req*model.Request,rsp*model.Response,errerror)(*model.Response,error){fmt.Println("Global AfterModel: completed.")returnnil,nil})_=tool.NewCallbacks().RegisterBeforeTool(func(ctxcontext.Context,toolNamestring,d*tool.Declaration,jsonArgs*[]byte)(any,error){fmt.Printf("Global BeforeTool: %s.\n",toolName)// jsonArgs is a pointer; modifications are visible to the caller.returnnil,nil}).RegisterAfterTool(func(ctxcontext.Context,toolNamestring,d*tool.Declaration,jsonArgs[]byte,resultany,runErrerror)(any,error){fmt.Printf("Global AfterTool: %s done.\n",toolName)returnnil,nil})_=agent.NewCallbacks().RegisterBeforeAgent(func(ctxcontext.Context,inv*agent.Invocation)(*model.Response,error){fmt.Printf("Global BeforeAgent: %s.\n",inv.AgentName)returnnil,nil}).RegisterAfterAgent(func(ctxcontext.Context,inv*agent.Invocation,runErrerror)(*model.Response,error){fmt.Println("Global AfterAgent: completed.")returnnil,nil})