优化生成流程
This commit is contained in:
@@ -4,6 +4,7 @@ import copy
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -12,10 +13,21 @@ from jsonschema import Draft202012Validator
|
||||
from jsonschema.exceptions import SchemaError
|
||||
|
||||
from vendor.cdsl_preview_runtime import step_to_glb
|
||||
from app.services.quality import evaluate_quality, validate_verification
|
||||
from app.services.storage import WorkspaceStore, now_iso, write_json
|
||||
from app.settings import Settings
|
||||
|
||||
|
||||
class QualityVerificationError(RuntimeError):
|
||||
"""A built CDSL document missed a blocking generic verification rule."""
|
||||
|
||||
def __init__(self, quality_report: dict[str, Any]) -> None:
|
||||
super().__init__("Blocking CDSL verification checks failed")
|
||||
self.quality_report = quality_report
|
||||
self.task_id = ""
|
||||
self.revision_id = ""
|
||||
|
||||
|
||||
def load_engine(settings: Settings) -> Any:
|
||||
parent = str(settings.engine_root.parent)
|
||||
if parent not in sys.path:
|
||||
@@ -165,10 +177,12 @@ def validate_cdsl(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
sketch_ids = {str(sketch.get("id")) for sketch in sketches}
|
||||
semantic_contract = _engine_schema(engine)
|
||||
atomic_contracts = semantic_contract["feature_atomic_ids"]
|
||||
supported_atomic_ids = sorted(
|
||||
declared_atomic_ids = {
|
||||
str(atomic_id)
|
||||
for atomic_id in semantic_contract.get("runtime_supported_atomic_ids", atomic_contracts)
|
||||
)
|
||||
}
|
||||
registered_atomic_ids = {str(atomic_id) for atomic_id in getattr(engine, "SUPPORTED_ATOMIC_IDS", ())}
|
||||
supported_atomic_ids = sorted(declared_atomic_ids & registered_atomic_ids)
|
||||
feature_ids: set[str] = set()
|
||||
for feature in features:
|
||||
fid = str(feature.get("id") or "")
|
||||
@@ -400,7 +414,6 @@ def _part_skill_audit(
|
||||
skill_ids = [str(item) for item in audit.get("skill_ids") or [] if str(item)]
|
||||
if not skill_ids:
|
||||
skill_ids = [str(item.get("id")) for item in skills if item.get("id")]
|
||||
evidence = audit.get("evidence") if isinstance(audit.get("evidence"), dict) else {}
|
||||
audit.update({
|
||||
"schema_version": str(audit.get("schema_version") or "1.0"),
|
||||
"request": str(audit.get("request") or request),
|
||||
@@ -409,10 +422,6 @@ def _part_skill_audit(
|
||||
"skills": skills,
|
||||
"inherited_skill_ids": [str(item) for item in audit.get("inherited_skill_ids") or [] if str(item)],
|
||||
"assumptions": [str(item) for item in generation_assumptions or []],
|
||||
"evidence": evidence,
|
||||
"capability_translations": [
|
||||
item for item in audit.get("capability_translations") or [] if isinstance(item, dict)
|
||||
],
|
||||
})
|
||||
return audit
|
||||
|
||||
@@ -420,20 +429,9 @@ def _part_skill_audit(
|
||||
def _generation_context(
|
||||
reference_ids: list[str],
|
||||
part_skill_audit: dict[str, Any],
|
||||
design_intent: dict[str, Any] | None = None,
|
||||
design_intent_path: str = "",
|
||||
design_intent_id: str = "",
|
||||
) -> dict[str, Any]:
|
||||
skills = [item for item in part_skill_audit.get("skills") or [] if isinstance(item, dict)]
|
||||
intent = design_intent if isinstance(design_intent, dict) else {}
|
||||
return {
|
||||
"design_intent_id": str(intent.get("intent_id") or design_intent_id or ""),
|
||||
"design_intent_path": design_intent_path,
|
||||
"design_intent_structures": [
|
||||
str(item.get("id") or "")
|
||||
for item in intent.get("structures") or []
|
||||
if isinstance(item, dict) and item.get("id")
|
||||
],
|
||||
"cdsl_reference_ids": list(reference_ids),
|
||||
"part_skill_ids": list(part_skill_audit.get("skill_ids") or []),
|
||||
"part_skill_paths": [
|
||||
@@ -445,9 +443,6 @@ def _generation_context(
|
||||
for item in skills
|
||||
],
|
||||
"generation_assumptions": list(part_skill_audit.get("assumptions") or []),
|
||||
"design_intent_assumptions": list(part_skill_audit.get("design_intent_assumptions") or []),
|
||||
"design_intent_capability_gaps": list(part_skill_audit.get("design_intent_capability_gaps") or []),
|
||||
"capability_translations": list(part_skill_audit.get("capability_translations") or []),
|
||||
}
|
||||
|
||||
|
||||
@@ -465,10 +460,9 @@ def build_revision(
|
||||
input_attachments: list[dict[str, Any]] | None = None,
|
||||
part_skills: dict[str, Any] | None = None,
|
||||
generation_assumptions: list[str] | None = None,
|
||||
design_intent: dict[str, Any] | None = None,
|
||||
design_intent_path: str = "",
|
||||
design_intent_id: str = "",
|
||||
design_intent_status: str = "",
|
||||
repair_attempts: int = 0,
|
||||
verification: dict[str, Any] | None = None,
|
||||
reference_records: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
engine = load_engine(settings)
|
||||
task = store.ensure_task(task_id, request)
|
||||
@@ -483,30 +477,19 @@ def build_revision(
|
||||
selector_path = revision_dir / "model.selector.json"
|
||||
edges_path = revision_dir / "model.edges.json"
|
||||
part_skills_path = revision_dir / "part-skills.json"
|
||||
intent = copy.deepcopy(design_intent) if isinstance(design_intent, dict) else {}
|
||||
intent_assumptions = [str(item) for item in intent.get("assumptions") or [] if str(item)]
|
||||
merged_assumptions = list(dict.fromkeys([
|
||||
*intent_assumptions,
|
||||
*(str(item) for item in generation_assumptions or [] if str(item)),
|
||||
]))
|
||||
part_skill_audit = _part_skill_audit(part_skills, request, merged_assumptions)
|
||||
intent_id = str(intent.get("intent_id") or design_intent_id or "")
|
||||
if design_intent_path:
|
||||
design_intent_path = str(design_intent_path)
|
||||
part_skill_audit.update({
|
||||
"design_intent_id": intent_id,
|
||||
"design_intent_path": design_intent_path,
|
||||
"intent_status": str(intent.get("status") or design_intent_status or ""),
|
||||
"design_intent_assumptions": intent_assumptions,
|
||||
"design_intent_capability_gaps": [
|
||||
item for item in intent.get("capability_gaps") or [] if isinstance(item, dict)
|
||||
],
|
||||
})
|
||||
generation_context = _generation_context(reference_ids, part_skill_audit, intent, design_intent_path, intent_id)
|
||||
quality_path = revision_dir / "quality-report.json"
|
||||
snapshot_manifest_path = revision_dir / "snapshot-manifest.json"
|
||||
part_skill_audit = _part_skill_audit(part_skills, request, generation_assumptions)
|
||||
generation_context = _generation_context(reference_ids, part_skill_audit)
|
||||
write_json(request_path, {"request": request, "created_at": now_iso()})
|
||||
write_json(references_path, {"reference_ids": reference_ids})
|
||||
write_json(references_path, {"reference_ids": reference_ids, "records": [item for item in reference_records or [] if isinstance(item, dict)]})
|
||||
write_json(part_skills_path, part_skill_audit)
|
||||
|
||||
write_json(snapshot_manifest_path, {
|
||||
"schema_version": "1.0",
|
||||
"status": "unavailable",
|
||||
"reason": "Snapshot runner is not attached to this backend build",
|
||||
"snapshots": [],
|
||||
})
|
||||
def revision_record(status: str, *, error: str = "", engine_name: str = "") -> dict[str, Any]:
|
||||
record = {
|
||||
"revision_id": revision_id,
|
||||
@@ -517,8 +500,10 @@ def build_revision(
|
||||
"report_path": report_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"parameters_path": parameters_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"part_skills_path": part_skills_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"design_intent_id": intent_id,
|
||||
"design_intent_path": design_intent_path,
|
||||
"quality_path": quality_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"snapshot_manifest_path": snapshot_manifest_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"snapshot_status": "unavailable",
|
||||
"snapshot_paths": [snapshot_manifest_path.relative_to(store.task_dir(task["task_id"])).as_posix()],
|
||||
"part_skill_ids": list(part_skill_audit.get("skill_ids") or []),
|
||||
"generation_assumptions": list(part_skill_audit.get("assumptions") or []),
|
||||
"reference_ids": reference_ids,
|
||||
@@ -526,6 +511,7 @@ def build_revision(
|
||||
"parent_revision_id": parent_revision_id or "",
|
||||
"operation": operation or {},
|
||||
"input_attachments": input_attachments or [],
|
||||
"repair_attempts": max(0, int(repair_attempts)),
|
||||
}
|
||||
if status == "success":
|
||||
record.update({
|
||||
@@ -539,6 +525,8 @@ def build_revision(
|
||||
record["error"] = error
|
||||
return record
|
||||
|
||||
quality_report: dict[str, Any] | None = None
|
||||
quality_status = ""
|
||||
try:
|
||||
cdsl_copy = copy.deepcopy(cdsl)
|
||||
if not isinstance(cdsl_copy, dict):
|
||||
@@ -562,6 +550,17 @@ def build_revision(
|
||||
selector, edges = topology_sidecars(engine_result, preview)
|
||||
write_json(selector_path, selector)
|
||||
write_json(edges_path, edges)
|
||||
rules = validate_verification(verification, cdsl_copy)
|
||||
quality_report = evaluate_quality(rules, cdsl_copy, engine_result)
|
||||
quality_report["evaluated_at"] = now_iso()
|
||||
write_json(quality_path, quality_report)
|
||||
quality_status = (
|
||||
"accepted" if rules and quality_report["status"] == "passed"
|
||||
else "built_with_warnings" if quality_report["status"] == "passed"
|
||||
else "needs_repair"
|
||||
)
|
||||
if quality_report["status"] != "passed":
|
||||
raise QualityVerificationError(quality_report)
|
||||
report = {
|
||||
"engine_result": engine_result,
|
||||
"preview": preview,
|
||||
@@ -570,16 +569,40 @@ def build_revision(
|
||||
}
|
||||
write_json(report_path, report)
|
||||
revision = revision_record("success", engine_name=str(engine_result["engine"]))
|
||||
revision["quality_status"] = quality_status or "accepted"
|
||||
revision["verification_summary"] = {
|
||||
"requested": bool(rules),
|
||||
"blocking_failures": len(quality_report.get("blocking_failures") or []),
|
||||
"warnings": len(quality_report.get("warnings") or []),
|
||||
}
|
||||
except Exception as error:
|
||||
if not isinstance(error, QualityVerificationError):
|
||||
# The failed candidate is retained in the conversation diagnostics,
|
||||
# not as a task revision. Runtime/schema failures must not create
|
||||
# an editable revision that looks like a model version.
|
||||
shutil.rmtree(revision_dir, ignore_errors=True)
|
||||
raise
|
||||
if not cdsl_path.is_file() and isinstance(cdsl, dict):
|
||||
write_json(cdsl_path, copy.deepcopy(cdsl))
|
||||
if quality_report is not None and not quality_path.is_file():
|
||||
write_json(quality_path, quality_report)
|
||||
write_json(report_path, {
|
||||
"error": str(error),
|
||||
"generation_context": generation_context,
|
||||
"quality": quality_report,
|
||||
"validated_at": now_iso(),
|
||||
})
|
||||
revision = revision_record("failed", error=str(error))
|
||||
revision = revision_record("needs_repair" if quality_report is not None else "failed", error=str(error))
|
||||
revision["quality_status"] = quality_status or ("needs_repair" if quality_report else "failed")
|
||||
if quality_report is not None:
|
||||
revision["verification_summary"] = {
|
||||
"requested": bool(quality_report.get("verification_requested")),
|
||||
"blocking_failures": len(quality_report.get("blocking_failures") or []),
|
||||
"warnings": len(quality_report.get("warnings") or []),
|
||||
}
|
||||
store.update_task(task["task_id"], revision)
|
||||
error.task_id = task["task_id"]
|
||||
error.revision_id = revision_id
|
||||
raise
|
||||
store.update_task(task["task_id"], revision)
|
||||
return {"task_id": task["task_id"], **revision}
|
||||
|
||||
Reference in New Issue
Block a user