Public API 是面向外部系统和 AI Agent 的只读接口,仅返回已发布版本的数据。它提供三类能力:文献查阅、语义检索、带引用的问答(RAG)。
提示:本文档为静态摘要。建议同时访问 Swagger 交互式文档获得准确、完整的接口定义:
/api/public/v1/docs(支持在线调试)
所有端点前缀:/api/public/v1
所有请求必须在 header 中提供 API Key(图片代理端点 /images/{path} 除外):
X-API-Key: <your-api-key>
或者使用 Bearer 格式:
Authorization: Bearer <your-api-key>
API Key 在后台「设置 → API 管理」中创建。创建后请立即复制保存,关闭后将无法再次查看密钥原文。
GET /guidelines 列出已发布的文献
GET /guidelines/{id} 获取单篇文献详情 + 完整正文
GET /guidelines/{id}/body 同上(别名)
POST /search 跨库语义检索,返回原文段落
POST /ask 问答(RAG),返回带引用角标的答案
POST /ask/stream 同 /ask,SSE 流式响应(推荐浏览器使用)
GET /images/{path} 图片代理(无需鉴权)
GET /guidelines列出所有已发布的文献,支持多维过滤和分页。
Query 参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| publisher | string | - | 过滤发布机构(如 CSCO、CMA) |
| year | integer | - | 过滤年份 |
| tag | string | - | 过滤标签(如 lung) |
| skip | integer | 0 | 分页偏移 |
| limit | integer | 20 | 每页条数(最大 100) |
| sort_by | string | created_at | 排序字段 |
| sort_order | string | desc | 排序方向(asc / desc) |
响应结构:
{
"items": [
{
"id": "34c0458349ce4c7bb5ebbaa3e3f70224",
"title": "中华医学会指南:肺癌临床诊疗指南(2025年版)",
"publisher": "CMA",
"publisher_id": null,
"year": 2025,
"version": "1.0",
"tags": ["肺癌", "指南"],
"is_indexed": true,
"chunk_count": 124,
"created_at": "2025-10-20T02:34:48"
}
],
"pagination": {
"total": 5,
"skip": 0,
"limit": 20,
"has_more": false
},
"sort": {
"sort_by": "created_at",
"sort_order": "desc"
}
}
GET /guidelines/{id}获取单篇文献的元数据和完整 Markdown 正文。
GET /guidelines/{id}/body行为完全一致,两者互为别名。
响应字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 文献 ID |
| title | string | 标题(含发布机构名称) |
| publisher | string | 发布机构 |
| year | integer | 年份 |
| version | string | 版本号 |
| tags | string[] | 标签 |
| is_indexed | boolean | 是否已建立索引 |
| chunk_count | integer | 索引分块数 |
| extracted_text | string | 完整 Markdown 正文(含图片引用、页码标记) |
| created_at | datetime | 创建时间 |
POST /search语义检索端点。 根据关键词跨所有已发布文献检索,返回最匹配的原文段落(完整章节上下文)及引用元数据。
定位:输入是检索关键词,输出是原文。不经过 LLM 改写,适合需要原文溯源的场景。
请求:
{
"query": "EGFR突变阳性晚期非小细胞肺癌一线治疗",
"top_k": 5,
"guideline_id": null
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| query | string | 是 | 搜索问题(1-2000 字符) |
| top_k | integer | 否 | 返回结果数(1-20,默认 5) |
| guideline_id | string | 否 | 限定搜索范围到单篇文献 |
响应结构:
{
"search": {
"query": "EGFR突变阳性晚期非小细胞肺癌一线治疗",
"top_k": 5,
"guideline_id": null,
"result_count": 5
},
"results": [
{
"rank": 1,
"score": 0.862,
"text": "(原文段落,已去除 embedding 元数据前缀,直接可读)",
"guideline_id": "9deec8c1ea024486b50bae9f708e8ef3",
"guideline_title": "中国临床肿瘤学会(CSCO)原发性肺癌诊疗指南(2016.V1)",
"publisher": "CSCO",
"year": 2026,
"version_label": "1.0",
"section_path": "四、基于病理类型... > 5.IV 期驱动基因阳性非小细胞肺癌的治疗 > 注释:",
"source_pages": [27, 28, 29, 30, 31],
"citation_label": "中国临床肿瘤学会(CSCO)... > v1.0 > 四、... > 注释:",
"evidence_level": "",
"source_start": 44730,
"associated_images": [
{
"url": "/api/public/v1/images/9deec8c1ea024486b50bae9f708e8ef3/v9/page_28_visual_1.png",
"type": "image",
"description": "表1 EGFR-TKI 一线治疗研究汇总",
"page": 28
}
]
}
],
"agent_context": {
"query": "EGFR突变阳性晚期非小细胞肺癌一线治疗",
"passages": [ /* 同 results,为 AI Agent 设计的精简结构 */ ],
"suggested_usage": "These are original text passages from published oncology guidelines..."
}
}
各字段说明:
| 字段 | 说明 |
|---|---|
| rank | 排名(1-based) |
| score | 语义相似度评分(0-1) |
| text | 原文段落(完整章节上下文,已清理 embedding 前缀,可直接展示) |
| section_path | 完整的章节路径(从顶级标题到最细粒度小节,用 > 分隔) |
| citation_label | 可直接展示的引用字符串(标题 > 版本 > 章节路径) |
| source_pages | 来源页码列表 |
| source_start | 该段落在文献 Markdown 正文中的字符偏移量,用于精确定位原文位置 |
| evidence_level | 证据等级/推荐等级(如 1类推荐证据),从文本中自动提取 |
| associated_images | 相关图片列表(流程图、表格等),url 为相对路径 |
关于 source_start:
返回的是该结果在文献 Markdown 正文中的字符偏移量(0-based)。可用于精确定位原文位置——例如在前端review页面通过 /review/{guideline_id}?sourceOffset={source_start} 跳转到对应位置,避免章节名重复(如多个"注释"小节)导致的定位歧义。
检索机制:
top_k × 10(最多 500),保证多文献多章节的覆盖POST /ask问答端点(RAG)。 输入可以是远比关键词复杂的一段文本(包含多个问题、背景信息、病历描述等),系统会理解意图、拆分子问题、多次检索、最后用检索到的原文生成一个带引用角标的答案。
定位:输入是问题/描述,输出是AI 生成的答案 + 引用原文。每个事实、数字、推荐方案都带
[n]角标,对应citations中的原文片段。
请求:
{
"question": "我爷爷78岁确诊EGFR突变阳性晚期非小细胞肺癌,一线用什么药?如果奥希替尼耐药了怎么办?",
"history": [
{ "question": "EGFR突变是什么?", "answer": "EGFR突变是..." }
],
"top_k": 5,
"guideline_id": null
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| question | string | 是 | 问题或描述(1-4000 字符,支持复杂输入) |
| history | array | 否 | 历史对话(最多 2 轮,用于多轮对话上下文) |
| history[].question | string | - | 历史用户问题 |
| history[].answer | string | - | 历史 AI 回答 |
| top_k | integer | 否 | 每个子问题的检索数(1-10,默认 5) |
| guideline_id | string | 否 | 限定检索范围到单篇文献 |
处理流程:
(文献, 章节) 合并去重,最多取 12 条原文片段[n] 角标响应结构:
{
"question": "我爷爷78岁确诊EGFR突变阳性晚期非小细胞肺癌...",
"answer": "### 一、一线治疗\n\n推荐使用 EGFR-TKI 靶向药物 [1][2]...\n\n### 二、奥希替尼耐药后\n\n推荐含铂双药化疗 [3]...",
"citations": [
{
"ref": 1,
"text": "针对EGFR突变阳性晚期NSCLC一线治疗,多个随机对照研究显示...(原文片段)",
"guideline_id": "9deec8c1ea024486b50bae9f708e8ef3",
"guideline_title": "中国临床肿瘤学会(CSCO)原发性肺癌诊疗指南(2016.V1)",
"publisher": "CSCO",
"year": 2026,
"section_path": "四、... > 5.IV 期驱动基因阳性... > 注释:",
"citation_label": "中国临床肿瘤学会(CSCO)... > v1.0 > 四、... > 注释:",
"source_pages": [28],
"source_start": 45205,
"score": 0.865
}
],
"sub_questions": [
{ "question": "EGFR突变阳性晚期非小细胞肺癌一线治疗推荐方案", "passage_count": 5 },
{ "question": "奥希替尼一线治疗后进展的二线治疗方案", "passage_count": 5 }
]
}
字段说明:
| 字段 | 说明 |
|---|---|
| answer | AI 生成的答案(Markdown 格式),其中 [n] 角标对应 citations 中的 ref |
| citations | 引用来源列表,每个对象包含 ref(角标编号)、原文片段 text、文献元数据、source_start |
| citations[].ref | 角标编号,与答案中的 [n] 一一对应 |
| citations[].text | 被引用的原文片段(已清理,可直接展示) |
| citations[].source_start | 该片段在文献正文中的字符偏移,可用于精确定位 |
| sub_questions | 拆分出的子问题列表及各自检索到的片段数 |
角标使用:
答案中的 [1]、[2] 等角标对应 citations 数组中 ref 字段相同的项。前端可将角标渲染为可点击链接,点击后展开对应的原文片段,或跳转到文献原文页面(/review/{guideline_id}?sourceOffset={source_start})。
注意:
/ask是同步接口,RAG 流程(拆分+检索+生成)可能耗时 30-60 秒。浏览器或经过代理的客户端建议使用下方的流式接口/ask/stream以避免超时。
POST /ask/stream/ask 的 SSE 流式版本,请求参数完全一致,响应为 text/event-stream。
推荐浏览器客户端使用此接口——既能避免代理超时(流式连接保持活跃),又能实现答案逐字输出的体验。
事件序列:
event: sub_questions
data: [{"question":"EGFR突变...一线治疗推荐方案","passage_count":0}, ...]
event: citations
data: [{"ref":1,"text":"...","guideline_id":"...","source_start":45205,...}, ...]
event: answer_delta
data: {"text":"根据指南推荐"}
event: answer_delta
data: {"text":",EGFR突变阳性"}
...(多次 answer_delta)
event: done
data: {}
| 事件 | data 内容 | 说明 |
|---|---|---|
| sub_questions | 子问题数组 | 拆分完成,开始检索 |
| citations | 引用来源数组(全部候选片段) | 检索完成,ref 已编号 |
| answer_delta | {"text":"增量文本"} | 答案逐段输出,需拼接 |
| done | {} | 流结束 |
| error | {"message":"..."} | 出错(流终止) |
前端接入示例(JavaScript):
const resp = await fetch('/api/public/v1/ask/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': '<your-key>',
'Accept': 'text/event-stream',
},
body: JSON.stringify({ question: '...', top_k: 5 }),
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let answer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE 事件以 \n\n 分隔
let sep;
while ((sep = buffer.indexOf('\n\n')) !== -1) {
const rawEvent = buffer.slice(0, sep);
buffer = buffer.slice(sep + 2);
const lines = rawEvent.split('\n');
let eventType = '', dataStr = '';
for (const line of lines) {
if (line.startsWith('event: ')) eventType = line.slice(7).trim();
else if (line.startsWith('data: ')) dataStr += line.slice(6);
}
if (!eventType) continue;
const data = JSON.parse(dataStr);
if (eventType === 'answer_delta') {
answer += data.text;
// 更新 UI 显示 answer
} else if (eventType === 'citations') {
// 渲染引用列表
} else if (eventType === 'done') {
// 完成
}
}
}
GET /images/{path}图片代理。从存储(MinIO)读取文献中的图片、表格截图。此端点无需鉴权。
associated_images[].url 返回相对路径,使用时需要用 API Base URL 拼接完整:
Base URL: https://onc.md/api/public/v1
Image URL: /api/public/v1/images/34c0458349ce4c7bb5ebbaa3e3f70224/v1/page_18_visual_fallback_1.png
完整地址: https://onc.md/api/public/v1/images/34c0458349ce4c7bb5ebbaa3e3f70224/v1/page_18_visual_fallback_1.png
响应带 Cache-Control: public, max-age=86400(缓存 24 小时)。
所有错误统一返回:
{
"error": {
"code": "not_found",
"message": "Guideline 'xxx' not found or not published",
"details": null
}
}
常见错误码:not_found(404)、image_not_found(404)、validation_error(422)、unauthorized(401)、forbidden(403)、qa_failed(500,问答处理失败)。
/api/public/v1X-API-Key: <your-key>(图片代理除外)allow-origin: *)POST /search(输入关键词,返回原文段落)POST /ask 或 POST /ask/stream(输入问题,返回带引用的 AI 答案)/ask/stream,避免代理超时,体验更好citation_label + source_pages + source_start 可精确追溯每条结果/引用的来源位置associated_images[].url 返回的是相对路径(以 /api/public/v1/images/ 开头),调用方需要用 API Base URL 自行拼接完整/search 的 agent_context.passages 专为 AI 拼接设计,配合 suggested_usage 使用