Files
cdsl-cad/backend/tests/test_design_intent_flow.py
T
2026-08-24 14:52:08 +08:00

125 lines
6.7 KiB
Python

from __future__ import annotations
import asyncio
import json
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend"))
from app.services.agent_service import AgentService # noqa: E402
from app.services.engine_service import load_engine # noqa: E402
from app.services.library import CdslLibrary # noqa: E402
from app.services.part_skills import PartSkillLibrary # noqa: E402
from app.services.storage import WorkspaceStore # noqa: E402
from app.settings import ProviderConfig, ProviderModel, Settings # noqa: E402
from tests.test_design_intent import mounting_plate_cdsl, mounting_plate_intent # noqa: E402
BACKEND = ROOT / "backend"
PART_SKILL_ROOT = BACKEND / "agent" / "skills" / "cad-engine" / "references" / "part-skills"
class DesignIntentFlowTests(unittest.TestCase):
def settings(self, root: Path) -> Settings:
provider = ProviderConfig("test", "Test", "https://example.invalid/v1", "test-key", (ProviderModel("test-model"),))
return Settings(
task_root=root / "tasks",
conversation_root=root / "conversations",
library_root=BACKEND / "cdsl_library",
engine_root=BACKEND / "engine" / "cdsl_engine",
llm_base_url=provider.base_url,
llm_api_key=provider.api_key,
llm_model="test-model",
llm_timeout_s=1,
default_provider_id="test",
providers=(provider,),
)
def test_design_intent_is_required_before_library_or_cdsl(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = self.settings(Path(directory))
store = WorkspaceStore(settings)
agent = AgentService(settings, store, CdslLibrary(settings), PartSkillLibrary(PART_SKILL_ROOT))
state = {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
searched, _ = asyncio.run(agent._run_tool("search_cdsl_library", {"query": "mounting plate"}, "", "mounting plate", [], intent_state=state))
generated, _ = asyncio.run(agent._run_tool("generate_cdsl_model", {"design_intent_id": "intent_aaaaaaaaaaaa", "cdsl": {}, "summary": "x", "assumptions": []}, "", "mounting plate", [], intent_state=state))
self.assertEqual(searched["code"], "DESIGN_INTENT_REQUIRED")
self.assertEqual(generated["code"], "DESIGN_INTENT_REQUIRED")
self.assertEqual(list(settings.task_root.glob("cad_*")), [])
def test_ready_plan_persists_before_build_and_links_success_revision(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = self.settings(Path(directory))
store = WorkspaceStore(settings)
skills = PartSkillLibrary(PART_SKILL_ROOT)
agent = AgentService(settings, store, CdslLibrary(settings), skills)
request = "Create a mounting plate with four holes and a center slot"
state = {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
planned, _ = asyncio.run(agent._run_tool(
"propose_design_intent",
{"intent": mounting_plate_intent(), "summary": "plate plan", "assumptions": []},
"", request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertTrue(planned["ok"])
task = store.read_task(planned["task_id"])
self.assertEqual(task["current_revision"], "")
planning_path = store.artifact_path(planned["task_id"], planned["design_intent_path"])
self.assertTrue(planning_path.is_file())
saved = json.loads(planning_path.read_text(encoding="utf-8"))
self.assertEqual(saved["part_skill_ids"], skills.select(request)["skill_ids"])
searched, _ = asyncio.run(agent._run_tool("search_cdsl_library", {"query": "plate"}, planned["task_id"], request, [], intent_state=state))
self.assertTrue(searched["ok"])
result, built = asyncio.run(agent._run_tool(
"generate_cdsl_model",
{"design_intent_id": planned["design_intent_id"], "cdsl": mounting_plate_cdsl(), "summary": "plate", "assumptions": []},
planned["task_id"], request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertTrue(result["ok"])
self.assertIsNotNone(built)
revision = store.read_task(planned["task_id"])["revisions"][-1]
self.assertEqual(revision["design_intent_id"], planned["design_intent_id"])
self.assertEqual(revision["design_intent_path"], planned["design_intent_path"])
audit = json.loads(store.artifact_path(planned["task_id"], revision["part_skills_path"]).read_text(encoding="utf-8"))
self.assertEqual(audit["design_intent_id"], planned["design_intent_id"])
self.assertEqual(audit["design_intent_assumptions"], [])
def test_blocked_plan_is_persisted_without_a_revision_and_new_plan_supersedes_it(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = self.settings(Path(directory))
store = WorkspaceStore(settings)
skills = PartSkillLibrary(PART_SKILL_ROOT)
agent = AgentService(settings, store, CdslLibrary(settings), skills)
request = "Create a mounting plate"
state = {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
blocked_intent = mounting_plate_intent()
blocked_intent["open_questions"] = [{"id": "thickness", "question": "Thickness?", "blocking": True}]
blocked_intent["status"] = "needs_clarification"
blocked, _ = asyncio.run(agent._run_tool(
"propose_design_intent",
{"intent": blocked_intent, "summary": "blocked", "assumptions": []},
"", request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertEqual(blocked["code"], "DESIGN_INTENT_BLOCKED")
task = store.read_task(blocked["task_id"])
self.assertEqual(task["revisions"], [])
self.assertEqual(task["design_intents"][0]["status"], "pending")
ready, _ = asyncio.run(agent._run_tool(
"propose_design_intent",
{"intent": mounting_plate_intent(), "summary": "ready", "assumptions": []},
blocked["task_id"], request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertTrue(ready["ok"])
task = store.read_task(blocked["task_id"])
self.assertEqual(task["design_intents"][0]["status"], "superseded")
self.assertEqual(task["design_intents"][-1]["status"], "accepted")