先把写入路径跑通再谈召回——OpenViking 接入 Agent 的第一步

文章导读
接入 Agent 后召回效果不理想,先要分清两件事:记录根本没写进去,还是写进去了但检索条件写得不对。这两类问题的排查成本差很多。建议的顺序是:先确认写入端拿到的数据完整、能到达服务端、能按同一标识读回来;写入链路验证通过之前,检索侧的模型、切片和过滤条件保持不动。这样出问题时,你至少知道该往哪一侧看。
📋 目录
  1. Ⅰ 确认写入端拿到的上下文是完整的
  2. Ⅱ 用一条最小记录验证写入链路
  3. Ⅲ 按标识把刚写入的记录读回来
  4. Ⅳ 记录写入失败时的可见信号
  5. Ⅴ 把写入成功当成开检索调试的前置门槛
A A

接入 Agent 后召回效果不理想,先要分清两件事:记录根本没写进去,还是写进去了但检索条件写得不对。这两类问题的排查成本差很多。建议的顺序是:先确认写入端拿到的数据完整、能到达服务端、能按同一标识读回来;写入链路验证通过之前,检索侧的模型、切片和过滤条件保持不动。这样出问题时,你至少知道该往哪一侧看。

召回差时不要先调检索参数。按“写入前打印原始字段与长度 → 用一条最小记录触发写入 → 用同一标识读回”跑一遍:能写回并读回,说明数据已落地,问题在检索或索引侧;写回失败或读不回来,就按超时、状态码异常、字段缺失三类信号分别定位。写入未验证通过前,检索侧参数固定不变,避免两端同时变更导致无法归因。

确认写入端拿到的上下文是完整的

上游丢数据是接入早期最常见的一层,而且很难靠肉眼看日志发现。做法是在调用写入方法之前,把这几项打到同一行日志里:原始 content 的长度、字段键列表、业务标识(id / tenant / user 等)、metadata 的键、本次请求的文件或切片条数。打印长度比打印内容更可靠,内容片段被日志系统截断时,长度值仍然能反映真实大小。

需要对比的值通常是这几组:上游拼接完成后的内容长度,与写入函数入参里的 content 长度是否一致;切片阶段产出的片段数量,与请求里真正带上的条数是否一致;同一个业务标识在上下游打印出来的值是否完全相同。常见的丢失位置包括:模板拼装时把正文放到了错误键(例如 text 与 content 混用)、上游过滤把 content 为空的记录提前丢掉、切片后只把最后一片传下去、编码转换后出现了空字符串。这一层排除掉之后,再谈写入是否成功才有意义。

用一条最小记录验证写入链路

准备一条不会和现有数据冲突的探针记录:标识用固定前缀加一个可识别的后缀,内容里放一个唯一串,长度适中,确保后面检索时能唯一定位。下面是通用调用骨架,字段名和调用方式按你实际的 OpenViking 接入参数替换。

# 通用接入骨架,字段名按实际接入参数替换
payload = {
    "id": "probe-0001",                      # 调试期固定的业务标识
    "content": "写入链路探针,唯一串 probe-0001",
    "metadata": {"source": "write-probe"},
}
print("len(content)=", len(payload["content"]))
print("keys=", sorted(payload.keys()))
print("id=", payload["id"])

resp = client.write(payload)                 # 替换为实际写入方法或 HTTP 调用
print("status=", resp.status_code)
print("body=", resp.text[:500])

如果直接用 HTTP 形态接入,请求大致长这样,路径、字段和鉴权头以实际服务为准:

POST {BASE_URL}/v1/write
Authorization: Bearer {TOKEN}
Content-Type: application/json

{
  "ids": ["probe-0001"],
  "documents": ["写入链路探针,唯一串 probe-0001"],
  "metadatas": [{"source": "write-probe"}]
}

判定标准建议定成固定规则,不要凭感觉:返回状态为 2xx,且响应体中 error 字段为空或不存在,记为写入成功;返回非 2xx、连接超时、或响应体中 error 非空,一律记为失败。失败时把响应体原文和请求体原文一起保留,后面分类要用到。

先把写入路径跑通再谈召回——OpenViking 接入 Agent 的第一步

按标识把刚写入的记录读回来

读取必须用和写入同一套键:同一个标识、同一个命名空间或集合。如果写入返回的 id 是服务端生成的,就先用响应里返回的那个 id 去读,不要假设它等于你传进去的值。

GET {BASE_URL}/v1/get?ids=probe-0001
Authorization: Bearer {TOKEN}

读回后比对四个字段:标识是否一致、content 的长度与前缀是否和写入值相同、metadata.source 是否为 write-probe、写入时间是否落在刚才这次请求的时间窗内。读不到记录时,建议按下面的顺序逐项排查,不要跳步:

  1. 回看写入响应是否真的成功,避免把失败当成功继续往下查。
  2. 核对读取和写入用的命名空间、集合或索引名是否完全一致。
  3. 确认标识有没有被服务端重写或拼接(大小写、前缀、租户前缀都算)。
  4. 确认该服务是否需要显式刷新或提交之后才能被读到,也就是存在可见性延迟。
  5. 检查读取请求里的过滤条件是否把这条记录排除了,例如租户、状态、时间窗。

记录写入失败时的可见信号

把失败信号分成三类,能更快缩小范围,也避免把网络问题当成字段问题去改代码。

  • 超时或连接失败:先确认地址、端口和连通性,再检查单次批量是否过大。调试期把批量固定为 1,如果小批量能通过、大批量超时,问题通常在批量或服务端处理时间上,而不是字段结构。
  • 状态码异常:401、403 多为鉴权范围或 token 失效;404 多为路径、库名、集合名写错;400 多为字段缺失或类型不符;429 是限流;5xx 归到服务端或请求格式。动作是先抄下响应体里的 code 与 message,再和请求原文逐字段对照。
  • 返回成功但字段缺失或读不到:常见原因是字段名映射错误、必填字段为空、content 传了空字符串、metadata 类型不合法被丢弃。动作是把请求体原样打印,只保留最小必填字段重试一次,确认是哪个字段导致的。

把写入成功当成开检索调试的前置门槛

建议的验证顺序是:写一条 → 读回一条 → 用这条记录的标识或唯一串做一次检索 → 命中之后,再逐步扩大数据量、再调 topk、阈值和切片。这样一旦检索结果不对,你能判断是数据没进去,还是检索侧的问题。

调试期间需要固定不变的变量清单:embedding 模型与版本、向量维度、距离度量、切片长度与重叠、命名空间与集合名、鉴权 token 与租户、检索 topk 与过滤条件、写入批量。每次只改其中一项,改完立刻用同一条探针记录复验。需要注意的边界是:读回成功但检索不到,通常要去看索引是否已建立、写入向量和查询向量是否来自同一模型、过滤条件是否过严;如果读回本身就失败,先不要动检索参数,那只会让问题更难定位。