GLM-Legal 接入法律问答页面 / 先把输出格式和引用字段定死

文章导读
把 GLM-Legal 接进法律问答页面,顺序上建议反过来做:先定死请求字段和响应字段,再写调用代码。原因是法律问答的答案要落到页面上展示,用户看到的不是一段自由文本,而是「结论 + 法条引用」这样的结构;如果响应格式随模型发挥,前端拿不到稳定的 citations,接入层也无法判断这一条能不能发出去。所以第一步是约束输入(问什么、在哪个法域、时间范围、要不要引用),第二步是约束输出(结论、法条引
📋 目录
  1. 一 定义请求字段:问题、法域、时间范围、是否需要法条引用
  2. 二 定义响应字段:结论、法条引用、置信度、待人工确认项
  3. 三 用通用 HTTP 客户端示例写出调用骨架
  4. 四 对模型返回做 JSON 校验,检查引用字段是否缺失
  5. 五 用固定法律问题做回归测试,记录格式错误和引用缺失
A A

把 GLM-Legal 接进法律问答页面,顺序上建议反过来做:先定死请求字段和响应字段,再写调用代码。原因是法律问答的答案要落到页面上展示,用户看到的不是一段自由文本,而是「结论 + 法条引用」这样的结构;如果响应格式随模型发挥,前端拿不到稳定的 citations,接入层也无法判断这一条能不能发出去。所以第一步是约束输入(问什么、在哪个法域、时间范围、要不要引用),第二步是约束输出(结论、法条引用数组、置信度、待人工确认项),这两张字段表定下来之后,代码骨架和校验脚本才有意义。

先把请求字段和响应字段定死,再写调用代码:question、jurisdiction、time_range、need_citation 决定模型怎么答;conclusion、citations、confidence、needs_human_review 决定前端和接入层怎么判。citations 缺失时不要让页面直接渲染,应在接入层拦下并记录,由人工确认后再决定是否重试。

定义请求字段:问题、法域、时间范围、是否需要法条引用

请求字段的作用不是把模型绑死,而是把「这次问的是什么」写清楚,减少自由发挥的空间。字段名建议在接入层做一层归一化,前端传什么都在网关里映射成下面的结构,后续换模型或换提示词时不用改前端。

字段名类型是否必填示例值说明
questionstring必填劳动合同到期不续签,需要支付经济补偿吗?用户原始问题,保留原话,不要在接入层做改写
jurisdictionstring必填CN-mainland法域标识,用你自己维护的枚举值,不要写成自由文本
time_rangeobject选填,可传空对象{"from":"2020-01-01","to":"2024-12-31"}问题涉及的时间范围,用于提示新旧规则适用;不传时下游按「不限」处理
need_citationboolean必填true是否需要法条引用。设为 true 时,响应里 citations 不允许是空数组

落地时先在接入层做参数校验:jurisdiction 必须在枚举里,need_citation 必须是布尔值,time_range 的 from 不能晚于 to。校验不通过直接返回 400 并写日志,不要交给模型去猜。验证方式很简单,用一条非法法域和一条空 question 各发一次,看接入层是否在前置阶段就拦住了。

定义响应字段:结论、法条引用、置信度、待人工确认项

响应字段要解决的是「前端怎么画」和「接入层怎么判」。建议约定为固定四个顶层字段,模型多返回的字段忽略,少返回的字段由校验脚本报错。空值规则要提前说清楚:数组类字段无内容时返回空数组而不是 null,字符串类字段无内容时返回空字符串而不是缺字段,这样前端不需要写两套判断。

字段名类型空值规则示例
conclusionstring不允许为空字符串;信息不足时必须显式写出「信息不足,无法给出结论」这类说明合同到期用人单位不续签的,通常需要按工作年限支付经济补偿,具体情形需结合是否维持或提高原条件判断。
citationsarray<object>无匹配时返回空数组;数组元素必须含 law_name、article、quote[{"law_name":"中华人民共和国劳动合同法","article":"第四十六条","quote":"……"}]
confidencenumber(0 到 1)不允许为 null;模型无法判断时给保守低值,不要省字段0.7
needs_human_reviewarray<string>不需要人工确认时返回空数组["是否维持或提高原劳动合同约定条件需人工核对"]

一个可直接对照的响应样例:

GLM-Legal 接入法律问答页面 / 先把输出格式和引用字段定死
{
  "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 时不要重试太多次,法律问答页面更适合直接提示「请稍后重试」并保留用户问题。

GLM-Legal 接入法律问答页面 / 先把输出格式和引用字段定死

对模型返回做 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 缺失出现在哪几条问题上。这两件事稳定之后,再考虑接入页面的展示细节和人工确认流程。