# Agent Web Chat 基于 LangGraph 的多模态对话 Agent,支持文本、图片、音频、多种类型文件输入,具备屏幕截图、浏览器控制、RAG 知识库、数据库查询、本地文件获取等工具调用能力。 ## 快速开始 ### 1. 克隆仓库并进入项目目录 ```bash git clone 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 次..." 1、打开https://erpdemo.fanpusoft.com/main.aspx# 2、点击采购管理 3、点击新增供应商 4、在新增供应商页面进行以下操作:输入供应商名称:“minor”、输入供应商类别:“工程材料供应商”、输入部门:“采购部门B”、输入职务:“CEO”、输入联系人:“赵永顺”、输入生日:“2004/07/09” 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) === 每行一对坐标: (, ) ↓ === 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 -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 # 查看状态码(200表示健康) 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