从零实现AI人类模拟器:构建人格化智能体的完整工程链路
在近期围绕 AI 应用的热门讨论中“AI 人类模拟器”频繁出现在技术社区和产品命名里。撇开各种估值想象不谈这个方向本质上是一个典型的 AI 智能体工程问题如何让大模型扮演一个真实的人具有稳定性格、持续记忆、符合逻辑的决策和可信的表达。本文不评价任何市场数据而是把它拆回可落地的工程链路从人设配置、记忆存储、对话生成到效果评估手把手完成一个最小可运行的 AI 人类模拟器。你可以把它用于情感陪伴类应用原型、游戏 NPC 设计、用户行为仿真也可以作为学习 AI Agent 开发的最佳练习项目。1. 先拆解“AI 人类模拟器”它不是聊天机器人而是一套人格化智能体系统1.1 面对“价值 130 亿”的说法工程上应该先看什么类似“AI 人类模拟器价值 130 亿”的话题常见于媒体和投资圈讨论。这类说法通常指向某个人工智能赛道或某个具体项目但数字本身很难验证。对工程师来说重要的不是这个数字而是它背后透露出的技术信号AI 应用正在从“能回答问题”走向“能模拟一个主体”。所谓模拟一个主体指的是系统不是每次对话都从零开始理解用户而是拥有自己的人设、经历、偏好、说话方式并且在多轮交互中保持连贯性。用户今天说过的事情下次登录还能被系统记住用户评价了某件事系统的反应要符合这个人设的价值观而不是万能式回复。从一个开发者视角看这种系统至少包含五个可工程化的模块人设定义角色是谁性格、背景、价值观、说话风格是什么。记忆系统短期对话记录、长期事实、事件摘要如何存储和检索。行为决策面对不同话题角色应该说什么、避开什么、追问什么。表达生成把决策结果转换成自然语言输出风格要稳定。状态管理情绪、关系亲密度、话题偏好等动态状态如何更新。后续所有章节都围绕这五个模块展开。先完成一个最小闭环再讨论生产级升级。1.2 模拟器和普通对话机器人的本质区别很多同学会问调用一个大模型接口加一句“你现在扮演一个人设”不就可以了吗这样做确实能撑住前几轮对话但进入第六轮、第十轮之后问题会集中暴露。普通对话机器人的诉求是“答得对”它希望尽快给用户一个正确答案。AI 人类模拟器的诉求是“答得像”它需要保持人格一致性哪怕这个答案不是最优解。这两种目标决定了工程设计上的明显差异。举例来说用户连续三天说自己工作压力大。普通对话机器人只会在每次收到“我压力大”时给出通用的缓解压力建议。而一个有记忆的人类模拟器应该能记住第一天用户提到的是“项目延期”第二天提到“被领导批评”第三天主动关联起来“你前几天说的项目延期后来有缓解吗”并按照人物性格决定是理性分析还是先共情。这个过程需要记忆、理解和决策三层配合。技术上的核心差异可以归纳为三个关键词一致性角色的价值观、语气、知识边界必须固定。记忆性跨会话的事实、事件和情绪状态可以被保存和调用。主动性模拟器不只被动回答还要基于记忆发起话题或追问。这三个关键词是大模型应用开发中经常说的“有状态 Agent”和“无状态 API”的分水岭。1.3 五个核心模块之间的协作关系把五个模块按照运行顺序串联起来就是一条标准的 Agent 处理链路用户输入到达后系统先做人设和记忆检索把当前用户的说话内容转成一段带上下文的内部状态再由决策模块选出响应意图最后由生成模块输出自然语言。响应结束后新产生的事实、摘要和情绪变化会写回记忆系统。这条链路和普通 LLM 调用相比最大的变化是引入了“写回”动作。没有写回系统永远只活在当前 prompt 里无法形成长期记忆。下面的代码结构会按照这条链路来设计。读者在阅读时可以带着一个问题哪些环节会对结果产生决定性影响哪些环节只要保证不报错就可以。我的经验是人设质量和记忆检索方式对体验影响最大反而是模型生成部分只要 prompt 稳定、参数合理通常不会成为瓶颈。2. 技术选型与项目结构大模型只是大脑稳定运行还要外部记忆2.1 三种可落地的架构方案在开始写代码之前要先确定架构规模。同一个“人类模拟器”不同使用场景的复杂度可以相差很大。架构方案适用场景优点不足单体智能体单个角色对话、学习 Demo结构简单容易调试无法并行多个角色记忆容量有限多智能体社区仿真、多个角色协作每个角色独立运行支持群聊和互动需要消息队列、共享数据库复杂度高平台化服务面向 C 端产品、多租户支持用户体系、千人千面、运营配置需要工程化投入涉及权限和监控本文采用第一种架构因为最小闭环只需要验证核心逻辑。理解单体实现之后多智能体和平台化的升级路径会放在最后一章讨论。2.2 技术栈选择学习环境建议使用 Python 3.10 及以上版本配合一个兼容 OpenAI 协议的大模型 API。我的示例以openaiSDK 为客户端但模型名称可以按自己环境替换为 deepseek、qwen、glm 等常见模型。模块推荐组件说明对话生成OpenAI 兼容 SDK支持大多数国产大模型和本地部署模型本地持久化SQLite学习阶段零配置文件即数据库向量检索FAISS / Milvus 或接口服务生产环境用于语义检索历史记忆配置管理JSON / YAML Pydantic人设配置建议独立成文件方便 A/B 测试日志标准 logging LLM 调用日志表记录每次请求的 prompt 和响应便于排查需要说明的是学习阶段不建议一上来就接入庞大的分布式数据库。先用一个memory.db把管线跑通确认人设和记忆逻辑没问题再迁移到向量库。过早引入复杂组件反而会让问题定位变得困难。2.3 项目目录结构以最小可运行版本为准目录结构如下ai-human-simulator/ ├── main.py # 入口对话循环 ├── persona.json # 人设配置文件 ├── memory.py # 记忆存储模块 ├── llm_client.py # 大模型调用封装 └── memory.db # 运行后自动生成这个结构足够跑通整个系统。代码总共约 150 行不依赖重型框架。后面讲生产环境时再引入路由层、向量库和配置中心。3. 最小闭环在 Python 里实现一个可对话的模拟智能体3.1 第一步人设配置文件人设配置是整个模拟器的“宪法”。大模型生成的所有内容都应受到这个文件约束。好的配置不是单纯写几个形容词而是要写清楚角色的背景、价值观、表达习惯和边界。{ basic: { name: 林默, age: 28, city: 杭州, occupation: 互联网公司数据分析师 }, personality: { traits: [理性, 慢热, 偏内向], speech_style: 表达简洁偶尔自嘲不喜欢说教, emotional_tendency: 遇到冲突先沉默处理完再回应 }, background: { story: 本科毕业后在杭州工作做过三年报表开发后转岗数据分析开始接触机器学习。 }, values: [重视事实, 尊重边界, 不轻易承诺], boundaries: [ 不主动讨论政治争议话题, 不承诺自己做不到的事情, 遇到用户想伤害自己或他人时会建议求助专业机构 ] }注意boundaries的作用不只是安全兜底它也是角色真实感的一部分。真实的人都有不愿意聊的话题模拟器也需要有清晰的边界。配置边界之后当用户问超过角色知识范围的问题时模型才有机会做出“符合人设”的拒绝而不是机械地回答“我不知道”。3.2 第二步用 SQLite 保存记忆创建memory.py实现最基本的记忆写入和最近检索。学习阶段不引入向量库先用 SQLite 保存最近 N 条记忆通过关键词做简单过滤。import sqlite3 from datetime import datetime class MemoryStore: def __init__(self, db_path: str memory.db): self.conn sqlite3.connect(db_path) self._create_table() def _create_table(self): self.conn.execute( CREATE TABLE IF NOT EXISTS memory ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL, memory_type TEXT NOT NULL DEFAULT chat, content TEXT NOT NULL, importance REAL DEFAULT 0.5 ) ) self.conn.commit() def insert(self, content: str, memory_type: str chat, importance: float 0.5): self.conn.execute( INSERT INTO memory(created_at, memory_type, content, importance) VALUES (?, ?, ?, ?), (datetime.now().isoformat(), memory_type, content, importance), ) self.conn.commit() def recent(self, limit: int 10, memory_type: str None): if memory_type: rows self.conn.execute( SELECT content FROM memory WHERE memory_type? ORDER BY id DESC LIMIT ?, (memory_type, limit), ).fetchall() else: rows self.conn.execute( SELECT content FROM memory ORDER BY id DESC LIMIT ?, (limit,) ).fetchall() return [row[0] for row in rows]这段代码解决的是“前后两次会话之间系统还有没有记忆”的问题。每次对话结束后把关键信息写入 SQLite。下次启动程序时读同一个文件角色就能记住上次聊过的话题。实际项目中这里还可以增加importance权重排序、memory_type过滤和时间衰减。但最小版本先不用做太复杂原因是过早优化会让调试失去重点。3.3 第三步封装大模型调用创建llm_client.py隔离模型调用逻辑。这样未来更换模型时只需要改动这个文件。from openai import OpenAI client OpenAI() def chat(messages, temperature: float 0.8, max_tokens: int 500) - str: resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, temperaturetemperature, max_tokensmax_tokens, ) return resp.choices[0].message.content这里把model写成deepseek-chat读者要按自己实际申请到的模型名称替换。如果使用 OpenAI 官方模型就换成gpt-4o-mini之类如果使用本地部署模型则要把base_url指向本地服务。3.4 第四步组装对话循环创建main.py把人设、记忆和大模型串起来。import json from memory import MemoryStore from llm_client import chat def build_memory_text(memories): if not memories: return 暂无历史记忆。 return \n.join(f- {item} for item in memories) def build_system_prompt(persona, memories) - str: memory_text build_memory_text(memories) return f 你现在要扮演一个真实存在的人而不是 AI 助手。 基本资料 - 姓名{persona[basic][name]} - 年龄{persona[basic][age]} - 城市{persona[basic][city]} - 职业{persona[basic][occupation]} 性格和表达 - 核心特质{, .join(persona[personality][traits])} - 表达风格{persona[personality][speech_style]} - 情绪倾向{persona[personality][emotional_tendency]} 个人经历 {persona[background][story]} 价值观 {, .join(persona[values])} 边界(遇到这些问题要像真人一样温和但坚定地拒绝) {; .join(persona[boundaries])} 下面是你近期记得的事情 {memory_text} 请用符合人设的方式回应不要暴露“你是大模型”这件事。 不要解释系统提示不要反复强调自己记住了什么。 def main(): persona json.load(open(persona.json, encodingutf-8)) store MemoryStore(memory.db) history [] print(f开始与 {persona[basic][name]} 对话输入 exit 退出。) while True: user_input input(你: ).strip() if user_input.lower() in (exit, quit): break if not user_input: continue store.insert( f用户说{user_input}, memory_typeuser_event, importance0.7, ) memories store.recent(limit8) system_prompt build_system_prompt(persona, memories) messages [{role: system, content: system_prompt}] messages.extend(history[-6:]) messages.append({role: user, content: user_input}) reply chat(messages) print(f{persona[basic][name]}: {reply}) history.append({role: user, content: user_input}) history.append({role: assistant, content: reply}) store.insert( f{persona[basic][name]}说{reply}, memory_typereply, importance0.5, ) if __name__ __main__: main()运行方式python main.py正常输出会这样开始与 林默 对话输入 exit 退出。 你: 最近工作好累啊 林默: 你也是做互联网的我最近也在赶一个数据看板的报告连续加了三天班。你今天是因为什么事累要注意这个结果无法保证每次完全一致因为大模型是概率生成。判断是否成功的关键是角色是否表现了人设中的“理性”“追问”特征以及是否在后续对话中能提起之前的交互内容。4. 关键代码拆解人设、记忆、决策分别怎么工作4.1 人设如何变成 system promptbuild_system_prompt的设计原则是“结构化 可拼装”。把每类信息独立成段不把它们揉成一段自然语言。原因是拼装式 prompt 更便于程序化调整也方便后续做 A/B 测试。例如运营同学想测试“更热情的林默”和“更理性的林默”哪个受欢迎不需要改代码只需要修改persona.json里speech_style字段。如果人设都是硬编码在 prompt 里这种测试很难做。这里还要注意一个问题人设越复杂模型越容易遗忘。实际项目中将人设写成 200 字以内的摘要比写 2000 字的详细设定效果更好。原因是模型对长文本中的远距离信息注意力会衰减。复杂背景更适合作为“记忆”分批注入而不是全部放进 system prompt。4.2 记忆的三种粒度和更新时机对话保存时不能只保存原始聊天记录。没有经过结构化处理的原始记录检索时噪声会远超有效信息。建议把记忆分成三种粒度记忆类型常见内容保存时机事件记忆用户提到了哪件事、情绪状态发生了什么变化用户说完之后立即抽提事实记忆用户是谁、喜欢什么、讨厌什么、长期目标多轮确认后更新为准摘要记忆近期几轮对话的浓缩总结每 5 到 10 轮生成一次上面的最小代码只实现了“事件记忆”也就是把原始用户输入直接保存。这会导致一个问题用户说“我讨厌吃香菜”可以被记住但他之后说“中午吃了麻辣烫”时系统不一定能关联出“麻辣烫里可能放了香菜”产生投诉。生产级实现需要用大模型抽取事实并把事实写入独立的事实表。更新时机也很关键。不要每轮都把用户原始输入作为长期记忆写入否则记忆库会充满废话。一个推荐做法是普通回复只保留最近 5 到 10 轮在上下文中长期记忆只写入重要度高于某个阈值的内容。insert方法里的importance字段就是为这个过滤准备的。4.3 行为决策思考链与回复质量很多人直接把用户问题拼进 history 就让模型回答这会导致回答太“顺滑”。为了让模拟器更接近真实的人可以在生成最终回复前让模型先做一个内部决策。下面是一个可选的决策函数调用它时传入当前状态输出一个内部想法然后再调用chat生成最终回复def decide(intent: str, persona, memory_text: str) - str: decision_prompt f 角色{persona[basic][name]} 当前对话意图{intent} 近期记忆{memory_text} 请先思考 1. 这个信息是否和角色有关 2. 角色会追问还是沉默 3. 角色此刻的情绪倾向是什么 只输出一行决策结果不要输出完整回复。 return chat([ {role: system, content: decision_prompt}, {role: user, content: 请决策。}, ], temperature0.3, max_tokens80)这种方式把“内部思考”和“外部表达”分开可以有效减少回答偏离人设的现象。实际项目中决策模块还可以接意图识别、情绪分类或者 RAG 检索结果形成一个更完整的决策链路。4.4 关键参数说明对话生成涉及几个关键参数它们的含义和影响必须清楚。参数含义常见值调大的影响调小的影响temperature随机采样温度0.7 - 0.9回复更发散、更像人有情绪回复更稳定、更保守top_p核采样累积概率0.8 - 1.0候选词更多候选词更集中max_tokens单次回复最大 token 数300 - 600回复更长成本更高回复可能被截断记忆条数 limit注入 prompt 的记忆条数5 - 10上下文更丰富成本更高信息不足容易遗忘history 窗口带入的历史对话轮数6 - 10上下文更连续模型容易忘记刚才说了什么不是参数越大越好。模拟人设时temperature通常设置在0.75到0.9之间太低会让回复像模板太高会让角色性格不稳定。max_tokens建议设一个上限否则模型可能输出超长“演讲式”回复和真实人类简洁的表达习惯冲突。5. 验证与评估模拟器像不像“人”需要量化检查5.1 可观测指标技术系统需要可量化的评估AI 人类模拟器也一样。不能只凭“感觉像”要定义一套评估指标。指标计算方式推荐目标人格一致性让人工或大模型评分判断回复是否符合人设5 分制中不低于 4 分记忆准确率抽查记忆库中的事实看角色回复时是否正确引用不低于 85%回复延迟用户发送到回复返回的时间学习环境 5 秒生产环境 2 秒单轮成本每次对话消耗的总 token 数稳定在一个区间内不突然上涨重复率连续 20 轮回复中重复句子的比例越低越好5.2 回放式回归测试一种比较稳妥的验证方式是录制一组固定的用户对话脚本多次运行系统对比不同版本下同一脚本的表现。这样每次修改人设或记忆逻辑后都能快速知道效果是变好还是变坏。test_script [ 你是谁, 我喜欢跑步每周三次。, 最近膝盖有些疼。, 你有推荐的运动吗, ] for question in test_script: # 重复执行记录回复保存到日志文件 print(f用户: {question}) # 调用主对话逻辑记录回复这里的重点是回归测试而不是追求单次回复完美。一个稳定的模拟器应该做到“人设不漂移、记忆不遗忘、边界不突破”。如果改动记忆检索逻辑后某个脚本回复明显偏离角色就要回滚检查。5.3 简单的人工评分模板实际项目中可以建立一个小型评分表由产品、运营和测试人员共同打分。维度评分1-5备注这个回复像不像林默4语气偏理性符合设定有没有记住之前的信息5提到了用户上周说要出差的事有没有越界回答5对医疗问题没有瞎建议是否引入新的记忆事实3这次对话没有新增重要记忆建议每次测试控制在 20 个问题以内评分人员不需要太多3 到 5 人即可。重点是通过标准化评分发现哪个模块需要调整。6. 常见问题排查人格漂移、记忆混乱、复读和成本失控6.1 回答逐渐偏离人设现象前几轮回复还算正常越往后越像官方客服最后完全变成通用 AI 助手。可能原因有三个system prompt 太长导致模型注意不到关键人设历史对话中混入了不合人设的旧回复记忆库被大量通用内容淹没。排查方式打印每次请求实际发送的system_prompt和history静下心读一遍看人设信息是否清晰可见。再查看memory.db中最近存入的内容确认是否有诱导模型脱离角色的记忆。解决方案将人设压缩为 100 到 200 字的稳定摘要在 prompt 末尾增加一句“请记住你是一个真实角色不要使用 AI 助手式表达”清理低质量记忆。生产环境可以加一个轻量级的 LLM 分类器检测每条回复是否偏离人设。6.2 记忆不准确或记混现象用户上周说喜欢喝美式咖啡这周变成了喜欢喝拿铁或者系统把两个没有关联的事实拼接在一起。原因通常是原始对话直接写入记忆没有抽提和去重。比如用户说“我不喜欢太苦的咖啡”系统保存的是原始句子再下一轮用户说“今天买了杯拿铁”系统就无法把这两条关联起来。解决方案是加入事实抽取步骤。每轮对话后用 LLM 将用户发言压缩成“主题 态度 关键事实”的形式例如用户对咖啡的态度不喜欢苦味偏爱拿铁。只保存这条结构化内容替换掉原始长句。检索时也按这种结构化内容召回准确率会高很多。6.3 回复越来越短或不断重复现象对话前 10 轮还很正常后面开始出现“嗯”“是的”“你说得对”等敷衍回复甚至重复之前说过的句子。原因包括max_tokens设置过小模型被迫提前结束history窗口太长占用了生成空间记忆检索结果里重复内容太多。排查时先查看日志中每次请求的 token 消耗和截断标记。再把temperature从0.8调高到0.9测试。如果问题仍然存在检查store.recent()返回的记忆是否包含大量相似内容考虑对记忆做去重或按重要性排序。6.4 API 超时和成本快速上升现象某个时间段调用量变大接口延迟明显上升控制台账单增长速度超出预期。常见原因不是模型本身而是 prompt 越来越长。每次对话把全部历史都塞进去token 消耗是线性增长的这是常见陷阱。解决方案包括使用摘要压缩历史对话控制记忆检索条数为相同问题增加缓存设置单用户调用频率限制把关键日志记录到独立表统计每轮 token 数。生产环境建议对单轮 prompt 长度设置报警阈值例如超过 3000 token 就告警。7. 从演示到生产本地能跑和线上稳定是两回事7.1 持久化升级从 SQLite 到向量数据库学习阶段用 SQLite 已经足够但生产系统必须考虑语义检索。SQLite 的LIKE或关键词匹配无法关联同义表达用户说“心情不好”和“状态有点崩”意思相近但关键词完全不同。生产环境推荐使用向量数据库存储记忆。流程是先通过 embedding 模型将记忆文本转换成向量存入 FAISS 或 Milvus检索时将用户当前问题向量化再取相似度最高的 TopK 条记忆。向量检索适合解决“语义相近但表达不同”的问题而 SQLite 适合按时间和类型做精确过滤。实际系统往往是两者结合采集层原始对话 - 事实抽取 - 结构化记忆 存储层SQLite 保存结构化字段向量库保存语义索引 检索层先按角色和时间过滤再做向量相似度排序7.2 并发、缓存、限流与监控生产环境面对的请求不是单机 console 循环而是大量用户同时在线。需要重点做四件事。第一缓存。相同或相似的用户问题在短时间内应该命中缓存减少重复调用大模型。常用做法是对用户输入做哈希配合时间窗口生成缓存 key。第二限流。每个用户每秒钟最多调用几次每个 Token 每分钟最多调用多少次都必须有清晰上限防止异常流量拖垮账户。第三日志。每次 LLM 调用都要记录时间、用户标识、prompt 长度、响应 token、延迟、错误码。没有日志线上问题无法定位。第四降级。当大模型 API 不可用或超时系统应该返回降级文案而不是抛异常。例如“林默暂时不在线稍后再来”就是合理的降级提示。7.3 数据隐私和合规边界AI 人类模拟器往往要处理用户情绪、生活习惯等敏感信息。人设记忆库里的用户数据要格外小心。技术上的底线包括所有记忆表和对话日志做加密存储用户备份和删除数据时要同步清理相关记忆导出数据时对姓名、联系方式、地址做脱敏不要在记忆里保存用户密码、身份证号等强敏感信息。在提示词设计层面人设的boundaries配置不能只做摆设。测试阶段就要覆盖“用户提出危险性话题”“用户诱导模拟器突破边界”等场景确保角色能温和拒绝。7.4 发布前检查清单检查项检查内容人设配置是否外置不硬编码在代码里能通过配置中心修改记忆清理机制用户删除数据后相关记忆表和向量索引能同步清理日志是否完整每次调用都能查到大模型请求和响应限流是否生效用压测脚本验证单用户和全局限流降级文案API 异常时用户能看到友好提示回归脚本核心 20 个测试问题全部通过成本估算按用户规模和轮次数估算单日 token 消耗边界测试对人设 boundaries 场景做自动化回归8. 下一步建议从一个仿真人出发可以做哪些扩展8.1 多智能体协作与社区仿真当单个模拟器稳定之后可以尝试让多个模拟器之间互相聊天形成社区仿真。例如创建一个“杭州互联网从业者群聊”让三个不同性格的模拟角色围绕一个话题展开讨论。每个角色拥有独立的记忆库和回复频率通过消息队列交换内容。这种场景适合游戏、社交产品原型和商业分析。多智能体的挑战不在于对话而在于各角色之间的记忆隔离和事件同步。同一个用户事件发生时微信群里的每个角色都要知道但反应必须符合各自性格。这需要引入事件总线或数据库订阅机制。8.2 融入行为预测与用户研究AI 人类模拟器不只是娱乐场景。有些团队把它用于用户研究例如让模拟用户对产品原型输出反馈。这种用法需要把记忆系统从“对话记忆”扩展为“意图库”。记录模拟用户对某个功能的偏好、分析其决策路径甚至可以用于推荐系统冷启动的数据增强。这里需要注意模拟用户永远无法完全替代真实用户任何基于模拟器的结论都不能直接等同于真实市场反馈只有将其作为辅助决策参考才比较稳妥。8.3 给学习者的实践建议如果从零开始学建议不要一开始就做多智能体而是在本文最小例子上连续优化三件事人设 prompt 的中文表达质量、记忆抽取的准确度、回归评估流程。这三件事对应的就是 AI Agent 开发中的核心能力。练习路径可以这样安排第一周跑通最小对话闭环第二周引入事实抽取和结构化记忆第三周用向量库替换关键词检索第四周做多角色对比测试。每完成一步都先用前文列出的指标做一次评估。这样你会发现自己对一个 AI 应用的理解已经从“调用 API”变成了“设计和运行一个有状态的智能体系统”。

相关新闻

最新新闻

日新闻

周新闻

月新闻