f3071e4b79
Task 5-2-01: prompts/examiner.py (Socratic oral-defense examiner; one question per
turn; grounded in TraceDigest + variant statement — never raw trace, never learner id,
D-028 mirror; rubric internals never revealed) + agents/examiner.py — ExaminerAgent
(next_question for the SSE pipeline; final_verdict -> DefenseVerdict via the D-020
defense). BOUNDARY: the examiner is a text agent and imports NO voice/ (STT/TTS belong
to the endpoints; integrity signals computed from turn metadata — A-109). Registry
registers all seven agents centrally (G-4); registry test updated six -> seven.
6 examiner tests (digest-grounded prompt w/o learner id; D-020 retry; 7-agent roster;
boundary import scan). Suite 367 green; ruff clean.
---ci---
phase: 5
milestone: v0.3
status: execute
requirements: {covered: [REQ-3-006], partial: []}
---/ci---
137 lines
4.5 KiB
Python
137 lines
4.5 KiB
Python
"""Agent registry tests — register/get round-trip, error paths (G-4), builtins."""
|
|
|
|
|
|
import pytest
|
|
|
|
from ai_service.agents.base import BaseAgent
|
|
from ai_service.agents.coach import CoachAgent
|
|
from ai_service.agents.registry import (
|
|
AgentRegistry,
|
|
DuplicateAgentError,
|
|
UnknownAgentError,
|
|
register_builtin_agents,
|
|
)
|
|
from ai_service.agents.tutor import TutorAgent
|
|
from ai_service.config import Settings
|
|
from ai_service.llm.mock import MockProvider
|
|
|
|
|
|
class DummyAgent(BaseAgent):
|
|
name = "dummy"
|
|
|
|
def system_prompt(self, learner_context=None) -> str:
|
|
return "dummy"
|
|
|
|
|
|
def make_factory():
|
|
def factory(provider, settings):
|
|
return DummyAgent(provider, settings)
|
|
return factory
|
|
|
|
|
|
def test_register_and_get():
|
|
registry = AgentRegistry()
|
|
registry.register("dummy", make_factory())
|
|
agent = registry.get(MockProvider(), Settings(provider="mock"), "dummy")
|
|
assert isinstance(agent, DummyAgent)
|
|
assert agent.name == "dummy"
|
|
|
|
|
|
def test_unknown_agent_raises():
|
|
registry = AgentRegistry()
|
|
with pytest.raises(UnknownAgentError):
|
|
registry.get(MockProvider(), Settings(provider="mock"), "ghost")
|
|
|
|
|
|
def test_duplicate_registration_raises():
|
|
registry = AgentRegistry()
|
|
registry.register("dummy", make_factory())
|
|
with pytest.raises(DuplicateAgentError):
|
|
registry.register("dummy", make_factory())
|
|
|
|
|
|
def test_names_sorted():
|
|
registry = AgentRegistry()
|
|
registry.register("zeta", make_factory())
|
|
registry.register("alpha", make_factory())
|
|
assert registry.names() == ["alpha", "zeta"]
|
|
|
|
|
|
def test_builtin_agents_register_and_resolve():
|
|
registry = AgentRegistry()
|
|
register_builtin_agents(registry)
|
|
assert set(registry.names()) >= {"coach", "tutor"}
|
|
settings = Settings(provider="mock")
|
|
coach = registry.get(MockProvider(), settings, "coach")
|
|
tutor = registry.get(MockProvider(), settings, "tutor")
|
|
assert isinstance(coach, CoachAgent)
|
|
assert isinstance(tutor, TutorAgent)
|
|
|
|
|
|
def test_lab_and_assessor_resolve_via_registry():
|
|
"""Phase 4: lab + assessor registered centrally (Task 4-3-01)."""
|
|
from ai_service.agents.assessor import AssessorAgent
|
|
from ai_service.agents.lab import LabAgent
|
|
|
|
registry = AgentRegistry()
|
|
register_builtin_agents(registry)
|
|
assert {"lab", "assessor"} <= set(registry.names())
|
|
settings = Settings(provider="mock")
|
|
lab = registry.get(MockProvider(), settings, "lab")
|
|
assessor = registry.get(MockProvider(), settings, "assessor")
|
|
assert isinstance(lab, LabAgent)
|
|
assert isinstance(assessor, AssessorAgent)
|
|
|
|
|
|
def test_proctor_and_mentor_resolve_via_registry():
|
|
"""Phase 5: proctor + mentor registered centrally (Tasks 5-1-02/5-2-01)."""
|
|
from ai_service.agents.mentor import MentorAgent
|
|
from ai_service.agents.proctor import ProctorAgent
|
|
|
|
registry = AgentRegistry()
|
|
register_builtin_agents(registry)
|
|
settings = Settings(provider="mock")
|
|
proctor = registry.get(MockProvider(), settings, "proctor")
|
|
mentor = registry.get(MockProvider(), settings, "mentor")
|
|
assert isinstance(proctor, ProctorAgent)
|
|
assert isinstance(mentor, MentorAgent)
|
|
|
|
|
|
def test_registry_resolves_all_seven_agents():
|
|
"""Must-Have (Phase 5): the full roster — six tutors + the Examiner."""
|
|
from ai_service.agents.assessor import AssessorAgent
|
|
from ai_service.agents.coach import CoachAgent
|
|
from ai_service.agents.examiner import ExaminerAgent
|
|
from ai_service.agents.lab import LabAgent
|
|
from ai_service.agents.mentor import MentorAgent
|
|
from ai_service.agents.proctor import ProctorAgent
|
|
from ai_service.agents.tutor import TutorAgent
|
|
|
|
registry = AgentRegistry()
|
|
register_builtin_agents(registry)
|
|
assert registry.names() == [
|
|
"assessor", "coach", "examiner", "lab", "mentor", "proctor", "tutor",
|
|
]
|
|
settings = Settings(provider="mock")
|
|
expected = {
|
|
"coach": CoachAgent,
|
|
"tutor": TutorAgent,
|
|
"lab": LabAgent,
|
|
"assessor": AssessorAgent,
|
|
"proctor": ProctorAgent,
|
|
"mentor": MentorAgent,
|
|
"examiner": ExaminerAgent,
|
|
}
|
|
for name, cls in expected.items():
|
|
agent = registry.get(MockProvider(), settings, name)
|
|
assert isinstance(agent, cls), f"{name} resolved to {type(agent).__name__}"
|
|
assert agent.name == name
|
|
|
|
|
|
def test_builtin_registration_is_idempotent_safe():
|
|
"""Duplicate registration raises — builtin bootstrap must be called once."""
|
|
registry = AgentRegistry()
|
|
register_builtin_agents(registry)
|
|
with pytest.raises(DuplicateAgentError):
|
|
register_builtin_agents(registry)
|