mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 23:18: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
@@ -0,0 +1,91 @@
|
||||
package loki
|
||||
|
||||
// chat_video_tool.go — l'outil see_video (repris d'AJEAN) : le modèle charge
|
||||
// lui-même un fichier vidéo du disque dans sa VISION, sans que l'utilisateur ait
|
||||
// à le joindre. Même mécanique que see_image (chat_vision_tool.go) : le résultat
|
||||
// de l'outil reste un accusé texte, la vidéo part juste après dans un message
|
||||
// utilisateur multimodal (partie input_video), que llama-server décode en
|
||||
// images avec ffmpeg. Toujours ÉPHÉMÈRE : jamais dans l'historique.
|
||||
//
|
||||
// Annoncé seulement quand le moteur sait lire la vidéo (videoInputSupported) ;
|
||||
// appelé quand même, il le dit avec la marche à suivre (ffmpeg puis see_image).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// toolSeeVideo lit un fichier vidéo et renvoie (accusé texte, partie input_video).
|
||||
// La partie vaut nil en cas d'erreur : l'appelant n'injecte alors rien. Même
|
||||
// ordre de refus que toolSeeImage, la sonde du moteur en dernier.
|
||||
func toolSeeVideo(path string) (string, map[string]any) {
|
||||
if !visionEnabled() {
|
||||
return "[erreur] la vision n'est pas active sur ce preset (aucun projecteur MMPROJ configuré) — impossible de voir une vidéo", nil
|
||||
}
|
||||
if agentTargetSlug() != "" {
|
||||
return "[erreur] voir une vidéo n'est pas possible sur un poste distant (le fichier est sur l'autre machine)", nil
|
||||
}
|
||||
if path == "" {
|
||||
return "[erreur] chemin de fichier manquant", nil
|
||||
}
|
||||
if !videoInputSupported() {
|
||||
return "[erreur] " + strings.TrimSuffix(videoFallbackNote, "\n"), nil
|
||||
}
|
||||
abs := resolveAgentPath(path)
|
||||
mime := videoMime(abs)
|
||||
if mime == "" {
|
||||
return "[erreur] format non reconnu comme vidéo (attendu : mp4, webm, mov, mkv, avi) : " + path, nil
|
||||
}
|
||||
st, err := os.Stat(abs)
|
||||
if err != nil {
|
||||
return "[erreur] fichier introuvable : " + path, nil
|
||||
}
|
||||
if st.IsDir() {
|
||||
return "[erreur] c'est un dossier, pas une vidéo : " + path, nil
|
||||
}
|
||||
if st.Size() > maxVideoBytes {
|
||||
return fmt.Sprintf("[erreur] vidéo trop lourde (%s, max %s) — coupe-la en un clip plus court : %s",
|
||||
humanBytes(st.Size()), humanBytes(maxVideoBytes), path), nil
|
||||
}
|
||||
if !engineSeesImages() {
|
||||
return "[erreur] le moteur en service ne traite pas les images (projecteur non chargé ?) — impossible de voir " + path, nil
|
||||
}
|
||||
b, err := os.ReadFile(abs)
|
||||
if err != nil {
|
||||
return "[erreur] lecture impossible : " + err.Error(), nil
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return "[erreur] fichier vide : " + path, nil
|
||||
}
|
||||
return "[ok] vidéo chargée : " + filepath.Base(abs), videoPart(b, mime)
|
||||
}
|
||||
|
||||
// seeVideoMessage emballe la partie vidéo dans le message utilisateur qui la
|
||||
// porte jusqu'au modèle ; la légende nomme le fichier (voir seeImageMessage).
|
||||
func seeVideoMessage(label string, vid map[string]any) Message {
|
||||
text := "Vidéo :"
|
||||
if label != "" {
|
||||
text = "Vidéo demandée (" + label + ") :"
|
||||
}
|
||||
return Message{Role: "user", Content: []map[string]any{
|
||||
{"type": "text", "text": text},
|
||||
vid,
|
||||
}}
|
||||
}
|
||||
|
||||
// seeVideoTool — schéma envoyé au modèle, tenu court comme seeImageTool.
|
||||
func seeVideoTool() Tool {
|
||||
return Tool{Type: "function", Function: ToolFunction{
|
||||
Name: "see_video",
|
||||
Description: "Ouvre un fichier vidéo du disque pour la REGARDER (mp4, webm, mov, mkv, avi). Clips courts.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"file": map[string]any{"type": "string", "description": "Chemin de la vidéo (relatif au dossier de travail, ou absolu)"},
|
||||
},
|
||||
"required": []string{"file"},
|
||||
},
|
||||
}}
|
||||
}
|
||||
Reference in new issue
Block a user