Skip to content

Commit 2eb21d2

Browse files
committed
Slightly nicer handling of the handoffs and transfers
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
1 parent 6093a8c commit 2eb21d2

2 files changed

Lines changed: 42 additions & 25 deletions

File tree

pkg/runtime/runtime.go

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,13 @@ const (
5353
ResumeTypeReject ResumeType = "reject"
5454
)
5555

56-
// ToolHandler is a function type for handling tool calls
57-
type ToolHandler func(ctx context.Context, sess *session.Session, toolCall tools.ToolCall, events chan Event) (*tools.ToolCallResult, error)
56+
// ToolHandlerFunc is a function type for handling tool calls
57+
type ToolHandlerFunc func(ctx context.Context, sess *session.Session, toolCall tools.ToolCall, events chan Event) (*tools.ToolCallResult, error)
58+
59+
type ToolHandler struct {
60+
handler ToolHandlerFunc
61+
tool tools.Tool
62+
}
5863

5964
// ElicitationRequestHandler is a function type for handling elicitation requests
6065
type ElicitationRequestHandler func(ctx context.Context, message string, schema map[string]any) (map[string]any, error)
@@ -369,8 +374,26 @@ func (r *LocalRuntime) EmitStartupInfo(ctx context.Context, events chan Event) {
369374
// registerDefaultTools registers the default tool handlers
370375
func (r *LocalRuntime) registerDefaultTools() {
371376
slog.Debug("Registering default tools")
372-
r.toolMap[builtin.ToolNameTransferTask] = r.handleTaskTransfer
373-
r.toolMap[builtin.ToolNameHandoff] = r.handleHandoff
377+
378+
tt := builtin.NewTransferTaskTool()
379+
ht := builtin.NewHandoffTool()
380+
ttTools, _ := tt.Tools(context.TODO())
381+
htTools, _ := ht.Tools(context.TODO())
382+
allTools := append(ttTools, htTools...)
383+
384+
handlers := map[string]ToolHandlerFunc{
385+
builtin.ToolNameTransferTask: r.handleTaskTransfer,
386+
builtin.ToolNameHandoff: r.handleHandoff,
387+
}
388+
389+
for _, t := range allTools {
390+
if h, exists := handlers[t.Name]; exists {
391+
r.toolMap[t.Name] = ToolHandler{handler: h, tool: t}
392+
} else {
393+
slog.Warn("No handler found for default tool", "tool", t.Name)
394+
}
395+
}
396+
374397
slog.Debug("Registered default tools", "count", len(r.toolMap))
375398
}
376399

@@ -900,43 +923,37 @@ func (r *LocalRuntime) processToolCalls(ctx context.Context, sess *session.Sessi
900923
))
901924

902925
slog.Debug("Processing tool call", "agent", a.Name(), "tool", toolCall.Function.Name, "session_id", sess.ID)
903-
handler, exists := r.toolMap[toolCall.Function.Name]
926+
def, exists := r.toolMap[toolCall.Function.Name]
904927
if exists {
905-
tool := tools.Tool{
906-
Annotations: tools.ToolAnnotations{
907-
// TODO: We need to handle the transfer task tool better
908-
Title: "Transfer Task",
909-
},
910-
}
911928
slog.Debug("Using runtime tool handler", "tool", toolCall.Function.Name, "session_id", sess.ID)
912-
// TODO: make this better, these tols define themselves as read-only
913-
if sess.ToolsApproved || toolCall.Function.Name == builtin.ToolNameTransferTask || toolCall.Function.Name == builtin.ToolNameHandoff {
914-
r.runAgentTool(callCtx, handler, sess, toolCall, tool, events, a)
929+
// TODO: make this better, these tools define themselves as read-only
930+
if sess.ToolsApproved || def.tool.Annotations.ReadOnlyHint {
931+
r.runAgentTool(callCtx, def.handler, sess, toolCall, def.tool, events, a)
915932
} else {
916933
slog.Debug("Tools not approved, waiting for resume", "tool", toolCall.Function.Name, "session_id", sess.ID)
917934

918-
events <- ToolCallConfirmation(toolCall, tool, a.Name())
935+
events <- ToolCallConfirmation(toolCall, def.tool, a.Name())
919936

920937
select {
921938
case cType := <-r.resumeChan:
922939
switch cType {
923940
case ResumeTypeApprove:
924941
slog.Debug("Resume signal received, approving tool handler", "tool", toolCall.Function.Name, "session_id", sess.ID)
925-
r.runAgentTool(callCtx, handler, sess, toolCall, tool, events, a)
942+
r.runAgentTool(callCtx, def.handler, sess, toolCall, def.tool, events, a)
926943
case ResumeTypeApproveSession:
927944
slog.Debug("Resume signal received, approving session", "tool", toolCall.Function.Name, "session_id", sess.ID)
928945
sess.ToolsApproved = true
929-
r.runAgentTool(callCtx, handler, sess, toolCall, tool, events, a)
946+
r.runAgentTool(callCtx, def.handler, sess, toolCall, def.tool, events, a)
930947
case ResumeTypeReject:
931948
slog.Debug("Resume signal received, rejecting tool handler", "tool", toolCall.Function.Name, "session_id", sess.ID)
932-
r.addToolRejectedResponse(sess, toolCall, tool, events)
949+
r.addToolRejectedResponse(sess, toolCall, def.tool, events)
933950
}
934951
case <-callCtx.Done():
935952
slog.Debug("Context cancelled while waiting for resume", "tool", toolCall.Function.Name, "session_id", sess.ID)
936953
// Synthesize cancellation responses for the current and any remaining tool calls
937-
r.addToolCancelledResponse(sess, toolCall, tool, events)
954+
r.addToolCancelledResponse(sess, toolCall, def.tool, events)
938955
for j := i + 1; j < len(calls); j++ {
939-
r.addToolCancelledResponse(sess, calls[j], tool, events)
956+
r.addToolCancelledResponse(sess, calls[j], def.tool, events)
940957
}
941958
callSpan.SetStatus(codes.Ok, "tool call canceled by user")
942959
return
@@ -1063,7 +1080,7 @@ func (r *LocalRuntime) runTool(ctx context.Context, tool tools.Tool, toolCall to
10631080
sess.AddMessage(session.NewAgentMessage(a, &toolResponseMsg))
10641081
}
10651082

1066-
func (r *LocalRuntime) runAgentTool(ctx context.Context, handler ToolHandler, sess *session.Session, toolCall tools.ToolCall, tool tools.Tool, events chan Event, a *agent.Agent) {
1083+
func (r *LocalRuntime) runAgentTool(ctx context.Context, handler ToolHandlerFunc, sess *session.Session, toolCall tools.ToolCall, tool tools.Tool, events chan Event, a *agent.Agent) {
10671084
// Start a child span for runtime-provided tool handler execution
10681085
ctx, span := r.startSpan(ctx, "runtime.tool.handler.runtime", trace.WithAttributes(
10691086
attribute.String("tool.name", toolCall.Function.Name),

pkg/session/session.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,13 @@ func (s *Session) GetMessages(a *agent.Agent) []chat.Message {
276276
subAgentsStr := ""
277277
var validAgentIDs []string
278278
for _, subAgent := range subAgents {
279-
subAgentsStr += "ID: " + subAgent.Name() + " | Name: " + subAgent.Name() + " | Description: " + subAgent.Description() + "\n"
279+
subAgentsStr += "Name: " + subAgent.Name() + " | Description: " + subAgent.Description() + "\n"
280280
validAgentIDs = append(validAgentIDs, subAgent.Name())
281281
}
282282

283283
messages = append(messages, chat.Message{
284284
Role: chat.MessageRoleSystem,
285-
Content: "You are a multi-agent system, make sure to answer the user query in the most helpful way possible. You have access to these sub-agents:\n" + subAgentsStr + "\nIMPORTANT: You can ONLY transfer tasks to the agents listed above using their ID. The valid agent IDs are: " + strings.Join(validAgentIDs, ", ") + ". You MUST NOT attempt to transfer to any other agent IDs - doing so will cause system errors.\n\nIf you are the best to answer the question according to your description, you can answer it.\n\nIf another agent is better for answering the question according to its description, call `transfer_task` function to transfer the question to that agent using the agent's ID. When transferring, do not generate any text other than the function call.\n\n",
285+
Content: "You are a multi-agent system, make sure to answer the user query in the most helpful way possible. You have access to these sub-agents:\n" + subAgentsStr + "\nIMPORTANT: You can ONLY transfer tasks to the agents listed above using their ID. The valid agent names are: " + strings.Join(validAgentIDs, ", ") + ". You MUST NOT attempt to transfer to any other agent IDs - doing so will cause system errors.\n\nIf you are the best to answer the question according to your description, you can answer it.\n\nIf another agent is better for answering the question according to its description, call `transfer_task` function to transfer the question to that agent using the agent's ID. When transferring, do not generate any text other than the function call.\n\n",
286286
})
287287
}
288288

@@ -291,13 +291,13 @@ func (s *Session) GetMessages(a *agent.Agent) []chat.Message {
291291
agentsInfo := ""
292292
var validAgentIDs []string
293293
for _, agent := range handoffs {
294-
agentsInfo += "ID: " + agent.Name() + " | Name: " + agent.Name() + " | Description: " + agent.Description() + "\n"
294+
agentsInfo += "Name: " + agent.Name() + " | Description: " + agent.Description() + "\n"
295295
validAgentIDs = append(validAgentIDs, agent.Name())
296296
}
297297

298298
messages = append(messages, chat.Message{
299299
Role: chat.MessageRoleSystem,
300-
Content: "You are part of a multi-agent team. Your goal is to answer the user query in the most helpful way possible.\n\nAvailable agents in your team:\n" + agentsInfo + "\nYou can hand off the conversation to any of these agents at any time by using the `handoff` function with their ID. The valid agent IDs are: " + strings.Join(validAgentIDs, ", ") + ".\n\nWhen to hand off:\n- If another agent's description indicates they are better suited for the current task or question\n\n- If any of the tools of the agent indicate that this agent is able to respond correctly- If the user explicitly asks for a specific agent\n- If you need specialized capabilities that another agent provides\n\nIf you are the best agent to handle the current request based on your capabilities, respond directly. When handing off to another agent, only handoff without talking about the handoff.",
300+
Content: "You are part of a multi-agent team. Your goal is to answer the user query in the most helpful way possible.\n\nAvailable agents in your team:\n" + agentsInfo + "\nYou can hand off the conversation to any of these agents at any time by using the `handoff` function with their ID. The valid agent names are: " + strings.Join(validAgentIDs, ", ") + ".\n\nWhen to hand off:\n- If another agent's description indicates they are better suited for the current task or question\n\n- If any of the tools of the agent indicate that this agent is able to respond correctly- If the user explicitly asks for a specific agent\n- If you need specialized capabilities that another agent provides\n\nIf you are the best agent to handle the current request based on your capabilities, respond directly. When handing off to another agent, only handoff without talking about the handoff.",
301301
})
302302
}
303303

0 commit comments

Comments
 (0)