### Index --- # 自定义官网式首页,内容见 .vitepress/theme/components/YuxiHome.vue layout: home title: 语析 Yuxi · 融合 RAG 与知识图谱的智能体 Harness 平台 --- --- ### Develop Guides/Changelog # 版本变更记录 本页用于记录各版本发布说明(新增、修复与破坏性变更)。 同一版本的多次功能更新时,应以功能为单位进行更新,比如之前添加了 A 功能的更新,在后续的更新中修复了因 A 功能引入的 bug,那么这个修复说明应该和 A 功能描述放在一起(重新修改表达,而不仅仅是补充),而不是新增一条修复记录,功能更新同理。必须遵守:每一个修改不应超过 200 字,注意高度凝练。 ## v0.7.2 (current) ::: warning 升级提醒 1. 升级到 v0.7.2 后,管理员此前创建的 stdio MCP 会被禁用,也无法重新启用。请在详情页迁移为 SSE 或 Streamable HTTP,或直接删除;代码内置的系统 stdio MCP 不受影响。 ::: - 清理测试套件冗余:删除 5 个自证式/假绿/重复覆盖的测试文件(`test_hash_utils`、`test_skills_backend_error_handling`、`test_graph_router_list`、`test_agent_sync_e2e`、`test_viewer_filesystem_e2e`),合并约 50 个文件的重复场景与参数转发断言,抽取 eval 与 e2e 共享 helper;净减约 2,900 行测试代码,真实回归覆盖不变。 - 完善 Agent Token 用量统计:state 同时保留近似上下文与主 Agent 模型返回的 Provider `usage_metadata`,实际用量拆分为最近调用、当前 Run 和线程累计;前端只读取 state,终态 chunk 不传递用量,worker 在 Run 终态时将父线程中 Run ID 匹配的 state 快照写入 AgentRun。支持 OpenAI priority/flex 缓存明细;L2 摘要内部调用暂未计入完整账单口径。 - 对话输入草稿按线程保存:输入内容实时写入 localStorage(按线程 ID 区分),切换对话时保存旧线程草稿并还原新线程草稿,新建对话使用独立草稿、发送创建线程后自动清理,刷新页面后草稿仍可还原,删除对话时同步清理对应草稿。 - 对话消息补充时间信息:历史接口为 assistant 消息附上关联 run 的 started_at/finished_at;复制按钮右侧以纯文本显示消息完成时间,点击切换为执行耗时(如"耗时 5s")再点切回,无背景色块与时钟图标;时间按相对规则展示(今天 HH:mm、昨天、一周内周几、更早显示日期、跨年补全年份)。流式生成时 loading 旁实时显示已执行时长,loading 指示器去除背景色块并改为轻微呼吸的文字样式。 - 修复 Agent 产物 Word 文件无法预览:查看器文件预览路径(outputs、uploads、沙盒文件)与工作区一致,docx/pptx 先经 LibreOffice 转换为 PDF 再预览,转换失败返回明确错误,不再判定为二进制文件拒绝预览。 - Agent 文件预览的 HTML 预览新增缩放比例调节(默认 90%,范围 60%–150%,步进 10%):内联、顶部工具栏与全屏预览均提供缩小/放大按钮并实时显示百分比,切换文件时重置为默认比例,预览/源码模式切换仅在预览态显示缩放控件。 - 修复公开图片上传的存储型 XSS 风险:头像与用户图片不再信任客户端 MIME 或文件名后缀,服务端校验真实图片内容且仅接受 PNG、JPEG、WebP、GIF,对象名使用识别出的固定安全后缀,拒绝伪装成图片的 SVG。 - 修复知识库图片公开访问风险:解析产生的知识库图片从 `public` bucket 迁移到私有 `kb-images` bucket,新增带知识库读权限校验的后端代理接口按需读取;Markdown 预览对代理图片携带鉴权头加载为 blob URL,未登录或无权限用户无法匿名访问图片,头像/Agent 图标等公开资源不受影响。 - 修复个人 Skill 列表权限解析错误:个人工作区 Skill 不再进入共享配置解析,所有者获得管理权限,其他用户不可访问;同时修正数据库 UTC-naive 时间的序列化,子智能体运行时间不再错误偏移 8 小时。 - 优化知识库文档列表性能:根目录虚拟目录分组改用部分索引(`idx_kf_kb_parent_segment` 按路径首段聚合),平铺文件筛选与排序用 `idx_kf_kb_parent_flat` 支撑,避免大知识库全表扫描与 46MB 磁盘排序溢出;文件统计聚合结果增加 10 秒 Redis 短缓存,列表、统计、子目录计数与创建人查询并行执行,前端自动刷新轮询间隔同步调整为 10 秒。36 万文件知识库列表接口耗时由约 1.3s 降至约 300ms。 - 修复 MCP 管理接口可通过 stdio 启动任意本地进程的问题:用户配置仅允许 SSE/Streamable HTTP,运行时拒绝加载历史用户 stdio 记录,系统内置 stdio 的连接参数改为仅由代码维护;前端移除用户 stdio 配置入口,文档补充内置 stdio 的代码添加与验证方式。 - 工作区新增只读历史对话文件入口 `agents/chats/{thread_id}`,网页以 `YYYY-MM-DD-title` 显示并按日期标题倒序浏览各 thread 的非空 uploads 与 outputs;空目录、无文件对话及 `large_tool_results`、`conversation_history` 等内部中间产物不展示。该目录由 API 虚拟映射,不创建符号链接或复制文件,也不进入当前会话 viewer 与 sandbox 挂载,避免 Agent 读取其他会话历史。面包屑中该目录固定显示为"历史对话"与目录列表一致,多选过滤改用只读路径集合避免逐项查找。 - 收窄知识库状态边界:读取模型统一收口至 `read_models.py`;创建、列表、详情与更新由 Manager 统一返回 `KnowledgeBaseSummary/Detail`,Router 只转换 HTTP 响应;Manager 协调查询配置、主记录与聚合统计,Repository 在行锁内合并统计投影;executor 接收 frozen `KnowledgeBaseConfig`,负责类型资源、文档操作与类型专属一致性检测,不再写知识库主记录。 - 修复 Agent worker 知识库运行配置不一致:`get_kb_config` 从 Redis 读取最小 Config 快照,未命中时在 KB 级分布式锁内回源 PostgreSQL,Redis 连接故障时只读请求直接回源且不回填;更新与删除先可靠失效缓存再提交数据库,避免旧请求回填过期配置。查询参数在数据库行锁内合并,并发保存不再互相覆盖。 - 精简知识库文档内容接口响应:`GET /api/knowledge/databases/{kb_id}/documents/{doc_id}/content` 不再返回分块内部的实体 ID 与抽取结果,避免向文档预览请求传输仅供知识图谱构建使用的数据。 - 清理未使用的共享访问级别常量模块,移除 Agent、Skill 与知识库中的冗余导入和别名;共享范围校验继续由统一权限模块负责。 - 统一 Agent、Skill 与知识库共享权限:配置拆分读取/管理范围并统一解析 `none/read/manage`;启动时将旧 `share_config` 幂等迁移为 v2 且只回填只读范围、不追溯授予管理权,运行时代码仅接受 v2;创建者与超管保留管理权。非管理员创建或编辑 Agent 时共享范围与 Skill 一致收敛为仅个人可见,避免越权扩大到部门或全局;共享 Skill 仅管理员可安装,普通用户固定安装到个人工作区。知识库路由统一按 READ/MANAGE ACL 校验,配置弹窗按基础信息、权限、检索分栏,非检索保存不再覆盖检索参数,编辑表单不再残留已移除的“自动生成问题”开关;用户编辑不允许修改角色身份。同步修复 Agent/Skill `share_config` 列实际类型为 `json` 而非预期 `jsonb` 导致迁移语句报错、后端无法启动的问题。 - 收敛消息型 AgentRun 提交:Web Chat 与 Agent Call/Eval 共用 `run_submission_service.submit_run_command`,Call/Eval 拆为独立 Router;Request/Run 固化 `source/channel/external_id/origin_metadata` 来源快照,Eval 评估上下文继续透传到 worker 与 Langfuse,保留现有接口与响应兼容性,Resume、Subagent 生命周期不变。 - 新增个人工作区 Skill:安装确认可选择个人或共享位置;个人 Skill 保存到 `workspace/agents/skills` 且不入库,元数据按用户缓存 5 分钟并在安装、删除、手动刷新后立即更新;Card List 与 Agent 运行时统一按个人版本覆盖同名共享版本,卡片与聊天技能选择列表共用 slug 到 Lucide 图标映射;Agent 直接读取工作区真实路径,不再复制到线程 `/home/gem/skills` 投影;共享 Skill 投影统一以来源映射为单一数据源。 - 统一后端真实路径根目录校验:Skill、工作区和沙盒复用 `ensure_within_root`,保持原有越界拒绝语义并减少重复安全判断。 - 统一前端单元测试目录为 `web/test/unit`,测试脚本仅收集该目录;测试规范同步说明主应用与独立 CLI 包的目录约定,避免同一子项目混用 `test` 和 `tests`。 - Skill 推荐区升级为套件卡片,首批提供 Anthropic 文档处理套件;统一选择、短时加载、生效范围和结果四步弹窗,远程仓库与全局搜索保持在同一弹窗,选择页标签居中并以桌面三列网格展示,技能列表支持纵向滚动;确认页支持移除、范围摘要及失败草稿清理;公共扩展卡片采用紧凑 gray 样式;上传入口共用普通请求流程,支持部分失败与重试。 - 远程 Skill 来源策略迁移到 PostgreSQL,并在「基本设置」提供精确域名白名单配置;默认允许 GitHub 与 ModelScope,空列表会关闭远程安装。远程 Skill CLI 改用无环境凭据的一次性 Sandbox,删除失败时也清理一次性连接缓存;Kubernetes 不挂载 ServiceAccount token;数据回流严格校验相对路径并限制文件数、目录深度和总大小,同时统一命令与 HTTP 超时,修复 #895 并缩小 #855 的风险面。 - 新增 PDF 解析前置页树校验:PDF 进入 PyPDFLoader、MinerU 或 OCR 引擎前会用 PyMuPDF 逐页加载页槽,提前识别加密、空文档、null 页槽和非 Page 对象等结构异常,并返回可操作的中文错误,避免解析服务内层延迟失败。 - 修复真实 API 测试资源清理:integration 与 E2E 会在测试会话前后通过公开接口删除名称以 `pytest`(兼容旧 `py_test`)开头的评估基准、评估运行和知识库,并删除带显式 E2E 标记的对话、临时智能体及独立沙盒目录;知识库删除改用列表真实返回的 `kb_id`,清理失败会明确中止测试,避免测试数据污染最近对话或长期残留。 - CLI 新增 `yuxi chat` 本地网页调试入口:临时服务仅监听 `127.0.0.1`,使用本地保存的 remote 与 API Key 代理 Agent Call 和 Run SSE,浏览器可连续对话并实时显示文本增量;API Key 不进入页面,支持指定智能体、remote 及仅打印地址。 - 新增纯文本 Channel 入口:`/api/agent-invocation/channel/messages` 统一接收 CLI/未来 IM 消息,普通文本复用 `submit_run_command` 并默认使用 `steer`,首版支持 `/state` 查询线程状态与 `/approve` 恢复工具审批;`yuxi chat` 已切换到该入口,并将工具审批中断显示为等待 `/approve` 的正常状态,页面采用无侧边栏的微信 PC 对话布局与色块头像,暂不处理 `ask_user_question`。同步修复测试模块重名、Subagent Run 来源快照错误,并在 HTTP 与共享提交边界拒绝超长来源字段。 - 优化知识图谱构建:以持续队列并发执行 LLM 抽取、结构写入和向量索引,失败自动重试并持久化进度;已有结果支持断点恢复,新增按最近 Chunk 查询失败样例和向量 reconcile 接口,任务与前端分别展示抽取、结构、向量进度,索引面板支持键盘操作。 - HTML 辅助可视化迁移为内置 `html-preview` Skill:默认 Chatbot Prompt 不再常驻注入 `html:preview` 专属说明,Agent 改为通过统一的 Skill 描述发现并按需读取静态 HTML/CSS 的适用场景、布局和安全边界;未显式配置 Skills 的 Agent 按现有默认规则自动获得该能力,使用显式 Skills 允许列表的 Agent 需选择 `html-preview`,内置 `deep-research` 已声明依赖;保留前端既有围栏清洗、sandboxed iframe、自适应高度和流式占位行为,普通 HTML 源码继续使用 `html` 代码块。 - 模型供应商的单个 chat 模型配置新增“模型请求参数 JSON”:管理员可为每个模型独立保存、回显、修改和清空思考参数,未配置或空对象保持原行为;运行时模型缓存会携带该配置,测试模型连接与正式聊天/Agent 调用统一在模型加载入口合并。该字段仅面向 OpenAI/OpenRouter 等 OpenAI 兼容供应商,并通过 `extra_body` 透传;出于安全考虑,顶层字段采用白名单机制,当前支持 `enable_thinking`、`thinking_budget`、`thinking`、`reasoning` 和 `reasoning_effort`,对象内部结构交由供应商校验。 - 新增通用管理员配置 Options 模块:系统运行时配置迁移到 PostgreSQL,API 与 worker 通过带版本失效的 Redis 短缓存共享最新值,Redis 故障时回源数据库;旧 `base.toml` 只补充缺失字段且读取失败后可重试。LangGraph checkpoint 默认使用 PostgreSQL,跨进程串行初始化且异常解锁时销毁持锁连接;附件正式文件迁移到 MinIO,本地按需缓存,Run 在提交数据库快照后恢复附件,避免重复查询和长事务。 - 修复部署配置:开发与生产 Compose 中的 Milvus 现在复用对应环境文件里的自定义 MinIO 凭据,避免对象存储认证失败导致服务无法健康启动;Web 生产镜像会统一将静态资源目录设为 `755`、文件设为 `644`,避免 Nginx 因构建产物权限过严返回 403。 - 丰富模型选型参考信息:接入 `@opencode-ai/models` 内置 snapshot,补充模型上下文、能力和价格等信息;模型选择器移除价格悬浮提示并关闭搜索自动完成,底部增加“配置模型”入口;模型供应商候选列表支持美元与人民币价格切换,默认美元,人民币按固定汇率 `1 USD = ¥7` 换算,并明确 models.dev 数据与固定汇率仅供参考;候选模型工具栏统一靠右排列搜索、币种和类型筛选控件,类型文案改为“对话 / 向量 / 重排”。DashScope 中国站内置标识修正为 `alibaba-cn`,`alibaba` 改为使用 `dashscope-intl.aliyuncs.com` 的国际站定义。 - 优化 Agent 会话与设置页多项交互细节:修复文件侧栏重新展开丢失预览、审批模式改为本地记忆最近选择、下拉面板点击外部区域自动收起、输入框添加内容入口改为 `+` 并可直接引用知识库与 Skill、Skill 图标统一为 Lucide `WandSparkles`、账户设置合并 Memory 开关等;侧栏导航“智能体管理”简化为“智能体”,路由由 `/model-manage` 重命名为 `/agent-manage`;智能体卡片增加共享范围标签,并将“去对话”调整为紧凑文字入口;文件预览标签的关闭按钮补充紧凑圆角悬浮、按下和键盘聚焦状态;同步优化首页首屏视觉细节。 - 新增 Agent backend 工具审批与完全信任模式:Agent 配置可定义默认模式,聊天 run、Agent Call 与评估请求可用 `tool_approval_mode` 做单次覆盖,实际模式固化到 `AgentRun` 并由 resume/子智能体继承;默认审批仅拦截 `write_file`、`edit_file`、`execute`,只读 filesystem 工具直接执行,完全信任保持自动执行。前端输入框左下角新增线程级“请求审批 / 完全信任”选择器,支持刷新恢复显式选择;智能体配置页将带选项的 `string` 字段正确渲染为选择控件,模式解析按“线程显式选择、智能体显式配置、本地最近选择、系统默认值”依次回退,避免本地缓存覆盖智能体配置。同轮多个工具调用按顺序一次展示一项,参数默认只显示单行摘要,点击后带轻量动画在卡片内展开完整内容,展开区最大高度为 300px。审批卡进一步调整为直接覆盖输入区,按工具类型优先展示命令或目标路径,操作固定为“拒绝 / 允许”,审批期间底层输入控件不可交互。默认模式下子智能体隐藏敏感 backend 工具,避免绕过主线程审批。补充模式解析、middleware、run 继承、事件压缩、子智能体过滤、前端状态恢复测试,并在真实页面验证刷新恢复、逐项批准或拒绝、参数点击展开与完全信任自动执行。模式解析合并为单次 context 共享(resolve_agent_run_config)避免 run 创建/intake 重复加载上下文;resume 与子智能体继承统一读取固化快照,前端审批弹窗参数序列化改为 computed,并清理 `reviewConfigs` 等未消费状态。resume 解析 tool_approval_mode 时对旧版本固化、缺少该字段的 interrupted 运行回退默认值而非报错,避免历史中断会话升级后无法恢复。 - 新增智能体请求队列(Phase 1/2/Steer):同一线程运行中提交的新请求默认持久化排队,并按 FIFO 顺序自动执行;新消息或已有排队项也可设为 Steer,在当前步骤结束后的下一次模型调用前停止旧 Graph,再复用 completed 接力优先执行。支持实时查看排队位置、单条取消和刷新恢复,也可通过 `reject` 策略保持“不能立即执行就拒绝”。聊天、Agent Call 和评估统一接入该队列,前端会分开展示排队请求与当前回复,并修复连续请求交接时的流状态和消息顺序问题;排队请求区域改为紧贴输入框的附属列表,移除冗余标题与表格式分割线,保留顺序和删除操作但弱化次要信息。同步 Agent Call 遇到忙线程时会直接返回拒绝结果,不再因缺少 `run_id` 进入等待逻辑并返回 500。failed/cancelled 时已有积压请求会明确进入暂停状态,用户可手动继续 FIFO 队头;队列为空后的新请求可正常执行。interrupted 必须先完成 resume,前端会禁用普通消息发送,后端也会在持久化 Message/Request 前返回 `run_interrupted` 冲突,不能被继续动作或绕过前端的请求破坏恢复顺序;completed 自动接力的故障窗口会在 worker 重试和启动恢复时补齐。Phase 2.1 进一步使用 Conversation 行锁串行化 intake、resume、continue 和自动接力,保证并发 enqueue 仍严格遵守 FIFO;`pending` AgentRun 作为持久化投递意图,completed job 重试和 worker startup 会优先重新投递已有 run,不再依赖重启修复提交后 ARQ 投递失败;终态写入增加单赢家语义,后到的 cancelled/failed/completed 不再造成 AgentRun、Message 与 SSE 状态分裂;request_id 幂等绑定 uid、agent、thread、source 和 queue policy,跨作用域复用返回结构化冲突。并发 `reject` 现在会在锁定 FIFO 队头后确认当前请求确实能够立即派发,竞争失败的请求原子转为 rejected;终态 run 缺少 `finished_at` 时显式暴露数据不变量,实时进入 interrupted 后也会立即刷新队列提示。Request SSE 恢复时会复用已有连接,开发容器也会在有长连接时按时完成热重载。审批中断改为在消息和 Agent 状态写入事件流后再发布中断终态,resume 时保留排队 Request SSE;交付物按 `present_artifacts` 所在对话渲染,避免连续审批必须刷新和后续消息错位。Steer 增加无工具模型轮次的 `aafter_model` 兜底检查,含工具调用时仍等待完整批次,并由 worker 接力和启动恢复保证持久化意图不丢失。新增《Agent 请求队列与调度设计》文档,说明调度目标、策略、状态、异常处理和当前范围。补充队列、取消、暂停恢复、消息排序、流交接及接口测试。 普通发送遇到 `run_interrupted` 冲突时会从 checkpoint 重载中断载荷、恢复审批框,并回填后端未持久化的文本与图片草稿;恢复过程按线程和请求版本隔离,避免旧请求污染当前草稿或重新显示已处理的审批,状态读取失败时明确提示刷新重试。 - 知识库列表直接读取 PostgreSQL 中的概览和持久化统计,不再为展示卡片同步初始化 Milvus 等知识库实例,避免首次进入知识库页面时卡片长时间不可点击。 - 新增知识库 external API 与 `yuxi kb` 查询类命令:后端在 `/api/knowledge/databases/external/*` 下暴露列库、文件搜索、检索、打开和文件内查找接口,统一走认证身份校验;CLI 新增 `yuxi kb list/files/query/open/find`,补充后端 external API 集成测试与 CLI client/命令测试。管理端同步新增 `GET /api/knowledge/databases/{kb_id}/documents/search`,复用底层文件名搜索能力供前端调用;知识库详情页工具栏新增「搜索文件」按钮,打开命令面板式弹窗(与历史对话搜索弹窗同风格),输入关键词按文件名搜索并在结果列表展示状态/大小/更新时间,placeholder 明确标注仅匹配文件名、不搜索文件内容,点击结果可直接打开文件详情。搜索请求增加序号校验,连续回车与快速重搜时丢弃过期响应,避免后发先至覆盖当前关键词结果。文件名搜索改为数据库侧按 filename/status 过滤、`updated_at desc` 排序与 count 统计,避免大知识库超过仓储单页上限时静默漏结果;管理端搜索先做当前用户可见性校验再判断文档能力,external open/find 统一清晰处理只读源与预期参数错误;CLI 查询结果从 `metadata.score` 读取并展示检索得分。 - 新增 `download_kb_file` 知识库工具:通过 `file_id` 调用 `knowledge_base.get_file_download(variant="original")` 从 MinIO 拉取原始二进制(pdf/docx/xlsx 等),落盘到沙盒 `outputs` 目录并返回沙盒内可见的虚拟路径,供后续代码工具以文件对象方式读取(`openpyxl.load_workbook`、`pdfplumber.open` 等),弥补 `query_kb`/`open_kb_document` 只返回文本切片、丢失原始文件结构的不足。复用会话可见知识库校验与 `ocr_parse_file` 的落盘范式,支持 `save_as` 指定文件名(剥离目录防穿越,重名追加 `_N` 后缀),工具只搬运不解析、不自动登记交付物。工具已登记到 knowledge-base 内置 Skill 的 `tool_dependencies`,并在 SKILL.md 可用工具段补充说明;只读源拦截下沉到 `manager.get_file_download` 内部,与 `open_document`/`find_in_document` 落点一致,工具/router/CLI 所有调用方统一获得 dify 等只读检索源的拦截;返回的 `size_bytes` 与落盘统一用下标访问 `data["content"]`,避免回退掩盖契约异常。 - 内置网页搜索工具新增豆包联网搜索支持:新增 `WEB_SEARCH_PROVIDER`(`doubao`/`tavily`)与 `DOUBAO_SEARCH_API_KEY` 配置,未显式指定时按已配置的 API Key 自动识别 provider;两种 provider 统一注册为工具名 `web_search`,`deep-research` Skill 依赖同步从 `tavily_search` 改为 `web_search`。 - 只读用户知识库开放图谱与知识导图查看:非管理权限用户也能打开“知识图谱”“知识导图”页签查看已构建内容,但隐藏配置抽取器、开始索引、生成/重新生成/增量更新等写操作;空状态提示改为等待管理员构建。 - 模型供应商编辑表单优化:API Key 密码框关闭浏览器自动填充识别,避免被误判为登录用户名/密码;供应商处于停用状态时,保存按钮拆分为“仅保存”与“保存并启用”,后者保存后自动启用,避免漏开启。 - 新增内置模型供应商 OpenCode Go(`opencode-go`),Base URL 与模型发现地址指向 `https://opencode.ai/zen/go/v1`,配置方式与 OpenCode 一致。 - 模型供应商列表优化:卡片移除右下角启用/禁用开关,供应商按启用状态拆分为「已启用 / 未启用」两组展示;已启用供应商保留 Base URL、能力与「管理模型」入口,未启用供应商只展示图标与名称的小卡片,不再展示 Base URL 与管理模型按钮。 - 知识库卡片补充共享权限标签:参考 Skill / 智能体卡片,在现有类型与嵌入模型标签之外新增共享范围标签(如「只读全局」),与 Agent 卡片保持一致的灰色样式。 - 收敛 Ruff CI 行为:Pull Request 与 push 到 main 均只检查、不修改仓库内容,发现问题直接标红并提示本地运行 `make format`;工作流仅保留 `contents: read` 权限,不再自动提交或创建修复 PR。 - 优化知识库详情页自动轮询:轮询从固定 1s `setInterval` 改为链式调度,等上一轮知识库信息与文件列表请求全部返回后再排下一轮,慢接口下不再出现请求堆积重叠;全库 `processing_count` 持续不变时按退避因子拉长间隔(上限 30s),连续多轮无进展即自动停止轮询;离开详情页时 `onUnmounted` 主动清理定时器。 - 统一工具调用展示名称映射:知识库工具显示名改为后端 `display_name` 定义,前端拉取完整工具元数据;工具卡片与折叠摘要按「工具列表 display name → middleware 兜底映射 → 工具 id」统一展示。 - 侧边栏对话列表新增 Thread 运行状态:以 `AgentRun` 为事实来源,后端把每个线程最新顶层 chat/resume Run 聚合成 `thread_status`(进行中显示 loading、已终态未查看显示 ready 点、已查看或无 Run 为 done),列表接口一次窗口查询完成聚合、不逐项请求;`Conversation` 新增 `last_viewed_run_id` 持久化查看边界,`POST /api/chat/thread/{id}/viewed` 幂等标记已读。前端打开线程、当前线程收到终态/中断事件时自动标记已读,发送或恢复 Run 时置为 loading,侧边栏可见时低频轮询刷新后台线程状态;历史线程上线时按各自最新顶层 Run 一次性回填为已读,新建线程写入未读哨兵避免回填误清新产生的未读点。 - 修复 Thread 运行状态两个正确性问题:终态/中断事件仅在事件线程等于当前打开线程时才自动标记已读,后台线程完成保留 ready 点直至用户打开;无 chat/resume Run 的历史会话(agent_call / agent_evaluation 调用、从未对话过的线程)在回填时写入未读哨兵,使回填探测条件收敛为 false,避免每次启动都重复对 `agent_runs` 做全表聚合。 - 优化侧边栏对话列表操作渐隐:`.actions-mask` 三态(默认/悬浮/激活)渐隐统一为线性延伸至操作按钮左边缘(距右缘 28px)再转为实色,修复悬浮时渐隐铺满整条遮罩导致按钮下方文字残留鬼影、以及三处渐隐宽度不一致的问题。 ## v0.7.1 (2026-07-17) ### 安全 - 生产 Compose 不再回退到公开的 Neo4j、MinIO 和 PostgreSQL 默认凭证,并要求显式配置 JWT 随机密钥与实例标识;相关配置缺失时会在解析阶段拒绝启动并提示具体变量名。管理员初始化、创建用户、创建部门管理员及修改用户密码在前后端统一要求密码不少于 8 位。 - 修复沙箱执行边界:每个动态 Docker 沙箱使用只与 provisioner 相连的独立网络,沙箱之间不能互访,也不再加入业务 `app-network` 或发布随机宿主机端口;provisioner 重启后会重新接入已有沙箱网络,清理时只删除自身创建且标签匹配的网络。API/worker 使用至少 32 字符的 `SANDBOX_PROVISIONER_TOKEN` 调用 provisioner,并通过认证代理访问沙箱文件与命令接口,代理在应用生命周期内复用 HTTP 连接池。生产 Compose 同时移除 PostgreSQL 和解析服务的宿主机端口,阻断沙箱对其他租户、业务数据库、对象存储和无鉴权 provisioner 的横向访问。 - 公开头像和 Agent 图片改用同源 `/minio/public/...` 地址,由开发 Vite 和生产 Nginx 只读代理 `public` bucket;MinIO `9000` 对象 API 与 `9001` 管理控制台无需对外开放,私有 bucket 不进入前端代理。 - Markdown 渲染兼容历史 PDF 解析结果中的 `http(s)://:9000/public/...` 图片链接,在展示时转换为同源 `/minio/public/...`,无需批量重写 MinIO 中已有的 `.md` 文件或重新解析文档。 ### 破坏性变更 - 沙箱 provisioner 现在强制要求 `SANDBOX_PROVISIONER_TOKEN`。升级前运行初始化脚本自动补生成,或手工使用 `openssl rand -hex 32` 生成并写入 `.env` / `.env.prod`;API、worker、provisioner 必须使用同一个值,但不能把它写入 `sandbox.env`。已有动态沙箱会因网络不匹配被 provisioner 删除并按新网络重建。 - API Key 收紧到具体用户:`api_keys.user_id` 收紧为非空,启动 schema 演进会先清理 `cli_auth_sessions` 中对未绑定 API Key 的引用,再 `DELETE FROM api_keys WHERE user_id IS NULL`,最后 `ALTER COLUMN user_id SET NOT NULL`。**升级前请在 0.7.0 库执行 `SELECT id, name, department_id FROM api_keys WHERE user_id IS NULL;`**,决定每个未绑定 Key 的归属用户并手动 `UPDATE`,未绑定的 Key 升级后会被静默删除且无法恢复;清理前后端日志会输出 `Schema migration will delete N unbound API key(s)` 告警以便回溯。 - Dashboard 收紧到 superadmin:所有 `/api/dashboard/*` 端点从 `get_admin_user` 收紧为 `get_superadmin_user`,前端路由同步收紧。0.7.0 中创建过 `role='admin'`(非 superadmin)的运维用户升级后将失去 Dashboard 访问权限,且应用内无自助提权路径;升级前请在数据库中将需要继续访问 Dashboard 的 admin 用户 `UPDATE users SET role='superadmin' WHERE uid=...`。首装场景的首个管理员始终是 superadmin,新部署不受影响。 - CORS 生产环境默认拒绝跨域:CORS 改为通过 `YUXI_CORS_ORIGINS` 显式配置允许来源;`YUXI_ENV=production` 且未设置该变量时返回空列表(拒绝所有跨域),显式设为 `*` 时会自动关闭 credentials。**前后端跨域部署的运维请在升级前设置 `YUXI_CORS_ORIGINS=https://your-frontend.example.com`**,否则浏览器跨域请求将被拒绝;同源部署(前端与 API 同源)不需要额外配置。 - 系统配置接口权限下放:`GET /api/system/config` 由 admin 收紧到任意登录用户可读,便于普通用户读取 `default_ocr_engine` 等运行时配置;接口会暴露 `sandbox_provisioner_url`、`sandbox_virtual_path_prefix`、默认模型 ID 等基础设施信息(不包含任何密钥/Token),如有更高保密要求请通过反向代理限制该路径。 ### 开发记录 - 修复 Milvus 知识图谱子图查询忽略 `max_depth` 的问题:查询会按请求深度展开路径,并完整返回路径中的中间节点与关系;排除 Chunk 时同时限制整条路径,避免通过 Chunk 间接扩展。路径结果继续遵循现有节点和边数量上限。 - 修复线程文件接口的同步文件 I/O 阻塞:交付物预览仅异步读取媒体类型识别所需的 512 字节文件头,不再同步加载完整文件;线程文件全文读取和目录扫描下沉到工作线程,避免大文件或大目录并发访问时阻塞 API 事件循环。 - 修复应用 lifespan 关闭时未释放共享 Neo4j driver 的问题,避免同进程重载或重复启动后残留图数据库连接。 - 修复删除 Milvus 知识库阻塞事件循环:`MilvusKB.delete_database` 恢复异步基类契约,并将同步的主集合与图集合清理下沉到工作线程,避免删除期间阻塞其他对话和 SSE 推送。 - 修复 Agent 对话流式输出时的前端性能问题:自动滚动改为监听 `conversations` computed 的顶层引用变化,不再对完整对话与消息树执行深度 watch,避免每个 token 到达时递归遍历全部历史消息。 - 修复删除知识库文件图谱时清理范围过宽:Neo4j 仅删除本次文件 `MENTIONS` 边触及且已无任何 `MENTIONS` 引用的实体,不再顺带删除同知识库内其他文件遗留的孤儿实体。 - 对照当前解析器、知识库工具和 Agent 运行链路重整正式文档:补充默认 OCR、文件级处理参数、工作区 `AGENTS.md` / `USER.md` / `MEMORY.md`、知识库 `knowledge-base` Skill、`search_file`、`ocr_parse_file`、子智能体进度和图片 OCR 回退语义;更正知识库工具使用 `kb_id`、MCP 配置按数据库实时读取等过时描述;移除正式文档中的问答式栏目。 - 统一用户菜单的设置入口:管理员与普通用户均显示“设置”,打开后默认进入账户设置;管理员专属的基本设置、用户管理等标签继续按原权限展示。 - 工作区 `agents` 目录新增 `USER.md` 与 `MEMORY.md` 上下文文件,并与 `AGENTS.md` 一起在 Agent 运行开始时加载;三个默认文件首次创建时均写入对应标题和说明,不再生成空文件,已有内容保持不变。 - 新增 Summary 上下文压缩实时状态流式同步:`YuxiSummarizationMiddleware` 触发压缩时通过 `langgraph.config.get_stream_writer()` 推送 `yuxi.context_compression` 自定义事件(started/completed/failed),复用 DeepAgents 已有 `_summarization_event` 作为完成数据源;`base.py` 通过 `astream_events(version="v3")` 的 `CustomTransformer` 透传 custom 流,`chat_service`/`agent_run_service` 将事件映射为 `context_compression` chunk 并透传到前端;前端收到 `started` 时将"正在生成回复"加载态文案切换为"正在压缩上下文",压缩结束(`completed`/`finished`)即切回,不额外渲染分隔符、不保留压缩完成态。为避免摘要 LLM 调用的 token 流被 LangGraph messages stream 捕获并广播成 phantom 摘要消息,重写 `_create_summary`/`_acreate_summary` 在摘要模型 invoke 的 config 上挂 `TAG_NOSTREAM`,让流式层在源头跳过该调用,主 messages 流天然只含用户可见回复,无需 `chat_service` 下游过滤(参考 DeerFlow 实现)。异步 L2 压缩路径的 `_aoffload_to_backend` 与 `_acreate_summary` 改回 `asyncio.gather` 并发执行,与 DeepAgents 父类一致,避免串行等待一次文件 I/O 与一次摘要 LLM 调用;两路复用 `_SUMMARY_SANITIZED_MESSAGES` 的 id 缓存。L1-only 调用若仍触发 provider context overflow,会回落到 L2 summary 后重试;`summary_tool_result_token_limit` 默认改为 300,并同时作为 L1 工具结果 offload 阈值和预览上限,L2 只消费 L1 视图,不再对工具结果做第二轮 offload;L2 摘要模型的待摘要历史输入上限改为与 `summary_threshold` 对齐,避免固定 4000 token 裁剪丢失早期历史;新增 `summary_l2_trigger_ratio` 管理 L1 后进入 L2 的比例阈值,默认 `0.4`。 - 知识库文件列表新增文件级 Token 内容量与创建人头像、用户名展示,内容量悬浮可查看 Chunk 数量;创建人复用全局用户头像解析规则,长用户名省略且悬浮可查看完整名称。 - 知识库编辑配置表单移除“自动生成问题”开关,不再由前端编辑或提交该配置。 - 知识库详情页新增整页内容加载态:切换或首次进入详情时,在知识库信息返回前仅展示居中 loading,避免标题、标签页和文件区域先渲染旧数据或空状态。 - 修复知识库文件处理中频繁刷新时,旧目录请求覆盖当前子目录列表并造成列表抖动的问题。 - `InfoCard` 新增统一的 `card-more-action-corner` 菜单插槽,并在组件内部固定渲染横向三点按钮;更多操作从卡片绝对定位改为进入 header 的正常 flex 布局,与图标、标题和 `status` 共享同一垂直中心线,业务页面只能提供菜单内容;智能体、知识库和用户管理卡片均改为复用该组件与菜单能力,用户部门/角色标签使用现有 `status` 插槽展示在标题区右侧,菜单图标与文字使用统一行高居中,知识库菜单支持复制 ID、直接打开编辑弹窗,以及确认后删除并刷新列表。 - 智能体管理页的普通智能体卡片新增“去对话”入口,点击后进入新建对话并预选对应智能体;子智能体卡片不展示该入口。 - 修复 API/Worker Docker 镜像构建失败:后端项目要求 Python `>=3.12,<3.14`,Dockerfile 基础镜像与 `.python-version` 同步到 `python:3.13-slim`,并将 `docker/api.Dockerfile` 的 `COPY` 源路径改为相对仓库根目录的 `backend/...`,与 `docker-compose` 中 `build.context: .` 保持一致;同时移除 `uv sync` 对 BuildKit `--mount` 的依赖并启用 `--no-cache`,避免分别因 Python 版本不兼容、`../backend/...` 越出 build context、未启用 BuildKit 或 uv 缓存残留导致镜像构建失败或体积膨胀。 - 新增用户级配置:保留现有全局配置链路不变,新增 `user_config` 表、`UserConfigSchema` 与无缓存的 `UserConfig` PostgreSQL 读取/保存入口;新增 `/api/user/config`,所有登录用户可读写自己的配置。首个字段为 `enable_memory`(是否启用 Memory),作为预留开关仅持久化与展示,不接入运行逻辑;设置弹窗新增“用户配置” Tab 展示并保存该开关。 - 优化 Skills 管理页展示文案:补充推荐 Skills 与内置 `mysql-reporter` 的卡片描述,避免短描述在两行卡片布局下显得过空。 - 新增 PaddleOCR 云端 API OCR 解析器:支持 `paddleocr_vl_1_6` 调用 `PaddleOCR-VL-1.6` 输出版面 Markdown,支持 `paddleocr_pp_ocrv6` 调用 `PP-OCRv6` 输出纯 OCR 文本;解析器复用 PaddleOCR jobs 提交、轮询与 JSONL 下载逻辑,健康检查仅校验 `PADDLEOCR_API_TOKEN` 配置状态,不创建真实 OCR 任务;知识库上传与临时附件解析弹窗同步增加两个 OCR 选项。 - 优化对话消息代码块交互:助手消息中的 Markdown 代码块右上角新增简约复制按钮,支持点击快速复制代码内容并显示短暂“已复制”反馈。 - 新增 Markdown `html:preview` 辅助可视化预览:仅显式标记的围栏会渲染为 sandboxed iframe,普通 `html` 继续展示源码;预览使用清洗后的静态 HTML/CSS `srcdoc`,按内容自适应高度并最高限制为 700px,超高时保留 iframe 内滚动,流式输出期间复用预览节点避免闪烁;内置 Agent Prompt 同步约束 Markdown 仍为回答主体,HTML 只补齐指标、对比、时间线、关系结构等可视化短板,不承载大段叙事、完整报告或正文解释。 - 新增历史对话搜索:侧边栏增加“搜索对话”入口,打开命令面板式弹窗,支持默认最近对话、新对话入口、搜索中骨架屏、结果列表、方向键选择与 Enter 跳转;后端新增 `/api/chat/threads/search`,按当前用户 active 对话中的非工具消息 `content` 检索并按对话聚合返回命中片段,同时将侧边栏导航项高度统一调整为 32px。 - 模型供应商管理前端开放 Anthropic provider type:Provider Type 下拉仅保留 OpenAI Completions API 与 Anthropic Messages API 两种可选项,保存值继续使用后端枚举,并在供应商卡片中展示友好类型名称。 - 优化 Agent 状态面板子智能体弹窗:弹窗消息列表复用对话消息渲染路径,打开运行中的子智能体时会展示主 run SSE 已路由到 child thread 的流式消息,并在生成中保持与主对话一致的处理态;修复当前 run 的历史半成品消息与 ongoing 流式片段叠加导致同一个子智能体在主对话中重复展示的问题,子智能体状态查询工具不再渲染成独立 Agent 卡片,弹窗会随子智能体条目补齐 run_id 后订阅对应 SSE,并复用主对话的流式平滑输出与底部跟随滚动控制;已完成的子智能体改为直接读取持久化 Message 历史,不再从 Redis run event 重放渲染。 - 增强异步子智能体 `subagent_status`:状态查询会从子 run 的 Redis 事件流反向提取最近 3 条可读进度摘要,并在工具卡中优先展示,终态结果读取语义保持不变;同时移除模型侧 `subagent_events` 工具,Redis 原始事件流继续仅供运行基础设施与前端 SSE 使用,避免包含重复 metadata、query 与嵌套 payload 的事件信封进入模型上下文并被写入 `large_tool_results`。 - 优化任务中心(Tasker)定位为「后台作业实体 + 只读进度面板」。前端修正失效的任务类型标签、状态判断收敛、任务详情补充参数/结果,并把轮询收敛到 store 修复抽屉关闭后角标不更新;取消操作提交后提示“取消请求已提交”,不再将请求受理误报为任务已取消。后端 `TaskContext` 暴露 `payload` 消除私有穿透,进度更新按增量节流降低写放大,新增终态任务保留上限自动裁剪内存与数据库,`_load_state` 恢复历史任务使任务中心重启后仍可见。修复运行中任务关闭时 `shutdown()` 持有状态锁等待 worker、worker 又等待同一锁写入取消状态形成的死锁;生命周期操作改用独立锁串行化,等待 worker 前释放状态锁,并区分服务关闭取消与任务协作式取消,确保关闭能够完成且普通任务取消不会损失 worker。后台任务增加默认 6 小时且可通过 `TASKER_DEFAULT_TIMEOUT_SECONDS` 调整的执行上限,入队时可按单任务覆盖;超时会取消并等待业务协程清理后释放 worker,知识库文件与评估任务同步退出“处理中”状态。 - 知识库访问能力迁移为内置 Skill:新增 `knowledge-base` Skill,绑定 `list_kbs`、`query_kb`、`find_kb_document`、`open_kb_document`、`get_mindmap` 等知识库工具;内置 Agent 不再默认挂载知识库工具,改为读取并激活 Skill 后按需加载,同时保留 `knowledges` 作为知识库资源范围与权限边界。Agent 配置页在启用知识库但显式未选择 `knowledge-base` Skill 时实时展示提示,保存时不阻断。修复 Skill 依赖工具的可执行性:`create_agent` 中「模型可见工具」与「ToolNode 可执行工具」是两套,仅靠 `awrap_model_call` 动态追加工具只会绑定给模型、不进 ToolNode,导致激活 Skill 后调用 `list_kbs`/`query_kb` 报 `not a valid tool`;现由 `resolve_configured_runtime_tools` 统一把所有可见 Skill 依赖的本地工具随基础工具一起注册进 ToolNode(可执行),`SkillsMiddleware` 运行期再按 Skill 激活状态门控模型可见性(保持按需加载)。新增 `search_file` 工具支持按文件名关键词跨/指定知识库搜索文件,并已加入 `knowledge-base` Skill 的依赖工具;其分页统计基于全量扫描结果计算 `total`/`has_more`,避免按 `limit+offset` 截断导致计数失真。 - 增强知识库工具结果豁免:`open_kb_document` 工具结果加入 Summary 卸载豁免名单,避免大文档窗口被摘要后丢失上下文。 - 新增 Yuxi Python CLI 首版底座:新增独立 `packages/yuxi-cli` 包,提供 `remote add/use/list/ping`、`login --browser`、`login --api-key`、`whoami`、`status`、`logout`;配置统一写入 `~/.yuxi/config.toml`,remote URL 只保留实例入口并派生 `/api` 请求路径。后端新增 `/api/auth/cli/sessions` device flow 授权接口与 `cli_auth_sessions` 持久表,浏览器确认后为当前用户创建一次性返回的 API Key;新增公开 `/api/system/discovery` 声明服务端版本、API 前缀、CLI 能力和关键端点,CLI 登录前校验服务端版本至少为 `0.7.1`(`0.7.1.dev*` 按 release tuple 兼容)及对应能力;前端新增 `/auth/cli/authorize` 授权确认页。补充 CLI 本地单测与后端服务/路由单测。 - 安全与健壮性加固:token 兑换接口改为 `POST /api/auth/cli/sessions/token`,`device_code` 改走请求体,避免凭据出现在访问日志的 URL 路径中;兑换与批准会话时对会话行加 `with_for_update` 行锁,防止并发/重试导致重复签发 API Key;CLI 浏览器登录轮询区分瞬时错误(网络层错误、5xx)与终止错误,瞬时错误继续重试而非中断整个登录;`config.toml` 以 `0600` 原子创建并对名称等写入值做引号/反斜杠转义,避免明文凭据短暂可读及特殊字符破坏配置;API Key 认证在绑定用户失效时改为直接拒绝,不再 fallback 到部门管理员或 superadmin,创建 API Key 时校验部门与关联用户一致,用户软删除会同步禁用其 API Key;进一步要求 API Key 必须绑定具体用户,启动 schema 演进会清理历史未绑定用户的 API Key 并将 `api_keys.user_id` 收紧为非空;Dashboard 管理接口与前端入口改为仅 superadmin 可访问;用户软删除脱敏名改用用户主键生成,避免短哈希碰撞触发唯一索引冲突;前端授权页新增确认提示与对结构化错误 `detail` 的兼容渲染。 - 收敛 API Key 生成逻辑:移除独立 API Key 生成服务,统一通过 `AuthUtils.generate_api_key()` 生成 CLI 授权与用户管理中的 API Key。 - 收敛认证模块命名:CLI 浏览器授权路由合并到 `auth_router.py`,授权会话服务迁移到 `auth_service.py`。 - 为 CLI 知识库上传补齐后端接口边界:discovery 新增 `cli.kb_upload` 能力声明;普通文件上传接口在传入 `kb_id` 时先校验知识库存在且支持文档,校验通过后才读取文件或写 MinIO;新增同步 `POST /api/knowledge/databases/{kb_id}/documents/add`,用于把已上传的 MinIO 文件添加为知识库文档记录但不解析、不入库、不进入 Tasker;新增 `GET /api/knowledge/databases/{kb_id}/documents/exists?filename=...`,用于上传前按文件名或相对路径检查知识库内是否已有同名文件;旧 `/documents` ingest 入口保留兼容,但在 enqueue 前补充空 items、非 MinIO URL 与缺失 content hash 的请求级校验。 - 新增 `yuxi kb upload` 上传命令:默认仅包含 `.md/.txt/.docx/.html/.htm`,省略 `--kb-id` 时会从 remote 拉取并只展示支持文档上传的知识库,支持非全屏的方向键单选知识库与多选文件类型;支持 `--include-ext/--exclude-ext` 与 `--concurrency` 控制本地并发队列,并发默认 10、上限 300;交互终端上传阶段显示进度条,非交互输出保留文本进度;每个并发单元默认会先按相对路径调用 `/documents/exists` 检查知识库中是否已有文件,存在则直接跳过,传入 `--force-upload-file` 时跳过该预检并完全依赖上传接口的重复文件校验;单文件上传成功后立即调用 `/documents/add` 添加该文件记录,不触发解析/OCR/入库;目录上传通过 `source_paths` 保留相对路径,后端创建文件记录时使用该路径作为展示文件名以保持前端目录层级;上传接口返回“同内容文件已存在”时按已上传过跳过,不再作为错误展示;大批量上传调度改为有界提交,避免数十万文件时一次性创建全部 future 导致资源峰值过高。 - 发布 `yuxi-cli` 到 PyPI,并新增 GitHub Release 触发的 PyPI Trusted Publishing 工作流;文档新增命令行工具使用说明;CLI 运行访问 remote 的命令前会先输出当前 CLI 版本、remote 名称和 URL。CLI 输出测试在断言前去除 ANSI 样式,避免 GitHub Actions 的强制彩色输出拆分版本号、URL 与参数名并误阻塞 PyPI 发布。 - 修复知识库文件入库/解析成功却被统计为失败(#793):成功的文件元数据会固定携带 `error: None`,而后台任务此前以「结果中是否存在 `error` 键」判定失败,导致成功项也被计入失败数并在全部成功时仍抛出「处理完成,失败 N 个」。改为统一通过 `_is_failed_item` 按「显式 `status == failed` 或非空 `error`」判定,覆盖入库、解析、单独解析/入库三处统计。 - 修复 Windows 初始化脚本自动生成 JWT 配置失败(#804):`init.ps1` 改用 Windows PowerShell 兼容的 `RandomNumberGenerator.Create().GetBytes(...)` 生成随机字节,避免旧 .NET 环境缺少 `RandomNumberGenerator.Fill()` 导致按 Enter 自动生成时报错。 - 优化 Bash 与 Windows 初始化脚本:目标镜像标签已存在时直接跳过重复拉取;已有 `.env` 会逐项检查必填 API Key、JWT 密钥、实例 ID 和 Sandbox Provisioner Token,缺失或为空时提示输入,安全配置支持回车生成,并避免写入重复键。 - 优化知识库文件列表状态流转与文件预览边界:`uploaded/parsed/error_parsing/error_indexing` 状态分别展示解析、入库或重试操作;源文件预览与解析后的 Markdown 查看分离,txt/图片/Markdown/HTML/PDF/代码类按源文件类型预览;Office 源文件仅支持 `.docx/.pptx`,点击预览时按需生成并缓存 PDF 预览内容,由同一个预览接口直接返回,不再把解析 Markdown 产物当作源文件预览。 - 收敛知识库分块策略选项来源:后端以单一 `CHUNK_PRESETS` 配置派生 preset id、描述和选项列表,并新增 `/api/knowledge/chunk-presets`;前端分块策略选择器改为通过接口读取选项,避免前后端重复维护同一份文案。 - 优化大规模知识库文件列表加载:知识库详情接口默认不再返回全量 `files`,新增按 `parent_id/path_prefix/page/page_size/status` 查询的轻量文件列表接口;前端文件管理页改为目录懒加载与服务端分页,后端按 `source_path`/路径型文件名聚合虚拟目录,列表项只保留交互所需字段,顶部统计改用后端聚合结果,避免数十万文件场景下前端全量建树和传输压力。工作区知识库文件浏览统一改用同一套分页懒加载查询,支持真实目录和虚拟目录页码分页,非文档型知识库不再出现在工作区文件源中;文件浏览组件和后端列表接口均不再承载文件名搜索,后续搜索能力由独立后端接口和组件实现;文件列表展示抽出共享 `FileBrowserTable`,知识库详情和工作区共用展示层,并移除原知识库文件列表拖拽移动入口。 修复二级目录点击“全部文件”仍沿用当前 `parent_id` 的问题,显式根目录导航会清空目录上下文并重新加载根目录。 - 优化知识库启动元数据加载:服务启动时不再把全部 `knowledge_files` 记录加载进 `self.files_meta`,文件解析、入库、预览、下载、打开内容等单文件操作改为按 `file_id` 从数据库懒加载;文件状态流转改为通过数据库窄字段更新和状态条件更新完成,移除进程内处理队列修复逻辑,避免 api/worker 多进程下出现虚假的状态修复;文件统计刷新改用数据库聚合,文件大小补全从启动阶段移入显式统计修复任务,并收敛处理参数合并日志,避免大规模文档场景下启动内存和日志压力随文件数线性放大。 - 调整知识库待处理统计卡行为:文件管理顶部“待解析/待入库”统计卡从状态筛选改为提交对应后台处理任务;新增按待处理状态批量解析/入库接口,任务内按 500 条游标分页读取文件 ID,避免前端一次拉取和提交海量 ID;显式选中文件解析/入库接口增加 1000 个 ID 的单次上限。 - 修复大规模知识库统计修复失败:`repair_missing_file_stats` 不再对未入库文件查询 chunk 表,未入库文件残留的 chunk/token 统计会归零;chunk repository 的批量 `IN` 查询统一分批执行,避免 asyncpg 单条 SQL 参数超过 32767。 - 优化思维导图构建接口设计,支持增量构建和更新:新增 GET /mindmap/diff 接口检测文件变更,POST /mindmap/generate 新增 incremental 参数支持增量更新;纯删除场景无需 AI 调用(递归树手术),新增文件时 AI 整合进现有分类结构;思维导图文件加载改为显式 repository 查询,增量 diff 会按已追踪 file_id 补查分页外文件,避免把分页文件列表误当全量文件集;前端导图 Tab 新增"增量更新"按钮和变更数量 badge。修复删除文件后知识导图仍展示旧内容:单文件删除接口成功后调用 `remove_file_from_mindmap`、批量删除接口成功后调用 `batch_remove_files_from_mindmap`,同步移除导图快照中对应叶子节点,无需用户再手动增量更新。 导图文件查询改为覆盖知识库全部目录中的非文件夹记录,根目录为空但子目录有文件时也可正常生成和增量检测。 - 优化文档结构与智能体运行说明:项目简介去除对 LangGraph 具体版本的强调;中间件文档按当前内置 Agent 链路重写,补充知识库工具、Skills 激活、附件/文件系统、子智能体 task、Summary 上下文压缩与工具结果卸载机制;知识库文档补充知识导图与示例问题生成机制;Langfuse 集成文档从“智能体开发”移动到“高级配置”分组。 - 移除知识库普通上传接口遗留的 `allow_jsonl` 参数,上传类型判断统一依赖 `SUPPORTED_FILE_EXTENSIONS`;评估数据集 JSONL 继续通过独立评估接口上传。 - 修复 Dependabot esbuild 告警:web 与 docs 统一锁定 `esbuild@0.28.1`,docs 同步升级 Vite/Vue 插件 override 并固定 pnpm 版本,避免旧锁文件继续解析到存在漏洞的 esbuild 版本。 - 修复 CORS 与依赖安全告警:后端 CORS 改为通过 `YUXI_CORS_ORIGINS` 配置允许来源,开发环境默认仅允许本机前端端口,生产环境未配置时不开放跨域,显式使用 `*` 时会关闭 credentials;同步刷新前后端锁文件,将 `aiohttp`、`cryptography`、`langchain`、`langchain-anthropic`、`pypdf`、`python-multipart`、`starlette`、`pyjwt`、`torch`、`torchvision`、`dompurify`、`js-yaml`、`markdown-it`、`vite` 升级到安全版本。 - 修复添加/编辑 MCP 弹窗中环境变量无法新增的问题:环境变量编辑器存在 rows -> object -> rows 的双向同步回环,`modelValue` 变化时会完全根据已有 key 重建行,导致只填了 key 的行(含刚点击「添加变量」生成的空行)被过滤掉而无法新增;现在仅当传入值与组件自身 emit 的内容不一致时才重建行,避免回声覆盖未填 key 的行。 - 修复模型与知识库后端导入循环:`yuxi.models` 改为惰性导出模型选择函数,知识库可见范围和知识库工具延迟读取全局 `knowledge_base` 实例,避免单测、热重载或轻量导入知识库包时因模块尚未完成初始化而失败。 - 修复知识库创建权限持久化一致性:创建知识库时由 Manager 归一化 `share_config/created_by` 后作为受控记录字段随首次知识库元数据插入写入数据库,避免先插入基础记录再二次更新权限字段产生短暂不一致。 - 修复 HTML 预览 iframe 高度问题:侧边预览模式改为 `height: 100%` 适应父容器,避免底部内容裁切;全屏预览模式移除 `min-height: calc(80vh - 40px)`,避免短内容下方白边;iframe 设为 `display: block` 消除行内基线间隙导致的底部白边;全屏渲染改用独立 `srcdoc`(不注入 `zoom`)按 100% 显示,侧边预览仍保持 0.75 缩放。 - 对话消息图片支持点击全屏预览:对话中用户上传的图片支持点击放大查看,复用文件预览的全屏蒙层交互(Teleport 蒙层,点击图片/空白处或按 Esc 关闭),不引入额外依赖。 - 新增 Agent token usage 状态快照,在状态面板中作为普通可折叠分组展示完整 `messages`、当前传给 LLM 的 `messages`、system/tools 构成、输入构成堆叠条和上下文窗口占用估算。 - 优化 Agent token usage 状态面板展示:后端补充 LLM 内容消息与工具消息的 token/count 拆分字段,前端将内容消息、工具消息、系统消息与工具定义分开展示,并修正上下文窗口/剩余信息换行与对话流式输出期间的底部跟随滚动。 - 收敛 Agent `read_file` 多模态边界:仅 UTF-8 文本和图片可读,PDF/Office 文档会引导使用 `ocr_parse_file` 转为 Markdown,音视频及未知二进制不再注入模型消息;OpenAI 兼容链路的 tool-role 图片桥接从私有 payload 覆盖迁移到公开模型中间件,Provider 明确拒绝图片输入时会自动调用 `ocr_parse_file` 提取文字,并在后续请求中移除同一张历史图片,避免文本模型重复报错。 - 新增默认 OCR 解析引擎配置 `default_ocr_engine`,普通登录用户可读取系统配置;知识库上传弹窗与临时附件解析弹窗默认选中系统默认 OCR,解析入口仅在未显式传入 `ocr_engine` 时使用该默认值。修复读取该配置时因反向导入知识库模块导致配置初始化循环、并中断后续配置加载的问题;OCR 注册表改为轻量模块,知识库单例迁移到显式 runtime 入口,解析器调用方直接导入真实定义模块,包初始化不再加载运行对象。 - 新增 Agent 内置 `ocr_parse_file` 工具:只允许解析 `/home/gem/user-data/{workspace,uploads,outputs}` 下的沙盒虚拟路径文件,使用指定或系统默认 OCR 引擎生成 Markdown,并把结果写入 `outputs/ocr/*.md`;工具返回结果文件路径、字符数和短预览,不写入知识库 MinIO,也不创建知识库文件记录。 - 收敛 Agent Invocation 服务边界:新增 `agent_invocation_service.py` 承接 agent-call/eval 的外部调用语义、同步等待、异步响应与 OpenAI-compatible 响应装配;`agent_invocation_router.py` 收敛为 HTTP 适配层,`agent_run_service.py` 只保留通用 AgentRun 生命周期能力,`subagent_run_service.py` 改为调用公开 AgentRun 创建 API,不再穿透私有函数。 - 修复 Agent 状态读取与消息落库在重新读取 LangGraph checkpoint 时未传入运行时 context 的问题,避免主智能体或子智能体线程因系统默认模型已不可用而查询状态/保存历史失败;模型供应商管理页新增默认模型保护,阻止删除、停用默认模型所属供应商或移除当前默认模型。 - 评估数据集自动生成支持断点续跑:生成过程中按 `YUXI_DATASET_PERSIST_BATCH_SIZE`(默认 1)批量持久化已生成的题目,任务失败或中断后可从已持久化进度继续生成;新增 `POST /api/evaluation/databases/{kb_id}/datasets/{dataset_id}/resume` 接口与前端"继续生成"按钮。修复生成器先收集后产出导致批量持久化在生成中途不生效的问题:改为 worker 产出即流式回报、消费端按 attempt_no 重排输出,异常或取消时已产出未落库的题目(含队列中未消费与 buffer 残余)一并保存;恢复接口改用原子化入队,消除并发恢复创建重复任务引发的唯一约束冲突。失败数据集支持查看已持久化题目:数据集详情接口状态限制放宽为 completed/failed 白名单,前端放开失败数据集的点击查看,下载与发起评估仍仅限生成完成。 - 优化 Agent 上下文压缩:Yuxi 的 DeepAgents summary adapter 在生成 summary 与写入 conversation history 时,会先对本次模型调用的临时消息视图执行 L1 结构精简,截断旧 `write_file`/`edit_file` 大参数,并把超过阈值的大 `ToolMessage.content` 写入 `outputs/large_tool_results` 后替换为路径和有限预览;L1 不修改 LangGraph state 原始消息,L1 后若上下文低于入口阈值的 40% 则直接调用模型,不生成 summary event,仍超过时才进入 L2 summary。L2 继续使用 DeepAgents `_summarization_event.cutoff_index` 重建 effective messages;Summary 阈值判断改为使用 Yuxi 自己的近似 token 计算结果,不再根据 provider `usage_metadata.total_tokens` 或 usage scaling 提前触发;首次写入 `conversation_history` 前读取旧文件的 sandbox 404 会按 `file_not_found` 处理,不再产生误导性 warning;`present_artifacts` 会拒绝展示 `large_tool_results` 与 `conversation_history` 等工具调用阶段文件。新增管理员可配置项 `summary_keep_messages`、`summary_prompt`、`summary_tool_result_token_limit` 与 `max_execution_steps`,分别控制摘要后保留消息数、摘要提示词、summary 阶段工具结果预览上限和 LangGraph `recursion_limit`。 - 收敛普通聊天模型加载链路:`select_model` 保留旧 `.call()` 调用契约,内部改为通过 LangChain chat model adapter 复用 Agent 侧模型加载器,统一 OpenAI-compatible、Anthropic 与 Gemini 等 provider 的运行时适配;移除旧 `OpenAIBase` wrapper,默认重试策略迁移为 LangChain provider 参数。 - 统一 Redis 客户端管理:新增 `yuxi.storage.redis` 作为 Redis 配置、短生命周期同步客户端、共享异步客户端与 ARQ RedisSettings 的唯一基础设施入口;运行队列、系统配置快照同步、模型缓存和 worker 不再各自散落读取 `REDIS_URL` 或直接创建 Redis 客户端,Redis 连接失败日志统一使用脱敏 URL。 - 新增系统配置 Redis 快照同步:管理员保存配置时仍以 `saves/config/base.toml` 作为唯一持久化来源,成功写入后将可运行时同步的公开配置字段写入 `yuxi:runtime_config`;API 与 worker 进程在启动时各拉起一个后台同步线程,按 5 秒间隔从快照刷新内存值,读取端按普通属性访问、无需感知,Redis 不可用时继续使用当前内存值。`save_dir` 是启动期内部路径配置,不在管理员配置中展示、不从 `base.toml` 读取、不写入 Redis 快照且不支持通过管理员配置接口修改;sandbox 相关配置仍属于启动期敏感配置,运行中的已初始化组件不承诺完整热更新,修改后仍需重启保证生效;移除已无运行时调用点的 `enable_reranker` 与 `default_agent_id` 配置字段。 - 优化 FastAPI 请求链路并发能力:Milvus 知识库检索中的同步 embedding、向量/BM25/混合检索调用,以及图谱查询中的同步 Milvus/Neo4j 读操作(含连接建立)统一通过有界 `asyncio.to_thread` 在线程中执行,避免阻塞 API 事件循环;并发上限按事件循环懒加载信号量控制,不改变检索默认行为与参数上限。 - 修复 AgentRun worker 在 LLM 流式响应期间长期占用 PostgreSQL 连接:chat 与 resume 在完成运行时解析、会话和附件等预处理后,进入流式执行前显式提交事务并归还业务连接,最终消息保存时再按需获取连接。 - 修复异步文档解析阻塞 API 事件循环:DOCX、PPTX、XLS/XLSX、DOC、CSV 与 HTML 的同步转换统一下沉到工作线程,文本读取改用异步文件 I/O;Docling 单例转换增加线程互斥,避免并发解析共享转换器,并补充事件循环可继续调度的回归测试。 - 改进 OpenAI 兼容提供商流式工具调用兼容(替代 v0.7.0 的按 provider 禁流式处理):根因是 LangGraph v3 流式累积对 tool_call 字段“后值覆盖”,SiliconFlow、阿里云百炼等在参数续片里把 `name`/`id` 下发为空字符串覆盖首片真实值。改为 `_ToolCallChunkFixChatOpenAI` 把续片空串 `name`/`id` 归一化为 `None`,对所有 OpenAI 兼容 provider 通用生效且保留流式,移除原 `_NON_STREAMING_TOOL_CALL_PROVIDERS` 名单。 - 新增 Agent 评估运行入口:`POST /api/agent-invocation/eval/runs` 会创建正常对话与 AgentRun,复用 worker 执行链路,并以 `source=agent_evaluation` 与 `agent_invocation_meta.evaluation` 标记写入 conversation、AgentRun 输入消息与 Langfuse trace;接口阻塞至运行结束后直接返回最终结果(状态、最终 assistant 输出、Langfuse trace id),并支持通过 `include_trajectory_summary` 按需返回轻量工具调用轨迹摘要。`yuxi-cli` 新增 `yuxi agent eval` 命令,用于从 Langfuse 数据集读取输入并回传实验输出 - 对话消息点赞/点踩反馈接入 Langfuse score:本地 `MessageFeedback` 保存成功后,如助手消息已关联 Langfuse trace,则同步写入 `user-feedback` score,点赞为 `1`、点踩为 `0`,点踩原因写入 comment,便于在 Langfuse 中按用户反馈筛选 trace。 - 新增外部系统 Agent 调用入口:独立 `agent-invocation` router 提供 `POST /api/agent-invocation/agent-call/runs` 与 `POST /api/agent-invocation/agent-call/runs/result`,字段沿用 Yuxi 命名(`agent_slug/thread_id/request_id/model_spec`),复用 AgentRun 队列和结果读取能力;支持非流式同步等待或 `async_mode=true` 立即返回 `run_id`,Agent Call 不允许通过 `agent_call_meta.context` 覆盖 Agent context,运行时模型覆盖只允许走独立 `model_spec`;修复无 `thread_id` 且模型校验失败时提前提交空对话,导致孤儿对话和 `request_id` 失败重试非幂等的问题;Agent Call 的 `messages[].content` 兼容 OpenAI 风格的 `text`/`image_url` 多模态数组,纯文本数组不再误报 422,图片输入会保留原始 LangChain 多模态消息供 AgentRun worker 恢复;Agent Eval 与 Agent Call 统一通过 conversation-backed invocation helper 创建 run,后续定时任务等入口只需做请求解析和结果出口适配。 - 修复 Agent Invocation 创建的 eval/call 对话进入用户对话导航的问题:侧边栏最近对话与对话搜索会按 conversation metadata `source` 排除 `agent_evaluation` 与 `agent_call`,保留 run/conversation 持久化与结果追踪能力。 - 下沉 AgentRun 基础能力:将「读取某个 run 的最终结果」(`get_agent_run_result`/`load_agent_run_result`,含状态、最终 assistant 输出、Langfuse trace id 与错误)与「阻塞至 run 终结再取结果」(`await_agent_run_result`,复用有限事件流、无额外轮询)提升进 `agent_run_service`,供 chat/eval 及未来定时任务统一复用;eval 运行入口改为非流式复用该能力(不再做 SSE 封装),移除其私有结果构建逻辑(结果不变)。 - 重构 AgentRun 接口底座:`agent_run_service` 拆出内部 `create_agent_run`、`enqueue_agent_run` 与 `request_cancel_agent_run`,保留现有 `/api/agent/runs` 行为并新增 `/api/agent/runs/{run_id}/result` 结果读取接口;`AgentRunRepository` 增加按 `parent_agent_run_id` 查询 child run 的能力,为后续异步 subagent 生命周期控制预留统一入口。 - 修复子智能体流式事件兼容:Yuxi task middleware 的 DeepAgents 子智能体 transformer 改用专用 `yuxi_subagents` projection,避免与 LangChain `create_agent` 默认注册的 `subagents` projection 冲突导致运行流式消息时报错;子线程路由收集优先读取 Yuxi projection,并保留原 `subagents` fallback。 - 重构 AgentRun 与子智能体运行链路:保留现有 `/api/agent/runs` 行为并新增 `/api/agent/runs/{run_id}/result` 结果读取接口;子智能体新增 `subagent_start/status/cancel/await` 工具,支持后台启动、轻量进度查询、等待结果、取消运行和已完成 child thread 续跑;同一用户、同一子智能体、同一 conversation thread 存在运行中 run 时返回 busy,不做隐藏排队。 - 修复子智能体同步等待超时语义:`await_agent_run_result` 在有限 SSE 等待结束后会校验 run 终态,非终态时抛出明确等待超时;`task` 与 `subagent_await` 不再把仍在运行的子智能体误报为“已完成但无文本结果”,同步 Agent Call / Eval 入口遇到等待超时返回 504 和当前 run 快照。 - 收紧子智能体运行创建边界:`SubagentRunService` 显式拒绝以子智能体 run 作为父 run 创建新的子智能体,固化“不支持孙子智能体”的架构约束。 - 修复 AgentRun busy 检查的并发窗口:为同一用户、智能体和 conversation thread 的非终态 run 增加数据库部分唯一索引,并在插入冲突时返回现有 `run_busy` 结构,避免不同 `request_id` 并发启动绕过忙碌检查;AgentRun 创建冲突改用局部 savepoint 处理,避免 `_create_agent_run` 在共享 session 上 rollback 撤销调用方刚创建的子智能体线程关系或输入消息。 - 收敛 AgentRun 数据模型与输入语义:运行记录统一使用 `agent_slug`、`conversation_thread_id`、`created_by_run_id`、`input_message_id` 等字段,子智能体通过 `subagent_threads` 关系表维护 parent/child conversation 归属;补齐旧库升级时 `agent_runs` 旧字段到新字段、`subagent_threads.subagent_slug/created_by_run_id` 的静默回填与约束收敛,并在创建部分唯一索引前终结重复活跃 run,避免早期分支库保留 nullable schema 或历史重复活跃数据阻塞升级;Agent 状态中的 `subagent_runs` 改为以 `run_id` 作为执行身份,`resume` 请求字段明确为 `Command(resume=...)` 输入载荷。 - 精简旧链路与失败语义:恢复审批统一走 `POST /api/agent/runs` 的 `resume` 载荷,移除旧 `POST /api/chat/thread/{id}/resume` 流式接口和已废弃的 `chat_service.agent_chat`;子智能体运行缺少必要线程上下文时直接报错,状态查询只在真实缺失或无权访问时返回 404,内部运行记录格式异常返回 500。 - 统一流式事件线程 ID 提取契约:新增共享 `extract_thread_id` 工具,`BaseAgent`、聊天服务和 run worker 统一只读取规范化事件的一层稳定路径,并通过显式 fallback 处理父线程归属,避免递归扫描嵌套 metadata 导致父/子线程事件路由分歧。 ## v0.7.0 (2026-06-13) ### 破坏性变更 - Provider 与模型配置收敛:移除旧版 v1 模型配置与 Ollama 支持,运行时模型统一使用 `provider_id:model_id` 与独立 provider 模块;自定义 provider 实现逻辑从文件移动到数据库,并从 config 文件迁移到 provider 模块。 - 智能体运行时语义收敛:用户可见的 `AgentConfig` 收敛为数据库持久化的一级 `Agent`,内置 Python Agent 改为智能体后端;聊天、运行任务、恢复审批和文件预览均从线程绑定的 Agent 解析运行时上下文,前端只提交 `agent_id`。 - 知识库能力边界收敛:移除 Upload 与 LightRAG 知识库/图谱能力,知识库类型收敛为 Milvus 与只读连接器;知识库 API 统一使用 `/databases/{kb_id}/xxx` 形式,并整合 mindmap / eval 等子接口。 - Agent 资源默认选择与权限过滤:未显式配置工具、知识库、MCP、Skills、子智能体时默认启用当前用户可访问/可用的全部资源,显式选择后按允许列表过滤;Agent 创建前统一完成最终资源权限过滤、知识库 `kb_id` 可见范围派生和 Skill prompt/readable 依赖闭包派生。 - Skill 安装与权限模型收敛:Skill 元数据使用 `source_type/share_config/enabled` 表达来源、生效范围与启用状态;内置 Skill 启动或同步时自动写入数据库并默认全局启用,上传和远程添加统一改为解析草稿后确认安装,不保留旧直接安装兼容路径。 - 历史兼容层精简:移除 sandbox provisioner `local` 后端别名、ask_user_question 单问题旧协议、JWT 历史默认密钥特殊判断、内置 Skill `SKILLS.md` 文件名回退、运行事件数字 seq 兼容和前端旧字段回退。 - 用户身份命名收敛:原业务登录标识统一改为 `uid`,Agent/LangGraph runtime、conversation、agent_run、sandbox 路径和前端用户态均使用字符串 `uid`;`user_id` 仅保留给外部响应中的数值 `users.id` 或真实外键场景。 ### 开发记录 - 发布版本号更新至 `0.7.0`,同步 package、Docker 镜像标签与快速开始分支引用。 - 新增内置「深度研究」多智能体:编排器 Agent(`deep-research`,ChatbotAgent 后端)负责澄清、拆解、并行调度子智能体与综合成稿,配套两个子智能体 `research-explorer`(围绕单个子问题多轮检索网页/知识库并返回带引用发现)和 `fact-verifier`(对抗式核验关键论断、标注冲突与置信度);完整研究方法论沉淀为新增内置 Skill `deep-research`(依赖 `tavily_search`),编排器运行时读取并据此调度。三者随 `lifespan` 启动通过 `AgentRepository.ensure_deep_research_agents` 幂等落库(已存在不覆盖管理员修改)。 - 新增内置 `general-purpose` 通用任务子智能体:使用 `SubAgentBackend` 与空运行配置,作为 `task` 工具的通用委派目标,由启动初始化自动写入数据库。 - 收敛 MCP 创建与编辑入口:前端移除整段配置文本入口和模式切换器,仅保留表单字段提交;后端 MCP 创建/更新请求拒绝额外配置字段,避免绕过表单约束。 - 调整内置 MCP 默认项:移除 `sequentialthinking` 的系统内置同步,启动同步时清理历史系统内置记录,保留用户手动创建的同名 MCP。 - 图片生成能力迁移为 Skill:Qwen-Image 从内置 Python 生成工具迁移到内置 Skill `image-gen`,模型调用与图片下载在 Agent 沙盒中完成,生成结果保存到 outputs 并通过 `present_artifacts` 展示,为多图片生成模型接入复用同一产物展示链路。 - 优化前端头像加载兜底:用户与智能体头像优先展示已配置图片,加载失败后回退到基于 ID 的 DiceBear 默认头像;离线或默认头像不可达时显示名称前两个字和稳定背景色。 - 降低知识库路由与工具模块复杂度:示例问题生成迁移到知识库 utils,文件上传统一 100 MB 限制,URL 预处理入库路径与旧 `content_type=url` 行为收敛,并修复 uid、导出 MIME 与异常透传等路由问题。 - 重构智能体配置语义:用户可见的 `AgentConfig` 收敛为数据库持久化的一级 `Agent`,内置 Python Agent 改为智能体后端;新增 `/api/agent` 管理与运行接口,聊天、运行任务、恢复审批和文件预览均从线程绑定的 Agent 解析运行时上下文,前端只提交 `agent_id`,并在模型配置页新增“智能体”管理页签。 - 删除 Upload 与 LightRAG 图谱/知识库能力:知识库类型收敛为 Milvus 与 Dify,只保留 Milvus 知识库内图谱构建/展示/检索,移除独立 `/graph` 页面和默认上传图谱工具。 - 收敛只读知识源连接器:新增 `ReadOnlyConnectors` 基类,Dify 改为声明自身创建参数与校验规则,新增 Notion Data Source 只读知识库并支持 Search/Find/Open;知识库类型接口返回创建参数 schema,前端新建表单按类型动态渲染非 Milvus 配置并统一保存到 `additional_params`。 - 新增知识库 Chunk 持久化:Milvus 知识库索引/更新流程会将 chunks 双写到 PostgreSQL `knowledge_chunks` 表与 Milvus,文件内容查看优先查询 PostgreSQL,并为位置信息、图谱实体关联、标签和抽取结果预留结构化字段;chunk 入库改为分批 embedding 与分批写入,避免大文件一次性写入触发 gRPC 消息大小限制;入库成功后将单文件 chunk 数与 token 数写入文件元数据,并将知识库级总 chunk 与总 token 汇总保存到 metadata,前端文件管理页展示该统计并支持一键修复历史文件缺失的统计值。 - 完善 Milvus 知识库图谱构建:修复 Chunk 图谱写入返回值、Neo4j 同步写入阻塞事件循环、重复构建任务竞态、图谱查询提前终止、Neo4j 连接复用、LLM 抽取超时重试和前端错误详情展示等问题;图谱构建会将 entity/triple 本体与 chunk 引用写入 PostgreSQL,并为唯一 entity/triple 建立 Milvus 语义索引,单文件删除时同步清理图谱引用和孤儿向量。 - 优化图谱抽取器配置:未配置时在图谱中心展示配置入口,抽取方案收敛为 LLM,前端仅保留“更多拓展中”占位;LLM 抽取器使用固定 Prompt + 自定义 Schema,并支持模型参数与并发队列数;已配置后允许修改参数并提示重置重抽风险。修复上传并入库新文件时旧内存 metadata 覆盖数据库图谱配置的问题。 - 新增 Milvus 图谱检索链路:Query 可召回图谱实体和三元组,结合 Chunk 命中实体构造 seed entity,读取 Neo4j 2-hop 子图后用 igraph 执行 PPR,最终以 Chunk 为产物并通过 RRF 与原 Chunk 召回融合;检索配置改为 dataclass 元数据生成,支持 `depend_on` 控制重排序和图检索参数展示。 - 收紧用户管理部门隔离:普通管理员创建用户时固定归属本部门,用户列表、访问选项、详情、更新和删除接口均限制在本部门范围内。 - 修复用户管理列表超过 100 人时被默认分页截断的问题:前端按 `skip/limit` 分批加载用户,并在用户卡片列表中补充分页渲染。 - 调整 Agent 资源默认选择与运行时上下文:未显式配置工具、知识库、MCP、Skills、子智能体时默认启用当前用户可访问/可用的全部资源,显式选择后按允许列表过滤;Agent 创建前统一完成最终资源权限过滤、知识库 `kb_id` 可见范围派生和 Skill prompt/readable 依赖闭包派生,聊天运行时与文件系统预览复用同一结果。 - 重构 Skills 权限与安装流程:Skill 增加 `source_type/share_config/enabled`,内置 Skill 作为启动同步入库的全局资源,不再保留前端安装/更新状态,支持启停但不允许删除;上传和远程添加统一为解析草稿后确认生效范围,安装 slug 优先读取 `SKILL.md` 的 `slug` 字段并保留 `name` 展示名,压缩包名称不参与 slug 校验;管理端支持编辑生效范围与启停;Agent 运行时按当前用户可访问 Skills 派生 prompt/readable 依赖闭包并限制挂载/激活,Skills prompt 改为模型请求级注入以避免污染 runtime context;主智能体恢复 `install_skill` 工具,允许当前用户安装私有 Skill 并激活当前会话,子智能体配置和运行态均禁用该工具。 - 精简历史兼容层:移除 sandbox provisioner `local` 后端别名、ask_user_question 单问题旧协议、JWT 历史默认密钥特殊判断、内置 Skill `SKILLS.md` 文件名回退、运行事件数字 seq 兼容和前端若干旧字段回退。 - 重构知识库共享权限:`share_config` 改为全局共享、部门共享、指定人可访问三档,部门共享必须包含当前用户部门,指定人可访问必须包含当前用户,并补充权限过滤测试。 - 移除知识库沙盒文件系统映射:不再通过 `/home/gem/kbs` 暴露知识库文件树,Agent 继续使用 `query_kb` 与 `open_kb_document` 访问知识库内容。 - 修复 MinerU 文档解析配置说明:文档处理指南原先指引启动 `openai-server`(30000 端口,仅提供 `/v1/chat/completions`),与解析器实际调用的 `/file_parse` 接口不匹配导致 `mineru_ocr` 不可用;更正为使用项目内置的 `mineru-api` 服务(30001 端口),并补充镜像构建与显存调优说明。 - 规范 Agent 知识库 Search/Find/Open 工具协议:`resource_id` 统一表示知识库 `kb_id`,Search 返回结构化 `resource_id/file_id/chunk` 结果,新增 `find_kb_document` 在已知文件内做关键词或正则定位,Open 默认窗口扩大到 1800 行。 - 收敛知识库分块配置:分块预设仅表达策略选择,通用分块参数统一通过 `chunk_parser_config` 传递;移除 `chunk_size`、`chunk_overlap`、`qa_separator` 等旧 root 字段兼容。 - 收敛知识库文件解析参数:文件级 `processing_params` 统一保存 `ocr_engine` 与 `ocr_engine_config`,解析阶段直接使用该结构并保留分块参数快照。 - 修复知识库文件大小显示为 0 的问题:文件上传时 `file_sizes` 参数未正确传播或历史数据缺失导致 DB 中 `file_size` 为 `None`;新增 `MinIOClient.stat_file/astat_file` 获取文件大小方法,`add_file_record` 在 `size` 缺失时从 MinIO 回补,`_load_metadata` 加载元数据后自动为缺少 `size` 的文件从 MinIO 补全并持久化。 - 优化评估基准自动生成:生成任务支持配置队列并发数,默认 10,范围 1-20。 - 完善模型供应商类型:普通聊天模型运行时新增 Anthropic provider type 适配,并清理不再支持的旧 provider type 入口。 - 重梳理知识库评估存储:评估数据集、题目、评估运行和逐题结果统一入库,JSONL 仅作为导入/导出格式;后端和前端 API 统一使用 dataset/run 语义;评估运行支持用户命名,历史记录按名称展示,综合评分只聚合检索指标。 - 扩展知识库上传来源:添加“从工作区上传”模式,后端将当前用户工作区文件预处理上传到 MinIO,前端沿用现有 `addDocuments` 入库链路提交 MinIO URL、内容哈希和文件大小。 - 重构知识库详情页布局:`DatabaseInfo` 改为顶部详情 header + 左侧功能 tab 侧边栏 + 右侧内容区,Milvus 默认进入文件管理,并将检索测试、知识图谱、知识导图、检索配置、RAG 评估和评估基准统一纳入侧边栏导航;只读连接器保留检索测试与检索配置。 - 整合知识导图接口:移除独立 mindmap router 与前端 API 模块,思维导图生成、查询和文件列表接口统一收敛到知识库 API 下。 - 收敛独立模型配置模块运行时:运行时 chat / embedding / rerank 均统一从 provider 模块与模型缓存读取 `provider_id:model_id`;旧版静态模型配置、v1 slash spec、旧模型列表接口和 Ollama 适配已移除;内置 provider 模板补充 XiaomiMiMo、XiaomiMiMo Token Plan CN 与 Kimi Code(`kimi-for-coding`)。 - 调整智能体模型配置默认值:`BaseContext.model` 默认保持为空,运行时按“请求模型 > 智能体配置模型 > 系统默认模型”解析;子智能体未配置模型时继承主智能体当前运行模型,避免把系统默认模型固化进每个智能体配置。 - 调整智能体配置归属与字段权限:`AgentConfig` 从部门共享改为按 `uid` 隔离,所有登录用户可管理自己的配置;`BaseContext` 支持字段级 `auth` 元数据,后端按用户角色过滤可见与可保存的配置项。 - 新增用户级沙盒环境变量:增加 `agent_envs` 表与 `/api/user/agent-env` 接口,设置面板支持当前用户维护 Agent 沙盒环境变量;创建新沙盒时与全局 `sandbox.env` 合并注入,用户变量优先。 - 收敛用户身份命名:原业务登录标识统一改为 `uid`,Agent/LangGraph runtime、conversation、agent_run、sandbox 路径和前端用户态均使用字符串 `uid`;`user_id` 仅保留给外部响应中的数值 `users.id` 或真实外键场景。 - 工作区知识库分类显示:知识库侧边栏按创建者分组为“我的知识库”和“共享知识库”,自己创建的知识库显示在“我的知识库”下,非自己创建的显示在“共享知识库”下;`knowledge_bases` 表新增 `created_by` 字段记录创建者 uid。 - 工作区文件上传支持多选:`/workspace/upload` 与 Viewer 工作区上传统一使用 `files` 多文件字段,一次最多上传 50 个文件,批量上传失败时清理本次已写入文件。 - 聊天附件新增 MinIO tmp 临时上传、可选 PDF/图片解析、确认后加入线程附件的流程;前端改为弹窗内上传、解析与确认。 - 修复智能体对话上传透明 PNG 后图片失真的问题:多模态图片处理在导出 RGB 前会先按白底合成 alpha 通道,避免透明像素中的隐藏颜色被直接转为可见像素;交付物预览优先按文件头识别 MIME,避免 `.jpg` 文件名包裹 PNG 内容时前端按错误格式加载;Agent run 输入消息会持久化为 `multimodal_image`,刷新历史后仍能显示用户上传图片。 - 优化智能体对话页细节:状态面板隐藏空 section,待办名称限制为 20 个中文汉字以内,模型选择器展示供应商名称,并收紧附件状态标签与文件编辑浮动操作样式; - 标准化 Agent run/SSE 执行链路:run 创建时持久化输入消息并提交后入队,worker 统一写入 Redis Stream envelope,SSE 输出 `event/data/id`、心跳注释、`Last-Event-ID` 回放和终止 `end` 事件;前端强制使用 run API 并支持 ask_user_question 中断后以 resume run 恢复;事件 envelope 构造收敛到统一 helper,前端优先使用 envelope 一级 `thread_id` 路由。 - 修复 AgentRun 恢复与取消边界:无显式 `request_id` 的 resume run 改为按父 run 与恢复载荷派生稳定幂等 key,避免恢复重试创建重复 run;取消请求先提交数据库状态再发布 Redis 取消信号,避免 worker 在提交窗口内读到旧状态。 - Agent run SSE 新增 `verbose=false` 精简模式:默认仍返回完整事件载荷;精简模式仅在 SSE 输出前重建最小 payload,跳过 `metadata` 和空 `yuxi.agent_state`,将同一 data 内的 `request_id` 外提为单个字段,移除 chunk 中重复的 `meta`、`metadata`、`thread_id`、`response`、空 `namespace` 和图片 base64 等调试字段,保留消息增量、工具调用、工具结果、非空 Agent state、终止状态和 SSE 游标,前端订阅默认使用精简模式。 - 修复 SiliconFlow MiniMax 与阿里云百炼工具调用流式兼容:二者的 OpenAI 兼容流经 LangGraph v3 event stream 累积工具调用时会丢失关键字段(MiniMax 在参数增量 chunk 返回空 `function.name`,百炼丢失 `tool_call.id`),空值被写入 checkpoint 后会导致工具执行失败或工具结果无法按 `tool_call_id` 关联、工具状态永远停留在“进行中”;这两类提供商默认对工具调用禁用流式模型响应(正文回答仍流式),保留 LangGraph v3 运行事件并拿到完整 tool_call。该缺陷属 LangChain v3 流式协议上游问题(参见 langchain#37420、langchainjs#10937、langgraphjs#2496),截至 langchain-core 1.4.4 仍未修复,待上游修复后可移除对应提供商的禁流式处理。 - 收敛后端模块边界:文档解析从 `plugins.parser` 移动到 `knowledge.parser`,内容审查从 `plugins.guard` 移动到 `services.guard`。 - 收敛文件服务边界:文件预览判断抽为独立服务,Viewer 文件系统的 workspace 分支复用用户 workspace 服务,线程运行时上下文解析从泛化 `filesystem_service` 拆出为 agent runtime helper。 - 升级 DeepAgents 到 0.6.7 并适配新版文件系统协议:SubAgentMiddleware 改为显式 subagent spec,Skills prompt 补齐新版占位符;sandbox/skills backend 复用新版 `ReadResult`、`GlobResult`、`GrepResult` 等协议类型,文件权限在 backend 层明确区分 skills、uploads、outputs 与 workspace,保留最小 `CustomCompositeBackend` 以避免非 route glob 误扫其他 route;Agent 上下文压缩改为复用 DeepAgents SummarizationMiddleware,历史摘要与大工具结果统一 offload 到 outputs。 - 优化聊天输入 @ 文件提及:未创建 Thread 时可搜索用户 workspace,创建 Thread 后按当前对话文件优先、workspace 兜底的来源顺序搜索,并拆分 workspace/thread 缓存避免假 thread 与跨用户缓存污染;输入框与用户消息支持将 raw mention 渲染为带类型图标的引用单元,文件仅显示文件名且保留原始沙盒路径文本。 - 重构子智能体为 Agent-backed 形态:移除旧 `subagents` 表与 `/api/system/subagents` 管理链路,子智能体改为 `agents.is_subagent=true` 且使用 `SubAgentBackend`,创建/编辑统一走 Agent 管理入口;内置后端收敛为 `ChatbotAgent` 与 `SubAgentBackend`,Context 分为 `BaseContext`、`ChatBotContext` 与 `SubAgentContext`;主 Agent 通过 Yuxi task middleware 启动真实子 Agent graph,子智能体不再嵌套调用子智能体。沙盒挂载同步拆分为 child checkpoint thread、父对话 uploads/outputs、用户级 workspace 与子 Agent skills scope;主线程状态记录 `subagent_runs` 并在前端 task 工具中展示子智能体名称、执行状态、child thread 和产物,task 工具结果会暴露 child thread ID 且支持传回 `thread_id` 继续既有子智能体线程;子智能体执行复用 `agent_runs(run_type=subagent)` 记录父 run、child thread 与状态,child thread state 查询以 `agent_runs` 关系为准,不再解析 thread ID 反推父线程;真实流式 E2E 覆盖子智能体输出文件可由父线程文件/Viewer API 读取。流式链路参考 DeepAgents event streaming,后端将 LangGraph v3 raw event 归一化为 Yuxi semantic stream event,按父/子线程归属隔离 run SSE chunk,并支持通过 child thread state 拉取子智能体中间过程。 - 修正评估综合得分计算:`overall_score` 改为有答案准确率时取各题准确率平均,否则取各题 `recall@10` 平均,不再把 recall/f1/各 k 检索指标混合平均;历史已存运行不回填。 - 清理无效鉴权中间件:移除启动时未实际校验令牌的 `AuthMiddleware` 和公开路径残留判断,后端认证边界明确收敛到路由依赖;`/api/auth/me` 改为强制登录并补充未登录访问返回 401 的集成测试。 ## v0.6.2 (2026-05-22) ### 新增 - 新增个人工作区预览与管理:提供独立于对话 thread 的用户级 workspace API,并增加“工作区”页面,用于浏览、预览、编辑、上传、下载、删除个人 workspace 文件;默认创建 `agents/AGENTS.md`,并在 Agent 执行时将其内容追加到系统提示词。 - 新增独立模型配置模块:增加 `model_providers` 表、独立管理接口和“模型配置”页面,支持 provider 基础信息、远端候选模型、enabled models 配置和手动添加模型能力。 - 新增远程 Skill 批量安装能力:后端新增 `install_remote_skills_batch()` 与 `POST /remote/install-batch`,前端补充批处理安装 API 和 UI 逻辑。 ### 优化 - 下放扩展管理权限:普通管理员现在可进入扩展管理并完整管理 Tools、MCP、SubAgent、Skills;同步放开 Skill 管理接口权限并补充权限测试。 - 调整 Agent 知识库默认选择:未显式配置知识库时默认启用当前用户可访问的全部知识库,显式保存空列表仍表示不启用知识库。 - 优化评估基准自动生成:仅支持 commonrag/Milvus 知识库,默认参考 chunks 数量改为 1;多 chunk 场景复用知识库向量检索选择相似 chunks,不再对全量 chunks 重新计算 embedding。 - 优化 Agent 输入框文件 mention:用户级 workspace 文件候选改为从独立 workspace API 递归加载,不再依赖 active thread;插入时仍转换为 `/home/gem/user-data/workspace/` 沙盒虚拟路径。 - 调整知识库思维导图后端结构:将思维导图路由文件重命名为知识库语义更明确的 router,并把文件列表整理、提示词构建、AI JSON 解析等纯逻辑下沉到知识库 utils。 - 收敛知识库评估后端结构:将评估指标、单题评估、答案生成提示词和自动基准生成算法下沉到 `knowledge/eval`,`EvaluationService` 保留任务、文件和持久化编排职责。 - 扩展管理界面交互逻辑重构:MCP / Subagents / Skills 从“左侧边栏 + 右侧详情面板”调整为“卡片式网格布局 + 路由跳转二级页面”,工具标签页改为卡片网格布局 + 弹窗详情。 - 统一卡片样式:`ExtensionCard` 新增 `tags` prop 并复用于知识库列表页,知识库列表改用 `ExtensionCard` + `ExtensionCardGrid` 替代原有自定义卡片。 - 调整应用主导航:`AppLayout` 升级为默认展开的侧边栏,保留折叠态图标导航,并统一导航项、任务中心、GitHub、用户信息的图标与文字对齐。 - 合并智能体对话导航:移除 `AgentChatComponent` 内部聊天侧边栏,将新建对话入口和对话历史移动到 `AppLayout` 主侧边栏,并通过共享线程 store 统一管理。 - 统一前端 Markdown 预览渲染:新增共享 `MarkdownPreview` 组件与 `markdown_preview` 渲染工具,替换 Agent 消息、文件预览、知识库 chunk、任务工具结果、聊天导出等场景中的旧预览实现。 ### 修复 - 修复聊天中普通用户 `@` 提及出不来技能和 MCP 列表的问题:放宽技能列表与 MCP 服务器列表读取接口至已登录用户,并对普通用户请求的 MCP 列表进行敏感连接参数脱敏。 - 修复知识库文档入库状态回退:当已解析文件缺失 `markdown_file` 解析产物时,索引流程会将文件状态恢复为未解析,便于重新解析。 - 修复附件上传后未立即刷新 mention 候选的问题。 - 加固 JWT 鉴权安全:移除历史默认密钥回退,初始化脚本支持生成并持久化 `JWT_SECRET_KEY` 与 `YUXI_INSTANCE_ID`,签发和验证令牌时校验 `iss/aud`,并拒绝已删除或登录锁定用户继续使用旧令牌访问系统。 - 修复模型配置路由请求模型未接收 `embedding_base_url` / `rerank_base_url` 导致前端已填写仍被后端校验拦截的问题。 - 修复知识库文档处理任务状态不一致问题:文件解析失败时任务中心正确显示"失败"而非"已完成"。 ## v0.6.1 (2026-04-24) ### 新增 - 合并知识库导航入口:左侧导航仅保留"知识库",文档知识库与图知识库在页面 header 中通过同一组轻量切换入口切换 - 抽象页面轻量切换 header:知识库与扩展管理页直接共用 `ViewSwitchHeader`,收敛文档知识库、知识图谱、Tools、MCP、Subagents、Skills 等入口的信息层级 - 调整任务中心交互:入口移动到 GitHub 按钮下方,并将右侧抽屉展示改为居中弹窗 - 将 `yuxi` 从 uv workspace 成员调整为 `backend/package` 下可独立构建的本地 Python 包,backend 通过 path dependency 以已安装包形式发现依赖 - 新增 Skills 远程安装能力:Skills 管理页支持填写 `owner/repo` 或 GitHub URL,后端通过隔离的临时 `HOME` 调用 `npx skills add` 下载指定 skill - 调整部门删除语义:删除部门时不再要求用户数为 0,而是将部门下用户迁移到默认部门 - 扩展 viewer 工作区文件操作:`/home/gem/user-data/workspace` 支持从文件系统面板新建文件夹和上传文件 - 为历史线程补充前端本地配置变更提示:当已有历史消息的对话中切换 Agent、切换配置或编辑配置项时,插入非持久化的信息提示 - 调整 Worker run 模式下的消息首屏反馈:前端发送消息时先乐观渲染用户消息,再将前端生成的 `request_id` 透传给 `/api/chat/runs` 与服务端 `init` 对账 - 调整聊天首页的智能体切换入口:当智能体数量 `>= 4` 或内容区宽度小于 `380px` 时自动收敛为"当前智能体 + 下拉按钮"形式 - 调整智能体对话中的工具调用展示:连续工具调用默认折叠为"调用了 N 个工具"的轻量摘要 - 调整输入框配置入口与侧边栏头尾交互:输入区配置按钮改为轻量 dropdown 触发器 ### 修复 - 修复沙盒 `workspace` 隔离粒度:宿主机目录从共享 `saves/threads/shared/workspace` 收敛为用户级 `saves/threads/shared//workspace` - 收紧文件系统安全边界:viewer/chat 下载与删除路径统一基于解析后的真实路径做允许目录校验,阻止通过软链接逃逸工作区/线程目录 - 修复 OIDC 原始用户名绑定中的占位用户解析:解析目标用户 ID 时改为从右侧拆分,避免 `sub` 中包含冒号时把已绑定账号误判成冲突账号 - 修复 DOCX 解析中的图片回插顺序:Docling 导出的多个 `` 占位符现在按文档图片顺序替换 - 修复前端依赖安全告警:通过 `pnpm.overrides` 将传递依赖 `flatted` 锁定到 `3.4.2`、`lodash-es` 锁定到 `4.18.1` - 修复对话摘要中间件的工具结果卸载链路:摘要触发时改为将大体积 `ToolMessage` 写入当前 agent 可见的 sandbox outputs 路径 - 修复 agents 页对话侧边栏在 `keep-alive` 路由切换后的误关闭问题 - 调整 Milvus 混合检索实现:集合 schema 增加 BM25 稀疏向量字段、BM25 函数和中文 analyzer 配置 - 重构 MCP 运行时配置加载模型:移除 `MCP_SERVERS` 作为运行正确性前提的设计,改为每次直接从数据库读取最新 MCP 配置 - 为知识库检索工具补充 `metadata.filepath` 注入:在 `query_kb` 统一出口基于会话可见知识库构建 `file_id -> /home/gem/kbs/...` 映射并回填 Milvus 检索结果 - 移除知识库沙盒文件系统映射:Agent 不再通过 `/home/gem/kbs` 遍历知识库文件,继续通过 `query_kb` 和 `open_kb_document` 检索与打开文档。 ## v0.6.0 (2026-04-01) ### 新增 - 重构后端代码 src -> backend/package/yuxi - 重构文档解析,统一文档解析体验,并新增 Parser 类 - 新增 LITE 模式启动,启动时不加载知识库、知识图谱相关模块,可以使用 make up-lite 快捷启动 - 新增沙盒环境,详见后续文档更新,统一沙盒虚拟路径前缀默认值为 `/home/gem/user-data` - 新增基于沙盒的文件系统,前端工作台可以查看文件系统,支持预览(文本、图片、PDF、HTML)、下载文件 - 新增 `present_artifacts` 内置工具:Agent 可将 `/home/gem/user-data/outputs/` 下的结果文件显式写入 LangGraph state 的 `artifacts` 字段,前端支持在输入框顶部以默认折叠的堆叠卡片展示本轮交付物文件,并保持可下载、可预览能力 - 交付物卡片新增“保存到工作区”能力:支持将单个交付物复制到共享目录 `workspace/saved_artifacts/`,并复用现有文件树/预览/mention 体系立即可见 - 新增基于沙盒的知识库只读映射,按“用户可访问知识库 ∩ 当前 Agent 已启用知识库”暴露原始文件与解析后的 Markdown - 重构附件系统,直接集成在了沙盒文件系统中,附件上传后直接落盘到沙盒挂载目录 - 优化前端流式消息体验:新增通用 `useStreamSmoother` 调度层,统一平滑 Agent runs SSE、普通聊天流与审批恢复流中的 `loading` chunk - 优化项目文档说明,并添加贡献指南 - 重构前端 Agent 路由结构,体验更加顺畅,切换更加自然(类 chatgpt 体验) - 新增 API Key 认证功能,支持外部系统通过 API Key 调用系统服务 - 新增 subagents 的支持,支持在 web 中添加 subagents,以及两个内置的子智能体 - 新增内置Skills reporter,并移除内置 Agent reporter,数据库报表将由 Skills 完成 - 新增内置 Skills `deep-reporter`,用于指导生成科研报告、行业调研和其他深度分析类长报告 - 重构内置 Skills/MCP/Subagents 安装/添加/移除机制:内置 skill 支持按需安装、基于 `version + content_hash` 的更新提示与覆盖确认,不再使用服务器级开关切换 - 新增知识库 PDF、图片的预览功能 - 重构后端测试目录结构:按 `unit / integration / e2e` 分层迁移现有测试,拆分全局 `conftest.py`,统一测试入口为 `uv run --group test pytest`,并新增独立测试规范文档 `docs/develop-guides/testing-guidelines.md` - 新增工具元数据 `config_guide` 字段:后端工具列表接口现在可返回“给人看的配置说明”,前端工具详情页会展示该说明,用于提示工具使用前需要配置的环境变量或入口;首批为 MySQL 工具和 `Qwen-Image` 补充了配置指引 - 补充 Langfuse 集成方案文档:明确采用“云端优先、先 tracing 后 feedback”的接入路径,并约定 Yuxi 的 `user/thread` 到 Langfuse `user_id/session_id` 的映射关系 - 新增面向用户的 Langfuse 集成文档:在“高级配置”分组中说明 Langfuse 的定位、能力、配置方式与查看路径,并与当前 `LANGFUSE_BASE_URL` 配置保持一致 ### 修复 - 调整聊天首页的智能体切换入口:在无历史对话时,智能体数量 `<= 3` 且 `chat-main` 宽度不小于 `380px` 时继续使用横向 segmented;当智能体数量 `>= 4` 或内容区宽度小于 `380px` 时自动收敛为“当前智能体 + 下拉按钮”形式,避免多智能体或窄屏场景下入口被截断 - 发布前一致性修复:统一 0.6.0 版本号(backend/package/web)、更新 dev/prod 镜像标签语义(`0.6.0.dev` / `0.6.0`),并为 `/api/system/health` 补充 `version` 字段,提升部署可观测性与发版追溯能力 - 收敛“状态工作台”自动弹出规则:前端不再因为共享 `workspace` 或文件系统天然存在内容而默认展开,改为仅在 `/home/gem/user-data/uploads` 或 `/home/gem/user-data/outputs` 下检测到实际文件时自动弹出;手动打开、关闭、刷新和伸缩交互保持不变 - 调整智能体 todo 展示语义:待办状态不再作为 `capabilities` 前端开关,而是直接根据运行态 `agent_state.todos` 渲染;同时将 todo 入口从 Agent Panel 移到输入框内的轻量浮层,并让右侧“状态工作台”收敛为文件系统视图,输入框按钮文案同步由“状态”调整为“文件” - 优化 Agent 输入框 mention 行为:在保留附件 mention 的同时,将共享 `workspace` 文件纳入候选范围;并将 `@` 空查询时的候选列表改为空,仅在继续输入后再执行筛选,避免工作区文件过多时直接铺满下拉面板 - 为前端工作台文件树补齐文件删除能力:`/api/viewer/filesystem/file` 新增删除接口,`AgentPanel` 文件节点新增删除按钮与确认交互,删除后会同步刷新树与预览状态 - 扩展 Agent Panel 状态工作台删除能力:继续复用 `DELETE /api/viewer/filesystem/file`,在保持接口不变的前提下支持删除文件夹;空目录与非空目录现在都会递归删除,`workspace` 下目录也可直接清理,前端目录节点同步新增删除入口与对应确认文案 - 调整前端工作台文件预览交互:恢复默认侧边/弹窗预览,并新增显式“全屏预览”入口;全屏模式下由预览内容直接覆盖整页,仅保留右上角悬浮关闭按钮;同时修复 HTML 文件首次在弹窗中预览偶现白屏的问题,改为在内容更新后强制重建 `iframe` - 统一 Agent Panel 文件预览与消息区交付物预览组件:两处改为复用同一套 `AgentFilePreview` 预览实现,并为交付物预览补齐与工作台一致的“全屏预览”入口 - 修复交付物卡片展开后的长列表展示:当单轮交付物文件超过面板可见高度时,卡片内容区改为显示纵向滚动条,避免超过约 10 项后底部文件与操作按钮被裁切 - 兼容旧版已安装的内置 `reporter` 技能记录:`update_builtin_skill` 现在会识别由 `system` 或 `builtin-system` 管理的历史记录,避免更新时误报“技能 `reporter` 不是内置 skill” - 调整沙盒 user-data 目录隔离策略:`workspace` 改为共享目录 `saves/threads/shared/workspace`,`uploads/outputs` 继续保持 thread 级隔离;同时更新 thread artifact 权限校验、viewer 文件系统列举逻辑,以及对应的 router/E2E 测试 - 重构聊天接口请求模型:流式与非流式聊天统一使用 `query + agent_config_id` 请求体,并移除路径中的 `agent_id`;同时修复非流式接口实际误走流式执行链路的问题,改为调用 `invoke_messages` 一次性执行,并补充对应测试 - 修复对话线程与 Agent 配置错位的问题:发送消息时将当前 `agent_config_id` 绑定到 thread 的 `extra_metadata`,线程列表接口返回该绑定值,前端切换历史 thread 时会自动恢复对应配置 - 为沙盒与 viewer 文件系统补齐知识库只读映射:新增 `/home/gem/kbs` 命名空间,按“用户可访问知识库 ∩ 当前 Agent 已启用知识库”暴露原始文件与解析后的 Markdown,并补充对应后端与 viewer 路由测试 - 优化 viewer 文件系统目录树加载:根目录与 `/home/gem/user-data` 改为直接读取本地线程挂载目录,不再为只读树视图触发 sandbox 冷启动,并补充对应后端测试 - 修复 `/home/gem/user-data` 根目录文件不可见的问题:根目录现在会同时展示 thread 目录下的真实文件和 `workspace` 入口,不再只保留固定命名空间目录 - 修复前端工具图标与渲染匹配不准确的问题:工具管理列表与工具调用结果统一改为基于工具 `id` 的精确映射,避免模糊匹配导致的误渲染,未命中的工具不再显示默认扳手图标 - 修复 GitHub Pages 文档部署工作流失败:移除 `actions/setup-node@v4` 对不存在 `docs/package-lock.json` 的缓存依赖,并将 `docs` 目录安装命令从 `npm ci` 调整为 `npm install`,避免因未提交锁文件导致 CI 在依赖缓存和安装阶段直接失败 - 修正沙盒 provisioner backend 命名与配置说明:统一对外使用 `docker` / `kubernetes`,保留 `local` 作为兼容别名;同步清理 compose 中未生效的 provisioner 环境变量、补齐 K8s 相关变量注释,并更新沙盒架构文档中的默认模式与 backend 描述 - 修复智能体配置列表接口在“无配置自动创建默认配置”路径下的参数缺失:补齐 `get_or_create_default` 的 `agent_id` 入参,避免 `/api/chat/agent/{agent_id}/configs` 返回 500 - 修复 LightRAG 同库写入并发导致的入库失败:为 `index_file` / `update_content` 增加按知识库维度的串行锁,并补齐 `documents` 接口 `auto_index` 阶段对最新解析状态的回写与回归测试,避免长时间入库任务进行中再次选择同库文件时直接并发写入报错 --- ## v0.5 ### 新增 - 优化 OCR 体验并新增对 Deepseek OCR 的支持 - 优化 RAG 检索,支持根据文件 pattern 来检索(Agentic Mode) - 重构智能体对于“工具变更/模型变更”的处理逻辑,无需导入更复杂的中间件 - 重构知识库的 Agentic 配置逻辑,与 Tools 解耦 - 将工具与知识库解耦,在 context 中就完成解耦,虽然最终都是在 Agent 中的 get_tools 中获取 - 优化chunk逻辑,移除 QA 分割,集成到普通分块中,并优化可视化逻辑 - 重构知识库处理逻辑,分为 上传—解析—入库 三个阶段 - 重构 MCP 相关配置,使用数据库来控制 [#469](https://github.com/xerrors/Yuxi/pull/469) - 使用 docling 解析 office 文件(docx/xlsx/pptx) - 优化后端的依赖,减少镜像体积 [#428](https://github.com/xerrors/Yuxi/issues/428) - 优化 liaghtrag 的知识库调用结果,提供 content/graph/both 多个选项 - 优化数据库查询工具,可通过设计环境变量添加描述,让模型更好的调用 - 优化任务组件,改用 postgresql 存储,并新增删除任务的接口 - 支持更多类型的文档源的导入功能(支持后端配置的白名单的 URL 导入) ### 修复 - 修复文件上传弹窗中 OCR 下拉选项展开时不会自动检查服务状态的问题 - 修复知识图谱上传的向量配置错误,并新增模型选择以及 batch size 选择 - 修复部分场景下获取工具列表报错 [#470](https://github.com/xerrors/Yuxi/pull/470) - 修改方法备注信息 [#478](https://github.com/xerrors/Yuxi/pull/478) - 修复多次 human-in-the-loop 的渲染解析问题 [#453](https://github.com/xerrors/Yuxi/issues/453) [#475](https://github.com/xerrors/Yuxi/pull/475) - 修复沙盒后端接入回归:补齐 composite backend 的 `sandbox_backend` 参数、限制 `/api/sandbox/prepare` 仅允许访问当前用户线程、确保 `release()` 之后的 `destroy()` 会真正停止热池容器,并恢复 docker-compose 的完整模式默认值 - 重构沙盒为 deer-flow 风格的 AIO provider:切换为 thread-local sandbox、统一 `/home/gem/user-data/{workspace,uploads,outputs}` 固定路径、移除公开 `/api/sandbox/*` 生命周期接口,并补充 lite 模式下的 provider 生命周期、filesystem API 与 sandbox 复用/隔离 E2E 验证 - 调整聊天附件存储链路:线程附件改为直接落盘到 `saves/threads//user-data/uploads`,解析成功后额外生成 `uploads/attachments/*.md`,不再依赖 MinIO 或显式上传到 sandbox - 修复知识库文件列表包体异常膨胀:上传阶段不再把批次级 `content_hashes` 写入每个文件的 `processing_params`,并从数据库详情列表接口中移除该字段,改为按需读取单文件详情 ## v0.4 ### 新增 - 新增对于上传附件的智能体中间件,详见[文档](https://xerrors.github.io/Yuxi/advanced/agents-config.html#%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E4%B8%AD%E9%97%B4%E4%BB%B6) - 新增多模态模型支持(当前仅支持图片),详见[文档](https://xerrors.github.io/Yuxi/advanced/agents-config.html#%E5%A4%9A%E6%A8%A1%E6%80%81%E5%9B%BE%E7%89%87%E6%94%AF%E6%8C%81) - 新建 DeepAgents 智能体(深度分析智能体),支持 todo,files 等渲染,支持文件的下载。 - 新增基于知识库文件生成思维导图功能([#335](https://github.com/xerrors/Yuxi/pull/335#issuecomment-3530976425)) - 新增基于知识库文件生成示例问题功能([#335](https://github.com/xerrors/Yuxi/pull/335#issuecomment-3530976425)) - 新增知识库支持文件夹/压缩包上传的功能([#335](https://github.com/xerrors/Yuxi/pull/335#issuecomment-3530976425)) - 新增自定义模型支持、新增 dashscope rerank/embeddings 模型的支持 - 新增文档解析的图片支持,已支持 MinerU Officical、Docs、Markdown Zip格式 - 新增暗色模式支持并调整整体 UI([#343](https://github.com/xerrors/Yuxi/pull/343)) - 新增知识库评估功能,支持导入评估基准或者自动构建评估基准(目前仅支持Milvus类型知识库)详见[文档](https://xerrors.github.io/Yuxi/intro/evaluation.html) - 新增同名文件处理逻辑:遇到同名文件则在上传区域提示,是否删除旧文件 - 新增生产环境部署脚本,固定 python 依赖版本,提升部署稳定性 - 优化图谱可视化方式,统一图谱数据结构,统一使用基于 G6 的可视化方式,同时支持上传带属性的图谱文件,详见[文档](https://xerrors.github.io/Yuxi/intro/knowledge-base.html#_1-%E4%BB%A5%E4%B8%89%E5%85%83%E7%BB%84%E5%BD%A2%E5%BC%8F%E5%AF%BC%E5%85%A5) - 优化 DBManager / ConversationManager,支持异步操作 - 优化 知识库详情页面,更加简洁清晰,增强文件下载功能 ### 修复 - 修复 GitHub Actions 的 Ruff CI 在仓库根目录执行 `uv sync` 导致找不到 `backend/pyproject.toml` 的问题,同时统一检查路径为 `backend/package` - 修复重排序模型实际未生效的问题 - 修复消息中断后消息消失的问题,并改善异常效果 - 修复当前版本如果调用结果为空的时候,工具调用状态会一直处于调用状态,尽管调用是成功的 - 修复检索配置实际未生效的问题 - 修复 sandbox 文件系统 `ls` 在异常输出下触发 `KeyError: 'path'` 的问题,并将工具调用异常降级为错误消息,避免直接中断聊天 stream - 修复智能体状态面板中文件树仍依赖 `agent_state.files` 的问题,改为通过真实 `/api/filesystem/*` 接口按层懒加载后端可见文件系统,并让输入框下方状态按钮常态化打开工作区视图 - 为工作台新增 viewer-oriented filesystem service 与 `/api/viewer/filesystem/*` 接口,解耦 agent backend 语义,支持真实目录浏览、原始文件读取与下载 - 重写沙盒技术文档,明确 thread-local sandbox、viewer-oriented filesystem service、`/mnt` 命名空间、skills 可见性与当前实现边界,替换过时的 `/api/sandbox/*` 与 user-level 设计描述 - 收紧沙盒遗留代码:修复未注册 `sandbox_router` 中残留的 user/thread 参数错位,改进宿主机挂载路径映射逻辑,并为 remote sandbox provisioner 增加基础 URL 校验与销毁失败日志 - 修复 builtin skill 内容哈希计算对单文件使用 `read_bytes()` 的无上限内存读取问题,改为分块计算并补充回归测试 ### 破坏性更新 - 移除 Chroma 的支持,当前版本标记为移除 - 移除模型配置预设的 TogetherAI ## v0.3 ### Added - 添加测试脚本,覆盖最常见的功能(已覆盖API) - 新建 tasker 模块,用来管理所有的后台任务,UI 上使用侧边栏管理。Tasker 中获取历史任务的时候,仅获取 top100 个 task。 - 优化对文档信息的检索展示(检索结果页、详情页) - 优化全局配置的管理模型,优化配置管理 - 支持 MinerU 2.5 的解析方法 - 修改现有的智能体Demo,并尽量将默认助手的特性兼容到 LangGraph 的 [`create_agent`](https://docs.langchain.com/oss/python/langchain/agents) 中 - 基于 create_agent 创建 SQL Viewer 智能体 - 优化 MCP 逻辑,支持 common + special 创建方式 - LightRAG 知识库应该可以支持修改 LLM ### Fixed - 修复本地知识库的 metadata 和 向量数据库中不一致的情况。 - v1 版本的 LangGraph 的工具渲染有问题 - upload 接口会阻塞主进程 - LightRAG 知识库查看不了解析后的文本,偶然出现,未复现 - 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。 - 前端工具调用渲染出现问题 - 当前 ReAct 智能体有消息顺序错乱的 bug,且不会默认调用工具 - 修复文件管理:(1)文件选择的时候会跨数据库;(2)文件校验会算上失败的文件; --- ### Develop Guides/Contributing # 参与贡献 感谢你对 Yuxi 的关注。我们欢迎 Bug 修复、功能改进、测试补充、文档更新以及其他能够让项目变得更好的贡献。 本文面向通过 Fork 参与开发的贡献者,介绍从领取任务到提交 Pull Request(以下简称 PR)的完整流程。如果你只需要快速了解仓库入口,可以先阅读根目录的 [CONTRIBUTING.md](../../CONTRIBUTING.md)。 贡献者名单 ## 开始之前 开始开发前,请先完成以下确认: - 搜索已有 [Issues](https://github.com/xerrors/Yuxi/issues),避免重复提交相同问题。 - 如果任务来自 GitHub Project,阅读任务描述、关联 Issue、验收标准和已有讨论,并确认任务已经分配给你。 - 对影响范围较大、需求边界不明确或会改变现有架构的改动,先通过 Issue 或 [Discussions](https://github.com/xerrors/Yuxi/discussions) 对齐方案。 - 一个 PR 只解决一个明确问题,不混入无关重构、格式化或“顺手优化”。 修改不熟悉的模块前,请先阅读 [ARCHITECTURE.md](https://github.com/xerrors/Yuxi/blob/main/ARCHITECTURE.md),了解前后端边界、主要运行链路和架构不变量,再通过代码搜索定位具体实现。 ## 贡献流程概览 一次完整贡献通常包括: 1. Fork 并克隆仓库。 2. 配置上游仓库并同步最新 `main`。 3. 从最新 `main` 创建独立分支。 4. 完成开发、测试和文档更新。 5. 使用独立上下文的 Reviewer Agent 完成提交前 Code Review,并处理 Review 结论。 6. 提交改动并将分支推送到自己的 Fork。 7. 从 Fork 分支向 `xerrors/Yuxi:main` 发起 PR。 8. 根据 CI 和 Review 反馈继续修改,直至合并。 如果任务来自 GitHub Project,开始开发时将任务状态更新为进行中;PR 创建后关联对应 Issue 或 Project 条目。任务只有在必要测试通过并完成合并后,才应标记为完成。 ## 1. Fork 与克隆仓库 先在 GitHub 上 Fork [xerrors/Yuxi](https://github.com/xerrors/Yuxi),然后克隆自己的 Fork: ```bash git clone https://github.com//Yuxi.git cd Yuxi ``` 克隆完成后,默认的 `origin` 应指向你的 Fork。将官方仓库配置为 `upstream`: ```bash git remote add upstream https://github.com/xerrors/Yuxi.git git remote -v ``` 推荐保持以下远程仓库关系: ```text origin 你的 Fork,用于推送开发分支 upstream xerrors/Yuxi,用于获取主仓库更新 ``` 不要把开发分支直接推送到 `upstream`。 ## 2. 同步最新主分支 每次开始新任务前,先同步官方仓库的最新 `main`: ```bash git fetch upstream git switch main git merge --ff-only upstream/main git push origin main ``` `--ff-only` 可以避免在本地 `main` 上意外产生额外的合并提交。如果该命令失败,说明本地 `main` 已经包含独立修改;请先检查分支状态,不要直接覆盖或删除未确认的工作。 ## 3. 创建任务分支 必须从同步后的 `main` 创建独立分支,不要直接在 `main` 上开发: ```bash git switch -c feat/knowledge-graph-import ``` 分支名应简短、明确,并能表达改动目的。推荐格式: ```text feat/ 新功能 fix/ Bug 修复 docs/ 文档更新 refactor/ 重构 test/ 测试改进 chore/ 工程或辅助任务 ``` 示例: ```bash git switch -c fix/chat-stream-interrupt git switch -c docs/update-contributing-guide ``` ## 4. 开发环境 Yuxi 使用 Docker Compose 管理开发环境。开发、调试和测试应在运行中的容器环境中完成。 首次启动前,根据 `.env.template` 准备项目根目录下的 `.env`,然后启动服务: ```bash docker compose up -d ``` 确认容器状态和后端日志: ```bash docker ps docker logs api-dev --tail 100 ``` Compose 服务 `api` 和 `web` 对应的容器名分别为 `api-dev` 和 `web-dev`,默认支持热重载。修改本地代码后通常不需要重启容器。 服务定义和挂载方式见 [docker-compose.yml](../../docker-compose.yml)。 ## 5. 实现原则 提交代码时请遵循以下原则: - 使用满足验收标准的最小实现,不增加当前任务未要求的功能、配置或扩展点。 - 保持主流程简单、线性、易读;不要为了单次使用的逻辑创建多层抽象。 - 修改范围应能直接追溯到当前任务,不格式化或重构无关代码。 - 预设条件不成立时应明确失败,不使用静默回退或吞异常掩盖问题。 - 修复 Bug 时,优先增加能够稳定复现问题的回归测试,再修复实现。 - 行为、接口或配置发生变化时,同步更新相关文档。 ### 前端改动 前端代码位于 `web/`,请遵循以下约束: - 使用 `pnpm` 管理依赖。 - API 接口统一定义在 `web/src/apis`。 - Icon 优先使用 `lucide-vue-next`,并保持尺寸一致。 - 样式使用 `less`。 - 非特殊情况使用 [base.css](../../web/src/assets/css/base.css) 中已有的颜色变量。 - 遵循 [界面设计规范](./design.md),保持现有交互和视觉语言一致。 不要在没有必要的情况下引入新的前端依赖。如果确实需要新增依赖,请在 PR 中说明用途和替代方案。 ### 后端改动 后端代码位于 `backend/`,请遵循以下约束: - 使用 Python 3.12+ 支持的现代、Pythonic 写法。 - 保持路由、服务、仓储和领域逻辑的现有边界。 - 新增测试应放入 `backend/test/unit`、`backend/test/integration` 或 `backend/test/e2e` 对应目录。 - 不在测试或文档中写入 `.env` 中的账号、密码、Token 或其他敏感值。 测试分层、fixture 和 skip 规则见 [测试规范与工作流](./testing-guidelines.md)。 ## 6. 检查与测试 提交前按照“检查 → 测试 → Lint”的顺序验证改动。测试范围应与改动风险匹配: - 纯逻辑改动:运行相关单元测试。 - API、权限或持久化改动:补充并运行相关集成测试。 - 关键用户链路:补充并运行对应 E2E 测试。 - 前端改动:运行 Lint、相关单元测试和构建检查。 ### 后端测试 ```bash # 单元测试 docker compose exec api uv run --group test pytest test/unit -m "not slow" # 集成测试 docker compose exec api uv run --group test pytest test/integration # E2E 测试 docker compose exec api uv run --group test pytest test/e2e -m e2e ``` 也可以使用项目脚本: ```bash backend/test/run_tests.sh unit backend/test/run_tests.sh integration backend/test/run_tests.sh e2e backend/test/run_tests.sh all ``` 优先运行与改动直接相关的最小测试集,再根据影响范围扩大回归测试。不要只因为本地缺少默认数据就跳过测试,应通过 fixture 显式准备需要的资源。 ### 格式化与静态检查 应用项目格式化规则: ```bash make format ``` 如需在不修改文件的情况下核对后端 Ruff 检查,可运行: ```bash docker compose exec api uv run ruff check package docker compose exec api uv run ruff format package --check ``` 前端改动还应运行: ```bash docker compose exec web pnpm run lint docker compose exec web pnpm run test:unit docker compose exec web pnpm run build ``` 最后检查补丁是否包含空白错误: ```bash git diff --check ``` 如果某项检查因环境或外部服务不可用而无法执行,请在 PR 的测试说明中明确记录原因和未验证范围,不要把未执行写成已通过。 ## 7. 提交前独立 Agent Code Review 所有包含代码变更的任务,在开发和测试完成后、执行 `git commit` 前,必须使用 Codex、Claude Code 或同等工具完成一次独立 Agent Code Review。 这里的“独立”是指 Reviewer Agent 必须运行在一个全新的会话和上下文中: - 不继承当前开发会话的对话历史、推理过程或实现结论。 - 不使用继承当前上下文的 SubAgent 代替独立 Review。 - Reviewer 应根据需求、完整 diff、相关代码、测试和项目规范独立判断,而不是只检查开发者指定的局部代码。 Review 重点检查以下内容: 1. **功能正确且完整**:实现满足需求和验收标准,主路径、关键边界、错误处理和测试可信,没有遗漏主要使用场景或引入明显回归。 2. **实现简单且低认知负担**:优先复用现有能力,减少重复开发和重复代码;避免过度设计、过度防御、不必要抽象、细碎 helper、冗余 fallback 和过长调用链;主流程应直接、清晰、易读。 3. **风格一致且位置合理**:新代码与相邻实现保持一致,Python 代码符合 Python 3.12+ 和 Pythonic 风格;路由、服务、仓储、前端 API、测试等内容位于正确边界,并符合 `AGENTS.md`、[ARCHITECTURE.md](https://github.com/xerrors/Yuxi/blob/main/ARCHITECTURE.md)、[测试规范](./testing-guidelines.md) 和相关开发文档。 发现影响功能、代码边界或明显增加冗余和认知负担的问题时,应在提交前修正。这个阶段用于提升代码质量,不要求在 PR 中记录 Review 过程或问题清单。 独立 Agent Review 用于提升代码质量,不改变代码作者的责任;作者仍需对最终实现、测试结果和维护成本负责。 ## 8. 提交改动 提交前先检查本次变更范围: ```bash git status git diff ``` 不要提交 `.env`、密钥、运行数据、构建产物或与当前任务无关的文件。 提交信息遵循 [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/);标题应简洁说明改动内容。项目提交信息推荐使用中文: ```bash git add git commit -m "docs: 完善 Fork 与 PR 贡献流程" ``` 常用类型: ```text feat 新功能 fix Bug 修复 docs 文档更新 refactor 不改变行为的代码重构 test 测试新增或调整 chore 构建流程或辅助工具变更 ``` 保持提交历史易于 Review。不要为了“整齐”而修改已经共享的提交历史;如果 Reviewer 要求整理提交,再根据具体情况处理。 ## 9. 推送分支并创建 PR 将任务分支推送到自己的 Fork: ```bash git push -u origin docs/update-contributing-guide ``` 创建 PR 时确认目标和来源: ```text base repository: xerrors/Yuxi base branch: main head repository: /Yuxi compare branch: 当前任务分支 ``` 不要向自己 Fork 的 `main` 创建 PR,也不要把多个任务分支合并后再统一提交。 由 Agent(Codex、Claude Code 等)创建的 PR,在调用创建命令前先向用户展示拟提交的标题和完整正文,等待明确确认后再创建。确认针对的是标题和正文,不代表跳过测试、敏感信息检查、Fork/远程目标检查和 CI 结果记录等提交前必做项。 PR 标题直接表达变更目标。正文按照 [PR 模板](https://github.com/xerrors/Yuxi/blob/main/.github/PULL_REQUEST_TEMPLATE.md) 填写,并按实际改动补充对应章节: **Fix(Bug 修复)**,至少包含: - 触发场景和可复现步骤:用户做了什么、在什么环境下触发。 - 当前错误表现与影响范围。 - 根因定位和修复方式。 - 回归测试或验证方式,说明如何证明问题已修复。 - 兼容性、数据影响和仍未覆盖的边界。 稳定复现、范围聚焦、带回归验证的 Fix 是优先欢迎的贡献类型。 **Feature(新功能)**,至少包含: - 背景、用户场景和要解决的问题。 - 目标、非目标及主要验收标准。 - 实现方案、关键设计取舍和受影响模块/接口。 - 配置、数据、兼容性或迁移影响。 - 实际测试、E2E 场景、截图/录屏(如适用)和未验证风险。 **UI 改动**,必须提供最终界面截图或录屏: - 重要视觉改动提供修改前/后对比或关键交互状态。 - 适用时覆盖浅色/暗色、响应式尺寸、loading/empty/error 等关键状态,并在正文中标明截图对应场景。 - 提交时因 Docker、浏览器或环境问题暂时无法取得截图,必须在提交前明确告知用户截图缺失和补充计划;PR 创建后补充到 GitHub PR/评论,并把可访问链接回填到 PR 正文或 Project 任务。 - 没有截图时不得声称 UI 已完成视觉验证。 如果 PR 对应仓库 Issue,请在正文中使用: ```text Closes #123 ``` 这样 PR 合并后可以自动关闭对应 Issue。准备好接受 Review 时提交普通 PR;仍需讨论方案或 CI 尚未完成时,可以先提交 Draft PR。 ## 10. 处理 CI 和 Review PR 创建后,贡献者负责持续跟进 CI 和 Review: 1. 查看失败检查的具体日志,定位与本次改动有关的问题。 2. 在原任务分支继续修改、测试和提交。 3. 将新提交推送到同一远程分支,PR 会自动更新。 4. 回复 Review 时说明如何处理;如果不采纳建议,应解释具体原因和取舍。 5. 所有必要检查通过、Review 意见解决后再请求合并。 不要为同一个任务重复创建 PR。除非维护者明确要求,也不要关闭原 PR 后重新提交来隐藏讨论记录。 来自 Fork 的 PR 默认无法读取主仓库 Secrets。不要通过修改工作流、打印环境变量或扩大权限来绕过这一限制;如果验证必须依赖受保护凭证,请在 PR 中说明并由维护者执行对应检查。 如果开发期间 `main` 有更新,先获取最新上游代码: ```bash git fetch upstream ``` 只有在出现冲突、CI 明确要求更新,或维护者要求时,再将任务分支更新到最新 `upstream/main`。涉及 rebase 和强制推送时,应使用 `--force-with-lease`,并确保该分支没有其他贡献者共同开发。 ## 11. 合并后的清理 PR 合并后,可以删除已经完成的远程和本地任务分支: ```bash git push origin --delete docs/update-contributing-guide git switch main git branch -d docs/update-contributing-guide ``` 然后重新同步 `upstream/main`,再开始下一个任务。不要复用已经合并过的任务分支提交新的需求。 如果任务来自 GitHub Project,确认关联 PR 已合并、验收结果已记录,再将任务更新为完成状态。 ## 文档维护 代码改动后,请检查是否需要同步更新文档: - 正式文档位于 `docs/`。 - 文档导航定义在 `docs/.vitepress/config.mts`;新增正式页面时需要同步加入导航。 - 已完成的用户可见变更或发布说明更新到 [changelog.md](./changelog.md)。 - 未来规划和未完成事项更新到 [roadmap.md](./roadmap.md),不要将已完成变更继续保留为路线图事项。 - 仅开发者可见且确有必要的临时设计记录放在 `docs/vibe/`。 ## AI 辅助贡献 使用 Codex、Claude Code 等 AI 工具辅助开发时,贡献者仍需对代码、测试、文档和安全性承担完整责任,提交流程与人工贡献完全一致:完成开发与测试、按本文档创建 PR、如实填写 PR 内容。 Agent 可以在任务范围和用户授权内完成 commit、push 和创建 PR,不因使用 Agent 而要求额外的 Human Review。创建 PR 前,Agent 需要先向用户展示拟提交的标题和正文并等待确认,具体见上文「创建 PR」一节。 不要把仓库 Secrets、`.env` 内容、用户数据或其他敏感信息提供给不受信任的服务。 ## 获取帮助 - Bug 反馈:[GitHub Issues](https://github.com/xerrors/Yuxi/issues) - 功能和方案讨论:[GitHub Discussions](https://github.com/xerrors/Yuxi/discussions) 感谢每一位贡献者的投入。 --- ### Develop Guides/Design # 产品体验与界面设计规范 本文档定义 Yuxi 的产品体验与界面设计规范,适用于需求设计、交互方案、视觉设计、`web/src` 下的新页面、新组件和现有 UI 调整。它同时面向产品、设计、人类开发者和 AI coding agent:开始实现前先说明用户目标、信息层级和交互语义,再复用现有组件、CSS 变量和交互模式。 ## 0. 产品判断先于界面实现 界面不是接口字段的可视化投影。设计前先明确:目标用户、主要任务、需要做出的判断、成功与异常状态,以及哪些信息只是实现细节。 - 每个可见元素必须帮助用户识别、选择、判断状态、执行操作或恢复错误;否则删除。 - 默认使用用户语言描述用途、差异和影响。内部 ID、枚举和环境变量只在复制、对接或排障时按需展示。 - 不同概念不能因为数据结构相同就放进同一列表。“不使用服务”“自动选择”等通常是策略,不是资源项。 - 一个页面或分区只解决一个主要问题;高级和低频配置使用渐进披露。 - “接口返回了”“其他后台都有”“看起来更丰富”不能成为展示理由。 ## 1. 视觉气质 Yuxi 是知识库、知识图谱与 Agent 开发平台,界面应保持克制、清晰、工程化。设计服务于长时间阅读、配置、调试和数据管理,不做营销页式装饰。 核心原则: - 功能优先:视觉层级帮助用户理解任务、状态和下一步操作,不为装饰牺牲信息密度。 - 一致优先:相似功能使用相同布局、颜色、状态和交互反馈。 - 轻量优先:用背景、边框、字号和留白建立层级,避免重阴影、夸张渐变和非功能性动效。 - 可维护优先:新增样式必须基于现有 token 和组件模式,不为单次需求引入新的设计体系。 ## 2. 颜色与 Token 颜色必须优先使用 `web/src/assets/css/base.css` 和 `web/src/assets/css/base.dark.css` 中定义的 CSS 变量。不要在组件中随意新增硬编码色值;确需新增全局色值时,先补充 token 并说明用途。 ### 主色 `--main-*` 和 `--main-color` 用于品牌主色与关键交互: | Token | 使用场景 | | --- | --- | | `--main-color` | 主按钮、选中态、重点链接、关键图标 | | `--main-700` / `--main-600` / `--main-500` | 主色文字、hover、active、强调状态 | | `--main-50` / `--main-30` / `--main-10` | 主色浅背景、选中行背景、轻量提示背景 | 主色只用于表达“当前选择、主要操作、关键入口”。不要把主色当作普通装饰色铺在卡片或大面积背景上。 ### 中性色 `--gray-*` 是默认界面骨架,用于背景、文本、边框和分割线: | Token | 使用场景 | | --- | --- | | `--gray-0` | 页面和卡片主背景 | | `--gray-10` / `--gray-25` / `--gray-50` | 次级背景、hover 背景、弱分区背景 | | `--gray-100` / `--gray-150` / `--gray-200` | 分割线、输入框边框、卡片边框 | | `--gray-900` / `--gray-1000` | 标题和主要正文 | | `--gray-600` / `--gray-500` / `--gray-400` | 辅助说明、占位文本、禁用文本 | 文本也可以使用 Ant Design 兼容语义变量:`--color-text`、`--color-text-secondary`、`--color-text-tertiary`。组件内优先选语义变量;需要更细层级时再使用 `--gray-*`。 ### 语义色 语义色只用于状态和反馈,不用于装饰: | Token 组 | 使用场景 | | --- | --- | | `--color-success-*` | 成功、已完成、连接正常 | | `--color-error-*` | 错误、危险操作、删除、失败 | | `--color-warning-*` | 警告、待处理、需要注意但未失败 | | `--color-info-*` | 信息提示、说明性状态 | | `--color-accent-*` | 少量辅助强调,不能替代主色 | 状态标签建议使用浅背景 + 深文字,例如 `background: var(--color-success-50); color: var(--color-success-700);`。不要只依靠颜色传达状态,必要时配合文字或图标。 ### 图表色 图表和统计可视化优先使用 `--chart-palette-*`。不要复用错误色、警告色做普通图表分类,避免与状态反馈混淆。 ### 暗色模式 Yuxi 通过 `:root.dark` 覆盖同名 token。新增 UI 必须使用 CSS 变量而不是固定浅色值,并检查浅色、暗色两套表现。 新增组件时至少检查: - 背景、卡片、输入框不出现纯白硬编码导致的暗色穿帮。 - 文本和边框在暗色模式下仍有足够对比度。 - hover、focus、disabled、selected、error 等状态在暗色模式下可辨认。 - 图表、代码块和第三方组件需要显式传入 theme 时,使用 `useThemeStore()` 的现有模式。 ## 3. 字体与文本层级 全局字体栈定义在 `web/src/assets/css/main.css`,新增组件不要私自引入新字体。代码、命令、路径和技术标识可使用 monospace,优先复用现有 `@mono-font` 或系统 monospace 栈。 建议层级: | 角色 | 建议样式 | 使用场景 | | --- | --- | --- | | 页面标题 | 20-24px,600 | 页面主标题、弹窗主标题 | | 分组标题 | 16-18px,600 | 卡片标题、表单分组标题 | | 正文 | 14-15px,400 | 常规说明、列表内容 | | 辅助说明 | 12-13px,400 | helper text、元信息、时间、统计说明 | | 标签/状态 | 12px,500 | tag、chip、状态徽标 | | 代码/路径 | 12-14px,monospace | 文件路径、命令、代码片段 | 文本规范: - 标题要短,优先描述对象,不写营销式口号。 - 按钮文案使用明确动作,如“保存配置”“重新检测”“删除文件”。 - 危险操作必须让文案直接表达后果,如“删除知识库”。 - 不使用 placeholder 替代表单 label;placeholder 只做输入示例。 - 不使用负字距或按视口宽度缩放字体,避免宽屏和小屏出现不可控排版。 ## 4. 组件样式 ### 技术栈约束 - 包管理器:`pnpm` - 图标库:优先使用 `lucide-vue-next` - 样式语言:LESS - 颜色变量:使用 `base.css` / `base.dark.css` 中的 CSS 变量 - UI 基础:复用 Ant Design Vue 和项目现有组件模式,避免为单次需求封装新组件体系 ### 按钮 按钮应按操作优先级区分: | 类型 | 使用场景 | 样式规则 | | --- | --- | --- | | 主按钮 | 页面主操作、确认提交 | 使用主色背景或 Ant Design primary,不在同一区域放多个主按钮 | | 次按钮 | 返回、取消、普通操作 | 使用中性边框和浅背景,hover 只增强边框或背景 | | 文本/链接按钮 | 表格行内操作、轻量入口 | 保持轻量,不扩大视觉权重 | | 危险按钮 | 删除、撤销、清空等不可逆操作 | 使用 error 语义色,并配合确认弹窗或明确文案 | | 图标按钮 | 工具栏、折叠、刷新、复制 | 使用 `lucide-icon-btn` 保证图标与文本居中 | 交互状态: - hover 可以改变 `background`、`border-color`、`color`,不要位移或放大。 - focus 必须可见,不能移除键盘焦点样式。 - disabled 使用弱化文本和背景,不绑定 hover 强反馈。 - loading 应保留按钮宽度,避免布局跳动。 ### 输入框与表单 表单用于配置和管理任务,优先保证可读性和错误可恢复: - 输入框背景使用 `--gray-0` 或 Ant Design 默认容器色。 - 边框使用 `--gray-150` / `--gray-200`,focus 使用主色或框架默认 focus ring。 - label 必须稳定显示,helper text 放在输入框下方。 - 错误信息使用 `--color-error-*`,并写清楚修复方式。 - 多字段表单按逻辑分组,避免把无关设置塞进同一行。 - 参数名称应说明业务含义和单位;非显然参数补充调整影响、推荐范围或示例。 - 只允许用户修改真正属于业务配置的值。官方端点、内部枚举和部署参数由系统管理,不伪装成普通输入项。 ### 卡片、列表与表格 Yuxi 的信息界面以配置卡片、列表和表格为主,默认使用轻量分层: - 普通卡片:`background: var(--gray-0); border: 1px solid var(--gray-150); border-radius: 8px;` - 次级区域:可使用 `var(--gray-10)` / `var(--gray-25)` 做轻背景。 - 点击态列表行:hover 只改变背景或边框,不使用 `transform`。 - 表格行内操作保持紧凑,避免每行出现多个高权重按钮。 - 空状态要说明“当前没有什么”和“下一步可以做什么”,不要只显示图标。 阴影只用于真实浮层,如弹窗、抽屉、下拉菜单、tooltip。普通卡片和列表不要用阴影制造装饰性层级。 #### 列表、状态与操作 - 摘要层按“名称 → 用途或关键差异 → 需关注状态 → 操作”组织,不机械展示名称、ID、类型和全部状态。 - 内部 ID 默认隐藏;确需展示时放入详情,使用 monospace 和明确标签或复制入口。 - 一个状态只保留一种主要表达。开关已经表达启用状态时,不再增加绿色点和“已启用”文字。 - 正常状态保持安静,只突出异常和需要行动的状态;颜色不能作为唯一信息载体。 - “不启用”“自动”“沿用默认”等策略放在选择器或策略区,不伪装成普通资源项。 - 开关默认表示立即生效。需要统一保存时,应明确显示未保存状态,不能混用两种提交模型。 - 同类行共享稳定的展开、文本、状态和操作列。图标使用固定盒、统一 `gap` 和 `line-height`,右侧操作保持同一边界。 - 配置列表优先使用一个列表容器和轻分割线,避免每行形成厚重卡片;展开内容与触发行保持连续。 - 至少在真实页面的目标宽度、1024px、768px 和 375px 检查对齐、长文本和溢出。 ### 状态标签 状态标签使用语义色浅背景 + 深文字: | 状态 | 推荐 token | | --- | --- | | 成功/正常 | `--color-success-50` + `--color-success-700` | | 失败/错误 | `--color-error-50` + `--color-error-700` | | 警告/待处理 | `--color-warning-50` + `--color-warning-900` | | 信息/运行中 | `--color-info-50` + `--color-info-700` | | 普通/未知 | `--gray-100` + `--gray-600` | 标签可以使用 pill 圆角,但不要把 pill 形状扩散到所有按钮和卡片。 ### 图标 - 常规图标尺寸使用 16px、18px 或 20px。 - 图标颜色默认继承文本色;需要强调时使用语义 token。 - 图标按钮添加 `lucide-icon-btn`,避免图标与文本或按钮中心线错位。 - 不为同一概念混用多个图标;相同操作在不同页面保持一致。 ## 5. 布局与间距 间距以 4px / 8px 为基础节奏: | 场景 | 建议值 | | --- | --- | | 图标与文本间距 | 6px / 8px | | 表单项内部间距 | 6px / 8px | | 卡片内部 padding | 16px / 20px / 24px | | 列表行间距 | 8px / 12px | | 页面主要区块间距 | 24px / 32px | | 弹窗内容区间距 | 16px / 24px | 布局原则: - 配置型页面保持适度信息密度,不做大面积营销式留白。 - 相邻操作靠近对应内容,页面级操作放在标题区或工具栏。 - 一组按钮中主操作在视觉上最明确,取消/返回等次操作弱化。 - 宽屏下限制正文行长,避免说明文字横跨整个页面。 - 小屏下优先纵向堆叠,避免强行压缩表格和表单字段。 ## 6. 深度与层级 Yuxi 默认采用“背景 + 边框”的轻量层级: | 层级 | 处理方式 | 使用场景 | | --- | --- | --- | | 页面背景 | `var(--gray-0)` 或布局已有背景 | 主页面 | | 次级背景 | `var(--gray-10)` / `var(--gray-25)` | 分区、弱提示、列表 hover | | 卡片边界 | `1px solid var(--gray-150)`,8px 圆角 | 配置卡片、内容块 | | 浮层 | 框架默认阴影或轻量 `--shadow-*` | 弹窗、抽屉、dropdown、tooltip | | 焦点 | 主色 outline / Ant Design focus ring | 键盘可达控件 | 不要在普通内容卡片上使用重阴影。只有当元素真实覆盖其他内容、需要表达浮层关系时,才允许使用阴影。 ## 7. Do / Don't ### Do - 使用 `base.css` 和 `base.dark.css` 中的 token。 - 为新增交互补齐 hover、focus、disabled、loading、empty、error 等必要状态。 - 用背景、边框、字号、间距建立层级。 - 保持浅色和暗色模式一致可用。 - 复用 `lucide-vue-next`、Ant Design Vue 和项目已有组件模式。 - 在复杂配置区保留简短说明,帮助用户理解设置影响。 ### Don't - 不要在 hover 时使用位移、放大、旋转等装饰性 transform。 - 不要使用浓重阴影装饰普通卡片。 - 不要使用夸张渐变或大面积高饱和背景。 - 不要把语义色用于纯装饰。 - 不要新增一次性 helper、样式体系或无复用价值的抽象。 - 不要硬编码浅色模式色值导致暗色模式失效。 - 不要在同一区域放置多个同等视觉权重的主操作。 ## 8. 响应式行为 响应式设计以“功能不丢失、信息不挤压”为优先: - 小屏下表单字段纵向排列,按钮组可换行。 - 侧栏、抽屉和弹窗要保证最小宽度内内容可读。 - 表格在小屏下允许横向滚动;不要把关键字段压缩到不可读。 - 图标按钮和主要操作需要保持可点击区域,移动端目标尺寸尽量不小于 40px。 - 长文本使用省略号时,应提供 tooltip、title 或详情入口查看完整内容。 - 图谱、图表、代码块等宽内容应保留横向滚动或自适应缩放策略。 ## 9. 设计交付与审阅流程 UI/UX 质量不能只由构建通过和没有溢出证明。交付前按三层审阅: 1. **产品层**:每个元素是否有价值,概念是否符合用户心智,是否暴露无用实现细节。 2. **交互层**:操作是否可发现,生效时机是否明确,失败能否恢复,状态是否一致。 3. **视觉层**:层级、对齐、间距、文字、图标、颜色和响应式是否精确一致。 UI 修改必须提供真实运行页面截图,覆盖核心状态和至少一个边界状态。使用真实数据检查长名称、加载、错误、空状态、浅色/暗色和关键响应式宽度;未验证内容在交付说明中列出。 ## 10. Agent Prompt Guide AI agent 修改或生成 Yuxi UI 时,优先按这一节执行。 ### Quick Reference - 页面背景:`var(--gray-0)` - 次级背景:`var(--gray-10)` / `var(--gray-25)` - 主要文本:`var(--color-text)` 或 `var(--gray-900)` - 次级文本:`var(--color-text-secondary)` 或 `var(--gray-600)` - 边框:`var(--gray-150)` / `var(--gray-200)` - 主色:`var(--main-color)` - 卡片圆角:`8px` - 小控件圆角:`4px` / `6px` - 状态标签圆角:`999px` - 常规图标尺寸:`16px` / `18px` / `20px` - 卡片 padding:`16px` / `20px` / `24px` - 普通 hover:只改变背景、边框或文字颜色 ### Example Prompts 实现配置卡片: ```text 实现一个 Yuxi 风格的配置卡片:背景使用 var(--gray-0),边框 1px solid var(--gray-150),圆角 8px,不加阴影。标题使用 var(--color-text),说明文字使用 var(--color-text-secondary)。hover 只轻微改变边框或背景,不使用 transform。 ``` 实现工具栏按钮: ```text 实现一个工具栏按钮:优先使用 lucide-vue-next 图标,图标尺寸 16px,按钮添加 lucide-icon-btn。默认使用中性色,hover 时使用 var(--main-color) 或 var(--main-10) 强化,不位移、不放大。 ``` 实现状态标签: ```text 实现状态标签:成功使用 var(--color-success-50) 背景和 var(--color-success-700) 文字;错误使用 var(--color-error-50) 背景和 var(--color-error-700) 文字;警告使用 var(--color-warning-50) 背景和 var(--color-warning-900) 文字。圆角使用 999px,文字保持 12px。 ``` ### 实现检查清单 - 已说明用户任务、信息层级和提交模型。 - 没有平铺无用字段、重复状态或伪装成资源的策略项。 - 名称和描述使用用户语言,内部 ID 仅按需展示。 - 图标、文字、输入框和操作列已完成视觉对齐检查。 - 使用现有 CSS 变量,没有新增随意硬编码色值。 - 浅色和暗色模式都检查过。 - hover、focus、disabled、loading、empty、error 状态符合场景。 - 没有使用 hover 位移、放大、旋转或装饰性动画。 - 普通卡片没有使用重阴影。 - 图标来自 `lucide-vue-next`,尺寸和对齐符合现有模式。 - API 接口、组件位置、样式语言符合前端开发规范。 - 已在真实页面和关键响应式宽度截图验收,而不只依赖构建通过。 ## 参考资料 - `web/src/assets/css/base.css`:浅色模式 token - `web/src/assets/css/base.dark.css`:暗色模式 token - `web/src/assets/css/main.css`:全局字体、布局基础样式和 `lucide-icon-btn` - [Awesome DESIGN.md](https://github.com/VoltAgent/awesome-design-md):面向 AI agent 的 `DESIGN.md` 样例集合 --- ### Develop Guides/Roadmap # 开发路线图 路线图可能会经常变更,如果有强烈的建议,可以在 [issue](https://github.com/xerrors/Yuxi/issues) 中提。 项目看板(Maintainer Only):[GitHub Project](https://github.com/users/xerrors/projects/2) 后续 0.8 的非兼容计划更新 1. Milvus 3.0 https://milvus.io/docs/zh/release_notes.md 2. API 和 Worker 等从文件系统解耦,分布式部署 ### 看板 **知识库** - [ ] 知识库 Mindmap 扩展:新增基于文件名的文件“边”构建,支持聚类算法形成社区节点,并提供思维导图 (Mindmap) 可视化结构展示 - [ ] 知识库工具新增 query_keywords 工具,专门用于基于关键词命中的排序 - [ ] 增强知识库检索体验:增强 metadata、标签等 - [ ] 个人工作区增加可检索能力(但是不做向量化) **智能体** - [ ] 子智能体缺少 steer 机制 - [ ] 子智能体的双向通信,缺少 ask_for_main_agent 的机制 - [ ] 子智能体与子智能体的通信机制 **其他** - [ ] 集成 Memory,基于 deepagents 的文件后端实现,需要考虑定位 - [ ] 优化 Agent 向用户追问交互:支持较长文本回答输入,并在流式输出时保持聊天区跟随最新内容([#753](https://github.com/xerrors/Yuxi/issues/753)) ### Bugs - [ ] 点开对话的时候要能够自动定位到尾部,而不是最开始。 --- 历史版本发布记录已迁移到 [版本变更记录](./changelog.md)。 维护说明: - roadmap 仅保留未来规划(看板/Bugs/里程碑方向)。 - 具体版本发布内容统一维护在 changelog。 --- ### Develop Guides/Testing Guidelines # 测试规范与工作流 本文档用于指导 Yuxi 后续如何创建测试文件、修改测试文件,以及如何验证项目功能。目标是务实、稳定、可执行,不追求过度设计。 ## 1. 测试分层 当前测试统一分为三层: - `backend/test/unit` - 纯单元测试 - 不依赖运行中的 Docker 服务 - 优先使用 `monkeypatch`、fake repo、stub、`tmp_path` - `backend/test/integration` - 真实 API 集成测试 - 依赖 `docker compose up -d` 后的运行环境 - 统一通过真实 HTTP 接口验证认证、权限、参数和副作用 - `backend/test/e2e` - 关键链路端到端测试 - 覆盖 run、viewer、附件、文件落盘等完整流程 - 默认数量少、执行更慢 其他子项目约定: - 前端单元测试统一放在 `web/test/unit`,通过 `pnpm test:unit` 运行。 - `packages/yuxi-cli` 是独立 Python 包,沿用 Python 社区惯例放在 `packages/yuxi-cli/tests`。 - 同一个子项目内不要同时创建 `test` 和 `tests` 两个测试根目录。 ## 2. 新增测试时怎么选目录 新增测试前先判断: 1. 只测 Python 逻辑,不需要真实服务 放到 `unit` 2. 需要请求真实接口 放到 `integration/api` 3. 需要验证从入口到最终结果的完整链路 放到 `e2e` 不要再默认把测试直接丢到 `backend/test/` 根目录。 ## 3. 文件和命名规范 文件名: - 使用 `test__.py` - 一个文件只测一个明确主题 函数名: - 使用 `test_<行为>_<预期结果>` - 名称直接表达业务语义 示例: - `test_create_agent_run_commits_before_enqueue` - `test_viewer_download_returns_attachment_response` - `test_agent_bubble_sort_run_creates_expected_artifacts` ## 4. 写测试的基本要求 每个测试尽量保持三段式: 1. Arrange:准备数据、打桩、创建资源 2. Act:调用被测行为 3. Assert:断言结果 要求: - 不要只断言 `status_code == 200` - 要断言关键业务字段和副作用 - 失败信息要能帮助定位问题 ## 5. fixture 规范 原则: - 同一个文件内复用,优先写本地 helper - 多个文件复用,再提取到对应层级的 `conftest.py` - 根 `backend/test/conftest.py` 只保留通用 marker,不绑定真实环境 当前约定: - `backend/test/integration/conftest.py` - 管理 `test_client`、`admin_headers`、`standard_user`、`knowledge_database` - `backend/test/e2e/conftest.py` - 管理 `e2e_client`、`e2e_headers`、`e2e_agent_context` ## 6. 允许与禁止 允许: - 在单元测试里使用 `monkeypatch` - 在集成测试里通过 fixture 创建测试资源 - 在 E2E 中使用轮询等待最终状态 禁止: - 在测试文件里硬编码真实账号密码 - 在单元测试里请求真实 HTTP 服务 - 在根 `conftest.py` 里继续添加重环境依赖 - 写 `if __name__ == "__main__":` 作为测试入口 - 用 `print` 作为通过/失败判断手段 - 因为系统里没有默认数据就直接 `skip` ## 7. skip 的使用规则 只在下面两类场景允许 `pytest.skip`: 1. 外部可选能力不可用 例如 OCR 服务、外部模型服务未启动 2. E2E 环境变量未配置 例如没有配置专用测试账号 不允许把“系统里没有 agent / config / 预置数据”当成正常 skip 条件。 这类情况应优先改为 fixture 显式准备资源,或者直接 fail 暴露环境问题。 ## 8. 修改测试文件时的规则 如果是修 bug: 1. 先补一个能稳定复现 bug 的测试 2. 再修代码 3. 先跑最小相关测试集 4. 再跑相关层级回归 如果是改已有功能: - 行为变了,就更新断言 - 文件职责混乱,就顺手拆分或迁移目录 - 依赖现成系统状态的测试,优先改成 fixture 建资源 ## 9. 运行方式 启动环境: ```bash docker compose up -d docker ps docker logs api-dev --tail 100 ``` 运行单元测试: ```bash docker compose exec api uv run --group test pytest test/unit -m "not slow" ``` 运行集成测试: ```bash docker compose exec api uv run --group test pytest test/integration ``` 运行 E2E: ```bash docker compose exec api uv run --group test pytest test/e2e -m e2e ``` 运行全部测试: ```bash docker compose exec api uv run --group test pytest test ``` 也可以使用: ```bash backend/test/run_tests.sh unit backend/test/run_tests.sh integration backend/test/run_tests.sh e2e backend/test/run_tests.sh all ``` 运行前端单元测试: ```bash docker compose exec web pnpm test:unit ``` ## 10. 推荐的日常开发流程 建议顺序: 1. 本地改代码 2. 先跑相关单元测试 3. 涉及接口时跑相关集成测试 4. 涉及关键主链路时补跑对应 E2E 5. 提交前至少完成“检查 -> 测试 -> Lint” ## 11. 当前落地原则 这套规范的重点不是一步到位重写所有旧测试,而是: - 新增测试必须按新目录落位 - 改到旧测试时顺手迁移 - 优先保持测试可执行和可信 - 优先减少假绿和环境耦合 对当前 Yuxi 来说,这就是最务实、也最容易持续执行的测试标准。 --- ### Agents/Agent Evaluation # 智能体评估 Yuxi 的智能体评估用于回答一个具体问题:某个 Agent 在一组固定任务上能不能稳定完成工作。它不在 Yuxi 内部维护评估数据集、评分规则或对比报表,而是把这些能力交给 Langfuse;Yuxi 只负责按真实 Agent 运行链路执行每条样例,并把结果回写到 Langfuse experiment。 ## 适用边界 这个功能面向 Agent 端到端行为评估,不是知识库检索指标评估。如果你要评估 RAG 检索召回、答案准确率和知识库基准,请使用「知识库评估」。如果你要评估一个 Agent 在编程、研究、工具调用、规划或多步骤任务上的真实表现,则使用本页介绍的 Langfuse dataset experiment 流程。 评估链路保持三个边界: - Langfuse 负责 dataset、experiment、score、对比和可视化。 - Yuxi 后端负责创建正常 conversation 和 AgentRun,并复用 worker 执行链路。 - `yuxi` CLI 只负责读取 Langfuse dataset、运行 experiment、调用 Yuxi eval API,不负责创建或上传 dataset。 ## 前置条件 1. Yuxi 后端已经启用 Langfuse tracing,并在 `.env` 中配置: ```bash LANGFUSE_PUBLIC_KEY=... LANGFUSE_SECRET_KEY=... LANGFUSE_BASE_URL=https://cloud.langfuse.com ``` 2. 本机 CLI 环境也能读取同一组 Langfuse 环境变量。`yuxi agent eval` 需要直接调用 Langfuse SDK 读取 dataset 和创建 experiment。 3. 已经登录 Yuxi CLI: ```bash yuxi remote add local http://localhost:5173 yuxi login --browser ``` 评估命令必须使用当前 remote 的登录态,不支持在 `yuxi agent eval` 上直接传 token。CI 环境也必须先执行登录步骤,例如: ```bash yuxi login --api-key "$YUXI_API_KEY" ``` 4. 要评估的 Agent 已经存在,并且当前 CLI 登录用户有权限访问该 Agent。命令使用的是 Agent slug,例如 `default-chatbot`。 ## 准备 Langfuse Dataset 评估数据集必须先在 Langfuse 中准备好。CLI 不提供上传能力,避免把数据集管理职责混进运行命令。 Dataset item 的 `input` 推荐使用下面任一字段承载任务文本: ```json {"input": "请用 Python 完成任务并给出最终答案:..."} ``` 也兼容 `query`、`question`、`prompt`。`expected_output` 可以写标准答案,后续在 Langfuse UI 或 evaluator 中使用。 ## 运行评估 上传 dataset 后,用 dataset name 运行: ```bash yuxi agent eval \ --dataset-name yuxi-python-tasks-20260619-demo \ --agent-slug default-chatbot \ --experiment-name default-chatbot-python-tasks-20260619 \ --max-concurrency 1 \ --timeout-seconds 900 ``` 命令执行流程: 1. 从 Langfuse 读取 dataset。 2. 对每条 dataset item 提取任务文本。 3. 调用 `POST /api/agent-invocation/eval/runs`。 4. Yuxi 后端创建正常 conversation 和 AgentRun。 5. worker 按真实 Agent 链路执行任务。 6. 接口阻塞到 run 终态后返回最终 assistant output。 7. CLI 将 output 写回 Langfuse experiment item。 `--max-concurrency` 控制 Langfuse experiment runner 的并发数。复杂 Agent 或本地开发环境建议从 `1` 开始,避免同时压垮模型服务、worker 或沙盒。 ## 查看结果 评估完成后,在 Langfuse 控制台打开对应 dataset,可以看到刚创建的 experiment run。每条 item 会保存本次 Yuxi Agent 的最终输出。Yuxi 后端会在运行内部使用 `agent_invocation_meta.evaluation` 保存评估上下文,并给 Langfuse trace 写入 `agent_evaluation` 标记,方便筛选: - `source=agent_evaluation` - `evaluation_dataset_name=` - `evaluation_dataset_item_id=` - `evaluation_experiment_name=` 如果没有看到 experiment,先确认 CLI 环境中的 Langfuse key 和 dataset name 是否正确。如果 experiment 有记录但 Yuxi trace 缺失,检查 `api-dev` 容器是否读取到了同一组 Langfuse 配置。 --- ### Agents/Agent Request Queue # Agent 请求队列与调度设计 Agent 运行可能包含模型调用、知识库检索、工具执行和文件读写,持续时间通常高于普通接口请求。在一次运行尚未结束时,同一对话线程可能收到新的用户输入或外部调用。请求队列用于接收这些请求,并控制它们进入 Agent 执行链路的顺序。 本文介绍 Yuxi Agent 请求队列的设计目标、调度规则、状态变化和当前功能边界。具体接口、数据表和事务实现不在本文展开。 ## 设计目标 请求队列主要处理以下问题: - 同一对话线程内的多个请求不应并发修改同一份对话上下文。 - Agent 运行期间仍可接收后续请求,不要求调用方等待当前运行结束后再次提交。 - 排队状态需要持久化,页面刷新或服务恢复后仍可查询。 - 调用方需要区分“请求已接收”和“Agent 已开始运行”。 - 网页聊天和同步 API 对忙碌线程的处理方式不同,需要提供明确的策略选择。 请求队列不负责提高单次 Agent 运行速度,也不改变 Agent 内部的模型和工具执行方式。它负责确定请求何时进入现有的运行链路。 ## 调度范围 队列以用户、Agent 和对话线程共同确定的执行范围为单位。同一范围内最多存在一个活跃的 Agent 运行,后续请求按提交顺序等待。 不同对话线程拥有独立的调度范围,可以并行运行。例如,一个用户在两个不同对话中分别提交任务,两条线程不会因为队列而相互阻塞。 ```text 线程一:请求 A(运行中) -> 请求 B(队列第 1 位) -> 请求 C(队列第 2 位) 线程二:请求 D(运行中) -> 请求 E(队列第 1 位) ``` 普通请求采用 FIFO(先入先出)规则。顺序以服务端记录的创建时间和稳定顺序字段为准,不依赖浏览器时间;待处理的 Steer 会成为下一条请求,其余请求之间仍保持 FIFO。 ## 请求与运行 Yuxi 将 Agent 请求和 Agent 运行作为两个不同阶段处理。 **Agent 请求**表示系统已经接收了一次输入。请求在提交时创建,可处于排队、已派发、已取消或已拒绝等状态。 **Agent 运行**表示请求已经获得执行机会,并进入实际的 Agent 执行链路。只有请求被派发后,才会创建对应运行。 这种划分主要有三项作用: 1. 排队请求可以独立查询和取消。 2. 页面刷新后可以恢复队列,而不依赖前端内存状态。 3. 排队中的用户消息不会提前进入当前 Agent 运行的上下文。 第三点用于保证对话顺序。假设请求 A 正在运行,请求 B 和 C 已经排队,A 对应的 Agent 上下文不会提前包含 B 和 C。B 被派发后,其输入才会成为下一轮 Agent 运行的一部分。 ## 调度过程 一次普通请求的处理过程如下: 1. 系统接收输入并创建请求记录。 2. 如果线程当前空闲,请求立即派发并创建 Agent 运行。 3. 如果请求不能立即成为并派发 FIFO 队头,请求根据队列策略进入等待或被拒绝。 4. 运行成功结束后,调度器检查同一线程的队头请求。 5. 如果存在排队请求,队头请求被派发,其他请求的位置相应前移。 同一请求不会因为客户端重试而重复排队。调用方使用相同请求 ID 重试时,系统返回已有请求及其运行状态。请求 ID 被其他用户或不匹配的目标复用时,应作为冲突处理。 ## 队列策略 当前支持 `enqueue`、`reject` 和 `steer` 三种策略。 | 策略 | 线程空闲 | 线程忙碌 | 主要适用场景 | | --- | --- | --- | --- | | `enqueue` | 立即派发 | 保存并进入 FIFO 队列 | 网页聊天、异步 Agent Call | | `reject` | 立即派发 | 返回拒绝结果,不进入队列 | 同步 Agent Call、需要立即决策的调用方 | | `steer` | 立即派发 | 当前步骤结束后优先执行 | 运行中修正后续方向 | ### enqueue `enqueue` 用于允许延后执行的请求。请求排队后,调用方可以读取其当前位置,也可以在派发前取消。 网页聊天默认采用该策略,因此当前回复生成期间仍可提交后续输入。排队请求与当前回复分开展示,避免尚未执行的输入提前出现在对话正文中。 ### reject `reject` 用于不接受排队的调用。只要请求不能立即成为并派发 FIFO 队头,系统就记录并返回拒绝状态,不创建 Agent 运行。这包括线程忙碌、已有积压请求、队列因失败或取消暂停,以及运行正在等待人工回答的情况。 同步 Agent Call 默认采用该策略。同步调用会等待最终运行结果,如果允许其进入队列,请求等待时间将同时包含排队和执行两个阶段。因此,同步入口通过明确拒绝,使调用方可以自行决定重试、切换线程或终止本次调用。 拒绝是预期的调度结果,不属于服务器内部错误。 ### steer `steer` 是 `enqueue` 的优先执行形式,不引入新的请求或运行状态。请求仍以 `queued` 保存;Chatbot Middleware 在下一次模型调用前发现待处理 Steer 时结束当前 Graph,worker 按既有 `completed` 接力流程派发该请求。 因此,已经开始的模型调用和工具批次会正常完成并写入 checkpoint,Steer 不会强制取消工具。当前 Run 按普通 `completed` 结束,Steer 创建的新 Run 继续读取同一线程上下文。已有普通 Chat 排队项也可以原地提升为 Steer;同一线程一次只接受一个待处理 Steer。 Steer 意图采用持久化请求作为唯一事实来源,按以下生命周期边界消费,避免到达时机造成丢失: 1. 请求事务提交后,`abefore_model` 在下一次模型调用前检查待处理 Steer。 2. `aafter_model` 对不含工具调用的模型轮次再次检查,覆盖 Steer 恰好到达最后一次模型检查之后的窗口;含工具调用时不跳过工具批次。 3. 当前 Run 以 `completed` 结束后,worker 通过队列头派发 Steer;若进程在接力前退出,worker 启动恢复会重新扫描 queued 请求并执行同一派发逻辑。 这套兜底只保证 Steer 意图最终进入下一次 Run,不改变“已开始的模型调用和完整工具批次不可强制终止”的安全边界。 ## 状态说明 请求和运行分别维护状态。请求状态用于描述排队阶段,运行状态用于描述实际执行阶段。 | 请求状态 | 说明 | | --- | --- | | `queued` | 请求已保存,正在等待派发 | | `dispatched` | 请求已派发,并已关联 Agent 运行 | | `cancelled` | 请求在派发前被取消 | | `rejected` | 采用 `reject` 策略时因线程忙碌被拒绝 | | `failed` | 请求在派发前处理失败 | 请求派发后,执行结果由 Agent 运行状态表达,例如完成、失败、取消或中断。前端在排队阶段订阅请求状态,在收到运行创建信息后切换到运行事件流。 ## 取消处理 取消排队请求和停止 Agent 运行是两个独立操作。 - 排队请求尚未开始执行,可以单独取消。取消后不会影响当前活跃运行,后续请求的位置会重新计算。 - Steer 在等待活跃 Run 到达安全点时不能取消,避免取消操作与 Middleware 消费引导意图竞态;若目标 Run 失败或取消、队列进入暂停后,可以删除该 Steer。 - 已派发请求已经进入运行阶段,需要通过运行取消能力停止,不再通过队列取消接口处理。 这种区分可以避免取消一个排队项时误停当前运行,也可以保持请求状态与实际执行状态一致。 ## 失败、中断与后续请求 Agent 运行成功完成后自动派发下一条请求。运行失败、被取消或进入需要人工处理的中断状态时,系统按以下规则处理后续请求: - failed/cancelled 时已经在等待的请求会保持暂停。页面会展示原因,用户可以点击“继续队列”;该动作只派发当前 FIFO 队头。 - failed/cancelled 发生时队列为空,之后提交的新请求属于新的输入意图,可以正常立即执行。 - interrupted 表示当前运行正在等待回答或审批。中断前已经存在的排队请求继续保留;中断期间的新普通请求会在写入 Message/Request 前返回 `run_interrupted`,也不能通过“继续队列”绕过。用户完成 resume 后,既有队列才会按原有完成链路继续。 页面刷新后会恢复暂停原因和继续操作。若完成后的自动派发因短暂故障遗漏,系统会把该队列识别为待恢复状态并继续既有 completed 调度语义,而不会把仍有请求的队列视为已空闲。 ## 持久化与恢复 请求、输入消息和派发关系保存在数据库中。浏览器刷新后,前端可以重新读取当前线程的排队请求和位置。 Agent 运行由后台任务系统执行。`pending` AgentRun 同时表达已经提交、仍需投递或等待 worker 接收的执行意图。为处理“数据库已经记录派发,但任务尚未成功投递”这一故障窗口,completed hook 重试和服务启动恢复都会优先重新投递已有 pending run;没有 pending run 时才会派发 ready 队头。恢复过程复用已有请求和运行记录,不创建重复运行。 同一对话线程的 intake、resume、continue 和自动接力会锁定线程对应的 Conversation 记录,再读取和修改 request/run 事实。线程级共同锁负责保证并发请求的严格 FIFO,active-run 唯一索引继续作为最终数据库保护。 持久化恢复保证的是调度状态可继续处理,不代表失败中的 Agent 运行会自动重新执行。具体是否重试由运行层的重试规则决定。 ## 对话展示 排队区和对话正文承担不同职责: - 排队区展示尚未开始的请求、当前位置和取消操作。 - 对话正文展示已经进入 Agent 运行的用户输入和回复。 请求派发后,其用户消息从排队状态转入对应运行轮次。多个请求的展示顺序与执行顺序一致,例如: ```text 请求 A -> A 的回复 -> 请求 B -> B 的回复 ``` 排队中的 B 不会覆盖或打断正在生成的 A 的回复。 ## 当前范围 当前版本包含以下能力: - 普通聊天、异步 Agent Call 和评估入口使用统一请求接收流程。 - 支持 `enqueue`、`reject` 和主会话 Chat 的 `steer` 策略。 - 同一线程串行调度,不同线程可并行运行。 - 支持排队位置查询、页面刷新恢复和派发前取消。 - 请求派发后转入已有 Agent 运行与事件流链路。 当前版本不包含以下能力: - 强制取消正在执行的模型或工具。 - 多个 Steer 的排序、合并或连续接替。 - 通用请求优先级和任意插队。 - 运行失败后的自动回滚。 - 多个请求合并为一次 Agent 运行。 这些能力涉及运行上下文、工具副作用和消息展示语义,需要在扩展队列策略时分别设计。 --- ### Agents/Agents Config # 智能体配置 Yuxi 的智能体系统基于 LangGraph 构建。对开发者来说,最重要的不是单独理解某个页面或某个字段,而是理解三件事: - Agent 如何被定义和发现 - Context 如何驱动配置界面 - Context 如何贯穿一次 Agent 运行周期 本文聚焦这三部分。 ## 1. 整体结构 智能体开发围绕四个核心对象展开: - **`BaseAgent`**:统一的 Agent 抽象,定义 `get_graph()`、`context_schema`、`capabilities` - **`BaseContext`**:配置 Schema,也是前端配置项的来源 - **Graph / Middleware**:LangGraph 图与中间件链,决定运行时行为 - **Agent**:数据库中的一级智能体实例,保存展示信息、后端 `backend_id`、共享权限和 `config_json.context` 仓库中已经内置了可直接参考的智能体: - `chatbot`:通用对话智能体,使用 `ChatBotContext` 扩展可调用子智能体配置 - `subagent`:专用子智能体后端,使用 `SubAgentContext`,用于被主 Agent 通过 task 工具调用 ## 2. Agent 的代码组织 建议在 `backend/package/yuxi/agents` 下按包组织一个智能体: ```text backend/package/yuxi/agents/ └── my_agent/ ├── __init__.py ├── context.py └── graph.py ``` 最小实现通常包含: - 一个继承 `BaseAgent` 的主类 - 一个 `context_schema` - 一个 `get_graph()` 实现 示例: ```python from yuxi.agents import BaseAgent, BaseContext, load_chat_model from langchain.agents import create_agent class MyAgent(BaseAgent): name = "我的智能体" description = "示例智能体" context_schema = BaseContext async def get_graph(self, context=None, **kwargs): context = context or self.context_schema() graph = create_agent( model=load_chat_model(context.model), system_prompt=context.system_prompt, checkpointer=await self._get_checkpointer(), ) return graph ``` ## 3. Context 是配置模型,不只是运行时参数 ### 3.1 `BaseContext` 的角色 `BaseContext` 定义在 `backend/package/yuxi/agents/context.py`,它不是一个普通的数据类,而是整个智能体配置链路的核心: - 它定义了 Agent 可以配置哪些字段 - 它定义了这些字段在前端如何展示 - 它也是运行期传入 Graph 和中间件的上下文对象 当前基础字段包括: | 字段 | 作用 | | --- | --- | | `system_prompt` | 系统提示词 | | `model` | 主模型 | | `tools` | 启用的内置工具 | | `knowledges` | 关联知识库 | | `mcps` | 启用的 MCP 服务器 | | `skills` | 关联 Skills | | `summary_threshold` | 摘要触发阈值 | | `summary_prompt` | 摘要触发时使用的提示词 | | `summary_keep_messages` | 摘要后保留的最近消息数 | | `summary_tool_result_token_limit` | 工具结果 offload 阈值和预览 token 上限 | | `summary_l2_trigger_ratio` | L1 后进入 L2 summary 的触发比例 | | `max_execution_steps` | 单次运行最大执行步数 | | `model_retry_times` | 模型调用失败时的最大重试次数 | | `thread_id` / `uid` | 运行期标识,不作为页面配置项暴露 | `tools`、`knowledges`、`mcps`、`skills` 在未显式配置时会默认启用当前用户可访问的全部资源。 `ChatBotContext` 在 `BaseContext` 之上增加 `subagents` 字段,表示当前主 Agent 允许调用的子智能体。`subagents` 未显式配置或保存空列表时会默认启用当前用户可见的全部子智能体;显式选择后则作为允许列表过滤。 `SubAgentContext` 在 `BaseContext` 之上增加 `parent_thread_id`、`file_thread_id`、`skills_thread_id` 与 `is_subagent_runtime` 等隐藏运行态字段,不包含 `subagents`,因此子智能体不能继续配置下一层子智能体。 ### 3.2 前端配置项如何从 Context 生成 `BaseContext.get_configurable_items()` 会遍历字段定义,把字段类型、默认值、描述、模板元数据整理成 `configurable_items`。 随后: 1. `BaseAgent.get_info()` 暴露 `configurable_items` 2. 前端读取 Agent 详情 3. `AgentRuntimeConfigForm` 按 `kind` 渲染不同控件 也就是说,`AgentRuntimeConfigForm` 不是手写每个字段,而是直接消费 `context_schema` 生成的配置描述。 这也是为什么: - 新增一个 Context 字段,往往会直接影响侧边栏 - 字段的 `metadata` 信息会直接影响展示方式 ### 3.3 配置表单与 Agent 的联动关系 这部分是最关键的。 在前端: - `AgentRuntimeConfigForm.vue` 负责渲染配置表单 - `agentStore` 加载配置时,读取 `config_json.context` - 如果某些字段未配置,会用 `configurable_items` 中的默认值补全 - 保存时,前端将当前表单写回 `config_json: { context: agentConfig }` 因此真实关系是: ```text context_schema -> get_configurable_items() -> Agent detail API 返回 configurable_items -> AgentRuntimeConfigForm 渲染表单 -> 用户编辑后保存到 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): context_schema = MyAgentContext ``` 这会同时影响: - 后端可接收的配置结构 - 前端配置侧边栏的展示内容 - 运行期 `context` 可访问的字段 ## 4. Context 如何贯穿 Agent 的运行周期 Context 的价值不只在“配置页面”。它贯穿了从配置加载到实际执行的整条链路。 ### 4.1 配置加载阶段 在聊天请求进入后端时,服务会先解析请求中的 `agent_id` 或线程已绑定的 Agent,再加载对应配置。 当前主流程在 `chat_service.py` 中: 1. 新线程通过 `agent_id` 查找用户可访问的 Agent 2. 已有线程通过 `thread_id` 读取 `Conversation.agent_id`,并拒绝运行中切换 Agent 3. 取出 Agent 的 `config_json.context` 4. 与 `uid`、`thread_id` 合并成运行时输入 也就是说,运行期 Context 的基础来源并不是前端临时状态,而是数据库中保存的 Agent。 用户工作区会默认创建 `agents/AGENTS.md`、`agents/USER.md` 与 `agents/MEMORY.md`。每次 Agent 运行开始时,后端按这三个文件的固定顺序读取非空内容并追加到 `system_prompt`:前者适合放长期工作约束,`USER.md` 记录稳定的用户偏好,`MEMORY.md` 保存可跨对话复用的事实。它们属于用户级共享工作区;文件不存在、为空或不可读时不会阻断运行。每个文件最多读取 64 KiB,超出部分会截断并标记。 合并后的提示词结构可以理解为: ```text Agent.config_json.context.system_prompt + 用户工作区 agents/AGENTS.md、USER.md、MEMORY.md 内容 + 运行期中间件继续追加的系统提示段 ``` 一次性要求仍应直接写在当前对话中,而不应写入这三个跨对话文件。 ### 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` - 摘要阈值:`context.summary_threshold` 因此 Graph 不是和 Context 解耦的。相反,Graph 的构造本身就依赖 Context。普通 Agent 在归一化后的 `context.subagents` 非空时会挂载 Yuxi 的 task middleware;`SubAgentBackend` 自身隐藏并清空 `subagents` 字段,因此子智能体不会继续调用子智能体。 ### 4.4 Graph 构建与中间件运行阶段 `get_graph()` 创建 LangGraph 前会先调用 `prepare_agent_runtime_context`,用当前用户重新过滤资源字段,并派生运行时字段: - `_visible_knowledge_bases`:当前会话实际可查询的知识库对象 - `_prompt_skills`:需要注入提示词的 Skill 闭包 - `_readable_skills`:当前运行时可读的 Skill 闭包,包括共享只读投影与个人工作区 Skill 随后 Graph 构建会直接使用这份 Context: - `load_chat_model(context.model)` 选择主模型 - `build_prompt_with_context(context)` 生成系统提示词 - `resolve_configured_runtime_tools(context)` 组装已配置的内置工具和 MCP 工具 - `SkillsMiddleware` 根据 `_prompt_skills` 注入 Skill 提示段,并在 Skill 被激活后按需让模型看见其工具与 MCP 依赖;知识库工具由内置 `knowledge-base` Skill 提供 - `save_attachments_to_fs` 将线程附件转换为运行时可读的文件提示 文件系统与沙盒接入同样读取这些运行时字段: - 普通 Agent 默认使用当前 `thread_id` 作为文件与 Skills 作用域 - 子智能体使用 child `thread_id` 做 checkpoint,`file_thread_id` 指向父会话 uploads/outputs,`skills_thread_id` 指向子智能体自身 Skills 作用域 - 通过 `_readable_skills` 与来源映射决定共享/内置 Skill 的 `/home/gem/skills` 投影;个人 Skill 直接读取工作区 所以 Context 既是输入配置,也是 Graph 创建前整理出的运行时资源上下文。 ### 4.5 文件系统与 Viewer 阶段 文件系统服务不会重新发明一套配置结构,而是再次从 `config_json.context` 还原出 runtime context,用于: - 判断当前线程下 Agent 可见的 Skills - 构造 Agent 视图的 composite backend - 构造 Viewer 视图的文件系统展示 这也是为什么 Context 不只是聊天链路的一部分,它还影响: - Agent 文件工具 - Viewer 文件浏览器 - Skills 可见性 - 沙盒挂载语义 ### 4.6 恢复运行阶段 在 `resume` 流程中,系统同样会通过线程绑定的 Agent 重新构造 Context,再继续执行 Graph。 也就是说,无论是: - 首次对话 - 中断恢复 - 文件系统查看 它们都依赖同一份 Context 配置来源。 ## 5. `capabilities` 的作用 `capabilities` 用于声明前端可直接从 Agent 静态元数据判断的能力开关,控制上传入口、文件面板等固定 UI,不等同于 Context,也不适合表达运行中才会出现的状态。 示例: ```python class MyAgent(BaseAgent): capabilities = ["file_upload", "files"] ``` 当前常见能力包括: | capability | 说明 | | --- | --- | | `file_upload` | 启用上传入口 | | `files` | 启用文件面板 | 像 todo 这类运行态信息,不建议再放进 `capabilities`。Yuxi 当前会直接从 LangGraph state 中提取 `agent_state`,前端在创建对话后常态化展示状态入口,并在状态面板中渲染 `todos`、`files`、`artifacts`、`subagent_runs` 等运行时内容。 它解决的是“Agent 先天支持什么固定入口”,而不是“运行时当前产生了什么状态”。 ## 6. 开发建议 ### 6.1 新增配置时优先改 Context 如果一个配置项会影响 Agent 行为,优先考虑把它做成 `context_schema` 字段,而不是前端单独维护状态。 ### 6.2 把 Graph 逻辑和配置逻辑分开 推荐做法: - `context.py` 定义配置模型 - `graph.py` 使用这些配置构建 Graph 这样前后端联动关系会清晰很多。 ### 6.3 把“配置来源”和“运行时状态”区分开 建议始终区分两层语义: - `config_json.context`:持久化配置来源 - `runtime.context`:实际运行对象,可能被中间件继续补充或修改 ## 7. 相关主题 - [工具系统](./tools-system.md) - [中间件](./middleware.md) - [沙盒架构与设计](./sandbox-architecture.md) - [MCP 集成](./mcp-integration.md) - [Skills 管理](./skills-management.md) - [子智能体](./subagents-management.md) - [Langfuse 集成](../advanced/langfuse-integration.md) --- ### Agents/Mcp Integration # MCP 集成 MCP(Model Context Protocol)是扩展智能体能力的重要方式。系统支持通过管理界面动态配置 MCP 服务器,无需修改代码。 内置 MCP 服务器以代码为事实源:系统启动时会自动补齐缺失项,并用代码中的最新连接与展示字段覆盖数据库定义;是否“已添加”以及工具级禁用列表仍保留数据库状态。 ## 支持的传输协议 | 协议 | 说明 | 适用场景 | |------|------|----------| | Streamable HTTP | 流式 HTTP 连接 | 远程 MCP 服务 | | SSE | Server-Sent Events | 标准 HTTP 长连接 | | Stdio | 标准输入输出 | 仅限代码中维护的系统内置 MCP | ## 配置示例 ### 远程 MCP 服务 ```json { "name": "custom-remote-mcp", "transport": "streamable_http", "url": "https://example.com/mcp" } ``` 管理接口只允许配置 `streamable_http` 与 `sse` 远程服务。`stdio` 会在 API / worker 容器内启动本地进程, 因此仅允许 `_DEFAULT_MCP_SERVERS` 中代码定义的系统内置 MCP;管理员不能通过接口新增 stdio 服务, 也不能修改内置 MCP 的连接配置。升级前已保存的用户 stdio 配置会被禁用,需要迁移为远程 MCP。 ## 添加系统内置 stdio MCP 只有经过代码审查、确实需要在 Yuxi 容器内启动本地进程的 MCP 才应使用 stdio。能够部署为远程服务时, 优先使用 SSE 或 Streamable HTTP,通过管理界面添加即可。 编辑 `backend/package/yuxi/agents/mcp/service.py` 中的 `_DEFAULT_MCP_SERVERS`,新增一个全局唯一的 slug。 下面的包名和版本仅作结构参考,实际提交时应替换为经过审查并固定版本的 MCP 包: ```python _DEFAULT_MCP_SERVERS = { # 已有内置 MCP ... "example-mcp": { "command": "npx", "args": ["-y", "@scope/example-mcp@1.2.3"], "transport": "stdio", "description": "示例内置 MCP,请替换为真实用途说明", "icon": "🧩", "tags": ["内置", "示例"], }, } ``` 常用字段如下: | 字段 | 要求 | |------|------| | `command` | 容器内已安装或明确可用的可执行程序,不接受用户输入 | | `args` | 固定参数列表;使用包执行器时应固定包版本,不使用动态脚本参数 | | `transport` | 固定为 `stdio` | | `description` | 说明 MCP 的具体能力和使用场景 | | `icon` / `tags` | 管理界面的展示信息 | | `env` | 仅允许非敏感固定值;密钥不得提交到代码或同步进数据库 | 新增 slug 前应确认数据库和 `_DEFAULT_MCP_SERVERS` 中没有同名项。运行时只信任 `_DEFAULT_MCP_SERVERS` 的固定 slug 白名单;`created_by` 仅用于审计,不能通过复用用户记录或手工修改 `created_by` 来创建内置 MCP。 开发环境会在 API / worker 热重载后的启动阶段调用 `ensure_builtin_mcp_servers_in_db()`;生产部署需要重新 构建并启动 API 与 worker。新内置项首次同步时默认 `enabled=false`,管理员需要在 MCP 管理页中“添加”后 才会进入运行时。后续启动会用代码定义覆盖连接与展示字段,同时保留启用状态和工具禁用列表。 添加后执行一次验证: ```bash docker compose up -d --build api worker docker logs api-dev --tail 100 docker logs worker-dev --tail 100 ``` 确认日志中没有同步异常,并在管理页添加该 MCP,检查能够发现预期工具。验证过程不得执行文件写入、 Shell 命令或其他无关副作用。 ::: danger 安全边界 stdio MCP 与在 API / worker 容器内执行程序等价。提交前必须审查可执行程序、依赖来源、固定版本、参数、 网络访问和工具副作用;不得从 HTTP 请求、数据库用户配置或环境中的非受信任内容拼接 `command`、`args` 或 `env`,也不得通过把用户记录改成 `created_by=system` 绕过运行时限制。 ::: ## 服务器管理 管理界面使用“添加 / 移除”语义管理 MCP 服务器: - 已添加:`enabled=true`;远程 MCP 读取数据库中的最新连接配置,内置 stdio MCP 使用代码中的固定连接配置 - 可添加:`enabled=false`,记录保留但不会进入运行时 Agent 配置中的 `mcps` 决定本次运行可使用哪些已添加服务器;未显式配置时使用当前用户可见的全部服务器。工具对象会按配置哈希做本地缓存,更新服务器配置后会自动使用新的缓存键,不需要重启服务。 ## 工具管理 MCP 工具支持粒度控制:管理员可以单独启用或禁用某个 MCP 服务器下的特定工具,实现精细化的权限管理。 --- ### Agents/Middleware # 中间件系统 中间件是 Yuxi 扩展智能体运行行为的主要机制。它工作在 LangGraph Agent 的模型调用、工具调用、状态更新和文件系统访问路径上,用来把知识库、Skills、附件、子智能体、上下文压缩和运行观测接入同一条执行链路。 内置 `ChatbotAgent` 与 `SubAgentBackend` 都会在 `get_graph()` 中构建中间件列表。运行前的资源过滤不再依赖旧版运行时配置中间件,而是在创建 Graph 前由 `prepare_agent_runtime_context` 完成。 ## 运行时准备 运行时准备不是中间件,但它决定后续中间件能看到什么资源。内置 Agent 创建 Graph 前会先执行以下步骤: - `prepare_agent_runtime_context`:按当前用户权限过滤工具、知识库、MCP、Skills 和子智能体,并派生 `_visible_knowledge_bases`、`_prompt_skills`、`_readable_skills` 与最终 `_runtime_skill_sources` - `build_prompt_with_context`:基于 Context 生成系统提示词 - `load_chat_model(context.model)`:加载主模型 - `resolve_configured_runtime_tools(context)`:加载已配置的内置工具和 MCP 工具 这意味着中间件不负责重新判断“用户是否能访问某个资源”。它们消费的是已经归一化后的 runtime context。 ## 内置中间件链路 当前内置 `ChatbotAgent` 的中间件顺序如下: | 中间件 | 作用 | | --- | --- | | `create_agent_filesystem_middleware` | 接入沙盒文件系统、用户工作区、线程 uploads/outputs 与只读 Skills 路由,并在工具结果过大时把内容写入 `outputs/large_tool_results` | | `save_attachments_to_fs` / `AttachmentMiddleware` | 从 LangGraph state 的 `uploads` 读取附件路径,把可读路径注入系统提示,提示模型按需使用 `read_file` | | `SkillsMiddleware` | 注入可见 Skill 的提示段,监听读取 `SKILL.md` 后的 Skill 激活,并按依赖追加工具和 MCP 工具;知识库工具由内置 `knowledge-base` Skill 按需加载 | | `YuxiSubAgentMiddleware` | 仅主 Agent 在存在可见子智能体时挂载,提供 `task` 工具调用真实子 Agent graph | | `YuxiSummarizationMiddleware` | 基于 DeepAgents `SummarizationMiddleware` 做长上下文压缩,并清洗被摘要历史里的工具结果 | | `TodoListMiddleware` | 提供待办状态,让前端状态面板可展示 Agent 运行进度 | | `PatchToolCallsMiddleware` | 修正部分工具调用消息形态,提升工具调用兼容性 | | `ModelRetryMiddleware` | 在模型调用失败时按配置重试 | | `ImageInputCompatibilityMiddleware` | 仅为 OpenAI Chat Completions 兼容链路桥接 `read_file` 返回的图片;模型明确拒绝图片输入时自动改为 `ocr_parse_file` | | `TokenUsageMiddleware` | 在 LangGraph state 写入近似上下文、本次与线程累计的 Provider 实际 token 用量、模型标识和缓存命中率,供前端状态面板查看 | `SubAgentBackend` 使用同一组核心能力,但不会挂载 `YuxiSubAgentMiddleware`,并额外过滤 `present_artifacts`、`ask_user_question`、`install_skill` 等不适合子智能体直接使用的工具。 ## 知识库工具 知识库访问能力沉淀为内置 `knowledge-base` Skill。Agent 读取 `/home/gem/skills/knowledge-base/SKILL.md` 激活该 Skill 后,`SkillsMiddleware` 会按依赖追加 `list_kbs`、`query_kb`、`find_kb_document`、`open_kb_document`、`get_mindmap` 等知识库工具。 实际可见知识库仍由 `prepare_agent_runtime_context` 根据当前用户和 Agent 配置写入 `_visible_knowledge_bases`,工具执行时只会在这批知识库中检索。`context.knowledges` 是资源范围,不是 Skill 本身。 系统不会把知识库文件树挂进沙盒。Agent 访问知识库内容应使用 `query_kb`、`find_kb_document` 和 `open_kb_document`,而不是遍历 `/home/gem/kbs` 这类旧路径。 ## Skills 注入与激活 `SkillsMiddleware` 分两步工作: 1. 模型调用前读取 `_prompt_skills`,把可见 Skill 的名称、描述和 `SKILL.md` 路径追加到系统提示。 2. 工具调用后检查模型是否读取了共享路径 `/home/gem/skills//SKILL.md` 或个人路径 `/home/gem/user-data/workspace/agents/skills//SKILL.md`。如果该 Skill 在 `_readable_skills` 范围内,就把它写入 `activated_skills`,并在后续模型调用中追加它声明的工具和 MCP 依赖。 这种设计让 Skill 可以先作为说明可见,只有模型真正读取并激活后才扩展工具集,避免一开始就把所有依赖工具塞进上下文。 ## 附件与文件系统 附件上传后会先落盘到线程文件系统,并在 LangGraph state 中记录 `uploads`。`AttachmentMiddleware` 只把文件名和可读路径注入提示词,不会把文件内容整体塞进模型上下文。模型需要查看附件时,应通过 `read_file` 读取对应路径。 文件系统中间件负责把 sandbox backend、线程 uploads/outputs、用户工作区和只读 Skills 组合成 Agent 可访问的虚拟文件系统。普通 Agent 默认使用当前 `thread_id` 作为文件作用域;子智能体使用 child `thread_id` 做 checkpoint,同时沿用父线程的 uploads/outputs,并使用子 Agent 自己的 Skills 作用域。 ## 子智能体任务 主 Agent 如果配置了可见子智能体,会挂载 `YuxiSubAgentMiddleware` 并获得 `task` 工具。这个工具不会调用旧版独立 SubAgents 表,而是查找 `agents.is_subagent=true` 且后端为 `SubAgentBackend` 的真实 Agent 配置,然后启动对应子 Agent graph。 子智能体执行时会获得独立 child thread、独立 checkpoint 和 `agent_runs(run_type=subagent)` 记录;工具结果会返回 child thread ID,后续可以把该 ID 传回 `task` 继续同一个子任务。子智能体自身不会再挂载下一层 `task` 中间件,避免形成嵌套子智能体链路。 ## Summary 上下文压缩 长对话压缩由 Yuxi 封装的 `YuxiSummarizationMiddleware` 负责。它基于 DeepAgents 的 `SummarizationMiddleware`,但针对 Yuxi 的知识库检索和工具调用结果做了额外处理。 触发条件来自 Agent Context: | 字段 | 说明 | | --- | --- | | `summary_threshold` | 上下文超过该 K token 阈值后触发摘要;L2 摘要模型的待摘要历史输入上限也使用同一阈值 | | `summary_keep_messages` | 摘要后保留最近消息数 | | `summary_prompt` | 摘要模型使用的提示词 | | `summary_tool_result_token_limit` | 工具结果 offload 阈值和预览 token 上限 | | `summary_l2_trigger_ratio` | L1 后进入 L2 summary 的触发比例,建议 `0.1~1.0`,默认 `0.4` | 触发判断使用 Yuxi 自己的近似 token 计算结果,不使用模型返回的 `usage_metadata.total_tokens` 作为触发依据,避免 provider 的计费口径、累计口径或异常上报导致短对话过早压缩。 ## Token 用量统计 `TokenUsageMiddleware` 同时维护两种口径,二者不能混用: - 近似上下文统计通过 `count_tokens_approximately` 计算,用于上下文窗口、摘要阈值和消息构成展示。 - 实际用量直接保留主 Agent 模型调用返回的 `AIMessage.usage_metadata`,包括 `input_tokens`、`output_tokens`、`total_tokens`、缓存和推理 token 明细。当前不包含 Summary 中间件内部直接发起的 L2 摘要模型调用,因此尚不能作为完整账单口径。 state 中的实际用量分为 `latest`、`run` 和 `thread`:分别表示最近一次模型调用、当前 AgentRun 累计和当前 LangGraph 线程累计。前端状态面板只读取 state;流式终态 chunk 不携带用量。worker 从当前父线程、`current_run_id` 匹配的 AgentState 提取 `run`,在 Run 进入终态时将该快照写入 `AgentRun.token_usage`。`run.models` 与 `thread.models` 使用 Yuxi `provider_id:model_id` 配置 spec 分桶,并记录 Provider 响应中的实际模型 ID,避免模型切换后把不同模型的 token 混为一组,也避免把 OpenAI 兼容协议类型误当作业务供应商。 每个模型桶独立计算缓存 token 命中率:使用 Provider 明确上报的 `input_token_details.cache_read / input_tokens`;OpenAI `priority` / `flex` service tier 对应识别 `priority_cache_read` / `flex_cache_read`。累计时先汇总该模型已上报缓存明细的输入 token,再计算 `sum(cache_read) / sum(input_tokens)`;缺少缓存读取字段表示 Provider 未上报,不按 0 命中处理。当前 Run 的按模型聚合会在终态事务中写入 `AgentRun.token_usage`,父 Run 与 SubAgent Run 分别保存,不重复合并。 `siliconflow-cn` 与 `siliconflow` 当前返回的 usage 格式与统计契约不一致,暂列入 Token 用量 Provider 黑名单。黑名单模型仍计算近似上下文占用,但不写入最近调用、Run 或线程的 Provider 实际用量聚合。 触发后,中间件先执行 L1 结构精简:在本次模型调用的临时消息视图里截断旧 `write_file`/`edit_file` 工具调用的大参数;`ToolMessage.content` 估算 token 数超过 `summary_tool_result_token_limit` 时,会写入当前 Agent 可见的 `outputs/large_tool_results`,消息内替换为工具名、近似 token 数、完整结果路径和不超过同一 token 上限的预览。未超过该上限的工具结果保持原样。这个步骤不修改 LangGraph state 中的原始消息。 L1 后会重新计算上下文大小;如果仍超过入口阈值乘以 `summary_l2_trigger_ratio`,才进入 L2 summary,把较早的 L1 视图消息压缩成一条 summary message,并保留最近窗口内的原始消息。比例越小越容易进入 L2;`1.0` 表示 L1 后仍超过原始触发阈值才进入 L2。L2 传给摘要模型的待摘要历史上限等于 `summary_threshold` 对应的 token 数,避免用过小的固定窗口丢掉早期关键信息。L2 不再对工具结果做第二轮 offload,只写入 `_summarization_event`,后续调用仍由 DeepAgents 的 cutoff 语义重建 effective messages。 这对知识库检索尤其重要:`query_kb`、`open_kb_document`、`find_kb_document` 等工具可能返回较长的片段、引用和文档内容。Summary 阶段保留“查过什么、结果在哪里、关键预览是什么”,同时避免把大量检索原文反复卷入摘要,减少上下文污染和 token 压力。压缩开始、完成或失败会以 `context_compression` 流事件同步到前端;摘要模型自身的 token 流不会作为聊天消息输出。 未达到入口阈值的常规模型调用不会额外清洗工具结果;达到入口阈值但 L1 后低于 L2 门槛时,会直接用 L1 精简后的临时视图调用模型,不生成 summary event。 ## 自定义中间件 新增中间件时,将实现放入 `backend/package/yuxi/agents/middlewares`,再在具体 Agent 的 `get_graph()` 中加入 `middleware` 列表。新增前先确认它属于哪一种职责: - 资源过滤、权限收敛和默认资源选择应放在 `prepare_agent_runtime_context` 一类的 Graph 创建前逻辑中。 - 模型提示注入、工具动态追加、工具结果处理和 state 更新适合做成 LangChain Agent middleware。 - 文件读写、工具结果卸载和 artifacts 展示应优先复用 `create_agent_filesystem_middleware` 与沙盒 backend。 仓库中仍保留 `DynamicToolMiddleware`,但当前内置 Agent 的工具和 MCP 加载已经由 `resolve_configured_runtime_tools(context)` 与 `SkillsMiddleware` 承担。新增功能时不要默认复用旧的动态工具中间件,除非确实需要“预注册后按请求筛选”的模式。 --- ### Agents/Sandbox Architecture # Yuxi 沙盒架构说明 ::: tip info 本文档是由 Codex 联合撰写,开发者审阅,尽管已经多次校对,但仍可能存在不准确或过时的描述。如果你发现任何问题,欢迎提交 issue 或 PR 来帮助我们改进文档。 ::: 我们在 Yuxi 里引入沙盒,不是为了让架构更“重”,而是因为 Agent 一旦从纯文本对话进入真实执行阶段,就一定会碰到一组很具体的运行时需求:执行命令、读写文件、处理用户上传附件、产出可下载结果,以及在受控目录里保留中间过程文件。如果把这些能力直接放进 API 进程本身,权限边界、租户隔离、环境一致性和后续运维成本都会迅速恶化。 从设计目标上看,沙盒这一层主要解决三件事。第一,给 Agent 一个可写、可执行、可回收的独立运行空间,而不是让它直接操作应用主进程。第二,把模型可见文件系统整理成稳定的命名空间,例如 `/home/gem/user-data` 和 `/home/gem/skills`,这样 prompt、工具、viewer 和 artifact 下载接口可以共享同一套路径语义。第三,让这套能力既能在本地 Docker 开发环境里稳定工作,也能在需要时切到 Kubernetes 这类更适合多实例部署的承载方式。 这份文档说明当前项目中“沙盒”这一层到底是什么、为什么同时会看到 Docker 和 Kubernetes、默认开发环境实际启用的是哪一种模式,以及沙盒如何和 `skills`、附件、工作区文件系统组合在一起工作。内容以当前仓库实现为准,我们重点解释真实调用链、配置入口、路径语义和运维边界,而不是抽象地介绍容器技术。 ## 一、先说明白:Docker 和 K8s 在这里是什么关系 Docker 和 Kubernetes 不是互斥关系。Docker 解决的是“把一个进程放进容器里运行”这个问题,Kubernetes 解决的是“如何在一组机器上批量调度、暴露、重建和管理这些容器”这个问题。可以把 Docker 理解成容器运行时和镜像分发方式,把 Kubernetes 理解成容器编排平台。 放到 Yuxi 里,这个关系更具体一些。Yuxi 本身并不直接决定“沙盒一定跑在 Docker 还是一定跑在 K8s 上”,它只要求后端拿到一个可访问的沙盒地址,然后通过 `agent-sandbox` 的 HTTP API 去执行命令、读写文件。真正负责创建和回收沙盒实例的是 `sandbox-provisioner` 这个单独的服务。也就是说,Yuxi 的应用层只依赖 “provisioner”,而 provisioner 的后端可以选择用本机 Docker 去起容器,也可以选择向 Kubernetes 集群创建 Pod 和 Service。 所以项目里看到的概念其实分成两层。第一层是应用层的 `SANDBOX_PROVIDER`,当前代码只支持 `provisioner`。第二层是 provisioner 内部的 `SANDBOX_PROVISIONER_BACKEND`,它决定具体用哪种底层实现去创建沙盒。当前真正应该对外理解和配置的是 `docker`、`kubernetes`,测试或占位场景可以使用 `memory`。 ## 二、当前项目的真实沙盒调用链 当前仓库里,后端只支持 `SANDBOX_PROVIDER=provisioner`。当某个对话线程第一次需要执行文件操作或命令执行时,后端会基于文件线程与 skills 线程生成稳定的 `sandbox_id`,然后请求 `sandbox-provisioner` 创建或复用对应沙盒;普通 Agent 的文件线程和 skills 线程都回退为当前 `thread_id`。应用层拿到返回的 `sandbox_url` 之后,才会真正通过 `agent-sandbox` 客户端去调用远程沙盒的文件 API 和 shell API。 调用链可以概括为:Web/API 请求进入 Yuxi 后端,后端构造 `ProvisionerSandboxBackend`,再经由 `ProvisionerClient` 调用 `sandbox-provisioner` 的 `/api/sandboxes` 接口。`sandbox-provisioner` 根据 `SANDBOX_PROVISIONER_BACKEND` 选择内存占位实现、Docker 容器实现或 Kubernetes 实现。沙盒真正启动后,对外暴露一个 HTTP 地址,Yuxi 再使用这个地址完成执行命令、上传文件、下载文件、目录遍历等操作。 当前仓库的默认配置和默认开发环境都应该理解为 `docker`。正常情况下运行中的 provisioner 健康检查应返回 `backend=docker`。这意味着我们用 `docker compose up -d` 启动项目时,应用并不是直接把代码跑在宿主机上,而是通过 `sandbox-provisioner` 再去用 Docker 启一个真正的沙盒容器。 ## 三、`memory`、`docker`、`kubernetes` 分别是什么 当前实现里,`memory`、`docker`、`kubernetes` 是三种需要区分的语义。 `memory` 是一个纯内存登记实现。它不会真正创建容器,也不会提供真实隔离,主要适合测试或极轻量的占位场景。它只是记录一个 `sandbox_id -> sandbox_url` 的映射,因此不能把它理解成生产可用的沙盒。 `docker` 是当前默认也是推荐的本机容器后端。`sandbox-provisioner` 会使用 `LocalContainerProvisionerBackend` 通过宿主机 Docker daemon 动态创建沙盒容器。 `kubernetes` 则是另一条实现路径。它不会再去调用本机 Docker 起容器,而是使用 Kubernetes API 在指定 namespace 中创建一个 Pod 和一个 NodePort Service,然后把这个 Service 对应的可访问地址回传给 Yuxi 后端。 因此,如果在界面、文档或者环境变量里看到 “docker / k8s” 这几个词,最准确的理解应该是:Yuxi 的应用层只有一种 provider,也就是 `provisioner`;provisioner 下面有多种 backend;其中 `docker` 是默认的本机 Docker 后端,`kubernetes` 是另一种远程集群后端。 ## 四、默认开发模式到底是什么 默认开发模式是 Docker Compose 启动整个项目,再由 `sandbox-provisioner` 按 `docker` 后端去创建沙盒容器。也就是说,项目本身跑在 Compose 里,沙盒也跑在 Docker 里,只不过沙盒不是 Compose 静态声明的长期服务,而是 provisioner 按需动态拉起和回收的短生命周期容器。 这也是为什么在 `docker-compose.yml` 中既能看到 `api`、`worker`、`sandbox-provisioner` 这样的常驻服务,又能看到 `sandbox-provisioner` 挂载了 `/var/run/docker.sock`。这不是重复设计,而是为了让 provisioner 有能力继续调用宿主机 Docker daemon 去创建新的“每线程沙盒容器”。 换句话说,当前项目不存在单独的 “纯宿主机 local 模式”。本机开发和单机部署应显式使用 `docker` 后端。 这里还需要把 Compose 里的环境变量分两层看。`api` 和 `worker` 关注的是应用层变量,例如 `SANDBOX_PROVIDER`、`SANDBOX_PROVISIONER_URL`、`SANDBOX_PROVISIONER_TOKEN`、`SANDBOX_VIRTUAL_PATH_PREFIX`、`SANDBOX_EXEC_TIMEOUT_SECONDS`、`SANDBOX_MAX_OUTPUT_BYTES`。`sandbox-provisioner` 自己则有另一组变量,负责决定具体如何创建沙盒实例。两层不要混看,否则很容易误以为改了 API 环境变量就能切换底层承载方式。 ## 五、Docker 本机后端是如何工作的 当 `SANDBOX_PROVISIONER_BACKEND=docker` 时,`sandbox-provisioner` 会进入 `LocalContainerProvisionerBackend`。它会检查 Docker 是否可用,解析自身容器里 `/app/saves` 这个挂载点在宿主机上的真实路径,并据此推导出线程数据目录。随后它为每组文件线程与 skills 线程准备一个稳定的 `sandbox_id`,把容器命名为类似 `yuxi-sandbox-` 的形式,并在 Docker 网络中启动真正的沙盒镜像。 这个沙盒镜像默认来自 `SANDBOX_IMAGE`,容器内部监听的端口默认是 `8080`。provisioner 会为每个动态沙盒创建独立的 Docker bridge 网络,只把 provisioner 和该沙盒接入其中;沙盒之间不能互访,也不能访问承载 PostgreSQL、Redis、Neo4j、MinIO 等服务的 `app-network`。沙盒端口不发布到宿主机,provisioner 通过对应的独立网络访问真实容器,再以需要 Bearer token 的代理地址向 API/worker 提供文件和命令接口。API/worker 不直接持有沙盒容器地址。 这个拓扑把沙箱按“其中代码可能被完全控制”处理。`SANDBOX_PROVISIONER_TOKEN` 只配置给 API、worker 和 provisioner,绝不能写进 `sandbox.env` 或用户级 Agent 环境变量,否则沙箱会重新获得 provisioner 管理权限。 Docker 后端在启动沙盒时,会挂载三类关键目录。第一类是用户级 workspace,挂载到容器内的 `/home/gem/user-data/workspace`。第二类是文件线程级 uploads/outputs,分别挂载到 `/home/gem/user-data/uploads` 和 `/home/gem/user-data/outputs`。第三类是 skills 线程可见的 skills 目录,挂载到 `/home/gem/skills`,而且是只读挂载。除此之外,容器的 `/home/gem` 本身还会额外挂一个 `tmpfs`,原因是当前沙盒镜像启动时要求 `/home/gem` 可写,但 Yuxi 希望真正持久化的只有 `user-data` 下面的内容。 为了避免长期空闲的沙盒一直占资源,provisioner 还带了一个 idle reaper。它会记录每个沙盒最近一次被 touch 的时间,超过 `SANDBOX_IDLE_TIMEOUT_SECONDS` 之后自动删除。当前默认空闲超时是 120 秒,但如果这个值小于命令执行超时,系统会自动把它提高到“命令超时 + 30 秒”,以免执行中的任务被误回收。 对应到 `docker-compose.yml` 和 `docker-compose.prod.yml`,当前 `sandbox-provisioner` 实际会读取的 Docker 后端相关变量主要是这些: - 通用变量:`PROVISIONER_BACKEND`、`SANDBOX_IMAGE`、`SANDBOX_CONTAINER_PORT`、`SANDBOX_HEALTH_TIMEOUT_SECONDS`、`SANDBOX_IDLE_TIMEOUT_SECONDS`、`SANDBOX_IDLE_CHECK_INTERVAL_SECONDS`、`SANDBOX_EXEC_TIMEOUT_SECONDS`、`MEMORY_SANDBOX_URL_TEMPLATE` - Docker 后端变量:`DOCKER_NETWORK_PREFIX`、`DOCKER_THREADS_HOST_PATH`、`DOCKER_SANDBOX_PREFIX` - 容器代理变量:`HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY` `DOCKER_NETWORK_PREFIX` 用于生成每个沙盒的独立网络名称。`DOCKER_THREADS_HOST_PATH` 也是 Docker 后端专用;如果不显式传入,provisioner 会尝试根据自身容器挂载反推出宿主机路径。 ## 六、Kubernetes 后端是如何工作的 当 `SANDBOX_PROVISIONER_BACKEND=kubernetes` 时,`sandbox-provisioner` 会改用 Kubernetes Python 客户端。它会先加载 kubeconfig 或集群内配置,然后在指定的 namespace 中创建一个沙盒 Pod,再创建一个同名的 NodePort Service,把这个 Service 的 `nodePort` 暴露给 Yuxi 后端使用。 Kubernetes 后端下,沙盒还是同一套镜像,还是暴露同样的 HTTP API,但存储方式和暴露方式变了。它不会依赖宿主机 Docker bind mount,而是要求有一个可写的 PVC。当前实现里真正使用的是 `THREAD_PVC`,Pod 会把这块共享存储挂到 `/mnt/shared-data`,然后用 `subPath` 的方式把 `threads/shared//workspace` 挂到 `/home/gem/user-data/workspace`,把 `threads//user-data/uploads` 与 `threads//user-data/outputs` 分别挂到 uploads/outputs,把 `threads//skills` 挂到 `/home/gem/skills`。这样做的好处是目录结构仍然可以和 Docker 模式保持一致,同时允许子智能体共享父对话文件但隔离 skills。 需要特别说明的是,代码里虽然读取了 `SKILLS_PVC` 这个环境变量,但当前 Pod 规格实际没有使用单独的 skills PVC,而是统一从 `THREAD_PVC` 中切 `threads//skills` 这个子路径。因此,如果看到环境变量里同时出现 `SKILLS_PVC` 和 `THREAD_PVC`,应当以 `THREAD_PVC` 的真实挂载语义为准,`SKILLS_PVC` 目前更像一个预留字段。 Kubernetes 后端还需要一个 `NODE_HOST`。这是因为当前实现使用的是 NodePort Service,而不是 Ingress,也不是 ClusterIP。provisioner 创建完 Service 后会通过 `http://:` 访问目标沙箱,但返回给 Yuxi 后端的仍是 provisioner 认证代理地址。所以 `NODE_HOST` 必须从 provisioner 可达,不需要直接暴露给 API/worker。 当前 Compose 中与 Kubernetes 后端对应的变量主要是: - `K8S_NAMESPACE` - `KUBECONFIG_PATH` - `NODE_HOST` - `THREAD_PVC` - `SKILLS_PVC` 其中真正决定运行时挂载的是 `THREAD_PVC`。`SKILLS_PVC` 目前只保留为代码层读取字段,并没有进入实际 Pod 挂载。 ## 七、如果要使用“远程 K8s”,应该怎么接 这里最容易误解的一点是,所谓“选择远程 K8s”,并不是在 Yuxi 页面里点一个开关,然后系统自动发现一个集群。当前实现没有内建集群选择器,也没有多集群管理界面。它的工作方式很直接:我们把 `sandbox-provisioner` 配置成 `kubernetes` 后端,并让它能拿到目标集群的 kubeconfig 或者运行在集群内即可。对 provisioner 来说,只要 Kubernetes 客户端能连上 API Server,这个集群就是它要操作的“远程 K8s”。 如果 Yuxi 部署在 Docker Compose 里,而 Kubernetes 集群在另一台机器或云厂商托管环境中,那么最常见的做法是把本地 kubeconfig 文件挂载进 `sandbox-provisioner` 容器,然后设置 `KUBECONFIG_PATH`。同时把 `SANDBOX_NODE_HOST` 改成一个从 `api` 容器也能访问的节点公网 IP、负载均衡域名,或者已经做过反向代理的地址。 一个典型的 Compose 覆盖配置会长这样: ```yaml services: sandbox-provisioner: environment: - PROVISIONER_BACKEND=kubernetes - K8S_NAMESPACE=yuxi-know - KUBECONFIG_PATH=/root/.kube/config - THREAD_PVC=yuxi-thread - SKILLS_PVC=yuxi-skills - NODE_HOST=203.0.113.10 volumes: - ~/.kube/config:/root/.kube/config:ro ``` 这段配置表达的意思不是“把整个应用迁到 K8s”,而是“仍然用 Compose 跑 Yuxi 主服务,但沙盒实例改为由远程 Kubernetes 集群承载”。这是当前代码最自然的混合部署方式。 如果 `sandbox-provisioner` 本身就运行在 Kubernetes 集群内部,那么通常不需要显式提供 `KUBECONFIG_PATH`。它会优先尝试 `incluster_config`,也就是使用 Pod 的服务账号权限直接访问 Kubernetes API。此时更需要关注的是 namespace、PVC 和 NodePort 的可达性,而不是 kubeconfig 文件本身。 ## 八、当前项目的沙盒文件系统是如何设计的 从模型和工具调用的视角看,Yuxi 主要向 Agent 暴露两类路径:`/home/gem/user-data` 和 `/home/gem/skills`。其中 `user-data` 是可写的用户工作区,`skills` 是只读的技能目录。知识库不再映射为沙盒文件系统路径,模型应通过知识库工具检索和打开文档。 在宿主机侧,和线程相关的数据主要放在 `saves` 目录下。当前可读的目录结构可以概括为下面这样: ```text saves/ ├── skills/ │ ├── / │ └── ... ├── threads/ │ ├── / │ │ ├── user-data/ │ │ │ ├── uploads/ │ │ │ ├── outputs/ │ │ │ └── ... │ │ └── skills/ │ │ ├── / │ │ └── ... │ ├── shared/ │ │ └── / │ │ └── workspace/ │ └── ... ``` 这里要重点理解 `workspace` 和 `uploads/outputs` 的区别。按照当前宿主机路径解析逻辑,`workspace` 被定义为用户级共享目录,位置是 `saves/threads/shared//workspace`;而 `uploads` 和 `outputs` 属于文件线程目录,位置分别是 `saves/threads//user-data/uploads` 和 `saves/threads//user-data/outputs`。普通 Agent 的 `file_thread_id` 就是当前对话 `thread_id`,子智能体运行时则使用父对话作为 `file_thread_id`,因此可以读取父对话附件并把产物写回父对话 outputs。 与此同时,运行时 provisioner 在创建 Docker 容器或 Kubernetes Pod 时,会把用户级 `saves/threads/shared//workspace` 单独挂到 `/home/gem/user-data/workspace`,再把文件线程的 `uploads/outputs` 分别挂到 `/home/gem/user-data/uploads` 和 `/home/gem/user-data/outputs`。因此在排查文件问题时,需要先明确一个前提:当前项目里同时存在“宿主机侧目录组织”和“容器内统一虚拟路径”两层概念。对外接口和 viewer 语义与底层挂载实现现在是一致的,workspace 是用户共享空间,而 uploads/outputs 跟随文件线程隔离或共享。 ## 九、路径暴露规则是什么 Yuxi 不会把整个容器文件系统都开放给 Agent 或 viewer。当前 viewer 根目录只会列出几个命名空间入口,而不会直接暴露 `/` 的真实文件树。这样做是为了避免只看文件树就触发沙盒冷启动,也为了让权限边界更稳定。 `/home/gem/user-data` 是主要工作区。它允许模型和工具写入,但推荐语义并不相同。内置 prompt 中已经明确说明,`workspace` 应当放中间文件,`outputs` 应当放最终产物,`uploads` 是用户上传文件的位置。对于普通对话 Agent,文案甚至提示“非必要不要写 workspace,而优先写 outputs”。 `/home/gem/skills` 是共享与内置 Skill 的只读目录。它不是简单地把 `saves/skills` 整个暴露进去,而是按当前运行时最终生效的共享与内置 Skill,将来源同步到 `saves/threads//skills`,再把线程目录只读挂进沙盒。个人 Skill 不进入这层投影,Agent 直接读取已经挂载的 `/home/gem/user-data/workspace/agents/skills/`。 知识库访问不属于沙盒文件系统暴露规则。当前 Agent 可见知识库仍由用户权限和 Agent 配置共同决定,但只通过 `query_kb`、`open_kb_document` 等工具访问,不提供沙盒目录投影。 ## 十、skills、知识库、附件是怎么和沙盒结合的 skills 的结合方式分成两层。第一层是提示词层,`prepare_agent_runtime_context` 会先根据当前 Agent 配置的 `context.skills` 展开依赖闭包,`SkillsMiddleware` 再把 `_prompt_skills` 注入到系统提示里,并给出每个 Skill 的真实运行入口。第二层是文件系统层:共享与内置 Skill 由 `sync_thread_readable_skills` 同步到当前 `skills_thread_id` 的线程目录,并只读挂载到 `/home/gem/skills`;个人 Skill 直接使用用户工作区中的 `workspace/agents/skills`,不再生成线程副本。 附件的结合方式更偏向“先落盘,再把路径告诉模型”。用户上传文件后,系统会先把原始文件写入 `saves/threads//user-data/uploads`。如果该文件可以被解析,系统还会额外生成一个 Markdown 副本,写到 `saves/threads//user-data/uploads/attachments/.md`。普通 Agent 的文件线程就是当前对话线程;子智能体沿用父对话文件线程,所以能访问父对话附件。随后,LangGraph state 中会维护一份 `uploads` 列表,`AttachmentMiddleware` 会把这些可读路径注入系统提示,告诉模型优先用 `read_file` 去读取这些路径。因此,附件并不是“作为消息大段内联塞给模型”,而是被转换成沙盒文件系统中的路径对象。 知识库不再与沙盒文件系统结合。它不会被复制到每个线程目录,也不会生成虚拟目录;模型通过专门的知识库工具检索,并在需要更完整上下文时用 `open_kb_document` 按 `kb_id` 和 `file_id` 打开文档内容。 ## 十一、当前推荐如何使用 Docker 沙盒 如果只是正常开发、调试或单机部署,最简单也是当前默认的方式就是保留 `SANDBOX_PROVIDER=provisioner`,同时把 `SANDBOX_PROVISIONER_BACKEND` 设为 `docker`。这会让整个项目继续由 Docker Compose 管理,而沙盒实例由 provisioner 动态创建。通常不需要手工 `docker run` 沙盒镜像,也不需要在 Compose 文件里静态声明每一个沙盒容器。 最小必要配置通常就是下面这几项: ```env SANDBOX_PROVIDER=provisioner SANDBOX_PROVISIONER_URL=http://sandbox-provisioner:8002 SANDBOX_PROVISIONER_TOKEN=<至少 32 个随机字符> SANDBOX_PROVISIONER_BACKEND=docker SANDBOX_VIRTUAL_PATH_PREFIX=/home/gem/user-data SANDBOX_DOCKER_NETWORK_PREFIX=yuxi-know-sandbox ``` 然后用常规方式启动即可: ```bash docker compose up -d curl http://localhost:8002/health ``` 如果健康检查返回 `backend: docker`,就说明 provisioner 已经处于默认的 Docker 本机后端。真正的沙盒容器不会在系统启动时立即全部出现,而是在你第一次创建线程并触发需要文件系统或命令执行的操作后才会被创建。 升级已有开发环境时可以重新运行初始化脚本,让脚本补生成 `SANDBOX_PROVISIONER_TOKEN`。历史共享沙盒网络中的容器会在下次发现时被重建到各自独立的网络。 ## 十二、如何理解文件管理与暴露边界 从产品行为上看,viewer 文件系统和 artifact 下载接口优先走的是宿主机路径解析,而不是无条件透传到沙盒容器内部。这么设计有两个直接收益。第一,浏览 `/` 或 `/home/gem/user-data` 这样的树形入口时,不需要为了只读查看而冷启动沙盒。第二,权限边界更好做,因为 `resolve_virtual_path` 会把用户可见路径严格限制在预定义的 `user-data` 和 `skills` 命名空间内。 从工程上看,当前实现更像“双层文件系统”。对 Agent 执行来说,真正工作的对象是远程沙盒进程暴露的文件 API;对 viewer、附件下载和一部分 artifact 查看来说,系统会优先在宿主机侧解析虚拟路径,再用本地文件读取或只读 backend 下载内容。这也是为什么你会看到既有 `ProvisionerSandboxBackend`,又有 `viewer_filesystem_service`、`SelectedSkillsReadonlyBackend` 这样的配套实现。 ## 十三、环境变量配置与传递链 sandbox-provisioner 的环境变量传递分**两层**,需要分别理解: ### 第一层:应用层 → sandbox-provisioner `api` 和 `worker` 服务通过 `SANDBOX_*` 前缀的环境变量告诉后端如何连接 provisioner。这些变量定义在 `docker-compose.yml` 的 `x-api-worker-env` 锚点中: | 变量名 | 说明 | 默认值 | |--------|------|--------| | `SANDBOX_PROVIDER` | 提供者类型,固定为 `provisioner` | `provisioner` | | `SANDBOX_PROVISIONER_URL` | provisioner 服务地址 | `http://sandbox-provisioner:8002` | | `SANDBOX_PROVISIONER_TOKEN` | provisioner 管理与代理接口 Bearer token,至少 32 个字符 | 无,必填 | | `SANDBOX_VIRTUAL_PATH_PREFIX` | 虚拟路径前缀 | `/home/gem/user-data` | | `SANDBOX_EXEC_TIMEOUT_SECONDS` | 命令执行超时时间 | `180` | | `SANDBOX_MAX_OUTPUT_BYTES` | 最大输出字节数 | `262144` | ### 第二层:sandbox-provisioner 内部配置 `sandbox-provisioner` 服务本身读取另一组环境变量,决定如何创建沙盒容器。这些变量直接写在 `docker-compose.yml` 的 `sandbox-provisioner.environment` 中: **通用配置:** | 变量名 | 说明 | 默认值 | |--------|------|--------| | `PROVISIONER_BACKEND` | 底层后端类型,`docker` 或 `kubernetes` | `docker` | | `SANDBOX_IMAGE` | 沙盒容器镜像 | 详见 compose 文件 | | `SANDBOX_CONTAINER_PORT` | 沙盒容器内部端口 | `8080` | | `SANDBOX_IDLE_TIMEOUT_SECONDS` | 空闲回收时间 | `120` | | `SANDBOX_HEALTH_TIMEOUT_SECONDS` | 健康检查超时 | `300` | **Docker 后端专用:** | 变量名 | 说明 | 默认值 | |--------|------|--------| | `DOCKER_NETWORK_PREFIX` | 每沙盒独立网络的名称前缀 | `yuxi-know-sandbox` | | `DOCKER_SANDBOX_PREFIX` | 沙盒容器名前缀 | `yuxi-sandbox` | | `DOCKER_THREADS_HOST_PATH` | 线程数据宿主机路径 | 自动推断 | **Kubernetes 后端专用:** | 变量名 | 说明 | 默认值 | |--------|------|--------| | `K8S_NAMESPACE` | Kubernetes namespace | `yuxi-know` | | `NODE_HOST` | Kubernetes 节点地址 | `host.docker.internal` | | `KUBECONFIG_PATH` | kubeconfig 文件路径 | 空(使用 incluster 配置) | | `THREAD_PVC` | 线程数据持久化卷 | `yuxi-thread` | | `SKILLS_PVC` | 技能目录持久化卷(预留) | `yuxi-skills` | ### 环境变量传递链 ``` 宿主机 .env / 系统环境变量 ↓ docker-compose.yml ↓ ┌────────────────────────────────┐ │ api/worker 服务 │ 应用层变量 (SANDBOX_*) │ SANDBOX_PROVISIONER_URL │ │ SANDBOX_PROVISIONER_TOKEN │ └────────────┬───────────────────┘ ↓ 带 Bearer token 的 HTTP 调用 ┌────────────────────────────────┐ │ sandbox-provisioner 服务 │ 沙盒层变量 (PROVISIONER_BACKEND, DOCKER_*, K8S_*) │ PROVISIONER_BACKEND │ └────────────┬───────────────────┘ ↓ Docker API / K8s API + 认证 HTTP 代理 ┌────────────────────────────────┐ │ 动态创建的沙盒容器 │ └────────────────────────────────┘ ``` 两层变量不要混看。改了 `api/worker` 的 `SANDBOX_PROVISIONER_URL` 只是改了后端找 provisioner 的地址;改了 `sandbox-provisioner` 的 `PROVISIONER_BACKEND` 才是改了 provisioner 本身用什么方式创建沙盒。 ### sandbox.env 的特殊作用 `docker/sandbox_provisioner/sandbox.env` 文件的用途与上述两层变量不同。它通过 volume 挂载到 provisioner 容器内 (`/app/sandbox.env`),然后由 `LocalContainerProvisionerBackend` 在创建沙盒容器时读取,解析后的键值对会作为**环境变量注入到每个动态创建的沙盒容器**中。 ```yaml # docker-compose.yml 中 sandbox-provisioner 的挂载 sandbox-provisioner: volumes: - ./docker/sandbox_provisioner/sandbox.env:/app/sandbox.env:ro ``` 也就是说,`sandbox.env` 配置的是沙盒容器内部可见的环境变量,而不是 provisioner 本身的配置。当前该文件内容为: ```env CHECK_YUXI_SANDBOX_ENV_EXISTS=True ``` 如果需要给所有沙盒容器注入额外的环境变量(如代理配置、认证信息等),可以添加到 `sandbox.env` 文件中。 远程 Skill 拉取使用专门的一次性 Sandbox,不继承这里的全局环境变量或用户级 Agent 环境变量,避免不可信仓库通过复制文件带出凭据。Kubernetes 创建的 Sandbox 同时会禁用 ServiceAccount token 自动挂载。 ### 配置方式汇总 | 配置目标 | 配置位置 | 示例变量 | |----------|----------|----------| | 应用层连接 provisioner | `.env` 或 compose 环境 | `SANDBOX_PROVISIONER_URL`, `SANDBOX_PROVISIONER_TOKEN` | | provisioner 自身行为 | `.env` 或 compose 环境 | `PROVISIONER_BACKEND`, `DOCKER_*` | | 沙盒容器内部环境 | `sandbox.env` 文件 | 代理、认证等运行时变量 | ## 十四、和旧版文档相比,今天最重要的理解方式 当前项目不应再按“应用直接管理一个长期存在的本地 sandbox 服务”去理解。更准确的认识应该是:Yuxi 只管理线程和上下文;provisioner 负责创建线程对应的沙盒实例;文件系统不是简单地暴露一个容器根目录,而是把可写工作区、只读 skills 等组合成一个受控命名空间(知识库不再映射为沙盒目录,改由 `query_kb`/`open_kb_document` 等工具访问)。 因此,当你在界面上“启用沙盒”或者在文档里“选择 K8s”时,本质上做的不是切换一段业务逻辑,而是在切换 provisioner 的底层实例承载方式。选择 `docker` 时,沙盒由当前部署机上的 Docker daemon 动态创建;选择 `kubernetes` 时,沙盒由目标 K8s 集群动态创建。Yuxi 自己始终只面对一个 provisioner 服务地址。 ## 十五、排障时建议先看什么 如果怀疑是 provisioner 级问题,先看 `http://localhost:8002/health`,确认 backend 类型和 idle timeout 是否符合预期。默认 Docker 部署下这里应看到 `backend=docker`。接着看 `docker logs sandbox-provisioner --tail 200`,因为这里能直接看到创建容器、复用旧实例、健康检查失败和 idle reaper 删除的日志。 如果怀疑是 Docker 地址不可达,先确认每个动态沙箱只连接自己的 `yuxi-know-sandbox-` 网络,provisioner 同时连接该网络,而 API/worker 只在 `app-network`。provisioner 日志中的目标地址应是动态容器名,API/worker 拿到的地址应是 `/api/sandboxes//proxy`;代理请求必须携带 `SANDBOX_PROVISIONER_TOKEN`。如果怀疑是 Kubernetes 地址不可达,重点检查 `NODE_HOST` 和 NodePort 是否从 provisioner 可达。 如果怀疑是文件看得到但模型读不到,或者模型写了但 viewer 看不到,优先把问题拆成两层:一层是宿主机路径是否存在于 `saves/...` 下,另一层是该路径是否真的被当前线程沙盒挂载并暴露到了 `/home/gem/user-data` 或 `/home/gem/skills`。只要先分清“宿主机侧文件语义”和“沙盒侧运行时挂载语义”,定位问题通常会快很多。 --- ### Agents/Skills Management # Skills 管理系统 Skills 是 Yuxi 系统中用于扩展 Agent 能力的重要机制。通过 Skills,开发者可以将特定的工具、提示词模板或领域知识打包成可复用的技能包,让 Agent 在对话过程中能够调用这些额外能力。 ## 为什么需要 Skills 在实际业务场景中,我们常常会遇到一些特定的需求:比如需要 Agent 能够查询特定的 API、调用某个外部服务、或者使用特定的提示词模板来完成特定任务。传统的做法是在代码中硬编码这些功能,但这样会导致系统变得越来越臃肿,且难以复用。 Skills 系统的设计理念就是将这类"可插拔"的能力封装成独立的技能包。每个 Skill 包含完整的实现文件和元数据,Agent 可以根据配置动态加载所需的技能,实现能力的灵活组合。 ## 架构设计 Skills 系统分为平台共享与个人工作区两层。共享 Skill 采用「文件系统存内容,数据库存索引」; 个人 Skill 只存在于当前用户 workspace,并使用 Redis 保存 5 分钟的元数据快照: ``` ┌─────────────────────────────────────────────────────────────┐ │ Skills 存储架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ /app/saves/skills/ 数据库索引 │ │ ├── skill-a/ ┌──────────────┐ │ │ │ ├── SKILL.md │ skills 表 │ │ │ │ ├── tools/ │ - slug │ │ │ │ └── prompts/ │ - name │ │ │ └── skill-b/ │ - description│ │ │ ├── SKILL.md │ - dir_path │ │ │ └── ... │ - source_type│ │ │ │ - share_config │ │ │ - enabled │ │ │ │ - deps... │ │ │ └──────────────┘ │ │ │ │ workspace/agents/skills/ Redis 临时索引 │ │ └── my-skill/ - 按 uid 隔离 │ │ └── SKILL.md - 5 分钟失效 │ │ - 安装/删除/刷新后立即更新 │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 存储结构 - **文件系统**:`/app/saves/skills` 目录下,每个 Skill 占用一个子目录 - **数据库索引**:`skills` 表存储元数据(slug、name、description、来源、共享范围、启用状态、依赖关系等) - **关联机制**:通过 `dir_path` 字段关联文件系统目录与数据库记录 - **个人工作区**:`workspace/agents/skills/` 保存当前用户个人 Skill,不创建数据库记录 - **个人缓存**:解析后的名称、slug 和描述按用户缓存到 Redis,默认 5 分钟失效 ::: tip 两种存储边界 共享 Skill 必须通过系统导入并写入数据库;个人 Skill 可以由安装流程写入工作区,也可以在工作区中手动维护。手动修改后点击 Skills 页刷新,或等待最多 5 分钟重新解析。 ::: ## 创建方式 系统提供以下方式创建或安装 Skills: 1. **推荐 Skill 安装**:在 Skills 管理页的推荐分组点击 `+`,系统会拉取对应远程来源并生成安装草稿 2. **ZIP / SKILL.md 上传**:上传后先解析为安装草稿,再选择安装到个人工作区或共享 Skill 库 3. **远程仓库安装**:填写 skills 仓库地址、ModelScope Skill 地址或合集地址,下载并解析后选择安装位置 4. **在线编辑**:对已有且可管理的 Skill 在线创建目录、编辑文件和维护依赖 5. **Agent 内安装**:主智能体可通过 `install_skill` 工具安装个人工作区 Skill;子智能体禁用该工具 个人 Skill 不解析 `tool_dependencies`、`mcp_dependencies` 或 `skill_dependencies`。需要平台依赖、共享范围或在线管理时,应选择共享安装。 ## Skills 来源 Skills 本质上是提示词和工具的封装,以下是一些可以参考的 Skills 实现: - **Anthropic 官方 Tools**:https://github.com/anthropics/skills 可以参考其 skills 的组织方式和提示词设计 - **ModelScope Skill 市场**:https://modelscope.cn/skills 支持单个 Skill 地址,也支持合集地址批量拉取 - **MiniMax-AI CLI**:https://github.com/MiniMax-AI/cli 文本、图片、视频、语音和音乐生成 + Web 搜索(可通过 `MiniMax-AI/cli` 远程安装) - **社区 Skills**:各平台分享的 Agent 提示词模板 - **自定义开发**:根据业务需求自行开发 系统也会在启动时同步仓库内置 Skills。内置 `html-preview` 用于指导 Agent 在普通 Markdown 不足以清晰表达指标、对比、流程、时间线或层级关系时,按需输出 `html:preview` 静态 HTML/CSS 围栏;普通 HTML 源码仍使用 `html` 代码块。该 Skill 不依赖额外工具,前端继续通过清洗后的 sandboxed iframe 渲染预览。 未显式配置 Skills 的 Agent 会按现有资源默认规则自动获得该 Skill。使用显式 Skills 允许列表的 Agent 需要选择 `html-preview` 才能使用;内置 `deep-research` 已声明该依赖, 升级后仍可继续输出辅助可视化。 ## 快速开始 ### 创建你的第一个 Skill 一个标准的 Skill 目录结构如下: ``` my-awesome-skill/ ├── SKILL.md # 必选,Skill 的核心定义文件 ├── tools/ # 可选,相关的工具脚本 │ └── helper.py └── prompts/ # 可选,提示词模板 └── system.md ``` 其中 `SKILL.md` 是每个 Skill 必须包含的核心文件,它采用 Markdown + Frontmatter 格式: ```markdown --- name: My Awesome Skill slug: my-awesome-skill description: 这是一个用于处理特定任务的技能 --- # Skill 使用说明 这里是技能的详细使用文档,Agent 会读取这部分内容来了解如何使用这个技能。 ## 功能列表 1. 功能一:xxx 2. 功能二:yyy ## 使用示例 当用户 xxx 时,可以调用此技能... ``` **Frontmatter 字段说明:** | 字段 | 必填 | 说明 | |------|------|------| | `name` | 是 | Skill 展示名称,可使用更易读的名称(如 `Word / DOCX`) | | `slug` | 否 | Skill 唯一标识,必须是小写字母、数字、短横线的组合,且不能连续短横线(如 `my-skill`)。未填写时兼容旧格式,系统会使用 `name` 作为 slug,此时 `name` 也必须满足 slug 规则 | | `description` | 是 | Skill 的功能描述,会在 Agent 配置时展示 | ### 导入 Skill 可以通过以下方式导入或安装 Skill: **方式一:从推荐列表安装** 1. 在系统设置的「Skills 管理」页面查看「推荐」分组 2. 未安装的推荐 Skill 会以普通 Skill 卡片样式展示,右侧显示 `+` 3. 点击推荐卡片或 `+` 后,系统会使用该 Skill 的远程来源拉取内容 4. 拉取成功后会弹出安装草稿,选择个人工作区或共享 Skill 后完成安装 已安装的推荐 Skill 不会继续显示在「推荐」分组中。 **方式二:通过 ZIP 包或 SKILL.md 上传** 1. 将 Skill 目录打包成 ZIP 文件(注意:ZIP 的根目录就是 Skill 目录) 2. 在系统设置的「Skills 管理」页面,点击「上传 Skill」 3. 上传 ZIP 文件或单个 `SKILL.md` 4. 系统解析上传内容并返回安装草稿 5. 选择安装位置后完成安装;选择共享 Skill 时继续确认共享范围,也可以放弃草稿 系统会自动: - 校验 ZIP 内容和路径安全性 - 检查 slug 冲突:共享安装沿用全局冲突处理,个人安装同用户冲突时明确失败且不改写 slug - 解析 SKILL.md 的 frontmatter;只有共享安装会写入数据库 - 按当前用户角色校验可选择的共享范围 **方式三:从远程来源安装** 管理员可以在「设置 → 基本设置 → Skill 配置 → 远程来源白名单」中配置允许远程安装 Skill 的来源域名;对应系统配置项为 `remote_skill_source_policy.allowed_hosts`。 该策略保存在 PostgreSQL `config_options` 中,默认允许 `github.com` 和 `modelscope.cn`,只做 精确域名匹配,不自动放行子域名;保存空列表时,远程 Skill 安装会被禁用。运行时以数据库 配置为准,不依赖 `base.toml`、环境变量或 Redis 配置快照。 1. 在 Skills 管理页面点击「远程安装」 2. 在“按仓库拉取”中填写来源,例如: - `anthropics/skills` - `https://github.com/anthropics/skills` - `https://modelscope.cn/skills/@anthropics/pdf` - `https://modelscope.cn/collections/MiniMax/MiniMax-Office-skills` 3. 点击“拉取技能”获取该来源中可发现的 Skills 列表 4. 单个 Skill 地址通常会自动选中;仓库或合集地址可在列表中勾选一个或多个 Skills 5. 点击“解析并确认”,系统返回安装草稿;选择个人工作区或共享 Skill 后正式安装 也可以切换到“全局搜索发现”,输入关键字检索 skills.sh 上的开源 Skills,再选择结果安装。 系统会在后端: - 只接受管理员白名单中的 HTTPS 来源;GitHub `owner/repo` 简写按 `github.com` 校验 - 在不继承全局或用户环境变量的一次性 Sandbox 中执行 `npx skills`,Kubernetes Sandbox 不挂载 ServiceAccount token - 通过 Sandbox 文件 API 提取对应 Skill,严格校验返回的相对路径,并限制文件数、目录深度和总大小;个人确认写入 workspace,共享确认写入 `/app/saves/skills` 与数据库 来源白名单用于限制产品允许的远程仓库,并不等同于 Sandbox 网络出口防火墙。 ::: tip ModelScope 合集适合批量安装 ModelScope 合集地址可以作为远程来源填写,例如 `https://modelscope.cn/collections/MiniMax/MiniMax-Office-skills`。拉取后在列表中勾选需要的 Skills,再统一解析为安装草稿。 ::: **方式四:在线编辑已有 Skill** 在 Skills 管理页面,你可以: - 新建目录或文件 - 在线编辑文本文件(支持 .md、.py、.js、.json 等格式) - 直接在网页上修改 SKILL.md 内容 只有具备 `can_manage` 权限的用户才能编辑文件、依赖、共享范围和启用状态。 ::: tip 安装位置决定管理能力 个人工作区适合公开、平台无关或用户自定义 Skill;共享 Skill 适合需要工具、MCP、Skill 依赖以及部门或全局共享的能力。 ::: ## 依赖系统 Skills 之间可以建立依赖关系,形成一个松耦合的技能网络。 ### 依赖类型 每个 Skill 可以声明三类依赖: | 依赖类型 | 说明 | 加载时机 | |----------|------|----------| | `tool_dependencies` | 需要的内置工具 | 激活后按需加载 | | `mcp_dependencies` | 需要的 MCP 服务 | 激活后按需加载 | | `skill_dependencies` | 依赖的其他 Skill | 会话启动即生效 | ### 渐进式加载机制 系统采用三级渐进式加载策略,确保资源的高效利用: **阶段一:会话启动** 当 Agent 会话启动时,系统会: 1. 在创建 Graph 前读取已过滤的 `context.skills` 列表 2. 递归展开 `skill_dependencies`,派生 `_prompt_skills` 和 `_readable_skills` 3. 将 `_prompt_skills` 对应的技能说明注入到系统提示词中 这意味着:只要配置了某个 Skill,它的依赖 Skill 就会立即进入提示词;共享与内置 Skill 进入沙盒 `/home/gem/skills` 只读范围,个人 Skill 直接使用 `/home/gem/user-data/workspace/agents/skills/`。 **阶段二:技能激活** 当 Agent 通过 `read_file` 读取共享路径 `/home/gem/skills//SKILL.md`,或个人工作区路径 `/home/gem/user-data/workspace/agents/skills//SKILL.md` 时,视为“激活”该技能。系统会: 1. 验证该技能在可见列表中 2. 将其添加到 `activated_skills` 列表 3. 后续的模型调用会使用激活列表来加载依赖 **阶段三:按需加载** 每次模型调用时,系统会: 1. 检查 `activated_skills` 中的技能 2. 收集这些技能的 `tool_dependencies` 和 `mcp_dependencies` 3. 动态将需要的工具和 MCP 服务添加到可用工具集中 这种设计的好处是:不会在会话开始时加载所有工具,而是根据 Agent 实际使用情况按需加载,既节省资源又保证响应速度。 ### 依赖声明示例 假设我们有三个 Skills: - **base-skill**:基础技能,无依赖 - **advanced-skill**:依赖 `base-skill` - **pro-skill**:依赖 `advanced-skill` 当在 Agent 配置中只选择 `pro-skill` 时: 1. 启动阶段:`_readable_skills` = [`pro-skill`, `advanced-skill`, `base-skill`](自动展开依赖链) 2. Agent 首次调用任何 skill 时:所有三个 Skill 都可读 3. 当 Agent 读取 `pro-skill/SKILL.md` 时:触发激活,工具和 MCP 依赖被加载 ## 权限管理 数据库中的共享与内置 Skills 使用 `source_type`、`share_config` 和 `enabled` 控制来源、共享范围和启用状态。个人工作区 Skill 由认证用户目录天然隔离,不携带 `share_config`,避免与数据库“指定用户共享”语义混淆。 | 字段 | 说明 | |------|------| | `source_type` | `builtin`、`upload`、`remote` 或 `personal` | | `share_config` | 仅共享与内置 Skill 使用;v2 配置分别声明 `read_scope` 和 `manage_scope` | | `enabled` | 是否允许在 Agent 配置与运行时使用 | 访问与管理规则: | 用户 | 可见 / 可用 | 可管理 | |------|-------------|--------| | 超级管理员 / 管理员 | 可查看可管理或已启用且可访问的 Skills | 可管理所有非内置 Skills;可启停内置 Skills | | 普通用户 | 可查看已启用且对自己可访问的 Skills,只能把新 Skill 安装到个人工作区 | 可管理自己创建的非内置 Skills | | 内置 Skills | 默认全局共享并启用 | 管理员可启停;不允许删除或直接编辑文件 | | 个人工作区 Skills | 只对当前用户可见,文件与缓存按 uid 隔离 | 当前用户可预览、删除和手动刷新 | 共享范围限制: - `global`:所有用户可访问 - `department`:指定部门用户可访问 - `user`:指定用户可访问 - 只有管理员可以把 Skill 安装到平台共享 Skill 库;普通用户安装固定进入个人工作区,不创建数据库记录,也不配置共享范围 旧版单层共享范围会在 PostgreSQL 启动迁移中复制为读取和管理范围;运行时接口仅接受 v2 配置。 管理员和普通用户在创建或编辑 Agent 时,都只能从自己可访问且启用的 Skills 中选择能力。 个人 Skill 与共享 Skill 同 slug 时,个人 Skill 整项覆盖共享版本;共享版本的工具、MCP 和 Skill 依赖不会继续加载。删除个人版本后,用户仍有权访问的同名共享版本会在下一次运行恢复生效。 ## 运行时行为 ### Agent 如何使用 Skills 1. **提示词注入**:系统在每次模型请求时动态注入可用 Skills 的描述(请求级注入,避免污染 runtime context) 2. **文件访问**:共享与内置 Skill 从只读 `/home/gem/skills//...` 读取;个人 Skill 直接从工作区读取 3. **工具调用**:当 Agent 需要使用某个 Skill 时,会先读取对应的 SKILL.md 了解使用方法 ### 文件操作限制 共享与内置 Skill 的运行时 `/home/gem/skills` 路径有以下限制: - **只读**:Agent 只能读取文件内容 - **禁止写入**:不能创建、修改或删除文件 - **路径安全**:所有路径都经过安全校验,防止目录穿越攻击 ::: tip 只读不等于不可执行 `/home/gem/skills` 对 Agent 是只读的,但沙盒命令工具仍可执行其中的脚本。Skill 应把依赖、运行方式和产物位置写清楚;脚本若需要写文件,应写入 workspace 或 outputs,而不是 Skill 目录。 ::: 个人 Skill 位于 `/home/gem/user-data/workspace/agents/skills`,属于用户可写工作区。运行时不会再把它复制到 线程 `/home/gem/skills` 目录,因此工作区中的修改会直接成为后续读取内容。 ### 会话隔离 每个 Agent 会话都有独立的 Skills 可见集: - 不同会话可以配置不同的 Skills - 同一会话内修改 `context.skills` 会重建共享与内置 Skill 的只读投影 - 个人 Skill 元数据最多缓存 5 分钟;安装、删除和 Skills 页手动刷新会立即更新缓存 - 每次构建运行时只同步最终生效的共享与内置 Skill;个人版本同名覆盖时直接使用工作区路径 ## 最佳实践 ### Skill 命名规范 - `slug` 使用小写字母、数字和短横线,不能连续短横线 - `slug` 应具有描述性,如 `weather-query`、`sql-reporter` - `name` 用于展示,可比 `slug` 更自然,例如 `Word / DOCX` - 避免过长的 `name` 和 `slug` ### 依赖管理建议 - **保持依赖链简洁**:层级不宜过深,一般 1-2 层为宜 - **避免循环依赖**:系统会检测并阻止循环依赖 - **明确依赖必要性**:只在真正需要共享能力时才建立依赖 ### SKILL.md 编写技巧 ```markdown --- name: example-skill description: 简短描述技能功能 --- # 技能名称 这里是详细的使用说明... ## 何时使用 描述在什么场景下应该使用这个技能... ## 使用方法 1. 第一步... 2. 第二步... ## 示例 ``` 具体的使用示例... ``` ``` --- ### Agents/Subagents Management # 子智能体 Yuxi 的子智能体是 Agent-backed 形态:它仍然是 `agents` 表中的一级 Agent,只是额外带有 `is_subagent=true` 标记,并使用专用后端 `SubAgentBackend`。子智能体不再有独立的创建入口、独立表或独立管理接口。 ## 用户视角 ### 子智能体能解决什么问题 当任务复杂、需要分工处理时,主 Agent 可以通过 `task` 工具把一个子任务交给子智能体。例如: - 通用型子任务:交给内置 `general-purpose` 子智能体,使用默认运行配置处理分析、整理、写作或文件处理。 - 研究型子任务:聚焦检索和资料整理。 - 评审型子任务:对草稿进行结构和质量审查。 - 领域型子任务:使用指定模型、工具、知识库或 Skills 处理特定领域问题。 ### 在哪里创建和编辑 子智能体与普通 Agent 使用同一个管理入口:进入模型配置中的“智能体”管理页,点击新增智能体,并在后端类型中选择 `SubAgentBackend`。 创建和编辑流程与普通 Agent 保持一致: - 展示信息、共享权限、系统提示词和运行配置都保存在同一份 Agent 配置中。 - 模型、工具、知识库、MCP 和 Skills 仍通过 Agent runtime config 表单配置。 - 子智能体不会出现在聊天页的 Agent 快速切换列表中。 - 子智能体不能再配置或调用其他子智能体。 ### 如何让主 Agent 调用子智能体 主 Agent 会通过 runtime config 的“子智能体”字段确定 `task` 工具可调用的子智能体范围。 `subagents` 字段表示当前主 Agent 的允许列表: - 未选择或保存空列表时,默认启用当前用户可见的全部子智能体,包括内置 `general-purpose`。 - 显式选择后,只允许调用所选子智能体。 - 只会调用当前用户可访问且 `is_subagent=true` 的 Agent。 - 每个子智能体使用自己的 `config_json.context`,包括模型、工具、知识库、MCP、Skills 和系统提示词。 内置 `general-purpose` 的 `config_json.context` 为空,运行时会按 `SubAgentContext` 和 `BaseContext` 默认值解析模型、工具、知识库、MCP 与 Skills。 ## 开发者视角 ### 数据模型 子智能体复用 `agents` 表,核心字段包括: | 字段 | 说明 | |------|------| | `backend_id` | 子智能体固定使用 `SubAgentBackend` | | `is_subagent` | 子智能体标记,`SubAgentBackend` 必须对应 `true` | | `config_json.context` | 子智能体自己的运行配置 | | `share_config` | 可见性与管理权限,沿用 Agent 共享模型 | 后端会校验 `backend_id` 与 `is_subagent` 一致:普通 Agent 不能伪装成子智能体,`SubAgentBackend` 也不能以普通 Agent 形态保存。子智能体不能被设置为默认 Agent。 ### API 与列表语义 子智能体沿用 `/api/agent` CRUD: - `GET /api/agent` 默认只返回聊天可用的普通 Agent。 - `GET /api/agent?include_subagents=true` 返回管理页需要的完整 Agent 列表。 - 创建或更新 `SubAgentBackend` 时,payload 会携带或推导 `is_subagent=true`。 - 详情、更新和删除仍走同一套 Agent 管理接口,并复用现有权限过滤。 旧的独立 SubAgent 管理链路已经移除,不再维护单独的启停状态、内置初始化或 spec 缓存。 ### 运行时调用链 主 Agent 构图时,会先把 `context.subagents` 归一化为当前用户可见的允许列表;允许列表非空时挂载 Yuxi 的 task middleware。middleware 会把允许的子智能体列表注入模型提示,并暴露一个 `task` 工具。 工具参数为: ```python class TaskToolSchema(BaseModel): description: str subagent_slug: str thread_id: str | None = None ``` `thread_id` 是可选的子智能体线程 ID。新任务不需要填写;如果要继续之前同一个子智能体任务,应使用上一次 `task` 工具结果中的 `子智能体线程 ID`。 执行时的关键流程: 1. 从父 Agent 的 `context.subagents` 读取允许的子智能体 slug;未显式配置或空列表会展开为当前用户可见的全部子智能体。 2. 使用 `AgentRepository` 加载当前用户可见且 `is_subagent=true` 的 Agent。 3. 新任务会为本次调用生成 child checkpoint thread id,例如 `_sub__`;续跑任务会校验并复用传入的 `thread_id`。 4. 使用子智能体自己的 `SubAgentContext` 和 `config_json.context` 构建真实 Agent graph。 5. 调用结束后,把子智能体线程 ID 和最终 assistant 文本作为 `task` 工具结果返回给主 Agent。 `SubAgentBackend` 复用普通 Agent 的运行时资源归一化流程,但不会挂载 task middleware;它的 `subagents` 字段隐藏且默认为空,因此不会形成嵌套子智能体调用。 ### 同步调用与异步调用 `task` 是同步工具:父智能体调用后会阻塞等待子智能体 run 走到终态,再拿到最终 assistant 文本。这种模式适合短任务,例如父智能体必须立即依赖子智能体结果继续推理时。 但当子任务耗时较长或可以并行多个时,同步等待会让父智能体长时间停在工具调用上,无法继续工作。因此 middleware 还同时暴露一组异步子智能体生命周期工具: | 工具 | 作用 | 关键参数 | |------|------|----------| | `subagent_start` | 异步启动子智能体 run,立即返回 `run_id` 和 `thread_id` | `description`、`subagent_slug`、可选 `thread_id` | | `subagent_status` | 按 `run_id` 查询状态,附带最近 3 条可读进度摘要;run 终态时返回最终结果 | `run_id` | | `subagent_cancel` | 取消运行中的子智能体 run | `run_id` | | `subagent_await` | 阻塞等待子智能体 run 终态并返回最终结果;超时返回当前快照和 `wait_timed_out` 标志 | `run_id` | 调用约束: - 长任务或多个可并行任务优先使用 `subagent_start`,让父智能体继续推进主流程;短任务需要立即拿到结果时继续使用 `task`。 - `thread_id` 是子智能体的长期上下文 ID,同一个 `thread_id` 终态后可以再创建新的 run 续跑。若同线程已有运行中的 run,`subagent_start` 会返回 busy 结构,不会隐藏排队。 - `subagent_status`、`subagent_cancel`、`subagent_await` 都按 `run_id` 操作,并校验该 run 是否归属当前父 run 创建的子智能体,避免越权访问其它子任务。 - Redis 原始事件流只供运行基础设施和前端 SSE 订阅使用,不作为模型工具结果返回;父智能体通过 `subagent_status` 获取轻量进度,通过 `subagent_await` 获取最终结果。 - 父智能体不应通过 shell、curl 或 HTTP API 间接调用子智能体,所有调用必须走上述工具。 异步子智能体在状态面板的「子智能体」分组中按 `run_id` 展示运行身份;状态查询工具不会渲染成独立 Agent 卡片,弹窗会随子智能体条目补齐 `run_id` 后订阅对应 SSE,已完成的子智能体改为直接读取持久化 Message 历史。 ### 文件系统与沙盒作用域 子智能体与主 Agent 共享文件系统时使用拆分作用域: | 路径/作用域 | 普通 Agent | 子智能体 | |------|------|------| | LangGraph checkpoint | 当前 `thread_id` | child `thread_id` | | `/home/gem/user-data/workspace` | 当前 `uid` 的共享工作区 | 同一 `uid` 的共享工作区 | | `/home/gem/user-data/uploads` | 当前会话文件作用域 | 父会话 `file_thread_id` | | `/home/gem/user-data/outputs` | 当前会话文件作用域 | 父会话 `file_thread_id` | | `/home/gem/skills` | 当前 Agent 的 Skills 作用域 | 子智能体自己的 `skills_thread_id` | 这保证子智能体可以读取父会话上传、产物也会回到父会话 artifacts 中,同时子智能体的 Skills 不会污染主 Agent。 --- ### Agents/Tools System # 工具系统 Yuxi 的工具系统基于注册机制,支持多种工具类型的动态组装。 ## 工具注册机制 Yuxi 的工具系统采用 `@tool` 装饰器注册机制,核心位于 `backend/package/yuxi/agents/toolkits/registry.py`。 ### @tool 装饰器 ```python from yuxi.agents.toolkits.registry import tool @tool(category="buildin", tags=["示例"], display_name="示例工具") def example_tool(text: str) -> str: """示例工具:返回处理后的文本""" ... ``` 装饰器参数: - **category**: 工具分类,用于分组,例如 `buildin`、`knowledge`、`debug` - **tags**: 标签列表,用于前端展示 - **display_name**: 显示名称(给人看的名字) - **icon**: 图标名称(可选) ### 自动发现 导入 `toolkits` 包时会自动触发注册: ```python from yuxi.agents.toolkits import buildin, debug # 触发模块内 @tool 装饰器执行 ``` `toolkits/__init__.py` 会导入 `buildin` 与 `debug` 模块;知识库工具由内置 `knowledge-base` Skill 的依赖显式注册,而不是作为所有 Agent 的默认工具。 ## 工具分类 ### 内置工具 (buildin) | 工具 | 说明 | |------|------| | `ask_user_question` | 向用户发起交互式提问 | | `ocr_parse_file` | 将 uploads、outputs 或 workspace 中的 PDF、Office 或图片文件转换为 Markdown | | `present_artifacts` | 展示 Agent 沙盒 outputs 目录下的产物文件 | | `install_skill` | 从沙盒路径或 Git 来源安装当前用户私有 Skill,并激活当前主智能体会话;子智能体禁用 | | `tavily_search` | Tavily 网页搜索(需配置 `TAVILY_API_KEY`) | Qwen-Image 生成能力已迁移为内置 Skill `image-gen`。模型调用与图片下载在 Agent 沙盒中完成,生成后的图片保存到 `/home/gem/user-data/outputs/`,再通过 `present_artifacts` 展示。 ### 知识库工具 (kbs) 知识库工具使用 `@tool(category="knowledge")` 注册,并通过内置 `knowledge-base` Skill 的 `tool_dependencies` 按需加载。`get_common_kb_tools()` 仍可用于直接获取完整工具列表: ```python from yuxi.agents.toolkits.kbs import get_common_kb_tools kb_tools = get_common_kb_tools() # 返回: [list_kbs, get_mindmap, query_kb, find_kb_document, open_kb_document] ``` | 工具 | 说明 | |------|------| | `list_kbs` | 列出用户可访问的知识库 | | `get_mindmap` | 获取知识库的思维导图结构 | | `query_kb` | 按 `kb_id` 检索内容,返回结构化的 `kb_id`、`file_id` 与命中片段 | | `find_kb_document` | 在已知文件内按关键词或正则定位内容 | | `open_kb_document` | 按 `file_id` 分段打开知识库文档(默认窗口 1800 行) | | `search_file` | 按文件名在指定或全部可见知识库中搜索文件 | ## 工具组装 工具组装在 Graph 创建阶段完成。内置 Agent 会先调用 `prepare_agent_runtime_context` 过滤当前用户可用资源,再调用 `resolve_configured_runtime_tools(context)` 加载已配置工具: 1. **基础工具**:从 `context.tools` 中按名称筛选 2. **MCP 工具**:根据 `context.mcps` 加载 MCP 服务器工具 3. **Skill 依赖工具**:由 `SkillsMiddleware` 在 Skill 激活后按需追加,包括 `knowledge-base` 绑定的知识库工具 ```python from yuxi.agents.context import prepare_agent_runtime_context from yuxi.agents.toolkits.service import resolve_configured_runtime_tools context = await prepare_agent_runtime_context(context, user=current_user, db=db) tools = await resolve_configured_runtime_tools(context) ``` ## Skills 集成 Skills 与工具是两种不同的扩展机制。工具是具体的功能实现,而 Skills 是包含提示词、工具依赖和元数据的完整技能包。通过 `context.skills` 配置 Skills 时,共享与内置 Skill 从 `/home/gem/skills//...` 读取,个人 Skill 从 `/home/gem/user-data/workspace/agents/skills//...` 读取;智能体通过对应的 SKILL.md 了解使用方式。 关于 Skills 的详细机制,请参阅 [Skills 管理](./skills-management.md)。 --- ### Advanced/Api Key Integration # API Key 外部集成 Yuxi 平台提供了 API Key 认证机制,允许外部系统在无需用户登录的情况下调用智能体对话接口。本文档详细介绍 API Key 的使用方法、接口调用方式以及安全注意事项。 ## API Key 概述 API Key 是一种用于身份验证的密钥字符串,外部系统可以通过它在请求头中携带凭据来访问 Yuxi 的对话接口。与传统的用户名密码登录方式相比,API Key 更加适合用于系统间的自动化调用场景。Yuxi 的 API Key 以 `yxkey_` 为前缀,长度为 54 个字符,采用 SHA-256 哈希存储,确保密钥本身不会在数据库中明文保存。系统会记录每个 API Key 的最后使用时间,方便管理员追踪使用情况。 ## 创建 API Key 登录系统后,进入 API Key 管理界面,可以创建新的密钥。创建时需要为 API Key 设置一个名称,用于标识其用途,例如"外部客服系统"或"数据同步服务"。创建的 API Key 会自动绑定到当前登录用户,绑定后的 API Key 在调用接口时会以该用户的身份执行操作。API Key 还支持设置过期时间,过期后该密钥将自动失效。 需要特别注意的是,创建 API Key 时返回的完整密钥(secret)只会显示一次,务必在创建时将其安全保存。如果遗失,请删除旧密钥并创建一个新的 API Key。 管理接口同样走通用认证: - `GET /api/user/apikey/`:列出当前用户可见的 API Key - `POST /api/user/apikey/`:创建 API Key - `PUT /api/user/apikey/{api_key_id}`:更新名称、状态或过期时间 - `DELETE /api/user/apikey/{api_key_id}`:删除密钥 ## 确定 API 访问地址 Yuxi 后端服务绑定在 `0.0.0.0:5050`,不会自动探测或对外宣告本机 IP。实际访问地址取决于部署环境: - **本地开发**:`http://localhost:5050` - **生产部署(Nginx 反向代理)**:**强烈建议使用 HTTPS**,即 `https://<服务器域名>`(443 端口)。由于 API Key 会在请求头中以明文形式传输,使用 HTTP(80 端口)会导致密钥在网络传输过程中被窃听或篡改,必须避免 完整的 API 交互流程可参考自动生成的 Swagger 文档:`{base_url}/docs`。 ## 接口调用方式 > **关于 `agent_id` / `agent_slug` 的说明**:创建会话线程时仍使用 `agent_id` 绑定目标 Agent;创建运行任务时使用 `agent_slug` 快照本次运行目标。二者的取值都是智能体的 **slug**(如 `default-chatbot`),不是数据库自增 ID 或 `agent_config_id`。 外部系统通过 HTTP 请求调用 Yuxi 接口时,需要在请求头中携带 API Key: ```http Authorization: Bearer yxkey_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` 当前智能体对话采用 run + SSE 流程: 1. 创建对话线程:`POST /api/chat/thread` 2. 创建运行任务:`POST /api/agent/runs` 3. 订阅事件流:`GET /api/agent/runs/{run_id}/events` `POST /api/agent/runs` 请求体必填 `query`、`agent_slug` 和 `thread_id`,可选字段包括 `meta`、`image_content`、`resume`、`created_by_run_id`。接口返回 `run_id`、`thread_id`、`status`、`request_id` 和 `stream_url`。 以下是一个典型的 Python 调用示例: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` 如果已经有会话线程,可以复用已有 `thread_id` 直接创建 run: ```json { "query": "继续上一轮话题", "agent_slug": "default-chatbot", "thread_id": "existing-thread-id", "meta": {} } ``` ### 读取运行结果 如果不需要逐事件消费 SSE,可以在 run 终态后直接拉取最终结果: ```http GET /api/agent/runs/{run_id}/result ``` 返回结构包含运行状态、最终 assistant 输出、Langfuse trace id 和错误信息。该接口只读,不会再次触发 run。对外部系统只关心「最终答案」、不需要展示中间过程的场景,比订阅 SSE 更简单。 ### 外部系统调用入口 除通用 `/api/agent/runs` 之外,Yuxi 还提供专为外部系统设计的 `agent-invocation` 路由,复用同一套 AgentRun 队列与结果读取能力: | 接口 | 用途 | 关键字段 | |------|------|----------| | `POST /api/agent-invocation/agent-call/runs` | 外部系统调用 Agent;`async_mode=true` 时立即返回 `run_id`,否则阻塞到 run 终态返回结果 | `agent_slug`、`messages`、`thread_id`、`request_id`、`model_spec`、`async_mode` | | `POST /api/agent-invocation/agent-call/runs/result` | 读取 agent-call run 的 OpenAI 兼容结果结构 | `run_id`、可选 `agent_slug` | | `POST /api/agent-invocation/eval/runs` | 运行一次评估样例,阻塞到 run 终态后返回最终输出与可选轨迹摘要 | `query`、`agent_slug`、`evaluation`、`include_trajectory_summary` | agent-call 的 `messages[].content` 兼容 OpenAI 风格的 `text`/`image_url` 多模态数组:纯文本数组不会触发 422,图片输入会保留原始 LangChain 多模态消息供 worker 恢复。出于安全考虑,**不允许通过 `agent_call_meta.context` 覆盖 Agent 运行上下文**;运行时模型覆盖只允许走独立 `model_spec` 字段。Agent Eval 通常通过 `yuxi agent eval` CLI 触发,详见[智能体评估](../agents/agent-evaluation.md)。 ## 响应格式 运行事件流采用 Server-Sent Events 格式,响应头为 `text/event-stream`。每个事件包含: - `event`:事件类型,可能是模型输出、工具调用、子智能体输出等语义事件,也可能是 `error` 或终止事件 `end` - `data`:JSON 编码的事件 envelope,包含 `run_id`、`thread_id`、事件载荷等字段 - `id`:Redis Stream 序号,可作为断线重连游标 服务端还会定期发送以 `:` 开头的 heartbeat 注释,客户端应忽略。断线重连时,可以在请求头中传 `Last-Event-ID`,或在 query 参数中传 `after_seq`,服务端会从该序号后继续回放事件。 事件流默认返回完整载荷,便于排查 LangGraph/Langfuse 运行细节。如果只需要渲染消息、工具调用、工具结果、Agent state 和终止状态,可以在订阅地址追加 `?verbose=false`。精简模式会保留 SSE `event/data/id`、data 中的 `run_id/thread_id/request_id/payload` 以及客户端消费所需字段;同一 data 内的 `request_id` 会外提为单个字段。精简模式还会跳过 `metadata` 和空 `yuxi.agent_state`,并去掉每个 chunk 中重复的 `meta`、`metadata`、`thread_id`、`response`、空 `namespace` 和图片 base64 等调试字段。 每次创建 run 都会返回 `request_id`,可用于日志追踪和问题排查。如果需要在多轮对话中使用同一个会话,请复用 `thread_id`,系统会将同一线程的消息串联起来形成连贯的对话上下文。 ## 认证方式 Yuxi 的 API 接口统一支持两种认证方式: 1. **API Key 认证**:使用 `Authorization: Bearer ` 格式,其中 API Key 必须以 `yxkey_` 前缀开头 2. **JWT Token 认证**:使用 `Authorization: Bearer ` 格式 系统根据 token 的前缀自动判断认证方式。以 `yxkey_` 开头的 token 被视为 API Key,其他 token 则作为 JWT Token 处理。这种设计使得同一个接口可以同时支持外部系统(使用 API Key)和内部前端应用(使用用户登录态)调用。 ## 安全注意事项 **传输层安全**:API Key 在请求头中以明文形式传输,**生产环境必须通过 HTTPS(443 端口)调用**,避免在公网上以 HTTP 明文传输造成密钥泄露。建议在 Nginx 反向代理层启用 TLS 并强制 HTTP 重定向到 HTTPS。 保管好 API Key 密钥是最重要的安全原则。由于 API Key 一旦泄露就可能被滥用,建议不要将密钥硬编码在代码中,而是通过环境变量或配置中心来管理。如果怀疑密钥泄露,应立即在管理界面禁用或删除该 API Key,并创建新密钥替换。启用密钥过期功能是一种良好的安全实践,可以设置较短的有效期并定期轮换。 在生产环境中,建议为不同的外部系统创建独立的 API Key,这样可以在某个密钥泄露时快速定位问题并限制影响范围。同时,建议在管理界面定期查看 API Key 的使用记录,检查是否存在异常调用情况。 关于权限控制,API Key 的权限等同于其绑定的用户在系统中的角色。如果 API Key 绑定到特定用户,则该用户的所有权限都会体现在 API Key 的操作中,因此务必妥善保管。 --- ### Advanced/Branding # 品牌自定义 Yuxi 支持完整的品牌自定义,包括 Logo、组织名称、版权信息、登录协议等,方便企业用户进行品牌定制。 ## 品牌信息配置 ### 步骤 1:复制模板文件 ```bash cp backend/package/yuxi/config/static/info.template.yaml backend/package/yuxi/config/static/info.local.yaml ``` ### 步骤 2:编辑品牌信息 在 `backend/package/yuxi/config/static/info.local.yaml` 中配置你的品牌信息: - 应用名称 - 组织名称 - Logo - 版权信息 - 登录页用户协议/隐私协议链接 ### 步骤 3:指定配置文件 在 `.env` 中指定配置文件路径: ```env YUXI_BRAND_FILE_PATH=backend/package/yuxi/config/static/info.local.yaml ``` ::: tip 配置优先级 `info.local.yaml` > `info.template.yaml`(默认) ::: ## 登录协议配置 登录页支持从品牌配置中读取用户协议与隐私协议链接。 ### 配置项 在 `backend/package/yuxi/config/static/info.local.yaml` 的 `footer` 下新增以下字段: ```yaml footer: copyright: "© your org 2026" user_agreement_url: "/protocols/user-agreement.template.html" privacy_policy_url: "/protocols/privacy-policy.template.html" ``` ### 显示规则 - 当 `user_agreement_url` 和 `privacy_policy_url` 都有值时,登录页会显示协议勾选项。 - 任一字段为空时,登录页不显示协议勾选项。 - 未勾选协议时,提交登录/初始化会通过消息提示用户先同意协议。 ### 协议模板文件 系统默认提供两个 HTML 模板文件: - `web/public/protocols/user-agreement.template.html` - `web/public/protocols/privacy-policy.template.html` 你可以直接编辑这两个文件中的协议内容,并替换占位符(如 `{{ORG_NAME}}`、`{{PRODUCT_NAME}}`、`{{EFFECTIVE_DATE}}`)。 如果你有自己的协议页面,也可以将 `user_agreement_url` 和 `privacy_policy_url` 指向自定义路径或外部链接。 ### Icon 定制 系统预设了多种 Icon,如需更多图标,可以从 `lucide-vue-next` 中引入。 ## 样式定制 系统支持完整的主题色定制。配置文件位于 `web/src/assets/css/base.css`,以及 `web/src/assets/css/base.dark.css`: ```css :root { --main-color: #1890ff; /* 主色调 */ --main-1000: #f0f2f5; /* 色板 */ --main-900: #e6f7ff; /* 色板 */ /* ... 其他色板 */ } ``` 修改配色变量后,界面会实时更新,无需重启服务。 此外,`web/src/stores/theme.js` 中的 `colorPrimary` 也需要同步修改。 --- ### Advanced/Configuration # 配置系统详解 ## 概述 系统采用分层配置架构,模型供应商和运行时系统配置由网页界面管理,启动期配置由环境变量提供。 ## 配置层级 ``` 代码默认值 → 环境变量 → PostgreSQL 管理员配置 (低) (高) ``` ## 模型配置 由网页统一管理,详见 [模型配置](../intro/model-config.md)。 ## 应用配置 运行时系统配置定义于 `backend/package/yuxi/config/options.py`,管理员通过系统配置页面或 `/api/system/config` 接口修改,值保存到 PostgreSQL。API 与 worker 通过 Redis 短期缓存共享配置;Redis 不可用时直接回源 PostgreSQL。 ### 读取配置 ```python from yuxi.config.options import system_options values = await system_options.get() default_model = values["default_model"] ``` 管理员更新配置后,服务先提交 PostgreSQL,再失效 Redis 缓存。运行中的 API 和 worker 会在下一次读取时获得最新配置,不需要重启。 `SAVE_DIR`、数据库、Redis、sandbox 和 LangGraph checkpointer 后端仍属于启动期环境变量配置。运行中的已初始化组件不承诺这些配置热更新,修改后需要重启服务。 升级时,服务会一次性把旧 `saves/config/base.toml` 或旧数据库系统配置迁移到 PostgreSQL;已存在的管理员值不会被旧配置覆盖。迁移完成后不再读取 `base.toml`。 --- ### Advanced/Deployment # 生产部署指南 本文档介绍如何在生产环境中部署 Yuxi。 ## 前置要求 - Docker Engine (v24.0+) - Docker Compose (v2.20+) - NVIDIA Container Toolkit(如需使用 GPU 服务) ::: warning 注意事项 1. 生产环境和开发环境建议使用不同的机器,避免端口和资源冲突 2. 虽然名为「生产环境」,但这只是基本配置,真正上线需要根据实际情况调整 3. 前端有调试面板(长按侧边栏触发),生产环境建议关闭 ::: ## 部署步骤 ### 1. 准备配置文件 为避免与开发环境冲突,生产环境建议使用 `.env.prod` 文件: ```bash cp .env.template .env.prod ``` 编辑 `.env.prod`,设置强密码和必要的 API 密钥: ```sh POSTGRES_PASSWORD= NEO4J_PASSWORD= MINIO_ACCESS_KEY= MINIO_SECRET_KEY= JWT_SECRET_KEY= YUXI_INSTANCE_ID= SANDBOX_PROVISIONER_TOKEN= SILICONFLOW_API_KEY= ``` 生产 Compose 会在前七项配置缺失或为空时拒绝启动,并提示具体变量名。`JWT_SECRET_KEY` 和 `SANDBOX_PROVISIONER_TOKEN` 均应至少使用 32 字节随机值并持久保存,可分别使用 `openssl rand -hex 32` 生成;两者不能复用。`YUXI_INSTANCE_ID` 应是每套部署稳定且唯一的实例标识。模型 API 密钥按实际使用的供应商配置。 ### 2. 启动服务 使用生产环境配置文件启动: ```bash # 仅启动核心服务(CPU 模式) docker compose -f docker-compose.prod.yml up -d --build # 启动所有服务(包含 GPU OCR) docker compose -f docker-compose.prod.yml --profile all up -d --build ``` ### 3. 验证部署 - Web 访问:http://localhost(直接通过 80 端口) - API 健康检查:`curl http://localhost/api/system/health` 公开头像和 Agent 图片通过前端同源路径 `/minio/public/...` 读取,由 Nginx 只读代理到 MinIO 的 `public` bucket。无需也不应向公网开放 MinIO 的 `9000` 对象 API 或 `9001` 管理控制台;知识库等私有 bucket 不经过这个代理。需要使用独立静态资源域名时,可在 `.env.prod` 中设置 `MINIO_PUBLIC_URL=https://assets.example.com`,并在该域名侧保持同等的只读 bucket 限制。 历史 PDF 解析 Markdown 中已经写入的 `http://localhost:9000/public/...` 或其他 `:9000/public/...` 图片地址,会在前端渲染时自动转换为同源路径,不需要重新解析 PDF。 ## 跨域(CORS)配置 `docker-compose.prod.yml` 默认把 `YUXI_ENV` 设为 `production`,后端在该环境下会按 `YUXI_CORS_ORIGINS` 显式声明允许的来源。**未配置时返回空列表,浏览器跨域请求会被拒绝**。生产部署前请根据前端与 API 的相对位置选择策略: | 部署形态 | 推荐配置 | |----------|----------| | 前端与 API 同源(Nginx 同端口反代) | 不需要设置,留空即可 | | 前端与 API 跨域部署 | `YUXI_CORS_ORIGINS=https://your-frontend.example.com` | | 多个前端域名 | 逗号分隔,如 `https://a.example.com,https://b.example.com` | | 完全放开(不推荐) | `YUXI_CORS_ORIGINS=*`,会自动关闭 credentials,登录态/JWT 无法跨域携带 | 开发环境(`YUXI_ENV=development` 且未设置该变量)默认允许 `http://localhost:5173` 与 `http://127.0.0.1:5173`,方便本地前后端独立启动调试。从 0.7.0 升级到 0.7.1 时,如果此前是跨域部署但未显式声明来源,必须补上 `YUXI_CORS_ORIGINS`,否则前端跨域请求会被拒绝。 ## 维护与更新 ### 从使用默认凭据的版本升级 如果部署曾使用仓库历史默认的 PostgreSQL、Neo4j 或 MinIO 凭据,升级 Compose 文件本身不会保证已有数据卷中的服务凭据已经改变。升级前应分别通过对应服务的管理命令真实修改凭据,再把新值写入 `.env.prod`;完成后重建相关服务,并使用旧凭据验证登录已被拒绝。 PostgreSQL 可以在数据库容器内使用交互式命令修改,避免新密码出现在 shell 历史和进程参数中: ```bash docker compose -f docker-compose.prod.yml exec postgres psql -U postgres -d yuxi -c '\password postgres' ``` Neo4j 应使用 `cypher-shell` 的当前用户密码修改流程;MinIO 应使用 `mc admin` 或部署所采用的密钥管理流程。不要把真实密码写入文档、测试脚本或命令历史。完成凭据轮换并配置 `SANDBOX_PROVISIONER_TOKEN` 后,再执行下面的重建命令。 ### 更新代码 ```bash # 拉取最新代码 git pull # 重新构建并启动 docker compose -f docker-compose.prod.yml up -d --build ``` 生产 Compose 不再向宿主机发布 PostgreSQL 和文档解析服务端口。确需从宿主机维护时,优先使用 `docker compose exec`;不要为了临时调试把这些端口重新暴露到公网。 ### 查看日志 ```bash # API 日志 docker logs -f api-prod # Nginx 访问日志 docker logs -f web-prod ``` --- ### Advanced/Document Processing # 文档处理与 OCR Yuxi 将上传文件先保存为原文件,再解析为 Markdown 并按知识库分块策略入库。管理员可在“设置 → OCR 配置”选择默认 OCR 方法,并维护确有需要的服务地址和云端凭证;知识库上传或临时附件解析仍可逐次选择方法,未显式选择时使用系统默认项。 ## 支持的文件类型 ### 常规文档 | 类型 | 格式 | 说明 | |------|------|------| | 文本 | .txt, .md, .html, .htm | 直接提取内容 | | Word | .docx | 保留格式和结构 | | PowerPoint | .pptx | 保留主要文本结构 | | PDF | .pdf | 支持文本和图片 PDF | | 表格 | .csv, .xls, .xlsx | 识别表格结构 | | JSON | .json | 结构化数据 | ### 图片文件 图片文件必须选择 OCR 引擎才能提取文字: - .jpg, .jpeg, .png, .bmp, .tiff, .tif ### 压缩包 支持上传 ZIP 压缩包,系统会: - 自动提取并处理其中的 Markdown 文件 - 处理图片并上传到对象存储 - 智能识别 `full.md` 或第一个 `.md` 文件 ### 网页内容 知识库支持先从 URL 抓取页面内容,再作为文件进入现有上传、解析与入库链路: 1. 配置 `YUXI_URL_WHITELIST` 环境变量启用白名单机制 2. 系统自动将 HTML 转换为 Markdown 3. 内置去重机制,避免重复抓取 ::: tip URL 白名单配置 示例:`YUXI_URL_WHITELIST=github.com,*.wikipedia.org,docs.python.org` ::: ## OCR 方案选择 系统提供多种 OCR 方案,适用于不同场景: ### 方案对比 | 方案 | 适用场景 | 硬件要求 | 特点 | |------|----------|----------|------| | RapidOCR | 基础文字识别 | CPU | 免费开源,速度快 | | MinerU | 复杂 PDF、表格 | GPU | 精度高,版面分析好 | | MinerU Official | 复杂文档 | 无 | 官方云服务,开箱即用 | | PP-Structure-V3 | 表格、票据 | GPU | 专业版面解析 | | DeepSeek OCR | 智能理解 | 无 | 复用 SiliconFlow 模型供应商,Markdown 输出 | | PaddleOCR-VL-1.6 | 复杂文档、表格、图片 PDF | 无 | 百度 AI Studio 云端服务,输出 Markdown | | PP-OCRv6 | 基础文字识别 | 无 | 百度 AI Studio 云端 OCR,输出纯文本 | 后端保存的引擎标识与界面名称对应如下:`rapid_ocr`、`mineru_ocr`、`mineru_official`、`pp_structure_v3_ocr`、`deepseek_ocr`、`paddleocr_vl_1_6`、`paddleocr_pp_ocrv6`。 ### 选择建议 - **个人使用或 CPU 环境**:选择 RapidOCR,免费且资源占用低 - **高精度需求**:选择 MinerU(需要 GPU)或 MinerU Official - **表格密集型文档**:选择 PP-Structure-V3 - **云端版面解析**:选择 PaddleOCR-VL-1.6,适合希望输出 Markdown 的 PDF 或图片文档 - **云端纯文字识别**:选择 PP-OCRv6,适合只需要提取图片文字的场景 - **简单云服务**:选择 DeepSeek OCR 或 PaddleOCR API ## 快速配置 管理员打开“设置 → OCR 配置”后,可以: - 选择全局默认 OCR 方法 - 配置 MinerU、PP-Structure 等自托管服务端点 - 每个服务使用独立卡片;非编辑状态以禁用输入框展示当前数据库值或环境变量来源,敏感字段只显示脱敏预览 - 点击卡片右上角“编辑”后修改配置;取消不会修改,留空保存会清除数据库值并改为读取环境变量 DeepSeek OCR 固定复用 `siliconflow-cn` 模型供应商的 API 密钥与 Base URL,不显示独立配置表单。其他服务配置保存在通用 `config_options` 表中:每次运行时读取都会查询数据库,数据库非空值优先,字段为空时读取对应环境变量。API Key / Token 允许明文写入数据库,这是明确的部署取舍;需要脱敏的字段由定义中的 `sensitive` 元数据显式标记。读取接口不会回显密钥原文:数据库值只返回真实首尾字符的脱敏预览,环境变量只返回配置来源,前端不会获得环境变量内容。 附件添加和知识库解析统一使用 OCR Selector。Selector 每次展开都会刷新全部 OCR 方法的健康状态;可用方法直接显示标题和状态,不可用方法默认折叠,管理员可通过右上角“去配置”直接打开 OCR 配置页。后端系统配置仍允许将默认值设为 `disable`,但 Selector 默认不展示该选项;需要显示时必须由调用方显式开启。 ### RapidOCR 启动后会默认下载,无需配置 ### MinerU(高精度) 项目已内置 mineru-api 服务(位于 docker-compose.yml,属于 all profile),无需额外下载官方 compose 文件。首次构建镜像时会基于 docker/mineru.Dockerfile 下载模型,该过程耗时较长。 启动服务(需要 GPU): ```bash docker compose --profile all up -d --build mineru-api ``` 该服务在 `30001` 端口提供 `/file_parse` 接口,后端 `api` / `worker` 默认通过 `MINERU_API_URI=http://mineru-api:30001` 连接,通常无需额外配置。 ::: tip 显存不足 若显存有限导致启动失败,可在 `docker-compose.yml` 的 `mineru-api` 服务下放开 `--gpu-memory-utilization` 参数(如 `0.5`,必要时进一步降低)。 ::: ### MinerU Official(云服务) 从 [MinerU 官网](https://mineru.net) 获取 API 密钥,在 .env 配置环境变量 ```env MINERU_API_KEY=your-api-key-here ``` ### PP-Structure-V3(结构化) 启动服务(需要 GPU) ```bash docker compose up paddlex -d ``` ### DeepSeek OCR(简单云服务) 在模型供应商配置中启用 `siliconflow-cn` 并配置 API 密钥。DeepSeek OCR 会复用该供应商的 Base URL 和凭证,不需要额外配置。 ### PaddleOCR API(百度 AI Studio 云服务) PaddleOCR API 使用百度 AI Studio 的 Access Token。获取方式: 1. 登录 [百度 AI Studio Access Token 页面](https://aistudio.baidu.com/account/accessToken) 2. 在页面中复制 Access Token 3. 在 `.env` 中配置为 `PADDLEOCR_API_TOKEN` ```env PADDLEOCR_API_TOKEN=your-access-token-here ``` 如需使用自定义 PaddleOCR API 地址,可额外配置: ```env PADDLEOCR_API_URL=https://paddleocr.aistudio-app.com/api/v2/ocr/jobs ``` 配置完成后,重启后端服务,在上传文件或解析临时附件时可以选择: - `PaddleOCR-VL-1.6`:对应 `paddleocr_vl_1_6`,用于文档版面解析,返回 Markdown - `PP-OCRv6`:对应 `paddleocr_pp_ocrv6`,用于基础 OCR,返回按行拼接的纯文本 ## 解析参数与分块快照 知识库分块配置由两部分组成:`chunk_preset_id` 只表示策略(`general`、`qa`、`book`、`laws`、`semantic`、`separator`),具体参数统一放在 `chunk_parser_config` 中。不要再写入旧的根级 `chunk_size`、`chunk_overlap` 或 `qa_separator` 字段。 文件级 `processing_params` 保存 `ocr_engine`、分块策略和 `chunk_parser_config`。OCR 的连接端点和凭证在执行时从通用配置或环境变量读取,不写入文件快照。 ## 图片显示配置 上传文档中的图片需要正确配置才能在外部显示: 在 `.env` 中设置服务器 IP: ``` HOST_IP=your_server_ip ``` ## 注意事项 1. **图片文件必须启用 OCR**:否则无法提取内容 2. **GPU 要求**:MinerU 和 PP-Structure-V3 需要 GPU 支持 3. **API 密钥**:DeepSeek OCR 复用 `siliconflow-cn` 模型供应商凭证;MinerU Official 和 PaddleOCR API 需要各自的 API 密钥或 Access Token 4. **超时处理**:复杂文档解析可能耗时较长,可通过 `MINERU_TIMEOUT` 环境变量调整超时时间 5. **文件大小限制**:知识库与工作区的单个上传文件大小均不超过 100 MB;工作区一次最多上传 50 个文件 6. **解析配置**:文件只保存当次 `ocr_engine` 与分块参数快照;端点和凭证执行时使用最新通用配置或环境变量 7. **Agent 读取非文本文件**:Agent 的 `read_file` 只直接读取 UTF-8 文本和图片;遇到 PDF、Office 或其他二进制文件时,应使用 `ocr_parse_file` 生成 Markdown 后再读取 --- ### Advanced/Langfuse Integration # Langfuse 集成 ## 为什么 Yuxi 需要 Langfuse Langfuse 是一套面向大模型应用的可观测性平台,适合用来观察一次智能体执行过程中到底发生了什么。在 Yuxi 里,一轮用户消息通常不会只对应一次简单的模型调用,它往往会伴随 LangGraph 图执行、工具调用、知识库检索以及多轮中间状态切换。仅靠普通后端日志,虽然也能定位问题,但往往需要在多个文件和多个服务日志之间来回跳转,阅读成本高,而且很难从用户、线程和智能体三个维度统一查看。Langfuse 的价值就在于,它把这些原本分散的执行细节收拢到同一条 trace 里,让你能够从一次对话出发,回看模型输入输出、工具链路、耗时和错误位置。 在 Yuxi 当前的实现中,Langfuse 主要承担的是智能体执行观测层,而不是业务主流程的一部分。换句话说,它不会替代模型服务,也不会替代聊天接口本身,而是帮助你在智能体已经能够工作的前提下,看清楚它是如何工作的。对于调试复杂 Agent、排查工具调用失败、评估多轮会话质量以及分析不同智能体的耗时与成本来说,这类观测能力非常关键。尤其是在一个线程里连续发生多轮交互时,Langfuse 可以帮助你把“这轮请求是谁发起的、落在哪个 thread、触发了哪个 agent、调用了哪些模型和工具”这些信息统一串起来。 ## 在 Yuxi 中能做什么 Yuxi 对 Langfuse 的映射方式比较直接。一个 Yuxi 用户会映射为 Langfuse 中的 `user_id`,一个对话线程会映射为 `session_id`,而每次用户输入触发的一轮智能体执行会形成一条独立的 trace。这样做的好处是,既能按单轮请求排查问题,也能在同一个线程维度下连续查看多轮会话。对于需要长期分析使用质量、成本和延迟的场景,这种映射方式能够兼顾可读性和后续统计需求。 当 Langfuse 与 Yuxi 连通之后,它最直接的作用是帮助你看清一轮智能体请求内部发生了哪些步骤。你可以看到这一轮调用关联的是哪个用户、哪个线程、哪个智能体,也可以进一步观察模型调用、工具调用和整体耗时表现。对于日常调试来说,它让“问题到底出在模型、工具、配置还是图流程”这件事变得更容易判断。对于长期运行的系统来说,它也为后续做延迟分析、成本分析和用户反馈分析提供了统一的观察入口。 用户在对话界面对助手消息提交点赞或点踩后,Yuxi 会继续把反馈保存在本地业务表中;如果该助手消息已经关联 Langfuse trace,则会同步写入 Langfuse score。同步到 Langfuse 的 score 名称为 `user-feedback`,点赞值为 `1`,点踩值为 `0`,点踩原因会作为 score comment 保存,便于在 Langfuse 中按低分反馈筛选和分析具体 trace。 ## 如何配置 如果你准备启用 Langfuse,首先需要在 Langfuse Cloud 中创建项目并获取访问凭证。当前版本推荐优先使用云端模式,因为接入成本最低,也更适合先把 tracing 跑通。你需要在运行 Yuxi 的环境中配置 `LANGFUSE_PUBLIC_KEY`、`LANGFUSE_SECRET_KEY` 和 `LANGFUSE_BASE_URL`。其中前两个字段用于鉴权,`LANGFUSE_BASE_URL` 用于指定 Langfuse 服务地址;如果你使用官方云服务,通常可以直接填写 `https://cloud.langfuse.com`。在大多数部署场景下,只要把这些变量写入 `.env` 并通过 Docker Compose 传给 `api` 服务即可生效。 从当前实现来看,Langfuse 只有在 key 配置完整时才会被启用。如果没有配置 `LANGFUSE_PUBLIC_KEY` 或 `LANGFUSE_SECRET_KEY`,Yuxi 会自动退化为“不启用 tracing”的状态,正常聊天功能不会因此中断。这意味着 Langfuse 是一个可选增强项,而不是系统启动的前置依赖。对于希望先验证主流程、后续再逐步补全观测能力的部署者来说,这种行为比较友好,因为它降低了接入门槛,也减少了配置错误对主业务的影响。 ## 配置后系统会如何工作 理解 Langfuse 的另一个关键点在于,它并不等于“所有调试信息都会立即显示在界面里”。当前 Yuxi 的设计重点是先把 trace id 与执行上下文稳定关联起来,再把这些信息作为后续调试和分析的基础。也正因为如此,系统会优先保证聊天主链路的稳定性,而不是为了获取额外的可点击 URL 去同步等待 Langfuse 的远程接口。换句话说,Langfuse 在 Yuxi 里首先是观测数据的来源,其次才是一个方便跳转查看的外部页面入口。 这也意味着,Langfuse 接入的目标并不是改变用户聊天体验,而是在不破坏主流程稳定性的前提下,为系统补上一层可观测性。只要配置正确,用户的对话仍然按照原有方式执行,只是在后台额外留下可追踪的执行记录。对于运维和开发来说,这类“尽量不影响主流程”的接入方式更适合在现有系统中逐步落地。 ## 如何查看是否生效 启用完成后,你最常见的查看方式是在 Langfuse 控制台中按项目查看 traces。进入项目后,可以按照用户、线程、Agent 或时间范围来筛选,定位到某一轮具体的请求。打开单条 trace 之后,你通常可以看到这一轮智能体执行的整体耗时、模型调用、工具调用以及相关 metadata。对于排查问题来说,这比直接翻阅后端日志更高效,因为你不需要自己手动拼装上下文。对于性能分析来说,也可以更直观地看出某个智能体是否在某类请求上耗时异常,或者某个工具是否经常成为慢点。 如果你是系统管理员或开发者,希望快速确认 Yuxi 是否已经成功把 tracing 打到 Langfuse,最简单的方法不是先看代码,而是先发起一条真实对话,再到 Langfuse 控制台中按最近时间排序查看是否出现新的 trace。如果配置正确,你应该可以看到对应线程下新增的一轮执行记录;如果没有看到,则优先检查 `.env` 中的三个关键变量是否正确传入 `api` 容器,以及容器内依赖是否已经包含 Langfuse SDK。对于基于 Docker Compose 的开发环境,这一步尤其重要,因为仅修改 `pyproject.toml` 并不会自动把新依赖装进已经运行中的镜像,通常还需要重新构建或更新容器。 ## 当前建议的接入方式 目前 Yuxi 推荐的接入顺序是先完成 tracing,再使用反馈 score 分析用户满意度,后续再扩展到更完整的运营面板。这样做的原因很简单:只有在 trace 关联已经稳定、用户和线程维度映射已经一致的前提下,后续的评分、质量分析和使用统计才会真正可靠。也正因如此,当前文档重点介绍的是 Langfuse 的定位、接入方式和查看路径,而不是一次性覆盖所有更复杂的高级功能。对于大多数项目来说,先把“能看清每轮智能体执行发生了什么”这件事做好,已经能显著改善调试和运维体验。 --- ### Advanced/Misc # 其他配置 本文档介绍 Yuxi 的其他配置选项,包括内容安全、网页搜索和服务端口等。 ## 内容安全 系统内置内容审查机制,帮助保障服务内容的合规性。 ### 启用方式 在「系统设置」→「基本设置」页面中配置,可选择启用关键词过滤和 LLM 内容审查。 ### 检测流程 系统会在以下时机进行检测: 1. **用户输入检测**:接收到用户消息后立即检测 2. **流式输出检测**:实时检测输出的关键词(仅关键词模式) 3. **输出完成检测**:流式输出结束后进行全面检测 ### 检测模式 **关键词检测** 敏感词库位于 `backend/package/yuxi/config/static/bad_keywords.txt`,每行一个关键词。修改后实时生效,无需重启服务。 **LLM 检测** 使用大模型对内容进行审查,可以更好地识别提示词注入等复杂问题,但会增加响应延迟。 ::: warning 性能考虑 LLM 检测会增加用户交互的延迟,请根据实际需求选择是否启用。 ::: ## 网页搜索 系统集成了 Tavily 联网搜索能力,让大模型能够获取实时网页信息。 ### 配置步骤 1. 访问 [Tavily 官网](https://app.tavily.com/) 注册并创建 API Key 2. 在 `.env` 文件中添加: ```env TAVILY_API_KEY=sk-xxxxxxxxxxxxxxxx ``` 3. 重启服务: ```bash docker compose up -d api-dev web-dev ``` ### 使用方式 配置完成后,在智能体的工具配置区域会看到 Tavily 搜索工具。模型会自动判断何时需要调用搜索来获取最新信息。 如需关闭,删除或清空 `TAVILY_API_KEY` 后重启服务即可。 ## 服务端口 系统各服务通过以下端口提供访问: | 端口 | 服务 | 说明 | |------|------|------| | 5173 | Web 前端 | 用户界面 | | 5050 | API 后端 | 核心服务接口 | | 7474 | Neo4j HTTP | 图数据库管理界面 | | 7687 | Neo4j Bolt | 图数据库连接 | | 9000/9001 | MinIO | 对象存储 | | 19530/9091 | Milvus | 向量数据库 | | 5432 | PostgreSQL | 业务数据库 | ### 可选服务端口 | 端口 | 服务 | 说明 | |------|------|------| | 30000 | MinerU | PDF 解析服务 | | 8080 | PP-Structure-V3 | OCR 服务 | | 8081 | vLLM | 本地推理服务 | ### 快速访问 - Web 界面:http://localhost:5173 - API 文档:http://localhost:5050/docs - Neo4j 管理:http://localhost:7474 --- ### Advanced/Third Party Auth # 第三方登录认证 Yuxi 支持以OIDC接入第三方登录认证,方便企业用户集成现有的身份认证系统。 > 此功能默认关闭,需要在配置文件中启用并提供相关参数。 ## 配置步骤 ### 1. 前提条件 在你的SSO系统中注册一个新的客户端应用,获取以下信息: - 客户端ID(Client ID) - 客户端密钥(Client Secret) - ISSUER URL 填入回调地址(Redirect URI):https:///api/auth/oidc/callback ### 2. 配置Yuxi 在Yuxi的.env文件中添加以下配置项: ``` /* Detailed source-code truncated for AI context efficiency. */ ``` ### 3. 重启Yuxi服务使配置生效 ```bash docker restart api-dev web-dev ``` ## 功能说明 ### 使用原始用户名(OIDC_USE_RAW_USERNAME=true) 当你需要将 Yuxi 系统中已有的本地账号与 OIDC SSO 绑定,可以开启此选项。 **绑定原理**(无需修改数据库): 系统会创建一个标记为删除的占位用户 `oidc:{sub}:{target_user_id}` 来记录 OIDC sub 与 Yuxi 用户的绑定关系,确保只有绑定过的 OIDC 身份才能登录对应的账号,**防止账号冒用**。其中 `target_user_id` 是数据库中的数值 `users.id`;用户登录标识仍使用字符串 `uid`。 ### 自动获取部门信息(OIDC_FETCH_DEPARTMENT_INFO=true) 开启后,系统会从 OIDC userinfo 中读取部门名称和描述,自动在 Yuxi 中创建部门并将用户关联到该部门。 - 对从 OIDC 获取的部门名称会自动做 `strip()` 去空格,并截断到 50 字符 - 部门描述会自动截断到 255 字符 - 如果部门名称处理后为空,会回退到使用 `OIDC_DEFAULT_DEPARTMENT` 默认部门 --- ### Intro/Cli # 命令行工具 `yuxi-cli` 是 Yuxi 的命令行客户端,适合在本地脚本或终端中管理远程实例、登录账号、上传知识库文件,以及运行部分智能体任务。 ## 安装 推荐使用 `uv` 或 `pipx` 安装: ```bash uv tool install yuxi-cli ``` 也可以临时运行: ```bash uvx --from yuxi-cli yuxi --help ``` 安装后可通过 `yuxi --version` 查看当前版本。 ## 配置远程实例 先添加一个 Yuxi 实例地址,再设为当前默认 remote: ```bash yuxi remote add local http://localhost:5173 yuxi remote use local yuxi remote ping ``` 配置会保存在 `~/.yuxi/config.toml`。如果需要同时管理多个实例,可以继续添加其他 remote,并通过 `yuxi remote use ` 切换。 ## 登录 默认使用浏览器授权登录: ```bash yuxi login --browser ``` 如果已经在 Yuxi 中创建了 API Key,也可以直接导入: ```bash yuxi login --api-key yxkey_xxx ``` 常用账号状态命令: ```bash yuxi whoami yuxi status yuxi logout ``` ## 本地网页聊天 运行下面的命令会在 `127.0.0.1` 的随机端口启动一个临时网页,并自动在浏览器中打开: ```bash yuxi chat ``` 该网页通过 CLI 代理当前 remote 的 Agent API,使用已经保存在本地的 API Key,并流式显示回答。API Key 不会发送到浏览器。关闭终端中的进程后,本地网页服务随即停止。 默认调用 `default-chatbot`,也可以指定其他智能体或 remote: ```bash yuxi chat --agent-slug my-agent yuxi chat --remote production ``` 在不能自动打开浏览器的环境中,可使用 `yuxi chat --no-open`,然后手动访问终端打印的本地地址。当前页面定位为基础调试工具,支持纯文本对话、新建会话、`/state` 查看线程状态和 `/approve` 继续工具审批;附件与 `ask_user_question` 仍需使用正式 Web 界面。 ## 上传知识库文件 上传目录时,如果不指定 `--kb-id`,CLI 会拉取当前 remote 中可用的知识库并在终端中选择: ```bash yuxi kb upload ./docs ``` 默认会选择常见文本和 Office 文档类型,可在预览阶段调整文件类型;也可以通过参数直接指定: ```bash yuxi kb upload ./docs --kb-id kb_xxx --concurrency 4 yuxi kb upload ./docs --include-ext md,html,docx ``` CLI 会让每个并发单元完成单个文件的上传和文档记录添加,`--concurrency` 默认 10,允许范围 1-300,用于控制同时处理的文件数。上传会保留目录中的相对路径,便于在知识库文件列表中按原目录结构查看。 ## 运行智能体评估 如果实例已配置 Langfuse 数据集,可以用 CLI 触发智能体评估: ```bash yuxi agent eval \ --dataset-name demo-dataset \ --agent-slug default-agent \ --experiment-name cli-demo ``` 该命令会读取 Langfuse 数据集输入,调用 Yuxi 智能体运行,并把结果回传到对应实验中。 --- ### Intro/Evaluation # 知识库评估指南 知识库评估是 RAG 系统开发中的重要环节。通过量化评估,我们可以了解检索和生成的质量,发现问题并持续优化。 ## 为什么需要评估 在构建知识库系统时,你可能会遇到这些问题: - 检索结果不准确,用户找不到想要的内容 - 生成答案与文档不符,存在幻觉 - 调整了分块策略或模型,效果是变好还是变差了? 评估功能就是为了回答这些问题。它通过预设的测试问题和标准答案,量化系统的表现,帮助你做出数据驱动的优化决策。 ## 评估指标解读 系统提供以下核心指标: | 指标 | 含义 | 参考值 | |------|------|--------| | Recall@1 | 第一个检索结果包含正确文档的比例 | > 0.6 为佳 | | Recall@5 | 前5个检索结果包含正确文档的比例 | > 0.8 为佳 | | F1@K | 精确率和召回率的调和平均 | 用于横向对比 | | 答案准确性 | 生成答案与标准答案的一致性 | 越高越好 | ## 创建评估基准 ### 手动准备数据 准备 JSONL 格式的评估文件,每行一个样本: ```json {"query": "什么是人工智能?", "gold_chunk_ids": ["chunk_001"], "gold_answer": "人工智能是..."} {"query": "机器学习有哪些类型?", "gold_chunk_ids": ["chunk_005"], "gold_answer": "主要包括监督学习..."} ``` 字段说明: - `query`:测试问题,必需 - `gold_chunk_ids`:期望被检索到的文档块 ID,可选 - `gold_answer`:标准答案,用于评估生成质量,可选 JSONL 只是导入和导出的交换格式。导入后,系统会把评估数据集、题目、评估运行和逐题结果保存到数据库中,不依赖本地 JSONL 文件作为内部存储。 ::: tip 推荐工具 可以使用 [EasyDataset](https://github.com/ConardLi/easy-dataset) 从文档批量生成问答对。注意导出时将字段名改为 `query` 和 `gold_answer`。 ::: ### 自动生成 系统也支持自动生成评估数据:随机采样知识库中的文档块,用嵌入模型查找相似内容,最后用大模型生成问答对。 推荐参数: - 问题数量:10-50 个 - 相似文档数:2-5 个 - 构建并发数:默认 10,最大 20;模型服务限流较严格时可调低 ## 运行评估 在知识库详情页左侧边栏,「评估基准」Tab 用于管理评估数据集,「RAG 评估」Tab 用于运行评估并查看结果。在「RAG 评估」中填写评估名称、选择评估数据集后配置: 1. **答案生成模型**(可选):基于检索到的文档块生成答案 2. **评判模型**(可选):评估生成答案与标准答案的一致性 点击「开始评估」,系统在后台执行,完成后会显示各项指标结果。 ## 评估结果分析 拿到评估结果后,可以从以下几个角度分析: - **Recall@1 低**:说明最相关的内容没有被首先检索到,可能需要调整嵌入模型或分块策略 - **Recall@5 低**:说明相关文档没有被检索到,可能需要增加检索数量或优化查询 - **答案准确性低**:说明生成质量有问题,可能需要调整提示词或更换模型 ## 使用场景 - **上线前验证**:知识库建设完成后,评估效果是否满足要求 - **配置对比**:调整分块策略、嵌入模型后,对比评估结果 - **定期监控**:定期评估,及时发现质量下降 - **参数调优**:通过多次评估找到最优参数组合 --- 评估是一个持续的过程。建议在初始建设时就建立评估基准,后续每次重大变更都进行评估,形成数据驱动的优化闭环。 --- ### Intro/Knowledge Base # 知识库与知识图谱 Yuxi 提供文档知识库、向量检索、知识导图和知识图谱构建能力。当前支持 Milvus 知识库、Milvus 知识库内的图谱构建/展示/检索,以及 Dify Dataset、Notion Data Source 只读检索。 ## 为什么需要知识库 在大模型应用场景中,仅依靠模型的内部知识往往不够准确和全面。通过构建知识库,我们可以: - **注入私有知识**:让模型能够回答基于私有文档的问题 - **降低幻觉**:回答内容可追溯到原始文档 - **知识复用**:一次上传,多轮对话中重复使用 ## 知识库类型 | 类型 | 特点 | 适用场景 | |------|------|----------| | **Milvus** | 高性能向量检索,支持文档入库、检索测试、知识导图、评估和知识图谱构建 | 自建文档知识库与生产检索 | | **Dify** | 连接 Dify Dataset 检索 API,只读连接器 | 复用已有 Dify 数据集 | | **Notion** | 连接 Notion Data Source 检索 API,只读连接器 | 复用已有 Notion 页面内容 | 只读连接器(Dify、Notion)仅用于检索,不支持上传与入库;历史 LightRAG 类型不再作为受支持类型展示或创建。 ## 创建知识库 访问 Web 界面的「知识库」页面,点击「新建知识库」: 1. 填写知识库名称和描述 2. 选择知识库类型(Milvus、Dify 或 Notion) 3. Milvus 配置嵌入模型和分块策略;只读连接器(Dify、Notion)按类型动态渲染连接参数(如 API URL、Token、Dataset ID 等) 4. 配置访问权限 5. 保存 ::: tip 提示 知识库的名称和描述会被智能体用来判断何时应该使用这个知识库进行检索,所以请尽量详细地描述。 ::: ## 文件处理流程 Milvus 文件从上传到可检索,经历三个阶段: ### 1. 上传阶段 将本地文件上传到服务器。文件保持原始格式存储。 ### 2. 解析阶段 系统将文件转换为 Markdown 格式: - 提取文本内容 - 图片上传到 MinIO,并在 Markdown 中用 URL 引用 - 表格、公式等尽量保持结构化 ### 3. 入库阶段 系统对 Markdown 内容进行分块,将 chunk 内容与元数据双写到 PostgreSQL 的 `knowledge_chunks` 表,并将向量写入 Milvus。 上传完成后可以分别执行解析和入库,也可以选择自动入库。文件管理支持目录懒加载、服务端分页以及按待处理状态批量提交解析或入库任务;单次显式选择最多 1000 个文件。 ### 分块配置 分块策略由 `chunk_preset_id` 选择(`general`、`qa`、`book`、`laws`、`semantic`、`separator`),通用分块参数放在 `chunk_parser_config` 中。文件记录会保存解析和分块参数快照;旧的根级 `chunk_size`、`chunk_overlap`、`qa_separator` 不再作为兼容字段。 ## 知识导图与示例问题 Milvus 知识库详情页提供「知识导图」Tab,用来把知识库文件列表整理成层次化结构。生成时,后端会读取当前知识库的文件元数据,把文件名和类型交给默认模型生成 JSON 树,并保存到知识库的 `mindmap` 字段。文件数量较多时,当前实现最多取前 20 个文件参与生成,避免一次提示词过长。Agent 运行时也可以通过 `get_mindmap` 工具读取这份结构,用来快速判断知识库大致包含哪些资料。 导图支持增量更新:详情页的「增量更新」按钮会先调用 `GET /api/knowledge/databases/{kb_id}/mindmap/diff` 检测当前文件列表与已追踪文件之间的变化,再通过 `POST /api/knowledge/databases/{kb_id}/mindmap/generate?incremental=true` 仅处理新增/删除的文件。纯删除场景不需要 AI 调用,直接对现有树做递归手术;新增文件时由 AI 整合进现有分类结构。单文件删除与批量删除接口成功后也会同步移除导图快照中对应的叶子节点,不需要再手动触发增量更新。 知识库还支持生成示例问题。该能力会基于文件列表生成适合检索测试的问题,并保存到 `sample_questions` 字段;前端检索测试区域会优先使用这些问题作为查询示例。示例问题只依赖文件元数据,不等同于对每个文档全文做总结;如果要在对话中围绕具体内容回答,仍应通过 `query_kb`、`find_kb_document` 和 `open_kb_document` 检索原始 chunk 或文档片段。 ## 知识库权限控制 每个知识库可以配置独立的访问权限: - **全局共享**:所有用户可访问 - **部门共享**:指定部门可访问,且必须包含当前用户所在部门 - **指定人**:仅创建者、管理员及被明确授权的人员可访问 权限规则: - 超级管理员可访问所有知识库 - 管理员可访问共享知识库和本部门的知识库 - 普通用户只能访问已授权的知识库 ## 知识图谱 Milvus 知识库详情页提供「知识图谱」Tab。图谱构建流程会从已入库 chunks 中抽取实体和关系,将 entity/triple 本体与 chunk 引用写入 Neo4j 和 PostgreSQL,并为唯一实体/三元组建立 Milvus 语义索引;检索时可召回图谱实体与三元组,并与 chunk 命中结果融合(RRF)。 主要能力: - 配置 LLM 抽取器,更多抽取方式拓展中 - 构建待索引 chunks 的图谱实体与关系 - 查看构建状态、标签和统计信息 - 在知识库详情页搜索和展示子图 - 重置图谱配置与已构建数据 Neo4j 仍作为 Milvus 图谱存储服务保留,但不再提供独立 `/graph` 前端页面,也不再支持上传 JSONL 三元组到默认全局图谱。 ### Neo4j 配置 Neo4j 连接信息可以在 `.env` 中配置: - 默认账户:`neo4j` - 默认密码:`0123456789` - 管理界面:http://localhost:7474 - 连接地址:bolt://localhost:7687 ## API 使用 程序化上传应先将文件上传到 MinIO,再创建文档记录;CLI 可使用 `yuxi kb upload` 完成这条链路。接口如下: ```bash # 1. 上传文件 POST /api/knowledge/files/upload?kb_id=<知识库ID> # 返回 file_path 和 content_hash # 2. 创建文件记录,不触发解析或入库 POST /api/knowledge/databases/{kb_id}/documents/add # 3. 按需提交解析和入库 POST /api/knowledge/databases/{kb_id}/documents ``` 系统会自动去重:基于内容哈希判断是否已存在相同文件。 --- ### Intro/Model Config # 模型配置 ## 概述 系统统一通过 **智能体管理 → 模型供应商** 页面管理所有模型(对话模型、嵌入模型、重排模型),无需修改配置文件。 ## 配置路径 ``` 智能体管理 → 模型供应商 ``` 模型供应商页签仅管理员可见。如果当前账号不是管理员,只能看到普通的智能体管理和个人设置入口。 ## API 凭证配置 支持两种凭证配置方式: | 方式 | 适用场景 | |------|----------| | 环境变量 | 生产环境或不愿在界面暴露 Key 的场景 | | 直接填写 | 开发调试,追求配置便利性 | **环境变量方式**:在供应商配置中填写变量名(如 `SILICONFLOW_API_KEY`),确保运行时环境已配置对应变量。 **直接填写方式**:在供应商配置中直接填入 API Key。 ## 供应商管理 ### 内置供应商模板 系统启动时会同步一组内置 provider 模板。模板只提供 Provider ID、Base URL、凭证环境变量和远端模型发现地址;实际是否可用仍取决于你是否配置凭证、启用供应商并添加模型。 | 供应商 | Provider ID | 支持类型 | 凭证环境变量 | |--------|-------------|----------|--------------| | OpenAI | `openai` | chat | `OPENAI_API_KEY` | | DeepSeek | `deepseek` | chat | `DEEPSEEK_API_KEY` | | DashScope | `alibaba` | chat, embedding, rerank | `DASHSCOPE_API_KEY` | | Aliyun Coding Plan | `alibaba-coding-plan-cn` | chat | `DASHSCOPE_API_KEY` | | Aliyun Coding Plan International | `alibaba-coding-plan` | chat | `DASHSCOPE_API_KEY` | | Zhipu BigModel | `zhipuai` | chat | `ZHIPUAI_API_KEY` | | Zhipu BigModel Coding Plan | `zhipuai-coding-plan` | chat | `ZHIPUAI_API_KEY` | | Z.AI | `zai` | chat | `ZAI_API_KEY` | | Z.AI Coding Plan | `zai-coding-plan` | chat | `ZAI_API_KEY` | | XiaomiMiMo Token Plan | `xiaomi-token-plan-cn` | chat | `XIAOMI_MIMO_TOKEN_PLAN_API_KEY` | | XiaomiMiMo | `xiaomi` | chat | `XIAOMI_MIMO_API_KEY` | | Kimi Code | `kimi-for-coding` | chat | `KIMI_CODE_API_KEY` | | Moonshot | `moonshotai-cn` | chat | `MOONSHOT_API_KEY` | | Moonshot International | `moonshotai` | chat | `MOONSHOT_API_KEY` | | MiniMax | `minimax-cn` | chat | `MINIMAX_API_KEY` | | MiniMax International | `minimax` | chat | `MINIMAX_API_KEY` | | OpenRouter | `openrouter` | chat, embedding | `OPENROUTER_API_KEY` | | ModelScope | `modelscope` | chat | `MODELSCOPE_ACCESS_TOKEN` | | OpenCode | `opencode` | chat | 无默认环境变量 | | OpenCode Go | `opencode-go` | chat | 无默认环境变量 | | SiliconFlow | `siliconflow-cn` | chat, embedding, rerank | `SILICONFLOW_API_KEY` | | SiliconFlow International | `siliconflow` | chat, embedding, rerank | `SILICONFLOW_GLOBAL_API_KEY` | 其中 `alibaba`、`siliconflow-cn` 预置了部分 embedding / rerank 模型;其他供应商通常需要进入详情页通过「获取远程模型」或「手动添加」补充模型。 ### 操作流程 1. **新增供应商**:点击「新增供应商」,填写基本信息(Provider ID、Base URL 等) 2. **配置凭证**:填写 API Key 或环境变量名 3. **启用供应商**:开启供应商状态开关 4. **获取模型**:进入供应商详情,点击「获取远程模型」从 API 拉取可用模型列表 ## 模型管理 ### 添加模型 **方式一:从远端拉取** 进入供应商详情 → 点击「获取远程模型」→ 从候选列表中选择添加 **方式二:手动添加** 进入供应商详情 → 点击「手动添加」→ 填写模型 ID 和类型 ### 配置参数 嵌入模型(embedding)需配置向量维度,请参考模型提供商的规格说明。 OpenAI 兼容供应商的对话模型可在「模型请求参数 JSON」中配置思考模式。这里填写的 JSON 会作为 OpenAI SDK 的 `extra_body` 传入;SDK 会将其中字段合并到最终 HTTP 请求体顶层。 出于安全考虑,该配置采用白名单机制,仅允许以下顶层字段: | 字段 | 常见供应商或用途 | |------|------------------| | `enable_thinking` | DashScope、SiliconFlow 等供应商的思考开关 | | `thinking_budget` | DashScope、SiliconFlow 等供应商的思考 Token 预算 | | `thinking` | DeepSeek、智谱、Kimi、火山方舟等供应商的思考配置对象 | | `reasoning` | OpenRouter 等供应商的推理配置对象 | | `reasoning_effort` | OpenAI 风格的推理强度 | 白名单只校验顶层字段,`thinking`、`reasoning` 等对象的内部结构由对应供应商校验。不同模型支持的取值和预算范围可能不同,应以供应商的当前文档为准。项目维护者如需支持新的顶层字段,可修改 `backend/package/yuxi/models/providers/service.py` 中的 `ALLOWED_EXTRA_BODY_FIELDS`,并补充相应测试和本文档。 例如,关闭思考: ```json { "enable_thinking": false } ``` 限制思考预算: ```json { "enable_thinking": true, "thinking_budget": 1024 } ``` ### 移除模型 在供应商详情的已启用模型列表中移除不需要的模型。 ## 模型标识格式 运行时模型统一使用 `provider_id:model_id` 格式,例如 `siliconflow-cn:Pro/BAAI/bge-m3`。`model_id` 可以包含 `/`,系统只按第一个 `:` 区分供应商与模型 ID。 旧版 `provider/model`、旧版知识库 JSON 模型字段、配置文件中的 `model_names` / `embed_model_names` / `reranker_names` 不再作为运行时模型来源。历史知识库或 Agent 配置如果仍保存旧格式,需要在界面中重新选择新版模型后保存。 ## Ollama 支持 当前版本不再内置 Ollama provider type,也不再提供 Ollama embedding 运行时适配。已有 Ollama embedding 知识库需要管理员选择新的 embedding 模型并重建索引,避免不同向量空间混用。 --- ### Intro/Project Overview # 项目简介 Yuxi (语析) 是一个智能知识库和知识图谱 Agent 开发平台,能够帮助你构建结合检索增强生成 (RAG) 与知识图谱推理的生产级 AI 应用。该平台基于 LangGraph、Vue.js 3、FastAPI、Milvus 和 Neo4j 构建,提供创建对话式 AI 系统所需的智能体编排、知识检索、图谱推理、工具调用和文件系统能力。 ## 设计理念 项目的设计目标是为开发者提供一个易于上手、功能强大的 AI 应用开发框架。我们坚持以下原则: - **技术栈简洁**:选择主流且成熟的技术,降低学习和维护成本 - **MIT 开源协议**:完全开源,允许自由使用和二次开发 - **容器化部署**:通过 Docker Compose 管理,简化部署流程 ## 技术架构 | 层级 | 技术 | 用途 | |------|------|------| | 前端 | Vue.js 3, Vite, Ant Design Vue | 现代响应式 UI 框架与组件库 | | 状态管理 | Pinia | 前端集中式状态管理 | | 后端 API | FastAPI, Uvicorn | 高性能异步 Python Web 框架 | | Agent 框架 | LangGraph | Agent 编排、状态管理与 checkpoint | | 知识库 | Milvus(可建库入库)、Dify / Notion(只读连接器) | 向量知识库 RAG 与外部只读数据源检索 | | 图数据库 | Neo4j | Milvus 知识库内知识图谱存储与查询 | | 文档处理 | MinerU, PaddleX, RapidOCR | 多格式文档解析与 OCR | | 任务队列 | Redis, PostgreSQL Workers | 异步任务处理 | | 对象存储 | MinIO | 文件与文档存储 | | 关系型数据库 | PostgreSQL | 元数据与用户数据持久化 | | 部署 | Docker, Docker Compose | 容器化部署与编排 | ## 核心能力 Yuxi 的核心能力不在于“把大模型接进来”,而在于把 **智能体开发、知识库/RAG、知识图谱** 放进同一套系统里,并让它们在运行时真正协同工作。 ### 1. 面向真实业务的智能体开发 Yuxi 基于 LangGraph 提供智能体开发能力,不只是一个固定问答入口,而是一套可配置、可扩展的 Agent 运行框架。开发者可以围绕同一个 Agent 配置模型、提示词、工具、MCP、Skills、子智能体与中间件,使“对话能力”变成“可编排的业务能力”。 这一层是项目的控制中心,决定了模型如何调用工具、如何访问知识、如何接入文件系统以及如何与其他子智能体协作。 ### 2. 知识库与 RAG 一体化能力 Yuxi 提供完整的知识入库链路,而不是只做检索接口封装。文档从上传开始,会经过解析、分块、向量化、检索配置和评估等阶段,最终成为 Agent 可直接调用的知识来源。 将组织的文档转换为智能对话助手。上传 PDF 手册、技术规格、政策文档和培训材料,以创建可搜索、具备推理能力的知识库,员工可以使用自然语言查询。 该系统能够理解复杂的问题,并提供带有来源引用的上下文感知答案。 ### 3. 知识图谱参与推理,而不只是展示 Yuxi 的知识图谱能力不是孤立的可视化模块,而是和 Milvus 知识库入库链路联动的。系统可以从已入库 chunks 中抽取实体和关系,写入 Neo4j 与 PostgreSQL 并为唯一实体/三元组建立 Milvus 语义索引;检索时可召回图谱实体与三元组,并与 chunk 命中结果融合(RRF),在知识库详情页展示和检索子图。 ### 4. 面向生产落地的文档理解与平台能力 为了让知识真正可用,Yuxi 集成了 MinerU、PP-Structure-V3、RapidOCR、DeepSeek OCR 等解析能力,覆盖 PDF、Office、Markdown、图片等常见格式,解决原始资料进入系统前的结构化处理问题。 在此基础上,平台还补齐了业务落地常用的工程能力,例如: - 部门与权限管理 - 内容审查与守卫能力 - 文件管理与任务管理 - Docker Compose 部署与热重载开发 ## 适用场景 Yuxi 适用于以下场景: - **企业知识库**:构建私有知识问答系统 - **智能客服**:基于文档的自动问答 - **知识管理**:文档自动解析、分类、构建图谱 - **AI 应用开发**:快速构建基于大模型的应用原型 ## 下一步 - 快速开始:阅读 [快速开始指南](./quick-start.md) - 模型配置:阅读 [模型配置](./model-config.md) - 知识库使用:阅读 [知识库与知识图谱](./knowledge-base.md) - 智能体开发:阅读 [智能体开发](../agents/agents-config.md) --- ### Intro/Quick Start # 快速开始指南 欢迎使用 Yuxi(语析),这是一个智能知识库和知识图谱 Agent 开发平台。 本指南将帮助你在几分钟内启动并运行系统,使你能够利用 LangGraph、RAG 技术和知识图谱构建 AI 驱动的知识应用。 ::: tip 提示 除了此文档网站外,你还可以访问 [Zread](https://zread.ai/xerrors/Yuxi) 或 [DeepWiki](https://deepwiki.com/xerrors/Yuxi) 查看自动生成的详细项目文档。 ::: ## 环境要求 项目采用微服务架构设计,默认服务无需 GPU 支持。如果需要使用 OCR 功能,可以通过环境变量配置外部服务。 ## 快速安装 ### 步骤一:获取项目代码 ```bash # 克隆最新版本 git clone --branch v0.7.1 --depth 1 https://github.com/xerrors/Yuxi.git cd Yuxi ``` `--depth 1` 标志会创建一个浅克隆,仅包含最新的提交,从而显著减少下载时间和磁盘使用量。下表提供了版本选择的指导。 | 版本 | 适用场景 | |------|----------| | v0.7.1 | 当前稳定版本,推荐生产使用 | | main | 开发版本,包含最新特性(可能不稳定) | ### 步骤二:配置环境变量 **方式一:使用初始化脚本(推荐)** 我们提供了自动化脚本,帮你完成环境配置和 Docker 镜像拉取: ```bash # Linux/macOS ./scripts/init.sh # Windows PowerShell .\scripts\init.ps1 ``` 脚本会引导你完成以下配置: - 创建 `.env` 配置文件 - 设置 `SILICONFLOW_API_KEY`(必需,用于调用大模型) - 设置 `TAVILY_API_KEY`(可选,用于搜索服务) - 自动拉取必需的 Docker 镜像 ::: tip API Key 获取 - **硅基流动**:访问 [cloud.siliconflow.cn](https://cloud.siliconflow.cn/i/Eo5yTHGJ),注册认证即送 16 元额度 - **Tavily**:访问 [app.tavily.com](https://app.tavily.com/) 获取搜索 API Key(可选) ::: **方式二:手动配置** 如果偏好手动配置: ```bash # 复制环境变量模板 cp .env.template .env # 编辑 .env 文件,填入你的 API Key ``` ### 步骤三:启动服务 ```bash # 构建并启动所有服务 docker compose up --build -d ``` 服务首次启动需要等待镜像拉取和编译,请耐心等待 2-3 分钟。 ::: tip 轻量模式(Lite Mode) 如果你不需要知识库和知识图谱功能,可以使用轻量模式启动,跳过 Milvus、Neo4j、etcd 等服务,节省系统资源: ```bash make up-lite # macOS or Linux ``` 轻量模式仅启动核心服务(前端、后端、PostgreSQL、Redis、MinIO),前端侧边栏会自动隐藏知识库和图谱入口。切换回完整模式只需运行 `make up`。 ::: ### 步骤四:访问系统 服务启动后,访问以下地址: | 服务 | 地址 | |------|------| | Web 界面 | http://localhost:5173 | | API 文档 | http://localhost:5050/docs | 首次访问时,系统会要求你设置超级管理员账号和密码,请妥善保存。 ## 故障排除 ### 查看服务状态 ```bash # 查看所有容器状态 docker ps # 实时查看后端日志 docker logs api-dev -f # 实时查看前端日志 docker logs web-dev -f ``` ### 部署故障排查
Docker 镜像拉取失败 如果网络原因导致镜像拉取失败,可以尝试: ```bash # 手动拉取基础镜像 bash scripts/pull_image.sh python:3.13-slim ``` **离线环境部署方案**: ```bash # 在有网络的环境导出镜像,注意检查镜像列表,不一定是最新的。 bash docker/save_docker_images.sh # 传输到目标机器 scp docker_images_xxx.tar user@host:/path/ # 导入镜像 docker load -i docker_images_xxx.tar ```
构建失败 多数构建失败是由于网络问题。尝试配置代理: ```bash # Linux/macOS export HTTP_PROXY=http://IP:PORT export HTTPS_PROXY=http://IP:PORT # Windows PowerShell $env:HTTP_PROXY="http://IP:PORT" $env:HTTPS_PROXY="http://IP:PORT" ``` 如果配置代理后反而失败,尝试移除代理后重试。
Milvus 服务启动失败 ```bash # 重启 Milvus 服务 docker compose up milvus -d docker restart api-dev ```
::: tip 调试面板 前端提供了调试面板(在头像菜单中可找到),可以查看详细的请求和响应信息。生产环境建议关闭此特性。 ::: ## 下一步 - 了解如何配置模型:阅读 [模型配置](./model-config.md) - 探索知识库功能:阅读 [知识库与知识图谱](./knowledge-base.md) - 学习智能体开发:阅读 [智能体开发](../agents/agents-config.md) - 深入了解配置系统:阅读 [配置系统详解](../advanced/configuration.md) --- ### ARCHITECTURE # ARCHITECTURE.md 本文档是 Yuxi 的代码地图,只描述相对稳定的系统边界、目录职责、核心运行链路和架构不变量。它用于帮助贡献者判断“一个改动应该落在哪里”,不替代具体模块文档、测试规范或源码注释。 修改不熟悉的模块前,先阅读对应章节,再使用符号搜索定位具体类型、函数和路由。开发与运行拓扑始终以 `docker-compose.yml` 为准。 ## 鸟瞰 Yuxi 是一个面向 RAG、知识图谱和多智能体工作流的知识库平台。用户通过 Vue 前端管理智能体、知识库、模型、工具、Skills、MCP 与 SubAgents;前端通过 `/api` 调用 FastAPI;后端服务层协调 PostgreSQL、Redis、MinIO、Milvus、Neo4j、LangGraph 和沙盒。 普通智能体请求先在 PostgreSQL 中保存为请求和消息,再立即派发或进入线程级 FIFO 队列。派发后的 `AgentRun` 通过 Redis/ARQ 交给独立 worker 执行,运行事件写入 Redis Stream,最终状态和业务记录写回 PostgreSQL,前端通过 SSE 消费排队与运行事件。 核心开发服务包括: - `web-dev`:Vue 3 / Vite 前端,挂载 `web/src` 并热重载。 - `api-dev`:FastAPI API 服务,挂载 `backend/server`、`backend/package` 和测试目录并热重载。 - `worker-dev`:ARQ worker,执行已经派发的 AgentRun,并负责异常恢复扫描。 - `sandbox-provisioner`:为智能体工具执行提供隔离沙盒。 - `postgres`:业务数据、知识库元数据、请求队列、AgentRun 与 LangGraph checkpoint。 - `redis`:ARQ 投递、运行事件、取消信号以及跨进程配置和模型缓存。 - `minio`:附件、知识库原始文件和其他对象数据。 - `milvus`、`etcd`:向量检索及其元数据协调。 - `graph`:Neo4j 知识图谱。 - `mineru-api`、`paddlex`:通过 `all` profile 可选启动的文档解析和 OCR 服务。 ## 后端代码地图 后端分成两个顶层边界:`backend/server` 是 Web 应用入口与 HTTP 适配层,`backend/package/yuxi` 是业务和基础设施主体。新增领域逻辑通常优先放在 `yuxi` 包中,路由层只处理请求模型、认证上下文和响应装配。 ### Web 与 worker 入口 - `server/main.py` 创建 FastAPI 应用、注册中间件,并将业务路由统一挂载到 `/api`。 - `server/routers` 是 HTTP 路由边界,所有路由集中在 `server/routers/__init__.py` 注册。 - `server/utils/lifespan.py` 管理数据库、内置模型/MCP/Skills、知识库、Redis、沙盒、LangGraph checkpoint 和通用 Tasker 的启动与关闭。 - `server/worker_main.py` 是 ARQ worker 入口,实际执行设置位于 `yuxi.services.run_worker`。 `LITE_MODE` 下保留认证、智能体、聊天、Skills、MCP、模型、工作区和系统管理接口,但不注册 `external_kb`、`knowledge`、`evaluation` 和 `graph` 路由,也不初始化知识库管理器。 ### `backend/package/yuxi` - `agents` 定义 LangGraph 智能体体系。`BaseAgent` 是智能体基类,`BaseContext` 是运行上下文;`buildin/chatbot` 和 `buildin/subagent` 放内置智能体;`middlewares` 组合文件系统、Skills、SubAgent、摘要、审批、模型兼容和用量统计;`toolkits` 管理本地工具;`backends` 对接沙盒、知识库和 Skills 文件系统;`skills` 与 `mcp` 管理扩展能力及其运行时加载。 - `services` 是用例层。智能体主链路重点分为请求接入与排队、Run 生命周期、运行时配置、worker 执行和 SubAgent 调用;聊天历史、附件、工作区、文件预览、评估、认证和观测等跨模块流程也从这里找入口。 - `repositories` 是 PostgreSQL 访问边界,封装业务对象、知识库元数据、AgentRun、请求队列、Task 和扩展配置查询。路由不应绕过 repository 直接拼装持久化逻辑。 - `storage/postgres` 管理 SQLAlchemy 模型、业务连接池和 LangGraph checkpoint 连接池。 - `storage/redis` 管理同步/异步 Redis 客户端和 ARQ 连接参数;业务 key、事件格式和缓存语义留在各自服务中。 - `storage/minio` 管理对象上传、下载和临时文件访问。 - `storage/neo4j` 管理共享 Neo4j Driver、生命周期和图查询辅助。 - `knowledge` 是知识库、文档解析、评估和图谱领域。`runtime.py` 暴露运行时知识库管理器;`implementations` 放 Milvus、Dify、Notion 和只读连接器;`parser` 统一封装 OCR/文档解析;`chunking` 管理分块策略;`graphs` 管理 Milvus 与 Neo4j 图谱能力。 - `models` 封装 chat、embedding 和 rerank 模型适配;`models/providers` 使用 PostgreSQL 保存模型供应商,并通过 Redis 缓存向 API 和 worker 提供一致视图。 - `config` 区分系统级配置和用户级配置。系统配置写入 `base.toml` 并同步 Redis 快照,用户配置保存在 PostgreSQL。 - `utils` 只放跨领域且足够通用的日志、时间、SSE 和轻量工具。 ### 两类后台任务 项目中存在两套用途不同的后台执行机制,不应混用: - AgentRun:通过 PostgreSQL 保存事实状态,使用 Redis/ARQ 投递到 `worker-dev`,支持运行事件、取消、恢复和线程请求队列。 - `services/task_service.py` 中的 Tasker:运行在 API 进程内,用于知识库解析、评估和图谱构建等通用后台任务;任务摘要持久化到 PostgreSQL,但可执行 coroutine 和内存队列不具备跨进程重建能力。 测试代码位于 `backend/test`,按 `unit`、`integration`、`e2e` 分层。新增或修改后端行为时,测试应放在最能覆盖真实风险的层级。 ## 前端代码地图 前端是 Vue 3 + Vite 应用,业务入口集中在 `web/src`。 - `main.js` 挂载应用,`App.vue` 是根组件。 - `router` 定义公开首页、登录、智能体、工作区、智能体管理、扩展和仪表盘路由,并负责认证、管理员和超级管理员守卫。 - `apis` 是后端接口封装边界。新增接口应在这里定义,复用 `base.js` 的请求、鉴权和错误处理。 - `stores` 保存用户、智能体配置、主题和其他跨页面状态。 - `views` 是页面级入口,`components` 是可复用界面块。智能体对话的主要交互位于 `AgentChatComponent`,由 `AgentView` 负责页面组合。 - `composables` 封装请求排队、Run SSE、流式消息、审批、线程状态、提及和其他可组合逻辑。 - `utils` 放轻量转换和展示辅助;全局样式集中在 `assets/css`,颜色和基础规范优先复用 `base.css`。 `/` 是公开首页;登录后的核心工作区是 `/agent`。`/extensions` 对所有登录用户开放,其中 Skills 对普通用户可见,知识库、工具和 MCP 管理能力仅管理员可见;Dashboard 仅超级管理员可访问。后端权限检查始终是最终边界,前端守卫只负责页面体验。 ## 智能体运行链路 一次普通智能体请求经过以下边界: 1. `AgentView` 和 `AgentChatComponent` 收集文本、图片、附件、模型与审批配置。 2. `web/src/apis/agent_api.js` 调用 `POST /api/agent/runs`。 3. `server/routers/agent_router.py` 校验用户和智能体,将请求交给 `agent_request_queue_service`。 4. 服务在同一数据库事务中创建用户消息和 AgentRunRequest,并按用户、智能体和线程检查活跃 Run 与 FIFO 队头。 5. 请求可以立即派发、进入等待队列或按 `reject` 策略拒绝;只有数据库提交成功后才向 ARQ 投递 Run。 6. `worker-dev` 中的 `run_worker` 加载 AgentRun、智能体配置和运行上下文,执行对应 LangGraph。 7. 智能体通过 middleware 组合沙盒文件系统、附件、Skills、MCP、SubAgent、审批、摘要和工具能力。知识库能力主要由内置 `knowledge-base` Skill 及其依赖工具按需开放。 8. Run 事件写入 Redis Stream,取消通过 Redis key/pubsub 传递;AgentRun、消息投递状态和最终结果写入 PostgreSQL。 9. 前端在排队阶段消费 Request SSE,派发后切换到 Run SSE,并根据数据库状态处理断线恢复和终态补偿。 10. 附件和对象数据保存在 MinIO;智能体需要操作的文件映射到线程隔离的沙盒路径,生成物写入用户可见的输出目录。 审批或人机输入产生的 resume 请求会从 LangGraph checkpoint 恢复,并创建新的 AgentRun;它不重新进入普通消息 FIFO 接入流程。 ## 架构不变量 - Docker Compose 是开发环境的事实来源。开发时先检查容器、日志和热重载,不默认要求本地裸跑服务。 - HTTP 路由保持薄;用例流程放在 `yuxi.services`,持久化查询放在 `yuxi.repositories`。 - 请求接入与 Run 执行是两个阶段:先提交 PostgreSQL 事实,再投递 ARQ,不能让队列消息先于数据库状态可见。 - 同一用户、智能体和线程的普通请求通过 FIFO 队列串行派发;排队请求与运行中的 Run 使用不同状态模型和 SSE。 - PostgreSQL 保存业务事实状态;Redis 承担投递、事件、取消和缓存,不作为 AgentRun 最终状态的唯一来源。 - 前端 API 调用集中在 `web/src/apis`,组件不要散落拼接普通 HTTP 接口。 - 智能体能力通过 context、middleware、toolkits、Skills、MCP 和 backends 组合;不要把知识库、沙盒或扩展逻辑硬编码进单个页面或路由。 - Skill 依赖工具只有在对应 Skill 激活后才对模型开放;基础工具与受 Skill 门控的工具要保持边界。 - LITE 模式必须允许跳过知识库、图谱和评估等重依赖能力,新增导入、路由和启动逻辑时要尊重该边界。 - 沙盒虚拟路径以 `SANDBOX_VIRTUAL_PATH_PREFIX` 为边界,用户可见路径、对象存储 URL 与宿主机真实路径不能混用。 - 面向用户和外部系统的输入在边界校验;内部服务优先依赖已有类型、事务和仓储约束,避免用静默回退掩盖设计错误。 ## 跨切面关注点 - **配置**:Compose 和 `.env` 提供部署配置;管理员系统配置写入 `base.toml` 并通过 Redis 快照同步;用户配置与模型供应商以 PostgreSQL 为事实来源。 - **权限**:前端路由和页面标签提供体验级约束,FastAPI 认证依赖和 repository 可见性查询提供最终授权。 - **状态与存储**:PostgreSQL 保存请求、Run、消息、业务和知识库元数据;LangGraph checkpoint 使用 PostgreSQL,必要时可回退 SQLite/内存;Redis 保存短期事件、取消信号、ARQ 和跨进程缓存;MinIO、沙盒与本地 `saves` 分别承载不同生命周期的文件。 - **文档处理**:上传文件先进入对象存储和文件元数据边界,再经过解析、分块和知识库实现;解析器、分块策略和知识库连接器保持可替换。 - **观测与调试**:优先查看 `api-dev`、`worker-dev` 和相关依赖日志;Langfuse 集中在服务层和 AgentRun 上下文;SSE 问题同时检查 Redis 事件与 PostgreSQL 终态。 --- ### CONTRIBUTING # Contributing to Yuxi 感谢你关注 Yuxi。欢迎提交 Issue、改进文档、修复 Bug 或贡献新功能。 更完整的开发文档可参考 [docs/develop-guides/contributing.md](docs/develop-guides/contributing.md)。 ## 开始之前 - 提交前请先搜索现有 [Issues](https://github.com/xerrors/Yuxi/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,并说明修改内容、原因和验证方式 5. PR 模板 [PULL_REQUEST_TEMPLATE.md](.github/PULL_REQUEST_TEMPLATE.md) 中的检查项需要在提交前完成 示例: ```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/changelog.md](docs/develop-guides/changelog.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 反馈/功能讨论: 感谢你的贡献 ❤️。 --- ### README

语析 Yuxi

多租户 Harness + 企业知识库
让企业知识可被智能体检索、推理与交付

[](https://github.com/xerrors/Yuxi/blob/main/docker-compose.yml) [](https://github.com/xerrors/Yuxi/issues) [](https://github.com/xerrors/Yuxi/blob/main/LICENSE) [](https://deepwiki.com/xerrors/Yuxi) [](https://www.bilibili.com/video/BV1erE26iEgv/?share_source=copy_web&vd_source=37b0bdbf95b72ea38b2dc959cfadc4d8) xerrors%2FYuxi | Trendshift [[项目文档]](https://xerrors.github.io/Yuxi) · [[版本特性]](http://xhslink.com/o/5Y6QWnmjF2d) · [[🇬🇧 English README]](README.en.md)
## 简介 语析(Yuxi)是一个基于大模型的智能知识库与知识图谱智能体开发平台。它把 **RAG 检索**、**Milvus 知识库内知识图谱** 与 **LangGraph 多智能体编排** 整合进统一的多租户工作台:管理员配置知识库、模型与权限,用户在类 ChatGPT 的界面中与可挂载 Skills、MCP、子智能体和沙盒工具的智能体对话,并获得带引用来源、知识图谱推理与可交付产物的回答。 导航:[项目介绍](https://xerrors.github.io/Yuxi/) | [快速开始](https://xerrors.github.io/Yuxi/intro/quick-start) | [开发路线图](https://xerrors.github.io/Yuxi/develop-guides/roadmap) | [0.7 版本特性](http://xhslink.com/o/5Y6QWnmjF2d);最新开发动态,详见 [changelog](https://xerrors.github.io/Yuxi/develop-guides/changelog)。 > 📢 求职:作者为江南大学软件工程博士研究生,研究方向 AI Agent、知识图谱与大模型应用,预计 2027 年毕业,现寻求实习/全职机会,欢迎联系:wenjie.zhang@stu.jiangnan.edu.cn --- ## 技术栈 | 层 | 技术 | | --- | --- | | 前端 | Vue 3 · Vite · Pinia | | 后端 | FastAPI · LangGraph · ARQ (异步 worker) | | 存储 | PostgreSQL · Redis · MinIO · Milvus · Neo4j | | 文档解析 | MinerU · PaddleX · RapidOCR | | 部署 | Docker Compose | ## 快速开始 **前置要求**:已安装 [Docker](https://docs.docker.com/get-docker/) 与 Docker Compose,并准备至少一个兼容 OpenAI 接口的大模型 API。 **1. 克隆代码并初始化** ```bash git clone --branch v0.7.1 --depth 1 https://github.com/xerrors/Yuxi.git cd Yuxi # Linux/macOS ./scripts/init.sh # Windows PowerShell .\scripts\init.ps1 ``` **2. 使用 Docker 启动** ```bash docker compose up --build ``` **3. 访问平台** 等待启动完成后,浏览器打开 `http://localhost:5173`,使用初始化时生成的管理员账户登录即可。 > 💡 不需要知识库 / 知识图谱等重依赖时,可使用 `make up-lite` 以 LITE 轻量模式启动,加快冷启动速度。更多部署说明见 [项目文档](https://xerrors.github.io/Yuxi)。 ## 致谢 本项目参考并引用了以下优秀开源项目,在此致以诚挚的感谢: - [LightRAG](https://github.com/HKUDS/LightRAG) - 早期版本曾参考其图谱构建与检索思路;当前 Yuxi 已实现自研 Milvus 知识库/图谱链路以替换历史集成,降低兼容性问题 - [DeepAgents](https://github.com/langchain-ai/deepagents) - 直接引入作为深度智能体框架 - [DeerFlow](https://github.com/bytedance/deer-flow) - 参考了其 Sandbox 智能体架构的实现思路 - [RAGflow](https://github.com/infiniflow/ragflow) - 参考了其文档 Text Chunking 的分块策略 - [LangGraph](https://github.com/langchain-ai/langgraph) - 多智能体编排框架,本项目的核心架构基础 - [QwenPaw](https://github.com/agentscope-ai/QwenPaw) - 参考模型配置与个人文件区域设计 ## 参与贡献 感谢所有贡献者的支持! ## Star History [](https://star-history.com/#xerrors/Yuxi) ## 📄 许可证 本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。 ---
**如果这个项目对您有帮助,请不要忘记给我们一个 ⭐️**
---