更新功能

This commit is contained in:
2026-08-26 14:13:11 +08:00
parent beaa5dd9fe
commit a508e6f07e
47 changed files with 5616 additions and 520 deletions
+62 -1
View File
@@ -9,7 +9,7 @@ from types import SimpleNamespace
from unittest.mock import patch
from app.models.contracts import ChatMessage, MessagePart
from app.services.agent_service import AgentService, CDSL_TOOL_SCHEMA, RepeatedToolArgumentsError, StrictToolSchemaError, TOOL_SCHEMAS, ToolArgumentsError, engine_capability_manifest, normalize_image_analysis, parse_tool_arguments, response_language_instruction, system_prompt, tools_for_model, user_visible_error_message
from app.services.agent_service import AgentService, CDSL_TOOL_SCHEMA, RepeatedToolArgumentsError, StrictToolSchemaError, TOOL_SCHEMAS, ToolArgumentsError, engine_capability_manifest, get_repair_step_key, normalize_image_analysis, parse_tool_arguments, response_language_instruction, system_prompt, tools_for_model, user_visible_error_message
from app.services.engine_service import load_engine
from app.services.quality import QUALITY_RULE_TYPES
from app.services.library import CdslLibrary
@@ -79,6 +79,38 @@ class ParseToolArgumentsTests(unittest.TestCase):
with self.assertRaisesRegex(ToolArgumentsError, "JSON object"):
parse_tool_arguments('["not", "tool arguments"]')
class RepairStepKeyTests(unittest.TestCase):
def test_generation_and_patch_share_the_same_feature_step_budget(self) -> None:
state = {
"phase": "CDSL_REPAIR",
"feature_plan": {
"plan_id": "plan_1",
"nodes": [
{"id": "boss", "status": "ready"},
{"id": "hole", "status": "waiting_for_selection"},
],
},
}
self.assertEqual(
get_repair_step_key(state, "generate_cdsl_model"),
get_repair_step_key(state, "patch_cdsl_model"),
)
def test_completed_nodes_do_not_change_the_active_step_key(self) -> None:
state = {
"phase": "CDSL_REPAIR",
"feature_plan": {
"plan_id": "plan_1",
"nodes": [
{"id": "base", "status": "completed"},
{"id": "boss", "status": "ready"},
],
},
}
self.assertEqual(get_repair_step_key(state, "generate_cdsl_model"), "plan:plan_1:boss")
def test_recovers_only_the_known_premature_cdsl_wrapper_close(self) -> None:
payload = parse_tool_arguments(
'{"cdsl":{"schema":"cad.cdsl.llm.v1"}}, "summary":"fixed envelope"}',
@@ -88,6 +120,28 @@ class ParseToolArgumentsTests(unittest.TestCase):
self.assertEqual(payload["summary"], "fixed envelope")
self.assertEqual(payload["cdsl"], {"schema": "cad.cdsl.llm.v1"})
def test_recovers_trailing_cdsl_metadata_after_a_complete_envelope(self) -> None:
payload = parse_tool_arguments(
'{"cdsl":{"schema":"cad.cdsl.llm.v1"},"summary":"fixed envelope",'
'"assumptions":["metric"]}, "summary":"repeated envelope",'
'"assumptions":["metric"]}',
recover_cdsl_wrapper=True,
)
self.assertEqual(payload, {
"cdsl": {"schema": "cad.cdsl.llm.v1"},
"summary": "fixed envelope",
"assumptions": ["metric"],
})
def test_rejects_a_second_cdsl_payload_after_a_complete_envelope(self) -> None:
with self.assertRaisesRegex(ToolArgumentsError, "trailing content"):
parse_tool_arguments(
'{"cdsl":{"schema":"cad.cdsl.llm.v1"},"summary":"original",'
'"assumptions":[]}, "cdsl":{"schema":"different"}}',
recover_cdsl_wrapper=True,
)
def test_does_not_recover_arbitrary_trailing_tool_content(self) -> None:
with self.assertRaisesRegex(ToolArgumentsError, "trailing content"):
parse_tool_arguments(
@@ -123,6 +177,13 @@ class ParseToolArgumentsTests(unittest.TestCase):
rule_type = verification["properties"]["rules"]["items"]["properties"]["type"]
self.assertEqual(set(rule_type["enum"]), set(QUALITY_RULE_TYPES))
def test_verification_schema_requires_feature_for_feature_scoped_rules(self) -> None:
generate_tool = next(tool for tool in TOOL_SCHEMAS if tool["function"]["name"] == "generate_cdsl_model")
rule = generate_tool["function"]["parameters"]["properties"]["verification"]["properties"]["rules"]["items"]
self.assertTrue(any("feature" in branch.get("then", {}).get("required", []) for branch in rule["allOf"]))
self.assertIn("overall_width", rule["properties"]["type"]["enum"])
self.assertIn("overall_height", rule["properties"]["type"]["enum"])
def test_strict_tool_schema_covers_generation_arguments(self) -> None:
tools = tools_for_model(ProviderModel("strict-model", strict_tool_schema=True))
strict_tools = [tool["function"]["name"] for tool in tools if tool["function"].get("strict")]