mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 07:28:13 +02:00
Vision : le modèle peut regarder des vidéos (outil see_video et vidéos jointes), repris d'AJEAN
Reprise des commits 91f7f977 (PR #100 de Trombo38) et 1dfac0cf d'AJEAN. Une vidéo jointe (mp4, webm, mov, mkv, avi) part au modèle en partie input_video, et l'outil see_video charge une vidéo du disque, mais seulement si le moteur actif sait la lire : preset llama.cpp local avec projecteur (MMPROJ) et binaire dont l'aide annonce --video-fps (videoInputSupported, aide relue quand le binaire change, chemin des bibliothèques passé à la seule commande pour ne pas figer binHelp). Strata et les API externes ignoraient la partie en silence : la vidéo reste alors un fichier, annoncé avec la marche à suivre (ffmpeg puis see_image), et see_video n'est pas proposé. llama.cpp a --video-fps depuis la PR #24318 (fin août 2026) ; l'image server-cuda officielle, base du conteneur, installe ffmpeg. La consigne de repli dit quoi faire si ffmpeg manque (installation hors conteneur). Adapté à Loki : - la vidéo de see_video est toujours éphémère (jamais dans l'historique, KEEP_TURN_IMAGES compris) ; même ordre de refus que see_image, sonde /props en dernier, refus sur un poste distant ; - une vidéo jointe est rangée par référence (loki-vid:, LOKI_HOME/chatvid, chiffrée comme le fil), parties recopiées et non mutées comme les images ; retirée d'un fil rouvert (stripImageParts, videoLostMarker) et effacée par le ménage des médias ; - marqueur de perte à la compaction, élision dans l'export JSON, relais par l'API interne des outils (vision_video). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a45f19831
commit
776de75dd0
12 files changed
+886
-38
No files matched your search
+30
-11
@@ -515,6 +515,12 @@ func EnabledTools(caps Caps) []Tool {
|
||||
// pas — il promettrait alors de regarder, puis se contredirait.
|
||||
if caps.Agent && visionEnabled() {
|
||||
tools = append(tools, seeImageTool())
|
||||
// Voir une vidéo : seulement si le moteur sait la lire (llama.cpp local
|
||||
// avec --video-fps, chat_video.go). Ailleurs, la vidéo resterait lettre
|
||||
// morte : Strata et les API externes ignorent la partie sans erreur.
|
||||
if videoInputSupported() {
|
||||
tools = append(tools, seeVideoTool())
|
||||
}
|
||||
}
|
||||
if caps.Agent && (caps.Internet || caps.Code) && screenshotAvailable() {
|
||||
tools = append(tools, webScreenshotTool())
|
||||
@@ -670,12 +676,12 @@ func fillToolResult(ev *ToolUsedEvent, result string) *ToolUsedEvent {
|
||||
// - leur résultat dépend de l'ÉTAT VIVANT de la page (un browser_snapshot sans
|
||||
// argument a forcément la même clé de dédup à chaque appel, alors que la page
|
||||
// a changé) — les dédupliquer fige l'IA sur un vieux cliché ;
|
||||
// - surtout, browser_screenshot et see_image portent leur IMAGE dans un message
|
||||
// - surtout, browser_screenshot et see_image (see_video : sa vidéo) portent leur IMAGE dans un message
|
||||
// à part (visionImg) que le chemin de dédup ne rejoue PAS : l'IA recevrait
|
||||
// « [déjà fait] » SANS l'image et tournerait en boucle.
|
||||
func dedupableTool(name string) bool {
|
||||
switch name {
|
||||
case "bash", "bash_bg", "bash_tail", "see_image":
|
||||
case "bash", "bash_bg", "bash_tail", "see_image", "see_video":
|
||||
return false
|
||||
}
|
||||
return !strings.HasPrefix(name, "browser_")
|
||||
@@ -1121,7 +1127,7 @@ func toolCallLabel(name string, args map[string]any) string {
|
||||
switch name {
|
||||
case "mem_search", "web_search", "web_images", "recall_search":
|
||||
label, _ = args["query"].(string)
|
||||
case "mem_read", "mem_add", "mem_edit", "edit", "write", "read", "git_diff", "see_image":
|
||||
case "mem_read", "mem_add", "mem_edit", "edit", "write", "read", "git_diff", "see_image", "see_video":
|
||||
label, _ = args["file"].(string)
|
||||
case "recall":
|
||||
label, _ = args["id"].(string)
|
||||
@@ -1221,9 +1227,10 @@ type chatPayloadOpts struct {
|
||||
func buildChatPayload(ep chatEndpoint, sent []Message, temperature float64, o chatPayloadOpts) map[string]any {
|
||||
payload := map[string]any{
|
||||
"model": ep.Model,
|
||||
// Les images de l'historique y sont rangées par référence
|
||||
// (chat_images.go) : on remet leurs octets juste avant l'envoi.
|
||||
"messages": expandImageRefs(sent),
|
||||
// Les images et vidéos de l'historique y sont rangées par référence
|
||||
// (chat_images.go, chat_video.go) : on remet leurs octets juste avant
|
||||
// l'envoi.
|
||||
"messages": expandVideoRefs(expandImageRefs(sent)),
|
||||
"stream": true,
|
||||
"temperature": temperature,
|
||||
// include_usage → chunk final avec `usage.prompt_tokens` = taille TOTALE
|
||||
@@ -2343,7 +2350,7 @@ func runChatLoop(ctx context.Context, kt *keepImages, messages []Message, tools
|
||||
var diffAdd, diffDel int // vrais totaux (diff est tronqué pour l'UI)
|
||||
// visionImg : partie image_url rendue par see_image, réinjectée après
|
||||
// le résultat de l'outil (un message `tool` ne porte que du texte).
|
||||
var visionImg map[string]any
|
||||
var visionImg, visionVideo map[string]any
|
||||
// Appel rigoureusement identique déjà exécuté dans ce tour : on ne le
|
||||
// rejoue pas. Les petits modèles réémettent volontiers deux fois la
|
||||
// même écriture ; la rejouer produisait une fausse erreur (« old
|
||||
@@ -2375,7 +2382,7 @@ func runChatLoop(ctx context.Context, kt *keepImages, messages []Message, tools
|
||||
} else {
|
||||
ex = execTool(ctx, tc.Function.Name, args, label, caps)
|
||||
}
|
||||
result, diff, diffAdd, diffDel, visionImg = ex.Result, ex.Diff, ex.Added, ex.Removed, ex.VisionImg
|
||||
result, diff, diffAdd, diffDel, visionImg, visionVideo = ex.Result, ex.Diff, ex.Added, ex.Removed, ex.VisionImg, ex.VisionVideo
|
||||
if ex.Ask != nil {
|
||||
cb(StreamEvent{Ask: ex.Ask})
|
||||
}
|
||||
@@ -2433,6 +2440,13 @@ func runChatLoop(ctx context.Context, kt *keepImages, messages []Message, tools
|
||||
if visionImg != nil {
|
||||
messages, extra = kt.relay(messages, extra, seeImageMessage(label, visionImg), stepStart)
|
||||
}
|
||||
// see_video a réussi : même relais, toujours ÉPHÉMÈRE — dans
|
||||
// `messages` seulement, jamais dans `extra`, KEEP_TURN_IMAGES
|
||||
// compris (une vidéo pèse des milliers de jetons visuels, et
|
||||
// kt.relay ne sait ranger que des images).
|
||||
if visionVideo != nil {
|
||||
messages = append(messages, seeVideoMessage(label, visionVideo))
|
||||
}
|
||||
}
|
||||
// Compaction EN COURS DE TOUR. Le seuil n'était testé qu'AU DÉBUT du tour :
|
||||
// une boucle d'outils peut à elle seule remplir la fenêtre (résultats
|
||||
@@ -2616,7 +2630,10 @@ type toolExecResult struct {
|
||||
Added int `json:"added,omitempty"`
|
||||
Removed int `json:"removed,omitempty"`
|
||||
VisionImg map[string]any `json:"vision_img,omitempty"`
|
||||
Ask *AskEvent `json:"ask,omitempty"`
|
||||
// VisionVideo : partie input_video rendue par see_video (chat_video_tool.go),
|
||||
// relayée au modèle comme l'image, mais toujours éphémère.
|
||||
VisionVideo map[string]any `json:"vision_video,omitempty"`
|
||||
Ask *AskEvent `json:"ask,omitempty"`
|
||||
}
|
||||
|
||||
// execToolHook : nil en service. Les tests de parité Go/Rust
|
||||
@@ -2631,11 +2648,13 @@ func execTool(ctx context.Context, name string, args map[string]any, label strin
|
||||
result := ""
|
||||
var diff []DiffLine
|
||||
var diffAdd, diffDel int
|
||||
var visionImg map[string]any
|
||||
var visionImg, visionVideo map[string]any
|
||||
var ask *AskEvent
|
||||
switch name {
|
||||
case "see_image":
|
||||
result, visionImg = toolSeeImage(label)
|
||||
case "see_video":
|
||||
result, visionVideo = toolSeeVideo(label)
|
||||
case "recall":
|
||||
result = toolRecall(args)
|
||||
case "recall_search":
|
||||
@@ -2820,5 +2839,5 @@ func execTool(ctx context.Context, name string, args map[string]any, label strin
|
||||
result = "[erreur] outil inconnu: " + name
|
||||
}
|
||||
}
|
||||
return toolExecResult{Result: result, Diff: diff, Added: diffAdd, Removed: diffDel, VisionImg: visionImg, Ask: ask}
|
||||
return toolExecResult{Result: result, Diff: diff, Added: diffAdd, Removed: diffDel, VisionImg: visionImg, VisionVideo: visionVideo, Ask: ask}
|
||||
}
|
||||
Reference in new issue
Block a user