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
@@ -33,7 +33,7 @@ use super::compact::{
|
||||
use super::engine::{EngineHttp, classify, friendly_error};
|
||||
use super::persist::{Persister, StoreSink, ToolResJob};
|
||||
use super::stream::{CacheHintFn, PreflightFn, RetryPatterns};
|
||||
use super::turn::{CompactPhase, Reply, SendError, ToolOutcome, TurnHost};
|
||||
use super::turn::{CompactPhase, PerfMarks, PerfRec, Reply, SendError, ToolOutcome, TurnHost};
|
||||
use super::types::{AskEvent, DiffLine, Message, Tool};
|
||||
use super::wire::{
|
||||
ChatEndpoint, ChatPayloadOpts, EchoRefusals, EffortFallbacks, IMG_REF_SCHEME,
|
||||
@@ -212,8 +212,14 @@ impl EngineSummarizer {
|
||||
Err(_) => false,
|
||||
};
|
||||
if decoded && let Some((store, conv)) = &self.perf {
|
||||
let rec = super::perf::PerfRec::from_wire(super::perf::KIND_COMPACT, conv, &raw);
|
||||
super::perf::record(store, &rec, &[], self.ep.external).await;
|
||||
let (complete, stats) = super::perf::wire_stats(&raw);
|
||||
let rec = PerfRec {
|
||||
complete,
|
||||
external: self.ep.external,
|
||||
stats,
|
||||
..Default::default()
|
||||
};
|
||||
super::perf::record(store, super::perf::KIND_COMPACT, conv, &rec).await;
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -331,6 +337,65 @@ pub struct GoTurnHost {
|
||||
pub compact: TurnCompact,
|
||||
/// Messages mis en file par l'utilisateur pendant le tour.
|
||||
pub queued: Box<dyn Fn() -> Vec<Message> + Send + Sync>,
|
||||
/// Nature des complétions pour la télémétrie : `main` (tour de la
|
||||
/// discussion), `verify` (passe de vérification du mode Code).
|
||||
pub perf_kind: &'static str,
|
||||
}
|
||||
|
||||
/// Rend le ticket « en vol » au Go quand la réponse du moteur est lue ou
|
||||
/// abandonnée (`engineRequestBeginSide` … `end`).
|
||||
struct GateTicket {
|
||||
store: Store,
|
||||
ticket: u64,
|
||||
}
|
||||
|
||||
impl Drop for GateTicket {
|
||||
fn drop(&mut self) {
|
||||
let (store, ticket) = (self.store.clone(), self.ticket);
|
||||
if let Ok(rt) = tokio::runtime::Handle::try_current() {
|
||||
rt.spawn(async move {
|
||||
let _ = store
|
||||
.internal_json(
|
||||
hyper::Method::POST,
|
||||
"/internal/v1/oai/end",
|
||||
Some(&json!({ "ticket": ticket })),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GoTurnHost {
|
||||
/// Ouvre un ticket « en vol » auprès du Go : le slot de la discussion
|
||||
/// n'est pas effacé pendant la requête, et un préchauffage est annulé.
|
||||
/// `None` si le Go ne répond pas — la requête part quand même.
|
||||
async fn gate_begin(&self) -> Option<GateTicket> {
|
||||
let main_conv = if self.perf_kind == super::perf::KIND_MAIN {
|
||||
self.conv_id.as_str()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
match self
|
||||
.shared
|
||||
.store
|
||||
.internal_json(
|
||||
hyper::Method::POST,
|
||||
"/internal/v1/oai/begin",
|
||||
Some(&json!({ "main_conv": main_conv })),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v["ticket"].as_u64().map(|ticket| GateTicket {
|
||||
store: self.shared.store.clone(),
|
||||
ticket,
|
||||
}),
|
||||
Err(e) => {
|
||||
eprintln!("[loki-rs] moteur : requête non comptée en vol ({e})");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `toolResID` : « <discussion>.<24 hex> ».
|
||||
@@ -389,10 +454,35 @@ pub fn see_image_message(label: &str, img: Value) -> Message {
|
||||
|
||||
impl TurnHost for GoTurnHost {
|
||||
async fn send(&self, ep: &ChatEndpoint, body: Vec<u8>) -> Result<Reply, SendError> {
|
||||
// Moteur local : requête comptée en vol jusqu'à la fin de lecture du
|
||||
// flux (le ticket voyage avec le corps). Rien pour une API externe.
|
||||
let gate = if ep.external {
|
||||
None
|
||||
} else {
|
||||
self.gate_begin().await
|
||||
};
|
||||
match self.shared.http.post(&ep.url, &ep.key, body).await {
|
||||
Ok(resp) => {
|
||||
let status = resp.status().as_u16();
|
||||
let body = resp.bytes_stream().map(|r| r.map_err(|e| e.to_string()));
|
||||
if status == 200
|
||||
&& let Some(g) = &gate
|
||||
{
|
||||
// Le slot porte désormais cette requête (engineServed,
|
||||
// engineMarkMain pour une étape de la discussion).
|
||||
let _ = self
|
||||
.shared
|
||||
.store
|
||||
.internal_json(
|
||||
hyper::Method::POST,
|
||||
"/internal/v1/oai/served",
|
||||
Some(&json!({ "ticket": g.ticket })),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let body = resp.bytes_stream().map(move |r| {
|
||||
let _keep = &gate;
|
||||
r.map_err(|e| e.to_string())
|
||||
});
|
||||
Ok(Reply {
|
||||
status,
|
||||
body: Box::pin(body),
|
||||
@@ -408,17 +498,6 @@ impl TurnHost for GoTurnHost {
|
||||
}
|
||||
}
|
||||
|
||||
async fn perf(
|
||||
&self,
|
||||
rec: &super::perf::PerfRec,
|
||||
prefix: &[u64],
|
||||
external: bool,
|
||||
) -> super::perf::PerfNoted {
|
||||
let mut rec = rec.clone();
|
||||
rec.conv = self.conv_id.clone();
|
||||
super::perf::record(&self.shared.store, &rec, prefix, external).await
|
||||
}
|
||||
|
||||
async fn exec_tool(&self, name: &str, args: &Map<String, Value>, label: &str) -> ToolOutcome {
|
||||
// Outils fichiers et shell : ici (lot 7), l'état partagé restant au Go.
|
||||
if crate::tools::is_local(name) {
|
||||
@@ -571,6 +650,10 @@ impl TurnHost for GoTurnHost {
|
||||
None
|
||||
}
|
||||
|
||||
async fn perf_record(&self, rec: PerfRec) -> PerfMarks {
|
||||
super::perf::record(&self.shared.store, self.perf_kind, &self.conv_id, &rec).await
|
||||
}
|
||||
|
||||
fn preflight(&self) -> Option<PreflightFn> {
|
||||
None
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user