Files
AutoZero/llm/stream_tool_test.go
Sakurasan d5dac5a6fc feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)
llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、
builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、
编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。

产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt)
留在 AutoGo,宿主接入方式见 MIGRATION.md。
2026-10-26 00:54:46 +08:00

91 lines
3.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package llm
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
func sseProviderServer(t *testing.T, sse string) *httptest.Server {
t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte(sse))
}))
}
func collectToolCallDeltas(t *testing.T, p Provider) []ToolCall {
t.Helper()
var got []ToolCall
_, err := p.Stream(context.Background(), "s", []Message{{Role: RoleUser, Content: "hi"}}, nil, StreamOptions{},
func(d StreamDelta) {
if d.ToolCall != nil {
got = append(got, *d.ToolCall)
}
})
if err != nil {
t.Fatal(err)
}
return got
}
// TestAnthropicEmitsCompletedToolCall:content_block_stop 一到就提前通知,
// 不等 message_stop。
func TestAnthropicEmitsCompletedToolCall(t *testing.T) {
srv := sseProviderServer(t, ""+
"data: {\"type\":\"message_start\",\"message\":{\"usage\":{\"input_tokens\":10}}}\n\n"+
"data: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"t1\",\"name\":\"read\"}}\n\n"+
"data: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"path\\\":\\\"a\\\"}\"}}\n\n"+
"data: {\"type\":\"content_block_stop\",\"index\":0}\n\n"+
"data: {\"type\":\"message_stop\"}\n\n")
defer srv.Close()
p := &AnthropicProvider{BaseURL: srv.URL, APIKey: "k", Model: "m", MaxTokens: 128, HTTP: srv.Client()}
got := collectToolCallDeltas(t, p)
if len(got) != 1 {
t.Fatalf("tool call deltas = %d, want 1", len(got))
}
if got[0].ID != "t1" || got[0].Name != "read" || got[0].Input != `{"path":"a"}` {
t.Fatalf("tool call = %+v", got[0])
}
}
// TestOpenAIEmitsCompletedToolCalls:下一个 index 出现时前一个调用完整;
// finish_reason 时最后一个完整。两者都通过 delta 通道提前通知。
func TestOpenAIEmitsCompletedToolCalls(t *testing.T) {
srv := sseProviderServer(t, ""+
"data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"t1\",\"function\":{\"name\":\"read\",\"arguments\":\"{\\\"path\\\":\\\"\"}}]}}]}\n\n"+
"data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"a\\\"}\"}}]}}]}\n\n"+
"data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":1,\"id\":\"t2\",\"function\":{\"name\":\"read\",\"arguments\":\"{}\"}}]}}]}\n\n"+
"data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"tool_calls\"}]}\n\n"+
"data: [DONE]\n\n")
defer srv.Close()
p := &OpenAIProvider{BaseURL: srv.URL, APIKey: "k", Model: "m", HTTP: srv.Client()}
got := collectToolCallDeltas(t, p)
if len(got) != 2 {
t.Fatalf("tool call deltas = %d, want 2: %+v", len(got), got)
}
if got[0].ID != "t1" || got[0].Input != `{"path":"a"}` {
t.Fatalf("first call = %+v", got[0])
}
if got[1].ID != "t2" || got[1].Input != "{}" {
t.Fatalf("second call = %+v", got[1])
}
}
// TestResponsesEmitsCompletedToolCall:output_item.done 的 parameters 已完整。
func TestResponsesEmitsCompletedToolCall(t *testing.T) {
srv := sseProviderServer(t, ""+
"data: {\"type\":\"response.output_item.done\",\"item\":{\"type\":\"function_call\",\"id\":\"item_1\",\"call_id\":\"c1\",\"name\":\"read\",\"arguments\":\"{\\\"path\\\":\\\"a\\\"}\"}}\n\n"+
"data: {\"type\":\"response.completed\",\"response\":{\"output\":[{\"type\":\"function_call\",\"call_id\":\"c1\",\"name\":\"read\",\"arguments\":\"{\\\"path\\\":\\\"a\\\"}\"}],\"usage\":{\"input_tokens\":1,\"output_tokens\":1}}}\n\n")
defer srv.Close()
p := &ResponsesProvider{BaseURL: srv.URL, APIKey: "k", Model: "m", HTTP: srv.Client()}
got := collectToolCallDeltas(t, p)
if len(got) != 1 {
t.Fatalf("tool call deltas = %d, want 1 (dedup by ID): %+v", len(got), got)
}
if got[0].ID != "c1" || got[0].Input != `{"path":"a"}` {
t.Fatalf("tool call = %+v", got[0])
}
}