chore: import upstream snapshot with attribution
Ruff Format Check / Ruff Format & Lint (push) Failing after 7m39s
Deploy VitePress site to Pages / build (push) Failing after 9m11s
Deploy VitePress site to Pages / Deploy (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:26 +08:00
commit 1443d3fdf9
732 changed files with 196602 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
import { defineConfig } from 'vitepress'
import markdownItTaskCheckbox from 'markdown-it-task-checkbox'
// https://vitepress.dev/reference/site-config
export default defineConfig({
lang: 'zh-CN',
title: "Yuxi",
description: "语析",
base: '/Yuxi/',
ignoreDeadLinks: [
/localhost/,
/CONTRIBUTING$/,
/docker-compose\.yml$/,
/^\.\/intro\//
],
markdown: {
config: (md) => {
md.use(markdownItTaskCheckbox)
}
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
logo: "/favicon.svg",
nav: [
{ text: '快速开始', link: '/intro/quick-start' },
{ text: '智能体开发', link: '/agents/agents-config' }
],
sidebar: [
{
text: '简介',
items: [
{ text: '什么是 Yuxi', link: '/intro/project-overview' },
{ text: '快速开始', link: '/intro/quick-start' },
{ text: '命令行工具', link: '/intro/cli' },
{ text: '模型配置', link: '/intro/model-config' },
{ text: '知识库与知识图谱', link: '/intro/knowledge-base' },
{ text: '知识库评估', link: '/intro/evaluation' }
]
},
{
text: '智能体开发',
items: [
{ text: '智能体配置', link: '/agents/agents-config' },
{ text: '工具系统', link: '/agents/tools-system' },
{ text: '中间件', link: '/agents/middleware' },
{ text: '智能体评估', link: '/agents/agent-evaluation' },
{ text: '沙盒架构与设计', link: '/agents/sandbox-architecture' },
{ text: 'MCP 集成', link: '/agents/mcp-integration' },
{ text: 'Skills 管理', link: '/agents/skills-management' },
{ text: '子智能体', link: '/agents/subagents-management' }
]
},
{
text: '高级配置',
items: [
{ text: '配置系统详解', link: '/advanced/configuration' },
{ text: 'Langfuse 集成', link: '/advanced/langfuse-integration' },
{ text: '文档解析', link: '/advanced/document-processing' },
{ text: '品牌自定义', link: '/advanced/branding' },
{ text: '其他配置', link: '/advanced/misc' },
{ text: '生产部署', link: '/advanced/deployment' },
{ text: 'API Key 外部集成', link: '/advanced/api-key-integration' }
]
},
{
text: '开发指南',
items: [
{ text: '参与贡献', link: '/develop-guides/contributing' },
{ text: '开发路线图', link: '/develop-guides/roadmap' },
{ text: '版本变更记录', link: '/develop-guides/changelog' },
{ text: '界面设计规范', link: '/develop-guides/design' },
{ text: '测试规范', link: '/develop-guides/testing-guidelines' },
]
}
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/xerrors/Yuxi' }
],
footer: {
message: '本项目基于 MIT License 开源,欢迎使用和贡献。',
copyright: 'Copyright © 2025-present Yuxi'
},
editLink: {
pattern: 'https://github.com/xerrors/Yuxi/edit/main/docs/:path',
text: '在 GitHub 上编辑此页'
},
lastUpdated: {
text: '最后更新时间',
formatOptions: {
dateStyle: 'full',
timeStyle: 'medium'
}
},
search: {
provider: 'local'
},
docFooter: {
prev: '上一页',
next: '下一页'
}
},
})
@@ -0,0 +1,848 @@
<script setup>
import { ref, computed } from 'vue'
import { withBase } from 'vitepress'
const GITHUB = 'https://github.com/xerrors/Yuxi'
const DEMO = 'https://www.bilibili.com/video/BV1TZEx6NEit/'
// 关键数字(占位,后续替换为真实数据)
const stats = [
{ value: '15+', label: '模型供应商' },
{ value: '7', label: 'Harness 能力' },
{ value: 'MIT', label: '开源协议' },
{ value: 'v0.7', label: '当前版本' }
]
// Harness 能力中枢(bento
const capabilities = [
{
icon: 'box', span: true,
title: '沙盒文件系统',
desc: '每个会话拥有独立的虚拟文件系统(workspace / uploads / outputs),智能体产物自动落盘,支持文本、图片、PDF、HTML 在线预览与下载。',
tags: ['预览', '下载', 'Artifacts 产物'],
shot: 'https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260604203704426.png'
},
{
icon: 'sparkles',
title: 'Skills 技能系统',
desc: '内置图像生成、深度报告、数据报表等技能,支持上传与远程安装,「解析草稿 → 确认安装」。',
tags: ['内置', '上传', '远程']
},
{
icon: 'plug',
title: 'MCP 集成',
desc: '通过 Model Context Protocol 标准协议接入外部工具服务,统一启停与权限管理。',
tags: ['标准协议']
},
{
icon: 'wrench',
title: '内置工具',
desc: 'present_artifacts 交付产物、提问中断等待用户、按需安装技能、联网检索等开箱即用。',
tags: ['开箱即用']
},
{
icon: 'fork',
title: '子智能体 SubAgents',
desc: '主智能体可编排隔离的子智能体,独立 child thread 执行复杂子任务并回传产物。',
tags: ['隔离编排']
},
{
icon: 'layers',
title: '中间件编排',
desc: '知识库检索注入、附件处理、历史摘要 offload、动态工具注入等中间件可组合编排。',
tags: ['可组合']
},
{
icon: 'cpu',
title: '异步 Worker',
desc: '基于 ARQ 的后台任务,将分钟到小时级长耗时任务异步执行,支持取消与流式输出。',
tags: ['长任务', '可取消', '流式']
}
]
// 知识引擎:可切换的能力 tab,右侧媒体随选中项切换
const engineTabs = [
{
key: 'parse', icon: 'scan', title: '多格式解析',
desc: 'MinerU、PaddleX、RapidOCR 统一解析 PDF、Office、图片等为结构化 Markdown。',
shot: 'https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260605205221908.png'
},
{
key: 'retrieval', icon: 'database', title: 'Agentic RAG',
desc: '智能体自主决定检索时机与查询,多轮向量检索 + Rerank,回答带可溯源引用。',
shot: 'https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260604205342546.png'
},
{
key: 'graph', icon: 'share', title: '知识图谱',
desc: '抽取实体与关系构建知识图谱,子图检索参与增强,并支持可视化探索。',
shot: 'https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260604204056298.png'
},
{
key: 'eval', icon: 'chart', title: '检索评估',
desc: '内置检索质量评估,支持命名运行与指标对比,量化召回与回答效果。',
shot: 'https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260604210111977.png'
},
{
key: 'sources', icon: 'plug', title: '多知识源接入',
desc: '支持 Dify、Notion、飞书(规划中)等外部知识源接入,统一检索与引用。',
shot: 'https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260604205611168.png'
}
]
const activeEngine = ref(0)
const currentEngine = computed(() => engineTabs[activeEngine.value])
// 模型供应商墙(横向跑马灯,两行错位反向滚动)
const ICON_BASE = 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons'
const providers = [
{ name: 'OpenAI', icon: `${ICON_BASE}/openai.svg` },
{ name: 'DeepSeek', icon: `${ICON_BASE}/deepseek-color.svg` },
{ name: '通义千问', icon: `${ICON_BASE}/bailian-color.svg` },
{ name: '智谱 AI', icon: `${ICON_BASE}/zhipu-color.svg` },
{ name: 'Moonshot', icon: `${ICON_BASE}/moonshot.svg` },
{ name: 'MiniMax', icon: `${ICON_BASE}/minimax-color.svg` },
{ name: 'SiliconFlow', icon: `${ICON_BASE}/siliconcloud-color.svg` },
{ name: 'OpenRouter', icon: `${ICON_BASE}/openrouter.svg` },
{ name: 'ModelScope', icon: `${ICON_BASE}/modelscope-color.svg` },
{ name: 'OpenCode', icon: `${ICON_BASE}/opencode.svg` },
{ name: '小米 MiMo', icon: `${ICON_BASE}/xiaomimimo.svg` }
]
// 第二行换个起始顺序以形成错位;每行内容复制一份用于无缝循环
const providersTop = [...providers, ...providers]
const providersBottom = (() => {
const rotated = [...providers.slice(5), ...providers.slice(0, 5)]
return [...rotated, ...rotated]
})()
// 工作原理
const steps = [
{ n: '01', title: '配置底座', desc: '管理员接入模型供应商、构建知识库与知识图谱、划分用户与部门权限。' },
{ n: '02', title: '编排智能体', desc: '为 Agent 挂载 Skills、MCP、Tools 与子智能体,组合所需中间件能力。' },
{ n: '03', title: '检索与推理', desc: '对话中融合向量检索与知识图谱推理,沙盒工具执行真实任务。' },
{ n: '04', title: '交付产物', desc: '返回带引用来源的回答,并以可预览、可下载的产物卡片交付结果。' }
]
// 产品截图一览
const shots = [
{ title: '对话工作台', desc: '类 ChatGPT 的智能体对话与产物交付' },
{ title: '智能体配置', desc: '挂载 Skills / MCP / 子智能体与中间件' },
{ title: '知识图谱可视化', desc: '实体关系抽取与子图检索展示' },
{ title: '智能体拓展', desc: '统一管理 Skills 与 MCP 服务' }
]
// 企业级
const enterprise = [
{ icon: 'shield', title: '多租户与权限', desc: '用户 / 部门级隔离,知识库支持全局、部门、指定人三档共享。' },
{ icon: 'key', title: 'API Key 集成', desc: '签发独立密钥,供外部系统以 API 方式安全调用平台能力。' },
{ icon: 'rocket', title: 'LITE 轻量启动', desc: 'make up-lite 跳过重依赖快速冷启动,Docker Compose 开箱即用。' }
]
// 应用场景
const cases = [
{ title: '企业知识问答助手', desc: '将内部资料沉淀为可检索、可推理的知识资产,回答带来源引用。' },
{ title: '科研与行业调研报告', desc: '借助 deep-reporter 技能生成结构化的深度分析长报告。' },
{ title: '内部 AI 能力底座', desc: '为各业务系统提供可管理、可扩展的统一智能体服务。' }
]
// 技术栈分层
const techStack = [
{ group: '前端', items: ['Vue 3', 'Vite', 'Pinia'] },
{ group: '后端', items: ['FastAPI', 'LangGraph', 'ARQ'] },
{ group: '存储', items: ['PostgreSQL', 'Redis', 'MinIO', 'Milvus', 'Neo4j'] },
{ group: '解析', items: ['MinerU', 'PaddleX', 'RapidOCR'] },
{ group: '部署', items: ['Docker Compose'] }
]
// 开源致谢
const credits = [
{ name: 'LightRAG', url: 'https://github.com/HKUDS/LightRAG' },
{ name: 'DeepAgents', url: 'https://github.com/langchain-ai/deepagents' },
{ name: 'DeerFlow', url: 'https://github.com/bytedance/deer-flow' },
{ name: 'RAGflow', url: 'https://github.com/infiniflow/ragflow' },
{ name: 'LangGraph', url: 'https://github.com/langchain-ai/langgraph' },
{ name: 'QwenPaw', url: 'https://github.com/agentscope-ai/QwenPaw' }
]
// lucide 风格图标路径(stroke 1.5
const icons = {
box: '<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
sparkles: '<path d="M9.94 15.5A2 2 0 0 0 8.5 14.06l-6.14-1.58a.5.5 0 0 1 0-.96L8.5 9.94A2 2 0 0 0 9.94 8.5l1.58-6.14a.5.5 0 0 1 .96 0L14.06 8.5A2 2 0 0 0 15.5 9.94l6.14 1.58a.5.5 0 0 1 0 .96L15.5 14.06a2 2 0 0 0-1.44 1.44l-1.58 6.14a.5.5 0 0 1-.96 0z"/>',
plug: '<path d="M12 22v-5"/><path d="M9 7V2"/><path d="M15 7V2"/><path d="M6 13V8h12v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4Z"/>',
wrench: '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
fork: '<circle cx="12" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9"/><path d="M12 12v3"/>',
layers: '<path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/>',
cpu: '<rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/>',
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14a9 3 0 0 0 18 0V5"/><path d="M3 12a9 3 0 0 0 18 0"/>',
share: '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" x2="15.42" y1="13.51" y2="17.49"/><line x1="15.41" x2="8.59" y1="6.51" y2="10.49"/>',
scan: '<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M7 12h10"/>',
shield: '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>',
key: '<path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/>',
rocket: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
chart: '<path d="M3 3v18h18"/><path d="M7 16v-5"/><path d="M12 16V8"/><path d="M17 16v-3"/>'
}
// 滚动进场指令
const vReveal = {
mounted(el) {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
el.classList.add('reveal')
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
if (e.isIntersecting) {
el.classList.add('in-view')
io.unobserve(el)
}
}
}, { threshold: 0.1 })
io.observe(el)
}
}
</script>
<template>
<div class="yx-home">
<!-- ===== Hero ===== -->
<section class="yx-hero">
<div class="yx-ambient" aria-hidden="true">
<span class="yx-orb yx-orb--1"></span>
<span class="yx-orb yx-orb--2"></span>
<span class="yx-orb yx-orb--3"></span>
<div class="yx-grid"></div>
</div>
<div class="yx-container yx-hero__inner">
<span class="yx-badge">v0.7.0 · MIT 开源 · LangGraph 驱动</span>
<h1 class="yx-hero__title">语析 <span class="yx-accent">Yuxi</span></h1>
<p class="yx-hero__subtitle">融合 RAG 与知识图谱的智能体 Harness 平台</p>
<p class="yx-hero__desc">
管理员配置知识库模型与权限用户在类 ChatGPT 的界面中
与可挂载 SkillsMCP子智能体与沙盒工具的智能体对话
获得带引用来源知识图谱推理与可交付产物的回答
</p>
<div class="yx-hero__actions">
<a class="yx-btn yx-btn--primary" :href="withBase('/intro/quick-start')">快速开始</a>
<a class="yx-btn yx-btn--ghost" :href="GITHUB" target="_blank" rel="noreferrer"> GitHub 查看</a>
<a class="yx-btn yx-btn--text" :href="DEMO" target="_blank" rel="noreferrer"> 演示视频</a>
</div>
<div class="yx-hero__shot">
<img
class="yx-hero__img"
src="https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260608002434299.png"
alt="语析 Yuxi 产品界面预览"
loading="lazy"
/>
</div>
</div>
</section>
<!-- ===== 数据条 ===== -->
<section class="yx-stats">
<div class="yx-container yx-stats__inner">
<div v-for="s in stats" :key="s.label" class="yx-stat">
<div class="yx-stat__value">{{ s.value }}</div>
<div class="yx-stat__label">{{ s.label }}</div>
</div>
</div>
</section>
<!-- ===== Harness 能力中枢 ===== -->
<section class="yx-section">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">智能体运行时</span>
<h2 class="yx-head__title">不止于对话更能执行与交付</h2>
<p class="yx-head__sub">Yuxi 内置一套完整的 Harness沙盒技能工具子智能体与中间件让智能体真正动手完成任务</p>
</header>
<div class="yx-bento">
<article
v-for="cap in capabilities"
:key="cap.title"
v-reveal
class="yx-cap"
:class="{ 'yx-cap--lg': cap.span }"
>
<span class="yx-cap__icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" v-html="icons[cap.icon]" />
</span>
<h3 class="yx-cap__title">{{ cap.title }}</h3>
<p class="yx-cap__desc">{{ cap.desc }}</p>
<div class="yx-cap__tags">
<span v-for="t in cap.tags" :key="t" class="yx-tag">{{ t }}</span>
</div>
<img v-if="cap.shot" class="yx-cap__shot-img" :src="cap.shot" :alt="cap.title" loading="lazy" />
</article>
</div>
</div>
</section>
<!-- ===== 知识引擎 ===== -->
<section class="yx-section yx-section--soft">
<div class="yx-container yx-split">
<div v-reveal class="yx-split__text">
<span class="yx-head__eyebrow">知识引擎</span>
<h2 class="yx-head__title">从文档到可推理的知识资产</h2>
<ul class="yx-tabs">
<li
v-for="(t, i) in engineTabs"
:key="t.key"
class="yx-tab"
:class="{ 'yx-tab--active': activeEngine === i }"
@click="activeEngine = i"
>
<span class="yx-list__ic" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" v-html="icons[t.icon]" />
</span>
<div class="yx-tab__body">
<strong>{{ t.title }}</strong>
<div class="yx-tab__desc"><p>{{ t.desc }}</p></div>
</div>
</li>
</ul>
</div>
<div v-reveal class="yx-split__media">
<div class="yx-engine-media">
<Transition name="yx-fade" mode="out-in">
<img
v-if="currentEngine.shot"
:key="currentEngine.key"
class="yx-engine-frame"
:src="currentEngine.shot"
:alt="currentEngine.title"
loading="lazy"
/>
<div
v-else
:key="currentEngine.key"
class="yx-engine-frame yx-engine-ph"
role="img"
:aria-label="currentEngine.title + ' 预览'"
>
<span>{{ currentEngine.title }} · 预览</span>
</div>
</Transition>
</div>
</div>
</div>
</section>
<!-- ===== 模型供应商墙 ===== -->
<section class="yx-section">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">模型供应商</span>
<h2 class="yx-head__title">一处接入随处切换</h2>
<p class="yx-head__sub">统一 <code>provider_id:model_id</code> 配置覆盖主流模型供应商并支持自定义 provider</p>
</header>
<div v-reveal class="yx-marquee">
<div class="yx-marquee__row">
<div class="yx-marquee__track">
<div v-for="(p, i) in providersTop" :key="'t' + i" class="yx-mq-item">
<img :src="p.icon" :alt="p.name" loading="lazy" />
<span>{{ p.name }}</span>
</div>
</div>
</div>
<div class="yx-marquee__row yx-marquee__row--rev">
<div class="yx-marquee__track">
<div v-for="(p, i) in providersBottom" :key="'b' + i" class="yx-mq-item">
<img :src="p.icon" :alt="p.name" loading="lazy" />
<span>{{ p.name }}</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ===== 工作原理 ===== -->
<section class="yx-section yx-section--soft">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">工作原理</span>
<h2 class="yx-head__title">四步搭建你的智能体应用</h2>
</header>
<div class="yx-steps">
<div v-for="(st, i) in steps" :key="st.n" v-reveal class="yx-step">
<div class="yx-step__n">{{ st.n }}</div>
<h3 class="yx-step__title">{{ st.title }}</h3>
<p class="yx-step__desc">{{ st.desc }}</p>
<span v-if="i < steps.length - 1" class="yx-step__arrow" aria-hidden="true"></span>
</div>
</div>
</div>
</section>
<!-- ===== 产品一览 ===== -->
<section class="yx-section">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">产品一览</span>
<h2 class="yx-head__title">一个工作台覆盖全流程</h2>
</header>
<div class="yx-shots">
<figure v-for="sh in shots" :key="sh.title" v-reveal class="yx-shot">
<div class="yx-placeholder" role="img" :aria-label="sh.title + ' 截图占位'">
<span>{{ sh.title }} · 16:9</span>
</div>
<figcaption>
<strong>{{ sh.title }}</strong>
<span>{{ sh.desc }}</span>
</figcaption>
</figure>
</div>
</div>
</section>
<!-- ===== 企业级 ===== -->
<section class="yx-section yx-section--soft">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">企业级与可集成</span>
<h2 class="yx-head__title">从原型验证到团队落地</h2>
</header>
<div class="yx-cards3">
<article v-for="e in enterprise" :key="e.title" v-reveal class="yx-card">
<span class="yx-cap__icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" v-html="icons[e.icon]" />
</span>
<h3>{{ e.title }}</h3>
<p>{{ e.desc }}</p>
</article>
</div>
</div>
</section>
<!-- ===== 应用场景 ===== -->
<section class="yx-section">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">应用场景</span>
<h2 class="yx-head__title">适配你的真实业务</h2>
</header>
<div class="yx-cards3">
<article v-for="c in cases" :key="c.title" v-reveal class="yx-card yx-card--case">
<h3>{{ c.title }}</h3>
<p>{{ c.desc }}</p>
</article>
</div>
</div>
</section>
<!-- ===== 技术栈 ===== -->
<section class="yx-section yx-section--soft">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">技术栈</span>
<h2 class="yx-head__title">现代而稳健的工程基座</h2>
</header>
<div class="yx-tech">
<div v-for="t in techStack" :key="t.group" v-reveal class="yx-tech__row">
<div class="yx-tech__group">{{ t.group }}</div>
<div class="yx-tech__items">
<span v-for="it in t.items" :key="it" class="yx-chip">{{ it }}</span>
</div>
</div>
</div>
</div>
</section>
<!-- ===== 快速开始 ===== -->
<section class="yx-section">
<div class="yx-container">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">快速开始</span>
<h2 class="yx-head__title">三步本地跑起来</h2>
</header>
<div v-reveal class="yx-quick">
<pre class="yx-code"><code><span class="yx-c-cmt"># 1. 克隆并初始化</span>
git clone --branch v0.7.1.beta1 --depth 1 https://github.com/xerrors/Yuxi.git
cd Yuxi && ./scripts/init.sh
<span class="yx-c-cmt"># 2. 使用 Docker 启动</span>
docker compose up --build
<span class="yx-c-cmt"># 3. 浏览器访问</span>
open http://localhost:5173</code></pre>
<p class="yx-quick__tip">无需知识库 / 知识图谱等重依赖时可用 <code>make up-lite</code> LITE 轻量模式快速启动</p>
</div>
</div>
</section>
<!-- ===== 贡献者 & 致谢 ===== -->
<section class="yx-section yx-section--soft">
<div class="yx-container yx-center">
<header v-reveal class="yx-head">
<span class="yx-head__eyebrow">社区</span>
<h2 class="yx-head__title">由开源社区共同构建</h2>
</header>
<a v-reveal :href="GITHUB + '/graphs/contributors'" target="_blank" rel="noreferrer" class="yx-contrib">
<img src="https://contrib.rocks/image?repo=xerrors/Yuxi&max=60&columns=12" alt="Yuxi 贡献者头像墙" loading="lazy" />
</a>
<p v-reveal class="yx-credits">
站在巨人的肩上
<template v-for="(c, i) in credits" :key="c.name">
<a :href="c.url" target="_blank" rel="noreferrer">{{ c.name }}</a><span v-if="i < credits.length - 1"> · </span>
</template>
</p>
</div>
</section>
<!-- ===== 最终 CTA ===== -->
<section class="yx-cta">
<div class="yx-container yx-cta__inner" v-reveal>
<h2>立即开始构建你的智能体</h2>
<p>开源可自托管面向真实业务场景</p>
<div class="yx-hero__actions yx-cta__actions">
<a class="yx-btn yx-btn--primary" :href="withBase('/intro/quick-start')">快速开始</a>
<a class="yx-btn yx-btn--ghost" :href="GITHUB" target="_blank" rel="noreferrer">前往 GitHub </a>
</div>
</div>
</section>
</div>
</template>
<style scoped>
.yx-home {
--yx-max: 1152px;
--yx-gap: 24px;
--yx-brand: var(--main-700);
--yx-brand-hover: var(--main-bright);
--yx-brand-soft: rgba(4, 106, 130, .12);
--yx-lime: #c6f43a;
--yx-lime-strong: #6f9300;
--yx-lime-soft: rgba(198, 244, 58, .24);
color: var(--vp-c-text-1);
font-size: 16px;
line-height: 1.6;
}
.yx-container {
max-width: var(--yx-max);
margin: 0 auto;
padding: 0 24px;
}
.yx-center { text-align: center; }
.yx-accent { color: var(--yx-brand); }
/* 段落节奏 */
.yx-section { padding: 96px 0; }
.yx-section--soft { background: var(--vp-c-bg-soft); }
/* 标题块 */
.yx-head { max-width: 720px; margin: 0 auto 48px; text-align: center; }
.yx-head__eyebrow {
display: inline-block;
font-size: 13px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--yx-brand);
margin-bottom: 12px;
}
.yx-head__title { font-size: 34px; font-weight: 700; line-height: 1.25; margin: 0; letter-spacing: -.01em; }
.yx-head__sub { margin: 16px auto 0; color: var(--vp-c-text-2); font-size: 17px; }
.yx-head__sub code, .yx-quick__tip code {
font-size: .85em; padding: 2px 6px; border-radius: 6px;
background: var(--vp-c-bg-alt); color: var(--yx-brand);
}
/* ===== Hero ===== */
.yx-hero { padding: 88px 0 64px; text-align: center; position: relative; overflow: hidden; }
.yx-hero__inner { position: relative; z-index: 1; }
/* 氛围背景:浮动光球 + 网格 mesh */
.yx-ambient { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.yx-orb { position: absolute; border-radius: 50%; filter: blur(70px); will-change: transform; }
.yx-orb--1 {
width: 460px; height: 460px; top: -180px; right: -80px;
background: var(--yx-brand-soft); opacity: .6;
animation: yxOrbFloat 18s ease-in-out infinite;
}
.yx-orb--2 {
width: 380px; height: 380px; top: -120px; left: -120px;
background: var(--yx-lime-soft); opacity: .5;
animation: yxOrbFloat 22s ease-in-out infinite reverse;
}
.yx-orb--3 {
width: 320px; height: 320px; top: 30px; left: 46%;
background: var(--yx-brand-soft); opacity: .5;
animation: yxOrbFloat 26s ease-in-out infinite;
}
.yx-grid {
position: absolute; inset: 0;
background-image:
linear-gradient(to right, var(--vp-c-divider) 1px, transparent 1px),
linear-gradient(to bottom, var(--vp-c-divider) 1px, transparent 1px);
background-size: 60px 60px; opacity: .5;
-webkit-mask-image: radial-gradient(ellipse 75% 60% at 50% 0%, #000, transparent 72%);
mask-image: radial-gradient(ellipse 75% 60% at 50% 0%, #000, transparent 72%);
}
@keyframes yxOrbFloat {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(0, -26px) scale(1.04); }
}
.yx-badge {
display: inline-block; font-size: 13px; font-weight: 500;
padding: 6px 14px; border-radius: 999px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg); color: var(--vp-c-text-2);
margin-bottom: 24px;
}
.yx-hero__title { font-size: 60px; font-weight: 800; line-height: 1.05; margin: 0; letter-spacing: -.03em; }
.yx-hero__subtitle { font-size: 24px; font-weight: 600; margin: 18px 0 0; color: var(--vp-c-text-1); }
.yx-hero__desc { max-width: 640px; margin: 18px auto 0; color: var(--vp-c-text-2); font-size: 17px; }
.yx-hero__actions { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; margin-top: 32px; }
/* 按钮 */
.yx-btn {
display: inline-flex; align-items: center; gap: 6px;
height: 44px; padding: 0 22px; border-radius: 10px;
font-size: 15px; font-weight: 600; cursor: pointer;
transition: transform .2s ease, background-color .2s ease, border-color .2s ease, color .2s ease;
border: 1px solid transparent; text-decoration: none;
}
.yx-btn--primary { background: var(--yx-brand); color: #fff; }
.yx-btn--primary:hover { background: var(--yx-brand-hover); transform: translateY(-1px); }
.yx-btn--ghost { border-color: var(--vp-c-divider); color: var(--vp-c-text-1); background: var(--vp-c-bg); }
.yx-btn--ghost:hover { border-color: var(--yx-brand); color: var(--yx-brand); }
.yx-btn--text { color: var(--vp-c-text-2); }
.yx-btn--text:hover { color: var(--yx-brand); }
.yx-hero__shot { margin-top: 56px; }
.yx-hero__img {
display: block; width: 100%; border-radius: 14px;
border: 1px solid var(--vp-c-divider);
box-shadow: 0 24px 60px -28px rgba(0, 0, 0, .25);
}
/* 占位图 */
.yx-placeholder {
aspect-ratio: 16 / 9; width: 100%;
display: flex; align-items: center; justify-content: center;
border: 1px dashed var(--vp-c-divider); border-radius: 14px;
background:
linear-gradient(var(--vp-c-bg-soft), var(--vp-c-bg-soft)) padding-box,
var(--vp-c-bg);
color: var(--vp-c-text-3); font-size: 14px; letter-spacing: .02em;
}
.yx-placeholder--hero {
border-style: solid;
box-shadow: 0 24px 60px -28px rgba(0, 0, 0, .25);
}
/* ===== 数据条 ===== */
.yx-stats { border-top: 1px solid var(--vp-c-divider); border-bottom: 1px solid var(--vp-c-divider); }
.yx-stats__inner { display: grid; grid-template-columns: repeat(4, 1fr); }
.yx-stat { text-align: center; padding: 32px 16px; }
.yx-stat + .yx-stat { border-left: 1px solid var(--vp-c-divider); }
.yx-stat__value {
font-size: 36px; font-weight: 800; color: var(--yx-brand);
font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.yx-stat__label { margin-top: 6px; font-size: 14px; color: var(--vp-c-text-2); }
/* ===== Bento ===== */
.yx-bento {
display: grid; gap: var(--yx-gap);
grid-template-columns: repeat(3, 1fr);
}
.yx-cap {
border: 1px solid var(--vp-c-divider); border-radius: 16px;
padding: 28px; background: var(--vp-c-bg);
transition: transform .2s ease, border-color .2s ease;
}
.yx-cap:hover { transform: translateY(-3px); border-color: var(--yx-brand); }
.yx-cap--lg { grid-column: span 1; grid-row: span 2; display: flex; flex-direction: column; }
.yx-cap__icon {
display: inline-flex; align-items: center; justify-content: center;
width: 44px; height: 44px; border-radius: 12px;
background: var(--yx-brand-soft); color: var(--yx-brand);
margin-bottom: 18px;
}
.yx-cap__icon svg { width: 22px; height: 22px; }
.yx-cap__title { font-size: 18px; font-weight: 700; margin: 0 0 8px; }
.yx-cap__desc { color: var(--vp-c-text-2); font-size: 14.5px; margin: 0; }
.yx-cap__tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.yx-tag {
font-size: 12px; padding: 3px 10px; border-radius: 999px;
background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.yx-cap__shot-img {
display: block; width: 100%; margin-top: 20px;
border-radius: 12px; border: 1px solid var(--vp-c-divider);
}
/* ===== Split(知识引擎)===== */
.yx-split { display: grid; grid-template-columns: 0.82fr 1.18fr; gap: 48px; align-items: start; }
.yx-split .yx-head__eyebrow { display: inline-block; }
.yx-split .yx-head__title { text-align: left; font-size: 30px; }
.yx-tabs { list-style: none; margin: 28px 0 0; padding: 0; display: grid; gap: 8px; }
.yx-tab {
display: flex; align-items: center; gap: 14px; padding: 12px 14px;
border-radius: 12px; border: 1px solid transparent; cursor: pointer;
transition: background-color .2s ease, border-color .2s ease;
}
.yx-tab:hover { background: var(--vp-c-bg); }
.yx-tab--active { background: var(--vp-c-bg); border-color: var(--vp-c-divider); }
.yx-tab .yx-list__ic { opacity: .55; transition: opacity .2s ease; }
.yx-tab--active .yx-list__ic { opacity: 1; }
.yx-list__ic {
flex: none; width: 38px; height: 38px; border-radius: 10px;
display: inline-flex; align-items: center; justify-content: center;
background: var(--yx-brand-soft); color: var(--yx-brand);
}
.yx-list__ic svg { width: 20px; height: 20px; }
.yx-tab--active .yx-list__ic { background: var(--yx-lime-soft); color: var(--yx-lime-strong); }
.yx-tab__body { min-width: 0; }
.yx-tab strong { display: block; font-size: 16px; }
.yx-tab__desc {
display: grid; grid-template-rows: 0fr;
transition: grid-template-rows .25s ease;
}
.yx-tab--active .yx-tab__desc { grid-template-rows: 1fr; }
.yx-tab__desc p {
overflow: hidden; margin: 0; color: var(--vp-c-text-2); font-size: 14px; line-height: 1.55;
}
.yx-tab--active .yx-tab__desc p { margin-top: 4px; }
/* 知识引擎媒体区(4:3,随 tab 切换) */
.yx-engine-media {
position: relative; width: 100%; aspect-ratio: 4 / 3; margin-top: 40px;
border-radius: 14px; overflow: hidden; border: 1px solid var(--vp-c-divider);
}
.yx-engine-frame { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
img.yx-engine-frame { object-fit: cover; }
.yx-engine-ph {
display: flex; align-items: center; justify-content: center;
background: var(--vp-c-bg-soft); color: var(--vp-c-text-3);
font-size: 14px; letter-spacing: .02em;
}
.yx-fade-enter-active, .yx-fade-leave-active { transition: opacity .25s ease; }
.yx-fade-enter-from, .yx-fade-leave-to { opacity: 0; }
/* ===== 模型供应商墙(跑马灯)===== */
.yx-marquee {
display: flex; flex-direction: column; gap: 18px;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.yx-marquee__row { display: flex; overflow: hidden; }
.yx-marquee__track {
display: flex; gap: 16px; flex: none; width: max-content;
padding-right: 16px;
animation: yxMarquee 42s linear infinite;
}
.yx-marquee__row--rev .yx-marquee__track { animation-direction: reverse; }
.yx-marquee:hover .yx-marquee__track { animation-play-state: paused; }
.yx-mq-item {
display: flex; align-items: center; gap: 10px; flex: none;
padding: 10px 20px; border-radius: 999px;
border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg);
white-space: nowrap;
}
.yx-mq-item img { width: 24px; height: 24px; object-fit: contain; flex: none; }
.yx-mq-item span { font-weight: 600; font-size: 15px; color: var(--vp-c-text-1); }
@keyframes yxMarquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
/* ===== 工作原理 ===== */
.yx-steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--yx-gap); }
.yx-step { position: relative; padding: 28px 24px; border: 1px solid var(--vp-c-divider); border-radius: 16px; background: var(--vp-c-bg); }
.yx-step__n { font-size: 26px; font-weight: 800; color: var(--yx-brand); font-variant-numeric: tabular-nums; }
.yx-step__title { font-size: 17px; font-weight: 700; margin: 10px 0 8px; }
.yx-step__desc { margin: 0; color: var(--vp-c-text-2); font-size: 14px; }
.yx-step__arrow {
position: absolute; right: -16px; top: 50%; transform: translateY(-50%);
color: var(--vp-c-text-3); font-size: 18px; z-index: 1;
}
/* ===== 产品一览 ===== */
.yx-shots { display: grid; grid-template-columns: repeat(2, 1fr); gap: 32px; }
.yx-shot { margin: 0; }
.yx-shot figcaption { margin-top: 14px; }
.yx-shot figcaption strong { font-size: 16px; }
.yx-shot figcaption span { display: block; color: var(--vp-c-text-2); font-size: 14px; margin-top: 2px; }
/* ===== 三卡(企业级 / 场景)===== */
.yx-cards3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--yx-gap); }
.yx-card {
border: 1px solid var(--vp-c-divider); border-radius: 16px;
padding: 28px; background: var(--vp-c-bg);
transition: transform .2s ease, border-color .2s ease;
}
.yx-card:hover { transform: translateY(-3px); border-color: var(--yx-brand); }
.yx-card h3 { font-size: 18px; font-weight: 700; margin: 0 0 8px; }
.yx-card p { margin: 0; color: var(--vp-c-text-2); font-size: 14.5px; }
.yx-card--case { border-left: 3px solid var(--yx-lime); }
/* ===== 技术栈 ===== */
.yx-tech { max-width: 860px; margin: 0 auto; }
.yx-tech__row { display: grid; grid-template-columns: 100px 1fr; gap: 24px; align-items: center; padding: 18px 0; }
.yx-tech__row + .yx-tech__row { border-top: 1px solid var(--vp-c-divider); }
.yx-tech__group { font-weight: 600; color: var(--vp-c-text-2); font-size: 14px; }
.yx-tech__items { display: flex; flex-wrap: wrap; gap: 10px; }
.yx-chip {
font-size: 14px; font-weight: 500; padding: 6px 14px; border-radius: 8px;
background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider);
}
/* ===== 快速开始 ===== */
.yx-quick { max-width: 760px; margin: 0 auto; }
.yx-code {
margin: 0; padding: 24px; border-radius: 14px;
background: #1b1b1f;
border: 1px solid var(--vp-c-divider);
overflow-x: auto; font-size: 14px; line-height: 1.8;
font-family: var(--vp-font-family-mono);
color: #e6e6e6;
}
.yx-c-cmt { color: #6a9955; }
.yx-quick__tip { margin: 18px 0 0; text-align: center; color: var(--vp-c-text-2); font-size: 14px; }
/* ===== 社区 ===== */
.yx-contrib { display: block; max-width: 720px; margin: 0 auto; }
.yx-contrib img { width: 100%; border-radius: 12px; }
.yx-credits { margin: 32px 0 0; color: var(--vp-c-text-2); font-size: 14.5px; }
.yx-credits a { color: var(--yx-brand); text-decoration: none; }
.yx-credits a:hover { text-decoration: underline; }
/* ===== 最终 CTA ===== */
.yx-cta { padding: 96px 0; text-align: center; border-top: 1px solid var(--vp-c-divider); }
.yx-cta__inner h2 { font-size: 34px; font-weight: 800; margin: 0; letter-spacing: -.01em; }
.yx-cta__inner p { margin: 14px 0 0; color: var(--vp-c-text-2); font-size: 17px; }
.yx-cta__actions { margin-top: 28px; }
/* ===== 进场动画 ===== */
.reveal { opacity: 0; transform: translateY(18px); transition: opacity .6s ease, transform .6s ease; }
.reveal.in-view { opacity: 1; transform: none; }
/* ===== 响应式 ===== */
@media (max-width: 1024px) {
.yx-bento { grid-template-columns: repeat(2, 1fr); }
.yx-cap--lg { grid-column: span 2; grid-row: auto; }
}
@media (max-width: 768px) {
.yx-section, .yx-hero, .yx-cta { padding: 64px 0; }
.yx-hero { padding-top: 64px; }
.yx-hero__title { font-size: 44px; }
.yx-hero__subtitle { font-size: 20px; }
.yx-head__title { font-size: 28px; }
.yx-bento, .yx-cards3, .yx-steps, .yx-shots { grid-template-columns: 1fr; }
.yx-cap--lg { grid-column: auto; }
.yx-split { grid-template-columns: 1fr; gap: 32px; }
.yx-split .yx-head__title { text-align: center; }
.yx-split__text { text-align: center; }
.yx-tab { text-align: left; }
.yx-stats__inner { grid-template-columns: repeat(2, 1fr); }
.yx-stat:nth-child(3) { border-left: none; }
.yx-stat:nth-child(odd) { border-left: none; }
.yx-stat:nth-child(even) { border-left: 1px solid var(--vp-c-divider); }
.yx-stat:nth-child(n+3) { border-top: 1px solid var(--vp-c-divider); }
.yx-step__arrow { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.reveal { opacity: 1; transform: none; transition: none; }
.yx-btn:hover, .yx-cap:hover, .yx-card:hover, .yx-provider:hover { transform: none; }
.yx-orb, .yx-marquee__track { animation: none; }
}
</style>
+44
View File
@@ -0,0 +1,44 @@
/* 导入项目的颜色变量 */
@import url('../../../web/src/assets/css/base.css');
@import url('../../../web/src/assets/css/base.dark.css');
/* VitePress 主题色覆盖 */
:root {
/* 覆盖 VitePress 的主题色,使用项目中定义的 main-color */
--vp-c-brand-1: var(--main-color);
--vp-c-brand-2: var(--main-bright);
--vp-c-brand-3: var(--main-700);
--vp-c-brand-4: var(--main-800);
--vp-c-brand-5: var(--main-900);
--vp-c-brand-6: var(--main-1000);
/* 浅色模式下的文本颜色 */
--vp-c-brand-light: var(--main-color);
--vp-c-brand-lighter: var(--main-bright);
--vp-c-brand-lightest: var(--main-400);
/* 深色模式下的文本颜色 */
--vp-c-brand-dark: var(--main-300);
--vp-c-brand-darker: var(--main-200);
--vp-c-brand-darkest: var(--main-100);
}
/* 深色模式覆盖 */
.dark {
--vp-c-brand-1: var(--main-color);
--vp-c-brand-2: var(--main-bright);
--vp-c-brand-3: var(--main-700);
--vp-c-brand-4: var(--main-800);
--vp-c-brand-5: var(--main-900);
--vp-c-brand-6: var(--main-1000);
/* 浅色模式下的文本颜色 */
--vp-c-brand-light: var(--main-color);
--vp-c-brand-lighter: var(--main-bright);
--vp-c-brand-lightest: var(--main-400);
/* 深色模式下的文本颜色 */
--vp-c-brand-dark: var(--main-300);
--vp-c-brand-darker: var(--main-200);
--vp-c-brand-darkest: var(--main-100);
}
+15
View File
@@ -0,0 +1,15 @@
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import YuxiHome from './components/YuxiHome.vue'
import './custom.css'
export default {
extends: DefaultTheme,
// 在首页(layout: home)的最顶部注入完全自定义的官网首页,
// 同时保留 VitePress 的顶部导航、搜索、暗黑切换与页脚。
Layout() {
return h(DefaultTheme.Layout, null, {
'home-hero-before': () => h(YuxiHome)
})
}
}
+192
View File
@@ -0,0 +1,192 @@
# 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)只会显示一次,务必在创建时将其安全保存。如果遗失,需要通过"重新生成"功能生成新的密钥,原有的密钥将立即失效。
管理接口同样走通用认证:
- `GET /api/user/apikey/`:列出当前用户可见的 API Key
- `POST /api/user/apikey/`:创建 API Key
- `PUT /api/user/apikey/{api_key_id}`:更新名称、状态或过期时间
- `POST /api/user/apikey/{api_key_id}/regenerate`:重新生成密钥
- `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 调用示例:
```python
import json
import requests
base_url = "https://your-yuxi-server" # 生产环境务必使用 HTTPS;本地开发可改为 http://localhost:5050
headers = {
"Authorization": "Bearer yxkey_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
}
thread_resp = requests.post(
f"{base_url}/api/chat/thread",
headers=headers,
json={
"agent_id": "default-chatbot",
"title": "外部系统会话",
"metadata": {},
},
)
thread_resp.raise_for_status()
thread_id = thread_resp.json()["id"]
run_resp = requests.post(
f"{base_url}/api/agent/runs",
headers=headers,
json={
"query": "你好,请介绍一下你自己",
"agent_slug": "default-chatbot",
"thread_id": thread_id,
"meta": {"request_id": "external-request-001"},
},
)
run_resp.raise_for_status()
run = run_resp.json()
with requests.get(f"{base_url}{run['stream_url']}", headers=headers, stream=True) as response:
response.raise_for_status()
event_type = None
data_lines = []
for line in response.iter_lines(decode_unicode=True):
if line is None:
continue
if line.startswith(":"):
continue
if line == "":
if event_type and data_lines:
payload = json.loads("\n".join(data_lines))
print(event_type, payload)
if event_type == "end":
break
event_type = None
data_lines = []
continue
if line.startswith("event:"):
event_type = line.removeprefix("event:").strip()
elif line.startswith("data:"):
data_lines.append(line.removeprefix("data:").strip())
```
如果已经有会话线程,可以复用已有 `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>` 格式,其中 API Key 必须以 `yxkey_` 前缀开头
2. **JWT Token 认证**:使用 `Authorization: Bearer <jwt_token>` 格式
系统根据 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 的操作中,因此务必妥善保管。
## 常见问题
**Q: API Key 认证失败返回什么错误?**
A: 认证失败时返回 401 Unauthorized 错误,错误信息为"无效的凭证"。请检查请求头中 `Authorization` 字段的格式是否正确,是否包含完整的密钥,且密钥必须以 `yxkey_` 开头。
**Q: 可以同时使用 API Key 和 JWT Token 吗?**
A: 不可以。系统根据 token 前缀自动判断认证方式。以 `yxkey_` 开头的 token 使用 API Key 认证,其他 token 使用 JWT 认证。
**Q: API Key 是否有调用频率限制?**
A: 目前没有单独的频率限制,但 API Key 的行为等同于其绑定的用户身份,因此会受到用户角色相关的一些限制。
**Q: 对话返回的内容是乱码怎么办?**
A: 确保客户端正确处理了 UTF-8 编码。流式响应中可能包含中文字符,需要使用正确的编码方式解析。如果在终端显示乱码,可以检查终端的编码设置。
+86
View File
@@ -0,0 +1,86 @@
# 品牌自定义
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` 也需要同步修改。
+37
View File
@@ -0,0 +1,37 @@
# 配置系统详解
## 概述
系统采用多层配置架构,模型配置由网页界面管理,应用配置基于 Pydantic + TOML。
## 配置层级
```
代码默认值 → TOML 文件 → 环境变量
(低) (高)
```
## 模型配置
由网页统一管理,详见 [模型配置](../intro/model-config.md)。
## 应用配置
配置项定义于 `backend/package/yuxi/config/app.py`,用户修改保存至 `saves/config/base.toml`
### 修改配置
```python
from yuxi.config import config
config.default_model = "provider-id:model-id"
config.save()
```
配置会在保存 `base.toml` 后写入 Redis 快照(`yuxi:runtime_config`)。快照包含可运行时同步的公开配置字段,不包含 `_` 开头的内部属性和 `save_dir`API/worker 进程在启动时各拉起一个后台同步线程,按 5 秒间隔从该快照刷新内存值,读取端无需感知。Redis 不可用时继续使用当前内存值。
`save_dir` 是启动期内部路径配置,不在管理员配置中展示,也不支持通过管理员配置接口、`base.toml` 或运行时 Redis 快照修改。sandbox 相关配置仍属于启动期敏感配置,运行中的已初始化组件不承诺完整热更新,修改后需要重启服务保证生效。
## 常见问题
**配置文件损坏**:删除 `saves/config/base.toml`,系统将重新生成默认配置。
+83
View File
@@ -0,0 +1,83 @@
# 生产部署指南
本文档介绍如何在生产环境中部署 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 密钥:
- `NEO4J_PASSWORD`:修改默认密码
- `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY`:修改默认密钥
- `SILICONFLOW_API_KEY` 等模型密钥
### 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`
## 跨域(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`,否则前端跨域请求会被拒绝。
## 维护与更新
### 更新代码
```bash
# 拉取最新代码
git pull
# 重新构建并启动
docker compose -f docker-compose.prod.yml up -d --build
```
### 查看日志
```bash
# API 日志
docker logs -f api-prod
# Nginx 访问日志
docker logs -f web-prod
```
+152
View File
@@ -0,0 +1,152 @@
# 文档处理与 OCR
Yuxi 支持多种文档格式的智能解析,从简单的文本文件到复杂的 PDF 文档,都能自动提取内容并转换为可检索的格式。
## 支持的文件类型
### 常规文档
| 类型 | 格式 | 说明 |
|------|------|------|
| 文本 | .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 | 智能理解 | 无 | 云端服务,Markdown 输出 |
| PaddleOCR-VL-1.6 | 复杂文档、表格、图片 PDF | 无 | 百度 AI Studio 云端服务,输出 Markdown |
| PP-OCRv6 | 基础文字识别 | 无 | 百度 AI Studio 云端 OCR,输出纯文本 |
### 选择建议
- **个人使用或 CPU 环境**:选择 RapidOCR,免费且资源占用低
- **高精度需求**:选择 MinerU(需要 GPU)或 MinerU Official
- **表格密集型文档**:选择 PP-Structure-V3
- **云端版面解析**:选择 PaddleOCR-VL-1.6,适合希望输出 Markdown 的 PDF 或图片文档
- **云端纯文字识别**:选择 PP-OCRv6,适合只需要提取图片文字的场景
- **简单云服务**:选择 DeepSeek OCR 或 PaddleOCR API
## 快速配置
### 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(简单云服务)
在 .env 配置(使用已有的 SiliconFlow 密钥)
```env
SILICONFLOW_API_KEY=your-api-key-here
```
### 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,返回按行拼接的纯文本
## 图片显示配置
上传文档中的图片需要正确配置才能在外部显示:
`.env` 中设置服务器 IP
```
HOST_IP=your_server_ip
```
## 注意事项
1. **图片文件必须启用 OCR**:否则无法提取内容
2. **GPU 要求**MinerU 和 PP-Structure-V3 需要 GPU 支持
3. **API 密钥**MinerU Official、DeepSeek OCR、PaddleOCR API 等云服务需要额外的 API 密钥或 Access Token 配置
4. **超时处理**:复杂文档解析可能耗时较长,可通过 `MINERU_TIMEOUT` 环境变量调整超时时间
5. **文件大小限制**:单个上传文件大小不超过 100 MB
+37
View File
@@ -0,0 +1,37 @@
# 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 的定位、接入方式和查看路径,而不是一次性覆盖所有更复杂的高级功能。对于大多数项目来说,先把“能看清每轮智能体执行发生了什么”这件事做好,已经能显著改善调试和运维体验。
+83
View File
@@ -0,0 +1,83 @@
# 其他配置
本文档介绍 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
+103
View File
@@ -0,0 +1,103 @@
# 第三方登录认证
Yuxi 支持以OIDC接入第三方登录认证,方便企业用户集成现有的身份认证系统。
> 此功能默认关闭,需要在配置文件中启用并提供相关参数。
## 配置步骤
### 1. 前提条件
在你的SSO系统中注册一个新的客户端应用,获取以下信息:
- 客户端IDClient ID
- 客户端密钥(Client Secret
- ISSUER URL
填入回调地址(Redirect URI):https://<your_yuxi_host>/api/auth/oidc/callback
### 2. 配置Yuxi
在Yuxi的.env文件中添加以下配置项:
```sh
# 是否启用 OIDC 认证 (true/false)
# OIDC_ENABLED=false
# 认证源名称(显示在登录按钮上的文字,建议简短且具有辨识度, 默认: OIDC登录)
# OIDC_PROVIDER_NAME="OIDC登录"
# OIDC Provider 的 Issuer URL (例如: https://auth.example.com)
# OIDC_ISSUER_URL=
# OIDC Client ID
# OIDC_CLIENT_ID=
# OIDC Client Secret
# OIDC_CLIENT_SECRET=
# OIDC 回调 URL (可选,默认自动构建为 /api/auth/oidc/callback, 不建议自定义)
# 填写完整的地址:https://<your_yuxi_host>/api/auth/oidc/callback
# 需要确保此 URL 在 OIDC Provider 中已注册
# OIDC_REDIRECT_URI=
# 授权端点 (可选,自动从 discovery 获取)
# OIDC_AUTHORIZATION_ENDPOINT=
# Token 端点 (可选,自动从 discovery 获取)
# OIDC_TOKEN_ENDPOINT=
# UserInfo 端点 (可选,自动从 discovery 获取)
# OIDC_USERINFO_ENDPOINT=
# 登出端点 (可选,自动从 discovery 获取)
# OIDC_END_SESSION_ENDPOINT=
# 请求的 scope (默认: openid profile email)
# OIDC_SCOPES=openid profile email
# 是否自动创建用户 (true/false,默认: true)
# OIDC_AUTO_CREATE_USER=true
# OIDC 用户的默认角色 (user/admin,默认: user)
# OIDC_DEFAULT_ROLE=user
# OIDC 用户的默认部门名称 (默认: OIDC用户)
# OIDC_DEFAULT_DEPARTMENT=OIDC用户
# 用户名映射字段 (默认: preferred_username)
# OIDC_USERNAME_CLAIM=preferred_username
# 邮箱映射字段 (默认: email)
# OIDC_EMAIL_CLAIM=email
# 姓名映射字段 (默认: name)
# OIDC_NAME_CLAIM=name
# 是否使用原始用户名(不带 oidc: 前缀),允许映射到 Yuxi 已有的本地账号 (true/false,默认: false)
# 开启后,OIDC 返回的 username 会直接作为业务登录标识 uid 登录,需要管理员提前创建好用户账号
# OIDC_USE_RAW_USERNAME=false
# 是否从OIDC userinfo 中获取部门信息并自动创建关联部门 (true/false,默认: false)
# OIDC_FETCH_DEPARTMENT_INFO=false
# 部门名称字段映射 (默认: department)
# OIDC_DEPARTMENT_CLAIM=department
# OIDC 登录时是否强制提示用户重新登录 (添加 prompt=login 参数,true/false,默认: true)
# OIDC_FORCE_PROMPT_LOGIN=true
```
### 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` 默认部门
+88
View File
@@ -0,0 +1,88 @@
# 智能体评估
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=<dataset name>`
- `evaluation_dataset_item_id=<item id>`
- `evaluation_experiment_name=<experiment name>`
如果没有看到 experiment,先确认 CLI 环境中的 Langfuse key 和 dataset name 是否正确。如果 experiment 有记录但 Yuxi trace 缺失,检查 `api-dev` 容器是否读取到了同一组 Langfuse 配置。
+336
View File
@@ -0,0 +1,336 @@
# 智能体配置
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` | 单次运行最大执行步数 |
| `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`。当 Agent 开始执行时,后端会读取当前用户工作区下的这个文件,并将其内容追加到 `system_prompt`,用于补充该用户对 Agent 的长期指令或工作区约定。该文件属于用户级共享工作区,内容会随 `uid` 和当前运行的线程作用域映射到运行时工作区路径;文件不存在、为空或不可读时不会影响 Agent 启动,单次注入内容最多读取 64 KiB,超出部分会截断并追加提示。
合并后的提示词结构可以理解为:
```text
Agent.config_json.context.system_prompt
+ 用户工作区 agents/AGENTS.md 内容
+ 运行期中间件继续追加的系统提示段
```
因此,`agents/AGENTS.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``/home/gem/skills` 和沙盒可读的 Skill 闭包
随后 Graph 构建会直接使用这份 Context:
- `load_chat_model(context.model)` 选择主模型
- `build_prompt_with_context(context)` 生成系统提示词
- `resolve_configured_runtime_tools(context)` 组装已配置的内置工具和 MCP 工具
- `KnowledgeBaseMiddleware` 根据 `_visible_knowledge_bases` 暴露知识库工具
- `SkillsMiddleware` 根据 `_prompt_skills` 注入 Skill 提示段,并在 Skill 被激活后按需挂载工具与 MCP 依赖
- `save_attachments_to_fs` 将线程附件转换为运行时可读的文件提示
文件系统与沙盒接入同样读取这些运行时字段:
- 普通 Agent 默认使用当前 `thread_id` 作为文件与 Skills 作用域
- 子智能体使用 child `thread_id` 做 checkpoint`file_thread_id` 指向父会话 uploads/outputs`skills_thread_id` 指向子智能体自身 Skills 作用域
- 通过 `_readable_skills` 决定 `/home/gem/skills` 的可读范围
所以 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)
+51
View File
@@ -0,0 +1,51 @@
# MCP 集成
MCPModel Context Protocol)是扩展智能体能力的重要方式。系统支持通过管理界面动态配置 MCP 服务器,无需修改代码。
内置 MCP 服务器以代码为事实源:系统启动时会自动补齐缺失项,并用代码中的最新连接与展示字段覆盖数据库定义;是否“已添加”以及工具级禁用列表仍保留数据库状态。
## 支持的传输协议
| 协议 | 说明 | 适用场景 |
|------|------|----------|
| Streamable HTTP | 流式 HTTP 连接 | 远程 MCP 服务 |
| SSE | Server-Sent Events | 标准 HTTP 长连接 |
| Stdio | 标准输入输出 | 本地进程 |
## 配置示例
### 远程 MCP 服务
```json
{
"name": "custom-remote-mcp",
"transport": "streamable_http",
"url": "https://example.com/mcp"
}
```
### 本地 Python 进程
```json
{
"name": "mysql-mcp-server",
"transport": "stdio",
"command": "uvx",
"args": ["mysql_mcp_server"],
"env": {
"MYSQL_HOST": "localhost",
"MYSQL_DATABASE": "your_database"
}
}
```
## 服务器管理
管理界面使用“添加 / 移除”语义管理 MCP 服务器:
- 已添加:`enabled=true`,会加载到运行时缓存并可供 Agent 使用
- 可添加:`enabled=false`,记录保留但不会进入运行时
## 工具管理
MCP 工具支持粒度控制:管理员可以单独启用或禁用某个 MCP 服务器下的特定工具,实现精细化的权限管理。
+97
View File
@@ -0,0 +1,97 @@
# 中间件系统
中间件是 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`
- `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` | 在模型调用失败时按配置重试 |
| `TokenUsageMiddleware` | 在 LangGraph state 写入本轮 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/<slug>/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 的计费口径、累计口径或异常上报导致短对话过早压缩。
触发后,中间件先执行 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 压力。
未达到入口阈值的常规模型调用不会额外清洗工具结果;达到入口阈值但 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` 承担。新增功能时不要默认复用旧的动态工具中间件,除非确实需要“预注册后按请求筛选”的模式。
+306
View File
@@ -0,0 +1,306 @@
# 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_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-<id>` 的形式,并在 Docker 网络中启动真正的沙盒镜像。
这个沙盒镜像默认来自 `SANDBOX_IMAGE`,容器内部监听的端口默认是 `8080`。provisioner 在启动容器时,会把这个端口随机映射到宿主机上的一个可用端口,再用 `DOCKER_SANDBOX_HOST` 拼出形如 `http://host.docker.internal:<random_port>` 的访问地址。Yuxi 后端拿到的就是这个地址。
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``DOCKER_THREADS_HOST_PATH``DOCKER_SANDBOX_PREFIX``DOCKER_SANDBOX_HOST`
- 容器代理变量:`HTTP_PROXY``HTTPS_PROXY``NO_PROXY`
其中 `DOCKER_SANDBOX_HOST` 只在 Docker 后端下用于拼接返回给 API 的 `sandbox_url``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/<uid>/workspace` 挂到 `/home/gem/user-data/workspace`,把 `threads/<file_thread_id>/user-data/uploads``threads/<file_thread_id>/user-data/outputs` 分别挂到 uploads/outputs,把 `threads/<skills_thread_id>/skills` 挂到 `/home/gem/skills`。这样做的好处是目录结构仍然可以和 Docker 模式保持一致,同时允许子智能体共享父对话文件但隔离 skills。
需要特别说明的是,代码里虽然读取了 `SKILLS_PVC` 这个环境变量,但当前 Pod 规格实际没有使用单独的 skills PVC,而是统一从 `THREAD_PVC` 中切 `threads/<thread_id>/skills` 这个子路径。因此,如果看到环境变量里同时出现 `SKILLS_PVC``THREAD_PVC`,应当以 `THREAD_PVC` 的真实挂载语义为准,`SKILLS_PVC` 目前更像一个预留字段。
Kubernetes 后端还需要一个 `NODE_HOST`。这是因为当前实现使用的是 NodePort Service,而不是 Ingress,也不是 ClusterIP。provisioner 创建完 Service 之后,会把最终访问地址拼成 `http://<NODE_HOST>:<nodePort>` 返回给 Yuxi 后端。所以 `NODE_HOST` 必须是 Yuxi 后端能够访问到的 Kubernetes 节点地址、负载均衡地址或者对 NodePort 做了透出的外部域名。
当前 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/
│ ├── <skill-slug>/
│ └── ...
├── threads/
│ ├── <thread_id>/
│ │ ├── user-data/
│ │ │ ├── uploads/
│ │ │ ├── outputs/
│ │ │ └── ...
│ │ └── skills/
│ │ ├── <skill-slug>/
│ │ └── ...
│ ├── shared/
│ │ └── <uid>/
│ │ └── workspace/
│ └── ...
```
这里要重点理解 `workspace``uploads/outputs` 的区别。按照当前宿主机路径解析逻辑,`workspace` 被定义为用户级共享目录,位置是 `saves/threads/shared/<uid>/workspace`;而 `uploads``outputs` 属于文件线程目录,位置分别是 `saves/threads/<file_thread_id>/user-data/uploads``saves/threads/<file_thread_id>/user-data/outputs`。普通 Agent 的 `file_thread_id` 就是当前对话 `thread_id`,子智能体运行时则使用父对话作为 `file_thread_id`,因此可以读取父对话附件并把产物写回父对话 outputs。
与此同时,运行时 provisioner 在创建 Docker 容器或 Kubernetes Pod 时,会把用户级 `saves/threads/shared/<uid>/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` 是只读目录。它不是简单地把 `saves/skills` 整个暴露进去,而是先根据当前运行时的 `_readable_skills`,把这些技能从全局 skills 根目录同步复制到 `saves/threads/<skills_thread_id>/skills`,再把这个 skills 线程目录只读挂进沙盒。这样做的结果是,不同主/子 Agent 看到的 skill 集可能不同,而且模型永远不能在运行时修改 skills 内容。
知识库访问不属于沙盒文件系统暴露规则。当前 Agent 可见知识库仍由用户权限和 Agent 配置共同决定,但只通过 `query_kb``open_kb_document` 等工具访问,不提供沙盒目录投影。
## 十、skills、知识库、附件是怎么和沙盒结合的
skills 的结合方式分成两层。第一层是提示词层,`prepare_agent_runtime_context` 会先根据当前 Agent 配置的 `context.skills` 展开依赖闭包,`SkillsMiddleware` 再把 `_prompt_skills` 注入到系统提示里,让模型知道哪些 skill 存在、它们的入口文件一般在 `/home/gem/skills/<slug>/SKILL.md`。第二层是文件系统层,运行时会调用 `sync_thread_readable_skills`,把 `_readable_skills` 对应的 skill 目录复制到当前 `skills_thread_id``saves/threads/<skills_thread_id>/skills` 下,再由沙盒只读挂载到 `/home/gem/skills`。也就是说,skill 既是 prompt 中的能力说明,也是文件系统中的只读知识目录。
附件的结合方式更偏向“先落盘,再把路径告诉模型”。用户上传文件后,系统会先把原始文件写入 `saves/threads/<file_thread_id>/user-data/uploads`。如果该文件可以被解析,系统还会额外生成一个 Markdown 副本,写到 `saves/threads/<file_thread_id>/user-data/uploads/attachments/<name>.md`。普通 Agent 的文件线程就是当前对话线程;子智能体沿用父对话文件线程,所以能访问父对话附件。随后,LangGraph state 中会维护一份 `uploads` 列表,`AttachmentMiddleware` 会把这些可读路径注入系统提示,告诉模型优先用 `read_file` 去读取这些路径。因此,附件并不是“作为消息大段内联塞给模型”,而是被转换成沙盒文件系统中的路径对象。
知识库不再与沙盒文件系统结合。它不会被复制到每个线程目录,也不会生成虚拟目录;模型通过专门的知识库工具检索,并在需要更完整上下文时用 `open_kb_document``resource_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_BACKEND=docker
SANDBOX_VIRTUAL_PATH_PREFIX=/home/gem/user-data
SANDBOX_DOCKER_SANDBOX_HOST=host.docker.internal
```
然后用常规方式启动即可:
```bash
docker compose up -d
curl http://localhost:8002/health
```
如果健康检查返回 `backend: docker`,就说明 provisioner 已经处于默认的 Docker 本机后端。真正的沙盒容器不会在系统启动时立即全部出现,而是在你第一次创建线程并触发需要文件系统或命令执行的操作后才会被创建。
如果运行在 Linux,而不是 Docker Desktop,那么 `host.docker.internal` 不一定总是可用。这时要把 `SANDBOX_DOCKER_SANDBOX_HOST` 改成一个从 `api` 容器可达的宿主机地址,或者改成当前网络环境里更稳定的名字。否则 provisioner 虽然能成功起容器,但后端可能拿到一个自己无法访问的 `sandbox_url`
## 十二、如何理解文件管理与暴露边界
从产品行为上看,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_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` | Docker 网络名称 | `yuxi-know_app-network` |
| `DOCKER_SANDBOX_PREFIX` | 沙盒容器名前缀 | `yuxi-sandbox` |
| `DOCKER_SANDBOX_HOST` | 宿主机访问地址 | `host.docker.internal` |
| `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 │
└────────────┬───────────────────┘
↓ HTTP 调用
┌────────────────────────────────┐
│ sandbox-provisioner 服务 │ 沙盒层变量 (PROVISIONER_BACKEND, DOCKER_*, K8S_*)
│ PROVISIONER_BACKEND │
└────────────┬───────────────────┘
↓ Docker API / K8s API
┌────────────────────────────────┐
│ 动态创建的沙盒容器 │
└────────────────────────────────┘
```
两层变量不要混看。改了 `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` 文件中。
### 配置方式汇总
| 配置目标 | 配置位置 | 示例变量 |
|----------|----------|----------|
| 应用层连接 provisioner | `.env` 或 compose 环境 | `SANDBOX_PROVISIONER_URL` |
| 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 地址不可达,重点检查 `SANDBOX_DOCKER_SANDBOX_HOST` 和随机映射端口是否从 `api` 容器可访问。可以在 `api` 容器内直接 `curl` provisioner 返回的 `sandbox_url`。如果怀疑是 Kubernetes 地址不可达,重点检查 `NODE_HOST` 和 NodePort 的外部连通性,因为当前实现并不是通过集群内部 Service 名称回连。
如果怀疑是文件看得到但模型读不到,或者模型写了但 viewer 看不到,优先把问题拆成两层:一层是宿主机路径是否存在于 `saves/...` 下,另一层是该路径是否真的被当前线程沙盒挂载并暴露到了 `/home/gem/user-data``/home/gem/skills`。只要先分清“宿主机侧文件语义”和“沙盒侧运行时挂载语义”,定位问题通常会快很多。
+350
View File
@@ -0,0 +1,350 @@
# Skills 管理系统
Skills 是 Yuxi 系统中用于扩展 Agent 能力的重要机制。通过 Skills,开发者可以将特定的工具、提示词模板或领域知识打包成可复用的技能包,让 Agent 在对话过程中能够调用这些额外能力。
## 为什么需要 Skills
在实际业务场景中,我们常常会遇到一些特定的需求:比如需要 Agent 能够查询特定的 API、调用某个外部服务、或者使用特定的提示词模板来完成特定任务。传统的做法是在代码中硬编码这些功能,但这样会导致系统变得越来越臃肿,且难以复用。
Skills 系统的设计理念就是将这类"可插拔"的能力封装成独立的技能包。每个 Skill 包含完整的实现文件和元数据,Agent 可以根据配置动态加载所需的技能,实现能力的灵活组合。
## 架构设计
Skills 系统采用「文件系统存内容,数据库存索引」的分离架构:
```
┌─────────────────────────────────────────────────────────────┐
│ 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... │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 存储结构
- **文件系统**`/app/saves/skills` 目录下,每个 Skill 占用一个子目录
- **数据库索引**`skills` 表存储元数据(slug、name、description、来源、共享范围、启用状态、依赖关系等)
- **关联机制**:通过 `dir_path` 字段关联文件系统目录与数据库记录
::: tip 不能直接在文件系统创建
由于 Skills 的元数据需要写入数据库,因此不能直接在文件系统中创建 Skill。必须通过系统的导入或安装功能来完成,系统会自动处理数据库记录的创建。
:::
## 创建方式
系统提供以下方式创建或安装 Skills:
1. **推荐 Skill 安装**:在 Skills 管理页的推荐分组点击 `+`,系统会拉取对应远程来源并生成安装草稿
2. **ZIP / SKILL.md 上传**:上传后先解析为安装草稿,确认共享范围后再写入正式 Skills 存储和数据库
3. **远程仓库安装**:填写 skills 仓库地址、ModelScope Skill 地址或合集地址,后端调用 `npx skills` 下载并解析为安装草稿,确认后导入系统
4. **在线编辑**:对已有且可管理的 Skill 在线创建目录、编辑文件和维护依赖
5. **Agent 内安装**:主智能体可通过 `install_skill` 工具,从沙盒路径或 Git 来源安装当前用户私有 Skill;子智能体禁用该工具
不建议直接操作数据库或文件系统导入 Skill。直接写文件不会自动生成 `skills` 表记录,也无法参与权限、依赖和沙盒挂载。
## 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 提示词模板
- **自定义开发**:根据业务需求自行开发
## 快速开始
### 创建你的第一个 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 不会继续显示在「推荐」分组中。
**方式二:通过 ZIP 包或 SKILL.md 上传**
1. 将 Skill 目录打包成 ZIP 文件(注意:ZIP 的根目录就是 Skill 目录)
2. 在系统设置的「Skills 管理」页面,点击「上传 Skill」
3. 上传 ZIP 文件或单个 `SKILL.md`
4. 系统解析上传内容并返回安装草稿
5. 确认共享范围后完成安装;也可以放弃草稿
系统会自动:
- 校验 ZIP 内容和路径安全性
- 检查 slug 冲突(如有冲突会自动追加 `-v2` 等后缀)
- 解析 SKILL.md 的 frontmatter 并存储到数据库
- 按当前用户角色校验可选择的共享范围
**方式三:从远程来源安装**
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. 点击“解析并确认”,系统返回安装草稿,确认共享范围后正式安装
也可以切换到“全局搜索发现”,输入关键字检索 skills.sh 上的开源 Skills,再选择结果安装。
系统会在后端:
- 调用 `npx skills add <source> --list` 校验来源并发现可安装的 skills
- 使用隔离的临时 `HOME` 执行 `npx skills add <source> --skill <name> -g -y --copy`
- 从临时目录中提取对应 skill,再按现有导入流程生成草稿;确认后写入 `/app/saves/skills` 与数据库
::: tip ModelScope 合集适合批量安装
ModelScope 合集地址可以作为远程来源填写,例如 `https://modelscope.cn/collections/MiniMax/MiniMax-Office-skills`。拉取后在列表中勾选需要的 Skills,再统一解析为安装草稿。
:::
**方式四:在线编辑已有 Skill**
在 Skills 管理页面,你可以:
- 新建目录或文件
- 在线编辑文本文件(支持 .md、.py、.js、.json 等格式)
- 直接在网页上修改 SKILL.md 内容
只有具备 `can_manage` 权限的用户才能编辑文件、依赖、共享范围和启用状态。
::: tip 远程安装不会把 ~/.agents/skills 作为系统主存储
远程安装只把 `skills.sh` CLI 作为“下载器”使用。Yuxi 仍然以 `/app/saves/skills + skills 表` 作为正式来源,这样才能与现有的权限、线程可见性和沙盒挂载机制保持一致。
:::
## 依赖系统
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 就会立即进入提示词和沙盒 `/home/gem/skills` 只读范围。
**阶段二:技能激活**
当 Agent 通过 `read_file` 工具读取 `/home/gem/skills/<slug>/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` 控制来源、共享范围和启用状态。
| 字段 | 说明 |
|------|------|
| `source_type` | `builtin``upload``remote` |
| `share_config.access_level` | `global``department``user` |
| `enabled` | 是否允许在 Agent 配置与运行时使用 |
访问与管理规则:
| 用户 | 可见 / 可用 | 可管理 |
|------|-------------|--------|
| 超级管理员 / 管理员 | 可查看可管理或已启用且可访问的 Skills | 可管理所有非内置 Skills;可启停内置 Skills |
| 普通用户 | 可查看已启用且对自己可访问的 Skills,也可安装自己的私有 Skill | 可管理自己创建的非内置 Skills |
| 内置 Skills | 默认全局共享并启用 | 管理员可启停;不允许删除或直接编辑文件 |
共享范围限制:
- `global`:所有用户可访问
- `department`:指定部门用户可访问
- `user`:指定用户可访问;普通用户安装时只能选择个人范围
管理员和普通用户在创建或编辑 Agent 时,都只能从自己可访问且启用的 Skills 中选择能力。
## 运行时行为
### Agent 如何使用 Skills
1. **提示词注入**:系统在每次模型请求时动态注入可用 Skills 的描述(请求级注入,避免污染 runtime context
2. **文件访问**Skills 目录以只读方式挂载到 `/home/gem/skills/<slug>/...`
3. **工具调用**:当 Agent 需要使用某个 Skill 时,会先读取对应的 SKILL.md 了解使用方法
### 文件操作限制
运行时 `/home/gem/skills` 路径有以下限制:
- **只读**:Agent 只能读取文件内容
- **禁止写入**:不能创建、修改或删除文件
- **路径安全**:所有路径都经过安全校验,防止目录穿越攻击
::: tip 虚拟文件系统限制
当前 Skills 目录挂载为虚拟文件系统,**不支持 shell 命令执行**。Skill 中的脚本仅作为提示词参考,Agent 无法直接执行这些脚本。如果需要执行特定功能,建议通过 MCP 工具或自定义工具实现。
:::
### 会话隔离
每个 Agent 会话都有独立的 Skills 可见集:
- 不同会话可以配置不同的 Skills
- 同一会话内修改 `context.skills` 会触发快照重建
- 后台修改 Skills 内容后,已有会话不会自动刷新
## 最佳实践
### Skill 命名规范
- `slug` 使用小写字母、数字和短横线,不能连续短横线
- `slug` 应具有描述性,如 `weather-query``sql-reporter`
- `name` 用于展示,可比 `slug` 更自然,例如 `Word / DOCX`
- 避免过长的 `name``slug`
### 依赖管理建议
- **保持依赖链简洁**:层级不宜过深,一般 1-2 层为宜
- **避免循环依赖**:系统会检测并阻止循环依赖
- **明确依赖必要性**:只在真正需要共享能力时才建立依赖
### SKILL.md 编写技巧
```markdown
---
name: example-skill
description: 简短描述技能功能
---
# 技能名称
这里是详细的使用说明...
## 何时使用
描述在什么场景下应该使用这个技能...
## 使用方法
1. 第一步...
2. 第二步...
## 示例
```
具体的使用示例...
```
```
## 常见问题
**Q:为什么我配置的 Skill 没有生效?**
A:请检查以下几点:
1. Skill 的 slug 是否正确配置在 Agent 的 `context.skills`
2. SKILL.md 是否存在且 frontmatter 格式正确
3. 如果使用了依赖,确保依赖链完整
**Q:如何更新已导入的 Skill**
A:可以通过以下方式:
1. 导出当前 Skill,修改后重新导入
2. 在 Skills 管理页面在线编辑文件
3. 远程来源的 Skill 可重新解析并确认安装,形成新的导入结果
**QSkill 依赖的工具/MCP 不存在怎么办?**
A:系统会在保存依赖配置时进行校验,如果引用的工具或 MCP 不存在,会报错并阻止保存。
---
通过 Skills 机制,Yuxi 为 Agent 提供了一个灵活、可扩展的能力扩展框架。你可以将自己积累的业务知识、工具能力封装成 Skills,让不同的 Agent 复用这些能力,极大地提升了系统的可维护性和复用性。
+140
View File
@@ -0,0 +1,140 @@
# 子智能体
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,例如 `<parent_thread_id>_sub_<slug>_<uuid8>`;续跑任务会校验并复用传入的 `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_events` | 按运行 Redis 流游标读取增量事件 | `run_id`、可选 `after_seq`(默认 `0-0`)、`limit`1-50 |
| `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_events``subagent_cancel``subagent_await` 都按 `run_id` 操作,并校验该 run 是否归属当前父 run 创建的子智能体,避免越权访问其它子任务。
- 父智能体不应通过 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。
## 常见问题
### 为什么创建了子智能体,主 Agent 仍不会调用?
主 Agent 只会调用当前用户可访问的子智能体。如果主 Agent 显式保存了子智能体允许列表,新建子智能体需要被加入该列表;未显式配置或空列表会使用当前用户可见的全部子智能体。
### 为什么聊天 Agent 列表里看不到子智能体?
这是预期行为。子智能体是被主 Agent 调用的后端配置,不是直接进入聊天的 Agent;管理页会使用包含子智能体的列表。
### 子智能体能否继承主 Agent 的模型或工具?
子智能体运行时使用自己的 Agent 配置。确实需要一致时,应在子智能体配置中显式选择相同模型、工具或 Skills;运行时只继承必要的父会话作用域,例如 uploads/outputs。
+96
View File
@@ -0,0 +1,96 @@
# 工具系统
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``mysql``debug`
- **tags**: 标签列表,用于前端展示
- **display_name**: 显示名称(给人看的名字)
- **icon**: 图标名称(可选)
### 自动发现
导入 `toolkits` 包时会自动触发注册:
```python
from yuxi.agents.toolkits import buildin, mysql # 触发 @tool 装饰器执行
```
`toolkits/__init__.py` 中已包含 `buildin``mysql``debug` 模块的导入,这些模块加载时会自动注册所有带 `@tool` 装饰器的函数。
## 工具分类
### 内置工具 (buildin)
| 工具 | 说明 |
|------|------|
| `ask_user_question` | 向用户发起交互式提问 |
| `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` 展示。
### MySQL 工具 (mysql)
| 工具 | 说明 |
|------|------|
| `mysql_list_tables` | 列出数据库中所有表 |
| `mysql_describe_table` | 获取表结构信息 |
| `mysql_query` | 执行只读 SQL 查询 |
### 知识库工具 (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` | 在指定知识库中检索内容,返回结构化的 `resource_id`(即 `kb_id`/`file_id`/`chunk` |
| `find_kb_document` | 在已知文件内按关键词或正则定位内容 |
| `open_kb_document` | 按 `file_id` 分段打开知识库文档(默认窗口 1800 行) |
## 工具组装
工具组装在 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 时,对应的技能文件会被挂载到沙盒的 `/home/gem/skills/<slug>/...`,智能体可以通过读取 SKILL.md 来了解如何使用这些技能。
关于 Skills 的详细机制,请参阅 [Skills 管理](./skills-management.md)。
+369
View File
@@ -0,0 +1,369 @@
# 版本变更记录
本页用于记录各版本发布说明(新增、修复与破坏性变更)。
同一版本的多次功能更新时,应以功能为单位进行更新,比如之前添加了 A 功能的更新,在后续的更新中修复了因 A 功能引入的 bug,那么这个修复说明应该和 A 功能描述放在一起,而不是新增一条修复记录,功能更新同理。
## v0.7.1 (current)
### 破坏性变更
- 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),如有更高保密要求请通过反向代理限制该路径。
### 开发记录
- 新增 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`
- 知识库详情页新增整页内容加载态:切换或首次进入详情时,在知识库信息返回前仅展示居中 loading,避免标题、标签页和文件区域先渲染旧数据或空状态。
- 智能体管理页的普通智能体卡片新增“去对话”入口,点击后进入新建对话并预选对应智能体;子智能体卡片不展示该入口。
- 修复 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 typeProvider 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 条可读进度摘要,并在工具卡中优先展示,终态结果读取语义保持不变。
- 优化任务中心(Tasker)定位为「后台作业实体 + 只读进度面板」。前端修正失效的任务类型标签、状态判断收敛、任务详情补充参数/结果,并把轮询收敛到 store 修复抽屉关闭后角标不更新;后端 `TaskContext` 暴露 `payload` 消除私有穿透,进度更新按增量节流降低写放大,新增终态任务保留上限自动裁剪内存与数据库,`_load_state` 恢复历史任务使任务中心重启后仍可见。
- 知识库访问能力迁移为内置 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。
- 修复知识库文件入库/解析成功却被统计为失败(#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 自动生成时报错。
- 优化知识库文件列表状态流转与文件预览边界:`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`,知识库详情和工作区共用展示层,并移除原知识库文件列表拖拽移动入口。
- 优化知识库启动元数据加载:服务启动时不再把全部 `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 拆分字段,前端将内容消息、工具消息、系统消息与工具定义分开展示,并修正上下文窗口/剩余信息换行与对话流式输出期间的底部跟随滚动。
- 对齐 DeepAgents `read_file` 的非文本读取边界:已知非文本扩展和小型未知二进制返回 base64,多模态工具结果可直接携带图片;二进制预览沿用 DeepAgents 500 KiB 上限,OpenAI 兼容模型请求会把 tool-role 图片额外镜像为 user-role 图片消息。
- 新增默认 OCR 解析引擎配置 `default_ocr_engine`,普通登录用户可读取系统配置;知识库上传弹窗与临时附件解析弹窗默认选中系统默认 OCR,解析入口仅在未显式传入 `ocr_engine` 时使用该默认值。
- 新增 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 的问题,避免主智能体或子智能体线程因系统默认模型已不可用而查询状态/保存历史失败;模型供应商管理页新增默认模型保护,阻止删除、停用默认模型所属供应商或移除当前默认模型。
- 优化 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 messagesSummary 阈值判断改为使用 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 事件循环;并发上限按事件循环懒加载信号量控制,不改变检索默认行为与参数上限。
- 修复异步文档解析阻塞 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/events/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。
- 图片生成能力迁移为 SkillQwen-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 envelopeSSE 输出 `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 specSkills prompt 补齐新版占位符;sandbox/skills backend 复用新版 `ReadResult``GlobResult``GrepResult` 等协议类型,文件权限在 backend 层明确区分 skills、uploads、outputs 与 workspace,保留最小 `CustomCompositeBackend` 以避免非 route glob 误扫其他 routeAgent 上下文压缩改为复用 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/<user_id>/workspace`
- 收紧文件系统安全边界:viewer/chat 下载与删除路径统一基于解析后的真实路径做允许目录校验,阻止通过软链接逃逸工作区/线程目录
- 修复 OIDC 原始用户名绑定中的占位用户解析:解析目标用户 ID 时改为从右侧拆分,避免 `sub` 中包含冒号时把已绑定账号误判成冲突账号
- 修复 DOCX 解析中的图片回插顺序:Docling 导出的多个 `<!-- image -->` 占位符现在按文档图片顺序替换
- 修复前端依赖安全告警:通过 `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/<thread_id>/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 的解析方法 <Badge type="info" text="0.3.5" />
- 修改现有的智能体Demo,并尽量将默认助手的特性兼容到 LangGraph 的 [`create_agent`](https://docs.langchain.com/oss/python/langchain/agents) 中
- 基于 create_agent 创建 SQL Viewer 智能体 <Badge type="info" text="0.3.5" />
- 优化 MCP 逻辑,支持 common + special 创建方式 <Badge type="info" text="0.3.5" />
- LightRAG 知识库应该可以支持修改 LLM
### Fixed
- 修复本地知识库的 metadata 和 向量数据库中不一致的情况。
- v1 版本的 LangGraph 的工具渲染有问题
- upload 接口会阻塞主进程
- LightRAG 知识库查看不了解析后的文本,偶然出现,未复现
- 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。
- 前端工具调用渲染出现问题
- 当前 ReAct 智能体有消息顺序错乱的 bug,且不会默认调用工具
- 修复文件管理:(1)文件选择的时候会跨数据库;(2)文件校验会算上失败的文件;
+179
View File
@@ -0,0 +1,179 @@
# 参与贡献
感谢你对 Yuxi 的兴趣。我们欢迎 Issue、文档改进、Bug 修复、测试补充以及新功能贡献。
如果你只是想快速了解仓库入口信息,可以先看根目录的 [CONTRIBUTING.md](../../CONTRIBUTING.md)。
<a href="https://github.com/xerrors/Yuxi/contributors">
<img src="https://contributors.nn.ci/api?repo=xerrors/Yuxi" alt="贡献者名单">
</a>
## 开始之前
提交前建议先完成以下检查:
- 搜索已有 [Issues](https://github.com/xerrors/Yuxi/issues)
- 对较大的功能改动,先发 Issue 讨论设计和边界
- 保持一次 PR 只解决一个明确问题,避免把无关重构混在一起
## 开发原则
本项目默认遵循以下开发原则:
- 避免过度设计,只做当前需求直接需要的改动
- 不额外添加“顺手优化”、兼容层或未来需求抽象
- 尽量复用现有实现,保持代码简单、聚焦、可维护
- 只在系统边界做必要校验,不为不可能发生的内部场景增加复杂度
## 开发环境
Yuxi 基于 Docker Compose 管理开发环境。开发、调试、测试都应尽量在运行中的容器中完成。
### 启动项目
```bash
docker compose up -d
```
### 常用检查命令
```bash
docker ps
docker logs api-dev --tail 100
```
`api-dev``web-dev` 默认支持热重载。通常情况下,本地修改代码后不需要重启容器。
如需进一步了解服务定义,可查看 [docker-compose.yml](../../docker-compose.yml)。
## 贡献流程
### 1. Fork 仓库
在 GitHub 上 Fork 本仓库到你的个人账户。
### 2. 创建分支
请使用语义明确的分支名,例如:
```bash
git checkout -b feature/amazing-feature
git checkout -b fix/chat-stream-interrupt
git checkout -b docs/update-contributing-guide
```
### 3. 开发与验证
按项目规范完成代码、测试与文档更新。开发完成后,至少完成:
- 检查
- 测试
- Lint
- 必要的端到端验证
如果现有测试脚本不足以覆盖你的改动,应补充对应测试,测试脚本优先放在 `backend/test`
### 4. 提交代码
```bash
git commit -m "feat: add knowledge graph import flow"
```
### 5. 推送并发起 Pull Request
```bash
git push origin feature/amazing-feature
```
创建 PR 时,请写清楚:
- 修改内容
- 修改原因
- 影响范围
- 验证方式
如果涉及 UI 改动,建议附上截图或录屏。
## 前端贡献规范
前端目录位于 `web/`,提交前请遵循以下约束:
- 包管理器使用 `pnpm`
- 所有 API 接口定义统一放在 `web/src/apis`
- Icon 优先使用 `lucide-vue-next`
- 样式使用 `less`
- 非特殊情况必须优先复用 [web/src/assets/css/base.css](../../web/src/assets/css/base.css) 中的颜色变量
界面设计和样式约束可参考 [design.md](./design.md)。
## 后端贡献规范
后端目录位于 `backend/`,提交时请注意:
- Python 风格尽量保持 pythonic
- 优先使用较新的语法,兼容目标为 Python 3.12+
- 优先在容器内运行调试和测试命令
示例:
```bash
docker compose exec api uv run python test/your_script.py
```
测试脚本建议放在 `backend/test` 下。
## 质量检查
提交前请至少完成以下检查:
### 格式化与静态检查
```bash
make format
make lint
```
如果测试依赖管理员账户,可从项目根目录的 `.env` 中读取相关配置。
## 文档维护要求
代码改动后,请同步检查是否需要更新文档。
- 通用开发文档位于 `docs/`
- 文档导航定义在 `docs/.vitepress/config.mts`
- 未完成规划、未来里程碑或已知问题更新 [roadmap.md](./roadmap.md);已完成的用户可见变更或发布说明更新 [changelog.md](./changelog.md)
- 若确需新增仅开发者可见的说明文档,放在 `docs/vibe/`
## 提交信息规范
推荐使用清晰、可检索的提交前缀:
```text
feat: 添加新功能
fix: 修复 bug
docs: 更新文档
refactor: 代码重构
test: 添加测试
chore: 构建过程或辅助工具的变动
```
## 智能体
如果是 Agent 提交的 PR(如 Claude Code、Codex 等),请在 PR 标题最后添加 🤖 标志。
并在 PR 正文中添加
```
<details>
<summary>贡献说明</summary>
本 PR 由 [Agent Name] 自动生成,且没有人工干预。
</details>
```
## 反馈渠道
- Bug 反馈:<https://github.com/xerrors/Yuxi/issues>
- 功能讨论:<https://github.com/xerrors/Yuxi/discussions>
感谢每一位贡献者的投入。
+291
View File
@@ -0,0 +1,291 @@
# 界面设计规范
本文档定义 Yuxi 前端界面的基础设计规范,适用于 `web/src` 下的新页面、新组件和现有 UI 调整。它同时面向人类开发者和 AI coding agent:修改界面前先阅读本页,优先复用现有组件、CSS 变量和交互模式。
## 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-15px400 | 常规说明、列表内容 |
| 辅助说明 | 12-13px400 | helper text、元信息、时间、统计说明 |
| 标签/状态 | 12px500 | tag、chip、状态徽标 |
| 代码/路径 | 12-14pxmonospace | 文件路径、命令、代码片段 |
文本规范:
- 标题要短,优先描述对象,不写营销式口号。
- 按钮文案使用明确动作,如“保存配置”“重新检测”“删除文件”。
- 危险操作必须让文案直接表达后果,如“删除知识库”。
- 不使用 placeholder 替代表单 labelplaceholder 只做输入示例。
- 不使用负字距或按视口宽度缩放字体,避免宽屏和小屏出现不可控排版。
## 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。普通卡片和列表不要用阴影制造装饰性层级。
### 状态标签
状态标签使用语义色浅背景 + 深文字:
| 状态 | 推荐 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. 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。
```
### 实现检查清单
- 使用现有 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` 样例集合
+55
View File
@@ -0,0 +1,55 @@
# 开发路线图
路线图可能会经常变更,如果有强烈的建议,可以在 [issue](https://github.com/xerrors/Yuxi/issues) 中提。
项目看板(Maintainer Only):[GitHub Project](https://github.com/users/xerrors/projects/2)
日志添加规范(For Agent:
### 看板
**知识库**
- [ ] 知识库工具接口化与 CLI 集成:将当前知识库的主要工具(如上传、检索、建索引等)封装为后端 API 接口,并集成到 Yuxi CLI 工具中
- [ ] 知识库 Mindmap 扩展:新增基于文件名的文件“边”构建,支持聚类算法形成社区节点,并提供思维导图 (Mindmap) 可视化结构展示
- [ ] 知识库工具新增 query_keywords 工具,专门用于基于关键词命中的排序 <Badge text="v0.7.1" />
- [ ] 增强知识库检索体验:增强 metadata、标签等
- [ ] 个人工作区增加可检索能力(但是不做向量化) <Badge text="v0.7.1" />
- [ ] 新增基于 PaddleOCR 的解析器:接入 PaddleOCR-VL-1.6、PP-OCRv6、PP-StructureV3,并抽象共用基类复用相似的脚本调用、产物收集和配置处理
**智能体**
- [ ] 修复不同用户安装相同 Skill 时,因目前 Skill slug 全局唯一导致无法安装、会自动新增 versiontag 的问题,排查其对安装流程 and 版本管理的影响
- [x] 子智能体缺少异步的机制 <Badge text="v0.7.1" />
- [ ] 子智能体缺少 steer 机制 <Badge text="v0.7.1" />
- [ ] 子智能体的双向通信,缺少 ask_for_main_agent 的机制
- [ ] 子智能体与子智能体的通信机制
- [x] 优化 Agent `read_file` 工具:至少对齐 DeepAgents 的读取行为 <Badge text="v0.7.1" />
- [ ] Skill 详情页增强绑定能力展示:内置 Skill 也应清晰展示只读的工具/MCP/Skill 依赖说明
- [x] 添加 Agent 独立调用接口,方便后续评估使用
- [ ] 任务队列:调研是否可以通过修改 state 实现,添加一个中间件(after agents 钩子),并支持通过 after model 触发的引导模式。 <Badge text="v0.7.2" />
- [x] 反馈接入到 Langfuse
**其他**
- [x] 历史对话新增搜索能力([#790](https://github.com/xerrors/Yuxi/issues/790)
- [x] 消息中的代码块增加快速复制按钮([#790](https://github.com/xerrors/Yuxi/issues/790)
- [ ] 集成 Memory,基于 deepagents 的文件后端实现,需要考虑定位
- [x] 优化 Task 模块定位:区分真正的后台任务实体与进度条管理工具,重新定义任务中心/Tasker 的职责边界
- [x] 模型供应商类型继续补齐非 OpenAI 兼容适配,并清理不再支持的 provider type 字样 <Badge text="v0.7.1" />
- [ ] 优化 Agent 向用户追问交互:支持较长文本回答输入,并在流式输出时保持聊天区跟随最新内容([#753](https://github.com/xerrors/Yuxi/issues/753)
**仅设想**
- [ ] Yuxi CLI 更多管理命令,放在后续版本中实现(不是类似于编程助手,而是管理平台工具,等各个 router 接口优化之后)
### Bugs
- [ ] 目前的知识库的图片存在公开访问风险
- [ ] 点开对话的时候要能够自动定位到尾部,而不是最开始。
---
历史版本发布记录已迁移到 [版本变更记录](./changelog.md)。
维护说明:
- roadmap 仅保留未来规划(看板/Bugs/里程碑方向)。
- 具体版本发布内容统一维护在 changelog。
+194
View File
@@ -0,0 +1,194 @@
# 测试规范与工作流
本文档用于指导 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、附件、文件落盘等完整流程
- 默认数量少、执行更慢
## 2. 新增测试时怎么选目录
新增测试前先判断:
1. 只测 Python 逻辑,不需要真实服务
放到 `unit`
2. 需要请求真实接口
放到 `integration/api`
3. 需要验证从入口到最终结果的完整链路
放到 `e2e`
不要再默认把测试直接丢到 `backend/test/` 根目录。
## 3. 文件和命名规范
文件名:
- 使用 `test_<domain>_<target>.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
```
## 10. 推荐的日常开发流程
建议顺序:
1. 本地改代码
2. 先跑相关单元测试
3. 涉及接口时跑相关集成测试
4. 涉及关键主链路时补跑对应 E2E
5. 提交前至少完成“检查 -> 测试 -> Lint”
## 11. 当前落地原则
这套规范的重点不是一步到位重写所有旧测试,而是:
- 新增测试必须按新目录落位
- 改到旧测试时顺手迁移
- 优先保持测试可执行和可信
- 优先减少假绿和环境耦合
对当前 Yuxi 来说,这就是最务实、也最容易持续执行的测试标准。
+5
View File
@@ -0,0 +1,5 @@
---
# 自定义官网式首页,内容见 .vitepress/theme/components/YuxiHome.vue
layout: home
title: 语析 Yuxi · 融合 RAG 与知识图谱的智能体 Harness 平台
---
+83
View File
@@ -0,0 +1,83 @@
# 命令行工具
`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 <name>` 切换。
## 登录
默认使用浏览器授权登录:
```bash
yuxi login --browser
```
如果已经在 Yuxi 中创建了 API Key,也可以直接导入:
```bash
yuxi login --api-key yxkey_xxx
```
常用账号状态命令:
```bash
yuxi whoami
yuxi status
yuxi logout
```
## 上传知识库文件
上传目录时,如果不指定 `--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 智能体运行,并把结果回传到对应实验中。
+83
View File
@@ -0,0 +1,83 @@
# 知识库评估指南
知识库评估是 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 低**:说明相关文档没有被检索到,可能需要增加检索数量或优化查询
- **答案准确性低**:说明生成质量有问题,可能需要调整提示词或更换模型
## 使用场景
- **上线前验证**:知识库建设完成后,评估效果是否满足要求
- **配置对比**:调整分块策略、嵌入模型后,对比评估结果
- **定期监控**:定期评估,及时发现质量下降
- **参数调优**:通过多次评估找到最优参数组合
---
评估是一个持续的过程。建议在初始建设时就建立评估基准,后续每次重大变更都进行评估,形成数据驱动的优化闭环。
+118
View File
@@ -0,0 +1,118 @@
# 知识库与知识图谱
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。
在前端界面中,默认会自动完成前两个阶段。如果需要自动入库,勾选「上传后自动入库」选项;否则需要手动点击入库按钮。
## 知识导图与示例问题
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 使用
如果需要通过程序批量处理文件,可以使用以下接口:
```bash
# 1. 上传文件
POST /api/knowledge/files/upload?kb_id=<知识库ID>
# 返回 file_path 和 content_hash
# 2. 解析并入库
POST /api/knowledge/databases/{kb_id}/documents
# 返回 status=queued 和 task_id
```
系统会自动去重:基于内容哈希判断是否已存在相同文件。
+101
View File
@@ -0,0 +1,101 @@
# 模型配置
## 概述
系统统一通过 **智能体管理 → 模型供应商** 页面管理所有模型(对话模型、嵌入模型、重排模型),无需修改配置文件。
## 配置路径
```
智能体管理 → 模型供应商
```
模型供应商页签仅管理员可见。如果当前账号不是管理员,只能看到普通的智能体管理和个人设置入口。
## 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 | 无默认环境变量 |
| 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)需配置向量维度,请参考模型提供商的规格说明。
### 移除模型
在供应商详情的已启用模型列表中移除不需要的模型。
## 模型标识格式
运行时模型统一使用 `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 模型并重建索引,避免不同向量空间混用。
## 常见问题
**凭证缺失警告**:检查 API Key 是否正确配置,或确认环境变量是否已设置。
**模型配置未生效**:确认模型已添加至供应商的已启用列表中。
+78
View File
@@ -0,0 +1,78 @@
# 项目简介
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)
+178
View File
@@ -0,0 +1,178 @@
# 快速开始指南
欢迎使用 Yuxi(语析),这是一个智能知识库和知识图谱 Agent 开发平台。
本指南将帮助你在几分钟内启动并运行系统,使你能够利用 LangGraph、RAG 技术和知识图谱构建 AI 驱动的知识应用。
![系统架构图](https://xerrors.oss-cn-shanghai.aliyuncs.com/github/arch.png)
::: tip 提示
除了此文档网站外,你还可以访问 [Zread](https://zread.ai/xerrors/Yuxi) 或 [DeepWiki](https://deepwiki.com/xerrors/Yuxi) 查看自动生成的详细项目文档。
:::
## 环境要求
项目采用微服务架构设计,默认服务无需 GPU 支持。如果需要使用 OCR 功能,可以通过环境变量配置外部服务。
## 快速安装
### 步骤一:获取项目代码
```bash
# 克隆最新版本
git clone --branch v0.7.1.beta1 --depth 1 https://github.com/xerrors/Yuxi.git
cd Yuxi
```
`--depth 1` 标志会创建一个浅克隆,仅包含最新的提交,从而显著减少下载时间和磁盘使用量。下表提供了版本选择的指导。
| 版本 | 适用场景 |
|------|----------|
| v0.7.0 | 当前稳定版本,推荐生产使用 |
| 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
```
### 常见问题
<details>
<summary><strong>Docker 镜像拉取失败</strong></summary>
如果网络原因导致镜像拉取失败,可以尝试:
```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
```
</details>
<details>
<summary><strong>构建失败</strong></summary>
多数构建失败是由于网络问题。尝试配置代理:
```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"
```
如果配置代理后反而失败,尝试移除代理后重试。
</details>
<details>
<summary><strong>Milvus 服务启动失败</strong></summary>
```bash
# 重启 Milvus 服务
docker compose up milvus -d
docker restart api-dev
```
</details>
::: tip 调试面板
前端提供了调试面板(在头像菜单中可找到),可以查看详细的请求和响应信息。生产环境建议关闭此特性。
:::
## 下一步
- 了解如何配置模型:阅读 [模型配置](./model-config.md)
- 探索知识库功能:阅读 [知识库与知识图谱](./knowledge-base.md)
- 学习智能体开发:阅读 [智能体开发](../agents/agents-config.md)
- 深入了解配置系统:阅读 [配置系统详解](../advanced/configuration.md)
+22
View File
@@ -0,0 +1,22 @@
{
"scripts": {
"dev": "vitepress dev",
"build": "vitepress build",
"preview": "vitepress preview"
},
"dependencies": {
"markdown-it-task-checkbox": "^1.0.6",
"vitepress": "^1.6.4"
},
"devDependencies": {
"esbuild": "0.28.1"
},
"pnpm": {
"overrides": {
"esbuild": "0.28.1",
"vitepress>@vitejs/plugin-vue": "6.0.6",
"vitepress>vite": "8.0.16"
}
},
"packageManager": "pnpm@10.11.0"
}
+1817
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="128" height="128" style="enable-background:new 0 0 128 128;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M63.79,8.64C1.48,8.64,0,78.5,0,92.33c0,13.83,28.56,25.03,63.79,25.03 c35.24,0,63.79-11.21,63.79-25.03C127.58,78.5,126.11,8.64,63.79,8.64z" style="fill:#FCC21B;"/>
<path d="M63.91,104.82c-3.43,0-6.87-0.43-10.25-1.31c-1.6-0.42-2.56-2.06-2.15-3.66 c0.42-1.6,2.06-2.56,3.66-2.14c11.65,3.04,24.21-0.21,32.78-8.48c1.19-1.15,3.09-1.12,4.24,0.08c1.15,1.19,1.12,3.09-0.08,4.24 C84.54,100.85,74.32,104.82,63.91,104.82z" style="fill:#2F2F2F;"/>
<path d="M55.53,67.26c-0.01,0.01-0.02,0.02-0.02,0.02C55.51,67.27,55.52,67.26,55.53,67.26z" style="fill:#2F2F2F;"/>
<g>
<path d="M98.21,41.34c-13.36,0-15.15,2.03-21.4,3.36C70.56,46.02,64,46.02,64,46.02s-6.56,0-12.81-1.33 c-6.25-1.33-8.05-3.36-21.4-3.36c-13.36,0-29.37,2.89-29.37,2.89v8.51c0,0,3.59,0.47,3.91,3.75c0.16,1.33-3.12,28.35,23.51,28.35 c18.9,0,26.87-11.33,29.45-20.54c1.17-4.37,2.19-9.37,6.72-9.37c4.53,0,5.55,5,6.72,9.37c2.58,9.22,10.54,20.54,29.45,20.54 c26.63,0,23.35-27.03,23.51-28.35c0.31-3.28,3.91-3.75,3.91-3.75v-8.51C127.58,44.23,111.57,41.34,98.21,41.34z" style="fill:#2F2F2F;"/>
<path d="M95.94,45.05c-6.62,0.23-11.65,1.31-11.65,1.31c-9.84,2.06-10.55,8.14-9.93,12.97 c0.8,6.07,3.29,13.75,10.04,18.49c0.53,0.38,1.76,0.79,2.35-0.77c0,0-0.02,0.11,0,0c2.22-10.48,5.52-20.14,10.78-29.89l0,0 C98.14,45.37,96.71,45.02,95.94,45.05z" style="fill:#FFFFFF;"/>
<path d="M31.06,45.02c-4.27-0.09-9.11,0.19-13.65,1.34c-5.1,1.28-7.07,3.85-7.6,9.39 c-0.53,5.43-1.13,19.27,8.73,24.46c0.57,0.3,1.83,0.5,2.44-0.91l0,0C24,66.21,25.61,60.13,32.54,47.22l0,0 C33.11,45.49,31.83,45.03,31.06,45.02z" style="fill:#FFFFFF;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB