Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Adding OpenAI Agents SDK adapter#149

Merged
KIvanow merged 1 commit into
BetterDB-inc:masterfrom
amitkojha05:feat/openai-agents-adapter
Jul 9, 2026
Merged

Adding OpenAI Agents SDK adapter#149
KIvanow merged 1 commit into
BetterDB-inc:masterfrom
amitkojha05:feat/openai-agents-adapter

Conversation

@amitkojha05

@amitkojha05 amitkojha05 commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds betterdb_agent_cache.adapters.openai_agents — cache adapter for the OpenAI Agents SDK (openai-agents).

This is distinct from the existing openai.py (Chat Completions) and openai_responses.py (Responses API) adapters. The Agents SDK wraps those behind its own Model abstract class with get_response() / stream_response(), and Runner orchestrates multi-turn agent loops with handoffs and guardrails on top. This adapter intercepts at the Model.get_response() level — so agent workloads that replay the same tool-call sequences (evaluation, testing, multi-agent
orchestration) skip the API entirely.

Changes

  • New: betterdb_agent_cache/adapters/openai_agents.py

    • prepare_params() — normalizes Agents SDK get_response() inputs (system_instructions + Responses API input items) to LlmCacheParams
    • CachedModel(model, cache) — wraps any Agents SDK Model, intercepts get_response() for cache-before-call. stream_response() delegated directly (streaming not cached — BetterDB convention)
    • CachedModelProvider(provider, cache) — wraps any ModelProvider so every Model it returns is cache-enabled. Recommended integration via RunConfig(model_provider=...)
    • _normalize_input_item(item, normalizer) extracted as private helper matching the _normalize_user_content / _normalize_block pattern from peer adapters
    • _rebuild_output() gracefully handles OpenAI SDK Pydantic models when available, falls back to SimpleNamespace for older stacks
  • New: tests/adapters/test_openai_agents.py — 9 tests covering prepare_params normalization (string input, list input, settings), CachedModel (miss+store text, miss+store tool calls, hit, different prompts, error propagation, streaming delegation), CachedModelProvider

  • New: examples/openai_agents/ — runnable example (text agent + tools agent) with README

  • Modified: pyproject.toml — added openai_agents optional extra, openai-agents>=0.0.14 to dev and all, openai-agents to keywords

No changes to proprietary/. No changes to packages/agent-cache/ (TypeScript).

Design decisions

  • prepare_params takes (system_instructions, input, model_name, model_settings) as separate args because the SDK separates them in get_response() — same rationale as the Pydantic AI adapter
  • tools, handoffs, output_schema excluded from cache key — safe when one CachedModel wraps a single Agent (documented inline)
  • Binary content limitation documented in module docstring and _normalize_input_item docstring; follow-up can add normalizer dispatch

Checklist

  • Unit / integration tests added
  • Docs added / updated
  • Roborev review passed (internal)
  • Competitive analysis done / discussed (internal)
  • Blog post about it discussed (internal)

Test note: 9 tests cover all paths including tool call round-trips and SDK version compatibility. ruff check clean, pytest green.


Note

Medium Risk
Introduces a new caching integration at the Agents SDK Model.get_response() layer and changes the LLM cache hit return shape to include stored token counts, which could affect any callers relying on prior LlmCacheResult fields or token/cost accounting.

Overview
Adds an OpenAI Agents SDK adapter (adapters/openai_agents.py) that wraps an Agents SDK Model/ModelProvider to cache get_response() results (including tool-call outputs) while explicitly not caching stream_response().

Extends LLM cache hit results to return stored input_tokens/output_tokens (persisted in cache entries) so adapters can reconstruct usage on cache hits, and adds tests plus a runnable examples/openai_agents demo; pyproject.toml gains an openai_agents extra and dev/all deps for openai-agents.

Reviewed by Cursor Bugbot for commit 842c541. Bugbot is set up for automated code reviews on this repo. Configure here.

@amitkojha05

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@amitkojha05

Copy link
Copy Markdown
Contributor Author

@KIvanow @jamby77 Please review this PR ,would love to hear your feedback.

@jamby77 jamby77 requested a review from KIvanow May 5, 2026 15:28
@jamby77

jamby77 commented May 5, 2026

Copy link
Copy Markdown
Collaborator

@amitkojha05 you need to resolve conflicts


async def _normalize_input_item(
item: Any,
normalizer: BinaryNormalizer,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If not used in thie iteration, drop the parameter

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — dropped the parameter from _normalize_input_item

cached = await self._cache.llm.check(params)
if cached.hit:
output = _rebuild_output(cached.content_blocks, cached.response)
return _cache_hit_model_response(output, _make_usage(0, 0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

pass real cached tokens into _make_usage instead of (0, 0).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — added input_tokens and output_tokens to LlmCacheResult, populated from the stored entry in llm_cache.py. Cache hits now call _make_usage(cached.input_tokens, cached.output_tokens) instead of (0, 0)

Comment thread packages/agent-cache-py/betterdb_agent_cache/adapters/openai_agents.py Outdated
# This is safe when one CachedModel wraps a single Agent whose tools
# don't change between calls — the typical usage pattern.
params = await prepare_params(
system_instructions, input, model_name, model_settings, self._opts,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tool context omitted from cache key

Medium Severity

tools, handoffs, and output_schema affect get_response() behavior but are excluded from params. Shared CachedModelProvider usage can therefore replay tool calls or structured outputs from another agent configuration.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit be73f0f. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentional design — documented in the inline comment above. Safe when one CachedModel wraps a single Agent with fixed tools, which is the typical usage pattern. Including tools in the cache key is deferred to a follow-up PR.

Comment thread packages/agent-cache-py/betterdb_agent_cache/adapters/openai_agents.py Outdated
amitkojha05 added a commit to amitkojha05/monitor that referenced this pull request May 6, 2026
amitkojha05 added a commit to amitkojha05/monitor that referenced this pull request May 6, 2026
) -> LlmCacheParams:
"""Convert OpenAI Agents SDK get_response() args to canonical ``LlmCacheParams``."""
_ = opts

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prepare options do nothing

Low Severity

prepare_params discards opts, leaving OpenAIAgentsPrepareOptions.normalizer and wrapper opts arguments with no effect. This creates a public option that cannot influence binary or multimodal normalization despite mirroring the peer adapter API.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ceedb6b. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

opts is retained for API consistency with peer adapters. The normalizer field will be wired into _normalize_input_item when binary/multimodal dispatch is added — documented in the inline comment.

@amitkojha05

Copy link
Copy Markdown
Contributor Author

@jamby77 @KIvanow all review points addressed in the latest commits. make_persisting_valkey_client added to conftest.py . Happy to iterate if anything needs adjustment.

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 842c541. Configure here.

role="assistant",
content=text_parts,
),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing nested try/except in message rebuild fallback

Medium Severity

The except TypeError handler for ResponseOutputMessage.model_construct at line 307–315 calls model_construct again without a nested try/except, unlike the text_part helper (lines 248–251) which correctly wraps the retry in its own try/except Exception. In Python, if the retry inside except TypeError raises a different exception, the sibling except Exception on line 316 does not catch it — it only catches exceptions from the original try block. This means a failed retry would propagate as an unhandled exception, crashing the cache-hit path instead of falling back to SimpleNamespace.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 842c541. Configure here.

params,
store_blocks,
LlmStoreOptions(tokens={"input": inp, "output": out_tok}),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cache store failure discards valid LLM response

High Severity

After a successful LLM call, _extract_blocks and store_multipart run without any error handling. store_multipart raises ValkeyCommandError if Valkey is unreachable, and the valid response is never returned to the caller. A transient cache infrastructure failure (Valkey down, network blip) would crash the entire agent workflow even though the LLM already produced a valid result. A caching layer's store path needs to fail gracefully so it doesn't block the primary operation.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 842c541. Configure here.

@KIvanow

KIvanow commented Jul 8, 2026

Copy link
Copy Markdown
Member

Hi @amitkojha05 , just did another review on this one (apologies on the delays, but I am catching up) Great work overall! Here are a few things that warrant fixing

Cross-PR coordination (the thing to actually manage):

  1. feat(agent-cache-py): add Pydantic AI adapter #131 and Adding OpenAI Agents SDK adapter #149 both add a make_persisting_valkey_client to conftest.py - with different implementations. feat(agent-cache-py): add Pydantic AI adapter #131's uses a storage dict + a pipeline mock; Adding OpenAI Agents SDK adapter #149's uses kv + hashes with hget/hset/hincrby and no pipeline. Whichever merges second will conflict on conftest.py. Decide on one canonical helper and have the second PR adopt it rather than blindly resolving. (Adding OpenAI Agents SDK adapter #149's is the more complete of the two.)
  2. Token-handling inconsistency between the two adapters. Adding OpenAI Agents SDK adapter #149 restores real stored tokens on a hit (and added the core field to do so); feat(agent-cache-py): add Pydantic AI adapter #131 returns zeroed RequestUsage. There should be consistent behavior across adapters, feat(agent-cache-py): add Pydantic AI adapter #131 should be updated to use the same mechanism Adding OpenAI Agents SDK adapter #149 just added - which is another reason to merge Adding OpenAI Agents SDK adapter #149 first, then rebase feat(agent-cache-py): add Pydantic AI adapter #131 onto it.

Minor / optional:
3. openai-agents>=0.0.14 floor is misleadingly low (tested it on 0.18 myself) - consider a realistic floor.
4. The hit test asserts only hasattr(out, "output") - weak on its face, though the real-SDK construction upstream makes it meaningful. A Runner-level end-to-end test would close the last gap but is reasonable to defer.

amitkojha05 added a commit to amitkojha05/monitor that referenced this pull request Jul 8, 2026
amitkojha05 added a commit to amitkojha05/monitor that referenced this pull request Jul 8, 2026
@amitkojha05 amitkojha05 force-pushed the feat/openai-agents-adapter branch from 500d7b4 to 22235ef Compare July 8, 2026 20:24
@amitkojha05

Copy link
Copy Markdown
Contributor Author

Hi @amitkojha05 , just did another review on this one (apologies on the delays, but I am catching up) Great work overall! Here are a few things that warrant fixing

Cross-PR coordination (the thing to actually manage):

  1. feat(agent-cache-py): add Pydantic AI adapter #131 and Adding OpenAI Agents SDK adapter #149 both add a make_persisting_valkey_client to conftest.py - with different implementations. feat(agent-cache-py): add Pydantic AI adapter #131's uses a storage dict + a pipeline mock; Adding OpenAI Agents SDK adapter #149's uses kv + hashes with hget/hset/hincrby and no pipeline. Whichever merges second will conflict on conftest.py. Decide on one canonical helper and have the second PR adopt it rather than blindly resolving. (Adding OpenAI Agents SDK adapter #149's is the more complete of the two.)
  2. Token-handling inconsistency between the two adapters. Adding OpenAI Agents SDK adapter #149 restores real stored tokens on a hit (and added the core field to do so); feat(agent-cache-py): add Pydantic AI adapter #131 returns zeroed RequestUsage. There should be consistent behavior across adapters, feat(agent-cache-py): add Pydantic AI adapter #131 should be updated to use the same mechanism Adding OpenAI Agents SDK adapter #149 just added - which is another reason to merge Adding OpenAI Agents SDK adapter #149 first, then rebase feat(agent-cache-py): add Pydantic AI adapter #131 onto it.

Minor / optional: 3. openai-agents>=0.0.14 floor is misleadingly low (tested it on 0.18 myself) - consider a realistic floor. 4. The hit test asserts only hasattr(out, "output") - weak on its face, though the real-SDK construction upstream makes it meaningful. A Runner-level end-to-end test would close the last gap but is reasonable to defer.

Thanks for the thorough review, @KIvanow! Addressed all P0/P1 items:

  • Bugbot fixes: Store path now fails open (logs + returns the live response on cache store failures) with a dedicated test, and _rebuild_output now safely falls back to SimpleNamespace if model_construct retry fails.
  • Shared test helper: make_persisting_valkey_client is now the canonical helper with a stateful pipeline, so feat(agent-cache-py): add Pydantic AI adapter #131 can adopt it directly on rebase.
  • Token handling: Cache hits now restore stored input_tokens/output_tokens via LlmCacheResult; feat(agent-cache-py): add Pydantic AI adapter #131 can reuse the same fields after this merges.
  • Version floor: Bumped openai-agents to >=0.1.0 across extra, dev, and all.
  • Tests: Strengthened the cache-hit test to verify reconstructed content, not just the response shape.

@KIvanow KIvanow left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed the latest push (6a09af8) and this is in great shape - thanks for turning it around so thoroughly, @amitkojha05. Verified each fix against the code, not just the summary:

  • Fail-open store ✅ - get_response now catches ValkeyCommandError (and defensively any Exception), logs, and returns the live response. Covered by test_store_failure_is_fail_open.
  • Refusals preserved ✅ - _extract_blocks now stores ResponseOutputRefusal text instead of dropping it.
  • Rebuild fallback ✅ - the model_construct retries are wrapped in nested try/except -> SimpleNamespace, so older/newer SDK shapes both degrade gracefully.
  • Token accounting ✅ - hits restore stored input_tokens/output_tokens via the new LlmCacheResult fields; test_cached_model_hit_propagates_stored_tokens locks it in.
  • Model settings ✅ - frequency_penalty, presence_penalty, parallel_tool_calls, reasoning folded into the key (only verbosity still out - fine to leave).
  • Version floor ✅ - openai-agents>=0.1.0.
  • Canonical conftest helper ✅ - the stateful kv+hash+pipeline make_persisting_valkey_client is exactly the shared surface we wanted #131 to adopt on rebase.

The remaining Bugbot items (previous_response_id/conversation_id/prompt and tools/handoffs/output_schema omitted from the key, and the deferred normalizer wiring) are all documented, intentional tradeoffs tied to the one-CachedModel-per-Agent usage pattern. I'm comfortable accepting those as-is with the inline docs; multimodal normalizer dispatch is a fair follow-up.

Approving. Let's land this one first, since #131 will rebase onto both the shared conftest helper and the token fields it introduces. 🚀 Thanks again for the excellent work.

@KIvanow KIvanow merged commit 18cab93 into BetterDB-inc:master Jul 9, 2026
2 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants