mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-21 22:18:13 +02:00
Repris d'AJEAN 0.15.1 → 0.15.7 (tool_results.go adapté : pas de chiffrement chez nous). VOIR PLUS Chaque résultat d'outil partait en entier dans le flux (jusqu'à 12 000 caractères) et dans le journal rejoué à chaque ouverture : un long fil d'agent en transportait des centaines. Le flux ne porte plus qu'un aperçu de 1 600 caractères, la taille réelle (le « ~N tok » de la bulle reste juste) et un id. Le résultat complet est rangé dans le bucket `toolres`, sous « <discussion>.<aléa> » — un id propre, pas le tool_call_id que certains parseurs recyclent. « voir plus » le charge au clic via /api/chat/tool-result et lève le plafond de hauteur du bloc ; il se range avec « copier » dans une barre au coin du résultat. Les résultats partent avec leur discussion (convDelete) ; les orphelins sont nettoyés toutes les 200 écritures. COUPES ANNONCÉES - Terminal : une sortie de plus de 8 000 caractères était coupée en silence ; le modèle croyait tout voir. Elle commence désormais par « [sortie tronquée : N caractères au total, seuls les 8000 derniers sont gardés] ». - MCP : l'amont transmet désormais la réponse en entier. On s'en approche sans renoncer au garde-fou (65k de contexte) : le plafond suit la fenêtre (CTX caractères, ≈ un quart de la fenêtre, jamais sous les 12 000 d'avant), et la coupe dit la taille réelle. Vérifié dans le navigateur avec un faux moteur qui appelle bash : aperçu de 1 600 caractères, « voir plus » charge les 8 099, mention de coupe en tête. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
package loki
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
// tool_results.go — résultats COMPLETS des outils, pour le bouton « voir plus ».
|
|
// Repris d'AJEAN 0.15.5 / 0.15.7, sans le chiffrement (Loki n'en a pas).
|
|
//
|
|
// Le flux n'envoie à l'UI qu'un aperçu (toolPreviewChars) ; le reste se charge
|
|
// au clic. Il ne peut pas être cherché dans conv.Messages : pendant un tour
|
|
// d'agent, les messages tool n'y sont versés qu'à la FIN du tour, et après un
|
|
// compactage les anciens ont disparu de la vue modèle. On garde donc chaque
|
|
// résultat coupé dans un bucket dédié, sous un id PROPRE (pas le tool_call_id,
|
|
// que certains parseurs réutilisent d'un tour à l'autre).
|
|
//
|
|
// Rangement PAR DISCUSSION : la clé est « <id de discussion>.<aléa> ». Les
|
|
// résultats vivent aussi longtemps que leur discussion (supprimés avec elle,
|
|
// voir deleteToolResultsFor) ; les orphelins sont nettoyés de temps en temps.
|
|
|
|
const (
|
|
bkToolRes = "toolres"
|
|
toolResPruneGap = 200 // nettoyage tenté toutes les N écritures
|
|
)
|
|
|
|
var toolResWrites atomic.Int64
|
|
|
|
func toolResID(sid string) string {
|
|
var b [12]byte
|
|
if _, err := rand.Read(b[:]); err != nil {
|
|
return ""
|
|
}
|
|
if sid = strings.TrimSpace(sid); sid == "" {
|
|
sid = "nosession"
|
|
}
|
|
return sid + "." + hex.EncodeToString(b[:])
|
|
}
|
|
|
|
// saveToolResult enregistre un résultat complet de la discussion ACTIVE et
|
|
// renvoie son id ("" en cas d'échec : l'appelant envoie alors le résultat
|
|
// entier dans le flux).
|
|
func saveToolResult(result string) string {
|
|
id := toolResID(getStr(bkChat, ckActive))
|
|
if id == "" || putBytes(bkToolRes, id, []byte(result)) != nil {
|
|
return ""
|
|
}
|
|
if toolResWrites.Add(1)%toolResPruneGap == 0 {
|
|
go pruneToolResults()
|
|
}
|
|
return id
|
|
}
|
|
|
|
// loadToolResult relit un résultat enregistré.
|
|
func loadToolResult(id string) (string, bool) {
|
|
if strings.ContainsAny(id, "/\\") {
|
|
return "", false
|
|
}
|
|
b, err := getBytesErr(bkToolRes, id)
|
|
if err != nil || b == nil {
|
|
return "", false
|
|
}
|
|
return string(b), true
|
|
}
|
|
|
|
// deleteToolResultsFor supprime les résultats d'une discussion supprimée.
|
|
func deleteToolResultsFor(sid string) {
|
|
if sid == "" {
|
|
return
|
|
}
|
|
prefix := sid + "."
|
|
_ = update(bkToolRes, func(b *bolt.Bucket) error {
|
|
c := b.Cursor()
|
|
var keys [][]byte
|
|
for k, _ := c.Seek([]byte(prefix)); k != nil && strings.HasPrefix(string(k), prefix); k, _ = c.Next() {
|
|
keys = append(keys, append([]byte(nil), k...))
|
|
}
|
|
for _, k := range keys {
|
|
if err := b.Delete(k); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// pruneToolResults supprime les résultats de discussions qui n'existent plus.
|
|
func pruneToolResults() {
|
|
alive := map[string]bool{getStr(bkChat, ckActive): true}
|
|
for _, m := range convIndex() {
|
|
alive[m.ID] = true
|
|
}
|
|
_ = update(bkToolRes, func(b *bolt.Bucket) error {
|
|
var orphans []string
|
|
_ = b.ForEach(func(k, _ []byte) error {
|
|
key := string(k)
|
|
if i := strings.LastIndexByte(key, '.'); i > 0 && key[:i] != "nosession" && !alive[key[:i]] {
|
|
orphans = append(orphans, key)
|
|
}
|
|
return nil
|
|
})
|
|
for _, k := range orphans {
|
|
if err := b.Delete([]byte(k)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|