This commit is contained in:
2026-08-16 21:35:48 +02:00
parent 106f602232
commit 389608ea56
28 changed files with 2491 additions and 115 deletions
+95 -8
View File
@@ -201,8 +201,8 @@ func TestOpenAIClient_doComplete_emptyContentLengthRetriesWithBudget(t *testing.
})
return
}
if maxTok != float64(maxTokensReasoningBudget) {
t.Errorf("retry max_tokens=%v want %d", maxTok, maxTokensReasoningBudget)
if maxTok != float64(MaxTokensEnhanceRetry) {
t.Errorf("retry max_tokens=%v want %d", maxTok, MaxTokensEnhanceRetry)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"model": "code-fast",
@@ -228,6 +228,61 @@ func TestOpenAIClient_doComplete_emptyContentLengthRetriesWithBudget(t *testing.
}
}
func TestOpenAIClient_doComplete_truncatedJSONLengthRetriesFromEnhanceBudget(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 != float64(MaxTokensEnhance) {
t.Errorf("first max_tokens=%v want %d", maxTok, MaxTokensEnhance)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"model": "code-fast",
"choices": []map[string]any{{
"finish_reason": "length",
"message": map[string]any{
"role": "assistant",
"content": `{"name":"Lenovo","description":"<h1>Lenovo G27-20: Igralski monitor za vrhunsko vizualno izkušn`,
},
}},
"usage": map[string]int{"prompt_tokens": 200, "completion_tokens": MaxTokensEnhance, "total_tokens": 200 + MaxTokensEnhance},
})
return
}
if maxTok != float64(MaxTokensEnhanceRetry) {
t.Errorf("retry max_tokens=%v want %d", maxTok, MaxTokensEnhanceRetry)
}
_ = 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":"Lenovo G27-20","description":"<h1>Lenovo G27-20</h1><p>Monitor.</p>"}`,
},
}},
"usage": map[string]int{"prompt_tokens": 200, "completion_tokens": 80, "total_tokens": 280},
})
}))
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: MaxTokensEnhance, Temperature: 0.2})
if err != nil {
t.Fatal(err)
}
if calls != 2 {
t.Fatalf("calls=%d want 2 (length truncated must not succeed)", calls)
}
if !strings.Contains(comp.Text, "</h1>") {
t.Fatalf("text=%q", comp.Text)
}
}
func TestOpenAIClient_Complete_timeoutNotRetried(t *testing.T) {
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -269,7 +324,7 @@ func TestOpenAIClient_Complete_timeoutNotRetried(t *testing.T) {
}
}
func TestOpenAIClient_Complete_emptyAtReasoningBudgetNotRetried(t *testing.T) {
func TestOpenAIClient_Complete_emptyAtEnhanceRetryBudgetNotRetried(t *testing.T) {
t.Parallel()
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -280,21 +335,53 @@ func TestOpenAIClient_Complete_emptyAtReasoningBudgetNotRetried(t *testing.T) {
"finish_reason": "length",
"message": map[string]any{"role": "assistant", "content": "", "reasoning_content": "still thinking"},
}},
"usage": map[string]int{"prompt_tokens": 1, "completion_tokens": 4096, "total_tokens": 4097},
"usage": map[string]int{"prompt_tokens": 1, "completion_tokens": MaxTokensEnhanceRetry, "total_tokens": 1 + MaxTokensEnhanceRetry},
})
}))
defer srv.Close()
c := NewOpenAIClient("test-key", srv.URL, "code-fast", 0, 3)
c.HTTPClient = srv.Client()
_, err := c.CompleteWithOptions(context.Background(), "sys", "user", CompleteOptions{MaxTokens: maxTokensReasoningBudget})
_, err := c.CompleteWithOptions(context.Background(), "sys", "user", CompleteOptions{MaxTokens: MaxTokensEnhanceRetry})
if err == nil {
t.Fatal("expected empty response error")
t.Fatal("expected length-capped error")
}
if !strings.Contains(err.Error(), "empty response") {
if !strings.Contains(err.Error(), "length-capped") {
t.Fatalf("err=%v", err)
}
if calls != 1 {
t.Fatalf("calls=%d want 1 (no retry at reasoning budget)", calls)
t.Fatalf("calls=%d want 1 (no retry at enhance retry ceiling)", calls)
}
}
func TestOpenAIClient_Complete_truncatedAtEnhanceRetryBudgetNotSuccess(t *testing.T) {
t.Parallel()
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
_ = json.NewEncoder(w).Encode(map[string]any{
"model": "code-fast",
"choices": []map[string]any{{
"finish_reason": "length",
"message": map[string]any{
"role": "assistant",
"content": `{"name":"X","description":"<h1>cut off`,
},
}},
"usage": map[string]int{"prompt_tokens": 10, "completion_tokens": MaxTokensEnhanceRetry, "total_tokens": 10 + MaxTokensEnhanceRetry},
})
}))
defer srv.Close()
c := NewOpenAIClient("test-key", srv.URL, "code-fast", 0, 3)
c.HTTPClient = srv.Client()
_, err := c.CompleteWithOptions(context.Background(), "sys", "user", CompleteOptions{MaxTokens: MaxTokensEnhanceRetry})
if err == nil {
t.Fatal("truncated JSON at ceiling must not succeed")
}
if !strings.Contains(err.Error(), "length-capped") {
t.Fatalf("err=%v", err)
}
if calls != 1 {
t.Fatalf("calls=%d want 1", calls)
}
}