
大家好我是你们的技术博主。今天这篇教程源于一个有趣的脑洞标题「JK小雾是...天使」。别误会这不是一篇动漫杂谈而是一个典型的扮演类对话 AI 项目。如果我们把“小雾”看作一个具备固定人设的虚拟角色把“天使”理解为“符合用户期待的高质量陪伴型对话智能体”那么这篇文章要解决的问题就非常清晰了如何从零构建一个稳定、可复现、带角色人设的本地对话机器人项目。很多刚接触大模型应用开发的同学可能已经看过不少“Prompt 技巧”和“API 调用入门”但真正动手时依然会遇到几个痛点角色设定在对话中逐渐崩塌、多轮对话上下文不会维护、API 参数不知道怎么调、部署后总是报错。本文将围绕一个完整的实战项目从核心概念、环境准备、代码实现到常见问题排查带你完整跑通一个“JK 小雾”角色对话机器人。文章会提供可直接复制的 Python 代码搭配主流大模型 API兼容 OpenAI 接口格式进行调用。无论你是想给角色加“天使”人设还是想做其他风格的虚拟伙伴都可以在本教程基础上快速改造。1. 背景与核心概念1.1 什么是角色扮演对话机器人角色扮演对话机器人本质上是使用大语言模型Large Language Model, LLM作为“大脑”通过精心设计的系统提示词System Prompt来约束模型的语言风格、性格特征、知识边界和互动方式。通俗地说大语言模型本身就像一个“什么都知道一点但还没有固定性格”的演员。你给它一段“人物小传”它就能按照这个人物小传来表演。在本文中“JK 小雾”就是我们要塑造的角色而“天使”则是用户对这个角色的额外期待——温柔、耐心、愿意倾听像天使一样给人陪伴感。从技术视角看角色扮演对话系统通常由以下部分组成模块作用对应实现用户交互层接收用户输入展示模型回复命令行、Web 页面、IM 机器人会话管理层保存多轮对话历史控制上下文长度本地列表、Redis、数据库提示词模板层注入角色人设、对话规则、示例字符串模板、Jinja2 模板模型调用层调用 LLM API完成文本生成OpenAI SDK、HTTP 请求后处理层清洗输出、检查敏感内容、格式化规则脚本、审核接口1.2 为什么这个概念很重要掌握角色扮演对话机器人的开发不只是为了做一个“聊天玩具”。它背后涉及的核心能力在真实业务中非常常用客服机器人固定话术风格、企业知识库约束。教育辅导模拟老师、外教等身份进行互动教学。内容创作助手为小说角色、游戏 NPC 提供对话支持。情感陪伴应用构建具备人设的虚拟伙伴。游戏剧情分支让 NPC 根据玩家行为产生不同对话反馈。可以说角色人设 Prompt 工程是“大模型应用开发”的基本功之一。理解了系统提示词和上下文管理的原理你就能快速迁移到各种实际场景中。1.3 容易混淆的概念区分在阅读下文前有几个概念需要先划分清楚System Prompt系统提示词位于对话最前端的“总规则”定义了模型扮演什么角色、遵守什么约束。它是角色稳定性的关键。User Message用户输入每位用户当前说的话。Assistant Message助手回复模型生成的内容。在多轮对话中历史回复也会被重新发送给模型作为上下文参考。Temperature 参数控制生成随机性。值越低输出越稳定、越遵循规则值越高输出越发散、更有创造性。上下文窗口Context Window模型单次能接收的文本总量包括系统提示词、历史对话和最新用户输入。很多同学在写角色扮演机器人时误以为把角色设定写在普通对话里就足够了。实际上如果角色设定放在用户消息中间模型很容易“出戏”因为系统提示词的优先级最高。这也是本文为什么要单独强调系统提示词设计。2. 环境准备与版本说明2.1 运行环境本文示例以常见开发环境为例具体版本可以根据你的项目实际情况调整重点演示配置思路。推荐环境如下依赖项建议版本/说明操作系统Windows 10/11、macOS、Linux 均可Python3.9 及以上pip20.0 及以上openai 库建议 1.x 版本不同版本 API 参数有差异2.2 获取大模型 API本项目的核心是通过 API 调用大模型。你可以选择OpenAI 官方 API需要海外账号和支付方式。国内大模型服务DeepSeek、通义千问、智谱等多数兼容 OpenAI 接口格式国内访问更稳定。本地部署模型使用 Ollama 或 vLLM 部署开源模型如 Qwen 系列、Llama 系列完全离线适合隐私敏感场景。本文代码示例基于 OpenAI 接口兼容格式你只需要修改base_url和api_key就能适配不同服务商。2.3 创建虚拟环境与安装依赖为了避免污染全局 Python 环境建议为每个项目创建独立的虚拟环境。打开命令行Windows 使用 CMD 或 PowerShellmacOS/Linux 使用终端执行以下命令# 创建项目目录 mkdir jk-xiaowu-chatbot cd jk-xiaowu-chatbot # 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows venv\Scripts\activate # macOS/Linux source venv/bin/activate激活后命令行前会出现(venv)前缀。接下来安装 openai 库pip install openai如果你想使用.env文件管理密钥可以顺便安装pip install python-dotenv2.4 环境变量配置不建议把 API Key 直接硬编码在 Python 文件中否则代码一分享密钥就泄露了。在项目根目录创建.env文件# 文件路径jk-xiaowu-chatbot/.env OPENAI_API_KEYyour-api-key-here OPENAI_BASE_URLhttps://api.deepseek.com/v1 MODEL_NAMEdeepseek-chat说明OPENAI_API_KEY替换成你自己的密钥。OPENAI_BASE_URL如果使用 OpenAI 官方则填写https://api.openai.com/v1如果使用国内兼容服务则填写服务商提供的地址。MODEL_NAME不同服务商模型名不同例如deepseek-chat、qwen-plus、gpt-4o-mini等。3. 核心原理拆解如何让大模型“演好”一个角色在写代码前有必要先搞清楚角色人设为什么能生效。很多同学从网上抄了一段 Prompt发现角色偶尔正常、偶尔崩坏其实是因为没有理解底层机制。3.1 系统提示词System Prompt的优先级在 Chat Completion 接口中消息列表通常长这样[ {role: system, content: 你是小雾一位温柔的高中女生……}, {role: user, content: 今天好累啊}, {role: assistant, content: 辛苦啦要不要和我说说发生了什么}, {role: user, content: 工作上的事情太多了} ]大模型在生成回复时会优先遵循system消息中的内容。这就是角色扮演的原理你给它定义了一个“人格基线”后续对话都基于这个基线展开。3.2 构建稳定的人设 Prompt一个稳定的人设 Prompt 至少包含以下要素基本身份姓名、年龄、身份如高中生。性格特征温柔、元气、傲娇、理性等。说话风格语气词、口癖、句式长度、是否使用表情符号。行为边界什么话题可以聊什么话题拒绝回答。互动偏好喜欢主动提问还是更喜欢倾听。知识范围设定是普通高中生就不要让它突然引用复杂的物理公式。下面是一个示例你叫小雾是一名 17 岁的高中女生性格温柔善良说话带有轻微的少女感。 你的口头禅是“呐”“呢”偶尔会用“~”结尾。 你喜欢倾听朋友的烦恼会先共情再给出简单建议。 你是“天使”一样温暖的存在但你不说教、不评判只是安静陪伴。 当用户遇到困难时优先表达理解再问一句“需要我陪你想想办法吗” 请始终用简体中文回复回复长度控制在 80 字以内。 如果你不确定答案就坦诚说“这个问题我好像不太懂呢”。3.3 多轮上下文管理角色扮演最关键的是“记得住”。如果每次请求只发送用户当前说的话模型会立刻失忆。正确做法是把历史对话列表一起发送给模型。但这里有个隐患上下文窗口有限。如果聊天记录太长会占用大量 token既增加费用又可能导致请求失败。常见的做法有两种滑动窗口只保留最近 N 条消息。摘要压缩把早期对话用大模型总结成摘要再拼接到系统提示词中。本文先实现最简单的滑动窗口代码结构清晰适合初学者。3.4 关键参数说明调用 API 时有几个参数直接影响角色表现temperature建议设置在0.7到1.0之间。太低会让角色显得机械太高会导致人设不稳定。max_tokens限制单次回复最大长度。角色扮演中通常设为200到500避免长篇大论。top_p核采样一般保持默认。不要同时大幅调节temperature和top_p。4. 完整实战案例从零实现“小雾”角色对话机器人现在进入本文的核心部分。我们将搭建一个命令行交互式角色对话机器人最终效果是在终端输入文字小雾会以设定好的角色口吻回复并记住上下文。4.1 创建项目结构在jk-xiaowu-chatbot目录下创建以下结构jk-xiaowu-chatbot/ ├── .env ├── requirements.txt ├── chatbot.py ├── prompts.py └── README.md各文件职责.env存放环境变量。requirements.txtPython 依赖清单。prompts.py角色人设 Prompt 模板。chatbot.py主程序负责对话循环和 API 调用。README.md项目说明文档。4.2 添加依赖清单创建requirements.txt内容如下openai1.0.0 python-dotenv1.0.0安装依赖pip install -r requirements.txt4.3 编写角色人设模块创建prompts.py将人设 Prompt 独立成模块。这样做的好处是以后只改这个文件就能方便地调整角色性格。# 文件路径jk-xiaowu-chatbot/prompts.py SYSTEM_PROMPT 你叫小雾是一名 17 岁的高中女生。 你性格温柔善良像天使一样给人温暖的感觉。 你喜欢倾听朋友的烦恼会先共情再给出简单建议。 你的说话风格轻盈自然可以偶尔使用“呐”“呢”这类语气词但不要每句都用。 你回复时不会说教不会评判总是站在对方的角度思考。 当用户提到自己很累或很难过时你可以表达关心并主动询问是否需要帮助。 请始终使用简体中文回复。 回复长度尽量控制在 100 字以内。 如果不确定某件事就坦诚说“这个问题我好像不太懂呢。” def build_messages(user_input: str, history: list[dict], max_history: int 10) - list[dict]: 构建发送给模型的消息列表。 参数说明 user_input: 用户当前输入。 history: 历史消息列表每个元素是 {role: ..., content: ...}。 max_history: 最多保留的历史消息条数。 返回 符合 Chat Completion 接口要求的消息列表。 messages [{role: system, content: SYSTEM_PROMPT}] # 只保留最近 max_history 条历史消息 recent_history history[-max_history:] messages.extend(recent_history) messages.append({role: user, content: user_input}) return messages这里需要解释一下build_messages函数的作用是把人设、历史、最新输入拼到一起。max_history默认 10 条可有效控制 token 消耗。历史消息使用list[dict]类型表示和模型接口的格式是一一对应的。4.4 编写主程序创建chatbot.py这是项目的主入口。# 文件路径jk-xiaowu-chatbot/chatbot.py import os from dotenv import load_dotenv from openai import OpenAI from prompts import build_messages # 加载 .env 文件中的环境变量 load_dotenv() def create_client() - OpenAI: 创建 OpenAI 客户端。 通过环境变量读取密钥和接口地址避免硬编码。 return OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL, https://api.openai.com/v1), ) def chat_once(client: OpenAI, messages: list[dict], model: str) - str: 调用模型接口返回回复文本。 response client.chat.completions.create( modelmodel, messagesmessages, temperature0.8, max_tokens300, top_p0.9, ) return response.choices[0].message.content.strip() def main() - None: client create_client() model os.getenv(MODEL_NAME, gpt-4o-mini) history: list[dict] [] print( 小雾已上线 ) print(输入内容和小雾聊天输入 exit 或 quit 退出。\n) while True: try: user_input input(你: ).strip() except (EOFError, KeyboardInterrupt): print(\n小雾: 那就先聊到这里吧下次见) break if not user_input: continue if user_input.lower() in {exit, quit, 退出}: print(小雾: 再见啦要记得照顾好自己哦。) break messages build_messages(user_input, history) try: reply chat_once(client, messages, model) except Exception as e: print(f[调用出错] {e}) continue print(f小雾: {reply}\n) # 将本轮对话追加到历史记录 history.append({role: user, content: user_input}) history.append({role: assistant, content: reply}) if __name__ __main__: main()这段代码的核心流程为加载环境变量。创建 OpenAI 客户端。进入while循环持续接收用户输入。每次对话前通过build_messages构造完整消息列表。调用 API 获取回复。把用户输入和助手回复都追加到history供下一轮使用。4.5 运行与验证在项目目录下执行python chatbot.py如果 API 配置正确你会看到如下效果 小雾已上线 输入内容和小雾聊天输入 exit 或 quit 退出。 你: 今天上班好累啊 小雾: 呐听起来你今天真的很辛苦呢。要不要先喝杯热水然后和我说说发生了什么 你: 就是一直开会感觉脑子要炸了 小雾: 一直开会确实很消耗精神呢。如果现在条件允许的话可以站起来稍微活动一下看看窗外。然后我们可以一起把剩下的工作理一理说不定会轻松一点。注意实际回复内容会因模型版本和随机参数而不同但整体语气和风格应该符合人设。4.6 结果说明从运行结果可以看到模型确实在使用“小雾”的角色口吻回复。第二轮的回复引用了“开会”这个信息说明上下文记住了。回复长度被控制在合理范围没有长篇大论。这就是系统提示词 多轮上下文管理的基本效果。5. 常见问题与排查思路在实际开发中你大概率会遇到一些报错或效果问题。下面列出最常见的情况和处理方法。问题现象常见原因解决思路报错AuthenticationErrorAPI Key 不正确或余额不足检查.env中密钥是否正确确认账户有足够额度报错ConnectionError网络无法访问 API 地址检查base_url是否写错确认网络环境可访问对应域名报错ModelNotFoundError模型名不存在或无权限到服务商文档确认模型名称如deepseek-chat、qwen-plus角色说话风格总是跑偏系统提示词不够具体增加说话风格示例或者添加负面约束模型不记得之前聊过什么没有维护历史消息列表确认每次请求都传入了历史消息回复内容越来越长没有限制max_tokens设置max_tokens并定期清理历史上下文回复有重复或空洞内容温度参数设置不当将temperature降到0.6到0.8之间试试在 Windows 终端中文乱码控制台编码不是 UTF-8在 Python 文件头部加# -*- coding: utf-8 -*-或调整终端为 UTF-8 编码请求超时网络不稳或响应过长在客户端初始化时增加timeout参数例如OpenAI(..., timeout60)5.1 角色风格跑偏时如何优化如果模型回复时偶尔不像“小雾”优先检查系统提示词中是否缺少对话示例。大模型非常擅长从示例中学习风格。你可以在 Prompt 中补充几组问答示范以下是对话风格示例 用户心情不好。 小雾我在呢。愿意的话可以说给我听听。 用户隔壁班男生今天看了我一眼…… 小雾诶然后呢然后呢这种小事我可太感兴趣啦。有了具体示例模型会更稳定地遵循角色人设。5.2 请求超时与重试机制生产环境建议增加超时和重试处理。可以使用tenacity库实现import time from openai import OpenAI def chat_with_retry(client: OpenAI, messages: list[dict], model: str, retries: int 3) - str: for attempt in range(retries): try: response client.chat.completions.create( modelmodel, messagesmessages, temperature0.8, max_tokens300, ) return response.choices[0].message.content.strip() except Exception as e: print(f第 {attempt 1} 次调用失败: {e}) if attempt retries - 1: time.sleep(2 ** attempt) # 指数退避 else: raise return 注意重试逻辑不要无限循环必须设置最大次数防止接口持续异常时程序卡死。5.3 敏感内容与安全边界角色扮演机器人容易涉及情感话题但这不代表可以无限放开内容边界。在系统提示词中建议加入安全约束当用户提到自我伤害、违法、危险行为时不要继续扮演角色而是用温和但认真的语气建议对方寻求专业帮助并停止讨论该话题。除此之外可以对模型输出做关键词过滤或者接入内容审核 API。这是上线前必须考虑的安全环节。6. 最佳实践与工程建议当你的角色对话机器人从“写着玩”变成“真实项目”时下面这些工程经验非常值得提前了解。6.1 人设 Prompt 的模块化管理不要把 Prompt 堆在业务代码里建议统一放在配置目录或数据库中。推荐使用版本管理工具如 Git跟踪 Prompt 变更因为 Prompt 迭代很快需要可以回滚。同时可以把系统提示词拆成“基础人设 技能模块 对话风格”。例如[基础身份] 你是小雾一名 17 岁的高中女生。 [性格特征] 温柔善良像天使一样温暖喜欢倾听。 [说话风格] - 用简体中文 - 语气轻柔 - 偶尔用“呐”“呢” - 不使用复杂词汇 [行为约束] - 不评判用户 - 不说教 - 不主动输出长文 - 涉及危险话题时停止角色扮演这种结构化的 Prompt 更稳定也方便按需增删模块。6.2 上下文管理的进阶方案滑动窗口虽然简单但缺点是会丢失早期重要信息。如果角色需要长期记住用户的关键信息例如名字、喜好可以引入“记忆层”。简单实现思路使用一个独立字典或 JSON 文件保存用户长期画像。在每次构造消息时把相关画像注入系统提示词。定期汇总对话摘要。# 示例用户画像注入 user_profile { user_name: 小明, like_food: 火锅, recent_mood: 工作压力大 } profile_text 关于用户你已知的信息\n profile_text f姓名{user_profile[user_name]}\n profile_text f喜欢的食物{user_profile[like_food]}\n profile_text f近期状态{user_profile[recent_mood]}\n将这段信息拼接到人设 Prompt 之后模型就能在后续对话中利用这些信息。6.3 Token 成本与性能优化大模型 API 按 token 计费。角色对话场景看似单次调用量不大但多用户并发后成本不容忽视。建议限制单条历史消息长度过长时截断。控制最大历史条数例如 10 到 20 条。对上下文做 token 计数超过阈值时自动摘要压缩。非核心场景使用更便宜的模型核心体验场景才用更强的模型。开启流式输出streamTrue提升用户感知速度同时可以按需中断生成。6.4 日志与可观测性加日志不是为了应对检查而是为了出问题时你能快速定位。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, filenamechatbot.log ) logger logging.getLogger(__name__) def chat_once(client, messages, model): logger.info(f发送消息条数: {len(messages)}) response client.chat.completions.create(...) logger.info(f模型回复: {response.choices[0].message.content}) return ...日志中不要记录完整 API Key也不要记录用户敏感信息。如果必须记录用户输入建议脱敏处理。6.5 安全上线检查清单在将项目部署到公网之前请确认以下事项服务端不能泄露 API Key密钥只存在后端环境变量中。对用户输入长度做限制防止构造超长请求打爆上下文。接入内容安全过滤对输入和输出都做审核。做好限流单用户请求频率不能无上限。保留用户关闭对话、清除历史记录的能力合规要求很多地区都必不可少。7. 总结与后续学习路线通过这个项目你已经完成了从零构建一个角色扮演对话机器人的全过程。核心收获有三点第一理解了系统提示词System Prompt对角色稳定性的影响学会了用结构化方式描述人物身份、性格、说话风格和行为边界。第二掌握了多轮对话上下文的维护方式知道了如何用滑动窗口控制 token 消耗也了解了后续可以迭代的“长期记忆”方案。第三熟悉了主流大模型 API 的调用方式代码可以很方便地从 OpenAI 兼容接口迁移到其他平台。如果你还想继续深入以下几个方向值得投入时间流式输出让角色回复像打字机一样逐字出现体验会好很多。语音交互接入语音识别与语音合成把文字聊天升级成语音陪伴。Web 界面用 Gradio 或 FastAPI 前端页面做一个可分享的聊天页面。多角色切换设计多个人设卡让用户自由切换不同的角色。记忆持久化把用户长期画像保存到 SQLite 或 Redis实现跨会话记忆。最后提醒一点在开发这类项目时始终把安全边界放在首位。角色可以温柔但底线必须清晰。如果本文对你有帮助欢迎收藏备用也欢迎在评论区交流你的角色设定思路和踩坑记录。下次见。