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
Parent: #276
Problem
IR validation caching is currently fragmented and coupled:
_skip_tools_validationflag) — no independent IR-level cachevalidated_msg_cache— correctly decoupled, keyed by IR content, no converter tagThe correct architecture: one unified IR validation cache that covers all IR entry types, completely decoupled from conversion caches.
Architecture: Hub and Spoke
Implication rules
Current state
tool_entry_cache(with tag)validated_msg_cache(no tag)Proposed change
1. Merge
validated_msg_cacheinto a unifiedir_validation_cacheReplace the separate
validated_msg_cachewith a single cache that handles all IR types:2. Decouple
_validate_ir_requestfrom conversion cacheRemove the
_skip_tools_validationparameter. Instead,_validate_ir_requestchecks the IR validation cache directly for both tools AND messages:3. Simplify converter call sites
4. Cross-converter sharing
Acceptance criteria
ir_validation_cachereplacesvalidated_msg_cache_skip_tools_validationparameter removed_cache_tools_from_pno-op removedmake testpasses