Skip to content

fix(code): inject OpenAI prompt_cache_key for any OpenAI-provider endpoint - #4995

Merged
Mason Daugherty (mdrxy) merged 7 commits into
mainfrom
mdrxy/code/openai-prompt-cache-key-any-endpoint
Jul 24, 2026
Merged

fix(code): inject OpenAI prompt_cache_key for any OpenAI-provider endpoint#4995
Mason Daugherty (mdrxy) merged 7 commits into
mainfrom
mdrxy/code/openai-prompt-cache-key-any-endpoint

Conversation

@mdrxy

Copy link
Copy Markdown
Member

dcode now attaches the per-thread prompt_cache_key to every model whose provider resolves to openai, regardless of base URL, so reliable prompt-cache prefix routing also applies through the LangSmith gateway and other OpenAI-compatible endpoints — not just api.openai.com. A default-on models.openai_prompt_cache_key opt-out is added for endpoints that reject unknown request fields.


Previously ConfigurableModelMiddleware only injected prompt_cache_key when the resolved ChatOpenAI base URL was api.openai.com (or an official regional subdomain). Any OpenAI-compatible proxy — including the managed LangSmith gateway (gateway.smith.langchain.com/openai/v1) that dcode is provisioned to use — was classified as a custom endpoint and silently dropped the key, so consecutive turns over a stable ~58-59K-token prefix missed prompt caching and were billed at full input price (observed ~7x per-turn cost spikes).

prompt_cache_key is an optional, additive OpenAI request field: it sharpens prefix-cache routing on model families that support it and is otherwise inert. Rather than maintaining a hostname allowlist, _is_openai_model now treats any model reporting ls_provider == "openai" as eligible and attempts the key unconditionally. Endpoints that reject unknown fields can opt out via the new models.openai_prompt_cache_key config option ([models].openai_prompt_cache_key in config.toml, or DEEPAGENTS_CODE_OPENAI_PROMPT_CACHE_KEY, default on). A user-supplied key (on the model or per-invocation) is still preserved, and the Fireworks session-affinity path is unchanged.

Verified end to end that a model created with the gateway base URL now serializes prompt_cache_key in the Responses payload, and that the opt-out suppresses it.

Co-authored-by: open-swe[bot] open-swe@users.noreply.github.com

Made by Open SWE

…endpoint

Attach the per-thread `prompt_cache_key` to every model whose provider
resolves to `openai`, regardless of base URL, so the cache-key optimization
also applies through the LangSmith gateway and other OpenAI-compatible
endpoints — not just `api.openai.com`. Adds a default-on
`models.openai_prompt_cache_key` opt-out (env
`DEEPAGENTS_CODE_OPENAI_PROMPT_CACHE_KEY`) for endpoints that reject unknown
request fields. A user-supplied key is still preserved.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
@github-actions github-actions Bot added dcode Related to `deepagents-code` feature New feature/enhancement or request for one internal User is a member of the `langchain-ai` GitHub organization size: M 200-499 LOC labels Jul 23, 2026
flow. Parsed by `is_env_truthy`: accepts `1`, `true`, `yes`, `on` as enabled.
"""

OPENAI_PROMPT_CACHE_KEY = "DEEPAGENTS_CODE_OPENAI_PROMPT_CACHE_KEY"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if a better long-term solution is to just use a generic PROMPT_CACHE_KEY env variable for multi-provider coverage

I don't think decomposing this into provider-specific variables is useful to the end user

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also - I could see orgs wanting to set org- or team-specific cache keys to maximal static prefix sharing. imo better to support str | falsy

@mdrxy Mason Daugherty (mdrxy) Jul 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Keeping this OpenAI-scoped and as a bool + per-thread default for this PR.

Naming: Injection is not one shared behavior across providers. Fireworks already has its own path (prompt_cache_key + x-session-affinity), and Anthropic uses a different caching model entirely. A generic PROMPT_CACHE_KEY name would overpromise common semantics and get ambiguous when those providers disagree. Happy to revisit a shared name later if multiple providers genuinely share one setting and one meaning.

Value shape: Org/team keys for shared static prefixes are a real product use case, but I'm not expanding this into str | falsy here. The bool matches the bug fix (restore key injection behind non-api.openai.com endpoints) without changing the config mental model. Power users can already supply a custom prompt_cache_key on the model / invocation today. If we want first-class org/team keys in config, I'd rather add a dedicated value setting later than overload one knob that means both enable and value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hmmmmm I think first-class org/team keys routed to cache keys is a logical disconnect and canonicalizing differences in nomenclature under one key (PROMPT_CACHE_KEY or whatever) is better UX. at some point dcode needs to be opinionated rather than saying "here are 5 different levels you can pull

but won't let this get in the way of shipping a fix

@aolsenjazz Alexander Olsen (aolsenjazz) Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

curious what your thoughts are vs this LLMs thoughts

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The job of this PR is restoring prompt_cache_key injection.

On the longer-term shape: I’m aligned that we shouldn’t end up with a pile of provider-specific “almost the same” knobs. I’d rather unify cache-key semantics when we actually introduce generalizable prompt-caching middleware — i.e. one opinionated setting/path that different providers can plug into, instead of inventing a fake-generic PROMPT_CACHE_KEY ahead of shared behavior existing.

resolve_scalar,
)

option = get_option("models.openai_prompt_cache_key")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This will change if we support str | falsy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep — only if we take the str | falsy shape. Staying with the bool toggle here, so this resolver remains enabled -> bool.

@@ -104,45 +103,20 @@ def _is_fireworks_model(model: object) -> bool:


def _is_openai_model(model: object) -> bool:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function can just go away if this is how we're rewriting it

I see this as a good thing - inspecting the base url feels like overloading this function

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Keeping the thin helper.

It now only checks ls_provider == "openai" (base-URL allowlisting is gone, which I agree was overload), but that mirrors _is_anthropic_model / _is_fireworks_model and keeps call sites readable. The docstring here is also the natural home for the eligibility policy (any OpenAI-provider endpoint, not just api.openai.com).

I'd only inline if we collapsed all of the provider helpers the same way — out of scope for this PR.

same key is sent to every OpenAI model without a version gate. This mirrors
the Fireworks path (`_with_fireworks_session_settings`), which sets
`prompt_cache_key` the same way and additionally sends an
same key is sent to every OpenAI-provider model — including custom base URLs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these docs feel out of scope of this variable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good call — trimmed this docstring to injection-only behavior (add key if missing; preserve user-supplied keys). Provider eligibility / endpoint policy / opt-out stay on the caller helpers instead.

updated_settings = _with_fireworks_session_settings(settings, ctx.thread_id)
injected = "Fireworks session settings"
elif _is_openai_model(effective_model):
elif _is_openai_model(effective_model) and _openai_prompt_cache_key_enabled():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we do str | falsy we do not need this additional check

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

True under a key-or-off config shape. Keeping the separate enabled gate for the bool opt-out: resolve once at construction, then if openai_prompt_cache_key: before inject. That only collapses if we redesign the setting as in the str | falsy suggestion.

@mdrxy
Mason Daugherty (mdrxy) marked this pull request as ready for review July 23, 2026 13:41

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Open SWE Review: No issues found

Open SWE reviewed this PR and found no potential bugs to report.

Open in WebView Open SWE trace

Mason Daugherty (mdrxy) and others added 5 commits July 23, 2026 11:16
Read models.openai_prompt_cache_key once when ConfigurableModelMiddleware is built so config.toml never blocks a model call, and cover empty-env opt-out plus Fireworks isolation.
Leave provider eligibility and opt-out policy on the caller helpers.
Keep both openai_prompt_cache_key and goal auto-accept criteria config helpers.
@mdrxy Mason Daugherty (mdrxy) changed the title feat(code): inject OpenAI prompt_cache_key for any OpenAI-provider endpoint fix(code): inject OpenAI prompt_cache_key for any OpenAI-provider endpoint Jul 24, 2026
@github-actions github-actions Bot added fix A bug fix (PATCH) and removed feature New feature/enhancement or request for one labels Jul 24, 2026
Re-raise BlockingError from the openai_prompt_cache_key config read so event-loop I/O violations are not masked, and cover async opt-out plus that path.
@github-actions github-actions Bot added size: L 500-999 LOC and removed size: M 200-499 LOC labels Jul 24, 2026
@mdrxy
Mason Daugherty (mdrxy) merged commit 6d6632f into main Jul 24, 2026
54 checks passed
@mdrxy
Mason Daugherty (mdrxy) deleted the mdrxy/code/openai-prompt-cache-key-any-endpoint branch July 24, 2026 01:39
Mason Daugherty (mdrxy) pushed a commit that referenced this pull request Jul 24, 2026
> [!CAUTION]
> Merging this PR will automatically publish to **PyPI** and create a
**GitHub release**.

For the full release process, see
[`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md).

---

_Release notes preview: keep this section in sync with the package
`CHANGELOG.md`. The published GitHub release body is extracted from the
merged `CHANGELOG.md` by `release.yml`, not from this PR description._

---


##
[0.1.46](deepagents-code==0.1.45...deepagents-code==0.1.46)
(2026-07-24)

### Highlights

- Auto mode is now generally available.
[#4957](#4957)
- Added configurable Auto goal-criteria acceptance.
[#4940](#4940)
- Improved Auto behavior by authorizing actions from active goal/rubric
directives, avoiding redundant approval prompts, showing the enable
notice only on first global enable, deduplicating classifier-unavailable
transcript spam, logging underlying classifier failures, and reporting
classifier timeout budgets.
[#5017](#5017)
[#4993](#4993)
[#5012](#5012)
[#5013](#5013)
[#5011](#5011)
[#5025](#5025)
- Added Hooks v2 capability snapshots and session transcripts, and
hardened Hooks v2 command execution.
[#4916](#4916)
[#4918](#4918)
[#4917](#4917)
- Raised the agent recursion limit to 2000 and made it configurable.
[#4994](#4994)

### Improvements and fixes

- Let the rubric grader inspect working-directory files, show rubric
grader defaults, and improved `/rubric` help and empty-state messaging.
[#4835](#4835)
[#4966](#4966)
[#5015](#5015)
- Unified goal activation signaling.
[#4980](#4980)
- Made Version, Model, and CWD copyable in the Debug Console.
[#4975](#4975)
- Improved `config get` output when a key is missing.
[#4976](#4976)
- Aborted YOLO launch on `Ctrl+C`/`Ctrl+D` and made the YOLO warning
friendlier for new users.
[#4953](#4953)
[#4950](#4950)
- Updated LangSmith handling: secret redaction is disabled by default,
`/trace` now flags empty env overrides that shadow the LangSmith key,
and the default US endpoint is no longer treated as a custom target.
[#4970](#4970)
[#4996](#4996)
[#5022](#5022)
- Injected OpenAI `prompt_cache_key` for any OpenAI-provider endpoint.
[#4995](#4995)
- Improved tool and schema presentation: finished calls stay on the live
tool-group line, first-party tool schemas now include field
descriptions, and `web_search`/`fetch_url` tool descriptions were
trimmed. [#4927](#4927)
[#5019](#5019)
[#5016](#5016)
- Omitted `plugins/` and `conversation_history/` from the `/agent`
picker. [#4991](#4991)
- Made selector modal backdrop dimming consistent.
[#4990](#4990)
- Restored the `"Server log preserved at:"` notice on exit.
[#4999](#4999)
- Used the SDK pin as the effective editable version.
[#4949](#4949)

_End release notes preview._

---

> [!NOTE]
> A **New Contributors** section is appended to the GitHub release notes
automatically at publish time (see [Release
Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline),
step 2).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com>
Marcelo5444 pushed a commit to Marcelo5444/deepagents that referenced this pull request Jul 30, 2026
…ndpoint (langchain-ai#4995)

`dcode` now attaches the per-thread `prompt_cache_key` to every model
whose provider resolves to `openai`, regardless of base URL, so reliable
prompt-cache prefix routing also applies through the LangSmith gateway
and other OpenAI-compatible endpoints — not just `api.openai.com`. A
default-on `models.openai_prompt_cache_key` opt-out is added for
endpoints that reject unknown request fields.

---

Previously `ConfigurableModelMiddleware` only injected
`prompt_cache_key` when the resolved `ChatOpenAI` base URL was
`api.openai.com` (or an official regional subdomain). Any
OpenAI-compatible proxy — including the managed LangSmith gateway
(`gateway.smith.langchain.com/openai/v1`) that dcode is provisioned to
use — was classified as a custom endpoint and silently dropped the key,
so consecutive turns over a stable ~58-59K-token prefix missed prompt
caching and were billed at full input price (observed ~7x per-turn cost
spikes).

`prompt_cache_key` is an optional, additive OpenAI request field: it
sharpens prefix-cache routing on model families that support it and is
otherwise inert. Rather than maintaining a hostname allowlist,
`_is_openai_model` now treats any model reporting `ls_provider ==
"openai"` as eligible and attempts the key unconditionally. Endpoints
that reject unknown fields can opt out via the new
`models.openai_prompt_cache_key` config option
(`[models].openai_prompt_cache_key` in `config.toml`, or
`DEEPAGENTS_CODE_OPENAI_PROMPT_CACHE_KEY`, default on). A user-supplied
key (on the model or per-invocation) is still preserved, and the
Fireworks session-affinity path is unchanged.

Verified end to end that a model created with the gateway base URL now
serializes `prompt_cache_key` in the Responses payload, and that the
opt-out suppresses it.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

Made by [Open
SWE](https://openswe.vercel.app/agents/09eb0978-379d-0b80-9beb-73aa2ef6ab79)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Marcelo5444 pushed a commit to Marcelo5444/deepagents that referenced this pull request Jul 30, 2026
> [!CAUTION]
> Merging this PR will automatically publish to **PyPI** and create a
**GitHub release**.

For the full release process, see
[`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md).

---

_Release notes preview: keep this section in sync with the package
`CHANGELOG.md`. The published GitHub release body is extracted from the
merged `CHANGELOG.md` by `release.yml`, not from this PR description._

---


##
[0.1.46](langchain-ai/deepagents@deepagents-code==0.1.45...deepagents-code==0.1.46)
(2026-07-24)

### Highlights

- Auto mode is now generally available.
[langchain-ai#4957](langchain-ai#4957)
- Added configurable Auto goal-criteria acceptance.
[langchain-ai#4940](langchain-ai#4940)
- Improved Auto behavior by authorizing actions from active goal/rubric
directives, avoiding redundant approval prompts, showing the enable
notice only on first global enable, deduplicating classifier-unavailable
transcript spam, logging underlying classifier failures, and reporting
classifier timeout budgets.
[langchain-ai#5017](langchain-ai#5017)
[langchain-ai#4993](langchain-ai#4993)
[langchain-ai#5012](langchain-ai#5012)
[langchain-ai#5013](langchain-ai#5013)
[langchain-ai#5011](langchain-ai#5011)
[langchain-ai#5025](langchain-ai#5025)
- Added Hooks v2 capability snapshots and session transcripts, and
hardened Hooks v2 command execution.
[langchain-ai#4916](langchain-ai#4916)
[langchain-ai#4918](langchain-ai#4918)
[langchain-ai#4917](langchain-ai#4917)
- Raised the agent recursion limit to 2000 and made it configurable.
[langchain-ai#4994](langchain-ai#4994)

### Improvements and fixes

- Let the rubric grader inspect working-directory files, show rubric
grader defaults, and improved `/rubric` help and empty-state messaging.
[langchain-ai#4835](langchain-ai#4835)
[langchain-ai#4966](langchain-ai#4966)
[langchain-ai#5015](langchain-ai#5015)
- Unified goal activation signaling.
[langchain-ai#4980](langchain-ai#4980)
- Made Version, Model, and CWD copyable in the Debug Console.
[langchain-ai#4975](langchain-ai#4975)
- Improved `config get` output when a key is missing.
[langchain-ai#4976](langchain-ai#4976)
- Aborted YOLO launch on `Ctrl+C`/`Ctrl+D` and made the YOLO warning
friendlier for new users.
[langchain-ai#4953](langchain-ai#4953)
[langchain-ai#4950](langchain-ai#4950)
- Updated LangSmith handling: secret redaction is disabled by default,
`/trace` now flags empty env overrides that shadow the LangSmith key,
and the default US endpoint is no longer treated as a custom target.
[langchain-ai#4970](langchain-ai#4970)
[langchain-ai#4996](langchain-ai#4996)
[langchain-ai#5022](langchain-ai#5022)
- Injected OpenAI `prompt_cache_key` for any OpenAI-provider endpoint.
[langchain-ai#4995](langchain-ai#4995)
- Improved tool and schema presentation: finished calls stay on the live
tool-group line, first-party tool schemas now include field
descriptions, and `web_search`/`fetch_url` tool descriptions were
trimmed. [langchain-ai#4927](langchain-ai#4927)
[langchain-ai#5019](langchain-ai#5019)
[langchain-ai#5016](langchain-ai#5016)
- Omitted `plugins/` and `conversation_history/` from the `/agent`
picker. [langchain-ai#4991](langchain-ai#4991)
- Made selector modal backdrop dimming consistent.
[langchain-ai#4990](langchain-ai#4990)
- Restored the `"Server log preserved at:"` notice on exit.
[langchain-ai#4999](langchain-ai#4999)
- Used the SDK pin as the effective editable version.
[langchain-ai#4949](langchain-ai#4949)

_End release notes preview._

---

> [!NOTE]
> A **New Contributors** section is appended to the GitHub release notes
automatically at publish time (see [Release
Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline),
step 2).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dcode Related to `deepagents-code` fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: L 500-999 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants