“AI 同事”这个概念,听起来很炫,但真正落到企业内部,你会发现最大的难点不是让模型“答得对”,而是让它“记得住、能纠错、敢交底”。最近在搭建一套长期在线、可持续迭代的 AI 助手时,我踩了不少坑:模型偶尔给出错误结论、用户纠错之后下次依然犯同样的毛病、业务方要求所有数据和模型都不能出内网。这些问题串起来,正好对应三个关键词:持久化、AI 纠错、模型主权。以 Maersk 这类跨国物流企业为代表,AI 一旦进入生产流程,任何一次错误预测都可能影响真实运营,因此“纠错机制”和“模型可控性”就被提到了比模型效果更高的位置。
本文不讨论具体的新闻事件,而是从工程角度拆解:一个具备持久记忆、可被人工纠错的 AI 同事应该如何设计,模型主权又如何通过私有化部署和治理机制落地。全文包含完整可运行的 FastAPI 项目示例,涉及记忆存储、反馈回流、本地模型调用等内容,适合正在做 AI 应用开发、AI Agent 工程化、企业私有化模型部署的开发者参考。
1. 从问答助手到 AI 同事,变化的不仅是“会聊天”
先看一个很常见的业务场景:一线业务人员开始习惯每天问 AI“今天有哪些重点柜子要跟踪”“最近航线的准班率怎么样”。一开始大家用的是网页版 AI 工具,问一次是一次,关闭页面后什么都不剩。后来团队想做一个企业内部的“AI 同事”,要求它能记住昨天的讨论、知道某个客户的历史习惯,甚至在回答错误后可以被业务人员直接纠正,并且下次不再犯。
这就是“持久化 AI 同事”和“一次性 AI 助手”的本质区别。一次性助手是无状态的,每次请求都从零开始;持久化 AI 同事是有状态的,它需要跨会话记住事实、偏好、纠错反馈,并把这些信息用于后续推理。
1.1 为什么不能继续用一次性问答
一次性问答工具的问题非常直观:
- 业务人员每次都要重复背景信息,AI 没有部门上下文。
- 模型犯过的错误无法被记录,同样的坑会反复踩。
- 无法区分不同用户的权限和记忆范围。
- 外部 API 调用会把业务数据发送到第三方平台,合规风险高。
如果只是写个 Demo,一次性问答完全够用。但企业一旦想把 AI 当成“团队成员”来用,状态管理、记忆持久化、反馈闭环就是绕不开的工程问题。
1.2 持久化 AI 同事的核心能力模型
我把一个可落地的持久化 AI 同事拆成四个能力层:
| 能力层 | 说明 | 关键技术 |
|---|---|---|
| 记忆层 | 保存用户身份、业务偏好、历史对话、纠错记录 | 数据库、向量检索、Redis |
| 推理层 | 根据当前问题和记忆生成回答 | 大模型、Prompt 工程、Agent 流程 |
| 纠错层 | 收集用户对错误回答的反馈并回流到后续生成 | 日志、人工标注、评测集、微调 |
| 治理层 | 控制模型版本、数据边界、访问权限,保证模型主权 | 私有化部署、模型路由、审计 |
这里最关键的是第四层。模型如果部署在外部平台上,企业很难确认数据是否被第三方保存,也无法在模型异常时快速回滚。所以 Maersk 这类对运营稳定性要求极高的企业,在 AI 落地时会把“模型主权”作为硬性条件。
1.3 为什么模型主权是底线
模型主权,简单来说就是企业对模型拥有“看得见、改得动、收得回”的控制权。拆开看包括:
- 数据主权:业务数据只保存在企业内部,不出域。
- 部署主权:模型运行在自己可控的服务器或私有云上。
- 版本主权:模型升级、回滚、灰度发布由企业自主决定。
- 审计主权:每次请求的输入输出、模型版本、系统提示词都能追溯。
如果模型完全依赖外部 API,数据要出域、版本要跟着平台走、错误无法自主修复,这三点足以让企业级项目卡在试点阶段。因此下文实现的示例服务,会优先选择 OpenAI 兼容的本地推理接口,把模型服务地址放在内网。
2. 核心设计:记忆、纠错与模型主权
进入代码之前,先把设计思路讲清楚。我不会直接把一堆接口堆在一起,而是按照“记忆层 → 纠错层 → 推理层 → 服务层”的顺序逐步构建。
2.1 记忆层:三类记忆不能混在一起
持久化 AI 同事至少需要三类记忆:
| 记忆类型 | 生命周期 | 示例 | 存储方式 |
|---|---|---|---|
| 短期对话记忆 | 数分钟到数小时 | 当前会话中的上下文 | Redis + 会话 ID |
| 长期业务记忆 | 数天到数月 | 用户偏好、项目事实、历史结论 | PostgreSQL 或 JSON 持久化 |
| 纠错记忆 | 长期,持续更新 | 用户纠正过的错误问答对 | 独立表或独立文件 |
本文示例会用一个简单的 JSON 文件保存长期记忆和纠错记录,目的是把核心流程讲清楚。生产环境建议将长期记忆放到 PostgreSQL 中,纠错记录单独建表,短期会话放到 Redis,这样各自生命周期不同,不会互相污染。
2.2 纠错闭环:Human-in-the-Loop 不是口号
纠错机制是持久化 AI 同事区别于普通聊天机器人的重要分水岭。一个完整的纠错闭环如下:
用户提问 ↓ 模型回答 ↓ 用户/审核人员发现错误 ↓ 记录纠错样本(错误回答 + 正确回答 + 上下文) ↓ 纠错样本回流到 Prompt 或微调数据 ↓ 回归测试通过 ↓ 发布到新版本这里最容易被忽略的是“上下文”。很多团队只记录“用户问题”和“正确回答”,却没有记录“模型当时是基于哪些历史信息回答的”。这样一来,纠错样本无法准确复现,后续评测也就无从谈起。
2.3 模型主权与本地推理服务
要实现模型主权,最简单的方式是部署一个内部的 OpenAI 兼容推理服务。这样代码层面不需要改动太多,只要把base_url指向内网地址,同时把api_key设置成内部占位值即可。主流的本地推理框架都支持 OpenAI 兼容接口,具体命令以各框架官方文档为准。
本地部署带来的好处是:请求不再出内网、模型版本可控、数据可以审计。代价是:需要自己维护 GPU 资源和推理服务稳定性。二者的平衡需要根据业务量评估。
3. 环境准备与项目结构
3.1 运行环境
示例项目使用 Python 3.10 及以上版本,依赖库如下:
fastapi uvicorn[standard] openai pydantic安装命令:
pip install -r requirements.txt本地需要一个支持 OpenAI 兼容接口的推理服务。你可以选择:
- vLLM:适合 GPU 资源充足、并发要求高的场景。
- Ollama:适合本机快速验证,默认也提供兼容接口。
- LocalAI:适合 CPU/GPU 混合场景。
本文代码默认连接http://127.0.0.1:8000/v1,这个地址可以按你的实际服务地址修改。
3.2 项目目录结构
ai-colleague/ ├── app/ │ ├── __init__.py │ ├── config.py │ ├── memory.py │ ├── feedback.py │ ├── model.py │ ├── main.py │ └── data/ │ ├── memories.json │ └── corrections.json ├── requirements.txt └── README.mddata目录用于存放持久化数据,首次运行会自动创建。生产环境建议换成数据库。
4. 实战:构建一个带记忆和纠错反馈的 AI 同事
下面开始写代码。我会按照模块拆解,最后给出运行和验证方式。
4.1 编写全局配置 config.py
# 文件路径:app/config.py import os # 本地 OpenAI 兼容推理服务地址 MODEL_API_KEY = os.getenv("MODEL_API_KEY", "EMPTY") MODEL_BASE_URL = os.getenv("MODEL_BASE_URL", "http://127.0.0.1:8000/v1") MODEL_NAME = os.getenv("MODEL_NAME", "qwen-7b") # 数据目录 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.join(BASE_DIR, "data") MEMORY_FILE = os.path.join(DATA_DIR, "memories.json") CORRECTION_FILE = os.path.join(DATA_DIR, "corrections.json") os.makedirs(DATA_DIR, exist_ok=True)这里把本地推理服务地址放在环境变量里,很方便切换环境。MODEL_NAME需要和你的推理服务启动时配置的模型名一致。
4.2 编写记忆模块 memory.py
# 文件路径:app/memory.py import json import os import time from typing import Dict, List from app.config import MEMORY_FILE def load_memory() -> Dict[str, List[Dict]]: """加载全部用户的长期记忆。""" if not os.path.exists(MEMORY_FILE): return {} with open(MEMORY_FILE, "r", encoding="utf-8") as f: return json.load(f) def save_memory(memory: Dict[str, List[Dict]]) -> None: """保存长期记忆到本地文件。""" with open(MEMORY_FILE, "w", encoding="utf-8") as f: json.dump(memory, f, ensure_ascii=False, indent=2) def add_memory(user_id: str, content: str, source: str = "user") -> None: """追加一条记忆,源可以是 user 或 assistant。""" memory = load_memory() memory.setdefault(user_id, []) memory[user_id].append({ "content": content, "source": source, "ts": int(time.time()) }) # 每个用户保留最近 50 条,避免文件无限膨胀 memory[user_id] = memory[user_id][-50:] save_memory(memory) def get_recent_memory(user_id: str, limit: int = 8) -> List[Dict]: """获取用户最近的若干条记忆。""" memory = load_memory() return memory.get(user_id, [])[-limit:]这段代码的逻辑是:所有历史对话按user_id区分存放,每次追加后截断到最近 50 条。这个截断策略在生产环境中只是为了兜底,真正做长期记忆时,应该把“需要长期保留的事实”单独提炼出来,而不是把原始对话无限堆下去。
4.3 编写纠错模块 feedback.py
# 文件路径:app/feedback.py import json import os import time from typing import Dict, List from app.config import CORRECTION_FILE def load_corrections() -> Dict[str, List[Dict]]: """加载所有用户的纠错样本。""" if not os.path.exists(CORRECTION_FILE): return {} with open(CORRECTION_FILE, "r", encoding="utf-8") as f: return json.load(f) def save_corrections(corrections: Dict[str, List[Dict]]) -> None: """保存纠错样本到本地文件。""" with open(CORRECTION_FILE, "w", encoding="utf-8") as f: json.dump(corrections, f, ensure_ascii=False, indent=2) def add_correction(user_id: str, query: str, bad_answer: str, correct_answer: str) -> None: """记录一条人工纠错样本。""" data = load_corrections() data.setdefault(user_id, []) data[user_id].append({ "query": query, "bad_answer": bad_answer, "correct_answer": correct_answer, "ts": int(time.time()) }) # 每个用户保留最近 20 条纠错样本 data[user_id] = data[user_id][-20:] save_corrections(data) def build_correction_prompt(user_id: str, limit: int = 3) -> str: """从纠错样本生成 Few-shot Prompt 片段。""" data = load_corrections() records = data.get(user_id, [])[-limit:] if not records: return "" lines = ["请参考以下人工纠错记录,避免再次犯同样的错误:"] for i, r in enumerate(records, 1): lines.append( f"{i}. 用户问题:{r['query']}\n" f" 之前错误回答:{r['bad_answer']}\n" f" 正确回答:{r['correct_answer']}" ) return "\n".join(lines)这里的关键点是build_correction_prompt,它会把最近的人工纠错记录拼接到系统提示词中。这种方式的优点是实现简单,不依赖模型微调;缺点是 Prompt 会随着纠错记录增多而变长,因此只取最近 3 条。后续可以升级为向量检索,每次都找与当前问题最相关的纠错样本。
4.4 编写模型调用模块 model.py
# 文件路径:app/model.py from openai import OpenAI from app.config import MODEL_API_KEY, MODEL_BASE_URL, MODEL_NAME # 初始化 OpenAI 兼容客户端 _client = OpenAI( api_key=MODEL_API_KEY, base_url=MODEL_BASE_URL ) def ask_model(system_prompt: str, user_prompt: str) -> str: """调用本地大模型。""" response = _client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.3, ) return response.choices[0].message.content这里使用的是 OpenAI Python SDK,但base_url指向本地推理服务。如果你使用的是 Ollama,则地址通常是http://127.0.0.1:11434/v1,模型名需要改成你在 Ollama 中拉取的模型名。温度设置为 0.3,目的是让回答更稳定、更贴近确定性任务。
4.5 编写 FastAPI 接口 main.py
# 文件路径:app/main.py from fastapi import FastAPI from pydantic import BaseModel from app.feedback import add_correction, build_correction_prompt from app.memory import add_memory, get_recent_memory, load_memory from app.model import ask_model app = FastAPI(title="AI Colleague") class ChatRequest(BaseModel): user_id: str message: str class FeedbackRequest(BaseModel): user_id: str query: str bad_answer: str correct_answer: str class ChatResponse(BaseModel): reply: str memory_size: int @app.get("/health") def health(): return {"status": "ok"} @app.post("/chat", response_model=ChatResponse) def chat(req: ChatRequest): # 1. 先把用户问题写入长期记忆 add_memory(req.user_id, req.message, source="user") # 2. 获取最近记忆,拼成历史上下文 recent = get_recent_memory(req.user_id, limit=8) history_lines = [] for item in recent: role = "用户" if item["source"] == "user" else "AI" history_lines.append(f"{role}:{item['content']}") history_text = "\n".join(history_lines) # 3. 获取纠错样本,拼进系统提示词 correction_prompt = build_correction_prompt(req.user_id) system_prompt = ( "你是一名企业内部 AI 同事,回答要简洁、严谨、可追溯。" "你可以参考用户的历史对话和人工纠错记录。" ) if correction_prompt: system_prompt += "\n\n" + correction_prompt user_prompt = f"历史对话:\n{history_text}\n\n当前用户问题:{req.message}" # 4. 调用本地模型 reply = ask_model(system_prompt, user_prompt) # 5. 把模型回答也写入长期记忆 add_memory(req.user_id, reply, source="assistant") memory_size = len(load_memory().get(req.user_id, [])) return ChatResponse(reply=reply, memory_size=memory_size) @app.post("/feedback") def feedback(req: FeedbackRequest): add_correction( user_id=req.user_id, query=req.query, bad_answer=req.bad_answer, correct_answer=req.correct_answer ) return {"status": "ok", "message": "纠错样本已保存"}现在一个完整的 AI 同事服务已经成型:
/chat接收用户问题和用户 ID,自动带出历史记忆和纠错记录,然后调用本地模型回答。/feedback接收一条人工纠错记录,写入纠错样本,后续/chat生成回答时会自动参考这些样本。/health用于健康检查。
4.6 启动推理服务
以 vLLM 为例,启动一个本地 OpenAI 兼容服务:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen-7b \ --port 8000注意不同版本的 vLLM 命令可能略有差异,请以官方文档为准。如果你只是为了本机验证,也可以直接用 Ollama 拉取模型,然后用它的兼容接口。重点是把MODEL_BASE_URL指向你实际的本地服务地址。
4.7 启动 AI 同事服务
在项目根目录执行:
uvicorn app.main:app --host 0.0.0.0 --port 8100启动后先做健康检查:
curl http://127.0.0.1:8100/health预期输出:
{"status":"ok"}然后调用对话接口:
curl -X POST http://127.0.0.1:8100/chat \ -H "Content-Type: application/json" \ -d '{"user_id":"user01","message":"上海到洛杉矶的海运大概多少天?"}'预期输出为 JSON,其中reply是模型回答,memory_size表示该用户当前记忆条数。
再提交一条纠错反馈:
curl -X POST http://127.0.0.1:8100/feedback \ -H "Content-Type: application/json" \ -d '{"user_id":"user01","query":"上海到洛杉矶的海运大概多少天?","bad_answer":"一般是5天","correct_answer":"需要根据航线、船司和季节判断,通常是20-35天,建议以最新船期为准。"}'下次再问类似问题时,系统提示词中会自动带上这条纠错记录。
5. 深入纠错机制:从记录到持续评测
上一节的/feedback接口只是把纠错样本存了下来。要让纠错真正发挥作用,还需要做三件事:现场回放、评测回归、版本发布。
5.1 错误记录与现场回放
持久化 AI 同事上线后,第一要务是记录“错误现场”。每次请求至少需要保存以下字段:
| 字段 | 说明 |
|---|---|
| request_id | 请求唯一 ID |
| user_id | 用户标识 |
| prompt | 实际发送给模型的完整提示词 |
| model_version | 模型版本 |
| answer | 模型回答 |
| feedback | 用户或审核人员的纠正结果 |
| timestamp | 请求时间 |
有了这些信息,才能回答“模型当时为什么答错”。很多 AI 项目最后无法迭代,就是因为日志只记了问题文本和回答,没有记录完整 Prompt 和模型版本,导致错误无法复现。
5.2 纠错样本回流到 Prompt
我上面在build_correction_prompt中演示的是最简单的方式:把纠错样本直接拼进系统提示词。它的适用场景是:
- 纠错样本数量不大。
- 模型尚未到微调阶段。
- 希望快速看到纠错效果。
缺点是样本多了以后 Prompt 会变得很长,而且模型可能“记住”某个具体纠错,却无法泛化到类似问题。更合理的做法是:在每次请求前,通过向量检索从纠错样本库中召回与当前问题最相似的 2 到 3 条,再拼入 Prompt。这样既能控制长度,又能提升相关性。
5.3 离线评测集与回归测试
纠错不能“凭感觉”。建议维护一个离线评测集,里面包含三类数据:
- 基础正确用例:保证常规功能不退化。
- 历史纠错用例:验证之前指出过的错误是否被修复。
- 边界用例:覆盖敏感词、模糊问题、权限越权等。
每次调整 Prompt、升级模型或新增纠错样本后,都要跑一遍离线评测集。可以写一个简单的评测脚本,批量调用模型接口,再和标准答案比对。得分不达标就不允许发布。
5.4 从 Prompt 纠错升级到模型微调
当纠错样本积累到一定规模,Prompt 方式的收益会变低。这时候可以把高质量纠错样本整理成微调数据集,对开源基座模型做增量微调。微调完成后通过模型路由灰度发布,先让小比例流量使用新模型,观察准确率和响应延迟,再逐步放大。
模型微调不是只跑一次训练脚本,它同样需要版本管理。每个模型文件都要记录训练数据来源、基座版本、训练时间、评测结果。只有这样,发布新版本后出现问题时,才能快速回滚到旧的稳定版本。
6. 模型主权的落地路径
6.1 私有化推理服务
实现模型主权最简单的方式,就是把模型部署到企业内部服务器。前文代码中MODEL_BASE_URL指向127.0.0.1:8000,就是这个思路。在这个架构下:
- 用户的提问和回答不会离开内网。
- 模型文件由企业自己的镜像仓库管理。
- 推理服务的启动、升级、回滚都由企业控制。
6.2 模型路由与多模型管理
实际项目中,一个模型往往不够用。可以引入一个简单的模型配置中心,根据请求内容路由到不同模型:
{ "default_model": "qwen-7b", "router_rules": [ { "keywords": ["拒付", "索赔", "投诉"], "model": "qwen-72b" }, { "keywords": ["天气", "节日"], "model": "qwen-7b" } ] }这个路由表应该保存在配置中心或数据库中,而不是写死在代码里。运维人员修改路由规则后,不需要重新部署服务,配置刷新即可生效。
6.3 数据不出域与访问审计
即使模型部署在本地,也不能忽略权限和数据审计。需要注意以下几点:
- 每个
user_id必须来自登录态,而不是前端直接传入。 - 不同部门之间的记忆数据要隔离,防止越权读取。
- 所有外部导出、日志访问都要有审计记录。
- 记忆数据需要定期加密备份,防止单点故障。
模型主权不是单纯“把模型部署到本地”,而是一套覆盖数据、模型、权限、审计的治理体系。只有这四层都做到位,AI 才能真正成为企业信任的“同事”。
7. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
/chat返回连接失败 | 本地推理服务未启动或端口错误 | 检查推理服务日志;确认MODEL_BASE_URL可访问 |
| 模型一直答非所问 | 模型名称配置错误 | 确认MODEL_NAME与推理服务中的模型名一致 |
| 纠错记录不生效 | 纠错样本数量超过 Prompt 限制或相关性低 | 增加向量检索,只召回最相关的纠错样本 |
| 记忆数据越写越大 | 只追加不清理 | 增加数量上限,或采用长期事实提炼方案 |
| 不同用户互相看到数据 | 只有 user_id 隔离,没有严格权限 | 接入统一登录认证,user_id 由后端解析 |
| 模型回答不稳定 | temperature 过高或模型过小 | 调低 temperature,或者切换到更大参数模型 |
| 升级模型后老问题复发 | 没有离线评测集 | 建立回归测试,发布前必须跑一遍评测 |
| 服务重启后记忆丢失 | 数据只存在内存中 | 使用 JSON 文件、数据库或 Redis 持久化 |
从实际排查经验来看,最常见的问题不是模型能力不够,而是“上下文没拼对”或“记忆数据没有隔离”。很多开发者把大量精力花在调 Prompt 上,却忽略了系统设计层面的问题。
8. 最佳实践与工程建议
8.1 记忆分三层,不要只用一个 JSON
本文示例使用 JSON 文件是为了把逻辑讲清楚。生产环境建议:
- 短期会话记忆:使用 Redis,设置过期时间。
- 长期事实记忆:使用 PostgreSQL,按用户和主题建表。
- 纠错样本:单独建表,字段包含上下文、错误答案、正确答案、来源、状态。
三个数据层的生命周期、访问频率和写入频率都不同,强行放在一个存储引擎里,后续运维会很难受。
8.2 纠错样本要有人审核
不是所有用户反馈都适合直接进入 Prompt 或微调数据集。用户可能误判,也可能故意对抗。因此纠错样本应该分为“待审核”“已通过”“已拒绝”三种状态。只有审核通过的样本才能进入系统提示词或微调数据。这个环节可以由产品经理、业务专家或 AI 工程师共同负责。
8.3 每次请求必须可追溯
生产环境下,最好为每次请求生成request_id,并把下面的信息写入审计日志:
- 完整 Prompt。
- 模型名称和版本。
- 推理服务返回的完整结果。
- 用户 ID。
- 请求时间和响应时间。
没有可追溯性,纠错机制就是空中楼阁。因为你无法确认某个错误回复究竟是模型问题、Prompt 问题还是上下文问题。
8.4 小步灰度,先低风险业务再铺开
AI 同事刚上线时,不建议让它直接处理高风险的决策任务。可以先让它处理信息整理、流程引导、文书草拟等低风险场景,积累评测数据和纠错样本。当准确率和稳定性达标后,再逐步扩大权限范围。每一步都要做到可回滚。
8.5 关注成本与延迟
不要把每个请求都路由到超大模型。可以在入口处做意图分类:简单问题走小模型,复杂推理走大模型。这样既保证效果,也能控制推理成本和响应时间。模型路由规则要不断根据线上表现调整,而不是配置一次就再也不管。
9. 总结与下一步学习路线
本文从“持久化 AI 同事”这一概念出发,完整拆解了三个核心问题:
- 持久化:通过用户记忆模块,让 AI 跨会话记住历史上下文。
- AI 纠错:通过反馈接口和纠错样本回流,让 AI 的错误可以被记录、修正并复用。
- 模型主权:通过本地 OpenAI 兼容推理服务,让数据和模型都留在企业内部。
示例项目虽然简单,但已经打通了“提问 → 取记忆 → 拼纠错 → 调模型 → 存记忆 → 用户反馈 → 纠错回流”的完整链路。
接下来可以继续学习的方向包括:
- 用向量数据库替换 JSON 文件,实现语义级别的长期记忆。
- 引入 RAG,让 AI 同事能基于企业知识库回答问题。
- 将纠错样本整理成微调数据集,对开源模型做增量训练。
- 使用 Agent 框架让 AI 同事能够调用业务 API,执行具体操作。
- 完善权限体系和审计体系,让 AI 同事满足企业合规要求。
如果你的项目也卡在“AI 总说错、又记不住、数据又不敢外传”这个阶段,可以先照着本文的代码跑通一个最小闭环,再逐步增加向量检索、评测集和模型路由。等这一套机制稳定运行后,你会发现 AI 从“工具”变成“同事”,其实只差持久化、纠错和模型主权这三个关键工程步骤。