调用示例¶
供调用方参考。按场景提供完整可运行的代码,将 BASE 替换为实际地址后即可使用。
所有示例默认服务地址为 http://127.0.0.1:8864。示例中的 --noproxy '*' 用于绕过本机代理、直连内网服务;若当前环境未配置代理,可省略该参数。
完整问答¶
最常用的调用方式。CG_RAG 负责检索、重排及模型条文选取,调用方只接收最终结果。
import requests
BASE = "http://127.0.0.1:8864"
resp = requests.post(
f"{BASE}/cg-rag/rag",
json={
"query": "脚手架搭设完毕后,验收应当由谁组织?",
"scope": "usual",
"topk": 5,
"max_items": 3,
},
timeout=180,
)
resp.raise_for_status()
body = resp.json()
generation = body["generation"]
if not generation.get("ok"):
raise RuntimeError(generation.get("error"))
print(generation["answer_text"])
for item in generation["pred_items"]:
print("-", item)
const BASE = "http://127.0.0.1:8864";
const resp = await fetch(`${BASE}/cg-rag/rag`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: "脚手架搭设完毕后,验收应当由谁组织?",
scope: "usual",
topk: 5,
max_items: 3,
}),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const { generation } = await resp.json();
if (!generation.ok) throw new Error(JSON.stringify(generation.error));
console.log(generation.answer_text);
响应中最重要的三个字段:
generation.answer_text:拼接完成的答案文本,可直接用于展示。generation.pred_indices:模型选中的候选序号,1-based,对应retrieval.retrieval_docs中的位置。generation.pred_items:按序号提取的条文,已格式化为「《规范名》第 X 条:正文」。
先看 generation.ok
HTTP 200 不代表生成成功。检索成功但模型未选出条文时,HTTP 仍返回 200,而 generation.ok 为 false、generation.error.code 为 no_stable_article。业务代码须判断 generation.ok,不可仅依赖 HTTP 状态码。
仅检索¶
不调用 LLM,响应速度最快,资源开销最低。适用于自行处理后处理逻辑,或将候选条文传入自有模型的场景。
curl --noproxy '*' -sS \
-X POST http://127.0.0.1:8864/cg-rag/retrieve-rerank \
-H 'Content-Type: application/json' \
-d '{"query":"脚手架验收需要检查哪些条文?","scope":"usual","topk":10}'
返回的每条文档结构如下:
{
"id": "17038",
"law_name": "DB44-1876-2016轮扣式钢管脚手架安全技术规程",
"article_number": "9.3.5",
"contents": "《DB44-1876-2016轮扣式钢管脚手架安全技术规程》 模板支撑架搭设完毕后,应组织相关人员验收,验收合格后方可进入下一工序施工。",
"text": "模板支撑架搭设完毕后,应组织相关人员验收,验收合格后方可进入下一工序施工。",
"category": "standard_dibiao",
"source": "bm25",
"retrieval_score": 0.9409563541412354
}
source 表示该条文由哪一路召回(bm25 或稠密检索)。retrieval_score 为重排后的分数,已按降序排列。
相同的 query + scope + topk 组合在 TTL 内命中缓存时,响应中的 cache.hit 将为 true:
对已有候选执行受约束生成¶
若已完成检索(或希望复用前一步的结果),可单独调用受约束生成。
import requests
BASE = "http://127.0.0.1:8864"
query = "脚手架搭设完毕后,验收应当由谁组织?"
# 1. 先检索
docs = requests.post(
f"{BASE}/cg-rag/retrieve-rerank",
json={"query": query, "scope": "usual", "topk": 10},
timeout=60,
).json()["retrieval_docs"]
# 2. 再让模型从这批候选里挑
result = requests.post(
f"{BASE}/cg-rag/constrained-generate",
json={"query": query, "retrieval_docs": docs, "max_items": 3},
timeout=180,
).json()
print(result["answer_text"])
print("选中序号:", result["pred_indices"])
retrieval_docs 中每项至少须包含 law_name、article_number、contents,其余字段将被忽略。超出服务端 max_context_docs(默认 78)的部分将被截断。
边生成边显示(SSE)¶
/cg-rag/rag/stream 通过 Server-Sent Events 返回过程事件,适用于前端实现逐字输出效果。
import json
import requests
BASE = "http://127.0.0.1:8864"
with requests.post(
f"{BASE}/cg-rag/rag/stream",
json={"query": "脚手架搭设完毕后,验收应当由谁组织?", "scope": "usual", "topk": 5},
headers={"Accept": "text/event-stream"},
stream=True,
timeout=180,
) as resp:
resp.raise_for_status()
event = None
for line in resp.iter_lines(decode_unicode=True):
if not line:
continue
if line.startswith("event:"):
event = line[6:].strip()
elif line.startswith("data:"):
payload = json.loads(line[5:].strip())
if event == "token":
print(payload.get("text", ""), end="", flush=True)
elif event == "final":
print("\n完成:", payload["generation"]["ok"])
elif event == "error":
print("\n出错:", payload)
事件顺序为 progress → retrieval → token* → final;出错时为 error 后紧跟一个 final。完整语义见 HTTP 参考 · SSE。
受约束生成的「流式」是重放,不是真流
受约束生成的答案由服务端按模型选中的序号拼接生成,并非模型逐字输出。服务端在拼接完成后将其切分为若干 token 事件,按 stream_answer_token_delay_seconds(默认 0.025 秒)依次发出,以使前端获得打字机效果,同时保持与其他模式一致的渲染路径。
但须明确:这不会使答案更早到达。第一个 token 到达时,生成实际上已经结束。真正边生成边到达的是视觉接口和自由生成。
图片隐患识别¶
图片以 base64 编码传输,不使用 multipart。
import base64
import requests
BASE = "http://127.0.0.1:8864"
with open("site.jpg", "rb") as fh:
encoded = base64.b64encode(fh.read()).decode("ascii")
payload = {
"question": "请检测图中的安全隐患",
"images": [{"data": encoded, "mime_type": "image/jpeg"}],
}
# 自由生成:只看图,快,不给法规引用
fast = requests.post(f"{BASE}/cg-rag/vision/answer", json=payload, timeout=180).json()
print(fast["answer"])
# 智能生成:模型自己去检索法规,答案里带 [cite^n] 引用
deep = requests.post(
f"{BASE}/cg-rag/vision/agentic",
json={**payload, "scope": "full", "topk": 8},
timeout=600,
).json()
print(deep["answer"])
for citation in deep.get("citations", []):
print(citation)
两种模式的差异对比:
自由生成 vision/answer |
智能生成 vision/agentic |
|
|---|---|---|
| 是否检索法规 | 否 | 是,模型自行决定检索轮次 |
| 答案里有无引用 | 无 | 有 [cite^n],可追溯条文 |
| 典型耗时 | 数秒 | 一到三分钟 |
| 适合 | 现场快速核查 | 出具有依据的判定 |
智能生成耗时较长,原因在于其执行多轮检索。若场景要求低延迟响应,应使用自由生成。
字段全集及隐患定级口径见 视觉。
通过 MCP 调用¶
CG_RAG 通过 Streamable HTTP 暴露 MCP,地址为 http://127.0.0.1:8864/cg-rag/mcp/(末尾斜杠不可省略)。
Claude Code:
其他客户端的通用配置:
可用工具:cg_health、cg_list_profiles、cg_retrieve_rerank、cg_constrained_generate、cg_rag、cg_vision_answer、cg_vision_observe、cg_vision_agentic。各工具参数与 HTTP 接口一一对应,详见 MCP 工具。
让 CG_RAG 开机就热¶
首次请求某个 scope 时需加载索引,响应延迟会明显升高。上线后应提前执行 warmup:
curl --noproxy '*' -sS \
-X POST http://127.0.0.1:8864/cg-rag/warmup \
-H 'Content-Type: application/json' \
-d '{"scopes":["usual","full"]}'
注意:retriever 同时仅保留一个 active profile,warmup 将按顺序加载,最终停留在数组中最后一个 scope。应将最常用的 scope 置于数组末尾。
客户端超时建议¶
建议按接口分别配置,不宜统一设定:
| 接口 | 建议客户端超时 |
|---|---|
/cg-rag/health、/cg-rag/profiles |
10 s |
/cg-rag/retrieve-rerank |
60 s(首次加载索引可能耗时较长) |
/cg-rag/constrained-generate、/cg-rag/rag |
180 s |
/cg-rag/vision/answer |
180 s |
/cg-rag/vision/agentic |
600 s,该接口执行多轮检索 |
/cg-rag/vision/hybrid |
600 s,图片分析后继续执行检索 |
服务端自身的上游超时由 CG_RAG_TIMEOUT_SECONDS 控制,默认 120 秒(生产部署通常调整为 180 秒)。客户端超时不应小于该值,否则客户端将先于服务端断开连接,日志中仅留下断连记录。当前生效值可在 health 响应的 generation.timeout_seconds 字段中查看。