Files
OpenJarvis/tests/learning/test_icl_updates.py
T
2026-03-12 17:29:39 +00:00

206 lines
6.9 KiB
Python

"""Tests for the new ICLUpdaterPolicy versioned example database features."""
from __future__ import annotations
from openjarvis.learning.icl_updater import ICLUpdaterPolicy
class TestICLUpdaterAddExample:
def test_add_example_quality_gate(self):
"""Examples below the quality threshold should be rejected."""
policy = ICLUpdaterPolicy(min_score=0.7)
accepted = policy.add_example(
query="low quality",
response="bad answer",
outcome=0.3,
)
assert accepted is False
assert len(policy.example_db) == 0
assert policy.version == 0
def test_add_example_stores(self):
"""Examples above the threshold should be stored."""
policy = ICLUpdaterPolicy(min_score=0.5)
accepted = policy.add_example(
query="What is 2+2?",
response="4",
outcome=0.9,
metadata={"source": "test"},
)
assert accepted is True
assert len(policy.example_db) == 1
ex = policy.example_db[0]
assert ex["query"] == "What is 2+2?"
assert ex["response"] == "4"
assert ex["outcome"] == 0.9
assert ex["metadata"] == {"source": "test"}
assert ex["version"] == 1
def test_add_example_at_threshold(self):
"""An example exactly at the threshold should be accepted."""
policy = ICLUpdaterPolicy(min_score=0.7)
accepted = policy.add_example(
query="borderline",
response="ok",
outcome=0.7,
)
assert accepted is True
assert len(policy.example_db) == 1
class TestICLUpdaterRollback:
def test_rollback(self):
"""Rollback should remove examples added after the given version."""
policy = ICLUpdaterPolicy(min_score=0.5)
policy.add_example("q1", "r1", 0.8)
policy.add_example("q2", "r2", 0.9)
v2 = policy.version
assert v2 == 2
policy.add_example("q3", "r3", 0.85)
policy.add_example("q4", "r4", 0.95)
assert len(policy.example_db) == 4
assert policy.version == 4
policy.rollback(v2)
assert policy.version == 2
assert len(policy.example_db) == 2
queries = [ex["query"] for ex in policy.example_db]
assert "q1" in queries
assert "q2" in queries
assert "q3" not in queries
assert "q4" not in queries
def test_rollback_to_zero(self):
"""Rollback to version 0 should remove all examples."""
policy = ICLUpdaterPolicy(min_score=0.5)
policy.add_example("q1", "r1", 0.8)
policy.add_example("q2", "r2", 0.9)
policy.rollback(0)
assert policy.version == 0
assert len(policy.example_db) == 0
class TestICLUpdaterGetExamples:
def test_get_examples(self):
"""Should return examples sorted by outcome."""
policy = ICLUpdaterPolicy(min_score=0.5)
policy.add_example("math q1", "r1", 0.7)
policy.add_example("math q2", "r2", 0.95)
policy.add_example("math q3", "r3", 0.8)
results = policy.get_examples(top_k=2)
assert len(results) == 2
assert results[0]["outcome"] >= results[1]["outcome"]
assert results[0]["outcome"] == 0.95
def test_get_examples_with_query_class(self):
"""Filtering by query_class should narrow results."""
policy = ICLUpdaterPolicy(min_score=0.5)
policy.add_example("math problem", "42", 0.9)
policy.add_example("code review", "looks good", 0.85)
policy.add_example("math equation", "x=5", 0.8)
results = policy.get_examples(query_class="math", top_k=10)
assert len(results) == 2
assert all("math" in ex["query"].lower() for ex in results)
def test_get_examples_empty(self):
"""No examples should return empty list."""
policy = ICLUpdaterPolicy(min_score=0.5)
results = policy.get_examples(top_k=5)
assert results == []
class TestICLUpdaterVersioning:
def test_version_increments(self):
"""Each successful add should increment the version."""
policy = ICLUpdaterPolicy(min_score=0.5)
assert policy.version == 0
policy.add_example("q1", "r1", 0.8)
assert policy.version == 1
policy.add_example("q2", "r2", 0.9)
assert policy.version == 2
# Rejected add should NOT increment version
policy.add_example("q3", "r3", 0.1)
assert policy.version == 2
def test_max_examples_limit(self):
"""Adding beyond max_examples should trim the oldest entries."""
policy = ICLUpdaterPolicy(min_score=0.5, max_examples=3)
policy.add_example("q1", "r1", 0.8)
policy.add_example("q2", "r2", 0.85)
policy.add_example("q3", "r3", 0.9)
assert len(policy.example_db) == 3
# Adding a 4th should trim q1 (the oldest)
policy.add_example("q4", "r4", 0.95)
assert len(policy.example_db) == 3
queries = [ex["query"] for ex in policy.example_db]
assert "q1" not in queries
assert "q4" in queries
assert "q2" in queries
assert "q3" in queries
class TestICLUpdaterAutoApply:
def test_auto_apply_default_false(self):
"""auto_apply should default to False."""
policy = ICLUpdaterPolicy()
assert policy._auto_apply is False
def test_auto_apply_configurable(self):
"""auto_apply can be set via constructor."""
policy = ICLUpdaterPolicy(auto_apply=True)
assert policy._auto_apply is True
class TestICLUpdaterBackwardCompat:
def test_existing_update_still_works(self):
"""The original update() method must still function."""
from dataclasses import dataclass, field
from typing import Optional
from openjarvis.core.types import StepType, TraceStep
@dataclass
class _MockTrace:
query: str = ""
model: str = "model-a"
outcome: str = "success"
feedback: Optional[float] = 0.8
steps: list = field(default_factory=list)
total_latency_seconds: float = 1.0
class _MockTraceStore:
def __init__(self, traces):
self._traces = traces
def list_traces(self):
return self._traces
step = TraceStep(
step_type=StepType.TOOL_CALL,
timestamp=0.0,
input={"tool": "calculator"},
output={"result": "ok"},
metadata={"tool_name": "calculator"},
)
traces = [
_MockTrace(
query="What is 2+2?",
outcome="success",
feedback=0.9,
steps=[step],
),
]
policy = ICLUpdaterPolicy(min_score=0.5)
store = _MockTraceStore(traces)
result = policy.update(store)
assert len(result["examples"]) == 1
assert result["examples"][0]["query"] == "What is 2+2?"