mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 07:28:13 +02:00
Fusion de main : conversation Rust par défaut, télémétrie du tour
main a reçu en parallèle la télémétrie du tour tenu par la façade (/internal/v1/turn/perf), les tickets « en vol » du moteur et la conversation tenue par la façade par défaut (LOKI_CHAT=go pour revenir au Go). Le lot 29 faisait la même chose par /internal/v1/perf/record : un seul chemin est gardé, celui de main. - TurnHost::perf_record (main) remplace TurnHost::perf ; la nature est posée par l'hôte (verify pour la passe isolée seulement, main pour les corrections du builder, comme le Go). - Les résumés de compaction passent par le même point d'entrée, nature compact : chat/perf.rs ne garde que le relevé de la réponse entière (perfRecFromWire) et l'envoi. - La sonde du gabarit reste à la façade (lot 23) : /internal/v1/turn/perf ne la lance plus côté Go. - facade_perf.go retiré ; ses cas (perte, intercalé, alerte, preset externe, incomplète, GET) rejoignent web_internal_turnperf_test.go. - README : LOKI_CHAT défaut Rust, avec ce que les lots 19, 23, 29 et 30 y ajoutent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
20 files changed
+713
-457
No files matched your search
@@ -125,7 +125,7 @@ pub struct FakeGo {
|
||||
/// pour une action donnée.
|
||||
pub service_os: Mutex<Vec<String>>,
|
||||
pub service_os_fail: Mutex<Option<(String, String)>>,
|
||||
/// /internal/v1/perf/record reçus (corps entiers).
|
||||
/// /internal/v1/turn/perf reçus (corps entiers).
|
||||
pub perf_records: Mutex<Vec<serde_json::Value>>,
|
||||
}
|
||||
|
||||
@@ -502,14 +502,6 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
}
|
||||
resp(200, r#"{"ran":true,"error":""}"#)
|
||||
}
|
||||
"/internal/v1/perf/record" => {
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap_or_default();
|
||||
let mut log = st.perf_records.lock().unwrap();
|
||||
log.push(v);
|
||||
let out = serde_json::json!({"seq": log.len(), "lost": 12, "lost_after": "task", "alert": false});
|
||||
resp(200, out.to_string())
|
||||
}
|
||||
"/internal/v1/tune/context" => {
|
||||
let v = serde_json::json!({
|
||||
"busy": *st.tune_ctx_busy.lock().unwrap(),
|
||||
@@ -673,15 +665,51 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
"/internal/v1/oai/begin" => {
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
st.oai_log.lock().unwrap().push(format!(
|
||||
"begin read={} side={} post_v1={}",
|
||||
v["read"], v["side"], v["post_v1"]
|
||||
));
|
||||
if let Some(conv) = v["main_conv"].as_str() {
|
||||
st.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(format!("begin main_conv={conv}"));
|
||||
} else {
|
||||
st.oai_log.lock().unwrap().push(format!(
|
||||
"begin read={} side={} post_v1={}",
|
||||
v["read"], v["side"], v["post_v1"]
|
||||
));
|
||||
}
|
||||
if let Some(r) = st.refuse.lock().unwrap().clone() {
|
||||
return resp(200, serde_json::json!({ "refuse": r }).to_string());
|
||||
}
|
||||
resp(200, r#"{"ticket":7}"#)
|
||||
}
|
||||
"/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();
|
||||
st.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(format!("served {}", v["ticket"]));
|
||||
resp(204, "")
|
||||
}
|
||||
"/internal/v1/turn/perf" => {
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
st.oai_log.lock().unwrap().push(format!(
|
||||
"perf kind={} conv={} iter={} complete={} prefix={}",
|
||||
v["kind"].as_str().unwrap_or(""),
|
||||
v["conv"].as_str().unwrap_or(""),
|
||||
v["iter"],
|
||||
v["complete"],
|
||||
v["prefix"].as_array().map_or(0, Vec::len),
|
||||
));
|
||||
// Comme le Go : la nature reçue (un tour sans nature).
|
||||
let kind = v["kind"]
|
||||
.as_str()
|
||||
.filter(|k| !k.is_empty())
|
||||
.unwrap_or("main");
|
||||
let out = serde_json::json!({"kind": kind, "lost": 3, "lost_after": "tâche", "lost_alert": true});
|
||||
st.perf_records.lock().unwrap().push(v);
|
||||
resp(200, out.to_string())
|
||||
}
|
||||
"/internal/v1/oai/end" => {
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
@@ -692,6 +692,7 @@ async fn tour_complet_par_le_go() {
|
||||
kwargs: Default::default(),
|
||||
},
|
||||
queued: Box::new(Vec::new),
|
||||
perf_kind: "main",
|
||||
};
|
||||
let bash = Tool {
|
||||
kind: "function".into(),
|
||||
@@ -711,7 +712,6 @@ async fn tour_complet_par_le_go() {
|
||||
echo: Default::default(),
|
||||
compact: CompactConfig::default(),
|
||||
patterns: shared.patterns.clone(),
|
||||
perf_kind: "main",
|
||||
};
|
||||
let question = Message {
|
||||
role: "user".into(),
|
||||
@@ -767,15 +767,15 @@ async fn tour_complet_par_le_go() {
|
||||
// discussion ; la première, sans usage, reste incomplète.
|
||||
let perfs = go.perf_records.lock().unwrap().clone();
|
||||
assert_eq!(perfs.len(), 2);
|
||||
assert_eq!(perfs[0]["rec"]["complete"], false);
|
||||
assert_eq!(perfs[0]["complete"], false);
|
||||
assert_eq!(perfs[0]["prefix"], serde_json::json!([]));
|
||||
let r1 = &perfs[1]["rec"];
|
||||
let r1 = &perfs[1];
|
||||
assert_eq!(
|
||||
(
|
||||
r1["conv"].clone(),
|
||||
r1["kind"].clone(),
|
||||
r1["iter"].clone(),
|
||||
r1["total"].clone()
|
||||
r1["stats"]["prompt_tokens_total"].clone()
|
||||
),
|
||||
(
|
||||
serde_json::json!("c7"),
|
||||
@@ -794,7 +794,10 @@ async fn tour_complet_par_le_go() {
|
||||
_ => None,
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!((last.lost, last.lost_after.as_str()), (Some(12), "task"));
|
||||
assert_eq!(
|
||||
(last.kind.as_str(), last.lost, last.lost_after.as_str()),
|
||||
("main", Some(3), "tâche")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -942,6 +945,47 @@ async fn tour_de_bout_en_bout() {
|
||||
// Bail rendu, fin signalée, discussion enregistrée.
|
||||
assert_eq!(*go.lease.lock().unwrap(), None);
|
||||
let id = svc.sessions.conv().id();
|
||||
// Chaque requête au moteur local est comptée en vol par le Go (ticket
|
||||
// ouvert, servi, rendu), et sa complétion rangée dans la télémétrie ;
|
||||
// la copie finale des stats porte ce que le Go en dit.
|
||||
until(|| {
|
||||
go.oai_log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|l| l.starts_with("end"))
|
||||
.count()
|
||||
== 2
|
||||
})
|
||||
.await;
|
||||
let log = go.oai_log.lock().unwrap().clone();
|
||||
let want_begin = format!("begin main_conv={id}");
|
||||
assert_eq!(
|
||||
log.iter().filter(|l| **l == want_begin).count(),
|
||||
2,
|
||||
"{log:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
log.iter().filter(|l| *l == "served 7").count(),
|
||||
2,
|
||||
"{log:?}"
|
||||
);
|
||||
// Appel d'outil sans usage ni timings : complétion incomplète (comme le
|
||||
// Go : ni empreintes) ; la réponse finale, avec usage, complète. La
|
||||
// sonde du gabarit reste à la façade (`sonde_de_gabarit_apres_un_tour`).
|
||||
for want in [
|
||||
format!("perf kind=main conv={id} iter=0 complete=false prefix=0"),
|
||||
format!("perf kind=main conv={id} iter=1 complete=true prefix=4"),
|
||||
] {
|
||||
assert!(log.contains(&want), "{want} absent de {log:?}");
|
||||
}
|
||||
let finals: Vec<_> = deltas(&svc, "stats")
|
||||
.into_iter()
|
||||
.filter(|s| s["lost_alert"] == true)
|
||||
.collect();
|
||||
assert_eq!(finals.len(), 1, "stats finales sans les marques du Go");
|
||||
assert_eq!(finals[0]["lost"], 3);
|
||||
assert_eq!(finals[0]["kind"], "main");
|
||||
let body = opened(&go, BK_CHAT, &format!("conv:{id}")).unwrap();
|
||||
assert_eq!(decode_state(&body).messages.len(), 4);
|
||||
// Réessai du même envoi : reconnu.
|
||||
@@ -1024,6 +1068,7 @@ async fn hote_et_sonde_de_gabarit() {
|
||||
kwargs: Default::default(),
|
||||
},
|
||||
queued: Box::new(Vec::new),
|
||||
perf_kind: "main",
|
||||
}
|
||||
};
|
||||
let bash = Tool {
|
||||
@@ -1848,10 +1893,11 @@ async fn telemetrie_des_resumes() {
|
||||
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[0]["rec"],
|
||||
perfs[0],
|
||||
serde_json::json!({ "kind": "compact", "conv": "c9", "iter": 0, "complete": true,
|
||||
"total": 3000, "new": 3000, "pp_ms": 1500.0, "gen": 200, "tg_tps": 40.0 })
|
||||
"external": false, "prefix": [],
|
||||
"stats": { "prompt_tokens_total": 3000, "prompt_tokens": 3000, "prompt_ms": 1500.0,
|
||||
"gen_tokens": 200, "gen_per_second": 40.0 } })
|
||||
);
|
||||
assert_eq!(perfs[0]["prefix"], serde_json::json!([]));
|
||||
assert_eq!(perfs[1]["rec"]["complete"], false);
|
||||
assert_eq!(perfs[1]["complete"], false);
|
||||
}
|
||||
@@ -195,7 +195,6 @@ async fn replay(fx: Fixture) -> Result<(), String> {
|
||||
echo,
|
||||
patterns: RetryPatterns::builtin(),
|
||||
cfg,
|
||||
perf_kind: "main",
|
||||
};
|
||||
let host = ParityHost {
|
||||
replies: Mutex::new(fx.replies.into()),
|
||||
|
||||
Reference in new issue
Block a user