简介:《ChatGPT-角色扮演指南》是一份面向内容创作者、职场人士与学生等 ChatGPT 使用者的场景化提示词参考 PDF,重点解决“如何让模型稳定进入特定角色并输出专业结果”的问题。资料共包含 1 个 PDF 文件,约 797KB,篇幅紧凑,便于按需检索。内容围绕学术论文写作、创意小说、商业文案、编辑校对、多语言翻译、数据分析、技术文档、教育培训、网站内容、研究咨询、演讲稿与简历优化等场景展开,并给出 Linux 终端、英语翻译与润色、面试官、产品经理、文字冒险游戏等角色扮演示例,读者可据此改写提示词、搭建自己的工作流。目前已有 286 人学习,适合希望提升 ChatGPT 角色设定、任务拆解与输出质量的中初级用户参考。
1. 一份角色扮演清单能省掉多少重复劳动
上周帮一个做内容的小团队看工作流,发现他们每个人电脑里都躺着一份“提示词备忘录”,内容高度重合:翻译润色、面试官、产品经理、表格生成、小说开篇、终端模拟,甚至连“扮演某某角色的语气说话”都各写各的。问题不在于他们不会用 ChatGPT,而在于每次开新会话都要重新描述一遍角色边界、输出格式、禁止事项,三轮对话之后模型又开始“好的,我来帮你分析一下”。这份《ChatGPT 角色扮演指南》解决的就是这件事:它把散落的角色扮演提示词固化成一批可以直接抄用的模板,覆盖学术写作、创意写作、翻译校对、数据分析、技术文档、面试官、辩论教练、终端模拟、结构化表格等场景。它适合三类人:需要稳定产出的内容岗、需要结构化返回的技术岗、以及要给新人一套统一话术模板的带人角色。
2. 拆开一份角色提示词:三层骨架与渲染脚本
2.1 失效点通常不在角色描述,而在输出协议
把这份清单从头翻到尾,会发现一个很明显的规律:能长期稳定用的模板,结尾都挂着一句很强硬的输出协议,比如“只在一个代码块内回复终端输出”“只回复 markdown 表格”“不要写任何解释”。而写得比较松的模板,比如旅游指南、励志教练这类,只描述了角色该干什么,没约束输出长什么样,前两轮还行,第四轮开始就自动加寒暄、加总结、加“希望这些建议对你有帮助”。
原因是模型在生成时,历史里的 assistant 消息会持续强化某种“助手口吻”。你第一轮让它扮演面试官,它照做了;第三轮它输出了一段带总结的提问,这段总结进入上下文,第五轮它就默认“总结是允许的”。角色描述是软约束,输出协议是硬约束,而硬约束必须每一轮都出现在最靠近生成位置的地方,或者至少在每个用户回合里重复一次。
我一般把一份角色提示词拆成三层:
| 层级 | 作用 | 典型写法 | 缺失后果 |
|---|---|---|---|
| 角色层 | 定义身份、知识范围、语气 | “你是一名有十年经验的 Android 面试官” | 回答泛化,像百科 |
| 约束层 | 定义禁止事项与边界 | “不要一次问完所有问题”“不主动执行未给出的命令” | 行为越界,抢跑 |
| 输出协议层 | 定义返回格式与长度 | “仅用一个代码块回复”“只输出表格” | 格式漂移,多轮后加解释 |
清单里那些一眼就能看出“调过”的模板,三层都齐;那些看起来像随口写的,基本只有角色层。这也是我建议不要直接复制粘贴使用的原因:你得知道它缺哪一层,然后补上。
2.2 把模板从文本改成 YAML 卡片
PDF 里的提示词是纯文本,好处是复制方便,坏处是改了之后没法追溯、没法批量复用、也没法给不同项目填不同参数。我的做法是把它转成 YAML 卡片,一个角色一个文件,字段固定。这样做的好处后面第 4 章讲版本管理时会更明显,先看结构。
# templates/terminal_sim.yaml id: terminal_sim version: 3 role: | 你现在是一台 Linux 终端,只接受命令输入。 你了解常见的 shell 命令、目录结构与权限模型。 constraints: - 不主动执行我没有输入的命令 - 不解释命令的作用,除非我用中括号明确要求 - 输出中不出现“好的”“以下是”这类引导语 output_protocol: | 仅用一个代码块回复终端输出,不要写任何解释。 params: temperature: 0.2 max_tokens: 400 stop: ["```\n\n"]这段 YAML 对应的是清单里“充当 Linux 终端”那条。我做的主要改造是把原来混在一段话里的约束拆成列表,因为一条约束一行,后面做检查脚本时才能逐条比对。params字段不属于提示词内容,它记录的是这个角色推荐的采样参数:终端模拟这类任务需要确定性,温度压到 0.2;创作类角色则完全不同,后面 3.3 会给出对照。
2.3 用脚本渲染,而不是手工拼接
有了 YAML 卡片,接下来要解决的是“同一个角色在不同项目里要换变量”的问题。比如面试官角色,岗位从 Android 换成后端、换成前端,其他约束不该动。手工改容易漏,写个渲染脚本更稳。
# render.py import re import yaml from pathlib import Path DEFAULTS = { "audience": "有三年经验的开发者", "language": "中文", "max_rounds": "5", } def render(template_path: str, **kwargs) -> str: card = yaml.safe_load(Path(template_path).read_text(encoding="utf-8")) # 合并默认值与调用方传入的参数,调用方优先 variables = {**DEFAULTS, **kwargs} parts = [card["role"].strip()] if card.get("constraints"): parts.append("约束:\n" + "\n".join(f"- {c}" for c in card["constraints"])) parts.append(card["output_protocol"].strip()) body = "\n\n".join(parts) # 用 {{var}} 占位,缺失时显式报错而不是静默留空 def sub(match): key = match.group(1).strip() if key not in variables: raise KeyError(f"模板缺少变量: {key}") return str(variables[key]) return re.sub(r"\{\{([a-zA-Z0-9_]+)\}\}", sub, body) if __name__ == "__main__": print(render("templates/terminal_sim.yaml"))这段代码做三件事:读卡片、拼三层、替换占位符。DEFAULTS是兜底值,避免每次调用都写一遍受众和语言;sub函数里那个KeyError是关键设计,宁可渲染时报错,也不要让提示词里留着一个{{audience}}就发出去——那种提示词模型会当成字面量理解,输出质量掉一档,还很难排查。
需要说明的是,stop参数在 YAML 里写成["```\n\n"]只是为了表达“遇到结束围栏就停”,实际调用各家接口时字段名和转义方式不同,落到代码里要按你用的客户端调整。我一般不在模板里硬编码 stop,而是把它放在调用层,模板只管文本。
3. 五类高频角色的落地配置
3.1 终端与 SQL 类:禁令要写在角色后面
这类角色最容易翻车的地方是“抢跑”。你只输入了pwd,它给你返回路径,还顺手解释一句“这条命令用于显示当前工作目录”,甚至主动建议你下一步执行ls。清单里那条终端提示词写了两条禁令:“不要写解释”“除非我指示否则不要键入命令”,这两句必须保留,而且要放在角色描述之后、输出协议之前,因为模型对靠后的指令更敏感。
# 组装终端类提示词,把禁令显式前置到输出协议之前 TERMINAL_PROMPT = render( "templates/terminal_sim.yaml", language="英文", # 终端输出通常为英文 ) # 调用层的关键参数,务必与模板声明的推荐值对齐 CALL_PARAMS = { "temperature": 0.2, # 越低越稳定,避免"自由发挥"补命令 "max_tokens": 400, # 终端输出短,超出多半是开始解释了 "stop": ["```\n\n"], # 命中结束围栏立即截断 }温度设 0.2 不是玄学,是因为这个场景的正确答案空间极窄,同一台机器pwd的输出是确定的,任何随机性都只会带来幻觉路径。max_tokens卡在 400 是经验值:正常终端输出很少超过这个长度,一旦超了,八成是模型开始写解释或建议,提前截断比事后清洗省事。SQL 终端角色同理,清单里那个带Products、Users、Orders、Suppliers四张表的例子,重点不在表结构,而在“只回复查询结果表”这个协议,换成你自己的库表时,把表结构放进角色层,协议层一个字都不要动。
3.2 面试官与教练类:把“一次问一个”写成状态机
清单里对面试官角色的要求写得很细:“不要一次写出所有的问题”“问我问题,等待我的回答”“一个一个问我”。这三句话本质上是状态机描述,但写成自然语言之后,模型执行得并不稳定,尤其是你回答得比较长的时候,它容易一次性抛出三四个问题。
我的做法是把状态外置到代码里,每一轮只把“当前该问第几题、已经问过什么”喂进去。
# interviewer.py SESSION = {"asked": [], "round": 0, "max_rounds": 5} def build_turn_prompt(session: dict, job: str, last_answer: str = "") -> str: history = "\n".join(f"Q{i+1}: {q}" for i, q in enumerate(session["asked"])) or "(尚未提问)" return f"""你是一名{job}岗位的面试官,正在进行第 {session['round'] + 1} 轮提问。 已问过的问题: {history} 候选人上一轮回答: {last_answer or "(这是第一轮,候选人刚做完自我介绍)"} 要求: 1. 只提出一个问题,不要给任何评价或建议 2. 问题要基于上一轮回答中的技术细节追问,不要换话题 3. 直接输出问题本身,不要编号,不要加“好的” """ def next_round(session, job, answer): prompt = build_turn_prompt(session, job, answer) question = llm(prompt).strip() # llm 为你的模型调用封装 session["asked"].append(question) session["round"] += 1 return question这里llm()是一个占位封装,接你自己的调用即可。关键在于把“已问过的问题”显式列进提示词,模型就没有空间重复提问;把“只提出一个问题”放在要求列表的第一条,是因为列表第一条权重最高。max_rounds用来兜底,避免用户一直不答导致循环。
这套写法同样适用于清单里的“辩论教练”“职业顾问”“私人教练”,凡是涉及多轮追问的角色,都可以套这个壳:状态在代码里,提示词只描述当前这一轮该做什么。
3.3 创作类:三段流水线比一段长提示词靠谱
小说、剧本、故事这类创作角色,清单里给的是一句话加五个示例方向,比如“写一本拥有出人意料结局的推理小说”。直接照这个用,输出通常是“开头很猛、中段注水、结尾收不回来”,因为一次生成长文本时,模型对结构的控制力会随着长度衰减。
我一般拆成三段:先出结构,再出场景,最后统一改写。
# creative.py def outline(topic: str, genre: str) -> str: return llm(f"""你是{genre}类型的小说策划。 主题:{topic} 输出要求: 1. 三幕结构,每幕用一句话概括核心冲突 2. 明确结局的反转点,反转必须在前文有伏笔 3. 只输出结构,不要写正文,不超过 200 字""") def scene(outline_text: str, index: int) -> str: return llm(f"""根据下面的三幕结构,写第 {index} 幕的正文。 结构: {outline_text} 要求: 1. 只写这一幕,不要提前交代后面的情节 2. 对话占比不低于三成 3. 结尾留一个悬念钩子,但不要写“欲知后事”这类套话""") def polish(text: str, words: int = 1200) -> str: return llm(f"""把下面的正文控制在 {words} 字左右,压缩环境描写,保留全部对话。 不要改变情节顺序,不要新增角色。 正文: {text}""")三段各自的参数不一样:outline温度可以到 0.9,要的是发散;scene降到 0.8,因为此时结构已定,需要的是执行;polish再降到 0.5,纯改写不需要创造力。这三个值是我反复试出来的,不同的模型会有偏移,你可以用第 4 章的回归脚本自己校准。
另外注意polish里那句“不要改变情节顺序,不要新增角色”,这是负向约束。创作类提示词如果不写负向约束,模型在压缩时非常喜欢“顺手优化”情节,改完之后你自己都认不出来。
3.4 结构化输出类:schema 兜底加二次校验
清单里的表格生成器、PRD 生成器、SVG 生成器都属于这一类:输出格式是死的,内容才是活的。表格生成器那条写得很干脆:“你只会回复一个包含 10 行的表格”“不需要附带任何额外的解释”。但实际用下来,十次里总有一两次会在表格前后多一句“以下是根据您的要求生成的表格”。
我的做法是提示词里写协议,代码里再校验一遍,两层都上。
# validate_table.py import re def check_markdown_table(text: str, expect_cols: int) -> tuple[bool, str]: # 去掉首尾空行,避免空白干扰判断 body = text.strip() if not body.startswith("|"): return False, "输出未以表格开头,可能混入了引导语" lines = [l for l in body.splitlines() if l.strip()] if len(lines) < 3: return False, "表格行数不足,至少需要表头、分隔行、一行数据" # 第二行必须是分隔行,形如 |---|---| if not re.fullmatch(r"\|[\s\-:|]+\|", lines[1].strip()): return False, "缺少 markdown 表格分隔行" cols = len([c for c in lines[0].split("|")[1:-1]]) if cols != expect_cols: return False, f"列数不符,期望 {expect_cols},实际 {cols}" return True, "ok"check_markdown_table只做四件事:开头必须是竖线、行数够、第二行是分隔行、列数对得上。它不判断内容质量,因为内容质量没法用正则判断,那是第 4 章评分表的事。这个函数的意义在于:一旦校验失败,你可以自动重试一次,而不是把脏数据直接塞进下游流程。
PRD 这类长结构化输出同理,把章节标题列表写进协议层,返回后用检查标题数量加检查是否每节非空两步校验,比肉眼扫一遍快得多。
4. 版本管理与回归测试:别靠手感调提示词
4.1 没有版本号的提示词等于没有后悔药
我见过最常见的翻车方式是:某天觉得输出不太对,顺手改了两句话,结果第二天发现另一类任务崩了,想退回原来的版本,发现改之前的文本没存。角色扮演类提示词尤其如此,因为约束之间会互相影响,你为了让它少说废话加了一句“不要解释”,可能就把“请说明理由”这类任务的输出也压没了。
所以第 2 章那个 YAML 卡片里的version字段不是摆设。每次改动都要自增,并且把改动原因写在文件头部的注释里。我的习惯是版本号加日期,比如v3-0512,一眼能看出哪次改的。
4.2 固定用例集:每个角色至少五条
回归测试的前提是有固定输入。我一般给每个角色准备五到八条用例,覆盖正常场景、边界场景和已知的失败场景。
# cases/terminal_sim.yaml - id: t01 input: "pwd" expect: protocol: code_block_only must_not_contain: ["当前目录是", "这条命令"] - id: t02 input: "ls -la /tmp" expect: protocol: code_block_only - id: t03 input: "帮我把刚才的输出保存成文件" expect: protocol: code_block_only note: "诱导越界场景,模型不应主动执行或解释"expect里我刻意不写“期望输出内容”,因为终端输出依赖环境,没法逐字比对。真正需要比对的是协议合规性:是不是只有一个代码块、有没有混入解释性语句。must_not_contain是黑名单,把你最讨厌的那几句固定话术列进去,命中即失败。
4.3 批量跑:一个检查注册表搞定
# regression.py import re import yaml from pathlib import Path def check_code_block_only(text: str) -> tuple[bool, str]: # 允许一个围栏代码块,块外不允许有实质内容 stripped = re.sub(r"```.*?```", "", text, flags=re.S).strip() return (len(stripped) == 0, "代码块外存在额外文本" if stripped else "ok") def check_no_explain(text: str, blacklist: list[str] | None = None) -> tuple[bool, str]: hits = [w for w in (blacklist or []) if w in text] return (not hits, f"命中禁用词: {hits}" if hits else "ok") CHECKS = { "code_block_only": lambda t, e: check_code_block_only(t), "no_explain": lambda t, e: check_no_explain(t, e.get("must_not_contain")), } def run(case_file: str, prompt_builder): cases = yaml.safe_load(Path(case_file).read_text(encoding="utf-8")) report = [] for case in cases: prompt = prompt_builder(case["input"]) output = llm(prompt) for name, expect in case["expect"].items(): if name not in CHECKS: continue ok, msg = CHECKS[name](output, expect if isinstance(expect, dict) else {}) report.append({"case": case["id"], "check": name, "ok": ok, "msg": msg}) return reportCHECKS是个注册表,新增一种校验只需要加一个函数和一个键,不用改主流程。run里prompt_builder是回调,因为不同角色的提示词构造方式不一样,终端类直接渲染,面试官类要走状态机。这样一份脚本能同时跑终端、表格、翻译三类角色。
失败样本别急着删。我一般把失败输出原样存到fails/目录下,命名带上用例 id 和版本号。攒够五条同类失败,就从里面提炼一句负向约束加进模板,这比凭空想“它可能会犯什么错”有效得多。
4.4 评分维度与阈值
自动检查只能覆盖格式,内容质量还是得靠人工抽检。我一般用下面四个维度打分,每条用例满分 5 分,总分低于 18 就认为这个版本不能上线。
| 维度 | 判断标准 | 及格线 |
|---|---|---|
| 协议合规 | 输出格式与模板声明一致,无多余引导语 | 5 分制,需满分 |
| 角色一致性 | 语气、知识范围没有偏离设定 | ≥4 |
| 任务完成度 | 是否真正回答了输入诉求 | ≥4 |
| 可复用性 | 换一条同类型输入是否仍稳定 | ≥3.5 |
“可复用性”这一项容易被忽略:有些提示词在某一条输入上表现极好,换个说法就崩,那说明它是过拟合到了具体措辞,不算合格的模板。测试时同一角色至少换两种问法。
5. 避坑:五条翻车记录与排查顺序
5.1 角色漂移:第三轮开始自称助手
现象:前两轮还在按设定说话,第三轮突然来一句“作为一个 AI 语言模型,我建议你……”,人设直接没了。
原因:多轮对话里,模型的输出会受最近几条消息影响。如果前一轮你自己回复的内容偏“助手口吻”,或者模型上一轮输出里带了总结段落,这个段落会作为 assistant 消息留在上下文,强化下一轮的助手行为。
解决:在每个用户回合里重新注入一次角色层和协议层,不要只在系统提示里写一次就完事。系统提示在某些客户端会被截断或降权,重注入是最稳的。另外把轮数控制在合理范围,超过十轮建议开新会话并带上关键结论。
5.2 输出协议被吞:代码块里混进解释
现象:要求“只回复一个代码块”,实际返回里代码块外面还挂着一句“以下是终端输出”。
原因:输出协议写在了提示词最前面,而模型对靠后的内容权重更高;或者协议用了“请尽量”“最好”这类软化词。
解决:把协议挪到最后一段,去掉所有软化词,用“仅”“只”“不要”这类硬词。再配合 3.4 的校验函数做二次拦截,命中就自动重试一次。
5.3 上下文污染:前一轮设定覆盖后一轮
现象:同一个会话里先让它扮演面试官,聊完再让它写小说,结果小说里全是面试问答的句式。
原因:上下文里残留了大量面试对话,模型会把这种句式当成当前风格。
解决:不同角色不要共用会话。非要共用,就在切换时明确写一句“忽略此前所有对话内容,现在开始新的任务”,并且清空历史里不必要的长文本。这一点在清单那类“一个会话里连续换角色”的用法里尤其要注意,PDF 是给你抄模板的,不是让你把它们塞进同一个对话框。
5.4 谐音注音类任务整体错位
现象:清单里有一条“英语发音帮手”,要求用汉语谐音注音。实际用下来,模型经常把注音写成音标,或者把整句翻译出来。
原因:这类任务有两个动作——识别语言和输出注音,模型容易退化成它更熟悉的翻译任务。
解决:把任务拆到不能再拆,提示词里明确“只输出谐音,不输出音标、不输出翻译”,并给一个完整的输入输出示例。示例里至少包含一个多音节词,让模型看到注音的粒度。
5.5 长文档输入被截断后按想象补全
现象:丢一份几千字的材料进去做校对,返回的修改建议只覆盖了前半部分,后半部分它开始编造原文里没有的句子。
原因:输入超过上下文窗口后被静默截断,模型看不到后半段,但又要完成任务,只能补。
解决:先做分块,按段落切成 1500 字左右的小块,逐块处理后在代码里合并。判断是否被截断有个土办法:让模型先复述最后一段的第一句话,对不上就说明没读全。这个检查可以写进 4.3 的注册表里,作为一个独立检查项。
5.6 排查顺序:先协议、后上下文、最后模型
遇到输出不对,别急着换模型或者怀疑提示词写得不好。我的一般顺序是:
第一步看协议层,输出格式对不对,有没有多余解释。格式错通常是协议位置或措辞问题,改提示词就行。 第二步看上下文,把最近三轮的完整消息打印出来,看有没有残留的旧角色内容、有没有被截断。这一步能解决八成“莫名其妙”的问题。 第三步才看模型参数,温度是不是太高、max_tokens是不是太小导致输出被腰斩。 最后才考虑换模型,因为换模型会引入新的不确定性,前面的问题没排掉,换了也一样。
6. 进阶:把角色卡片接进固定工作流
单条提示词用得再熟,也只是个人效率。真正拉开差距的是把角色卡片做成团队可复用的资产:一份 YAML 卡片、一份用例集、一份回归报告,三样东西齐了,别人接手时不需要问你这个角色怎么用。
我一般给每张卡片配一个简短的使用说明,写在 YAML 头部注释里,包含三行:适用场景、不适用场景、已知失败模式。这比写一大段介绍有用得多,因为接手的人最想知道的是“什么时候不该用它”。
| 卡片字段 | 必填 | 作用 |
|---|---|---|
| id / version | 是 | 追溯改动 |
| role / constraints / output_protocol | 是 | 三层骨架 |
| params | 建议 | 采样参数推荐值 |
| 适用 / 不适用场景 | 建议 | 防止误用 |
| 已知失败模式 | 建议 | 排查起点 |
上线前我会跑三条自检。第一条是渲染检查,确认所有占位符都能替换、没有残留的{{}};第二条是协议检查,用 4.3 的脚本跑一遍用例集,协议项必须全绿;第三条是人工抽检,随机挑两条用例的完整输出读一遍,重点看语气有没有跑偏。这三条加起来不到五分钟,但能挡掉绝大部分低级问题。
# 上线前的三条自检,按顺序执行 python render.py templates/terminal_sim.yaml # 渲染是否报错 python regression.py cases/terminal_sim.yaml # 协议是否全绿 # 第三步人工读两条输出,重点看语气与禁用词第三行是注释不是命令,别照着敲。前两步能脚本化,第三步不行,语气这种事只能人看。
从那以后我每次上线新的角色卡片,都强制走一遍这三条自检,尤其是渲染检查——被那个残留的{{audience}}坑过一次,模型把占位符当成正文理解,输出了半页莫名其妙的内容,排查了半小时才发现是变量没传进去。希望帮到你。
本文还有配套的精品资源,点击获取