docs: 更新大量文档

This commit is contained in:
Wenjie Zhang 2026-03-25 00:36:42 +08:00
parent 423eca3fb0
commit e49d8dda53
23 changed files with 1292 additions and 1787 deletions

View File

@ -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

View File

@ -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
View 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>
感谢你的贡献 ❤️。

View File

@ -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>

View File

@ -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"

View File

@ -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' }
]
}
],

View File

@ -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 以只读方式注入
- 工作台查看真实后端文件系统
但它仍然是一个 **面向当前产品场景的工程化沙盒**,而不是一个已经完成强安全与强控制面建设的通用沙箱平台。

View File

@ -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)

View File

@ -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
```

View 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 只读挂载场景,但它依然是 **面向当前业务目标的工程化沙盒**,不是已经完成强安全、强控制面建设的通用沙箱平台。

View File

@ -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 集成

View File

@ -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 需要修复时:
### 情况 1main 上没有未完成的新功能
直接在 main 修复并发布:
```bash
git commit -m "fix: 解决配置解析器崩溃问题"
git tag -a v0.3.1 -m "Hotfix v0.3.1"
git push origin main --tags
```
### 情况 2main 上已有新功能未完成
从上一个 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
```
---
感谢每一位贡献者的付出!

View File

@ -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 中提问。

View File

@ -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
...
全部匹配: ✅ 是
```

View File

@ -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. 最终消息与工具调用正常入库。

View 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>
感谢每一位贡献者的投入。

View File

@ -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 中的前端开发规范

View File

@ -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

View File

@ -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)

View File

@ -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 自定义模型供应商仅支持对话模型

View File

@ -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 部署与热重载开发
内置内容审查机制,保障服务合规性。
## 适用场景

View File

@ -1,6 +1,10 @@
# 快速开始指南
Yuxi语析是一个基于知识图谱和向量数据库的智能知识库系统。通过本文档你可以在几分钟内完成环境搭建并开始使用。
欢迎使用 Yuxi语析这是一个智能知识库和知识图谱 Agent 开发平台。
本指南将帮助你在几分钟内启动并运行系统,使你能够利用 LangGraph、RAG 技术和知识图谱构建 AI 驱动的知识应用。
![系统架构图](https://private-user-images.githubusercontent.com/35524243/559928805-96742b56-eda7-4aae-a4df-6fe9d3c30fd1.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQzNjgxODMsIm5iZiI6MTc3NDM2Nzg4MywicGF0aCI6Ii8zNTUyNDI0My81NTk5Mjg4MDUtOTY3NDJiNTYtZWRhNy00YWFlLWE0ZGYtNmZlOWQzYzMwZmQxLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAzMjQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMzI0VDE1NTgwM1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTQwODFlNzI0YjMwMTI4YWMzOWU2YjAxMjUyM2E4NTUzOWRiNzY5OWMzY2Y0OGE0MzJjZmZjNWI3ODMzZjYyMWMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.uw8QmYsqyXDoEP8wLPHvUYdhS2qbCo2engwiaprs-VM)
::: 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. 开始对话
![智能体配置界面](/images/agent.png)
## 故障排除
### 查看服务状态

View File

@ -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)