102 lines
3.7 KiB
Go
102 lines
3.7 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAnthropicThinkingOutput(t *testing.T) {
|
|
for _, tool := range []bool{false, true} {
|
|
visible := `{"type":"text","text":"OK"}`
|
|
initial := `{"type":"text","text":""}`
|
|
delta := `{"type":"text_delta","text":"OK"}`
|
|
finish := "end_turn"
|
|
if tool {
|
|
visible = `{"type":"tool_use","id":"call_1","name":"weather","input":{}}`
|
|
initial = visible
|
|
delta = `{"type":"input_json_delta","partial_json":"{}"}`
|
|
finish = "tool_use"
|
|
}
|
|
hidden := `{"type":"thinking","thinking":"PRIVATE","signature":"SIGNATURE"}`
|
|
redacted := `{"type":"redacted_thinking","data":"CIPHER"}`
|
|
body := `{"id":"m","type":"message","role":"assistant","model":"m","content":[` + hidden + "," + redacted + "," + visible + `],"stop_reason":"` + finish + `","usage":{"input_tokens":9,"output_tokens":7}}`
|
|
events := []string{
|
|
`{"type":"message_start","message":{"id":"m","content":[],"usage":{"input_tokens":9,"output_tokens":0}}}`,
|
|
`{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}}`,
|
|
`{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"PRIVATE"}}`,
|
|
`{"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"SIGNATURE"}}`,
|
|
`{"type":"content_block_stop","index":0}`,
|
|
`{"type":"content_block_start","index":1,"content_block":` + redacted + "}",
|
|
`{"type":"content_block_stop","index":1}`,
|
|
`{"type":"content_block_start","index":2,"content_block":` + initial + "}",
|
|
`{"type":"content_block_delta","index":2,"delta":` + delta + "}",
|
|
`{"type":"content_block_stop","index":2}`,
|
|
`{"type":"message_delta","delta":{"stop_reason":"` + finish + `"},"usage":{"output_tokens":7}}`,
|
|
`{"type":"message_stop"}`,
|
|
}
|
|
src := reasoningSSE(events...)
|
|
for _, target := range []Kind{Chat, Responses, Anthropic} {
|
|
out, err := ConvertResponse(Anthropic, target, []byte(body))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var dst bytes.Buffer
|
|
u, err := Stream(context.Background(), &dst, strings.NewReader(src), Anthropic, target, "m")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !u.Known || u.Input != 9 || u.Output != 7 {
|
|
t.Fatalf("usage %+v", u)
|
|
}
|
|
if target == Anthropic {
|
|
if string(out) != body || dst.String() != src {
|
|
t.Fatal("native bytes changed")
|
|
}
|
|
continue
|
|
}
|
|
for _, result := range []string{string(out), dst.String()} {
|
|
for _, secret := range []string{"PRIVATE", "SIGNATURE", "CIPHER", "thinking"} {
|
|
if strings.Contains(result, secret) {
|
|
t.Fatal("thinking leaked")
|
|
}
|
|
}
|
|
want := "OK"
|
|
if tool {
|
|
want = "weather"
|
|
}
|
|
if !strings.Contains(result, want) {
|
|
t.Fatal("lost visible output")
|
|
}
|
|
}
|
|
terminal := "[DONE]"
|
|
if target == Responses {
|
|
terminal = "response.completed"
|
|
}
|
|
if !strings.Contains(dst.String(), terminal) {
|
|
t.Fatal("no terminal")
|
|
}
|
|
}
|
|
for _, bad := range []string{
|
|
strings.Replace(src, `{"type":"content_block_stop","index":0}`, `{"type":"ping"}`, 1),
|
|
strings.Replace(src, `"type":"thinking_delta"`, `"type":"text_delta"`, 1),
|
|
strings.Replace(src, `"index":2,"content_block"`, `"index":0,"content_block"`, 1),
|
|
} {
|
|
var dst bytes.Buffer
|
|
if _, err := Stream(context.Background(), &dst, strings.NewReader(bad), Anthropic, Chat, "m"); err == nil {
|
|
t.Fatal("accepted invalid thinking lifecycle")
|
|
}
|
|
}
|
|
}
|
|
for _, block := range []object{
|
|
{"type": "thinking", "thinking": true, "signature": ""},
|
|
{"type": "thinking", "thinking": "", "signature": "", "unknown": true},
|
|
{"type": "redacted_thinking", "data": nil},
|
|
} {
|
|
if _, err := anthropicThinking(block); err == nil {
|
|
t.Fatal("accepted malformed thinking")
|
|
}
|
|
}
|
|
}
|