欢迎访问本站,持续更新中…

WIKI 知识库 v1.1.4 正式发布|答案能点回原文了

WIKI 知识库 v1.1.4 正式发布,新增引用归因功能。检索命中分片会按列表顺序编成 [1][2][3],塞进提示词要求模型句末标号,流式答案里解析 [n],前端角标可点击定位第 n 条来源卡。验收口径(产品规划)归因率 ≥95%,跳转正确率抽检 ≥99% 不明显拖慢首 token。

那么这个功能能解决什么问题呢?

混合检索再准用户心里仍会问一句这段话到底从哪来的?以前常见问题有三种:

  1. 回答写得很自信但侧栏来源对不上没法核对。
  2. 模型随口写据资料显示却没有可点的角标。
  3. 有检索命中但答案和来源列表各说各的信任感上不去。

v1.1.4 把链路钉死成一条可核对路径:

检索命中分片
  → 按列表顺序编成 [1][2][3]。
  → 塞进提示词要求模型句末标号。
  → 流式答案里解析 [n]。
  → 前端角标可点击定位第 n 条来源卡。

验收口径(产品规划)归因率 ≥95%,跳转正确率抽检 ≥99% 不明显拖慢首 token。

整体链路方便转发分享

┌──────── 用户检索问答页────────┐
│  提问 → SSE 流式回答               │
│  答案中的 [1]/[2] 可点击          │
│  右侧「知识来源」高亮对应分片       │
└──────────────┬───────────────────┘
               │ POST /api/query?stream=1
               ▼
┌──────── 后端 KnowledgeBaseApi ────┐
│ 1. 检索与证据门槛                 │
│ 2. _assign_source_ranks → rank=n   │
│ 3. _build_context → n 文件名   │
│ 4. 系统提示词强制须标注来源编号 │
│ 5. SSE meta.results + token 流     │
└────────────────────────────────────┘
层次 路径 说明
上下文编号 src/api/http_server.py → _build_context 按顺序生成 [1] 文件…
提示词约束 _base_system_prompt(with_context=True) 要求句末标号、禁止编造
来源名次 _assign_source_ranks rank 与角标同序
单测 tests/test_kb12_citation.py 编号 / 提示词 / 名次对齐
页面 frontend/src/views/chat/RetrievalQa.vue 解析 [n]、点击定位
流式 API frontend/src/api/query.ts → streamQuery SSE 拿 results + 答案

普通用户又怎么用?

  1. 升级到社区版 v1.1.4。
  2. 打开控制台 切换至 检索问答。
  3. 提一个知识库里有依据的问题。
  4. 看回答气泡句子末尾会出现可点击的 [1]、[2]。
  5. 点击角标右侧知识来源滚到并高亮对应分片,再点来源卡可核对原文。

若回答是根据当前知识库无法确定:

  • 侧栏会改成检索候选避免引用很多条却答不出的误解;
  • 候选默认收起需要时可手动展开核对。

带代码开发者怎么接呢?

1. 流式问答一次请求同时拿答案和来源如下示例:

curl -N -X POST "http://127.0.0.1:5000/api/query?stream=1" \
  -H "Content-Type: application/json" \
  -d "{
    \"query\": \"混合检索的 RRF 常数默认是多少?\",
    \"k\": 5,
    \"generate_answer\": true,
    \"deep_think\": false
  }"

SSE 里典型会先有一条带 results来源列表,含 rank / filename / preview_text的 meta 再持续推送回答 token。答案文本中应出现与 rank 对齐的 [1] [2],非流式调试方便看整包如下示例:

curl -s -X POST "http://127.0.0.1:5000/api/query" \
  -H "Content-Type: application/json" \
  -d "{\"query\":\"本地知识库支持哪些导入方式?\",\"k\":5,\"generate_answer\":true}" \
  | python -m json.tool

关注字段:

  • results[].rank来源序号从 1 开始
  • results[].filename / text可核对原文
  • 回答正文里的 [n]必须落在 1…len(results) 内

2. 后端:上下文如何编号

核心逻辑简化示意,与仓库实现一致:

# src/api/http_server.py · _build_context
def _build_context(results, max_chars: int = 5000) -> str:
    blocks = []
    total = 0
    for idx, item in enumerate(results, start=1):
        filename = str(item.get("filename") or f"doc_{idx}")
        text = str(item.get("text") or "").strip()
        if not text:
            continue
        piece = f"[{idx}] {filename}\n{text}"
        if blocks and total + len(piece) > max_chars:
            break
        blocks.append(piece)
        total += len(piece)
    return "\n\n".join(blocks)

喂给模型的用户侧如下内容:

知识库上下文:
[1] 混合检索说明.md
RRF 默认常数为 60

[2] 检索配置.md
hybrid_enabled 默认 true

用户问题:RRF 常数默认是多少?

系统提示词强制要求:

引用上下文内容时须在对应句子末尾标注来源编号如 [1]、[2],
编号必须与上下文编号一致不得编造不存在的编号。

3. 来源名次角标和侧栏必须同序

# src/api/http_server.py · _assign_source_ranks
@staticmethod
def _assign_source_ranks(items: list[dict]) -> None:
    peak = max((float(item["similarity"]) for item in items), default=0.0)
    for rank, item in enumerate(items, start=1):
        sim = float(item["similarity"])
        relative = 1.0 if peak <= 1e-12 else max(0.0, min(1.0, sim / peak))
        item["rank"] = rank
        item["relative_similarity"] = round(relative, 4)

约定答案里的 [n] = 当前来源列表的第 n 项(1-based)。证据门槛过滤后会重新编号避免角标指向被丢掉的旧序号。

4. 前端把 [n] 变成可点按钮

// frontend/src/views/chat/RetrievalQa.vue · answerSegments(示意)
function answerSegments(msg: ChatMessage) {
  const text = String(msg.content || '')
  const total = (msg.sources?.length ? msg.sources : sources.value).length
  const out = []
  const re = /\[(\d+)\]/g
  let last = 0
  for (let m = re.exec(text); m; m = re.exec(text)) {
    const n = Number(m[1])
    if (n < 1 || n > total) continue // 越界编号直接忽略,防幻觉角标
    if (m.index > last) out.push({ text: text.slice(last, m.index), cite: 0 })
    out.push({ text: m[0], cite: n })
    last = m.index + m[0].length
  }
  if (last < text.length) out.push({ text: text.slice(last), cite: 0 })
  return out.length ? out : [{ text, cite: 0 }]
}

点击角标时调用 locateSource(msg, n),高亮 sources[n - 1]越界的 [99] 不会变成可点按钮——这是有意的避免假引用。

5. 自测仓库里已有定向用例

python -m pytest tests/test_kb12_citation.py -q

用例会检查:

  • 上下文出现 [1] 文件名、[2] 文件名
  • 提示词包含标注来源编号,不得编造
  • rank 为完整的 1…n答案角标落在合法区间。

怎么和之前版本衔接

版本 能力 和归因的关系
v1.1.2 检索配置热更新 调 k / 阈值,影响「能归因到哪些块」
v1.1.3 混合检索 召回更稳,归因才有东西可点
v1.1.4 引用归因 把命中变成可核对的 [n]

当没有可靠检索归因只是空壳没有归因检索再准也难让人放心,两者是一套质量主轴上的前后环。

总结

v1.1.4 要交付的不是多画几个角标而是一条可复核的信任链路如下链路:

检索 → 编号 → 生成 → 点击 → 原文。

欢迎试用提 Issue 提 PR。