问题摘要
getDefaultAgent 在 DefaultAgentID 未配置或未找到时,遍历 s.agents map 返回"第一个"条目。Go 的 map 迭代顺序是有意随机的(每次运行不同),因此对 getDefaultAgent 的重复调用可能返回不同的 Agent,导致 To 字段为空的消息路由不稳定。
环境信息
- 仓库: BaSui01/agentflow
- 分支: dev
- 文件: agent/execution/protocol/a2a/server_agent.go
- 关键位置: 第 125-142 行 (getDefaultAgent) + 第 136-139 行 (fallback)
- 调用方: server_handler.go:80 (AgentCard discovery) + server_handler.go:295 (message routing)
Bug 详细分析
非确定性的 fallback 逻辑
// server_agent.go:125-142
func (s *HTTPServer) getDefaultAgent() (Agent, error) {
s.agentsMu.RLock()
defer s.agentsMu.RUnlock()
// 首先尝试默认代理ID
if s.config.DefaultAgentID != "" {
if ag, ok := s.agents[s.config.DefaultAgentID]; ok {
return ag, nil // 已配置 → 确定 ✅
}
}
// 返回第一个可用的代理
for _, ag := range s.agents { // map 遍历顺序是随机的!
return ag, nil // 每次可能返回不同的 Agent ❌
}
return nil, ErrAgentNotFound
}
Go 语言规范明确规定:map 的迭代顺序未定义,且每次运行有意不同(runtime 在 map 迭代器中使用随机种子)。当你注册了 3 个 Agent [A, B, C] 且未设 DefaultAgentID 时:
调用 1: 遍历 → B → 返回 B
调用 2: 遍历 → A → 返回 A (不同!)
调用 3: 遍历 → C → 返回 C (又不同!)
两个受影响的路由点
1. Agent Card Discovery(server_handler.go:68-93)
func (s *HTTPServer) handleAgentCardDiscovery(w http.ResponseWriter, r *http.Request) {
agentID := r.URL.Query().Get("agent_id")
if agentID != "" {
card, err = s.GetAgentCard(agentID) // 明确指定 → 确定 ✅
} else {
ag, agErr := s.getDefaultAgent() // 无指定 → 随机!❌
card, err = s.GetAgentCard(ag.ID())
}
}
客户端 GET /.well-known/agent.json(不传 agent_id 参数)时,每次返回的可能是不同 Agent 的卡片。
2. 消息路由(server_handler.go:291-317)
func (s *HTTPServer) routeMessage(msg *A2AMessage) (Agent, error) {
agentID := strings.TrimSpace(msg.To)
if agentID == "" {
return s.getDefaultAgent() // To 为空 → 随机路由!❌
}
// ...
}
当客户端发送 To: "" 的消息时,同一个客户端的不同请求可能被路由到不同 Agent。
实际影响场景
假设服务器注册了 3 个 Agent:math-agent(计算)、weather-agent(天气)、translate-agent(翻译):
- 客户端 A 发送
{To: "", Payload: {query: "1+1"}} → 随机路由到 weather-agent → 无法处理计算结果
- 重试同样请求 → 随机路由到
math-agent → 成功
这导致:
- 不可复现的行为:同一请求的结果取决于路由到的 Agent
- 调试困难:开发者难以复现问题
- 负载不均:可能总是选中同一个 Agent
复现步骤
func TestGetDefaultAgent_Deterministic(t *testing.T) {
server := NewHTTPServer(Config{BaseURL: "http://localhost:8080"}, nil)
// 注册 3 个 Agent,不设 DefaultAgentID
for i := 0; i < 3; i++ {
server.RegisterAgent(newMockAgent(fmt.Sprintf("agent-%d", i), "test"))
}
// 多次调用 getDefaultAgent
seen := make(map[string]int)
for i := 0; i < 100; i++ {
ag, err := server.getDefaultAgent()
require.NoError(t, err)
seen[ag.ID()]++
}
t.Logf("agent distribution: %v", seen)
// 预期:固定返回同一个 Agent
// 实际:每个 Agent 都有不同的计数(非确定性分布)
}
影响范围
| 维度 |
评估 |
| 严重度 |
中 — 不导致数据丢失,但路由不确定导致行为不可复现 |
| 影响面 |
注册多个 Agent 但未配置 DefaultAgentID 的 A2A 部署 |
| 触发条件 |
msg.To 为空 或 agent card discovery 无 agent_id 参数 |
| 后果 |
1) 消息随机路由 2) 结果不可复现 3) 跨 Agent 能力不匹配 |
修复建议
方案一:要求显式配置 DefaultAgentID(推荐)
当存在多个 Agent 且未配置 DefaultAgentID 时,返回错误而非随机选取:
func (s *HTTPServer) getDefaultAgent() (Agent, error) {
s.agentsMu.RLock()
defer s.agentsMu.RUnlock()
// 首先尝试默认代理ID
if s.config.DefaultAgentID != "" {
if ag, ok := s.agents[s.config.DefaultAgentID]; ok {
return ag, nil
}
}
// 只有一个 Agent → 它就是默认的
if len(s.agents) == 1 {
for _, ag := range s.agents {
return ag, nil
}
}
// 多个 Agent 但没有配置默认 → 返回错误
if len(s.agents) > 1 {
return nil, fmt.Errorf(
"multiple agents registered (%d) but no default agent configured; set DefaultAgentID in server config",
len(s.agents),
)
}
return nil, ErrAgentNotFound
}
方案二:确定性选择(排序后取第一个)
// 返回第一个可用的代理(按 ID 排序保证确定性)
agentIDs := make([]string, 0, len(s.agents))
for id := range s.agents {
agentIDs = append(agentIDs, id)
}
sort.Strings(agentIDs)
return s.agents[agentIDs[0]], nil
方案二兼容性好(不改变 API 行为),但不能解决"随机路由到不适合的 Agent"的根本问题——因为那个 Agent 仍然不是调用方期望的。
推荐方案一:显式错误优于静默不确定性。用户要么配置 DefaultAgentID,要么在只有一个 Agent 时自动使用它。
验证清单
问题摘要
getDefaultAgent 在 DefaultAgentID 未配置或未找到时,遍历 s.agents map 返回"第一个"条目。Go 的 map 迭代顺序是有意随机的(每次运行不同),因此对 getDefaultAgent 的重复调用可能返回不同的 Agent,导致 To 字段为空的消息路由不稳定。
环境信息
Bug 详细分析
非确定性的 fallback 逻辑
Go 语言规范明确规定:map 的迭代顺序未定义,且每次运行有意不同(runtime 在 map 迭代器中使用随机种子)。当你注册了 3 个 Agent [A, B, C] 且未设 DefaultAgentID 时:
两个受影响的路由点
1. Agent Card Discovery(server_handler.go:68-93)
客户端
GET /.well-known/agent.json(不传 agent_id 参数)时,每次返回的可能是不同 Agent 的卡片。2. 消息路由(server_handler.go:291-317)
当客户端发送
To: ""的消息时,同一个客户端的不同请求可能被路由到不同 Agent。实际影响场景
假设服务器注册了 3 个 Agent:
math-agent(计算)、weather-agent(天气)、translate-agent(翻译):{To: "", Payload: {query: "1+1"}}→ 随机路由到weather-agent→ 无法处理计算结果math-agent→ 成功这导致:
复现步骤
影响范围
修复建议
方案一:要求显式配置 DefaultAgentID(推荐)
当存在多个 Agent 且未配置 DefaultAgentID 时,返回错误而非随机选取:
方案二:确定性选择(排序后取第一个)
方案二兼容性好(不改变 API 行为),但不能解决"随机路由到不适合的 Agent"的根本问题——因为那个 Agent 仍然不是调用方期望的。
推荐方案一:显式错误优于静默不确定性。用户要么配置 DefaultAgentID,要么在只有一个 Agent 时自动使用它。
验证清单