feat: provider manager, cancellation, session summaries (store+agent)
This commit is contained in:
parent
406a523d54
commit
f1b2093b22
10 changed files with 883 additions and 37 deletions
|
|
@ -2,6 +2,8 @@ package agent
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -91,3 +93,116 @@ func TestHistoryDropsOrphanToolMessages(t *testing.T) {
|
|||
t.Fatalf("%+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
// blocking streams one delta then waits for cancellation, like a slow provider.
|
||||
type blocking struct{ started chan struct{} }
|
||||
|
||||
func (b *blocking) Chat(ctx context.Context, _ llm.Request, d llm.DeltaFunc) (llm.Response, error) {
|
||||
d("Teil")
|
||||
d("antwort")
|
||||
close(b.started)
|
||||
<-ctx.Done()
|
||||
return llm.Response{}, ctx.Err()
|
||||
}
|
||||
|
||||
func TestCancelStoresPartialAnswer(t *testing.T) {
|
||||
st := newStore(t)
|
||||
ss, _ := st.CreateSession(context.Background(), "")
|
||||
b := &blocking{started: make(chan struct{})}
|
||||
a := &Agent{LLM: b, Store: st, Tools: tools.NewRegistry(), MaxIterations: 3}
|
||||
if a.Cancel(ss.ID) {
|
||||
t.Fatal("nothing should be running")
|
||||
}
|
||||
go func() {
|
||||
<-b.started
|
||||
if !a.Cancel(ss.ID) {
|
||||
t.Error("cancel should find the run")
|
||||
}
|
||||
}()
|
||||
text, err := a.Run(context.Background(), ss.ID, "erzähl was", nil)
|
||||
if !errors.Is(err, ErrCancelled) || text != "Teilantwort" {
|
||||
t.Fatalf("%q %v", text, err)
|
||||
}
|
||||
msgs, _ := st.Messages(context.Background(), ss.ID, 0)
|
||||
if len(msgs) != 2 || !msgs[1].Cancelled || msgs[1].Content != "Teilantwort" {
|
||||
t.Fatalf("%+v", msgs)
|
||||
}
|
||||
if a.Running(ss.ID) {
|
||||
t.Fatal("run must be unregistered")
|
||||
}
|
||||
}
|
||||
|
||||
// recorder answers summary requests with "SUMMARY" and normal turns with "ok".
|
||||
type recorder struct{ reqs []llm.Request }
|
||||
|
||||
func (r *recorder) Chat(_ context.Context, req llm.Request, _ llm.DeltaFunc) (llm.Response, error) {
|
||||
r.reqs = append(r.reqs, req)
|
||||
if strings.Contains(req.Messages[0].Content, "compress chat histories") {
|
||||
return llm.Response{Content: "SUMMARY"}, nil
|
||||
}
|
||||
return llm.Response{Content: "ok"}, nil
|
||||
}
|
||||
|
||||
func TestAutoSummarize(t *testing.T) {
|
||||
st := newStore(t)
|
||||
ctx := context.Background()
|
||||
ss, _ := st.CreateSession(ctx, "")
|
||||
p := &recorder{}
|
||||
a := &Agent{LLM: p, Store: st, Tools: tools.NewRegistry(), MaxIterations: 2, SystemPrompt: "sys",
|
||||
Summarize: SummarizeConfig{Enabled: true, MaxMessages: 6, KeepRecent: 2}}
|
||||
for i := 0; i < 3; i++ { // 6 messages -> at threshold, no summary yet
|
||||
a.Run(ctx, ss.ID, fmt.Sprintf("frage %d", i), nil)
|
||||
}
|
||||
if _, err := st.LatestSummary(ctx, ss.ID); err == nil {
|
||||
t.Fatal("summary created too early")
|
||||
}
|
||||
var events []string
|
||||
a.Run(ctx, ss.ID, "frage 3", func(e Event) { events = append(events, e.Type) })
|
||||
sm, err := st.LatestSummary(ctx, ss.ID)
|
||||
if err != nil || sm.Content != "SUMMARY" {
|
||||
t.Fatalf("%+v %v", sm, err)
|
||||
}
|
||||
if events[0] != "summarized" {
|
||||
t.Fatalf("%v", events)
|
||||
}
|
||||
last := p.reqs[len(p.reqs)-1].Messages
|
||||
if last[1].Role != llm.RoleSystem || !strings.Contains(last[1].Content, "SUMMARY") {
|
||||
t.Fatalf("summary not in context: %+v", last)
|
||||
}
|
||||
// the verbatim tail starts at a user message and older messages are not sent
|
||||
if last[2].Role != llm.RoleUser || strings.Contains(fmt.Sprint(last), "frage 0") {
|
||||
t.Fatalf("bad window: %+v", last)
|
||||
}
|
||||
if all, _ := st.Messages(ctx, ss.ID, 0); len(all) != 8 {
|
||||
t.Fatalf("originals must be kept, got %d", len(all))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoSummarizeByTokens(t *testing.T) {
|
||||
st := newStore(t)
|
||||
ctx := context.Background()
|
||||
ss, _ := st.CreateSession(ctx, "")
|
||||
a := &Agent{LLM: &recorder{}, Store: st, Tools: tools.NewRegistry(), MaxIterations: 2,
|
||||
Summarize: SummarizeConfig{Enabled: true, MaxMessages: 1000, MaxTokens: 50, KeepRecent: 1}}
|
||||
a.Run(ctx, ss.ID, strings.Repeat("lang ", 100), nil)
|
||||
a.Run(ctx, ss.ID, "kurz", nil)
|
||||
if _, err := st.LatestSummary(ctx, ss.ID); err != nil {
|
||||
t.Fatal("token threshold should trigger summary")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSummarizeNowManual(t *testing.T) {
|
||||
st := newStore(t)
|
||||
ctx := context.Background()
|
||||
ss, _ := st.CreateSession(ctx, "")
|
||||
a := &Agent{LLM: &recorder{}, Store: st, Tools: tools.NewRegistry(), MaxIterations: 2, Summarize: SummarizeConfig{KeepRecent: 2}}
|
||||
if sm, err := a.SummarizeNow(ctx, ss.ID); err != nil || sm != nil {
|
||||
t.Fatalf("empty session: %v %v", sm, err)
|
||||
}
|
||||
a.Run(ctx, ss.ID, "eins", nil)
|
||||
a.Run(ctx, ss.ID, "zwei", nil)
|
||||
sm, err := a.SummarizeNow(ctx, ss.ID)
|
||||
if err != nil || sm == nil || sm.Content != "SUMMARY" {
|
||||
t.Fatalf("%v %v", sm, err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue