0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
343 lines
9.4 KiB
Python
343 lines
9.4 KiB
Python
"""
|
|
Base types and interfaces for cache optimization.
|
|
|
|
This module defines the core abstractions that all cache optimizers implement.
|
|
The design allows for provider-specific implementations while maintaining a
|
|
consistent interface for users.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Any, Literal, Protocol, runtime_checkable
|
|
|
|
|
|
class CacheStrategy(Enum):
|
|
"""Cache optimization strategy."""
|
|
|
|
# Just stabilize prefix (move dates, normalize whitespace)
|
|
PREFIX_STABILIZATION = "prefix_stabilization"
|
|
|
|
# Insert explicit cache breakpoints (Anthropic)
|
|
EXPLICIT_BREAKPOINTS = "explicit_breakpoints"
|
|
|
|
# Manage separate cached content objects (Google)
|
|
CACHED_CONTENT = "cached_content"
|
|
|
|
# No optimization possible (provider doesn't support caching)
|
|
NONE = "none"
|
|
|
|
|
|
class BreakpointLocation(Enum):
|
|
"""Where to insert cache breakpoints."""
|
|
|
|
AFTER_SYSTEM = "after_system"
|
|
AFTER_TOOLS = "after_tools"
|
|
AFTER_EXAMPLES = "after_examples"
|
|
CUSTOM = "custom"
|
|
|
|
|
|
@dataclass
|
|
class CacheBreakpoint:
|
|
"""
|
|
Represents a cache breakpoint location.
|
|
|
|
For Anthropic, this maps to cache_control blocks.
|
|
For other providers, this is informational.
|
|
"""
|
|
|
|
# Message index where breakpoint should be inserted
|
|
message_index: int
|
|
|
|
# Location type
|
|
location: BreakpointLocation
|
|
|
|
# For content arrays, index within the content
|
|
content_index: int | None = None
|
|
|
|
# Token count at this breakpoint
|
|
tokens_at_breakpoint: int = 0
|
|
|
|
# Reason for this breakpoint
|
|
reason: str = ""
|
|
|
|
|
|
@dataclass
|
|
class CacheConfig:
|
|
"""Configuration for cache optimization."""
|
|
|
|
# Whether to optimize at all
|
|
enabled: bool = True
|
|
|
|
# Strategy to use (auto-detected if None)
|
|
strategy: CacheStrategy | None = None
|
|
|
|
# Minimum tokens before caching makes sense
|
|
min_cacheable_tokens: int = 1024
|
|
|
|
# Maximum number of breakpoints (Anthropic limit is 4)
|
|
max_breakpoints: int = 4
|
|
|
|
# Patterns to extract and move to dynamic section
|
|
date_patterns: list[str] = field(
|
|
default_factory=lambda: [
|
|
r"Today is \w+ \d{1,2},? \d{4}\.?",
|
|
r"Current date: \d{4}-\d{2}-\d{2}",
|
|
r"The current time is .+\.",
|
|
]
|
|
)
|
|
|
|
# Whether to normalize whitespace
|
|
normalize_whitespace: bool = True
|
|
|
|
# Collapse multiple blank lines
|
|
collapse_blank_lines: bool = True
|
|
|
|
# Separator between static and dynamic content
|
|
dynamic_separator: str = "\n\n---\n\n"
|
|
|
|
# Dynamic content detection tiers (for OpenAI prefix stabilization)
|
|
# - "regex": Fast pattern matching (~0ms) - always recommended
|
|
# - "ner": Named Entity Recognition via spaCy (~5-10ms) - catches names, money, etc.
|
|
# - "semantic": Embedding similarity (~20-50ms) - catches volatile patterns
|
|
# Default is regex-only for speed. Add tiers for better detection at cost of latency.
|
|
dynamic_detection_tiers: list[Literal["regex", "ner", "semantic"]] = field(
|
|
default_factory=lambda: ["regex"]
|
|
)
|
|
|
|
# For semantic caching
|
|
semantic_cache_enabled: bool = False
|
|
semantic_similarity_threshold: float = 0.95
|
|
semantic_cache_ttl_seconds: int = 300
|
|
|
|
|
|
@dataclass
|
|
class CacheMetrics:
|
|
"""Metrics about cache optimization."""
|
|
|
|
# Prefix analysis
|
|
stable_prefix_tokens: int = 0
|
|
stable_prefix_hash: str = ""
|
|
|
|
# Breakpoint info
|
|
breakpoints_inserted: int = 0
|
|
breakpoint_locations: list[CacheBreakpoint] = field(default_factory=list)
|
|
|
|
# Cache hit estimation
|
|
prefix_changed_from_previous: bool = False
|
|
previous_prefix_hash: str | None = None
|
|
estimated_cache_hit: bool = False
|
|
|
|
# Savings estimation
|
|
estimated_savings_percent: float = 0.0
|
|
cacheable_tokens: int = 0
|
|
non_cacheable_tokens: int = 0
|
|
|
|
# Provider-specific
|
|
provider_cache_id: str | None = None # For Google's CachedContent
|
|
cache_ttl_remaining_seconds: int | None = None
|
|
|
|
|
|
@dataclass
|
|
class OptimizationContext:
|
|
"""Context for optimization request."""
|
|
|
|
# Request tracking
|
|
request_id: str = ""
|
|
timestamp: datetime = field(default_factory=datetime.now)
|
|
|
|
# Provider info
|
|
provider: str = ""
|
|
model: str = ""
|
|
|
|
# Query for relevance (used by semantic cache)
|
|
query: str | None = None
|
|
|
|
# Previous request info (for cache hit detection)
|
|
previous_prefix_hash: str | None = None
|
|
|
|
# Additional metadata
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class CacheResult:
|
|
"""Result of cache optimization."""
|
|
|
|
# Optimized messages
|
|
messages: list[dict[str, Any]]
|
|
|
|
# Whether this was a semantic cache hit
|
|
semantic_cache_hit: bool = False
|
|
|
|
# Cached response (if semantic cache hit)
|
|
cached_response: Any | None = None
|
|
|
|
# Optimization metrics
|
|
metrics: CacheMetrics = field(default_factory=CacheMetrics)
|
|
|
|
# Tokens before/after
|
|
tokens_before: int = 0
|
|
tokens_after: int = 0
|
|
|
|
# Transforms applied
|
|
transforms_applied: list[str] = field(default_factory=list)
|
|
|
|
# Warnings
|
|
warnings: list[str] = field(default_factory=list)
|
|
|
|
|
|
@runtime_checkable
|
|
class CacheOptimizer(Protocol):
|
|
"""
|
|
Protocol for cache optimizers.
|
|
|
|
All provider-specific optimizers must implement this interface.
|
|
This allows for easy swapping of implementations and plugin registration.
|
|
"""
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
"""Name of this optimizer."""
|
|
...
|
|
|
|
@property
|
|
def provider(self) -> str:
|
|
"""Provider this optimizer is for."""
|
|
...
|
|
|
|
@property
|
|
def strategy(self) -> CacheStrategy:
|
|
"""The caching strategy this optimizer uses."""
|
|
...
|
|
|
|
def optimize(
|
|
self,
|
|
messages: list[dict[str, Any]],
|
|
context: OptimizationContext,
|
|
config: CacheConfig | None = None,
|
|
) -> CacheResult:
|
|
"""
|
|
Optimize messages for caching.
|
|
|
|
Args:
|
|
messages: The messages to optimize.
|
|
context: Optimization context with request info.
|
|
config: Optional configuration override.
|
|
|
|
Returns:
|
|
CacheResult with optimized messages and metrics.
|
|
"""
|
|
...
|
|
|
|
def get_metrics(self) -> CacheMetrics:
|
|
"""Get aggregated metrics from this optimizer."""
|
|
...
|
|
|
|
def estimate_savings(
|
|
self,
|
|
messages: list[dict[str, Any]],
|
|
context: OptimizationContext,
|
|
) -> float:
|
|
"""
|
|
Estimate potential savings from optimization.
|
|
|
|
Returns:
|
|
Estimated savings as a percentage (0-100).
|
|
"""
|
|
...
|
|
|
|
|
|
class BaseCacheOptimizer(ABC):
|
|
"""
|
|
Abstract base class for cache optimizers.
|
|
|
|
Provides common functionality for all optimizers.
|
|
"""
|
|
|
|
def __init__(self, config: CacheConfig | None = None):
|
|
self.config = config or CacheConfig()
|
|
self._metrics_history: list[CacheMetrics] = []
|
|
self._previous_prefix_hash: str | None = None
|
|
|
|
@property
|
|
@abstractmethod
|
|
def name(self) -> str:
|
|
"""Name of this optimizer."""
|
|
...
|
|
|
|
@property
|
|
@abstractmethod
|
|
def provider(self) -> str:
|
|
"""Provider this optimizer is for."""
|
|
...
|
|
|
|
@property
|
|
@abstractmethod
|
|
def strategy(self) -> CacheStrategy:
|
|
"""The caching strategy this optimizer uses."""
|
|
...
|
|
|
|
@abstractmethod
|
|
def optimize(
|
|
self,
|
|
messages: list[dict[str, Any]],
|
|
context: OptimizationContext,
|
|
config: CacheConfig | None = None,
|
|
) -> CacheResult:
|
|
"""Optimize messages for caching."""
|
|
...
|
|
|
|
def get_metrics(self) -> CacheMetrics:
|
|
"""Get aggregated metrics."""
|
|
if not self._metrics_history:
|
|
return CacheMetrics()
|
|
|
|
# Return most recent metrics
|
|
return self._metrics_history[-1]
|
|
|
|
def estimate_savings(
|
|
self,
|
|
messages: list[dict[str, Any]],
|
|
context: OptimizationContext,
|
|
) -> float:
|
|
"""Estimate potential savings."""
|
|
# Default implementation - subclasses can override
|
|
result = self.optimize(messages, context)
|
|
return result.metrics.estimated_savings_percent
|
|
|
|
def _record_metrics(self, metrics: CacheMetrics) -> None:
|
|
"""Record metrics for history."""
|
|
self._metrics_history.append(metrics)
|
|
# Keep only last 100 entries
|
|
if len(self._metrics_history) > 100:
|
|
self._metrics_history = self._metrics_history[-100:]
|
|
|
|
def _compute_prefix_hash(self, content: str) -> str:
|
|
"""Compute a short hash of content."""
|
|
import hashlib
|
|
|
|
return hashlib.md5(content.encode()).hexdigest()[:12] # nosec B324
|
|
|
|
def _extract_system_content(self, messages: list[dict[str, Any]]) -> str:
|
|
"""Extract content from system messages."""
|
|
parts = []
|
|
for msg in messages:
|
|
if msg.get("role") == "system":
|
|
content = msg.get("content", "")
|
|
if isinstance(content, str):
|
|
parts.append(content)
|
|
elif isinstance(content, list):
|
|
# Handle content blocks
|
|
for block in content:
|
|
if isinstance(block, dict) and block.get("type") == "text":
|
|
parts.append(block.get("text", ""))
|
|
return "\n".join(parts)
|
|
|
|
def _count_tokens_estimate(self, text: str) -> int:
|
|
"""Rough token count estimate (4 chars per token)."""
|
|
return len(text) // 4
|