基座(cad_mcp_frame): - 新增 RecvOneFrame: 以 '\n' 为消息边界分帧,去掉单次 recv(2048) 限制(上限 8MB); 用 select 短探测兼容不发 '\n' 的旧客户端;clientSocket 设 SO_RCVTIMEO 防卸载卡死 - JSON-RPC id 支持 string/number;无 id 的通知不回包 - isError 映射: 协议级错误保留顶层 error,工具执行错误转 result.isError=true - 响应补 '\n' 作为消息边界 Agent(新增 dwg_review 包): - mcp_client: 唯一 TCP/JSON-RPC 封装(单飞锁/分帧/重试/兼容 error 与 isError) - cad_tools: 从 tools/list 动态建表,含 15 个只读白名单 - phases: planning -> evidence -> review(受控只读取证子循环)-> report - annotator: 批注回写 AI_REVIEW 层,支持 dry-run 与撤销 - config: 配置全部走环境变量;Windows 下回退读注册表以读到新设的系统变量 其它: 新增 verify_mcp_frame.py 验证脚本;.gitignore 补 Python/AutoCAD 锁文件规则; ai.memory 记录架构决策与实测结论
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""阶段 4:汇总报告 + 结构化问题抽取。"""
|
||
|
||
import json
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
from .. import config
|
||
|
||
|
||
def summarize(ctx, findings, log=print):
|
||
"""用 LLM 把各图框结论汇总为总报告。"""
|
||
if not findings:
|
||
return "未识别到可审查的图框,或所有图框均无有效包围盒。"
|
||
|
||
report_input = "\n\n".join(
|
||
f"【图框 {f['frame']}(图幅 {f.get('sheet')})】\n{f['finding']}" for f in findings
|
||
)
|
||
system = (
|
||
"你是图纸审查负责人。请把下面各图框的审查意见汇总为一份结构化的审查报告:"
|
||
"先给整体结论,再按图框列出问题,最后给出整改建议。语言简洁、条目化。"
|
||
)
|
||
try:
|
||
return ctx.llm.chat_text(system, report_input)
|
||
except Exception as exc: # noqa: BLE001
|
||
log(f"[汇总失败] {exc}")
|
||
return report_input
|
||
|
||
|
||
_EXTRACT_SYSTEM = (
|
||
"你是结构化信息抽取器。请把下面的图纸审查意见抽取为 JSON 数组,"
|
||
"每个元素形如:"
|
||
'{"frame": <图框序号>, "handle": "<实体句柄,没有则省略>", '
|
||
'"category": "<图层/文字/标注/几何/图面整体>", '
|
||
'"severity": "<high|medium|low>", "message": "<问题描述>"}。'
|
||
"只输出 JSON 数组,不要输出任何解释、不要用 markdown 代码块包裹。"
|
||
"若某条意见无法定位到具体实体,省略 handle 字段。"
|
||
)
|
||
|
||
|
||
def extract_issues(ctx, findings, log=print):
|
||
"""把自由文本的审查意见抽取为结构化问题列表(用于批注回写)。"""
|
||
if not findings:
|
||
return []
|
||
source = "\n\n".join(
|
||
f"图框 {f['frame']}:\n{f['finding']}" for f in findings
|
||
)
|
||
try:
|
||
text = ctx.llm.chat_text(_EXTRACT_SYSTEM, source)
|
||
text = text.strip()
|
||
if text.startswith("```"):
|
||
text = text.strip("`")
|
||
text = text.split("\n", 1)[1] if "\n" in text else text
|
||
data = json.loads(text)
|
||
if isinstance(data, list):
|
||
return data
|
||
except Exception as exc: # noqa: BLE001
|
||
log(f"[结构化抽取失败] {exc}")
|
||
return []
|
||
|
||
|
||
def save_report(markdown, findings, plan, issues=None, log=print):
|
||
"""落盘 Markdown + JSON 报告,返回两个路径。"""
|
||
out_dir = Path(config.REPORT_DIR)
|
||
out_dir.mkdir(parents=True, exist_ok=True)
|
||
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
md_path = out_dir / f"review_{stamp}.md"
|
||
json_path = out_dir / f"review_{stamp}.json"
|
||
|
||
md_path.write_text(markdown, encoding="utf-8")
|
||
json_path.write_text(
|
||
json.dumps(
|
||
{
|
||
"generated_at": stamp,
|
||
"findings": findings,
|
||
"issues": issues or [],
|
||
"frames": plan.get("frames"),
|
||
},
|
||
ensure_ascii=False,
|
||
indent=2,
|
||
),
|
||
encoding="utf-8",
|
||
)
|
||
log(f"[报告] 已写入 {md_path} 与 {json_path}")
|
||
return md_path, json_path
|