docs: 更新大量文档
This commit is contained in:
parent
423eca3fb0
commit
e49d8dda53
@ -1,8 +1,5 @@
|
||||
MODEL_DIR=./models
|
||||
SAVE_DIR=./saves
|
||||
LANGGRAPH_CHECKPOINTER_BACKEND=postgres
|
||||
VITE_USE_RUNS_API=false # 部分体验有待优化,debug
|
||||
YUXI_SANDBOX_IMAGE=python:3.12-slim
|
||||
|
||||
# region model_provider
|
||||
SILICONFLOW_API_KEY= # 推荐使用硅基流动免费服务 https://cloud.siliconflow.cn/i/Eo5yTHGJ
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/提交一个docker启动问题.md
vendored
2
.github/ISSUE_TEMPLATE/提交一个docker启动问题.md
vendored
@ -16,8 +16,6 @@ assignees: ''
|
||||
|
||||
例如:"执行 `docker compose up -d` 后,api-dev 服务一直重启,查看日志显示无法连接到 Milvus"
|
||||
|
||||
您可以先看一下常见问题与解决方案:https://xerrors.github.io/Yuxi-Know/latest/changelog/faq.html
|
||||
|
||||
|
||||
## 2️⃣ 环境信息
|
||||
|
||||
|
||||
93
CONTRIBUTING.md
Normal file
93
CONTRIBUTING.md
Normal file
@ -0,0 +1,93 @@
|
||||
# Contributing to Yuxi
|
||||
|
||||
感谢你关注 Yuxi。欢迎提交 Issue、改进文档、修复 Bug 或贡献新功能。
|
||||
|
||||
更完整的开发文档可参考 [docs/develop-guides/contributing.md](docs/develop-guides/contributing.md)。
|
||||
|
||||
## 开始之前
|
||||
|
||||
- 提交前请先搜索现有 [Issues](https://github.com/xerrors/Yuxi-Know/issues)
|
||||
- 对于较大的功能改动,建议先开 Issue 讨论方案
|
||||
- 保持改动聚焦,避免在一次 PR 中混入无关重构
|
||||
|
||||
## 开发方式
|
||||
|
||||
本项目通过 Docker Compose 进行开发,推荐直接在容器环境中调试。
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker ps
|
||||
docker logs api-dev --tail 100
|
||||
```
|
||||
|
||||
项目中的 `api-dev` 和 `web-dev` 默认支持热重载,本地修改代码后通常无需重启容器。
|
||||
|
||||
## 提交流程
|
||||
|
||||
1. Fork 仓库并创建分支
|
||||
2. 在对应目录完成开发与测试
|
||||
3. 提交清晰的 Commit Message
|
||||
4. 发起 Pull Request,并说明修改内容、原因和验证方式
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
git checkout -b feature/your-change
|
||||
git commit -m "feat: add knowledge graph import flow"
|
||||
git push origin feature/your-change
|
||||
```
|
||||
|
||||
## 代码要求
|
||||
|
||||
### 通用
|
||||
|
||||
- 保持实现简单直接,避免过度设计
|
||||
- 只修改当前任务所需内容,不顺手做额外重构
|
||||
- 更新相关文档
|
||||
- 如有必要,同步更新 [docs/develop-guides/roadmap.md](docs/develop-guides/roadmap.md)
|
||||
- 设计部分请参考 [docs/develop-guides/design.md](docs/develop-guides/design.md)
|
||||
|
||||
### 后端
|
||||
|
||||
- 使用 Python 3.12+ 风格
|
||||
- 提交前运行:
|
||||
|
||||
```bash
|
||||
make format
|
||||
make lint
|
||||
docker compose exec api uv run pytest
|
||||
```
|
||||
|
||||
- 测试脚本建议放在 `backend/test`
|
||||
|
||||
### 前端
|
||||
|
||||
- 使用 `pnpm`
|
||||
- API 接口统一放在 `web/src/apis`
|
||||
- 优先使用 `lucide-vue-next` 图标
|
||||
- 样式使用 `less`
|
||||
- 非特殊情况优先复用 [web/src/assets/css/base.css](web/src/assets/css/base.css) 中的颜色变量
|
||||
|
||||
## Pull Request 建议
|
||||
|
||||
- 标题清晰,能说明变更目标
|
||||
- 描述中包含改动内容、影响范围和验证结果
|
||||
- 如果涉及 UI,请附截图或录屏
|
||||
- 如果涉及接口或行为变化,请补充文档
|
||||
|
||||
## 提交信息建议
|
||||
|
||||
推荐使用以下前缀:
|
||||
|
||||
- `feat`
|
||||
- `fix`
|
||||
- `docs`
|
||||
- `refactor`
|
||||
- `test`
|
||||
- `chore`
|
||||
|
||||
## 问题反馈
|
||||
|
||||
- Bug 反馈/功能讨论:<https://github.com/xerrors/Yuxi-Know/issues>
|
||||
|
||||
感谢你的贡献 ❤️。
|
||||
@ -231,7 +231,7 @@ docker compose up --build
|
||||
感谢所有贡献者的支持!
|
||||
|
||||
<a href="https://github.com/xerrors/Yuxi-Know/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=xerrors/Yuxi-Know&max=100&columns=15" />
|
||||
<img src="https://contrib.rocks/image?repo=xerrors/Yuxi-Know&max=100&columns=10" />
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ actions:
|
||||
url: "https://github.com/xerrors/Yuxi-Know/issues/new/choose"
|
||||
- name: "开发路线图"
|
||||
icon: "roadmap"
|
||||
url: "https://xerrors.github.io/Yuxi-Know/latest/changelog/roadmap.html"
|
||||
url: "https://xerrors.github.io/Yuxi-Know/latest/roadmap.html"
|
||||
|
||||
|
||||
|
||||
|
||||
@ -39,9 +39,9 @@ export default defineConfig({
|
||||
text: '智能体开发',
|
||||
items: [
|
||||
{ text: '智能体配置', link: '/agents/agents-config' },
|
||||
{ text: '上下文配置', link: '/agents/context-config' },
|
||||
{ text: '工具系统', link: '/agents/tools-system' },
|
||||
{ text: '中间件', link: '/agents/middleware' },
|
||||
{ text: '沙盒架构与设计', link: '/agents/sandbox-architecture' },
|
||||
{ text: 'MCP 集成', link: '/agents/mcp-integration' },
|
||||
{ text: 'Skills 管理', link: '/agents/skills-management' },
|
||||
{ text: 'SubAgents 管理', link: '/agents/subagents-management' }
|
||||
@ -51,7 +51,6 @@ export default defineConfig({
|
||||
text: '高级配置',
|
||||
items: [
|
||||
{ text: '配置系统详解', link: '/advanced/configuration' },
|
||||
{ text: '沙盒改造与验收', link: '/advanced/sandbox-validation' },
|
||||
{ text: '文档解析', link: '/advanced/document-processing' },
|
||||
{ text: '品牌自定义', link: '/advanced/branding' },
|
||||
{ text: '其他配置', link: '/advanced/misc' },
|
||||
@ -62,16 +61,9 @@ export default defineConfig({
|
||||
{
|
||||
text: '开发指南',
|
||||
items: [
|
||||
{ text: '参与贡献', link: '/develop-guides/contributing' },
|
||||
{ text: '开发路线图', link: '/develop-guides/roadmap' },
|
||||
{ text: '界面设计规范', link: '/develop-guides/design' },
|
||||
{ text: '路线图', link: '/develop-guides/roadmap' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '更新日志',
|
||||
items: [
|
||||
{ text: '参与贡献', link: '/changelog/contributing' },
|
||||
{ text: '常见问题', link: '/changelog/faq' },
|
||||
{ text: '迁移至 v0.5', link: '/changelog/migrate_to_v0-5' }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@ -1,960 +0,0 @@
|
||||
# Yuxi Sandbox 技术设计文档
|
||||
|
||||
## 文档目标
|
||||
|
||||
这份文档描述的是 **Yuxi 当前已经落地的沙盒系统设计**,不是一个理想化方案,也不是 API 参考手册。
|
||||
|
||||
::: warning 预发布
|
||||
此部分所涉及到的技术路线和方案文档,均可能会随时改变。
|
||||
:::
|
||||
|
||||
|
||||
重点回答五个问题:
|
||||
|
||||
1. 当前沙盒到底解决了什么问题
|
||||
2. 当前沙盒在系统里是怎么接入的
|
||||
3. Agent、工作台文件浏览器、Skills 与沙盒之间的关系是什么
|
||||
4. 线程级隔离、路径隔离、容器生命周期是如何实现的
|
||||
5. 当前方案的边界、局限性和后续演进方向是什么
|
||||
|
||||
如果只想快速建立整体认识,建议先看:
|
||||
|
||||
- 第 1 节:设计结论
|
||||
- 第 2 节:系统边界
|
||||
- 第 3 节:整体架构
|
||||
- 第 5 节:生命周期
|
||||
- 第 11 节:当前局限性
|
||||
|
||||
---
|
||||
|
||||
## 1. 设计结论
|
||||
|
||||
当前 Yuxi 的沙盒不是“泛化的安全执行平台”,而是一套 **围绕线程工作区、Agent 文件工具、工作台文件浏览器** 设计的受控执行环境。
|
||||
|
||||
它的核心特征可以概括为:
|
||||
|
||||
- **线程级隔离**:资源归属从早期的 user 级沙盒收敛为 `thread_id -> sandbox_id`
|
||||
- **容器级执行**:默认用本机 Docker 容器承载执行环境
|
||||
- **命名空间约束**:Agent 和前端主要通过 `/mnt/user-data` 与 `/mnt/skills` 看到文件系统
|
||||
- **惰性初始化**:不再依赖公开的 `/api/sandbox/*` 生命周期接口;首次访问时自动获取沙盒
|
||||
- **双视图设计**:
|
||||
- Agent 工具侧使用 composite backend,兼顾技能只读路由
|
||||
- 工作台侧使用 viewer-oriented filesystem service,强调真实目录浏览和原始文件内容
|
||||
|
||||
这意味着它更接近:
|
||||
|
||||
- “给 Agent 提供一个线程隔离的工作目录和有限 shell 执行能力”
|
||||
|
||||
而不是:
|
||||
|
||||
- “一个对外承诺强安全边界的多租户通用沙箱平台”
|
||||
|
||||
这个定位非常重要。后续阅读整份文档时,所有设计取舍都建立在这个现实目标之上。
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统边界
|
||||
|
||||
### 2.1 当前沙盒负责什么
|
||||
|
||||
当前沙盒主要负责以下能力:
|
||||
|
||||
- 为某个 thread 提供独立的用户数据目录
|
||||
- 在 Docker 容器中执行 Agent 发起的命令
|
||||
- 为 Agent 文件工具提供受限文件系统
|
||||
- 为工作台文件浏览器提供可视化文件访问入口
|
||||
- 将 skills 以只读目录暴露给 Agent 和工作台
|
||||
|
||||
### 2.2 当前沙盒不负责什么
|
||||
|
||||
当前实现并不试图完整解决以下问题:
|
||||
|
||||
- 强多租户对抗场景下的高强度安全隔离
|
||||
- 资源配额的精细控制
|
||||
- 审计级别的命令执行记录
|
||||
- 持久化的沙盒元数据控制平面
|
||||
- 面向通用用户的文件编辑、删除、重命名、上传管理 API
|
||||
|
||||
这不是缺陷描述,而是范围定义。文档后面提到的“限制”,也应放在这个范围下理解。
|
||||
|
||||
---
|
||||
|
||||
## 3. 整体架构
|
||||
|
||||
### 3.1 架构分层
|
||||
|
||||
当前沙盒相关实现大致分为五层:
|
||||
|
||||
1. **sandbox 子包**(位于 `backends/sandbox/`)
|
||||
- [sandbox_executor.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_executor.py) — 执行器
|
||||
- [sandbox_provisioner.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_provisioner.py) — 资源调配器
|
||||
- [sandbox_provisioner_base.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_provisioner_base.py) — provisioner 抽象基类
|
||||
- [sandbox_local_container.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_local_container.py) — 本地容器管理器
|
||||
- [sandbox_remote.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_remote.py) — 远程沙盒管理器
|
||||
- [sandbox_config.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_config.py) — 配置常量
|
||||
- [sandbox_info.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_info.py) — 沙盒信息
|
||||
- [path_security.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/path_security.py) — 路径安全
|
||||
- [docker_api.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/docker_api.py) — Docker API 封装
|
||||
2. **Agent / Viewer 接入层**
|
||||
- [composite.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/composite.py) — Agent filesystem 路由
|
||||
- [filesystem_service.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/services/filesystem_service.py) — Agent 文件系统服务
|
||||
- [viewer_filesystem_service.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/services/viewer_filesystem_service.py) — 工作台文件浏览服务
|
||||
3. **HTTP Router / UI 使用层**
|
||||
- [filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/server/routers/filesystem_router.py)
|
||||
- [viewer_filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/server/routers/viewer_filesystem_router.py)
|
||||
- [AgentPanel.vue](/Users/wenjie/Documents/projects/Yuxi-Know/web/src/components/AgentPanel.vue)
|
||||
4. **Agent / Viewer 接入层**
|
||||
- [composite.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/composite.py)
|
||||
- [filesystem_service.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/services/filesystem_service.py)
|
||||
- [viewer_filesystem_service.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/services/viewer_filesystem_service.py)
|
||||
5. **HTTP Router / UI 使用层**
|
||||
- [filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/server/routers/filesystem_router.py)
|
||||
- [viewer_filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/server/routers/viewer_filesystem_router.py)
|
||||
- [AgentPanel.vue](/Users/wenjie/Documents/projects/Yuxi-Know/web/src/components/AgentPanel.vue)
|
||||
|
||||
### 3.2 一张图看清调用关系
|
||||
|
||||
```text
|
||||
Agent / Frontend
|
||||
|
|
||||
+-- Agent filesystem tool
|
||||
| |
|
||||
| +-- create_agent_composite_backend(...)
|
||||
| |
|
||||
| +-- default: YuxiSandboxBackend
|
||||
| +-- route /mnt/skills/: SelectedSkillsReadonlyBackend
|
||||
|
|
||||
+-- Workbench viewer panel
|
||||
|
|
||||
+-- /api/viewer/filesystem/*
|
||||
|
|
||||
+-- viewer_filesystem_service
|
||||
|
|
||||
+-- thread ownership check
|
||||
+-- resolve agent config / visible skills
|
||||
+-- provider.acquire(thread_id)
|
||||
+-- sandbox backend or skills backend
|
||||
|
||||
Provider
|
||||
|
|
||||
+-- LocalContainerBackend (default)
|
||||
| |
|
||||
| +-- docker run / docker exec / docker stop
|
||||
| +-- Docker API fallback when no docker CLI
|
||||
|
|
||||
+-- RemoteSandboxBackend (reserved for remote provisioner mode)
|
||||
```
|
||||
|
||||
### 3.3 当前最重要的设计分叉
|
||||
|
||||
当前系统里有两套“文件系统视图”,这不是重复实现,而是目标不同:
|
||||
|
||||
- **Agent 文件系统视图**
|
||||
- 关注工具调用和 prompt 约束
|
||||
- 通过 composite backend 暴露 `/mnt/skills`
|
||||
- 语义偏 agent-oriented
|
||||
|
||||
- **工作台文件浏览视图**
|
||||
- 关注真实目录浏览、原始文件内容、下载
|
||||
- 通过独立 viewer service 暴露
|
||||
- 语义偏 viewer-oriented
|
||||
|
||||
这套分离是当前架构里一个非常关键的改动。它解决了之前”工作台直接复用 agent backend 导致语义错位”的问题。
|
||||
|
||||
### 3.4 四种 Backend 的角色区分
|
||||
|
||||
理解这四个类的职责边界非常重要,它们分属不同层次:
|
||||
|
||||
#### `SandboxBackend` — 抽象基类,生命周期接口
|
||||
|
||||
[sandbox_provisioner_base.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_provisioner_base.py) 定义了沙盒管理器的**抽象生命周期接口**:
|
||||
|
||||
```python
|
||||
class SandboxBackend(ABC):
|
||||
def create(self, *, thread_id, sandbox_id, extra_mounts) -> SandboxInfo: ...
|
||||
def destroy(self, info: SandboxInfo) -> None: ...
|
||||
def is_alive(self, info: SandboxInfo) -> bool: ...
|
||||
def discover(self, sandbox_id: str) -> SandboxInfo | None: ...
|
||||
```
|
||||
|
||||
它只回答”如何创建一个沙盒实例、如何销毁它、沙盒是否还活着”——**不涉及任何文件操作或命令执行**。
|
||||
|
||||
#### `LocalContainerBackend` — 本地容器管理器
|
||||
|
||||
继承 `SandboxBackend`,是**本地 Docker 容器**的创建者和管理者:
|
||||
|
||||
- 通过 `docker run` 启动沙盒容器
|
||||
- 通过 `docker exec` 在容器中执行命令(实际委托给 `YuxiSandboxBackend`)
|
||||
- 通过 `docker stop` 销毁容器
|
||||
- 支持 Docker CLI 和 Docker Unix Socket API 两种调用方式
|
||||
- 负责端口分配、容器发现、warm pool
|
||||
|
||||
#### `RemoteSandboxBackend` — 远程沙盒管理器
|
||||
|
||||
同样继承 `SandboxBackend`,但通过 **HTTP API** 与远程沙盒 provisioner 交互:
|
||||
|
||||
- `create()` → POST `/api/sandboxes`
|
||||
- `destroy()` → DELETE `/api/sandboxes/{id}`
|
||||
- `is_alive()` → GET `/api/sandboxes/{id}` 检查状态
|
||||
- `discover()` → GET `/api/sandboxes/{id}`
|
||||
|
||||
适用于无法在本地管理容器的场景(如云端沙盒服务)。
|
||||
|
||||
#### `YuxiSandboxBackend` — 执行后端,不是 SandboxBackend
|
||||
|
||||
继承自 deepagents 的 `BaseSandbox`,是**在已有容器内执行操作的具体实现**:
|
||||
|
||||
- `execute(command)` — 在容器内执行 shell 命令
|
||||
- `read() / write() / edit()` — 文件读写编辑
|
||||
- `ls_info() / glob_info()` — 目录扫描和 glob
|
||||
- `upload_files() / download_files()` — 文件上传下载
|
||||
- 路径规范化与输出遮蔽(防止宿主机路径泄露)
|
||||
|
||||
**关键区别**:`YuxiSandboxBackend` 不负责创建/销毁容器,它工作在容器创建好之后。它的定位是”执行器”,而 `LocalContainerBackend` / `RemoteSandboxBackend` 的定位是”容器管理器”。
|
||||
|
||||
#### 关系总结
|
||||
|
||||
```
|
||||
SandboxProvider
|
||||
│
|
||||
├── LocalContainerBackend — 创建/销毁本地 Docker 容器
|
||||
│ │
|
||||
│ └── YuxiSandboxBackend — 在容器内执行命令和文件操作
|
||||
│
|
||||
└── RemoteSandboxBackend — 通过 HTTP 与远程 provisioner 交互
|
||||
│
|
||||
└── 远程沙盒(由外部服务管理)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 运行拓扑
|
||||
|
||||
### 4.1 本地 Docker Compose 模式
|
||||
|
||||
在默认开发模式中,运行拓扑是:
|
||||
|
||||
```text
|
||||
宿主机
|
||||
├── Docker Daemon
|
||||
│ ├── api-dev
|
||||
│ ├── web-dev
|
||||
│ └── yuxi-sandbox-<sandbox_id>
|
||||
└── project workspace
|
||||
|
||||
api-dev 容器
|
||||
└── Yuxi 后端进程
|
||||
└── 通过 docker CLI 或 Docker API 管理 yuxi-sandbox-* 容器
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
- `api-dev` 并不在自身进程里执行用户命令
|
||||
- 真正的执行发生在独立的 `yuxi-sandbox-*` 容器中
|
||||
- `api-dev` 只是控制面,负责创建、发现、复用、销毁沙盒容器
|
||||
|
||||
### 4.2 本地容器模式与远程 provisioner 模式
|
||||
|
||||
[sandbox_provisioner.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_provisioner.py) 会根据配置决定底层 backend:
|
||||
|
||||
- 如果设置了 `YUXI_SANDBOX_PROVISIONER_URL`
|
||||
- 使用 `RemoteSandboxBackend`
|
||||
- 否则
|
||||
- 使用 `LocalContainerBackend`
|
||||
|
||||
当前主路径和测试覆盖都以 `LocalContainerBackend` 为主。
|
||||
|
||||
### 4.3 Docker CLI 与 Docker API 双路径
|
||||
|
||||
[sandbox_local_container.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_local_container.py) 和 [sandbox_executor.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_executor.py) 都支持两种运行方式:
|
||||
|
||||
- 有 `docker` CLI 时,优先走 `docker run`、`docker exec`、`docker stop`
|
||||
- 没有 `docker` CLI 时,退回到 Docker Unix Socket API
|
||||
|
||||
这样做的原因很现实:
|
||||
|
||||
- 开发机和容器内环境不总是完全一致
|
||||
- 有些部署只挂载了 docker socket,没有安装 docker CLI
|
||||
|
||||
这套 fallback 的目标不是优雅,而是保证控制面在不同运行环境下还能工作。
|
||||
|
||||
---
|
||||
|
||||
## 5. 生命周期设计
|
||||
|
||||
### 5.1 当前资源归属:thread-local
|
||||
|
||||
当前 provider 的核心规则是:
|
||||
|
||||
```text
|
||||
thread_id -> deterministic sandbox_id -> sandbox container
|
||||
```
|
||||
|
||||
对应实现见 [sandbox_provisioner.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_provisioner.py)。
|
||||
|
||||
`sandbox_id` 由 thread_id 的 SHA256 前 8 位生成:
|
||||
|
||||
```python
|
||||
hashlib.sha256(thread_id.encode()).hexdigest()[:8]
|
||||
```
|
||||
|
||||
这个设计带来的性质是:
|
||||
|
||||
- 同一个 thread 多次访问时,倾向复用同一个沙盒
|
||||
- 不同 thread 不共享容器
|
||||
- thread 级的工作目录和运行上下文被绑定在一起
|
||||
|
||||
这与旧方案最大的差异在于:
|
||||
|
||||
- **旧方案倾向 user 级复用**
|
||||
- **当前方案明确是 thread 级隔离**
|
||||
|
||||
### 5.2 acquire 的完整逻辑
|
||||
|
||||
`provider.acquire(thread_id)` 的流程不是单纯“起一个容器”,而是按顺序尝试:
|
||||
|
||||
1. 看当前 thread 是否已经绑定活跃沙盒
|
||||
2. 看 warm pool 里是否有这个 sandbox_id
|
||||
3. 看 Docker 里是否已经存在同名运行中容器
|
||||
4. 如果都没有,才真正创建新容器
|
||||
|
||||
这个流程很重要,因为它同时兼顾了:
|
||||
|
||||
- 惰性初始化
|
||||
- 同线程复用
|
||||
- 进程重启后的容器发现
|
||||
- warm pool 热复用
|
||||
|
||||
### 5.3 release 与 destroy 的语义
|
||||
|
||||
当前 provider 仍然保留了 `release` 和 `destroy` 语义,但它们已经不再作为前端公开 API 的一部分。
|
||||
|
||||
- `release(thread_id)`
|
||||
- 从活跃映射中移除
|
||||
- 放入 `_warm_pool`
|
||||
- 容器保持运行
|
||||
|
||||
- `destroy(thread_id)`
|
||||
- 同时处理 active 和 warm pool 中的对应沙盒
|
||||
- 真正调用底层 backend 停止容器
|
||||
|
||||
这里有一个历史经验教训:如果 `destroy()` 只认 active map,不认 warm pool,就会出现“释放后无法真正停掉容器”的泄漏问题。当前实现已经修正了这类问题。
|
||||
|
||||
### 5.4 空闲回收
|
||||
|
||||
provider 在初始化时会启动一个 idle checker 线程:
|
||||
|
||||
- 间隔:`IDLE_CHECK_INTERVAL`
|
||||
- 默认空闲超时:`DEFAULT_IDLE_TIMEOUT = 600`
|
||||
|
||||
回收对象有两类:
|
||||
|
||||
- 活跃但长期无访问的沙盒
|
||||
- 已经释放到 warm pool 且超时的沙盒
|
||||
|
||||
### 5.5 为什么现在不再依赖 `/api/sandbox/*`
|
||||
|
||||
当前公开路由注册见 [server/routers/__init__.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/server/routers/__init__.py)。可以看到:
|
||||
|
||||
- 已注册:
|
||||
- `/api/filesystem/*`
|
||||
- `/api/viewer/filesystem/*`
|
||||
- 未注册:
|
||||
- `/api/sandbox/*`
|
||||
|
||||
这代表生命周期已经被收回到系统内部,不再由前端显式驱动。
|
||||
|
||||
这么做的核心理由有三个:
|
||||
|
||||
1. 避免前端为了“预热容器”而直接参与控制面
|
||||
2. 避免 thread ownership 校验分散在多个入口
|
||||
3. 让 Agent 工具调用和工作台访问都走同一种惰性初始化模型
|
||||
|
||||
---
|
||||
|
||||
## 6. 文件系统模型
|
||||
|
||||
### 6.1 真实宿主机目录
|
||||
|
||||
每个 thread 在宿主机侧都有独立的数据目录,根路径大致是:
|
||||
|
||||
```text
|
||||
saves/threads/<thread_id>/user-data/
|
||||
```
|
||||
|
||||
provider 会确保下面这些子目录存在:
|
||||
|
||||
- `workspace`
|
||||
- `outputs`
|
||||
- `uploads`
|
||||
- `uploads/attachments`
|
||||
- `large_tool_results`
|
||||
|
||||
对应代码在 [sandbox_provisioner.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_provisioner.py) 的 `ensure_thread_dirs()`。
|
||||
|
||||
### 6.2 容器内固定命名空间
|
||||
|
||||
宿主机目录挂载进容器后,Agent 和工作台看到的是固定命名空间:
|
||||
|
||||
- `/mnt/user-data`
|
||||
- `/mnt/user-data/workspace`
|
||||
- `/mnt/user-data/uploads`
|
||||
- `/mnt/user-data/outputs`
|
||||
- `/mnt/user-data/large_tool_results`
|
||||
- `/mnt/skills`
|
||||
|
||||
这里要区分两个概念:
|
||||
|
||||
- **标准命名空间**
|
||||
- 用于约束、归一化、提示词和工具行为
|
||||
- **真实 listing**
|
||||
- 用于工作台展示实际文件结构
|
||||
|
||||
### 6.3 为什么工作台现在显示真实 listing
|
||||
|
||||
之前工作台复用 agent filesystem 视图时,一个典型问题是:
|
||||
|
||||
- Agent 在 `/mnt/user-data/bubble_sort.py` 创建了文件
|
||||
- 工作台却只显示硬编码的 `workspace/uploads/outputs`
|
||||
- 用户会误以为文件没有创建成功
|
||||
|
||||
所以当前 viewer service 的策略是:
|
||||
|
||||
- `/mnt/user-data` 按真实目录扫描结果返回
|
||||
- 不再人为注入固定目录
|
||||
|
||||
这使得工作台真正展示“当前线程在后端实际看到的文件系统”。
|
||||
|
||||
### 6.4 兼容路径为什么还存在
|
||||
|
||||
[path_security.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/path_security.py) 中仍然保留了若干兼容映射,例如:
|
||||
|
||||
- `/workspace` -> `/mnt/user-data/workspace`
|
||||
- `/uploads` -> `/mnt/user-data/uploads`
|
||||
- `/outputs` -> `/mnt/user-data/outputs`
|
||||
- `/skills` -> `/mnt/skills`
|
||||
- `/attachments` -> `/mnt/user-data/uploads/attachments`
|
||||
- `/home` -> `/mnt/user-data/workspace`
|
||||
|
||||
它们存在的原因不是鼓励继续使用旧路径,而是为了兼容:
|
||||
|
||||
- 模型自己生成的老路径
|
||||
- 某些工具或 prompt 里的历史路径语义
|
||||
- 迁移期间尚未完全收敛的调用方
|
||||
|
||||
文档层面应该明确:
|
||||
|
||||
- **标准输出和标准认知应基于 `/mnt/...` 命名空间**
|
||||
- 兼容映射只是过渡层
|
||||
|
||||
---
|
||||
|
||||
## 7. Skills 挂载与可见性模型
|
||||
|
||||
### 7.1 Skills 不是简单的本地目录暴露
|
||||
|
||||
skills 在物理上会被只读挂载到容器的 `/mnt/skills`,但“能否看见全部 skills”不是由挂载本身决定,而是由上层 backend 路由决定。
|
||||
|
||||
当前相关逻辑分成两层:
|
||||
|
||||
1. **物理挂载**
|
||||
- provider 在 `_get_extra_mounts()` 中将 skills 根目录以只读形式挂进容器
|
||||
2. **逻辑可见性**
|
||||
- [composite.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/composite.py) 和 [viewer_filesystem_service.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/services/viewer_filesystem_service.py) 基于当前 runtime context 的 `skills` 选择可见 skills
|
||||
|
||||
### 7.2 Agent 侧如何看到 Skills
|
||||
|
||||
Agent 侧创建 backend 时,会调用:
|
||||
|
||||
- [graph.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/buildin/chatbot/graph.py)
|
||||
- [composite.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/composite.py)
|
||||
|
||||
结果是:
|
||||
|
||||
- 默认文件系统后端使用沙盒 backend
|
||||
- `/mnt/skills/` 路由到 `SelectedSkillsReadonlyBackend`
|
||||
|
||||
所以 Agent 看到的 `/mnt/skills` 是“当前 agent config 允许它看到的 skills 子集”,而不是物理目录的全量镜像。
|
||||
|
||||
### 7.3 工作台为什么也要按 agent config 过滤 Skills
|
||||
|
||||
工作台的目标不是管理员视角的“看所有技能”,而是:
|
||||
|
||||
- “当前这个 thread 对应的 agent,在此刻实际能看到什么”
|
||||
|
||||
因此 viewer service 会先解析:
|
||||
|
||||
- 当前用户
|
||||
- 当前 thread
|
||||
- 当前 agent_id
|
||||
- 可选的 agent_config_id
|
||||
|
||||
然后再决定 `/mnt/skills` 下暴露哪些内容。
|
||||
|
||||
这保证了两件事:
|
||||
|
||||
1. 工作台与 Agent 的 skills 可见范围一致
|
||||
2. 前端不会看到“实际 Agent 用不到的 skills”
|
||||
|
||||
---
|
||||
|
||||
## 8. 路径安全与命令执行约束
|
||||
|
||||
### 8.1 核心约束原则
|
||||
|
||||
当前安全模型并不是一个完整的系统调用级隔离模型,而是建立在以下几层约束之上:
|
||||
|
||||
1. 线程级独立容器
|
||||
2. 受限文件命名空间
|
||||
3. 绝对路径白名单检查
|
||||
4. 只读 skills 挂载
|
||||
5. 前端/服务层的 thread ownership 校验
|
||||
|
||||
### 8.2 路径白名单
|
||||
|
||||
[path_security.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/path_security.py) 中定义了允许的命名空间前缀:
|
||||
|
||||
- `/mnt/user-data`
|
||||
- `/mnt/skills`
|
||||
|
||||
只要请求落在这两个根之外,就会被拒绝。
|
||||
|
||||
同时还会显式拦截:
|
||||
|
||||
- `..` 路径遍历
|
||||
- 非允许命名空间访问
|
||||
|
||||
### 8.3 命令执行时的路径检查
|
||||
|
||||
`validate_execute_command_paths()` 会对 shell 命令做 token 级别的绝对路径检查:
|
||||
|
||||
- `/mnt/...` 允许,但仍要经过 `ensure_path_allowed()`
|
||||
- `/bin/`、`/usr/bin/`、`/usr/local/bin/` 放行
|
||||
- `/usr/lib/` 作为运行时依赖路径放行
|
||||
- 其他绝对路径拒绝
|
||||
|
||||
这意味着当前模型允许类似:
|
||||
|
||||
```bash
|
||||
python3 /mnt/user-data/workspace/app.py
|
||||
ls /mnt/user-data
|
||||
/bin/sh -c '...'
|
||||
```
|
||||
|
||||
但不允许:
|
||||
|
||||
```bash
|
||||
cat /etc/passwd
|
||||
python /tmp/evil.py
|
||||
```
|
||||
|
||||
### 8.4 输出中的宿主机路径遮蔽
|
||||
|
||||
为了减少宿主机路径泄露,`sandbox_backend` 会对输出做一次映射遮蔽:
|
||||
|
||||
- 宿主机 thread 目录 -> `/mnt/user-data`
|
||||
- skills 宿主机目录 -> `/mnt/skills`
|
||||
|
||||
这是一个很实用的细节。它不能提供真正的安全保证,但可以降低“内部物理路径暴露到模型上下文或前端界面”的概率。
|
||||
|
||||
### 8.5 需要明确的现实边界
|
||||
|
||||
当前这套安全模型是“工程上可控”,不是“高强度对抗级别安全”。
|
||||
|
||||
例如,当前仍然没有做到:
|
||||
|
||||
- 细粒度 CPU / memory / pid 限制
|
||||
- 默认禁网
|
||||
- 严格只读 rootfs
|
||||
- seccomp / apparmor 的定制化收敛
|
||||
- 进程树和系统调用级审计
|
||||
|
||||
当前本地容器启动只设置了:
|
||||
|
||||
- `--security-opt seccomp=unconfined`
|
||||
|
||||
这从强安全角度看其实是偏宽松的。文档必须如实反映这一点,而不是把当前方案描述成“高安全沙盒”。
|
||||
|
||||
---
|
||||
|
||||
## 9. Agent、旧 filesystem API 与 viewer API 的关系
|
||||
|
||||
### 9.1 Agent 侧接入
|
||||
|
||||
以 [chatbot/graph.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/buildin/chatbot/graph.py) 为例,FilesystemMiddleware 的 backend 由 `_create_fs_backend()` 动态创建。
|
||||
|
||||
关键流程是:
|
||||
|
||||
1. 从 runtime context 里拿到 `thread_id`
|
||||
2. `provider.acquire(thread_id)`
|
||||
3. 用 `create_agent_composite_backend()` 组装:
|
||||
- default = sandbox backend
|
||||
- `/mnt/skills/` = SelectedSkillsReadonlyBackend
|
||||
|
||||
这代表:
|
||||
|
||||
- Agent 的工具调用第一次触发文件系统时,就会惰性初始化沙盒
|
||||
- 不需要先调用某个 prepare API
|
||||
|
||||
### 9.2 旧 `/api/filesystem/*` 的定位
|
||||
|
||||
[filesystem_service.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/services/filesystem_service.py) 和 [filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/server/routers/filesystem_router.py) 仍然存在。
|
||||
|
||||
它们的定位是:
|
||||
|
||||
- 提供兼容性的 filesystem 访问接口
|
||||
- 继续基于 agent-oriented composite backend 工作
|
||||
|
||||
它们会:
|
||||
|
||||
1. 校验 thread ownership
|
||||
2. 解析当前用户可用的 agent config context
|
||||
3. acquire sandbox
|
||||
4. 组装 composite backend
|
||||
5. 调用 `ls_info` 或 `download_files`
|
||||
|
||||
### 9.3 新 `/api/viewer/filesystem/*` 的定位
|
||||
|
||||
viewer API 对应:
|
||||
|
||||
- [viewer_filesystem_service.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/services/viewer_filesystem_service.py)
|
||||
- [viewer_filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/server/routers/viewer_filesystem_router.py)
|
||||
|
||||
这组接口的设计目标非常明确:
|
||||
|
||||
- 为工作台文件浏览器服务
|
||||
- 强调真实目录浏览
|
||||
- 返回原始文件内容
|
||||
- 支持下载
|
||||
- 与 Agent 的 skills 可见范围一致
|
||||
|
||||
当前提供三类只读能力:
|
||||
|
||||
- `tree`
|
||||
- `file`
|
||||
- `download`
|
||||
|
||||
为什么说它不是“API 参考”而是“技术设计”的一部分?因为它的重要性不在于 URL 形状,而在于 **它代表了工作台视图从 agent backend 中解耦** 这个架构决定。
|
||||
|
||||
### 9.4 为什么不让工作台继续复用旧 filesystem API
|
||||
|
||||
之前工作台直接复用 agent filesystem 逻辑,会遇到一串语义问题:
|
||||
|
||||
- 文件读取带行号,因为底层 `read()` 更偏向 Agent 阅读语义
|
||||
- 目录展示不够真实,更偏向工具侧规范目录
|
||||
- skills 可见性容易与当前 agent config 脱节
|
||||
- 前端不得不理解更多 backend 细节
|
||||
|
||||
viewer API 的存在,实质上是承认:
|
||||
|
||||
- “Agent 文件系统”与“用户浏览器文件系统”是两个不同产品对象
|
||||
|
||||
这是当前沙盒周边设计里最重要的一个收敛。
|
||||
|
||||
---
|
||||
|
||||
## 10. 典型执行流程
|
||||
|
||||
### 10.1 Agent 首次使用文件系统
|
||||
|
||||
```text
|
||||
用户发起对话
|
||||
-> Agent graph 构建 middleware
|
||||
-> Agent 首次触发 filesystem tool
|
||||
-> _create_fs_backend()
|
||||
-> provider.acquire(thread_id)
|
||||
-> 若无现成容器则创建新容器
|
||||
-> 返回 composite backend
|
||||
-> Agent 开始读写 /mnt/user-data 或访问 /mnt/skills
|
||||
```
|
||||
|
||||
### 10.2 工作台首次打开文件系统
|
||||
|
||||
```text
|
||||
前端打开 AgentPanel
|
||||
-> 请求 /api/viewer/filesystem/tree?thread_id=...&path=/
|
||||
-> 后端校验 thread ownership
|
||||
-> 解析当前 agent config
|
||||
-> provider.acquire(thread_id)
|
||||
-> 构造 sandbox backend + skills backend
|
||||
-> 返回根目录条目
|
||||
```
|
||||
|
||||
### 10.3 点击进入某个目录
|
||||
|
||||
```text
|
||||
前端点击目录
|
||||
-> 请求 viewer tree(path=<that dir>)
|
||||
-> 后端仅列当前层目录
|
||||
-> 前端懒加载子节点
|
||||
```
|
||||
|
||||
这意味着工作台不会一次性扫完整棵树,而是按层级逐步展开。
|
||||
|
||||
### 10.4 读取文件内容
|
||||
|
||||
```text
|
||||
前端点击文件
|
||||
-> 请求 /api/viewer/filesystem/file
|
||||
-> 后端 download_files([path])
|
||||
-> 取原始 bytes
|
||||
-> UTF-8 decode,失败时 replace
|
||||
-> 返回纯文本内容
|
||||
```
|
||||
|
||||
这里特意不走 deepagents 的 `read()`,就是为了避免“所有文件都被自动加行号”的 agent-oriented 行为污染工作台体验。
|
||||
|
||||
---
|
||||
|
||||
## 11. 容器管理细节
|
||||
|
||||
### 11.1 命名规则
|
||||
|
||||
容器名格式:
|
||||
|
||||
```text
|
||||
yuxi-sandbox-<sandbox_id>
|
||||
```
|
||||
|
||||
其中 `<sandbox_id>` 是 thread_id 的确定性哈希截断值。
|
||||
|
||||
### 11.2 启动方式
|
||||
|
||||
本地容器 backend 典型启动命令等价于:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--security-opt seccomp=unconfined \
|
||||
--rm \
|
||||
-d \
|
||||
-p <port>:8080 \
|
||||
--name yuxi-sandbox-<sandbox_id> \
|
||||
-v <thread_user_data_dir>:/mnt/user-data \
|
||||
-v <skills_root>:/mnt/skills:ro \
|
||||
<sandbox_image> \
|
||||
sh -c "sleep infinity"
|
||||
```
|
||||
|
||||
为什么是 `sleep infinity`:
|
||||
|
||||
- 容器被创建后保持存活
|
||||
- 真正的执行通过后续 `docker exec` 进入
|
||||
- 这样可以避免每次命令都重新起一个新容器
|
||||
|
||||
### 11.3 容器发现
|
||||
|
||||
当 provider 启动后,如果内存态映射丢失,但 Docker 中容器还活着,provider 会尝试 `discover(sandbox_id)`:
|
||||
|
||||
- 根据容器名判断是否存在
|
||||
- 解析暴露端口
|
||||
- 重建 `SandboxInfo`
|
||||
|
||||
这是一种“弱恢复”能力,不是严格的持久化控制面。
|
||||
|
||||
### 11.4 端口分配
|
||||
|
||||
本地 backend 会从 `base_port` 开始找空闲端口,并向后扫描。
|
||||
|
||||
需要诚实指出:
|
||||
|
||||
- 这是典型的“先探测端口再绑定”的 TOCTOU 模式
|
||||
- 在高并发/高竞争环境下并不完美
|
||||
|
||||
当前之所以可接受,是因为它主要服务于本地开发和较小规模部署,而不是大规模沙盒平台。
|
||||
|
||||
---
|
||||
|
||||
## 12. 配置项
|
||||
|
||||
当前配置核心定义见 [sandbox_config.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/package/yuxi/agents/backends/sandbox/sandbox_config.py)。
|
||||
|
||||
常用项包括:
|
||||
|
||||
- `YUXI_SANDBOX_IMAGE`
|
||||
- 沙盒镜像
|
||||
- `YUXI_SANDBOX_BASE_PORT`
|
||||
- 本地容器端口起始值
|
||||
- `YUXI_SANDBOX_CONTAINER_PREFIX`
|
||||
- 容器名前缀
|
||||
- `YUXI_SANDBOX_IDLE_TIMEOUT`
|
||||
- 空闲超时
|
||||
- `YUXI_SANDBOX_MAX_REPLICAS`
|
||||
- 最大并发沙盒数
|
||||
- `YUXI_SANDBOX_HOST`
|
||||
- 宿主机访问沙盒容器的 host
|
||||
- `YUXI_SANDBOX_PROVISIONER_URL`
|
||||
- 远程 provisioner 模式入口
|
||||
- `YUXI_DOCKER_API_BASE`
|
||||
- Docker API base
|
||||
- `YUXI_DOCKER_API_SOCKET`
|
||||
- Docker Unix Socket 路径
|
||||
|
||||
理解这些配置时,要把它们分成三类:
|
||||
|
||||
- **容器资源寻址**
|
||||
- **provider 生命周期控制**
|
||||
- **Docker 控制面访问方式**
|
||||
|
||||
---
|
||||
|
||||
## 13. 测试与验证策略
|
||||
|
||||
### 13.1 单元与集成测试
|
||||
|
||||
当前沙盒和 viewer 相关测试覆盖包括:
|
||||
|
||||
- [test_sandbox_provider_lifecycle.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/test/test_sandbox_provider_lifecycle.py)
|
||||
- [test_sandbox_path_compat.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/test/test_sandbox_path_compat.py)
|
||||
- [test_filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/test/api/test_filesystem_router.py)
|
||||
- [test_viewer_filesystem_router.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/test/api/test_viewer_filesystem_router.py)
|
||||
|
||||
覆盖点主要包括:
|
||||
|
||||
- provider 生命周期逻辑
|
||||
- 路径兼容与路径安全
|
||||
- 旧 filesystem API 的 thread ownership 和命名空间行为
|
||||
- viewer API 的真实目录浏览、原始文件读取、下载行为
|
||||
|
||||
### 13.2 端到端测试
|
||||
|
||||
脚本式 E2E 见:
|
||||
|
||||
- [test_sandbox_e2e_no_skip.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/test/api/test_sandbox_e2e_no_skip.py)
|
||||
- [test_sandbox_e2e_reuse.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/test/api/test_sandbox_e2e_reuse.py)
|
||||
- [test_viewer_filesystem_e2e.py](/Users/wenjie/Documents/projects/Yuxi-Know/backend/test/api/test_viewer_filesystem_e2e.py)
|
||||
|
||||
这些脚本验证的是更接近真实使用链路的场景:
|
||||
|
||||
- thread 创建
|
||||
- provider acquire
|
||||
- 容器内写文件
|
||||
- API 层读取/浏览/下载
|
||||
- 同线程复用
|
||||
- 不同线程隔离
|
||||
|
||||
### 13.3 为什么测试里要主动清理 sandbox 容器
|
||||
|
||||
由于本地容器模式本质上会真正起 `yuxi-sandbox-*` 容器,如果测试没有处理好清理,会出现:
|
||||
|
||||
- 端口耗尽
|
||||
- warm pool / discover 干扰后续用例
|
||||
- 测试之间状态污染
|
||||
|
||||
因此测试基建里增加了 session 级 sandbox cleanup。这个细节本身也说明:
|
||||
|
||||
- 当前 provider 的控制面仍偏内存态
|
||||
- 真实容器状态与测试进程状态可能漂移
|
||||
|
||||
---
|
||||
|
||||
## 14. 当前局限性
|
||||
|
||||
### 14.1 安全强度仍然偏工程化
|
||||
|
||||
当前方案不是强隔离安全产品,其局限包括:
|
||||
|
||||
- 没有严格资源配额
|
||||
- 没有默认禁网策略
|
||||
- 容器启动参数偏宽松
|
||||
- 依赖路径白名单而不是系统调用级控制
|
||||
|
||||
### 14.2 Provider 状态未持久化
|
||||
|
||||
provider 的核心状态都在进程内:
|
||||
|
||||
- `_sandboxes`
|
||||
- `_sandbox_infos`
|
||||
- `_thread_sandboxes`
|
||||
- `_warm_pool`
|
||||
|
||||
这意味着:
|
||||
|
||||
- 进程重启后要依赖 `discover()`
|
||||
- 不能把当前方案理解为“有完整控制数据库的沙盒平台”
|
||||
|
||||
### 14.3 viewer 仍然是只读
|
||||
|
||||
当前 viewer-oriented filesystem service 只做了:
|
||||
|
||||
- tree
|
||||
- file
|
||||
- download
|
||||
|
||||
没有做:
|
||||
|
||||
- rename
|
||||
- move
|
||||
- delete
|
||||
- upload
|
||||
- inline edit
|
||||
|
||||
这是刻意收敛范围的结果,不是遗漏。
|
||||
|
||||
### 14.4 兼容路径仍然存在
|
||||
|
||||
兼容路径仍保留,说明路径模型虽然已经大致收敛,但还没有完全把所有调用方都清到统一规范。
|
||||
|
||||
### 14.5 本地容器 backend 仍有工程折中
|
||||
|
||||
例如:
|
||||
|
||||
- 端口分配不是完全无竞争
|
||||
- Docker CLI / Docker API 双实现会增加维护成本
|
||||
- 文件上传下载与命令执行仍不是完全同一种底层路径
|
||||
|
||||
这些都是当前方案的真实成本。
|
||||
|
||||
---
|
||||
|
||||
## 15. 后续演进方向
|
||||
|
||||
如果未来继续演进,这条线最值得做的事情不是再堆更多 viewer 功能,而是继续收敛基础模型:
|
||||
|
||||
### 15.1 强化控制面
|
||||
|
||||
- 将 sandbox 元数据持久化
|
||||
- 更清晰地区分 active、warm、destroyed 状态
|
||||
- 降低 discover 对系统一致性的依赖
|
||||
|
||||
### 15.2 强化安全边界
|
||||
|
||||
- 增加资源限制
|
||||
- 明确网络策略
|
||||
- 收紧容器安全选项
|
||||
- 评估更适合沙盒场景的运行时
|
||||
|
||||
### 15.3 继续收敛路径模型
|
||||
|
||||
- 逐步减少兼容别名
|
||||
- 统一让 `/mnt/...` 成为唯一规范路径
|
||||
- 减少模型与前端对历史路径的依赖
|
||||
|
||||
### 15.4 继续拆分用户视图与 Agent 视图
|
||||
|
||||
viewer service 证明了一件事:
|
||||
|
||||
- “给 Agent 用的 backend”和“给用户看的文件浏览器后端”应该明确区分
|
||||
|
||||
如果以后要做:
|
||||
|
||||
- 文件编辑器
|
||||
- 差异查看
|
||||
- 搜索
|
||||
- 批量下载
|
||||
- 二进制预览
|
||||
|
||||
应该继续沿 viewer-oriented 的方向扩展,而不是回退去复用 agent-oriented backend 语义。
|
||||
|
||||
---
|
||||
|
||||
## 16. 一句话总结
|
||||
|
||||
Yuxi 当前的沙盒方案,本质上是一套 **线程级容器工作区 + `/mnt` 命名空间 + 惰性获取的 provider + Agent/Viewer 双视图文件系统接入模型**。
|
||||
|
||||
它已经足够支撑:
|
||||
|
||||
- Agent 在独立线程工作区中执行命令
|
||||
- Skills 以只读方式注入
|
||||
- 工作台查看真实后端文件系统
|
||||
|
||||
但它仍然是一个 **面向当前产品场景的工程化沙盒**,而不是一个已经完成强安全与强控制面建设的通用沙箱平台。
|
||||
@ -1,147 +1,309 @@
|
||||
# 智能体开发指南
|
||||
# 智能体配置
|
||||
|
||||
Yuxi 的智能体系统基于 LangGraph 构建,提供了灵活而强大的 Agent 开发能力。通过统一的 `AgentManager`,系统能够自动发现和管理所有智能体,让开发者能够专注于业务逻辑的实现。
|
||||
Yuxi 的智能体系统基于 LangGraph 构建。对开发者来说,最重要的不是单独理解某个页面或某个字段,而是理解三件事:
|
||||
|
||||
## 智能体架构
|
||||
- Agent 如何被定义和发现
|
||||
- Context 如何驱动配置界面
|
||||
- Context 如何贯穿一次 Agent 运行周期
|
||||
|
||||
### 核心概念
|
||||
本文聚焦这三部分。
|
||||
|
||||
系统的智能体架构围绕几个核心组件展开:
|
||||
## 1. 整体结构
|
||||
|
||||
- **BaseAgent**:所有智能体的基类,定义了统一的接口规范
|
||||
- **AgentContext**:智能体的配置上下文,包含模型、提示词、工具等配置
|
||||
- **Graph**:LangGraph 图结构,定义智能体的执行流程
|
||||
- **Middleware**:中间件系统,用于扩展和定制智能体行为
|
||||
智能体开发围绕四个核心对象展开:
|
||||
|
||||
### 自动发现机制
|
||||
- **`BaseAgent`**:统一的 Agent 抽象,定义 `get_graph()`、`context_schema`、`capabilities`
|
||||
- **`BaseContext`**:配置 Schema,也是前端配置项的来源
|
||||
- **Graph / Middleware**:LangGraph 图与中间件链,决定运行时行为
|
||||
- **AgentConfig**:数据库中的配置实例,前端侧边栏编辑的就是它
|
||||
|
||||
智能体采用自动发现模式。在 `backend/package/yuxi/agents/__init__.py` 中,系统会遍历 `backend/package/yuxi/agents` 目录,自动注册所有继承自 `BaseAgent` 的类。这意味着开发者只需要按照规范编写代码,智能体就会自动被系统识别,无需手动配置。
|
||||
仓库中已经内置了可直接参考的智能体:
|
||||
|
||||
仓库预置了几个可以直接使用的智能体示例:
|
||||
- `chatbot`:通用对话智能体
|
||||
- `deep_agent`:深度分析智能体
|
||||
|
||||
- **chatbot**:通用对话智能体,支持动态工具调度
|
||||
- **reporter**:报表生成智能体,演示多工具协作
|
||||
- **deep_agent**:深度分析智能体,支持复杂推理任务
|
||||
## 2. Agent 的代码组织
|
||||
|
||||
这些示例展示了如何组织代码结构、如何定义上下文、如何组合中间件,新增智能体时可以作为参考。
|
||||
建议在 `backend/package/yuxi/agents` 下按包组织一个智能体:
|
||||
|
||||
## 创建自定义智能体
|
||||
|
||||
### 目录结构
|
||||
|
||||
在 `backend/package/yuxi/agents` 目录下创建新的智能体包,建议保持以下结构:
|
||||
|
||||
```
|
||||
```text
|
||||
backend/package/yuxi/agents/
|
||||
└── my_agent/
|
||||
├── __init__.py # 暴露主类
|
||||
├── graph.py # Graph 构造逻辑
|
||||
└── metadata.toml # 元数据配置(可选)
|
||||
├── __init__.py
|
||||
├── context.py
|
||||
└── graph.py
|
||||
```
|
||||
|
||||
### 基本实现
|
||||
最小实现通常包含:
|
||||
|
||||
智能体类需要继承 `BaseAgent` 并实现异步的 `get_graph` 方法:
|
||||
- 一个继承 `BaseAgent` 的主类
|
||||
- 一个 `context_schema`
|
||||
- 一个 `get_graph()` 实现
|
||||
|
||||
示例:
|
||||
|
||||
```python
|
||||
from yuxi.agents import BaseAgent
|
||||
from langgraph.prebuilt import create_agent
|
||||
from yuxi.agents import BaseAgent, BaseContext, load_chat_model
|
||||
from langchain.agents import create_agent
|
||||
|
||||
|
||||
class MyAgent(BaseAgent):
|
||||
async def get_graph(self, **kwargs):
|
||||
# 获取配置上下文
|
||||
context = self.get_context()
|
||||
name = "我的智能体"
|
||||
description = "示例智能体"
|
||||
context_schema = BaseContext
|
||||
|
||||
# 获取工具列表
|
||||
tools = await get_tools_from_context(context)
|
||||
|
||||
# 构建 LangGraph 图
|
||||
async def get_graph(self, context=None, **kwargs):
|
||||
context = context or self.context_schema()
|
||||
graph = create_agent(
|
||||
model=load_chat_model(context.model),
|
||||
tools=tools,
|
||||
system_prompt=context.system_prompt,
|
||||
checkpointer=await self._get_checkpointer(),
|
||||
)
|
||||
|
||||
return graph
|
||||
```
|
||||
|
||||
### 能力配置
|
||||
## 3. Context 是配置模型,不只是运行时参数
|
||||
|
||||
`capabilities` 属性用于声明智能体的前端能力,控制 UI 组件的显示:
|
||||
### 3.1 `BaseContext` 的角色
|
||||
|
||||
`BaseContext` 定义在 `backend/package/yuxi/agents/context.py`,它不是一个普通的数据类,而是整个智能体配置链路的核心:
|
||||
|
||||
- 它定义了 Agent 可以配置哪些字段
|
||||
- 它定义了这些字段在前端如何展示
|
||||
- 它也是运行期传入 Graph 和中间件的上下文对象
|
||||
|
||||
当前基础字段包括:
|
||||
|
||||
| 字段 | 作用 |
|
||||
| --- | --- |
|
||||
| `system_prompt` | 系统提示词 |
|
||||
| `model` | 主模型 |
|
||||
| `tools` | 启用的内置工具 |
|
||||
| `knowledges` | 关联知识库 |
|
||||
| `mcps` | 启用的 MCP 服务器 |
|
||||
| `skills` | 关联 Skills |
|
||||
| `subagents_model` | 子智能体默认模型 |
|
||||
| `subagents` | 启用的子智能体 |
|
||||
| `summary_threshold` | 摘要触发阈值 |
|
||||
| `thread_id` / `user_id` | 运行期标识,不作为页面配置项暴露 |
|
||||
|
||||
### 3.2 前端配置项如何从 Context 生成
|
||||
|
||||
`BaseContext.get_configurable_items()` 会遍历字段定义,把字段类型、默认值、描述、模板元数据整理成 `configurable_items`。
|
||||
|
||||
随后:
|
||||
|
||||
1. `BaseAgent.get_info()` 暴露 `configurable_items`
|
||||
2. 前端读取 Agent 详情
|
||||
3. `AgentConfigSidebar` 按 `template_metadata.kind` 渲染不同控件
|
||||
|
||||
也就是说,`AgentConfigSidebar` 不是手写每个字段,而是直接消费 `context_schema` 生成的配置描述。
|
||||
|
||||
这也是为什么:
|
||||
|
||||
- 新增一个 Context 字段,往往会直接影响侧边栏
|
||||
- 字段的 `metadata`、`Annotated` 类型信息,会直接影响展示方式
|
||||
|
||||
### 3.3 `AgentConfigSidebar` 与 AgentConfig 的联动关系
|
||||
|
||||
这部分是最关键的。
|
||||
|
||||
在前端:
|
||||
|
||||
- `AgentConfigSidebar.vue` 负责渲染配置表单
|
||||
- `agentStore` 加载配置时,读取 `config_json.context`
|
||||
- 如果某些字段未配置,会用 `configurable_items` 中的默认值补全
|
||||
- 保存时,前端将当前表单写回 `config_json: { context: agentConfig }`
|
||||
|
||||
因此真实关系是:
|
||||
|
||||
```text
|
||||
context_schema
|
||||
-> get_configurable_items()
|
||||
-> Agent detail API 返回 configurable_items
|
||||
-> AgentConfigSidebar 渲染表单
|
||||
-> 用户编辑后保存到 config_json.context
|
||||
```
|
||||
|
||||
这里需要特别注意两点:
|
||||
|
||||
- **侧边栏展示结构来自 `context_schema`**
|
||||
- **配置实例值来自数据库中的 `config_json.context`**
|
||||
|
||||
前者决定“能配什么、怎么展示”,后者决定“当前配置实际选了什么”。
|
||||
|
||||
### 3.4 自定义 Context 的推荐方式
|
||||
|
||||
如果某个智能体有额外配置,不要在前端单独加一套表单,而是直接扩展 Context:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
from yuxi.agents import BaseContext
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class MyAgentContext(BaseContext):
|
||||
custom_mode: str = field(
|
||||
default="default",
|
||||
metadata={
|
||||
"name": "运行模式",
|
||||
"description": "控制智能体的自定义行为",
|
||||
"options": ["default", "strict"],
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
然后在 Agent 中声明:
|
||||
|
||||
```python
|
||||
class MyAgent(BaseAgent):
|
||||
capabilities = ["file_upload", "files", "todo"] # 支持文件上传、文件管理、待办事项
|
||||
context_schema = MyAgentContext
|
||||
```
|
||||
|
||||
**可用能力:**
|
||||
这会同时影响:
|
||||
|
||||
| capability | 说明 | 前端效果 |
|
||||
|------------|------|----------|
|
||||
| `file_upload` | 文件上传 | 显示上传按钮 |
|
||||
| `files` | 文件管理 | 显示文件管理面板 |
|
||||
| `todo` | 待办事项 | 显示待办组件 |
|
||||
- 后端可接收的配置结构
|
||||
- 前端配置侧边栏的展示内容
|
||||
- 运行期 `context` 可访问的字段
|
||||
|
||||
**示例:**
|
||||
## 4. Context 如何贯穿 Agent 的运行周期
|
||||
|
||||
Context 的价值不只在“配置页面”。它贯穿了从配置加载到实际执行的整条链路。
|
||||
|
||||
### 4.1 配置加载阶段
|
||||
|
||||
在聊天请求进入后端时,服务会先解析 `agent_config_id`,再加载对应配置。
|
||||
|
||||
当前主流程在 `chat_stream_service.py` 中:
|
||||
|
||||
1. 通过 `agent_config_id` 查找配置
|
||||
2. 若未指定,则获取该部门下该 Agent 的默认配置
|
||||
3. 取出 `config_json.context`
|
||||
4. 与 `user_id`、`thread_id` 合并成运行时输入
|
||||
|
||||
也就是说,运行期 Context 的基础来源并不是前端临时状态,而是数据库中保存的 AgentConfig。
|
||||
|
||||
### 4.2 Context 实例化阶段
|
||||
|
||||
`BaseAgent` 在运行前会创建 `context_schema()` 实例,并通过 `update_from_dict()` 注入配置值。
|
||||
|
||||
这一步完成后,Context 才真正成为运行期对象。
|
||||
|
||||
可以把它理解为:
|
||||
|
||||
```text
|
||||
config_json.context + runtime ids -> context_schema instance
|
||||
```
|
||||
|
||||
### 4.3 Graph 构建阶段
|
||||
|
||||
`get_graph(context=context)` 会收到这份 Context。
|
||||
|
||||
以内置 `chatbot` 为例,Context 会直接参与:
|
||||
|
||||
- 主模型选择:`context.model`
|
||||
- 系统提示词拼接:`context.system_prompt`
|
||||
- 子智能体默认模型:`context.subagents_model`
|
||||
- 子智能体列表:`context.subagents`
|
||||
- 摘要阈值:`context.summary_threshold`
|
||||
|
||||
因此 Graph 不是和 Context 解耦的。相反,Graph 的构造本身就依赖 Context。
|
||||
|
||||
### 4.4 中间件运行阶段
|
||||
|
||||
中间件通过 `request.runtime.context` 或 `runtime.context` 继续读取和修改 Context。
|
||||
|
||||
例如:
|
||||
|
||||
- `RuntimeConfigMiddleware`
|
||||
- 读取 `model`、`system_prompt`、`tools`、`mcps`
|
||||
- 动态覆盖模型、系统提示词和工具列表
|
||||
- `SkillsMiddleware`
|
||||
- 读取 `skills`
|
||||
- 计算可见技能闭包
|
||||
- 将 skills 提示段注入 `system_prompt`
|
||||
- 在运行期回写 `_visible_skills`
|
||||
- 文件系统与沙盒接入
|
||||
- 通过 `thread_id` 获取对应沙盒
|
||||
- 通过 `skills` 决定 `/mnt/skills` 的可见范围
|
||||
|
||||
所以 Context 既是输入配置,也是中间件共享的运行时状态载体。
|
||||
|
||||
### 4.5 文件系统与 Viewer 阶段
|
||||
|
||||
文件系统服务不会重新发明一套配置结构,而是再次从 `config_json.context` 还原出 runtime context,用于:
|
||||
|
||||
- 判断当前线程下 Agent 可见的 Skills
|
||||
- 构造 Agent 视图的 composite backend
|
||||
- 构造 Viewer 视图的文件系统展示
|
||||
|
||||
这也是为什么 Context 不只是聊天链路的一部分,它还影响:
|
||||
|
||||
- Agent 文件工具
|
||||
- Viewer 文件浏览器
|
||||
- Skills 可见性
|
||||
- 沙盒挂载语义
|
||||
|
||||
### 4.6 恢复运行阶段
|
||||
|
||||
在 `resume` 流程中,系统同样会重新加载 AgentConfig,并重新构造 Context,再继续执行 Graph。
|
||||
|
||||
也就是说,无论是:
|
||||
|
||||
- 首次对话
|
||||
- 中断恢复
|
||||
- 文件系统查看
|
||||
|
||||
它们都依赖同一份 Context 配置来源。
|
||||
|
||||
## 5. `capabilities` 的作用
|
||||
|
||||
`capabilities` 用于声明前端能力开关,控制 UI 组件显示,不等同于 Context。
|
||||
|
||||
示例:
|
||||
|
||||
```python
|
||||
# 只需要文件上传能力
|
||||
capabilities = ["file_upload"]
|
||||
|
||||
# 需要文件上传和待办事项
|
||||
capabilities = ["file_upload", "todo"]
|
||||
|
||||
# 全部能力
|
||||
capabilities = ["file_upload", "files", "todo"]
|
||||
class MyAgent(BaseAgent):
|
||||
capabilities = ["file_upload", "files", "todo"]
|
||||
```
|
||||
|
||||
注意:即使启用了能力,也需要在中间件中正确配置对应的处理逻辑,功能才能正常工作。例如启用 `file_upload` 需要配合 `inject_attachment_context` 中间件。
|
||||
当前常见能力包括:
|
||||
|
||||
### 配置文件
|
||||
| capability | 说明 |
|
||||
| --- | --- |
|
||||
| `file_upload` | 启用上传入口 |
|
||||
| `files` | 启用文件面板 |
|
||||
| `todo` | 启用待办能力 |
|
||||
|
||||
可以通过 `metadata.toml` 定义智能体的元数据:
|
||||
它解决的是“页面上显示什么”,而不是“运行时如何配置模型和工具”。
|
||||
|
||||
```toml
|
||||
name = "我的智能体"
|
||||
description = "这是一个示例智能体"
|
||||
examples = [
|
||||
"帮我写一首诗",
|
||||
"解释一下量子计算",
|
||||
]
|
||||
```
|
||||
## 6. 开发建议
|
||||
|
||||
这些信息会在前端界面展示,帮助用户了解每个智能体的用途。
|
||||
### 6.1 新增配置时优先改 Context
|
||||
|
||||
## 相关主题
|
||||
如果一个配置项会影响 Agent 行为,优先考虑把它做成 `context_schema` 字段,而不是前端单独维护状态。
|
||||
|
||||
- [上下文配置](./context-config.md) - BaseContext 和自定义配置
|
||||
- [工具系统](./tools-system.md) - 工具获取机制和 Skills 集成
|
||||
- [中间件系统](./middleware.md) - 中间件开发与使用
|
||||
- [MCP 集成](./mcp-integration.md) - MCP 服务器配置
|
||||
- [SubAgents 管理](./subagents-management.md) - 子智能体配置、调用链和开发注意事项
|
||||
### 6.2 把 Graph 逻辑和配置逻辑分开
|
||||
|
||||
## 开发建议
|
||||
推荐做法:
|
||||
|
||||
### 代码组织
|
||||
- `context.py` 定义配置模型
|
||||
- `graph.py` 使用这些配置构建 Graph
|
||||
|
||||
- 将智能体的核心逻辑放在 `graph.py` 中
|
||||
- 复杂的工具逻辑单独放在 `toolkits` 目录下
|
||||
- 共享的组件放在 `common` 目录下
|
||||
这样前后端联动关系会清晰很多。
|
||||
|
||||
### 热重载
|
||||
### 6.3 把“配置来源”和“运行时状态”区分开
|
||||
|
||||
在容器环境中,修改代码后会自动触发热重载。如果需要强制刷新,可以调用:
|
||||
建议始终区分两层语义:
|
||||
|
||||
```python
|
||||
agent_manager.get_agent(<agent_id>, reload=True)
|
||||
```
|
||||
- `config_json.context`:持久化配置来源
|
||||
- `runtime.context`:实际运行对象,可能被中间件继续补充或修改
|
||||
|
||||
### 调试技巧
|
||||
## 7. 相关主题
|
||||
|
||||
1. 使用前端的「调试面板」查看详细的请求和响应
|
||||
2. 查看后端日志:`docker logs api-dev -f`
|
||||
3. 利用 LangGraph 的可视化能力理解图结构
|
||||
|
||||
---
|
||||
|
||||
智能体系统的设计目标是让开发者能够快速构建和迭代 AI 应用。通过本文档介绍的概念和示例,你应该能够掌握创建自定义智能体的核心方法。遇到问题时,建议先参考预置智能体的实现,它们涵盖了大多数常见场景。
|
||||
- [工具系统](./tools-system.md)
|
||||
- [中间件](./middleware.md)
|
||||
- [沙盒架构与设计](./sandbox-architecture.md)
|
||||
- [MCP 集成](./mcp-integration.md)
|
||||
- [Skills 管理](./skills-management.md)
|
||||
- [SubAgents 管理](./subagents-management.md)
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
# 上下文配置
|
||||
|
||||
`BaseContext` 是智能体的配置基类,封装了常用的配置字段,定义了智能体的运行时行为。
|
||||
|
||||
## BaseContext 详解
|
||||
|
||||
```python
|
||||
from yuxi.agents import BaseContext
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class MyAgentContext(BaseContext):
|
||||
# 继承以下字段:
|
||||
# model: str - 使用的语言模型
|
||||
# system_prompt: str - 系统提示词
|
||||
# tools: list[str] - 启用的工具列表
|
||||
# knowledges: list[str] - 关联的知识库
|
||||
# mcps: list[str] - 启用的 MCP 服务器
|
||||
# skills: list[str] - 关联的 Skills
|
||||
|
||||
# 可在此添加自定义字段
|
||||
custom_field: str = "默认值"
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| model | str | 使用的语言模型 |
|
||||
| system_prompt | str | 系统提示词 |
|
||||
| tools | list[str] | 启用的内置工具列表 |
|
||||
| knowledges | list[str] | 关联的知识库 |
|
||||
| mcps | list[str] | 启用的 MCP 服务器 |
|
||||
| skills | list[str] | 关联的 Skills |
|
||||
|
||||
## 自定义工具选项
|
||||
|
||||
有时需要自定义工具选项,比如 ReporterAgent 需要包含 MySQL 工具:
|
||||
|
||||
```python
|
||||
from yuxi.agents import BaseContext, get_tool_info
|
||||
from yuxi.agents.toolkits.buildin import calculator, query_knowledge_graph
|
||||
from yuxi.agents.toolkits.mysql import get_mysql_tools
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ReporterContext(BaseContext):
|
||||
tools: Annotated[list[dict], {"__template_metadata__": {"kind": "tools"}}] = field(
|
||||
default_factory=lambda: [t.name for t in get_mysql_tools()],
|
||||
metadata={
|
||||
"name": "工具",
|
||||
"options": lambda: get_tool_info(
|
||||
[calculator, query_knowledge_graph, _create_tavily_search()] + get_mysql_tools()
|
||||
),
|
||||
"description": "包含内置工具和 MySQL 工具包。",
|
||||
},
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
self.mcps = ["mcp-server-chart"] # 默认启用图表 MCP
|
||||
```
|
||||
569
docs/agents/sandbox-architecture.md
Normal file
569
docs/agents/sandbox-architecture.md
Normal file
@ -0,0 +1,569 @@
|
||||
# Yuxi 沙盒架构与设计
|
||||
|
||||
## 文档说明
|
||||
|
||||
本文描述的是 **Yuxi 当前已经落地的沙盒实现**,目标是解释它的职责边界、系统结构、运行机制与工程限制。
|
||||
|
||||
这不是理想化方案说明,也不是 API 参考手册。阅读本文时,应始终基于一个前提:
|
||||
|
||||
> Yuxi 当前的沙盒,定位是“为线程级 Agent 运行与文件访问提供受控工作区”,而不是“对外承诺强安全边界的通用多租户沙箱平台”。
|
||||
|
||||
::: warning 预发布
|
||||
此部分涉及的技术路线与实现细节仍可能调整,文档会随实现演进而更新。
|
||||
:::
|
||||
|
||||
## 1. 设计目标与边界
|
||||
|
||||
### 1.1 设计目标
|
||||
|
||||
Yuxi 的沙盒主要服务于以下场景:
|
||||
|
||||
- 为每个对话线程提供隔离的工作目录
|
||||
- 为 Agent 提供有限的命令执行能力
|
||||
- 为 Agent 文件工具提供受控文件系统访问
|
||||
- 为工作台文件浏览器提供真实目录浏览与文件读取能力
|
||||
- 将 Skills 以只读形式暴露给 Agent 与工作台
|
||||
|
||||
### 1.2 非目标
|
||||
|
||||
当前实现明确不覆盖以下目标:
|
||||
|
||||
- 强对抗场景下的高强度多租户隔离
|
||||
- 完整的资源配额与调度系统
|
||||
- 持久化的沙盒控制平面
|
||||
- 审计级命令记录与合规追踪
|
||||
- 面向终端用户的完整文件管理系统
|
||||
|
||||
这一点不是缺陷描述,而是范围定义。很多工程选择都建立在这个前提上。
|
||||
|
||||
## 2. 总体设计结论
|
||||
|
||||
当前沙盒方案可以概括为五个关键词:
|
||||
|
||||
- **线程级隔离**:资源归属以 `thread_id` 为核心,采用 `thread_id -> sandbox_id` 的映射
|
||||
- **容器级执行**:默认使用独立 Docker 容器承载执行环境
|
||||
- **固定命名空间**:Agent 与工作台通过 `/mnt/user-data`、`/mnt/skills` 访问文件系统
|
||||
- **惰性获取**:不再依赖公开的 `/api/sandbox/*` 生命周期接口,首次使用时自动获取沙盒
|
||||
- **双视图接入**:Agent 与工作台共享底层沙盒,但使用不同的文件系统语义与接口
|
||||
|
||||
这意味着 Yuxi 当前沙盒的本质是:
|
||||
|
||||
- 一个线程隔离的容器工作区
|
||||
- 一个受限的文件访问模型
|
||||
- 一个围绕 Agent 与 Viewer 场景设计的工程化执行环境
|
||||
|
||||
## 3. 系统架构
|
||||
|
||||
### 3.1 架构分层
|
||||
|
||||
从职责上,当前实现可以分为四层:
|
||||
|
||||
1. **Provisioner 层**
|
||||
负责创建、发现、复用和销毁沙盒实例。
|
||||
|
||||
2. **Executor 层**
|
||||
负责在已经存在的沙盒中执行命令、读写文件、列目录、下载文件。
|
||||
|
||||
3. **接入层**
|
||||
负责把沙盒能力接入 Agent 文件系统与工作台文件浏览器。
|
||||
|
||||
4. **HTTP/UI 层**
|
||||
负责将文件浏览能力暴露给前端页面。
|
||||
|
||||
### 3.2 关键模块
|
||||
|
||||
核心实现主要位于以下位置:
|
||||
|
||||
- `backend/package/yuxi/agents/backends/sandbox/`
|
||||
- `backend/package/yuxi/agents/backends/composite.py`
|
||||
- `backend/package/yuxi/services/filesystem_service.py`
|
||||
- `backend/package/yuxi/services/viewer_filesystem_service.py`
|
||||
- `backend/server/routers/filesystem_router.py`
|
||||
|
||||
### 3.3 调用关系
|
||||
|
||||
```text
|
||||
Agent / Frontend
|
||||
|
|
||||
+-- Agent filesystem tool
|
||||
| |
|
||||
| +-- create_agent_composite_backend(...)
|
||||
| |
|
||||
| +-- default: sandbox backend
|
||||
| +-- route /mnt/skills/: readonly skills backend
|
||||
|
|
||||
+-- Viewer filesystem panel
|
||||
|
|
||||
+-- /api/viewer/filesystem/*
|
||||
|
|
||||
+-- viewer_filesystem_service
|
||||
|
|
||||
+-- thread ownership check
|
||||
+-- runtime context / selected skills
|
||||
+-- provider.acquire(thread_id)
|
||||
|
||||
Sandbox Provider
|
||||
|
|
||||
+-- LocalContainerBackend
|
||||
| |
|
||||
| +-- create/discover/destroy docker container
|
||||
| +-- YuxiSandboxBackend executes inside container
|
||||
|
|
||||
+-- RemoteSandboxBackend
|
||||
|
|
||||
+-- delegate lifecycle to remote provisioner
|
||||
```
|
||||
|
||||
## 4. 核心抽象与职责划分
|
||||
|
||||
这一部分是理解整套实现的关键。当前代码中有多个名字相近的 backend,但职责并不相同。
|
||||
|
||||
### 4.1 `SandboxBackend`
|
||||
|
||||
定义于 `sandbox_provisioner_base.py`,是生命周期管理接口,负责回答四个问题:
|
||||
|
||||
- 如何创建沙盒
|
||||
- 如何销毁沙盒
|
||||
- 沙盒是否存活
|
||||
- 是否可以发现已有沙盒
|
||||
|
||||
它只关心 **实例生命周期**,不关心文件访问和命令执行。
|
||||
|
||||
### 4.2 `LocalContainerBackend`
|
||||
|
||||
定义于 `sandbox_local_container.py`,是默认实现,负责:
|
||||
|
||||
- 使用 `docker run` 创建本地沙盒容器
|
||||
- 使用 `docker stop` 销毁容器
|
||||
- 发现现存的 `yuxi-sandbox-*` 容器
|
||||
- 在本地模式下管理端口、容器命名与 warm pool
|
||||
|
||||
它的定位是 **本地容器管理器**。
|
||||
|
||||
### 4.3 `RemoteSandboxBackend`
|
||||
|
||||
定义于 `sandbox_remote.py`,负责通过 HTTP 与远程 provisioner 交互。
|
||||
|
||||
它解决的问题是:如果运行环境不适合由当前进程直接管理 Docker 容器,就把生命周期操作交给外部服务。
|
||||
|
||||
它的定位是 **远程生命周期代理**。
|
||||
|
||||
### 4.4 `YuxiSandboxBackend`
|
||||
|
||||
定义于 `sandbox_executor.py`,继承 deepagents 的 `BaseSandbox`,负责:
|
||||
|
||||
- 在容器内执行 shell 命令
|
||||
- 读写和编辑文件
|
||||
- 列目录、glob、上传、下载
|
||||
- 进行路径标准化与输出遮蔽
|
||||
|
||||
它不负责创建和销毁容器。它工作的前提是:沙盒实例已经存在。
|
||||
|
||||
它的定位是 **执行器**,不是生命周期管理器。
|
||||
|
||||
### 4.5 `YuxiSandboxProvider`
|
||||
|
||||
定义于 `sandbox_provisioner.py`,是实际的协调者,负责:
|
||||
|
||||
- 维护 `thread_id -> sandbox_id` 的内存态映射
|
||||
- 首次访问时创建或发现沙盒
|
||||
- 重复访问时复用已有沙盒
|
||||
- `release()` 后放入 warm pool
|
||||
- 在超时或进程退出时回收资源
|
||||
|
||||
它的定位是 **统一入口与资源调度器**。
|
||||
|
||||
## 5. 运行模式与部署拓扑
|
||||
|
||||
### 5.1 默认模式:本地 Docker 容器
|
||||
|
||||
在默认开发环境中,系统拓扑如下:
|
||||
|
||||
```text
|
||||
宿主机
|
||||
├── Docker Daemon
|
||||
│ ├── api-dev
|
||||
│ ├── web-dev
|
||||
│ └── yuxi-sandbox-<sandbox_id>
|
||||
└── project workspace
|
||||
|
||||
api-dev 容器
|
||||
└── Yuxi 后端进程
|
||||
└── 通过 docker CLI 或 Docker API 管理 yuxi-sandbox-* 容器
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
- `api-dev` 不直接执行用户命令
|
||||
- 用户命令实际运行在独立的 `yuxi-sandbox-*` 容器中
|
||||
- 后端承担的是控制面职责,而不是执行面职责
|
||||
|
||||
### 5.2 远程 provisioner 模式
|
||||
|
||||
如果设置了 `YUXI_SANDBOX_PROVISIONER_URL`,provider 会切换为 `RemoteSandboxBackend`。
|
||||
|
||||
此时:
|
||||
|
||||
- 当前进程不直接创建本地沙盒容器
|
||||
- 生命周期由远程服务负责
|
||||
- 本地主路径不再是 Docker 容器管理,而是 HTTP 协调
|
||||
|
||||
当前主路径与主要测试覆盖仍以本地容器模式为主。
|
||||
|
||||
### 5.3 Docker CLI 与 Docker API 双路径
|
||||
|
||||
当前实现支持两种与 Docker 交互的方式:
|
||||
|
||||
- 优先使用 Docker CLI
|
||||
- 当环境中不可用时,回退到 Docker Unix Socket API
|
||||
|
||||
这是一种工程兼容设计,目的是提升不同运行环境下的可用性。
|
||||
|
||||
## 6. 生命周期模型
|
||||
|
||||
### 6.1 线程级资源归属
|
||||
|
||||
当前资源归属模型是:
|
||||
|
||||
```text
|
||||
thread_id -> deterministic sandbox_id -> sandbox instance
|
||||
```
|
||||
|
||||
`sandbox_id` 由 `thread_id` 的 SHA256 截断生成。这样做有两个直接收益:
|
||||
|
||||
- 同一个线程重复访问时更容易复用沙盒
|
||||
- provider 重启后可以通过 `discover(sandbox_id)` 找回仍然存活的容器
|
||||
|
||||
### 6.2 `acquire()` 逻辑
|
||||
|
||||
`provider.acquire(thread_id)` 的核心流程如下:
|
||||
|
||||
1. 检查当前线程是否已绑定活跃沙盒
|
||||
2. 若无,检查 warm pool 中是否存在对应 `sandbox_id`
|
||||
3. 若无,尝试 `discover(sandbox_id)` 发现已有容器
|
||||
4. 若仍不存在,则创建新沙盒
|
||||
5. 将结果注册到 provider 的内存态映射中
|
||||
|
||||
这套流程兼顾了:
|
||||
|
||||
- 同线程复用
|
||||
- provider 重建后的容器发现
|
||||
- 空闲沙盒快速回收后的再利用
|
||||
|
||||
### 6.3 `release()` 与 `destroy()`
|
||||
|
||||
两者语义不同:
|
||||
|
||||
- `release(thread_id)`:解除线程绑定,把实例放入 warm pool,等待后续复用或超时清理
|
||||
- `destroy(thread_id)`:直接销毁对应实例,不再保留
|
||||
|
||||
这一区分是为了兼顾响应速度和资源回收。
|
||||
|
||||
### 6.4 空闲回收
|
||||
|
||||
provider 内部有空闲检查线程,按固定周期清理:
|
||||
|
||||
- 长时间无访问的活跃沙盒
|
||||
- 已经进入 warm pool 且超时的沙盒
|
||||
|
||||
相关默认值定义在 `sandbox_config.py`:
|
||||
|
||||
- `DEFAULT_IDLE_TIMEOUT = 600`
|
||||
- `IDLE_CHECK_INTERVAL = 60`
|
||||
|
||||
### 6.5 为什么不再依赖 `/api/sandbox/*`
|
||||
|
||||
当前设计不再暴露公开的沙盒生命周期 API,原因很明确:
|
||||
|
||||
- 生命周期属于内部资源管理逻辑
|
||||
- 前端不应承担“先准备沙盒、再访问文件”的协调责任
|
||||
- 惰性获取可以减少状态同步与接口耦合
|
||||
|
||||
因此当前模式是:
|
||||
|
||||
- 首次访问文件系统时自动 `acquire()`
|
||||
- 不再要求前端显式调用沙盒准备接口
|
||||
|
||||
## 7. 文件系统模型
|
||||
|
||||
### 7.1 虚拟命名空间
|
||||
|
||||
当前沙盒对外暴露两个核心命名空间:
|
||||
|
||||
- `/mnt/user-data`
|
||||
- `/mnt/skills`
|
||||
|
||||
其中:
|
||||
|
||||
- `/mnt/user-data` 是线程私有工作区
|
||||
- `/mnt/skills` 是可见 Skills 的只读视图
|
||||
|
||||
### 7.2 `user-data` 目录结构
|
||||
|
||||
每个线程会在宿主机侧创建自己的 `user-data` 根目录,并确保以下子目录存在:
|
||||
|
||||
- `workspace`
|
||||
- `outputs`
|
||||
- `uploads`
|
||||
- `large_tool_results`
|
||||
- `uploads/attachments`
|
||||
|
||||
这套目录最终映射到容器内的 `/mnt/user-data/*`。
|
||||
|
||||
### 7.3 路径别名
|
||||
|
||||
为了兼容 Agent 使用习惯,当前实现保留了若干路径别名,会被统一映射到 `/mnt` 命名空间,例如:
|
||||
|
||||
- `/workspace` -> `/mnt/user-data/workspace`
|
||||
- `/outputs` -> `/mnt/user-data/outputs`
|
||||
- `/uploads` -> `/mnt/user-data/uploads`
|
||||
- `/attachments` -> `/mnt/user-data/uploads/attachments`
|
||||
- `/skills` -> `/mnt/skills`
|
||||
|
||||
这些别名是兼容层,不是推荐的长期抽象。新的系统语义应以 `/mnt/*` 为准。
|
||||
|
||||
### 7.4 路径安全约束
|
||||
|
||||
路径校验主要由 `path_security.py` 负责。核心规则如下:
|
||||
|
||||
- 仅允许访问 `/mnt/user-data` 与 `/mnt/skills`
|
||||
- 禁止路径穿越
|
||||
- 命令中的绝对路径只允许 `/mnt/*` 与少量系统运行时前缀
|
||||
|
||||
允许的系统前缀主要包括:
|
||||
|
||||
- `/bin/`
|
||||
- `/usr/bin/`
|
||||
- `/usr/local/bin/`
|
||||
- `/usr/lib/`
|
||||
|
||||
这类放行是为了保证容器内基础命令与运行时依赖可用。
|
||||
|
||||
### 7.5 输出遮蔽
|
||||
|
||||
执行结果在返回前会做宿主机路径遮蔽,避免把宿主机真实路径直接暴露给 Agent 或前端。
|
||||
|
||||
这一步不是强安全措施,但它对保持虚拟路径语义一致非常重要。
|
||||
|
||||
## 8. Agent 与 Viewer 的双视图设计
|
||||
|
||||
### 8.1 为什么需要双视图
|
||||
|
||||
当前文件系统接入并不是一套接口复用到底,而是明确拆成两种语义:
|
||||
|
||||
- **Agent 视图**
|
||||
面向工具调用与 prompt 约束,强调“模型可以怎样访问文件”
|
||||
|
||||
- **Viewer 视图**
|
||||
面向工作台浏览、读取和下载,强调“用户可以怎样查看文件”
|
||||
|
||||
这不是重复建设,而是避免语义错位。
|
||||
|
||||
### 8.2 Agent 侧接入
|
||||
|
||||
Agent 侧通过 `create_agent_composite_backend(...)` 构建 composite backend:
|
||||
|
||||
- 默认 backend 为 sandbox backend
|
||||
- `/mnt/skills/` 路由到只读的 `SelectedSkillsReadonlyBackend`
|
||||
|
||||
这保证了:
|
||||
|
||||
- 线程工作区来自沙盒
|
||||
- Skills 只读且受当前 agent 配置约束
|
||||
|
||||
### 8.3 Viewer 侧接入
|
||||
|
||||
工作台不再直接复用 Agent backend,而是通过 `viewer_filesystem_service.py` 暴露独立能力:
|
||||
|
||||
- `/api/viewer/filesystem/tree`
|
||||
- `/api/viewer/filesystem/file`
|
||||
- `/api/viewer/filesystem/download`
|
||||
|
||||
它的特点是:
|
||||
|
||||
- 只暴露浏览、读取、下载语义
|
||||
- 根目录视图明确展示 `user-data` 与 `skills`
|
||||
- Skills 可见范围仍受当前 agent config 约束
|
||||
|
||||
### 8.4 旧 `/api/filesystem/*` 的定位
|
||||
|
||||
当前仍保留旧的 Agent 文件系统接口:
|
||||
|
||||
- `/api/filesystem/ls`
|
||||
- `/api/filesystem/cat`
|
||||
|
||||
它们的定位是:
|
||||
|
||||
- 服务于 Agent 语义下的文件浏览
|
||||
- 使用 composite backend
|
||||
- 保持与 Agent 可见文件系统一致
|
||||
|
||||
因此,不应把它视为工作台浏览器的长期接口。
|
||||
|
||||
## 9. Skills 的挂载与可见性
|
||||
|
||||
Skills 不是简单的宿主机目录透传,而是受运行时上下文控制的只读视图。
|
||||
|
||||
当前模型有两个关键点:
|
||||
|
||||
- Skills 物理上以只读挂载方式进入沙盒
|
||||
- 逻辑上只有当前 agent config 选中的 skills 对 Agent 和 Viewer 可见
|
||||
|
||||
这意味着:
|
||||
|
||||
- “挂载存在”不等于“对当前线程可见”
|
||||
- Viewer 与 Agent 必须共享同一套可见性规则
|
||||
|
||||
这是当前设计中很重要的一条一致性约束。
|
||||
|
||||
## 10. 典型执行流程
|
||||
|
||||
### 10.1 Agent 首次访问文件系统
|
||||
|
||||
```text
|
||||
Agent tool call
|
||||
-> resolve_sandbox_backend(thread_id)
|
||||
-> provider.acquire(thread_id)
|
||||
-> create or discover sandbox
|
||||
-> build composite backend
|
||||
-> execute ls/read/write/command inside sandbox
|
||||
```
|
||||
|
||||
### 10.2 工作台首次打开文件浏览器
|
||||
|
||||
```text
|
||||
Viewer request
|
||||
-> /api/viewer/filesystem/tree
|
||||
-> verify thread ownership
|
||||
-> load runtime context and selected skills
|
||||
-> provider.acquire(thread_id)
|
||||
-> list sandbox or skills namespace
|
||||
```
|
||||
|
||||
### 10.3 读取与下载文件
|
||||
|
||||
读取和下载都遵循同样的原则:
|
||||
|
||||
- `user-data` 路径走 sandbox backend
|
||||
- `skills` 路径走只读 skills backend
|
||||
- 非 `/mnt` 命名空间路径会被拒绝
|
||||
|
||||
## 11. 配置项
|
||||
|
||||
沙盒核心配置定义于 `sandbox_config.py`。常用项如下:
|
||||
|
||||
| 配置项 | 作用 |
|
||||
| --- | --- |
|
||||
| `YUXI_SANDBOX_IMAGE` | 沙盒镜像 |
|
||||
| `YUXI_SANDBOX_BASE_PORT` | 本地沙盒基础端口 |
|
||||
| `YUXI_SANDBOX_CONTAINER_PREFIX` | 容器名前缀 |
|
||||
| `YUXI_SANDBOX_IDLE_TIMEOUT` | 空闲超时时间 |
|
||||
| `YUXI_SANDBOX_MAX_REPLICAS` | 最大并发沙盒数 |
|
||||
| `YUXI_SANDBOX_HOST` | 宿主机访问沙盒容器的地址 |
|
||||
| `YUXI_SANDBOX_PROVISIONER_URL` | 远程 provisioner 地址 |
|
||||
| `YUXI_SANDBOX_SECURITY_OPTS` | 传递给容器的安全选项 |
|
||||
| `YUXI_HOST_PROJECT_DIR` | 宿主机项目目录映射辅助配置 |
|
||||
| `YUXI_DOCKER_API_BASE` | Docker API base URL |
|
||||
| `YUXI_DOCKER_API_SOCKET` | Docker Unix Socket 路径 |
|
||||
|
||||
## 12. 测试与验证
|
||||
|
||||
当前与沙盒相关的测试主要覆盖三类问题:
|
||||
|
||||
### 12.1 后端与 provider 行为
|
||||
|
||||
- `backend/test/test_sandbox_backends.py`
|
||||
|
||||
覆盖内容包括:
|
||||
|
||||
- composite backend 与 sandbox backend 的接入关系
|
||||
- provider warm pool 与生命周期逻辑
|
||||
- remote backend 的基础校验
|
||||
- 路径规范化与异常输入处理
|
||||
|
||||
### 12.2 文件系统接口
|
||||
|
||||
- `backend/test/api/test_filesystem_router.py`
|
||||
- `backend/test/api/test_viewer_filesystem_router.py`
|
||||
|
||||
覆盖内容包括:
|
||||
|
||||
- Agent 视图与 Viewer 视图接口行为
|
||||
- 用户权限与线程归属校验
|
||||
- 目录浏览、文件读取与错误分支
|
||||
|
||||
### 12.3 端到端验证
|
||||
|
||||
- `backend/test/api/test_sandbox_e2e.py`
|
||||
- `backend/test/api/test_viewer_filesystem_e2e.py`
|
||||
|
||||
覆盖内容包括:
|
||||
|
||||
- 真实起沙盒容器
|
||||
- 在容器内执行命令
|
||||
- 验证文件写入、读取、附件复制与 viewer 行为
|
||||
|
||||
测试中会主动清理 `yuxi-sandbox-*` 容器,这本身也说明当前方案依赖真实容器资源,而不是纯内存 mock。
|
||||
|
||||
## 13. 当前限制
|
||||
|
||||
### 13.1 安全强度是工程化的,不是强安全承诺
|
||||
|
||||
当前实现通过容器隔离、路径约束、命名空间限制来降低风险,但它并不等同于强对抗场景下的高强度沙箱。
|
||||
|
||||
### 13.2 Provider 状态未持久化
|
||||
|
||||
当前 `YuxiSandboxProvider` 的核心状态保存在进程内存中,例如:
|
||||
|
||||
- `_sandboxes`
|
||||
- `_sandbox_infos`
|
||||
- `_thread_sandboxes`
|
||||
- `_warm_pool`
|
||||
|
||||
这意味着它更适合当前产品架构,而不是完整的分布式控制面设计。
|
||||
|
||||
### 13.3 Viewer 仍以只读为主
|
||||
|
||||
当前工作台文件浏览器主要提供:
|
||||
|
||||
- 浏览
|
||||
- 读取
|
||||
- 下载
|
||||
|
||||
它不是完整的文件操作终端,也不是通用文件管理器。
|
||||
|
||||
### 13.4 兼容路径仍然存在
|
||||
|
||||
`/workspace`、`/uploads`、`/attachments` 等别名仍在使用,这说明路径模型尚处于兼容收敛阶段。
|
||||
|
||||
### 13.5 本地容器模式仍有工程折中
|
||||
|
||||
例如:
|
||||
|
||||
- 依赖 Docker 运行环境
|
||||
- 兼容 Docker CLI 与 Docker API 双路径
|
||||
- 依赖本地资源条件与端口可用性
|
||||
|
||||
这些都属于工程现实,而不是抽象层面的完美设计。
|
||||
|
||||
## 14. 后续演进方向
|
||||
|
||||
后续如需继续完善,优先方向应是:
|
||||
|
||||
1. **强化控制面**
|
||||
将沙盒元数据、生命周期和回收策略从单进程状态进一步抽离。
|
||||
|
||||
2. **收敛路径模型**
|
||||
逐步减少历史别名,统一到 `/mnt/user-data` 与 `/mnt/skills`。
|
||||
|
||||
3. **强化安全边界**
|
||||
在容器运行时、权限模型、配额控制和审计能力上继续补强。
|
||||
|
||||
4. **继续区分 Agent 视图与用户视图**
|
||||
保持两类接口语义清晰,避免再次把 Viewer 语义挤回 Agent backend。
|
||||
|
||||
## 15. 总结
|
||||
|
||||
Yuxi 当前的沙盒方案,本质上是一套 **线程级容器工作区 + `/mnt` 命名空间 + provider 惰性获取 + Agent/Viewer 双视图接入模型**。
|
||||
|
||||
它已经能够较好支撑当前产品中的 Agent 文件工具、工作台文件浏览和 Skills 只读挂载场景,但它依然是 **面向当前业务目标的工程化沙盒**,不是已经完成强安全、强控制面建设的通用沙箱平台。
|
||||
@ -1,32 +1,12 @@
|
||||
# 工具系统
|
||||
|
||||
Yuxi 提供了统一的工具获取机制,支持多种工具类型的动态组装。
|
||||
|
||||
## 工具获取机制
|
||||
|
||||
系统提供统一的工具获取入口 `get_tools_from_context(context)`,它会自动组装三类工具:
|
||||
|
||||
1. **基础工具**:从 `context.tools` 筛选的内置工具
|
||||
2. **知识库工具**:根据 `context.knowledges` 自动生成检索工具
|
||||
3. **MCP 工具**:根据 `context.mcps` 加载并过滤的 MCP 服务器工具
|
||||
|
||||
```python
|
||||
from yuxi.agents.tools import get_tools_from_context
|
||||
|
||||
async def get_graph(self, **kwargs):
|
||||
context = self.get_context()
|
||||
tools = await get_tools_from_context(context)
|
||||
```
|
||||
Yuxi 的工具系统基于注册机制,支持多种工具类型的动态组装。
|
||||
|
||||
## 工具注册机制
|
||||
|
||||
Yuxi 的工具系统基于注册机制而非继承体系,这一点与 LangChain 原生的 `@tool` 装饰器有本质区别。
|
||||
Yuxi 的工具系统采用 `@tool` 装饰器注册机制,核心位于 `backend/package/yuxi/agents/toolkits/registry.py`。
|
||||
|
||||
LangChain 的 `@tool` 装饰器通常需要继承特定基类或实现特定接口,创建的工具有着强烈的框架耦合。而 Yuxi 的工具注册表是一个独立的全局注册中心,任何符合规范的函数都可以通过 `@tool` 装饰器注册到系统中,无需继承任何基类,也不需要了解框架内部实现。
|
||||
|
||||
需要特别说明的是,Yuxi 的 `@tool` 装饰器并非全新实现,而是基于 LangChain 原生 `@tool` 的扩展。装饰器的核心逻辑继承自 LangChain,新增了 `category`、`tags`、`display_name` 等元数据字段用于前端展示和分类,原有的 LangChain 特性(如函数参数注解、描述文档等)完全兼容。
|
||||
|
||||
注册表的核心位于 `backend/package/yuxi/agents/common/toolkits/registry.py`,它维护着一个全局的工具实例列表。当系统启动时,所有导入 `toolkits` 包的模块都会自动执行其内部的工具注册逻辑,这意味着开发者只需要在自己的模块中添加装饰器,工具就会自动被发现和使用。
|
||||
### @tool 装饰器
|
||||
|
||||
```python
|
||||
from yuxi.agents.toolkits.registry import tool
|
||||
@ -34,29 +14,86 @@ from yuxi.agents.toolkits.registry import tool
|
||||
@tool(category="buildin", tags=["计算"], display_name="计算器")
|
||||
def calculator(a: float, b: float, operation: str) -> float:
|
||||
"""计算器:对给定的2个数字进行基本数学运算"""
|
||||
if operation == "add":
|
||||
return a + b
|
||||
# ...
|
||||
...
|
||||
```
|
||||
|
||||
使用这个装饰器时,需要指定 `category` 和 `tags`,前者用于工具分类,后者用于前端展示。装饰器内部仍然调用 LangChain 的工具封装逻辑,因此 LangChain 工具的所有特性(如多参数支持、参数类型注解等)都保持兼容。
|
||||
装饰器参数:
|
||||
- **category**: 工具分类,用于分组(`buildin`、`mysql`、`debug`)
|
||||
- **tags**: 标签列表,用于前端展示
|
||||
- **display_name**: 显示名称(给人看的名字)
|
||||
- **icon**: 图标名称(可选)
|
||||
|
||||
获取工具时,通过 `get_all_tool_instances()` 可以拿到所有已注册的工具实例列表,这个函数会被 `get_tools_from_context` 调用,根据上下文配置筛选出需要使用的工具。
|
||||
### 自动发现
|
||||
|
||||
## 内置工具
|
||||
导入 `toolkits` 包时会自动触发注册:
|
||||
|
||||
系统内置了几类常用工具。计算类包括 calculator,可进行加减乘除运算。搜索类包括 tavily_search,需要在环境变量中配置 `TAVILY_API_KEY` 才能启用。知识图谱类包括 query_knowledge_graph,用于查询通过三元组导入的全局知识图谱。交互类包括 ask_user_question,用于在智能体执行过程中向用户发起交互式提问,当前协议支持一次提交 `questions` 数组并返回 `{question_id: answer}` 的批量答案映射。数据库类包括 mysql_list_tables、mysql_describe_table 和 mysql_query,用于连接和查询 MySQL 数据库。
|
||||
```python
|
||||
from yuxi.agents.toolkits import buildin, mysql # 触发 @tool 装饰器执行
|
||||
```
|
||||
|
||||
这些工具都通过上述注册机制自动加载,开发者无需手动引入。
|
||||
`toolkits/__init__.py` 中已包含 `buildin`、`mysql`、`debug` 模块的导入,这些模块加载时会自动注册所有带 `@tool` 装饰器的函数。
|
||||
|
||||
## 知识库工具
|
||||
## 工具分类
|
||||
|
||||
与内置工具不同,知识库工具是动态生成的。当在智能体配置中指定 `context.knowledges` 时,系统会根据指定的 knowledge 名称动态创建对应的检索工具。这种设计使得知识库工具不需要预先注册,而是在运行时按需生成。
|
||||
### 内置工具 (buildin)
|
||||
|
||||
| 工具 | 说明 |
|
||||
|------|------|
|
||||
| `calculator` | 计算器,支持加减乘除 |
|
||||
| `ask_user_question` | 向用户发起交互式提问 |
|
||||
| `query_knowledge_graph` | 查询知识图谱三元组 |
|
||||
| `text_to_img_qwen_image` | 使用 Qwen-Image 生成图片 |
|
||||
| `tavily_search` | Tavily 网页搜索(需配置 `TAVILY_API_KEY`) |
|
||||
|
||||
### MySQL 工具 (mysql)
|
||||
|
||||
| 工具 | 说明 |
|
||||
|------|------|
|
||||
| `mysql_list_tables` | 列出数据库中所有表 |
|
||||
| `mysql_describe_table` | 获取表结构信息 |
|
||||
| `mysql_query` | 执行只读 SQL 查询 |
|
||||
|
||||
### 知识库工具 (kbs)
|
||||
|
||||
知识库工具通过 `get_common_kb_tools()` 获取,不通过 `@tool` 装饰器注册:
|
||||
|
||||
```python
|
||||
from yuxi.agents.toolkits.kbs import get_common_kb_tools
|
||||
|
||||
kb_tools = get_common_kb_tools(knowledge_names=["kb1", "kb2"])
|
||||
kb_tools = get_common_kb_tools()
|
||||
# 返回: [list_kbs, get_mindmap, query_kb]
|
||||
```
|
||||
|
||||
| 工具 | 说明 |
|
||||
|------|------|
|
||||
| `list_kbs` | 列出用户可访问的知识库 |
|
||||
| `get_mindmap` | 获取知识库的思维导图结构 |
|
||||
| `query_kb` | 在指定知识库中检索内容 |
|
||||
|
||||
## 工具组装
|
||||
|
||||
工具组装在 `RuntimeConfigMiddleware` 中完成。根据上下文配置筛选工具:
|
||||
|
||||
1. **基础工具**:从 `context.tools` 中按名称筛选
|
||||
2. **MCP 工具**:根据 `context.mcps` 加载 MCP 服务器工具
|
||||
3. **知识库工具**:由 `KnowledgeBaseMiddleware` 独立处理
|
||||
|
||||
```python
|
||||
# 中间件中的工具筛选逻辑
|
||||
async def get_tools_from_context(self, context) -> list:
|
||||
selected_tools = []
|
||||
|
||||
# 1. 基础工具
|
||||
for tool_name in context.tools or []:
|
||||
if tool_name in tools_map:
|
||||
selected_tools.append(tools_map[tool_name])
|
||||
|
||||
# 2. MCP 工具
|
||||
for server_name in context.mcps or []:
|
||||
mcp_tools = await get_enabled_mcp_tools(server_name)
|
||||
selected_tools.extend(mcp_tools)
|
||||
|
||||
return selected_tools
|
||||
```
|
||||
|
||||
## Skills 集成
|
||||
|
||||
@ -1,120 +0,0 @@
|
||||
# 参与贡献
|
||||
|
||||
感谢你对 Yuxi 项目的兴趣!我们欢迎任何形式的贡献,包括但不限于代码提交、功能建议、问题反馈和文档改进。
|
||||
|
||||
<a href="https://github.com/xerrors/Yuxi-Know/contributors">
|
||||
<img src="https://contributors.nn.ci/api?repo=xerrors/Yuxi-Know" alt="贡献者名单">
|
||||
</a>
|
||||
|
||||
## 贡献流程
|
||||
|
||||
### 1. Fork 项目
|
||||
|
||||
在 GitHub 上点击 Fork 按钮,将项目复制到你的账户。
|
||||
|
||||
### 2. 创建功能分支
|
||||
|
||||
```bash
|
||||
git checkout -b feature/amazing-feature
|
||||
```
|
||||
|
||||
### 3. 开发并提交
|
||||
|
||||
```bash
|
||||
git commit -m 'feat: 添加新功能'
|
||||
```
|
||||
|
||||
### 4. 推送代码
|
||||
|
||||
```bash
|
||||
git push origin feature/amazing-feature
|
||||
```
|
||||
|
||||
### 5. 创建 Pull Request
|
||||
|
||||
在 GitHub 上创建 PR,详细描述你的更改内容和动机。
|
||||
|
||||
## 代码规范
|
||||
|
||||
项目对代码质量有一定要求,提交前请确保:
|
||||
|
||||
- Python 代码使用 `make format` 格式化
|
||||
- 使用 `make lint` 检查代码质量
|
||||
- 添加必要的测试用例
|
||||
- 更新相关文档
|
||||
|
||||
## 提交信息规范
|
||||
|
||||
使用清晰规范的提交信息:
|
||||
|
||||
```
|
||||
feat: 添加新功能
|
||||
fix: 修复 bug
|
||||
docs: 更新文档
|
||||
style: 代码格式调整
|
||||
refactor: 代码重构
|
||||
test: 添加测试
|
||||
chore: 构建过程或辅助工具的变动
|
||||
```
|
||||
|
||||
## Bug 修复发布流程
|
||||
|
||||
当发布后发现 bug 需要修复时:
|
||||
|
||||
### 情况 1:main 上没有未完成的新功能
|
||||
|
||||
直接在 main 修复并发布:
|
||||
|
||||
```bash
|
||||
git commit -m "fix: 解决配置解析器崩溃问题"
|
||||
git tag -a v0.3.1 -m "Hotfix v0.3.1"
|
||||
git push origin main --tags
|
||||
```
|
||||
|
||||
### 情况 2:main 上已有新功能未完成
|
||||
|
||||
从上一个 tag 建立 hotfix 分支:
|
||||
|
||||
```bash
|
||||
git checkout -b hotfix/0.3.1 v0.3.0
|
||||
# 修复问题
|
||||
git commit -m "fix: 解决配置解析器崩溃问题"
|
||||
git push origin hotfix/0.3.1
|
||||
|
||||
# 测试后合并回 main 并打 tag
|
||||
git checkout main
|
||||
git merge --no-ff hotfix/0.3.1
|
||||
git tag -a v0.3.1 -m "Hotfix v0.3.1"
|
||||
git push origin main --tags
|
||||
|
||||
# 删除临时分支
|
||||
git branch -d hotfix/0.3.1
|
||||
git push origin --delete hotfix/0.3.1
|
||||
```
|
||||
|
||||
## 测试指南
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 全量路由测试
|
||||
make router-tests
|
||||
|
||||
# 运行特定测试
|
||||
make router-tests PYTEST_ARGS="-k knowledge_router"
|
||||
|
||||
# 直接运行 pytest
|
||||
uv run --group test pytest test/api -vv
|
||||
```
|
||||
|
||||
### 测试配置
|
||||
|
||||
首次运行测试前,需要配置测试凭据:
|
||||
|
||||
```bash
|
||||
cp test/.env.test.example test/.env.test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
感谢每一位贡献者的付出!
|
||||
@ -1,98 +0,0 @@
|
||||
# 常见问题
|
||||
|
||||
以下是 Yuxi 在安装和使用过程中最常见的问题及其解决方案。
|
||||
|
||||
## Docker 与启动问题
|
||||
|
||||
### 镜像拉取或构建失败
|
||||
|
||||
**镜像拉取问题**:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
bash scripts/pull_image.sh
|
||||
|
||||
# Windows PowerShell
|
||||
powershell -ExecutionPolicy Bypass -File scripts/pull_image.ps1
|
||||
```
|
||||
|
||||
**构建失败问题**:
|
||||
|
||||
如果配置了代理仍然失败,尝试以下步骤:
|
||||
|
||||
1. 注释 `api.Dockerfile` 中的代理配置
|
||||
2. 注释 `docker-compose.yml` 中的代理构建参数
|
||||
3. 添加国内镜像源加速:
|
||||
|
||||
```dockerfile
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --no-dev --index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
```
|
||||
|
||||
### 服务启动失败
|
||||
|
||||
1. 检查端口占用:`lsof -i :5050` 或 `netstat -tuln | grep 5050`
|
||||
2. 确认 Docker 服务状态
|
||||
3. 查看日志定位问题:
|
||||
```bash
|
||||
docker logs --tail=100 api-dev
|
||||
docker logs --tail=100 web-dev
|
||||
```
|
||||
|
||||
### 数据库服务问题
|
||||
|
||||
**Milvus / Neo4j 启动失败**:
|
||||
|
||||
```bash
|
||||
# 重启服务
|
||||
docker compose up milvus -d && docker restart api-dev
|
||||
```
|
||||
|
||||
**Neo4j 连接信息**:
|
||||
- 用户名:neo4j
|
||||
- 密码:0123456789
|
||||
- 管理界面:http://localhost:7474
|
||||
|
||||
### 账号相关问题
|
||||
|
||||
**首次运行创建管理员**:
|
||||
|
||||
Web 首次启动会引导初始化。也可以通过 API 创建:
|
||||
|
||||
```bash
|
||||
# 检查是否首次运行
|
||||
GET /api/auth/check-first-run
|
||||
|
||||
# 初始化管理员账号
|
||||
POST /api/auth/initialize
|
||||
# Body: {"user_id": "your_username", "password": "your_password"}
|
||||
```
|
||||
|
||||
### 日志查看
|
||||
|
||||
```bash
|
||||
# 查看所有容器状态
|
||||
docker ps
|
||||
|
||||
# 查看实时日志
|
||||
docker logs api-dev -f
|
||||
docker logs web-dev -f
|
||||
|
||||
# 查看所有服务日志
|
||||
docker compose logs --tail=100
|
||||
```
|
||||
|
||||
## 功能使用问题
|
||||
|
||||
### OCR 服务不可用
|
||||
|
||||
- **RapidOCR**:确保 `MODEL_DIR/SWHL/RapidOCR` 下存在 `PP-OCRv4` 模型
|
||||
- **MinerU / PP-Structure-V3**:检查 GPU 和 CUDA 版本是否兼容
|
||||
|
||||
### 登录失败被锁定
|
||||
|
||||
多次登录失败会临时锁定账户,请根据页面提示等待后重试。
|
||||
|
||||
---
|
||||
|
||||
如果以上问题无法解决你的问题,欢迎在 GitHub Issues 中提问。
|
||||
@ -1,108 +0,0 @@
|
||||
# v0.5 数据迁移指南
|
||||
|
||||
v0.5 版本将数据存储从 SQLite + JSON 文件迁移到 PostgreSQL。本指南帮助你完成数据迁移。
|
||||
|
||||
::: tip warning
|
||||
迁移脚本可能会存在问题,不建议在生产环境下尝试,生产环境下,请新建或仔细检查迁移脚本,慎重迁移。
|
||||
:::
|
||||
|
||||
## 迁移内容
|
||||
|
||||
| 数据类型 | 源 | 目标 | 存储内容 |
|
||||
|---------|-----|------|---------|
|
||||
| 业务数据 | SQLite (`saves/database/server.db`) | PostgreSQL | 用户、部门、对话、消息、工具调用、MCP 服务器等 |
|
||||
| 知识库元数据 | JSON 文件 (`saves/knowledge_base_data/`) | PostgreSQL | 知识库配置、文件信息、评估数据 |
|
||||
| Tasker 任务记录 | JSON 文件 (`saves/tasks/tasks.json`) | PostgreSQL | 后台任务状态、进度、结果(独立存储) |
|
||||
|
||||
## 迁移前准备
|
||||
|
||||
### 1. 启动服务
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### 2. 备份数据
|
||||
|
||||
**重要:** 迁移前必须备份数据!
|
||||
|
||||
```bash
|
||||
# 备份 saves 目录(包含 SQLite 数据库和知识库元数据)
|
||||
cp -r saves saves_backup_$(date +%Y%m%d)
|
||||
|
||||
# 如果使用外部数据库,也请备份 PostgreSQL
|
||||
pg_dump -U postgres -d yuxi_know > pg_backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
### 3. 确保 PostgreSQL 已启动
|
||||
|
||||
```bash
|
||||
docker compose up -d postgres
|
||||
# 等待健康检查通过
|
||||
```
|
||||
|
||||
## 执行迁移
|
||||
|
||||
### 方式一:使用统一迁移脚本(推荐)
|
||||
|
||||
```bash
|
||||
# 1. 预览迁移(不执行)
|
||||
docker compose exec api python scripts/migrate_all.py --dry-run
|
||||
|
||||
# 2. 执行迁移
|
||||
docker compose exec api python scripts/migrate_all.py --execute
|
||||
|
||||
# 3. 验证迁移结果
|
||||
docker compose exec api python scripts/migrate_all.py --verify
|
||||
```
|
||||
|
||||
### 方式二:分阶段迁移
|
||||
|
||||
```bash
|
||||
# 只迁移业务数据
|
||||
docker compose exec api python scripts/migrate_all.py --execute --stage business
|
||||
|
||||
# 只迁移知识库元数据
|
||||
docker compose exec api python scripts/migrate_all.py --execute --stage knowledge
|
||||
|
||||
# 只迁移 Tasker 任务记录
|
||||
docker compose exec api python scripts/migrate_all.py --execute --stage tasker
|
||||
```
|
||||
|
||||
## 重启服务
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 验证迁移
|
||||
|
||||
### 检查服务状态
|
||||
|
||||
```bash
|
||||
# 查看 API 服务日志
|
||||
docker logs api-dev --tail 50
|
||||
|
||||
# 检查健康状态
|
||||
curl http://localhost:5050/api/system/health
|
||||
```
|
||||
|
||||
### 验证数据
|
||||
|
||||
```bash
|
||||
# 使用迁移脚本验证
|
||||
docker compose exec api python scripts/migrate_all.py --verify
|
||||
```
|
||||
|
||||
预期输出:
|
||||
|
||||
```
|
||||
============================================================
|
||||
📊 验证结果汇总
|
||||
============================================================
|
||||
✅ departments: SQLite=X, PostgreSQL=X
|
||||
✅ users: SQLite=X, PostgreSQL=X
|
||||
✅ conversations: SQLite=X, PostgreSQL=X
|
||||
...
|
||||
全部匹配: ✅ 是
|
||||
```
|
||||
@ -1,185 +0,0 @@
|
||||
# Run 流式架构改造说明
|
||||
|
||||
## 1. 改造目标
|
||||
|
||||
本次改造将对话输出从 **HTTP 直连流** 升级为 **异步任务执行 + SSE 增量拉取**,目标是:
|
||||
|
||||
1. 页面离开/刷新不影响后台执行。
|
||||
2. 前端支持断线重连与续流。
|
||||
3. 提升系统稳定性、并发能力与可观测性。
|
||||
4. 控制存储成本:过程数据短期保存,结果数据长期保存。
|
||||
|
||||
---
|
||||
|
||||
## 2. 改造前后对比(仅与 HTTP 直连流对比)
|
||||
|
||||
| 维度 | 改造前(HTTP 直连流) | 改造后(Run + SSE) |
|
||||
|---|---|---|
|
||||
| 触发方式 | `POST /agent/{id}` 后长连接直接流式输出 | `POST /runs` 创建任务,worker 异步执行 |
|
||||
| 任务生命周期 | 绑定前端连接 | 与前端连接解耦 |
|
||||
| 页面离开/刷新 | 常导致任务中断或前端丢上下文 | 任务继续执行,前端可续流 |
|
||||
| 前端消费方式 | 同一个请求内读取 chunk | `GET /runs/{id}/events?after_seq=...` 增量拉取 |
|
||||
| 恢复能力 | 弱,重连后难恢复 | 强,依赖 seq 游标恢复 |
|
||||
| 取消语义 | 中断连接即可能影响任务 | 仅显式 `cancel` 才取消任务 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 架构方案
|
||||
|
||||
### 3.1 组件职责
|
||||
|
||||
1. **FastAPI**:负责创建 run、查询 run、SSE 输出、cancel 接口。
|
||||
2. **ARQ Worker**:负责真正执行模型流式任务。
|
||||
3. **Redis**:
|
||||
- ARQ 队列
|
||||
- run 过程事件流(Redis Stream)
|
||||
- 取消信号(key + pub/sub)
|
||||
4. **Postgres**:
|
||||
- run 执行状态(`agent_runs`)
|
||||
- 最终业务消息(`messages/tool_calls`)
|
||||
- checkpointer(会话运行状态)
|
||||
|
||||
### 3.2 架构图
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
FE["Frontend"] -->|"POST /runs"| API["FastAPI"]
|
||||
API -->|"create run"| PG[("Postgres")]
|
||||
API -->|"enqueue"| R[("Redis")]
|
||||
W["ARQ Worker"] -->|"dequeue"| R
|
||||
W -->|"update run status"| PG
|
||||
W -->|"write stream events"| R
|
||||
FE -->|"GET /runs/:id/events?after_seq=..."| API
|
||||
API -->|"read incremental events"| R
|
||||
API -->|"SSE events"| FE
|
||||
FE -->|"POST /runs/:id/cancel"| API
|
||||
API -->|"cancel mark"| PG
|
||||
API -->|"publish cancel"| R
|
||||
W -->|"persist messages/tool_calls"| PG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 端到端流程(事件流转)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant FE as Frontend
|
||||
participant API as FastAPI
|
||||
participant R as Redis
|
||||
participant W as ARQ Worker
|
||||
participant PG as Postgres
|
||||
|
||||
FE->>API: POST /api/chat/agent/{agent_id}/runs
|
||||
API->>PG: create agent_runs(status=pending)
|
||||
API->>R: enqueue process_agent_run(run_id)
|
||||
API-->>FE: run_id
|
||||
|
||||
FE->>API: GET /api/chat/runs/{run_id}/events?after_seq=0
|
||||
W->>R: dequeue run job
|
||||
W->>PG: mark running
|
||||
W->>R: append loading/tool/state events (stream)
|
||||
API->>R: read events after_seq
|
||||
API-->>FE: SSE incremental events
|
||||
|
||||
FE->>API: POST /api/chat/runs/{run_id}/cancel (optional)
|
||||
API->>PG: mark cancel_requested
|
||||
API->>R: publish cancel signal
|
||||
W->>W: cancel current task
|
||||
W->>PG: mark terminal status
|
||||
W->>PG: persist messages/tool_calls
|
||||
API-->>FE: close event
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 接口与协议变更
|
||||
|
||||
### 5.1 对外路径(保持稳定)
|
||||
|
||||
1. `POST /api/chat/agent/{agent_id}/runs`
|
||||
2. `GET /api/chat/runs/{run_id}`
|
||||
3. `GET /api/chat/runs/{run_id}/events?after_seq=...`
|
||||
4. `POST /api/chat/runs/{run_id}/cancel`
|
||||
|
||||
### 5.2 `after_seq` 语义
|
||||
|
||||
1. 主格式为字符串游标(Redis Stream ID,如 `1700000000000-3`)。
|
||||
2. 兼容旧整数参数输入。
|
||||
3. SSE 返回 `seq` 字段统一为字符串,前端按单调递增去重。
|
||||
|
||||
### 5.3 SSE 事件格式
|
||||
|
||||
```json
|
||||
{
|
||||
"run_id": "...",
|
||||
"seq": "1700000000000-3",
|
||||
"event_type": "loading",
|
||||
"payload": {"items": [...]},
|
||||
"ts": 1700000000000
|
||||
}
|
||||
```
|
||||
|
||||
控制事件:`heartbeat` / `error` / `close`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 前端行为变化
|
||||
|
||||
1. 本地记录活跃 run 快照:`active_run:{threadId}`。
|
||||
2. 刷新/切回页面时按 `run_id + last_seq` 自动续流。
|
||||
3. 接收事件先做 seq 去重,再更新 UI。
|
||||
4. 保留打字机效果(`requestAnimationFrame + throttle`)。
|
||||
5. 保留首条消息自动更新会话标题逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 7. 稳定性设计
|
||||
|
||||
1. 幂等:`request_id` 避免重复创建 run。
|
||||
2. 重试:仅可恢复错误触发 ARQ 重试(`max_tries=2`)。
|
||||
3. 取消:DB 状态 + Redis 信号双通道。
|
||||
4. SSE 生命周期:心跳、超时、终态关闭、断线重连。
|
||||
5. 状态单一真相:执行态在 `agent_runs`,业务态在 `messages/tool_calls + checkpointer`。
|
||||
|
||||
---
|
||||
|
||||
## 8. 本次代码变更范围(未提交部分)
|
||||
|
||||
### 后端
|
||||
|
||||
1. `/Yuxi-Know/backend/package/yuxi/services/run_queue_service.py`
|
||||
2. `/Yuxi-Know/backend/package/yuxi/services/run_worker.py`
|
||||
3. `/Yuxi-Know/backend/package/yuxi/services/agent_run_service.py`
|
||||
4. `/Yuxi-Know/backend/package/yuxi/repositories/agent_run_repository.py`
|
||||
5. `/Yuxi-Know/server/routers/chat_router.py`
|
||||
6. `/Yuxi-Know/server/worker_main.py`
|
||||
7. `/Yuxi-Know/backend/package/yuxi/storage/postgres/manager.py`
|
||||
8. `/Yuxi-Know/backend/package/yuxi/storage/postgres/models_business.py`
|
||||
|
||||
### 前端
|
||||
|
||||
1. `/Yuxi-Know/web/src/apis/agent_api.js`
|
||||
2. `/Yuxi-Know/web/src/components/AgentChatComponent.vue`
|
||||
|
||||
### 测试
|
||||
|
||||
1. `/Yuxi-Know/test/test_run_queue_service.py`
|
||||
2. `/Yuxi-Know/test/test_agent_run_service.py`
|
||||
3. `/Yuxi-Know/test/test_run_worker.py`
|
||||
|
||||
### 配置
|
||||
|
||||
1. `/Yuxi-Know/docker-compose.yml`
|
||||
2. `/Yuxi-Know/docker-compose.prod.yml`
|
||||
3. `/Yuxi-Know/.env.template`
|
||||
|
||||
---
|
||||
|
||||
## 9. 验收标准
|
||||
|
||||
1. 发送消息后,SSE 能持续收到增量事件。
|
||||
2. 页面刷新后,可按 `after_seq` 恢复输出。
|
||||
3. 页面离开不影响后台执行。
|
||||
4. 取消后 run 状态正确收敛,输出停止。
|
||||
5. 最终消息与工具调用正常入库。
|
||||
211
docs/develop-guides/contributing.md
Normal file
211
docs/develop-guides/contributing.md
Normal file
@ -0,0 +1,211 @@
|
||||
# 参与贡献
|
||||
|
||||
感谢你对 Yuxi-Know 的兴趣。我们欢迎 Issue、文档改进、Bug 修复、测试补充以及新功能贡献。
|
||||
|
||||
如果你只是想快速了解仓库入口信息,可以先看根目录的 [CONTRIBUTING.md](../../CONTRIBUTING.md)。
|
||||
|
||||
<a href="https://github.com/xerrors/Yuxi-Know/contributors">
|
||||
<img src="https://contributors.nn.ci/api?repo=xerrors/Yuxi-Know" alt="贡献者名单">
|
||||
</a>
|
||||
|
||||
## 开始之前
|
||||
|
||||
提交前建议先完成以下检查:
|
||||
|
||||
- 搜索已有 [Issues](https://github.com/xerrors/Yuxi-Know/issues) 和 [Discussions](https://github.com/xerrors/Yuxi-Know/discussions)
|
||||
- 对较大的功能改动,先发 Issue 讨论设计和边界
|
||||
- 保持一次 PR 只解决一个明确问题,避免把无关重构混在一起
|
||||
|
||||
## 开发原则
|
||||
|
||||
本项目默认遵循以下开发原则:
|
||||
|
||||
- 避免过度设计,只做当前需求直接需要的改动
|
||||
- 不额外添加“顺手优化”、兼容层或未来需求抽象
|
||||
- 尽量复用现有实现,保持代码简单、聚焦、可维护
|
||||
- 只在系统边界做必要校验,不为不可能发生的内部场景增加复杂度
|
||||
|
||||
## 开发环境
|
||||
|
||||
Yuxi 基于 Docker Compose 管理开发环境。开发、调试、测试都应尽量在运行中的容器中完成。
|
||||
|
||||
### 启动项目
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 常用检查命令
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
docker logs api-dev --tail 100
|
||||
```
|
||||
|
||||
`api-dev` 和 `web-dev` 默认支持热重载。通常情况下,本地修改代码后不需要重启容器。
|
||||
|
||||
如需进一步了解服务定义,可查看 [docker-compose.yml](../../docker-compose.yml)。
|
||||
|
||||
## 贡献流程
|
||||
|
||||
### 1. Fork 仓库
|
||||
|
||||
在 GitHub 上 Fork 本仓库到你的个人账户。
|
||||
|
||||
### 2. 创建分支
|
||||
|
||||
请使用语义明确的分支名,例如:
|
||||
|
||||
```bash
|
||||
git checkout -b feature/amazing-feature
|
||||
git checkout -b fix/chat-stream-interrupt
|
||||
git checkout -b docs/update-contributing-guide
|
||||
```
|
||||
|
||||
### 3. 开发与验证
|
||||
|
||||
按项目规范完成代码、测试与文档更新。开发完成后,至少完成:
|
||||
|
||||
- 检查
|
||||
- 测试
|
||||
- Lint
|
||||
- 必要的端到端验证
|
||||
|
||||
如果现有测试脚本不足以覆盖你的改动,应补充对应测试,测试脚本优先放在 `backend/test`。
|
||||
|
||||
### 4. 提交代码
|
||||
|
||||
```bash
|
||||
git commit -m "feat: add knowledge graph import flow"
|
||||
```
|
||||
|
||||
### 5. 推送并发起 Pull Request
|
||||
|
||||
```bash
|
||||
git push origin feature/amazing-feature
|
||||
```
|
||||
|
||||
创建 PR 时,请写清楚:
|
||||
|
||||
- 修改内容
|
||||
- 修改原因
|
||||
- 影响范围
|
||||
- 验证方式
|
||||
|
||||
如果涉及 UI 改动,建议附上截图或录屏。
|
||||
|
||||
## 前端贡献规范
|
||||
|
||||
前端目录位于 `web/`,提交前请遵循以下约束:
|
||||
|
||||
- 包管理器使用 `pnpm`
|
||||
- 所有 API 接口定义统一放在 `web/src/apis`
|
||||
- Icon 优先使用 `lucide-vue-next`
|
||||
- 样式使用 `less`
|
||||
- 非特殊情况必须优先复用 [web/src/assets/css/base.css](../../web/src/assets/css/base.css) 中的颜色变量
|
||||
|
||||
界面设计和样式约束可参考 [design.md](./design.md)。
|
||||
|
||||
## 后端贡献规范
|
||||
|
||||
后端目录位于 `backend/`,提交时请注意:
|
||||
|
||||
- Python 风格尽量保持 pythonic
|
||||
- 优先使用较新的语法,兼容目标为 Python 3.12+
|
||||
- 优先在容器内运行调试和测试命令
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
docker compose exec api uv run python test/your_script.py
|
||||
```
|
||||
|
||||
测试脚本建议放在 `backend/test` 下。
|
||||
|
||||
## 质量检查
|
||||
|
||||
提交前请至少完成以下检查:
|
||||
|
||||
### 格式化与静态检查
|
||||
|
||||
```bash
|
||||
make format
|
||||
make lint
|
||||
```
|
||||
|
||||
如果测试依赖管理员账户,可从项目根目录的 `.env` 中读取相关配置。
|
||||
|
||||
## 文档维护要求
|
||||
|
||||
代码改动后,请同步检查是否需要更新文档。
|
||||
|
||||
- 通用开发文档位于 `docs/`
|
||||
- 文档导航定义在 `docs/.vitepress/config.mts`
|
||||
- 若本次改动值得记录,请更新 [roadmap.md](./roadmap.md)
|
||||
- 若确需新增仅开发者可见的说明文档,放在 `docs/vibe/`
|
||||
|
||||
## 提交信息规范
|
||||
|
||||
推荐使用清晰、可检索的提交前缀:
|
||||
|
||||
```text
|
||||
feat: 添加新功能
|
||||
fix: 修复 bug
|
||||
docs: 更新文档
|
||||
refactor: 代码重构
|
||||
test: 添加测试
|
||||
chore: 构建过程或辅助工具的变动
|
||||
```
|
||||
|
||||
## Bug 修复发布流程
|
||||
|
||||
当版本发布后发现 Bug,需要按实际分支状态处理。
|
||||
|
||||
### 情况 1:`main` 上没有未完成的新功能
|
||||
|
||||
直接在 `main` 修复并发布:
|
||||
|
||||
```bash
|
||||
git commit -m "fix: resolve config parser crash"
|
||||
git tag -a v0.3.1 -m "Hotfix v0.3.1"
|
||||
git push origin main --tags
|
||||
```
|
||||
|
||||
### 情况 2:`main` 上已有未完成的新功能
|
||||
|
||||
从上一个 tag 创建 hotfix 分支:
|
||||
|
||||
```bash
|
||||
git checkout -b hotfix/0.3.1 v0.3.0
|
||||
|
||||
# 修复问题
|
||||
git commit -m "fix: resolve config parser crash"
|
||||
git push origin hotfix/0.3.1
|
||||
|
||||
# 测试后合并回 main 并打 tag
|
||||
git checkout main
|
||||
git merge --no-ff hotfix/0.3.1
|
||||
git tag -a v0.3.1 -m "Hotfix v0.3.1"
|
||||
git push origin main --tags
|
||||
|
||||
# 删除临时分支
|
||||
git branch -d hotfix/0.3.1
|
||||
git push origin --delete hotfix/0.3.1
|
||||
```
|
||||
|
||||
## 测试配置
|
||||
|
||||
首次运行部分测试前,需要准备测试环境变量:
|
||||
|
||||
```bash
|
||||
cp test/.env.test.example test/.env.test
|
||||
```
|
||||
|
||||
如果你的改动涉及认证、知识库、文件系统或智能体流程,建议补充对应集成测试,避免只覆盖单元逻辑。
|
||||
|
||||
## 反馈渠道
|
||||
|
||||
- Bug 反馈:<https://github.com/xerrors/Yuxi-Know/issues>
|
||||
- 功能讨论:<https://github.com/xerrors/Yuxi-Know/discussions>
|
||||
|
||||
感谢每一位贡献者的投入。
|
||||
@ -18,12 +18,6 @@
|
||||
- 颜色、字体、间距等遵循 [base.css](web/src/assets/css/base.css) 中的设计变量
|
||||
- 相似功能的组件使用相同的设计模式
|
||||
|
||||
### 稳定性
|
||||
|
||||
- 禁止悬停位移:交互元素在悬停时应保持位置稳定,避免视觉跳动
|
||||
- 避免过度阴影:阴影仅用于层级区分,不得用于装饰目的
|
||||
- 避免过度渐变:渐变色应谨慎使用,优先使用纯色或微弱渐变
|
||||
|
||||
## 禁止项
|
||||
|
||||
以下 UI 做法在本项目中明确禁止:
|
||||
@ -42,29 +36,10 @@
|
||||
### 技术栈
|
||||
|
||||
- 包管理器:pnpm
|
||||
- 图标库:lucide-vue-next(注意尺寸控制)
|
||||
- 图标库:lucide-vue-next(注意尺寸控制),作为 button 需要添加 lucide-icon-btn class 来保证居中
|
||||
- 样式语言:LESS
|
||||
- 样式变量:必须使用 [base.css](web/src/assets/css/base.css) 中定义的颜色变量
|
||||
- 样式变量:必须使用 [base.css](web/src/assets/css/base.css) 中定义的颜色变量,且适配暗色模式
|
||||
|
||||
### 开发规范
|
||||
|
||||
1. **API 接口**:所有接口定义在 `web/src/apis` 目录下
|
||||
2. **样式复用**:优先复用现有组件和样式,避免重复定义
|
||||
3. **响应式设计**:适配不同屏幕宽度,保持布局稳定
|
||||
4. **无障碍设计**:保证基本的无障碍支持,如适当的对比度、键盘可操作等
|
||||
|
||||
### 颜色变量
|
||||
|
||||
必须从 `web/src/assets/css/base.css` 中获取以下变量:
|
||||
|
||||
```less
|
||||
// 典型变量示例(实际以 base.css 为准)
|
||||
@primary-color: // 主色调
|
||||
@text-color: // 文本颜色
|
||||
@background-color: // 背景颜色
|
||||
@border-color: // 边框颜色
|
||||
// ... 其他变量
|
||||
```
|
||||
|
||||
### 组件示例
|
||||
|
||||
@ -77,7 +52,6 @@
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '@/assets/css/base.css';
|
||||
|
||||
.my-component {
|
||||
display: flex;
|
||||
@ -92,4 +66,3 @@
|
||||
## 参考资料
|
||||
|
||||
- [base.css](web/src/assets/css/base.css) - 全局样式变量
|
||||
- [前端开发规范](AGENTS.md#前端开发规范) - AGENTS.md 中的前端开发规范
|
||||
|
||||
@ -29,10 +29,14 @@
|
||||
## v0.6
|
||||
|
||||
<!-- 添加到这里 -->
|
||||
- 新增根目录 `CONTRIBUTING.md`,补充面向仓库入口的贡献说明,统一说明 Docker Compose 开发流程、PR 提交流程与前后端贡献要求,并链接到 `docs/develop-guides/contributing.md`
|
||||
- 优化 `docs/intro/project-overview.md` 的“核心能力”章节,按智能体开发、知识库与 RAG、知识图谱、平台落地能力重写内容,突出项目主线与差异化重点
|
||||
- 调整文档归类:将沙盒文档迁移到 `docs/agents/sandbox-architecture.md` 并归入“智能体开发”;将“上下文配置”并入 `docs/agents/agents-config.md`,重点补充 Context 与 `AgentConfigSidebar` 的联动关系,以及 Context 在 Agent 运行周期中的传递链路
|
||||
- 重构沙盒文档结构,重写 `docs/agents/sandbox-architecture.md`,按目标边界、架构分层、生命周期、文件系统模型、双视图接入、配置与限制重新组织内容
|
||||
- 调整 Agent 路由为 `/agent/{thread_id}`:`/agent` 进入未选中对话空态,不再在 URL 中展示 `agent_id`;发送首条消息时基于当前 `selectedAgentId` 与配置创建新对话,并自动跳转到对应线程路由。
|
||||
- 新增 API Key 管理功能,支持外部系统通过 API Key 调用 Agent 对话接口(`POST /api/chat/agent/{agent_id}`)。统一使用 `Authorization: Bearer <api_key>` 认证,API Key 以 `yxkey_` 开头。获取 API Key 即代表拥有绑定用户的所有接口访问权限。
|
||||
- 将 后端代码 和 agents 解耦,agents 作为单独的 package 使用
|
||||
- 添加 subagents
|
||||
- 添加 subagents 的支持,支持在 web 中添加 subagents
|
||||
- 将内置 skills 的初始化从符号链接改为复制,以兼容沙盒绑定场景
|
||||
|
||||
## v0.5
|
||||
|
||||
@ -4,37 +4,52 @@ layout: home
|
||||
|
||||
hero:
|
||||
name: "Yuxi"
|
||||
text: "智能知识库与知识图谱问答系统"
|
||||
tagline: 基于 LangGraph + Vue.js + FastAPI + LightRAG 架构构建的智能问答平台
|
||||
text: "智能知识库与知识图谱智能体开发平台"
|
||||
tagline: 基于 LangGraph v1 + Vue.js + FastAPI + LightRAG,统一构建 RAG、知识图谱与多智能体应用
|
||||
image:
|
||||
src: /bb.png
|
||||
alt: VitePress
|
||||
alt: Yuxi
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 快速开始
|
||||
link: /intro/quick-start
|
||||
- theme: alt
|
||||
text: 智能体开发
|
||||
link: /agents/agents-config
|
||||
- theme: alt
|
||||
text: GitHub
|
||||
link: https://github.com/xerrors/Yuxi-Know
|
||||
|
||||
features:
|
||||
- title: 🤖 智能体与模型
|
||||
details: 支持主流大模型及 vLLM、Ollama 等,支持自定义智能体开发,兼容 LangGraph 部署
|
||||
- title: 📚 灵活知识库
|
||||
details: 支持 LightRAG、Milvus、Chroma 等存储形式,配置 MinerU、PP-Structure-V3 文档解析引擎
|
||||
- title: 🤖 智能体开发
|
||||
details: 基于 LangGraph v1,支持 Agents、SubAgents、Tools、MCP、Skills 与中间件配置,适合搭建真实业务智能体
|
||||
- title: 📚 知识库与 RAG
|
||||
details: 支持多格式文档上传、解析、分块、向量检索与评估,兼容结构化与非结构化知识管理场景
|
||||
- title: 🕸️ 知识图谱
|
||||
details: 支持 LightRAG 自动图谱构建,以及自定义图谱问答,可接入现有知识图谱
|
||||
- title: 👥 权限安全
|
||||
details: 支持超级管理员、管理员、普通用户三级权限体系,并配置内容审查以及守卫模型
|
||||
- title: 🔧 易于部署
|
||||
details: 基于 Docker Compose 一键部署,支持热重载开发,无需显卡即可运行
|
||||
- title: 🎯 生产就绪
|
||||
details: 完整的测试套件、API 文档、监控日志,适合企业级部署和使用
|
||||
details: 基于 LightRAG 构建图谱问答与图谱检索,支持自动图谱生成、属性图谱导入与可视化分析
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
## 项目定位
|
||||
|
||||
```sh
|
||||
docker compose up --build -d
|
||||
```
|
||||
Yuxi(语析)不是单一的问答页面,而是一个面向开发者与团队的 AI 应用平台。它把知识库、知识图谱和智能体开发放在同一套系统里,适合从原型验证一路走到团队内部落地。
|
||||
|
||||
## 在线演示
|
||||
你可以用它完成这些事情:
|
||||
|
||||
观看视频演示:[Bilibili](https://www.bilibili.com/video/BV1DF14BTETq)
|
||||
- 构建面向业务场景的 RAG + 知识图谱智能体
|
||||
- 将 PDF、Word、Markdown、图片等资料转为可检索、可推理的知识资产
|
||||
- 基于 LangGraph v1 搭建多智能体、子智能体和工具调用流程
|
||||
- 为内部系统提供可管理、可扩展的 AI 能力底座
|
||||
|
||||
## 文档入口
|
||||
|
||||
- [快速开始](/intro/quick-start):完成环境初始化、容器启动与首次登录
|
||||
- [项目简介](/intro/project-overview):了解整体定位、技术栈与核心能力
|
||||
- [知识库与知识图谱](/intro/knowledge-base):查看知识导入、检索与图谱能力
|
||||
- [智能体开发](/agents/agents-config):配置 Agent、Tools、MCP、Skills 与中间件
|
||||
- [生产部署](/advanced/deployment):了解部署方式与上线建议
|
||||
|
||||
|
||||
自动生成文档镜像:
|
||||
|
||||
- [Zread](https://zread.ai/xerrors/Yuxi-Know)
|
||||
- [DeepWiki](https://deepwiki.com/xerrors/Yuxi-Know)
|
||||
|
||||
@ -20,22 +20,12 @@
|
||||
|
||||
在 `.env` 文件中添加对应的环境变量:
|
||||
|
||||
|
||||
|
||||
::: tip 免费获取 API Key
|
||||
[硅基流动](https://cloud.siliconflow.cn/i/Eo5yTHGJ) 注册即送 14 元额度,支持多种开源模型。
|
||||
[硅基流动](https://cloud.siliconflow.cn/i/Eo5yTHGJ) 注册即送 16 元额度,支持多种开源模型。
|
||||
:::
|
||||
|
||||
<<< @/../.env.template#model_provider{bash 5}
|
||||
|
||||
### 默认对话模型格式
|
||||
|
||||
系统的默认对话模型可以在设置页面配置,也可以通过配置项 `default_model` 指定,格式统一为 `模型提供商/模型名称`,例如:
|
||||
|
||||
```yaml
|
||||
default_model: default="siliconflow/Pro/deepseek-ai/DeepSeek-V3.2
|
||||
```
|
||||
|
||||
## 自定义模型供应商
|
||||
|
||||
::: tip 自定义模型供应商仅支持对话模型
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 项目简介
|
||||
|
||||
Yuxi(语析)是一个基于大模型的智能知识库与知识图谱智能体开发平台。它融合了 RAG(检索增强生成)技术与知识图谱技术,为用户提供智能问答和知识管理服务。
|
||||
Yuxi (语析) 是一个智能知识库和知识图谱 Agent 开发平台,能够帮助你构建结合检索增强生成 (RAG) 与知识图谱推理的生产级 AI 应用。该平台基于现代架构构建,采用 LangGraph v1、Vue.js 3、FastAPI 和 LightRAG,提供了创建对话式 AI 系统的全面工具包,这些系统能够理解并对你的业务知识进行推理。
|
||||
|
||||
## 设计理念
|
||||
|
||||
@ -12,53 +12,56 @@ Yuxi(语析)是一个基于大模型的智能知识库与知识图谱智能
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 后端服务
|
||||
| 层级 | 技术 | 用途 |
|
||||
|------|------|------|
|
||||
| 前端 | Vue.js 3, Vite, Ant Design Vue | 现代响应式 UI 框架与组件库 |
|
||||
| 状态管理 | Pinia | 前端集中式状态管理 |
|
||||
| 后端 API | FastAPI, Uvicorn | 高性能异步 Python Web 框架 |
|
||||
| Agent 框架 | LangGraph v1 | 声明式 Agent 编排与状态管理 |
|
||||
| 知识库 | LightRAG, Milvus | 基于向量存储的 RAG 实现 |
|
||||
| 图数据库 | Neo4j | 知识图谱存储与查询 |
|
||||
| 文档处理 | MinerU, PaddleX, RapidOCR | 多格式文档解析与 OCR |
|
||||
| 任务队列 | Redis, PostgreSQL Workers | 异步任务处理 |
|
||||
| 对象存储 | MinIO | 文件与文档存储 |
|
||||
| 关系型数据库 | PostgreSQL | 元数据与用户数据持久化 |
|
||||
| 部署 | Docker, Docker Compose | 容器化部署与编排 |
|
||||
|
||||
- **FastAPI**:现代高性能 Python Web 框架
|
||||
- **LangGraph**:基于 LangChain 的智能体编排框架
|
||||
- **PostgreSQL**:业务数据存储
|
||||
- **Milvus**:向量数据库,支持大规模语义检索
|
||||
- **Neo4j**:图数据库,存储知识图谱
|
||||
- **MinIO**:对象存储,用于文件托管
|
||||
|
||||
### 前端界面
|
||||
|
||||
- **Vue.js 3**:渐进式前端框架
|
||||
- **Ant Design Vue**:企业级 UI 组件库
|
||||
|
||||
### 文档处理
|
||||
|
||||
- **LightRAG**:文档理解与知识图谱构建
|
||||
- **MinerU**:文档智能解析
|
||||
- **PP-Structure-V3**:PDF 结构化提取
|
||||
|
||||
## 核心能力
|
||||
|
||||
### 智能问答
|
||||
Yuxi 的核心能力不在于“把大模型接进来”,而在于把 **智能体开发、知识库/RAG、知识图谱** 放进同一套系统里,并让它们在运行时真正协同工作。
|
||||
|
||||
系统支持接入多种大语言模型,通过对话方式提供智能问答服务。模型可配置、工具可组合、提示词可定制,满足不同业务场景需求。
|
||||
### 1. 面向真实业务的智能体开发
|
||||
|
||||
### 知识库管理
|
||||
Yuxi 基于 LangGraph v1 提供智能体开发能力,不只是一个固定问答入口,而是一套可配置、可扩展的 Agent 运行框架。开发者可以围绕同一个 Agent 配置模型、提示词、工具、MCP、Skills、SubAgents 与中间件,使“对话能力”变成“可编排的业务能力”。
|
||||
|
||||
支持 Milvus 向量数据库和 LightRAG 知识图谱两种存储形式:
|
||||
- Milvus 适合大规模文档检索场景
|
||||
- LightRAG 适合需要理解实体关系的复杂查询
|
||||
这一层是项目的控制中心,决定了模型如何调用工具、如何访问知识、如何接入文件系统以及如何与其他子智能体协作。
|
||||
|
||||
### 知识图谱
|
||||
### 2. 知识库与 RAG 一体化能力
|
||||
|
||||
自动从文档中提取实体和关系,构建结构化知识图谱。支持可视化查看和图查询,帮助理解知识之间的联系。
|
||||
Yuxi 提供完整的知识入库链路,而不是只做检索接口封装。文档从上传开始,会经过解析、分块、向量化、检索配置和评估等阶段,最终成为 Agent 可直接调用的知识来源。
|
||||
|
||||
### 文档解析
|
||||
将组织的文档转换为智能对话助手。上传 PDF 手册、技术规格、政策文档和培训材料,以创建可搜索、具备推理能力的知识库,员工可以使用自然语言查询。
|
||||
该系统能够理解复杂的问题,并提供带有来源引用的上下文感知答案。
|
||||
|
||||
支持 PDF、Word、图片等多种格式的智能解析,自动提取文本、表格、公式等内容。
|
||||
|
||||
### 权限管理
|
||||
### 3. 知识图谱参与推理,而不只是展示
|
||||
|
||||
基于部门的知识库访问控制,确保数据安全。
|
||||
Yuxi 的知识图谱能力不是孤立的可视化模块,而是和知识库、Agent 推理链路联动的。
|
||||
构建需要理解实体之间关系的应用程序。LightRAG 自动从文档中提取实体和关系,创建系统可以查询以进行复杂推理任务的知识图谱。
|
||||
以标准格式上传现有图谱数据,或使用自动构建功能从非结构化文本生成图谱。
|
||||
|
||||
### 内容安全
|
||||
### 4. 面向生产落地的文档理解与平台能力
|
||||
|
||||
为了让知识真正可用,Yuxi 集成了 MinerU、PP-Structure-V3、Docling 等解析能力,覆盖 PDF、Office、Markdown、图片等常见格式,解决原始资料进入系统前的结构化处理问题。
|
||||
|
||||
在此基础上,平台还补齐了业务落地常用的工程能力,例如:
|
||||
|
||||
- 部门与权限管理
|
||||
- 内容审查与守卫能力
|
||||
- 文件管理与任务管理
|
||||
- Docker Compose 部署与热重载开发
|
||||
|
||||
内置内容审查机制,保障服务合规性。
|
||||
|
||||
## 适用场景
|
||||
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
# 快速开始指南
|
||||
|
||||
Yuxi(语析)是一个基于知识图谱和向量数据库的智能知识库系统。通过本文档,你可以在几分钟内完成环境搭建并开始使用。
|
||||
欢迎使用 Yuxi(语析),这是一个智能知识库和知识图谱 Agent 开发平台。
|
||||
本指南将帮助你在几分钟内启动并运行系统,使你能够利用 LangGraph、RAG 技术和知识图谱构建 AI 驱动的知识应用。
|
||||
|
||||

|
||||
|
||||
|
||||
::: tip 提示
|
||||
除了此文档网站外,你还可以访问 [Zread](https://zread.ai/xerrors/Yuxi-Know) 或 [DeepWiki](https://deepwiki.com/xerrors/Yuxi-Know) 查看自动生成的详细项目文档。
|
||||
@ -20,7 +24,7 @@ git clone --branch v0.6.0-dev --depth 1 https://github.com/xerrors/Yuxi-Know.git
|
||||
cd Yuxi-Know
|
||||
```
|
||||
|
||||
版本选择建议:
|
||||
`--depth 1` 标志会创建一个浅克隆,仅包含最新的提交,从而显著减少下载时间和磁盘使用量。下表提供了版本选择的指导。
|
||||
|
||||
| 版本 | 适用场景 |
|
||||
|------|----------|
|
||||
@ -48,7 +52,7 @@ cd Yuxi-Know
|
||||
- 自动拉取必需的 Docker 镜像
|
||||
|
||||
::: tip API Key 获取
|
||||
- **硅基流动**:访问 [cloud.siliconflow.cn](https://cloud.siliconflow.cn/i/Eo5yTHGJ),注册即送 14 元额度
|
||||
- **硅基流动**:访问 [cloud.siliconflow.cn](https://cloud.siliconflow.cn/i/Eo5yTHGJ),注册认证即送 16 元额度
|
||||
- **Tavily**:访问 [app.tavily.com](https://app.tavily.com/) 获取搜索 API Key(可选)
|
||||
:::
|
||||
|
||||
@ -76,7 +80,7 @@ docker compose up --build -d
|
||||
如果你不需要知识库和知识图谱功能,可以使用轻量模式启动,跳过 Milvus、Neo4j、etcd 等服务,节省系统资源:
|
||||
|
||||
```bash
|
||||
make up-lite
|
||||
make up-lite # macOS or Linux
|
||||
```
|
||||
|
||||
轻量模式仅启动核心服务(前端、后端、PostgreSQL、Redis、MinIO),前端侧边栏会自动隐藏知识库和图谱入口。切换回完整模式只需运行 `make up`。
|
||||
@ -93,18 +97,6 @@ make up-lite
|
||||
|
||||
首次访问时,系统会要求你设置超级管理员账号和密码,请妥善保存。
|
||||
|
||||
## 开始使用
|
||||
|
||||
完成上述配置后,你就可以开始使用了:
|
||||
|
||||
1. 登录系统(使用刚才设置的超级管理员账号)
|
||||
2. 进入「智能体」页面
|
||||
3. 选择或创建一个智能体
|
||||
4. 在右侧面板配置提示词、选择模型和工具
|
||||
5. 开始对话
|
||||
|
||||

|
||||
|
||||
## 故障排除
|
||||
|
||||
### 查看服务状态
|
||||
|
||||
@ -152,7 +152,7 @@ const userStore = useUserStore()
|
||||
const infoStore = useInfoStore()
|
||||
const agentStore = useAgentStore()
|
||||
const repoUrl = 'https://github.com/xerrors/Yuxi-Know'
|
||||
const faqUrl = 'https://xerrors.github.io/Yuxi-Know/latest/changelog/faq.html'
|
||||
const faqUrl = 'https://xerrors.github.io/Yuxi-Know/'
|
||||
|
||||
// 加载状态
|
||||
const isLoading = ref(true)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user