Skip to content

Commit 173321d

Browse files
authored
a2a: fix multi tools transfer on a2a (#740)
修复LLM在单次请求中发起多个工具调用时,a2a 传递tool response只传递第一个response的问题
1 parent a7972e8 commit 173321d

File tree

2 files changed

+24
-18
lines changed

2 files changed

+24
-18
lines changed

server/a2a/converter.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,11 @@ func (c *defaultEventToA2AMessage) convertToolCallToA2AMessage(
285285
return nil, nil
286286
}
287287

288-
choice := event.Response.Choices[0]
289288
var parts []protocol.Part
290289

291290
// Handle tool call requests (assistant making function calls)
291+
// OpenAI returns tool calls in a single choice with multiple ToolCalls
292+
choice := event.Response.Choices[0]
292293
if len(choice.Message.ToolCalls) > 0 {
293294
for _, toolCall := range choice.Message.ToolCalls {
294295
// Convert ToolCall to map for DataPart
@@ -309,25 +310,28 @@ func (c *defaultEventToA2AMessage) convertToolCallToA2AMessage(
309310
}
310311

311312
// Handle tool call responses (tool returning results)
312-
if choice.Message.Role == model.RoleTool || choice.Message.ToolID != "" {
313-
// Convert tool response to DataPart
314-
toolResponseData := map[string]any{
315-
ia2a.ToolCallFieldName: choice.Message.ToolName,
316-
ia2a.ToolCallFieldID: choice.Message.ToolID,
317-
}
313+
// OpenAI returns each tool response in a separate choice
314+
for _, choice := range event.Response.Choices {
315+
if choice.Message.Role == model.RoleTool || choice.Message.ToolID != "" {
316+
// Convert tool response to DataPart
317+
toolResponseData := map[string]any{
318+
ia2a.ToolCallFieldName: choice.Message.ToolName,
319+
ia2a.ToolCallFieldID: choice.Message.ToolID,
320+
}
318321

319-
// Pass content as-is without parsing
320-
// Client will receive the raw response string and display it directly
321-
if choice.Message.Content != "" {
322-
toolResponseData[ia2a.ToolCallFieldResponse] = choice.Message.Content
323-
}
322+
// Pass content as-is without parsing
323+
// Client will receive the raw response string and display it directly
324+
if choice.Message.Content != "" {
325+
toolResponseData[ia2a.ToolCallFieldResponse] = choice.Message.Content
326+
}
324327

325-
// Create DataPart with metadata indicating this is a function response
326-
dataPart := protocol.NewDataPart(toolResponseData)
327-
dataPart.Metadata = map[string]any{
328-
ia2a.DataPartMetadataTypeKey: ia2a.DataPartMetadataTypeFunctionResp,
328+
// Create DataPart with metadata indicating this is a function response
329+
dataPart := protocol.NewDataPart(toolResponseData)
330+
dataPart.Metadata = map[string]any{
331+
ia2a.DataPartMetadataTypeKey: ia2a.DataPartMetadataTypeFunctionResp,
332+
}
333+
parts = append(parts, dataPart)
329334
}
330-
parts = append(parts, dataPart)
331335
}
332336

333337
if len(parts) == 0 {

server/a2a/server.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,9 @@ func (m *messageProcessor) processAgentStreamingEvents(
425425
m.handleStreamingProcessingError(ctx, a2aMsg, subscriber, err)
426426
}
427427

428-
finalArtifact := protocol.NewTaskArtifactUpdateEvent(taskID, *a2aMsg.ContextID, protocol.Artifact{}, true)
428+
finalArtifact := protocol.NewTaskArtifactUpdateEvent(taskID, *a2aMsg.ContextID, protocol.Artifact{
429+
Parts: []protocol.Part{},
430+
}, true)
429431
if err := subscriber.Send(protocol.StreamingMessageEvent{Result: &finalArtifact}); err != nil {
430432
log.Errorf("failed to send final artifact message: %v", err)
431433
m.handleStreamingProcessingError(ctx, a2aMsg, subscriber, err)

0 commit comments

Comments
 (0)