This commit is contained in:
2026-08-16 16:57:36 +02:00
parent 96f8c1115c
commit 532d439c41
106 changed files with 10147 additions and 494 deletions
+119
View File
@@ -2,12 +2,39 @@ package processing
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestHeuristicCompleter_thinProductInventFromAttrs(t *testing.T) {
t.Parallel()
h := HeuristicCompleter{}
system := `Retail product copywriter. Schema: {"name":"string","description":"string"}`
user := "Category: Monitors\nName: UltraView 27\nDesc: UltraView 27\nAttrs: {\"brand\":\"Acme\",\"size\":\"27 inch\",\"panel\":\"IPS\"}"
comp, err := h.Complete(context.Background(), system, user)
if err != nil {
t.Fatal(err)
}
var out map[string]string
if err := json.Unmarshal([]byte(comp.Text), &out); err != nil {
t.Fatalf("json: %v text=%q", err, comp.Text)
}
if out["name"] != "UltraView 27" {
t.Fatalf("name=%q", out["name"])
}
desc := out["description"]
if desc == "" || strings.EqualFold(desc, "UltraView 27") || desc == "Product description" {
t.Fatalf("desc=%q want invented non-title copy", desc)
}
if !strings.Contains(strings.ToLower(desc), "acme") && !strings.Contains(strings.ToLower(desc), "ips") && !strings.Contains(strings.ToLower(desc), "27") {
t.Fatalf("desc=%q want attrs-derived facts", desc)
}
}
func TestNewOpenAIClient_capsRetries(t *testing.T) {
c := NewOpenAIClient("k", "https://api.openai.com/v1", "m", 0, 99)
if c.MaxRetries != maxOpenAIRetries {
@@ -107,3 +134,95 @@ func TestNewOpenAIClient_blocksPrivateDialInProduction(t *testing.T) {
t.Fatalf("expected host is not allowed, got: %v", err)
}
}
func TestChoiceMessageText_prefersContentThenReasoningJSON(t *testing.T) {
t.Parallel()
if got := choiceMessageText(json.RawMessage(`"{\"name\":\"A\",\"description\":\"B\"}"`), "", ""); got == "" {
t.Fatal("expected content string")
}
reasoning := `Thinking…\nDraft JSON:\n{"name":"NOSILEC W53070","description":"Stenski nosilec za TV."}\nVerify…`
got := choiceMessageText(json.RawMessage(`""`), reasoning, "")
if !strings.Contains(got, `"name"`) || !strings.Contains(got, "NOSILEC") {
t.Fatalf("got=%q want JSON from reasoning_content", got)
}
if got := choiceMessageText(json.RawMessage(`null`), "no json here", ""); got != "" {
t.Fatalf("expected empty without JSON, got %q", got)
}
}
func TestOpenAIClient_doComplete_emptyContentWithReasoningJSON(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"model": "code-fast",
"choices": []map[string]any{{
"finish_reason": "length",
"message": map[string]any{
"role": "assistant",
"content": "",
"reasoning_content": `steps… {"name":"TV Mount","description":"A wall mount for TVs."} more`,
},
}},
"usage": map[string]int{"prompt_tokens": 10, "completion_tokens": 50, "total_tokens": 60},
})
}))
defer srv.Close()
c := NewOpenAIClient("test-key", srv.URL, "code-fast", 0, 1)
c.HTTPClient = srv.Client()
comp, err := c.CompleteWithOptions(context.Background(), "sys", "user", CompleteOptions{MaxTokens: 350, Temperature: 0.2})
if err != nil {
t.Fatal(err)
}
if !strings.Contains(comp.Text, "TV Mount") {
t.Fatalf("text=%q", comp.Text)
}
}
func TestOpenAIClient_doComplete_emptyContentLengthRetriesWithBudget(t *testing.T) {
t.Parallel()
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
var req map[string]any
_ = json.NewDecoder(r.Body).Decode(&req)
maxTok, _ := req["max_tokens"].(float64)
if calls == 1 {
if maxTok != 350 {
t.Errorf("first max_tokens=%v want 350", maxTok)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"model": "code-fast",
"choices": []map[string]any{{
"finish_reason": "length",
"message": map[string]any{"role": "assistant", "content": "", "reasoning_content": "still thinking"},
}},
"usage": map[string]int{"prompt_tokens": 1, "completion_tokens": 350, "total_tokens": 351},
})
return
}
if maxTok != float64(maxTokensReasoningBudget) {
t.Errorf("retry max_tokens=%v want %d", maxTok, maxTokensReasoningBudget)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"model": "code-fast",
"choices": []map[string]any{{
"finish_reason": "stop",
"message": map[string]any{"role": "assistant", "content": `{"name":"X","description":"Y product description text here."}`},
}},
"usage": map[string]int{"prompt_tokens": 1, "completion_tokens": 40, "total_tokens": 41},
})
}))
defer srv.Close()
c := NewOpenAIClient("test-key", srv.URL, "code-fast", 0, 2)
c.HTTPClient = srv.Client()
comp, err := c.CompleteWithOptions(context.Background(), "sys", "user", CompleteOptions{MaxTokens: 350, Temperature: 0.2})
if err != nil {
t.Fatal(err)
}
if calls != 2 {
t.Fatalf("calls=%d want 2", calls)
}
if !strings.Contains(comp.Text, "\"name\"") {
t.Fatalf("text=%q", comp.Text)
}
}