added auto-continue mechanics, updated default ctx to 262144 tokens
This commit is contained in:
@@ -67,7 +67,7 @@ var llmTransport = &http.Transport{
|
|||||||
DialContext: (&net.Dialer{Timeout: 300 * time.Second}).DialContext,
|
DialContext: (&net.Dialer{Timeout: 300 * time.Second}).DialContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
var defCfg = Cfg{"https://opencode.ai/zen/v1", "big-pickle", "-", 0.7, 300, 120, 1000, true, "auto", 200000, nil}
|
var defCfg = Cfg{"https://opencode.ai/zen/v1", "big-pickle", "-", 0.7, 300, 120, 1000, true, "auto", 262144, nil}
|
||||||
|
|
||||||
func atoiD(s string, d int) int {
|
func atoiD(s string, d int) int {
|
||||||
if v, e := strconv.Atoi(strings.TrimSpace(s)); e == nil {
|
if v, e := strconv.Atoi(strings.TrimSpace(s)); e == nil {
|
||||||
@@ -115,7 +115,7 @@ var cwCache = map[string]int{}
|
|||||||
|
|
||||||
func fetchContextWindow(cfg *Cfg) int {
|
func fetchContextWindow(cfg *Cfg) int {
|
||||||
// Only values discovered from the /models endpoint are cached, keyed by
|
// Only values discovered from the /models endpoint are cached, keyed by
|
||||||
// endpoint+model. The context_window-override and 200000 default are derived
|
// endpoint+model. The context_window-override and 262144 default are derived
|
||||||
// per call from cfg so they never shadow each other across configs.
|
// per call from cfg so they never shadow each other across configs.
|
||||||
key := cfg.Endpoint + "\x00" + cfg.Model
|
key := cfg.Endpoint + "\x00" + cfg.Model
|
||||||
cwCacheMu.Lock()
|
cwCacheMu.Lock()
|
||||||
@@ -131,9 +131,9 @@ func fetchContextWindow(cfg *Cfg) int {
|
|||||||
return cw
|
return cw
|
||||||
}
|
}
|
||||||
if v, ok := cfg.Raw["context_window"]; ok {
|
if v, ok := cfg.Raw["context_window"]; ok {
|
||||||
return atoiD(v, 200000)
|
return atoiD(v, 262144)
|
||||||
}
|
}
|
||||||
return 200000
|
return 262144
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCfg(path string) Cfg {
|
func getCfg(path string) Cfg {
|
||||||
@@ -705,7 +705,7 @@ func estTokens(msgs []Message) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func contextPct(u Usage, cw int) float64 {
|
func contextPct(u Usage, cw int) float64 {
|
||||||
if cw <= 0 { cw = 200000 }
|
if cw <= 0 { cw = 262144 }
|
||||||
return float64(u.PromptTokens) * 100.0 / float64(cw)
|
return float64(u.PromptTokens) * 100.0 / float64(cw)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1087,7 +1087,17 @@ func AL(ctx context.Context, cfg *Cfg, msgs []Message, sp string, depth int) ([]
|
|||||||
}
|
}
|
||||||
if m.Content != nil { fmt.Println(renderMD(*m.Content)) }
|
if m.Content != nil { fmt.Println(renderMD(*m.Content)) }
|
||||||
}
|
}
|
||||||
if len(m.ToolCalls) == 0 { done = true; break }
|
if len(m.ToolCalls) == 0 {
|
||||||
|
// If the model returned only a reasoning block with no non-reasoning
|
||||||
|
// tokens or tool calls, nudge it to continue rather than ending the turn.
|
||||||
|
if m.ReasoningContent != "" && (m.Content == nil || strings.TrimSpace(*m.Content) == "") {
|
||||||
|
fmt.Println(c("[auto continue: response was reasoning-only]", 33))
|
||||||
|
msgs = append(msgs, Message{Role: "user", Content: strp("continue")})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
for _, tc := range m.ToolCalls {
|
for _, tc := range m.ToolCalls {
|
||||||
if err := ctx.Err(); err != nil { return msgs, turnUsage, err }
|
if err := ctx.Err(); err != nil { return msgs, turnUsage, err }
|
||||||
fn, astr := tc.Function.Name, filterText(tc.Function.Arguments)
|
fn, astr := tc.Function.Name, filterText(tc.Function.Arguments)
|
||||||
|
|||||||
+46
-3
@@ -1124,6 +1124,49 @@ func TestALToolLoop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestALReasoningOnlyAutoContinue(t *testing.T) {
|
||||||
|
// First response is reasoning-only (no content, no tool calls); the agent
|
||||||
|
// must auto-append a "continue" user message and keep looping until a real
|
||||||
|
// answer arrives.
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req struct {
|
||||||
|
Messages []Message `json:"messages"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
t.Errorf("decode request: %v", err)
|
||||||
|
}
|
||||||
|
for _, m := range req.Messages {
|
||||||
|
if m.Role == "user" && m.Content != nil && strings.TrimSpace(*m.Content) == "continue" {
|
||||||
|
w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":"final answer"}}]}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":null,"reasoning_content":"thinking hard"}}]}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
cfg := defCfg
|
||||||
|
cfg.Endpoint = srv.URL
|
||||||
|
cfg.Stream = false
|
||||||
|
cfg.APIKey = "-"
|
||||||
|
msgs, _, err := AL(context.Background(), &cfg, []Message{{Role: "system", Content: strp("sys")}, {Role: "user", Content: strp("start")}}, "sys", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AL: %v", err)
|
||||||
|
}
|
||||||
|
if got := last(msgs); got != "final answer" {
|
||||||
|
t.Errorf("last = %q, want %q", got, "final answer")
|
||||||
|
}
|
||||||
|
var continues int
|
||||||
|
for _, m := range msgs {
|
||||||
|
if m.Role == "user" && m.Content != nil && strings.TrimSpace(*m.Content) == "continue" {
|
||||||
|
continues++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if continues != 1 {
|
||||||
|
t.Errorf("expected exactly 1 auto continue message, got %d", continues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestALRunSubagent(t *testing.T) {
|
func TestALRunSubagent(t *testing.T) {
|
||||||
var n int
|
var n int
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
@@ -1842,12 +1885,12 @@ func TestFetchContextWindowFallbackConfig(t *testing.T) {
|
|||||||
t.Errorf("expected fallback to raw context_window 65536, got %d", cw)
|
t.Errorf("expected fallback to raw context_window 65536, got %d", cw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: Config does not specify context_window -> default 200000
|
// Case 2: Config does not specify context_window -> default 262144
|
||||||
cfg2 := defCfg
|
cfg2 := defCfg
|
||||||
cfg2.Endpoint = srv.URL
|
cfg2.Endpoint = srv.URL
|
||||||
cfg2.Raw = map[string]string{}
|
cfg2.Raw = map[string]string{}
|
||||||
if cw := fetchContextWindow(&cfg2); cw != 200000 {
|
if cw := fetchContextWindow(&cfg2); cw != 262144 {
|
||||||
t.Errorf("expected default 200000, got %d", cw)
|
t.Errorf("expected default 262144, got %d", cw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user