优化生成流程
This commit is contained in:
@@ -5,7 +5,9 @@ import base64
|
||||
from copy import deepcopy
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import secrets
|
||||
import sys
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -14,13 +16,10 @@ import httpx
|
||||
|
||||
from app.models.contracts import ChatMessage
|
||||
from app.services.engine_service import build_revision, load_engine, normalize_cdsl_for_engine, validate_cdsl
|
||||
from app.services.flange_sleeve_template import (
|
||||
TEMPLATE_ID as FLANGE_SLEEVE_TEMPLATE_ID,
|
||||
build_flange_sleeve_cdsl,
|
||||
flange_sleeve_plan_schema,
|
||||
)
|
||||
from app.services.cdsl_patch import CdslPatchError, apply_cdsl_patch
|
||||
from app.services.library import CdslLibrary
|
||||
from app.services.part_skills import PartSkillLibrary
|
||||
from app.services.quality import QUALITY_RULE_TYPES, validate_verification
|
||||
from app.services.sse import event
|
||||
from app.services.storage import WorkspaceStore, now_iso
|
||||
from app.settings import ProviderConfig, ProviderModel, Settings
|
||||
@@ -42,6 +41,14 @@ class RepeatedToolArgumentsError(RuntimeError):
|
||||
self.diagnostic_paths = diagnostic_paths or []
|
||||
|
||||
|
||||
class CdslRepairLimitError(RuntimeError):
|
||||
"""The direct CDSL repair budget is exhausted for this request."""
|
||||
|
||||
def __init__(self, diagnostic_paths: list[str]) -> None:
|
||||
super().__init__("CDSL repair limit reached")
|
||||
self.diagnostic_paths = diagnostic_paths
|
||||
|
||||
|
||||
def user_visible_error_message(error: Exception, user_text: str) -> str:
|
||||
if isinstance(error, StrictToolSchemaError) and any(
|
||||
"\u4e00" <= char <= "\u9fff" for char in str(user_text or "")
|
||||
@@ -61,6 +68,11 @@ def user_visible_error_message(error: Exception, user_text: str) -> str:
|
||||
"请检查所选模型的函数调用兼容性;若仍出现此错误,请关闭该模型的严格工具 schema 开关后再试。"
|
||||
+ diagnostics
|
||||
)
|
||||
if isinstance(error, CdslRepairLimitError):
|
||||
diagnostics = "、".join(error.diagnostic_paths)
|
||||
if any("\u4e00" <= char <= "\u9fff" for char in str(user_text or "")):
|
||||
return "模型重试达到四次 CDSL 修复上限,未创建新的成功 revision。每次 CDSL 校验失败的诊断已保存到:" + diagnostics + "。"
|
||||
return "The four-attempt CDSL repair limit was reached. Diagnostics were saved to: " + diagnostics + "."
|
||||
return str(error)
|
||||
|
||||
|
||||
@@ -84,7 +96,7 @@ def _repair_premature_tool_wrapper_close(source: str, parsed_value: Any, parsed_
|
||||
return None
|
||||
if candidate[candidate_end:].strip() or not isinstance(value, dict):
|
||||
return None
|
||||
if not set(value).issubset({"cdsl", "summary", "assumptions"}):
|
||||
if not set(value).issubset({"cdsl", "summary", "assumptions", "verification"}):
|
||||
return None
|
||||
if not isinstance(value.get("cdsl"), dict) or not isinstance(value.get("summary"), str):
|
||||
return None
|
||||
@@ -135,10 +147,38 @@ def invalid_tool_arguments_result(name: str, error: ToolArgumentsError) -> dict[
|
||||
}
|
||||
|
||||
|
||||
def invalid_cdsl_result(error: ValueError) -> dict[str, Any]:
|
||||
def _cdsl_error_details(error: Exception) -> dict[str, Any]:
|
||||
message = str(error)
|
||||
path_match = re.search(r"(?:at|path) (\$[^: ]*)", message)
|
||||
quality = getattr(error, "quality_report", None)
|
||||
if isinstance(quality, dict):
|
||||
return {
|
||||
"code": "VERIFICATION_FAILED",
|
||||
"path": "$.verification.rules",
|
||||
"kind": "verification",
|
||||
"repair_instruction": "Correct the CDSL feature geometry or the verification rule, then submit a local patch or complete replacement.",
|
||||
"quality": quality,
|
||||
}
|
||||
if "schema violation" in message:
|
||||
return {
|
||||
"code": "CDSL_SCHEMA_INVALID",
|
||||
"path": path_match.group(1) if path_match else "$",
|
||||
"kind": "schema",
|
||||
"repair_instruction": "Correct the field at the reported JSONPath using the authoritative CDSL schema.",
|
||||
}
|
||||
return {
|
||||
"code": "CDSL_RUNTIME_INVALID",
|
||||
"path": path_match.group(1) if path_match else "$",
|
||||
"kind": "runtime",
|
||||
"repair_instruction": "Correct the invalid CDSL structure, dependency, selector, or runtime parameter and retry.",
|
||||
}
|
||||
|
||||
|
||||
def invalid_cdsl_result(error: Exception) -> dict[str, Any]:
|
||||
details = _cdsl_error_details(error)
|
||||
return {
|
||||
"ok": False,
|
||||
"code": "INVALID_CDSL",
|
||||
**details,
|
||||
"message": (
|
||||
f"The submitted CDSL is incomplete or invalid: {error}. "
|
||||
"Read the authoritative local engine schema, then call generate_cdsl_model "
|
||||
@@ -149,7 +189,7 @@ def invalid_cdsl_result(error: ValueError) -> dict[str, Any]:
|
||||
|
||||
def user_visible_tool_message(result: dict[str, Any], user_text: str) -> str:
|
||||
code = str(result.get("code") or "")
|
||||
if code == "INVALID_CDSL":
|
||||
if code in {"CDSL_SCHEMA_INVALID", "CDSL_RUNTIME_INVALID", "VERIFICATION_FAILED"}:
|
||||
if any("\u4e00" <= char <= "\u9fff" for char in str(user_text or "")):
|
||||
return "CDSL 不符合 engine 的模型契约,正在请求模型按 schema 修正后重新生成。"
|
||||
return "The CDSL model does not match the engine contract. Asking the model to correct it and retry."
|
||||
@@ -185,14 +225,109 @@ def _cdsl_tool_schema() -> dict[str, Any]:
|
||||
schema_name = str(contract.get("cdsl_json_schema_file") or "")
|
||||
if not schema_name or Path(schema_name).name != schema_name:
|
||||
raise RuntimeError("Local engine contract has no valid CDSL JSON Schema path")
|
||||
return json.loads((engine_dir / schema_name).read_text(encoding="utf-8"))
|
||||
schema = json.loads((engine_dir / schema_name).read_text(encoding="utf-8"))
|
||||
supported_atomics = {
|
||||
str(item)
|
||||
for item in contract.get("runtime_supported_atomic_ids") or ()
|
||||
if str(item)
|
||||
}
|
||||
declared_atomics = set((contract.get("feature_atomic_ids") or {}).keys())
|
||||
if not supported_atomics or not supported_atomics.issubset(declared_atomics):
|
||||
raise RuntimeError("Engine capability contract has invalid runtime atomic ids")
|
||||
engine_parent = str(engine_dir.parent)
|
||||
if engine_parent not in sys.path:
|
||||
sys.path.insert(0, engine_parent)
|
||||
import cdsl_engine
|
||||
|
||||
registered_atomics = {str(item) for item in getattr(cdsl_engine, "SUPPORTED_ATOMIC_IDS", ())}
|
||||
supported_atomics &= registered_atomics
|
||||
if not supported_atomics:
|
||||
raise RuntimeError("Engine has no registered atomic executors in common with its capability contract")
|
||||
feature_atomic_definition = schema.get("$defs", {}).get("feature_atomic_ids")
|
||||
if not isinstance(feature_atomic_definition, dict):
|
||||
raise RuntimeError("Local CDSL JSON Schema has no feature atomic definition")
|
||||
feature_atomic_definition["enum"] = sorted(supported_atomics)
|
||||
profile_definition = schema.get("$defs", {}).get("profile_type")
|
||||
supported_profiles = {
|
||||
str(item)
|
||||
for item in contract.get("runtime_supported_profiles") or ()
|
||||
if str(item)
|
||||
}
|
||||
supported_profiles &= {str(item) for item in getattr(cdsl_engine, "SHAPE_GENERATORS", ())}
|
||||
if isinstance(profile_definition, dict) and supported_profiles:
|
||||
profile_definition["enum"] = sorted(supported_profiles)
|
||||
return schema
|
||||
except (OSError, json.JSONDecodeError, AttributeError) as error:
|
||||
raise RuntimeError("Local CDSL JSON Schema is unavailable or invalid") from error
|
||||
|
||||
|
||||
CDSL_TOOL_SCHEMA = _cdsl_tool_schema()
|
||||
FLANGE_SLEEVE_PLAN_SCHEMA = flange_sleeve_plan_schema()
|
||||
GENERATION_TOOL_NAMES = {"generate_cdsl_model", "generate_flange_sleeve_model"}
|
||||
GENERATION_TOOL_NAMES = {"generate_cdsl_model", "patch_cdsl_model"}
|
||||
|
||||
VERIFICATION_SCHEMA: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"maxItems": 32,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string", "minLength": 1},
|
||||
"type": {"enum": sorted(QUALITY_RULE_TYPES)},
|
||||
"feature": {"type": "string"},
|
||||
"expected": {
|
||||
"description": (
|
||||
"For bbox, use [dx, dy, dz], "
|
||||
"{min: [x, y, z], max: [x, y, z]}, or "
|
||||
"{x_min, x_max, y_min, y_max, z_min, z_max}."
|
||||
),
|
||||
},
|
||||
"tolerance": {"type": "number", "minimum": 0},
|
||||
"severity": {"enum": ["blocking", "warning", "informational"]},
|
||||
},
|
||||
"required": ["id", "type", "expected"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["rules"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
JSON_PATCH_OPERATION_SCHEMA: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"op": {"enum": ["add", "remove", "replace", "move", "copy", "test"]},
|
||||
"path": {"type": "string", "pattern": "^/"},
|
||||
"from": {"type": "string", "pattern": "^/"},
|
||||
"value": {},
|
||||
},
|
||||
"required": ["op", "path"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
|
||||
def engine_capability_manifest(settings: Settings) -> dict[str, Any]:
|
||||
"""Build the compact planner-facing capability contract from engine files."""
|
||||
load_engine(settings)
|
||||
try:
|
||||
profile = json.loads((settings.engine_root / "profile_schema.json").read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {"supported_profiles": [], "runtime_atomic_ids": [], "required_params": {}, "unsupported_profiles": []}
|
||||
atomic_contracts = profile.get("feature_atomic_ids") if isinstance(profile.get("feature_atomic_ids"), dict) else {}
|
||||
declared_atomics = {str(item) for item in profile.get("runtime_supported_atomic_ids") or atomic_contracts}
|
||||
registered_atomics = {str(item) for item in getattr(load_engine(settings), "SUPPORTED_ATOMIC_IDS", ())}
|
||||
supported_atomics = sorted(declared_atomics & registered_atomics)
|
||||
declared_profiles = {str(item) for item in profile.get("runtime_supported_profiles") or ()}
|
||||
registered_profiles = {str(item) for item in getattr(load_engine(settings), "SHAPE_GENERATORS", ())}
|
||||
return {
|
||||
"supported_profiles": sorted(declared_profiles & registered_profiles),
|
||||
"runtime_atomic_ids": supported_atomics,
|
||||
"required_params": {str(key): list(value.get("required_params") or []) for key, value in atomic_contracts.items() if isinstance(value, dict)},
|
||||
"unsupported_profiles": sorted(str(item) for item in profile.get("unsupported_profiles") or []),
|
||||
"verification_rule_types": sorted(QUALITY_RULE_TYPES),
|
||||
}
|
||||
|
||||
|
||||
TOOL_SCHEMAS: list[dict[str, Any]] = [
|
||||
@@ -285,29 +420,6 @@ TOOL_SCHEMAS: list[dict[str, Any]] = [
|
||||
"parameters": {"type": "object", "properties": {}, "additionalProperties": False},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "generate_flange_sleeve_model",
|
||||
"description": (
|
||||
"Generate a square or rectangular flange sleeve from a compact semantic plan. "
|
||||
"Use this preferred tool for a flange plate with a coaxial hollow tube, "
|
||||
"a stepped or tapered tip, four mounting holes, and counterbores. "
|
||||
"The backend's verified template creates all CDSL workplanes, sketches, "
|
||||
"feature dependencies, and feature parameters."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plan": FLANGE_SLEEVE_PLAN_SCHEMA,
|
||||
"summary": {"type": "string", "minLength": 1},
|
||||
"assumptions": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"required": ["plan", "summary", "assumptions"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -319,12 +431,32 @@ TOOL_SCHEMAS: list[dict[str, Any]] = [
|
||||
"cdsl": CDSL_TOOL_SCHEMA,
|
||||
"summary": {"type": "string", "minLength": 1},
|
||||
"assumptions": {"type": "array", "items": {"type": "string"}},
|
||||
"verification": VERIFICATION_SCHEMA,
|
||||
},
|
||||
"required": ["cdsl", "summary", "assumptions"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "patch_cdsl_model",
|
||||
"description": "Apply RFC 6902 JSON Patch operations to one existing CDSL revision, validate and rebuild it as a new revision.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"base_revision_id": {"type": "string", "minLength": 1},
|
||||
"patches": {"type": "array", "minItems": 1, "maxItems": 32, "items": JSON_PATCH_OPERATION_SCHEMA},
|
||||
"summary": {"type": "string", "minLength": 1},
|
||||
"assumptions": {"type": "array", "items": {"type": "string"}},
|
||||
"verification": VERIFICATION_SCHEMA,
|
||||
},
|
||||
"required": ["base_revision_id", "patches", "summary", "assumptions"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -332,7 +464,6 @@ def tools_for_model(
|
||||
model: ProviderModel,
|
||||
*,
|
||||
include_image_analysis: bool = True,
|
||||
flange_sleeve_only: bool = False,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Return this model's tool contract without mutating the shared schema."""
|
||||
tools = deepcopy(TOOL_SCHEMAS)
|
||||
@@ -342,17 +473,6 @@ def tools_for_model(
|
||||
for tool in tools
|
||||
if tool.get("function", {}).get("name") != "analyze_image_reference"
|
||||
]
|
||||
if flange_sleeve_only:
|
||||
tools = [
|
||||
tool
|
||||
for tool in tools
|
||||
if tool.get("function", {}).get("name") not in {
|
||||
"search_cdsl_library",
|
||||
"read_cdsl_reference",
|
||||
"read_current_cdsl",
|
||||
"generate_cdsl_model",
|
||||
}
|
||||
]
|
||||
if not model.strict_tool_schema:
|
||||
return tools
|
||||
|
||||
@@ -445,33 +565,6 @@ def image_reference_analysis(conversation: dict[str, Any]) -> dict[str, Any] | N
|
||||
return None
|
||||
|
||||
|
||||
def use_flange_sleeve_template(analysis: dict[str, Any] | None, task_id: str) -> bool:
|
||||
"""Route only a high-confidence new image reference to the verified template."""
|
||||
if task_id or not isinstance(analysis, dict):
|
||||
return False
|
||||
visible = analysis.get("visibleFeatures", analysis.get("visible_features", []))
|
||||
values = [analysis.get("partType", analysis.get("part_type", ""))]
|
||||
if isinstance(visible, list):
|
||||
values.extend(visible)
|
||||
text = " ".join(str(value or "").casefold() for value in values)
|
||||
has_flange = "法兰" in text or "flange" in text
|
||||
has_sleeve = any(token in text for token in ("套筒", "管座", "圆筒", "cylind", "sleeve", "tube"))
|
||||
return has_flange and has_sleeve
|
||||
|
||||
|
||||
def flange_sleeve_template_instruction(enabled: bool) -> str:
|
||||
if not enabled:
|
||||
return ""
|
||||
return """
|
||||
Template routing (highest priority for this request):
|
||||
- This new image reference has been classified as a flange sleeve.
|
||||
- After describe_design_intent, call generate_flange_sleeve_model exactly once.
|
||||
- The complete-CDSL generation tool is intentionally unavailable for this request.
|
||||
- Supply only visible semantic dimensions and assumptions; omitted dimensions use
|
||||
the verified template defaults. Do not ask for CDSL implementation fields.
|
||||
"""
|
||||
|
||||
|
||||
def image_reference_instruction(
|
||||
attachments: list[dict[str, Any]],
|
||||
analysis: dict[str, Any] | None,
|
||||
@@ -635,15 +728,30 @@ def system_prompt(
|
||||
part_skill_context: str = "",
|
||||
image_reference_context: str = "",
|
||||
cad_request_context: str = "",
|
||||
flange_sleeve_template_mode: bool = False,
|
||||
) -> str:
|
||||
capability_manifest = json.dumps(engine_capability_manifest(settings), ensure_ascii=False, separators=(",", ":"))
|
||||
skill_path = settings.engine_root.parent.parent / "agent" / "skills" / "cad-engine" / "SKILL.md"
|
||||
skill = skill_path.read_text(encoding="utf-8") if skill_path.is_file() else ""
|
||||
readme_path = settings.engine_root / "README.md"
|
||||
engine_readme = readme_path.read_text(encoding="utf-8") if readme_path.is_file() else ""
|
||||
profile_schema_path = settings.engine_root / "profile_schema.json"
|
||||
profile_schema = profile_schema_path.read_text(encoding="utf-8") if profile_schema_path.is_file() else ""
|
||||
supported_profiles = ", ".join(sorted(load_engine(settings).SHAPE_GENERATORS))
|
||||
generation_contract = """- For generate_cdsl_model, pass the complete CDSL as the cdsl object directly, not as Markdown or a JSON string.
|
||||
- For a revision, call read_current_cdsl before generate_cdsl_model. Preserve unrelated CDSL features unless the user requests whole-part replacement.
|
||||
- Use patch_cdsl_model only for a local RFC 6902 repair of a known base_revision_id. For structural changes, submit a complete replacement CDSL with generate_cdsl_model.
|
||||
- For every feature.atomic_id, use only an ID listed in the compact capability manifest's runtime_atomic_ids. Other semantic contracts are not executable in this runtime.
|
||||
- The backend normalizes only these unambiguous aliases: sketch_id -> id on sketches, sketch -> sketch_id on features, legacy plane/offset_mm -> workplane, and axis.point_mm -> axis.origin_mm.
|
||||
- Every explicit user dimension, feature count, hole size, or hole position that can be measured must have a generic verification rule. Rule feature values must be IDs from the submitted CDSL.
|
||||
- Do not output build123d source, compiler_context, unknown_shape, complex_arc_shape, entities, contour_edges_mm, or contour_regions_mm."""
|
||||
workflow = """3. Search the local official CDSL library after the design brief. Read a relevant reference when a match exists; samples are expression guidance, not templates or higher-priority requirements.
|
||||
4. Generate one complete generic CDSL and call generate_cdsl_model.
|
||||
5. On a schema, runtime, or verification failure, use patch_cdsl_model for a local correction or generate_cdsl_model for a complete replacement.
|
||||
6. Never claim success unless the tool returns a successful CDSL-only STEP and GLB artifact."""
|
||||
authoring_context = f"""You own the complete CDSL: profiles, workplanes, sketch IDs, feature IDs, dependencies, selectors, and atomic IDs must be valid under the engine schema. Do not invent unsupported capabilities.
|
||||
|
||||
Local skill:
|
||||
{skill}
|
||||
|
||||
Authoritative engine schema:
|
||||
{profile_schema}"""
|
||||
return f"""You are the CDSL CAD Agent for CDSL CAD Studio.
|
||||
|
||||
Language policy:
|
||||
@@ -661,31 +769,12 @@ Language policy:
|
||||
Tool call contract:
|
||||
- Every function call arguments field must contain exactly one valid JSON object.
|
||||
- Do not append prose, Markdown code fences, comments, or a second JSON value.
|
||||
- For generate_cdsl_model, pass the complete CDSL as the cdsl object directly,
|
||||
not as Markdown and not as a concatenated JSON string.
|
||||
- For a square/rectangular flange with a coaxial hollow sleeve, stepped or
|
||||
tapered front, and four mounting counterbores, call
|
||||
generate_flange_sleeve_model. Submit only its compact semantic plan; do not
|
||||
create workplanes, sketch IDs, feature IDs, dependencies, or raw CDSL for
|
||||
that template. The backend creates and validates the complete CDSL.
|
||||
- The backend can normalize only these unambiguous legacy aliases before
|
||||
validation: sketch_id -> id on sketches, sketch -> sketch_id on features,
|
||||
legacy plane/offset_mm -> workplane, and axis.point_mm -> axis.origin_mm.
|
||||
Do not rely on it for geometry, dimensions, selectors, or feature intent.
|
||||
- Call describe_design_intent first with a concise natural-language plan.
|
||||
Its returned text is reference context for your CDSL work, not a second CAD
|
||||
contract and not a source of feature IDs, profile bindings, or selectors.
|
||||
- Do not call search_cdsl_library, read_cdsl_reference,
|
||||
generate_flange_sleeve_model, or generate_cdsl_model until
|
||||
describe_design_intent returns its plan.
|
||||
- The generate_cdsl_model `cdsl` parameter is the complete machine-enforced
|
||||
schema. Satisfy its nested object and array types exactly; do not substitute
|
||||
a shorthand array for an object. For example, every hole position is
|
||||
`{{"mm": [u_mm, v_mm, w_mm]}}`, never `[u_mm, v_mm]`.
|
||||
- If a tool reports INVALID_TOOL_ARGUMENTS, correct the arguments and call that
|
||||
tool again. Do not claim that the CAD model was generated.
|
||||
- If a generation tool reports INVALID_CDSL, correct its plan or complete CDSL
|
||||
as applicable and call that same tool again. Do not claim success.
|
||||
{generation_contract}
|
||||
|
||||
Workflow limits:
|
||||
- Do not expose internal planning or "let me" commentary to the user while
|
||||
@@ -698,7 +787,7 @@ Workflow limits:
|
||||
the missing dimension or decision. Do not combine it with a tool trace or a
|
||||
generic progress update.
|
||||
- Use at most two CDSL-library searches per user request. If neither finds a
|
||||
useful reference, stop searching and use the engine guide to either generate
|
||||
useful reference, stop searching and use the available capability contract to either generate
|
||||
the model or ask one concise clarification question.
|
||||
- Do not repeatedly search for the same unavailable feature or profile.
|
||||
|
||||
@@ -708,24 +797,12 @@ Workflow limits:
|
||||
|
||||
{cad_request_context}
|
||||
|
||||
{flange_sleeve_template_instruction(flange_sleeve_template_mode)}
|
||||
|
||||
You generate executable CAD through compact semantic plans or parameterized CDSL,
|
||||
never raw CAD source code. For new CAD requests:
|
||||
You generate executable CAD through complete parameterized CDSL, never raw CAD source code. For new CAD requests:
|
||||
1. Use the injected part-skill guidance, when present, only to establish the
|
||||
structural plan, feature dependency order, and parameter roles.
|
||||
structural plan, feature dependency order, parameter roles, and reference queries.
|
||||
2. Call describe_design_intent with a concise textual plan. Ask one concise
|
||||
user-facing question before CAD generation if essential dimensions are missing.
|
||||
3. If the requested family is a flange sleeve matching the dedicated template,
|
||||
call generate_flange_sleeve_model immediately after the textual plan. Do not
|
||||
search the CDSL library and do not use generate_cdsl_model for that case.
|
||||
4. For other families, search the local official CDSL library. Read at least
|
||||
one relevant reference when a match exists; samples provide schema-valid
|
||||
expressions, not higher-priority user requirements.
|
||||
5. For non-template families, generate one complete CDSL and call
|
||||
generate_cdsl_model. The backend-generated CDSL is the authoritative CAD
|
||||
model; the textual plan is guidance only.
|
||||
6. Never claim success unless the tool returns a successful CDSL-only STEP and GLB artifact.
|
||||
{workflow}
|
||||
|
||||
Precedence is strict: explicit user request, then CDSL schema/runtime, then
|
||||
part-skill guidance, then CDSL-library examples. Part skills never authorize
|
||||
@@ -738,29 +815,10 @@ whole-part replacement. A primary-family conflict is a hard clarification
|
||||
stop: ask one concise question and do not call a generation tool until the
|
||||
user resolves it.
|
||||
|
||||
For a revision, call read_current_cdsl first and preserve unrelated features.
|
||||
Use the flange sleeve template only when the user wants to replace the whole
|
||||
part or the current part already follows that family; otherwise submit the
|
||||
complete replacement CDSL. The backend automatically uses the latest
|
||||
successful revision as the parent; do not provide a revision ID.
|
||||
Do not output compiler_context, unknown_shape, complex_arc_shape, entities,
|
||||
contour_edges_mm, or contour_regions_mm. Use only self-contained named profiles
|
||||
and feature atomic IDs defined in the engine schema below. Read the engine
|
||||
schema before selecting an atomic ID, a profile, or their parameter names. Do
|
||||
not invent an atomic ID, profile, or their fields. Ask a concise clarification question when essential
|
||||
dimensions or intent are missing. Ordinary explanations must not create CAD.
|
||||
{authoring_context}
|
||||
|
||||
Local skill:
|
||||
{skill}
|
||||
|
||||
Local engine guide:
|
||||
{engine_readme}
|
||||
|
||||
Authoritative engine schema:
|
||||
{profile_schema}
|
||||
|
||||
Supported named profile types:
|
||||
{supported_profiles}
|
||||
Compact Engine Capability Manifest (planner-facing; backend validator remains authoritative):
|
||||
{capability_manifest}
|
||||
|
||||
Injected part-skill context:
|
||||
{part_skill_context or "No part-family skill guidance was selected for this request."}
|
||||
@@ -849,18 +907,17 @@ class AgentService:
|
||||
image_inputs = image_attachments(conversation)
|
||||
recorded_image_analysis = image_reference_analysis(conversation)
|
||||
requires_image_intake = bool(image_inputs) and recorded_image_analysis is None
|
||||
flange_sleeve_template_mode = use_flange_sleeve_template(recorded_image_analysis, task_id)
|
||||
yield event("progress", {"step": "analyze_request", "label": "分析需求", "status": "running", "message": "正在整理当前会话和 CAD 需求。"})
|
||||
references: list[str] = []
|
||||
library_searches = 0
|
||||
inherited_skill_ids = self.part_skill_library.inherited_from_task(current_task)
|
||||
part_skill_selection = self.part_skill_library.select(user_text, inherited_skill_ids)
|
||||
planning_state: dict[str, Any] = {"phase": "WAITING_FOR_PLAN", "design_brief": ""}
|
||||
planning_state: dict[str, Any] = {"phase": "INTAKE", "design_brief": "", "current_model_read": False}
|
||||
yield event("progress", {
|
||||
"step": "select_part_skill",
|
||||
"label": "识别零件族",
|
||||
"label": "选择建模 Skill",
|
||||
"status": "success",
|
||||
"message": "已完成零件族与辅助建模规则识别。",
|
||||
"message": "已完成建模 Skill 与辅助规则选择。",
|
||||
})
|
||||
model_messages: list[dict[str, Any]] = [{
|
||||
"role": "system",
|
||||
@@ -872,7 +929,6 @@ class AgentService:
|
||||
self.part_skill_library.render_context(part_skill_selection),
|
||||
image_reference_instruction(image_inputs, recorded_image_analysis),
|
||||
cad_request_instruction(task_id, current_task),
|
||||
flange_sleeve_template_mode,
|
||||
),
|
||||
}]
|
||||
model_messages.extend(messages_for_model(messages))
|
||||
@@ -881,16 +937,21 @@ class AgentService:
|
||||
tools = tools_for_model(
|
||||
model,
|
||||
include_image_analysis=requires_image_intake,
|
||||
flange_sleeve_only=flange_sleeve_template_mode,
|
||||
)
|
||||
required_tool_name: str | None = "analyze_image_reference" if requires_image_intake else None
|
||||
generate_argument_failures = 0
|
||||
tool_argument_diagnostics: list[str] = []
|
||||
cdsl_validation_diagnostics: list[str] = []
|
||||
generation_completed = False
|
||||
repair_attempts = 0
|
||||
|
||||
try:
|
||||
for iteration in range(8):
|
||||
if (
|
||||
planning_state.get("phase") == "CDSL_REPAIR"
|
||||
and repair_attempts >= self.settings.max_repair_attempts
|
||||
):
|
||||
raise CdslRepairLimitError(cdsl_validation_diagnostics)
|
||||
response = await self._complete(model_messages, tools, provider, model, required_tool_name)
|
||||
response_choice = response["choices"][0]
|
||||
choice = response_choice["message"]
|
||||
@@ -914,6 +975,15 @@ class AgentService:
|
||||
model_messages.append(choice)
|
||||
for call in tool_calls:
|
||||
name = str(call.get("function", {}).get("name") or "")
|
||||
if requires_image_intake and name != "analyze_image_reference":
|
||||
result = {
|
||||
"ok": False,
|
||||
"code": "IMAGE_ANALYSIS_REQUIRED",
|
||||
"message": "Analyze all current image attachments before using planning, library, or generation tools.",
|
||||
}
|
||||
model_messages.append({"role": "tool", "tool_call_id": call.get("id", ""), "content": json.dumps(result, ensure_ascii=False)})
|
||||
yield event("progress", {"step": name or "tool", "label": self._tool_label(name), "status": "error", "message": "请先完成图片参考分析。"})
|
||||
continue
|
||||
if name == "analyze_image_reference" and recorded_image_analysis is not None:
|
||||
result = {
|
||||
"ok": False,
|
||||
@@ -996,8 +1066,12 @@ class AgentService:
|
||||
"message": "CAD 工具参数格式无效,正在请求模型修正。",
|
||||
})
|
||||
continue
|
||||
if name in GENERATION_TOOL_NAMES and planning_state.get("phase") == "CDSL_REPAIR":
|
||||
if repair_attempts >= self.settings.max_repair_attempts:
|
||||
raise CdslRepairLimitError(cdsl_validation_diagnostics)
|
||||
repair_attempts += 1
|
||||
cdsl_attempt_path = ""
|
||||
if name == "generate_cdsl_model":
|
||||
if name in GENERATION_TOOL_NAMES:
|
||||
cdsl_attempt_path = self._record_cdsl_attempt(
|
||||
conversation_id=conversation["conversation_id"],
|
||||
arguments=arguments,
|
||||
@@ -1020,6 +1094,8 @@ class AgentService:
|
||||
planning_state=planning_state,
|
||||
image_attachment_ids=[str(attachment["id"]) for attachment in image_inputs],
|
||||
input_attachments=revision_input_attachments(conversation),
|
||||
conversation_id=conversation["conversation_id"],
|
||||
repair_attempts=repair_attempts,
|
||||
)
|
||||
except (ValueError, RuntimeError) as error:
|
||||
if name in GENERATION_TOOL_NAMES:
|
||||
@@ -1038,8 +1114,24 @@ class AgentService:
|
||||
)
|
||||
cdsl_validation_diagnostics.append(diagnostic_path)
|
||||
result = invalid_cdsl_result(error)
|
||||
result["diagnostic_path"] = diagnostic_path
|
||||
repair_task_id = str(getattr(error, "task_id", "") or "")
|
||||
repair_revision_id = str(getattr(error, "revision_id", "") or "")
|
||||
if repair_task_id and repair_revision_id:
|
||||
result.update({
|
||||
"task_id": repair_task_id,
|
||||
"base_revision_id": repair_revision_id,
|
||||
"repair_instruction": (
|
||||
"Patch this explicit base_revision_id for a local correction, or call read_current_cdsl "
|
||||
"before a complete CDSL replacement."
|
||||
),
|
||||
})
|
||||
if name == "patch_cdsl_model":
|
||||
result["code"] = "INVALID_CDSL_PATCH"
|
||||
result["repair_instruction"] = "Correct the RFC 6902 patch or call generate_cdsl_model with a complete replacement CDSL."
|
||||
generated = None
|
||||
required_tool_name = name
|
||||
planning_state["phase"] = "CDSL_REPAIR"
|
||||
else:
|
||||
raise
|
||||
if result.get("task_id"):
|
||||
@@ -1071,26 +1163,21 @@ class AgentService:
|
||||
"visibleFeatures": result["visible_features"],
|
||||
"uncertainFeatures": result["uncertain_features"],
|
||||
"dimensionCandidates": result["dimension_candidates"],
|
||||
"artifactPath": result.get("artifact_path"),
|
||||
}
|
||||
assistant_parts.append({"type": "data-cad-image-analysis", "data": image_payload})
|
||||
yield event("image_analysis", image_payload)
|
||||
recorded_image_analysis = image_payload
|
||||
flange_sleeve_template_mode = use_flange_sleeve_template(image_payload, task_id)
|
||||
requires_image_intake = False
|
||||
required_tool_name = None
|
||||
tools = tools_for_model(
|
||||
model,
|
||||
include_image_analysis=False,
|
||||
flange_sleeve_only=flange_sleeve_template_mode,
|
||||
)
|
||||
model_messages.append({
|
||||
"role": "system",
|
||||
"content": image_reference_instruction(image_inputs, image_payload),
|
||||
})
|
||||
if flange_sleeve_template_mode:
|
||||
model_messages.append({
|
||||
"role": "system",
|
||||
"content": flange_sleeve_template_instruction(True),
|
||||
})
|
||||
yield event("progress", {
|
||||
"step": "analyze_image_reference",
|
||||
"label": "识别图片参考",
|
||||
@@ -1114,6 +1201,12 @@ class AgentService:
|
||||
"summary": generated["summary"],
|
||||
"referenceIds": generated["reference_ids"],
|
||||
"engine": generated["engine"],
|
||||
"qualityStatus": generated.get("quality_status", ""),
|
||||
"qualityPath": generated.get("quality_path") or None,
|
||||
"assumptions": generated.get("generation_assumptions", []),
|
||||
"repairAttempts": generated.get("repair_attempts", 0),
|
||||
"snapshotPaths": generated.get("snapshot_paths", []),
|
||||
"snapshotStatus": generated.get("snapshot_status", "unavailable"),
|
||||
}
|
||||
assistant_parts.append({"type": "data-cad-result", "data": result_payload})
|
||||
yield event("cad_result", result_payload)
|
||||
@@ -1128,10 +1221,14 @@ class AgentService:
|
||||
if generation_completed:
|
||||
break
|
||||
if iteration == 7:
|
||||
if cdsl_validation_diagnostics:
|
||||
diagnostic_paths = "、".join(cdsl_validation_diagnostics)
|
||||
validation_diagnostics = cdsl_validation_diagnostics
|
||||
if validation_diagnostics:
|
||||
diagnostic_paths = "、".join(validation_diagnostics)
|
||||
if any("\u4e00" <= char <= "\u9fff" for char in user_text):
|
||||
message = f"模型重试达到安全上限。每次 CDSL 校验失败的诊断已保存到:{diagnostic_paths}。"
|
||||
message = (
|
||||
"模型重试达到安全上限。每次 CDSL 校验失败的诊断已保存到:"
|
||||
f"{diagnostic_paths}。"
|
||||
)
|
||||
else:
|
||||
message = (
|
||||
"Agent tool loop reached its safety limit. "
|
||||
@@ -1250,6 +1347,7 @@ class AgentService:
|
||||
error: Exception,
|
||||
) -> str:
|
||||
function = call.get("function") if isinstance(call.get("function"), dict) else {}
|
||||
details = _cdsl_error_details(error)
|
||||
payload = {
|
||||
"schema_version": "1.0",
|
||||
"recorded_at": now_iso(),
|
||||
@@ -1269,6 +1367,8 @@ class AgentService:
|
||||
"cdsl_attempt_path": cdsl_attempt_path,
|
||||
"summary": str(arguments.get("summary") or ""),
|
||||
"assumptions": arguments.get("assumptions") or [],
|
||||
"verification": arguments.get("verification"),
|
||||
"diagnostic": details,
|
||||
"validation_error_type": type(error).__name__,
|
||||
"validation_error": str(error),
|
||||
}
|
||||
@@ -1328,24 +1428,30 @@ class AgentService:
|
||||
planning_state: dict[str, Any] | None = None,
|
||||
image_attachment_ids: list[str] | None = None,
|
||||
input_attachments: list[dict[str, str | int]] | None = None,
|
||||
conversation_id: str | None = None,
|
||||
repair_attempts: int = 0,
|
||||
) -> tuple[dict[str, Any], dict[str, Any] | None]:
|
||||
state = planning_state if planning_state is not None else {"phase": "WAITING_FOR_PLAN", "design_brief": ""}
|
||||
phase = str(state.get("phase") or "WAITING_FOR_PLAN")
|
||||
state = planning_state if planning_state is not None else {"phase": "INTAKE", "design_brief": "", "current_model_read": False}
|
||||
phase = str(state.get("phase") or "INTAKE")
|
||||
if name == "analyze_image_reference":
|
||||
if not image_attachment_ids:
|
||||
raise ValueError("analyze_image_reference requires at least one image attachment")
|
||||
return {
|
||||
analysis = {
|
||||
"ok": True,
|
||||
"attachment_ids": list(dict.fromkeys(image_attachment_ids)),
|
||||
**normalize_image_analysis(arguments),
|
||||
}, None
|
||||
}
|
||||
if conversation_id:
|
||||
analysis["artifact_path"] = self.store.write_conversation_planning(conversation_id, "reference-observation", {"schema_version": "1.0", **analysis})
|
||||
state["phase"] = "REFERENCE_ANALYZED"
|
||||
return analysis, None
|
||||
if name == "describe_design_intent":
|
||||
plan = str(arguments.get("plan") or "").strip()
|
||||
assumptions = arguments.get("assumptions")
|
||||
if not plan or not isinstance(assumptions, list) or not all(isinstance(item, str) for item in assumptions):
|
||||
raise ValueError("describe_design_intent requires a non-empty plan and an array of string assumptions")
|
||||
state["design_brief"] = plan
|
||||
state["phase"] = "PLAN_RECORDED"
|
||||
state["phase"] = "PLANNED"
|
||||
return {
|
||||
"ok": True,
|
||||
"plan": plan,
|
||||
@@ -1353,60 +1459,108 @@ class AgentService:
|
||||
"message": "The design brief is recorded as reference only. CDSL remains the sole authoritative CAD model.",
|
||||
}, None
|
||||
if name == "search_cdsl_library":
|
||||
if phase not in {"PLAN_RECORDED", "LIBRARY_REFERENCE", "WAITING_FOR_CDSL"}:
|
||||
if phase not in {"PLANNED", "LIBRARY_SEARCHING", "LIBRARY_REFERENCE_READY", "CDSL_REPAIR"}:
|
||||
return {"ok": False, "code": "DESIGN_BRIEF_REQUIRED", "message": "Call describe_design_intent before searching CDSL references."}, None
|
||||
results = self.library.search(str(arguments.get("query") or request), int(arguments.get("limit") or 5))
|
||||
state["phase"] = "LIBRARY_REFERENCE"
|
||||
query = str(arguments.get("query") or request)
|
||||
normalized_query = " ".join(query.casefold().split())
|
||||
seen_queries = state.setdefault("library_queries", [])
|
||||
if len(seen_queries) >= 2:
|
||||
return {"ok": False, "code": "LIBRARY_SEARCH_LIMIT_REACHED", "message": "The CDSL library search limit for this request has been reached; continue with the available references."}, None
|
||||
if normalized_query in seen_queries:
|
||||
return {"ok": False, "code": "LIBRARY_QUERY_DUPLICATE", "message": "Do not repeat an identical unavailable library query; use the existing results or compile with the available capability."}, None
|
||||
seen_queries.append(normalized_query)
|
||||
results = self.library.search(query, min(8, int(arguments.get("limit") or 5)))
|
||||
state["phase"] = "LIBRARY_SEARCHING"
|
||||
return {"ok": True, "results": results}, None
|
||||
if name == "read_cdsl_reference":
|
||||
if phase not in {"PLAN_RECORDED", "LIBRARY_REFERENCE", "WAITING_FOR_CDSL"}:
|
||||
if phase not in {"PLANNED", "LIBRARY_SEARCHING", "LIBRARY_REFERENCE_READY", "CDSL_REPAIR"}:
|
||||
return {"ok": False, "code": "DESIGN_BRIEF_REQUIRED", "message": "Call describe_design_intent before reading CDSL references."}, None
|
||||
part_id = str(arguments.get("part_id") or "")
|
||||
sample = self.library.read_sample(part_id)
|
||||
if part_id in references:
|
||||
return {"ok": False, "code": "LIBRARY_REFERENCE_DUPLICATE", "message": "This CDSL reference is already loaded; choose another reference or continue to compilation."}, None
|
||||
if len(state.get("reference_records") or []) >= 2:
|
||||
return {"ok": False, "code": "LIBRARY_REFERENCE_LIMIT_REACHED", "message": "At most two complete CDSL library references may be read for one request."}, None
|
||||
try:
|
||||
sample = self.library.read_sample(part_id)
|
||||
except ValueError as error:
|
||||
return {"ok": False, "code": "LIBRARY_REFERENCE_NOT_FOUND", "message": str(error)}, None
|
||||
if part_id not in references:
|
||||
references.append(part_id)
|
||||
state["phase"] = "WAITING_FOR_CDSL"
|
||||
state.setdefault("reference_records", []).append({"part_id": part_id, "source": "cdsl_library", "summary": "official CDSL reference"})
|
||||
state["phase"] = "LIBRARY_REFERENCE_READY"
|
||||
return {"ok": True, "part_id": part_id, "cdsl": sample}, None
|
||||
if name == "read_current_cdsl":
|
||||
if not task_id:
|
||||
return {"ok": False, "message": "No current task exists. This is a new model request."}, None
|
||||
path = self.store.current_cdsl_path(task_id)
|
||||
revision_id = str((self.store.read_task(task_id) or {}).get("current_revision") or "")
|
||||
if path is None:
|
||||
return {"ok": False, "message": "The current task has no successful CDSL revision."}, None
|
||||
return {"ok": True, "task_id": task_id, "cdsl": json.loads(path.read_text(encoding="utf-8"))}, None
|
||||
repairable = self.store.latest_repairable_cdsl(task_id)
|
||||
if repairable is None:
|
||||
return {"ok": False, "message": "The current task has no successful or repairable CDSL revision."}, None
|
||||
revision_id, path = repairable
|
||||
payload: dict[str, Any] = {
|
||||
"ok": True,
|
||||
"task_id": task_id,
|
||||
"revision_id": revision_id,
|
||||
"cdsl": json.loads(path.read_text(encoding="utf-8")),
|
||||
}
|
||||
state["current_model_read"] = True
|
||||
state["read_revision_id"] = revision_id
|
||||
state["phase"] = "PLANNED"
|
||||
return payload, None
|
||||
if name in GENERATION_TOOL_NAMES:
|
||||
if phase not in {"PLAN_RECORDED", "LIBRARY_REFERENCE", "WAITING_FOR_CDSL"}:
|
||||
if phase not in {"PLANNED", "LIBRARY_SEARCHING", "LIBRARY_REFERENCE_READY", "CDSL_REPAIR"}:
|
||||
return {"ok": False, "code": "DESIGN_BRIEF_REQUIRED", "message": "Call describe_design_intent before generating CAD."}, None
|
||||
template_plan: dict[str, float | str] | None = None
|
||||
operation: dict[str, Any] | None = None
|
||||
if name == "generate_flange_sleeve_model":
|
||||
cdsl, template_plan = build_flange_sleeve_cdsl(arguments.get("plan"))
|
||||
operation = {
|
||||
"type": "parameterized_template",
|
||||
"template_id": FLANGE_SLEEVE_TEMPLATE_ID,
|
||||
"plan": template_plan,
|
||||
}
|
||||
else:
|
||||
selection = part_skill_selection or self.part_skill_library.select(request)
|
||||
if selection.get("conflict"):
|
||||
state["phase"] = "BLOCKED"
|
||||
return {
|
||||
"ok": False,
|
||||
"code": "PART_SKILL_CONFLICT",
|
||||
"message": str(selection["conflict"].get("message") or "Resolve the primary part-family conflict before generating CAD."),
|
||||
}, None
|
||||
state["phase"] = "CDSL_AUTHORING"
|
||||
if name == "generate_cdsl_model":
|
||||
if task_id and not state.get("current_model_read"):
|
||||
return {"ok": False, "code": "CURRENT_CDSL_REQUIRED", "message": "Call read_current_cdsl before replacing an existing task revision."}, None
|
||||
cdsl = arguments.get("cdsl")
|
||||
if isinstance(cdsl, str):
|
||||
cdsl = json.loads(cdsl)
|
||||
if not isinstance(cdsl, dict):
|
||||
raise ValueError("generate_cdsl_model requires a CDSL JSON object")
|
||||
parent_revision_id = str(state.get("read_revision_id") or "") if task_id else ""
|
||||
if task_id and not parent_revision_id:
|
||||
parent_revision_id = str((self.store.read_task(task_id) or {}).get("current_revision") or "")
|
||||
operation: dict[str, Any] = {"type": "cdsl_replacement" if parent_revision_id else "cdsl_create"}
|
||||
else:
|
||||
if not task_id:
|
||||
return {"ok": False, "code": "PATCH_TASK_REQUIRED", "message": "patch_cdsl_model requires an existing task."}, None
|
||||
base_revision_id = str(arguments.get("base_revision_id") or "")
|
||||
base_path = self.store.revision_cdsl_path(task_id, base_revision_id)
|
||||
if base_path is None:
|
||||
raise ValueError("PATCH_BASE_REVISION_NOT_FOUND: base_revision_id does not identify a readable CDSL revision")
|
||||
try:
|
||||
cdsl = apply_cdsl_patch(json.loads(base_path.read_text(encoding="utf-8")), arguments.get("patches"))
|
||||
except (CdslPatchError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"INVALID_CDSL_PATCH: {error}") from error
|
||||
parent_revision_id = base_revision_id
|
||||
operation = {"type": "cdsl_patch", "base_revision_id": base_revision_id, "patches": deepcopy(arguments.get("patches") or [])}
|
||||
cdsl, normalization_repairs = normalize_cdsl_for_engine(cdsl)
|
||||
# Reject malformed model output before build_revision allocates a task
|
||||
# directory or revision. build_revision will assign the real task ID.
|
||||
preflight_cdsl = {**cdsl, "part_id": str(cdsl.get("part_id") or "agent_preflight")}
|
||||
engine = load_engine(self.settings)
|
||||
state["phase"] = "PREFLIGHT"
|
||||
validate_cdsl(preflight_cdsl, engine)
|
||||
summary = str(arguments.get("summary") or "Parameterized CAD model")
|
||||
raw_assumptions = arguments.get("assumptions") or []
|
||||
if not isinstance(raw_assumptions, list) or not all(isinstance(item, str) for item in raw_assumptions):
|
||||
raise ValueError(f"{name} assumptions must be an array of strings")
|
||||
assumptions = [item.strip() for item in raw_assumptions if item.strip()]
|
||||
selection = part_skill_selection or self.part_skill_library.select(request)
|
||||
verification = arguments.get("verification")
|
||||
validate_verification(verification, cdsl)
|
||||
part_skill_audit = self.part_skill_library.audit(selection, cdsl, assumptions)
|
||||
current_task = self.store.read_task(task_id) if task_id else None
|
||||
parent_revision_id = str((current_task or {}).get("current_revision") or "")
|
||||
state["phase"] = "BUILDING"
|
||||
try:
|
||||
yieldable = await asyncio.to_thread(
|
||||
@@ -1422,10 +1576,13 @@ class AgentService:
|
||||
operation=operation,
|
||||
part_skills=part_skill_audit,
|
||||
generation_assumptions=assumptions,
|
||||
repair_attempts=repair_attempts,
|
||||
input_attachments=input_attachments,
|
||||
verification=verification,
|
||||
reference_records=state.get("reference_records"),
|
||||
)
|
||||
except Exception:
|
||||
state["phase"] = "PLAN_RECORDED"
|
||||
state["phase"] = "CDSL_REPAIR"
|
||||
raise
|
||||
state["phase"] = "COMPLETED"
|
||||
return {
|
||||
@@ -1434,8 +1591,7 @@ class AgentService:
|
||||
"task_id": yieldable["task_id"],
|
||||
"revision_id": yieldable["revision_id"],
|
||||
"normalization_repairs": normalization_repairs,
|
||||
"template_id": FLANGE_SLEEVE_TEMPLATE_ID if template_plan is not None else "",
|
||||
"template_plan": template_plan,
|
||||
"operation": operation,
|
||||
}, yieldable
|
||||
raise ValueError(f"Unknown agent tool: {name}")
|
||||
|
||||
@@ -1450,8 +1606,8 @@ class AgentService:
|
||||
"read_cdsl_reference": "读取 CDSL 参考模型",
|
||||
"read_current_cdsl": "读取当前 CDSL",
|
||||
"describe_design_intent": "整理设计说明",
|
||||
"generate_flange_sleeve_model": "生成参数化法兰套筒",
|
||||
"generate_cdsl_model": "生成 CDSL",
|
||||
"patch_cdsl_model": "修复 CDSL",
|
||||
}.get(name, "调用 CAD 工具")
|
||||
|
||||
def _attachment_message(self, conversation: dict[str, Any], model: ProviderModel) -> list[dict[str, Any]] | str:
|
||||
|
||||
Reference in New Issue
Block a user