Appendix B · section 18 of 32
ask/rephrase.ts
From Saying It the Law’s Way.
import Anthropic from "@anthropic-ai/sdk";
import { fileURLToPath } from "node:url";
import { SIDE_JOB_TIMEOUT_MS, SMALL_MODEL, requireApiKey, textOf } from "./setup.js";
// The learner's question, said again in the words the law uses, so that
// retrieval can find the law. Retrieval ranks by words and by meaning, and
// both answer in the learner's vocabulary. Absent or failing, retrieval runs
// on the question alone, exactly as before.
/** At most this many searches: each is one more pair of rankings fused into the pool. */
export const REPHRASE_SEARCHES = 2;
export const REPHRASE_RULES = `A learner driver asked a question about their state's road law.
Write one or two short searches, in the words a state's vehicle code and driver manual use for the same thing, that would find the law that answers it.
Use the law's names for things: "maximum speed limit" for the highest limit, "instruction permit" or "learner's permit" for a permit, "financial responsibility" or "liability insurance" for insurance, "implied consent" for a breath test; a code never says "texting": a search for it says "electronic message or text message", because some codes use one and some the other, and "wireless communication device" or "handheld telephone" for a phone.
Keep the kind of figure asked about (feet, miles per hour, days, years, dollars) but never a figure.
When the question asks what happens, what if, will I, do I have to, or how much, write two searches: one for the duty, as a code states what a driver must or may not do ("have in possession", "display on demand"), and one for its penalty or defence in the words a code uses for them ("fine", "offense", "defense to prosecution", "produces in court", "points", "suspend").
Each search is at most eight words. Return no searches if the question is not about road law.`;
const SCHEMA = {
type: "object",
properties: { searches: { type: "array", items: { type: "string" } } },
required: ["searches"],
additionalProperties: false,
} as const;
/** Searches in the law's words for a learner's question, best first; empty when it is not road law. */
export type Rephrase = (question: string) => Promise<string[]>;
// A search's learner words that no code uses, said as the codes say them.
// Haiku is told not to write "texting" and still does in a third of its
// searches; this is what holds when it does.
export function inLawWords(search: string): string {
return search.replace(/\btext(?:s|ing|ed)?\b(?!\s+messag)/gi, "electronic message text message");
}
/** The client the side jobs use: a short timeout and no retries, so a hung call cannot hold a question. */
export function sideJobClient(): Anthropic {
return new Anthropic({ timeout: SIDE_JOB_TIMEOUT_MS, maxRetries: 0 });
}
// Throws on anything it cannot read, so the caller searches with the
// question alone.
export function haikuRephraseFor(client: Pick<Anthropic, "messages">): Rephrase {
return async (question) => {
const response = await client.messages.create({
model: SMALL_MODEL,
max_tokens: 1024,
system: REPHRASE_RULES,
output_config: {
effort: "low",
format: { type: "json_schema", schema: SCHEMA },
},
messages: [{ role: "user", content: question }],
});
const text = textOf(response);
const parsed = JSON.parse(text) as { searches?: unknown };
if (!Array.isArray(parsed.searches) || parsed.searches.some((search) => typeof search !== "string")) {
throw new Error(`the rephraser answered with no searches: ${text}`);
}
return (parsed.searches as string[])
.map((search) => search.trim())
.filter((search) => search.length > 0 && search.length <= 80)
.slice(0, REPHRASE_SEARCHES)
.map(inLawWords);
};
}
async function main(): Promise<void> {
requireApiKey();
const question = process.argv.slice(2).join(" ");
if (question.length === 0) {
console.error('usage: tsx ask/rephrase.ts "<question>"');
process.exit(1);
}
const rephrase = haikuRephraseFor(sideJobClient());
for (const search of await rephrase(question)) console.log(search);
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
}