系统提示词泄露语料库:结构化整理、模块切分与工程范式
1. 从 system_prompts_leaks 说起这个项目到底在做什么第一次看到 system_prompts_leaks 这个名字的人大概率会愣一下。system_prompts 好理解系统提示词leaks 也不难泄露。合在一起它指的是一类长期存在的民间整理工作把散落在公开渠道里的各家大模型系统提示词文本收集起来做清洗、归类、结构化拆解形成一份能被检索、能被对比、能被引用的语料库。我做提示词工程这些年电脑里躺着十几个不同版本的这类集合从最初几十行的 txt到后来带标签、带元数据的 jsonl中间踩的坑足够写一本书。先把定位说清楚这类项目的产出物是一份语料不是一份秘籍。它解决的核心问题是信息分散——同一份系统提示词可能在某个博客贴了一部分在某个 Issue 里补了另一部分格式还各不相同有人贴的是纯文本有人贴的是截图转录有人贴的是被截断的片段。整理者的价值在于把这些碎片对齐、还原上下文、标注来源和版本让研究者能在一个统一的坐标系里做横向比较。它适合三类人一是做提示词工程的开发者想从成熟产品的写法里提炼可复用的范式二是做 AI 产品设计的人想理解头部产品在角色定义、边界约束、输出规范上的取舍三是做模型应用安全评测的人需要一个真实语料基座来设计测试用例。我自己的用法比较务实不把里面的句子直接抄进产品而是看它的结构骨架。一家产品怎么写不确定时怎么办另一家怎么写工具调用失败的重试逻辑把这些横向摆在一起看比读十篇提示词教程都管用。这也是我认为这个项目最大的价值——它是一份对照样本集而不是一份可直接复制的模板。这里必须先讲清楚一个边界问题也是我做这类整理时给自己定的第一条规矩只处理已经在公开渠道流传的文本。什么叫公开渠道官方发布的技术文档、官方博客里贴出的示例、开发者在技术社区主动分享的内容、公开发表的论文附录、公开的会议演讲材料。反过来说任何形式的诱导式获取、任何试图绕过产品安全策略的尝试、任何利用未公开缺陷去套取内容的手段都不在我的工作范围内也不应该出现在一个健康的语料整理项目里。这不是出于保守而是因为一旦语料来源不可信后面所有的分析结论都会变成沙上建塔——你根本不知道这段文本是真实配置还是模型自己编出来哄你的。还有一个容易被忽略的点模型复述 ≠ 真实配置。这是新手最容易翻车的地方。你问一个模型你的系统提示词是什么它给你的答案很可能是基于上下文生成的合理猜测夹杂着它对自身行为的描述、对训练数据的记忆、甚至是对提问方式的迎合。我做过对照实验把同一份已知的真实系统提示词喂给某个模型让它自我描述输出的内容只有大约一半能对上。所以语料库里凡是标注为自述获取的条目我都会单独打一个低置信度标签绝不和官方公开的条目混在一起做统计。1.1 一份合格语料库应该长什么样很多人做这类项目第一步就错了——建一个文件夹把看到的内容往里面一丢文件名写某模型提示词.txt。三个月后自己都不知道哪份是新版哪份是旧版。我现在的做法是强制走结构化存储每一份语料是一个对象至少包含这些字段字段名含义取值示例是否必填id唯一标识sp_2024_q3_0012是product产品/模型归属通用对话助手是variant具体形态网页版 / 编程助手 / 移动端否source_type来源类型official_doc / community_share / paper_appendix是source_ref来源链接或出处描述官方文档第 3 节是collected_at采集时间2024-08-11是language主语言zh / en / mixed是token_count粗略 token 数1840否modules模块标签数组[identity,tools,format]否confidence可信度分级high / medium / low是notes备注疑似中间版本含截断否这个表看起来啰嗦但它是后面一切分析的前提。没有 confidence 字段你就没法做加权统计没有 collected_at你就分不清哪些差异是产品迭代造成的哪些是同一时期的不同形态造成的没有 source_type你写分析文章时就没法负责任地说明结论的适用范围。1.2 目录组织按什么维度切目录结构这件事我改了三次才定下来。第一版按产品名分很快乱了因为同名产品有多个形态第二版按时间分也乱了因为采集时间和文本实际生效时间不是一回事。最终定下来的是三级结构第一级按 source_type 分official / community / paper因为来源决定了你分析时的引用方式第二级按 product 分同名产品的不同形态放进子目录第三级是具体条目文件文件名 产品_形态_YYYYMM_置信度.json。这样组织的好处是想找所有官方公开的内容直接看第一级想研究某个产品的演进进第二级按时间排序想快速筛掉低置信度的文件名里就带着标记。我在实际使用中发现把置信度写进文件名这个动作能省掉大量这份到底靠不靠谱的纠结时间。2. 语料采集与清洗从零散文本到可用数据集采集这一步很多人以为就是复制粘贴。真做起来你会发现从各个渠道扒下来的文本脏得超出想象。我处理过一份从网页复制的提示词里面混着导航栏文字、版权声明、三个不同层级的折叠面板残留、还有两处被截断的占位符。直接拿去分析结论必然是错的。2.1 采集渠道的优先级排序我把渠道按可信度排了个序这个排序直接决定了后面 confidence 字段的取值官方文档与官方博客中的完整示例——可信度最高通常还带版本信息直接标 high。公开论文附录与会议演讲材料——可信度次高因为是研究者为了复现而公开的一般会说明获取方式标 high 或 medium。开发者社区中的完整分享——需要交叉验证至少找到两个独立来源指向同一文本才能标 medium。单一来源的片段分享——信息不完整标 low只用于辅助印证不单独作为结论依据。模型自述内容——一律标 low且单独存放不参与任何比例统计。这个分级不是形式主义。我曾经写过一篇关于某类产品边界约束写法的分析结论的支撑全部来自 medium 以上的条目评论区有人拿一份 low 级别的自述内容质疑我的结论我把分级表贴出来争议就自然消解了。可追溯是这类项目能被认真对待的关键。注意采集时一定要同步记录采集时间。我吃过亏——两份内容差异很大一度以为是产品改版后来发现只是采集时间差了半年中间隔了一次大版本更新但没记录时间就完全对不上账。2.2 文本清洗的八个动作清洗环节我固定了八个步骤顺序不能乱否则某些噪音会被后面步骤放大import re import unicodedata def clean_prompt_text(raw: str) - str: # 1. 去除零宽字符与 BOM text raw.replace(\ufeff, ).replace(\u200b, ).replace(\u200c, ) # 2. 统一换行符 text text.replace(\r\n, \n).replace(\r, \n) # 3. 归一化 Unicode全角转半角在这里统一处理 text unicodedata.normalize(NFKC, text)# 4. 去掉 HTML 标签残留 text re.sub(r[^]{1,80}, , text) # 5. 去掉常见页眉页脚噪音导航、版权、分享提示 noise_patterns [ r^(首页|登录|注册|分享|收藏|招聘|关于我们)[\s\|·]*$, r^(版权所有|Copyright|All Rights Reserved).*$, ] for p in noise_patterns: text re.sub(p, , text, flagsre.MULTILINE) # 6. 折叠三个以上连续空行为两个 text re.sub(r\n{3,}, \n\n, text) # 7. 剥离首尾空白 text text.strip() # 8. 剔除长度过短且无标点的行典型的分页残留 lines [ln for ln in text.split(\n) if len(ln.strip()) 2 or not ln.strip()] return \n.join(lines)第 3 步的 Unicode 归一化特别值得说。中文提示词里经常混着全角冒号、全角括号、还有各种花式引号弯引号、直角引号不做归一化的话后面写正则匹配小节标题时会漏掉一大半。我第一次做模块切分统计结果显示只有 40% 的文本带有角色定义模块检查后发现是标题用的是全角空格加冒号正则没匹配上归一化之后这个比例升到 78%完全是两个结论。 第 4 步要小心。提示词里有时候确实包含类似 XML 的结构化标签比如用来分隔不同模块一刀切掉会把真实结构一起毁了。我的做法是先做一次检测如果标签出现频率很高且成对出现就保留如果是零散的单标签才当噪音去掉。 ### 2.3 去重SimHash 比编辑距离实用得多 同一份提示词在不同渠道被反复转发几乎是必然的。用编辑距离两两比对几千条的规模就是几百万次计算太慢。我用的是 SimHash 加汉明距离把每段文本压成一个 64 位指纹距离小于等于 3 的视为近重复 python import hashlib def simhash(text: str, bits: int 64) - int: tokens re.findall(r[\u4e00-\u9fff]|[a-zA-Z]|\d, text.lower()) v [0] * bits for tok in tokens: h int(hashlib.md5(tok.encode()).hexdigest(), 16) for i in range(bits): v[i] 1 if (h i) 1 else -1 fingerprint 0 for i in range(bits): if v[i] 0: fingerprint | 1 i return fingerprint def hamming(a: int, b: int) - int: return bin(a ^ b).count(1)这里有个坑中文分词如果用字符级切分短文本的指纹会很不准。我的经验是长度低于 200 字的条目别用 SimHash直接人工看因为数量本来就不多。另外去重不能简单地把重复项删掉——重复本身就是信息。一个文本在几个独立来源里出现说明它的可信度更高。所以我保留全部条目只在元数据里加一个 dup_group 字段分析时按组取代表。3. 结构解剖把一段黑盒文本拆成可复用模块语料清洗完真正的硬活开始了。一段系统提示词动辄上千 token看着像一锅粥但它其实是有骨架的。我拆过几百份之后总结出高频出现的七个模块。3.1 七个高频结构模块身份声明通常出现在开头一到三句话说明你是谁、以什么身份回应。有些写得直白有些用隐喻。能力边界是第二常见的模块说明哪些事不做、遇到某类请求怎么处理。语气风格规定用词偏好、句长、是否使用列表。输出格式规定结构、字段、长度上限。工具说明在带函数调用的产品里必然出现包括每个工具的用途、参数、触发条件。工作流程是一段决策逻辑常见形式是先做 A如果满足 C 则做 B。不确定性处理规定信息不足、无法确认时该怎么表达。这七个模块的排列顺序其实是有讲究的。我统计过一批语料身份声明在开头的比例超过九成工具说明靠近结尾的比例接近八成。这个规律背后的道理不难理解靠近结尾的内容在长上下文里更容易被模型记住而工具说明是最需要被严格执行的部分。模块出现位置倾向平均占比写作难度身份声明开头8%低能力边界前半22%高语气风格中段10%低输出格式中后段15%中工具说明结尾附近25%高工作流程中段12%高不确定性处理结尾附近8%中占比是粗略统计不同形态差异很大。编程助手类产品的工具说明占比能到四成而纯对话类产品有时候完全没有工具模块。3.2 用规则把模块切开切分我不用模型用规则就够了因为这类文本的标题格式相当规整。先归一小节标题再按标题切块SECTION_ALIASES { identity: [角色, 身份, 你是谁, role, identity, persona], boundary: [边界, 限制, 禁止, 不得, policy, restriction, refuse], tone: [语气, 风格, tone, style, voice], format: [输出格式, 格式要求, format, output, structure], tools: [工具, 函数, tool, function, api], workflow: [流程, 步骤, workflow, process, step], uncertainty: [不确定, 未知, 无法确认, uncertain, unknown], } def split_modules(text: str): blocks {} current preamble blocks[current] [] for line in text.split(\n): stripped line.strip().strip(#*: ) matched None for key, aliases in SECTION_ALIASES.items(): for a in aliases: if stripped.lower().startswith(a) and len(stripped) 40: matched key break if matched: break if matched: current matched blocks.setdefault(current, []) else: blocks[current].append(line) return {k: \n.join(v).strip() for k, v in blocks.items()}实测下来这套规则在结构规整的语料上能切对八成以上。剩下的两成主要是两种情况一是整段没有任何标题全靠自然段分隔二是标题用了不常见的说法比如把边界写成我们希望你注意的事项。前者只能人工标后者靠维护一份不断增补的同义词表来解决。我现在的同义词表已经积累到两百多条这大概是做这类项目最不好看但最有用的资产。提示切分结果一定要抽检。我的习惯是每处理 50 条随机抽 5 条人工比对切分边界是否合理。错过一次就会带着系统性偏差往下走后面所有统计都白做。3.3 用聚类找家族相似性切完模块可以做一个有意思的分析把能力边界模块单独抽出来做向量化后聚类。我用句子向量加 HDBSCAN因为不需要预先指定簇数量还能识别出离群点。结果挺有意思——几百条边界描述会自动聚成几大族一类是围绕不提供具体建议展开一类围绕涉及隐私的处理一类围绕时效性信息的处理。同一个族里的表述虽然措辞不同但逻辑结构高度相似这说明这类文本存在明显的行业惯用范式大家在写的时候多少都参考过相似的思路。聚类还有个副产品离群点往往是写法最独特的那几份值得单独精读。我从离群点里学到好几个写法技巧比如把约束条件写成当 X 出现时先 Y 再 Z的条件式表达比单纯罗列禁止项更容易被模型稳定执行。4. 从语料里提炼可复用的写作范式看了这么多份之后真正能带走的东西其实不多就那么几条。我挑几条最实在的说说。4.1 角色定义不要写成形容词堆砌新手写角色定义喜欢堆形容词你是一个专业的、严谨的、有耐心的、经验丰富的助手。这种写法信息量极低模型对形容词的敏感度远不如对具体行为描述。语料里写得好的角色定义通常是身份 行为倾向 典型场景三段式。比如先说明身份定位再用一句话说明在模糊情况下倾向于怎么做最后举一个典型场景说明期望的回应形态。你是面向初学者的代码讲解助手。 当用户给出的代码存在多种理解方式时优先按最常见的那种理解作答 并简短说明你采用的假设。 面向的问题通常是这段代码为什么这样写、这样写会有什么副作用。这段只有几十个字但比一长串形容词管用得多。原因在于它给了模型可执行的判断依据而不只是一个模糊的风格倾向。4.2 约束要写遇到什么做什么而不是不要做什么这是我在语料对比里发现的最大差异点。早期写法大量使用不要禁止开头而近两年的写法明显转向条件式先说触发条件再说期望动作。为什么我的理解是纯禁止式表述只告诉模型边界在哪没告诉它边界外该往哪走模型很容易在边缘地带给出一个含糊的回应。条件式表述把替代路径也写清楚了行为更可控。举个对照写法示例实测表现纯禁止不要给出医疗诊断容易变成敷衍的一句建议咨询医生条件式涉及诊断类问题时先说明无法提供诊断再给出就医建议并提示可以帮忙整理症状描述回应更有信息量用户满意度更高第二种写法我抄进自己的项目之后同类问题的用户追问率下降明显。这不是玄学是因为条件式表述给了模型一个可填补的行动框架。4.3 输出格式控制给结构也给判断依据格式控制这块语料里的写法分三档。最低档是请用列表输出基本没用。中间档是给出字段名和顺序。最高档是不仅给结构还给出什么时候用哪种结构的判断依据。比如说明简单问题用段落、多步骤问题用编号列表、对比类问题用表格并给出判定标准——涉及三个以上并列项时用列表涉及两个对象的多维比较时用表格。这套判断依据的价值在于它把选格式这件事从模型的主观偏好变成了可推导的决策。我做过简单的 A/B 测试加了判断依据的版本输出格式的一致性提升相当明显尤其是处理混合类型的多轮对话时。4.4 工具描述参数约束要写全带工具调用的语料里写得细的那些会明确列出每个参数的取值范围、必填还是可选、缺省行为、以及调用失败时的处理建议。写得粗的只给一句用途说明结果就是模型经常传错参数类型或者在不该调用的时候调用。我总结的最小可用模板是四段用途一句话、参数表名称/类型/必填/说明、触发条件什么情况下应该调用、失败处理调用返回错误时怎么做。第三段和第四段是区分度最高的部分也是最多人漏写的部分。5. 反向思考怎么保护自己的系统提示词做这类整理久了必然会想到另一面如果我自己在做一个产品该怎么处理这个问题。先说结论——指望系统提示词不外泄是不现实的把它当成产品机密来保护投入产出比很低。真正值得保护的是业务逻辑、数据、以及后端的权限控制而不是那几千字的文本。5.1 为什么藏这个思路本身就有问题原因有三层。第一只要文本被送进模型上下文在任何具备一定输出能力的系统里都存在被间接暴露的可能你能做的是拉高成本不是彻底封死。第二即使文本外泄如果它本身不含敏感业务信息损失也就是别人知道了你的写法而这些写法往往可以从产品行为里反推出来。第三把大量精力放在藏上容易忽视真正该做的事情——把权限校验放在服务端而不是靠提示词里的一句话来约束。我见过最典型的反模式是有人把未经授权不得访问某数据写进系统提示词然后以为这就安全了。这等于把门锁画在墙上。正确的做法是这条规则同时也必须在服务端强制执行提示词里那句只是为了让模型的回应更自然。5.2 分层防护的四层做法指令层系统提示词里不写具体的业务规则细节、不写内部代号、不写任何凭据类信息。业务规则放在服务端判断模型只负责表达。架构层动态拼接按当前场景只下发必要片段不要把一份几千字的完整配置全量塞进去。用户对话历史与系统配置严格分离避免模型在复述上下文时把两者混在一起输出。检测层对输出做模式检测。常见信号包括输出里出现大段与你配置高度相似的文本、输出结构突然从自然语言变成条目化的规则列表、用户连续多轮以不同方式询问配置细节。我把这几种模式做成简单的检测规则命中后触发一次温和的重定向回应。法务层服务条款里明确说明配置内容属于产品组成部分禁止用于训练竞品或其他商业用途。这一层不解决技术问题但它是后续所有动作的基础。5.3 版本管理别用一份文件打天下这一点跟语料整理是相通的。我建议系统提示词走版本管理每次改动留记录写明改了什么、为什么改、预期影响是什么。原因很实在——当你发现某个行为异常时你需要快速判断是不是最近一次改动引入的。没有版本记录排查只能靠回忆效率极低。我自己的做法是用一个简单的 yaml 保存配置加注释说明每段的意图改动走代码审查流程。这听起来小题大做但当你面对一个跑了半年、改了二十几版的产品时会感谢当初这么做。6. 常见问题与排查实录6.1 清洗与切分环节的五个典型坑坑一把用户输入误当成系统配置。很多公开帖子里提问内容和回应混在一起贴出来中间没有明确分隔。我最初靠缩进和引号判断准确率一般。后来改成看内容特征系统配置通常用陈述句、第二人称、包含规则性词汇用户提问通常是疑问句、第一人称、包含具体场景。用这两个特征做交叉判断准确率提升很多。坑二把工具 schema 当成提示词正文。带函数调用的产品其工具定义一般是结构化 JSON和自然语言的提示词是两回事。混在一起统计会严重拉高工具模块的占比。我的处理是把 JSON 块单独抽出来存到另一个字段。坑三多语言混杂导致聚类失真。同一份配置有多语言版本时向量会被语言差异主导聚类结果全是按语言分而不是按内容分。解决办法是聚类前统一按语言分组组内再聚类。坑四截断没标记。有些片段明显在句子中间断掉如果直接入库后面做长度统计时会出现异常的短条目。我的做法是检测末尾是否缺少结束标点命中就打上 truncated 标记。坑五同一条目重复入库但版本不同。这个最隐蔽。两份文本 95% 相同只有一两句话不同很容易被判为重复而合并但那一两句话可能正是关键差异。SimHash 距离阈值我调到 3 而不是 6宁可多留一些近似项也不冒漏掉版本差异的风险。6.2 分析结论失真的三种情况第一种样本选择偏差。整理者往往更容易收集到结构规整、格式漂亮的文本而那些写得零散的可能压根没人愿意转录。如果你直接统计有多少比例的配置包含某某模块结论会系统性偏高。我的应对是明确说明样本构成并尽量补充低质量样本。第二种时间混淆。跨年度的语料混在一起统计得出的规律可能是三个不同阶段的平均掩盖了演进趋势。按时间分桶之后再看趋势往往比静态结论有价值得多。第三种把个例当范式。某份配置里有一个很妙的写法就写成业界通用做法。我给自己定的门槛是一个模式要至少在三家不同来源里出现才用较为常见这个措辞。6.3 常见问题速查表现象可能原因排查动作模块切分比例异常低标题格式未归一化检查全角半角、冒号类型聚类结果按语言分开多语言混在一起按语言分组后再聚类同一产品多条差异巨大形态不同或版本不同核对 variant 与 collected_at统计占比明显偏高样本选择偏差检查低质量样本是否缺失去重后条目骤减SimHash 阈值过松阈值从 6 降到 3 重跑文本末尾缺标点原帖截断打 truncated 标记不参与长度统计还有一个实操心得值得单独说保留原始文本快照。清洗后的文本便于分析但一旦清洗规则有误你没法回头验证。我的目录里每个条目都存两份一份 raw 一份 cleanraw 只读不改。这个习惯帮我纠正过两次正则错误每次都救回了几十条被误删内容的条目。最后说个我自己的体会。做这类语料整理最大的收获不是某一句话怎么写而是慢慢建立起一种看结构的习惯。拿到一段提示词第一反应不再是它说了什么而是它由哪几块拼成、每块承担什么职责、先后顺序为什么这么排。这个视角一旦建立自己写配置的效率会有明显变化——从凭感觉堆砌变成按模块组装。我现在的做法是维护一个自己的模块库每类模块存三到五个写法版本写新配置时按需拼装再根据具体场景微调。这套方法没什么技术含量但比我试过的任何自动化生成工具都管用。