Stop the router repetition loop

Route now sets RepeatPenalty on the request, and the grammar's string rule is
capped at 120 characters. Two of 76 fixture cases looped one sentence inside
the text field until MaxTokens, which cut the JSON in half.
Reviewers: the new constant and the grammar string rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
This commit is contained in:
kami
2026-07-31 02:11:38 +04:00
parent 17b47ce206
commit d30618ecb7
2 changed files with 43 additions and 4 deletions
+27 -1
View File
@@ -13,9 +13,35 @@ import (
type mockLLM struct {
out string
err error
got *llm.Req // last request, when the test wants to inspect it
}
func (m mockLLM) Complete(_ context.Context, _ llm.Req) (string, error) { return m.out, m.err }
func (m mockLLM) Complete(_ context.Context, r llm.Req) (string, error) {
if m.got != nil {
*m.got = r
}
return m.out, m.err
}
// Without a repeat penalty the model loops inside the text field until MaxTokens
// and the truncated JSON fails to parse.
func TestLLMRouterSetsRepeatPenalty(t *testing.T) {
var got llm.Req
lr := NewLLMRouter(mockLLM{out: `{"intent":"chat","text":"привет"}`, got: &got})
if _, _, err := lr.Route(context.Background(), "привет", time.Now()); err != nil {
t.Fatalf("route: %v", err)
}
if got.RepeatPenalty <= 1 {
t.Fatalf("want repeat penalty above 1, got %v", got.RepeatPenalty)
}
}
// An unbounded string rule lets one field eat the whole token budget.
func TestRouteGrammarBoundsStrings(t *testing.T) {
if !strings.Contains(routeGrammar, `string ::= "\"" ([^"\\] | "\\" .){0,120} "\""`) {
t.Fatal("grammar string rule lost its length bound")
}
}
// A question naming a fact key used to be stored as a fact because the fact rule
// was tested first. Keep the query rule above it.