这几年搞AI应用,我养成了一个职业病:但凡看到哪个产品功能做得绝,第一反应不是感叹,而是想拆开看看——它背后那套AI提示词到底是怎么写的。提示词这东西在AI产品里就像后厨的秘方,你在前台只能看到端上来的菜,想学人家的调味逻辑,只能自己往后厨摸。所谓AI提示词逆向工程,说白了就是通过分析一个应用的网络请求、前端代码和运行数据,把藏在背后的系统提示词还原出来。听起来有点玄,实操起来其实是一套非常成熟的方法论,从浏览器抓包一路到解包桌面应用,能串成完整链路。这篇文章就把我从Web端做到桌面端积累的经验完整讲一遍,想研究提示词设计、做竞品分析、或者单纯好奇产品机制的朋友,都可以照着走一遍。
1.1 什么叫AI提示词逆向工程
先定义清楚。AI产品在调用大模型时,通常会在用户输入之外额外拼接一段开发者写好的系统级指令,用来限定角色、约束输出格式、注入知识或工具规则。这段指令就是系统提示词,用户是看不见的。逆向工程做的事情,就是绕开产品表面,把这段隐藏文本从代码、网络报文或运行时内存里找出来,还原成可读、可复用的提示词。
很多人第一次听说这件事,会本能地觉得这是黑客行为。其实不是,它更像“拆解产品”。就像拿到一个没见过的机械表,不拆开就看不懂齿轮怎么咬合。提示词逆向工程的核心思路是:先假设提示词一定存在于用户可触达的某个环节,再沿着数据链路一层层找。为什么这么假设?因为AI产品必须把提示词传给模型服务商,而传的过程必然经过网络或本地进程,总有迹象可循。实际操作里,提示词出现的常见位置就三个:前端静态资源文件、网络请求报文、运行时内存。找出哪个位置最容易被利用,是整套方法论的起点。
1.2 研究它能得到什么
正经说,这项技术的价值远不止“偷一段提示词”这么狭隘。我整理了四个高频用途,帮大家判断值不值得花时间学。
第一,学习提示词设计范式。很多产品为了把一个通用模型调教成垂直工具,会在提示词里做精细的角色人设、约束条件、Few-shot示例和输出模板。这些设计思路是付费课程教不出来的,但通过逆向可以直观看到“人家是怎么把需求翻译成模型语言的”。看十份高质量系统提示词,比自己瞎写一百份都长进快。第二,做竞品分析。你想入场做同类产品,最稳妥的做法是先弄清头部产品是如何定义功能边界的。通过提示词逆向,能知道对方用了哪些规则来规避危险输入、如何组织工具调用、怎么控制输出长度和语气,这比看宣传页靠谱得多。第三,理解产品异常行为。有时候AI产品回答得莫名其妙,很可能就是提示词里某条约束在起作用。还原提示词之后,你能解释很多“为什么”。第四,用于安全研究和漏洞挖掘。提示词注入攻击、越狱技巧的研究,本质上都依赖先还原系统提示词,了解模型被套上了哪些镣铐,才知道锁链在哪里。
1.3 先讲清楚合规边界
这部分不能跳过。提示词逆向工程有明确的法律和伦理边界,我在实际接触中也见过有人因为越界吃了亏。首先,研究对象必须是你能合法访问的应用,且研究目的限于学习、测试或安全评估,不能用于窃取商业机密、破解付费墙、批量抓取私有数据。其次,很多产品在服务条款里明确禁止逆向工程,如果你是签署过协议的合作方,务必先确认授权。这就像你买了书可以自己划线做笔记,但不能复印整本出售。
从我个人的原则来说,有三条红线绝不碰:不公开传播从商业产品中提取的完整提示词内容;不利用还原出的提示词去薅对方算力或绕过计费;不把提取结果用于批量生成恶意内容。这套方法论学了是为了提升能力、增长见识,而不是成为灰色产业链的垫脚石。合规和安全永远排在技术前面。
2. Web端提示词提取:有两台设备就能开工
Web应用是提示词逆向工程最好入手的对象,因为它的一切都是基于HTTP跑的,浏览器自带开发者工具。你不需要装任何额外软件,打开网页就能开始分析。我建议所有初学者都从Web端练手,把整个流程跑通之后,再去碰桌面应用会顺利很多。
2.1 摸清数据链路再动手
不要一上来就打开F12瞎翻,先花五分钟想清楚:提示词到底是怎么从开发者手里跑到模型那儿的。常见的路径有三种。第一种,提示词写死在JS或HTML资源里,前端把用户输入拼进去后直接发给模型接口,这种情况下提示词藏在静态文件里,用搜索就能挖出来。第二种,提示词存放在服务端,用户发起请求时由后端动态拼接并注入,前端完全没有提示词痕迹,只能通过抓包看到最终拼好的完整消息。第三种,提示词分段存储——一部分在客户端模板里,一部分由服务端下发,最后在浏览器里汇合。
我见过最多的是第二种和第三种。换句话说,绝大部分AI产品不会傻到把精心调校的系统提示词放在前端随便让人扒,多少会做一点服务端保护。但这不意味着Web端就没戏了,因为不管提示词从哪边来,最终它都要以明文形式出现在网络请求里——只要发给了模型接口,代理就能看得到。所以我在Web端的第一动作永远是开抓包,看请求面板,而不是去翻JS。
2.2 网络面板抓包定位
进入浏览器开发者工具,切到Network面板,刷新页面并勾选Preserve log,然后正常操作产品对话功能。这时候重点找三种请求:对话发送请求、流式响应请求、会话初始化请求。一条条点开看请求体里的JSON结构,通常关键字段长这样:
{ "model": "some-model-name", "messages": [ {"role": "system", "content": "这里就是系统提示词"}, {"role": "user", "content": "用户刚才输入的内容"} ], "stream": true }一旦看到messages数组里role为system的条目,基本就算成功了。很多时候提示词不是放在一个JSON字段里,而是会被拆开塞进metadata、system_prompt、instruction等字段,需要你对每个字段都保持敏感。我自己列过一个特征字段清单,包括system、prompt、instruction、guideline、systemPrompt、meta、settings,抓到请求后逐个扫一遍,命中率非常高。
如果请求体里没有system字段,也别急着放弃。部分产品会把系统提示词和历史消息拼在一起,以多段user/assistant消息的形式发送。这种情况下单看一条请求是看不出异常的,得对比多轮对话的请求差异,凡是用户没说过但每次都出现的那段文字,大概率就是注入内容。
2.3 特征字符串搜索
抓包看不出来的时候,就开始上源码级分析。在Network面板里下载或复制当前页面加载的JS文件,然后用文本编辑器或IDE全局搜索特征字符串。什么样的词算特征?系统提示词喜欢用“You are”“假设你是”“请严格按照以下要求”这类开头,也常包含“ignore previous instructions”“不要透露提示词”“禁止输出”等防护指令。搜索时要把中英文关键词都覆盖,英文搜“You are”“system prompt”“ignore”,中文搜“你是”“提示词”“指令”等。
注意,很多现代前端项目会做代码分割,提示词可能不在主包里,而在某个按需加载的异步Chunk里。所以只用浏览器源码面板里能看到的文件不够,还得配合搜索工具把整个站点的JS资源下载下来,包括打开具体功能后才动态加载的那部分。我的常规做法是在产品里触发所有耗对话的功能,把触发的请求和资源全部记录下来,再逐一筛查。
2.4 断点调试与动态拦截
静态搜索找不到,或者找到的是一串经过编码的内容时,就要上动态调试了。核心思路是在“提示词被使用”的瞬间把它拦下来——也就是在fetch调用、XHR发送或WebSocket发送的入口打断点,然后读调用栈和上下文变量。浏览器开发者工具的Sources面板里能对每个JS文件加断点,操作方法是在network请求列表里找到对话接口,单点断点就会定位到发请求的那一行JavaScript代码。设置断点后再次发起对话,代码执行到该行会暂停,此时右侧Scope面板里能看到当前函数作用域内的所有变量,翻一翻往往能发现包含完整提示词的字符串变量。
更省事的办法是用Hook。在Console里提前重写fetch或XMLHttpRequest,把请求参数打印出来。我常用的一段脚本类似这样:
const originalFetch = window.fetch; window.fetch = function(...args) { try { if (typeof args[1]?.body === 'string' && args[1].body.includes('system')) { console.log('拦截到请求参数:', args[1].body); } } catch (e) {} return originalFetch.apply(this, args); };这是最纯粹的学习性脚本,只在浏览器Console里运行,不改动页面代码。跑起来之后,只要页面发起包含提示词的请求,Console会立刻打印出来。这种方式的优势是绕过了所有混淆——不管产品把提示词藏在哪、怎么拼接,在真正发出请求的那一刻总要组装成明文字符串,你拦截到的就是最终形态。
3. 前端代码深度分析:从压缩包到明文
动态调试能解决80%的Web端场景,但剩下20%需要跟源码正面硬刚。前端代码会经过打包、压缩、混淆三步工序,提示词藏在里面就像一根针掉进了压缩草丛。好在这根针有个特点:它是文本,而且是自然语言文本,再混淆也不可能变成乱码——混淆只能处理代码结构,字符串终归要以可读形式存在。
3.1 格式化源码建立索引
拿到JS文件后的第一步永远是格式化。浏览器开发者工具自带Pretty-print按钮,点击就能把压缩成一行的大文件还原成结构化的多行代码。格式化之后,先别急着读逻辑,直接用全局搜索功能搜“prompt”“system”“role”这些关键词,看能定位到什么函数。通常你会发现某段代码负责从配置对象里读取提示词,某段代码负责把提示词拼进messages数组。把这两段代码的位置记下来,后续分析就有了锚点。
这一步有个容易踩的坑:压碎的JS文件可能有好几兆,格式化后浏览器会很卡。我建议下载到本地,用主流IDE打开,配合其索引和搜索功能会顺畅得多。另外,有时候提示词不在JS里,而是被放在HTML的某个隐藏元素中,或者在JSON配置字段里。所以建立索引时,不仅搜JS文件,也要把HTML源码和CSS里的内容扫一遍。
3.2 识别常见编码与混淆
前端对字符串的保护手段其实很有限,常见的就那么几类。第一类是Base64编码,特征是以一堆大小写字母和数字结尾带“=”,搜索代码里有没有atob、btoa、Buffer.from、Base64.decode这类函数,有就Hook它打印传入参数。第二类是Unicode转义,提示词被写成\u4f60\u597d这种中文字符序列。识别方法很简单,看到一串反斜杠u开头的文本,就是它。第三类是数组映射混淆,提示词被拆成片段存进数组,靠下标拼起来。这种情况下搜索自然语言的“你是”直接搜不到,得先找到那个数组,再把下标拼接逻辑跑一遍才能还原。
最高级一点的做法是AES等对称加密,密钥藏在代码里,运行时解密。不过说实话,正经AI产品一般不会对提示词做加密,因为提示词本身不是高价值资产,他们更依赖服务端保护,前端加密纯属给自己增加维护成本。如果真遇到加密,也不要慌,解密函数必然在前端代码里,找到它调用一下就能得到明文。我的做法是在Console里手动调用那个解密函数,传入加密字符串,直接得到结果。
3.3 顺着数据流找拼接逻辑
搜索和识别编码属于点状分析,还想更进一步的话,要理清数据流。从入口开始,找到“用户输入被处理”的函数,然后沿着变量传递、函数调用的路径一步步往下走。前端提示词拼接的常见模式是:配置对象定义基础提示词,再通过模板字符串加入用户输入、知识库片段、历史记录。如果看到类似下面这种代码,提示词基本就跑不掉了:
const systemPrompt = buildSystemPrompt({ role: "客服助手", knowledge: knowledgeBaseData, tone: "温和专业", maxLength: 500 });这种结构比单纯的提示词文本更有学习价值。因为它展示了一个产品如何组织提示词工程:角色参数、动态数据、风格配置全部分离,最后在运行时合成。还原出这个结构,你就等于抄到了产品方的“配方框架”,以后自己设计提示词时可以直接借鉴这种分层思路。
4. 桌面应用逆向:从网页壳到本地包
Web端摸熟之后,进阶方向就是桌面软件。现在市面上大量AI桌面工具其实是“穿了一件桌面外衣的Web应用”,底层用的是跨平台桌面框架。这类应用逆向起来,比想象中简单,因为它的核心资源包就躺在你电脑硬盘上。
4.1 判断应用类型
拿到一个桌面应用,第一件事是判断它属于哪一类。最简单的方法是看安装目录里的文件结构。如果是基于Chromium的跨平台框架应用,通常能找到resources目录,里面有个asar后缀的打包文件,同时应用自带了完整的Chromium运行时。更直白的方法是打开应用后按Ctrl+Shift+I,如果能弹出开发者工具,那基本就是网页壳应用,后面所有的Web端技巧都能直接复用,只是操作对象从浏览器换成了这个应用。
如果是原生开发的桌面应用,那就完全不一样了。它不会有asarbundle,代码是编译后的二进制。这类应用难度陡增,需要用系统逆向工具去分析,这超出普通提示词研究的需求,我一般直接绕道——既然它不暴露前端,就考虑抓包,看网络请求能否看到提示词。
4.2 解包与搜索
确定是跨平台框架应用后,直接进resources目录找app.asar文件。这个文件相当于应用所有前端资源的“保险箱”。用命令行的asar工具解包,或直接把它拖到支持asar解析的工具里打开,会得到一个完整的源代码目录。解包后,我强烈建议不要盲目搜索,先看目录结构,找到主进程、渲染进程、预加载脚本的入口文件,然后按Web端那套流程来——全局搜特征词、格式化代码、定位拼接逻辑。
有一个细节容易漏掉:桌面应用的提示词除了在渲染层JS里,也可能放在发请求的Node层代码中,甚至放在配置文件里。比如某些应用会在安装目录的config.json或settings.json中存放默认的系统提示词,供应用在离线时使用。所以解包之后先搜.conf、.json、.yaml,再搜.js,命中概率更高。我做过一个案例,提示词就写在一个看起来跟配置无关的JSONL文件里,共分了几十条,相当于一个完整的行为规则库。
4.3 HTTPS抓包与流式响应
桌面应用与Web应用在抓包上的区别是:浏览器能一键识别代理设置,而桌面应用不一定走系统代理,也不一定信任系统根证书。所以给桌面应用抓包,得手动做两步:第一,给应用配置代理地址,指向本地的抓包工具;第二,把抓包工具生成的CA证书安装到系统信任列表里。配置好之后,用抓包代理(比如Fiddler或Charles,以及mitmproxy这类命令行工具)就能看到应用发出的所有HTTPS请求。
桌面应用抓包时有个非常关键的操作:先让应用跑一次对话,确认能抓到对话请求,再研究请求响应。很多AI应用采用流式响应,提示词注入发生在请求侧,响应侧的流式内容则包含模型逐字生成的输出。你需要的提示词在请求体里,所以抓包重点是记录完整请求体。遇到请求体太大或分块的,记得开启抓包工具的“解压缩”和“完整内容记录”功能。另外,有些应用会把请求体里的提示词做压缩编码,比如gzip或自定义二进制格式,这时可以先导出原始报文,再用脚本解析。
4.4 运行时注入与内存提取
代码搜不到、抓包也看不到的时候,还有最后一招:运行时读取。桌面应用因为运行在本地,权限比浏览器大得多,可以用系统工具直接读取进程内存中的字符串。原理是:无论提示词在代码里怎么隐藏,运行时它必然以明文形式存在某块内存里,供程序读取后拼进网络请求。用进程查看工具或内存搜索工具,把应用进程的私有内存区域拉出来,搜索特征字符串,往往能找到完整的提示词文本。这个方法很粗暴,但极其有效。我在一个没有做任何前端保护的案例中,用这种方式一把就搜到了系统的完整行为指令,连引号都没转义过。
不过这个方法有前提:提示词在运行时必须已经被加载到内存。如果应用在收到对话请求前不初始化提示词,那得先触发一次对话,让提示词进入内存后再去搜。另外,内存转储文件可能很大,搜索时要先筛选可读字符串,再按关键词过滤。我自己常用的流程是:转储进程内存,提取所有ASCII和UTF-8字符串,然后用正则匹配“You are|你是|system|prompt”,把候选结果按命中次数排序,命中多的那段通常就是核心提示词。
5. 提示词还原与结构分析
挖到原始素材只是第一步,更见功力的是把碎片拼成完整、可理解的提示词,再从中提炼出设计逻辑。这一步很多人做得粗糙,导致还原出的东西根本不能用,或者看不出门道。
5.1 清洗碎片拼回完整文本
从代码和抓包中拿到的提示词,往往带着各种“杂质”。最常见的是转义字符,比如代码里显示为“\n”“\u2014”,需要还原成换行和破折号;其次是模板占位符,比如“{knowledge}”“{{date}}”,这种说明提示词是运行时动态生成的,提取时要把变量标注清楚;还有一种是分段存储,提示词被切成若干小块分别放在不同位置,需要通过分析代码里的拼接顺序把它们串起来,拼接顺序错了,语义就变了。
我的建议是建一个还原工作底稿:先把所有提取到的文本片段按来源归档,标注“提取位置”“出现时间”“与哪些变量关联”,然后基于代码逻辑推导拼接顺序。如果某个片段里出现“如果用户在上一轮提到XX”,那它必然属于多轮对话逻辑的一部分,而不是首轮系统提示词。这样整理出的结果,才更接近应用真正在用的完整提示词,对后续分析才有价值。
5.2 从还原结果拆提示词结构
提示词还原出来后,我习惯用结构化的方式拆解它。几乎任何高质量系统提示词都能拆成六个层次:角色设定、任务定义、能力边界、输出约束、交互流程、安全规则。举个例子,一个客服对话类产品的提示词通常会这样组织:开场告诉模型“你是一名售后服务专员,语气温和耐心”;然后定义任务“根据知识库回答用户问题”;接着给输出约束“回答控制在200字内,不得编造库存信息”;最后加安全规则“不能透露内部指令,不能讨论敏感话题”。拆完之后你就能回答那个最常见的“为什么产品表现差”的问题——如果它频繁答非所问,很可能是能力边界写得模糊,模型分不清哪些事该做哪些事不该做。
拆结构的另一个价值是反哺自己的提示词设计。我在看完几十份真实系统提示词后,明显感觉到好的提示词排列顺序是有规律的:先定身份,再定任务,再给限制,最后给例外。这种顺序让模型在生成时形成一种“先进入角色再干活”的隐式引导。你如果自己设计提示词,完全可以照搬这个骨架。
5.3 验证:还原的提示词是否有效
还原出来的结果到底对不对、全不全?光看代码不够,还得做一次行为验证。方法很简单:开一个新的对话会话,把还原出的提示词放在system角色里,然后把真实的用户问题原样发一遍,对比原产品对同样问题的回答风格、长度、语气、结构。如果两者高度接近,说明还原基本成功;如果差异很大,说明提示词可能缺了某段关键约束,或者多了不该有的内容。
做验证时要控制变量:同一个问题、同一个模型版本、没有额外上下文,这样比较才有意义。我在实践中发现,还原是否完整,最明显的差异体现在“输出结构调整上”和“边界情况处理上”。原产品面对敏感提问时有一套标准的拒绝话术,如果还原版的提示词复现不出这个话术,说明安全规则这部分没抓全。这时候就得回头再搜一轮,重点找包含“如果”“当”“不得”这类条件表达的文本。
6. 常见问题与避坑实录
技术方案讲得再好,落地时总会有各种“意外”。这些坑我在不同项目里反复踩过,整理成一份问题速查,希望能帮大家少走弯路。
6.1 提示词根本不在前端
最常见的失败案例是:抓包看到了请求,但请求里没有提示词,只有用户输入和一个会话ID。这说明提示词存在服务端,前端完全接触不到。遇到这种情况,我的建议是不要硬碰硬去猜服务端逻辑,而是尝试Web端的另一条路——看初始化请求。很多产品在创建会话时会请求一个初始化接口,接口可能返回给前端一份公开的“行为概要”或“开场白配置”,里面往往带有提示词的浓缩版本,或者完整版本。
另一个思路是研究“前置提示”。有些产品会用一个隐藏的system消息来约束所有回复,但它不是实时发送的,而是保存在服务端绑定到会话ID上。这种情况下,网络抓包看不到,前端代码也搜不到。能做的就是从行为层面反推:通过多组不同输入的输出差异,反推出约束条件。这个方法效率低,但确实是合法研究里最后的兜底方案。
6.2 抓包看到一堆乱码
抓包代理打开HTTPS请求,结果请求体是二进制或一堆不可读字符。大概率是应用启用了自定义编码或压缩。解决方案分两步:先看Content-Encoding响应头,如果是gzip、br、deflate,配置抓包工具自动解压即可;如果看到请求体是protobuf或MessagePack这类二进制格式,那就需要用对应工具解码。还有少数产品会把请求体整体做一层Base64编码,直接复制到终端里解码就能看到明文。
对桌面应用来说,乱码还有一种可能:应用没有走代理,抓包工具看到的只是其他进程的流量。这时检查一下应用是否配置了代理环境变量,或者是否强制使用系统代理。实在不行就换一种抓包方式,直接抓Loopback接口的原始收发包,配合协议解析工具来处理。
6.3 提取的内容不完整
提示词只截到了一半。这种情况通常有两个原因:一是流式响应被中断,提示词本身在首次对话请求中全部发送,但如果你是在流式输出的中途停止抓包,响应侧的内容就不全了。解决办法是抓完整的一次请求-响应周期,不要中途停。二是提示词采用分段懒加载,应用只在用户用到某个高级功能时才把对应提示词注入对话。处理方式是先把所有功能入口都点一遍,再回头去抓包或搜索,确保提示词的每一段都被触发过。
还有一种隐蔽的不完整:提示词里引用了外部变量,比如“请参考{{task}}模板”但真正的内容不在提示词文本里,而在一个单独配置中。遇到这种,把提示词和配置一起抓下来,才能拼出全貌。我在实操里养成了一个习惯:凡是提取到的提示词,除了记录文本本身,还会记录它出现的环境上下文,比如所在的函数、请求URL、触发条件。这么做的好处是后续拼接时我能快速定位缺口在哪。
6.4 工具、习惯与伦理清单
最后整理一下我的常用工具链和习惯。Web端:浏览器的开发者工具全家桶、一个顺手的IDE、一个支持正则全文搜索的桌面工具。桌面端:一个代理抓包工具、asar解包命令行工具、一个进程内存字符串提取工具。这些工具全是通用技术工具,合法合规,唯一要注意的是不要把它们用在未经授权的目标上。
再说说习惯。第一,工作前先记录版本:页面版本、应用版本、抓包时间。不同版本的提示词很可能不同,没有版本记录,过段时间再看还原结果会非常困惑。第二,提取结果要结构化存档,用Markdown维护一个“提示词样本库”。第三,定期复盘:每还原一个项目,写几百字总结,提炼它提示词设计里值得借鉴的三个点。这个习惯坚持半年,你对提示词设计的理解会上一个层次。
合规层面再强调一次:只研究自己有权访问的产品,不传播完整的提取内容,不用于恶意目的。做技术研究,底线永远比上限重要。
我个人在实际操作中的体会是,大部分AI产品的提示词保护其实没有想象中那么强,很多所谓“加密”只是Base64和字符串拼接,真正难的往往不是解密,而是从海量代码里精准定位到那一段文本。跑完几十个项目之后,我更强烈的感受是:逆向工程的最大价值不在“抄到一句提示词”,而在于看到不同团队如何把抽象的产品需求翻译成模型能听懂的语言——那份翻译的功力,才是真正值得反复揣摩的东西。下次遇到让你眼前一亮的AI产品,不妨自己动手拆一遍,你会发现这件事比单纯刷产品评测有趣得多。