Compare models
Founder-only model benchmark: compares Claude, xAI Grok, Groq, Google Gemini (through the Lovable gateway / direct Google, GEMINI_COMPARE_MODEL, default google/gemini-3.1-pro-preview) and Perplexity (PERPLEXITY_API_KEY, PERPLEXITY_COMPARE_MODEL, default sonar-pro, may web-search). Writes nothing to campaign data (only spend records). mode 'fixtures' (default) runs eval-fixtures/dataset-v1.json through the real QA judge rubric and reports accuracy / precision / recall on hard fails, mean scores, agreement with the first provider, latency and cost. mode 'judge' (campaign_id) runs the real qaReviewMessages on up to `limit` qualified speakers' stored, scored trios with each provider as the judge and reports per-slot scores, trio pass rate, agreement with the stored verdicts and with claude, rewrite rate, plus example disagreements. mode 'draft' (campaign_id) has each provider write M1 from the identical real skill_v1 prompt, concurrently per speaker, then scores every candidate with the hard-fail gate and (unless judge=false) the Claude judge; returns side-by-side samples with each provider's M1, gate codes, word count, latency and cost next to the stored M1, never persists a candidate. A provider that errors (e.g. a missing key, named in the error) is counted under its errors and the others still return. mode 'human' (campaign_id optional) scores each provider as the QA judge against your own decisions: drafts you rejected with a rewrite request or substantively hand-edited are BAD, your hand-edited versions and messages sent exactly as drafted are GOOD; reports good_pass_rate, bad_catch_rate, balanced_accuracy, auc, pair_win_rate (your edit vs the draft), mean scores, p50/p95 latency and cost per message. mode 'founder' scores each provider as the founder judge: it predicts the founder's own send / don't-send call (and his reason) on his calibration marks in eval-fixtures/founder-labels.json, held-out marks when there are any, otherwise leave-one-out; reports agreement, send_agree, dont_agree, rule_match, cost per message and passes_gate (at least 18 of 20 held-out marks), plus every disagreement in full. mode 'founder_draft' (campaign_id) is the blind writing test: per speaker it puts the stored draft ('current'), the recipe engine ('recipe', with this campaign's m1_recipe or an inline recipe for this campaign) and each writer model (writers, several models per provider allowed) with one identical short founder-style prompt side by side, plus any extra_candidates you pass, and judges every message with the founder judge (judge_provider / judge_model); returns each speaker's inputs, every message with word count, checks and the judge's call, and per-writer totals; never persists. Pass writers=[], include_baselines=false and judge=false to get the speakers' inputs only, with no model calls. mode 'signals' (campaign_id) asks Perplexity, Gemini (Google grounding), Grok (web + X search), Claude (web search), Firecrawl and Apify the same question (this speaker's, or with target='event' this conference's, recent dated signals) and scores every finding the same way: URL loads, cited by the source, dated within 90 days, judged relevant by Claude; reports usable findings per speaker, hit rate, latency and cost per source. mode 'x_signals' (campaign_id) asks xAI live X search for each qualified speaker's handle and recent posts and says whether that signal is new. Each provider stops once its estimated spend passes max_cost_usd. One call does a small page and stops taking new items after 40s: page with offset until next_offset is null; elapsed_ms says how long it took. Pass dry_run=true for the call count first; keys_configured shows which of the five providers have keys.
Request
curl -X POST "https://sayintel.com/api/public/v1/model-comparisons" \
-H "Authorization: Bearer $SAYINTEL_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'Try it out
Sent as a bearer token to this site only, and kept in this tab's session storage. It is never written to disk or sent anywhere else.
Body
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| mode | body | stringfixtures | judge | draft | x_signals | human | signals | founder | founder_draft | no | fixtures | judge | draft | x_signals | human | signals | founder | founder_draft. |
| campaign_id | body | string | no | Required for judge, draft, founder_draft and x_signals. Optional for human (narrows the labelled set to one of your campaigns; omitted = every campaign across the founder's own accounts). |
| providers | body | array | no | Any subset of claude, grok, groq, gemini, perplexity, openrouter (OPENROUTER_API_KEY; choose the model with models.openrouter, e.g. "openai/gpt-5-mini", "deepseek/deepseek-chat", "qwen/qwen3-235b-a22b"); defaults to ["claude","grok"]. For a four-way writing comparison use ["claude","grok","gemini","perplexity"] in draft mode. All work in fixtures, human, founder, judge and draft; gemini runs at the gateway drafting temperature (0.7) in every mode. The first is the agreement baseline in fixtures mode. x_signals always uses grok. |
| limit | body | number | no | Items this call: fixtures default 12 max 30; labelled messages for human default 8 max 30; marks for founder default 10 max 30; speakers for founder_draft default 3 max 8; speakers for signals default 2 max 5; speakers for judge default 4 max 10, draft default 3 max 8, x_signals default 3 max 8. Sized to finish inside ~45s. |
| offset | body | number | no | Skip the first N items (fixture rows, or speakers in id order). Default 0. Call again with the returned next_offset until it is null. |
| judge | body | boolean | no | draft mode: false skips the Claude judge call on each candidate and returns the deterministic gate verdict plus the texts, so one speaker finishes well inside a chat-turn timeout. founder_draft: false skips the founder judge. |
| models | body | object | no | Per-provider model override for this call, e.g. {"groq":"moonshotai/kimi-k2-instruct","claude":"claude-haiku-4-5","gemini":"google/gemini-3.8-flash"}. Omitted = the configured default. One model per provider per call; call again to compare two models of the same provider. A model id the provider does not serve shows up as that provider's error. Cost for a model missing from the price table is over-estimated at the most expensive rate. |
| sources | body | array | no | signals mode only: which search sources to compare (default all six). Model ids for perplexity/gemini/grok/claude come from `models`. |
| target | body | stringspeakers | event | no | signals mode only: 'speakers' (default) searches each qualified speaker; 'event' searches news and posts about the conference itself. |
| judge_context | body | stringmessage | full | no | human mode only: 'message' (default) judges each labelled message alone with a campaign-free rubric; 'full' runs the production judge with the speaker's real campaign context and trio, the labelled text in its slot, scored like rescore_drafts (gate caps included). Use limit 3-4 with 'full'. |
| judge_provider | body | stringclaude | grok | groq | gemini | perplexity | openrouter | no | draft mode only: which provider scores the candidates (default claude). Run once with a non-Claude judge to check Claude is not grading its own writing up. founder_draft: which provider plays the founder judge (default claude). |
| judge_model | body | string | no | founder_draft only: the founder judge's model id (default: the judge provider's default model). |
| speaker_ids | body | array | no | founder_draft only: these speakers (in this campaign) instead of the campaign's eligible ones, paged by offset / limit. |
| writers | body | array | no | founder_draft only: writer models, several per provider allowed, e.g. [{"provider":"claude","model":"claude-opus-5-5"},{"provider":"openrouter","model":"openai/gpt-5.5"}]. Default: providers with models. [] = no writer models. |
| include_baselines | body | boolean | no | founder_draft only: include the stored draft ('current') and the recipe engine ('recipe'). Default true. |
| recipe | body | unknown | no | founder_draft only: this campaign's m1_recipe, when it is not in its campaign_config yet. Its client_id and campaign_id must be this campaign's, or the call is refused. |
| extra_candidates | body | array | no | founder_draft only: hand-written messages to judge alongside, one per speaker per label. |
| max_cost_usd | body | number | no | Estimated spend cap per provider for this call (default 1.5, max 5). |
| dry_run | body | boolean | no | When true nothing is written, started, or spent. The tool returns only cost_preview so an agent can estimate credits first. |
Response
{
"ok": true,
"tool": "compare_models",
"kind": "write",
"data": { ... },
"error": null
}Notes
- Scope required: write. How scopes work.
- Accepts Idempotency-Key. A retry with the same key replays the first result instead of repeating the work.
- Accepts dry_run: true and always returns cost_preview before spending anything.
- Same capability over MCP as the tool compare_models. MCP setup.