mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 22:48:11 +02:00
Lot 32 : images gardées (KEEP_TURN_IMAGES) dans le tour de la façade
KEEP_TURN_IMAGES ne garde plus la conversation au Go. L'état du tour vit dans la façade ; les fichiers et la vision restent au Go. Go (facade_keepimg.go) : - /internal/v1/turn/keepimg : vision réellement active, budget des images gardées ; - /internal/v1/turn/keepimg/store : une image relayée rangée par référence (refRelayImage), relue à l'octet près. Rust : - chat/keepimg.rs : port de keepImages (image à l'essai, mesure par le moteur répartie au prorata des octets, budget, publication, fin de tour). - La boucle du tour : budget demandé pour le fil principal, image rangée par le Go, historique réécrit publié sans les relais non gardés ; les images gardées partent avant tout résumé (filet réactif, en cours de tour, fenêtre pleine). - Début et fin de tour : retirées si le contexte le demande ou si elles ne doivent plus partir (clé retirée, vision absente). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
17 files changed
+1435
-37
No files matched your search
@@ -132,6 +132,11 @@ pub struct FakeGo {
|
||||
/// reçues (« conv main »).
|
||||
pub turn_side_live: Mutex<bool>,
|
||||
pub turn_slots: Mutex<Vec<String>>,
|
||||
/// Message relayé par chaque outil exécuté ici (image d'une capture).
|
||||
pub tool_relay: Mutex<Option<serde_json::Value>>,
|
||||
/// /internal/v1/turn/keepimg : vision active ; rangements reçus.
|
||||
pub keep_vision: Mutex<bool>,
|
||||
pub keep_stores: Mutex<Vec<serde_json::Value>>,
|
||||
}
|
||||
|
||||
impl FakeGo {
|
||||
@@ -648,11 +653,14 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
let result = by_name
|
||||
.or_else(|| st.tool_result.lock().unwrap().clone())
|
||||
.unwrap_or_else(|| format!("exec:{}", v["name"].as_str().unwrap()));
|
||||
let out = serde_json::json!({
|
||||
let mut out = serde_json::json!({
|
||||
"label": v["args"]["command"],
|
||||
"result": result,
|
||||
"caps_agent": v["caps"]["Agent"],
|
||||
});
|
||||
if let Some(r) = st.tool_relay.lock().unwrap().clone() {
|
||||
out["relay"] = r;
|
||||
}
|
||||
resp(200, out.to_string())
|
||||
}
|
||||
"/internal/v1/oai/route" => {
|
||||
@@ -687,6 +695,24 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
}
|
||||
resp(200, r#"{"ticket":7}"#)
|
||||
}
|
||||
"/internal/v1/turn/keepimg" => {
|
||||
let v = serde_json::json!({
|
||||
"vision": *st.keep_vision.lock().unwrap(),
|
||||
"budget": 3276,
|
||||
});
|
||||
resp(200, v.to_string())
|
||||
}
|
||||
"/internal/v1/turn/keepimg/store" => {
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
let mut msg = v["msg"].clone();
|
||||
st.keep_stores.lock().unwrap().push(msg.clone());
|
||||
msg["content"][1]["image_url"]["url"] = serde_json::json!("loki-img:k1.png");
|
||||
resp(
|
||||
200,
|
||||
serde_json::json!({ "ok": true, "msg": msg, "weight": 4 }).to_string(),
|
||||
)
|
||||
}
|
||||
"/internal/v1/turn/slot" => {
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
@@ -1992,3 +1992,93 @@ async fn telemetrie_des_resumes() {
|
||||
);
|
||||
assert_eq!(perfs[1]["complete"], false);
|
||||
}
|
||||
|
||||
/// Lot 32 : KEEP_TURN_IMAGES tenu par la façade. L'image relayée par un
|
||||
/// outil est rangée par le Go, gardée au coût mesuré par le moteur ; au tour
|
||||
/// suivant, clé retirée, elle part d'abord (légende et marque gardées).
|
||||
#[tokio::test]
|
||||
async fn images_gardees_par_la_facade() {
|
||||
use loki_rs::chat::compact::{IMAGE_LOST_MARKER, msg_tokens};
|
||||
use loki_rs::chat::types::Message;
|
||||
let tool = Message {
|
||||
role: "tool".into(),
|
||||
tool_call_id: "c1".into(),
|
||||
content: Some(serde_json::json!("exec:bash")),
|
||||
..Default::default()
|
||||
};
|
||||
let prompt2 = 1020 + msg_tokens(&tool) + 300;
|
||||
let (url, seen) = fake_engine(vec![
|
||||
sse(&[
|
||||
serde_json::json!({"choices":[{"delta":{"tool_calls":[{"index":0,"id":"c1","type":"function","function":{"name":"bash","arguments":"{\"command\":\"ls\"}"}}]}}]}),
|
||||
serde_json::json!({"choices":[{"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":1000,"completion_tokens":20}}),
|
||||
]),
|
||||
sse(&[
|
||||
serde_json::json!({"choices":[{"delta":{"content": "vu"}}]}),
|
||||
serde_json::json!({"choices":[{"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":prompt2,"completion_tokens":5}}),
|
||||
]),
|
||||
answer("ensuite"),
|
||||
])
|
||||
.await;
|
||||
let (svc, go, _stop, t) = service(Some(&url)).await;
|
||||
// Le fichier que le Go range (ici, le faux ne l'écrit pas).
|
||||
std::fs::create_dir_all(t.path().join("chatimg")).unwrap();
|
||||
std::fs::write(t.path().join("chatimg/k1.png"), b"\x89PNG\r\n").unwrap();
|
||||
go.set("config", "KEEP_TURN_IMAGES", "on");
|
||||
*go.keep_vision.lock().unwrap() = true;
|
||||
*go.tool_relay.lock().unwrap() = Some(serde_json::json!({
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Voici la capture demandée."},
|
||||
{"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0K"}}
|
||||
]
|
||||
}));
|
||||
svc.send(serde_json::json!({"message": "capture", "cid": "k1"}))
|
||||
.await
|
||||
.unwrap();
|
||||
until_done(&svc, 1).await;
|
||||
until(|| go.turn_log.lock().unwrap().contains(&"done".to_string())).await;
|
||||
// Rangée par le Go, vue en data-URL par le moteur.
|
||||
assert_eq!(go.keep_stores.lock().unwrap().len(), 1);
|
||||
let sent = seen.lock().unwrap()[1].1.clone();
|
||||
let relay = &sent["messages"].as_array().unwrap()[4];
|
||||
assert_eq!(
|
||||
relay["content"][1]["image_url"]["url"],
|
||||
"data:image/png;base64,iVBORw0K"
|
||||
);
|
||||
assert!(relay.get("img_relay").is_none(), "marque envoyée au moteur");
|
||||
let msgs = svc.sessions.conv().with_state(|st| st.messages.clone());
|
||||
let kept = msgs.iter().find(|m| m.img_relay).expect("relais gardé");
|
||||
assert_eq!(kept.img_tokens, 300);
|
||||
assert_eq!(
|
||||
kept.content.as_ref().unwrap()[1]["image_url"]["url"],
|
||||
"loki-img:k1.png"
|
||||
);
|
||||
|
||||
// Clé retirée : l'image gardée part au début du tour suivant.
|
||||
go.set("config", "KEEP_TURN_IMAGES", "off");
|
||||
svc.send(serde_json::json!({"message": "et ensuite ?", "cid": "k2"}))
|
||||
.await
|
||||
.unwrap();
|
||||
until_done(&svc, 2).await;
|
||||
let msgs = svc.sessions.conv().with_state(|st| st.messages.clone());
|
||||
let stub = msgs
|
||||
.iter()
|
||||
.find(|m| m.img_relay)
|
||||
.expect("relais gardé en légende");
|
||||
assert_eq!(stub.img_tokens, 0);
|
||||
assert_eq!(
|
||||
stub.content,
|
||||
Some(serde_json::json!(format!(
|
||||
"Voici la capture demandée.{IMAGE_LOST_MARKER}"
|
||||
)))
|
||||
);
|
||||
let sent = seen.lock().unwrap()[2].1.clone();
|
||||
assert!(
|
||||
sent["messages"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.all(|m| !m["content"].to_string().contains("image_url")),
|
||||
"image encore envoyée : {sent}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user