Files
cdsl-cad/backend/app/cad_agent/adapters/review_gateway.py
T
2026-09-01 14:10:23 +08:00

59 lines
2.6 KiB
Python

"""Independent rendered-review adapter for protocol v3."""
from __future__ import annotations
import base64
import json
from pathlib import Path
from typing import Any
from app.cad_agent.adapters.structured_llm import StructuredModelGateway
from app.cad_agent.ports import AdapterUnavailable
class RenderedReviewGateway:
def __init__(self, models: StructuredModelGateway) -> None:
self.models = models
async def review(self, *, kind: str, payload: dict[str, Any], tool: dict[str, Any], provider_id: str, model_id: str) -> dict[str, Any]:
name = str((tool.get("function") or {}).get("name") or "")
if not name:
raise RuntimeError("Review tool is missing a name")
content: list[dict[str, Any]] = [{"type": "text", "text": json.dumps(payload, ensure_ascii=False)}]
if kind in {"candidate", "final"}:
manifest = payload.get("render_manifest") if isinstance(payload.get("render_manifest"), dict) else {}
for path in self._evidence_paths(manifest):
content.append(self._image_part(path))
return await self.models.call_tool(
messages=[
{"role": "system", "content": "You are an independent CAD reviewer. Inspect supplied deterministic facts and rendered images. Return only the specified structured tool call."},
{"role": "user", "content": content},
],
tool=tool,
provider_id=provider_id,
model_id=model_id,
required_tool_name=name,
)
@staticmethod
def _evidence_paths(manifest: dict[str, Any]) -> list[Path]:
selected: list[Path] = []
contact = Path(str(manifest.get("contact_sheet_path") or ""))
if contact.is_file():
selected.append(contact)
wanted = {"top", "front", "right", "isometric"}
for item in manifest.get("views") or ():
if not isinstance(item, dict) or str(item.get("id") or "") not in wanted:
continue
path = Path(str(item.get("path") or ""))
if path.is_file():
selected.append(path)
if not selected:
raise AdapterUnavailable("RENDER_SERVICE_UNAVAILABLE: review render evidence is unavailable")
return selected[:5]
@staticmethod
def _image_part(path: Path) -> dict[str, Any]:
media_type = "image/jpeg" if path.suffix.lower() in {".jpg", ".jpeg"} else "image/png"
return {"type": "image_url", "image_url": {"url": f"data:{media_type};base64,{base64.b64encode(path.read_bytes()).decode('ascii')}"}}