本地规范检索服务设计.md 14 KB

本地规范检索服务设计

1. 文档目的

本文档记录本地规范检索服务当前已经达成共识的需求和接口边界,作为后续讨论匹配算法、阈值、日志及实现方案的基础。

本文档同时记录第一版匹配算法、初始阈值和测试用例。后续可根据真实查询日志调整参数,但不得改变服务的职责边界。

2. 建设目标

施工方案审查 Agent(智能体)需要检查方案中引用规范的时效性。为减少 Web Search(网页搜索)的调用和等待时间,建设一个基于本地人工维护规范库的检索服务。

服务接收规范查询关键词,从本地规范库中检索相关规范,并返回最多 3 条候选。Agent(智能体)结合候选内容,自行判断引用规范的时效性。

本地规范库由人工维护,默认其中的数据准确可信。

3. 服务职责边界

3.1 服务负责

  • 接收一批规范查询关键词。
  • 支持按规范名称、规范编号、名称加编号进行查询。
  • 根据规范名称和规范编号执行本地匹配。
  • 每条查询最多返回 3 条候选规范。
  • 按匹配度从高到低排列候选规范。
  • 返回候选规范的状态、实施日期和替代情况。
  • 记录查询日志,支持后续分析、清洗和补充本地规范库。

3.2 服务不负责

  • 不直接判断引用规范是否有效或是否过期。
  • 不替 Agent(智能体)选择最终采用的规范。
  • 不声明查询结果是否唯一或是否存在歧义。
  • 不使用替代情况参与检索、召回或排序。
  • 不根据替代情况自动关联、扩展其他规范。
  • 不返回 data_trust(数据可信度)
  • 不进行 Web Search(网页搜索)兜底。

4. 接口设计

服务只提供一个批量查询业务接口:

POST /v1/search

4.1 请求结构

{
  "keywords": [
    "GB 50330-2013 建筑边坡工程技术规范",
    "混凝土结构设计规范",
    "GB 50330"
  ]
}

请求说明:

  • keywords(关键词列表)为必填数组,第一版每批允许 1~100 条。
  • 每个元素代表一条独立的规范查询。
  • 查询内容允许为规范名称、规范编号,或者名称加编号。

4.2 响应结构

{
  "results": [
    {
      "query": "GB 50330-2013 建筑边坡工程技术规范",
      "candidates": [
        {
          "code": "GB 50330-2013",
          "title": "建筑边坡工程技术规范",
          "status": "现行",
          "relation_text": "替代 GB 50330-2002",
          "implement_date": "2014-06-01",
          "match_score": 1.0
        }
      ]
    },
    {
      "query": "本地规范库中不存在的规范",
      "candidates": []
    }
  ]
}

响应说明:

  • results(查询结果列表)与请求中的查询顺序一致。
  • query(原始查询)原样返回,方便调用方建立对应关系。
  • candidates(候选列表)包含 0~3 条规范。
  • 候选列表为空表示本地没有达到返回条件的候选规范。
  • 服务不返回 resolution(解析状态)matched(是否命中)

5. 候选规范字段

每条候选规范当前返回以下字段:

字段 含义
code 规范编号
title 规范名称
status 规范状态,例如现行、已作废、已废止
relation_text 本地库中的替代情况原文
implement_date 实施日期
match_score 查询关键词与候选规范的匹配度

替代情况仅作为候选规范的附带信息原样返回,不参与匹配计算。

6. 匹配范围

服务只使用以下内容进行匹配:

  • 规范编号。
  • 规范名称。

允许处理的典型查询包括:

  • 完整规范编号,例如 GB 50330-2013
  • 不含年份的规范编号,例如 GB 50330
  • 规范名称,例如 建筑边坡工程技术规范
  • 规范编号加名称,例如 GB 50330-2013 建筑边坡工程技术规范

当名称或不含年份的编号匹配到多个规范版本时,服务可以返回多个候选,但最多返回 3 条。

6.1 本地索引字段

第一版使用以下字段建立检索索引:

  • csres_code(官方编号)
  • csres_normalized_code(标准化编号),作为编号别名使用。
  • csres_title(规范名称)

query_keyword(原始采集关键词)不作为规范名称索引,避免采集页面标题、应用指南等噪声影响匹配。

6.2 查询解析

每条查询先拆分为“规范编号”和“规范名称”两个部分。例如:

输入:《建筑边坡工程技术规范》GB 50330—2013
编号:GB 50330-2013
名称:建筑边坡工程技术规范

编号解析支持编号出现在文本开头、末尾或括号内。第一版约定每条关键词描述一项规范;如果一条关键词中包含多个规范编号,服务仍按相关度返回最多 3 条候选,但调用方应尽量提前拆分。

6.3 编号标准化

编号比较前进行以下处理:

  • 统一全角和半角字符。
  • 统一不同样式的横线和冒号。
  • 删除编号内部多余空格。
  • 英文字母统一为大写。
  • 保留编号中的斜杠、点号、冒号和横线。

编号匹配分级如下:

编号情况 编号匹配度
标准化后完整编号一致 1.00
两位年份与四位年份对应 0.98
编号主体和年份一致,但 GB/GB/T 等属性不同 0.93
编号主体一致,但查询缺少年份 0.90
查询缺少分册号、分部分号或年份 0.80
编号疑似存在一个字符错误,且年份一致 0.75
编号主体一致但年份不同 0.70

年份不同只降低编号匹配度,不直接淘汰候选。服务返回相关规范及其实际编号,由 Agent(智能体)判断查询年份是否错误。

6.4 名称标准化和匹配

名称比较前进行以下处理:

  • 统一全角和半角字符。
  • 删除空格、书名号和普通标点。
  • 英文字母统一为小写。
  • 保留“规范”“规程”“标准”等具有区分意义的词语。

名称匹配依次考虑:

  1. 标准化后完全一致,匹配度为 1.00。
  2. 官方名称完整包含在查询名称中,根据覆盖比例计算 0.90~0.98 的匹配度。
  3. 查询名称完整包含在官方名称中,根据覆盖比例计算 0.65~0.95 的匹配度。
  4. 使用字符相似度计算模糊匹配度。

查询名称少于 6 个中文字符时,不启用宽松模糊匹配,只允许完全一致或完整包含匹配。短名称包含匹配必须按覆盖比例降低分数,避免“验收规程”等通用短语获得虚高匹配度。

6.5 编号和名称的并行匹配

第一版采用编号、名称双通道并行匹配:

  1. 对本地库中的每条规范分别计算编号匹配度和名称匹配度。
  2. 编号或名称任一维度达到相关度阈值,该规范即可进入候选池。
  3. 编号、名称相互冲突时不淘汰候选,只降低最终匹配度。
  4. 年份不同不阻止名称高度相关的规范进入候选池。
  5. 所有候选统一评分、去重、排序后返回前 3 条。

这样既能召回类似 TG/T 5440-2018JTG/T 5440-2018 的单字符编号错误,也能让 JGJ 18-2022查询看到名称完全一致的 JGJ 18-2012,最终由 Agent(智能体)判断编号冲突。

7. 匹配度

match_score(匹配度)采用 0~1的数值范围:

  • 数值越大,表示查询关键词与候选规范越相关。
  • 匹配度只表示文本和编号的相关程度。
  • 匹配度不是候选规范正确的概率。
  • 多个不同年份的规范可能具有相同的高匹配度。

7.1 最终匹配度

只有编号时:

match_score = 编号匹配度

只有名称时:

match_score = 名称匹配度

同时包含编号和名称时:

较高分 = max(编号匹配度, 名称匹配度)
较低分 = min(编号匹配度, 名称匹配度)
match_score = 较高分 × 70% + 较低分 × 30%

该公式保证编号或名称任一维度高度相关时仍可返回,同时在另一维度冲突时降低最终匹配度。服务不再设置名称兜底分数上限。

7.2 候选进入条件

候选满足以下任一条件即可进入排序:

  • 查询包含编号,且编号匹配度不低于 0.70。
  • 查询包含名称,且名称匹配度不低于 0.75。

准入条件按两个通道独立判断,不使用最终加权分数淘汰单项高度相关的候选。

7.3 排序和截断

  • 候选按照最终匹配度降序排列。
  • 分数相同时,编号匹配度更高的候选优先。
  • 同一编号不同年份且前述分数相同时,按年份从新到旧展示。
  • 按官方规范编号去重。
  • 每条查询最多返回 3 条候选。
  • 状态、实施日期和替代情况不参与召回、评分或排序。

8. Agent(智能体)使用原则

调用服务的 Agent(智能体)应遵守以下原则:

  • 自行核对查询中的规范编号、年份和名称。
  • 不能因为服务只返回一条候选,就认定该候选一定唯一。
  • 当返回多个版本时,根据方案原文和候选信息自行判断。
  • 当无法确定具体版本时,应指出引用信息不足,而不是猜测。
  • 候选列表为空时,不进行规范时效性判断。
  • 根据候选规范的状态、实施日期和替代情况判断时效性。

9. 日志要求

服务后台需要记录查询日志,最低包含:

  • 查询时间。
  • 原始查询内容。
  • 查询结果。

日志用于:

  • 分析未命中的规范。
  • 发现误匹配和低质量匹配。
  • 指导后续补充和清洗本地规范库。
  • 复盘服务返回结果。

第一版使用 JSONL(逐行结构化日志),每条子查询记录一行,至少包含:

  • 带时区的查询时间。
  • 原始查询内容。
  • 返回的候选规范及匹配度。
  • 查询耗时。

日志文件使用 UTF-8(统一字符编码)写入。第一版不实现自动轮转,待实际运行后根据日志增长速度确定轮转和保留策略。

10. 并发处理

服务支持多个 HTTP(网络请求)并发访问。规范库在服务启动时加载到内存,查询阶段只读本地数据;单进程内日志写入使用线程锁保护。

第一版实现重点保证并发下不出现共享状态写坏的问题。匹配计算仍属于 CPU(计算密集型)工作,因此吞吐能力主要受单机 CPU(中央处理器)、Uvicorn(Python ASGI 服务)进程数和单批关键词数量影响。

当前建议:

  • 每批关键词最多 100 条。
  • 如果调用端存在 10 个左右并发请求,建议 HTTP 客户端读取超时不低于 15 秒。
  • 如果部署为多进程服务,需要重新设计日志写入方式,例如按进程分日志文件或接入标准 logging(日志模块)方案。

11. 当前已确认事项

  • 只提供一个批量查询业务接口。
  • 输入为规范查询关键词列表。
  • 查询支持名称、编号、名称加编号。
  • 只根据名称和编号匹配。
  • 每条查询最多返回 3 条候选。
  • 候选按匹配度降序排列。
  • 返回匹配度,但不返回解析状态或数据可信度。
  • 替代情况仅随候选结果原样返回。
  • Agent(智能体)负责最终时效性判断。
  • 本地未找到合适候选时返回空候选列表,不进行外部搜索。
  • 服务记录查询时间、查询内容和查询结果。

12. 后续待讨论事项

  • 单条关键词长度限制。
  • 日志轮转、保留时间和维护分析流程。
  • 本地规范库的加载、更新和版本管理方式。
  • 服务进程的部署、守护和开机启动方式。
  • 根据真实日志校准匹配阈值和编号纠错规则。

13. 第一版测试用例

编号 测试场景 示例查询 预期结果
1 完整编号和名称 GB 50330-2013 建筑边坡工程技术规范 2013 版排第一,匹配度为 1.00
2 全角符号和多余空格 GB 50330—2013 命中 GB 50330-2013
3 两位年份 CJJ 69-95 命中 CJJ 69-1995
4 规范属性差异 GB/T 6441-1986 可以召回 GB 6441-1986,匹配度低于精确编号
5 编号缺少年份 GB 50330 返回本地库中相关年份版本,最多 3 条
6 名称对应多个版本 建筑边坡工程技术规范 返回同名版本,最多 3 条
7 名称错别字 绕结多孔砖和多孔砌块 可以召回“烧结多孔砖和多孔砌块”
8 编号单字符错误且名称一致 TG/T 5440-2018 公路隧道加固技术规范 召回 JTG/T 5440-2018,匹配度低于精确编号
9 编号年份冲突 JTG D70/2-2014 公路隧道设计规范 可以返回名称相关的 JTG D70-2004,但匹配度应体现编号冲突
10 替代关系不参与查询 只输入一个仅出现在 relation_text 中的编号 不得因为替代关系召回规范
11 候选上限 名称或编号匹配到 4 条以上记录 只返回匹配度最高的 3 条
12 无匹配结果 本地规范库中不存在的规范 返回空候选列表
13 批量查询 一次输入多条关键词 结果数量和顺序与输入一致
14 日志记录 执行一次批量查询 每条子查询都有时间、查询内容、结果和耗时日志
15 文件编码 加载 UTF-8 或 GB18030 编码的规范清单 均可正确读取中文内容
16 名称相同但年份不同 钢筋焊接及验收规程JGJ 18-2022 返回 JGJ 18-2012供 Agent(智能体)判断,匹配度为 0.91
17 编号分隔符周围有空格 GB / T 6441 - 1986 正确解析并召回 GB 6441-1986
18 通用短名称 验收规程 可以返回相关候选,但匹配度不得虚高到 0.90 以上