Skip to content

fix: Move Per-Turn Context Out of the System Prompt When an Agent Has No Instructions - #668

Draft
Shabonasar wants to merge 1 commit into
LibreChat-AI:mainfrom
Shabonasar:fix/prompt-cache-dynamic-only-tail
Draft

Shabonasar wants to merge 1 commit into
LibreChat-AI:mainfrom
Shabonasar:fix/prompt-cache-dynamic-only-tail

Conversation

@Shabonasar

Copy link
Copy Markdown

Part of LibreChat-AI/LibreChat#17102

Fixes #667

Summary

With Anthropic or OpenRouter prompt caching, additional_instructions moves next to the newest user message so that per-turn context does not invalidate the cached history. The move was skipped when the agent had no instructions: the per-turn text then became the whole system message, ahead of the history, and every turn missed the cache. Triggered by RAG or memory in a chat with no system prompt (report). This PR moves the tail whenever it is set and omits the system message when nothing is left in it. Result in the run below: from turn 3 on, the history of earlier turns is read from cache instead of 0 tokens.

Related to LibreChat-AI/LibreChat#17102. Base: main, no dependency on other PRs; LibreChat needs no change.

How it works

before, no instructions:  system: "<RAG chunks of turn N>" | Q1 A1 ... Q(N)            system changes -> 0 read
after:                    Q1 A1 ... A(N-1) | "<RAG chunks of turn N>" Q(N)          history read from cache
  • shouldMoveDynamicInstructions no longer requires stable instructions.
  • buildSystemMessage returns no system message when there are no stable instructions and the tail was moved. The OpenRouter branch that kept a dynamic-only system message is no longer reachable and is removed.
  • Providers without prompt caching, and Bedrock, are unchanged.

Type of change

  • Bug fix

Testing

Tested environments/configuration:

LibreChat dev 76c25680c, custom endpoint with provider: anthropic, Qwen3.8-27B (vLLM behind NVIDIA Dynamo, through LiteLLM), no system prompt, no tools, a text file uploaded for File Search with each of 5 questions (retrieval from a local stand-in for rag_api). Two runs per arm; tokens read from cache / prompt, run 1:

turn 1 2 3 4 5 cached, runs 1 / 2
agents 4.2.1 0 / 10641 0 / 10653 0 / 10775 0 / 10896 0 / 11146 0.0% / 0.0%
this PR 0 / 10638 0 / 10651 8000 / 10774 8000 / 10893 8000 / 11133 44.4% / 44.4%
  • 4.2.1: every request differs from the previous one at the RAG chunk in system. This PR: from turn 3 each request reads up to the block limit (the model reuses whole 1600-token blocks); turn 2 still misses once, because the turn 1 tail stood before the first question.
  • LibreChat integration/prompt-cache with stableContext: true: 62.9-63.0%, 0 misses with and without this PR (there the retrieved context goes into the user message of its turn, not into additional_instructions).
  • Not run: the Anthropic API itself, OpenRouter. The original measurements with Claude through LiteLLM are in the report linked above.

Automated tests:

npx jest src/specs/prompt-cache src/messages src/llm src/summarization src/graphs src/agents
npx tsc --noEmit
  • AgentContext.test.ts: new "keeps the Anthropic history prefix when only per-turn context changes". Two existing tests are rewritten, since they asserted the old behavior: "omits Anthropic cache control when only dynamic system text exists" is now "sends dynamic-only Anthropic instructions without a system message", and "keeps dynamic-only OpenRouter instructions as system text" is now "moves dynamic-only OpenRouter instructions next to the latest user message". All three fail on main.
  • 3529 passed. 3 suites fail because they need API keys (anthropic, google, vertexai llm.spec.ts), as on main.
  • LibreChat packages/api src/agents with this change: 5884 passed, as without it (suites that need MongoDB or Redis not run).

Screenshots / recordings

No user-facing change.

Risk / compatibility

  • Agents without instructions that use Anthropic or OpenRouter prompt caching now send per-turn context as a user message before the newest question instead of as the system message. Agents with instructions already get this.
  • Conflicts with fix: Keep Per-Conversation Context and the Summary at a Fixed System Position #613 in the same condition; whichever lands second drops the stable-instructions term there: (promptCacheProvider != null || bedrockCache) && dynamicInstructions !== '', and the system message is omitted when stable and conversation instructions are both empty and the tail was moved (this also covers Bedrock).

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • User-facing or complex behavior is documented where necessary
  • Required dependency changes have been merged/published (none)
  • Required documentation PR: N/A

…re no stable instructions

With Anthropic or OpenRouter prompt caching, additional_instructions moved next
to the newest user message only when stable instructions existed. Without them
(no system prompt, RAG chunks or memory in the tail) the per-turn text stayed in
the system message, ahead of the history, and every turn missed the cache from
the first message. The tail now moves whenever it is set; the system message is
omitted when nothing is left in it.

Reported-by: jamesleslie-uom

This branch has not been deployed

No deployments
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.

Prompt cache misses on every turn when an agent has no instructions and per-turn context

1 participant