loopd: background ToolSummary for opted-in results
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -149,14 +149,22 @@ pub fn finish_turn(
|
||||
outcome: Result<TurnOutcome, TurnError>,
|
||||
) -> Result<TurnOutcome, TurnError> {
|
||||
match outcome {
|
||||
Err(TurnError::SessionFull) => match compact_session(session, rt, memory) {
|
||||
Ok(()) => run_turn(session, rt, text, speaker, on_event),
|
||||
Err(e) => {
|
||||
log_compact(session, &e);
|
||||
Err(TurnError::SessionFull)
|
||||
Err(TurnError::SessionFull) => {
|
||||
if let Err(e) = crate::summarise::missing(rt.cfg, rt.client, session) {
|
||||
eprintln!("loopd: tool summary: {e}");
|
||||
}
|
||||
},
|
||||
match compact_session(session, rt, memory) {
|
||||
Ok(()) => run_turn(session, rt, text, speaker, on_event),
|
||||
Err(e) => {
|
||||
log_compact(session, &e);
|
||||
Err(TurnError::SessionFull)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(done) => {
|
||||
if let Err(e) = crate::summarise::missing(rt.cfg, rt.client, session) {
|
||||
eprintln!("loopd: tool summary: {e}");
|
||||
}
|
||||
if let Some(usage) = session.last_usage()
|
||||
&& due(&usage, rt.cfg.compact.threshold, rt.cfg.expect.n_ctx)
|
||||
&& let Err(e) = compact_session(session, rt, memory)
|
||||
|
||||
@@ -15,5 +15,6 @@ pub mod selftest;
|
||||
pub mod session;
|
||||
pub mod socket;
|
||||
pub mod sse;
|
||||
pub mod summarise;
|
||||
pub mod tools;
|
||||
pub mod turn;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//! Opted-in tool-output summaries (M5d spec, section 4.3).
|
||||
|
||||
use crate::baseline::messages_with_carry;
|
||||
use crate::compact::cap_chars;
|
||||
use crate::config::Config;
|
||||
use crate::llama::{ChatMessage, ChatRequest, Client};
|
||||
use crate::session::Session;
|
||||
use bxcore::sanitize::neutralise;
|
||||
use bxcore::{CallId, DataClass, LogRecord, Timestamp, ToolCall};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// Append a `ToolSummary` for each opted-in `ToolResult` in this epoch that does not yet have
|
||||
/// one. Inference uses the background slot and no tools.
|
||||
pub fn missing(cfg: &Config, client: &Client, session: &mut Session) -> Result<(), String> {
|
||||
let path = cfg.baseline.system.with_file_name("summarise.md");
|
||||
let prompt = std::fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))?;
|
||||
let mut names: HashMap<String, String> = HashMap::new();
|
||||
let mut done: HashSet<u64> = HashSet::new();
|
||||
let mut pending: Vec<(CallId, String, String, DataClass, bool)> = Vec::new();
|
||||
for record in session.records() {
|
||||
match record {
|
||||
LogRecord::Assistant { tool_calls, .. } => {
|
||||
for ToolCall { id, name, .. } in tool_calls {
|
||||
names.insert(id.clone(), name.clone());
|
||||
}
|
||||
}
|
||||
LogRecord::ToolSummary { call, .. } => {
|
||||
done.insert(call.0);
|
||||
}
|
||||
LogRecord::ToolResult {
|
||||
call,
|
||||
tool_call_id,
|
||||
content,
|
||||
class,
|
||||
untrusted,
|
||||
..
|
||||
} => {
|
||||
let Some(name) = names.get(tool_call_id) else {
|
||||
continue;
|
||||
};
|
||||
if !cfg.compact.summarise.iter().any(|n| n == name) {
|
||||
continue;
|
||||
}
|
||||
pending.push((
|
||||
*call,
|
||||
tool_call_id.clone(),
|
||||
content.clone(),
|
||||
*class,
|
||||
*untrusted,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pending.retain(|(call, ..)| !done.contains(&call.0));
|
||||
for (call, tool_call_id, content, class, untrusted) in pending {
|
||||
let mut messages =
|
||||
messages_with_carry(session.baseline(), session.records(), session.carry());
|
||||
messages.push(ChatMessage::User {
|
||||
content: format!(
|
||||
"{}\n\n{}",
|
||||
prompt.trim_end(),
|
||||
cap_chars(&content, cfg.r#loop.tool_result_cap)
|
||||
),
|
||||
});
|
||||
let request = ChatRequest {
|
||||
slot: cfg.slots.background,
|
||||
messages,
|
||||
tools: Vec::new(),
|
||||
thinking: true,
|
||||
};
|
||||
let text = match client.chat_with_retry(&request, &mut |_| {}) {
|
||||
Ok(completion) => completion.content.unwrap_or_default(),
|
||||
Err(e) => {
|
||||
eprintln!("loopd: tool summary: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let text = cap_chars(neutralise(&text).as_ref(), cfg.compact.summary_chars);
|
||||
session
|
||||
.append(LogRecord::ToolSummary {
|
||||
time: Timestamp::now(),
|
||||
call,
|
||||
tool_call_id,
|
||||
content: text,
|
||||
class,
|
||||
untrusted,
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user