1330 lines
63 KiB
Go
1330 lines
63 KiB
Go
package llm
|
||
|
||
import (
|
||
"bufio"
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"log/slog"
|
||
"net"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"os"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"sync/atomic"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// attemptcut_test.go: the two things the cut-call boundary rests on — that the per-attempt deadline is
|
||
// DERIVED from the budget rather than typed, and that a delivered request is told apart from an
|
||
// undelivered one by the transport rather than by the size of what came back.
|
||
|
||
// draftGrant and editorGrant are the two real output budgets of the shipping pipeline: the draft's, and
|
||
// the editor's after one regeneration doubled it. Two, because a single grant cannot distinguish a
|
||
// formula from a constant that happens to match it.
|
||
const (
|
||
draftGrant = 8496
|
||
editorGrant = 32000
|
||
)
|
||
|
||
// vendorSeconds answers how long the vendor's own budgeting says it takes to emit n output tokens. It is
|
||
// deliberately the reciprocal ARRANGEMENT of the production formula — tokens × window ÷ budget, where
|
||
// deriveDeadline does tokens ÷ (budget ÷ window) — so a formula rewritten the wrong way round is caught.
|
||
//
|
||
// ⚠ IT PINS THE ARRANGEMENT AND NOT THE NUMBERS, and an earlier version of this comment claimed
|
||
// otherwise. Both sides read the SAME two package constants, so corrupting them moves both and the
|
||
// identity still holds: `vendorHourlyTokenBudget` was quadrupled and the whole battery stayed green.
|
||
// The numbers are pinned by TestTheVendorsPublishedPairIsWhatTheVendorPublishes, which quotes them
|
||
// against the source they came from; this function is the other half and neither replaces the other.
|
||
func vendorSeconds(n int) time.Duration {
|
||
return time.Duration(float64(n) * float64(vendorBudgetWindow) / float64(vendorHourlyTokenBudget))
|
||
}
|
||
|
||
// TestTheAttemptDeadlineIsDerivedFromTheBudget pins the FORMULA. It cannot be satisfied by any
|
||
// constant, because every expectation below is computed here from the vendor's two published numbers
|
||
// and the grant, and because the ratio assertion holds for a derivation and for nothing else.
|
||
//
|
||
// ⛔ WHY IT IS WRITTEN THIS WAY. The obvious version of this test asserts that the draft's grant
|
||
// derives to the deadline the config used to carry. That number is a PRODUCT of the formula, so
|
||
// writing it down turns the test into a pin on a literal — and the next person to see the arithmetic
|
||
// land a hair under a round number "fixes" the round number back in and the pin agrees. So: no
|
||
// expected duration appears in this file as a number at all, and TestTheDeadlineTestQuotesNoDeadline
|
||
// enforces that mechanically.
|
||
func TestTheAttemptDeadlineIsDerivedFromTheBudget(t *testing.T) {
|
||
// The claim this whole change rests on, stated as arithmetic: the deadline the shipping config
|
||
// carried is what the vendor's own rate gives for the DRAFT's budget, to within a second — which is
|
||
// why it was right there and wrong everywhere else. Asserted as agreement between two independently
|
||
// sourced numbers, one from the vendor and one from the config, with neither written down.
|
||
bare := RetryProfile{}
|
||
if got, want := bare.deriveDeadline(draftGrant), vendorSeconds(draftGrant); got != want {
|
||
t.Fatalf("draft grant %d derives to %s, the vendor's own rate says %s", draftGrant, got, want)
|
||
}
|
||
if got, want := bare.deriveDeadline(editorGrant), vendorSeconds(editorGrant); got != want {
|
||
t.Fatalf("editor grant %d derives to %s, the vendor's own rate says %s", editorGrant, got, want)
|
||
}
|
||
// The editor's doubled budget needs MORE than three times the draft's deadline. That is the whole
|
||
// finding: one attempt_s covered both, so the doubling axis was buying a budget the clock could
|
||
// never spend. The multiple is derived, not chosen — editorGrant/draftGrant.
|
||
if bare.deriveDeadline(editorGrant) <= 3*bare.deriveDeadline(draftGrant) {
|
||
t.Fatalf("the editor's doubled budget must need materially longer than the draft's: draft=%s editor=%s",
|
||
bare.deriveDeadline(draftGrant), bare.deriveDeadline(editorGrant))
|
||
}
|
||
|
||
// Doubling the budget doubles the GENERATION time and leaves the queue wait alone — the property
|
||
// that makes the slack a summand rather than a factor. True of a derivation, false of a constant,
|
||
// and false of any formula that amortized the wait over the budget.
|
||
withSlack := RetryProfile{TokensPerSecFloor: 50, QueueSlack: 7 * time.Minute}
|
||
n, twoN := 4000, 8000
|
||
single := withSlack.deriveDeadline(n) - withSlack.QueueSlack
|
||
double := withSlack.deriveDeadline(twoN) - withSlack.QueueSlack
|
||
if double != 2*single {
|
||
t.Fatalf("doubling the budget must double the generation term and nothing else: %s → %s (want %s)",
|
||
single, double, 2*single)
|
||
}
|
||
if withSlack.deriveDeadline(n) != withSlack.QueueSlack+single {
|
||
t.Fatalf("the vendor's documented queue wait is added whole, not amortized")
|
||
}
|
||
|
||
// A configured floor is USED. Without this the two arms above would both pass on a build that
|
||
// ignored the field and always took the default.
|
||
fast := RetryProfile{TokensPerSecFloor: 2 * (vendorHourlyTokenBudget / vendorBudgetWindow.Seconds())}
|
||
if got := fast.deriveDeadline(editorGrant); got != vendorSeconds(editorGrant)/2 {
|
||
t.Fatalf("a floor twice the vendor default must halve the derived time, got %s", got)
|
||
}
|
||
}
|
||
|
||
// TestTheDeadlineClampsAtBothEnds pins that attempt_s is a FLOOR and attempt_max_s a ceiling — the
|
||
// property that lets this land on every shipping config at once, because no provider can lose a second
|
||
// it has today.
|
||
func TestTheDeadlineClampsAtBothEnds(t *testing.T) {
|
||
floor := 5 * time.Minute
|
||
p := RetryProfile{AttemptTimeout: floor, TokensPerSecFloor: 50}
|
||
// A call small enough to derive under the configured deadline keeps the configured one.
|
||
if got := p.DeadlineFor(1); got != floor {
|
||
t.Fatalf("attempt_s is the FLOOR: a tiny call must keep it, got %s want %s", got, floor)
|
||
}
|
||
// One big enough to derive over it gets the derived time — otherwise nothing about this change works.
|
||
if got := p.DeadlineFor(editorGrant); got != p.deriveDeadline(editorGrant) {
|
||
t.Fatalf("a call the configured deadline cannot cover must get the derived one, got %s", got)
|
||
}
|
||
// And attempt_max_s bounds it.
|
||
capped := p
|
||
capped.AttemptMax = floor + time.Minute
|
||
if got := capped.DeadlineFor(editorGrant); got != capped.AttemptMax {
|
||
t.Fatalf("attempt_max_s must bound the derivation, got %s want %s", got, capped.AttemptMax)
|
||
}
|
||
// An unset ceiling is inert rather than zero — a zero read as a ceiling would cut every call to nothing.
|
||
if got := p.DeadlineFor(editorGrant); got <= 0 {
|
||
t.Fatalf("an unset attempt_max_s must not act as a zero ceiling, got %s", got)
|
||
}
|
||
}
|
||
|
||
// TestAnUnsetOrBrokenFloorFallsBackToTheVendorDefault: an empty field, a zero and a negative must all
|
||
// give the vendor rate. Not a division by zero, and — the one that would be silent — not a deadline of
|
||
// nothing, which would cut every call the instant it went out and bill every one of them as an estimate.
|
||
func TestAnUnsetOrBrokenFloorFallsBackToTheVendorDefault(t *testing.T) {
|
||
want := vendorSeconds(editorGrant)
|
||
for _, floor := range []float64{0, -1, -1e9} {
|
||
p := RetryProfile{TokensPerSecFloor: floor}
|
||
if got := p.deriveDeadline(editorGrant); got != want {
|
||
t.Fatalf("floor %v must fall back to the vendor rate: got %s want %s", floor, got, want)
|
||
}
|
||
if got := p.deriveDeadline(0); got != 0 && p.QueueSlack == 0 {
|
||
t.Fatalf("a zero budget derives a zero generation term, got %s", got)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestTheDeadlineTestQuotesNoDeadline is the guard on the guard. It reads THIS FILE and asserts that
|
||
// the four numbers the derivation produces for the shipping grants appear nowhere in it — so a later
|
||
// session cannot "simplify" the arithmetic above into the constant it evaluates to and keep a green
|
||
// test that pins nothing.
|
||
//
|
||
// The control value is printed with the negative, because "no matches" and "the file was not read" are
|
||
// the same output and only one of them is a passing test.
|
||
func TestTheDeadlineTestQuotesNoDeadline(t *testing.T) {
|
||
src, err := os.ReadFile("attemptcut_test.go")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// ⛔ THE BANNED LIST IS DERIVED, NOT TYPED, and the first version of this test failed against
|
||
// itself for exactly the reason the ban exists: writing the four numbers into the pattern put them
|
||
// in the file. Deriving them from the formula also means the ban follows the formula — change the
|
||
// vendor's published rate and this test bans the NEW products of it without anyone remembering to.
|
||
var banned []string
|
||
for _, grant := range []int{draftGrant, editorGrant} {
|
||
secs := int(vendorSeconds(grant).Round(time.Second).Seconds())
|
||
// The neighbours too: a rounded-up «nicer» value is precisely the shape a later session would
|
||
// substitute for the derivation.
|
||
banned = append(banned, strconv.Itoa(secs-1), strconv.Itoa(secs), strconv.Itoa(secs+1))
|
||
}
|
||
hits := regexp.MustCompile(`\b(`+strings.Join(banned, "|")+`)\b`).FindAllString(string(src), -1)
|
||
// The control: two numbers that ARE in the file, sought by the same instrument. Without it «no
|
||
// matches» and «the search never ran» print identically, and only one of them is a passing test.
|
||
control := regexp.MustCompile(`\b(`+strconv.Itoa(draftGrant)+`|`+strconv.Itoa(editorGrant)+`)\b`).FindAllString(string(src), -1)
|
||
t.Logf("read %d bytes of attemptcut_test.go; banned set %v → %d hit(s); control (the two grants) → %d hit(s)",
|
||
len(src), banned, len(hits), len(control))
|
||
if len(control) < 2 {
|
||
t.Fatalf("the control literals are absent — this test is not reading what it thinks it is")
|
||
}
|
||
if len(hits) != 0 {
|
||
t.Fatalf("the derived deadlines must not appear as literals in this file; found %v — a pin on a "+
|
||
"number the formula produced is a pin on nothing", hits)
|
||
}
|
||
}
|
||
|
||
// --- the delivery boundary ---
|
||
|
||
// hangingTLS is a listener that completes the TCP connection and then says nothing at all. A client
|
||
// speaking TLS to it blocks in the handshake, so the request is NEVER WRITTEN — the one shape in which
|
||
// «nothing was bought» is true by construction, and the one this fixture exists to keep distinct from
|
||
// every other failure. It is deliberately not an httptest server: an HTTP server that merely sleeps
|
||
// receives the request first, which is the opposite case.
|
||
func hangingTLS(t *testing.T) string {
|
||
t.Helper()
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
done := make(chan struct{})
|
||
go func() {
|
||
var held []net.Conn
|
||
for {
|
||
c, err := ln.Accept()
|
||
if err != nil {
|
||
for _, h := range held {
|
||
h.Close()
|
||
}
|
||
return
|
||
}
|
||
held = append(held, c) // accepted, never spoken to
|
||
select {
|
||
case <-done:
|
||
return
|
||
default:
|
||
}
|
||
}
|
||
}()
|
||
t.Cleanup(func() { close(done); ln.Close() })
|
||
return "https://" + ln.Addr().String()
|
||
}
|
||
|
||
func cutProfile(d time.Duration) RetryProfile {
|
||
// A floor high enough that the derivation never exceeds the deadline under test: these fixtures are
|
||
// about the cut, not about the arithmetic, and a derived deadline would make them slow.
|
||
return RetryProfile{AttemptTimeout: d, MaxAttempts: 3, BackoffBase: time.Millisecond,
|
||
BackoffCap: 5 * time.Millisecond, TokensPerSecFloor: 1e9}
|
||
}
|
||
|
||
// holdUntilCut blocks a fixture's handler until the client walks away — and NEVER longer than a bound
|
||
// of its own. `<-r.Context().Done()` alone reads correct and deadlocks the fixture: httptest.Close
|
||
// waits for outstanding handlers, and a handler waiting for a disconnect the server has not noticed
|
||
// yet waits for the Close that is waiting for it. A hang has no colour — neither the battery nor a
|
||
// mutation reports one — so the bound is the difference between a red test and a silent one.
|
||
func holdUntilCut(r *http.Request) {
|
||
select {
|
||
case <-r.Context().Done():
|
||
case <-time.After(3 * time.Second):
|
||
}
|
||
}
|
||
|
||
// TestDeliveryIsWhatSeparatesAPurchaseFromAFailure walks the three rows of the boundary table on a
|
||
// real client: a request that went out and got no headers, one that went out and got an early 200 with
|
||
// nothing in it, and one that never went out at all. All three used to arrive at the same branch.
|
||
func TestDeliveryIsWhatSeparatesAPurchaseFromAFailure(t *testing.T) {
|
||
t.Run("delivered, headers never came, our deadline", func(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
calls.Add(1)
|
||
holdUntilCut(r) // the provider has the request and is «generating»
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(150 * time.Millisecond)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("want an AttemptCutError, got %T %v", err, err)
|
||
}
|
||
if !cut.Delivered {
|
||
t.Fatalf("the server RECEIVED the request; Delivered must be true")
|
||
}
|
||
if cut.AfterHeaders {
|
||
t.Fatalf("no response byte ever arrived; AfterHeaders must be false")
|
||
}
|
||
if cut.Cause != CutBySelfDeadline {
|
||
t.Fatalf("our own deadline fired with the parent alive; cause = %s", cut.Cause)
|
||
}
|
||
// The other side of the count: one delivery reports one, or the field says nothing anywhere.
|
||
if cut.Deliveries != 1 {
|
||
t.Fatalf("this request reached the provider once; Deliveries=%d", cut.Deliveries)
|
||
}
|
||
// NOT retried: the provider is still generating what we just stopped listening to.
|
||
if got := calls.Load(); got != 1 {
|
||
t.Fatalf("the server must have been asked exactly once, got %d", got)
|
||
}
|
||
})
|
||
|
||
t.Run("delivered, early 200 with empty lines, our deadline", func(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
calls.Add(1)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusOK)
|
||
w.Write([]byte("\n")) // the vendor's documented «still queued» reply
|
||
w.(http.Flusher).Flush()
|
||
holdUntilCut(r)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(150 * time.Millisecond)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("want an AttemptCutError, got %T %v", err, err)
|
||
}
|
||
if !cut.Delivered || !cut.AfterHeaders {
|
||
t.Fatalf("a 200 arrived over a delivered request: Delivered=%t AfterHeaders=%t", cut.Delivered, cut.AfterHeaders)
|
||
}
|
||
// The BOTH-SIDES half of the field: whitespace here, JSON in the next case. A field asserted on
|
||
// one side only says nothing about whether it is computed at all.
|
||
if !cut.WhitespaceOnly {
|
||
t.Fatalf("the body was a newline; WhitespaceOnly must be true (bytes read: %d)", cut.BytesRead)
|
||
}
|
||
if got := calls.Load(); got != 1 {
|
||
t.Fatalf("a 200 that carried nothing must not be re-bought, got %d calls", got)
|
||
}
|
||
})
|
||
|
||
t.Run("delivered, cut mid-JSON, our deadline", func(t *testing.T) {
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusOK)
|
||
w.Write([]byte(`{"id":"x","choices":[{"message":{"content":"половина`))
|
||
w.(http.Flusher).Flush()
|
||
holdUntilCut(r)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(150 * time.Millisecond)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("want an AttemptCutError, got %T %v", err, err)
|
||
}
|
||
if cut.WhitespaceOnly {
|
||
t.Fatalf("the body carried a fragment of a real answer; WhitespaceOnly must be false")
|
||
}
|
||
if cut.BytesRead == 0 {
|
||
t.Fatalf("BytesRead must carry what did arrive")
|
||
}
|
||
})
|
||
|
||
t.Run("NOT delivered", func(t *testing.T) {
|
||
base := hangingTLS(t)
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: base, Profile: cutProfile(120 * time.Millisecond)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
if err == nil {
|
||
t.Fatal("want an error")
|
||
}
|
||
var cut *AttemptCutError
|
||
if errors.As(err, &cut) {
|
||
t.Fatalf("the request never went out — treating it as a delivered cut would settle money for "+
|
||
"a call nobody received: %v", cut)
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestACancelledRunKeepsBothTruthsInOneError is the exit-contract fixture, and both assertions live in
|
||
// ONE test on ONE error for a reason: they are the two halves of a single requirement. The process
|
||
// decides its exit code with errors.Is(err, context.Canceled), and the runner decides whether money is
|
||
// owed with errors.As(err, &AttemptCutError). Splitting them across two tests would let a change
|
||
// satisfy either one while breaking the other — a stop that exits with the wrong code, or a stop that
|
||
// silently loses the money for a call already on the wire.
|
||
func TestACancelledRunKeepsBothTruthsInOneError(t *testing.T) {
|
||
arrived := make(chan struct{})
|
||
var once atomic.Bool
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if once.CompareAndSwap(false, true) {
|
||
close(arrived)
|
||
}
|
||
holdUntilCut(r)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
go func() {
|
||
<-arrived // cancel only AFTER the provider has the request: this is the delivered case
|
||
cancel()
|
||
}()
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(10 * time.Second)}, nil)
|
||
_, err := c.Complete(ctx, LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
if !errors.Is(err, context.Canceled) {
|
||
t.Fatalf("the exit contract reads errors.Is(err, context.Canceled); a stop that stops reporting "+
|
||
"itself as one gives the run a foreign exit code. got %T %v", err, err)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("the same error must still say WHAT the stop interrupted, or the money for a delivered "+
|
||
"call is lost with it. got %T %v", err, err)
|
||
}
|
||
if cut.Cause != CutByParent {
|
||
t.Fatalf("cause = %s, want %s", cut.Cause, CutByParent)
|
||
}
|
||
if !cut.Delivered {
|
||
t.Fatalf("the provider had the request when the run was stopped; Delivered must be true")
|
||
}
|
||
}
|
||
|
||
// TestABrokenConnectionAfterDeliveryIsRetriedOnceAndOnlyOnce: the third cause. A socket that dies after
|
||
// the request went out is transport-shaped, so it is worth one more call — and exactly one, because the
|
||
// cap is keyed on DELIVERY rather than on a 2xx. Under the old key («did a 2xx arrive») a provider that
|
||
// answers 200 while still queueing could be re-asked under the full attempt budget.
|
||
func TestABrokenConnectionAfterDeliveryIsRetriedOnceAndOnlyOnce(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
calls.Add(1)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusOK)
|
||
w.Write([]byte(`{"id":"x","choices":[`))
|
||
w.(http.Flusher).Flush()
|
||
// Drop the connection mid-body: the client's read fails with neither deadline expired.
|
||
hj, ok := w.(http.Hijacker)
|
||
if !ok {
|
||
t.Error("the fixture needs a hijackable response writer")
|
||
return
|
||
}
|
||
conn, _, err := hj.Hijack()
|
||
if err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
conn.Close()
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(5 * time.Second)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("want an AttemptCutError, got %T %v", err, err)
|
||
}
|
||
if cut.Cause != CutByConnection {
|
||
t.Fatalf("cause = %s, want %s", cut.Cause, CutByConnection)
|
||
}
|
||
if got := calls.Load(); got != 2 {
|
||
t.Fatalf("a delivered request whose socket broke is worth ONE retry and no more (max_attempts=3), got %d", got)
|
||
}
|
||
if !cut.Delivered {
|
||
t.Fatalf("the request reached the provider both times; Delivered must be true")
|
||
}
|
||
// ⛔ AND THE CALLER IS TOLD HOW MANY TIMES. The store books spend only through a checkpoint and both
|
||
// deliveries share one key, so ONE estimate covers both — an under-count, which is the ratified
|
||
// direction, but a SILENT one is what row 360 was opened about. The number is what makes it readable.
|
||
if cut.Deliveries != 2 {
|
||
t.Fatalf("the provider was asked twice and the ledger will book once; the error must carry the "+
|
||
"count or the gap is invisible again. got Deliveries=%d", cut.Deliveries)
|
||
}
|
||
}
|
||
|
||
// TestTheFloorWarningIsSaidOnceAndOnlyWhenItApplies: the operator notice that makes a new provider
|
||
// usable as DATA. It has to fire when the derived deadline overrides the configured one — and it has to
|
||
// stay silent otherwise, because a warning printed on every healthy call is a warning nobody reads.
|
||
func TestTheFloorWarningIsSaidOnceAndOnlyWhenItApplies(t *testing.T) {
|
||
fires := func(p RetryProfile, maxTokens int, calls int) int {
|
||
var seen atomic.Int32
|
||
log := newCountingLogger(&seen, "no measured generation speed")
|
||
c := newOpenAIClient("p", "http://127.0.0.1:1", "", p, nil, nil, log)
|
||
for i := 0; i < calls; i++ {
|
||
c.attemptDeadline(context.Background(), maxTokens)
|
||
}
|
||
return int(seen.Load())
|
||
}
|
||
// No floor, and a budget the configured deadline cannot cover: said, once, however many calls.
|
||
noFloor := RetryProfile{AttemptTimeout: time.Second}
|
||
if got := fires(noFloor, editorGrant, 5); got != 1 {
|
||
t.Fatalf("the notice must be said exactly once per client, got %d", got)
|
||
}
|
||
// No floor, but every call fits inside the configured deadline: nothing to warn about.
|
||
if got := fires(RetryProfile{AttemptTimeout: time.Hour}, editorGrant, 5); got != 0 {
|
||
t.Fatalf("a provider whose calls fit inside its own attempt_s must hear nothing, got %d", got)
|
||
}
|
||
// A measured floor is the answer to the notice, so it silences it.
|
||
if got := fires(RetryProfile{AttemptTimeout: time.Second, TokensPerSecFloor: 50}, editorGrant, 5); got != 0 {
|
||
t.Fatalf("a provider with a measured floor must hear nothing, got %d", got)
|
||
}
|
||
}
|
||
|
||
// countingHandler counts log records whose message contains a substring. A slog.Handler rather than a
|
||
// buffer scan: asserting on a substring of a shared buffer is the shape that gives both false reds and
|
||
// quiet greens (D39.171), and the question here — «how many times was this record emitted» — is a count
|
||
// the handler can answer exactly.
|
||
type countingHandler struct {
|
||
n *atomic.Int32
|
||
needle string
|
||
}
|
||
|
||
func (h countingHandler) Enabled(context.Context, slog.Level) bool { return true }
|
||
func (h countingHandler) Handle(_ context.Context, r slog.Record) error {
|
||
if strings.Contains(r.Message, h.needle) {
|
||
h.n.Add(1)
|
||
}
|
||
return nil
|
||
}
|
||
func (h countingHandler) WithAttrs([]slog.Attr) slog.Handler { return h }
|
||
func (h countingHandler) WithGroup(string) slog.Handler { return h }
|
||
|
||
func newCountingLogger(n *atomic.Int32, needle string) *slog.Logger {
|
||
return slog.New(countingHandler{n: n, needle: needle})
|
||
}
|
||
|
||
// TestAReplyThatOutrunsOurOwnWriteIsStillDelivered is the money leak an adversarial pass measured on
|
||
// this pack, reproduced as a pin.
|
||
//
|
||
// net/http hands back a response as soon as one arrives; WroteRequest fires later, from the write
|
||
// loop. So a provider that answers EARLY — DeepSeek documents doing exactly that while a request waits
|
||
// to be scheduled — can have its 200 overtake our own callback while the request body is still
|
||
// draining. Read as «not delivered», that call books $0 and is retried under the full attempt budget:
|
||
// the defect this whole file exists to remove, entering through the door meant to close it.
|
||
//
|
||
// The fixture forces the ordering by making the request big enough that the write cannot finish inside
|
||
// the socket buffer, and by answering before reading it. On loopback that needs megabytes; on a real
|
||
// network the window is the send buffer and the congestion window, i.e. orders of magnitude smaller.
|
||
func TestAReplyThatOutrunsOurOwnWriteIsStillDelivered(t *testing.T) {
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer ln.Close()
|
||
done := make(chan struct{})
|
||
defer close(done)
|
||
go func() {
|
||
for {
|
||
c, aerr := ln.Accept()
|
||
if aerr != nil {
|
||
return
|
||
}
|
||
go func(c net.Conn) {
|
||
defer c.Close()
|
||
// Answer the moment the request LINE is in, promise a body far longer than what is
|
||
// sent — and then HOLD, reading nothing more. Two things follow, and both are needed:
|
||
// the client's own body write never finishes (nobody is draining it) and its read of
|
||
// the reply never finishes either, so what ends the call is OUR OWN deadline. That is
|
||
// what makes this deterministic. An earlier version closed the socket here instead, and
|
||
// under load the write failed first, leaving no reply to overtake anything — green
|
||
// alone, red inside a six-package baseline run.
|
||
br := bufio.NewReader(c)
|
||
if _, rerr := br.ReadString('\n'); rerr != nil {
|
||
return
|
||
}
|
||
fmt.Fprint(c, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 4096\r\n\r\n\n")
|
||
select {
|
||
case <-done:
|
||
case <-time.After(10 * time.Second):
|
||
}
|
||
}(c)
|
||
}
|
||
}()
|
||
|
||
// A prompt big enough that the request body cannot be written into the socket buffer in one go, so
|
||
// the write is still outstanding when the reply lands.
|
||
huge := strings.Repeat("длинный исходный текст главы. ", 200000)
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: "http://" + ln.Addr().String(), Profile: cutProfile(700 * time.Millisecond)}, nil)
|
||
_, err = c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: huge}}, MaxTokens: 16})
|
||
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("a 200 came back over this request, so the provider HAS it and may bill for it; booking "+
|
||
"$0 here is the leak this pack closes, arriving through its own door. got %T %v", err, err)
|
||
}
|
||
if !cut.Delivered {
|
||
t.Fatalf("Delivered must be true once a response byte has arrived: %+v", cut)
|
||
}
|
||
if !cut.AfterHeaders {
|
||
t.Fatalf("a 200 arrived; AfterHeaders must be true: %+v", cut)
|
||
}
|
||
if cut.Cause != CutBySelfDeadline {
|
||
t.Fatalf("our own deadline is what ended this call; cause = %s (%+v)", cut.Cause, cut)
|
||
}
|
||
}
|
||
|
||
// TestAFailedWriteIsNotADelivery is the other side of the same bit, and it had NO pin at all until an
|
||
// adversarial pass removed the guard and watched both packages stay green.
|
||
//
|
||
// `WroteRequest` fires with an ERROR when the write itself failed — the request did not reach the
|
||
// provider, nothing was generated, and settling an estimate would charge a reader for a call nobody
|
||
// received. The fixture kills the socket without reading the body and without answering, so the
|
||
// callback fires with a write error and no response byte ever arrives.
|
||
func TestAFailedWriteIsNotADelivery(t *testing.T) {
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer ln.Close()
|
||
go func() {
|
||
for {
|
||
c, aerr := ln.Accept()
|
||
if aerr != nil {
|
||
return
|
||
}
|
||
c.Close() // accepted, then dropped: the body write fails and nothing comes back
|
||
}
|
||
}()
|
||
|
||
huge := strings.Repeat("длинный исходный текст главы. ", 200000)
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: "http://" + ln.Addr().String(), Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err = c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: huge}}, MaxTokens: 16})
|
||
|
||
if err == nil {
|
||
t.Fatal("want an error")
|
||
}
|
||
var cut *AttemptCutError
|
||
if errors.As(err, &cut) {
|
||
t.Fatalf("the write itself failed and no reply ever came: the provider received nothing and owes "+
|
||
"nothing, so this must NOT be a paid cut. got %+v", cut)
|
||
}
|
||
}
|
||
|
||
// TestAStopDuringABackoffKeepsTheEvidence is the SECOND cancellation exit, and it was still throwing
|
||
// the evidence away after the first one stopped doing so.
|
||
//
|
||
// A run stopped during a retry backoff has an attempt behind it that may already have been delivered
|
||
// and billed. Returning a bare context error there leaves the runner nothing to settle and the chunk
|
||
// with no mark at all — the unexplained hole the whole cancelled-position machinery exists to prevent.
|
||
// The window is the entire sleep, up to a minute on the shipping config, and it opens exactly on the
|
||
// runs where a provider is flapping and an operator is therefore reaching for the stop.
|
||
//
|
||
// Both truths are asserted on one error for the same reason as the in-flight case: the exit code reads
|
||
// one and the money reads the other.
|
||
func TestAStopDuringABackoffKeepsTheEvidence(t *testing.T) {
|
||
arrived := make(chan struct{})
|
||
var once atomic.Bool
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
calls.Add(1)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusOK)
|
||
fmt.Fprint(w, `{"id":"x","choices":[`)
|
||
w.(http.Flusher).Flush()
|
||
hj, ok := w.(http.Hijacker)
|
||
if !ok {
|
||
t.Error("the fixture needs a hijackable response writer")
|
||
return
|
||
}
|
||
conn, _, err := hj.Hijack()
|
||
if err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
conn.Close() // a delivered request whose socket dies: retryable, so a backoff follows
|
||
if once.CompareAndSwap(false, true) {
|
||
close(arrived)
|
||
}
|
||
}))
|
||
defer srv.Close()
|
||
|
||
// ⚠ THE STOP IS DELAYED, and the delay is what makes this test about the backoff at all. Cancelling
|
||
// the instant the handler closes the socket is a RACE with the loop's own bookkeeping: the second
|
||
// attempt often starts first and is itself cut, so the error under test becomes a `cancelled` from
|
||
// somewhere else entirely. Measured before the delay was added: 28 failures in 80 runs, every one of
|
||
// them the wrong attempt. The window here is half a second inside a three-second sleep, and the
|
||
// call-count assertion below says outright when the fixture missed it.
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
go func() {
|
||
<-arrived
|
||
time.Sleep(500 * time.Millisecond)
|
||
cancel()
|
||
}()
|
||
p := cutProfile(5 * time.Second)
|
||
p.BackoffBase, p.BackoffCap = 3*time.Second, 3*time.Second
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: p}, nil)
|
||
_, err := c.Complete(ctx, LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
if n := calls.Load(); n != 1 {
|
||
t.Fatalf("the loop was meant to be ASLEEP before its retry when the stop landed; the server was "+
|
||
"asked %d time(s), so this run measured a different moment", n)
|
||
}
|
||
if !errors.Is(err, context.Canceled) {
|
||
t.Fatalf("a stopped run must still leave as a cancellation, got %T %v", err, err)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("the delivered attempt behind the backoff must survive the stop, or its money is lost "+
|
||
"and the chunk is left with no mark at all. got %T %v", err, err)
|
||
}
|
||
if cut.Cause != CutByConnection || !cut.Delivered {
|
||
t.Fatalf("the evidence must be the attempt's own, not a re-labelled cancellation: %+v", cut)
|
||
}
|
||
}
|
||
|
||
// TestARefusalIsNotAPurchaseEvenWhenTheReplyOutrunsTheWrite is the regression the FIX for the early-200
|
||
// leak introduced, and it is the more expensive of the two directions.
|
||
//
|
||
// A provider that REFUSES — 401, 403, 413, a quota 429 — and drops the connection while our request body
|
||
// is still writing gives GotFirstResponseByte=true and a WRITE error. http.Client.Do then returns the
|
||
// write failure, so the status line is never in our hands and the branch that reads it is never reached.
|
||
// Taking the response byte alone as proof of delivery paid an estimate for every one of those: measured
|
||
// at 22 refusals in 25, one of them $0.80 booked for a request the provider declined — and the
|
||
// delivered-cut retry sent the whole body to it a second time.
|
||
//
|
||
// So the reply counts as evidence only where a reply actually reached us. Here it did not.
|
||
func TestARefusalIsNotAPurchaseEvenWhenTheReplyOutrunsTheWrite(t *testing.T) {
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer ln.Close()
|
||
go func() {
|
||
for {
|
||
c, aerr := ln.Accept()
|
||
if aerr != nil {
|
||
return
|
||
}
|
||
go func(c net.Conn) {
|
||
// Refuse the moment the request line is in, then RESET — the body is still writing, so
|
||
// the client sees a write error and never gets a status line.
|
||
br := bufio.NewReader(c)
|
||
if _, rerr := br.ReadString('\n'); rerr != nil {
|
||
c.Close()
|
||
return
|
||
}
|
||
fmt.Fprint(c, "HTTP/1.1 401 Unauthorized\r\nContent-Length: 9\r\n\r\nno access")
|
||
if tc, ok := c.(*net.TCPConn); ok {
|
||
_ = tc.SetLinger(0) // RST rather than a graceful close
|
||
}
|
||
c.Close()
|
||
}(c)
|
||
}
|
||
}()
|
||
|
||
huge := strings.Repeat("длинный исходный текст главы. ", 200000)
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: "http://" + ln.Addr().String(), Profile: cutProfile(2 * time.Second)}, nil)
|
||
_, err = c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: huge}}, MaxTokens: 16})
|
||
|
||
if err == nil {
|
||
t.Fatal("a refused request must not report success")
|
||
}
|
||
var cut *AttemptCutError
|
||
if errors.As(err, &cut) {
|
||
t.Fatalf("the provider REFUSED this request and generated nothing; booking an estimate for it "+
|
||
"charges a reader for a call that never ran, and the delivered-cut retry sends the whole body "+
|
||
"again. got %+v", cut)
|
||
}
|
||
}
|
||
|
||
// TestARedirectIsNotADelivery: `Do` spans the WHOLE redirect chain and the delivery trace does not reset
|
||
// between its legs, so the first leg reaching a redirector sets WroteRequest for good — and a second leg
|
||
// whose connect is REFUSED still looked delivered. Measured through the ledger before the fix:
|
||
// $0.001056 booked for a `connect: connection refused` that never put a byte on any wire.
|
||
//
|
||
// The client now stops at the 3xx instead of chasing it, so a provider endpoint that redirects fails
|
||
// loud with its status rather than quietly costing money at the other end of the hop.
|
||
func TestARedirectIsNotADelivery(t *testing.T) {
|
||
// ⛔ EVERY CLIENT THIS PACKAGE BUILDS, and not just the cloud one. The guard first went only on
|
||
// keepAliveHTTPClient, and a mutation that removed it from the LOCAL client SURVIVED: this fixture
|
||
// exercised the cloud path alone, so a hole in the other constructor was invisible. They share
|
||
// attempt() and they share the delivery trace, so they must share the policy.
|
||
clients := map[string]func(base string) LLMClient{
|
||
"cloud": func(base string) LLMClient {
|
||
return NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: base, Profile: cutProfile(2 * time.Second)}, nil)
|
||
},
|
||
"local": func(base string) LLMClient {
|
||
return NewLocalClient(LocalConfig{BaseURL: base, Model: "m", Profile: cutProfile(2 * time.Second)}, nil)
|
||
},
|
||
// The third, and it was missing while the comment above already said «every». It is a deprecated
|
||
// adapter with no live provider, but it builds its own client, it sends `x-api-key`, and
|
||
// net/http strips only Authorization across a host change — so its redirect carried the key to
|
||
// the target. A fixture that says «every» and builds two of three is the shape of green that
|
||
// let the local client keep following redirects.
|
||
"anthropic": func(base string) LLMClient {
|
||
return NewAnthropicClient(AnthropicConfig{BaseURL: base, APIKey: "k", Profile: cutProfile(2 * time.Second)}, nil)
|
||
},
|
||
}
|
||
for name, build := range clients {
|
||
t.Run(name, func(t *testing.T) {
|
||
dead, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
target := dead.Addr().String()
|
||
dead.Close() // the control: this address refuses connections, so nothing can be delivered there
|
||
|
||
var hops atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
hops.Add(1)
|
||
http.Redirect(w, r, "http://"+target+"/chat/completions", http.StatusTemporaryRedirect)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
_, err = build(srv.URL).Complete(context.Background(),
|
||
LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
if err == nil {
|
||
t.Fatal("a provider endpoint that redirects must fail, not silently follow")
|
||
}
|
||
var cut *AttemptCutError
|
||
if errors.As(err, &cut) {
|
||
t.Fatalf("nothing was delivered anywhere: the redirector answered and the target refuses "+
|
||
"connections, so booking money for this charges a reader for a call no endpoint "+
|
||
"received. got %+v", cut)
|
||
}
|
||
// It fails as the status it is — the operator is told the base_url sends them somewhere else.
|
||
var hse *HTTPStatusError
|
||
if !errors.As(err, &hse) || hse.Status != http.StatusTemporaryRedirect {
|
||
t.Fatalf("the 3xx itself must reach the operator, got %T %v", err, err)
|
||
}
|
||
if n := hops.Load(); n == 0 {
|
||
t.Fatalf("the redirector was never asked — this fixture measured nothing (hops=%d)", n)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestADeliveredCutSurvivesATerminalStatusLaterInTheChain: only the LAST error used to leave the retry
|
||
// loop, so a chain that cut a delivered request and then met a terminal 4xx returned the status alone.
|
||
// The runner then read «the request never went out», released the reservation and booked $0 for a
|
||
// generation the provider had made — and left the position with no mark either. Reachable in the ordinary
|
||
// way: a 400/401/403/413 on the retry after a broken socket.
|
||
func TestADeliveredCutSurvivesATerminalStatusLaterInTheChain(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
if calls.Add(1) == 1 {
|
||
// First: a delivered request whose socket dies mid-body — retryable, and BILLABLE.
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusOK)
|
||
fmt.Fprint(w, `{"id":"x","choices":[`)
|
||
w.(http.Flusher).Flush()
|
||
hj, ok := w.(http.Hijacker)
|
||
if !ok {
|
||
t.Error("the fixture needs a hijackable response writer")
|
||
return
|
||
}
|
||
conn, _, herr := hj.Hijack()
|
||
if herr != nil {
|
||
t.Error(herr)
|
||
return
|
||
}
|
||
conn.Close()
|
||
return
|
||
}
|
||
// Then: a terminal status, which ends the chain and used to erase the first attempt.
|
||
w.WriteHeader(http.StatusForbidden)
|
||
fmt.Fprint(w, `{"error":"forbidden"}`)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
if n := calls.Load(); n != 2 {
|
||
t.Fatalf("the fixture needs both attempts to happen, got %d — it is measuring a different chain", n)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("the first attempt was DELIVERED and cut, so it is owed whatever ended the chain "+
|
||
"afterwards; losing it books $0 for a generation the provider made and leaves the position "+
|
||
"unmarked. got %T %v", err, err)
|
||
}
|
||
if !cut.Delivered || cut.Cause != CutByConnection {
|
||
t.Fatalf("the surviving evidence must be the cut's own: %+v", cut)
|
||
}
|
||
// And the status that ENDED the chain is still there — a caller must be able to see both.
|
||
var hse *HTTPStatusError
|
||
if !errors.As(err, &hse) || hse.Status != http.StatusForbidden {
|
||
t.Fatalf("the terminal status must also reach the caller, got %T %v", err, err)
|
||
}
|
||
}
|
||
|
||
// TestTheKeepalivePairSitsOnTheTransportTheCloudClientBuilds: the h2 keepalive bounds are worth their
|
||
// comment only if they sit on the transport the cloud client actually uses, and reading them back is
|
||
// the only way to know. tuneHTTP2 is that code path and it hands back what it set; asking
|
||
// ConfigureTransports a second time on an already-built client answers an error and no transport, so a
|
||
// check written that way asserts nothing at all.
|
||
func TestTheKeepalivePairSitsOnTheTransportTheCloudClientBuilds(t *testing.T) {
|
||
// The control comes first: both bounds are non-zero, so a build that wired NOTHING cannot pass the
|
||
// comparisons below by matching zero against zero.
|
||
if h2ReadIdleTimeout <= 0 || h2PingTimeout <= 0 {
|
||
t.Fatalf("a zero bound disables the keepalive silently: %s / %s", h2ReadIdleTimeout, h2PingTimeout)
|
||
}
|
||
// ⛔ THE CLIENT THE ENGINE ACTUALLY BUILDS, not a clone tuned beside it. Asking tuneHTTP2 on a fresh
|
||
// transport of the test's own would pass even if keepAliveHTTPClient stopped calling it at all —
|
||
// which is the shape the write bound's pin had, and it is why that pin survived its own mutation.
|
||
c, h2 := buildCloudClient()
|
||
if c == nil || h2 == nil {
|
||
t.Fatal("the cloud client could not be configured for h2 — this test measured nothing")
|
||
}
|
||
if c.Transport == nil {
|
||
t.Fatal("the cloud client has no transport at all — the bounds below would be read off nothing")
|
||
}
|
||
if h2.ReadIdleTimeout != h2ReadIdleTimeout {
|
||
t.Fatalf("the read-idle bound is not on the transport: got %s want %s", h2.ReadIdleTimeout, h2ReadIdleTimeout)
|
||
}
|
||
if h2.PingTimeout != h2PingTimeout {
|
||
t.Fatalf("the ping bound is not on the transport: got %s want %s", h2.PingTimeout, h2PingTimeout)
|
||
}
|
||
}
|
||
|
||
// billableCut answers 200 and flushes the first bytes of a body, then kills the socket: the provider
|
||
// ACKNOWLEDGED the request, so the cut is one we are billed for.
|
||
func billableCut(t *testing.T, w http.ResponseWriter) {
|
||
t.Helper()
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusOK)
|
||
fmt.Fprint(w, `{"id":"x","choices":[`)
|
||
w.(http.Flusher).Flush()
|
||
hijackKill(t, w)
|
||
}
|
||
|
||
// freeCut kills the socket with no status line at all: the request was delivered, nothing came back, and
|
||
// nobody is owed for it.
|
||
func freeCut(t *testing.T, w http.ResponseWriter) { t.Helper(); hijackKill(t, w) }
|
||
|
||
func hijackKill(t *testing.T, w http.ResponseWriter) {
|
||
t.Helper()
|
||
hj, ok := w.(http.Hijacker)
|
||
if !ok {
|
||
t.Error("the fixture needs a hijackable response writer")
|
||
return
|
||
}
|
||
conn, _, err := hj.Hijack()
|
||
if err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
conn.Close()
|
||
}
|
||
|
||
// TestAPaidCutSurvivesAPlainRetryableLaterInTheChain: a chain can be billed for an attempt that is not
|
||
// the attempt whose error ends it. Here the money is on attempt 1 and the chain ends on a 503 — an
|
||
// ordinary retryable that owes nobody anything. If the loop returns only what ended it, the engine
|
||
// settles $0 for a generation the provider made.
|
||
func TestAPaidCutSurvivesAPlainRetryableLaterInTheChain(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
if calls.Add(1) == 1 {
|
||
billableCut(t, w)
|
||
return
|
||
}
|
||
w.WriteHeader(http.StatusServiceUnavailable)
|
||
fmt.Fprint(w, `{"error":"busy"}`)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
if n := calls.Load(); n < 2 {
|
||
t.Fatalf("the fixture needs both attempts to happen, got %d — it is measuring a different chain", n)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("attempt 1 was DELIVERED, ACKNOWLEDGED and cut — the chain owes money for it whatever "+
|
||
"ended it later. Returning only the 503 books $0 for a generation the provider made. got %T %v", err, err)
|
||
}
|
||
if !cut.Billable {
|
||
t.Fatalf("the surviving cut must be the BILLABLE one: %+v", cut)
|
||
}
|
||
}
|
||
|
||
// TestAFreeCutLaterDoesNotMaskThePaidCutEarlier is the same mismatch one turn harder, and it is the case
|
||
// the type test could not see. Both attempts end in a cut, so «the chain already ends carrying one» is
|
||
// true — and wrong: the ending cut was never acknowledged and costs nothing, while the earlier one was
|
||
// and does. Money, not type, decides which one the caller must be handed.
|
||
func TestAFreeCutLaterDoesNotMaskThePaidCutEarlier(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
if calls.Add(1) == 1 {
|
||
billableCut(t, w)
|
||
return
|
||
}
|
||
freeCut(t, w)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
if n := calls.Load(); n < 2 {
|
||
t.Fatalf("the fixture needs both attempts to happen, got %d", n)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("some cut must reach the caller, got %T %v", err, err)
|
||
}
|
||
if !cut.Billable {
|
||
t.Fatalf("the caller settles from ONE error, so it must be the one with the money on it: a later "+
|
||
"cut nobody acknowledged erased an earlier one the provider did. got Billable=%t cause=%s",
|
||
cut.Billable, cut.Cause)
|
||
}
|
||
}
|
||
|
||
// TestTheCancellationExitCarriesWhatTheChainOwes covers the loop's OTHER cancellation exit — the one
|
||
// taken right after an attempt rather than during a backoff (that one has its own fixture). A run
|
||
// stopped here has a paid cut behind it and an ordinary retryable in front of it, and returning only
|
||
// «cancelled + the 503» leaves the runner nothing to settle and the chunk with no mark.
|
||
func TestTheCancellationExitCarriesWhatTheChainOwes(t *testing.T) {
|
||
var calls atomic.Int32
|
||
secondLanded := make(chan struct{})
|
||
stopped := make(chan struct{})
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
if calls.Add(1) == 1 {
|
||
billableCut(t, w)
|
||
return
|
||
}
|
||
// ⛔ SYNCHRONISED ON THE STOP, NOT ON A CLOCK. The loop checks the context AFTER the attempt
|
||
// returns, so the stop has to be in place before this handler answers — and a sleep here would
|
||
// make the fixture measure whichever of the two won the race that day.
|
||
close(secondLanded)
|
||
<-stopped
|
||
w.WriteHeader(http.StatusServiceUnavailable)
|
||
fmt.Fprint(w, `{"error":"busy"}`)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
defer cancel()
|
||
go func() {
|
||
<-secondLanded
|
||
cancel()
|
||
close(stopped)
|
||
}()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err := c.Complete(ctx, LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
if n := calls.Load(); n < 2 {
|
||
t.Fatalf("the fixture needs the stop to land on the SECOND attempt, got %d calls", n)
|
||
}
|
||
if !errors.Is(err, context.Canceled) {
|
||
t.Fatalf("a stopped run must still read as cancelled — the exit code depends on it. got %T %v", err, err)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) || !cut.Billable {
|
||
t.Fatalf("the stop exit must carry the cut the chain owes money for; without it the runner settles "+
|
||
"nothing and the position is left unmarked. got %T %v", err, err)
|
||
}
|
||
}
|
||
|
||
// TestAPaidCutAfterAFreeOneIsTheOneTheCallerSettlesFrom is the accumulator's own case, and it is the
|
||
// order the «first cut wins» rule cannot serve. Attempt 1 is delivered and never acknowledged — nobody
|
||
// is owed for it. Attempt 2 IS acknowledged and cut, so it is the one the engine must settle. Keeping
|
||
// whichever cut came first hands the caller the free one and books $0 for a generation that was made.
|
||
func TestAPaidCutAfterAFreeOneIsTheOneTheCallerSettlesFrom(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
switch calls.Add(1) {
|
||
case 1:
|
||
freeCut(t, w) // delivered, never acknowledged — costs nothing
|
||
case 2:
|
||
billableCut(t, w) // acknowledged, then cut — this is the money
|
||
default:
|
||
w.WriteHeader(http.StatusForbidden) // unreachable under the cap; here so a policy change fails loudly
|
||
fmt.Fprint(w, `{"error":"forbidden"}`)
|
||
}
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
// TWO attempts, not three: only a connection cut is retryable and the delivered-cut cap makes the
|
||
// second one terminal, so the chain ends ON the paid cut. That is the premise this test rests on —
|
||
// if a policy change ever lets a third attempt happen, the count says so instead of the assertions
|
||
// quietly measuring a different chain.
|
||
if n := calls.Load(); n != 2 {
|
||
t.Fatalf("the fixture needs exactly two attempts, got %d — it is measuring a different chain", n)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("a cut must reach the caller, got %T %v", err, err)
|
||
}
|
||
if !cut.Billable {
|
||
t.Fatalf("the chain produced a free cut and then a PAID one; the caller settles from one error, so "+
|
||
"it must be the paid one. Keeping whichever came first books $0 for a generation the provider "+
|
||
"made. got Billable=%t cause=%s", cut.Billable, cut.Cause)
|
||
}
|
||
}
|
||
|
||
// TestResponseBytesWithoutAReplyAreNotAPurchase pins the half of the money predicate that nothing else
|
||
// reaches: `answered`. A cut is billable only when the provider ACKNOWLEDGED the request with a reply —
|
||
// `Billable = answered && afterHeaders` — and the two halves fail apart. Every other fixture exercises
|
||
// the second: no response byte, so both are false and dropping the first changes nothing.
|
||
//
|
||
// This is the case where they disagree. The peer writes RESPONSE BYTES that are not a reply — a broken
|
||
// status line — so the delivery trace's first-byte flag is set while `Do` still fails and no 2xx object
|
||
// ever exists. Without `answered &&`, that reads as money owed, and the engine books an estimate for a
|
||
// call whose provider answered nothing.
|
||
func TestResponseBytesWithoutAReplyAreNotAPurchase(t *testing.T) {
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer ln.Close()
|
||
go func() {
|
||
for {
|
||
c, aerr := ln.Accept()
|
||
if aerr != nil {
|
||
return
|
||
}
|
||
go func(c net.Conn) {
|
||
defer c.Close()
|
||
br := bufio.NewReader(c)
|
||
if _, rerr := br.ReadString('\n'); rerr != nil {
|
||
return
|
||
}
|
||
// Bytes on the wire, and not a reply: the trace sees a first response byte, the parser
|
||
// never sees a status line.
|
||
fmt.Fprint(c, "NOT-HTTP garbage from a broken proxy\r\n")
|
||
}(c)
|
||
}
|
||
}()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: "http://" + ln.Addr().String(), Profile: cutProfile(2 * time.Second)}, nil)
|
||
_, err = c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
if err == nil {
|
||
t.Fatal("a reply that is not a reply must not report success")
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
// Not a failure of the property under test: the request WAS delivered, so it is a cut. If the
|
||
// engine ever stops classifying it as one, this fixture is measuring nothing and says so.
|
||
t.Fatalf("premise broken: the request was delivered and the peer answered garbage, so this is a "+
|
||
"cut. got %T %v", err, err)
|
||
}
|
||
if !cut.AfterHeaders {
|
||
t.Fatalf("premise broken: the fixture must produce a FIRST RESPONSE BYTE, otherwise both halves of "+
|
||
"the money predicate are false and this test cannot tell them apart: %+v", cut)
|
||
}
|
||
if cut.Billable {
|
||
t.Fatalf("response BYTES are not a reply. The provider acknowledged nothing, so nobody is owed for "+
|
||
"this call; booking it charges a reader for a generation that was never made. %+v", cut)
|
||
}
|
||
}
|
||
|
||
// TestTheDeliveryCountCountsDeliveriesNotCuts pins the number the operator reads. The ledger line the
|
||
// engine writes beside a cut says «the provider was asked N times; ONE estimate is booked for all of
|
||
// them» — it is the only place the gap between what was generated and what was billed becomes visible,
|
||
// and it was counting CUTS. A chain that was answered a 503 and then cut delivered twice and reported
|
||
// one.
|
||
func TestTheDeliveryCountCountsDeliveriesNotCuts(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
if calls.Add(1) == 1 {
|
||
w.WriteHeader(http.StatusServiceUnavailable) // delivered: the peer read the request to answer it
|
||
fmt.Fprint(w, `{"error":"busy"}`)
|
||
return
|
||
}
|
||
billableCut(t, w)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
// THREE deliveries: the 503, the cut, and the one retry a delivered cut is worth. The count is the
|
||
// premise as well as the subject — if the retry policy ever changes, this says so instead of letting
|
||
// the assertion below quietly measure a different chain.
|
||
if n := calls.Load(); n != 3 {
|
||
t.Fatalf("the fixture needs three attempts — a 503, a cut, and the cut's one retry — got %d", n)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("premise broken: the chain must end on a cut for the count to be carried anywhere: %T %v", err, err)
|
||
}
|
||
if cut.Deliveries != 3 {
|
||
t.Fatalf("the request reached the provider THREE times — a 503 is an answer, so the peer read it — and "+
|
||
"one estimate is booked for both. Counting only the cuts makes the operator's «asked N times» "+
|
||
"line understate a mixed chain, which is the one line where that gap is visible. got %d",
|
||
cut.Deliveries)
|
||
}
|
||
}
|
||
|
||
// TestTheStopExitStillReadsAsCancelledWhenTheAttemptDidNot closes a hole this pack's own pin had, and the
|
||
// hole is instructive: the fixture beside it drives the loop through the WIRE, and there a stop lands on
|
||
// a request in flight, so the attempt's own error is already a parent-cancelled cut carrying
|
||
// context.Canceled. Its «a stopped run must still read as cancelled» assertion was therefore satisfied by
|
||
// the cut's Parent no matter what the loop's exit did — and removing the exit's `cancelledDuring`
|
||
// survived the whole battery.
|
||
//
|
||
// The case that has nothing to lean on is the one an operator actually meets: an attempt fails with an
|
||
// ORDINARY error — a 503 — and the stop arrives immediately after, before the loop decides to retry.
|
||
// Nothing in that error knows about the context, so only the exit can carry the cancellation out; if it
|
||
// does not, `tmctl` exits 1 instead of 5 and the stream publishes `failed` where a person pressed stop.
|
||
//
|
||
// It drives retryLoop directly because the wire cannot produce this ordering on purpose: the window
|
||
// between an attempt returning and the loop reading the context is a few instructions wide, and a
|
||
// fixture that raced for it would be measuring the scheduler.
|
||
func TestTheStopExitStillReadsAsCancelledWhenTheAttemptDidNot(t *testing.T) {
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
defer cancel()
|
||
plain := &HTTPStatusError{Status: http.StatusServiceUnavailable, Body: "busy"}
|
||
calls := 0
|
||
_, err := retryLoop(ctx, cutProfile(time.Second), "p", nil, func() (*openAIResponse, bool, error) {
|
||
calls++
|
||
cancel() // the stop lands while this attempt is returning, not while it is in flight
|
||
return nil, true, plain
|
||
})
|
||
if calls != 1 {
|
||
t.Fatalf("premise broken: the loop must take the stop exit on the FIRST attempt, got %d calls", calls)
|
||
}
|
||
if !errors.Is(err, context.Canceled) {
|
||
t.Fatalf("a stopped run must read as cancelled even when the attempt that was in flight failed of "+
|
||
"its own accord: the exit code (5, not 1) and the `stopped` the stream publishes both hang off "+
|
||
"this. The attempt's error knows nothing about the context, so only this exit can carry it. got %T %v",
|
||
err, err)
|
||
}
|
||
// The control: the attempt's own error must ALSO survive, or the exit would be trading one truth for
|
||
// the other — the operator needs to know a 503 is why the engine was retrying when the stop arrived.
|
||
if !errors.Is(err, plain) {
|
||
t.Fatalf("the attempt's own error must reach the caller beside the cancellation: %T %v", err, err)
|
||
}
|
||
}
|
||
|
||
// TestTheVendorsPublishedPairIsWhatTheVendorPublishes pins the two numbers every derived deadline is
|
||
// built from, and it exists because the formula test beside it cannot: both of its sides read these same
|
||
// constants, so corrupting them moves the expectation with the result and the identity still holds.
|
||
// Measured on a copy: `vendorHourlyTokenBudget` quadrupled, and `internal/llm`, `internal/config` and the
|
||
// rest of the battery all stayed green.
|
||
//
|
||
// The direction is money and it is this pack's own subject. A budget four times too generous makes every
|
||
// derived deadline four times too SHORT, so calls the provider is still generating are cut by us — and
|
||
// since this pack, a self-cut that the provider acknowledged is PAID FOR. Five of the eight catalogued
|
||
// providers run on this default.
|
||
//
|
||
// ⚠ THESE ARE SOURCE NUMBERS, NOT PRODUCTS, which is why quoting them here does not violate
|
||
// TestTheDeadlineTestQuotesNoDeadline: that gate bans the DERIVED seconds, because a pin on a number the
|
||
// formula produced is a pin on nothing. A citation of the vendor's published pair is the opposite — it is
|
||
// the one place the arithmetic touches the outside world, and it belongs written down.
|
||
func TestTheVendorsPublishedPairIsWhatTheVendorPublishes(t *testing.T) {
|
||
// anthropic-sdk-go's CalculateNonStreamingTimeout budgets one hour per 128 000 output tokens. That
|
||
// pair is the vendor's, not ours: changing either is adopting a different vendor's arithmetic, and it
|
||
// must be a decision someone makes on purpose rather than an edit nothing notices.
|
||
if vendorHourlyTokenBudget != 128000 {
|
||
t.Fatalf("the vendor's published token budget is 128000 per window; this build says %d. Every "+
|
||
"provider without its own measured floor derives its deadline from this number, and a budget "+
|
||
"too generous makes the deadline too SHORT — cutting calls the provider is still generating, "+
|
||
"which this pack now charges for", vendorHourlyTokenBudget)
|
||
}
|
||
if vendorBudgetWindow != time.Hour {
|
||
t.Fatalf("the vendor's published window is one hour; this build says %s", vendorBudgetWindow)
|
||
}
|
||
// The control: the pair is actually what the derivation uses, not two constants sitting beside it.
|
||
// Without this the assertions above would hold on a build that derived from something else entirely.
|
||
if got := (RetryProfile{}).deriveDeadline(vendorHourlyTokenBudget); got != vendorBudgetWindow {
|
||
t.Fatalf("a grant of exactly the vendor's hourly budget must derive to exactly its window — that "+
|
||
"identity is what makes the two constants above the SOURCE of the derivation rather than "+
|
||
"decoration beside it. got %s, want %s", got, vendorBudgetWindow)
|
||
}
|
||
}
|
||
|
||
// TestAnOlderCutCarriesTheChainsFinalDeliveryCount is the count's other half, and the one a chain that
|
||
// ends on a cut cannot show. Here the money is on attempt 1 and the chain goes on WITHOUT cutting again:
|
||
// two 503s, then exhaustion. `chainError` hands up attempt 1's cut because it is the one that owes — and
|
||
// the number stamped on it when it was born knows about one delivery out of three.
|
||
//
|
||
// Nothing in the error tree can repair that: the later deliveries were answers, not cuts, so no cut in
|
||
// the tree has ever seen them. Only the chain's own counter has.
|
||
func TestAnOlderCutCarriesTheChainsFinalDeliveryCount(t *testing.T) {
|
||
var calls atomic.Int32
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
if calls.Add(1) == 1 {
|
||
billableCut(t, w)
|
||
return
|
||
}
|
||
w.WriteHeader(http.StatusServiceUnavailable)
|
||
fmt.Fprint(w, `{"error":"busy"}`)
|
||
}))
|
||
defer srv.Close()
|
||
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: srv.URL, Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16})
|
||
|
||
// The premise is the shape, not just the count: a cut FIRST, then deliveries that are not cuts.
|
||
if n := calls.Load(); n != 3 {
|
||
t.Fatalf("the fixture needs a cut and then two plain answers, got %d attempts", n)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("premise broken: the chain owes money for attempt 1 and must hand that cut up: %T %v", err, err)
|
||
}
|
||
if cut.Deliveries != 3 {
|
||
t.Fatalf("the provider was asked THREE times and one estimate covers all of them; the cut handed "+
|
||
"up was born on attempt 1 and reports %d. The later deliveries were answers, not cuts, so no "+
|
||
"cut in the error tree has ever seen them — only the chain's counter has, and the ledger line "+
|
||
"built from this number under-states the gap it exists to show", cut.Deliveries)
|
||
}
|
||
}
|
||
|
||
// TestAnUndeliveredAttemptIsNotCountedAsAsking pins the predicate the count rests on. Every other
|
||
// fixture here tells deliveries apart from CUTS; none tells them apart from ATTEMPTS, because none has
|
||
// an attempt that never reached the provider. So `askedToGenerate++` made unconditional survived
|
||
// everything: the number would say the provider was asked twice when it was asked once, in the one line
|
||
// where the generated-versus-billed gap is published.
|
||
func TestAnUndeliveredAttemptIsNotCountedAsAsking(t *testing.T) {
|
||
var conns atomic.Int32
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer ln.Close()
|
||
go func() {
|
||
for {
|
||
c, aerr := ln.Accept()
|
||
if aerr != nil {
|
||
return
|
||
}
|
||
go func(c net.Conn) {
|
||
defer c.Close()
|
||
if conns.Add(1) == 1 {
|
||
// The first attempt never reaches the provider: accepted and dropped WITHOUT reading,
|
||
// so the huge body's write fails partway and nothing was asked of anybody. A reset
|
||
// instead of this plain close would let the body land in the socket buffer first, and
|
||
// the trace would then be right to call it delivered — which is a different case.
|
||
return
|
||
}
|
||
// The second DOES reach it: the whole request is READ before anything is answered, so
|
||
// the client's write completes and the delivery trace says so. Answering early — the
|
||
// shape of the branch above — would make this attempt a failed write too, and the
|
||
// fixture would then be comparing two undelivered attempts.
|
||
br := bufio.NewReader(c)
|
||
req, rerr := http.ReadRequest(br)
|
||
if rerr != nil {
|
||
return
|
||
}
|
||
_, _ = io.Copy(io.Discard, req.Body)
|
||
req.Body.Close()
|
||
// Acknowledged, and then cut: one delivery, and money.
|
||
fmt.Fprint(c, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\"id\":\"x\",\"choices\":[")
|
||
if tc, ok := c.(*net.TCPConn); ok {
|
||
_ = tc.SetLinger(0)
|
||
}
|
||
}(c)
|
||
}
|
||
}()
|
||
|
||
huge := strings.Repeat("длинный исходный текст главы. ", 200000)
|
||
c := NewOpenAICompatClient(OpenAICompatConfig{Name: "p", BaseURL: "http://" + ln.Addr().String(), Profile: cutProfile(3 * time.Second)}, nil)
|
||
_, err = c.Complete(context.Background(), LLMRequest{Model: "m", Messages: []Message{{Role: "user", Content: huge}}, MaxTokens: 16})
|
||
|
||
// THREE attempts, TWO deliveries: the first never leaves, the second is delivered and cut, and a
|
||
// delivered cut is worth one retry — which delivers again and hits the cap. The count is the premise
|
||
// as well as the subject: if the retry policy moves, this says so instead of the assertion below
|
||
// quietly measuring a different chain.
|
||
if n := conns.Load(); n != 3 {
|
||
t.Fatalf("the fixture needs three attempts — one that never delivers, then a cut and its one "+
|
||
"retry — got %d", n)
|
||
}
|
||
var cut *AttemptCutError
|
||
if !errors.As(err, &cut) {
|
||
t.Fatalf("premise broken: the chain must end on the delivered attempt's cut: %T %v", err, err)
|
||
}
|
||
if cut.Deliveries != 2 {
|
||
t.Fatalf("the provider was asked TWICE across three attempts: the first request never left, so "+
|
||
"nobody was asked and nobody generated. Counting it says the engine bought three generations "+
|
||
"where it bought two, in the one line where that gap is published. got %d", cut.Deliveries)
|
||
}
|
||
}
|