前几周在落地一个多步骤 AI Agent 工程时,我遇到一个非常典型的问题:让大模型直接给出答案,效果不错;让大模型解释“为什么给出这个答案”,它也能说出一套逻辑;可一旦把问题换成“请找出自己刚才回答里的漏洞”,模型常常只是换一种说法把原答案重述一遍,甚至越描越自信。
这种“单次生成 + 自我解释”的链路,本质上缺少一个对抗性校验环节。这也是我为什么开始关注“AI 内部法庭”这一思路:与其让模型自言自语,不如在推理流程里显式地设置质疑、辩护、举证、裁决等角色,把一次不可见的黑盒输出,变成一场有结构、有记录、可评分的“内部审判”。
本文会围绕这个概念展开,讲清楚它解决的问题、映射到工程上的系统架构,并给出一个可以直接扩展的 Python 最小实现。读者对象是正在做 AI 应用开发、AI Agent 编排、大模型评测,或者对 LLM 推理可靠性感兴趣的开发者。读完你会掌握一套把“法庭思维”落地成代码的方法,以及在实际项目中避开常见坑的工程经验。
1. 什么是“AI 内部法庭”
1.1 一个比喻:为什么要把推理过程变成法庭
先看一个常见场景。你向大模型提问:“这段 Python 代码在高并发场景下会不会有问题?”模型给出回答后,你追问:“请再检查一遍。”它可能会修正一两个小问题,但也可能只是把原答案写得更长更详细。
问题出在哪里?因为“检查”和“生成”共用同一套参数、同一个上下文窗口、同一种思维模式。模型没有理由反对自己刚刚生成的内容,它对内部 token 的置信度估计又是黑盒的。于是,自我反思经常退化成自我确认。
把法庭概念引入 AI 流程,本质上是把“一个人承担所有角色”改成“多个角色互相制约”。法官不负责生成答案,而是负责裁定答案;控方专门找漏洞;辩方专门补证据;陪审团或汇总器负责把争议过程压缩成结构化结果。每一个角色都有独立的目标函数和约束,最终裁决必须建立在对抗过程之上,而不是建立在模型的“自我感觉良好”上。
1.2 解决什么问题
这套机制主要解决四类痛点:
- 单次生成幻觉:一次解码可能落入概率分布的局部高概率区域,缺少对事实的交叉验证。
- 过度自信:模型很难准确表达“我不知道”,因为语言模型天然倾向于生成流畅回答,而不是承认不确定性。
- 解释不可审计:普通对话式解释无法被结构化存储,也无法回放质疑与辩护过程。
- 多智能体协作失控:在 AI Agent 系统中,多个子 Agent 各自输出后缺少一个统一裁判,容易把错误中间结果一路传递。
1.3 容易混淆的概念区分
在开始写代码前,有必要把几个相近概念摆在一起看:
| 概念 | 所处阶段 | 核心作用 |
|---|---|---|
| RLHF 奖励模型 | 模型训练阶段 | 对完整回答打分,用于强化学习对齐 |
| LLM-as-a-Judge | 模型应用阶段 | 用另一个大模型对生成结果评分 |
| Multi-Agent Debate | 模型应用阶段 | 多个模型实例互相辩论,收敛到更优答案 |
| AI 内部法庭 | 应用系统工程 | 在生成链路中固定角色、流程、证据和裁决报告 |
可以这样理解:奖励模型负责“在训练时告诉模型什么好”,LLM-as-a-Judge 负责“在推理时评估结果”,而 AI 内部法庭是把评估、对抗、证据、裁决组合成一套可复用、可观测、可审计的流程。它可以单独使用,也可以作为更复杂 AI Agent 系统中的一个裁决组件。
2. 系统整体架构
2.1 法庭角色到 AI 模块的映射
我来给出一张直接可用的角色映射关系表。它不需要和法律体系完全对应,而是借鉴“分权制衡”的思路。
| 法庭角色 | AI 流程模块 | 核心职责 |
|---|---|---|
| 法官 | 最终裁决模型 | 汇总各方意见,按统一量表打分并给出结论 |
| 控方 | 对抗质疑器 | 主动寻找答案中的事实错误、逻辑跳跃、边界遗漏 |
| 辩方 | 答案辩护器 | 为原答案补充解释、引用证据、回应质疑 |
| 证人 | 外部证据源 | 通过 RAG、搜索工具、数据库查询提供事实依据 |
| 陪审团 | 聚合评估器 | 对多轮争议进行归纳,输出结构化报告 |
2.2 推理主流程
一次完整的 AI 法庭推理可以分为六个阶段:
- 问题解析:把用户问题拆解成可被评估的单点问题。
- 初始回答生成:由一个普通生成模型先给出候选答案。
- 对抗性质疑:控方针对候选答案逐条提出反驳点。
- 辩护与举证:辩方针对每个质疑点进行回应,必要时触发工具调用获取证据。
- 交叉验证:法官综合初始答案、质疑记录、辩护材料,进行独立判断。
- 结构化裁决:输出评分、结论、缺陷列表和证据链。
从工程角度看,这个流程本质上是把一次prompt -> response扩展成多次受控的 LLM 调用,并用structured output把中间结果固定下来。
2.3 和普通 Agent 编排的区别
普通 Agent 编排通常是“任务分解 - 子任务执行 - 汇总输出”,每一步默认可信,只有代码异常时才会报错。AI 法庭更像是在每个关键决策点前面插入一个质检关卡。它不直接替代业务逻辑,而是作为高质量输出的一道保险。
3. 环境准备与版本说明
3.1 技术选型
本文示例使用 Python,因为它对结构化输出、数据校验、异步调用的支持都比较好。大模型接口以 OpenAI SDK 风格为例,如果你使用其他厂商 SDK,只需要替换客户端创建和响应解析部分。
建议环境如下:
- Python 3.10 或更高版本
openai或兼容 OpenAI 协议的 SDKpydantic用于数据模型校验python-dotenv用于管理环境变量- 一个可用的 LLM API Key,或本地部署的兼容接口
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。
3.2 安装依赖
mkdir ai-courtroom cd ai-courtroom python -m venv .venv source .venv/bin/activate pip install openai pydantic python-dotenv jinja2如果你使用国产模型或开源模型的 OpenAI 兼容接口,同样只需要配置base_url即可。
3.3 项目结构
本文使用一个最小但可扩展的目录结构:
ai-courtroom/ ├── .env ├── src/ │ ├── __init__.py │ ├── schemas.py # 结构化数据模型 │ ├── prompts.py # 各角色提示词定义 │ ├── court.py # 法庭核心编排逻辑 │ └── main.py # 命令行演示入口4. 核心原理拆解
4.1 为什么单次生成不可靠
从大模型工作原理来看,生成答案是一个逐 token 采样的过程。即使温度设置为 0,浮点运算仍然可能存在不确定性;温度大于 0 时,同一个问题可能出现多种回答。更关键的是,模型没有内置的“事实数据库访问层”,所有知识都压缩在参数中,训练数据截止时间外的信息无法被确认。
因此,把“生成”和“校验”解耦,是提高可靠性的基本思路。模型负责提出假设,专门的流程负责验证假设。
4.2 对抗性论证为什么有效
对抗性论证的价值不在于“吵赢”,而在于把潜在的错误显式化。法庭流程要求每个角色只能做自己职责内的事情:
- 控方不能只给出“我认为答案不对”这种模糊结论,必须列举具体缺陷。
- 辩方不能只说“我不同意”,必须给出理由或证据。
- 法官不能只凭印象打分,必须依据控制方和辩方的事实清单。
这种约束会降低模型走捷径的概率,因为每个角色被限制在一个更窄的任务范围内。简单说,角色分化让“挑错”成了一个显式任务,而不是生成流畅文本时的副产品。
4.3 结构化输出
为了让 AI 法庭可集成、可审计,所有轮次都应尽量输出结构化数据。用pydantic定义评分模型,可以避免下游出现脏字段。
评判维度建议如下:
| 维度 | 说明 | 分值范围 |
|---|---|---|
| 正确性 | 答案事实和逻辑是否准确 | 0-10 |
| 完整性 | 是否覆盖问题所有子项 | 0-10 |
| 可证伪性 | 是否给出能被验证的条件或依据 | 0-10 |
| 一致性 | 是否存在前后矛盾 | 0-10 |
4.4 采样策略
在 AI 法庭中,不同角色应使用不同采样参数:
- 生成初始答案:温度可稍高,例如 0.7,保证候选答案多样性。
- 控方找漏洞:温度 0.3 左右,避免无意义发散。
- 法官裁决:温度固定为 0,追求稳定、可复现。
这不是绝对规则,但工程上建议把采样参数也纳入配置管理。
5. 完整实战案例:搭建一个最小 AI 法庭
下面来实现一个可以控制四个角色协同工作的最小系统。为了便于演示,这里将所有代码控制在几个核心文件中,并在注释中说明每个文件的作用。
5.1 定义数据模型
文件路径:src/schemas.py
# src/schemas.py from enum import Enum from typing import List, Optional from pydantic import BaseModel, Field class Verdict(str, Enum): """裁决结论""" APPROVE = "approve" REVISE = "revise" REJECT = "reject" class Argument(BaseModel): """一轮对抗中,某一方提出的论点""" role: str content: str # 可信度用于表达模型对本条论点的把握 confidence: float = Field(default=0.5, ge=0.0, le=1.0) class Evidence(BaseModel): """证据条目,可以来自检索器、数据库或外部工具""" claim: str source: str = "" summary: str = "" class CourtReport(BaseModel): """AI 法庭最终结构化报告""" question: str final_answer: str verdict: Verdict score: float = Field(ge=0, le=10) flaw_points: List[str] = Field(default_factory=list) defense_points: List[str] = Field(default_factory=list) evidence: List[Evidence] = Field(default_factory=list) verdict_reasoning: str = ""这里使用pydantic可以保证每一轮输出的字段完整、类型正确,后续不管接数据库还是接前端展示都很方便。
5.2 定义各角色提示词
文件路径:src/prompts.py
# src/prompts.py QUESTION_ANALYST_SYSTEM = """你是一个问题分析器。 你的职责是把用户问题拆解成 1 到 3 个明确的子问题,确保后续环节聚焦。 只输出 JSON,不要解释过程。""" ANSWER_GENERATOR_SYSTEM = """你是一个严谨的答案生成器。 请根据子问题清单给出完整、直接的回答。 回答要求:准确、简洁、有逻辑层次。""" PROSECUTOR_SYSTEM = """你是一个严格的对性质疑者。 你的任务不是生成新答案,而是找出候选答案中的漏洞。 你必须从以下角度检查: 1. 事实性错误:是否存在编造或者过时信息。 2. 逻辑跳跃:是否存在没有依据的推导。 3. 边界遗漏:是否没有考虑异常情况和反例。 4. 上下文忽略:是否没有完全回应用户问题。 要求: - 每条质疑必须具体,指出对应原文。 - 不要提出毫无根据的怀疑。 - 只输出 JSON 数组,每个元素为一条质疑点。""" DEFENDER_SYSTEM = """你是一个谨慎的辩护者。 你的职责是回答质疑者提出的每一条质疑。 要求: - 先承认合理的部分,再补充反驳理由。 - 如果质疑成立,请直接承认,不要强行辩护。 - 如果质疑不成立,请说明理由,并尽量补充出处或证据线索。 - 只输出 JSON 数组,每个元素对应一条质疑的回应。""" JUDGE_SYSTEM = """你是一个中立法官。 你会看到原始问题、初始答案、控方质疑、辩方辩护。 请独立做出最终裁决。 评分维度: - 正确性:逻辑和事实是否可靠。 - 完整性:是否覆盖问题关键点。 - 可证伪性:答案是否可被验证。 - 一致性:是否存在前后矛盾。 裁决标准: - 若所有质疑都被合理解释,且答案无明显漏洞,输出 verdict=approve。 - 若部分内容需要修改,输出 verdict=revise。 - 若答案存在严重错误或几乎无法辩护,输出 verdict=reject。 只输出 JSON 对象。"""需要提醒的是,这些提示词在实际项目中要根据你的领域进行定制。领域越明确,对抗质量越高。
5.3 实现法庭编排逻辑
文件路径:src/court.py
# src/court.py import json import logging from typing import Any, Dict, List from .prompts import ( ANSWER_GENERATOR_SYSTEM, DEFENDER_SYSTEM, JUDGE_SYSTEM, QUESTION_ANALYST_SYSTEM, PROSECUTOR_SYSTEM, ) from .schemas import Argument, CourtReport, Evidence, Verdict logger = logging.getLogger(__name__) class Courtroom: """AI 内部法庭的编排器""" def __init__( self, llm_client: Any, model: str = "gpt-4o-mini", temperature: float = 0.3, max_tokens: int = 2000, ): self.llm_client = llm_client self.model = model self.default_temperature = temperature self.max_tokens = max_tokens def _call( self, system_prompt: str, user_prompt: str, temperature: float, json_mode: bool = True, ) -> str: """封装大模型调用,统一日志和异常处理""" kwargs = { "model": self.model, "temperature": temperature, "max_tokens": self.max_tokens, } if json_mode: # 注意:部分模型不支持 response_format,需要按实际 SDK 调整 kwargs["response_format"] = {"type": "json_object"} response = self.llm_client.chat.completions.create( messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], **kwargs, ) return response.choices[0].message.content def _safe_json(self, text: str): """把模型输出解析成 JSON,失败时抛异常""" try: return json.loads(text) except json.JSONDecodeError as e: logger.error("模型输出不是有效 JSON: %s", text) raise ValueError(f"JSON 解析失败: {e}") from e def _analyze_question(self, question: str) -> List[str]: """拆解用户问题""" content = self._call( QUESTION_ANALYST_SYSTEM, f"用户问题:{question}", temperature=0.2, ) data = self._safe_json(content) sub_questions = data.get("sub_questions", [question]) return sub_questions if isinstance(sub_questions, list) else [question] def _generate_answer(self, question: str) -> str: """生成初始候选答案""" return self._call( ANSWER_GENERATOR_SYSTEM, f"问题:{question}", temperature=0.5, json_mode=False, ).strip() def _prosecute(self, question: str, answer: str) -> List[str]: """控方找漏洞""" user_prompt = ( f"用户问题:{question}\n\n" f"候选答案:\n{answer}\n\n" "请输出如下 JSON 格式:\n" '{"flaws": ["漏洞1", "漏洞2"]}' ) content = self._call(PROSECUTOR_SYSTEM, user_prompt, temperature=0.3) data = self._safe_json(content) return data.get("flaws", []) def _defend( self, question: str, answer: str, flaws: List[str] ) -> List[str]: """辩方回应""" user_prompt = ( f"用户问题:{question}\n\n" f"候选答案:\n{answer}\n\n" f"质疑点清单:\n{chr(10).join('- ' + f for f in flaws)}\n\n" "请逐条回应,输出如下 JSON 格式:\n" '{"defenses": ["回应1", "回应2"]}' ) content = self._call(DEFENDER_SYSTEM, user_prompt, temperature=0.3) data = self._safe_json(content) return data.get("defenses", []) def _judge( self, question: str, answer: str, flaws: List[str], defenses: List[str], ) -> Dict[str, Any]: """法官最终裁决""" user_prompt = f""" 用户问题: {question} 初始答案: {answer} 控方质疑点: {chr(10).join('- ' + f for f in flaws)} 辩方回应: {chr(10).join('- ' + d for d in defenses)} 请你以 JSON 对象形式输出最终裁决: {{ "final_answer": "修正后的最终答案", "verdict": "approve 或 revise 或 reject", "score": 0, "verdict_reasoning": "裁决理由", "evidence": [ {{"claim": "事实主张", "source": "来源", "summary": "摘要"}} ] }} """.strip() content = self._call(JUDGE_SYSTEM, user_prompt, temperature=0.0) return self._safe_json(content) def run(self, question: str) -> CourtReport: """执行一次完整法庭流程""" logger.info("开始处理问题: %s", question) sub_questions = self._analyze_question(question) question_with_sub = f"{question}\n子问题:{';'.join(sub_questions)}" answer = self._generate_answer(question_with_sub) # 如果问题比较简单,可以跳过对抗,只看答案可信度;这里示例直接走完整流程。 flaws = self._prosecute(question, answer) # 无缺陷时,直接进入独立评估 if not flaws: defenses = [] else: defenses = self._defend(question, answer, flaws) judge_result = self._judge(question, answer, flaws, defenses) evidence = [ Evidence(**item) for item in judge_result.get("evidence", []) ] return CourtReport( question=question, final_answer=judge_result.get( "final_answer", answer ), verdict=Verdict(judge_result.get("verdict", "revise")), score=float(judge_result.get("score", 0)), flaw_points=flaws, defense_points=defenses, evidence=evidence, verdict_reasoning=judge_result.get("verdict_reasoning", ""), )这个类把流程完全封装了。外部只需要创建一个Courtroom实例,然后调用run(question)即可拿到结构化报告。
5.4 编写演示入口
文件路径:src/main.py
# src/main.py import os from dotenv import load_dotenv from openai import OpenAI from .court import Courtroom load_dotenv() def build_client() -> OpenAI: """构建大模型客户端""" return OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL", None), ) def main(): client = build_client() courtroom = Courtroom( llm_client=client, model=os.getenv("LLM_MODEL", "gpt-4o-mini"), temperature=0.3, max_tokens=3000, ) question = "在 Python 中使用多线程处理 CPU 密集型任务,如何提升性能?" report = courtroom.run(question) print("=" * 50) print("问题:", report.question) print("裁决结论:", report.verdict.value) print("综合得分:", report.score) print("最终答案:") print(report.final_answer) print("\n缺陷清单:") for flaw in report.flaw_points: print(f"- {flaw}") print("\n辩护要点:") for defense in report.defense_points: print(f"- {defense}") print("\n证据链:") for ev in report.evidence: print(f"- {ev.claim} | 来源: {ev.source}") if __name__ == "__main__": main()5.5 运行与验证
在项目根目录创建.env文件:
LLM_API_KEY=你的API密钥 LLM_BASE_URL=你的接口地址 LLM_MODEL=你的模型名称运行命令:
python -m src.main预期输出大致长这样:
================================================== 问题: 在 Python 中使用多线程处理 CPU 密集型任务,如何提升性能? 裁决结论: revise 综合得分: 7.5 最终答案: 对于 CPU 密集型任务,推荐使用 multiprocessing 而非 threading... 缺陷清单: - 候选答案把 GIL 描述成完全无法并行,忽略了 I/O 场景下的多线程价值。 - 缺少 multiprocessing 与进程池的示例代码。 辩护要点: - 原回答的结论在 CPU 密集型场景下成立,但没有限定使用范围。 - 可以补充 ProcessPoolExecutor 示例。 证据链: - claim: CPython 的 GIL 会限制单个进程内的 Python 字节码并行执行 - source: Python 官方文档这个输出不是固定的,具体内容会受模型能力、提示词和参数影响,但结构应当保持一致。
5.6 代码连接方式说明
如果要在自己的项目中使用,不需要命令行入口,可以直接整合:
from openai import OpenAI from src.court import Courtroom client = OpenAI(api_key="...") courtroom = Courtroom(llm_client=client, model="your-model") report = courtroom.run("你的问题") print(report.model_dump_json())Courtroom类本身不依赖命令行,可以很容易嵌入到 FastAPI 服务、Celery 任务或数据管道中。
6. 常见问题与排查思路
在实际落地时,AI 法庭这种多角色编排很容易出现下面几类问题。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 法官评分每次都不一样 | 温度非 0,或者提示词约束不足 | 法官温度设为 0;增加结构化评分量表;多次运行取平均分 |
| 控方和辩方互相复读 | 角色提示词太相似,模型没有差异化 | 为不同角色提供“禁止做的事”;给控方提供领域错误清单模板 |
| 上下文太长导致细节丢失 | 所有环节使用完整全文拼接 | 每轮只传必要摘要;一个大环节输出一次结构化 JSON |
| 调用成本过高 | 所有问题都走完整法庭流程 | 增加预筛机制:先做普通评估,低置信度或高风险问题才进入法庭 |
| 模型幻觉被当作事实采信 | 没有外部证据源参与 | 整合检索工具或知识库,把证据项与事实绑定;关键结论要求给出来源 |
| 最终答案比初始答案还差 | 法官在裁决时过度改写 | 法官应优先修复有问题的局部内容,而非重写;增加“最小改动”原则 |
排查时建议先查看每一轮的原始返回,确认是提示词问题、参数问题还是模型能力问题。可以给Courtroom加上debug=True参数,把每个环节的输入输出记录到日志或文件中,这是最有效的定位手段。
7. 最佳实践与工程建议
7.1 按风险等级决定是否开启法庭流程
不是所有请求都需要走完整法庭流程。一个更经济的做法是:
简单查询 -> 普通回答 + 单模型打分 复杂推理 -> 完整法庭流程 高风险场景 -> 法庭流程 + 人工复核判断规则可以基于问题长度、是否包含高危关键词、是否要求代码方案、是否涉及金额/医疗/法律等敏感领域。
7.2 提示词工程细节
- 每个角色都要有“输出格式约束”和“禁止行为”两部分,只写“请严格检查”往往不够。
- 尽量让控方站在“领域专家的挑剔目光”下输出,而不是站在“一个想要反对的人”的位置。前者能识别专业漏洞,后者只会空泛抬杠。
- 法官拿到的不应该只有最终辩护文本,还应该有控方原话和辩方原话,否则无法独立判断“谁在说谎”。
7.3 评测闭环
AI 法庭本身也是 LLM 应用,也需要被评测。建议先准备一个 50 到 100 条问题的评测集,其中每条都包含“标准答案”和“关键错误点”。跑完法庭后检查:
- 最终答案和标准答案的重合率。
- 法官给出的缺陷点是否命中人工标注的错误点。
- 法官是够产生了误杀,也就是把正确内容标记为缺陷。
用这一套指标持续迭代,会比只靠“感觉更可靠”要可衡量得多。
7.4 日志与安全边界
既然引入“法庭”,就要保留完整的“案卷”。建议对每次运行保存:
- 问题原文。
- 初始答案。
- 各轮控方与辩方输出。
- 法官评分与理由。
- 版本号(提示词版本 + 模型版本 + 参数版本)。
在高风险领域,比如医疗建议、法律意见、财务决策,AI 法庭只能作为辅助分析,最终决策权必须保留给有资质的人。日志审计能力是这种场景的底线要求。
7.5 性能与成本优化
多角色编排意味着多次模型调用,成本大约是普通问答的 3 到 5 倍。可以按下面的方式控制成本:
- 把问题先交给一个轻量分类器,判断是否需要进入法庭。
- 对同一问题的多个子环节复用相同的检索结果。
- 对历史相似问题启用结果缓存。
同时建议开启大模型的流式日志和耗时统计,对时间敏感的线上服务要设置超时降级策略。
8. 总结与学习路线
本文从一个很具体的工程痛点出发:大模型的单次输出难以被自我纠错,解释过程也缺乏对抗性。我把它拆解成一套“AI 内部法庭”方案,包含角色映射、系统架构、结构化数据模型和一个可以在本地直接运行的最小 Python 实现。通过这个实现,你会看到控方、辩方、法官如何各自独立工作,最终产出一份包含答案、评分、缺陷和证据的报告。
接下来可以继续深入的方向包括:
- 把法庭流程接入 RAG 检索链路,让辩护方真正引用外部证据。
- 把法庭输出的优质“质疑-辩护”对收集成数据集,用于后续指令微调或偏好对齐。
- 将
Courtroom改造为异步流水线,嵌入到 AI Agent 长期运行的工作流中。 - 对法官模型单独做评估,分析评分偏差和误判模式。
如果你正在做 AI 应用开发或 AI Agent 工程,建议先从每周 20 个问题的法庭评测开始跑起,积累足够的日志后再调整角色提示词。一个小提示是:永远不要让负责裁决的模型和负责生成答案的模型共享相同的温度参数和提示词风格,否则“法庭”很容易变成走过场。