fix: 修复热点摘要超长上下文并统一 Windows 启动文档
为 http_request 增加响应体截断与头部精简,避免门户首页触发 LLM 上下文超限;同时新增政务/媒体及教育批量 Agent 脚本,并将 Windows 启停说明合并为唯一指南,补充本次超时故障复盘与标准重启流程。 Made-with: Cursor
This commit is contained in:
195
(红头)Windows服务器启动与重启唯一指南.md
Normal file
195
(红头)Windows服务器启动与重启唯一指南.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# (红头)Windows 服务器启动与重启唯一指南
|
||||
|
||||
本文是 `D:\aaa\aiagent` 在 Windows 本地开发环境下的**唯一启动/重启文档**。
|
||||
后续只看这一份即可。
|
||||
|
||||
---
|
||||
|
||||
## 0. 统一结论(先看)
|
||||
|
||||
- 推荐端口:
|
||||
- 前端:`3001`
|
||||
- 后端 API:`8037`
|
||||
- Redis:`6379`
|
||||
- `backend/.env` 必须与实际 Redis 端口一致:
|
||||
- 推荐:`REDIS_URL=redis://localhost:6379/0`
|
||||
- 任何 `.env` / 依赖 / 工具代码变更后,至少重启:
|
||||
- API + Celery(`restart_backend_celery.ps1`)
|
||||
- 若出现 `timeout of 30000ms exceeded`,优先检查:
|
||||
1) Redis 是否可连
|
||||
2) Celery Worker 是否在跑
|
||||
3) API 与 Worker 是否使用同一 `backend\venv`
|
||||
|
||||
---
|
||||
|
||||
## 1. 一键启动 / 停止 / 重启
|
||||
|
||||
在 PowerShell 中执行(仓库根目录):
|
||||
|
||||
```powershell
|
||||
cd D:\aaa\aiagent
|
||||
```
|
||||
|
||||
### 1.1 一键启动(全套)
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\start_aiagent.ps1
|
||||
```
|
||||
|
||||
默认目标:
|
||||
- 前端:`http://localhost:3001`
|
||||
- 后端文档:`http://127.0.0.1:8037/docs`
|
||||
- Redis:`127.0.0.1:6379`
|
||||
|
||||
### 1.2 一键停止(全套)
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\stop_aiagent.ps1
|
||||
```
|
||||
|
||||
### 1.3 仅重启后端 + Celery(最常用)
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\restart_backend_celery.ps1
|
||||
```
|
||||
|
||||
适用场景:改了 `.env`、Python 依赖、内置工具实现、Agent 执行逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 2. 标准“重启服务器”流程(推荐照抄)
|
||||
|
||||
```powershell
|
||||
cd D:\aaa\aiagent
|
||||
powershell -ExecutionPolicy Bypass -File .\stop_aiagent.ps1
|
||||
powershell -ExecutionPolicy Bypass -File .\start_aiagent.ps1
|
||||
```
|
||||
|
||||
完成后立刻验证:
|
||||
|
||||
```powershell
|
||||
netstat -ano | findstr :6379
|
||||
netstat -ano | findstr :8037
|
||||
netstat -ano | findstr :3001
|
||||
curl http://127.0.0.1:8037/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 本次故障复盘(学生作业管理助手超时)
|
||||
|
||||
### 3.1 现象
|
||||
|
||||
- Agent 对话区报错:`发送失败: timeout of 30000ms exceeded`
|
||||
- 前端可打开,但执行一直超时。
|
||||
|
||||
### 3.2 根因
|
||||
|
||||
1. `backend/.env` 配置为:
|
||||
- `REDIS_URL=redis://localhost:6380/0`
|
||||
2. 实际 Redis 监听在:
|
||||
- `6379`
|
||||
3. 导致 Celery 任务队列链路异常(或 Worker 无法稳定消费),Agent 执行超时。
|
||||
|
||||
### 3.3 修复
|
||||
|
||||
1. 将 `backend/.env` 改为:
|
||||
- `REDIS_URL=redis://localhost:6379/0`
|
||||
2. 执行:
|
||||
- `powershell -ExecutionPolicy Bypass -File .\restart_backend_celery.ps1`
|
||||
3. 验证:
|
||||
- `6379/8037/3001` 监听正常
|
||||
- `/health` 返回 `200`
|
||||
- Celery worker 进程存在
|
||||
|
||||
### 3.4 预防
|
||||
|
||||
- 不要混用两套端口约定(`6379` 与 `6380`)。
|
||||
- 每次重启后先做 1 分钟健康检查(端口 + `/health` + 1 条 Agent 测试消息)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 常见问题与快速处理
|
||||
|
||||
### 4.1 执行策略拦截脚本
|
||||
|
||||
```powershell
|
||||
Set-ExecutionPolicy -Scope Process Bypass
|
||||
```
|
||||
|
||||
### 4.2 `start_aiagent.ps1` 报 PowerShell 解析错误
|
||||
|
||||
症状:出现 `ParserError`、字符串终止符缺失、`[OK]` 附近报错。
|
||||
|
||||
处理:
|
||||
1. 临时手动启动(见 4.3)
|
||||
2. 将脚本保存为 **UTF-8(建议无 BOM)/ ASCII 兼容内容**,避免中文引号或异常字符
|
||||
|
||||
### 4.3 一键脚本不可用时的手动拉起(应急)
|
||||
|
||||
开 3~4 个终端:
|
||||
|
||||
1) Redis
|
||||
```powershell
|
||||
cd D:\aaa\aiagent\backend\redis
|
||||
.\redis-server.exe --port 6379
|
||||
```
|
||||
|
||||
2) API
|
||||
```powershell
|
||||
cd D:\aaa\aiagent\backend
|
||||
.\venv\Scripts\Activate.ps1
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8037
|
||||
```
|
||||
|
||||
3) Celery
|
||||
```powershell
|
||||
cd D:\aaa\aiagent\backend
|
||||
.\venv\Scripts\Activate.ps1
|
||||
python -m celery -A app.core.celery_app worker --loglevel=info --pool=threads --concurrency=8
|
||||
```
|
||||
|
||||
4) 前端
|
||||
```powershell
|
||||
cd D:\aaa\aiagent\frontend
|
||||
$env:AIAGENT_API_PROXY='http://127.0.0.1:8037'
|
||||
pnpm dev --port 3001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. OCR(上传图片识别)必查项
|
||||
|
||||
`backend/.env` 建议:
|
||||
|
||||
```ini
|
||||
TESSERACT_CMD=C:/Program Files/Tesseract-OCR/tesseract.exe
|
||||
TESSERACT_TESSDATA_DIR=D:/aaa/aiagent/tessdata
|
||||
```
|
||||
|
||||
自检:
|
||||
|
||||
```powershell
|
||||
cd D:\aaa\aiagent\backend
|
||||
.\venv\Scripts\python scripts\check_ocr_env.py
|
||||
```
|
||||
|
||||
若新增依赖后仍报 OCR 缺失,重启 Celery。
|
||||
|
||||
---
|
||||
|
||||
## 6. 访问地址
|
||||
|
||||
- 前端:`http://localhost:3001`
|
||||
- 后端 API:`http://127.0.0.1:8037`
|
||||
- 后端文档:`http://127.0.0.1:8037/docs`
|
||||
- 健康检查:`http://127.0.0.1:8037/health`
|
||||
|
||||
---
|
||||
|
||||
## 7. 维护规则(强制)
|
||||
|
||||
- 所有 Windows 启动/重启内容只维护本文件。
|
||||
- 其他旧文档仅保留跳转,不再写重复步骤。
|
||||
- 修改启动逻辑后,先更新本文件再通知团队。
|
||||
|
||||
137
(红头)上传图片和识别的实现文档.md
Normal file
137
(红头)上传图片和识别的实现文档.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 上传图片与文字识别实现说明
|
||||
|
||||
本文说明平台中 **设计器/预览聊天** 场景下:**图片如何上传、如何随消息发给 Agent、后端如何读出图中文字(OCR)**,以及涉及的主要文件与配置。
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 浏览器
|
||||
participant API as 后端 API
|
||||
participant FS as 工作区磁盘
|
||||
participant Q as消息队列
|
||||
participant W as Worker
|
||||
U->>API: POST /api/v1/uploads/preview (multipart, JWT)
|
||||
API->>FS: 写入 uploads/preview/{user_id}/{uuid}_{safe_name}
|
||||
API-->>U: relative_path, filename, content_type
|
||||
|
||||
U->>API: POST /api/v1/executions (input_data 含 query/USER_INPUT/attachments)
|
||||
API->>Q: 入队执行任务
|
||||
Q->>W: 执行 Agent 工作流
|
||||
W->>W: LLM 可调用 file_read(relative_path)
|
||||
W->>FS: 读同一工作区下文件
|
||||
W-->>W: 图片走 OCR,返回 JSON content
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
1. **上传**只负责把文件落到 **与工作区根一致** 的目录,并返回 **相对路径**。
|
||||
2. **识别**不发生在上传接口里,而是由工作流中的 **`file_read` 工具** 在读文件时,对图片扩展名走 **Tesseract OCR**。
|
||||
3. **Agent 是否真的会去 `file_read`** 取决于提示词与工作流设计;前端会把路径写进 `USER_INPUT` / `query` 便于模型使用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 前端实现(`frontend/src/components/AgentChatPreview.vue`)
|
||||
|
||||
| 能力 | 说明 |
|
||||
|------|------|
|
||||
| 选择文件 | 隐藏 `<input type="file">`,回形针触发;`accept` 含常见图片与文档扩展名。 |
|
||||
| 拖放图片 | 在 `chat-input-area` 上监听 `dragenter/dragover/drop`;**仅处理图片**(与 `isImageFile` 一致),非图片可提示忽略。 |
|
||||
| 上传 | `uploadPreviewFiles`:`FormData` + `POST /api/v1/uploads/preview`,需登录(JWT)。 |
|
||||
| 本地缩略图 | 图片用 `URL.createObjectURL(file)` 作为 `thumbUrl`;发送成功且消息仍引用时延迟 `revoke`,避免 blob 提前失效。 |
|
||||
| 随消息发送 | `POST /api/v1/executions`,`input_data` 包含:`USER_INPUT`、`query`(合并用户话 +附件路径说明)、`user_id`(预览会话)、`attachments`(含 `relative_path`、`filename`、`content_type` 等)。 |
|
||||
| 历史回显 | `GET /api/v1/agents/{id}/preview-chat-history` 返回轮次中的 `attachments`;图片再通过 `GET /api/v1/uploads/preview/file?file_path=...` 拉 blob 生成缩略图。 |
|
||||
| 交互 | 消息内多图横向滚动、缩略图点击放大(对话框)。 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端:上传与下载
|
||||
|
||||
**文件**:`backend/app/api/uploads.py`
|
||||
|
||||
| 接口 | 方法 | 作用 |
|
||||
|------|------|------|
|
||||
| `/api/v1/uploads/preview` | `POST` | 单文件上传;需 JWT。保存目录:`{LOCAL_FILE_TOOLS_ROOT}/uploads/preview/{当前用户 id}/`。文件名经 `_safe_filename` 处理,前缀短 UUID 防冲突。响应:`relative_path`(相对工作区根)、`filename`、`size`、`content_type`。超大返回413,上限见 `LOCAL_FILE_WRITE_MAX_BYTES`。 |
|
||||
| `/api/v1/uploads/preview/file` | `GET` | 按 `file_path`(即 `relative_path`)读文件;**仅允许** `uploads/preview/{当前用户 id}/` 前缀,防止越权。用于历史消息图片回显。 |
|
||||
|
||||
**工作区根**:与 `file_read` / `file_write` 一致,由 `LOCAL_FILE_TOOLS_ROOT` 配置;未配置时默认为 **仓库根目录**(见 `builtin_tools._local_file_workspace_root`)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 后端:图片识别(OCR)
|
||||
|
||||
**文件**:`backend/app/services/builtin_tools.py`
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 工具名 | `file_read`(异步,返回 JSON 字符串) |
|
||||
| 图片扩展名 | `.png` `.jpg` `.jpeg` `.webp` `.gif` `.bmp` `.tif` `.tiff` |
|
||||
| 识别实现 | `_read_image_ocr_sync`:`PIL.Image` 打开图片,`pytesseract.image_to_string`,语言依次尝试 **`chi_sim+eng`**、**`eng`**。 |
|
||||
| Tesseract 路径 | `settings.TESSERACT_CMD` 非空时赋值给 `pytesseract.pytesseract.tesseract_cmd`。 |
|
||||
| 语言包目录 | `settings.TESSERACT_TESSDATA_DIR`;若为空且仓库根下存在 `tessdata/*.traineddata`,会自动使用该目录(`_tessdata_dir_for_ocr`)。 |
|
||||
| 返回 | 成功:`extract_mode` 为 `image_ocr`,`content` 为识别文本(有 UTF-8 字节上限截断)。失败:JSON 内 `error` 字段说明原因(缺 Pillow/pytesseract、未找到 Tesseract、其它 OCR 异常)。 |
|
||||
|
||||
**依赖**(需安装在与 **Celery Worker** 相同的 Python 环境中):
|
||||
|
||||
- `Pillow`、`pytesseract`(见 `backend/requirements.txt`)
|
||||
- 本机安装 **Tesseract 可执行文件**(Windows 常见路径:`C:/Program Files/Tesseract-OCR/tesseract.exe`)
|
||||
|
||||
**环境变量**(`backend/.env` / `env.example`):
|
||||
|
||||
- `TESSERACT_CMD`:指向 `tesseract.exe`
|
||||
- `TESSERACT_TESSDATA_DIR`:可选,指向含 `chi_sim.traineddata` 的目录,中文效果更好
|
||||
|
||||
自检脚本:`backend/scripts/check_ocr_env.py`
|
||||
|
||||
---
|
||||
|
||||
## 5. 历史记录中的附件
|
||||
|
||||
**文件**:`backend/app/services/agent_workspace_chat_log.py`
|
||||
|
||||
- `fetch_agent_preview_chat_turns` 从已完成执行的 `input_data` 中解析 **`attachments`** 列表(`relative_path`、`filename`、`content_type`),随每轮 `user_text` / `agent_text` 返回给前端。
|
||||
|
||||
**文件**:`backend/app/api/agents.py`
|
||||
|
||||
- 响应模型 `PreviewChatTurnResponse` 含 **`attachments`** 字段。
|
||||
|
||||
---
|
||||
|
||||
## 6. 与 Agent / 工作流的关系
|
||||
|
||||
- 前端把附件路径写进 **`USER_INPUT` / `query`**,并保留结构化 **`attachments`**,便于工作流引擎提取 `user_query`及工具参数。
|
||||
- **真正执行 OCR** 的时机是:工作流中 LLM(或工具节点)调用 **`file_read`**且传入上传返回的 **`relative_path`**。
|
||||
- 若 Agent 未配置调用 `file_read` 或提示词未引导使用路径,则可能出现「已上传但助手仍说读不了」——需改 Agent 配置或提示词,而非仅改上传代码。
|
||||
|
||||
---
|
||||
|
||||
## 7. 常见问题
|
||||
|
||||
| 现象 | 可能原因 | 处理方向 |
|
||||
|------|----------|----------|
|
||||
| 上传 401 | 未登录或 token 过期 | 重新登录 |
|
||||
| 上传 413 | 超过 `LOCAL_FILE_WRITE_MAX_BYTES` | 压缩图片或调大配置(需评估安全) |
|
||||
| 助手仍提示缺 Pillow/pytesseract | Worker 使用的 venv 未安装依赖 | 在 `backend\venv` 执行 `pip install -r requirements.txt` 并 **重启 Celery** |
|
||||
| 提示未找到 Tesseract | 未安装或未配置 PATH / `TESSERACT_CMD` | 安装 Tesseract 并配置 `.env` |
|
||||
| 中文识别差 | 缺少 `chi_sim.traineddata` | 配置 `TESSERACT_TESSDATA_DIR` 或放置语言包到自动探测的 `tessdata/` |
|
||||
| OpenAPI 无 uploads | 后端为旧进程 | 重启 API |
|
||||
|
||||
---
|
||||
|
||||
## 8. 相关文件索引
|
||||
|
||||
| 路径 | 作用 |
|
||||
|------|------|
|
||||
| `frontend/src/components/AgentChatPreview.vue` | 上传、拖放、发送、缩略图、历史回显 |
|
||||
| `backend/app/api/uploads.py` | 预览上传、预览文件只读下载 |
|
||||
| `backend/app/services/builtin_tools.py` | `file_read`、图片 OCR |
|
||||
| `backend/app/services/agent_workspace_chat_log.py` | 历史轮次附带 `attachments` |
|
||||
| `backend/app/api/agents.py` | `preview-chat-history` 响应模型 |
|
||||
| `backend/scripts/check_ocr_env.py` | OCR 环境自检 |
|
||||
| `启动的注意事项.md` | Redis、venv、重启 Celery 等运维说明 |
|
||||
|
||||
---
|
||||
|
||||
文档版本:与当前仓库实现一致时可随代码迭代更新本文。
|
||||
@@ -1,104 +1,7 @@
|
||||
# 前后端服务器启动和停止说明
|
||||
# 前后端服务器启动和停止(已合并)
|
||||
|
||||
## 一、使用 Docker Compose(推荐)
|
||||
本文件已停止维护。
|
||||
请改看唯一文档:`(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
本项目前后端及依赖服务均通过 `docker-compose.dev.yml` 管理,需在项目根目录执行以下命令。
|
||||
路径:`D:\aaa\aiagent\(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
### 1. 启动所有服务(前端 + 后端 + Redis + Celery)
|
||||
|
||||
```bash
|
||||
cd /home/renjianbo/aiagent
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
### 2. 停止所有服务
|
||||
|
||||
```bash
|
||||
cd /home/renjianbo/aiagent
|
||||
docker-compose -f docker-compose.dev.yml down
|
||||
```
|
||||
|
||||
### 3. 重启所有服务
|
||||
|
||||
```bash
|
||||
cd /home/renjianbo/aiagent
|
||||
docker-compose -f docker-compose.dev.yml restart
|
||||
```
|
||||
|
||||
### 4. 仅重启前端或后端
|
||||
|
||||
```bash
|
||||
# 仅重启前端
|
||||
docker-compose -f docker-compose.dev.yml restart frontend
|
||||
|
||||
# 仅重启后端
|
||||
docker-compose -f docker-compose.dev.yml restart backend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、查看服务状态与日志
|
||||
|
||||
### 查看运行状态
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.dev.yml ps
|
||||
```
|
||||
|
||||
### 查看日志
|
||||
|
||||
```bash
|
||||
# 所有服务
|
||||
docker-compose -f docker-compose.dev.yml logs -f
|
||||
|
||||
# 仅前端
|
||||
docker-compose -f docker-compose.dev.yml logs -f frontend
|
||||
|
||||
# 仅后端
|
||||
docker-compose -f docker-compose.dev.yml logs -f backend
|
||||
|
||||
# 仅 Celery
|
||||
docker-compose -f docker-compose.dev.yml logs -f celery
|
||||
|
||||
# 仅 Redis
|
||||
docker-compose -f docker-compose.dev.yml logs -f redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、服务与端口说明
|
||||
|
||||
| 服务 | 宿主机端口 | 说明 |
|
||||
|--------|------------|----------------|
|
||||
| 前端 | 8038 | 低代码智能体平台页面 |
|
||||
| 后端 | 8037 | API 服务 |
|
||||
| Redis | 6380 | 缓存/队列(避免与宿主机 6379 冲突) |
|
||||
| Celery | — | 仅内网,无宿主机端口映射 |
|
||||
|
||||
---
|
||||
|
||||
## 四、访问地址
|
||||
|
||||
- **前端页面**: http://localhost:8038 或 http://101.43.95.130:8038
|
||||
- **后端 API**: http://localhost:8037 或 http://101.43.95.130:8037
|
||||
- **API 文档**: http://localhost:8037/docs
|
||||
- **健康检查**: http://localhost:8037/health
|
||||
|
||||
---
|
||||
|
||||
## 五、注意事项
|
||||
|
||||
1. 所有 `docker-compose` 命令均需指定 `-f docker-compose.dev.yml`,且建议在项目根目录 `/home/renjianbo/aiagent` 下执行。
|
||||
2. 停止服务使用 `down`,不会删除镜像和已创建的卷(如 Redis 数据卷)。
|
||||
3. 若宿主机 6379 已被占用,Redis 已改为使用宿主机端口 **6380**,无需再改配置。
|
||||
4. 云服务器部署时,需在安全组中放行 **8038**(前端)和 **8037**(后端)端口。
|
||||
|
||||
---
|
||||
|
||||
## 六、常见问题
|
||||
|
||||
| 现象 | 处理建议 |
|
||||
|----------------|----------|
|
||||
| 端口被占用 | 检查 8037、8038、6380 是否被占用;必要时修改 `docker-compose.dev.yml` 中端口映射。 |
|
||||
| 前端能开、登录报错 | 检查后端是否启动、8037 是否放行;在服务器上执行 `curl http://127.0.0.1:8037/health` 验证。 |
|
||||
| 容器反复退出 | 使用 `docker-compose -f docker-compose.dev.yml logs backend`(或对应服务名)查看报错并排查。 |
|
||||
|
||||
@@ -1,80 +1,7 @@
|
||||
# 启动注意事项
|
||||
# 启动注意事项(已合并)
|
||||
|
||||
本文面向在 **Windows** 上本地运行本仓库(后端 API、Celery、前端、Redis)时的常见配置与排错要点。
|
||||
本文件已停止维护。
|
||||
请改看唯一文档:`(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
---
|
||||
路径:`D:\aaa\aiagent\(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
## 1. Redis 端口与 `.env` 必须一致
|
||||
|
||||
- `backend/.env` 中的 **`REDIS_URL`**(例如 `redis://localhost:6380/0`)必须与**实际监听的 Redis 端口**一致。
|
||||
- 仓库内 `start_aiagent.ps1` 默认启动的是 **`6379`**。若 `.env` 写的是 **6380**,请要么:
|
||||
- 在本机启动监听 **6380** 的 Redis,要么
|
||||
- 把 `.env` 改为 **6379** 并使用脚本启动的 Redis。
|
||||
- **症状**:创建执行返回 **503**、日志中出现无法连接 Redis / Celery 入队失败。
|
||||
|
||||
---
|
||||
|
||||
## 2. API 与 Celery 必须使用同一虚拟环境
|
||||
|
||||
- 工作流/Agent 执行由 **Celery Worker** 跑,与 **uvicorn API** 应共用 **`backend\venv`**。
|
||||
- 更新依赖后请在 **`backend` 目录**执行:
|
||||
|
||||
```powershell
|
||||
.\venv\Scripts\pip install -r requirements.txt
|
||||
```
|
||||
|
||||
- **改完 `.env` 或 Python 依赖后**,需要**重启 API 和 Celery**,否则仍加载旧环境。
|
||||
- 仓库提供**仅重启后端 + Celery**(不停止前端/本机 Redis)的脚本:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File D:\aaa\aiagent\restart_backend_celery.ps1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 图片 OCR(作业/聊天里识别图中文字)
|
||||
|
||||
- `file_read` 读图片依赖:**Pillow**、**pytesseract**,以及本机安装 **Tesseract 可执行文件**。
|
||||
- 在 `backend/.env` 中建议配置(路径按本机修改):
|
||||
- **`TESSERACT_CMD`**:指向 `tesseract.exe`(例如 `C:/Program Files/Tesseract-OCR/tesseract.exe`)。
|
||||
- **`TESSERACT_TESSDATA_DIR`**(可选):指向含 **`chi_sim.traineddata`** 的目录,中文识别更稳定。
|
||||
- 自检:
|
||||
|
||||
```powershell
|
||||
cd D:\aaa\aiagent\backend
|
||||
.\venv\Scripts\python scripts\check_ocr_env.py
|
||||
```
|
||||
|
||||
- **症状**:助手回复里出现「请安装 Pillow / pytesseract」或无法识别图中文字 → 先检查 venv 是否已 `pip install`,再检查 Tesseract 与 `.env`,最后**重启 Celery**。
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端与后端地址
|
||||
|
||||
- 前端开发服通过代理访问 API;一键脚本里会通过环境变量指向当前 API 地址。
|
||||
- 若 OpenAPI 里**没有**新加的路由(例如上传),多半是 **API 进程仍是旧代码/旧进程**,需要重启后端。
|
||||
|
||||
---
|
||||
|
||||
## 5. 鉴权与安全
|
||||
|
||||
- 上传、执行等接口需要 **JWT**;预览对话若出现 **401**,请重新登录后再试。
|
||||
- **勿**在文档或 Git 中提交 **明文密码、密钥、完整 `.env`**;`.env` 应留在本机并加入版本忽略。
|
||||
|
||||
---
|
||||
|
||||
## 6. 一键启停脚本(参考)
|
||||
|
||||
| 脚本 | 作用 |
|
||||
|------|------|
|
||||
| `start_aiagent.ps1` | 启动 Redis(6379)、API、Celery、前端(按脚本内端口逻辑) |
|
||||
| `stop_aiagent.ps1` | 停止 API、Celery、前端、以及匹配的 redis-server 进程 |
|
||||
| `restart_backend_celery.ps1` | 仅重启 API(8037) + Celery,适合改依赖或 `.env` 后快速生效 |
|
||||
|
||||
实际端口以你本机 **`start_aiagent.ps1` / `restart_backend_celery.ps1`** 及 `.env` 为准。
|
||||
|
||||
---
|
||||
|
||||
## 7. 修改记录建议
|
||||
|
||||
- 变更依赖或环境变量后:在本机记一句「已重启 API + Celery」便于以后排查。
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
# Windows 启动指南(已合并)
|
||||
|
||||
本文件已停止维护,请只看以下唯一文档:
|
||||
|
||||
- `D:\aaa\aiagent\(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
说明:历史内容已合并至上方文档,后续不再在本文件更新。
|
||||
|
||||
# Windows 启动指南(已合并)
|
||||
|
||||
本文件已停止维护。
|
||||
请改看唯一文档:`(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
路径:`D:\aaa\aiagent\(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
# Windows 本地启动指南
|
||||
|
||||
## 前置要求
|
||||
|
||||
@@ -34,6 +34,9 @@ class Settings(BaseSettings):
|
||||
LOCAL_FILE_READ_MAX_BYTES: int = 2_097_152 # 单次读取上限(默认 2MB)
|
||||
LOCAL_FILE_WRITE_MAX_BYTES: int = 2_097_152 # 单次写入内容上限(UTF-8 字节)
|
||||
|
||||
# http_request 工具:写入 LLM 上下文的响应体最大字符数(HTML/JSON 过大时截断,避免超过模型 context)
|
||||
HTTP_REQUEST_MAX_BODY_CHARS: int = 32_000
|
||||
|
||||
# 图片 OCR(file_read 对 png/jpg 等):Tesseract 可执行文件路径,Windows 示例 C:/Program Files/Tesseract-OCR/tesseract.exe
|
||||
TESSERACT_CMD: str = ""
|
||||
# 自定义 tessdata 目录(内含 chi_sim.traineddata 等)。留空时若 LOCAL_FILE_TOOLS_ROOT/tessdata 下存在 .traineddata 则自动使用
|
||||
|
||||
@@ -67,11 +67,74 @@ def _resolve_path_under_workspace(file_path: str) -> Tuple[Optional[Path], Optio
|
||||
return p, None
|
||||
|
||||
|
||||
_HTTP_RESPONSE_HEADER_ALLOWLIST = frozenset(
|
||||
{
|
||||
"content-type",
|
||||
"content-length",
|
||||
"content-encoding",
|
||||
"date",
|
||||
"last-modified",
|
||||
"location",
|
||||
"server",
|
||||
"cache-control",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _compact_http_response_headers(response: httpx.Response) -> Dict[str, str]:
|
||||
"""减少 Set-Cookie 等大字段进入 LLM 上下文。"""
|
||||
out: Dict[str, str] = {}
|
||||
for key, value in response.headers.multi_items():
|
||||
lk = key.lower()
|
||||
if lk in ("set-cookie", "cookie"):
|
||||
continue
|
||||
if lk in _HTTP_RESPONSE_HEADER_ALLOWLIST:
|
||||
out[key] = value if len(value) <= 2048 else value[:2048] + "..."
|
||||
if not out:
|
||||
n = 0
|
||||
for key, value in response.headers.multi_items():
|
||||
lk = key.lower()
|
||||
if lk in ("set-cookie", "cookie"):
|
||||
continue
|
||||
if n >= 12:
|
||||
break
|
||||
out[key] = value if len(value) <= 512 else value[:512] + "..."
|
||||
n += 1
|
||||
return out
|
||||
|
||||
|
||||
def _truncate_http_body_for_tool(body: Any, max_chars: int) -> Tuple[Any, bool, Optional[str]]:
|
||||
"""
|
||||
将 HTTP 正文限制在 max_chars 字符以内,避免门户首页等大 HTML 撑爆模型 context。
|
||||
返回 (可能被截断后的 body, 是否截断, 说明文案)
|
||||
"""
|
||||
if max_chars <= 0:
|
||||
max_chars = 32_000
|
||||
note: Optional[str] = None
|
||||
if isinstance(body, str):
|
||||
if len(body) <= max_chars:
|
||||
return body, False, None
|
||||
note = (
|
||||
f"正文已截断:原始约 {len(body)} 字符,仅保留前 {max_chars} 字符。"
|
||||
"门户/频道首页通常过大,摘要请优先使用具体文章页 URL。"
|
||||
)
|
||||
return body[:max_chars], True, note
|
||||
try:
|
||||
serialized = json.dumps(body, ensure_ascii=False)
|
||||
except (TypeError, ValueError):
|
||||
serialized = str(body)
|
||||
if len(serialized) <= max_chars:
|
||||
return body, False, None
|
||||
note = f"JSON 响应已截断:序列化长度约 {len(serialized)} 字符,仅保留前 {max_chars} 字符。"
|
||||
return serialized[:max_chars], True, note
|
||||
|
||||
|
||||
async def http_request_tool(
|
||||
url: str,
|
||||
method: str = "GET",
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
body: Any = None
|
||||
body: Any = None,
|
||||
max_body_chars: Optional[int] = None,
|
||||
) -> str:
|
||||
"""
|
||||
HTTP请求工具
|
||||
@@ -81,12 +144,18 @@ async def http_request_tool(
|
||||
method: HTTP方法 (GET, POST, PUT, DELETE)
|
||||
headers: 请求头
|
||||
body: 请求体(POST/PUT时使用)
|
||||
max_body_chars: 响应正文写入工具结果的最大字符数;默认读取配置 HTTP_REQUEST_MAX_BODY_CHARS
|
||||
|
||||
Returns:
|
||||
JSON格式的响应结果
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
limit = max_body_chars
|
||||
if limit is None:
|
||||
limit = int(getattr(settings, "HTTP_REQUEST_MAX_BODY_CHARS", 32_000) or 32_000)
|
||||
limit = max(4096, min(limit, 200_000))
|
||||
|
||||
async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
||||
method_upper = method.upper()
|
||||
|
||||
if method_upper == "GET":
|
||||
@@ -103,15 +172,20 @@ async def http_request_tool(
|
||||
# 尝试解析JSON响应
|
||||
try:
|
||||
response_body = response.json()
|
||||
except:
|
||||
except Exception:
|
||||
response_body = response.text
|
||||
|
||||
result = {
|
||||
|
||||
truncated_body, truncated, trunc_note = _truncate_http_body_for_tool(response_body, limit)
|
||||
result: Dict[str, Any] = {
|
||||
"status_code": response.status_code,
|
||||
"headers": dict(response.headers),
|
||||
"body": response_body
|
||||
"headers": _compact_http_response_headers(response),
|
||||
"body": truncated_body,
|
||||
}
|
||||
|
||||
if truncated:
|
||||
result["body_truncated"] = True
|
||||
if trunc_note:
|
||||
result["truncation_note"] = trunc_note
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"HTTP请求工具执行失败: {str(e)}")
|
||||
@@ -1036,7 +1110,14 @@ HTTP_REQUEST_SCHEMA = {
|
||||
"body": {
|
||||
"type": "object",
|
||||
"description": "请求体(POST/PUT时使用,可选)"
|
||||
}
|
||||
},
|
||||
"max_body_chars": {
|
||||
"type": "integer",
|
||||
"description": (
|
||||
"响应正文写入结果的最大字符数(可选)。"
|
||||
"门户首页等大 HTML 默认会按平台配置截断;摘要单篇文章可适当调大(如 80000),仍可能受模型总上下文限制。"
|
||||
),
|
||||
},
|
||||
},
|
||||
"required": ["url", "method"]
|
||||
}
|
||||
|
||||
434
backend/scripts/create_education_agents_batch.py
Normal file
434
backend/scripts/create_education_agents_batch.py
Normal file
@@ -0,0 +1,434 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
批量创建或更新一批「教育行业」场景 Agent(单链 Start → LLM → End)。
|
||||
|
||||
用法:
|
||||
cd backend
|
||||
.\\venv\\Scripts\\python.exe scripts/create_education_agents_batch.py
|
||||
|
||||
# 只创建/更新其中一个(名称需与内置列表完全一致):
|
||||
set EDU_ONLY_AGENT=错题本分析助手
|
||||
.\\venv\\Scripts\\python.exe scripts/create_education_agents_batch.py
|
||||
|
||||
环境变量:
|
||||
PLATFORM_BASE_URL, PLATFORM_USERNAME, PLATFORM_PASSWORD
|
||||
EDU_LLM_PROVIDER / EDU_LLM_MODEL / EDU_LLM_TIMEOUT(可选;否则用 ENTERPRISE_* 或 deepseek-chat)
|
||||
EDU_ONLY_AGENT(可选):仅处理该名称的 Agent
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if BACKEND_DIR not in sys.path:
|
||||
sys.path.insert(0, BACKEND_DIR)
|
||||
|
||||
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
|
||||
USER = os.getenv("PLATFORM_USERNAME", "admin")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
|
||||
|
||||
PROVIDER = os.getenv(
|
||||
"EDU_LLM_PROVIDER",
|
||||
os.getenv("ENTERPRISE_LLM_PROVIDER", "deepseek"),
|
||||
)
|
||||
MODEL = os.getenv(
|
||||
"EDU_LLM_MODEL",
|
||||
os.getenv("ENTERPRISE_LLM_MODEL", "deepseek-chat"),
|
||||
)
|
||||
REQ_TIMEOUT = max(
|
||||
30,
|
||||
int(
|
||||
os.getenv(
|
||||
"EDU_LLM_TIMEOUT",
|
||||
os.getenv("ENTERPRISE_LLM_TIMEOUT", "180"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
ONLY = (os.getenv("EDU_ONLY_AGENT") or "").strip()
|
||||
|
||||
BUDGET_BASE = {
|
||||
"max_steps": 80,
|
||||
"max_llm_invocations": 6,
|
||||
"max_tool_calls": 24,
|
||||
}
|
||||
|
||||
TOOLS_STD = ["file_read", "text_analyze", "datetime", "json_process"]
|
||||
TOOLS_MATH = ["file_read", "text_analyze", "datetime", "json_process", "math_calculate"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EduAgentSpec:
|
||||
name: str
|
||||
label: str
|
||||
node_id: str
|
||||
description: str
|
||||
prompt: str
|
||||
tools: Tuple[str, ...]
|
||||
temperature: float = 0.35
|
||||
max_tool_iterations: int = 12
|
||||
|
||||
|
||||
def _slug(s: str) -> str:
|
||||
return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-")[:40] or "agent"
|
||||
|
||||
|
||||
AGENTS: List[EduAgentSpec] = [
|
||||
EduAgentSpec(
|
||||
name="错题本分析助手",
|
||||
label="错题分析",
|
||||
node_id="llm-edu-mistake",
|
||||
description=(
|
||||
"帮助学生整理错题:错因归类、知识点缺口、举一反三思路;"
|
||||
"支持上传题目照片或文档后用 file_read 读原文;不代写整题标准答案,侧重方法与自查。"
|
||||
),
|
||||
prompt="""你是「错题本分析助手」,面向中小学与大学基础课程。
|
||||
|
||||
【任务】
|
||||
1. 请用户说明学科、题型、自己的错选/错解(若上传了题目图片或文件,**先调用 file_read** 读取内容再分析)。
|
||||
2. 归纳:**错因类型**(概念/审题/计算/步骤跳步/知识遗忘等)、**涉及知识点**、**正确思路提纲**(分步,不直接给可照抄的完整答卷)。
|
||||
3. 给出 **1~2 道同类变式** 的方向描述或自检问题(不必编造具体数字题除非用户需要且合理)。
|
||||
4. 可用 json_process 或表格输出结构化错题卡片(日期、科目、知识点、错因、复习提醒)。
|
||||
|
||||
【原则】
|
||||
- 语气鼓励、具体;不羞辱式批评。
|
||||
- 不确定题干时先确认,不臆造题目条件。
|
||||
- 中文为主;公式用可读形式。
|
||||
""",
|
||||
tools=tuple(TOOLS_STD),
|
||||
temperature=0.3,
|
||||
),
|
||||
EduAgentSpec(
|
||||
name="语文阅读与写作助手",
|
||||
label="语文读写",
|
||||
node_id="llm-edu-chinese",
|
||||
description=(
|
||||
"语文阅读理解答题思路、作文立意与提纲、素材迁移;"
|
||||
"可读取用户上传的文章或题目材料(file_read);不代写整篇可提交的作文正文。"
|
||||
),
|
||||
prompt="""你是「语文阅读与写作助手」。
|
||||
|
||||
【阅读】
|
||||
- 概括主旨、结构、人物形象、关键手法;结合文本引用思路(若材料在附件中,**先 file_read**)。
|
||||
- 教「如何组织答案」:观点句+依据+简要分析;不编造原文没有的引文。
|
||||
|
||||
【写作】
|
||||
- 可提供立意角度、提纲、开头结尾范例句(短)、修改建议清单。
|
||||
- **不代写**整篇参赛/考试作文;可示范片段并说明可替换处。
|
||||
|
||||
【输出】
|
||||
- 分条清晰;尊重教材与考区差异,不确定时提示以教师要求为准。
|
||||
""",
|
||||
tools=tuple(TOOLS_STD),
|
||||
temperature=0.4,
|
||||
),
|
||||
EduAgentSpec(
|
||||
name="英语作文与语法助手",
|
||||
label="英语习作",
|
||||
node_id="llm-edu-english",
|
||||
description=(
|
||||
"英语作文结构、语法纠错说明、替换表达与连接词;可读取用户上传的英文草稿(file_read);"
|
||||
"不代写整篇可提交的英语作文。"
|
||||
),
|
||||
prompt="""你是「英语作文与语法助手」。
|
||||
|
||||
【能力】
|
||||
- 审题与段落结构(thesis / body / conclusion);提供连接词与句式升级建议。
|
||||
- 语法与用词:说明**规则**与**修改原因**,可给改写示例句(短)。
|
||||
- 若用户上传 doc/txt/图片,**先 file_read** 再基于原文反馈。
|
||||
|
||||
【边界】
|
||||
- 不生成整篇可一键提交的课堂/考试作文;可给框架与片段。
|
||||
- 输出中英可混排,解释优先中文便于理解。
|
||||
""",
|
||||
tools=tuple(TOOLS_STD),
|
||||
temperature=0.35,
|
||||
),
|
||||
EduAgentSpec(
|
||||
name="考试复习规划助手",
|
||||
label="复习规划",
|
||||
node_id="llm-edu-exam",
|
||||
description=(
|
||||
"根据目标考试与剩余时间,拆复习阶段、每日任务模板与优先级;"
|
||||
"可用 json_process 输出周计划表;结合 datetime 谈倒计时与节奏。"
|
||||
),
|
||||
prompt="""你是「考试复习规划助手」。
|
||||
|
||||
【输入】
|
||||
- 考试科目/范围、当前水平自评、可用每日学习时长、考试日期(可用 datetime 对齐「今天」与截止日)。
|
||||
|
||||
【输出】
|
||||
- 分阶段(基础→专题→模考→查漏);每周重点与**可执行**日任务(番茄钟量级即可)。
|
||||
- 用 json_process 或 Markdown 表格输出计划时保持可打印、可勾选。
|
||||
- 不保证分数;提醒睡眠与劳逸结合。
|
||||
|
||||
【原则】
|
||||
- 若信息不足,先问 1~2 个关键问题再出计划。
|
||||
""",
|
||||
tools=tuple(TOOLS_STD),
|
||||
temperature=0.35,
|
||||
),
|
||||
EduAgentSpec(
|
||||
name="家校沟通话术助手",
|
||||
label="家校沟通",
|
||||
node_id="llm-edu-parent",
|
||||
description=(
|
||||
"面向教师/班主任:与家长微信、电话、家长会沟通的礼貌、清晰话术草稿;"
|
||||
"情境包括成绩反馈、纪律、合作建议;不替代正式处分或法律意见。"
|
||||
),
|
||||
prompt="""你是「家校沟通话术助手」,读者主要是**教师**。
|
||||
|
||||
【能力】
|
||||
- 根据情境生成:**简短微信**、**电话开场**、**家长会发言要点**(客观、合作、具体建议)。
|
||||
- 语气:**尊重、不激化矛盾**;避免标签化学生与家长。
|
||||
- 可提供「若家长情绪激动」的缓和句式与边界话术。
|
||||
|
||||
【边界】
|
||||
- 不涉及具体法律结论;严重事件建议走学校流程与专业人士。
|
||||
- 不编造学生隐私细节;缺信息时用占位并请老师补全。
|
||||
|
||||
【输出】
|
||||
- 先给「目标」再给「话术草稿」与「可选修改点」。
|
||||
""",
|
||||
tools=tuple(TOOLS_STD),
|
||||
temperature=0.3,
|
||||
),
|
||||
EduAgentSpec(
|
||||
name="实验报告结构化助手",
|
||||
label="实验报告",
|
||||
node_id="llm-edu-lab",
|
||||
description=(
|
||||
"辅助理化生实验报告:目的、器材、步骤、数据表、误差与结论框架;"
|
||||
"可读取用户上传的实验数据或草稿(file_read);不伪造实验数据。"
|
||||
),
|
||||
prompt="""你是「实验报告结构化助手」。
|
||||
|
||||
【能力】
|
||||
- 按常见结构梳理:**目的、原理、器材、步骤、数据记录表、处理与误差、结论与讨论**。
|
||||
- 用户上传数据/草稿时 **先 file_read**,再帮助排版与补全「讨论角度」(不编造未出现的测量值)。
|
||||
- 可用 json_process 输出字段清单供粘贴到 Word。
|
||||
|
||||
【原则】
|
||||
- **严禁伪造数据**;缺失数据处标注「请填写实测」。
|
||||
- 涉及安全操作须提醒遵守实验室规范。
|
||||
|
||||
【输出】
|
||||
- 中文;公式与单位规范;表格用 Markdown。
|
||||
""",
|
||||
tools=tuple(TOOLS_STD),
|
||||
temperature=0.3,
|
||||
),
|
||||
EduAgentSpec(
|
||||
name="数学解题思路助手",
|
||||
label="数学辅导",
|
||||
node_id="llm-edu-math",
|
||||
description=(
|
||||
"数学题型思路、关键步骤与检验方法;可用 math_calculate 做简单数值校验;"
|
||||
"不输出可整卷照抄的解答,侧重引导与分步。"
|
||||
),
|
||||
prompt="""你是「数学解题思路助手」。
|
||||
|
||||
【能力】
|
||||
- 根据题目(含上传图片 **file_read** OCR结果)分析:**考点、等价变形、推荐步骤链、易错点**。
|
||||
- 需要时可用 **math_calculate** 做简单数值/表达式验算(步骤仍用文字说明)。
|
||||
- 给「下一步提示」而非一次性完整标准答案,引导学生自算。
|
||||
|
||||
【边界】
|
||||
- 竞赛/考试整卷代做请求应拒绝完整作答,改为方法提纲。
|
||||
- 不确定题意时先澄清条件。
|
||||
|
||||
【输出】
|
||||
- 分步编号;关键式子单独一行;中文说明。
|
||||
""",
|
||||
tools=tuple(TOOLS_MATH),
|
||||
temperature=0.25,
|
||||
max_tool_iterations=14,
|
||||
),
|
||||
EduAgentSpec(
|
||||
name="生涯选科咨询助手",
|
||||
label="选科咨询",
|
||||
node_id="llm-edu-career",
|
||||
description=(
|
||||
"浅度选科/分科、专业兴趣自我梳理:优势学科、职业想象、信息清单与决策问题;"
|
||||
"不提供唯一正确答案,不替代官方招生政策;引导查官方简章与老师。"
|
||||
),
|
||||
prompt="""你是「生涯选科咨询助手」,做**浅度、非决策替代**的引导。
|
||||
|
||||
【能力】
|
||||
- 用结构化提问帮用户梳理:**兴趣、学科感受、时间投入、长远想象**。
|
||||
- 输出「信息收集清单」(官方简章、学校开设组合、本校资源)与「决策维度表」,不替用户做唯一选择。
|
||||
- 可用 json_process 整理自评表。
|
||||
|
||||
【边界】
|
||||
- 不编造分数线、政策条款;涉及政策必提示以**教育部门与学校最新官方文件**为准。
|
||||
- 心理危机倾向请引导寻求专业人士与学校心理老师。
|
||||
|
||||
【语气】
|
||||
- 平等、务实;避免焦虑营销式表述。
|
||||
""",
|
||||
tools=tuple(TOOLS_STD),
|
||||
temperature=0.4,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _sanitize_edges(edges: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
seen: set = set()
|
||||
out: List[Dict[str, Any]] = []
|
||||
for e in edges or []:
|
||||
s, t = e.get("source"), e.get("target")
|
||||
if not s or not t or s == t:
|
||||
continue
|
||||
key = (s, t, e.get("sourceHandle") or "")
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
ne = dict(e)
|
||||
if not ne.get("targetHandle"):
|
||||
ne["targetHandle"] = "left"
|
||||
if not ne.get("id"):
|
||||
sh = ne.get("sourceHandle") or "r"
|
||||
ne["id"] = f"e_{s}_{t}_{sh}"
|
||||
out.append(ne)
|
||||
return out
|
||||
|
||||
|
||||
def build_workflow(spec: EduAgentSpec) -> Dict[str, Any]:
|
||||
llm_pos: Tuple[int, int] = (380, 220)
|
||||
tools = list(spec.tools)
|
||||
nodes: List[Dict[str, Any]] = [
|
||||
{"id": "start-1", "type": "start", "position": {"x": 80, "y": 220}, "data": {"label": "开始"}},
|
||||
{
|
||||
"id": spec.node_id,
|
||||
"type": "llm",
|
||||
"position": {"x": llm_pos[0], "y": llm_pos[1]},
|
||||
"data": {
|
||||
"label": spec.label,
|
||||
"prompt": spec.prompt,
|
||||
"provider": PROVIDER,
|
||||
"model": MODEL,
|
||||
"temperature": spec.temperature,
|
||||
"request_timeout": REQ_TIMEOUT,
|
||||
"enable_tools": True,
|
||||
"tools": tools,
|
||||
"selected_tools": tools,
|
||||
"max_tool_iterations": spec.max_tool_iterations,
|
||||
},
|
||||
},
|
||||
{"id": "end-1", "type": "end", "position": {"x": llm_pos[0] + 260, "y": 220}, "data": {"label": "结束"}},
|
||||
]
|
||||
edges = _sanitize_edges(
|
||||
[
|
||||
{"source": "start-1", "target": spec.node_id, "sourceHandle": "right", "targetHandle": "left"},
|
||||
{"source": spec.node_id, "target": "end-1", "sourceHandle": "right", "targetHandle": "left"},
|
||||
]
|
||||
)
|
||||
return {"nodes": nodes, "edges": edges}
|
||||
|
||||
|
||||
def _validate_local(wf: Dict[str, Any]) -> None:
|
||||
from app.services.workflow_validator import validate_workflow
|
||||
|
||||
r = validate_workflow(wf.get("nodes") or [], wf.get("edges") or [])
|
||||
if not r.get("valid"):
|
||||
errs = r.get("errors") or []
|
||||
raise ValueError("工作流校验失败: " + "; ".join(errs))
|
||||
|
||||
|
||||
def _find_agent_id(h: Dict[str, str], name: str) -> Optional[str]:
|
||||
r = requests.get(f"{BASE}/api/v1/agents", params={"search": name, "limit": 80}, headers=h, timeout=45)
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
for a in r.json() or []:
|
||||
if a.get("name") == name:
|
||||
return a.get("id")
|
||||
return None
|
||||
|
||||
|
||||
def upsert_agent(h: Dict[str, str], spec: EduAgentSpec) -> Tuple[bool, str]:
|
||||
wf = build_workflow(spec)
|
||||
_validate_local(wf)
|
||||
desc = spec.description + f" 默认模型 {PROVIDER}/{MODEL}。"
|
||||
existing = _find_agent_id(h, spec.name)
|
||||
if existing:
|
||||
ur = requests.put(
|
||||
f"{BASE}/api/v1/agents/{existing}",
|
||||
headers=h,
|
||||
json={
|
||||
"description": desc,
|
||||
"workflow_config": wf,
|
||||
"budget_config": BUDGET_BASE,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
if ur.status_code != 200:
|
||||
return False, f"更新失败 {spec.name}: {ur.status_code} {ur.text[:500]}"
|
||||
return True, f"更新 {spec.name} id={existing}"
|
||||
|
||||
cr = requests.post(
|
||||
f"{BASE}/api/v1/agents",
|
||||
headers=h,
|
||||
json={
|
||||
"name": spec.name,
|
||||
"description": desc,
|
||||
"workflow_config": wf,
|
||||
"budget_config": BUDGET_BASE,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
if cr.status_code != 201:
|
||||
return False, f"创建失败 {spec.name}: {cr.status_code} {cr.text[:500]}"
|
||||
aid = cr.json()["id"]
|
||||
return True, f"创建 {spec.name} id={aid}"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
specs = AGENTS
|
||||
if ONLY:
|
||||
specs = [s for s in AGENTS if s.name == ONLY]
|
||||
if not specs:
|
||||
print(f"未找到名称完全匹配的 Agent 规格: {ONLY}", file=sys.stderr)
|
||||
print("可选名称:", "、".join(s.name for s in AGENTS), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
r = requests.post(
|
||||
f"{BASE}/api/v1/auth/login",
|
||||
data={"username": USER, "password": PWD},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
timeout=15,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
print("登录失败:", r.status_code, r.text[:500], file=sys.stderr)
|
||||
return 1
|
||||
token = r.json().get("access_token")
|
||||
if not token:
|
||||
print("无 access_token", file=sys.stderr)
|
||||
return 1
|
||||
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
ok_n = 0
|
||||
results: List[Dict[str, str]] = []
|
||||
for spec in specs:
|
||||
try:
|
||||
ok, msg = upsert_agent(h, spec)
|
||||
print(msg)
|
||||
results.append({"name": spec.name, "ok": str(ok), "message": msg})
|
||||
if ok:
|
||||
ok_n += 1
|
||||
except ValueError as e:
|
||||
print(spec.name, "校验失败:", e, file=sys.stderr)
|
||||
results.append({"name": spec.name, "ok": "false", "message": str(e)})
|
||||
|
||||
print(json.dumps({"base": BASE, "succeeded": ok_n, "total": len(specs), "details": results}, ensure_ascii=False))
|
||||
return 0 if ok_n == len(specs) else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
368
backend/scripts/create_gov_media_agents_batch.py
Normal file
368
backend/scripts/create_gov_media_agents_batch.py
Normal file
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
批量创建或更新「政务 / 公共服务(办事指引)」与「媒体 / 市场 / 运营」场景 Agent(Start → LLM → End)。
|
||||
|
||||
用法:
|
||||
cd backend
|
||||
.\\venv\\Scripts\\python.exe scripts/create_gov_media_agents_batch.py
|
||||
|
||||
只处理其中一个(名称需与内置列表完全一致):
|
||||
set CROSS_ONLY_AGENT=政务办事指引助手
|
||||
.\\venv\\Scripts\\python.exe scripts/create_gov_media_agents_batch.py
|
||||
|
||||
环境变量:
|
||||
PLATFORM_BASE_URL, PLATFORM_USERNAME, PLATFORM_PASSWORD
|
||||
CROSS_LLM_PROVIDER / CROSS_LLM_MODEL / CROSS_LLM_TIMEOUT(可选;否则用 ENTERPRISE_*)
|
||||
CROSS_ONLY_AGENT(可选)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if BACKEND_DIR not in sys.path:
|
||||
sys.path.insert(0, BACKEND_DIR)
|
||||
|
||||
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
|
||||
USER = os.getenv("PLATFORM_USERNAME", "admin")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
|
||||
|
||||
PROVIDER = os.getenv(
|
||||
"CROSS_LLM_PROVIDER",
|
||||
os.getenv("ENTERPRISE_LLM_PROVIDER", "deepseek"),
|
||||
)
|
||||
MODEL = os.getenv(
|
||||
"CROSS_LLM_MODEL",
|
||||
os.getenv("ENTERPRISE_LLM_MODEL", "deepseek-chat"),
|
||||
)
|
||||
REQ_TIMEOUT = max(
|
||||
30,
|
||||
int(
|
||||
os.getenv(
|
||||
"CROSS_LLM_TIMEOUT",
|
||||
os.getenv("ENTERPRISE_LLM_TIMEOUT", "180"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
ONLY = (os.getenv("CROSS_ONLY_AGENT") or "").strip()
|
||||
|
||||
BUDGET_BASE = {
|
||||
"max_steps": 80,
|
||||
"max_llm_invocations": 6,
|
||||
"max_tool_calls": 24,
|
||||
}
|
||||
|
||||
TOOLS_GOV = ("file_read", "text_analyze", "datetime", "json_process")
|
||||
TOOLS_MEDIA = ("file_read", "text_analyze", "json_process", "datetime")
|
||||
TOOLS_MEDIA_HTTP = ("http_request", "text_analyze", "json_process", "datetime")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CrossAgentSpec:
|
||||
name: str
|
||||
label: str
|
||||
node_id: str
|
||||
description: str
|
||||
prompt: str
|
||||
tools: Tuple[str, ...]
|
||||
temperature: float = 0.35
|
||||
max_tool_iterations: int = 12
|
||||
|
||||
|
||||
AGENTS: List[CrossAgentSpec] = [
|
||||
CrossAgentSpec(
|
||||
name="政务办事指引助手",
|
||||
label="办事指引",
|
||||
node_id="llm-gov-guide",
|
||||
description=(
|
||||
"面向公众办事咨询:流程步骤、所需材料清单、常见补正与办理渠道说明;"
|
||||
"可用 json_process 输出可打印 checklist;政策数字与条文以用户提供的官方材料或官网为准,不编造。"
|
||||
),
|
||||
prompt="""你是「政务办事指引助手」,帮助用户理解**办事流程与材料准备**,语气清晰、中立、便民。
|
||||
|
||||
【能力】
|
||||
1. 根据用户描述的办事类型(如落户、社保、执照、出入境等),整理:**办理条件要点、步骤顺序、材料清单、常见补正、线上/线下渠道提示**。
|
||||
2. 优先使用 **json_process** 输出结构化清单(如 `items[]`:材料名称、原件/复印件、份数、备注),便于复制打印。
|
||||
3. 需要「当前日期/工作日表述」时用 **datetime**;对用户上传的通知、办事指南 PDF/图片,**先 file_read** 再归纳,勿编造未读到的条款。
|
||||
4. 对用户粘贴的长文,可用 **text_analyze** 做要点提取后再组织成步骤。
|
||||
|
||||
【边界(必须遵守)】
|
||||
- **不编造**法律法规条文、收费标准、办理时限数字;若用户未提供官方依据,明确写「请以属地政务服务网/窗口最新公示为准」,并列出用户应核对的官方渠道类型。
|
||||
- 不做**个案最终裁决**;涉及敏感资格认定提示以主管部门解释为准。
|
||||
- 不索取不必要的身份证号等隐私;引导用户通过正规渠道提交。
|
||||
|
||||
【输出】
|
||||
- 中文;先给「结论摘要」,再给分步与清单;关键处标注「需用户向 XX 部门核实」。
|
||||
""",
|
||||
tools=TOOLS_GOV,
|
||||
temperature=0.3,
|
||||
),
|
||||
CrossAgentSpec(
|
||||
name="政务表格填写说明助手",
|
||||
label="表格说明",
|
||||
node_id="llm-gov-form",
|
||||
description=(
|
||||
"解读政务与公共服务类表格字段含义、填写格式与常见错误;支持上传空白表或示例用 file_read;"
|
||||
"不替用户伪造信息,不保证与各地最新版式完全一致。"
|
||||
),
|
||||
prompt="""你是「政务表格填写说明助手」。
|
||||
|
||||
【能力】
|
||||
1. 用户上传表格(扫描件、PDF、Word)或描述表名时,**先 file_read**(若有路径/附件)识别字段名,逐字段说明:**含义、填写格式、示例(虚构示例需标注「示例」)、常见漏填**。
|
||||
2. 用 **json_process** 输出「字段说明表」(field, meaning, format, example, warning)。
|
||||
3. 对日期、编号格式等可用 **datetime** 说明一般写法(具体规则仍以表格脚注为准)。
|
||||
|
||||
【边界】
|
||||
- **不编造**某地区未提供的表格版本;若无法从材料识别字段,请用户补充表头或截图。
|
||||
- **不指导伪造**证明材料、不代填真实个人信息。
|
||||
- 与政策冲突时以官方表格备注与办事指南为准。
|
||||
|
||||
【输出】
|
||||
- 分字段编号;末尾给「提交前自检 5 条」。
|
||||
""",
|
||||
tools=TOOLS_GOV,
|
||||
temperature=0.28,
|
||||
),
|
||||
CrossAgentSpec(
|
||||
name="市场多版本文案助手",
|
||||
label="多版文案",
|
||||
node_id="llm-mkt-copy",
|
||||
description=(
|
||||
"同一卖点下的多风格文案:朋友圈/短视频口播/电商详情要点等;"
|
||||
"text_analyze 拆解用户给的旧稿或竞品片段;json_process 输出多版本结构化稿;不虚假承诺。"
|
||||
),
|
||||
prompt="""你是「市场多版本文案助手」,服务市场与运营同学。
|
||||
|
||||
【能力】
|
||||
1. 基于用户提供的**产品/活动信息**(可上传 Brief 或旧稿,**先 file_read**),产出多版本短文案:如「朋友圈2 条」「短视频口播 30s 提纲」「电商卖点 3 条」等。
|
||||
2. 对用户粘贴的长 briefing,用 **text_analyze** 抽核心卖点、受众、禁忌后再写。
|
||||
3. 用 **json_process** 输出 JSON:`versions[]` 含 channel、tone、copy、cta、字符数估计。
|
||||
|
||||
【边界】
|
||||
- **不虚假承诺**疗效、收益、官方背书;涉及广告法敏感词(最、第一、治愈等)给替代表述或提示合规审核。
|
||||
- 不确定的促销规则、价格以运营确认为准。
|
||||
|
||||
【输出】
|
||||
- 中文为主;可附英文标题如需出海;每版标注适用渠道与语气。
|
||||
""",
|
||||
tools=TOOLS_MEDIA,
|
||||
temperature=0.45,
|
||||
),
|
||||
CrossAgentSpec(
|
||||
name="投放素材与Brief拆解助手",
|
||||
label="Brief拆解",
|
||||
node_id="llm-mkt-brief",
|
||||
description=(
|
||||
"拆解市场 Brief:目标、受众、渠道、KPI、创意方向、交付物清单;"
|
||||
"text_analyze 与 json_process 结构化输出;可读取上传的 Brief 文档。"
|
||||
),
|
||||
prompt="""你是「投放素材与 Brief 拆解助手」。
|
||||
|
||||
【能力】
|
||||
1. 用户粘贴或上传 Brief 时,**先 file_read**(若有),输出:**目标(认知/转化)、受众画像、主信息与副信息、渠道与版位、预算/周期(若给定)、KPI、创意禁忌、交付物列表(尺寸/时长/格式)**。
|
||||
2. 用 **text_analyze** 识别 Brief 中的矛盾或缺失项,列出需客户/内部确认的 **澄清问题**(一次3~5 个)。
|
||||
3. 用 **json_process** 生成标准拆解单 JSON:`goal`, `audience`, `messages`, `channels`, `deliverables`, `risks`, `open_questions`。
|
||||
|
||||
【原则】
|
||||
- 不编造未出现在 Brief 中的数字与承诺;缺失项标「待补充」。
|
||||
- 涉及法务/代言人/竞品对比等,提示走内部合规流程。
|
||||
|
||||
【输出】
|
||||
- 先一页「执行摘要」,再结构化表格或 JSON 块。
|
||||
""",
|
||||
tools=TOOLS_MEDIA,
|
||||
temperature=0.35,
|
||||
),
|
||||
CrossAgentSpec(
|
||||
name="热点资讯摘要助手",
|
||||
label="热点摘要",
|
||||
node_id="llm-mkt-news",
|
||||
description=(
|
||||
"对用户给出的公开 URL 使用 http_request 拉取可访问内容后做摘要与要点;"
|
||||
"结合 text_analyze;失败时如实说明;不编造来源中不存在的引文。"
|
||||
),
|
||||
prompt="""你是「热点资讯摘要助手」,用于**公开网页/接口返回文本**的摘要(需用户或模型通过工具获得原文)。
|
||||
|
||||
【能力】
|
||||
1. 当用户提供 **可访问的 http(s) URL** 时,使用 **http_request** 获取内容(遵守平台与工具限制);若失败(超时、非 200、需登录),如实说明,不要编造正文。
|
||||
- **不要**用门户/频道**首页**做摘要(HTML 体量极大,工具会截断且噪声多);请引导用户改为**具体文章页**链接。若用户只给首页,说明限制并请其提供文章 URL。
|
||||
- 单篇长文若需更多正文可在工具参数中适当增大 `max_body_chars`(仍可能截断,以工具返回的 `truncation_note`、`body_truncated` 为准)。
|
||||
2. 对工具返回的正文用 **text_analyze** 提取:核心事实、各方观点、时间线、对品牌/活动的**慎用关联**提示。
|
||||
3. 用 **json_process** 输出:`title_guess`, `bullets[]`, `sources`(仅用户提供的 URL)、`limitations`(如仅摘要可见段落)。
|
||||
4. 用 **datetime** 标注摘要基准日或「截至今日」表述。
|
||||
|
||||
【边界】
|
||||
- **不编造**引文、数据、发言人言论;原文没有则写「原文未提及」。
|
||||
- 付费墙、登录墙内容可能无法抓取,提示用户粘贴正文或换公开来源。
|
||||
- 不做投资建议;涉政敏感解读保持克制,以信息整理为主。
|
||||
|
||||
【输出】
|
||||
- 中文;先3~6 条 bullet,再给「可进一步检索的关键词」。
|
||||
""",
|
||||
tools=TOOLS_MEDIA_HTTP,
|
||||
temperature=0.35,
|
||||
max_tool_iterations=14,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _sanitize_edges(edges: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
seen: set = set()
|
||||
out: List[Dict[str, Any]] = []
|
||||
for e in edges or []:
|
||||
s, t = e.get("source"), e.get("target")
|
||||
if not s or not t or s == t:
|
||||
continue
|
||||
key = (s, t, e.get("sourceHandle") or "")
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
ne = dict(e)
|
||||
if not ne.get("targetHandle"):
|
||||
ne["targetHandle"] = "left"
|
||||
if not ne.get("id"):
|
||||
sh = ne.get("sourceHandle") or "r"
|
||||
ne["id"] = f"e_{s}_{t}_{sh}"
|
||||
out.append(ne)
|
||||
return out
|
||||
|
||||
|
||||
def build_workflow(spec: CrossAgentSpec) -> Dict[str, Any]:
|
||||
llm_pos: Tuple[int, int] = (380, 220)
|
||||
tools = list(spec.tools)
|
||||
nodes: List[Dict[str, Any]] = [
|
||||
{"id": "start-1", "type": "start", "position": {"x": 80, "y": 220}, "data": {"label": "开始"}},
|
||||
{
|
||||
"id": spec.node_id,
|
||||
"type": "llm",
|
||||
"position": {"x": llm_pos[0], "y": llm_pos[1]},
|
||||
"data": {
|
||||
"label": spec.label,
|
||||
"prompt": spec.prompt,
|
||||
"provider": PROVIDER,
|
||||
"model": MODEL,
|
||||
"temperature": spec.temperature,
|
||||
"request_timeout": REQ_TIMEOUT,
|
||||
"enable_tools": True,
|
||||
"tools": tools,
|
||||
"selected_tools": tools,
|
||||
"max_tool_iterations": spec.max_tool_iterations,
|
||||
},
|
||||
},
|
||||
{"id": "end-1", "type": "end", "position": {"x": llm_pos[0] + 260, "y": 220}, "data": {"label": "结束"}},
|
||||
]
|
||||
edges = _sanitize_edges(
|
||||
[
|
||||
{"source": "start-1", "target": spec.node_id, "sourceHandle": "right", "targetHandle": "left"},
|
||||
{"source": spec.node_id, "target": "end-1", "sourceHandle": "right", "targetHandle": "left"},
|
||||
]
|
||||
)
|
||||
return {"nodes": nodes, "edges": edges}
|
||||
|
||||
|
||||
def _validate_local(wf: Dict[str, Any]) -> None:
|
||||
from app.services.workflow_validator import validate_workflow
|
||||
|
||||
r = validate_workflow(wf.get("nodes") or [], wf.get("edges") or [])
|
||||
if not r.get("valid"):
|
||||
errs = r.get("errors") or []
|
||||
raise ValueError("工作流校验失败: " + "; ".join(errs))
|
||||
|
||||
|
||||
def _find_agent_id(h: Dict[str, str], name: str) -> Optional[str]:
|
||||
r = requests.get(f"{BASE}/api/v1/agents", params={"search": name, "limit": 80}, headers=h, timeout=45)
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
for a in r.json() or []:
|
||||
if a.get("name") == name:
|
||||
return a.get("id")
|
||||
return None
|
||||
|
||||
|
||||
def upsert_agent(h: Dict[str, str], spec: CrossAgentSpec) -> Tuple[bool, str]:
|
||||
wf = build_workflow(spec)
|
||||
_validate_local(wf)
|
||||
desc = spec.description + f" 默认模型 {PROVIDER}/{MODEL}。"
|
||||
existing = _find_agent_id(h, spec.name)
|
||||
if existing:
|
||||
ur = requests.put(
|
||||
f"{BASE}/api/v1/agents/{existing}",
|
||||
headers=h,
|
||||
json={
|
||||
"description": desc,
|
||||
"workflow_config": wf,
|
||||
"budget_config": BUDGET_BASE,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
if ur.status_code != 200:
|
||||
return False, f"更新失败 {spec.name}: {ur.status_code} {ur.text[:500]}"
|
||||
return True, f"更新 {spec.name} id={existing}"
|
||||
|
||||
cr = requests.post(
|
||||
f"{BASE}/api/v1/agents",
|
||||
headers=h,
|
||||
json={
|
||||
"name": spec.name,
|
||||
"description": desc,
|
||||
"workflow_config": wf,
|
||||
"budget_config": BUDGET_BASE,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
if cr.status_code != 201:
|
||||
return False, f"创建失败 {spec.name}: {cr.status_code} {cr.text[:500]}"
|
||||
aid = cr.json()["id"]
|
||||
return True, f"创建 {spec.name} id={aid}"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
specs = AGENTS
|
||||
if ONLY:
|
||||
specs = [s for s in AGENTS if s.name == ONLY]
|
||||
if not specs:
|
||||
print(f"未找到名称完全匹配的 Agent 规格: {ONLY}", file=sys.stderr)
|
||||
print("可选名称:", "、".join(s.name for s in AGENTS), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
r = requests.post(
|
||||
f"{BASE}/api/v1/auth/login",
|
||||
data={"username": USER, "password": PWD},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
timeout=15,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
print("登录失败:", r.status_code, r.text[:500], file=sys.stderr)
|
||||
return 1
|
||||
token = r.json().get("access_token")
|
||||
if not token:
|
||||
print("无 access_token", file=sys.stderr)
|
||||
return 1
|
||||
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
ok_n = 0
|
||||
results: List[Dict[str, str]] = []
|
||||
for spec in specs:
|
||||
try:
|
||||
ok, msg = upsert_agent(h, spec)
|
||||
print(msg)
|
||||
results.append({"name": spec.name, "ok": str(ok), "message": msg})
|
||||
if ok:
|
||||
ok_n += 1
|
||||
except ValueError as e:
|
||||
print(spec.name, "校验失败:", e, file=sys.stderr)
|
||||
results.append({"name": spec.name, "ok": "false", "message": str(e)})
|
||||
|
||||
print(json.dumps({"base": BASE, "succeeded": ok_n, "total": len(specs), "details": results}, ensure_ascii=False))
|
||||
return 0 if ok_n == len(specs) else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,3 +1,18 @@
|
||||
# Windows 启动和停止用法(已合并)
|
||||
|
||||
本文件已停止维护,请只看以下唯一文档:
|
||||
|
||||
- `D:\aaa\aiagent\(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
说明:历史内容已合并至上方文档,后续不再在本文件更新。
|
||||
|
||||
# Windows 启动和停止用法(已合并)
|
||||
|
||||
本文件已停止维护。
|
||||
请改看唯一文档:`(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
路径:`D:\aaa\aiagent\(红头)Windows服务器启动与重启唯一指南.md`
|
||||
|
||||
# AIAgent Windows 启动和停止用法
|
||||
|
||||
## 一键启动
|
||||
|
||||
Reference in New Issue
Block a user