- compaction automatique du contexte LLM quand la taille atteint le seuil (contexte max - taille de réponse max), par résumé de l'historique ancien - filtre textuel (?q=) et pagination (20/page) de l'historique des conversations - correctif ORM : offset de pagination en base 0 - suppression d'une conversation : messages associés supprimés, session en cours annulée, popup de confirmation (ConfirmDialog) - titres de conversation nettoyés du markdown - styles des listes markdown, variante danger du bouton - go.mod/go.sum : dépendances go-rod et stealth manquantes du commit précédent
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
|
)
|
|
|
|
type fakeEngine struct{ summary string }
|
|
|
|
func (f *fakeEngine) ListProviderTypes() []domain.Item { return nil }
|
|
func (f *fakeEngine) ListModelsFromProvider(ctx context.Context, p *domain.Provider) ([]string, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeEngine) Stream(ctx context.Context, p *domain.Provider, m string, params *domain.LLMParams, msgs []*domain.Message) *domain.Message {
|
|
return &domain.Message{}
|
|
}
|
|
func (f *fakeEngine) Generate(ctx context.Context, p *domain.Provider, m string, msgs []*domain.Message) (string, error) {
|
|
return f.summary, nil
|
|
}
|
|
|
|
func mk(role, content string) *domain.Message {
|
|
return &domain.Message{Role: role, Content: content}
|
|
}
|
|
|
|
func TestEstimateTokens(t *testing.T) {
|
|
if got := estimateTokens(""); got != 0 {
|
|
t.Errorf("empty = %d", got)
|
|
}
|
|
if got := estimateTokens("abcd"); got != 1 {
|
|
t.Errorf("abcd = %d", got)
|
|
}
|
|
if got := estimateTokens("abcde"); got != 2 {
|
|
t.Errorf("abcde = %d", got)
|
|
}
|
|
}
|
|
|
|
func TestShouldCompactThreshold(t *testing.T) {
|
|
s := &ChatSession{
|
|
messages: []*domain.Message{mk(string(domain.RoleUser), strings.Repeat("a", 320))}, // 80 tokens
|
|
contextMaxTokens: 100,
|
|
maxResponseTokens: 20,
|
|
}
|
|
if !s.shouldCompact("system") { // 80 + 1 >= 100-20
|
|
t.Errorf("should compact at contextMax - maxResponse boundary")
|
|
}
|
|
s.maxResponseTokens = 40
|
|
s.contextMaxTokens = 130 // seuil 130-40=90 > 82 : pas de compaction
|
|
if s.shouldCompact("system") {
|
|
t.Errorf("should not compact below threshold")
|
|
}
|
|
}
|
|
|
|
func TestCompactContext(t *testing.T) {
|
|
s := &ChatSession{
|
|
provider: &domain.Provider{},
|
|
llmEngine: &fakeEngine{summary: "resume des echanges"},
|
|
contextMaxTokens: 100,
|
|
maxResponseTokens: 10,
|
|
messages: []*domain.Message{
|
|
mk(string(domain.RoleUser), "ancienne question 1"),
|
|
mk(string(domain.RoleAssistant), "ancienne reponse 1"),
|
|
mk(string(domain.RoleUser), "question courante"),
|
|
mk(string(domain.RoleAssistant), "reponse courante"),
|
|
},
|
|
}
|
|
|
|
s.compactContext(context.Background())
|
|
|
|
if len(s.messages) != 3 {
|
|
t.Fatalf("expected 3 messages after compaction, got %d", len(s.messages))
|
|
}
|
|
if !strings.Contains(s.messages[0].Content, "resume des echanges") {
|
|
t.Errorf("first message should be the summary, got %q", s.messages[0].Content)
|
|
}
|
|
if s.messages[0].Role != string(domain.RoleUser) {
|
|
t.Errorf("summary role = %s", s.messages[0].Role)
|
|
}
|
|
if s.messages[1].Content != "question courante" || s.messages[2].Content != "reponse courante" {
|
|
t.Errorf("tail not preserved: %v", s.messages)
|
|
}
|
|
}
|
|
|
|
func TestCompactContextNoUserCut(t *testing.T) {
|
|
s := &ChatSession{
|
|
messages: []*domain.Message{
|
|
mk(string(domain.RoleUser), "seule question"),
|
|
mk(string(domain.RoleAssistant), "reponse"),
|
|
},
|
|
}
|
|
s.compactContext(context.Background())
|
|
if len(s.messages) != 2 {
|
|
t.Errorf("nothing to compact, expected 2 messages, got %d", len(s.messages))
|
|
}
|
|
}
|