把 GLM-Legal 接进法律问答页面,顺序上建议反过来做:先定死请求字段和响应字段,再写调用代码。原因是法律问答的答案要落到页面上展示,用户看到的不是一段自由文本,而是「结论 + 法条引用」这样的结构;如果响应格式随模型发挥,前端拿不到稳定的 citations,接入层也无法判断这一条能不能发出去。所以第一步是约束输入(问什么、在哪个法域、时间范围、要不要引用),第二步是约束输出(结论、法条引用数组、置信度、待人工确认项),这两张字段表定下来之后,代码骨架和校验脚本才有意义。
先把请求字段和响应字段定死,再写调用代码:question、jurisdiction、time_range、need_citation 决定模型怎么答;conclusion、citations、confidence、needs_human_review 决定前端和接入层怎么判。citations 缺失时不要让页面直接渲染,应在接入层拦下并记录,由人工确认后再决定是否重试。
定义请求字段:问题、法域、时间范围、是否需要法条引用
请求字段的作用不是把模型绑死,而是把「这次问的是什么」写清楚,减少自由发挥的空间。字段名建议在接入层做一层归一化,前端传什么都在网关里映射成下面的结构,后续换模型或换提示词时不用改前端。
| 字段名 | 类型 | 是否必填 | 示例值 | 说明 |
|---|---|---|---|---|
| question | string | 必填 | 劳动合同到期不续签,需要支付经济补偿吗? | 用户原始问题,保留原话,不要在接入层做改写 |
| jurisdiction | string | 必填 | CN-mainland | 法域标识,用你自己维护的枚举值,不要写成自由文本 |
| time_range | object | 选填,可传空对象 | {"from":"2020-01-01","to":"2024-12-31"} | 问题涉及的时间范围,用于提示新旧规则适用;不传时下游按「不限」处理 |
| need_citation | boolean | 必填 | true | 是否需要法条引用。设为 true 时,响应里 citations 不允许是空数组 |
落地时先在接入层做参数校验:jurisdiction 必须在枚举里,need_citation 必须是布尔值,time_range 的 from 不能晚于 to。校验不通过直接返回 400 并写日志,不要交给模型去猜。验证方式很简单,用一条非法法域和一条空 question 各发一次,看接入层是否在前置阶段就拦住了。
定义响应字段:结论、法条引用、置信度、待人工确认项
响应字段要解决的是「前端怎么画」和「接入层怎么判」。建议约定为固定四个顶层字段,模型多返回的字段忽略,少返回的字段由校验脚本报错。空值规则要提前说清楚:数组类字段无内容时返回空数组而不是 null,字符串类字段无内容时返回空字符串而不是缺字段,这样前端不需要写两套判断。
| 字段名 | 类型 | 空值规则 | 示例 |
|---|---|---|---|
| conclusion | string | 不允许为空字符串;信息不足时必须显式写出「信息不足,无法给出结论」这类说明 | 合同到期用人单位不续签的,通常需要按工作年限支付经济补偿,具体情形需结合是否维持或提高原条件判断。 |
| citations | array<object> | 无匹配时返回空数组;数组元素必须含 law_name、article、quote | [{"law_name":"中华人民共和国劳动合同法","article":"第四十六条","quote":"……"}] |
| confidence | number(0 到 1) | 不允许为 null;模型无法判断时给保守低值,不要省字段 | 0.7 |
| needs_human_review | array<string> | 不需要人工确认时返回空数组 | ["是否维持或提高原劳动合同约定条件需人工核对"] |
一个可直接对照的响应样例:
{
"conclusion": "合同到期用人单位不续签的,通常需要按工作年限支付经济补偿,是否支付需结合是否维持或提高原条件判断。",
"citations": [
{
"law_name": "中华人民共和国劳动合同法",
"article": "第四十六条",
"quote": "有下列情形之一的,用人单位应当向劳动者支付经济补偿……"
}
],
"confidence": 0.7,
"needs_human_review": ["是否维持或提高原劳动合同约定条件需人工核对"]
}
这里有个取舍:confidence 只用来决定页面提示语气(例如低于某个阈值时显示「建议咨询专业人士」),不要用它直接决定是否展示答案,否则阈值一旦调错,用户看到的是空白页。是否放行应以 citations 是否完整为准。
用通用 HTTP 客户端示例写出调用骨架
下面是一段通用骨架,地址、鉴权头、超时时间都是占位,替换成你自己的网关即可。要点是请求体统一序列化、响应统一走同一个解析入口,方便后面挂校验。
import json
import requests
BASE_URL = 'http://your-gateway-host/v1/legal-qa' # 替换为实际接口地址
HEADERS = {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token>', # 按实际鉴权方式替换
}
def build_payload(question, jurisdiction, time_range=None, need_citation=True):
return {
'question': question,
'jurisdiction': jurisdiction,
'time_range': time_range or {},
'need_citation': need_citation,
'response_format': 'json',
}
def ask_legal(question, jurisdiction='CN-mainland', time_range=None, need_citation=True):
payload = build_payload(question, jurisdiction, time_range, need_citation)
resp = requests.post(
BASE_URL,
headers=HEADERS,
data=json.dumps(payload, ensure_ascii=False).encode('utf-8'),
timeout=30,
)
resp.raise_for_status()
return resp.json()
接入层需要额外处理两种情况:一是模型在 JSON 外面包了说明文字或代码围栏,这时建议先做一次「截取第一个花括号到最后一个花括号」的预处理,再做 json.loads,解析失败就记日志并按失败处理,不要把原始文本塞给前端;二是网关返回 5xx 时不要重试太多次,法律问答页面更适合直接提示「请稍后重试」并保留用户问题。
对模型返回做 JSON 校验,检查引用字段是否缺失
校验放在接入层,紧跟在解析之后、写库和渲染之前。校验函数只做判断不修数据,返回错误列表,调用方根据 need_citation 决定是拦下还是只记警告。
REQUIRED_TOP = ['conclusion', 'citations', 'confidence', 'needs_human_review']
CITATION_KEYS = ['law_name', 'article', 'quote']
def validate_answer(answer, need_citation=True):
errors = []
if not isinstance(answer, dict):
return ['response is not a JSON object']
for key in REQUIRED_TOP:
if key not in answer:
errors.append('missing field: ' + key)
conclusion = answer.get('conclusion')
if not isinstance(conclusion, str) or not conclusion.strip():
errors.append('conclusion is empty')
citations = answer.get('citations')
if not isinstance(citations, list):
errors.append('citations is not a list')
else:
if need_citation and len(citations) == 0:
errors.append('citations empty while need_citation=True')
for i, cite in enumerate(citations):
if not isinstance(cite, dict):
errors.append('citations[%d] is not an object' % i)
continue
for key in CITATION_KEYS:
if not str(cite.get(key, '')).strip():
errors.append('citations[%d].%s is empty' % (i, key))
confidence = answer.get('confidence')
if not isinstance(confidence, (int, float)) or confidence < 0 or confidence > 1:
errors.append('confidence out of range')
if not isinstance(answer.get('needs_human_review'), list):
errors.append('needs_human_review is not a list')
return errors
调用方式建议是:解析后立刻校验,错误的把错误列表和原始返回一起写入日志(带上 trace_id),页面侧按 need_citation 决定显示答案还是显示「暂时无法给出带法条引用的答复」。这样引用缺失是可观测的,而不是等用户截图反馈。
用固定法律问题做回归测试,记录格式错误和引用缺失
回归测试不用多,几条能覆盖不同分支即可,关键是每次改提示词或改字段后都跑同一批,观察错误类型有没有变化。测试用例和记录方式可以按下面的表来维护,实际返回值这一列填你日志里真实的校验错误列表,不要凭印象记。
| 测试问题 | 预期字段与引用要求 | 实际返回记录 | 修正动作 |
|---|---|---|---|
| 劳动合同到期不续签,需要支付经济补偿吗? | conclusion 非空;citations 至少一条且含 law_name、article、quote | 记录 trace_id、校验错误列表、耗时 | 若 citations 为空:检查提示词是否明确要求引用,或在接入层按需重试一次 |
| 时间范围填 2020-01-01 到 2020-12-31 的加班费问题 | conclusion 需体现时间适用范围;citations 至少一条 | 同上,额外记录 time_range 是否传入 | 若结论忽略时间范围:检查 time_range 是否被序列化丢失 |
| 请问怎么办?(信息不足) | conclusion 明确说明信息不足;needs_human_review 非空 | 记录 needs_human_review 内容和长度 | 若模型硬答:在提示词里补「信息不足时必须说明」的约束 |
| 法域填一个未在枚举内的值 | 接入层前置拦截,不调用模型 | 记录 HTTP 状态码和校验信息 | 若被放过:补全 jurisdiction 枚举校验 |
跑测试时把 need_citation 固定为 true,这样引用缺失会直接暴露成校验错误。每次调整字段定义、提示词或网关路由后重新跑一遍,只看两件事:有没有新增的格式错误类型,以及 citations 缺失出现在哪几条问题上。这两件事稳定之后,再考虑接入页面的展示细节和人工确认流程。