构建具有“性格”的 AI Agent Harness Engineering:角色扮演技术实战
1. 为什么你的 AI Agent 聊到第 10 轮就“人设崩塌”了做 AI Agent 角色扮演最让人头疼的不是模型不够聪明而是它太容易“忘本”。你精心写了一段 System Prompt告诉它“你是一个毒舌但心软的十年老友”前几轮对话确实有那味儿怼得恰到好处。可聊到第 8 轮、第 10 轮它突然开始一本正经地给你列起了“情绪管理三步法”语气温柔得像换了个人。这就是典型的 OOCOut of Character人设崩塌。这个问题的本质是上下文窗口的注意力稀释。大模型的注意力机制对越靠前的内容权重衰减越明显当对话轮次增加System Prompt 在整体 token 中的占比被不断压缩模型对“我是谁”的记忆就越来越模糊。单纯靠“把 Prompt 写长一点”解决不了因为写太长反而会挤占对话空间还会引入冲突信息。Harness Engineering性格锚定工程要解决的就是这件事它不是写一段角色设定就完事而是把角色定义、记忆分层、生成校验、反馈迭代串成一条可复现的工程链路。适合谁适合正在做客服 Agent、游戏 NPC、教育陪练、个人助理这类需要稳定人格的开发者。读完你能拿到一套可复制的角色配置模板、一段能跑通的校验代码以及一份真实报错排查清单。我试过用最朴素的方式——只写 System Prompt——去跑一个“毒舌老友”角色结果 12 轮之后它开始叫我“亲爱的用户”那一刻我就知道必须上工程手段了。2. TaoToken 前置准备把模型调用链路先跑通在写角色逻辑之前得先有一个稳定的模型调用入口。角色扮演对模型的指令遵循能力要求比较高尤其是性格校验环节需要频繁调用模型做二次判断所以调用链路的稳定性和成本控制很关键。这里我用 TaoToken 作为统一入口它兼容 OpenAI 的接口格式改个 Base URL 就能接上省去多平台切换的麻烦。你需要准备三样东西Base URL、API Key、Model ID。这三件套在后面的 Claude Code、Cline、Codex 配置里都会反复出现先记牢。第一步打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号。注册流程很常规邮箱验证后进控制台。第二步进控制台创建 API Key。地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 在 API Keys 页面点“新建密钥”复制出来保存好这个 Key 只显示一次。如果你只是想先验证模型效果可以直接去模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 试几句确认模型能正常响应再往下走。第三步确认 Model ID。角色扮演场景我一般用指令遵循强的模型具体可用列表在接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里能查到。API 端点统一是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 base_url 使用。如果你打算长期跑编码类或 Agent 类任务可以看下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 按套餐走比按量计费更划算。API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 随时可以轮换密钥。这里有个坑要提前说很多人把 base_url 写成https://taotoken.net/api/v1结果报 404。正确写法是 base_url 用https://taotoken.net/apiSDK 会自动拼/v1/chat/completions。这个细节后面排障章节还会展开。3. 可复制的角色配置模板从 JSON 到 settings 片段角色配置是整个 Harness 的地基。我的经验是结构化永远优于大段描述。大模型对键值对、分节标题的识别度远高于一段散文式的“你是一个开朗的人”。下面这份 JSON 模板可以直接拿去用字段设计覆盖了身份、性格维度、语言风格、禁忌和示例。{ role_id: sassy_friend_001, name: 小贱, identity: 用户认识10年的老友大学室友现在做自由职业, personality: { openness: 0.8, conscientiousness: 0.6, extraversion: 0.9, agreeableness: 0.2, neuroticism: 0.3 }, language_style: { sentence_length: 不超过30字, tone_words: [哈哈, 笑死, 你可拉倒吧, 行吧], forbidden_style: [书面语, 官方话术, 客服腔] }, forbidden_rules: [ 不能说脏话, 不能人身攻击, 不能涉及敏感内容, 不能突然变得温柔客气 ], few_shot: [ {user: 我今天升职了, assistant: 哟你也能升职你们老板是不是瞎了啊哈哈}, {user: 我最近失恋了好难过。, assistant: 旧的不去新的不来走啊晚上撸串去我请。} ] }这份 JSON 里的personality用的是大五人格五维打分范围 0 到 1。为什么要量化因为后面做一致性校验时需要把模型生成的回复也映射成同样的五维向量然后算余弦相似度。没有量化标准校验就无从谈起。如果你用的是 Claude Code 做角色 Agent 的开发可以把这份配置写进项目的settings.json。路径一般在项目根目录的.claude/settings.json内容如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: 你的_TaoToken_Key, ANTHROPIC_MODEL: 你的_Model_ID }, role_profile_path: ./configs/sassy_friend_001.json }注意这里的三件套Base URL 填https://taotoken.net/apiKey 填你刚创建的Model ID 填文档里查到的。三个缺一不可少一个就会在启动时报认证失败或模型不存在。如果你用的是 Cline 或 Roo Code 这类插件配置方式类似在 MCP 或 Provider 设置里选 OpenAI CompatibleBase URL 同样填https://taotoken.net/api然后填 Key 和 Model ID。Cline 的 MCP 配置里如果涉及角色记忆服务记得把记忆库的连接串单独放不要和模型 Key 混在一起。Codex 用户走的是auth.json路线文件通常在~/.codex/auth.json结构如下{ base_url: https://taotoken.net/api, api_key: 你的_TaoToken_Key, model: 你的_Model_ID }三件套写全Codex 启动时就不会再弹 OAuth 登录直接走 Key 认证。这一步很多人卡住是因为只填了 Key 没填 base_url结果默认走了官方端点自然连不上。4. 验证请求跑通一次带性格校验的对话配置写完得验证它真的能跑。我习惯先用一个最小请求确认链路通再上完整的校验逻辑。最小请求用 curl 就行curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer 你的_TaoToken_Key \ -d { model: 你的_Model_ID, messages: [ {role: system, content: 你是小贱说话毒舌但心善句子不超过30字。}, {role: user, content: 我今天考试考了满分} ], temperature: 0.8 }如果返回的choices[0].message.content是类似“哟你也能考满分是不是抄的啊哈哈”这种带怼味的回复说明链路和角色注入都生效了。如果返回的是“恭喜你取得好成绩继续加油”那说明 System Prompt 没被正确识别检查一下 messages 里 system 角色是不是放对了位置。链路通了之后上完整的一致性校验。核心思路是模型生成回复后再用一次模型调用把回复映射成五维人格向量和角色标准向量算余弦相似度低于阈值就重试。下面是可运行的 Python 片段import os import json import numpy as np from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.getenv(TAOTOKEN_API_KEY) ) ROLE_VEC np.array([0.8, 0.6, 0.9, 0.2, 0.3]) THRESHOLD 0.7 MAX_RETRY 3 def extract_personality(text): prompt f分析下面这句话的大五人格得分每维0到1返回JSONkey为o,c,e,a,n 回复{text} res client.chat.completions.create( model你的_Model_ID, messages[{role: user, content: prompt}], temperature0 ) data json.loads(res.choices[0].message.content) return np.array([data[o], data[c], data[e], data[a], data[n]]) def consistency_score(text): vec extract_personality(text) return float(np.dot(vec, ROLE_VEC) / (np.linalg.norm(vec) * np.linalg.norm(ROLE_VEC))) def chat_with_role(user_input, system_prompt): messages [ {role: system, content: system_prompt}, {role: user, content: user_input} ] for i in range(MAX_RETRY): res client.chat.completions.create( model你的_Model_ID, messagesmessages, temperature0.8 ) reply res.choices[0].message.content score consistency_score(reply) print(f第{i1}次生成一致性得分{score:.3f}) if score THRESHOLD: return reply return 哈哈你说啥我没听清再说一遍 if __name__ __main__: sp 你是小贱用户认识10年的老友说话毒舌但心善句子不超过30字不能说脏话。 print(chat_with_role(我今天考试考了满分, sp))跑起来你会看到类似这样的输出第1次生成一致性得分0.823 哟你也能考满分是不是抄的啊哈哈如果第一次得分就过阈值直接返回如果低于 0.7会重新生成最多三次。三次都不过就返回兜底话术避免把 OOC 内容吐给用户。这个兜底很重要宁可答非所问也不要破坏人设。实测下来加了校验层之后长对话的 OOC 率能从 20% 左右压到 5% 以内。代价是每次回复多一次模型调用成本翻倍所以阈值要根据场景调。娱乐场景可以放宽到 0.6客服场景建议 0.8 以上。5. 常见报错排查401、local proxy failed、reading choices、OAuth角色 Agent 跑不起来八成是下面这几类报错。我按真实遇到的频率排个序对照着查。401 Unauthorized。最常见原因就三个Key 没填、Key 填错、Key 前面多了空格。检查Authorization头是不是Bearer 你的Key中间一个空格别多别少。如果你用的是环境变量确认os.getenv真的读到了值打印一下长度看看。还有一种情况是 Key 被轮换了但代码里还是旧的去 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 确认当前有效的 Key。local proxy failed / connection refused。这个报错通常出现在你本地配了代理但代理没启动或者 base_url 写成了localhost。先确认 base_url 是https://taotoken.net/api不是本地地址。如果你之前配过其他工具的代理设置检查环境变量HTTP_PROXY、HTTPS_PROXY是不是指向了一个已经关掉的端口。清掉这两个变量再试。reading choices 报错 / KeyError: choices。这个说明返回的 JSON 里没有choices字段通常是请求根本没成功返回的是错误信息。打印完整的res看看常见原因是 Model ID 写错了服务端返回了model not found。去接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 核对可用的 Model ID注意大小写和连字符。OAuth 相关报错。Codex 或 Claude Code 如果没配auth.json或settings.json会尝试走 OAuth 登录流程报OAuth token expired或login required。解决办法就是把三件套写全Base URL、Key、Model ID。Codex 写进~/.codex/auth.jsonClaude Code 写进.claude/settings.json的env字段。写全之后重启工具就不会再弹登录。一致性校验一直不过疯狂重试。这不是报错但很烦。原因通常是阈值设太高或者角色向量和实际生成风格不匹配。先把阈值降到 0.6 试试如果还不过检查ROLE_VEC是不是和 System Prompt 描述的性格一致。比如你 Prompt 写的是“温柔耐心”但向量填的是高外倾低宜人那模型生成的温柔回复自然过不了校验。两者必须对齐。长对话后期突然 OOC 但校验没拦住。这是校验模型的盲区因为单句回复可能看起来符合性格但和上下文连起来就崩了。解决办法是校验时把最近 3 轮对话一起喂给校验模型让它判断“这句回复放在当前上下文里是否 OOC”。成本会再高一点但长对话稳定性明显提升。6. 把角色 Agent 接进你的工作流角色配置和校验逻辑跑通之后下一步是把它接进真实工作流。如果你做的是客服 Agent把校验阈值设到 0.8兜底话术换成“稍等我帮你转接人工”如果是游戏 NPC阈值可以放到 0.6允许一点性格波动反而更真实。记忆分层这块核心人设永远放上下文最前面短期记忆保留最近 10 轮更早的交互丢进向量库按需检索。每 5 轮往上下文头部插一次核心人设提醒能有效对抗注意力稀释。想快速验证不同角色的效果可以直接在模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里粘贴你的 System Prompt 试聊几轮不用写代码就能感受性格稳定性。确认方向对了再落到代码里。长期跑 Agent 任务的话Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 的套餐制比按量计费省心不用担心校验层翻倍调用把额度烧穿。接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里有完整的参数说明和模型列表配之前扫一眼能少踩很多坑。最后说个真实体会不要追求 100% 一致性。真人也有情绪波动偶尔一句不那么“毒舌”的回复反而让角色更立体。工程手段的目标是把 OOC 控制在可接受范围而不是消灭它。把阈值、重试次数、兜底话术这三个旋钮调好你的 Agent 就有了稳定的“性格底盘”。

相关新闻

Eigen-GNN:即插即用的图结构校准插件

Eigen-GNN:即插即用的图结构校准插件

1. 这篇论文到底在解决什么问题?——不是又一个GNN变体,而是给所有GNN装上“结构校准器”你有没有遇到过这种情况:训练一个图神经网络(GNN),节点特征明明很清晰,分类结果却总在边界样本上反复摇…

2026/10/5 22:13:38 阅读更多 →
Chaquopy+Compose+ZeroMQ:Android机器学习平台实战

Chaquopy+Compose+ZeroMQ:Android机器学习平台实战

Android 机器学习模型平台开发实战:Chaquopy Compose ZeroMQ 全栈解决方案做了几年 Android 端机器学习应用,有一个问题始终绕不开:Python 生态里现成的模型、预训练权重和数据处理库,和 Android 原生世界的 Java/Kotlin 代码之…

2026/10/7 1:18:59 阅读更多 →
开源Shell增强工具OpenShell:会话管理、命令补全与日志解析实战

开源Shell增强工具OpenShell:会话管理、命令补全与日志解析实战

每天一睁眼就是连服务器、翻日志、敲命令,这话听起来像段子,但干过运维或者重度终端用户的人都懂。我前阵子深度参与了一个叫 OpenShell 的开源 Shell 增强项目,折腾了快两个月,把日常命令行工作流彻底重写了一遍。OpenShell 不是…

2026/10/7 0:51:55 阅读更多 →

最新新闻

AI时代数学科研指南:给博士生与导师的实操建议

AI时代数学科研指南:给博士生与导师的实操建议

上周末参加了一场关于AI与数学科研的学术会议。说是"会议",其实规模不大,一个圆桌会议室,二十来人,但来的人构成很有意思:有做代数方向的老教授,有带过七八个博士生的中年导师,还有几…

2026/10/7 4:55:44 阅读更多 →
Vue3防重复提交实战:从按钮锁到请求拦截的完整方案

Vue3防重复提交实战:从按钮锁到请求拦截的完整方案

“点了提交没反应,又点了一下,结果生成了两条订单”——这种线上事故,凡是做过Vue3后台管理系统的人,大概率都碰到过。用户端的“没反应”往往不是真的没反应,而是接口慢、网络抖动、后端处理中,前端按钮没…

2026/10/7 4:55:44 阅读更多 →
Agent与LLM落地实践:从记忆库安全到可靠性工程与本地部署

Agent与LLM落地实践:从记忆库安全到可靠性工程与本地部署

整理这份日报的时候,后台收到的搜索词里,Agent和LLM几乎各占半壁江山。有人问框架选型,有人在排查报错,有人在研究记忆库投毒的论文,也有人折腾安卓8老手机跑GGUF。我把这些问题按热度排了个序,挑出今天最值…

2026/10/7 4:55:44 阅读更多 →
AI驱动浏览器自动化:Cursor+Playwright实战自动发布文章

AI驱动浏览器自动化:Cursor+Playwright实战自动发布文章

最近用 Cursor 配合 Playwright 做了一件挺有意思的事:让 AI 自己操作浏览器,登录头条号后台,填标题、写正文、点发布,一篇头条文章就这么自动发出去了。这个组合比我预期中要顺——Cursor 负责把自然语言变成可执行的 Playwright…

2026/10/7 4:55:44 阅读更多 →
AnyPS5 unused-filter=0/1/2 区别详解:PS5 移植工具的三级未用符号过滤终极对比

AnyPS5 unused-filter=0/1/2 区别详解:PS5 移植工具的三级未用符号过滤终极对比

AnyPS5 unused-filter0/1/2 区别详解:PS5 移植工具的三级未用符号过滤终极对比 【免费下载链接】AnyPS5 Tool for automatic PS5 executables porting to Linux and Windows 项目地址: https://gitcode.com/GitHub_Trending/an/AnyPS5 AnyPS5 是一款能把 PS5…

2026/10/7 4:55:44 阅读更多 →
C#高并发Socket实战:SAEA模型、粘包拆包与性能避坑指南

C#高并发Socket实战:SAEA模型、粘包拆包与性能避坑指南

简介:这份C#高并发SOCKET服务器与客户端完整工程实例源码,面向希望深入理解网络通信底层机制的.NET开发者,尤其适合需要掌握多线程与异步编程模型的进阶学习者。资源包共431个文件,约4.1MB,以cs源代码、csproj项目文件…

2026/10/7 4:54:43 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 6:26:51 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 4:21:51 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →