多源与跨学科发现
按主题、学科、年份和数量制定计划;同一来源可提交多条查询,用 track 区分学科轨道,用 tier 设置补充层。
CDK-authenticated literature discovery
主机 Agent 分析研究需求、设计查询矩阵并判断相关性。LitScout 云端校验 CDK、执行受控检索、统一字段、确定性去重并保留来源过程。
按主题、学科、年份和数量制定计划;同一来源可提交多条查询,用 track 区分学科轨道,用 tier 设置补充层。
返回标题、作者、摘要、DOI、来源、引用指标、OA/PDF 候选和 EasyScholar 期刊级质量字段。
Skill 不保存供应商接口,不下载、代理、缓存或探测 PDF,也不会读取其他 CDK 的历史和结果。
Agent / Skill / Cloud
前五步发生在用户主机,云端从预检开始接管受控执行。任务结束后,判断与写作仍回到主机 Agent。
提取主题、学科、年份、总数量、每库数量和用户指定来源。
查询 CDK 状态、剩余额度、并发和有效期。
读取来源权限、内容类型、查询特征、年份支持和数量上限。
生成学科内查询和跨学科桥接查询;同库可放入多条检索式。
主来源使用 tier: 0,不足时执行 tier: 1 或 tier: 2。
云端裁剪到 CDK 授权范围,返回有效查询、警告和额度预览;不预留额度。
携带至少 16 字符的 Idempotency-Key 接受异步任务并预留一次额度。
保存 jobId,按 pollAfterSeconds 查询状态,不新建替代任务。
执行来源、确定性去重、补充 DOI/OA 信息和期刊级质量字段。
Agent 复核相关性、整理证据、核验引用并完成后续写作。
使用 discipline:ai、discipline:biomedicine 和 bridge:ai-biomedicine 标记轨道。桥接查询必须同时包含两侧研究对象或方法,来源选择以当前 CDK 能力响应为准。
Authentication
Agent 使用 Bearer CDK。CDK 不应进入 URL、请求正文、普通配置、输出结果或日志。
Authorization: Bearer <LITSCOUT_CDK>
Accept: application/json
Skill 使用当前 LitScout HTTPS 源。本机开发只允许环回地址。
任务状态、历史和明细默认只允许创建任务的 CDK 读取。
database 只能使用能力目录返回的受控标识。
DOI 地址和 PDF 候选均不代表文章已经下载或验证。
Capability discovery
来源授权和数量限制属于 CDK,不应写死在 Skill 中。每次任务前读取能力,才能避免越权、超量或把增强来源当成检索来源。
/api/v1/me读取当前 CDK 摘要返回指纹、状态、剩余/预留/已结算额度、并发上限和有效期。冻结的 CDK 仍可读取历史,但不能创建新任务。
{
"cdkId": "cdk_example",
"fingerprint": "A1B2-C3D4",
"status": "ACTIVE",
"remainingUses": 20,
"reservedUses": 0,
"chargedUses": 4,
"maxConcurrentJobs": 2,
"expiresAt": null
}
/api/v1/me/capabilities读取有效能力任务级限制包括总结果数、查询数、查询长度、年份边界、PDF 候选模式和结果保留天数。每个来源还会返回以下安全画像:
database / label / role受控来源标识、显示名和检索或增强角色
disciplines / contentTypes适合的学科和文献类型
queryFeatures / supportsYearFilter查询能力和年份过滤支持
searchEnabled / doiLookupEnabled当前 CDK 可以使用的能力
maxResultsPerRequest / maxResultsPerJob来源级数量上限
/api/v1/sources读取安全来源目录返回能力响应中的来源列表。它不会返回供应商地址、凭据或云端调度优先级。只有 role: search 且 searchEnabled: true 的来源可以放入查询计划。
Plan preview
预检使用和正式任务相同的请求体。云端重新校验权限、年份、查询数量和结果上限,并返回最终生效计划。
/api/v1/jobs/preview校验检索计划{
"queries": [
{
"database": "openalex",
"query": "retrieval augmented generation clinical decision support",
"limit": 8,
"track": "bridge:ai-biomedicine",
"tier": 0
},
{
"database": "pubmed",
"query": "clinical decision support evidence synthesis",
"limit": 6,
"track": "discipline:biomedicine",
"tier": 1
}
],
"sinceYear": 2020,
"untilYear": 2026,
"totalLimit": 12,
"dedupe": "deterministic",
"includePdfCandidates": true,
"allowPartial": true,
"routing": {
"mode": "adaptive",
"stopWhen": {
"uniqueResultsAtLeast": 12,
"minimumByTrack": {"bridge:ai-biomedicine": 4}
}
}
}
effectiveQueries经过来源权限和总量分配后真正执行的查询行。
effectiveTotal所有层可能产生的来源请求总量,不是最终文献数。
finalResultLimit确定性去重后最多返回的文献数量。
clamped / denied被裁剪的输入和无法接受的原因。
warnings计划仍可执行,但客户端应向用户说明的事项。
quota当前剩余额度、已预留额度及正式接受时的预留量。
Asynchronous jobs
202 Accepted 只表示任务已通过校验并预留额度。保存返回的 jobId,后续请求始终使用这个任务。
/api/v1/jobs接受任务
GET/api/v1/jobs/{job_id}任务和来源状态
POST/api/v1/jobs/{job_id}/cancel请求取消
GET/api/v1/history当前 CDK 历史
/api/v1/jobs接受异步任务请求体与预检一致。必须携带至少 16 字符的 Idempotency-Key。相同 CDK、相同 Key 和相同请求返回原任务;Key 相同但请求不同会返回冲突。
Idempotency-Key: 7e8f3d5b-6e9d-4b8a-9b5f-4ad31e42d901
Content-Type: application/json
/api/v1/jobs/{job_id}读取任务状态响应包含任务状态、计费状态、时间、永久匿名摘要、结果到期时间和每个来源的请求量、返回量、层级、轨道及稳定错误码。
billing.status / units / reason预留、结算或释放及其原因
sourceRuns[].statuspending、running、succeeded、failed、timed_out、cancelled 或 skipped
resultSummary不含标题、作者、摘要、DOI 或候选 URL 的永久聚合
resultsExpiresAt文献结果明细的清理时间
/api/v1/jobs/{job_id}/cancel请求取消服务端取消尚未开始的来源运行。已经开始的来源不能假装未执行,结算以任务返回的 billing 为准。
/api/v1/history?limit=50读取当前 CDK 历史limit 范围为 1 到 100。列表按创建时间倒序返回任务状态、来源过程、计费结果、匿名摘要和结果保留时间。
Unified records
结果先确定性去重,再把所有来源命中写入 provenance。来源没有提供的字段保持为空,不由云端补造。
/api/v1/jobs/{job_id}/results读取统一结果只有创建任务的原 CDK 可以读取。响应包含 items[]、各来源汇总 sources[] 和永久匿名 resultSummary。
| 分组 | 字段 | 说明 |
|---|---|---|
| 基础 | id / title / publishedDate / year / type / language / abstract | 统一文献基础信息 |
| 作者 | authors[].name / given / family / orcid | 作者名称与可用标识 |
| 载体 | venue.name / publisher / issn / issnL / subjects | 期刊、会议、出版社和主题 |
| 标识符 | doi / pmid / pmcId / arxivId / halId / coreId / sciverseId / sourceIds | 跨来源标识与全部原始来源 ID |
| 指标 | metrics.citationCount / metricsSource | 来源提供的引用次数及提供方 |
| 链接 | links.landingPage / doiUrl | 书目落地页和规范 DOI 解析地址 |
| 开放获取 | openAccess.isOpenAccess / status / license / pdfCandidates[] | 来源声明的 OA 状态与全文候选线索 |
| 溯源 | provenance[].database / sourceId / query / rank / track / tier | 每次来源命中与客户端查询轨道 |
| 期刊质量 | quality.status / provider / match / metrics / rankings / snapshot / reason | EasyScholar 期刊级可解释字段 |
links.doiUrl 是格式有效 DOI 对应的解析地址,不代表文章开放获取。
候选包含 URL、来源、会话要求、内容类型、许可和证据。LitScout 不下载、代理、缓存或探测。
影响因子、五年影响因子、JCI、分区、Top 和预警只描述期刊,不能替代单篇论文质量判断。
Quota settlement
以任务历史中的 billing.status 和 billing.reason 为最终依据。预检不预留额度,任务接受后才预留。
| 情况 | 额度结果 |
|---|---|
| 预检或请求未通过校验 | 不预留 |
| 任务被接受 | 预留 1 次 |
| 至少一个来源成功,包括成功但返回 0 条 | 结算 1 次 |
任务状态为 partial | 结算 1 次 |
| 全部来源因云端或上游故障失败 | 释放预留 |
| 任一来源开始前取消 | 释放预留 |
| 来源开始后由用户取消 | 结算 1 次 |
文献明细默认保存 365 天,实际值以 CDK 的 resultRetentionDays 为准。明细清理后返回 410 RESULTS_EXPIRED;任务请求、有效计划、检索式、来源过程、计费账本、审计事件和匿名摘要永久保留。
Stable errors
错误响应始终提供稳定 code 和可追踪的 requestId。不要把新建任务当成通用重试手段。
{
"code": "CDK_FROZEN",
"message": "当前 CDK 暂停新检索",
"requestId": "req_example",
"details": null
}
| 错误码 | 含义 | 客户端处理 |
|---|---|---|
CDK_INVALID | CDK 无效或不可用 | 停止请求并要求用户检查凭据 |
CDK_FROZEN | 暂停新任务 | 停止创建任务,历史仍可读取 |
CDK_EXHAUSTEDQUOTA_EXHAUSTED | 没有可用或可预留额度 | 停止创建任务 |
CONCURRENCY_LIMIT | 并发任务数已满 | 等待现有任务结束,不创建替代任务 |
CDK_RATE_LIMITED | 接受任务频率超限 | 遵守返回的等待时间 |
IDEMPOTENCY_CONFLICT | 同一 Key 对应不同请求 | 检查调用方状态,不能盲目换 Key 重试 |
PLAN_REJECTED | 计划违反当前能力限制 | 读取 details.denied 并重新预检 |
NOT_FOUND | 任务不存在或不属于当前 CDK | 检查 jobId 和身份 |
RESULTS_EXPIRED | 文献明细已经清理 | 读取任务历史和匿名摘要,不自动补交任务 |
只有状态查询的瞬时网络错误适合有限退避重试,并且始终使用原 jobId。
End-to-end examples
示例使用当前站点域名,CDK 保持为占位符。生产环境应从主机 Agent 的秘密存储注入。
export LITSCOUT_BASE_URL="__LITSCOUT_ORIGIN__"
export LITSCOUT_CDK="<LITSCOUT_CDK>"
curl -sS "$LITSCOUT_BASE_URL/api/v1/me/capabilities" \
-H "Authorization: Bearer $LITSCOUT_CDK" \
-H "Accept: application/json"
curl -sS "$LITSCOUT_BASE_URL/api/v1/jobs/preview" \
-H "Authorization: Bearer $LITSCOUT_CDK" \
-H "Content-Type: application/json" \
--data @plan.json
curl -sS "$LITSCOUT_BASE_URL/api/v1/jobs" \
-H "Authorization: Bearer $LITSCOUT_CDK" \
-H "Idempotency-Key: 7e8f3d5b-6e9d-4b8a-9b5f-4ad31e42d901" \
-H "Content-Type: application/json" \
--data @plan.json
curl -sS "$LITSCOUT_BASE_URL/api/v1/jobs/<job_id>" \
-H "Authorization: Bearer $LITSCOUT_CDK"
curl -sS "$LITSCOUT_BASE_URL/api/v1/jobs/<job_id>/results" \
-H "Authorization: Bearer $LITSCOUT_CDK"
import asyncio
import os
import uuid
import httpx
BASE_URL = "__LITSCOUT_ORIGIN__"
CDK = os.environ["LITSCOUT_CDK"]
TERMINAL = {"succeeded", "partial", "failed", "cancelled", "expired"}
async def discover(payload: dict) -> dict:
headers = {"Authorization": f"Bearer {CDK}"}
async with httpx.AsyncClient(base_url=BASE_URL, headers=headers) as client:
preview = await client.post("/api/v1/jobs/preview", json=payload)
preview.raise_for_status()
if not preview.json()["valid"]:
raise RuntimeError(preview.json()["denied"])
accepted = await client.post(
"/api/v1/jobs",
json=payload,
headers={"Idempotency-Key": str(uuid.uuid4())},
)
accepted.raise_for_status()
job_id = accepted.json()["jobId"]
while True:
job = (await client.get(f"/api/v1/jobs/{job_id}")).json()
if job["status"] in TERMINAL:
break
await asyncio.sleep(job.get("pollAfterSeconds", 2))
result = await client.get(f"/api/v1/jobs/{job_id}/results")
result.raise_for_status()
return result.json()
import os
from litscout_client import LitScoutClient
async with LitScoutClient(
base_url="__LITSCOUT_ORIGIN__",
cdk=os.environ["LITSCOUT_CDK"],
) as client:
capabilities = await client.capabilities()
bundle = await client.discover(payload, timeout=300)
job = bundle["job"]
papers = bundle["results"]["items"]
source_runs = job["sourceRuns"]
Ready to search