package runtime import ( "context" "encoding/json" "sync" "testing" "agent-native/runtime/llm" "agent-native/runtime/tools" ) // ctxEchoTool 把自己从 ctx 读到的调用 ID 记下来(并发安全),验证内核 // 在 Execute 前注入了当前调用。 type ctxEchoTool struct { mu sync.Mutex ids []string bad int } func (c *ctxEchoTool) Name() string { return "echo" } func (c *ctxEchoTool) Description() string { return "echo" } func (c *ctxEchoTool) Parameters() json.RawMessage { return json.RawMessage(`{"type":"object"}`) } func (c *ctxEchoTool) Execute(ctx context.Context, _ json.RawMessage) (tools.Result, error) { call, ok := ToolCallFromContext(ctx) c.mu.Lock() defer c.mu.Unlock() if !ok || call.ID == "" || call.Name != "echo" { c.bad++ return tools.Result{Content: "call missing from context", IsError: true}, nil } c.ids = append(c.ids, call.ID) return tools.Result{Content: "ok " + call.ID}, nil } // TestLoopToolSeesOwnCallInContext 并行执行两个调用,各自读到自己的 ID。 func TestLoopToolSeesOwnCallInContext(t *testing.T) { tool := &ctxEchoTool{} reg := tools.NewRegistry(t.TempDir()) reg.Register(tool) res, err := Run(context.Background(), Config{ Model: "fake", Tools: reg, ToolExecution: ExecParallel, Stream: script( llm.Message{Role: llm.RoleAssistant, StopReason: llm.StopToolCalls, ToolCalls: []llm.ToolCall{ {ID: "pa", Name: "echo", Arguments: `{}`}, {ID: "pb", Name: "echo", Arguments: `{}`}, }}, llm.Message{Role: llm.RoleAssistant, Content: "done", StopReason: llm.StopEnd}, ), }, llm.Message{Role: llm.RoleUser, Content: "go"}) if err != nil { t.Fatal(err) } tool.mu.Lock() defer tool.mu.Unlock() if tool.bad != 0 || len(tool.ids) != 2 { t.Fatalf("bad=%d ids=%v", tool.bad, tool.ids) } seen := map[string]bool{} for _, id := range tool.ids { seen[id] = true } if !seen["pa"] || !seen["pb"] { t.Fatalf("ids = %v", tool.ids) } _ = res }