AI 的能力边界在快速刷新,但比模型能力更值得讨论的,是使用 AI 的人如何在不确定中做出判断。历史学家尤瓦尔·赫拉利在关于 AI、人类愚蠢与文明未来的访谈中反复提醒一件事:AI 是历史上第一个能够自主做出决策,并独立生成观点、作品甚至行动指令的技术。这个判断听起来偏哲学,实际上对从事 AI 工程的人有非常具体的操作含义。当模型输出被当作业务决策依据,甚至被接入 AI Agent 自动执行时,工程上就必须补齐记录、审核、回滚和观测能力,否则一次看似普通的模型调用,可能变成无法追溯的生产事故。
这篇文章不打算继续讨论文明兴衰,而是把赫拉利提出的两个核心问题——“AI 自主决策”和“人类愚蠢”——翻译成 AI 工程师能落地的系统设计。我们会从零构建一个最小可运行的“AI 决策审计服务”:它接收 AI 调用请求,记录输入输出,判断风险等级,对高风险动作执行拦截,并留下完整的审计日志。这个中间层适合接入内容回复、营销文案生成、AI 客服、AI 编程助手等需要控制 AI 自动执行权限的场景。学完之后,你可以用它理解 AI 应用开发中“可观测、可审核、可回滚”三件事到底怎么做,也可以在真实项目中扩展成更完整的 AI 治理基础设施。
1. 赫拉利在担心什么:把哲学警告翻译成工程师能处理的风险
1.1 为什么“AI 会自主决策”改变的不只是产品逻辑
赫拉利的核心观点之一是,AI 不同于印刷术、互联网这些工具。印刷术放大了人类已有的观点,互联网加速了信息传播,但 AI 可以自主生成新内容、做出新判断,甚至在无人干预的情况下采取行动。它的变化不是“工具更快”,而是“决策主体”出现了。
在工程里,这意味着模型输出不再是可预测的固定结果。同一个 prompt,可能因为换行、上下文、温度参数、模型版本不同而产生不一致输出;同一个用户问题,在不同时间点可能得到不同答案。如果把这个输出直接接到自动发邮件、自动审批、自动下单、自动生成代码的链路上,系统就要承担模型概率性带来的后果。
一个最小示例能说明问题:假设业务系统调用大模型生成一封活动邮件,并自动发送给用户。模型输出中包含错误链接,或者把用户称呼写错。此时如果系统没有记录模型输入、输出、模型版本、调用时间、触发人,出问题后根本不知道问题出在哪个环节。更严重的是,如果 AI 被赋予“自动执行”权限,错误会在没有人工确认的情况下完成扩散。放在赫拉利的语境里看,这就是“AI 的自主性”与“人类的疏忽”叠加后的典型风险。
所以,技术人员在吸收这类观点时,不应该停留在“AI 很危险”的恐慌上,而应该把“自主决策”转译成工程问题:模型输出不确定、上下文敏感、无法逐字复现。解决办法不是不用 AI,而是给每次决策加上审计边界。
1.2 “人类愚蠢”在工程里表现为哪几类系统性缺陷
赫拉利在访谈中多次提到人类容易受贪婪、愚蠢、注意力分散影响,从而误用新技术。放到工程现场,“人类愚蠢”很少表现为某个人恶意破坏,更多表现为团队在系统设计上的集体疏忽。下面是几类最常见的工程形态。
| 工程缺陷 | 典型症状 | 防御手段 |
|---|---|---|
| 盲目信任模型输出 | 不设人工复核,认为模型“足够聪明” | 设置风险等级,高风险动作必须人工审核 |
| 权限过大 | AI 能访问全部数据库、全部工具、全部用户数据 | 最小权限原则,按角色控制 AI 可执行动作 |
| 缺少可观测性 | 不记录日志,不复盘线上输出 | 建立决策审计日志,记录上下文和结果 |
| 提示词漏洞 | 用户隐私被拼进 prompt,模型输出被日志再次泄露 | 输入脱敏、字段加密、按需留痕 |
| 上线后不管 | 只看离线准确率,不监控线上漂移 | 建立监控、告警和回滚机制 |
这些形态看起来是“人的问题”,但工程上用系统机制是可以缓解的。比如不依赖某个开发人员记得脱敏,而是在数据写入审计表之前强制调用脱敏函数;不依赖运营人员记得审核,而是让风险高于阈值的动作直接被中间层拦截。好的系统设计,默认人容易犯错,因此把关键检查点做成强制流程,而不是提醒事项。
1.3 文明级风险如何落地为系统级风险
“文明未来”是一个宏观概念,落到 AI 系统上,可以拆成三个可度量的风险维度:影响面、传播速度、追溯能力。
影响面方面,模型输出通过 API、Agent 自动执行后,可能同时影响大量用户。一个带错误规则的推荐策略,可能在几小时内覆盖几百万次曝光。传播速度方面,AI 自动生成内容并自动发布后,问题扩散速度远高于人工编辑时代。最典型的是 AI 客服自动回复、AI 营销视频一键成片后自动发布,这类流程一旦出错,短时间很难撤回。追溯能力方面,如果系统没有记录决策上下文,事故复盘只能靠猜,回滚也不知道该回滚到哪一条数据。
因此,本文把宏大问题收敛成一个可执行目标:让每一次 AI 决策默认可追溯、可审核、可回滚。整个后面的系统实现都围绕这一条主线展开。
2. 从观点到系统:为 AI 决策审计设计一个最小闭环
2.1 系统目标与范围
我们构建一个“AI 决策审计服务”,位置在业务系统与大模型之间。业务流程如下:
- 业务系统发起一次 AI 决策请求。
- 审计服务先对输入文本做脱敏和摘要。
- 审计服务调用大模型,模型返回需要执行的动作和风险判断。
- 系统根据风险等级决定是放行、拦截,还是标记为待人工审核。
- 无论哪种结果,都写入审计数据库。
这个系统不解决“模型效果好不好”的问题,只解决“AI 做了什么、为什么这么做、能不能追回来”的问题。它适合嵌入到以下场景中:内容社区自动回复、营销文案生成、AI 客服工单分类、AI 编程助手执行代码建议等。由于我们使用通用接口设计,底层可以替换成任意大模型,也可以接入 Spring AI、LangChain 等框架的应用层。
2.2 技术选型
为了保证教程可复现,技术栈尽量精简,不引入分布式组件。学习环境中的运行组件如下表所示。
| 组件 | 选择 | 作用 | 说明 |
|---|---|---|---|
| Python | 3.11+ | 开发语言 | 落地前先确认本机 Python 版本 |
| Web 框架 | FastAPI | 提供 HTTP 接口 | 自动生成 OpenAPI 文档,便于调试 |
| 数据库 | SQLite | 存储审计日志 | 学习环境够用,生产建议换 PostgreSQL |
| 模型服务 | OpenAI 兼容接口 | 提供 AI 决策能力 | 示例中以 Mock 返回兜底,可替换 |
| 依赖管理 | requirements.txt | 固定核心依赖 | 版本号必须结合本机环境确认 |
不建议在学习阶段引入 Kafka、Redis、分布式链路追踪,否则会被基础设施牵着走,脱离审计逻辑本身。等系统跑通、瓶颈出现时再扩展,会更有针对性。
2.3 项目结构
项目目录如下,结构简单,但职责清晰。
ai-audit-demo/ ├── app/ │ ├── __init__.py │ ├── main.py │ ├── database.py │ ├── schemas.py │ ├── llm_service.py │ └── audit.py ├── requirements.txt └── README.md各文件职责:
main.py:FastAPI 入口,定义路由和请求处理逻辑。database.py:SQLite 连接和审计日志写入。schemas.py:Pydantic 请求模型。llm_service.py:模型调用抽象层,提供 Mock 实现。audit.py:脱敏、哈希和审计记录组装。
在编写代码前,先安装依赖:
pip install fastapi uvicorn pydantic3. 核心实现:记录一次 AI 决策的全部上下文
3.1 数据库表设计
审计日志表需要记录的是“一次决策的完整证据链”。字段设计如下:
CREATE TABLE IF NOT EXISTS decision_audit ( id INTEGER PRIMARY KEY AUTOINCREMENT, request_id TEXT NOT NULL, action_type TEXT NOT NULL, model_name TEXT, prompt_hash TEXT, input_summary TEXT, output_text TEXT, decision_result TEXT, risk_level TEXT, permission_role TEXT, created_at TEXT DEFAULT (datetime('now')), reviewed_by TEXT, reviewed_at TEXT, review_status TEXT );字段含义如下:
request_id:业务请求唯一标识,用于串联日志和回滚。action_type:AI 要执行的动作类型,比如content_reply、auto_email。model_name:实际调用的大模型名称或版本。prompt_hash:输入文本的哈希值,用于判断同一输入是否被重复请求。input_summary:脱敏后的输入摘要,不存完整明文。output_text:模型输出的脱敏结果。decision_result:allowed、blocked、pending_review或error。risk_level:风险等级,低、中、高。permission_role:发起请求的用户角色。review_status:人工审核状态,生产环境使用。
注意:created_at 使用 SQLite 的datetime('now'),存的是 UTC 时间。生产环境建议统一存入带时区的时间戳,并在展示层转换为本地时间。
3.2 请求入口与脱敏
先定义请求结构,使用 Pydantic:
from pydantic import BaseModel class AuditRequest(BaseModel): request_id: str = "" action_type: str permission_role: str = "user" input_text: str脱敏函数放在audit.py中。它的作用不是加密,而是把手机号、邮箱等敏感信息替换成占位符,避免敏感数据进入日志:
import re def mask_sensitive(text: str) -> str: text = re.sub(r"\b\d{11,}\b", "***PHONE***", text) text = re.sub(r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b", "***EMAIL***", text) return text这里要特别说明:脱敏要在日志写入前执行,并且无论有没有敏感字段,都应该对入库文本做一次处理。不要等到数据泄露了再补脱敏规则。
输入文本的哈希值用来做重复请求判断。这里不使用 Python 内置hash(),因为内置哈希在进程重启后可能改变,必须用稳定哈希算法:
import hashlib def hash_text(text: str) -> str: return hashlib.sha256(text.encode("utf-8")).hexdigest()3.3 调用 LLM 并拦截高风险动作
模型调用层负责把“模型输出”转成“结构化动作”。为了让教程在没有 API Key 的情况下也能跑通,先提供一个 Mock 实现:
def decide(input_text: str, use_mock: bool = True): if use_mock: return {"action": "reply", "risk": "low", "reason": "mock"} # 真实场景调用大模型接口,这里只留调用占位。 # payload = {"model": "gpt-4o-mini", "messages": [...]} # response = httpx.post("https://api.example.com/v1/chat/completions", # json=payload, timeout=10) # return parse_llm_json(response.text) raise NotImplementedError真实项目中,这里应该返回一个固定结构,例如:
{ "action": "reply", "risk": "high", "reason": "output contains sensitive operation" }这样后续逻辑不关心底层是哪个模型,只关心动作和风险等级。
然后是数据库写入逻辑,放在database.py:
import sqlite3 DB_PATH = "audit.db" def get_conn(): conn = sqlite3.connect(DB_PATH) conn.execute("PRAGMA journal_mode=WAL;") return conn def insert_audit(**kwargs): sql = """ INSERT INTO decision_audit (request_id, action_type, model_name, prompt_hash, input_summary, output_text, decision_result, risk_level, permission_role) VALUES (:request_id, :action_type, :model_name, :prompt_hash, :input_summary, :output_text, :decision_result, :risk_level, :permission_role) """ conn = get_conn() try: conn.execute(sql, kwargs) conn.commit() finally: conn.close()最后是 FastAPI 入口:
from fastapi import FastAPI from app.schemas import AuditRequest from app.llm_service import decide from app.database import insert_audit from app.audit import mask_sensitive, hash_text import uuid app = FastAPI() @app.post("/v1/decide") def create_decision(req: AuditRequest): if req.request_id == "": req.request_id = str(uuid.uuid4()) try: result = decide(req.input_text) risk = result.get("risk", "high") decision = "blocked" if risk == "high" else "allowed" insert_audit( request_id=req.request_id, action_type=req.action_type, model_name=result.get("model", "mock"), prompt_hash=hash_text(req.input_text), input_summary=mask_sensitive(req.input_text)[:200], output_text=mask_sensitive(str(result)), decision_result=decision, risk_level=risk, permission_role=req.permission_role, ) return {"decision": decision, "risk": risk} except Exception as exc: return {"decision": "error", "error": str(exc)}这里有几个关键点:
- 异常分支没有写审计日志。真实项目中,异常也需要记录,否则排查时看不到“模型调用失败”这次事件。
str(exc)直接返回给前端,在调试阶段帮助大,但生产环境会泄露堆栈信息。生产应只记录到日志,不返回给用户。- 这是一个最小链路,没有身份认证、限流、重试。后续章节单独说明生产改造。
4. 参数配置与工程取舍:温度、超时、权限和脱敏不能拍脑袋
4.1 模型参数对审计结果的影响
大模型参数会影响输出稳定性,而审计系统恰恰最需要稳定性。如果同一个输入在不同时刻产生不同风险判断,那么复核和回滚都会很困难。关键参数如下。
| 参数 | 含义 | 常见值 | 调大影响 | 调小影响 | 审计建议 |
|---|---|---|---|---|---|
| temperature | 采样随机性 | 0.7 | 输出更多样,复现困难 | 输出更保守、更稳定 | 审计场景建议 0 到 0.3 |
| max_tokens | 最大输出长度 | 按动作设置 | 输出完整,成本和时延上升 | 可能截断关键结构 | 解析 JSON 时留足余量 |
| timeout | 请求超时时间 | 10 到 30 秒 | 容错更高,用户等待更久 | 容易超时失败 | 审计中记录超时事件 |
| top_p | 核采样 | 1.0 | 输出更多样 | 输出更聚焦 | 固定值,避免排查混乱 |
另外还要记录模型版本。模型服务商更新后,同一个 prompt 可能输出不同结果。审计表如果只记模型名称不记版本,就无法定位是“模型变了”还是“规则变了”。
4.2 权限与角色:谁有资格让 AI 执行敏感操作
不是所有用户都应该让 AI 自动执行动作。这里用permission_role做最简单的分级:
| 角色 | 权限建议 | 典型动作 |
|---|---|---|
| guest | 只读,不允许 AI 自动执行 | 内容摘要、问题分类 |
| user | 生成草稿,不自动发布 | 文案生成、代码建议 |
| editor | 可生成并进入人工复核队列 | 自动回复、内容发布 |
| admin | 可修改规则和查看审计日志 | 配置变更、审核处理 |
设计原则是:审计日志的写入和修改权限必须低于普通用户权限。普通用户即使能调用 AI 服务,也不能删除或篡改审计记录。生产环境建议审计库使用独立账号,只允许中间服务写入,不允许业务账号直接访问。
4.3 脱敏和合规:日志不能变成数据泄露源
AI 调用过程中,最容易泄露的是输入文本里的用户隐私。一个常见场景是:为了回答用户问题,代码把手机号、邮箱、地址拼进 prompt,然后又把完整 prompt 写进日志。这样模型可能没泄露,日志反而泄露了。
建议按三层做处理:
- 日志层:审计表只存脱敏后的摘要,不存完整明文。
- 数据层:如果业务必须保存原始文本,使用字段级加密,不要明文落库。
- 展示层:查询审计页面时,默认只显示脱敏后的内容,敏感字段要用独立权限才能查看。
不要信任“这个接口只有内部人访问”这种说法。日志经常会被同步到日志平台,权限边界远远大于单一服务。
5. 运行验证:怎样确认系统真的能做到“可审计”
5.1 启动服务
进入项目目录,启动 FastAPI:
cd ai-audit-demo uvicorn app.main:app --reload --port 8000启动成功后,访问http://127.0.0.1:8000/docs可以看到 Swagger 文档,也可以直接使用下面的 curl 请求验证。
5.2 正常流程验证
发送一条低风险请求:
curl -X POST http://127.0.0.1:8000/v1/decide \ -H "Content-Type: application/json" \ -d '{"request_id":"demo-001","action_type":"content_reply","permission_role":"user","input_text":"请给用户回复一封活动邀请邮件,用户电话13800138000"}'预期返回:
{ "decision": "allowed", "risk": "low" }然后连接 SQLite 查看审计记录:
sqlite3 audit.db "select id, request_id, risk_level, decision_result, input_summary, output_text from decision_audit order by id desc limit 5;"如果脱敏生效,input_summary中的手机号应该显示为***PHONE***,而不是原始号码。这一步验证的是“日志不会成为泄露源”。
5.3 异常流程验证
验证高风险拦截时,可以修改llm_service.py中的 Mock 返回:
return {"action": "block", "risk": "high", "reason": "mock high risk"}重启服务后再次调用接口,预期返回:
{ "decision": "blocked", "risk": "high" }验证超时场景,可以在真实模型调用中把timeout调成很小,观察接口是否返回error,以及审计表是否记录了异常事件。注意当前最小示例没有把异常写入审计表,这本身就是改进点。
5.4 可审计系统检查清单
验证完成后,可以用下面的清单判断系统是否达到“可审计”标准:
- 是否每条 AI 决策都有唯一
request_id。 - 是否记录了模型名称和版本。
- 是否记录了输入摘要和输出结果,且敏感字段已脱敏。
- 高风险动作是否被拦截或进入人工复核。
- 是否保存了足够上下文,供事后复现和回滚。
如果任何一个答案是“否”,说明审计链路还有缺口。
6. 常见问题排查:日志、脱敏、权限和模型输出不一致
下面这张表整理了学习阶段最容易遇到的五类问题,每一类都可以按照“现象、原因、检查、解决”的顺序定位。
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 接口返回成功但数据表为空 | 事务未提交,或写入的不是同一个数据库文件 | 检查接口返回和 DB_PATH 路径,查看报错日志 | 确认commit(),统一数据库连接 |
| 日志里仍然出现手机号、邮箱 | 只对原始输入脱敏,没有对拼接后的文本脱敏 | 查询output_text和input_summary字段,确认是否含敏感信息 | 在写库前的最后一步再次脱敏 |
| 低风险动作被误判成高风险 | 模型返回的 JSON 格式不稳定,解析失败 | 查看output_text里的原始输出 | 使用结构化输出或 JSON Schema,解析失败时按高风险处理 |
| 前端限制了按钮但接口仍可调用 | 只在前端做了权限隐藏,后端没有校验权限 | 直接用 curl 伪造请求,观察是否绕过 | 在后端中间件或依赖注入中校验角色 |
| 模型更新后结果不可复现 | 审计表没有记录版本、参数、prompt 哈希 | 对比更新前后的审计记录 | 增加模型版本字段,固定 temperature 等参数 |
排查顺序也有讲究。遇到问题,先看接口返回的是decision还是error,再看数据库是否写入,然后看原始日志,最后才去看模型输出。不要一开始就怀疑模型,因为大多数问题出在调用链路、数据路径和权限校验上。
7. 生产级改造与扩展方向:从“最小审计”走向 AI 治理
7.1 学习环境与生产环境的差异
最小系统验证的是思路,生产环境还需要补齐大量细节。下面的表格可以帮助你判断当前环境的差距。
| 层面 | 学习环境 | 生产环境 |
|---|---|---|
| 数据库 | SQLite 单文件 | PostgreSQL 或专用审计存储,独立账号 |
| 鉴权 | 无鉴权 | OIDC + RBAC,后端强制校验角色 |
| 模型 | Mock 返回 | 多模型灰度,固定版本 |
| 监控 | 手动查 SQLite | 指标、日志、告警联动 |
| 回滚 | 手工修改记录 | 自动回滚策略,发布前备份 |
| 合规 | 不涉及 | 数据分类分级、日志保留周期、访问审计 |
生产环境中最容易被忽略的是日志保留周期。审计日志不能无限增长,也不能删得太早。建议按业务合规要求设置保留周期,例如 180 天或一年,到期后归档到冷存储,而不是直接删除。
7.2 可以从这个最小系统长出来的能力
这个设计只是起点,后续可以扩展成完整的 AI 治理平台。
- AI Agent 决策链路追踪:不只记录最终动作,还要记录 Agent 调用了哪些工具、每一步思考过程、每一步的输入输出。
- 主动式人工复核:高风险动作推送到即时通讯工具或人工审核工作台,确认后才放行。
- 模型效果与风险监控:统计每个模型的拒绝率、误判率、平均响应时间,发现异常后自动切流。
- 多模型灰度:在审计中间层按流量比例切换不同模型,对比输出质量和风险。
- 与 Spring AI 等框架集成:把审计服务包装成统一组件,上层 AI 应用开发框架透明接入。
这些方向也对应了当前 AI 工程实践、AI Agent 开发与 AI 模型部署中最常见的诉求。核心仍然是:先让 AI 决策可追溯,再谈模型效果和自动化程度。
7.3 给工程团队的三条落地建议
第一,先补记录再谈优化。没有审计数据之前,不要大规模放开 AI 自动执行。自动执行的前提是能回滚,而回滚的前提是知道执行了什么。
第二,审计规则要可配置。风险等级、敏感动作、白名单这些内容应该由业务方通过配置中心管理,不要硬编码在代码里。赫拉利说“人类愚蠢”不会消失,配置化就是承认需求会变、人会犯错,所以让规则调整不依赖发版。
第三,定期做事故演练。每月模拟一次高风险 AI 动作,验证审计日志是否完整、告警是否触发、人工审核是否及时、回滚是否有效。演练比应急方案更能暴露问题。
赫拉利提醒的核心是,人类愚蠢不会因为技术强大而自动消失。AI 工程师能做的,不是保证 AI 永远正确,而是让错误发生时被看见、被拦住、被纠正。本文的最小审计系统就是这套能力的第一块地基。