Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
459 changes: 459 additions & 0 deletions packages/agent-cache-py/betterdb_agent_cache/adapters/openai_agents.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,14 @@ async def check(self, params: LlmCacheParams) -> LlmCacheResult:
).inc()
span.set_attribute("cache.hit", True)

stored_tokens: dict[str, int] = entry.get("tokens") or {}
return LlmCacheResult(
hit=True,
response=entry.get("response"),
content_blocks=entry.get("contentBlocks"),
key=key,
input_tokens=int(stored_tokens.get("input", 0)),
output_tokens=int(stored_tokens.get("output", 0)),
)

await self._inc_stats({"llm:misses": 1})
Expand Down
2 changes: 2 additions & 0 deletions packages/agent-cache-py/betterdb_agent_cache/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ class LlmCacheResult:
response: str | None = None
content_blocks: list[ContentBlock] | None = None
key: str | None = None
input_tokens: int = 0
output_tokens: int = 0


@dataclass
Expand Down
25 changes: 25 additions & 0 deletions packages/agent-cache-py/examples/openai_agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# OpenAI Agents SDK example

This example shows how to wrap an OpenAI Agents SDK `ModelProvider` with `CachedModelProvider` so LLM responses are served from `betterdb-agent-cache` on repeat requests.

It demonstrates:
- text prompts via `Runner.run()`
- tool-calling flows with `@function_tool`

## Install

```bash
docker run -d --name valkey -p 6379:6379 valkey/valkey:8
pip install "betterdb-agent-cache[openai_agents]"
export OPENAI_API_KEY=sk-...
```

## Run

```bash
python main.py
```

## Expected output

The first call in each scenario is a miss and the second is a hit. At the end,cache stats show non-zero LLM hits and a positive cost-saved value.
72 changes: 72 additions & 0 deletions packages/agent-cache-py/examples/openai_agents/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
OpenAI Agents SDK + betterdb-agent-cache example

Demonstrates caching agent responses with two scenarios:
1. Simple text agent — responses cached by prompt hash
2. Agent with tools — tool calls round-trip through cache

Usage:
docker run -d --name valkey -p 6379:6379 valkey/valkey:8
pip install "betterdb-agent-cache[openai_agents]"
export OPENAI_API_KEY=sk-...
python main.py
"""
from __future__ import annotations

import asyncio

import valkey.asyncio as valkey_client
from agents import Agent, Runner, RunConfig, function_tool, OpenAIProvider

from betterdb_agent_cache import AgentCache, ModelCost, TierDefaults
from betterdb_agent_cache.adapters.openai_agents import CachedModelProvider
from betterdb_agent_cache.types import AgentCacheOptions


@function_tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
return f"Weather in {city}: sunny, 22°C"


async def main() -> None:
client = valkey_client.Valkey(host="localhost", port=6379)
cache = AgentCache(
AgentCacheOptions(
client=client,
tier_defaults={"llm": TierDefaults(ttl=3600)},
cost_table={
"gpt-4o-mini": ModelCost(input_per_1k=0.00015, output_per_1k=0.0006),
},
),
)

cached_provider = CachedModelProvider(OpenAIProvider(), cache=cache)
run_config = RunConfig(model="gpt-4o-mini", model_provider=cached_provider)

text_agent = Agent(name="Concise", instructions="You are concise.")
print("\n=== 1. Simple text agent ===")
for i in range(2):
result = await Runner.run(text_agent, "What is 2+2? One word.", run_config=run_config)
print(f" [{i + 1}] {result.final_output}")

tools_agent = Agent(name="Weather", instructions="Use tools.", tools=[get_weather])
print("\n=== 2. Agent with tools ===")
for i in range(2):
result = await Runner.run(tools_agent, "Weather in London?", run_config=run_config)
print(f" [{i + 1}] {result.final_output}")

stats = await cache.stats()
print("\n-- Cache Stats --")
print(
"LLM: "
f"{stats.llm.hits} hits / {stats.llm.misses} misses ({stats.llm.hit_rate:.0%})",
)
print(f"Cost saved: ${stats.cost_saved_micros / 1_000_000:.6f}")

await cache.shutdown()
await client.aclose()


if __name__ == "__main__":
asyncio.run(main())
16 changes: 15 additions & 1 deletion packages/agent-cache-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@ build-backend = "hatchling.build"
name = "betterdb-agent-cache"
version = "0.6.0"
description = "Multi-tier exact-match cache for AI agent workloads backed by Valkey. LLM responses, tool results, and session state with built-in OpenTelemetry and Prometheus instrumentation."
keywords = ["valkey", "redis", "agent", "cache", "llm", "opentelemetry", "prometheus", "langchain", "langgraph"]
keywords = [
"valkey",
"redis",
"agent",
"cache",
"llm",
"opentelemetry",
"prometheus",
"langchain",
"langgraph",
"openai-agents",
]
license = { text = "MIT" }
readme = "README.md"
requires-python = ">=3.11"
Expand All @@ -22,12 +33,14 @@ anthropic = ["anthropic>=0.20.0"]
langchain = ["langchain-core>=0.1.0"]
langgraph = ["langgraph>=0.1.0"]
llamaindex = ["llama-index-core>=0.10.0"]
openai_agents = ["openai-agents>=0.0.14"]
analytics = ["posthog>=3.0.0"]
normalizer = ["aiohttp>=3.9.0"]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"fakeredis[aioredis]>=2.20.0",
"openai-agents>=0.0.14",
]
all = [
"openai>=1.0.0",
Expand All @@ -36,6 +49,7 @@ all = [
"langgraph>=0.1.0",
"llama-index-core>=0.10.0",
"posthog>=3.0.0",
"openai-agents>=0.0.14",
"aiohttp>=3.9.0",
]

Expand Down
Loading
Loading