plan: held-out personas are mapped to the plan's needs by a model, ids only
The one manual step left after `plan` was mapping the attached personas' `need:` to whatever ids the model chose. A second call now does it: the model sees the plan's needs and the persona texts and returns ids only, after the plan is final, so no persona wording can reach a page. An id the plan lacks is ignored and the reader is told. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b1f0210691
commit
209e9d1258
+56
-15
@@ -141,19 +141,12 @@ pub fn run(argv: &[String]) -> anyhow::Result<()> {
|
|||||||
.unwrap_or(4);
|
.unwrap_or(4);
|
||||||
let intake =
|
let intake =
|
||||||
std::fs::read_to_string(&intake_path).with_context(|| format!("reading {intake_path}"))?;
|
std::fs::read_to_string(&intake_path).with_context(|| format!("reading {intake_path}"))?;
|
||||||
let personas = match value("--personas") {
|
let mut personas: Option<serde_yaml::Value> = match value("--personas") {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
let text = std::fs::read_to_string(&path).with_context(|| format!("reading {path}"))?;
|
let text = std::fs::read_to_string(&path).with_context(|| format!("reading {path}"))?;
|
||||||
let doc: serde_yaml::Value =
|
let doc: serde_yaml::Value =
|
||||||
serde_yaml::from_str(&text).with_context(|| format!("parsing {path}"))?;
|
serde_yaml::from_str(&text).with_context(|| format!("parsing {path}"))?;
|
||||||
let list = doc
|
Some(doc.get("personas").cloned().ok_or_else(|| anyhow!("{path} has no `personas:`"))?)
|
||||||
.get("personas")
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| anyhow!("{path} has no `personas:`"))?;
|
|
||||||
Some(serde_yaml::to_string(&serde_yaml::Mapping::from_iter([(
|
|
||||||
serde_yaml::Value::from("personas"),
|
|
||||||
list,
|
|
||||||
)]))?)
|
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
@@ -172,13 +165,23 @@ pub fn run(argv: &[String]) -> anyhow::Result<()> {
|
|||||||
Ok(plan) => {
|
Ok(plan) => {
|
||||||
let mut text = yaml.trim_end().to_string();
|
let mut text = yaml.trim_end().to_string();
|
||||||
text.push('\n');
|
text.push('\n');
|
||||||
if let Some(p) = &personas {
|
|
||||||
text.push_str("\n# Held out from every model that writes words.\n");
|
|
||||||
text.push_str(p);
|
|
||||||
}
|
|
||||||
// The attached personas were written against someone
|
// The attached personas were written against someone
|
||||||
// else's need ids. The person reading the plan maps
|
// else's need ids. The plan is final by now, so a model
|
||||||
// them; the mismatches are listed so they know which.
|
// may map them to this plan's ids without its words
|
||||||
|
// reaching any page: it returns ids and nothing else,
|
||||||
|
// and an id not in the plan is ignored. What it leaves
|
||||||
|
// unmapped is listed for the reader.
|
||||||
|
if let Some(list) = personas.as_mut() {
|
||||||
|
let mapped = map_personas(&cmd, &plan, list)?;
|
||||||
|
if mapped > 0 {
|
||||||
|
eprintln!("plan round {round}: {mapped} persona(s) mapped to this plan's needs");
|
||||||
|
}
|
||||||
|
text.push_str("\n# Held out from every model that writes words.\n");
|
||||||
|
text.push_str(&serde_yaml::to_string(&serde_yaml::Mapping::from_iter([(
|
||||||
|
serde_yaml::Value::from("personas"),
|
||||||
|
list.clone(),
|
||||||
|
)]))?);
|
||||||
|
}
|
||||||
let whole: Plan =
|
let whole: Plan =
|
||||||
serde_yaml::from_str(&text).context("the plan with its personas attached")?;
|
serde_yaml::from_str(&text).context("the plan with its personas attached")?;
|
||||||
let strangers: Vec<String> = whole
|
let strangers: Vec<String> = whole
|
||||||
@@ -240,6 +243,44 @@ pub fn run(argv: &[String]) -> anyhow::Result<()> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Personas whose `need:` is not one of the plan's ids get one mapped
|
||||||
|
/// by a model that sees the plan's needs and the persona texts and
|
||||||
|
/// returns ids only. Returns how many were mapped; an id the plan does
|
||||||
|
/// not have, or a persona the model leaves out, stays as it was.
|
||||||
|
fn map_personas(cmd: &str, plan: &Plan, personas: &mut serde_yaml::Value) -> anyhow::Result<usize> {
|
||||||
|
let ids: Vec<&str> = plan.needs.iter().map(|n| n.id.as_str()).collect();
|
||||||
|
let Some(list) = personas.as_sequence_mut() else { return Ok(0) };
|
||||||
|
let strangers: Vec<(String, String)> = list
|
||||||
|
.iter()
|
||||||
|
.filter(|p| !ids.contains(&p["need"].as_str().unwrap_or_default()))
|
||||||
|
.map(|p| (p["id"].as_str().unwrap_or_default().to_string(), p["text"].as_str().unwrap_or_default().to_string()))
|
||||||
|
.filter(|(id, _)| !id.is_empty())
|
||||||
|
.collect();
|
||||||
|
if strangers.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let needs: Vec<String> = plan.needs.iter().map(|n| format!("- {}: {} - wants {}", n.id, n.who, n.wants)).collect();
|
||||||
|
let people: Vec<String> = strangers.iter().map(|(id, text)| format!("- {id}: {text}")).collect();
|
||||||
|
let prompt = format!(
|
||||||
|
"A site has these needs, one per kind of visitor:\n{}\n\nThese people arrive at it:\n{}\n\nFor each person, which need is theirs? Reply with a JSON object mapping each person's id to one need id from the list, and nothing else. Leave a person out if no need fits.",
|
||||||
|
needs.join("\n"),
|
||||||
|
people.join("\n")
|
||||||
|
);
|
||||||
|
let reply = crate::llm_with(cmd, &prompt, &[("FORMAT", "json"), ("TEMPERATURE", "0.0")])?;
|
||||||
|
let Some(mapping) = first_object(&reply) else { return Ok(0) };
|
||||||
|
let mut mapped = 0;
|
||||||
|
for persona in list.iter_mut() {
|
||||||
|
let id = persona["id"].as_str().unwrap_or_default().to_string();
|
||||||
|
if let Some(need) = mapping.get(&id).and_then(|v| v.as_str()) {
|
||||||
|
if ids.contains(&need) && !ids.contains(&persona["need"].as_str().unwrap_or_default()) {
|
||||||
|
persona["need"] = serde_yaml::Value::from(need);
|
||||||
|
mapped += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(mapped)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parsed and validated exactly as `build` will.
|
/// Parsed and validated exactly as `build` will.
|
||||||
fn check(yaml: &str) -> anyhow::Result<Plan> {
|
fn check(yaml: &str) -> anyhow::Result<Plan> {
|
||||||
let plan: Plan =
|
let plan: Plan =
|
||||||
|
|||||||
Reference in New Issue
Block a user