627 lines
29 KiB
Markdown
627 lines
29 KiB
Markdown
# Agent Web Chat
|
||
|
||
基于 LangGraph 的多模态对话 Agent,支持文本、图片、音频、多种类型文件输入,具备屏幕截图、浏览器控制、RAG 知识库、数据库查询、本地文件获取等工具调用能力。
|
||
|
||
## 快速开始
|
||
|
||
### 1. 克隆仓库并进入项目目录
|
||
|
||
```bash
|
||
git clone <your-repo-url>
|
||
cd Agent
|
||
|
||
Windows:
|
||
bash
|
||
python -m venv myenv
|
||
.\myenv\Scripts\activate
|
||
macOS / Linux:
|
||
bash
|
||
python -m venv myenv
|
||
source myenv/bin/activate
|
||
|
||
pip install --upgrade pip
|
||
pip install -r requirements.txt
|
||
pip install -e .
|
||
|
||
gedit .env
|
||
必须填写的变量:
|
||
LLM_BASE_URL – 大模型 API 基础地址
|
||
LLM_API_KEY – API 密钥
|
||
LLM_MODEL – 模型名称(如 Qwen/Qwen2.5-72B-Instruct)
|
||
TTS_BASE_URL – 文本转语音服务地址(可选,若不使用音频回复可不填)
|
||
ASR_BASE_URL – 语音识别服务地址(可选)
|
||
RAG_BASE_URL – RAG 嵌入与重排序服务地址(可选)
|
||
|
||
# 启动 Web 对话服务(推荐)
|
||
# 确保在 Agent 目录下,且 PYTHONPATH 包含 src 目录
|
||
cd ./Agent
|
||
export PYTHONPATH=src:$PYTHONPATH # Linux/macOS
|
||
# 或 Windows PowerShell: $env:PYTHONPATH="src;$env:PYTHONPATH"
|
||
uvicorn web.server:app --host 0.0.0.0 --port 8765
|
||
|
||
# (可选)通过 Cloudflare Tunnel 暴露公网
|
||
cloudflared tunnel --url http://localhost:8765
|
||
|
||
# 进入项目目录并提交代码(Windows 下路径为 .\Agent\)
|
||
✅ 推送到公司
|
||
powershell
|
||
# 进入项目目录
|
||
cd Agent
|
||
# 暂存所有修改
|
||
git add .
|
||
# 提交修改(请替换双引号里的内容为实际说明)
|
||
git commit -m "修改内容的说明"
|
||
# 默认推送到公司
|
||
git push
|
||
🌐 推送到个人 GitHub
|
||
powershell
|
||
# 进入项目目录
|
||
cd Agent
|
||
# 暂存所有修改
|
||
git add .
|
||
# 提交修改(请替换双引号里的内容为实际说明)
|
||
git commit -m "修改内容的说明"
|
||
# 显式指定推送到个人仓库 (minor)
|
||
git push minor master
|
||
|
||
###循环检测
|
||
用户输入 → runtime.py 重置循环状态
|
||
→ graph.invoke 开始执行
|
||
│
|
||
├─ agent 节点 (nodes.py / call_model)
|
||
│ ├─ _build_reflection_prompt()
|
||
│ │ ├─ check_and_record_todolist_progress() ← todolist 停滞检测
|
||
│ │ │ └─ 10轮无进展 → 注入反思提示:"请反思当前卡点..."
|
||
│ │ │
|
||
│ │ └─ build_loop_reflection_prompt()
|
||
│ │ └─ detect_repeated_tool_calls() ← 连续相同工具检测
|
||
│ │ └─ 连续3次 → 注入反思提示:"你已连续多次调用同一工具..."
|
||
│ │
|
||
│ └─ LLM 调用(带反思提示引导)
|
||
│
|
||
├─ routing 节点 (routing.py / should_continue)
|
||
│ └─ should_force_end()
|
||
│ └─ 连续5次相同工具+相同参数 → 返回 END,强制终止
|
||
│
|
||
└─ 图结束后 (runtime.py / execute_agent)
|
||
└─ 检测到强制终止 → 向前端输出:
|
||
"检测到异常循环,工具 `xxx` 已连续调用 5 次..."
|
||
---
|
||
|
||
## 提示词工程文档
|
||
|
||
本 Agent 系统的提示词采用**分层架构**,按作用域和注入时机分为四个层级。以下逐一说明。
|
||
|
||
### 一、架构总览
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ Layer 1: 系统级提示词 (System Prompt) │
|
||
│ 作用于整个会话,定义 Agent 角色与核心行为准则 │
|
||
│ 文件: system_prompt.py / agent_config.json │
|
||
├─────────────────────────────────────────────────┤
|
||
│ Layer 2: 工具描述提示词 (Tool Description) │
|
||
│ 随工具定义注入,指导 LLM 如何正确调用工具 │
|
||
│ 文件: gui_tool.py / tool.py / tool_rag.py 等 │
|
||
├─────────────────────────────────────────────────┤
|
||
│ Layer 3: 运行时注入提示词 (Runtime Injection) │
|
||
│ 每轮 ReAct 循环动态拼接到消息列表 │
|
||
│ 文件: system_prompt.py / loop_detector.py │
|
||
├─────────────────────────────────────────────────┤
|
||
│ Layer 4: 内部模型提示词 (Internal Model Prompt) │
|
||
│ 工具内部调用子模型时使用,用户不可见 │
|
||
│ 文件: gui_tool.py (_build_batch_prompt) │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 二、Layer 1 — 系统级提示词
|
||
|
||
#### 2.1 MAIN_SYSTEM_PROMPT(Agent 模式)
|
||
|
||
**位置:** `agent/memory/system_prompt.py` → `MAIN_SYSTEM_PROMPT`
|
||
|
||
**作用:** Agent 模式的顶层系统提示词,定义 Agent 的身份和全局行为准则。
|
||
|
||
**核心规则:**
|
||
|
||
| 规则 | 目的 |
|
||
|------|------|
|
||
| 优先理解用户意图,高效完成任务 | 确立目标导向 |
|
||
| 对图片/音频直接使用多模态能力分析,不调用无关工具 | 防止误调用截图/OCR |
|
||
| 截图返回后必须基于实际结果推进,严禁凭空编造 | 防止幻觉 |
|
||
| **同一页面的 GUI 操作必须合并为一次 gui_tool 调用** | 减少延迟,避免单步执行 |
|
||
| 不确定是否需要工具时优先不调用 | 防止过度工具调用 |
|
||
| 复杂任务主动使用 todo_list 规划并跟踪进度 | 确保多步任务可追踪 |
|
||
|
||
**设计要点:**
|
||
- 第 8 行从"优先考虑动作序列"升级为"**必须**合并",并明确"严禁逐个单步调用",以对抗 ReAct 循环天然倾向于单步执行的问题。
|
||
- 第 6 行"基于实际结果继续推进"是为了让模型把截图当作真实的视觉反馈,而非想象屏幕状态。
|
||
|
||
---
|
||
|
||
#### 2.2 PLAN_MODE_PROMPT(Plan 模式)
|
||
|
||
**位置:** `agent/memory/system_prompt.py` → `PLAN_MODE_PROMPT`
|
||
|
||
**作用:** 当 `agent_mode == "plan"` 时替换 MAIN_SYSTEM_PROMPT,强制 Agent 先规划后执行。
|
||
|
||
**核心规则:**
|
||
- 收到用户请求后**第一步必须调用 todo_list** 进行任务拆解
|
||
- 步骤拆解必须详尽,覆盖完整流程
|
||
- 规划完成后立即开始执行,不等待用户确认
|
||
- 每完成一步调用 todo_list 标记进度
|
||
|
||
---
|
||
|
||
#### 2.3 Agent 级 system_prompt
|
||
|
||
**位置:** `agent/core/agent_config.json`
|
||
|
||
```json
|
||
"system_prompt": "你是一个多模态助手...你的默认行为是优先调用工具解决用户问题。"
|
||
```
|
||
|
||
**作用:** 当 `agent_config.json` 中配置了 `system_prompt` 时,会作为额外的行为准则注入。当前版本的指令较为简略,主要作为兜底。
|
||
|
||
---
|
||
|
||
#### 2.4 其他专用 System Prompt
|
||
|
||
| Prompt | 用途 | 特点 |
|
||
|--------|------|------|
|
||
| VISION_SYSTEM_PROMPT | 图像理解子 Agent | 要求客观描述、不臆测、多图编号 |
|
||
| DB_SYSTEM_PROMPT | 数据库查询子 Agent | 只允许 SELECT/WITH,强调安全底线 |
|
||
| SUMMARY_SYSTEM_PROMPT | 摘要子 Agent | 压缩至 20%-30%,保留关键信息 |
|
||
| TEAM_MODE_PROMPT | Team 模式占位 | 当前未实现,告知用户使用 Agent 模式 |
|
||
|
||
---
|
||
|
||
### 三、Layer 2 — 工具描述提示词
|
||
|
||
工具描述提示词直接定义在工具类的 `description` 属性中,随 LangChain 工具绑定注入到 LLM 上下文。
|
||
|
||
#### 3.1 gui_tool 描述
|
||
|
||
**位置:** `agent/tools/gui_tool.py` → `GUITool.description`
|
||
|
||
**结构:**
|
||
|
||
```
|
||
GUI 自动化工具。通过截图视觉定位桌面元素...
|
||
↓
|
||
【type 使用说明】type 支持 element_description 自动聚焦输入框
|
||
↓
|
||
【网页滚动提示】推荐用 hotkey space 代替 scroll 滚轮
|
||
↓
|
||
【下拉框处理策略】直接使用 type 动作,系统自动匹配选项
|
||
↓
|
||
示例:登录表单 actions 列表
|
||
```
|
||
|
||
**设计要点:**
|
||
- **type 操作说明:** 强调 `element_description` 参数可让 type 自动完成"定位→点击→输入",无需额外 click。这是减少动作数量的关键设计。
|
||
- **网页滚动提示:** 引导模型使用 `hotkey space`(空格键即 Page Down)而非 `scroll` 滚轮,因为浏览器中滚轮定位不准确。
|
||
- **下拉框处理:** 精简为一步 `type`,去掉了之前的两步 fallback 策略(点击→方向键→回车),减少不必要的操作。
|
||
- **登录表单示例:** 提供一个多 action 的 one-shot 示例,示范如何将两个 type 操作合并到一个 gui_tool 调用中。
|
||
|
||
---
|
||
|
||
#### 3.2 todo_list 描述
|
||
|
||
**位置:** `agent/tools/tool.py` → `TodoList.description`
|
||
|
||
**关键指令:**
|
||
- **步骤粒度要求:** 同一页面/界面的连续操作合并为一条步骤,用"并"连接
|
||
- 包含正例(推荐)和反例(禁止)的对比
|
||
- Plan 模式下为必须调用,Agent 模式下按需
|
||
|
||
**示例:**
|
||
```
|
||
推荐:"点击搜索框,输入XXX并点击搜索"(合并了点击、输入、搜索)
|
||
禁止:"1.点击搜索框 2.输入文字 3.点击搜索"(过于细碎)
|
||
```
|
||
|
||
---
|
||
|
||
### 四、Layer 3 — 运行时注入提示词
|
||
|
||
这类提示词在每轮 ReAct 循环中动态拼接到消息列表,作为 synthetic HumanMessage 注入,不会被记录到前端消息历史。
|
||
|
||
#### 4.1 REFLECTION_PROMPT(反思提示)
|
||
|
||
**位置:** `agent/memory/system_prompt.py` → `REFLECTION_PROMPT`
|
||
|
||
**注入时机:** 每轮 ReAct 循环(除首轮外),由 `nodes.py` 的 `_build_reflection_prompt()` 构建并注入。
|
||
|
||
**结构:**
|
||
|
||
```
|
||
【请先在你的回复中尽可能简短地输出你的反思...】
|
||
1. 分析上一轮执行结果
|
||
2. GUI 未命中 → 重新估算坐标重试
|
||
3. 操作成功 → 进行下一步
|
||
4. 调用工具或回复用户
|
||
|
||
【gui_tool 批量调用规则 - 务必遵守】
|
||
同一页面的连续操作必须合并为一次 gui_tool 调用
|
||
严禁拆分多次调用——会造成严重延迟
|
||
仅页面跳转/加载后才分步调用
|
||
```
|
||
|
||
**设计要点:**
|
||
- 前 4 条建立"观察→反思→行动"的思维链模式
|
||
- **第 5 条是关键补充:** 明确告知模型在做"下一步"计划时,如果同一页面有多个操作,必须打包成一个 gui_tool 调用。这是对抗 ReAct 循环"做一步看一步"惯性的核心机制。
|
||
- 仅当需要等页面跳转/加载后才能确定后续操作时,才允许分步,给了模型合理的例外判断空间。
|
||
|
||
---
|
||
|
||
#### 4.2 PLAN_FIRST_CALL_PROMPT(Plan 模式首轮注入)
|
||
|
||
**位置:** `agent/memory/system_prompt.py` → `PLAN_FIRST_CALL_PROMPT`
|
||
|
||
**注入时机:** Plan 模式下的首轮调用,在 `call_model` 中检测到 `agent_mode == "plan" && is_first_call` 时注入。
|
||
|
||
**核心指令:**
|
||
- **立即调用 todo_list** 进行任务拆解
|
||
- **合并同类操作:** 同一页面的连续操作合并为一条步骤
|
||
- 包含三个正例和一个反例的详细示范
|
||
|
||
---
|
||
|
||
#### 4.3 反循环反思提示
|
||
|
||
**位置:** `agent/core/loop_detector.py` → `build_loop_reflection_prompt()`
|
||
|
||
**注入时机:** 当检测到循环风险时,拼接到 REFLECTION_PROMPT 之后。
|
||
|
||
**两级响应:**
|
||
|
||
| 级别 | 触发条件 | 响应 |
|
||
|------|----------|------|
|
||
| Level 1 (警告) | 连续 3 次调用同一工具且参数相同 | 注入反思提示:"你已经连续 N 次调用工具...请反思当前策略" |
|
||
| Level 1 (警告) | TodoList 停滞 10 轮无进展 | 注入反思提示:"TodoList 已有 N 轮没有进展...考虑调整计划" |
|
||
| Level 2 (终止) | 连续 5 次调用同一工具且参数相同 | 强制结束图执行,向前端输出错误报告 |
|
||
|
||
**设计要点:**
|
||
- 两级响应机制避免误杀——先警告引导模型自我纠偏,纠偏失败再强制终止
|
||
- 循环检测阈值可通过 `env_config.json` 中的 `LOOP_DETECT_*` 配置项热更新
|
||
|
||
---
|
||
|
||
#### 4.4 截图反馈注入
|
||
|
||
**位置:** `agent/core/nodes.py` → `process_tool_artifact()`
|
||
|
||
**注入时机:** 每次 gui_tool 返回 artifact(截图)后。
|
||
|
||
**注入内容:** `[图片数据] + "上面是工具返回的截图。"`
|
||
|
||
**作用:** 将截图作为视觉反馈注入对话上下文,让多模态 LLM 看到执行后的屏幕状态,实现闭环的视觉推理。
|
||
|
||
---
|
||
|
||
### 五、Layer 4 — 内部模型提示词
|
||
|
||
#### 5.1 批量坐标定位 Prompt
|
||
|
||
**位置:** `agent/tools/gui_tool.py` → `_build_batch_prompt()`
|
||
|
||
**调用链:** `_run()` → `_collect_grounding_queries()` → `_batch_ground_elements()` → `_build_batch_prompt()`
|
||
|
||
**作用:** 将一次截图中需要定位的所有元素,批量发送给视觉定位模型,一次性返回所有坐标。
|
||
|
||
**结构:**
|
||
|
||
```
|
||
You are a visual grounding model for desktop GUI control.
|
||
↓
|
||
=== OUTPUT FORMAT (STRICT) ===
|
||
每行一对坐标: (<x>, <y>)
|
||
↓
|
||
=== ONE-SHOT EXAMPLE ===
|
||
4 个 query → 4 行坐标的完整示例
|
||
↓
|
||
=== NOW YOUR TURN ===
|
||
实际需要定位的元素列表
|
||
↓
|
||
=== RULES ===
|
||
7 条严格的输出格式规则
|
||
```
|
||
|
||
**设计要点:**
|
||
- **One-shot 示例**是核心: 用一个完整的 4 元素定位示例纠正模型的输出格式偏好,防止输出 JSON、Markdown 或其他非预期格式。
|
||
- **规则 7** 特别强调"按顺序生成坐标",确保 action 顺序与坐标顺序一一对应。
|
||
- 使用的模型为 `_get_gui_llm()` 获取的 GUI 专用模型(如有配置),否则回退到主模型。
|
||
|
||
---
|
||
|
||
### 六、提示词注入流程
|
||
|
||
#### 6.1 Agent 模式标准流程
|
||
|
||
```
|
||
用户输入
|
||
│
|
||
▼
|
||
[MAIN_SYSTEM_PROMPT] ← 作为 SystemMessage,会话开始时注入
|
||
│
|
||
▼
|
||
┌─ ReAct 循环 ────────────────────────────────────┐
|
||
│ │
|
||
│ call_model 节点 │
|
||
│ ├─ 首轮: 直接调用 LLM │
|
||
│ └─ 后续: 注入 [REFLECTION_PROMPT] │
|
||
│ + [TodoList 状态] │
|
||
│ + [反循环警告(如有)] │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ LLM 决策 → 调用 tool(带 [Tool Description]) │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ tools 节点 → 执行工具 │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ process_tool_artifact 节点 │
|
||
│ └─ gui_tool 返回 → 注入截图 + "上面是截图" │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ routing → 继续/结束 │
|
||
│ │
|
||
└──────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 6.2 Plan 模式特殊流程
|
||
|
||
```
|
||
用户输入
|
||
│
|
||
▼
|
||
[PLAN_MODE_PROMPT] ← 替换 MAIN_SYSTEM_PROMPT
|
||
│
|
||
▼
|
||
首轮 call_model:
|
||
注入 [PLAN_FIRST_CALL_PROMPT]
|
||
→ LLM 调用 todo_list 生成步骤规划
|
||
│
|
||
▼
|
||
后续 call_model:
|
||
注入 [REFLECTION_PROMPT] + [TodoList 状态]
|
||
→ LLM 按规划逐步执行
|
||
```
|
||
|
||
---
|
||
|
||
### 七、关键设计决策
|
||
|
||
| 决策 | 原因 | 实现方式 |
|
||
|------|------|----------|
|
||
| **动作序列优先于单步** | 单步调用每个 action 都经过一轮 LLM→截图→LLM 循环,N 个 action 需要 2N+1 次 LLM 调用。序列化后 N 个 action 只需 2 次 LLM 调用(1 次定位 + 1 次决策) | MAIN_SYSTEM_PROMPT + REFLECTION_PROMPT 双重强调 |
|
||
| **下拉框仅用 type** | 两步策略(type→键选 fallback)会增加不必要的 action 数量,type 本身即可完成下拉匹配 | gui_tool 描述中精简为单句 |
|
||
| **截图作为视觉闭环** | 让 LLM 看到真实屏幕状态而非猜测,是 GUI 自动化可靠性的基础 | process_tool_artifact 自动注入截图 |
|
||
| **反思提示每轮注入** | 让模型形成"观察→分析→行动"的习惯,减少盲目操作和幻觉 | REFLECTION_PROMPT 作为 synthetic HumanMessage |
|
||
| **循环检测两级响应** | 先警告引导,再强制终止,避免误杀同时防止无限循环 | loop_detector.py 的 warn→end 机制 |
|
||
| **坐标定位批量化** | N 个元素的坐标定位只需 1 次子模型调用,而非 N 次 | _batch_ground_elements + _build_batch_prompt |
|
||
| **One-shot 示例纠正格式** | 视觉定位模型容易输出 JSON/Markdown/解释文字,one-shot 强制纯坐标格式 | _build_batch_prompt 中的 4 行坐标示例 |
|
||
| **trajectory_rounds 短期记忆过滤** | 多轮 GUI 操作后上下文过长,仅保留最近 3 轮工具返回结果 | _filter_short_term_memory() |
|
||
|
||
#### 连接远程服务器
|
||
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -p 60007 minor123@semi-ai.tech
|
||
|
||
#### 本地端口映射
|
||
ssh -fN -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 8901:127.0.0.1:8901 -L 8902:127.0.0.1:8902 -L 8903:127.0.0.1:8903 minor123@semi-ai.tech -p 60007
|
||
ssh -fN -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 8903:127.0.0.1:8903 minor123@semi-ai.tech -p 60007
|
||
# 查看状态码(200表示健康)
|
||
curl.exe -o nul -s -w "%{http_code}\n" http://localhost:8901/health
|
||
curl.exe -o nul -s -w "%{http_code}\n" http://localhost:8902/health
|
||
curl.exe -o nul -s -w "%{http_code}\n" http://localhost:8903/health
|
||
|
||
curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8901/health
|
||
curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8902/health
|
||
curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8903/health
|
||
|
||
|
||
## 当前对话与执行流程总览
|
||
|
||
### 一、多轮对话整体流程
|
||
|
||
```
|
||
┌─ Web UI ──────────────────────────────────────────────────────────────────────┐
|
||
│ app.js → send.js │
|
||
│ POST /api/chat/start → POST /api/chat/complete │
|
||
│ 同时轮询 /api/tool-calls/{sid}/live (1.2s) 展示"思考中..."+工具调用实时面板 │
|
||
└────────────────────────────────────┬────────────────────────────────────────────┘
|
||
▼
|
||
┌─ server.py ────────────────────────────────────────────────────────────────────┐
|
||
│ /api/chat/complete → agent_runtime.execute_agent(chat_history, session_id) │
|
||
└────────────────────────────────────┬────────────────────────────────────────────┘
|
||
▼
|
||
┌─ runtime.py:execute_agent() ───────────────────────────────────────────────────┐
|
||
│ │
|
||
│ ① 从 SQLite message_store 加载历史消息 │
|
||
│ ② 从 session_meta 读取上轮 turn_tool_context(工具名/参/文字结果) │
|
||
│ ③ 拼装 messages = [SystemMessage + SQLite历史 + synthetic(工具上下文) + │
|
||
│ HumanMessage(用户输入)] │
|
||
│ ④ start_live_turn() 开启前端实时推送 │
|
||
│ │
|
||
│ ⑤ graph.invoke(messages) ────────────────▶ 见"图内 ReAct 循环" │
|
||
│ │
|
||
│ ⑥ _finalize_turn(): │
|
||
│ ├ save_turn() → tool_calling/{sid}/turn_{id}.json (工具调用轨迹) │
|
||
│ ├ _save_turn_tool_context() → session_meta.turn_tool_context (跨对话记忆) │
|
||
│ └ _persist_messages_from_pending_turn() → SQLite (仅user+最终AI, 不含工具) │
|
||
└─────────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 二、图内 ReAct 循环(`build_agent_graph` — `graph.py`)
|
||
|
||
```
|
||
START
|
||
│
|
||
▼
|
||
┌─────────────┐ 有 tool_calls ┌───────────┐ ┌─────────────────────┐
|
||
│ agent │───────────────────────▶│ tools │───▶│ process_tool_artifact│
|
||
│ (call_model)│◀───────────────────────│ (ToolNode) │ │ (截图 → synthetic │
|
||
└─────────────┘ 无 tool_calls └───────────┘ │ HumanMessage) │
|
||
│ ▲ └──────────┬──────────┘
|
||
│ [END] │ │
|
||
▼ └────────────────────────┘
|
||
END
|
||
```
|
||
|
||
**`agent` 节点内部(`call_model` — nodes.py):**
|
||
|
||
```
|
||
call_model(state)
|
||
├── _filter_short_term_memory() ← 裁剪超过 trajectory_rounds 的合成截图
|
||
├── _build_reflection_prompt() ← 注入反思引导文本
|
||
├── model_with_tools.invoke() ← LLM 调用
|
||
│
|
||
├── _compress_messages_in_loop() ← ★ 每步后检查压缩 ★
|
||
│ │
|
||
│ ├ 读 response.usage_metadata.total_tokens
|
||
│ ├ > MINI_COMPRESS_RATE (50%窗口) → 轻量压缩:保留本轮首个 agent 动作
|
||
│ ├ > HARD_COMPRESS_RATE (75%窗口) → 全量压缩:只保留当前 user 消息
|
||
│ └ state["messages"] 就地替换为 [SystemMessage(摘要) + 保留部分]
|
||
│
|
||
├── 提取 thinking (content) → 前端 think 面板
|
||
└── return {"messages": [response]} ← add_messages 追加到 state
|
||
```
|
||
|
||
**`should_continue` 路由(routing.py):**
|
||
|
||
```
|
||
最后一条 AIMessage 是否含 tool_calls?
|
||
├ YES → 检查策略
|
||
│ ├ confirm 级 → END(等待人工确认)
|
||
│ ├ 循环检测触发 → END
|
||
│ └ 正常 → "tools"
|
||
└ NO → END
|
||
```
|
||
|
||
---
|
||
|
||
### 三、多 Agent 模式(`multi_agent_graph.py`)
|
||
|
||
```
|
||
START
|
||
│
|
||
▼
|
||
┌──────────────┐ main完成 ┌─────────────────────┐
|
||
│ supervisor │─────────────────────▶│ parallel_sub_agents │
|
||
└──────────────┘ └──────────┬──────────┘
|
||
▲ │ 并行完成
|
||
│ ▼
|
||
└─────────────────────── ┌──────────────┐
|
||
│ supervisor │
|
||
子Agent结果注入 └──────┬───────┘
|
||
回 main 的 messages │ 有 agent_call → 循环
|
||
▼
|
||
┌──────────────┐ 无 agent_call
|
||
│ supervisor │──────────────────▶ END
|
||
└──────────────┘
|
||
```
|
||
|
||
**主 Agent 节点**(`make_main_agent_node`):单步执行 — model invoke → 工具分类 → 工具执行(单轮)→ 返回 supervisor。由 `_main_continues` 标志 + supervisor 控制循环。
|
||
|
||
**子 Agent 执行**(`_execute_single_sub_agent`):隔离的 [SystemMessage + task_description] → 子 Agent 的 ReAct 图(共用 `nodes.py` 的 `call_model`,自动受益于压缩)→ 返回 `tag_message` 注入主 Agent。
|
||
|
||
---
|
||
|
||
### 四、压缩机制对比
|
||
|
||
| | 旧(已移除) | 新 |
|
||
|---|---|---|
|
||
| **触发位置** | `execute_agent` 中,`graph.invoke()` 之前 | `call_model` 中,每次 LLM 调用之后 |
|
||
| **token 来源** | SQLite `session_meta.last_total_tokens` | 模型 response `usage_metadata.total_tokens` |
|
||
| **压缩范围** | 只压缩 SQLite 中的简化历史(user+AI) | 压缩图状态中完整消息(含工具调用/结果) |
|
||
| **持久化** | 写 SQLite `last_total_tokens` + `compressed_content` | 就地替换 `state["messages"]`,不写库 |
|
||
| **单 Agent** | 图外 pre-invoke 一次 | 图内每步 LLM 调用后检查 |
|
||
| **子 Agent** | 无 | 共用 `nodes.py` 的 `call_model`,自动覆盖 |
|
||
|
||
Agent/
|
||
├── config/
|
||
│ └── env_config.json # WORKSPACE_DIR 配置
|
||
│
|
||
├── history/ # [需挂载]
|
||
│ ├── multi_inputs/{session_id}/
|
||
│ │ ├── ui_messages.json # LangChain 聊天历史
|
||
│ │ ├── session_meta.json # 会话元数据 + 工具调用记忆
|
||
│ │ └── *.png # 用户上传的附件
|
||
│ └── tool_calling/{session_id}/
|
||
│ ├── turn_{turn_id}.json # 每轮工具调用完整记录
|
||
│ └── {turn_id}/
|
||
│ ├── _artifact_*.png # 工具产生的截图
|
||
│ └── _file_* # 工具产生的其他文件
|
||
│
|
||
└── workspace/ # [需挂载]
|
||
├── ppt_qingdao/ # Agent 产出的 PPT 子目录
|
||
├── trasformer/ # 另一个业务子目录
|
||
├── *.pptx # 散落的 PPT 文件
|
||
└── generate_charts.py # Agent 自动生成的脚本
|
||
关键发现:
|
||
两个目录的路径都是通过 __file__ 动态推导的,没有硬编码绝对路径 → 只要目录结构一致,挂载到容器内同名位置即可
|
||
terminal_execute 是唯一在代码中直接调用 get_workspace_dir() 的工具(作为默认工作目录)
|
||
doc_tool、send_file 依赖 LLM 通过 system_prompt 中注入的 workspace 路径来传参
|
||
16 个模块引用了 history 目录,核心入口是 tool_call_recorder.py 和 agent_utils.py
|
||
二、工具分类:谁进沙箱、谁留宿主机
|
||
进 Docker 沙箱 原因 留宿主机 原因
|
||
terminal_execute 必须隔离,执行任意命令 gui 需要桌面截图+键鼠模拟
|
||
web_search 纯 HTTP API 调用 browser_control 需要宿主机浏览器+快捷键
|
||
agent_call 纯进程内调用 software_control Win32 API + 注册表 + os.startfile
|
||
skill_router 只读 skills/ + LLM doc 用户可传任意宿主机路径
|
||
human_interaction 零外部依赖 send_file 同上
|
||
todo_list 纯内存操作 directory_listing 同上
|
||
date_query 纯 datetime.now() email SMTP 附件来自宿主机任意路径
|
||
rag ChromaDB + 嵌入API(限制路径后)
|
||
如果将 doc/send_file/directory_listing/email 的路径参数强制限定在 workspace 内,则它们也可以迁入沙箱。那样最终只有 3 个 GUI 工具必须在宿主机。
|
||
三、Docker 沙箱方案设计
|
||
terminal_execute 的改造方式:
|
||
沙箱内部预装 Python + myenv venv + 常用工具(git, node 等),每次命令在隔离容器中执行,产物自然落入挂载的 workspace/ 目录。
|
||
|
||
# app打包(使用pyinstaller和npm)
|
||
|
||
## 先用 PyInstaller 把 FastAPI 后端打成独立 exe
|
||
cd Agent
|
||
pip install pyinstaller
|
||
pyinstaller --onefile --name fastapi-backend --add-data "src;src" src/web/server.py
|
||
```
|
||
cd Agent\electron
|
||
## 打包 Electron 应用
|
||
```
|
||
# 第一步:安装 Electron + electron-builder(仅首次)
|
||
npm install
|
||
|
||
# 第二步:打包生成 .exe
|
||
npx electron-builder --win
|
||
```
|
||
生成的 .exe 在 Agent\electron\dist\ 目录下。
|
||
|
||
|
||
# 测试用例
|
||
(email、sendfile)
|
||
今天和昨天有什么邮件
|
||
发封邮件给19553226590@163.com,内容就是我桌面的图片"C:\Users\86166\Desktop\8.png"
|
||
|
||
(websearch、terminal、sendfile)
|
||
查一下济南和青岛近10年的GDP并画个图表
|
||
|
||
(gui、browser、human_interaction、改成英文)
|
||
进行以下gui任务,严格按照我的指令操作:
|
||
1、打开https://erpdemo.fanpusoft.com/main.aspx#
|
||
2、进行登录(账号密码调用human_interaction工具来问我)
|
||
3、点击销售管理
|
||
4、点击新增客户
|
||
5、在新增客户界面输入:
|
||
客户名称:minor;客户类别:零售商;公司地址:中国·青岛;公司简介:大狗叫叫叫;联系人姓名:叮咚鸡;
|
||
点击性别选项:女;部门:抽象部;生日:2004-07-09;
|
||
6、点击客户档案并连按两次空格(用来下滑到网页底部)
|
||
7、点击批量上传
|
||
8、在弹出的文件选择器的文件名输入框中输入"C:\Users\86166\Desktop\风景\8.png"并回车
|
||
9、输入跟进日期:2026-07-16
|
||
10、点击保存
|
||
11、点击确定
|