ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

system_prompts_leaks:系统提示词语料库构建与模板拆解

system_prompts_leaks:系统提示词语料库构建与模板拆解 深夜刷 GitHub Trending看到一个叫system_prompts_leaks的仓库冒到了前排点进去的第一反应是——这玩意儿居然有人真的系统整理了。几百个.md、.txt、.json文件每个文件对应一段完整的系统提示词从对话助手、代码补全、搜索问答到图像描述类工具覆盖得相当全。那一刻我有种很熟悉的感觉过去两年做提示工程时最缺的从来不是写提示词的技巧而是高质量、成规模、可检索的真实样本语料。这个仓库恰好补上了这块。我先把话说清楚免得跑偏这里讨论的是公开渠道流传的、用于学习和研究目的的系统提示词样本集合重点在于怎么把它们整理成一个真正能用的语料库、怎么拆解其中的结构规律、怎么把规律迁移到自己项目的提示词设计里。它解决的问题很具体——散落在论坛帖子、截图、二手转述里的东西格式乱、缺上下文、搜不到、没法对比整理成仓库之后你能横向比对、能做结构统计、能抽模板、能回测。适合三类人看正在写 Agent 和产品级提示词的工程同学、做提示评测和红队测试的同学、以及想理解产品级提示词长什么样的产品设计同学。哪怕你只是想让自己的个人助手回答得稳一点这套拆解方法也能直接抄。1. 先搞清楚这个仓库到底在解决什么问题1.1 我踩过的第一个坑样本散落导致一切分析都做不了在做提示工程的前两年我陆陆续续收藏过上百个某产品系统提示词曝光的帖子。收藏夹看着满满当当真要做点正经分析时问题全冒出来了。截图里的文字不能全文搜索我为了找一句关于拒答边界的表述得挨个翻图同一个产品的不同版本混在一起根本分不清哪句是新增的、哪句是删掉的更麻烦的是很多帖子只贴了片段前后文缺失你没法判断这个约束是独立的还是某个更大条件分支的一部分。零散样本最大的问题不是数量少而是缺少统一的组织维度。没有维度就没法回答这类产品的提示词普遍会写几层结构工具调用定义的平均长度是多少输出格式约束出现频率多高这类问题。而一旦把它们按统一的目录结构、统一的元数据 schema 落成文件这些统计立刻就能跑出来。这个差别就像把一堆纸质名片塞进抽屉和把联系人导进一个带字段的数据库之间的差别。1.2 分类维度怎么设计才不至于半年后推翻重来刚整理时最容易犯的错是按来源公司分类。看着直观用起来很痛苦一个公司可能有五六个不同形态的产品对话助手和代码补全的提示词结构完全是两回事混在一个目录里横向对比毫无意义。我后来改成多维度并行标注文件目录用一层粗分类元数据里塞细维度。下面这张表是我最终稳定下来的几个核心维度跑了半年没再改过。维度取值示例为什么要标能力形态对话问答 / 代码生成 / 检索增强 / 工具调用 / 多模态形态决定提示词骨架的差异最大任务域通用 / 编程 / 写作 / 数据分析 / 客服影响约束的松紧程度约束强度宽松 / 中等 / 严格多级拒答直接决定提示词长度和层级数输出格式自由文本 / Markdown / JSON / 结构化标签格式约束段落差异极大工具接口无 / 单工具 / 多工具并行决定函数定义部分的复杂度语言单语 / 双语 / 多语影响本地化策略段的写法表格里最容易被低估的是约束强度这一列。很多人以为提示词长度主要和模型能力有关其实不是。实测下来带多级拒答策略的提示词长度普遍是无约束版本的 2 到 3 倍多出来的部分几乎全是条件分支和优先级说明。搞清楚这一点之后我再看到某个产品的提示词特别长第一反应就不再是这家写得太啰嗦而是它大概率在做多级边界判定。1.3 目录结构三层足够多了就是自找麻烦目录结构我前后改过三版最终停在两层主目录加文件命名约定。第一版按公司分第二版按形态分但嵌套太深第三版才收敛到现在这个样子prompts/ chat/ # 对话问答类 general/ # 通用助手 roleplay/ # 角色扮演向 coding/ # 代码类 completion/ # 补全 agent/ # 编码助手/工具调用 search/ # 检索增强类 multimodal/ # 多模态类 meta/ # 元提示词、评测用提示词命名约定比目录更重要我用的是{形态}_{产品代号}_{版本日期}.md这种格式比如chat_assistantA_2024-08.md。别小看版本日期这个字段没有它你半年后根本分不清哪个文件是新的做时间序列对比时只能靠文件系统时间戳猜而文件系统时间戳在批量下载、重新解压之后基本全乱。提示产品代号建议用脱敏后的自定义编号别直接用真实名称。原因有两个一是方便后续做匿名化统计二是避免仓库变成点名对比的场所那样既无必要也容易给项目带来不必要的麻烦。2. 样本清洗把能看变成能用2.1 原始样本为什么不能直接用直接把下载来的样本丢进仓库三个月后你自己都不想打开它。原始样本的脏主要体现在五个地方。第一是OCR 噪声很多样本最初来自截图或 PDF全角半角混用、连字符被识别成破折号、列表符号丢失这些都会让全文检索失准。第二是Markdown 结构断裂标题层级错乱、代码块没有闭合标记渲染出来一片混乱。第三是变量占位符不统一同一个意思可能是{user_name}、[USER]、name不归一化就没法做模板抽取。第四是语言混杂一段英文提示词里混进几句其他语言的注释做词频统计时会污染结果。第五是重复样本同一段提示词被不同帖子转发多次文件大小略有差异肉眼很难看出来但会让你的统计结果严重失真。这五点里前四点靠脚本能解决大半第五点得靠专门的去重算法。2.2 归一化流程与清洗脚本我用的清洗流程分四步结构修复、字符归一、占位符统一、去重。结构修复主要是补全未闭合的代码块和标题层级字符归一处理全角半角、连续空白、不可见字符占位符统一把所有变量格式收敛成{{var_name}}去重则用 SimHash 做近似去重。下面这段脚本是核心部分写得比较糙但足够用import re import hashlib from collections import defaultdict def normalize_text(raw: str) - str: # 1. 不可见字符清理 text raw.replace(\u200b, ).replace(\xa0, ) # 2. 全角转半角仅针对 ASCII 可见字符范围 text .join( chr(ord(ch) - 0xFEE0) if 0xFF01 ord(ch) 0xFF5E else ch for ch in text ) # 3. 连续空白压缩但保留代码块内的缩进 text re.sub(r[ \t]{2,}, , text) text re.sub(r\n{3,}, \n\n, text) # 4. 占位符统一 patterns [r\{[a-zA-Z_][\w]*\}, r\[[A-Z_]\], r[^]] for p in patterns: text re.sub(p, lambda m: {{ slugify(m.group(0)) }}, text) return text.strip() def slugify(token: str) - str: inner re.sub(r[^\w], , token).lower() return inner or placeholder def simhash(text: str, bits: int 64) - int: tokens re.findall(r\w, text.lower()) weights defaultdict(int) for t in tokens: weights[t] 1 v [0] * bits for token, w in weights.items(): h int(hashlib.md5(token.encode()).hexdigest(), 16) for i in range(bits): v[i] w if (h i) 1 else -w result 0 for i in range(bits): if v[i] 0: result | (1 i) return result def hamming(a: int, b: int) - int: return bin(a ^ b).count(1)去重的阈值我调过几轮最终定在汉明距离小于等于 3 就判定为近似重复保留较早或者较完整的那份。阈值设太高比如 8会误杀因为同一个产品不同版本的提示词差异本来就不大容易被当成重复删掉设太低比如 1又几乎不去重。这个数字不是理论最优是拿几百个样本手工核对出来的经验值。2.3 元数据 Schema别嫌麻烦这是后期一切统计的地基清洗完的正文只是原料元数据才是让语料库活起来的关键。我给每个文件配一份同名 JSON字段设计如下{ id: chat_assistantA_2024-08, form: chat, task_domain: general, constraint_level: strict, output_format: markdown, tools: [search, calculator], languages: [en], source_type: public_post, collected_at: 2024-09-01, verified: false, notes: 含多级拒答分支工具定义部分完整 }这里有个小设计值得说一下verified字段。刚收集来的样本我默认标false只有我逐字核对过结构完整性、确认没有大面积缺段之后才改成true。做统计时只用verifiedtrue的子集避免残缺样本把平均值带偏。source_type也很有用区分它是完整文件、论坛片段还是二手转述可信度完全不同分析权重也应该不同。注意清洗阶段千万不要顺手润色原文。有人为了让样本读起来通顺会去改措辞、补标点这等于把原始证据毁掉了。你后面所有关于某个表述出现频率的统计都建立在这份原文之上动一个字结论就不可靠了。3. 拆解一段系统提示词的六层骨架3.1 身份与角色层不只是你是一个助手大多数人以为身份层就是一句你是一个乐于助人的助手看完大量样本之后你会发现远不止。成熟的身份层通常包含四个要素角色定位你是谁、能力边界声明你能做什么、不能做什么、语气风格定义正式、简洁、温和等、受众假设面向专业用户还是普通用户。这四个要素缺一个模型在边界场景下的表现就会飘。举个抽象化后的例子某类编程助手的身份层大致是这个结构这里做了脱敏和简化只保留骨架你是 {{product}}一个面向软件开发者的编程助手。 你擅长代码阅读、调试辅助与方案对比。 你不提供与编程无关的领域建议遇到这类问题应引导用户转向合适渠道。 你的回答应当直接、技术性强避免客套话。注意第三句和第四句的分工。第三句是能力边界第四句是风格约束。很多新手写提示词时把这两件事揉在一句话里结果模型要么在边界问题上含糊要么在风格上跑偏。拆开写每句只承担一个职责调试时你才能定位到底是哪一层出了问题。3.2 能力与工具层函数定义为什么总是最长的部分工具调用类样本里能力层的长度经常占到全文的一半以上。原因不难理解一段工具定义要同时说清工具名、用途、参数、参数类型、必填与否、参数语义、调用时机、返回处理。少一样模型在真实调用时就可能传错参或者在不该调用的时候调用。我在自己项目里踩过最典型的坑是没写调用时机结果模型在一句闲聊里也硬要调一次搜索工具白白增加延迟。工具定义我习惯写成紧凑的结构化形式比纯自然语言描述更稳工具weather_lookup 用途查询指定城市未来 24 小时天气 参数 city (string, 必填)城市名称使用用户原话中的写法 unit (string, 可选, 默认 celsius)温度单位celsius 或 fahrenheit 调用时机仅在用户明确询问天气时调用。 若用户只是闲聊提及天气不要调用。 返回包含温度、天气描述、更新时间三字段的 JSON。 失败处理城市无法识别时向用户请求更具体的地点不要编造数据。失败处理这一条是被最多人忽略的。真实场景里接口会超时、会返回空、会报参数错误提示词里不写清楚失败时该怎么办模型就会开始编——这是幻觉最集中的来源之一。自从我把失败处理补上之后工具类回答的编造率肉眼可见地下降。3.3 约束与安全边界层多级分支到底长什么样这一层是样本之间差异最大的地方也是最能体现产品级三个字的地方。简单版本就是一句不要回答有害问题复杂版本会有三到五个层级。我整理出来的常见分支结构大致是这样先判定请求类别再判定类别下的敏感度然后决定是直接回答、加限定回答、还是引导转向。关键在于每个层级的判定标准要写得可执行而不是含糊的形容词。下面是我从多个样本里提炼出来的简化骨架用来说明分层思路请求处理优先级从上到下命中即停 1. 与核心能力无关的领域请求 - 简要说明你的能力范围不展开 2. 信息不足以给出可靠回答 - 明确说明不确定并提出澄清问题 3. 涉及需要专业资质的建议 - 提供一般性信息并提示寻求专业意见 4. 其余情况 - 正常回答这里的精髓是命中即停和从上到下这两个顺序说明。很多提示词只列了规则没写规则之间的优先级结果模型遇到同时命中两条的请求时行为随机。补上这两句话之后输出的一致性会明显提升。这个技巧我是从大量样本的对比里发现规律的——凡是行为稳定的样本几乎都有显式的优先级声明。3.4 输出格式层格式约束写不好后面全是返工输出格式层看起来最简单实际是返工最多的地方。我统计过自己项目里因为格式问题被下游代码拒收的比例早期能到两成以上。原因通常是提示词只说了以 JSON 输出没说字段名、字段类型、缺值怎么填、能不能有额外字段、字符串里能不能带换行。这五个问题不写清楚模型每次的发挥都不一样。后来我固定成一套写法先给字段表再给一个完整示例最后加一句负面约束。示例比描述有效得多这一点在所有样本里都是共识。负面约束也必要因为模型有帮忙补全的倾向你不明确禁止它就会自作主张加字段。写法效果备注只说输出 JSON差字段名、结构随机列字段表中结构稳定但缺值处理随机字段表 完整示例好大部分场景够用字段表 示例 负面约束最好适合下游严格解析的场景3.5 示例与少样本层放几个例子才合适示例数量是个玄学问题但从样本里能看出比较清晰的规律。任务越依赖格式和风格示例越有效任务越依赖推理示例的边际收益越低甚至会限制模型的思路。我自己的经验是格式类任务放 2 到 3 个示例足够风格类任务放 3 到 5 个推理类任务放 0 到 1 个。超过这个量提示词长度暴涨效果提升却很有限。另一个容易被忽略的点是示例的顺序。放在约束之前还是之后效果不一样。我的做法是把示例放在紧邻输出格式说明的位置因为它是对格式的具体化。如果放在最后面模型读完一大段约束之后再看到示例容易把示例当成额外补充说明而不是格式基准。3.6 元指令与优先级层冲突时听谁的这是最上层、也最少被显式写出来的一层。当身份层说要简洁输出格式层说要完整列出所有字段而用户的请求又触发了约束层的限制回答这时候到底该听哪个成熟样本会在开头或结尾加一段元指令明确冲突仲裁顺序。常见写法是安全与合规约束优先于格式约束格式约束优先于风格约束。把这条线画清楚模型在复杂场景下的行为会稳得多。我自己的项目里元指令是最后加上去的但效果最立竿见影。加之前遇到用户要求用某种格式但内容触发了限制这类场景输出会时好时坏加之后基本稳定。这一条建议放在你写完所有其他层级之后再补因为只有写完你才知道哪些层之间可能打架。4. 从语料到可复用的提示词模板4.1 检索先建索引再谈复用语料库建好了如果只能靠肉眼翻文件价值发挥不到三成。复用第一步是让每个片段都能被检索到。我的做法是双层索引文件名和元数据走一层结构化查询正文切片走一层全文检索。前者回答有哪些工具调用类的严格约束样本后者回答哪些样本提到了失败处理。这两类问题在实际工作中出现频率都很高。全文检索我用的是 BM25 加轻量向量检索的混合方案。纯关键词检索对同义表达不友好比如搜拒答时用边界表述的样本会被漏掉纯向量检索又容易召回语义相近但结构完全不同的片段。混合之后按加权分数排序实测召回质量比单一方案好不少。下面是最小可用的检索示例from rank_bm25 import BM25Okapi import numpy as np def tokenize(text: str): return [t for t in text.lower().split() if len(t) 1] class HybridIndex: def __init__(self, docs): self.docs docs self.bm25 BM25Okapi([tokenize(d) for d in docs]) self.vectors [embed(d) for d in docs] # embed 用你手头的向量模型 def search(self, query, top_k10, alpha0.6): bm_scores np.array(self.bm25.get_scores(tokenize(query))) qv embed(query) vec_scores np.array([ float(np.dot(qv, v) / (np.linalg.norm(qv) * np.linalg.norm(v) 1e-9)) for v in self.vectors ]) norm_bm bm_scores / (bm_scores.max() 1e-9) norm_vec (vec_scores 1) / 2 final alpha * norm_bm (1 - alpha) * norm_vec order np.argsort(-final)[:top_k] return [(self.docs[i], float(final[i])) for i in order]alpha这个权重我一般设 0.6偏向关键词一点。原因是提示词里有很多专有名词和固定表述精确匹配的价值更高纯语义匹配在长文档上容易把不相关的段落排到前面。这个值可以根据你的语料特点调没有通用最优解。4.2 模板抽取与占位符化把具体产品抽成通用骨架语料的终极用途是帮你从几十个样本里抽出一套与具体产品无关的通用骨架。做法是把产品名、公司名、专有工具名全部替换成占位符把具体数值抽成参数剩下的结构就是可复用的部分。这一步做完你会发现表面上五花八门的提示词骨架重合度其实很高。我抽出的一版通用骨架大致是这样分段的身份声明、能力范围、工具定义、约束与优先级、输出格式、示例、失败处理。七段里失败处理和优先级最容易被漏但恰恰是效果差异最大的两段。抽取的时候建议保留原始样本的注释标明每一段是从哪个样本的哪个位置来的方便你后面回溯。4.3 回归测试与版本管理让改动有据可依提示词改一个字效果可能变一圈。没有回归测试你根本不知道这次改动是优化还是退化。我的做法是维护一个固定的小测试集通常 30 到 50 个用例覆盖正常请求、边界请求、格式要求、工具调用四类。每次改提示词跑一遍看通过率变化再决定是否保留改动。# 简化的回归测试流程 for case in tests/*.json; do prompt$(build_prompt $case) output$(call_model $prompt) check_result $output $case results/$(date %F).log done diff (cat results/yesterday.log) (cat results/$(date %F).log)测试集要小而精不要贪多。用例太多跑一次成本高你就会懒得跑最后形同虚设。我早期贪心建了两百个用例跑一次要十几分钟很快就放弃了缩减到四十个之后每次改动都能立刻验证反而坚持下来了。这个教训挺朴素能坚持的小流程比完美的庞大流程有用。5. 常见问题与排查速查表5.1 高频问题对照整理语料库这两年里遇到的问题来来回回就那么几类。我把它们做成了速查表遇到时直接对照排查能省不少时间。现象可能原因排查方向全文搜不到明明存在的句子字符归一没做干净全半角差异检查归一化脚本对比原始文件统计出的样本数量虚高近似重复没去掉调低 SimHash 汉明距离阈值重新去重模板抽取后骨架断成两截原文存在未闭合代码块检查 Markdown 结构修复步骤检索结果总是同一批样本向量模型对短文本区分度低提高 BM25 权重 alpha分类后某些样本找不到归属分类维度设计过窄补一个其他维度别硬塞元数据和正文对不上批量重命名时 JSON 没同步用脚本统一重命名禁止手工改表格里元数据和正文对不上这条看着低级实际最常发生。手工改文件名的时候顺手改了正文名忘了同步 JSON半年后你看到一份元数据写着严格约束打开正文却是个宽松版本整个人都是懵的。后来我立了个规矩任何重命名和移动都走脚本手工操作一律禁止。这条规矩救过我至少两次。5.2 我踩过的三个典型坑第一个坑是过度清洗。早期为了追求干净语料我把所有非标准 Markdown 语法都强转成标准格式结果把原文里那些特意用缩进表达层级的段落结构破坏了后面做层级分析时数据全乱了。教训是清洗只处理噪声不要动结构。第二个坑是元数据字段设计得太细。一开始我设计了二十多个字段填一份要好几分钟填到第三十份就烦了后面全是空的。后来砍到九个必填字段其余全放notes自由文本填写负担立刻降下来坚持度也高了。第三个坑是把样本当成标准答案。有段时间我做提示词设计时总想着某产品的样本里这么写我也这么写。但样本是有语境的它对应的模型能力、产品定位、用户群体都和我的项目不一样。照搬结构可以照搬具体措辞经常翻车。后来我改成参考结构自己重写措辞效果反而更好。6. 合规边界和长期维护的个人体会6.1 边界在哪儿学习研究可以越界不行做这类语料整理边界感比技术能力更重要。我的原则很简单只处理公开渠道可以获取的、已经流出的样本只用于结构分析、提示工程学习和评测方法研究。不参与任何试图获取非公开内容的行为不把语料用于商业复制也不做点名对比式的优劣评价。仓库里所有产品名都做脱敏处理改成自定义编号。还有一个实际考量语料库越大越容易变成这样写就对了的权威假象。我在仓库 README 里专门写了一段说明提醒使用者这些样本只是特定时间点的产物模型在迭代提示词也在迭代把任何一个样本当成终极答案都是误读。这段话看着像免责声明其实是我自己踩坑之后的真心话——我确实曾经因为过度依赖某份样本的写法做出了一个很快就过时的方案。6.2 维护节奏小步更新比大扫除靠谱维护这类项目的最大敌人不是难度是持续性。我见过太多语料库项目起步时雄心勃勃整理了两百个文件之后再也没更新过。我的做法是把维护拆成三个固定动作每个动作都很轻每周花二十分钟收一次新样本进inbox/目录每月做一次批量清洗和去重把 inbox 消化掉每季度更新一次索引和统计报告。三个动作加起来一周一小时左右可持续性比一次大扫除强太多了。统计报告这块我坚持用脚本自动生成内容包括样本总数、各维度分布、平均长度、工具定义占比变化。看着单调但时间序列拉长之后你能看到一些很有意思的趋势比如严格约束类样本的占比逐年上升工具定义的平均长度也在增长。这些趋势比单个样本更有参考价值因为它反映的是整个行业的演进方向而不是某一家的具体做法。回到最开始那个深夜。我合上电脑的时候想明白一件事system_prompts_leaks 这类项目的真正价值不在于让你看到别人是怎么写的而在于让你拥有一份可量化、可对比、可回溯的参照系。有了它你写的每一版提示词都不再是凭感觉试出来的而是有据可依地演进出来的。把这个参照系用好比收藏再多的神级提示词都管用。
返回列表