Add model-reviewed CAD experience pipeline
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
@@ -44,11 +46,10 @@ class CadRouterTests(unittest.TestCase):
|
||||
result = route_module.route(self.parse("做一个参数化旋钮", "--output", "scad"))
|
||||
self.assertEqual("cadam", result["selected_backend"])
|
||||
|
||||
def test_every_route_applies_caddesigner_ecip_and_reports_all_project_roles(self) -> None:
|
||||
def test_every_route_reports_only_runtime_project_roles(self) -> None:
|
||||
result = route_module.route(self.parse("生成一个 40mm 法兰", "--output", "step"))
|
||||
self.assertIn("caddesigner_ecip", result["workflow_profiles"])
|
||||
self.assertEqual(
|
||||
{"text-to-cad", "SimpleCADAPI", "CADAM", "CADDesigner-Code"},
|
||||
{"text-to-cad", "SimpleCADAPI", "CADAM"},
|
||||
set(result["project_contributions"]),
|
||||
)
|
||||
|
||||
@@ -63,6 +64,229 @@ class CadRouterTests(unittest.TestCase):
|
||||
selected = next(item for item in result["backend_scores"] if item["backend"] == "build123d")
|
||||
self.assertIn("explicit backend override", selected["reasons"])
|
||||
|
||||
def test_generalized_experience_is_loaded_without_case_geometry(self) -> None:
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": False,
|
||||
"absolute_coordinates_allowed": False,
|
||||
"single_case_promotion_allowed": False,
|
||||
"llm_semantic_review_required": True,
|
||||
"draft_evidence_verified": True,
|
||||
"router_consumable": True,
|
||||
},
|
||||
"experiences": [
|
||||
{
|
||||
"id": "constraint.coaxial_stack",
|
||||
"kind": "constraint",
|
||||
"scope": ["flanged_hub_adapter"],
|
||||
"when": {"relation": "coaxial_stack"},
|
||||
"guidance": "Preserve the semantic relationship.",
|
||||
"support": 40,
|
||||
"confidence": 0.95,
|
||||
}
|
||||
],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
library = Path(directory) / "library.json"
|
||||
library.write_text(json.dumps(payload), encoding="utf-8")
|
||||
result = route_module.route(
|
||||
self.parse(
|
||||
"根据经验生成一个法兰轴套",
|
||||
"--output",
|
||||
"step",
|
||||
"--experience-library",
|
||||
str(library),
|
||||
"--experience-family",
|
||||
"flanged_hub_adapter",
|
||||
)
|
||||
)
|
||||
context = result["experience_context"]
|
||||
self.assertEqual("flanged_hub_adapter", context["family"])
|
||||
self.assertEqual(1, len(context["experiences"]))
|
||||
self.assertFalse(context["policy"]["contains_instance_parameters"])
|
||||
|
||||
def test_case_parameters_are_rejected_as_experience(self) -> None:
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": False,
|
||||
"absolute_coordinates_allowed": False,
|
||||
"single_case_promotion_allowed": False,
|
||||
"llm_semantic_review_required": True,
|
||||
"draft_evidence_verified": True,
|
||||
"router_consumable": True,
|
||||
},
|
||||
"parameters": {"base_outer_diameter": 91.0},
|
||||
"experiences": [],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
library = Path(directory) / "library.json"
|
||||
library.write_text(json.dumps(payload), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "forbidden instance key"):
|
||||
route_module.load_experience_library(library)
|
||||
|
||||
def test_candidate_experience_is_visible_but_not_consumed(self) -> None:
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": False,
|
||||
"absolute_coordinates_allowed": False,
|
||||
"single_case_promotion_allowed": False,
|
||||
"llm_semantic_review_required": True,
|
||||
"draft_evidence_verified": True,
|
||||
"router_consumable": True,
|
||||
},
|
||||
"experiences": [],
|
||||
"candidate_experiences": [
|
||||
{
|
||||
"id": "motif.flange.base_flange+hollow_sleeve",
|
||||
"kind": "feature_motif",
|
||||
"scope": ["flange"],
|
||||
"when": {"features": ["base_flange", "hollow_sleeve"]},
|
||||
"guidance": "Candidate only.",
|
||||
"support": 1,
|
||||
"confidence": 1.0,
|
||||
"promotion_state": "candidate",
|
||||
}
|
||||
],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
library = Path(directory) / "library.json"
|
||||
library.write_text(json.dumps(payload), encoding="utf-8")
|
||||
context = route_module.load_experience_library(
|
||||
library, "flange", ["base_flange", "hollow_sleeve"]
|
||||
)
|
||||
self.assertEqual([], context["experiences"])
|
||||
|
||||
def test_unreviewed_statistical_library_is_rejected(self) -> None:
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": False,
|
||||
"absolute_coordinates_allowed": False,
|
||||
"single_case_promotion_allowed": False,
|
||||
},
|
||||
"experiences": [],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
library = Path(directory) / "library.json"
|
||||
library.write_text(json.dumps(payload), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "LLM semantic review"):
|
||||
route_module.load_experience_library(library)
|
||||
|
||||
def test_request_automatically_builds_experience_query_and_design_plan(self) -> None:
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": False,
|
||||
"absolute_coordinates_allowed": False,
|
||||
"single_case_promotion_allowed": False,
|
||||
"llm_semantic_review_required": True,
|
||||
"draft_evidence_verified": True,
|
||||
"router_consumable": True,
|
||||
},
|
||||
"experiences": [
|
||||
{
|
||||
"id": "motif.base_flange+hollow_sleeve",
|
||||
"kind": "feature_motif",
|
||||
"scope": ["flanged_hub_adapter"],
|
||||
"when": {"features": ["base_flange", "hollow_sleeve"]},
|
||||
"guidance": "Treat these feature roles as a reusable composition.",
|
||||
"support": 24,
|
||||
"confidence": 0.9,
|
||||
}
|
||||
],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
library = Path(directory) / "library.json"
|
||||
library.write_text(json.dumps(payload), encoding="utf-8")
|
||||
result = route_module.route(
|
||||
self.parse(
|
||||
"生成一个带法兰轴套和中心通孔",
|
||||
"--output",
|
||||
"step",
|
||||
"--experience-library",
|
||||
str(library),
|
||||
)
|
||||
)
|
||||
context = result["experience_context"]
|
||||
plan = result["design_plan"]
|
||||
self.assertEqual("flanged_hub_adapter", context["family"])
|
||||
self.assertIn("base_flange", context["requested_features"])
|
||||
self.assertIn("hollow_sleeve", context["requested_features"])
|
||||
self.assertEqual(1, len(context["experiences"]))
|
||||
self.assertEqual("matched", plan["experience_status"])
|
||||
self.assertEqual(
|
||||
"motif.base_flange+hollow_sleeve",
|
||||
plan["generalized_methods"][0]["id"],
|
||||
)
|
||||
|
||||
def test_step_edit_uses_source_and_library_not_private_case(self) -> None:
|
||||
result = route_module.route(
|
||||
self.parse(
|
||||
"把中心孔径调整大一些",
|
||||
"--edit-source",
|
||||
"/tmp/source.step",
|
||||
"--output",
|
||||
"step",
|
||||
)
|
||||
)
|
||||
plan = result["design_plan"]
|
||||
self.assertEqual("modify", plan["operation"])
|
||||
self.assertEqual(
|
||||
"direct_step_feature_rebuild",
|
||||
plan["edit_context"]["modification_mode"],
|
||||
)
|
||||
forbidden = plan["edit_context"]["forbidden_knowledge_sources"]
|
||||
self.assertIn("cad-experience-plugin/parser/output", forbidden)
|
||||
self.assertNotIn("private_case", plan["edit_context"])
|
||||
self.assertEqual(
|
||||
"pending_source_classification", plan["experience_status"]
|
||||
)
|
||||
self.assertTrue(
|
||||
plan["experience_query_plan"]["requires_source_inspection"]
|
||||
)
|
||||
|
||||
def test_native_generator_is_preferred_for_modification(self) -> None:
|
||||
result = route_module.route(
|
||||
self.parse(
|
||||
"把孔径改为 12mm",
|
||||
"--edit-source",
|
||||
"/tmp/model.py",
|
||||
"--output",
|
||||
"step",
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
"native_parameter_edit",
|
||||
result["design_plan"]["edit_context"]["modification_mode"],
|
||||
)
|
||||
|
||||
def test_parser_staging_files_are_rejected_as_edit_sources(self) -> None:
|
||||
parser_source = (
|
||||
Path(__file__).resolve().parents[5]
|
||||
/ "cad-experience-plugin"
|
||||
/ "parser"
|
||||
/ "input"
|
||||
/ "part.step"
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "cannot use"):
|
||||
route_module.route(
|
||||
self.parse(
|
||||
"修改这个零件",
|
||||
"--edit-source",
|
||||
str(parser_source),
|
||||
"--output",
|
||||
"step",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user