dc00a4f8f5
Wave 5-1 mypy adoption (D-058 relaxed-first): mypy 2.3.1, [tool.mypy] check_untyped_defs + no_implicit_optional + warn_unused_ignores + warn_redundant_casts + warn_unreachable, no overrides (every dep ships py.typed), files = ai_service + scripts/sandbox-agent.py + tests (not excluded). 215 errors → 0: ~140 fixed at source incl. genuine bugs found by the gate (LLMProvider protocol async-generator shape, SessionStore protocol sync/async mismatch, structured_reply genericity, get_args migrations, sandbox-agent Optional guard honesty, proctor test arg-order, dead ExecResult kwarg, 16 sqlmodel col() sites); 34 live suppressions scoped-coded + reasoned. Original 27 type:ignore dispositioned: 12 dead removed, 15 scoped (warn_unused_ignores self-eliminates the rest). Wired: scripts/typecheck.sh + package.json + turbo task + root ai:typecheck — pnpm typecheck now 5 tsc + mypy. Wave 5-2 recharts 2.15.4 → 3.10.1 (D-049/D-059): one breaking class (Tooltip formatter ValueType typing); 2 one-line inference fixes on the 2 chart files; no rollback needed (well inside the G-106 timebox). Deprecated direct dep resolved; uuid@9 gone from the tree. Build 248/248 with chart SSR markers verified in exported employer + dashboard pages. Wave 5-3 deprecated-transitive audit (G-109): 4 lockfile entries remain, all dev-only, none actionable within NFR budget — glob@7.2.3 + inflight + rimraf@3 via Storybook docgen chain (flat-cache@3); glob@10.5.0 via c8→test-exclude (D-052-binding coverage tool — swapping it is out of budget). Python: no EOL packages; zero-warning regime under filterwarnings=error is the machine-checked proof. Counts: 670 py / 44 web / 64 cli, zero warnings, G-104 allowlist still exactly 2. ---ci--- phase: 5 milestone: v0.7 status: execute ---/ci---
48 lines
2.0 KiB
Python
48 lines
2.0 KiB
Python
"""Lab agent tests — scenario-driven streaming feedback (REQ-2-007)."""
|
|
|
|
from ai_service.agents.lab import LabAgent
|
|
from ai_service.config import Settings
|
|
from ai_service.corpus.learner_context import get_learner_context
|
|
from ai_service.corpus.telemetry import get_lab_scenario, summarize_scenario
|
|
from ai_service.llm.mock import MockProvider
|
|
|
|
|
|
def make_lab() -> LabAgent:
|
|
return LabAgent(MockProvider(), Settings(provider="mock"))
|
|
|
|
|
|
def test_system_prompt_names_lab_persona():
|
|
prompt = make_lab().system_prompt(get_learner_context())
|
|
assert "Lab" in prompt
|
|
assert "Alex Rivera" in prompt
|
|
assert "telemetry" in prompt.lower()
|
|
|
|
|
|
async def test_stream_feedback_mentions_scenario_events():
|
|
"""Mock-scripted: feedback text derives from scenario timeline input —
|
|
distinct scenarios produce distinct (deterministic) replies."""
|
|
lab = make_lab()
|
|
ctx = get_learner_context()
|
|
strong = get_lab_scenario("lab-scenario-strong")
|
|
struggling = get_lab_scenario("lab-scenario-struggling")
|
|
assert strong is not None, "seed scenario must resolve"
|
|
assert struggling is not None, "seed scenario must resolve"
|
|
|
|
# P5 scoped: corpus scenario stands in for the TraceDigest (dormant-
|
|
# corpus test seam — render_digest_timeline duck-types model_dump_json).
|
|
strong_reply = "".join([t async for t in lab.stream_feedback(strong, ctx)]) # type: ignore[arg-type]
|
|
struggling_reply = "".join([t async for t in lab.stream_feedback(struggling, ctx)]) # type: ignore[arg-type]
|
|
assert strong_reply
|
|
assert strong_reply != struggling_reply # scenario-driven, not canned
|
|
|
|
|
|
def test_build_evaluation_messages_carry_timeline():
|
|
lab = make_lab()
|
|
scenario = get_lab_scenario("lab-scenario-flagged")
|
|
assert scenario is not None, "seed scenario must resolve"
|
|
timeline = summarize_scenario(scenario)
|
|
messages = lab.build_messages(None, timeline, get_learner_context())
|
|
assert messages[0].role == "system"
|
|
assert "paste" in messages[-1].content
|
|
assert "2,400 chars" in messages[-1].content
|