ReliableAgent:类似工程级可用的agent示例
ReliableAgent 的出发点很简单:
所以 ReliableAgent 不把目标设成"一个能演示的大文件脚本",而是设成一个更工业级的工程包:
- 标准的包结构(Core, Tools, Memory, Utils);
- 真正的动态 LLM 修复,拒绝硬编码;
- 极其健壮的 JSON 提取器,不怕 Markdown 干扰;
- 严格的安全沙箱校验;
- 拥有自愈能力的在线反馈循环。
核心取舍:
text
牺牲一点单文件的直观性 -> 换取高度模块化与解耦 -> 获得工业级可扩展的 Agent 架构
1. 整体工程架构 (Project Architecture)
原本的 Demo 习惯于把所有状态(内存、沙箱、工具定义)糅合在一个脚本里。如果硬要在一个文件里维护复杂的状态流转,通常会导致代码极度脆弱。
ReliableAgent 反过来:把复杂的流程拆解到不同的职责模块中。以下是真实的代码依赖关系:
#mermaid-svg-GZyY0hfCSmKL0NMD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GZyY0hfCSmKL0NMD .error-icon{fill:#552222;}#mermaid-svg-GZyY0hfCSmKL0NMD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GZyY0hfCSmKL0NMD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GZyY0hfCSmKL0NMD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GZyY0hfCSmKL0NMD .marker.cross{stroke:#333333;}#mermaid-svg-GZyY0hfCSmKL0NMD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GZyY0hfCSmKL0NMD p{margin:0;}#mermaid-svg-GZyY0hfCSmKL0NMD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GZyY0hfCSmKL0NMD .cluster-label text{fill:#333;}#mermaid-svg-GZyY0hfCSmKL0NMD .cluster-label span{color:#333;}#mermaid-svg-GZyY0hfCSmKL0NMD .cluster-label span p{background-color:transparent;}#mermaid-svg-GZyY0hfCSmKL0NMD .label text,#mermaid-svg-GZyY0hfCSmKL0NMD span{fill:#333;color:#333;}#mermaid-svg-GZyY0hfCSmKL0NMD .node rect,#mermaid-svg-GZyY0hfCSmKL0NMD .node circle,#mermaid-svg-GZyY0hfCSmKL0NMD .node ellipse,#mermaid-svg-GZyY0hfCSmKL0NMD .node polygon,#mermaid-svg-GZyY0hfCSmKL0NMD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GZyY0hfCSmKL0NMD .rough-node .label text,#mermaid-svg-GZyY0hfCSmKL0NMD .node .label text,#mermaid-svg-GZyY0hfCSmKL0NMD .image-shape .label,#mermaid-svg-GZyY0hfCSmKL0NMD .icon-shape .label{text-anchor:middle;}#mermaid-svg-GZyY0hfCSmKL0NMD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GZyY0hfCSmKL0NMD .rough-node .label,#mermaid-svg-GZyY0hfCSmKL0NMD .node .label,#mermaid-svg-GZyY0hfCSmKL0NMD .image-shape .label,#mermaid-svg-GZyY0hfCSmKL0NMD .icon-shape .label{text-align:center;}#mermaid-svg-GZyY0hfCSmKL0NMD .node.clickable{cursor:pointer;}#mermaid-svg-GZyY0hfCSmKL0NMD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GZyY0hfCSmKL0NMD .arrowheadPath{fill:#333333;}#mermaid-svg-GZyY0hfCSmKL0NMD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GZyY0hfCSmKL0NMD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GZyY0hfCSmKL0NMD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GZyY0hfCSmKL0NMD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GZyY0hfCSmKL0NMD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GZyY0hfCSmKL0NMD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GZyY0hfCSmKL0NMD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GZyY0hfCSmKL0NMD .cluster text{fill:#333;}#mermaid-svg-GZyY0hfCSmKL0NMD .cluster span{color:#333;}#mermaid-svg-GZyY0hfCSmKL0NMD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GZyY0hfCSmKL0NMD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GZyY0hfCSmKL0NMD rect.text{fill:none;stroke-width:0;}#mermaid-svg-GZyY0hfCSmKL0NMD .icon-shape,#mermaid-svg-GZyY0hfCSmKL0NMD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GZyY0hfCSmKL0NMD .icon-shape p,#mermaid-svg-GZyY0hfCSmKL0NMD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GZyY0hfCSmKL0NMD .icon-shape .label rect,#mermaid-svg-GZyY0hfCSmKL0NMD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GZyY0hfCSmKL0NMD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GZyY0hfCSmKL0NMD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GZyY0hfCSmKL0NMD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} demo.ipynb / Entry Point
core.agent: ReliableDebugAgent
core.dag: PlanDAG
core.loop: run_online_feedback_loop
tools.debugger: Python Sandbox & SafetyPolicy
utils.json_parser: extract_json_object
memory.retriever: TfidfRetriever
tools.registry: Tool Validation
2. 核心 DAG 逻辑 (The Execution Graph)
代码调试是一个具有严格前后依赖的过程。ReliableAgent 使用一个基于依赖描述的有向无环图(DAG)来管理这些阶段。DAG 的迷人之处在于它可以动态决定后续任务的走向。例如,如果没有查出错误,就不需要去执行后续消耗昂贵 Token 的修复流程:
#mermaid-svg-FsjkJqi64eYYb0ob{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FsjkJqi64eYYb0ob .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FsjkJqi64eYYb0ob .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FsjkJqi64eYYb0ob .error-icon{fill:#552222;}#mermaid-svg-FsjkJqi64eYYb0ob .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FsjkJqi64eYYb0ob .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FsjkJqi64eYYb0ob .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FsjkJqi64eYYb0ob .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FsjkJqi64eYYb0ob .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FsjkJqi64eYYb0ob .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FsjkJqi64eYYb0ob .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FsjkJqi64eYYb0ob .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FsjkJqi64eYYb0ob .marker.cross{stroke:#333333;}#mermaid-svg-FsjkJqi64eYYb0ob svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FsjkJqi64eYYb0ob p{margin:0;}#mermaid-svg-FsjkJqi64eYYb0ob .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FsjkJqi64eYYb0ob .cluster-label text{fill:#333;}#mermaid-svg-FsjkJqi64eYYb0ob .cluster-label span{color:#333;}#mermaid-svg-FsjkJqi64eYYb0ob .cluster-label span p{background-color:transparent;}#mermaid-svg-FsjkJqi64eYYb0ob .label text,#mermaid-svg-FsjkJqi64eYYb0ob span{fill:#333;color:#333;}#mermaid-svg-FsjkJqi64eYYb0ob .node rect,#mermaid-svg-FsjkJqi64eYYb0ob .node circle,#mermaid-svg-FsjkJqi64eYYb0ob .node ellipse,#mermaid-svg-FsjkJqi64eYYb0ob .node polygon,#mermaid-svg-FsjkJqi64eYYb0ob .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FsjkJqi64eYYb0ob .rough-node .label text,#mermaid-svg-FsjkJqi64eYYb0ob .node .label text,#mermaid-svg-FsjkJqi64eYYb0ob .image-shape .label,#mermaid-svg-FsjkJqi64eYYb0ob .icon-shape .label{text-anchor:middle;}#mermaid-svg-FsjkJqi64eYYb0ob .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FsjkJqi64eYYb0ob .rough-node .label,#mermaid-svg-FsjkJqi64eYYb0ob .node .label,#mermaid-svg-FsjkJqi64eYYb0ob .image-shape .label,#mermaid-svg-FsjkJqi64eYYb0ob .icon-shape .label{text-align:center;}#mermaid-svg-FsjkJqi64eYYb0ob .node.clickable{cursor:pointer;}#mermaid-svg-FsjkJqi64eYYb0ob .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FsjkJqi64eYYb0ob .arrowheadPath{fill:#333333;}#mermaid-svg-FsjkJqi64eYYb0ob .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FsjkJqi64eYYb0ob .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FsjkJqi64eYYb0ob .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FsjkJqi64eYYb0ob .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FsjkJqi64eYYb0ob .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FsjkJqi64eYYb0ob .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FsjkJqi64eYYb0ob .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FsjkJqi64eYYb0ob .cluster text{fill:#333;}#mermaid-svg-FsjkJqi64eYYb0ob .cluster span{color:#333;}#mermaid-svg-FsjkJqi64eYYb0ob div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FsjkJqi64eYYb0ob .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FsjkJqi64eYYb0ob rect.text{fill:none;stroke-width:0;}#mermaid-svg-FsjkJqi64eYYb0ob .icon-shape,#mermaid-svg-FsjkJqi64eYYb0ob .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FsjkJqi64eYYb0ob .icon-shape p,#mermaid-svg-FsjkJqi64eYYb0ob .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FsjkJqi64eYYb0ob .icon-shape .label rect,#mermaid-svg-FsjkJqi64eYYb0ob .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FsjkJqi64eYYb0ob .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FsjkJqi64eYYb0ob .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FsjkJqi64eYYb0ob :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 检测到预期 Error
无任何 Error
开始调试任务
节点: read_file
节点: check_syntax
节点: diagnose
节点: apply_fix
节点: 跳过修复
节点: verify
结束
在真实代码中,diagnose 节点的返回值直接控制着 apply_fix 的前置条件拦截。
3. 在线反馈自愈循环 (Online Feedback Loop)
这是 Agent 的心脏。原本的 Demo 是这样工作的:LLM返回畸形JSON -> 解析报错 -> 流程中断。
对于无人值守的自动化修复场景,重试和自愈比"第一次就蒙对"更可靠。ReliableAgent 会稳稳接住所有异常,并把它们转化为下一次尝试的经验:
#mermaid-svg-uHHiNQqx8uHp3XG1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uHHiNQqx8uHp3XG1 .error-icon{fill:#552222;}#mermaid-svg-uHHiNQqx8uHp3XG1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uHHiNQqx8uHp3XG1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .marker.cross{stroke:#333333;}#mermaid-svg-uHHiNQqx8uHp3XG1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uHHiNQqx8uHp3XG1 p{margin:0;}#mermaid-svg-uHHiNQqx8uHp3XG1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .cluster-label text{fill:#333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .cluster-label span{color:#333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .cluster-label span p{background-color:transparent;}#mermaid-svg-uHHiNQqx8uHp3XG1 .label text,#mermaid-svg-uHHiNQqx8uHp3XG1 span{fill:#333;color:#333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .node rect,#mermaid-svg-uHHiNQqx8uHp3XG1 .node circle,#mermaid-svg-uHHiNQqx8uHp3XG1 .node ellipse,#mermaid-svg-uHHiNQqx8uHp3XG1 .node polygon,#mermaid-svg-uHHiNQqx8uHp3XG1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uHHiNQqx8uHp3XG1 .rough-node .label text,#mermaid-svg-uHHiNQqx8uHp3XG1 .node .label text,#mermaid-svg-uHHiNQqx8uHp3XG1 .image-shape .label,#mermaid-svg-uHHiNQqx8uHp3XG1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-uHHiNQqx8uHp3XG1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uHHiNQqx8uHp3XG1 .rough-node .label,#mermaid-svg-uHHiNQqx8uHp3XG1 .node .label,#mermaid-svg-uHHiNQqx8uHp3XG1 .image-shape .label,#mermaid-svg-uHHiNQqx8uHp3XG1 .icon-shape .label{text-align:center;}#mermaid-svg-uHHiNQqx8uHp3XG1 .node.clickable{cursor:pointer;}#mermaid-svg-uHHiNQqx8uHp3XG1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .arrowheadPath{fill:#333333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uHHiNQqx8uHp3XG1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uHHiNQqx8uHp3XG1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uHHiNQqx8uHp3XG1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uHHiNQqx8uHp3XG1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uHHiNQqx8uHp3XG1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uHHiNQqx8uHp3XG1 .cluster text{fill:#333;}#mermaid-svg-uHHiNQqx8uHp3XG1 .cluster span{color:#333;}#mermaid-svg-uHHiNQqx8uHp3XG1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uHHiNQqx8uHp3XG1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uHHiNQqx8uHp3XG1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-uHHiNQqx8uHp3XG1 .icon-shape,#mermaid-svg-uHHiNQqx8uHp3XG1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uHHiNQqx8uHp3XG1 .icon-shape p,#mermaid-svg-uHHiNQqx8uHp3XG1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uHHiNQqx8uHp3XG1 .icon-shape .label rect,#mermaid-svg-uHHiNQqx8uHp3XG1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uHHiNQqx8uHp3XG1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uHHiNQqx8uHp3XG1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uHHiNQqx8uHp3XG1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 非法文本
有效 JSON 字典
参数缺失/工具未知
完美匹配
尝试越权/危险
合规
是
否
Task Prompt
LLM 生成回答
json_parser 提取
生成 Format Error 记录
Pydantic 校验参数
生成 Validation Error 记录
Safety Policy 安全核查
生成 Policy Error 记录
在 PythonDebugger 执行
返回 Observation 结果
尝试次数 < Max?
任务彻底失败
达成修复目标
每一次 Error 都不会让程序宕机,而是被追加到 Message History 中,迫使 LLM 反思,这不是过度设计,这是工业级 Agent 的底线。
4. 健壮的 JSON 状态机解析 (Robust JSON Parsing)
在使用大模型的过程中,LLM 总是喜欢在返回 JSON 前后加上诸如 Here is the tool call: 或 ```json 等废话。
一开始容易怀疑是 Prompt 不够好。但工业经验表明:永远不要信任 LLM 能严格遵守 JSON 格式。简单的 .index("{") 一旦遇到字符串里混有非相关花括号就会当场暴毙。
于是我们在 utils.json_parser.py 里实现了一个括号栈跟踪状态机,完美解决崩溃问题:
#mermaid-svg-cTSeGEeN3nsISodc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cTSeGEeN3nsISodc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cTSeGEeN3nsISodc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cTSeGEeN3nsISodc .error-icon{fill:#552222;}#mermaid-svg-cTSeGEeN3nsISodc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cTSeGEeN3nsISodc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cTSeGEeN3nsISodc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cTSeGEeN3nsISodc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cTSeGEeN3nsISodc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cTSeGEeN3nsISodc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cTSeGEeN3nsISodc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cTSeGEeN3nsISodc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cTSeGEeN3nsISodc .marker.cross{stroke:#333333;}#mermaid-svg-cTSeGEeN3nsISodc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cTSeGEeN3nsISodc p{margin:0;}#mermaid-svg-cTSeGEeN3nsISodc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cTSeGEeN3nsISodc .cluster-label text{fill:#333;}#mermaid-svg-cTSeGEeN3nsISodc .cluster-label span{color:#333;}#mermaid-svg-cTSeGEeN3nsISodc .cluster-label span p{background-color:transparent;}#mermaid-svg-cTSeGEeN3nsISodc .label text,#mermaid-svg-cTSeGEeN3nsISodc span{fill:#333;color:#333;}#mermaid-svg-cTSeGEeN3nsISodc .node rect,#mermaid-svg-cTSeGEeN3nsISodc .node circle,#mermaid-svg-cTSeGEeN3nsISodc .node ellipse,#mermaid-svg-cTSeGEeN3nsISodc .node polygon,#mermaid-svg-cTSeGEeN3nsISodc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cTSeGEeN3nsISodc .rough-node .label text,#mermaid-svg-cTSeGEeN3nsISodc .node .label text,#mermaid-svg-cTSeGEeN3nsISodc .image-shape .label,#mermaid-svg-cTSeGEeN3nsISodc .icon-shape .label{text-anchor:middle;}#mermaid-svg-cTSeGEeN3nsISodc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cTSeGEeN3nsISodc .rough-node .label,#mermaid-svg-cTSeGEeN3nsISodc .node .label,#mermaid-svg-cTSeGEeN3nsISodc .image-shape .label,#mermaid-svg-cTSeGEeN3nsISodc .icon-shape .label{text-align:center;}#mermaid-svg-cTSeGEeN3nsISodc .node.clickable{cursor:pointer;}#mermaid-svg-cTSeGEeN3nsISodc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cTSeGEeN3nsISodc .arrowheadPath{fill:#333333;}#mermaid-svg-cTSeGEeN3nsISodc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cTSeGEeN3nsISodc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cTSeGEeN3nsISodc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cTSeGEeN3nsISodc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cTSeGEeN3nsISodc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cTSeGEeN3nsISodc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cTSeGEeN3nsISodc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cTSeGEeN3nsISodc .cluster text{fill:#333;}#mermaid-svg-cTSeGEeN3nsISodc .cluster span{color:#333;}#mermaid-svg-cTSeGEeN3nsISodc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cTSeGEeN3nsISodc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cTSeGEeN3nsISodc rect.text{fill:none;stroke-width:0;}#mermaid-svg-cTSeGEeN3nsISodc .icon-shape,#mermaid-svg-cTSeGEeN3nsISodc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cTSeGEeN3nsISodc .icon-shape p,#mermaid-svg-cTSeGEeN3nsISodc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cTSeGEeN3nsISodc .icon-shape .label rect,#mermaid-svg-cTSeGEeN3nsISodc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cTSeGEeN3nsISodc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cTSeGEeN3nsISodc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cTSeGEeN3nsISodc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} '{'
'}'
否
是
成功
失败
接收 Raw Text
查找第一个 '{'
初始化括号栈 depth = 0
逐字符扫描
字符是 '{' 还是 '}'?
depth += 1
depth -= 1
depth == 0?
提取闭合子串
json.loads() 校验
返回纯净 JSON
回退 Regex 强行匹配
5. 沙箱安全与操作幂等性 (Sandbox Idempotency)
Agent 在慌乱的尝试中经常会"重试死循环",一遍遍地向同一个文件注入相同的补丁。为了防止文件被污染破坏,沙箱底层执行了绝对拦截:
#mermaid-svg-6jlvGfQAZ0445vS8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6jlvGfQAZ0445vS8 .error-icon{fill:#552222;}#mermaid-svg-6jlvGfQAZ0445vS8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6jlvGfQAZ0445vS8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6jlvGfQAZ0445vS8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6jlvGfQAZ0445vS8 .marker.cross{stroke:#333333;}#mermaid-svg-6jlvGfQAZ0445vS8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6jlvGfQAZ0445vS8 p{margin:0;}#mermaid-svg-6jlvGfQAZ0445vS8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6jlvGfQAZ0445vS8 .cluster-label text{fill:#333;}#mermaid-svg-6jlvGfQAZ0445vS8 .cluster-label span{color:#333;}#mermaid-svg-6jlvGfQAZ0445vS8 .cluster-label span p{background-color:transparent;}#mermaid-svg-6jlvGfQAZ0445vS8 .label text,#mermaid-svg-6jlvGfQAZ0445vS8 span{fill:#333;color:#333;}#mermaid-svg-6jlvGfQAZ0445vS8 .node rect,#mermaid-svg-6jlvGfQAZ0445vS8 .node circle,#mermaid-svg-6jlvGfQAZ0445vS8 .node ellipse,#mermaid-svg-6jlvGfQAZ0445vS8 .node polygon,#mermaid-svg-6jlvGfQAZ0445vS8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6jlvGfQAZ0445vS8 .rough-node .label text,#mermaid-svg-6jlvGfQAZ0445vS8 .node .label text,#mermaid-svg-6jlvGfQAZ0445vS8 .image-shape .label,#mermaid-svg-6jlvGfQAZ0445vS8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6jlvGfQAZ0445vS8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6jlvGfQAZ0445vS8 .rough-node .label,#mermaid-svg-6jlvGfQAZ0445vS8 .node .label,#mermaid-svg-6jlvGfQAZ0445vS8 .image-shape .label,#mermaid-svg-6jlvGfQAZ0445vS8 .icon-shape .label{text-align:center;}#mermaid-svg-6jlvGfQAZ0445vS8 .node.clickable{cursor:pointer;}#mermaid-svg-6jlvGfQAZ0445vS8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6jlvGfQAZ0445vS8 .arrowheadPath{fill:#333333;}#mermaid-svg-6jlvGfQAZ0445vS8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6jlvGfQAZ0445vS8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6jlvGfQAZ0445vS8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6jlvGfQAZ0445vS8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6jlvGfQAZ0445vS8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6jlvGfQAZ0445vS8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6jlvGfQAZ0445vS8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6jlvGfQAZ0445vS8 .cluster text{fill:#333;}#mermaid-svg-6jlvGfQAZ0445vS8 .cluster span{color:#333;}#mermaid-svg-6jlvGfQAZ0445vS8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6jlvGfQAZ0445vS8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6jlvGfQAZ0445vS8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6jlvGfQAZ0445vS8 .icon-shape,#mermaid-svg-6jlvGfQAZ0445vS8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6jlvGfQAZ0445vS8 .icon-shape p,#mermaid-svg-6jlvGfQAZ0445vS8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6jlvGfQAZ0445vS8 .icon-shape .label rect,#mermaid-svg-6jlvGfQAZ0445vS8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6jlvGfQAZ0445vS8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6jlvGfQAZ0445vS8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6jlvGfQAZ0445vS8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
否
是
存在语法错误
安全合规
apply_fix(fix_id, new_code)
fix_id 是否在 applied_fixes 中?
直接返回成功缓存 (幂等保护)
按行切分原始代码文件
line_start / end 是否越界?
返回 IndexError
执行文本替换
使用 ast.parse 做静态检查
驳回修改, 返回 SyntaxError
写入 applied_fixes 缓存
持久化修改并返回成功
6. 轻量级 RAG 匹配 (TF-IDF Retriever)
为了将报错代码(ErrorCode)翻译成指导性的经验法则,我们不需要去部署沉重的向量数据库,使用基于词频的 TF-IDF 加余弦相似度,就能在内存中极速完成查找:
#mermaid-svg-a516iP2jZ1OA6RPP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a516iP2jZ1OA6RPP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a516iP2jZ1OA6RPP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a516iP2jZ1OA6RPP .error-icon{fill:#552222;}#mermaid-svg-a516iP2jZ1OA6RPP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a516iP2jZ1OA6RPP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a516iP2jZ1OA6RPP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a516iP2jZ1OA6RPP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a516iP2jZ1OA6RPP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a516iP2jZ1OA6RPP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a516iP2jZ1OA6RPP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a516iP2jZ1OA6RPP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a516iP2jZ1OA6RPP .marker.cross{stroke:#333333;}#mermaid-svg-a516iP2jZ1OA6RPP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a516iP2jZ1OA6RPP p{margin:0;}#mermaid-svg-a516iP2jZ1OA6RPP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a516iP2jZ1OA6RPP .cluster-label text{fill:#333;}#mermaid-svg-a516iP2jZ1OA6RPP .cluster-label span{color:#333;}#mermaid-svg-a516iP2jZ1OA6RPP .cluster-label span p{background-color:transparent;}#mermaid-svg-a516iP2jZ1OA6RPP .label text,#mermaid-svg-a516iP2jZ1OA6RPP span{fill:#333;color:#333;}#mermaid-svg-a516iP2jZ1OA6RPP .node rect,#mermaid-svg-a516iP2jZ1OA6RPP .node circle,#mermaid-svg-a516iP2jZ1OA6RPP .node ellipse,#mermaid-svg-a516iP2jZ1OA6RPP .node polygon,#mermaid-svg-a516iP2jZ1OA6RPP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a516iP2jZ1OA6RPP .rough-node .label text,#mermaid-svg-a516iP2jZ1OA6RPP .node .label text,#mermaid-svg-a516iP2jZ1OA6RPP .image-shape .label,#mermaid-svg-a516iP2jZ1OA6RPP .icon-shape .label{text-anchor:middle;}#mermaid-svg-a516iP2jZ1OA6RPP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a516iP2jZ1OA6RPP .rough-node .label,#mermaid-svg-a516iP2jZ1OA6RPP .node .label,#mermaid-svg-a516iP2jZ1OA6RPP .image-shape .label,#mermaid-svg-a516iP2jZ1OA6RPP .icon-shape .label{text-align:center;}#mermaid-svg-a516iP2jZ1OA6RPP .node.clickable{cursor:pointer;}#mermaid-svg-a516iP2jZ1OA6RPP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a516iP2jZ1OA6RPP .arrowheadPath{fill:#333333;}#mermaid-svg-a516iP2jZ1OA6RPP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a516iP2jZ1OA6RPP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a516iP2jZ1OA6RPP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a516iP2jZ1OA6RPP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a516iP2jZ1OA6RPP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a516iP2jZ1OA6RPP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a516iP2jZ1OA6RPP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a516iP2jZ1OA6RPP .cluster text{fill:#333;}#mermaid-svg-a516iP2jZ1OA6RPP .cluster span{color:#333;}#mermaid-svg-a516iP2jZ1OA6RPP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a516iP2jZ1OA6RPP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a516iP2jZ1OA6RPP rect.text{fill:none;stroke-width:0;}#mermaid-svg-a516iP2jZ1OA6RPP .icon-shape,#mermaid-svg-a516iP2jZ1OA6RPP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a516iP2jZ1OA6RPP .icon-shape p,#mermaid-svg-a516iP2jZ1OA6RPP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a516iP2jZ1OA6RPP .icon-shape .label rect,#mermaid-svg-a516iP2jZ1OA6RPP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a516iP2jZ1OA6RPP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a516iP2jZ1OA6RPP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a516iP2jZ1OA6RPP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 捕获到的 Error 堆栈信息
TfidfVectorizer.transform
计算 cosine_similarity
过滤得分 > 0 的文档
根据相似度分数降序排列
返回 Top K 的修复经验 (KnowledgeDoc)
为什么这个项目有意思?
很多 LLM 教程默认追求"炫酷":更花哨的 UI、无脑接入更多的 API。
ReliableAgent 追求的是另一个方向:
text
高度解耦 + 极强自愈 + 工业级规范
它适合这种人:
- 手上有复杂的代码调试任务;
- 只想让 Agent 安安静静在后台跑通测试;
- 不能接受一个由于解析异常就宕机罢工的 Demo;
- 对代码架构有洁癖,渴望扩展。
这就是 ReliableAgent 的价值。
开源
项目仓库:
bash
gh repo clone STEPHENXING/hand-agent-debug-python-code
目前仓库已经完成了工业级的重构,并成功 Push。方向已经很明确:
不是做一个好玩的 Notebook,而是打造一个真正工程可用的代码调试 Agent。
彩蛋:值得收藏的工程代码片段 (Implementation Details)
在实现这个工业级 Agent 的过程中,有许多极为硬核且优雅的代码细节。为了方便各位同行在未来"抄作业",我把它们单独拎出来,作为一个可以随时查阅的"武器库"。
1. TF-IDF 计算与知识检索
如何不用沉重的向量数据库,手撸一个轻量高效的错误修复知识检索?利用 sklearn 的 TfidfVectorizer 和余弦相似度即可完成:
python
class TfidfRetriever:
def __init__(self, docs: list[KnowledgeDoc]):
self.docs = docs
self.vectorizer = TfidfVectorizer(lowercase=True, ngram_range=(1, 2), token_pattern=r"(?u)\b[\w_]+\b")
# 将标题、文本和 Tag 拼接成语料
corpus = [f"{d.title} {d.text} {' '.join(d.tags)}" for d in docs]
self.doc_matrix = self.vectorizer.fit_transform(corpus)
def retrieve(self, query: str, k: int = 3) -> list[tuple[KnowledgeDoc, float]]:
query_vector = self.vectorizer.transform([query])
# 将稀疏矩阵转换为余弦相似度分数
scores = cosine_similarity(query_vector, self.doc_matrix).flatten()
# 过滤掉得分为0的结果,并按相关性降序
return sorted(((doc, s) for doc, s in zip(self.docs, scores) if s > 0), key=lambda x: x[1], reverse=True)[:k]
2. DAG 的 DFS 循环依赖检测
任务编排最怕死锁。在初始化 PlanDAG 时,我们必须严格审查节点之间是否存在环。这里使用标准的深度优先搜索(DFS)加递归栈(Recursion Stack)来实现:
python
def _has_cycle(self, node_id: str, graph: dict[str, set[str]], visited: set[str], rec_stack: set[str]) -> bool:
visited.add(node_id)
rec_stack.add(node_id)
for neighbor in graph.get(node_id, set()):
if neighbor not in visited:
if self._has_cycle(neighbor, graph, visited, rec_stack):
return True
elif neighbor in rec_stack:
# 如果邻居已经存在于当前的递归栈中,说明成环!
return True
rec_stack.remove(node_id)
return False
def validate_acyclic(self) -> None:
graph = {n.node_id: n.dependencies for n in self.nodes.values()}
for node in self.nodes.values():
if self._has_cycle(node.node_id, graph, set(), set()):
raise ValueError(f"Cycle detected involving node: {node.node_id}")
3. DAG 的两个阶段:诊断与行动的分离
DAG 的迷人之处在于它可以动态决定后续任务的走向。比如,如果没有查出错误,就不需要去执行修复:
python
# Phase 1: Diagnose (确定性诊断)
if node.node_id == "diagnose":
syntax = dag.nodes["check_syntax"].result or {}
errors = syntax.get("errors", [])
has_expected = any(e.get("error_type") == expected_error_type for e in errors)
# 根据是否有期待的报错,产出 decision
node.result = {"decision": "fix" if has_expected else "skip"}
node.status = NodeStatus.SUCCEEDED
# Phase 2: Apply Fix (基于决策的行动)
elif node.node_id == "apply_fix":
if dag.nodes["diagnose"].result.get("decision") != "fix":
# 错误不存在,直接跳过当前节点,不消耗 LLM Token
node.status = NodeStatus.SKIPPED
continue
# 否则组装代码给 LLM 发起修复请求...
4. 工具调用的绝对幂等性
LLM 很容易陷入"重试死循环",一遍遍地执行修改代码的操作。如何防止文件被破坏?在 Agent 调度层面分配一个固定的 fix_id,底层工具执行拦截:
python
def apply_fix(self, file_path: str, line_start: int, line_end: int, new_code: str, fix_id: str) -> dict[str, Any]:
if fix_id in self.applied_fixes:
# 【关键保护机制】:遇到相同 fix_id,直接视为成功,防止 LLM 在迷失时重复污染代码!
return {**self.applied_fixes[fix_id], "idempotent_replay": True}
# 执行实际修改...
self.applied_fixes[fix_id] = result
return result
5. 离线数据的落盘(虚拟到现实)
Agent 大量操作在内存(PythonDebugger 沙箱)中进行。调试完成后,通过一个简洁的同步函数,将内存文件写回真实的工程中:
python
from pathlib import Path
PROJECT_DIR = Path("project")
def sync_debugger_to_disk(dbg: PythonDebugger) -> None:
"""将 PythonDebugger 内存中的文件状态真实写入磁盘"""
for file_path, lines in dbg.files.items():
# 清理路径前缀,对接真实目录
disk_path = PROJECT_DIR / file_path.replace("project/", "")
disk_path.parent.mkdir(parents=True, exist_ok=True)
# 强制 UTF-8 编码,规范换行
disk_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
6. 行为打分与可追溯性
为了衡量 Agent 是否"聪明",而不是简单地看最终是否成功,需要设计多维度的 RewardBreakdown,精确计算每一次挣扎的成本:
python
def reward_for_loop(result: LoopResult) -> RewardBreakdown:
breakdown = RewardBreakdown()
for record in result.records:
if record.parsed_json is not None: breakdown.format_valid = 0.5
if record.validated_call is not None: breakdown.arguments_valid = 1.0
if record.observation is not None: breakdown.execution_success = 2.0
if record.error:
# 惩罚机制:无限循环复读机扣重分!
if record.error.code == ErrorCode.REPEATED_CANDIDATE:
breakdown.repetition_cost += 2.0
# 惩罚机制:尝试越权访问扣分!
elif record.error.code == ErrorCode.POLICY_REJECTED:
breakdown.unsafe_cost += 1.0
if result.success: breakdown.goal_success = 4.0
return breakdown
7. Section 隔离与即时测试断言
为了在早期写 Notebook 阶段保证底盘不出 bug,每一个特性单元后面都必须紧跟一个 check。这比写大块头测试框架来得敏捷:
python
CHECK_RESULTS: list[tuple[str, bool, str]] = []
def check(name: str, fn: Callable[[], None]) -> None:
"""每个 Section 完成后立即调用此函数验证,保证底盘稳固"""
try:
fn()
except Exception as exc:
CHECK_RESULTS.append((name, False, f"{type(exc).__name__}: {exc}"))
print(f"❌ {name}\n {type(exc).__name__}: {exc}")
else:
CHECK_RESULTS.append((name, True, ""))
print(f"✅ {name}")