Skip to content

Trim AI prompts and cache Anthropic system prompts - #177

Merged
Programcode merged 2 commits into
mainfrom
cost/trim-prompts-caching
Oct 22, 2026
Merged

Programcode merged 2 commits into
mainfrom
cost/trim-prompts-caching

Conversation

@Programcode

@Programcode Programcode commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Cost fix 5 of 7. This makes AI prompts smaller and turns on Anthropic prompt caching. The other six cost fixes are separate PRs.

What changes

Research goes in once. Before, a research prompt listed every search's results under that search, then repeated the pages it had read in full. A page read in full therefore appeared twice, and a URL that several queries found appeared once per query. Now each source appears once. Pages read in full stay, and the 12 highest-ranked search snippets for other URLs go in beside them. The model still sees which queries ran.

Pages are cut to 2,000 characters. Research page reads and links pasted into Ask used to keep 8,000 characters (research prompts then used 4,000 of them). Both now keep 2,000, and we ask Exa for only that much.

Non-streamed Ask keeps the last 6 earlier messages. The server used to forward whatever history the browser sent (the browser caps it at 8). It now keeps the last 6, cuts each to 6,000 characters, and accepts only user and assistant turns. Before this, a request could put a system message into the history and the server passed it to the model. The new test caught that on the old code. Streamed Ask (ai-stream) used 8 and now reads the same setting, so it also keeps 6.

Anthropic system prompts are cached. For anthropic/* models, the system message goes to OpenRouter as a text part with cache_control: { type: "ephemeral" }. When the same prefix comes back within five minutes, Anthropic charges a tenth of the input price for it. The first call pays 1.25x on those tokens. Other models get exactly the request they got before.

The limits are config. They all live in supabase/functions/_shared/prompt-budget.ts: MAX_FETCHED_PAGE_CHARS (2,000), MAX_RESEARCH_SNIPPETS (12), MAX_ASK_HISTORY_MESSAGES (6), MAX_ASK_HISTORY_MESSAGE_CHARS (6,000), MAX_ASK_CONTEXT_CHARS (32,000, unchanged) and ANTHROPIC_PROMPT_CACHING (on).

How much it saves

On a fixture shaped like a Generate or Factoids run (6 overlapping searches of 10 results each and 5 pages read), the research block drops from about 100,000 characters to about 26,700, roughly 25,000 tokens down to 6,700. Real runs vary with how much the queries overlap.

I checked caching against real OpenRouter through the actual callOpenRouter code. I sent each prompt twice, and the second call read the system prompt from cache every time:

  • Haiku 5.5, Generate system prompt: 4,223 input tokens; 4,192 written, then 4,192 read from cache.
  • Sonnet 5.5, same prompt: the same numbers.
  • Haiku 5.5, Ask system prompt: 1,631 input tokens; 1,600 cached on the second call.
  • Haiku 5.5, Crossfire system prompt: 1,776 input tokens; 1,745 cached.

So every feature's system prompt is long enough to cache, even without Library sources or debate context.

OpenRouter reported cost: 0 on all of these calls. Cost fix 6 covers that; this PR doesn't change cost logging.

What the fake provider changed

e2e/full-stack/fake-openrouter.mjs now reads a system prompt sent as text parts, because the verify stack's default model is Anthropic. It also records messageCount and cacheBreakpoint for each call so a verify run can show what was sent.

Tests

  • research.test.ts: a page read in full isn't repeated as a snippet, a URL two searches found appears once, and page text stays within the cap.
  • handler-research.test.ts: non-streamed Ask sends only the last 6 earlier messages and drops a system role. This fails on main.
  • ai-stream/handler_test.ts: the streamed Ask request to an Anthropic model carries the cache breakpoint.

Verification

Local runs at 76f077e. GitHub Actions isn't running for this repo, so there is no CI result.

  • npm run verify steps: docs check, lint, typecheck, collaboration tests (30), Edge tests (357), build and the service worker test all pass. Unit tests: 298 of 305 passed in the full run. The 7 others hit the 5-second timeout while the VPS load average was over 40. All 7 are frontend tests this PR doesn't touch, and they pass when run one file at a time (53 of 53). Smoke e2e: 28 passed, plus 2 flaky that passed on retry (announcements and the home page).
  • The caching probe above used real OpenRouter.
  • Real-app run with verify-debatelab: not run. The parallel cost-fix threads kept the shared local Supabase stack lock busy the whole time. This change is backend-only with no UI, so instead I called the real ai Edge Function handler under Deno with real Exa research and real OpenRouter (Haiku 5.5). Only the Supabase auth, quota and logging calls were stubbed. I sent an Ask with web search, citations and 10 earlier messages, twice:
    • The main model call had 8 messages: the system prompt, the last 6 history turns, and the question.
    • Exa page reads asked for 2,000 characters. The prompt held 4 full pages and 12 snippets with 12 distinct URLs, and none of the full pages showed up again as a snippet.
    • The system message carried the cache breakpoint. The first request wrote 1,600 tokens to cache, and the second read 1,600 from cache.
    • Both requests returned 200 with citations (6 and 5).

Trade-offs

  • A link pasted into Ask now gives the model 2,000 characters of that page, not 8,000. That's what this fix asked for. If students paste long articles and ask about the end of them, raise MAX_FETCHED_PAGE_CHARS or give pasted links their own setting.
  • A one-off request whose prompt doesn't repeat within five minutes pays 25% more on its cached tokens. Chats, Crossfire and Sparring repeat the same system prompt within a round, so overall this should save money. The JSON repair retry adds text to the system prompt, so it misses the cache. Cost fix 7 covers that path.

🤖 Generated with Claude Code

Research prompts list each source once: a page read in full is no longer
repeated as a search snippet, a URL several queries found appears once,
and at most 12 snippets go in. Fetched pages, including links pasted into
Ask, keep 2,000 characters instead of 8,000. Non-streamed Ask keeps the
last 6 earlier messages, and only user and assistant turns. Anthropic
models get a cache breakpoint on the system prompt.

The limits live in supabase/functions/_shared/prompt-budget.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 11:27
@vercel

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
debate-navigator Ready Ready Preview Oct 22, 2026 12:31pm UTC
debatelab-staging Ready Ready Preview Oct 22, 2026 12:31pm UTC

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 52 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between e5850ee and 79d2d94.

📒 Files selected for processing: 10
  • docs/architecture.md
  • e2e/full-stack/fake-openrouter.mjs
  • supabase/functions/_shared/prompt-budget.ts
  • supabase/functions/_shared/research.test.ts
  • supabase/functions/_shared/research.ts
  • supabase/functions/ai-stream/handler_test.ts
  • supabase/functions/ai-stream/index.ts
  • supabase/functions/ai/features/ask.ts
  • supabase/functions/ai/handler-research.test.ts
  • supabase/functions/ai/provider.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

@Programcode

Copy link
Copy Markdown
Owner Author

Codex review (gpt-6.1-sol, high), round 1 at 76f077e: no findings. It covered the research consumers (Ask citation filtering, factoids verification, Generate ranking, the timeout partial copy), every reader of the system message including the JSON repair retry and the fake provider, non-Anthropic passthrough with require_parameters: true, and history handling.

# Conflicts:
#	supabase/functions/ai/handler-research.test.ts

This branch was successfully deployed

2 active deployments
Preview – debate-navigator — 79d2d942 Deployed Oct 22, 2026 by vercel[bot]
Preview – debatelab-staging — 79d2d942 Deployed Oct 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants