mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 15:38:12 +02:00
Lot 31 : slots du moteur pour le tour tenu par la façade
SIDE_SLOT et SLOT_PERSIST ne gardent plus la conversation au Go. La machinerie reste au Go (configuration, requêtes en vol, tampon du slot) ; la façade la consulte avant chaque requête au moteur local. Go : - /internal/v1/turn/slot (facade_slots.go) rend engineSlotFor et, pour une étape de tour, recharge d'abord l'état gardé de la discussion (slotPersistRestore). La sauvegarde à la bascule voit les étapes de la façade : /internal/v1/oai/served marque le slot (engineMarkMain). - facadeChatBlocker : SIDE_SLOT et SLOT_PERSIST retirés. Rust : - TurnHost::engine_slot : l'id_slot posé dans chaque requête (0 pour le fil de la discussion, 1 pour la vérification), le ticket « en vol » ouvert sur le second slot. - Cache KV plein en plein calcul (sideSlotPoolError) : rejoué deux fois tel quel, sans compaction, puis l'erreur. - Résumés de compaction comptés en vol comme postSummary : transcription sur le second slot, continuation sur le slot 0. - La passe de vérification est un travail annexe (engineSideJob) : le Go efface le slot à la fin s'il a servi (écart du lot 7 levé). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
14 files changed
+679
-58
No files matched your search
@@ -127,6 +127,11 @@ pub struct FakeGo {
|
||||
pub service_os_fail: Mutex<Option<(String, String)>>,
|
||||
/// /internal/v1/turn/perf reçus (corps entiers).
|
||||
pub perf_records: Mutex<Vec<serde_json::Value>>,
|
||||
/// /internal/v1/turn/slot : second slot en service (le fil de la
|
||||
/// discussion sur le 0, le reste sur le 1 ; sinon aucun) ; demandes
|
||||
/// reçues (« conv main »).
|
||||
pub turn_side_live: Mutex<bool>,
|
||||
pub turn_slots: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
impl FakeGo {
|
||||
@@ -666,10 +671,11 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
if let Some(conv) = v["main_conv"].as_str() {
|
||||
let side = if v["side"] == true { " side" } else { "" };
|
||||
st.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(format!("begin main_conv={conv}"));
|
||||
.push(format!("begin main_conv={conv}{side}"));
|
||||
} else {
|
||||
st.oai_log.lock().unwrap().push(format!(
|
||||
"begin read={} side={} post_v1={}",
|
||||
@@ -681,6 +687,21 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
}
|
||||
resp(200, r#"{"ticket":7}"#)
|
||||
}
|
||||
"/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();
|
||||
let main = v["main"] == true;
|
||||
st.turn_slots
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(format!("{} {main}", v["conv"].as_str().unwrap_or("")));
|
||||
let slot = match (*st.turn_side_live.lock().unwrap(), main) {
|
||||
(false, _) => -1,
|
||||
(true, true) => 0,
|
||||
(true, false) => 1,
|
||||
};
|
||||
resp(200, serde_json::json!({ "slot": slot }).to_string())
|
||||
}
|
||||
"/internal/v1/oai/served" => {
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
@@ -680,7 +680,7 @@ async fn tour_complet_par_le_go() {
|
||||
cfg: cfg.clone(),
|
||||
patterns: shared.patterns.clone(),
|
||||
port: 8080,
|
||||
perf: None,
|
||||
go: None,
|
||||
},
|
||||
compact: TurnCompact {
|
||||
cfg: CompactConfig::default(),
|
||||
@@ -693,6 +693,7 @@ async fn tour_complet_par_le_go() {
|
||||
},
|
||||
queued: Box::new(Vec::new),
|
||||
perf_kind: "main",
|
||||
side_slot: Default::default(),
|
||||
};
|
||||
let bash = Tool {
|
||||
kind: "function".into(),
|
||||
@@ -1056,7 +1057,7 @@ async fn hote_et_sonde_de_gabarit() {
|
||||
cfg,
|
||||
patterns: shared.patterns.clone(),
|
||||
port: 1,
|
||||
perf: None,
|
||||
go: None,
|
||||
},
|
||||
compact: TurnCompact {
|
||||
cfg: CompactConfig::default(),
|
||||
@@ -1069,6 +1070,7 @@ async fn hote_et_sonde_de_gabarit() {
|
||||
},
|
||||
queued: Box::new(Vec::new),
|
||||
perf_kind: "main",
|
||||
side_slot: Default::default(),
|
||||
}
|
||||
};
|
||||
let bash = Tool {
|
||||
@@ -1179,6 +1181,7 @@ async fn contrat_verifie_par_la_facade() {
|
||||
.await;
|
||||
let (svc, go, _stop, _t) = service(Some(&url)).await;
|
||||
*go.code.lock().unwrap() = true;
|
||||
*go.turn_side_live.lock().unwrap() = true;
|
||||
let id = svc.sessions.conv().id();
|
||||
go.set(
|
||||
"chat",
|
||||
@@ -1193,6 +1196,39 @@ async fn contrat_verifie_par_la_facade() {
|
||||
|
||||
let seen = seen.lock().unwrap().clone();
|
||||
assert_eq!(seen.len(), 3);
|
||||
// SIDE_SLOT (lot 31) : le tour sur le slot 0 (rechargement SLOT_PERSIST
|
||||
// demandé pour la discussion), la vérification sur le 1, comptée en vol
|
||||
// sur le second slot ; la passe est un travail annexe pour le Go.
|
||||
let slots: Vec<_> = seen.iter().map(|(_, b)| b["id_slot"].clone()).collect();
|
||||
assert_eq!(
|
||||
slots,
|
||||
[
|
||||
serde_json::json!(0),
|
||||
serde_json::json!(1),
|
||||
serde_json::json!(1)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
*go.turn_slots.lock().unwrap(),
|
||||
[format!("{id} true"), " false".into(), " false".into()]
|
||||
);
|
||||
let begins: Vec<String> = go
|
||||
.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|l| l.starts_with("begin"))
|
||||
.cloned()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
begins,
|
||||
[
|
||||
format!("begin main_conv={id}"),
|
||||
"begin main_conv= side".into(),
|
||||
"begin main_conv= side".into()
|
||||
]
|
||||
);
|
||||
assert_eq!(*go.side_jobs.lock().unwrap(), ["begin 1", "end 1 false"]);
|
||||
let v = &seen[1].1;
|
||||
assert_eq!(v["messages"][0]["content"], "VÉRIFICATEUR");
|
||||
assert_eq!(
|
||||
@@ -1855,7 +1891,7 @@ async fn parite_du_tour_complet() {
|
||||
async fn telemetrie_des_resumes() {
|
||||
use loki_rs::chat::compact::{Summarizer, TranscriptRequest};
|
||||
let (st, go, _stop, _t) = store().await;
|
||||
let (url, _seen) = fake_engine(vec![
|
||||
let (url, seen) = fake_engine(vec![
|
||||
serde_json::json!({
|
||||
"choices": [{"message": {"content": "résumé"}, "finish_reason": "stop"}],
|
||||
"usage": {"prompt_tokens": 3000, "completion_tokens": 200},
|
||||
@@ -1864,6 +1900,10 @@ async fn telemetrie_des_resumes() {
|
||||
.to_string(),
|
||||
r#"{"choices":[]}"#.into(),
|
||||
"pas du json".into(),
|
||||
serde_json::json!({
|
||||
"choices": [{"message": {"content": "suite"}, "finish_reason": "stop"}]
|
||||
})
|
||||
.to_string(),
|
||||
])
|
||||
.await;
|
||||
let s = EngineSummarizer {
|
||||
@@ -1877,8 +1917,9 @@ async fn telemetrie_des_resumes() {
|
||||
cfg: Default::default(),
|
||||
patterns: RetryPatterns::builtin(),
|
||||
port: 8080,
|
||||
perf: Some((st, "c9".into())),
|
||||
go: Some((st, "c9".into())),
|
||||
};
|
||||
*go.turn_side_live.lock().unwrap() = true;
|
||||
let req = || TranscriptRequest {
|
||||
system: "s".into(),
|
||||
transcript: "t".into(),
|
||||
@@ -1890,8 +1931,58 @@ async fn telemetrie_des_resumes() {
|
||||
);
|
||||
assert!(s.summarize_transcript(req()).await.is_err(), "réponse vide");
|
||||
assert!(s.summarize_transcript(req()).await.is_err(), "illisible");
|
||||
// Lot 31 : la transcription, prompt neuf, sur le second slot, comptée en
|
||||
// vol sur lui et rendue à la fin ; jamais de rechargement SLOT_PERSIST.
|
||||
let sent = seen.lock().unwrap().clone();
|
||||
assert!(sent.iter().all(|(_, b)| b["id_slot"] == 1), "{sent:?}");
|
||||
assert_eq!(
|
||||
*go.turn_slots.lock().unwrap(),
|
||||
[" false", " false", " false"]
|
||||
);
|
||||
until(|| {
|
||||
go.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|l| l.starts_with("end"))
|
||||
.count()
|
||||
== 3
|
||||
})
|
||||
.await;
|
||||
assert_eq!(
|
||||
go.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|l| *l == "begin main_conv= side")
|
||||
.count(),
|
||||
3
|
||||
);
|
||||
// En continuation, il prolonge le prompt de la discussion : le slot 0.
|
||||
use loki_rs::chat::compact::ContinuationRequest;
|
||||
let cont = s
|
||||
.summarize_continuation(ContinuationRequest {
|
||||
messages: vec![],
|
||||
tools: vec![],
|
||||
effort: String::new(),
|
||||
kwargs: Default::default(),
|
||||
max_tokens: 500,
|
||||
conv: "c9".into(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cont.content, "suite");
|
||||
assert_eq!(seen.lock().unwrap()[3].1["id_slot"], 0);
|
||||
assert_eq!(go.turn_slots.lock().unwrap()[3], " true");
|
||||
until(|| {
|
||||
go.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.contains(&"begin main_conv=".to_string())
|
||||
})
|
||||
.await;
|
||||
let perfs = go.perf_records.lock().unwrap().clone();
|
||||
assert_eq!(perfs.len(), 2, "la réponse illisible n'est pas rangée");
|
||||
assert_eq!(perfs.len(), 3, "la réponse illisible n'est pas rangée");
|
||||
assert_eq!(
|
||||
perfs[0],
|
||||
serde_json::json!({ "kind": "compact", "conv": "c9", "iter": 0, "complete": true,
|
||||
|
||||
Reference in new issue
Block a user