关于 quorum
Quorum 从公开档案中整理市场与客户证据,面向产品、品牌研究及尽职调查。它保留语料库,要求发现有多条独立记录佐证,并提供可检索的凭据 ID,便于核验主张。
主要功能
- 01从公开档案开展研究
- 02存储证据均有可检索 ID
- 03发现需独立记录佐证
- 04保留语料库以支持后续查询
- 05离线研究模式
- 06提供 CLI、SDK、HTTP API 和 MCP 服务
适用场景
- 01调查客户投诉
- 02通过独立检索比较竞品
- 03依据广告投放时长评估广告形式
- 04审计 Agent 工作流中的研究主张
MCP 相关
MCP 服务通过 stdio 提供五种工具,其中四种也可经 `/mcp` 远程调用。它使用同一研究流程,主张可通过存储的证据记录核验。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://quorum-api-j15n.onrender.com/mcpclaude mcp add --transport http godzilla-lab-quorum https://quorum-api-j15n.onrender.com/mcp{"mcpServers":{"godzilla-lab-quorum":{"type":"http","url":"https://quorum-api-j15n.onrender.com/mcp"}}}在 Cursor 中打开{"servers":{"godzilla-lab-quorum":{"type":"http","url":"https://quorum-api-j15n.onrender.com/mcp"}}}在 VS Code 中打开{"mcpServers":{"godzilla-lab-quorum":{"type":"http","url":"https://quorum-api-j15n.onrender.com/mcp"}}}{"mcpServers":{"godzilla-lab-quorum":{"serverUrl":"https://quorum-api-j15n.onrender.com/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
5| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| terms | array | 否 | 要询问的内容:例如 "sizing"、"durability" 等单一概念。可选。 |
| subject | string | 是 | 产品名称、主题或产品 URL。 |
详细介绍
Quorum
带有凭据的市场证据。
给它一个主题,就能了解市场的真实反馈。每条主张都附有凭据 ID,且每个凭据 ID 都能解析为一条真实存储的记录,供你取回。伪造引用在结构上不可能发生,而且有测试证明这一点,而不是仅靠 README 声称。
提示
有意思的地方不在于它能找到证据,而在于你可以核验。每个回答中的每个 ID 都能通过
GET /v1/evidence/{id}解析,而POST /v1/verify会针对语料库重新核验一组主张,无论语料库是我们的还是其他人的。无法解析的 ID,意味着这条主张从未真实存在。
{
"finding": "Sizing runs small, and buyers only discover it after delivery",
"corroboration": { "records": 31, "channels": 5 },
"receipts": ["rc_8f2a1", "rc_44b0e", "rc_91c37"],
"confidence": "finding"
}
GET /v1/evidence/rc_8f2a1 会返回实际评论、评分、发布平台、发布时间和永久链接。你的客户的客户可以点击一条主张,阅读发表这条评论的真人原话。
托管 API 的地址是 **https://quorum-api-j15n.onrender.com**,运行在闲置时会休眠的免费套餐实例上,因此安静一段时间后发出的首个请求,需要等待几秒钟让它唤醒。
目前 API 处于早期阶段,托管实例需要密钥。 API 根路径会说明如何申请密钥,并始终显示你所连接的实例当前处于哪种模式。健康检查端点无需密钥,因此你可以立即检查服务是否正常运行:
curl https://quorum-api-j15n.onrender.com/v1/healthz
自托管支持开放模式或按密钥进行身份验证、配额、租户管理和 webhook 密钥,所有这些都可以通过 GET /v1/usage 自行管理。
说明
在 Windows 上,Git Bash 自带的 curl 使用 Schannel TLS 协议栈;如果无法连接到证书吊销服务器(
CRYPT_E_REVOCATION_OFFLINE,在公司代理和 VPN 后面很常见),它会拒绝所有 HTTPS 连接。这是网络问题,不是此 API 的问题。请在 curl 命令中添加--ssl-revoke-best-effort,或者改用 WSL 或 PowerShell 调用。
目录
功能
从公共档案中获取客户之声,按竞争对手广告实际投放时长排名,并以算术而非观点判断视频与静态广告孰优。它会保留读取的所有内容,因此针对同一市场的第二个问题几乎不费成本,而且可以询问档案中的过往信息。
核心功能
- 凭据,而非链接。 指向保留语料库的稳定 ID,永远都能解析为相同内容,即使来源已删除原始内容也不例外。
- 以算术方式交叉印证。 一项主张至少需要三条独立记录才会作为发现结果展示,并附带记录数量。
- 一个接口整合十个来源,从志愿者维护的 Reddit 档案和 GitHub issue 搜索,到四个政府安全监管机构;每个来源在未配置时都会降级运行,而不是直接失败。
- 不断累积的语料库。 冷启动检索耗时 596 秒,约有 500 次请求受到限流。同一类别在预热后只需半秒即可获得答案。
- 离线模式,不访问网络且不产生费用。
- 十五个 HTTP 操作、一个类型化 SDK、一个 MCP 服务和一个 CLI,全部使用同一条流水线。
效果展示
以下是真实输出,摘自 2026-08-24 冷启动运行 npx quorum "espresso machine" 的结果:227 秒内获取 401 条记录,费用为 $0.00。
EVIDENCE a claim needs 3 independent receipts to be stated as a finding
the percentage is share of all 401 records held for this category
quality 88 receipts / 64 channels 21.9% A0 B3 C85 D0 [finding]
"I often hear the priority list being 1. Quality of beans 2. Quality
of the grinder 3. Quality of the espresso machine..."
hackernews Why a spritz of water before grinding coffee yields
better results rc_4d6d444821b0044f
ATTESTED 7 records from 6 named parties [finding]
A named party stated this to a regulator, on the record, with consequences
for lying. Two of these are a finding on their own.
"CPSC And Krups Announce Action On Espresso Makers..."
cpsc Krups rc_a4883bfb1b04d1c1
RECEIPTS 195 cited, 195 resolved back to real records
COST $0.0000 in 227.3s
该次运行写入的语料库中,可以检索输出里的每个 ID。如果某个 ID 无法解析,运行就会以非零状态退出,并指出具体是哪一个。
使用场景
- 产品和品牌研究。 了解买家实际抱怨什么,每项主张都附有对应评论,而不是让你只能相信的摘要。
- 竞争对手拆解。 每个竞争对手都进行一次完整检索,因此比较的是份额而非数量,也不会把共现误认为信号。
- 广告创意决策。 根据真实广告活动的投放时长判断视频与静态广告孰优,依据是带日期的观察记录,而非平台自己的标签。
- 可审计的 Agent 工具。 MCP 服务中的每项主张都能由调用模型独立检索,这是研究工具唯一无法伪造的东西。
- 尽职调查和监控。 由具名方提交给监管机构并经证明的记录,以及与同一主题上一份报告的差异对比。
状态
| 引擎 | 可用。1,330 项测试,离线且无需密钥 |
| CLI | 可用,支持所有标志 |
| MCP 服务 | 可用,通过 stdio 提供五个工具,其中四个也可通过 /mcp 远程使用 |
| JavaScript SDK | 可用,提供 11 个方法 |
| Python SDK | 已发布:pip install quorum-api。提供 14 个方法,仅使用标准库 |
| Webhooks | 可用。符合 Standard Webhooks 签名规范,持久化保存,重试时间约 75 小时 |
| 托管 API | 已部署并上线,地址为 https://quorum-api-j15n.onrender.com,使用 PostgreSQL,并已通过运行中的实例验证 |
| npm | 已发布:npx quorum-api "running shoes"。包含五个包,零外部依赖 |
| 语料库 | 规模尚小。截至 2026-08-25,托管实例已预热 22 个消费和开发者类别;其他类别只需进行一次冷启动检索 |
说明
目前请勿依赖 API 的形态。 它已足够稳定,可用于构建,但尚未冻结。
以下限制明确说明,不会让你意外发现:请求配额保存在内存中,因此每分钟查找计数器会在重启时重置,而且任何计数器都无法跨两个实例保持一致。每小时报告计数器是个例外:启动时会根据持久化报告重新初始化,因此重启不会补充配额。 此外,
evals/目前只有第一层,即在 CI 中评分的带标签相关性数据集;项目文档中所述的付费报告评分评估尚不存在。
要求
Node 22.18 或更高版本。引擎只有一个运行时依赖 pg,且仅用于托管服务:没有框架、没有 ORM、没有 HTTP 客户端、没有测试运行器,CLI 和语料库中更是完全没有依赖。这是有意为之,而非自夸,因为在一个会从公共互联网获取不可信文本的工具中,每个依赖都是另一种可能代表你访问网络的东西。pg 之所以值得保留,是因为它提供的连接池能在数据库重启后继续工作,而这不值得手动编写。
任何功能都不需要密钥。通过公共归档获取的 Reddit、Hacker News、App Store、CFPB 消费者投诉数据库,以及四个政府安全档案,全都免费且无需密钥。密钥只会增加数据源(使用免费 API 密钥获取 YouTube 评论、通过 Apify 获取 Amazon 评论和 Meta 广告库),缺少密钥只会让一次运行少获取一些数据,不会导致运行失败。
环境变量,全部为可选项。将它们放在仓库根目录下被 gitignore 忽略的 .env 文件中。
QUORUM_YOUTUBE_API_KEY | 免费的 YouTube Data API v3 密钥。测评视频下的评论会加入声音层级;若未设置,数据源会报告自身尚未配置,运行会继续进行,只是不使用该数据源。完全免费:每日免费配额足以支持数十次运行。 |
QUORUM_CONTACT_EMAIL | 这不是密钥。SEC 要求 User-Agent 标明调用方身份,否则会返回 403,因此在设置该变量前,sec-edgar 会报告自身尚未配置。角色邮箱的有效期比设置它的人更长。 |
OPENROUTER_API_KEY | 用于扩展主题、--synthesise 和 --read-images。这三项默认均关闭,且计数从不来自模型,因此没有此密钥时,确定性报告也完全相同。 |
APIFY_TOKEN | 两个按量计费的数据源:Meta 广告库和 Amazon 评论,二者都通过同一个 Apify 账户获取。每次调用都会计入费用计量并计入报告账单。若未设置,这两个数据源都会跳过,效果与广告数据源使用 --no-ads 完全相同。 |
QUORUM_CORPUS | SQLite 语料库路径,由服务端和 MCP 服务读取。默认值为 ./quorum.db。CLI 不会读取它:请改用 --corpus。 |
QUORUM_PG_URL | 托管语料库使用的 Postgres。请完整粘贴服务提供商的 URI:系统会解析 URI 而非将其拆分,因此 sslmode 会生效,密码中包含 @ 也不会出问题。 |
QUORUM_PG_CA | 服务提供商 CA 文件的路径。建议设置,因为 sslmode=require 仅表示加密,并不验证。 |
QUORUM_PG_CA_PEM | 将同一 CA 作为内联 PEM 提供,适用于以值而非文件形式提供密钥的主机。 |
QUORUM_API_KEYS | 服务器使用的逗号分隔 Bearer 密钥。未设置表示实例处于开放状态,服务器会在启动时明确说明。 |
QUORUM_WEBHOOK_SECRET | Webhook 传送使用的签名密钥,长度至少为 24 个字符。未设置表示接受 Webhook,但永不传送,服务器也会在启动时说明这一点。 |
QUORUM_CONCURRENCY | 并发生成报告的数量,默认值为 2。这不是吞吐量调节项:每次并发运行都会同时给同一批志愿者档案带来压力。 |
PORT | 默认值为 8787。主机通常会设置此项。 |
QUORUM_REPORTS_PER_MINUTE | 每个密钥每分钟可启动的报告数。默认值为 20。 |
QUORUM_LOOKUPS_PER_MINUTE | 其余所有操作的每分钟限额,按密钥计算。默认值为 600,即每秒 10 次。 |
QUORUM_MAX_CAP_USD | 单份报告的费用上限。调用方自己的 capUsd 只能调低此上限。默认值为 0。 |
QUORUM_SPEND_PER_KEY_USD | 每个密钥每天可在按量计费的数据源上花费的金额。默认值为 0。 |
QUORUM_SPEND_TOTAL_USD | 所有密钥合计每天可花费的金额。默认值为 0。 |
需要 Node 22.18 或更高版本,除此之外不需要任何东西。
npx quorum-api "running shoes"
安装就这么简单。只需从 npm 下载 5 个包,约 240 KB,没有任何外部依赖,即可开始生成第一份报告。
要开发 Quorum 本身,请使用克隆方式:
git clone https://github.com/Godzilla-lab/Quorum-API && cd Quorum-API
npm install
npm run build
npm test # 1,330 tests, offline, no keys
然后研究某个主题。输入是一个主题,而不是 URL。可以输入纯文本,也可以输入产品 URL;即使商店拒绝访问,该产品 URL 也仍然有效。这一点很重要,因为测量时,4 个真实商店页面全部阻止了服务端抓取。
npx quorum "running shoes" --offline # corpus only, no network, no cost
npx quorum "running shoes" --offline --corpus ./mine.db # somewhere else
说明
上述操作都不需要密钥,而且
--offline完全不会连接网络。 先一个密钥也不加,等报告指出无法访问的内容后再添加。
用法
CLI
在你的机器上,针对你自己的语料库运行整个引擎。
# A subject in plain words
npx quorum "running shoes" --communities running,runningshoegeeks
# A product url, including one a store refuses to serve to a server
npx quorum "https://allbirds.com/products/mens-wool-runners"
# Corpus only. No network, no cost, and it says so
npx quorum "running shoes" --offline
# Machine readable. Progress goes to stderr, so this pipes cleanly
npx quorum "running shoes" --json | jq '.findings[].term'
# Versus what: one full retrieval per rival
npx quorum "wool runner" --compare "brooks ghost"
# Skip the one metered source explicitly
npx quorum "running shoes" --no-ads
--compare 会针对每个竞争对手分别执行一次完整检索,这是必须的。在一个语料库中统计提及某个竞争对手的记录,只能衡量共同出现:一条评论说“这些比我的 Brooks 更小”,虽然提到了竞争对手和一项投诉,但并未将投诉归因于任何一方。因此,每一方都有自己的语料库,比较的是占比而非数量;对于这些语料库规模下处于抽样噪声范围内的差距,报告会判定为无差异,而不是将其当作结论。
它打印的每项结论要么是发现,即至少有 3 条独立证据支持;要么是弱信号,会显示出来供进一步追查,但绝不会被表述为市场规律。打印任何内容之前,系统都会从语料库中重新取回每条引用的证据;若有任何一条无法解析,运行就会以非零状态退出。
quorum --help 会列出各项标志。以上每条命令都可以通过 npx quorum-api 运行,无需克隆仓库;也可以通过代码库中的 quorum 二进制文件运行,两者是同一个二进制文件。
HTTP API
启动报告,轮询状态,然后解析报告引用的 ID。
# Start one. Returns 202 immediately with an id
curl -sX POST http://localhost:8787/v1/reports \
-H "authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d '{"subject":"wool runner","terms":["sizing","durability"]}'
{ "id": "rep_3568995af829290a", "status": "running", "coalesced": false,
"category": "wool runner", "queuePosition": 1, "estimatedSeconds": 60 }
# Poll it. Honours Retry-After while running, and ETags so polling costs nothing
curl -s http://localhost:8787/v1/reports/rep_3568995af829290a \
-H "authorization: Bearer $KEY" -H 'if-none-match: "rep_3568995af829290a-7"'
# Watch it instead of polling
curl -N http://localhost:8787/v1/reports/rep_3568995af829290a/stream \
-H "authorization: Bearer $KEY"
# Resolve one receipt. This is the endpoint everything else rests on
curl -s http://localhost:8787/v1/evidence/rc_a8697befab91e873 \
-H "authorization: Bearer $KEY"
# Resolve up to 200 at once, because a report cites more than one
curl -sX POST http://localhost:8787/v1/evidence/batch \
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"receiptIds":["rc_8f2a1","rc_44b0e","rc_91c37"]}'
# Is this category warm? Free, and worth asking before paying for a cold report
curl -s http://localhost:8787/v1/categories/running%20shoes \
-H "authorization: Bearer $KEY"
# Check somebody's claims, ours or a competitor's
curl -sX POST http://localhost:8787/v1/verify \
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"claims":[{"text":"sizing runs small","receipts":["rc_8f2a1"]}]}'
JavaScript SDK
带类型、零依赖,依据 spec/openapi.yaml 编写。
每个方法都对应该文件中的一个 operationId。
import { createClient } from '@quorum/sdk-js';
const quorum = createClient({ baseUrl: 'https://quorum-api-j15n.onrender.com', apiKey: process.env.QUORUM_KEY });
const found = await quorum.searchEvidence({ query: 'sizing', category: 'running shoes' });
if (!found.ok) throw new Error(found.error.message);
for (const record of found.data.records) console.log(record.receiptId, record.text);
错误会作为值返回,而不会抛出,这与引擎在任何可能导致供应商服务不可用的场景中遵循的规则一致。调用方会收到 { ok: false, error },其中包含服务器的 type、requestId 和任何 retryAfterSeconds。因为 429、503 以及报告尚未完成都属于正常情况,没有一种属于异常。
它会遵守服务器的节流策略。 waitForReport 会轮询直至完成;如果服务器发送了 Retry-After,就会按其指示等待,并将 503 视为负载削减机制,而不是故障。客户端如果在第一次被拒绝时就放弃,就会把繁忙的服务报告为已损坏,而负载削减机制正是为防止这种情况而存在的。
const started = await quorum.createReport({ subject: 'wool runner', offline: true });
if (started.ok) {
const report = await quorum.waitForReport(started.data.id, {
onPoll: (r) => console.log(r.status),
});
}
streamReport 会将同一次运行作为服务器发送事件的异步可迭代对象返回,并手动解析,因为 Node 中没有能接受 Authorization 标头的 EventSource。
Python SDK
Python 版的同一客户端位于 packages/sdk-py,依据同一份规范编写,并遵循相同的两项原则:错误会作为值返回,绝不抛出;wait_for_report 会遵守 Retry-After,避免将繁忙的服务报告为已损坏。仅使用标准库,没有任何依赖,并且传输层可注入,因此测试无需连接网络即可运行。
pip install quorum-api
from quorum_api import QuorumClient
quorum = QuorumClient("https://quorum-api-j15n.onrender.com", api_key="qk_...")
started = quorum.create_report("wool runner", offline=True)
if started.ok:
report = quorum.wait_for_report(started.data["id"])
MCP 服务
五个通过 JSON-RPC 调用的工具,无依赖,支持两种传输方式。
远程,适用于需要 URL 的连接器表单。托管实例通过 Streamable HTTP 在以下地址响应:
https://quorum-api-j15n.onrender.com/mcp
无需密钥即可访问:读取工具不消耗任何额度,并提供公开数据;匿名调用方共享一份速率额度,以免挤占密钥用户的额度。对于尚无数据的主题,research_product 会启动一次采集并立即返回;记录会在采集过程中陆续变得可搜索,因此 search_evidence 和 category_warmth 会在任务继续运行时返回结果,并说明这一点。公共入口一次只运行一项采集,每小时最多三次;使用密钥则可获得常规报告额度。广告和综合分析不会通过此入口运行。
本地,通过 stdio 传输,适用于启动命令的客户端:
QUORUM_CORPUS=./quorum.db node packages/mcp/src/bin.ts
{
"mcpServers": {
"quorum": {
"command": "node",
"args": ["packages/mcp/src/bin.ts"],
"env": { "QUORUM_CORPUS": "./quorum.db" }
}
}
}
| tool | 回答内容 |
|---|---|
search_evidence | 有多少条独立记录,涵盖多少个渠道,是否达到阈值,以及最有代表性的几条引述 |
get_receipt | 根据 ID 找到真实记录。Agent 就是这样核验我们的 |
category_warmth | 查询是否即时且免费,或需要几分钟且费用高昂 |
compare_formats | 比较视频与静态广告,依据是真实广告投放时长 |
research_product | 远程:启动采集并立即返回。本地:生成完整报告,默认关闭,除非设置 QUORUM_MCP_RESEARCH=1 |
有三个值得了解的设计决策,因为之后修改工具架构的成本很高:
五个工具,而不是每个端点各一个。 每个工具定义在每轮对话中会消耗 100 到 500 个 token,因此,让服务镜像十个路由,会在模型做任何事情之前就耗尽上下文窗口。
返回汇总,而不是逐行倾倒数据。 search_evidence 会返回计数和少量引述。将整个语料流式传入上下文窗口的研究工具,反而消耗了原本要节省的预算。
使用 Markdown,而不是 JSON,相同内容约只需 60% 的 token。
research_product 默认关闭,因为生成一份报告意味着要从志愿者维护的档案中进行数分钟的限速检索,而 Agent 不应意外启动此类任务。其他四个工具基于已有数据作答,不访问网络。
这样做的成果是其他研究服务无法提供的:调用方的 Agent 可以独立核验每一项声明。 search_evidence 会引用 ID,get_receipt 会解析这些 ID;因此,无法解析的 ID 就意味着那项声明从未有过真实依据,而一旦发生,这会成为响应中最醒目的提示。
Webhook
报告生成需要几分钟,因此无需轮询,POST /v1/reports 可以在完成后回调你。设置签名密钥即可启用投递:
QUORUM_WEBHOOK_SECRET=$(openssl rand -base64 32) npm start
curl -sX POST http://localhost:8787/v1/reports \
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"subject":"wool runner","webhookUrl":"https://your-app.example/hooks/quorum"}'
报告进入终态时,你会收到一个 POST 请求,其请求体与 GET /v1/reports/{id} 的响应逐字节相同,并带有三个遵循 Standard Webhooks 规范的标头。这样,你可以使用现成的库进行验证,而不必使用我们自行编写的代码:
| 标头 | 值 |
|---|---|
webhook-id | 报告 ID。重试期间保持不变,可据此去重 |
webhook-timestamp | 本次尝试发生时的 Unix 秒数 |
webhook-signature | v1,{base64 hmac-sha256},以空格分隔的列表 |
签名内容为 {webhook-id}.{webhook-timestamp}.{body},密钥是你密钥的 base64 正文,而不是 whsec_ 标签。你的密钥是 GET /v1/usage 返回的 webhookSecret 字段:它限定于你的密钥、保持稳定,并通过派生生成而非存储,因此只会在运维人员轮换实例密钥时随之轮换,绝不会在你不知情的情况下轮换。
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, headers, body, toleranceSeconds = 300) {
const id = headers['webhook-id'];
const timestamp = Number(headers['webhook-timestamp']);
/* A signature alone is replayable forever, so bound the age first. */
if (!Number.isFinite(timestamp)) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest();
/* The header is a list so a secret can be rotated. Any one match is enough. */
return String(headers['webhook-signature']).split(' ').some((part) => {
const [version, value] = part.split(',');
if (version !== 'v1') return false;
const offered = Buffer.from(value ?? '', 'base64');
return offered.length === expected.length && timingSafeEqual(offered, expected);
});
}
投递至少一次,并会重试约 75 小时。 收到 2xx 即表示成功。收到 5xx、429、超时或网络故障时,会按 Standard Webhooks 的计划重试(立即、5s、5m、30m、2h、5h、10h、14h、20h、24h,并加入随机抖动)。其他任何 4xx 都不会重试,因为接收方既然拒绝了请求体,再次发送也会被拒绝。投递记录保存在数据库中,因此重启不会导致记录丢失。
重要
系统会检查 URL,而检查时机决定了你会收到什么结果。 URL 必须使用
https,且不得包含凭据。字面 IP 地址会在提交时解析,因此https://169.254.169.254/会立即返回400。主机名则会在投递时解析,并固定到当时得到的地址,因为从你发出请求到我们建立连接期间,域名解析结果可能发生变化。每次重定向都会重新验证。有两项限制,特此明示,避免你事后才发现。第十次尝试后,投递会被放弃并记录日志;此处没有端点注册机制,因此不会禁用任何端点,也不会向任何人发送电子邮件。另外,在未设置
QUORUM_API_KEYS的实例上,所有调用方共用一个密钥标签,因此也共用一个签名密钥;签名只能证明投递来自该实例,不能证明是谁请求了投递。
自行运行 API
托管 API 由一个进程和一个语料库组成。它与 CLI 使用的是同一个引擎。
QUORUM_CORPUS=./quorum.db \
QUORUM_API_KEYS=$(openssl rand -hex 32) \
QUORUM_WEBHOOK_SECRET=$(openssl rand -base64 32) \
PORT=8787 npm start
请设置 QUORUM_API_KEYS,否则实例将处于开放状态,启动时实例会告知你这一点。之后,调用方使用 bearer token 进行身份验证:
curl -H "Authorization: Bearer $KEY" localhost:8787/v1/categories/running%20shoes
一个实例为所有人提供服务,这是有意为之。语料库是全局共享的,因为一个类别的调用方预热过的答案,会让下一个调用方立即获得答案;这正是保留单个实例的意义所在。报告和 webhook 投递是例外:它们归租户所有,并由行级安全机制强制隔离;此机制已通过真实的 PostgreSQL 服务验证。
Postgres
设置 QUORUM_PG_URL 后,服务会使用 Postgres 而非 SQLite。其他一切
都不变。
npm run migrate -w packages/corpus # applies the schema, refuses to re-run one
QUORUM_PG_URL='postgres://user:pass@host:5432/db?sslmode=require' \
QUORUM_PG_CA=./provider-ca.pem \
QUORUM_API_KEYS=$(openssl rand -hex 32) npm start
警告
任何非笔记本电脑环境都应这样做。
node:sqlite是同步的,因此 每次语料库读取都会阻塞事件循环:经测量,证据搜索的峰值为每秒 124 个请求, 而索引读取可处理 7,477 个请求;相同的报告也永远不会合并,因为第一个请求 运行时无法接收第二个请求。在没有持久磁盘的主机上,完全不能使用它, 因为每次重启时文件都会消失,语料库也随之丢失。
设置 QUORUM_PG_CA。 sslmode=require 意味着加密但不验证,而托管服务提供商
使用自己的 CA 签名,因此公共根证书库会拒绝它。下载服务提供商的 CA 后,连接就会经过验证,
而不只是加密。服务会在启动时说明你得到的是哪一种。
pg 是此仓库中唯一的运行时依赖,并且仅存在于
packages/server。语料库包仍然没有依赖,这就是它的驱动程序采用注入客户端的原因:使用
SQLite 运行 CLI 的用户无需安装 Postgres 驱动程序。参见 docs/postgres.md。
API 的功能
十个端点。完整契约及其架构见
spec/openapi.yaml。
报告,较慢的路径
类别尚未预热时,报告需要数分钟进行限速检索;预热后则约需半秒,因此它是一个作业,而不是请求。
POST /v1/reports | 启动报告。立即返回带有 ID 的 202。正在运行的相同主题会合并到同一次运行中,响应会说明你的请求是否已合并。 |
GET /v1/reports/:id | 轮询报告。返回 findings、weakSignals 和 rejected,并在运行期间遵循 Retry-After。 |
GET /v1/reports/:id/stream | 以服务器发送事件的方式获取同一次运行,让调用方看到每个阶段,而不是一直盯着转圈图标。 |
证据,较快的路径
报告中显示的每个数字都可以在此找到对应记录。这部分让主张可以核查,并能在预热后的语料库中以个位数毫秒响应。
GET /v1/evidence/:receiptId | 获取一条记录:某人撰写的文本、评分、渠道、永久链接和时间戳。 |
POST /v1/evidence/batch | 一次最多获取 200 条,因为报告会引用多条记录。 |
POST /v1/evidence/search | 在保留的语料库中进行全文搜索,并可按类别筛选。 |
GET /v1/evidence/ads/:adId | 获取某条广告的所有观察记录,这样一次运行的时长就能成为事实,而非估算值。 |
GET /v1/categories/:slug | 查看类别的预热程度:持有的记录数、渠道和数据年龄。免费,而且在为尚未预热的报告付费前值得先查看。 |
验证
POST /v1/verify | 提交带有回执 ID 的主张,服务会逐一针对语料库重新解析。它既能验证我们自己的输出,也能验证任何其他人的输出。 |
GET /v1/usage | 查看此密钥已使用的额度、允许使用的额度,以及它的 webhook 签名密钥。 |
GET /v1/healthz | 存活状态检查。不访问数据库,也永远不会受到速率限制。 |
限额
每个密钥有两项配额,而不是一项,因为报告需要耗费数分钟执行受限的上游检索,而证据查找只是一次索引读取。共用一项限额,要么会让查找请求得不到服务,要么会让报告得不到保护。
| 默认值 | |
|---|---|
| 已启动的报告 | 每分钟 20 个 |
| 其他所有请求 | 每分钟 600 个,即每秒 10 个 |
| 同时进行中的报告 | 3 个 |
每个计量请求的响应都会带上 X-RateLimit-Limit、X-RateLimit-Remaining 和 X-RateLimit-Reset,无论成功还是拒绝,因此调用方可以在遭到拒绝前控制请求频率,而不是事后才采取措施。GET /v1/healthz 不受限,因此不会发送这些标头:实例即使繁忙,也必须能够报告自身仍在运行。拒绝请求时会返回 429 和 Retry-After,并且被拒绝的请求不会计入当前限额窗口,否则客户端一旦进入重试循环,就永远无法恢复。
即使关闭身份验证,限额仍然生效。 没有密钥的实例自然只有一个调用方,因此配额是共享的,而不是不存在。限额必须能够应对的一种情况,就是有人忘记进行配置。
计量来源与费用
竞争对手广告来自付费供应商,因此所有调用方都可以使用 includeAds,但该功能受预算限制,而非速率限制。速率限制只限制频率,从不限制费用:每分钟 20 份启用广告的报告,就意味着每分钟运行 20 次供应商计量任务,而速率限制完全允许这样做。
| 限制对象 | |
|---|---|
QUORUM_MAX_CAP_USD | 防止单份失控的报告耗尽预算 |
QUORUM_SPEND_PER_KEY_USD | 防止单个调用方耗尽预算 |
QUORUM_SPEND_TOTAL_USD | 防止所有调用方合计耗尽预算 |
当广告对所有人开放时,第三项最重要。没有它,真正的上限就是每个密钥的金额乘以密钥数量,这并不是真正的上限。
三项默认值均为零,这会让广告保持关闭。 操作员如果没有设置预算,就代表尚未同意支付费用,因此忘记设置时,生成的报告不会包含广告,也不会产生账单。服务器启动时会说明当前所处的状态。
预算用尽并不算错误。报告仍会运行并返回结果,只是不会包含计量部分;降级列表会准确说明原因。仅仅因为一个可选附加项就让整份报告失败,会白白浪费数分钟的免费检索。
合并运行的费用由启动该运行的人支付,加入运行的调用方则无需付费。这正是合并请求的目的;让不同时刻加入的调用方分摊费用会显得随意,也无法核查。费用根据成本计量器实际记录的金额扣除,而非估算值;GET /v1/usage 会报告相关数据。
它为何与众不同
三个特性,没有一个取决于个人观点。
凭据不会失效。 其他所有研究 API 都会返回一个在查询时针对实时索引获取的网址,页面失效后,网址也会失效。凭据是指向保留语料库的稳定 ID。无论何时解析,结果都完全相同,即使来源删除了原始内容也不例外。
交叉印证可以用数字衡量。 一项主张至少需要三条独立记录,才会作为发现呈现,记录数量也会随之提供。“5 个渠道中有 31 条记录”是任何搜索 API 都无法生成的句子,因为它们都没有语料库可供统计。这也是防范提示词注入的措施:植入的评论无法自我印证。
可以询问档案中的过去。 因为记录会被保留,而不是临时获取,所以语料库能回答实时索引在结构上无法回答的问题:
- 趋势,按对话占比随时间的变化衡量,而不是看原始计数。按月统计记录会显示所有内容都在增长,因为它衡量的是我们的采集量,而不是市场。
- 截至某日,回答市场在 3 月说了什么,并按每条记录的撰写时间筛选,而不是按我们发现它的时间筛选。档案可以掌握比我们在 3 月时更多的 3 月信息。
- 差异,指出同一主题自上次报告以来发生了哪些变化,并列出新增凭据,而不是统计数量。
- 比较,针对每个竞争对手进行一次完整检索,因为统计同一语料库中提及某个竞争对手的记录,只能衡量共现,无法归因。
- 经证实的证据,指具名当事方向监管机构提交的记录,其证明力高于任何论坛评论,也没有任何监听工具持有此类记录。
还有一点容易被忽略:Meta 不会归档已停投的商业广告。 广告活动一旦停止,其投放了 94 天的记录就会消失,无论花多少钱都无法找回。Shopify 会在下架商品的当天,就将其从 /products.json 中移除。对于这些来源,语料库不是缓存,而是唯一可能存在的副本,而且只有在当天有人记录时才会留下。
文档
spec/openapi.yaml | API 契约。SDK 基于此编写 |
docs/citation-integrity.md | 其他地方测得的捏造率,以及让捏造在此处不可能发生的结构 |
docs/gummysearch-alternative.md | 写给 GummySearch 用户:重合之处、差异,以及本项目不是什么 |
docs/postgres.md | 在 Postgres 上运行,以及验证过程中发现的情况 |
docs/rate-limits.md | 每个上游服务、触及限制时的行为,以及我们的应对方式 |
bench/README.md | 负载与滥用测试,以及测试发现的缺陷 |
CONTRIBUTING.md | 添加来源,这是主要的贡献方式 |
SECURITY.md | 报告漏洞 |
参与贡献
添加来源是主要的贡献方式,且只涉及其自身目录:一个位于 Source 接口之后的适配器、一个测试夹具和一个一致性测试用例。请参阅 CONTRIBUTING.md。
npm run build # tsc, declarations only, node runs the source directly
npm test # node:test, offline, no keys required
npm run verify # build, test, copy style, security, drift, spec
有两条项目规则,违反任何一条都会导致构建失败:任何地方都不得使用 em dash 或 en dash,由 npm run lint:copy 强制检查;以及只能使用命名导出。
发现漏洞?请参阅 SECURITY.md,不要提交 issue。
开发
npm run build # tsc across workspaces
npm test # node:test, all packages, offline, no keys
npm run lint:copy # house style
npm run check:security # secrets, SSRF guard, no-auth rule, audit
npm run check:spec # the spec against the code it describes
npm run migrate -w packages/corpus # Postgres schema
npm run test:postgres # the driver against a real server, needs QUORUM_PG_URL
CI 会在没有通往主机外部路由的网络命名空间中运行测试套件,因此,若适配器悄悄尝试访问网络,会立即失败,而不是之后偶尔失败。1,330 项测试中有 3 项需要真实的 PostgreSQL 服务;如果未设置 QUORUM_PG_URL,这些测试会跳过。
许可证
整个引擎均采用 Apache 2.0 许可证:每个适配器、每个关卡、两个语料驱动程序、命令行工具和 SDK,均包含在内,没有任何功能受到限制。
托管服务提供了一项源代码无法提供的东西:预热语料。自行托管可以获得整个引擎,但需要从冷启动开始。这是一款真正的产品;如果它适合你,就选它。
致谢
本项目依赖其他人维护的档案,其中大多数都是免费提供的:
- Arctic Shift,公开的 Reddit 档案,由志愿者维护,因此我们也将其视作志愿项目
- Hacker News 搜索
- 按照文档所述,无需密钥即可使用的 GitHub 问题搜索 API,借助它,带有反应数的已提交缺陷得以进入 B 层
- Apple App Store 公开评论源
- CPSC、openFDA、NHTSA 和 EU Safety Gate,这四个监管机构的召回数据让经过认证的证据成为可能
- SEC EDGAR,它要求调用者表明身份,这也是
QUORUM_CONTACT_EMAIL存在的原因 - CFPB 消费者投诉数据库,通过其公开搜索 API 提供 1,700 万条依据 CC0 发布的投诉,包含消费者对事件经过的亲身陈述:在论坛内容稀少时,为任何金融相关事项提供真实声音
- 按照文档所述,使用免费密钥的 YouTube Data API:评测视频下的评论是买家在交流;每 100 条评论消耗 1 个配额单位,而每日免费额度为 10,000 个单位
- Apify,唯一的付费供应商,承载两项按量计费的服务:Meta 广告资料库抓取工具和 Amazon 商品评论。评论服务只读取未登录访客在 Amazon 公开商品页面上能看到的内容;只有当目标是 Amazon 商品时才会进行规划;每次调用都会按经真实账单核验的费率计入成本计量器
- Standard Webhooks,这样 Webhook 签名就采用了其他人经过充分审查的设计,而不是我们自己的设计
警告
可接受使用。 检索到的内容均为公开信息,且无需登录。本项目绝不会通过身份验证访问被抓取的数据源,今后也不会,因为这是本项目法律依据的根基。使用会话 Cookie 就会丧失这一依据,因此这条规则是架构上的要求,而非个人偏好。证书透明度数据仅用于发现品牌和商品,绝不用于枚举基础设施。请尊重上述档案:它们大多由志愿者维护,而本仓库中的限流曲线正是为此而设。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。