build --llm-for <prefix>, and rate reads the mails
The words model can be pointed at a subset of pages (`aggregates.yaml` for the mails alone) while everything else keeps the words it has. `rate --repo` rates each mail's prose on its own, LIX and length, since a person reads a mail with no card around it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
1191551f28
commit
b5f5c46ff2
@@ -1473,7 +1473,15 @@ pub fn run(argv: &[String]) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
if let Some(cmd) = value("--llm") {
|
if let Some(cmd) = value("--llm") {
|
||||||
std::fs::create_dir_all(out.join("llm"))?;
|
std::fs::create_dir_all(out.join("llm"))?;
|
||||||
|
// --llm-for <prefix>: the model writes only the pages whose
|
||||||
|
// path starts with it (`aggregates.yaml` for the mails alone,
|
||||||
|
// `questions/received/` for the followups), and everything
|
||||||
|
// else keeps the words it has.
|
||||||
|
let only = value("--llm-for");
|
||||||
for (n, (path, fallback)) in words.clone().iter().enumerate() {
|
for (n, (path, fallback)) in words.clone().iter().enumerate() {
|
||||||
|
if only.as_ref().is_some_and(|p| !path.starts_with(p.as_str())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let prompt = words_prompt(&plan, path, fallback);
|
let prompt = words_prompt(&plan, path, fallback);
|
||||||
let reply = crate::llm_with(&cmd, &prompt, &[("FORMAT", "json"), ("TEMPERATURE", "0.4")]).unwrap_or_default();
|
let reply = crate::llm_with(&cmd, &prompt, &[("FORMAT", "json"), ("TEMPERATURE", "0.4")]).unwrap_or_default();
|
||||||
std::fs::write(out.join(format!("llm/{:02}-{}.md", n + 1, path.replace('/', "_"))), format!("{prompt}\n\n=== REPLY ===\n{reply}"))?;
|
std::fs::write(out.join(format!("llm/{:02}-{}.md", n + 1, path.replace('/', "_"))), format!("{prompt}\n\n=== REPLY ===\n{reply}"))?;
|
||||||
|
|||||||
+50
@@ -158,6 +158,41 @@ fn followup_page(repo: Option<&Path>, step: &Value) -> Option<Value> {
|
|||||||
|
|
||||||
/// `/` is `questions/index.yaml`; `/a/b` is `questions/a/b.yaml` or
|
/// `/` is `questions/index.yaml`; `/a/b` is `questions/a/b.yaml` or
|
||||||
/// `questions/a/b/index.yaml`.
|
/// `questions/a/b/index.yaml`.
|
||||||
|
/// Every `mail:` on a state in the repo's aggregates.yaml, as
|
||||||
|
/// (`bucket:state`, subject, body) with the `{placeholders}` taken
|
||||||
|
/// out, so they read as the person will read them.
|
||||||
|
pub(crate) fn mail_texts(repo: &Path) -> Option<Vec<(String, String, String)>> {
|
||||||
|
let raw = std::fs::read_to_string(repo.join("aggregates.yaml")).ok()?;
|
||||||
|
let doc: Value = serde_yaml::from_str(&raw).ok()?;
|
||||||
|
let strip = |s: &str| {
|
||||||
|
let mut out = String::new();
|
||||||
|
let mut skip = false;
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'{' => skip = true,
|
||||||
|
'}' => skip = false,
|
||||||
|
_ if !skip => out.push(c),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.split_whitespace().collect::<Vec<_>>().join(" ")
|
||||||
|
};
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for aggregate in doc["aggregates"].as_array().into_iter().flatten() {
|
||||||
|
let bucket = aggregate["bucket"].as_str().unwrap_or_default();
|
||||||
|
for (state, spec) in aggregate["states"].as_object().into_iter().flatten() {
|
||||||
|
if let Some(mail) = spec.get("mail") {
|
||||||
|
out.push((
|
||||||
|
format!("{bucket}:{state}"),
|
||||||
|
strip(mail["subject"].as_str().unwrap_or_default()),
|
||||||
|
strip(mail["body"].as_str().unwrap_or_default()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(out)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn load_page(repo: &Path, route: &str) -> Option<Value> {
|
pub(crate) fn load_page(repo: &Path, route: &str) -> Option<Value> {
|
||||||
let rel = route.trim_matches('/');
|
let rel = route.trim_matches('/');
|
||||||
let candidates = if rel.is_empty() {
|
let candidates = if rel.is_empty() {
|
||||||
@@ -213,6 +248,21 @@ pub fn rate(argv: &[String]) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut report = BTreeMap::new();
|
let mut report = BTreeMap::new();
|
||||||
|
// The mails first, when the repo carries any: a person reads a
|
||||||
|
// mail alone, with no card around it, so its prose is rated on
|
||||||
|
// its own - LIX and length.
|
||||||
|
if let Some(mails) = repo.as_deref().and_then(mail_texts) {
|
||||||
|
if !mails.is_empty() {
|
||||||
|
println!("{:<40} {:>6} {:<14} {:>6}", "mail", "LIX", "", "words");
|
||||||
|
for (name, subject, body) in &mails {
|
||||||
|
let text = format!("{subject}. {body}");
|
||||||
|
let score = lix(&text);
|
||||||
|
println!("{:<40} {:>6.0} {:<14} {:>6}", name, score, lix_band(score), text.split_whitespace().count());
|
||||||
|
report.insert(format!("mail {name}"), json!({ "lix": score, "words": text.split_whitespace().count() }));
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
println!("{:<28} {:>6} {:<14} {:>8} {:>8} {:>8} {:>8} {:<22} {:<22}", "need", "LIX", "", "which", "where", "asked", "next", "gutted twin says", "page load");
|
println!("{:<28} {:>6} {:<14} {:>8} {:>8} {:>8} {:>8} {:<22} {:<22}", "need", "LIX", "", "which", "where", "asked", "next", "gutted twin says", "page load");
|
||||||
for need in model["needs"].as_array().into_iter().flatten() {
|
for need in model["needs"].as_array().into_iter().flatten() {
|
||||||
let id = need["id"].as_str().unwrap_or_default();
|
let id = need["id"].as_str().unwrap_or_default();
|
||||||
|
|||||||
Reference in New Issue
Block a user