面向企业技术支持场景,构建知识检索、回答 / 拒答与工单升级一体化的 RAG + 受控工单 Agent 后端。
系统首先从技术支持知识库检索相关证据,返回带来源的回答或在上下文不足时拒答;对于需要进一步处理的问题,通过工单分类、预览和人工确认后再执行真实建单,并以 AgentOps 记录关键运行与审批链路。这里的目标不是让模型直接接管业务状态,而是把知识问答与后续问题升级连接成一条可控、可追踪的技术支持流程。
在这条应用链路之上,项目长期以 TechQA 作为核心技术支持语料与统一评测基准,持续建立检索、生成、拒答与失败归因闭环,用受控评测回答“系统是否真的变好、失败在哪里、某次工程改动是否值得保留”。
当前定位: TechQA 是主技术支持语料与长期主评测基准,不再把它视为迁移到另一套主数据集之前的临时阶段。未来若增加多来源、冲突处理或 Agent 压力测试,只作为补充评测,不替换现有 TechQA 主线。
技术支持请求
│
▼
当前在线运行时:
Dense Chroma 检索
│
├──────────────► 回答 + 来源
│
└──────────────► 上下文不足时拒答
│
▼
工单 Agent 预览
├─ search_kb
├─ classify_ticket
└─ approval_request.pending
│
▼
人工确认
├─ 运行归属校验
├─ 待审批状态校验
└─ 服务端草稿一致性校验
│
▼
create_ticket
│
└──────────────► AgentOps 运行轨迹
├─ Agent 运行记录
├─ 工具调用
├─ 审批
└─ 检索日志 / 指标
评测与迭代
────────────────────────────────────────────────────────────
TechQA
28,481 篇 Technote / 610 条检索问题
910 条生成 / 拒答问答记录
│
▼
离线评测
Dense / 重排 / Hybrid
证据级审计
生成 / 拒答
│
▼
失败归因
│
▼
系统迭代
│
└────► RAG / 上下文策略 / 评测闭环
- 当前在线运行时: Dense Chroma 检索.
- 冻结工程候选: Dense100 + BM25100 -> RRF60 -> 融合 Top100 ->
qwen3-rerank-> 直接 Top14. - 集成状态: 尚未切换到在线运行时。
- 拒答 / 生成: 尚未冻结。
| 能力 | 当前实现 |
|---|---|
| RAG 在线运行时 | Chroma Dense 检索、租户 / 类别过滤、来源返回、低相关拒答 |
| 受控工单 Agent | search_kb / classify_ticket / create_ticket,预览-确认 + 人工审批 |
| 核心技术支持数据 | TechQA 28,481 Technotes、610 条可回答检索问题、910 条生成 / 拒答问答记录 |
| RAG 评测 | 冻结 TRAIN / DEV、文档级 Recall@K / MRR、生成 / 拒答评测框架 |
| 重排 / Hybrid | 保留冻结离线实验;可选 Hybrid 搜索、实验问答默认关闭;默认检索/问答及工单仍为 Dense |
| 失败归因 | 候选覆盖、chunk 拥挤、证据级审计、路线准入门槛 |
| AgentOps | Agent 运行记录 / 工具调用 / 审批 / 检索轨迹与聚合指标 |
| 工程化 | Alembic、Pytest、Ruff、GitHub Actions、Docker Compose、冒烟测试 |
TechQA 不是单独外挂的评测数据集,而是当前项目的数据与评测主线。
- 28,481 篇 Technote 技术支持文档;
- 610 条可回答检索问题;
- 每条问题恰好 1 个相关文档;
- qrels 为文档级标注;
- 实际检索器返回 chunk,因此正式检索评测会先保留原始 chunk 排序,再按
document_id首次出现位置去重成文档排序。
- 610 条可回答;
- 300 条不可回答;
- 共 910 条 问答记录。
这使同一 技术支持领域 可以连续支撑:
检索
↓
重排 / Hybrid 对比
↓
证据质量诊断
↓
生成正确性 / 忠实性
↓
拒答 / 幻觉评测
完整数据版本、SHA256、split 和评测契约见:
Ticket Agent 的核心目标不是让模型直接修改业务状态,而是将预览 / 审批阶段与真实写操作分离。
用户请求
│
▼
search_kb
│
▼
classify_ticket
│
├─ 无需建单 ──► 返回判定
│
└─ 需要建单
│
▼
工单草稿
│
▼
approval_request.pending
│
▼
人工确认
│
▼
create_ticket
当前使用三个业务工具语义:
search_kb
classify_ticket
create_ticket
其中 classify_ticket 当前是可解释的规则化决策步骤,不把它包装成自主 LLM 规划。
POST /agent/ticket/preview
预览阶段会:
- 创建
agent_run; - 执行并记录
search_kb; - 根据用户请求与 RAG 来源 执行并记录
classify_ticket; - 若需要建单,生成工单草稿;
- 将草稿持久化到
approval_request.draft_json,状态保持pending; - 返回预览结果,不产生真实工单写操作。
POST /agent/ticket/confirm
只有以下条件全部成立时才创建真实工单:
approval_request.agent_run_id == request.agent_run_id
approval_request.status == "pending"
request.draft == server-side approval_request.draft_json
真正用于创建工单的是服务端持久化的审批草稿,而不是客户端临时传入的数据。
这组校验用于拒绝:
- 跨 Agent 运行记录 使用其他审批请求;
- rejected / cancelled / already-approved 等非
pending审批再次确认; - 预览后由客户端篡改草稿内容。
审批更新通过数据库条件更新,限定审批 ID、租户和旧状态 pending。
并发 confirm / reject / cancel 只有一个决策成功,其他决策返回 HTTP 400;终态不可覆盖或重新打开。
这是审批决策至多一次消费,不是工单与审计写入的跨事务恰好一次保证,也不包含崩溃恢复。
更多实现细节见 docs/agent_workflow.md。
API 默认保持 Dense Chroma 检索;搜索和实验问答另提供默认关闭的 Hybrid 可选路径。
主要能力:
/rag/search/rag/ask- 文档 chunk 检索
tenant_id/category元数据过滤- 结构化 sources 返回
- 无上下文与低相关拒答
- 检索日志
问答路径仅根据检索 上下文 生成答案,并返回对应 sources。当前低相关拒答使用 Dense Top-1 距离作为工程信号。
运行时边界: Hybrid 搜索和实验问答需要显式选择并开启服务端开关,不接管默认问答或 Ticket Agent。实验问答使用尚未独立验收的 v5.3 gate,不代表稳定拒答策略已升级。代码接入不等于已部署、已开启或已证明整链质量。
先通过 Document Backend 上传并显式索引文档,再在服务端 .env 配置已有 embedding 凭据、专用 DASHSCOPE_RERANK_API_KEY / DASHSCOPE_RERANK_BASE_URL,并设置 RAG_HYBRID_SEARCH_ENABLED=true。地址格式参见 .env.example;重启服务或重建 Compose API 容器使配置生效。
开启前须批准数据外发与费用: 查询会发往 embedding 服务;查询及最多 100 条当前租户候选正文会发往配置的 rerank 服务。每次搜索都会调用模型,未配置全局费用额度;不要把密钥交给浏览器或提交到 Git。
使用现有 JWT 调用 POST /rag/search,请求体:
{"query": "VPN error 809", "category": "it", "retrieval_mode": "hybrid", "top_k": 14}- 路线固定为
Dense100 + BM25100 → RRF(k=60) Top100 → qwen3-rerank → Top14,返回其前top_k条;Hybrid 省略top_k时为 14,允许 1–14。Dense 仍默认 3、允许 1–10。 - 只检索 SQL 文档后端标记为
indexed、当前版本、当前租户且匹配 category 的 chunk;两个通道共享此范围。仅存在于旧静态 Chroma 索引、未通过文档后端登记的内容不进入 Hybrid;TechQA 离线语料也不会自动导入。 rerank_score越大越相关,但不是概率。fusion_score、dense_rank、bm25_rank用于解释来源;BM25 独有命中的distance/dense_rank为null。不得把 Dense 的 0.9 距离阈值用于这些分数。 审计 JSON 明确记录 Hybrid 模式和分数类型,Hybrid 的top_distance留空。- 失败返回脱敏 HTTP 503,不暗中切换检索方案。显式改为
retrieval_mode: "dense"可回退;关闭开关并重启可禁用 Hybrid。重排客户端不自动重试,超时 15 秒;embedding 沿用原客户端策略。 - Demo 上限为每租户/所选分类 5000 条可见 chunk,BM25 每请求重建,无持久化第二索引。英文技术词分词不覆盖中文;纯中文查询跳过 BM25,保留 Dense + 重排。规模或中文检索需要扩大时再单独测量和改进。
- 发送候选正文前及返回结果前复查 SQL 内容/可见性,发现删除或变更则终止请求;这不是 SQL 与 Chroma 的跨存储原子快照。上传、重建中、失败和删除状态不会被当作可见文档。
搜索接口不调用答案生成、不改变拒答逻辑、不创建工单;实验问答见下一节,完整问答与审批演示仍需独立验收。
小范围接线验收脚本(默认只展示计划,零模型调用且不读取 .env):
python -m scripts.smoke_hybrid_runtime
# 仅在明确批准本次数据外发和费用后执行;此参数本身不构成授权:
python -m scripts.smoke_hybrid_runtime --execute --env-file .env真实模式只发送脚本内固定的四篇虚构说明及两个查询,使用独立临时 SQLite、Chroma 与上传目录,经过 ASGI 路由及 Demo JWT 认证,不连接已运行的服务、不改现有数据库和开关。检查默认 Dense、Hybrid 简单排序、租户/分类隔离、删除生效、空语料零调用及检索审计;不是 HTTP 网络部署验收或准确率评测。
固定使用北京 text-embedding-v4(最多 8 次)及新加坡 qwen3-rerank(最多 3 次),禁用 SDK/传输层重试与重定向。发送前校验端点、模型及文本白名单;单次请求体最多 4 KiB、累计 16 KiB。控制台只记录尝试次数、字节、状态、耗时和服务端 usage,不打印密钥或提供方错误正文。任何失败即停止,不自动重新运行。
预算按 官方价格 保守预留 ¥1,短文本预计不足 ¥0.1;次数/字节限制不是提供方账单硬限额。中断后的请求可能已计费,重新执行须重新确认,不能把脚本重跑当成免费续跑。
2026-10-16 已获授权完成一次真实提供方冒烟:8 次 embedding + 3 次重排全部 HTTP 200,服务端 usage 分别为 126 / 162 tokens,9 项接线检查通过;逐次结果与边界。这是临时 ASGI 环境的小范围验证,不证明 Hybrid 优于 Dense,也不代表已开启现有服务或验收问答、拒答、工单整链。
该路径验证集成,不是 v5.3 的正式晋级:其独立语义质量尚未确认,历史误放行/误拒仍是已知风险。默认 /rag/ask 继续使用 Dense + Top1 距离阈值,Ticket Agent 不变。
只在批准数据外发及费用后,在服务端同时开启 RAG_HYBRID_SEARCH_ENABLED=true、RAG_HYBRID_ASK_ENABLED=true 并重启。须另配置专用 DASHSCOPE_PHASE_B_API_KEY / DASHSCOPE_PHASE_B_BASE_URL(运行时不回退到重排或通用密钥);生成沿用 DASHSCOPE_API_KEY / DASHSCOPE_BASE_URL,模型按 CHAT_MODEL 或 DASHSCOPE_MODEL 选择。默认示例分类端点为新加坡,通用生成端点为北京;批准外发时应分别核对。
使用现有 JWT 请求 POST /rag/ask:
{"question": "How do I resolve VPN error 809?", "category": "it", "retrieval_mode": "hybrid", "refusal_policy": "v5_3_experimental"}Hybrid 默认 top_k=14,允许 1–14,显式较小值只使用该 Top14 前缀;Dense 仍默认 3、最多 10。Hybrid 必须显式声明实验策略,且不能传 max_distance(包括原默认值 0.9),否则 HTTP 422;不把任何距离或重排分数作为充分性阈值。
Hybrid 检索(同一租户/分类、已索引当前版本,最多 Top14)
→ 空语料:no_context,零模型请求
→ v5.3 需求/适用性/版本覆盖检查 + 原文行引用校验
→ 不足/冲突/条件缺失/歧义:refused_insufficient_evidence,不生成
→ 充分:基于完整有序来源生成,校验 [Source N] 属于 gate 支持的来源
→ 生成器自行拒答:refused_by_generator;合法答案:ok
- 固定分类模型
qwen3.5-plus-2026-04-20,复用原 v5.3 提示词与严格解析器,不读取评测样本、标签或 DEV;分类最多 2048 输出 tokens,生成最多 1024,两者超时各 30 秒、无 SDK 自动重试。分类及 Qwen3.5 Plus 生成(含日期版本)显式设置enable_thinking=false,其他生成模型不附加该提供方参数。检索侧 embedding 仍沿用原客户端策略。 - 问题和最多 14 条全文会额外发往分类服务;放行后再发往配置的生成服务。非空请求比搜索最多增加两次 chat 调用;没有新增全局费用硬限额。首次真实问答验收未通过,见下一节。
- 分类格式错误、非法行引用、截断、超时、空生成或答案缺少/含未批准的
[Source N]均为脱敏 HTTP 503 +failed审计,不算正确拒答、不暗中放行或回退 Dense。显式选择 Dense 并移除实验策略可回退;关闭 ask 开关只禁用实验问答。 - 发送分类/生成上下文前及返回前复查 SQL 可见性和内容,变化则终止;仍不是跨 SQL/Chroma 原子快照。返回
top_distance=null,sources 保留距离与重排分数的不同语义。 - 响应与成功/正常拒答审计包含
refusal_policy、evidence_gate(需求、覆盖检查、原文证据、缺口与歧义);失败审计记录模式、策略、error_stage和最多 6 层error_types,不记录异常正文或任意异常备注。阶段为分类、生成或未细分的service;错误仍返回通用 503。AgentOps 可按两个新增拒答状态过滤,汇总retrieval_status_counts显示全部状态。
校验只证明格式、引用出处和状态接线,不证明语义蕴含、逐句忠实或抗提示注入。 不把有效引用或实验 gate 放行写成答案正确率,也不改写历史 NO_PROMOTION / FAIL。
当前不接受为已验收发布版。 已补齐仓库声明的评测测试依赖,在隔离 Windows / Python 3.12.4 环境运行完整 tests/:最新 656 项通过、零失败/跳过;不只原重点回归集。主 pytest 进程禁止外部 socket 与读取项目私有 data/,测试使用临时数据;CI 包含相同完整离线检查。离线成功不是模型质量验收。
真实问答计划复用同一脚本,默认仍然零调用:
python -m scripts.smoke_hybrid_runtime --ask
# 仅在单独授权后执行;异常即停,不自动续跑或重新执行:
python -m scripts.smoke_hybrid_runtime --ask --execute --env-file .env固定 7 篇虚构文档、8 类案例,预期标签只在本地检查。独立上限为 14 次北京 embedding、6 次新加坡重排、6 次新加坡分类、2 次北京生成,禁用重试和重定向;请求体最多 24 KiB、累计 96 KiB,保守预算 ¥2 而非账单硬限额。生成只获准用于两个预设可回答案例;若负例错误放行,发送守卫会阻断生成并判验收失败,不改写为正常拒答。旧搜索模式仍保留原 8/3 上限。
首次获授权执行一次,在首个可回答案例停止:9 次 embedding、1 次重排、1 次分类均 HTTP 200;生成尝试约 30 秒未获得可用响应,接口 HTTP 503。已报告 usage 合计 2639 tokens,该次生成用量未知,可能计费,不能当作零费用。当时剩余 7 类真实案例未执行,没有重试;完整失败记录 保留输出、调用计数及执行时脚本哈希,不被后续复测覆盖。
另有语义警示:问题明确指定版本 1.0,分类器却把 version_and_time 标为 NOT_REQUIRED。来源确实覆盖该版本,但这个审计判断不符合提示词约定,格式和引用通过不能证明语义判断可靠。
该次生成请求没有显式关闭思考模式,而 官方文档 说明 Qwen3.5 Plus 默认开启思考。这是配置风险,不是本次超时唯一原因的证据。
随后修复显式关闭 Qwen3.5 Plus 生成思考模式,并补齐脱敏诊断。验收脚本区分等待响应头、读取响应体、读取完成,保留已收到的 HTTP 状态、异常类型链、usage 分项及 finish_reason;不记录隐藏思考或提供方错误正文。等待响应头不代表服务器已接受请求,超时缺少 usage 时仍不能推断零计费。30 秒超时、输出上限、重试策略、提示词与拒答规则均未变。
修复后另获授权执行一次真实提供方复测:8 类流程检查均通过(英文/中文可回答各 1、缺资料/版本不符/冲突/无关拒答各 1、空范围/删除后无上下文各 1)。实际 14 次 embedding、6 次重排、6 次分类、2 次生成,共 28 次,全部 HTTP 200,无重试;usage 合计 14525 tokens,请求体合计 63541 bytes。两次生成分别 3480 / 1889 ms,正常结束;本次未复现超时,但单次成功不证明历史超时的唯一原因或稳定延迟,也不等于总体答案准确率。
复测仍有版本审计语义警示:NovaVPN 1.0 的显式版本约束仍被标成 NOT_REQUIRED;AcornExpense 2.0 的版本覆盖被标成 SUPPORTED,理由却承认来源只覆盖 1.0。后者整体因其他检查为 MISSING 而正确拒答,但不能据此认定每项审计可靠。保留这两项警示;该次复测未针对案例改提示词或规则,历史 NO_PROMOTION / FAIL 不变。
新增离线 v5.3.1 版本审计澄清候选:只追加通用说明,区分“来源声明自己的版本范围”和“证据满足问题所需版本”,保留可见兼容性依据,不退回版本关键词/字符串匹配。没有加入上述验收案例答案,沿用原输入、状态与严格引用解析器,不修补或重标模型输出。手写边界测试只证明接线与限制;候选未接入 API,现有 v5_3_experimental 提示词、运行行为与历史报告均不变。
随后单独授权完成一次6 题分类配对开发检查:输入、预期与请求守卫在调用前提交冻结,新旧提示词使用相同输入/参数、交替首跑顺序。版本审计符合预期为 v5.3 3/6、v5.3.1 5/6;整体放行/拒答均 6/6,没有决策差异。候选在版本不符、兼容范围两题改善分项审计,但仍把“版本已匹配、功能信息缺失”的版本检查标成 NOT_REQUIRED。这只是少量已知虚构开发场景,不是独立验收,也不证明回答准确率提升;不改预期、不重标结果、不因局部改善切换服务策略。
本次仅 12 次新加坡分类,均 HTTP 200,无重试;usage 合计 27481 tokens,请求体 119304 bytes,没有重跑检索或生成。python -m scripts.probe_version_audit 默认只输出零调用计划;真实执行还要求事先授权、配置专用新加坡凭据并绑定计划哈希。单次/累计请求体上限为 24/192 KiB,最多 12 次调用;发送守卫逐次比对完整请求体,预期标签不进入模型请求。任何网络、提供方或解析异常即停,不自动续跑。候选仅部分有效,下一步应先判断审计改进的收益,不自动进入新一轮付费调参。
只读检查确认 Docker 引擎仍不可连接;真实运行服务 HTTP、容器重启/持久化与回退验收均未完成。本次使用临时 ASGI 应用、SQL 和 Chroma,不是部署验收;Ticket Agent 仍使用 Dense,不声明全链 Hybrid,默认开关保持关闭。下一步仍需处理版本审计一致性与运行环境验收,而不是因 8 类流程通过就晋级发布。
experiments/evals/ 是正式离线评测入口。默认 API 与默认问答/工单仍使用 Dense Chroma 检索;可选 Hybrid 搜索及实验问答见上一节。下面记录的是离线实验与冻结工程选择,不是新运行时语料的质量验收或公网部署证明。
| 数据划分 | 可回答 | 不可回答 | 用途 |
|---|---|---|---|
| TRAIN | 450 | 150 | 开发、失败归因、方案选择 |
| DEV | 160 | 150 | 冻结验证,只用于正式独立对比 |
TechQA 的 qrel 是文档级,而检索器返回 chunk。正式 IR 评测会保留原始 chunk 排序,再按 document_id 首次出现位置去重成文档排序,计算 Document Recall@5、Recall@20 和 MRR@10。每条 可回答问题 只有 1 个 相关文档,因此这里 Recall@K 与 Hit@K 数值相同。
| 阶段 | 为什么做 / 本次改动 | 评测口径 | 核心结果 | 失败归因与决策 | 详情 |
|---|---|---|---|---|---|
| E0:Dense 基线 | 问题 → Dense Top100 chunks → 文档去重排序,先建立统一基线 |
TRAIN + 冻结 DEV | TRAIN:R@5 61.3%,R@20 74.0%,MRR 0.510;DEV:R@5 64.4%,R@20 81.9%,MRR 0.519 | 总指标只能说明效果不足,不能区分“候选没召回”和“候选已召回但排序靠后”,因此先做失败归因 | E0 失败分析 |
| E0:失败归因 | 对失败按 标准相关文档排名分桶,再抽固定样本人工审计 | TRAIN 450 条 answerable | 排除 5 条仅由查询文本尾部空白差异造成的跨运行漂移后:排名 4–5 有 20 条,排名 6–20 有 57 条,共 77/450 个明确排序问题;另有 117 个 Top20 未命中 | 30/117 个 miss 中:17 个 qrel / 问题歧义、7 个明显词法未命中、6 个语义未命中。另抽 30 个低正确率 样本:14 个评测 / 参考答案问题、12 个证据覆盖问题,仅 4 个明显生成问题。排序问题是当时最强、最确定的可操作失败类,因此 E1 先做 重排 | 完整归因与样本审计 |
| E1:Dense + 重排 | Dense Top100 → qwen3-重排 → 文档去重排序;只加重排器 |
冻结 DEV 正式对比 | R@5 64.4% → 72.5%;R@20 81.9% → 84.4%;MRR 0.519 → 0.561 | DEV Top5:21 个改善、8 个退化;Top20:5 个改善、1 个退化。重排有明显净收益,但不是单调改善。冻结 DEV 不用于逐样本反向调参,因此没有针对这 8 个 DEV 退化继续做正式根因拟合 | E0 / E1 对比 |
| R3:BM25 互补性验证 | 剩余失败里出现 错误码、版本号、CVE、固定技术词等精确词法查询,因此加入 BM25,用 RRF 验证候选互补性 | TRAIN,文档级互补性验证 | Dense 命中@100 387,BM25 375,融合 402;救回 Dense 未命中 19 个,净增 15 个 | BM25 单独不优于 Dense,但确实补回一批 Dense 漏掉的精确词法候选,因此允许进入正式 Hybrid + 重排 实验 | R3 准入结果 |
| R4 C1:Hybrid + 重排 | Dense100 + BM25100 → RRF60 → 融合 Top100 → qwen3-rerank;与 E1 TRAIN 对比 |
TRAIN 450 条 | E1:R@5 / R@20 / MRR = .691 / .816 / .567;C1:.702 / .831 / .571 | 三项都上涨,但预注册 MRR 门槛为 .577,实际只有 .571。最终 Top20 未命中中 56 个候选缺失、20 个排序不足;固定 Top100 融合预算既会救回候选,也会压掉部分单源尾部候选。正式结论 FAIL,不继续调 RRF k、权重、深度等参数 | 正式对比 · 失败案例与归因 |
查询文本尾部空白漂移: 历史 retrieval 与 generation 数据中,有些问题语义完全相同,只在末尾多了空格或换行。250/450 条 可回答问题 存在这种差异,其中 74 条 Top3 排序发生变化、5 条 标准相关文档准入 发生变化。由于这不是语义变化,这 5 条不用于因果失败计数;后续 模型服务调用前统一对 query 做
rstrip()。
R4 C1 的正式历史状态仍是 FAIL。后续冻结工程候选保留固定 Hybrid 路线,是基于整体证据做出的工程选择;可选搜索接入不改写历史结论,也不代表默认 API 或问答已切换到 Hybrid。
Document Recall 只能回答“相关文档是否出现”,但 RAG 真正交给生成模型的是 chunk,因此还需要回答:
命中了 标准相关文档,真正能回答问题的 chunk 是否进入了高位 上下文?
从 TRAIN 可回答样本 中确定性抽取 60 条,在 标准相关文档 内定位候选 chunk,共人工标注 187 个 chunk:
| 标签 | 含义 | 评测中的作用 |
|---|---|---|
0 = 弱证据 |
相关性弱,不能作为有效回答证据 | 不计入 有用证据 / 答案证据命中 |
1 = 有用证据 |
对解决问题有帮助,但本身不直接承载完整答案 | 计入 有用证据命中 |
2 = 答案证据 |
直接承载回答问题所需的关键证据 | 同时计入 有用证据命中 与 答案证据命中 |
60 条中有 6 条被标记为 questionable_gold=true:标准相关文档与问题主题不匹配、条件冲突,或不足以支撑 标准答案,因此正式证据指标只评 54 条。
| 排序链 | 答案证据命中@5 | 答案证据命中@20 | 有用证据命中@5 | 有用证据命中@20 |
|---|---|---|---|---|
| E0 Dense | 44.4% | 61.1% | 63.0% | 77.8% |
| C1 融合后、重排 前 | 46.3% | 53.7% | 68.5% | 77.8% |
| C1 重排 后 | 53.7% | 64.8% | 72.2% | 79.6% |
这里比较的不是三套不同人工标签,而是用同一批人工 chunk 标签去评三条不同的 chunk 排序。这也解释了为什么上面不能只写 “Dense 44.4% → C1 重排 53.7%”:中间的“融合后、重排 前”本身也是一条被测链路,而且它在 答案证据命中@20 上反而从 Dense 的 61.1% 降到 53.7%,随后 重排器 又把它提升到 64.8%。
因此后续实验不再只看文档 Recall/MRR,还单独追踪“真正能回答问题的证据是否进入高位 上下文”。
G1 并不是拿“Top5 文档”去对比 E1。历史 E1 生成评测链路 的 上下文 策略是:
Dense Top100
→ 全局 qwen3-rerank
→ 重排后 Top3 chunk 作为锚点
+ Dense 排名第1的 chunk 兜底
→ 每个锚点最多带 3 个向后相邻 chunk
→ 去重
→ 最多 16 chunks
它的特点是:先让全局 重排器 看完整个 Dense Top100,再围绕高位 anchor 做局部扩展。
| 阶段 | 为什么做 / 本次改动 | 核心结果 | 失败案例与归因 | 决策 | 详情 |
|---|---|---|---|---|---|
| G1:文档内证据扩展 | Dense 排名 → 前5篇去重文档 → 展开文档内 chunks → 合并后重排 → Top16。目标是解决“关键证据藏在同一文档更远位置”的问题 |
30 条:充分 24→28;宏平均关键事实覆盖率 0.825→0.956;6 个改善 / 22 个持平 / 2 个退化 | Q346:关键 chunk 原 Dense 排名 72,历史 E1 的全局 重排器 本来能在裁剪前把它救上来;G1 却先按 Dense 只保留5篇文档,关键文档在 重排 前已被永久裁掉。Q492 另有连续证据丢失 | NO_GO:平均提升明显,但出现 充分→不充分 的灾难性退化 | G1 结果与 Q346/Q492 分析 |
| G2-A:重排 后再做文档准入 | Dense Top100 → 全局 重排 → 前5篇去重文档 → 文档内展开 → 合并后重排 → Top16;只改文档准入顺序 |
新的 TRAIN 30 条:2 个改善 / 25 个持平 / 3 个退化;充分 19→19;宏平均关键事实覆盖率 .667→.650 | Q287/Q578 被全局 重排 救回,但 Q090/Q500 又因新的前5篇预算把原本有效文档挤出去。主因是准入排序不稳定 + 固定5篇预算放大这种不稳定 | NO_GO,不根据已经看过的失败 样本 继续补规则 | G2 五个变化 样本 逐条归因 |
宏平均关键事实覆盖率:先把标准答案拆成若干关键事实点,计算每条问题的事实覆盖率,再对问题等权平均。例如两个问题分别覆盖 100% 和 25%,宏平均关键事实覆盖率 为 62.5%。因此 G1 的 0.956 是平均关键事实覆盖率,不是“95.6% 准确率”。
直接 TopK 指:重排器 输出一条全局 chunk 排名后,直接取前 K 个 chunk 作为最终 上下文。这里的 “直接”表示不再做层级式或局部式扩展:不追加 相邻 chunk、不展开整篇文档、不做硬性文档准入,也不再进行第二次 重排。
当前冻结候选使用:
重排后的 chunks
→ 直接取前 14 个
→ final 上下文
历史 E1 / G1 实验曾使用最多 16 个 chunk;当前冻结工程候选是 直接 Top14,两者不是同一个上下文策略。
| 阶段 | 为什么做 / 本次改动 | 核心结果 | 失败归因与决策 | 详情 |
|---|---|---|---|---|
| 检索前沿审计 | G1/G2 都说明不同排序存在互补,但继续在已看过的 TRAIN 样本 上调 K、RRF、权重容易过拟合,因此只对冻结结果做零模型服务调用 的反事实分析 | 观察到 Dense 与全局 重排 确实存在互补,文档级 RRF 是未来可能方向 | 这些 30 条已经是设计/诊断数据,不能继续拿来证明新方案有效。关闭当前检索参数搜索 | 前沿审计 |
| 最终上下文预算 | 比较不同 直接 TopK,找达到当前证据命中上限的最小 K | 54 条证据样本:Top14 = 答案证据 35/54、有用证据 43/54;Top20 完全相同 | 从14增加到20没有新增 证据命中,只增加上下文。选择 直接 Top14;这是当前 TRAIN 开发集选择,不是通用最佳 K | 架构冻结 |
| 局部二次 重排 | 尝试对 Top14 未覆盖、且具备局部恢复条件的残留 样本 再做局部候选恢复与第二次 重排 | 答案证据 35→35;有用证据 43→43;7 个具备局部恢复条件的 残留样本,0/7 被救回 | 没有新增 证据命中,还增加一次 重排;p50 约 661 ms。拒绝 | 架构冻结 §4.1 |
| 结构保持扩展 | 尝试通过同文档 / 结构扩展恢复被固定 chunk 切分打散的信息 | 答案证据 35→32;有用证据 43→38;2 个未命中→命中,但 5 个命中→未命中 | 5/5 退化都来自上下文预算挤占:同一文档放入更多内容后,跨文档证据 被挤掉;上下文 中文档数中位数 12→4.5。拒绝,保留 直接 Top14 | 架构冻结 §4.2–5 |
最终冻结的检索 / 上下文 工程候选:
Dense Top100
+
BM25 Top100
↓
等权 RRF (k=60)
↓
fused Top100
↓
qwen3-rerank
↓
直接 Top14
- 检索参数研究:CLOSED
- 上下文 组装研究:CLOSED
- 当前默认运行时、问答和工单:仍是 Dense Chroma 检索
- Hybrid + 重排 + 直接 Top14:已接入默认关闭的可选搜索和实验问答,工单不变;整链质量尚未验收
冻结检索 / 上下文 后,离线研究转向判断 直接 Top14 是否包含足够证据;默认低相关拒答仍保留 Dense Top1 距离阈值(默认 0.9),没有被开发候选替换。另有显式开启的 v5.3 实验问答路径,不代表其通过独立质量验收。
| 阶段 | 本次方案 | 结果 | 失败归因 | 结论 | 详情 |
|---|---|---|---|---|---|
| 拒答 v1.1 | 直接 Top14 → qwen3.5-plus 证据充分性分类 |
平衡准确率 0.736;充分证据召回率 97.1%;不足证据召回率仅 50.0% | 16 个不足证据样本 中有 8 个被错误放行为“充分” | FAIL,不进入生成与 运行时集成 | v1.1 正式结果 |
| 拒答 v2.1 | 新分类契约 + 新的 AI 草稿代理标签 | 平衡准确率 0.775,门槛 0.80;充分召回 .85,不足召回 .70 | 只差 1 个 样本 即可过门槛,但后验检查发现部分 分歧 来自标注契约与问题实际要求不一致,不能简单继续调 提示词 | FAIL,不修改门槛后重跑 | v2.1 结果 |
| 拒答 v3 | 从盲标 AI 草稿中冻结 50 条 TRAIN,25 充分 / 25 不充分 | 与 AI proxy 一致 38/50;平衡准确率 0.76;两类召回均 0.76 | 充分题误拒 6 条、不足题误放行 6 条;标签不是独立人工金标 | FAIL,不进入生成验收或 runtime | v3 结果 |
| v4 / v5.1 配对开发诊断 | 20 条 TRAIN 同题对照;v5.1 选择来源行,由代码提取原文并推导决策 | 40 次调用全部有效;AI proxy 一致 16/20 → 17/20;估算总费 ¥0.741975 | 不足题误放行 4/6 → 2/6,但新增 1 个充分题误拒;费用约 2.06 倍、p50 延迟约 1.82 倍 | NO_PROMOTION;非独立 AI 标签、小型已见 TRAIN,不能宣称全面改善 | v5.1 冻结结果 |
| v5.2 必填覆盖审计 | 强制检查请求结果、适用性、版本与时效;复用行引用校验 | 14 条虚构控制题真实模型对照:v5.1 有效且符合构造标签 14/14,v5.2 12/14,另有 2 次格式失败 | 模型将已有证据的审计栏位输出为空引用;格式失败不是正确拒答 | NO_PROMOTION;控制题不是自然问题准确率 | 控制题结果、零调用机制演示 |
| v5.2.1 引用示例调整 | 仅修改提示词中的引用示例,不放宽 schema / 引用校验 | 同一批已见虚构控制题,新跑 v5.2 / v5.2.1 均 14/14 有效且符合构造标签 | 旧版本的 2 次格式失败在同期基线中也未重现,不能归功于新示例 | 未证明格式修复收益 / NO_PROMOTION | 同期回归结果 |
| v5.1 / v5.2.1 自然 TRAIN 诊断 | 30 条冻结自然问题、完整 Top14、两组新跑 | 25 条二元 AI proxy 标签一致 19/25 → 21/25;充分题误拒 5/21 → 3/21 | 修正 4 次误拒、引入 2 次;不足题误放行仍 1/4;费用约 1.36 倍 | 仅开发诊断 / NO_PROMOTION | 自然问题结果 |
| v5.2.1 / v5.3 问题范围回归 | 明确只检查必要请求,并要求具体事故的证据适用性;相同 30 题、两组重新运行 | 二元 AI proxy 标签一致 22/25 → 23/25;充分题误拒 2/21 → 1/21;60 次调用估算 ¥1.917294 | 修正 2 次误拒、新增 1 次;同一个不足案例仍误放行(1/4),部分标签一致的推理仍有无依据外推 | 仅已见题开发回归 / NO_PROMOTION,未解决语义可靠性 | v5.3 完整结果 |
后两次自然问题实验使用同一套冻结 AI proxy 标签:21 条充分、4 条不足、5 条有歧义; 5 条有歧义只作描述,不计入二元一致率。这不是独立专家金标,也没有生成答案或验证线上整链。 v5.3 的设计已经参考这些题;其同期 v5.2.1 基线为 22/25,不能拿历史 21/25 来夸大增益。 目前不继续在这 30 题上追加提示词或重复付费调用;后续语义验证需先明确新样本与冻结口径, 另行授权数据发送和费用。v5.2~v5.3 均未替换线上 Dense 距离拒答。
完整实验契约、索引与冻结产物见 experiments/evals/README.md。
文档后端提供从知识入库到下架的显式生命周期:
Upload
↓
文档记录
↓
显式索引
↓
切片 + 向量化
↓
RAG 检索
↓
Delete
├─ 关系库 chunk 删除
└─ Chroma 向量删除
主要 API:
POST /documents/upload
GET /documents
GET /documents/{document_id}
POST /documents/{document_id}/index
DELETE /documents/{document_id}当前支持 md / txt 上传,并由认证上下文中的 tenant 约束文档访问范围。
AgentOps 将关键执行信息持久化,而不是只写控制台日志。
主要实体:
agent_runs
├─ tool_calls
└─ approval_requests
retrieval_logs
主要查询能力:
GET /agent-ops/runs
GET /agent-ops/runs/{agent_run_id}
GET /agent-ops/runs/{agent_run_id}/trace
GET /agent-ops/tool-calls
GET /agent-ops/approval-requests
GET /agent-ops/retrieval-logs
GET /agent-ops/metrics/summary
GET /agent-ops/metrics/retrieval
GET /agent-ops/metrics/retrieval/sources
GET /agent-ops/metrics/retrieval/no-context-queries
GET /agent-ops/metrics/retrieval/failures支持按 tenant 查看:
- Agent 运行记录 状态;
- 工具调用 成功 / 失败与 error type;
- 审批 状态;
- 检索无上下文 / 拒答 / 失败;
- 检索来源分布;
- 单次运行轨迹。
项目包含用于工程验证的 Demo JWT Auth:
- Bearer token;
user_id;tenant_id;role;support/admin角色检查;- 按租户隔离的 Document / AgentOps / RAG 访问。
这里验证的是认证上下文和 租户范围 在应用链路中的传递,不把它描述为完整生产级 IAM / RBAC 或数据库级多租户隔离方案。
安全边界见 docs/security.md。
enterprise-support-ai-copilot-api/
├── main.py
├── auth.py
├── database.py
├── rag_runtime/
├── routers/
├── schemas/
├── services/
├── models/
├── experiments/
│ ├── evals/
│ ├── docs/
│ └── rag_local/
├── docs/
├── scripts/
├── tests/
├── migrations/
├── docker-compose.yml
├── Dockerfile
└── README.md
其中:
rag_runtime/:正式在线 RAG 运行时;experiments/evals/:TechQA 主评测、受控实验与产物;experiments/rag_local/:早期兼容入口;- Todo / AI Todo 路径保留为历史兼容,不作为当前项目定位。
在仓库根目录创建或编辑未提交的 .env,参考 .env.example(不要覆盖已有配置):
DASHSCOPE_API_KEY=your_dashscope_api_key_here
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
DASHSCOPE_MODEL=qwen3.5-plus
EMBEDDING_MODEL=text-embedding-v4
DATABASE_URL=sqlite:///data/todos.db
SQL_ECHO=true
DOCUMENT_STORAGE_ROOT=storage/documents以下命令在仓库根目录、同一已激活的 Python 环境中运行,确保 python 与 alembic 属于该环境。
先创建默认 SQLite 父目录,再将 .env 载入迁移进程;
若修改 DATABASE_URL,需创建对应父目录。迁移与启动都优先使用已有 shell 环境变量。
python -m pip install -r requirements.txt
python -c "from pathlib import Path; Path('data').mkdir(exist_ok=True)"
python -m dotenv -f .env run --no-override -- alembic upgrade headpython -m uvicorn main:app --reload --env-file .envSwagger:
http://127.0.0.1:8000/docs
docker compose up --buildDocker Compose 用于本地可复现运行与核心链路验证,不作为生产部署能力声明。
完整本地测试:
python -m pytest -q静态检查:
ruff check .
python -m compileall -q .Smoke:
下面的脚本需要 API 已启动,会调用已配置的向量模型并写入测试数据,请使用隔离的运行目录。 工单脚本会调用 confirm,保留测试工单与审计记录;文档脚本会移除测试向量,但保留软删除记录和原文件。 首次运行可先执行文档脚本建立 Chroma 集合。若要验证工单 preview 命中来源,应先上传并索引相关 IT 文档; 工单脚本本身不要求来源命中,不能据其成功宣称检索质量已通过验证。
python scripts/smoke_document_backend_flow.py
python scripts/smoke_agentops_flow.py零调用拒答 gate 演示(合成资料、手写响应;不是模型效果或线上端到端验证):
python -m scripts.demo_refusal_coverage运行步骤及已知错误放行示例见 离线 gate 演示。
GitHub Actions 的 test 作业执行 Python 3.11 环境配置、依赖安装、compileall、Ruff 和核心定向测试。
Workflow:
推荐阅读顺序:
- README.md — 项目定位与能力总览;
- experiments/evals/README.md — TechQA 长期主评测与实验契约;
- RAG 冻结架构 — 当前冻结的检索 / 上下文工程决策;
- docs/architecture.md — 系统结构与边界;
- docs/agent_workflow.md — 工单 Agent 预览 / 确认;
- docs/security.md — 当前认证边界提示与早期 MVP 安全记录;
experiments/evals/reports/— 检索 / Hybrid / 证据 / 生成实验产物。
docs/*_report.md 与 docs/superpowers/ 中保留历史阶段报告、设计与实验计划,用于追溯项目演进;历史路线图 不自动代表当前产品方向。
当前项目对外表述保持以下边界:
- TechQA 是长期主技术支持语料与主评测基准,不再计划迁移到另一套 primary corpus;
- 不把离线 BM25 / RRF / Hybrid 实验或默认关闭的可选搜索写成默认线上已切换或公网已部署;
- portfolio-v1 的 Hybrid + 重排已冻结并接入可选搜索和默认关闭的实验问答,工单未接入;R4 C1 的正式结论仍为 FAIL;
- 直接 Top14 是 TRAIN 开发阶段的选择,不是通用最优值;
- 局部扩展 / 结构扩展的负结果不能证明 Parent-Child 或文档结构方法普遍无效;
- 拒答策略和最终生成验证尚未冻结;
- 不把规则化 Ticket 分类写成自主 ReAct / 自主规划;
- 不把 Demo JWT + 租户范围 写成完整生产级 IAM / 多租户隔离;
- 不把 Docker Compose 写成生产部署;
- 不把 approval
pending校验写成并发恰好一次保证; - 不把 G1 的条件性证据充分性改善写成生产生成效果提升,也不声称 G1 已替代 E1;
- 不针对 Q346 / Q492 已知 TRAIN 退化样本 做 样本-specific patch 后再把原 30-样本 样本当作 新的独立验证;
- 不声称当前系统已具备完整 多来源 / 冲突处理 / 自主 Agentic RAG 能力。
项目当前关注的是:
围绕企业技术支持中的“知识检索 → 回答 / 拒答 → 工单升级”建立可控业务闭环,并用同一 TechQA 主线持续回答“系统是否真的变好、失败在哪里、某次工程改动是否值得保留”。
- 对外展示名:Enterprise Support AI Copilot
- 中文定位:企业技术支持 RAG + 受控工单 Agent
- 仓库:
Enterprise-Support-AI-Copilot-API