loopd: background ToolSummary for opted-in results
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
//! Tests for the opted-in tool-output summary. Do not edit.
|
||||
|
||||
mod support;
|
||||
|
||||
use bxcore::{CallId, DataClass, LogRecord, SessionId, ToolCall};
|
||||
use loopd::baseline::Baseline;
|
||||
use loopd::compact;
|
||||
use loopd::llama::Client;
|
||||
use loopd::memory::NoMemory;
|
||||
use loopd::session::Session;
|
||||
use loopd::summarise;
|
||||
use loopd::tools::Registry;
|
||||
use loopd::turn::{Runtime, run_turn};
|
||||
use support::{FakeServer, Home, Reply, ScriptedPort};
|
||||
|
||||
const CHAT: &str = "/v1/chat/completions";
|
||||
const SUMMARISE: &str = "Summarise this tool result.\n";
|
||||
|
||||
fn sse(content: &str) -> Reply {
|
||||
let content_json = serde_json::to_string(content).unwrap();
|
||||
let data1 = format!(
|
||||
r#"data: {{"choices":[{{"finish_reason":null,"index":0,"delta":{{"content":{content_json}}}}}],"id":"s","timings":{{"cache_n":0,"prompt_n":10,"predicted_n":1}}}}"#
|
||||
);
|
||||
let data2 = r#"data: {"choices":[{"finish_reason":"stop","index":0,"delta":{}}],"id":"s","timings":{"cache_n":0,"prompt_n":10,"predicted_n":1}}"#;
|
||||
let body = format!("{data1}\n\n{data2}\n\ndata: [DONE]\n\n");
|
||||
Reply::raw(format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
|
||||
body.len()
|
||||
))
|
||||
}
|
||||
|
||||
fn ts() -> bxcore::Timestamp {
|
||||
bxcore::Timestamp::parse("2026-09-18T08:00:00.000Z").unwrap()
|
||||
}
|
||||
|
||||
fn assistant(name: &str) -> LogRecord {
|
||||
LogRecord::Assistant {
|
||||
time: ts(),
|
||||
content: None,
|
||||
reasoning_content: None,
|
||||
tool_calls: vec![ToolCall {
|
||||
id: "call_f".to_string(),
|
||||
name: name.to_string(),
|
||||
arguments: "{}".to_string(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
fn result(untrusted: bool) -> LogRecord {
|
||||
LogRecord::ToolResult {
|
||||
time: ts(),
|
||||
call: CallId(1),
|
||||
tool_call_id: "call_f".to_string(),
|
||||
content: "the fetched page is about tea".to_string(),
|
||||
class: DataClass::Public,
|
||||
untrusted,
|
||||
truncated: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn session(home: &Home, cfg: &loopd::config::Config) -> Session {
|
||||
let registry = Registry::m2b();
|
||||
Session::create(
|
||||
&home.dir,
|
||||
SessionId::new("a").unwrap(),
|
||||
Baseline::assemble(cfg, ®istry).unwrap(),
|
||||
cfg.slots.main,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_opted_in_result_gets_a_summary_on_the_background_slot() {
|
||||
let home = Home::new();
|
||||
home.write("summarise.md", SUMMARISE);
|
||||
let server = FakeServer::start();
|
||||
let mut cfg = home.config(&server.socket);
|
||||
cfg.compact.summarise = vec!["http_fetch".to_string()];
|
||||
server.route(CHAT, vec![sse("tea page")]);
|
||||
let client = Client::new(cfg.clone());
|
||||
let mut s = session(&home, &cfg);
|
||||
s.append(assistant("http_fetch")).unwrap();
|
||||
s.append(result(false)).unwrap();
|
||||
summarise::missing(&cfg, &client, &mut s).unwrap();
|
||||
let sent = server.requests_to(CHAT);
|
||||
assert_eq!(sent.len(), 1);
|
||||
assert_eq!(sent[0].json()["id_slot"], 1);
|
||||
assert!(sent[0].json().get("tools").is_none());
|
||||
assert!(
|
||||
s.records().iter().any(|r| matches!(r, LogRecord::ToolSummary { content, call, untrusted, .. } if content == "tea page" && *call == CallId(1) && !*untrusted)),
|
||||
"{:?}",
|
||||
s.records()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_summary_copies_the_result_labels() {
|
||||
let home = Home::new();
|
||||
home.write("summarise.md", SUMMARISE);
|
||||
let server = FakeServer::start();
|
||||
let mut cfg = home.config(&server.socket);
|
||||
cfg.compact.summarise = vec!["http_fetch".to_string()];
|
||||
server.route(CHAT, vec![sse("x")]);
|
||||
let client = Client::new(cfg.clone());
|
||||
let mut s = session(&home, &cfg);
|
||||
s.append(assistant("http_fetch")).unwrap();
|
||||
s.append(result(true)).unwrap();
|
||||
summarise::missing(&cfg, &client, &mut s).unwrap();
|
||||
match s.records().last() {
|
||||
Some(LogRecord::ToolSummary {
|
||||
class,
|
||||
untrusted,
|
||||
tool_call_id,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(*class, DataClass::Public);
|
||||
assert!(*untrusted);
|
||||
assert_eq!(tool_call_id, "call_f");
|
||||
}
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tool_not_in_summarise_is_left_alone() {
|
||||
let home = Home::new();
|
||||
home.write("summarise.md", SUMMARISE);
|
||||
let server = FakeServer::start();
|
||||
let mut cfg = home.config(&server.socket);
|
||||
cfg.compact.summarise = vec!["http_fetch".to_string()];
|
||||
let client = Client::new(cfg.clone());
|
||||
let mut s = session(&home, &cfg);
|
||||
s.append(assistant("clock")).unwrap();
|
||||
s.append(result(false)).unwrap();
|
||||
summarise::missing(&cfg, &client, &mut s).unwrap();
|
||||
assert!(server.requests_to(CHAT).is_empty());
|
||||
assert!(
|
||||
!s.records()
|
||||
.iter()
|
||||
.any(|r| matches!(r, LogRecord::ToolSummary { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_result_that_already_has_a_summary_is_not_summarised_again() {
|
||||
let home = Home::new();
|
||||
home.write("summarise.md", SUMMARISE);
|
||||
let server = FakeServer::start();
|
||||
let mut cfg = home.config(&server.socket);
|
||||
cfg.compact.summarise = vec!["http_fetch".to_string()];
|
||||
server.route(CHAT, vec![sse("once")]);
|
||||
let client = Client::new(cfg.clone());
|
||||
let mut s = session(&home, &cfg);
|
||||
s.append(assistant("http_fetch")).unwrap();
|
||||
s.append(result(false)).unwrap();
|
||||
summarise::missing(&cfg, &client, &mut s).unwrap();
|
||||
summarise::missing(&cfg, &client, &mut s).unwrap();
|
||||
assert_eq!(server.requests_to(CHAT).len(), 1);
|
||||
assert_eq!(
|
||||
s.records()
|
||||
.iter()
|
||||
.filter(|r| matches!(r, LogRecord::ToolSummary { .. }))
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_stay_the_same_after_a_summary() {
|
||||
let home = Home::new();
|
||||
home.write("summarise.md", SUMMARISE);
|
||||
let server = FakeServer::start();
|
||||
let mut cfg = home.config(&server.socket);
|
||||
cfg.compact.summarise = vec!["http_fetch".to_string()];
|
||||
server.route(CHAT, vec![sse("tea page")]);
|
||||
let client = Client::new(cfg.clone());
|
||||
let mut s = session(&home, &cfg);
|
||||
s.append(assistant("http_fetch")).unwrap();
|
||||
s.append(result(false)).unwrap();
|
||||
let before = loopd::baseline::messages(s.baseline(), s.records());
|
||||
summarise::missing(&cfg, &client, &mut s).unwrap();
|
||||
let after = loopd::baseline::messages(s.baseline(), s.records());
|
||||
assert_eq!(before, after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_a_turn_an_opted_in_call_is_summarised() {
|
||||
let home = Home::new();
|
||||
home.write("summarise.md", SUMMARISE);
|
||||
home.write("compact.md", "Summarise.\n");
|
||||
let server = FakeServer::start();
|
||||
let mut cfg = home.config(&server.socket);
|
||||
cfg.compact.summarise = vec!["read_file".to_string()];
|
||||
server.route(
|
||||
CHAT,
|
||||
vec![
|
||||
Reply::fixture("tool_call"),
|
||||
Reply::fixture("plain"),
|
||||
sse("hosts file"),
|
||||
],
|
||||
);
|
||||
let client = Client::new(cfg.clone());
|
||||
let port = ScriptedPort::new(vec![support::ok_result("127.0.0.1 localhost\n")]);
|
||||
let registry = Registry::m2b();
|
||||
let mut s = Session::create(
|
||||
&home.dir,
|
||||
SessionId::new("a").unwrap(),
|
||||
Baseline::assemble(&cfg, ®istry).unwrap(),
|
||||
cfg.slots.main,
|
||||
)
|
||||
.unwrap();
|
||||
let rt = Runtime {
|
||||
cfg: &cfg,
|
||||
client: &client,
|
||||
port: &port,
|
||||
registry: ®istry,
|
||||
};
|
||||
let mut events = Vec::new();
|
||||
let outcome = run_turn(&mut s, &rt, "hosts?", None, &mut |e| events.push(e.clone()));
|
||||
compact::finish_turn(
|
||||
&mut s,
|
||||
&rt,
|
||||
&NoMemory,
|
||||
"hosts?",
|
||||
None,
|
||||
&mut |e| events.push(e.clone()),
|
||||
outcome,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
s.records()
|
||||
.iter()
|
||||
.any(|r| matches!(r, LogRecord::ToolSummary { .. })),
|
||||
"{:?}",
|
||||
s.records()
|
||||
);
|
||||
assert_eq!(
|
||||
server.requests_to(CHAT).last().unwrap().json()["id_slot"],
|
||||
1
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user