mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-29 18:40:38 +00:00
178 lines
5.8 KiB
Python
178 lines
5.8 KiB
Python
"""Live integration tests for the spec-search subsystem.
|
|
|
|
These tests use REAL API calls (CloudEngine with Anthropic) and real
|
|
TraceStore data. They are gated on the ``cloud`` marker — skip them
|
|
with ``pytest -m "not cloud"``.
|
|
|
|
Requires:
|
|
- ANTHROPIC_API_KEY environment variable set
|
|
- TraceStore at ~/.openjarvis/traces.db with some traces
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
# Skip entire module if no API key
|
|
pytestmark = pytest.mark.cloud
|
|
|
|
|
|
@pytest.fixture
|
|
def anthropic_key():
|
|
key = os.environ.get("ANTHROPIC_API_KEY")
|
|
if not key:
|
|
pytest.skip("ANTHROPIC_API_KEY not set")
|
|
return key
|
|
|
|
|
|
@pytest.fixture
|
|
def cloud_engine(anthropic_key):
|
|
from openjarvis.engine.cloud import CloudEngine
|
|
|
|
return CloudEngine()
|
|
|
|
|
|
@pytest.fixture
|
|
def real_trace_store():
|
|
from openjarvis.traces.store import TraceStore
|
|
|
|
db_path = Path.home() / ".openjarvis" / "traces.db"
|
|
if not db_path.exists():
|
|
pytest.skip("No traces.db found at ~/.openjarvis/")
|
|
store = TraceStore(db_path)
|
|
if store.count() < 5:
|
|
pytest.skip("Need at least 5 traces for live test")
|
|
return store
|
|
|
|
|
|
class TestCloudEngineDirectCall:
|
|
"""Verify CloudEngine works with real API."""
|
|
|
|
def test_generate_produces_content(self, cloud_engine) -> None:
|
|
from openjarvis.core.types import Message, Role
|
|
|
|
result = cloud_engine.generate(
|
|
messages=[
|
|
Message(
|
|
role=Role.USER,
|
|
content="What is 2+2? Answer with just the number.",
|
|
)
|
|
],
|
|
model="claude-sonnet-4-6",
|
|
max_tokens=10,
|
|
)
|
|
assert "content" in result
|
|
assert "4" in result["content"]
|
|
assert result.get("cost_usd", 0) > 0
|
|
|
|
|
|
class TestTeacherAgentLive:
|
|
"""Test TeacherAgent with a real CloudEngine."""
|
|
|
|
def test_teacher_agent_single_turn(self, cloud_engine) -> None:
|
|
from openjarvis.learning.spec_search.diagnose.teacher_agent import (
|
|
TeacherAgent,
|
|
)
|
|
|
|
agent = TeacherAgent(
|
|
engine=cloud_engine,
|
|
model="claude-sonnet-4-6",
|
|
tools=[],
|
|
max_turns=2,
|
|
max_cost_usd=0.50,
|
|
)
|
|
result = agent.run(
|
|
"You are being tested. Simply respond with: 'TeacherAgent works.'",
|
|
system_prompt="You are a test assistant. Follow instructions exactly.",
|
|
)
|
|
assert result.content
|
|
assert result.turns >= 1
|
|
assert result.total_cost_usd > 0
|
|
print(f" Teacher response: {result.content[:100]}")
|
|
print(f" Cost: ${result.total_cost_usd:.4f}, Turns: {result.turns}")
|
|
|
|
|
|
class TestDiagnosisRunnerLive:
|
|
"""Test DiagnosisRunner with real CloudEngine + real traces."""
|
|
|
|
def test_diagnosis_produces_output(
|
|
self, cloud_engine, real_trace_store, tmp_path
|
|
) -> None:
|
|
from openjarvis.learning.spec_search.diagnose.runner import (
|
|
DiagnosisRunner,
|
|
)
|
|
|
|
# Create minimal config
|
|
config_dir = tmp_path / "oj_home"
|
|
config_dir.mkdir()
|
|
(config_dir / "config.toml").write_text("[learning]\nenabled = true\n")
|
|
|
|
session_dir = tmp_path / "session"
|
|
runner = DiagnosisRunner(
|
|
teacher_engine=cloud_engine,
|
|
teacher_model="claude-sonnet-4-6",
|
|
trace_store=real_trace_store,
|
|
benchmark_samples=[],
|
|
student_runner=lambda q, **kw: type(
|
|
"R", (), {"content": "mock", "score": 0.5}
|
|
)(),
|
|
judge=type("J", (), {"score_trace": lambda self, t: (0.5, "mock")})(),
|
|
session_dir=session_dir,
|
|
session_id="live-test-001",
|
|
config={
|
|
"config_path": config_dir / "config.toml",
|
|
"openjarvis_home": config_dir,
|
|
},
|
|
max_turns=5, # Keep it cheap
|
|
max_cost_usd=1.0,
|
|
)
|
|
|
|
result = runner.run()
|
|
|
|
# Diagnosis should produce output
|
|
assert result.diagnosis_md, "Diagnosis produced no markdown"
|
|
assert len(result.diagnosis_md) > 50, "Diagnosis too short"
|
|
print(f" Diagnosis length: {len(result.diagnosis_md)} chars")
|
|
print(f" Clusters found: {len(result.clusters)}")
|
|
print(f" Cost: ${result.cost_usd:.4f}")
|
|
print(f" Tool calls: {len(result.tool_call_records)}")
|
|
|
|
# Artifacts should exist
|
|
assert (session_dir / "diagnosis.md").exists()
|
|
assert (session_dir / "teacher_traces" / "diagnose.jsonl").exists()
|
|
|
|
# Print first 200 chars of diagnosis
|
|
print(f" Diagnosis preview: {result.diagnosis_md[:200]}...")
|
|
|
|
|
|
class TestColdStartLive:
|
|
"""Test cold start behavior with real trace store."""
|
|
|
|
def test_orchestrator_cold_start_with_no_feedback(
|
|
self, cloud_engine, real_trace_store, tmp_path
|
|
) -> None:
|
|
"""With 373 traces but 0 feedback, the orchestrator should handle
|
|
this gracefully — either by running (traces > 20) or by giving
|
|
a clear message about what's missing."""
|
|
from openjarvis.learning.spec_search.gate.cold_start import (
|
|
check_benchmark_ready,
|
|
check_readiness,
|
|
)
|
|
|
|
# Check trace readiness
|
|
trace_ready = check_readiness(real_trace_store, min_traces=20)
|
|
print(f" Trace readiness: {trace_ready.ready} ({trace_ready.message})")
|
|
|
|
# Check benchmark readiness
|
|
bench_ready = check_benchmark_ready(
|
|
real_trace_store, min_feedback=0.7, min_samples=10
|
|
)
|
|
print(f" Benchmark readiness: {bench_ready.ready} ({bench_ready.message})")
|
|
|
|
# With 373 traces but 0 feedback: traces ready, benchmark not ready
|
|
assert trace_ready.ready, "Should have enough traces"
|
|
assert not bench_ready.ready, "Should not have enough high-feedback traces"
|