package runtime import ( "context" "encoding/json" "sync" "testing" "agent-native/runtime/llm" "agent-native/runtime/tools" ) // orderTool 记录自己的执行序号(并发安全)。 type orderTool struct { name string mu sync.Mutex seq []string } func (o *orderTool) Name() string { return o.name } func (o *orderTool) Description() string { return o.name } func (o *orderTool) Parameters() json.RawMessage { return json.RawMessage(`{"type":"object"}`) } func (o *orderTool) Execute(ctx context.Context, _ json.RawMessage) (tools.Result, error) { id := o.name if call, ok := ToolCallFromContext(ctx); ok { id = call.ID } o.mu.Lock() o.seq = append(o.seq, id) o.mu.Unlock() return tools.Result{Content: "ran " + id}, nil } // TestLoopSerialToolsKeepOrder 并行模式下 SerialTools 命中的调用按声明顺序执行 // (互斥不保序不够:卡片流等有先后依赖),未命中的并发执行。 func TestLoopSerialToolsKeepOrder(t *testing.T) { cards := &orderTool{name: "card"} reads := &orderTool{name: "read"} reg := tools.NewRegistry(t.TempDir()) reg.Register(cards) reg.Register(reads) res, err := Run(context.Background(), Config{ Model: "fake", Tools: reg, ToolExecution: ExecParallel, SerialTools: map[string]bool{"card": true}, Stream: script( llm.Message{Role: llm.RoleAssistant, StopReason: llm.StopToolCalls, ToolCalls: []llm.ToolCall{ {ID: "s1", Name: "card", Arguments: `{}`}, {ID: "p1", Name: "read", Arguments: `{}`}, {ID: "s2", Name: "card", Arguments: `{}`}, {ID: "s3", Name: "card", Arguments: `{}`}, }}, llm.Message{Role: llm.RoleAssistant, Content: "done", StopReason: llm.StopEnd}, ), }, llm.Message{Role: llm.RoleUser, Content: "go"}) if err != nil { t.Fatal(err) } cards.mu.Lock() got := append([]string(nil), cards.seq...) cards.mu.Unlock() if len(got) != 3 || got[0] != "s1" || got[1] != "s2" || got[2] != "s3" { t.Fatalf("card executions must follow declaration order s1→s2→s3, got %v", got) } reads.mu.Lock() nRead := len(reads.seq) reads.mu.Unlock() if nRead != 1 { t.Fatalf("read executions = %d", nRead) } _ = res }