M5c-2 plan: task 06, Honcho's results as recall items
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
//! Honcho's search results as `recall` items (M5c spec, section 8): kept only when they share a
|
||||
//! word with the query, one of each text per source, neutralised, on one line and capped like a
|
||||
//! local snippet, labelled by their source, and shown after the local index's hits up to `k`.
|
||||
//! Pure: no I/O.
|
||||
|
||||
use bxcore::DataClass;
|
||||
use bxcore::sanitize::neutralise;
|
||||
use roncho::{Conclusion, Message};
|
||||
|
||||
use crate::honcho::read::Label;
|
||||
use crate::index::class_name;
|
||||
use crate::recall::{SNIPPET, when};
|
||||
|
||||
/// One Honcho hit as `recall` shows it.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Item {
|
||||
/// `memory` or `memory-tainted`: its source's tag.
|
||||
pub tag: &'static str,
|
||||
/// Where it came from, after the tag in its header.
|
||||
pub from: String,
|
||||
pub class: DataClass,
|
||||
pub untrusted: bool,
|
||||
/// Neutralised, on one line, at most `SNIPPET` characters.
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
/// The item as the model reads it: a header in the local format, `[n] {tag} · {from} ·
|
||||
/// {class}`, then its text on one line indented by two spaces, so that no text can begin a
|
||||
/// line the way a header does.
|
||||
pub fn render(&self, n: usize) -> String {
|
||||
format!(
|
||||
"[{n}] {} · {} · {}\n {}",
|
||||
self.tag,
|
||||
self.from,
|
||||
class_name(self.class),
|
||||
self.text
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The words of `text`, lowercase: every run of alphanumeric characters (`char::is_alphanumeric`),
|
||||
/// as the local index's tokenizer splits them.
|
||||
pub fn words(text: &str) -> Vec<String> {
|
||||
text.split(|c: char| !c.is_alphanumeric())
|
||||
.filter(|w| !w.is_empty())
|
||||
.map(str::to_lowercase)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether `text` holds one of `query` (the query's words) as a whole word.
|
||||
pub fn relevant(query: &[String], text: &str) -> bool {
|
||||
words(text).iter().any(|w| query.contains(w))
|
||||
}
|
||||
|
||||
/// Text from Honcho as an item holds it: neutralised, every control character (a line break
|
||||
/// among them) and every Unicode line or paragraph separator a space, then cut to its first
|
||||
/// `SNIPPET` characters, as a local snippet is cut.
|
||||
pub fn clean(text: &str) -> String {
|
||||
neutralise(text)
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_control() || c == '\u{2028}' || c == '\u{2029}' {
|
||||
' '
|
||||
} else {
|
||||
c
|
||||
}
|
||||
})
|
||||
.take(SNIPPET)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `{name}: {peer} said, {when}` for a message.
|
||||
fn message_from(label: &Label, message: &Message) -> String {
|
||||
format!(
|
||||
"{}: {} said, {}",
|
||||
label.name,
|
||||
clean(&message.peer_id),
|
||||
when(&message.created_at)
|
||||
)
|
||||
}
|
||||
|
||||
/// `{name}: a conclusion about {peer}, {when}` for a conclusion.
|
||||
fn conclusion_from(label: &Label, conclusion: &Conclusion) -> String {
|
||||
format!(
|
||||
"{}: a conclusion about {}, {}",
|
||||
label.name,
|
||||
clean(&conclusion.observed_id),
|
||||
when(&conclusion.created_at)
|
||||
)
|
||||
}
|
||||
|
||||
/// One source's results as items, in Honcho's order: the messages, then the conclusions about
|
||||
/// the agent's peer, then those about the owner's (both empty for the fleet). A result is kept
|
||||
/// only when it holds a word of `query`, and only once: one whose cleaned text equals an earlier
|
||||
/// kept one's is dropped.
|
||||
pub fn items(
|
||||
label: &Label,
|
||||
query: &str,
|
||||
messages: &[Message],
|
||||
agent: &[Conclusion],
|
||||
owner: &[Conclusion],
|
||||
) -> Vec<Item> {
|
||||
// 1. `let query = words(query);`: the query's words.
|
||||
// 2. The results in order: each message as (its `content`, `message_from(label, m)`), then each
|
||||
// conclusion of `agent`, then each of `owner`, as (its `content`, `conclusion_from(label,
|
||||
// c)`).
|
||||
// 3. For each (content, from): skip it unless `relevant(&query, content)`; its text is
|
||||
// `clean(content)`; skip it when an item already kept has that same text; otherwise keep
|
||||
// Item { tag: label.tag(), from, class: label.class, untrusted: label.untrusted(), text }.
|
||||
// 4. The kept items, in that order.
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// The Honcho items `recall` shows after `local` hits of the local index, so that there are at
|
||||
/// most `k` in all: each source's first, in the order given, then each one's second, and so on.
|
||||
/// None when the local hits fill `k`.
|
||||
pub fn interleave(local: usize, k: usize, sources: Vec<Vec<Item>>) -> Vec<Item> {
|
||||
// `room` is `k.saturating_sub(local)`. Turn each source into an iterator (`into_iter`). Go
|
||||
// round the sources in order, taking the next item of each that has one, until `room` items
|
||||
// are taken or a whole round takes none. Return the items taken, in the order taken.
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// `items` rendered one after another, numbered from `first`, joined by newlines.
|
||||
pub fn render(first: usize, items: &[Item]) -> String {
|
||||
items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, item)| item.render(first + i))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/// The label the shown items give the answer: the highest of their classes, `private` at the
|
||||
/// least, and untrusted when any is. Only items shown count.
|
||||
pub fn label(items: &[Item]) -> (DataClass, bool) {
|
||||
// The class: the highest `class` of `items` (`DataClass::max`), starting from
|
||||
// `DataClass::Private`. Untrusted: any item's `untrusted` is true. Return (class, untrusted).
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// One `{name}: unavailable` line per source that did not answer, in the order given, joined by
|
||||
/// newlines; empty when every source answered.
|
||||
pub fn unavailable(names: &[&str]) -> String {
|
||||
names
|
||||
.iter()
|
||||
.map(|name| format!("{name}: unavailable"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
Reference in New Issue
Block a user