基本实现通用自动化(含图表、代码、ppt等生成)

This commit is contained in:
minorQAQ committed 2026-07-14 19:22:08 +08:00
1 parent 1111d208c4
commit b422e96b2c
197 files changed
+13923 -8081

No files matched your search

+182 -1
View File
@@ -421,9 +421,190 @@ 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"
发封邮件给19553226590@163.com,内容就是我桌面的图片"C:\Users\86166\Desktop\8.png"
(websearch、terminal、sendfile)
查一下济南和青岛近10年的GDP并画个图表