Files
X-Financial/document/development/knowledge-answers/lightweight-knowledge-ingestion-design.html
caoxiaozhu 88ff04bef8 feat: 新增归档中心页面并完善知识库与报销查询能力
新增前端归档中心视图及相关工具函数,扩充知识库文档分类和
提取器支持多种格式,增强编排器报销查询的多维度检索,优
化本体规则和用户代理审核消息,前端完善报销创建和审批详
情交互细节,补充单元测试覆盖。
2026-05-22 16:00:19 +08:00

897 lines
26 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>X-Financial 轻量知识库归集与问答优化开发文档</title>
<style>
:root {
--green-900: #064e3b;
--green-700: #047857;
--green-600: #10b981;
--green-100: #dff8ec;
--green-50: #effcf6;
--blue-700: #1d4ed8;
--blue-50: #eff6ff;
--amber-600: #d97706;
--amber-50: #fffbeb;
--red-600: #dc2626;
--red-50: #fef2f2;
--ink-900: #071124;
--ink-700: #24324a;
--ink-600: #58677f;
--ink-500: #728098;
--line: #dbe5ef;
--line-strong: #c6d4e2;
--surface: #ffffff;
--surface-soft: #f7fafc;
--shadow: 0 10px 26px rgba(15, 23, 42, 0.08);
--radius: 8px;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
background:
linear-gradient(180deg, rgba(239, 252, 246, 0.78), rgba(247, 250, 252, 0.96) 360px),
var(--surface-soft);
color: var(--ink-900);
font-family: "IBM Plex Sans", "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif;
line-height: 1.62;
letter-spacing: 0;
}
a {
color: inherit;
text-decoration: none;
}
code {
padding: 2px 6px;
border-radius: 6px;
background: rgba(15, 23, 42, 0.06);
color: var(--ink-700);
font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace;
font-size: 0.92em;
}
.shell {
display: grid;
grid-template-columns: 276px minmax(0, 1fr);
min-height: 100dvh;
}
.sidebar {
position: sticky;
top: 0;
align-self: start;
height: 100dvh;
padding: 28px 22px;
border-right: 1px solid var(--line);
background: rgba(255, 255, 255, 0.86);
backdrop-filter: blur(18px);
overflow: auto;
}
.brand {
display: flex;
gap: 12px;
align-items: center;
margin-bottom: 30px;
}
.logo-mark {
display: grid;
width: 42px;
height: 42px;
place-items: center;
border-radius: 8px;
background: linear-gradient(145deg, var(--green-700), var(--green-600));
color: #fff;
font-weight: 800;
box-shadow: 0 10px 18px rgba(16, 185, 129, 0.26);
}
.brand-title {
font-size: 15px;
font-weight: 800;
line-height: 1.2;
}
.brand-subtitle {
margin-top: 4px;
color: var(--ink-500);
font-size: 12px;
}
.nav-label {
margin: 22px 0 8px;
color: var(--ink-500);
font-size: 12px;
font-weight: 700;
}
.nav {
display: grid;
gap: 6px;
}
.nav a {
display: flex;
align-items: center;
min-height: 38px;
padding: 8px 10px;
border-radius: 8px;
color: var(--ink-700);
font-size: 13px;
font-weight: 650;
}
.nav a:hover {
background: var(--green-50);
color: var(--green-900);
}
.nav-dot {
width: 7px;
height: 7px;
margin-right: 10px;
border-radius: 999px;
background: var(--line-strong);
}
main {
min-width: 0;
padding: 34px 42px 56px;
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1.14fr) minmax(300px, 0.86fr);
gap: 24px;
align-items: stretch;
margin-bottom: 24px;
}
.hero-panel,
.metric-panel,
section,
.card {
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--surface);
box-shadow: var(--shadow);
}
.hero-panel {
padding: 30px;
border-color: rgba(16, 185, 129, 0.28);
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(239, 252, 246, 0.9)),
var(--surface);
}
.kicker {
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 4px 10px;
border: 1px solid rgba(16, 185, 129, 0.24);
border-radius: 999px;
background: rgba(16, 185, 129, 0.1);
color: var(--green-900);
font-size: 12px;
font-weight: 800;
}
h1 {
max-width: 780px;
margin: 16px 0 14px;
font-size: 34px;
line-height: 1.18;
letter-spacing: 0;
}
.hero-copy,
.section-desc,
.card p,
.phase span,
.footnote {
color: var(--ink-600);
font-size: 14px;
}
.hero-copy {
max-width: 800px;
margin: 0;
font-size: 15px;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 22px;
}
.pill {
display: inline-flex;
align-items: center;
min-height: 32px;
padding: 6px 11px;
border-radius: 999px;
background: #fff;
border: 1px solid var(--line);
color: var(--ink-700);
font-size: 12px;
font-weight: 750;
}
.pill.primary {
background: var(--green-700);
border-color: var(--green-700);
color: #fff;
}
.metric-panel {
display: grid;
gap: 12px;
padding: 18px;
}
.metric {
padding: 14px;
border-radius: 8px;
background: var(--surface-soft);
border: 1px solid var(--line);
}
.metric-label {
color: var(--ink-500);
font-size: 12px;
font-weight: 700;
}
.metric-value {
margin-top: 5px;
font-size: 18px;
line-height: 1.28;
font-weight: 800;
}
section {
margin-top: 18px;
padding: 24px;
}
.section-head {
display: flex;
gap: 18px;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 16px;
}
.section-title {
margin: 0 0 8px;
font-size: 22px;
line-height: 1.25;
}
.section-desc {
max-width: 920px;
margin: 0;
}
.tag {
flex: 0 0 auto;
min-height: 28px;
padding: 4px 10px;
border-radius: 999px;
background: var(--green-50);
color: var(--green-900);
font-size: 12px;
font-weight: 800;
border: 1px solid rgba(16, 185, 129, 0.22);
}
.grid-2,
.grid-3,
.grid-4 {
display: grid;
gap: 14px;
}
.grid-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.card {
padding: 17px;
box-shadow: none;
}
.card h3 {
margin: 0 0 10px;
font-size: 16px;
line-height: 1.3;
}
.card p {
margin: 0;
}
.card ul,
.card ol {
margin: 0;
padding-left: 18px;
color: var(--ink-600);
font-size: 14px;
}
.card li + li {
margin-top: 7px;
}
.tone-green {
background: var(--green-50);
border-color: rgba(16, 185, 129, 0.26);
}
.tone-blue {
background: var(--blue-50);
border-color: rgba(37, 99, 235, 0.18);
}
.tone-amber {
background: var(--amber-50);
border-color: rgba(217, 119, 6, 0.2);
}
.tone-red {
background: var(--red-50);
border-color: rgba(220, 38, 38, 0.16);
}
.flow {
display: grid;
gap: 10px;
margin-top: 6px;
}
.flow-row {
display: grid;
grid-template-columns: 132px minmax(0, 1fr);
gap: 12px;
align-items: start;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--surface-soft);
}
.flow-key {
color: var(--green-900);
font-size: 13px;
font-weight: 800;
}
.flow-body {
color: var(--ink-600);
font-size: 14px;
}
.diagram {
padding: 16px;
border: 1px solid var(--line);
border-radius: 8px;
background: #0f172a;
color: #e5eef8;
overflow-x: auto;
font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace;
font-size: 13px;
line-height: 1.65;
white-space: pre;
}
.timeline {
display: grid;
gap: 12px;
}
.phase {
display: grid;
grid-template-columns: 150px minmax(0, 1fr);
gap: 14px;
padding: 14px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--surface-soft);
}
.phase-key {
color: var(--green-900);
font-size: 13px;
font-weight: 850;
}
.phase-body strong {
display: block;
margin-bottom: 4px;
color: var(--ink-900);
font-size: 15px;
}
.checklist {
display: grid;
gap: 10px;
}
.check {
position: relative;
padding: 12px 12px 12px 34px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--surface-soft);
color: var(--ink-700);
font-size: 14px;
}
.check::before {
content: "";
position: absolute;
left: 13px;
top: 17px;
width: 8px;
height: 8px;
border-radius: 999px;
background: var(--green-600);
}
.link-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 14px;
}
.link-chip {
display: inline-flex;
align-items: center;
min-height: 30px;
padding: 5px 10px;
border: 1px solid var(--line);
border-radius: 999px;
background: #fff;
color: var(--ink-700);
font-size: 12px;
font-weight: 750;
}
.link-chip:hover {
border-color: rgba(16, 185, 129, 0.5);
color: var(--green-900);
}
footer {
margin-top: 22px;
color: var(--ink-500);
font-size: 12px;
text-align: center;
}
@media (max-width: 1080px) {
.shell {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
height: auto;
border-right: 0;
border-bottom: 1px solid var(--line);
}
.hero,
.grid-2,
.grid-3,
.grid-4 {
grid-template-columns: 1fr;
}
main {
padding: 24px 18px 42px;
}
}
@media (max-width: 680px) {
h1 {
font-size: 28px;
}
.section-head,
.phase,
.flow-row {
grid-template-columns: 1fr;
}
.section-head {
display: grid;
}
}
</style>
</head>
<body>
<div class="shell">
<aside class="sidebar">
<div class="brand">
<div class="logo-mark">KB</div>
<div>
<div class="brand-title">轻量知识库归集</div>
<div class="brand-subtitle">LightRAG + Hermes 优化方案</div>
</div>
</div>
<div class="nav-label">文档导航</div>
<nav class="nav">
<a href="#position"><span class="nav-dot"></span>定位与边界</a>
<a href="#architecture"><span class="nav-dot"></span>轻量架构</a>
<a href="#borrow"><span class="nav-dot"></span>Yuxi 借鉴点</a>
<a href="#modules"><span class="nav-dot"></span>模块设计</a>
<a href="#retrieval"><span class="nav-dot"></span>召回与回答</a>
<a href="#delivery"><span class="nav-dot"></span>实施路线</a>
<a href="#quality"><span class="nav-dot"></span>验收标准</a>
</nav>
<div class="nav-label">硬约束</div>
<div class="nav">
<a href="#quality"><span class="nav-dot"></span>不做重平台</a>
<a href="#quality"><span class="nav-dot"></span>证据优先回答</a>
<a href="#quality"><span class="nav-dot"></span>增量任务可追踪</a>
</div>
</aside>
<main>
<div class="hero">
<div class="hero-panel">
<span class="kicker">开发文档 · 先定边界再实现</span>
<h1>X-Financial 轻量知识库归集与问答优化方案</h1>
<p class="hero-copy">
本方案不把 X-Financial 改造成专业知识库平台,而是在现有
<code>LightRAG</code><code>Hermes</code><code>AgentRun</code>
和知识库 UI 上补齐最薄弱的归集、分块、召回和证据回答能力。
Yuxi 只作为成熟设计参考,借鉴其统一解析、分块预设和评估思想。
</p>
<div class="hero-actions">
<span class="pill primary">保留现有 LightRAG</span>
<span class="pill">轻量 Parser</span>
<span class="pill">条款级分块</span>
<span class="pill">混合召回</span>
<span class="pill">证据化回答</span>
</div>
</div>
<div class="metric-panel">
<div class="metric">
<div class="metric-label">核心目标</div>
<div class="metric-value">准、快、可解释</div>
</div>
<div class="metric">
<div class="metric-label">改造范围</div>
<div class="metric-value">归集与召回链路,不重做平台</div>
</div>
<div class="metric">
<div class="metric-label">并发预期</div>
<div class="metric-value">5-10 用户查询可降级、有上限</div>
</div>
</div>
</div>
<section id="position">
<div class="section-head">
<div>
<h2 class="section-title">定位与边界</h2>
<p class="section-desc">
知识库在 X-Financial 中是业务辅助能力,不是独立知识管理产品。
因此实现必须克制:不引入重型多租户平台,不替换现有业务数据模型,
不把知识库 UI 做成复杂后台,只补齐影响问答质量的关键薄层。
</p>
</div>
<span class="tag">轻量优先</span>
</div>
<div class="grid-3">
<div class="card tone-green">
<h3>要解决的问题</h3>
<ul>
<li>Word、PDF、Excel 等文件进入 RAG 前缺少统一结构。</li>
<li>制度类文档如果按普通 chunk 切分,条款容易被切散。</li>
<li>问答质量依赖向量召回,缺少关键词、标题、条款补召回。</li>
<li>效果优化缺少固定评测集,容易靠体感判断。</li>
</ul>
</div>
<div class="card tone-blue">
<h3>保留的现有能力</h3>
<ul>
<li><code>KnowledgeService</code> 继续负责文件库和状态入口。</li>
<li><code>KnowledgeRagService</code> 继续封装 LightRAG 查询和入库。</li>
<li><code>KnowledgeIndexTaskManager</code> 继续承接 Hermes 增量任务。</li>
<li>前端知识管理继续保持简单文件夹与文件列表形态。</li>
</ul>
</div>
<div class="card tone-amber">
<h3>明确不做</h3>
<ul>
<li>不整体引入 Yuxi 平台。</li>
<li>不把存储改成 Milvus + Neo4j。</li>
<li>不一次性接入全量 OCR 引擎。</li>
<li>不新增复杂多租户知识库后台。</li>
</ul>
</div>
</div>
</section>
<section id="architecture">
<div class="section-head">
<div>
<h2 class="section-title">轻量架构</h2>
<p class="section-desc">
新增能力只放在 LightRAG 前后两侧:前侧负责把文件变成稳定 Markdown 和业务友好 chunk
后侧负责混合召回、证据重排和可靠回答。LightRAG 仍是主召回核心。
</p>
</div>
<span class="tag">薄层增强</span>
</div>
<div class="diagram">原始文件
├── docx / pdf / xlsx / pptx / csv / txt
轻量 Parser
├── 统一 Markdown
├── 表格上下文
└── 页码 / sheet / 条款路径
Chunk Preset
├── laws制度条款
├── qa常见问答
└── table表格行组
现有 LightRAG / Qdrant
混合召回
├── LightRAG 语义召回
├── 标题与条款关键词召回
└── 轻量重排 top 3-5
证据化回答
├── 命中证据
├── 直接结论
└── 缺失信息说明</div>
</section>
<section id="borrow">
<div class="section-head">
<div>
<h2 class="section-title">Yuxi 借鉴点</h2>
<p class="section-desc">
Yuxi 的价值不在于整套平台,而在于成熟的归集分层思想:
文件先解析成 Markdown再按场景分块再索引再评估。
这些思想可以小规模落地到现有服务内。
</p>
</div>
<span class="tag">借鉴而非搬运</span>
</div>
<div class="grid-4">
<div class="card">
<h3>统一 Parser</h3>
<p>学习 Yuxi 把多格式文件统一转 Markdown 的入口设计,但只实现 X-Financial 当前需要的格式。</p>
</div>
<div class="card">
<h3>分块 Preset</h3>
<p>借鉴 RAGFlow-like preset。先做 <code>laws</code><code>qa</code><code>table</code> 三类。</p>
</div>
<div class="card">
<h3>两阶段状态</h3>
<p>内部区分解析和索引。UI 仍可显示简单归纳状态,后台记录真实失败点。</p>
</div>
<div class="card">
<h3>轻量评测</h3>
<p>不做评估平台,只维护 JSON 用例和脚本,持续检查召回与回答质量。</p>
</div>
</div>
<div class="link-list">
<a class="link-chip" href="https://github.com/xerrors/Yuxi">Yuxi README</a>
<a class="link-chip" href="https://github.com/xerrors/Yuxi/blob/main/backend/package/yuxi/plugins/parser/unified.py">Yuxi unified parser</a>
<a class="link-chip" href="https://github.com/xerrors/Yuxi/blob/main/backend/package/yuxi/knowledge/chunking/ragflow_like/presets.py">Yuxi chunk presets</a>
<a class="link-chip" href="https://github.com/xerrors/Yuxi/blob/main/backend/package/yuxi/knowledge/chunking/ragflow_like/parsers/laws.py">Yuxi laws parser</a>
</div>
</section>
<section id="modules">
<div class="section-head">
<div>
<h2 class="section-title">模块设计</h2>
<p class="section-desc">
新增模块必须小而清楚,避免把逻辑继续堆进单个 Service。
单个核心文件控制在 800 行以内,优先按解析、分块、召回、评测拆分。
</p>
</div>
<span class="tag">职责拆分</span>
</div>
<div class="flow">
<div class="flow-row">
<div class="flow-key">knowledge_parser.py</div>
<div class="flow-body">
负责把 docx、pdf、xlsx、csv、txt 等文件转成 Markdown。
输出正文、标题路径、页码、sheet、表头、解析告警。
</div>
</div>
<div class="flow-row">
<div class="flow-key">knowledge_chunking.py</div>
<div class="flow-body">
根据文件夹、文件类型和文档特征选择分块策略。
第一批只实现制度、问答、表格三类。
</div>
</div>
<div class="flow-row">
<div class="flow-key">knowledge_retrieval.py</div>
<div class="flow-body">
在 LightRAG 命中结果外补充关键词、条款标题和文件名召回。
最终输出小而准的证据块。
</div>
</div>
<div class="flow-row">
<div class="flow-key">knowledge_eval.py</div>
<div class="flow-body">
读取轻量评测用例,检查 expected 文件、关键词、证据和答案约束。
用于每次调整参数后的回归验证。
</div>
</div>
</div>
</section>
<section id="retrieval">
<div class="section-head">
<div>
<h2 class="section-title">召回与回答策略</h2>
<p class="section-desc">
目标不是让模型更会猜,而是让系统给模型更可靠的证据。
制度问题优先命中条款,表格问题保留表头与行上下文,回答必须暴露依据和缺失信息。
</p>
</div>
<span class="tag">证据优先</span>
</div>
<div class="grid-3">
<div class="card tone-green">
<h3>召回层</h3>
<ul>
<li>LightRAG 继续提供语义召回。</li>
<li>条款号、标题、文件名、关键词做补召回。</li>
<li>召回候选数量有上限,避免并发下无限扩张。</li>
</ul>
</div>
<div class="card tone-blue">
<h3>重排层</h3>
<ul>
<li>优先保留含问题关键词、标题路径和条款语义的块。</li>
<li>制度类按条款完整度加权。</li>
<li>最终给回答链路 3-5 条高质量证据。</li>
</ul>
</div>
<div class="card tone-amber">
<h3>回答层</h3>
<ul>
<li>能直接基于证据回答时,不强制二次模型整理。</li>
<li>模型只做压缩表达,不凭空补事实。</li>
<li>证据不足时明确说明缺什么。</li>
</ul>
</div>
</div>
</section>
<section id="delivery">
<div class="section-head">
<div>
<h2 class="section-title">实施路线</h2>
<p class="section-desc">
分四步小步交付。每一步都能单独验证,不把解析、索引、召回和评测揉成一次大改。
</p>
</div>
<span class="tag">渐进落地</span>
</div>
<div class="timeline">
<div class="phase">
<div class="phase-key">P0 / 文档落地</div>
<div class="phase-body">
<strong>先明确轻量边界</strong>
<span>完成本文档,确认不做重平台、不替换存储、不一次性引入复杂 OCR。</span>
</div>
</div>
<div class="phase">
<div class="phase-key">P1 / 统一解析</div>
<div class="phase-body">
<strong>补齐文件归集质量</strong>
<span>新增 Parser把 Word、PDF、Excel、CSV、TXT 稳定转为 Markdown并保存解析产物供索引复用。</span>
</div>
</div>
<div class="phase">
<div class="phase-key">P2 / 场景分块</div>
<div class="phase-body">
<strong>提升制度与表格命中率</strong>
<span>实现 laws、qa、table 三类分块。制度按章、节、条、款保留完整语义,表格保留 sheet、表头和行上下文。</span>
</div>
</div>
<div class="phase">
<div class="phase-key">P3 / 混合召回</div>
<div class="phase-body">
<strong>减少答偏和漏召回</strong>
<span>在 LightRAG 命中外补充关键词、条款标题、文件名召回,输出可控数量的证据块。</span>
</div>
</div>
<div class="phase">
<div class="phase-key">P4 / 轻量评测</div>
<div class="phase-body">
<strong>把效果优化变成可回归</strong>
<span>建设 30-50 条远光软件制度风格问答用例,覆盖报销、差旅、发票、预算、税务等高频问题。</span>
</div>
</div>
</div>
</section>
<section id="quality">
<div class="section-head">
<div>
<h2 class="section-title">验收标准</h2>
<p class="section-desc">
验收不只看页面状态,而要看文件是否真实入库、召回是否命中文档依据、
回答是否引用证据,以及并发访问时是否能稳定降级。
</p>
</div>
<span class="tag">真实验证</span>
</div>
<div class="checklist">
<div class="check">Word、PDF、Excel、CSV、TXT 文件能生成可读 Markdown且解析产物可复用。</div>
<div class="check">制度类文件能按章、节、条、款形成相对完整的证据块。</div>
<div class="check">Excel 表格问答能保留 sheet、表头、关键列和业务行上下文。</div>
<div class="check">Hermes 增量任务能区分解析失败、索引失败和归纳失败。</div>
<div class="check">常见制度问答优先返回证据化直接答案,模型超时时仍有可读降级答案。</div>
<div class="check">5-10 个用户同时访问时,查询候选数、重排数、模型调用数都有明确上限。</div>
<div class="check">轻量评测集覆盖至少 30 条问题,并记录命中文件、关键词和答案约束。</div>
<div class="check">不引入 Yuxi 平台级依赖,不改变现有知识库 UI 的主体交互。</div>
</div>
<p class="footnote">
后续实现时,优先在现有定向测试基础上补充 Parser、Chunking、Retrieval 和 Knowledge Eval 的小测试。
后端验证优先在 Docker 容器 <code>x-financial-main</code> 中运行。
</p>
</section>
<footer>
X-Financial 轻量知识库归集与问答优化开发文档 · 放置位置document/development/knowledge-answers/lightweight-knowledge-ingestion-design.html
</footer>
</main>
</div>
</body>
</html>