Original corpus for a non-memorised target; recall probes every item; encode prompt states the id form #40

Merged
weksa merged 3 commits from weksa-lowering/encode-id-form into main 2026-11-10 14:24:17 +00:00
9 changed files with 185 additions and 33 deletions

View file

@ -59,7 +59,7 @@ Fields:
- predications: a list of {id, concept, frame, roles, features}; roles is a list of {role, filler}, filler a node id.
- relations: a list of {relation, from, to, features}.
- stance: {features}; the speaker's felt affect and restraint, never the affect the line displays.
Node ids are n1, n2, and so on, one per referent and predication. Every other id comes from the inventory listed below and must be live at its revision. An entity comes only from the entities line.
Node ids are n1, n2, and so on, one per referent and predication. Every other id comes from the inventory listed below and must be live at its revision. Write each id in full, exactly as the inventory lines spell it, family prefix included: concept:7, never 7. An entity comes only from the entities line.
A feature is {feature, value}; value is {\"Grade\": 0 to 1000}, {\"Option\": label} or {\"Flag\": true or false}, as the inventory lists the feature's kind.";
/// The instructions a lowering prompt starts with: the lowerer's whole brief.
@ -71,10 +71,10 @@ A referent with an entity is named by one of that entity's forms on the entities
Answer with one JSON object and nothing else: {\"text\": \"...\"}. No code fence, no commentary.";
/// The instructions of a recall probe (`lowering-recall-diagnostic`): a
/// completion of a public-domain passage from its opening, asked of a model
/// that has no packet and no inventory. It measures recitation, not lowering.
/// completion of a passage from its opening, asked of a model that has no
/// packet and no inventory. It measures recitation, not lowering.
pub const RECALL_INSTRUCTIONS: &str = "\
Below is the opening of a passage from a published public-domain translation. Continue it exactly as that translation words it, from memory, until the passage ends. If you do not remember it, give your best guess in the same voice.
Below is the opening of a passage, with what is known of where it comes from. Continue it exactly as its source words it, from memory, until the passage ends. If you do not remember it, give your best guess in the same voice.
Answer with one JSON object and nothing else: {\"text\": \"...\"}, the continuation only, not the opening. No code fence, no commentary.";
/// The live Train items of a corpus, in key order. The only input minting
@ -547,13 +547,13 @@ impl Prompt {
})
}
/// A recall probe: the work, author and translator of a public-domain item
/// and the `opening` of its text, nothing else. Only `RunStores::recall`
/// builds one.
pub(crate) fn recall(key: &str, work: &str, author: &str, translator: &str, opening: &str) -> Self {
/// A recall probe: `provenance` (header lines saying where the item is
/// published, or that it is not) and the `opening` of its text, nothing
/// else. Only `RunStores::recall` builds one.
pub(crate) fn recall(key: &str, provenance: &str, opening: &str) -> Self {
Prompt {
text: format!(
"{RECALL_INSTRUCTIONS}\n\nwork: {work}\nauthor: {author}\ntranslator: {translator}\n\nopening: {}",
"{RECALL_INSTRUCTIONS}\n\n{provenance}\n\nopening: {}",
indented(opening)
),
items: vec![key.to_string()],

View file

@ -103,8 +103,8 @@ enum LoopCommand {
#[arg(long, default_value = "http://127.0.0.1:11434")]
ollama_url: String,
},
/// Probe how closely a fresh model recites the run's public-domain items
/// from their openings (chrF of its completion against the item's second
/// Probe how closely a fresh model recites the run's items from their
/// openings (chrF of its completion against the item's second
/// half). Prompts and answers live in `<ROOT>/recall`; the command exits 3
/// until every prompt has an answer. A diagnostic, never a gate.
Recall { root: PathBuf },

View file

@ -803,13 +803,13 @@ pub enum RoundOutcome {
}
/// A recall reading (`lowering-recall-diagnostic`): how closely a fresh model
/// completes each public-domain item from its opening alone, chrF against the
/// completes each live Train and Validation item from its opening alone, chrF against the
/// item's own second half. Recorded nowhere: the answers stay in `<root>/recall`.
/// It is never a gate; a high reading says a round's scores on these items are
/// recitation.
#[derive(Clone, Debug, PartialEq)]
pub struct RecallReading {
/// Public-domain items probed, Train and Validation.
/// Items probed, Train and Validation.
pub n: u32,
/// Items whose completion was a usable answer; a refused one counts 0.
pub answered: u32,
@ -819,7 +819,7 @@ pub struct RecallReading {
impl RecallReading {
pub fn line(&self) -> String {
format!(
"recall n={} answered={} chrf={:.2} (public-domain items, completion from the opening; a high chrf means round scores on them are recitation)",
"recall n={} answered={} chrf={:.2} (completion from the opening; unrelated English already scores chrf 14 to 20, so a reading near that floor is no recitation and one well above it is)",
self.n, self.answered, self.chrf
)
}
@ -843,26 +843,27 @@ fn recall_split(text: &str) -> Option<(String, String)> {
}
impl RunStores {
/// Probes every live public-domain Train and Validation item of the pinned
/// corpus once, through `model`.
/// Probes every live Train and Validation item of the pinned corpus once,
/// through `model`. A public-domain item names its work, author and
/// translator; any other says `work: not published`.
pub fn recall(&self, model: &mut dyn ModelPort) -> Result<RecallOutcome, RunError> {
let store = |_| RunError::Store;
let mut probes = Vec::new();
for item in self.corpus.all().map_err(store)? {
let Source::PublicDomain {
work,
author,
translator,
..
} = &item.source
else {
continue;
let provenance = match &item.source {
Source::PublicDomain {
work,
author,
translator,
..
} => format!("work: {work}\nauthor: {author}\ntranslator: {translator}"),
_ => "work: not published".to_string(),
};
if EncoderOffer::for_round(&item).is_err() {
continue;
}
if let Some((opening, rest)) = recall_split(&item.text) {
let prompt = Prompt::recall(&item.id, work, author, translator, &opening);
let prompt = Prompt::recall(&item.id, &provenance, &opening);
probes.push((prompt, rest));
}
}

View file

@ -686,3 +686,32 @@ mod lowering {
}
}
}
/// First real rounds: five of six encoders wrote bare integers (`"concept": 7`)
/// where the inventory lists `concept:7`, and the draft reader refused them.
/// The prompt says the form outright, and the inventory lines it points to
/// are spelled in that form.
#[test]
fn the_encode_prompt_states_the_id_form_the_inventory_lines_use() {
assert!(ENCODE.contains("family prefix included: concept:7, never 7"));
let w = world();
let run = run_over(w.dir.path());
let inv = run.inventory().inventory().unwrap();
let t = run.train().unwrap();
let offer = run.offer("item:nibu:1").unwrap().unwrap();
let text = Prompt::encode(&offer, &t, &inv, 4).text().to_string();
let listed: Vec<&str> = text
.split_once("inventory rev:4\n")
.unwrap()
.1
.split("\n\n")
.next()
.unwrap()
.lines()
.collect();
assert!(!listed.is_empty(), "the prompt lists entries");
for line in listed {
let id = line.split(": ").next().unwrap();
assert!(id.contains(':'), "an inventory line spells its id in full: {line}");
}
}

View file

@ -2988,7 +2988,7 @@ fn reciter(dao: &Dao, answer: fn(&str) -> String) -> Reciter {
}
#[test]
fn a_recall_probe_asks_for_the_second_half_of_each_live_public_domain_item_and_never_shows_it() {
fn a_recall_probe_asks_for_the_second_half_of_each_live_item_and_never_shows_it() {
use weksa::run::RecallOutcome;
let dao = dao();
let mut perfect = reciter(&dao, second_half);
@ -3047,13 +3047,13 @@ fn the_published_round_schema_does_not_require_a_field_older_records_lack() {
}
#[test]
fn a_recall_mean_is_over_every_probe_an_unreadable_answer_counts_0_and_only_live_public_domain_items_are_probed() {
fn a_recall_mean_is_over_every_probe_an_unreadable_answer_counts_0_and_a_withdrawn_item_is_not_probed() {
use weksa::corpus::Source;
use weksa::run::RecallOutcome;
let dao = dao_over(|corpus, entities| {
let entities = weksa::EntityStore::open(entities).unwrap();
let mut store = weksa::CorpusStore::open(corpus).unwrap();
// The same words under other sources: not public domain, not probed.
// Another source: probed too, but it names no work.
let mut written = store.get("item:daodejing:2").unwrap().unwrap();
written.id = "item:written:1".into();
written.text = "A line of some other source with words enough to split.".into();
@ -3074,11 +3074,17 @@ fn a_recall_mean_is_over_every_probe_an_unreadable_answer_counts_0_and_only_live
let full = both(&dao, None);
assert_eq!(
(full.n, full.answered),
(2, 2),
"neither the written item nor the withdrawn one is probed"
(3, 3),
"the written item is probed, the withdrawn one is not"
);
// One of two unreadable: the mean falls to a half.
// One of three unreadable: the mean falls to two thirds.
let part = both(&dao, Some("Heaven is long-enduring"));
assert_eq!((part.n, part.answered), (2, 1));
assert!((part.chrf - full.chrf / 2.0).abs() < 1e-9, "{}", part.chrf);
assert_eq!((part.n, part.answered), (3, 2));
assert!((part.chrf - full.chrf * 2.0 / 3.0).abs() < 1e-9, "{}", part.chrf);
// A public-domain prompt names its work; any other says it is not published.
let mut m = reciter(&dao, second_half);
dao.run.recall(&mut m).unwrap();
let named = m.seen.iter().filter(|t| t.contains("translator: James Legge")).count();
let unnamed = m.seen.iter().filter(|t| t.contains("work: not published")).count();
assert_eq!((named, unnamed), (2, 1));
}

View file

@ -0,0 +1,114 @@
//! The original corpus (`data/original`): passages written for the
//! non-memorised golf-loop target. It holds no public-domain text, so a model
//! cannot have it in pretraining, and every item says who wrote it.
use std::path::Path;
use weksa::corpus::{ModelFamily, Source};
use weksa::split::{Split, SplitScheme};
use weksa::{CorpusStore, EntityStore, InventoryStore};
fn stores() -> (CorpusStore, EntityStore) {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/original");
let tmp = tempfile::tempdir().unwrap();
for f in ["corpus.cc", "entity.cc", "inventory.cc"] {
std::fs::copy(dir.join(f), tmp.path().join(f)).unwrap();
}
let keep = tmp.keep();
(
CorpusStore::open(&keep.join("corpus.cc")).unwrap(),
EntityStore::open(&keep.join("entity.cc")).unwrap(),
)
}
#[test]
fn every_original_item_records_its_author_and_none_is_public_domain() {
let (corpus, entities) = stores();
let items = corpus.all().unwrap();
assert_eq!(items.len(), 6);
for item in &items {
match &item.source {
Source::ModelWritten {
family, generator, ..
} => {
assert_eq!(*family, ModelFamily::Anthropic, "{}", item.id);
let generator = generator.as_deref().unwrap_or_default();
assert!(generator.contains("claude-sonnet-5-5"), "{}", item.id);
}
other => panic!("{}: not model-written: {other:?}", item.id),
}
assert!(entities.get(&item.speaker).unwrap().is_some(), "{}", item.id);
}
}
#[test]
fn the_original_items_spread_over_train_and_validation_and_none_is_in_test() {
let (corpus, _) = stores();
let splits: Vec<Option<Split>> = corpus
.all()
.unwrap()
.iter()
.map(|i| SplitScheme::V1.live(i))
.collect();
let count = |s| splits.iter().filter(|x| **x == Some(s)).count();
assert_eq!((count(Split::Train), count(Split::Validation)), (3, 3));
assert_eq!(count(Split::Test), 0);
}
#[test]
fn the_original_inventory_is_the_dao_revision_three() {
let dao = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/daodejing/inventory.cc");
let own = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/original/inventory.cc");
assert_eq!(std::fs::read(dao).unwrap(), std::fs::read(&own).unwrap());
let tmp = tempfile::tempdir().unwrap();
let copy = tmp.path().join("inventory.cc");
std::fs::copy(&own, &copy).unwrap();
assert!(InventoryStore::open(&copy).is_ok());
}
/// The probe over the real original corpus: every item is offered, none names a
/// work, and a model that answers with unrelated words reads at the noise floor.
#[test]
fn the_recall_probe_covers_all_six_original_items_and_names_no_work() {
use weksa::model::{ModelPort, PortError};
use weksa::run::{RecallOutcome, RunPaths, RunStores};
struct Unrelated(Vec<String>);
impl ModelPort for Unrelated {
fn model(&self) -> &str {
"unrelated"
}
fn answer(&mut self, p: &weksa::context::Prompt) -> Result<String, PortError> {
self.0.push(p.text().to_string());
Ok(r#"{"text":"stone harbours remember distant lanterns"}"#.to_string())
}
}
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/original");
let tmp = tempfile::tempdir().unwrap();
let path = |f: &str| {
std::fs::copy(src.join(f), tmp.path().join(f)).unwrap();
tmp.path().join(f)
};
let (c, i, e) = (path("corpus.cc"), path("inventory.cc"), path("entity.cc"));
let run = RunStores::create(
&tmp.path().join("run"),
"orig",
RunPaths {
corpus: &c,
inventory: &i,
entities: &e,
},
"m",
"bag",
weksa::run::LowererReach::BriefedSubagent,
)
.unwrap();
let mut model = Unrelated(vec![]);
let RecallOutcome::Read(r) = run.recall(&mut model).unwrap() else {
panic!("answered");
};
assert_eq!((r.n, r.answered), (6, 6));
assert!(r.chrf < 25.0, "{}", r.chrf);
assert_eq!(model.0.len(), 6);
assert!(model.0.iter().all(|t| t.contains("work: not published")));
assert!(model.0.iter().all(|t| !t.contains("translator:")));
}

1
data/original/corpus.cc Normal file
View file

@ -0,0 +1 @@
“²cultcache.store.v1‘—±weksa.corpus_item±weksa.corpus_item´weksa.corpus_item.v1±weksa.corpus_itemÙV{"schemaName":"weksa.corpus_item","schemaVersion":"weksa.corpus_item.v1","members":[]}‘±weksa.corpus_item<EFBFBD>–”¯item:original:1±weksa.corpus_item´2026-11-10T14:17:08ZÄ훯item:original:1Ù0A quiet mind is like still water in a deep bowl.¥en-US­entity:keeper­original-bowl<EFBFBD><EFBFBD>¬ModelWritten“©AnthropicÙ]claude-sonnet-5-5 (Weksa Self, original prose written for the non-memorised golf-loop target)Àª2026-11-10ÀÂ<EFBFBD>”¯item:original:2±weksa.corpus_item´2026-11-10T14:17:08ZÅ›¯item:original:2ÙIThe river weakens the bank in spring and strengthens the roots in autumn.¥en-US­entity:keeper®original-river<EFBFBD><EFBFBD>¬ModelWritten“©AnthropicÙ]claude-sonnet-5-5 (Weksa Self, original prose written for the non-memorised golf-loop target)Àª2026-11-10ÀÂ<EFBFBD>”¯item:original:3±weksa.corpus_item´2026-11-10T14:17:08ZÄô›¯item:original:3Ù7The sage waits at the door until the water has settled.¥en-US­entity:keeper­original-door<EFBFBD><EFBFBD>¬ModelWritten“©AnthropicÙ]claude-sonnet-5-5 (Weksa Self, original prose written for the non-memorised golf-loop target)Àª2026-11-10ÀÂ<EFBFBD>”¯item:original:4±weksa.corpus_item´2026-11-10T14:17:08ZÄÿ›¯item:original:4ÙCThe old gardener empties the jar and fills it again with rainwater.¥en-US­entity:keeper¬original-jar<EFBFBD><EFBFBD>¬ModelWritten“©AnthropicÙ]claude-sonnet-5-5 (Weksa Self, original prose written for the non-memorised golf-loop target)Àª2026-11-10ÀÂ<EFBFBD>”¯item:original:5±weksa.corpus_item´2026-11-10T14:17:08ZÅ›¯item:original:5ÙJUnder the same sky the earth keeps its patience, and the baker keeps hers.¥en-US­entity:keeper¬original-sky<EFBFBD><EFBFBD>¬ModelWritten“©AnthropicÙ]claude-sonnet-5-5 (Weksa Self, original prose written for the non-memorised golf-loop target)Àª2026-11-10ÀÂ<EFBFBD>”¯item:original:6±weksa.corpus_item´2026-11-10T14:17:08ZÄ雯item:original:6Ù+A hungry belly argues louder than any will.¥en-US­entity:keeper®original-belly<EFBFBD><EFBFBD>¬ModelWritten“©AnthropicÙ]claude-sonnet-5-5 (Weksa Self, original prose written for the non-memorised golf-loop target)Àª2026-11-10ÀÂ<EFBFBD>

1
data/original/entity.cc Normal file
View file

@ -0,0 +1 @@
“²cultcache.store.v1‘—¬weksa.entity¬weksa.entity¯weksa.entity.v1¬weksa.entityÙL{"schemaName":"weksa.entity","schemaVersion":"weksa.entity.v1","members":[]}‘¬weksa.entity<EFBFBD>‘”­entity:keeper¬weksa.entity´2026-11-10T14:17:08ZÄ:“­entity:keeper‘“¥en-US‘ªthe keeperÀ<EFBFBD>¤Live‘®weksa-original

BIN
data/original/inventory.cc Normal file

Binary file not shown.