Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

75 lines
2.5 KiB
Go

package svc
import "github.com/zzet/gortex/internal/llm"
// maxAdaptiveDepth caps how deep an adaptive retry may bisect a single
// oversized assist request. Each level halves the candidate slice:
// depth 0 is the whole list (1 chunk), depth 1 is 2 chunks, depth 2 is
// 4, depth 3 is 8. Three halving levels is the "8x expansion" ceiling
// — beyond that a still-overflowing leaf is reported as a hard error.
const maxAdaptiveDepth = 3
// completeChunked runs call over the full candidate slice. When call
// fails with a model context-overflow error (llm.IsContextOverflow),
// it bisects the slice and retries each half — recursively, down to
// maxAdaptiveDepth — then merges the per-chunk results with merge.
//
// This is the chunk-bisection adaptive retry: a request too large for
// the model's context window is split into independently-sized pieces
// rather than failing outright. Non-overflow errors abort immediately
// (bisecting would not help). The bool return reports whether any
// bisection happened, so callers can surface that the result was
// assembled from chunks.
//
// call must be pure with respect to its input slice — it is invoked
// once per chunk — and merge must accept the per-chunk results in
// left-to-right order.
func completeChunked[C any, R any](
cands []C,
call func([]C) (R, error),
merge func([]R) R,
) (result R, chunked bool, err error) {
r, callErr := call(cands)
if callErr == nil {
return r, false, nil
}
if !llm.IsContextOverflow(callErr) || len(cands) <= 1 {
var zero R
return zero, false, callErr
}
return bisectChunked(cands, call, merge, 1)
}
// bisectChunked splits cands in two, runs call on each half, and
// recurses into a half that itself overflows until maxAdaptiveDepth.
// depth is the current bisection level (1 for the first split).
func bisectChunked[C any, R any](
cands []C,
call func([]C) (R, error),
merge func([]R) R,
depth int,
) (R, bool, error) {
mid := len(cands) / 2
parts := [][]C{cands[:mid], cands[mid:]}
results := make([]R, 0, len(parts))
for _, part := range parts {
r, err := call(part)
if err == nil {
results = append(results, r)
continue
}
if llm.IsContextOverflow(err) && len(part) > 1 && depth < maxAdaptiveDepth {
sub, _, subErr := bisectChunked(part, call, merge, depth+1)
if subErr != nil {
var zero R
return zero, false, subErr
}
results = append(results, sub)
continue
}
var zero R
return zero, false, err
}
return merge(results), true, nil
}