可编程 Scorer 使用指南

程序执行供本微服务 HTTP 与 CLI --direct 使用;第一阶段另提供显式 HTTP 自然语言生成接口,默认关闭。不提供独立 /v1/rank 或候选导出。qatlasd/网页的端到端接入需另行验证。

1. 三种排序入口

ranking

行为

缺省或 null

当前固定公式和默认权重

"auto"

原线:服务调用配置的 LLM 选择三个权重;失败仍回退默认并记 intent 错误

"scorer"

执行调用方提交的评分程序,不调用意图 LLM

ranking 是模式,scorer 是程序,不是一个权重参数。外部 Agent 可以阅读能力说明后编写程序,也可显式调用下文的生成接口;/v1/search 不会在 scorer 缺省时替它生成程序。

agent: true 仍只表示对最终结果生成总结;可以与 scorer 模式并用。

2. 快速使用

HTTP

以下命令假设微服务已经部署且可达;将地址和令牌替换为自己的值。配置了服务令牌时,搜索与能力接口都要求 Bearer 鉴权。

curl -s http://localhost:8600/v1/scoring/capabilities \
  -H 'Authorization: Bearer <service_token>'

curl -s http://localhost:8600/v1/search \
  -H 'Authorization: Bearer <service_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "surface code threshold",
    "sources": ["arxiv", "openalex", "semantic_scholar"],
    "ranking": "scorer",
    "scorer": {
      "language": "qatlas-expr-v1",
      "filter": "lexical >= 0.6",
      "score": "log1p(coalesce(citations, 0))"
    },
    "explain": true,
    "max_results": 10
  }'

CLI

从项目根目录执行:

# 仅显示本地语法、特征和限额,不需要查询、配置或网络
qatlas-search --list-scoring-capabilities

# 实际检索:读取本地 JSON 程序,直连已选数据源
qatlas-search --direct "surface code threshold" \
  --ranking scorer --scorer-file docs/examples/scorers/classic.json --json

qatlas-search --direct "surface code threshold" \
  --ranking scorer --scorer-file docs/examples/scorers/recent.json --explain --json

# 原有 LLM 配权重模式仍可使用;需服务 YAML 中配置 agent
qatlas-search --direct "surface code threshold 最新进展" --ranking auto --json
  • --max-results 仍为 CLI direct 的每源预算;--top 为展示条数。scorer 模式要求二者为正数。

  • scorer 文件必须是 UTF-8 JSON,整个文件最多 4 KiB(含空白);读取在 JSON 解析前受限。

  • scorer 模式的 --json 使用非缩进格式,防止美化输出扩大解释字节预算;旧默认/auto 输出格式不变。

  • CLI 默认的 server 路径走 qatlasd,其 scorer/file/explain 参数在请求及鉴权解析前明确报不支持。不要用 --server 指向本微服务来绕过这一点,两者协议不同。

  • CLI direct 的 --agent 不生成总结,沿用旧语义;需要总结用微服务 HTTP。

自然语言生成(第一阶段)

POST /v1/scoring/generate 只把自然语言要求转换为 qatlas-expr-v1,并使用现有编译器检查语法、类型和程序资源限制。它不检索论文、不执行生成的 scorer、不自动搜索或优化。检查程序、模型解释与警告后,由调用方把返回的 scorer 原样放入现有 /v1/search 请求,并指定 ranking: "scorer";生成成功不等于运行时定义域安全或满足全部自然语言语义。

请求与响应

请求对象只允许 query、requirements 两个必填字段;均为严格字符串,原始长度各为 1..4000 字符,不允许 null、类型转换或全空白,验证后去除首尾空白。不接受 api_keys 等额外字段。整个 UTF-8 JSON 请求体最多 64 KiB(含空白及分块传输);沿用解析前的编码及 JSON 嵌套保护。配置服务令牌时,先检查 Bearer 鉴权再读取请求体。

curl -s http://localhost:8600/v1/scoring/generate \
  -H 'Authorization: Bearer <service_token>' \
  -H 'Content-Type: application/json' \
  -d '{"query":"surface code threshold","requirements":"只保留 lexical >= 0.6 的论文,按 log1p 引用数降序,缺失引用数按 0 处理"}'

成功返回 HTTP 200:

字段

契约

scorer

编译通过的规范化程序:language、filter、score;缺省 filter 补 true

summary

以 Model explanation: 开头的模型解释,不是证明或真实执行轨迹;模型正文最多 2000 字符

warnings

模型警告字符串列表,最多 8 条,每条非空且最多 500 字符

scorer_hash

与现有编译器一致的规范化程序哈希

feature_version

当前特征版本

usage

{"llm_tokens": N},本次最多两次 completion 的已知累计 token 用量

失败始终使用 {"detail":{"code":"…","message":"…","field":"…"},"usage":{"llm_tokens":N}};field 可省略。错误不回显原请求、密钥或原始上游错误。已知用量即使后续修复失败或超时也保留;请求校验、预鉴权和准入拒绝均为 0。

HTTP

常见 code / 原因

400

request_encoding:非 UTF-8 JSON 或字面 NUL

401

unauthorized:缺失或错误令牌

413

request_too_large:请求体超限;token_budget_exceeded:预留或已报告用量超总预算

422

invalid_request、request_nesting_limit;unsupported_requirements、clarification;一次修复后仍非法的 generation_invalid

429

rate_limited:进程内并发槽已满,Retry-After: 1,不排队

502

model_response_too_large、model_response_invalid、model_error、model_unavailable 或安全兜底 generation_failed

503

generation_unavailable:未开启或共享模型配置不完整

504

generation_timeout:整次生成或网络超时

作者、期刊等不可用元数据不能转换成现有六特征公式。服务对 requirements 中常见作者/期刊/会议/机构/标识符/开放获取/影响因子等关键词直接 fail closed,不调用模型;该保守检测可能要求用户改写。对其他不支持或含糊要求,提示词要求模型明确返回 unsupported_requirements 或 clarification,不得静默丢弃约束或自造特征;用户需修改要求后重新请求。关键词检查和编译都不是完整的自然语言语义验证,仍需人工确认程序,不能把 summary 当作保证。

内置变量与错误拒绝

features 中的键不是待用户定义的概念,而是服务器已经提供、可在 filter 和 score 中直接引用的标量变量。score: "lexical" 是完整合法的评分表达式,不会再自动叠加 引用数、年份或默认评分公式。year 是可用的年份变量;未知年份使用 coalesce 处理,并不意味着系统不支持年份过滤。例如“只要2020年之后,仅按 lexical 排序”可写为:

{"language":"qatlas-expr-v1","filter":"coalesce(year, 0) > 2020","score":"lexical"}

这里不包含2020年,并排除未知年份;若包含2020年则使用 >= 2020。 生成提示词明确区分引用已经计算好的 lexical/phrase 信号与不支持的“新增原始文本 谓词/自造特征”,并包含相关性排序、严格年份边界及包含边界的完整示例。示例在离线 测试中经过模型输出 schema、编译和合成论文执行验证,而不是遇到失败时替换模型输出 的兜底规则。模型的 unsupported_requirements 解释仍是模型判断,不是编译器对系统 能力的权威判定;若模型拒绝,接口不会忽略要求或悄悄改用默认 scorer。

生成语义验收(显式选择真实模型)

容器中可直接运行:

# 默认纯离线:不读模型配置、不触网、不消耗模型 tokens。
docker exec qatlas-search python -m qatlas_search.scorer_acceptance

# 明确选择真实模型:读取现有配置,提交固定的中文回归需求。
# 会产生模型费用;这是运维验收,不经过网页的用户额度计量。
docker exec qatlas-search python -m qatlas_search.scorer_acceptance --live

固定需求为:“只要2020年之后,最终分数直接使用系统已有的 lexical 特征,按分数降序 排序,不加入引用数或其他加分项。”工具在生成后验证 168 个合成样本:严格排除 2019/2020/未知年份、保留2021年及以后;零/部分/全部词项相关性保持各自 lexical 分数, 改变引用数、年份和来源数不能引入加分。逐条命中及全量排序均与基准程序比较。

输出 JSON 含 passed、版本、机器 code 和已知 usage.llm_tokens;成功可显示 编译后的 scorer。失败返回非零退出码,不打印 key、base URL、模型原始解释或异常 堆栈。离线 passed:true 不代表真实模型生成成功。--live 沿用现有最多两次 completion、30秒默认总限时和 token/响应预算,不添加外层重试,不检索论文或入库。 即使真实模式通过,也只是固定需求的有限样本语义验收,不是所有需求/输入的证明。

启用与配置

scorer_generation_enabled 默认 false,独立于 agent_enabled。复用 agent_base_url、agent_api_key、agent_model,三者均须在 strip() 后非空;已有嵌套 agent: {base_url, api_key, model} 也会映射到这三个字段。仅开启旧 agent 不会开启生成;关闭旧 agent 也不妨碍单独开启生成。HTTP capabilities 增加 generation_available 表示上述条件是否满足,不代表上游连通性验证。

使用 DeepSeek 官方 endpoint 时,规则生成会自动采用非思考模式:仅当 agent_base_url 的 URL hostname 在大小写与末尾点规范化后精确等于 api.deepseek.com,每次 completion(含一次 repair)才在请求 JSON 顶层附加 thinking: {"type": "disabled"};不添加 reasoning_effort。官方根地址 https://api.deepseek.com 与 /v1 地址及末尾斜杠均适用,无需更换已有 API key 或 model。这不是按模型名字判断:其他 OpenAI-compatible 服务、第三方网关或代理 host 即使使用 deepseek-v4-pro,也不附加该字段,保留其默认行为。此调整仅限规则生成,原 Agentic 结果总结(agent: true)请求保持不变。参数依据:DeepSeek 官方 Thinking Mode 文档。

生成提示词使用 capabilities 的紧凑投影,仍保留全部可用特征/函数、nullable 与定义域约束,以及拒绝不支持要求和请求 clarification 的安全契约;公开 capabilities 不缩减。

以下字段均平铺于 YAML 的 search: 下,不使用嵌套 scoring::

字段

默认

允许范围 / 含义

scorer_generation_enabled

false

布尔值,显式开启生成

scorer_generation_max_concurrency

2

1..32,生成请求并发槽

scorer_search_max_concurrency

4

1..64,全部 /v1/search 请求并发槽

scorer_generation_timeout

30.0

1..120 秒,两次 completion 共用的总超时

scorer_generation_max_tokens

1024

64..4096,每次 completion 输出 token 上限

scorer_generation_total_tokens

16384

1024..65536,整次生成总输入 + 输出 token 预算

scorer_generation_max_response_bytes

65536

1024..262144,每次上游原始响应字节上限,默认 64 KiB

管理页 manifest 的 scoring 分组展示这些字段与说明;PUT /v1/admin/config 校验同样边界,拒绝 NaN/Inf。修改写回配置后必须重启服务,不会热更新预算或并发槽。生成通过运行时依赖 httpx 异步请求 OpenAI 兼容 /chat/completions,不依赖 OpenAI SDK。

有界生成与计量

  • 每次生成最多 2 次模型调用:一次初稿,只有输出 JSON/schema/DSL 无效时允许单次 repair。不对网络错误、不支持要求或 clarification 循环重试;修复不延长总超时,也不另开预算。

  • 默认仍为 30 秒整次生成总超时,输出/总 token 及响应字节预算不变;这是生成处理阶段的期限,不是慢速客户端上传的全链路超时。官方非思考模式与提示词缩短不保证在期限内完成,也不是性能或延迟保证。

  • 每次原始上游响应默认最多 64 KiB;拒绝压缩响应以避免解压膨胀。抽取的模型文本输出另外固定最多 16 KiB UTF-8,并继续受 scorer 4 KiB 及 summary/warnings 限制;这些不是整个 HTTP 成功响应的统一大小限额。

  • 每次调用前,以序列化消息的 UTF-8 字节数加 256 framing 余量,再加本次最大输出 token,进行byte-based 保守预留。以累计预留与上游已知累计用量中的较大者检查剩余总预算;修复携带前次输出,也需重新预留,因此即使第一次调用可执行,也可能没有修复预算。

  • 预留用于准入,不是实际 token 统计。usage.llm_tokens 只累计上游合法报告的 total_tokens,缺失时累计已知的 prompt_tokens / completion_tokens。未报告的未知 token 不得伪计为估算值;返回 0 或部分用量不代表上游没有成本,无法作为精确账单。已知超支返回 413,但不能撤销已经发生的模型调用。

进程内立即准入

默认 /v1/scoring/generate 同时最多 2 个请求,所有 /v1/search(包括默认、auto、scorer、multi)共用另一个上限 4。两组槽独立;即使生成关闭,search 上限仍生效。

纯 ASGI 准入在读取请求体、JSON 解析及同步线程池派发前非阻塞取槽,满额立即 429,不创建等待队列;正常、异常或取消退出均释放槽。它限制的是请求并发,不是每秒速率,也不是后端所有内部任务数;/v1/survey-search 不属于这两个槽组。

每个 ASGI app / worker 计数独立;多 worker / 多副本会使部署总并发随数量倍增,没有分布式限流或跨进程全局配额。需要部署级上限时须在网关或共享限流层另行控制。

3. 程序格式

{
  "language": "qatlas-expr-v1",
  "filter": "lexical >= 0.5",
  "score": "lexical * exp(-coalesce(age_years, 10) / 2)"
}
  • language、score 必填;filter 可选,默认 true。

  • 字段必须是字符串,不接受未知字段或额外配置。

  • filter 必须返回布尔值,score 必须返回非空有限数值。

  • 先过滤,再评分;false 候选的 score 不执行。

  • 服务对完整的合并候选集评分,不先按默认分数截断;按 score 降序,同分保持合并顺序,最后截取 max_results。

  • 平台不强制词法优先、不限制权重、不强制归一化;但不会替程序补充不存在的特征。

参数冲突返回 400:

  • ranking=scorer 却缺少 scorer;

  • 提交了非 null scorer,却未选择 ranking=scorer;

  • ranking=scorer 与 mode=multi 同时使用。

multi 无自定义评分,原有合法 auto/agent 参数继续忽略。explain 默认为 false,必须是真正的 JSON 布尔值;默认/auto 不生成新增解释树。

4. 六个特征

特征基于两轮检索汇总、身份去重和元数据合并后的论文,按实际执行路径惰性读取。

特征

类型

定义

lexical

number

当前未加权词项命中基础分,标题全权重、摘要半权重,0~1

phrase

number

当前短语奖励,标题每短语0.5、摘要0.25;可累计超过1

citations

number 或 null

累计引用数,负哨兵或未知为null,已报告的0保留0

year

number 或 null

发表年份,缺失或非正值为null

age_years

number 或 null

max(本次参考年份-year,0),无年份为null

source_count

number

合并后不同非空来源数量

词法/短语特征复用现有实现,不升级分词规则。空文本身份查询的 lexical、phrase 为0;若设置词法门槛导致身份命中被过滤,服务不擅自放宽。

不提供 raw_score/raw_rank、fame、任意字符串运算或跨候选集合统计。引用量不是“著名程度”的唯一含义,缺少的数据不会由公式凭空生成。

5. 语法与类型

支持有限十进制数字(含小数/科学计数)、小写 true/false、特征名称、括号以及:

+ - * /               算术及一元正负
< <= > >= == !=       单次比较
not and or            布尔逻辑

优先级从高到低:括号/函数、一元正负、乘除、加减、比较、not、and、or。二元算术左结合。禁止链式比较,写成 a < b and b < c。

函数

约束

min(a,b), max(a,b), abs(x)

非空有限数值

clamp(x,lo,hi)

lo <= hi

log1p(x)

ln(1+x),x > -1

sqrt(x)

x >= 0

exp(x)

结果必须有限,溢出报错

coalesce(x,fallback)

fallback是非空同类值,只在x为空时执行

is_missing(x)

返回是否为null;也允许传非空值

where(condition,a,b)

condition必须布尔;分支基类型一致,只执行选中分支

所有函数只接受固定数量的位置参数。and/or 短路执行,布尔不隐式转数字。没有循环、赋值、导入、属性、下标、自定义函数、幂、容器、反射、网络或文件访问。没有字符串、null字面量、注释、Unicode标识符、下划线数字或十六进制数字。

缺失值要显式处理

log1p(citations)                 # 422:citations可能为空
log1p(coalesce(citations, 0))    # 合法:作者明确选择缺失按0处理

编译器不做filter/分支的非空推导:即使filter已经检查引用非空,score中仍要写coalesce。where任一分支可能为空,则整个表达式也可能为空。

6. 结果与解释

自定义 score 可以为负或大于1,保留浮点精度,不可当作概率,也不能自动跨程序比较。

响应 ranking 记录:

  • source=scorer,weights=null;

  • language、feature_version、scorer_hash;

  • 固定的 as_of_year;

  • score_scale=custom;

  • 实际生效的 scorer 程序(外侧空白已规范化,filter缺省补true)。

哈希由带语言/特征版本的规范化类型计划生成:空白、冗余括号、等价数字拼写不改变哈希;年份、查询和候选数据不在哈希内。

同程序、同版本、同查询、同合并记录和同参考年份才应得到相同结果。线上引用数、来源或年份变化可以改变分数。

explain=true

自定义 hit 的 score_detail=null;不伪造五分量账单。score_explanation来自同一次真实执行,包含参与特征、中间值、分支、缺失替代、候选序号和最终分数。

  • 仅返回top hits的解释;不重新求值以生成解释。

  • 解释列表JSON编码预算64KiB,采用无缩进序列化口径;这是解释数据预算,不是整个响应体预算。

  • 执行器按候选/请求的top预算分配轨迹容量,有界保留top轨迹。

  • 裁剪明确标记truncated;极紧预算时可能仅返回 {"truncated": true},不影响分数。

  • 默认/auto的score_detail及四位小数账单保持原样。

7. 错误和资源限制

情况

HTTP结果

缺少/错误服务令牌

401,搜索在读取请求体前鉴权

模式冲突

400

非UTF-8搜索JSON或字面NUL字节

400

搜索请求体超过64KiB

413,按实际收到的字节数检查,包括分块请求

scorer对象超过4KiB

413

JSON容器嵌套超过64

422,在JSON完整解析前拒绝

程序语法/类型/未知名称/节点深度超限

422,在任何数据源和LLM调用前拒绝

合并后候选超过1000

422,不静默截断

实际执行节点除零、定义域错误、非有限结果

422,带表达式位置和candidate_index,不返回半套排名

全部被合法过滤

200,空hits,不自动放宽规则

filter+score合计最多256个AST节点,深度最多32(包含表达式根、操作符及名称上下文);解析前另检查括号/容器语法嵌套32。具体计数口径可查询capabilities。

HTTP的scorer大小按所提交字段的紧凑UTF-8 JSON(不ASCII转义、表达式外空白规范化前)计算;CLI文件限制按实际文件字节。候选限额按真实合并集合计算,不能仅因各源预算之和超过1000就推断唯一候选必然超限。

scorer错误返回安全detail对象(code/message/field/可选position/candidate_index),不回显原始对象或配置。位置采用1起始、对应返回的有效表达式,结束位置不包含在范围内。

显式scorer错误不回退默认、不跳过坏候选、不调用后续总结。backend失败仍按原有errors按源报告;scorer和数据源错误是两套不同契约。

程序只在本次请求编译一次,无全局无界编译缓存。执行器解释不可变的白名单计划,不使用eval/exec或Python字节码执行。

8. 离线测试与性能复测

从仓库根目录使用锁内开发依赖:

uv sync --locked --extra dev
uv run --no-sync --offline python -m pytest -q
uv run --no-sync --offline python scripts/benchmark_scorers.py --repeats 5

测试使用固定论文和模拟数据源,不需要服务配置或外网。覆盖原有路径、自定义语言攻击输入、 CLI/HTTP结果一致性、短路/缺失、程序与请求预算、解释、身份查询和reinject。

性能脚本使用已经合并的100/1,000个候选,固定参考年份2026及explain_top=100;记录重复运行中位数、 独立编译成本和另一次执行的Python分配峰值。它不测上游网络、去重、HTTP链路或真实检索质量, 也不是延迟SLA。实际运行结果应随环境重新测量。

9. 接入边界

/v1/survey-search 与本功能共存,但它不是 scorer 入口。该端点显式拒绝 顶层 ranking/scorer/explain(含null/false),不自动执行或生成评分程序。它与 /v1/search 共享请求体及预鉴权保护,其他规划/过滤/输出仍是独立协议,详见 Survey 整合指南。

  • 默认离线测试覆盖微服务HTTP(进程内TestClient)与CLI direct;不代表真实服务或第三方API已通过集成验证。

  • 接入qatlasd/前端链路前需检查请求透传、ranking.source、自定义负分/大于1的分数和解释字段;发布本服务不自动保证该链路兼容。

  • default_tools、服务令牌配置与部署网络假设不变;本功能不是对项目已有配置鉴权风险的全面整改。

  • scorer只能重排已召回候选,不能改变源内召回和二轮白名单。

  • 本服务不提供独立/rank、候选导出或自动优化程序循环。

版本、预发布和失败恢复统一遵循 项目概览:版本与发布开发流程,不在功能指南中单独维护另一套发版步骤。