Why: Parallel tool execution makes same-turn tool events interleave, so consumers can no longer pair starts and ends by order; the kernel Event needs the call ID. MiniMax interleaved-thinking models stream their reasoning inside content <think> blocks unless the OpenAI-compatible request opts in with reasoning_split=true, which forces reasoning into the dedicated reasoning_content field the SSE layer already captures. Evidence: go test ./... green; new tests TestLoopToolEventsCarryCallID and TestOpenAIChatBodyReasoningSplit pin both behaviors (regression anchors for the backend parallel-work change). Impact: Additive Event.CallID (empty for non-tool events; existing consumers unaffected) and an opt-in Client.ReasoningSplit flag that only adds a request field when set. No default behavior change. Refs: Agent-System-Dev proposal 2026-10-10-agent-delegation-routing-sandbox.md (F2/F5), Dev task feature/agent-e1-parallel-fixes.
48 lines
1.3 KiB
Go
48 lines
1.3 KiB
Go
package llm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestOpenAIChatBodyReasoningSplit 验证 Client.ReasoningSplit 置位时
|
|
// openai-chat 请求体带 "reasoning_split": true,未置位时不带(未知字段
|
|
// 不外溢给别的提供商)。
|
|
func TestOpenAIChatBodyReasoningSplit(t *testing.T) {
|
|
var bodies []string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
raw, _ := io.ReadAll(r.Body)
|
|
bodies = append(bodies, string(raw))
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
_, _ = io.WriteString(w, "data: [DONE]\n\n")
|
|
}))
|
|
defer srv.Close()
|
|
|
|
drain := func(c *Client) {
|
|
for range c.Stream(context.Background(), "MiniMax-M3",
|
|
[]Message{{Role: RoleUser, Content: "hi"}}, nil, StreamOptions{}) {
|
|
}
|
|
}
|
|
|
|
drain(&Client{BaseURL: srv.URL, APIKey: "k"})
|
|
drain(&Client{BaseURL: srv.URL, APIKey: "k", ReasoningSplit: true})
|
|
if len(bodies) != 2 {
|
|
t.Fatalf("requests = %d", len(bodies))
|
|
}
|
|
if strings.Contains(bodies[0], "reasoning_split") {
|
|
t.Fatalf("flag off must not leak the field: %s", bodies[0])
|
|
}
|
|
var got map[string]any
|
|
if err := json.Unmarshal([]byte(bodies[1]), &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v, ok := got["reasoning_split"].(bool); !ok || !v {
|
|
t.Fatalf("flag on must set reasoning_split=true: %s", bodies[1])
|
|
}
|
|
}
|