Files
my-v0-server/README.md
T
2026-10-14 08:33:32 -07:00

217 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Screen Agent
> **前端仓库**:[jiawei/my-v0](https://git.qflink.xyz/jiawei/my-v0)
可视化大屏编辑器的后端服务,提供两部分能力:
- **AI 编辑**:把自然语言需求转换为经过校验的结构化编辑动作,由前端应用到画布;支持上传数据文件,并用查询结果直接制图。
- **大屏管理**:大屏的保存、版本发布、回收站与线上访问。
## 功能
- 自然语言新建大屏,增删、更换图表,修改组件属性、位置、尺寸与画布,支持对齐和分布。
- 上传 CSV / TSV / Excel / JSON / JSONL / Parquet,对数据问答、汇总,并将查询结果填入图表。
- 动作按物料字段定义校验,并在画布副本上整批模拟执行;失败原因回传 Agent 修正,仅交付通过的动作。
- 多轮对话;信息不足时暂停任务向用户澄清,回答后继续,也可取消。
- 通过 SSE 推送处理进度、问答文本增量与最终结果。
- 大屏保存(乐观锁)、发布快照与历史版本、分享链接、回收站、封面图、数据源服务端代理。
## 架构
```mermaid
flowchart LR
Client[大屏编辑器] --> API[FastAPI]
API --> Graph[LangGraph 工作流]
Graph --> Intent[意图判断]
Intent -->|问答| QA[问答 Agent]
Intent -->|其他| Screen[大屏 Agent]
Screen --> MCP[MCP 物料查询]
Screen --> Batch[动作校验与模拟]
QA --> Data[DuckDB / Parquet]
Screen --> Data
Graph -->|SSE| Client
API --> Mongo[(MongoDB)]
```
外层 LangGraph 负责路由、状态检查点与澄清恢复,内层 LangChain Agent 通过工具循环完成任务。意图由 TypeSafe Jev 判断,仅在明确为问答且概率达到阈值时进入问答分支。
| 组件 | 技术 |
|------|------|
| Web / 模型 | FastAPI、Pydantic |
| Agent 工作流 | LangGraph、LangChain(`langchain-openai`) |
| 意图判断 | TypeSafe Jev |
| 物料与字段查询 | MCP(`mcp_servers/`,由应用自动拉起,端口 8201) |
| 存储 | MongoDB(物料、会话、检查点、文件元数据、大屏) |
| 数据文件 | python-calamine、PyArrow(Parquet)、DuckDB |
| 观测 | Loguru、Langfuse(可选) |
## 记忆与上下文管理
大屏是唯一的事实来源:每次请求由前端带来完整画布,记忆只保存画布上看不出来的东西,例如做过哪些修改、问答用过哪些查询。画布状态不进记忆,用户在前端手动修改或撤销后,不会出现记忆和画布不一致。
| 范围 | 做法 |
|------|------|
| 单次运行 | Agent 在工具循环里完成任务,模型调用次数有上限(`MAX_AGENT_STEPS`、`MAX_QA_STEPS`),上下文不压缩 |
| 同一会话 | 服务端按 `conversation_id` 读取历史,不采用前端传来的历史。最多带最近 `CONVERSATION_HISTORY_TURNS`(20)轮,再按 `HISTORY_MAX_TOKENS`(10000,o200k 计数,消息和记录合计)从最近一轮往前整轮保留;最近一轮总是保留,单独超出预算时截掉中间、保留开头和结尾 |
| 澄清恢复 | 任务暂停时状态存入检查点,恢复后同一任务覆盖保存为一轮,追问和回答记在这一轮里 |
| 跨会话 | 暂不做,新会话从当前画布开始 |
每轮除用户原话和回复外,服务端从运行结果中记下轮次记录,不经过模型:
- `changes`:交付的修改,按原始画布与交付后画布逐组件比较,记为新增、修改或删除
- `queries`:问答中成功执行的 SQL,以及结果的列和行数,不保存结果本身
- `clarifications`:本轮的追问和用户的回答
下一轮请求时,原话和回复作为对话消息;记录作为 `history_records` 放进请求 JSON,不拼进消息。其中 `on_screen` 按当前画布计算,组件被撤销后不会被误认为还在。读取规则见 [app/prompt/domain.py](app/prompt/domain.py)。意图判断只看原话、回复和澄清,不看 SQL 和修改记录。
暂不采用滚动摘要、向量检索和用户级偏好:各轮多是对可见画布的独立修改,摘要容易失真。跨会话的长期要求(如“金额统一用万元”)计划以大屏级备忘实现,待真实使用中出现需要时再做。
## 快速开始
**依赖**:Python ≥ 3.11、[uv](https://docs.astral.sh/uv/)、MongoDB、支持工具调用与结构化输出的 OpenAI 兼容聊天模型服务、TypeSafe API Key。
```sh
uv sync --locked
cp .env.example .env # 填写下表中的配置
uv run python -m scripts.seed_materials # 导入物料目录,见下方说明
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000
```
启动后访问 <http://127.0.0.1:8000/docs> 查看完整接口文档。
| 配置 | 说明 |
|------|------|
| `LLM_API_KEY` `LLM_MODEL` `LLM_BASE_URL` | 聊天模型。默认发送 `reasoning_effort=low`,服务端不支持时设 `LLM_REASONING_EFFORT=` |
| `TYPESAFE_API_KEY` `TYPESAFE_DEFAULT_MODEL` | 意图判断。模型固定为 `jev-1.13.0`,阈值按该版本校准,换版本需重新评测 |
| `MONGODB_URI` `MONGODB_DB` | 物料目录与大屏存取必需;未配置时会话与检查点退回进程内存,大屏接口返回 503 |
| `AUTH_ENABLED` `USER_CENTER_URL` | 鉴权。本地开发设 `AUTH_ENABLED=false`;开启后请求携带 `Authorization: Bearer <token>`,服务调用用户中心 `GET /api/users/me` 校验 |
| `LANGFUSE_TRACING_ENABLED` | 未部署 Langfuse 时设为 `false` |
全部配置及默认值见 [.env.example](.env.example) 与 [app/config.py](app/config.py)。
**物料初始化**:物料定义位于 [app/resources/materials](app/resources/materials),必须导入 MongoDB 后服务才能增改组件;导入会整体替换现有目录,之后需重启服务。导入脚本为 [scripts/seed_materials.py](scripts/seed_materials.py),可附带自定义物料目录作为参数。
## 接口
所有接口以 `/api` 为前缀,JSON 响应统一为 `{code, data, msg}`。除标注外均需登录。
### Agent
| 接口 | 说明 |
|------|------|
| `POST /agent/run` | 提交需求与当前画布,返回 SSE |
| `POST /agent/resume` | 回答澄清、选择选项或取消任务,返回 SSE |
请求示例:
```json
{
"prompt": "在画布顶部添加标题:销售数据概览",
"screen": { "canvas": { "width": 1920, "height": 1080 }, "nodes": [] },
"context": { "conversation_id": "<POST /conversations 返回的 ID>" }
}
```
每次 `run` 提交画布的完整快照;同一段对话复用 `conversation_id`,历史由服务端读取。
SSE 事件:
| 事件 | 含义 |
|------|------|
| `progress` | 当前处理阶段 |
| `answer_delta` | 问答文本增量,最终以 `final.data.answer` 为准 |
| `final` | 本轮结果,`data.status` 见下 |
| `error` | 流开始后的执行异常 |
`final.data.status`:`completed`(有 `actions` 时由前端按序应用)、`waiting_for_input`(展示 `clarification`,用 `thread_id` 调用 `resume`)、`failed`(业务失败,不交付动作)、`cancelled`。流开始前的错误以 HTTP 状态码返回;未收到 `final` 或 `error` 即断开,应视为结果不完整。
### 会话
| 接口 | 说明 |
|------|------|
| `POST /conversations` | 创建会话 |
| `GET /conversations` | 分页列出当前用户的会话 |
| `GET /conversations/{id}` | 读取会话历史 |
| `PATCH /conversations/{id}` | 重命名 |
| `DELETE /conversations/{id}` | 删除 |
### 数据文件
上传后后台解析(HTTP 202),轮询进度至 `ready` 后即可在同一会话中被 Agent 使用;已解析且未过期的文件自动带入后续对话。
| 接口 | 说明 |
|------|------|
| `GET /files/limits` | 上传限制与支持格式(无需登录) |
| `POST /files` | `multipart/form-data` 上传,需带 `conversation_id` |
| `GET /files?conversation_id=` | 会话下的文件 |
| `GET /files/{id}` | 文件详情与表信息 |
| `GET /files/{id}/progress` | 解析进度 |
| `GET /files/{id}/tables/{table}/rows` | 分页、排序读取 |
| `POST /files/{id}/tables/{table}/aggregate` | 分组汇总 |
| `DELETE /files/{id}` | 删除 |
### 大屏管理
大屏仅所有者可访问,他人的大屏与不存在同样返回 404。保存与发布需携带 `revision`,与服务端不一致时返回 409。
| 接口 | 说明 |
|------|------|
| `POST /screens` | 创建 |
| `GET /screens` | 列表:关键字、状态、排序、分页,`deleted=true` 查看回收站 |
| `GET /screens/{id}` | 详情 |
| `PUT /screens/{id}` | 保存(乐观锁) |
| `PATCH /screens/{id}` | 重命名 |
| `DELETE /screens/{id}` | 移入回收站,默认保留 30 天后自动清除 |
| `POST /screens/{id}/restore` | 从回收站恢复 |
| `DELETE /screens/trash` | 清空回收站 |
| `POST /screens/{id}/publish` | 发布当前版本,首次发布生成 `share_id` |
| `POST /screens/{id}/unpublish` | 下线 |
| `GET /screens/{id}/publishes` | 发布历史 |
| `POST /screens/{id}/publishes/{version}/restore` | 将历史版本恢复为草稿 |
| `POST /screens/{id}/copy` | 复制 |
| `PUT /screens/{id}/thumbnail` | 上传封面图(≤ 1 MB) |
### 公开与其他
| 接口 | 说明 |
|------|------|
| `GET /public/screens/{share_id}` | 线上版本(发布快照),匿名;数据源 `headers` 不下发 |
| `GET /public/screens/{share_id}/datasources/{id}` | 线上取数,请求配置取自发布快照,匿名 |
| `GET /public/thumbnails/{id}` | 封面图,匿名 |
| `POST /datasource/fetch` | 编辑态数据源代理;仅允许公网 http(s) 地址,限 5 秒、2 MB |
| `GET /materials` | 组件物料目录(无需登录) |
## 项目结构
```text
app/
├── api/ HTTP 路由
├── services/ 会话、大屏、物料、数据源代理与持久化装配
├── processor/ LangGraph 工作流与节点
├── agent/ 大屏 / 问答 Agent、工具、Skills 加载
├── actions/ 动作执行、布局计算、整批模拟
├── datafiles/ 数据文件解析、存储、查询与后台任务
├── schemas/ 请求、画布、动作、会话、大屏模型
├── resources/ 物料定义与字段规则
├── clients/ LLM、Jev、MCP、用户中心客户端
├── prompt/ 提示词
└── core/ common/ observability/ 认证、异常、日志、响应与 SSE、追踪
mcp_servers/ 应用管理的本地 MCP 服务(配置见 mcp_servers.json)
skills/ 大屏设计、编辑、图表替换与数据制图规则
```
阅读主链路:[app/api/agent.py](app/api/agent.py) → [app/services/agent.py](app/services/agent.py) → [app/processor/graph.py](app/processor/graph.py) → [app/agent/screen_agent.py](app/agent/screen_agent.py) → [app/actions/batch.py](app/actions/batch.py)。动作协议定义在 [app/schemas/actions](app/schemas/actions)。
## 部署注意
- **单实例**:任务与会话的并发控制、上传进度保存在进程内,请以单 worker 运行;同一会话同时只能执行一个任务。
- **本地存储**:数据文件与封面图写入磁盘(`DATA_FILE_DIR`、`THUMBNAIL_DIR`,默认在 `data/` 下),容器部署需挂载持久卷。
- **反向代理**:SSE 需关闭响应缓冲并放宽读取超时;上传大小限制需与 `DATA_FILE_MAX_MB` 一致。应用未配置 CORS,请通过同源代理转发 `/api`。
- **连接中断**:客户端断开会取消当前任务,不支持心跳、事件重放或断线续传。
- **历史计数**:会话历史用 tiktoken 的 o200k 编码表计数。Docker 镜像在构建时下载好;其他方式部署时首次请求会联网下载,离线环境需预先放入 `TIKTOKEN_CACHE_DIR`。下载失败时退回按字符估算,不影响请求。
- **日志**:写入 `logs/app.log`。
## 开发
扩展组件:修改物料定义,重新导入后重启。调整 Agent 行为:修改 [skills](skills) 与 [app/prompt](app/prompt)。新增 MCP 服务:修改 [mcp_servers.json](mcp_servers.json),连接配置支持 `${环境变量}` 展开。