Skip to content

Unified IR validation cache decoupled from conversion cache (hub-and-spoke) #282

@Oaklight

Description

@Oaklight

Parent: #276

Problem

IR validation caching is currently fragmented and coupled:

  • Tool validation: coupled to conversion cache hit (_skip_tools_validation flag) — no independent IR-level cache
  • Message validation: validated_msg_cache — correctly decoupled, keyed by IR content, no converter tag
  • Other IR types (tool_choice, generation config, reasoning, etc.): not cached at all

The correct architecture: one unified IR validation cache that covers all IR entry types, completely decoupled from conversion caches.

Architecture: Hub and Spoke

Conversion Caches (spokes, converter-specific)
┌─ anthropic:from_p ─┐       ┌─ anthropic:to_p ───┐
├─ openai_chat:from_p ┤       ├─ openai_chat:to_p ──┤
├─ responses:from_p  ─┤       ├─ responses:to_p ───┤
└─ google:from_p ────┘       └─ google:to_p ─────┘
          │                            ▲
          ▼                            │
    ┌─────────────────────────────────────────┐
    │   IR Validation Cache (hub, shared)      │
    │                                          │
    │   hash(IR entry) → validated             │
    │   • tools      ← NEW (currently missing) │
    │   • messages   ← EXISTS (validated_msg)   │
    │   • any IR dict                           │
    │                                          │
    │   No converter tag. Shared across all     │
    │   converters. One entry validated once,   │
    │   never re-validated regardless of        │
    │   source/target converter.                │
    └─────────────────────────────────────────┘

Implication rules

If this hits... Then...
Any spoke (conversion) cache Hub (validation) must also hit
Hub (validation) cache Spokes may miss (different converter)

Current state

IR type Conversion cache Validation cache Status
Tools tool_entry_cache (with tag) ❌ Coupled to conversion hit Needs fix
Messages N/A validated_msg_cache (no tag) Correct
tool_choice, generation, etc. N/A ❌ None Low priority (cheap to validate)

Proposed change

1. Merge validated_msg_cache into a unified ir_validation_cache

Replace the separate validated_msg_cache with a single cache that handles all IR types:

# Before (fragmented):
validated_msg_cache = LRUCache(maxsize=4096)  # messages only

# After (unified):
ir_validation_cache = LRUCache(maxsize=8192)  # all IR entries

def is_ir_entry_validated(entry: dict) -> bool:
    """Check if any IR entry (tool, message, etc.) was previously validated."""
    return ir_validation_cache.get(hash(_canonical_json_bytes(entry))) is not _SENTINEL

def mark_ir_entry_validated(entry: dict) -> None:
    """Record any IR entry as validated."""
    ir_validation_cache.put(hash(_canonical_json_bytes(entry)), True)

2. Decouple _validate_ir_request from conversion cache

Remove the _skip_tools_validation parameter. Instead, _validate_ir_request checks the IR validation cache directly for both tools AND messages:

def _validate_ir_request(self, data: dict) -> IRRequest:
    # Check tools against IR validation cache (not conversion cache)
    tools = data.get("tools", [])
    new_tools = [t for t in tools if not is_ir_entry_validated(t)]
    
    # Check messages against same cache
    messages = data.get("messages", [])
    new_msgs = [m for m in messages if not is_ir_entry_validated(m)]
    
    # Validate only new entries...
    # Mark validated entries...

3. Simplify converter call sites

# Before (every converter):
ir_tools, _tools_cached = self._get_cached_tools_from_p(tools)
result = self._validate_ir_request(data, _skip_tools_validation=_tools_cached)
if not _tools_cached and tools:
    self._cache_tools_from_p(tools, result.get("tools", []))

# After:
ir_tools, _ = self._get_cached_tools_from_p(tools)
result = self._validate_ir_request(data)  # validation cache handles everything

4. Cross-converter sharing

Request 1: Anthropic → IR → OpenAI Chat
  IR validation: miss → validate tools + messages → cache all
  
Request 2: OpenAI Responses → IR → OpenAI Chat (same tools, overlapping messages)
  IR validation: ✅ HIT for all tools (same IR content, different source converter)
  IR validation: ✅ HIT for shared messages, validate only new ones

Acceptance criteria

  • Unified ir_validation_cache replaces validated_msg_cache
  • _skip_tools_validation parameter removed
  • _cache_tools_from_p no-op removed
  • 4 converter call sites simplified
  • Cross-converter validation sharing verified in tests
  • make test passes
  • No performance regression (benchmark)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestperformancePerformance optimization

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions