/** * llmindex.io — agentic item templates: simulated tool-calling, deterministic ground truth * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * License: Proprietary — © Simon-Pierre Boucher, all rights reserved * * Original, home-made agentic evaluation (inspired by the *ideas* behind * public agentic benchmarks — mock environments, state-based grading — but * with our own environments, rules and grading; nothing is copied). * * Each item presents a mock tool catalog (with distractor tools), a world * state, deterministic business rules, and a goal. The model must emit the * EXACT ordered JSON call sequence; a built-in simulator computes the unique * correct sequence, so grading is canonical-JSON equality — no judges. */ import { BLOCK_ANSWER_FORMAT_INSTRUCTIONS, canonicalJson } from '../answer'; import type { Rng } from '../rng'; import type { ItemTemplate } from '../types'; interface ToolCall { tool: string; args: Record; } const OUTPUT_RULES = 'Output the full ordered sequence of tool calls needed to accomplish the goal, as a JSON array ' + 'of objects {"tool": string, "args": object}. Use exactly the tool and argument names from the ' + 'catalog. Do not call any tool that is not required.'; function catalogBlock(tools: Array<{ sig: string; desc: string }>): string { return tools.map((t) => `- ${t.sig} — ${t.desc}`).join('\n'); } /* ------------------------------------------------------------------ * * Shape A: support-desk triage (policy routing with distractor tools) * * ------------------------------------------------------------------ */ const INCIDENT_KINDS = [ { kind: 'payments', words: ['refund double-charged', 'card declined at checkout', 'invoice total wrong'] }, { kind: 'auth', words: ['cannot reset password', 'locked out after 2FA change', 'SSO loop on login'] }, { kind: 'data', words: ['export file corrupted', 'dashboard shows stale numbers', 'records missing after import'] }, { kind: 'infra', words: ['API latency spikes', 'webhooks not delivered', 'uploads failing intermittently'] }, ] as const; const AGENT_NAMES = ['rivera', 'chen', 'okafor', 'dubois', 'tanaka', 'silva', 'novak', 'haddad'] as const; export const agenticTriage: ItemTemplate = { id: 'agentic.tools.triage-v1', domain: 'agentic', description: 'Route generated incidents through a ticket system under escalation/skill policies; unique correct call sequence, distractor tools present.', paramSpace: 4 ** 4 * 8 ** 3 * 10 ** 4 * 24, render(rng: Rng, perturbSeed: string) { const nIncidents = rng.int(3, 4); const agents = rng.shuffle(AGENT_NAMES).slice(0, 3); const kinds = rng.shuffle(INCIDENT_KINDS).slice(0, 3); // Skill table: each of the 3 kinds handled by exactly one agent. const skills = kinds.map((k, i) => ({ kind: k.kind, agent: agents[i]! })); const escalateAt = rng.int(7, 9); interface Incident { desc: string; kind: string; priority: number; duplicateOf?: number; } const incidents: Incident[] = []; for (let i = 0; i < nIncidents; i++) { const k = kinds[i % kinds.length]!; incidents.push({ desc: rng.pick(k.words), kind: k.kind, priority: rng.int(2, 9), }); } // One incident (never the first) is a duplicate of an earlier one. const dupIdx = rng.int(1, nIncidents - 1); const origIdx = rng.int(0, dupIdx - 1); incidents[dupIdx] = { ...incidents[origIdx]!, duplicateOf: origIdx }; // Simulator: unique correct sequence under the stated policy. const expected: ToolCall[] = []; incidents.forEach((inc, i) => { const id = `TCK-${i + 1}`; expected.push({ tool: 'create_ticket', args: { title: inc.desc, priority: inc.priority } }); if (inc.duplicateOf !== undefined) { expected.push({ tool: 'close_ticket', args: { ticket_id: id, resolution: `duplicate of TCK-${inc.duplicateOf + 1}` }, }); return; } if (inc.priority >= escalateAt) expected.push({ tool: 'escalate', args: { ticket_id: id } }); const agent = skills.find((s) => s.kind === inc.kind)!.agent; expected.push({ tool: 'assign', args: { ticket_id: id, agent } }); }); const tools = [ { sig: 'create_ticket(title: string, priority: int)', desc: 'opens a ticket; IDs are assigned sequentially: the 1st created ticket is "TCK-1", the 2nd "TCK-2", etc.' }, { sig: 'assign(ticket_id: string, agent: string)', desc: 'assigns an open ticket to an agent' }, { sig: 'escalate(ticket_id: string)', desc: 'marks a ticket as escalated' }, { sig: 'close_ticket(ticket_id: string, resolution: string)', desc: 'closes a ticket with a resolution note' }, // distractors — never needed { sig: 'send_email(to: string, body: string)', desc: 'sends an email (not part of the triage policy)' }, { sig: 'archive_ticket(ticket_id: string)', desc: 'archives a closed ticket (nightly job does this automatically)' }, { sig: 'set_reminder(ticket_id: string, hours: int)', desc: 'sets a follow-up reminder' }, ]; const skillLines = skills.map((s) => `- ${s.kind} → ${s.agent}`).join('\n'); const incidentLines = incidents .map((inc, i) => `${i + 1}. "${inc.desc}" (category: ${inc.kind}, priority ${inc.priority})`) .join('\n'); const prompt = [ 'You operate a support desk strictly through tool calls.', '', 'TOOL CATALOG:', catalogBlock(tools), '', 'ROUTING POLICY (apply exactly, in this order, for each incident, processing incidents in the order listed):', '1. Create a ticket for the incident (title = the incident text verbatim, priority as given).', `2. If the incident is an exact duplicate of an earlier incident in this list, close its ticket immediately with resolution "duplicate of " and do nothing else for it.`, `3. Otherwise, if priority ≥ ${escalateAt}, escalate the ticket BEFORE assigning it.`, '4. Assign the ticket to the agent responsible for its category.', '', 'CATEGORY → AGENT:', skillLines, '', 'INCIDENTS:', incidentLines, '', OUTPUT_RULES, '', BLOCK_ANSWER_FORMAT_INSTRUCTIONS, ].join('\n'); return { templateId: this.id, domain: this.domain, prompt, answerKey: canonicalJson(expected), grading: 'json' as const, perturbSeed, }; }, }; /* --------------------------------------------------------------- * * Shape B: treasury ledger (stateful arithmetic + conditional flow) * * --------------------------------------------------------------- */ const ACCOUNT_NAMES = ['alpha', 'bravo', 'delta', 'echo', 'kilo', 'lima', 'oscar', 'tango'] as const; export const agenticLedger: ItemTemplate = { id: 'agentic.tools.ledger-v1', domain: 'agentic', description: 'Execute payment instructions over account balances; overdrafts must be pre-funded from reserve with the exact shortfall — requires running-state arithmetic.', paramSpace: 8 ** 3 * 900 ** 3 * 500 ** 4, render(rng: Rng, perturbSeed: string) { const accounts = rng.shuffle(ACCOUNT_NAMES).slice(0, 3); const balances = new Map(); for (const a of accounts) balances.set(a, rng.int(120, 900)); const nPayments = rng.int(4, 5); interface Payment { from: string; to: string; amount: number; } const payments: Payment[] = []; for (let i = 0; i < nPayments; i++) { const from = rng.pick(accounts); let to = rng.pick(accounts); while (to === from) to = rng.pick(accounts); payments.push({ from, to, amount: rng.int(80, 600) }); } // Simulator: transfers in order; shortfall → top_up_from_reserve first. const expected: ToolCall[] = []; const state = new Map(balances); for (const p of payments) { const bal = state.get(p.from)!; if (bal < p.amount) { const shortfall = p.amount - bal; expected.push({ tool: 'top_up_from_reserve', args: { account: p.from, amount: shortfall } }); state.set(p.from, bal + shortfall); } expected.push({ tool: 'transfer', args: { from: p.from, to: p.to, amount: p.amount } }); state.set(p.from, state.get(p.from)! - p.amount); state.set(p.to, state.get(p.to)! + p.amount); } const tools = [ { sig: 'transfer(from: string, to: string, amount: int)', desc: 'moves funds between accounts; FAILS if it would overdraw the source' }, { sig: 'top_up_from_reserve(account: string, amount: int)', desc: 'adds funds to an account from the corporate reserve' }, // distractors { sig: 'get_balance(account: string)', desc: 'reads a balance (you already have all balances below — reads are unnecessary and forbidden)' }, { sig: 'freeze_account(account: string)', desc: 'compliance freeze (not part of this task)' }, { sig: 'convert_currency(account: string, currency: string)', desc: 'FX conversion (all amounts are already in USD)' }, ]; const balanceLines = accounts.map((a) => `- ${a}: $${balances.get(a)}`).join('\n'); const paymentLines = payments .map((p, i) => `${i + 1}. pay $${p.amount} from "${p.from}" to "${p.to}"`) .join('\n'); const prompt = [ 'You are a treasury agent operating strictly through tool calls.', '', 'TOOL CATALOG:', catalogBlock(tools), '', 'OPENING BALANCES:', balanceLines, '', 'PAYMENT INSTRUCTIONS (execute in exactly this order):', paymentLines, '', 'RULES:', '- transfer() fails on overdraft. If a payment would overdraw its source account at the moment of execution, first call top_up_from_reserve() on the source with EXACTLY the shortfall (no more, no less), then execute the transfer.', '- Track balances as they change: earlier payments affect later ones.', '- Never call tools that are not needed.', '', OUTPUT_RULES, '', BLOCK_ANSWER_FORMAT_INSTRUCTIONS, ].join('\n'); return { templateId: this.id, domain: this.domain, prompt, answerKey: canonicalJson(expected), grading: 'json' as const, perturbSeed, }; }, }; /* ----------------------------------------------------------- * * Shape C: deployment pipeline (dependency-ordered operations) * * ----------------------------------------------------------- */ const SERVICE_NAMES = ['gateway', 'billing', 'search', 'notifier', 'reports', 'auth-svc'] as const; export const agenticDeploy: ItemTemplate = { id: 'agentic.tools.deploy-v1', domain: 'agentic', description: 'Deploy services respecting a dependency DAG and health-gate policy; correct topological order with deterministic tie-breaking.', paramSpace: 6 ** 4 * 2 ** 6 * 24, render(rng: Rng, perturbSeed: string) { const services = rng.shuffle(SERVICE_NAMES).slice(0, 4); // Build a random DAG over the 4 services: edges only from earlier to later // in a hidden topological order. const order = rng.shuffle(services); const deps = new Map(); for (const s of services) deps.set(s, []); for (let i = 1; i < order.length; i++) { const nDeps = rng.int(1, Math.min(2, i)); const chosen = rng.shuffle(order.slice(0, i)).slice(0, nDeps); deps.set(order[i]!, chosen.sort()); } const flaky = rng.pick(services); // needs a health check after deploy // Simulator: repeated passes; deploy every service whose deps are all // deployed, in ALPHABETICAL order within a pass (stated tie-break rule). const expected: ToolCall[] = []; const deployed = new Set(); while (deployed.size < services.length) { const ready = services .filter((s) => !deployed.has(s) && deps.get(s)!.every((d) => deployed.has(d))) .sort(); for (const s of ready) { expected.push({ tool: 'deploy', args: { service: s } }); if (s === flaky) expected.push({ tool: 'health_check', args: { service: s } }); deployed.add(s); } } const tools = [ { sig: 'deploy(service: string)', desc: 'deploys a service; FAILS if any dependency is not yet deployed' }, { sig: 'health_check(service: string)', desc: 'runs a post-deploy health probe' }, // distractors { sig: 'rollback(service: string)', desc: 'reverts a bad deploy (nothing fails in this scenario)' }, { sig: 'scale(service: string, replicas: int)', desc: 'changes replica count (out of scope)' }, { sig: 'restart(service: string)', desc: 'restarts a service (out of scope)' }, ]; const depLines = services .map((s) => `- ${s}: ${deps.get(s)!.length ? deps.get(s)!.join(', ') : '(none)'}`) .join('\n'); const prompt = [ 'You are a release agent operating strictly through tool calls.', '', 'TOOL CATALOG:', catalogBlock(tools), '', 'SERVICES AND THEIR DEPENDENCIES (a service can only be deployed after ALL its dependencies):', depLines, '', 'POLICY:', '- Deploy in waves: in each wave, deploy every service whose dependencies are already deployed, in alphabetical order; repeat until all services are deployed.', `- The service "${flaky}" is flagged unstable: call health_check on it immediately after deploying it.`, '- Call nothing else.', '', OUTPUT_RULES, '', BLOCK_ANSWER_FORMAT_INSTRUCTIONS, ].join('\n'); return { templateId: this.id, domain: this.domain, prompt, answerKey: canonicalJson(expected), grading: 'json' as const, perturbSeed, }; }, };