用 AI 反向代码案例:揭秘 OpenAI Canvas 如何根据用户操作拼接生成 Prompt
用一个实例来看看如何借助 AI 来反向代码,帮助你更好的理解一些商业产品功能的实现。这里以 OpenAI 新推出的 Canvas 为例,我们看看它是如何根据用户的操作生成不同 Prompt 的。
第一步先找到相应的代码模块
打开 Chrome Dev Tool,监听 Network Request,在操作 Cavans 时,你会发现它会根据你的操作拼接了一段 Prompt:
# Context
The user requests that you directly edit the document.
## Selected Text
The user has selected this text in "670415fa16b4819198e7ff0bdb23866a" in particular:
## Surrounding Context
Here is the surrounding context:
.............
# Instructions
Use the update_textdoc tool to make this edit. You MUST fully rewrite the entire document by using ".*" as the update regex pattern.
可以看得出这部分 Prompt 是在客户端拼接的,接下来我们来尝试通过关键词搜索找到这部分代码在哪个文件:
切换 Chrome Dev Tool 到 Sources Tab,在左侧导航的 top 上点右键,选“Search in all files”
选一段 Prompt 中用到的关键词,比如# Context
去搜索:
这样我们就找到了生成 Prompt 部分的源码所在文件。
第二步复制代码去 LLM 提问或者还原代码
取决于代码体积,如果 1000 行以内的代码可以直接要求还原成可读性高的代码,如果超过 1000 行可以基于某个模块提问,完整还原可能有点困难。要还原代码可以参考《分享一点前沿 AI 用法:用 o1 逆向代码》:https://baoyu.io/blog/ai/o1-cutting-edge-ai-use-reverse-engineering-code
这里代码有差不多 2000 行,我只要还原我关心的构建 Prompt 部分代码就好。
下面是一段混淆后的 JavaScript 代码,请先阅读代码,然后将根据用户操作拼接 Prompt,并发送请求处理返回结果的相关代码还原成类型定义完整的 TypeScript 代码,要求: - 命名友好,类型完整,不要使用 any 类型 - 加上适当的中文注释 - 下面代码中引用了一些外部方法,不需要生成这些外部方法,只需要注释中注明即可
以下是完整代码,请生成拼接 Prompt和发送请求的TypeScript代码:
然后你就可以得到完整的构建 Prompt 的代码:
现在比起之前那一堆乱码是不是看着舒服多了?
第三步:进一步追问帮助你理解或者生成更多内容
如果你觉得代码还是看不太明白,可以继续提要求,比如画个流程图:
现在请将这部分代码的逻辑,生成一份架构图和流程图,方便理解它是如何运行的
这图虽然 ASCII 画的,但还是挺直观的!
附录
返回的代码:
// 定义用户操作类型枚举
enum UserActionType {
ASK = 'ASK', // 提问
EDIT = 'EDIT', // 编辑
COMMENT = 'COMMENT', // 评论
CREATE_TEXTDOC = 'CREATE_TEXTDOC', // 创建文本文档
}
// 定义用户消息类型枚举
enum UserMessageType {
ASK_CHATGPT = 'ASK_CHATGPT', // 向 ChatGPT 提问
ACCEPT_COMMENT = 'ACCEPT_COMMENT', // 接受评论
FULL_SCREEN_SUBMIT = 'FULL_SCREEN_SUBMIT', // 全屏提交
ACCELERATOR = 'ACCELERATOR', // 加速器
}
// 定义选择类型枚举
enum SelectionType {
ENTIRE_DOCUMENT = 'entire document', // 整个文档
SELECTED_TEXT = 'selected text', // 选中文本
SURROUNDING_CONTEXT = 'surrounding context', // 周围上下文
}
// 定义文本文档类型枚举
enum TextdocType {
DOCUMENT = 'document', // 文档类型
// 其他类型...
}
// 定义文本选择范围接口
interface SourceRange {
start: number; // 选择起始位置
end: number; // 选择结束位置
}
// 定义文本文档版本接口
interface TextdocVersion {
textdocId: string; // 文档 ID
type: TextdocType; // 文档类型
versionInt: number; // 版本号
content: string; // 文档内容
}
// 定义加速器元数据接口
interface AcceleratorMetadata {
action: UserActionType; // 动作类型
id: string; // 加速器 ID
prompt: string; // 加速器提示
}
// 定义选择元数据接口
interface SelectionMetadata {
selection_type: SelectionType; // 选择类型
selection_position_range: SourceRange; // 选择的位置范围
}
// 定义请求参数接口
interface RequestParams {
content: string; // 用户输入内容
sourceRange?: SourceRange; // 文本选择范围(可选)
action: UserActionType; // 用户操作类型
userMessageType: UserMessageType; // 用户消息类型
acceleratorMetadata?: AcceleratorMetadata; // 加速器元数据(可选)
selectionMetadata?: SelectionMetadata; // 选择元数据(可选)
}
// 定义周围上下文接口
interface SurroundingContext {
before: string; // 选中内容前的文本
after: string; // 选中内容后的文本
allSurrounding: string; // 包含选中内容的完整上下文
}
// 生成提示的主函数,根据用户操作类型生成对应的提示
function generatePrompt(
textdocId: string,
textdocType: TextdocType,
action: UserActionType,
selectedText?: string,
surroundingContext?: SurroundingContext,
): string {
switch (action) {
case UserActionType.ASK:
return generateAskPrompt(textdocId, textdocType, selectedText, surroundingContext);
case UserActionType.EDIT:
return generateEditPrompt(textdocId, textdocType, selectedText, surroundingContext);
case UserActionType.COMMENT:
return generateCommentPrompt(textdocId, selectedText, surroundingContext);
case UserActionType.CREATE_TEXTDOC:
return generateCreateTextdocPrompt(textdocId, selectedText, surroundingContext);
default:
return '';
}
}
// 生成提问操作的提示
function generateAskPrompt(
textdocId: string,
textdocType: TextdocType,
selectedText?: string,
surroundingContext?: SurroundingContext,
): string {
const selectionDescription = describeSelection(selectedText, surroundingContext);
return `
# Context
${generateContext(textdocId, selectedText, surroundingContext)}
# Instructions
The user would like you to perform one of the following actions:
- Update the ${selectionDescription} via the \`update_textdoc\` tool.
${additionalInstructions(textdocType, selectionDescription)}
- Explain the selected text via chat, or answer a general question about the selected text (no tool call required).
- Comment on the ${selectionDescription} with feedback via the \`comment_textdoc\` tool. This should only be used if the user very explicitly asks for feedback, critique, or comments.
Based on the user's request, choose the appropriate action.
`.trim();
}
// 生成编辑操作的提示
function generateEditPrompt(
textdocId: string,
textdocType: TextdocType,
selectedText?: string,
surroundingContext?: SurroundingContext,
): string {
const selectionDescription = describeSelection(selectedText, surroundingContext);
const updateInstructions = generateUpdateInstructions(textdocType, selectionDescription);
return `
# Context
The user requests that you directly edit the document.
${describeSelectionInContext(textdocId, selectedText, surroundingContext)}
# Instructions
Use the \`update_textdoc\` tool to make this edit. ${updateInstructions}
`.trim();
}
// 生成评论操作的提示
function generateCommentPrompt(
textdocId: string,
selectedText?: string,
surroundingContext?: SurroundingContext,
): string {
return `
# Context
The user requests that you add comments about some text.
${describeSelectionInContext(textdocId, selectedText, surroundingContext)}
# Instructions
Do not respond to the user's question directly, just leave comments.
`.trim();
}
// 生成创建文本文档操作的提示
function generateCreateTextdocPrompt(
textdocId: string,
selectedText?: string,
surroundingContext?: SurroundingContext,
): string {
return `
# Context
${describeSelectionInContext(textdocId, selectedText, surroundingContext)}
# Instructions
The user would like you to create a new textdoc.
`.trim();
}
// 生成上下文信息
function generateContext(
textdocId: string,
selectedText?: string,
surroundingContext?: SurroundingContext,
): string {
return `
The user is requesting that you directly edit the document.
${describeSelectionInContext(textdocId, selectedText, surroundingContext)}
`.trim();
}
// 描述选择的文本或上下文
function describeSelectionInContext(
textdocId: string,
selectedText?: string,
surroundingContext?: SurroundingContext,
): string {
if (!selectedText || !surroundingContext) {
return `The user is referring to the entire text of "${textdocId}".`;
} else if (surroundingContext.allSurrounding === selectedText) {
return `
Selected Text
The user has selected this text in "${textdocId}" in particular:
${selectedText}
`.trim();
} else {
return `
Selected Text
The user has selected this text in "${textdocId}" in particular:
${selectedText}
Surrounding Context
Here is the surrounding context:
${surroundingContext.allSurrounding}
`.trim();
}
}
// 描述选择类型
function describeSelection(selectedText?: string, surroundingContext?: SurroundingContext): string {
if (!selectedText || !surroundingContext) {
return 'entire document';
} else if (selectedText === surroundingContext.allSurrounding) {
return 'selected text';
} else {
return 'surrounding context';
}
}
// 生成更新指令的附加说明
function additionalInstructions(textdocType: TextdocType, selectionDescription: string): string {
if (textdocType === TextdocType.DOCUMENT) {
if (selectionDescription === 'entire document') {
return `If you choose to update the ${selectionDescription}, you MUST fully rewrite the ${selectionDescription} by using "." as the update regex pattern.`;
} else {
return `If you choose to update the ${selectionDescription}, you MUST fully rewrite the entire document by using "." as the update regex pattern. When you do so, ONLY modify the ${selectionDescription} and rewrite other sections exactly as is, except for parts that must change based on this update.`;
}
}
return '';
}
// 生成编辑操作的更新指令
function generateUpdateInstructions(textdocType: TextdocType, selectionDescription: string): string {
if (textdocType === TextdocType.DOCUMENT) {
return `For the update pattern, create a regex which exactly matches the ${selectionDescription}. Edit just this string in order to fulfill the user's request. NEVER rewrite the entire document. Instead, ALWAYS edit ONLY the ${selectionDescription}. The only exception to this rule is if the ${selectionDescription} includes markdown lists or tables. In that case, fully rewrite the document using ".*" as the regex update pattern.`;
}
return '';
}
// 定义发送请求的函数(假设有一个外部方法 sendPromptRequest)
function sendPromptRequest(prompt: string): Promise<any> {
// TODO: 调用发送请求的外部方法(此处仅为示例,实际实现中需要调用真实的请求方法)
// 这里只需要注明有一个外部方法用于发送请求,不需要实现
return Promise.resolve();
}
// 主函数,处理用户操作
function handleUserOperation(params: RequestParams, textdocVersion: TextdocVersion) {
const {
content,
sourceRange,
action,
userMessageType,
acceleratorMetadata,
selectionMetadata,
} = params;
if (!textdocVersion || textdocVersion.versionInt == null) {
return;
}
const { textdocId, type, versionInt, content: docContent } = textdocVersion;
let selectedText: string | undefined;
let surroundingContext: SurroundingContext | null = null;
// 如果有选择范围,提取选中文本和上下文
if (sourceRange) {
selectedText = docContent.slice(sourceRange.start, sourceRange.end);
surroundingContext = extractSurroundingContext(sourceRange, docContent);
}
// 生成提示
const prompt = generatePrompt(textdocId, type, action, selectedText, surroundingContext);
// 调用发送请求的函数
sendPromptRequest(prompt).then((result) => {
// 处理返回结果(这里不需要实现)
// TODO: 在这里处理返回结果
});
}
// 提取周围的上下文
function extractSurroundingContext(sourceRange: SourceRange, content: string): SurroundingContext {
// 这里使用简单的逻辑提取上下文,可以根据需要调整
const contextLength = 30; // 上下文长度,可根据需要调整
const beforeStart = Math.max(0, sourceRange.start - contextLength);
const afterEnd = Math.min(content.length, sourceRange.end + contextLength);
const before = content.slice(beforeStart, sourceRange.start);
const after = content.slice(sourceRange.end, afterEnd);
const allSurrounding = before + content.slice(sourceRange.start, sourceRange.end) + after;
return { before, after, allSurrounding };
}
架构图
该代码的架构主要由以下几个部分组成:
枚举类型(Enums):定义了用户操作类型、消息类型、选择类型和文本文档类型等。
接口(Interfaces):定义了源代码范围、文本文档版本、加速器元数据、选择元数据等数据结构。
主要函数(Functions):
handleUserOperation
:主函数,处理用户操作。generatePrompt
:根据用户操作类型生成对应的提示(Prompt)。各种辅助函数,用于生成特定操作的提示和描述。
外部方法:
sendPromptRequest
:发送生成的提示到外部服务进行处理(实际实现不在此代码中)。
下面以文字和简单的类图形式描述代码的架构:
类图
+-------------------+
| UserActionType |
+-------------------+
| + ASK |
| + EDIT |
| + COMMENT |
| + CREATE_TEXTDOC |
+-------------------+
+-------------------+
| UserMessageType |
+-------------------+
| + ASK_CHATGPT |
| + ACCEPT_COMMENT |
| + FULL_SCREEN_SUBMIT |
| + ACCELERATOR |
+-------------------+
+-------------------+
| SelectionType |
+-------------------+
| + ENTIRE_DOCUMENT |
| + SELECTED_TEXT |
| + SURROUNDING_CONTEXT |
+-------------------+
+-------------------+
| TextdocType |
+-------------------+
| + DOCUMENT |
| (other types...) |
+-------------------+
+-------------------+
| SourceRange |
+-------------------+
| - start: number |
| - end: number |
+-------------------+
+-------------------+
| TextdocVersion |
+-------------------+
| - textdocId: string |
| - type: TextdocType |
| - versionInt: number |
| - content: string |
+-------------------+
+-------------------+
| AcceleratorMetadata |
+-------------------+
| - action: UserActionType |
| - id: string |
| - prompt: string |
+-------------------+
+-------------------+
| SelectionMetadata |
+-------------------+
| - selection_type: SelectionType |
| - selection_position_range: SourceRange |
+-------------------+
+-------------------+ +-------------------+
| handleUserOperation | | generatePrompt |
+-------------------+ +-------------------+
| - params: RequestParams | - ... |
| - textdocVersion: TextdocVersion |
| |
| Calls generatePrompt() |
| Calls sendPromptRequest() |
+-------------------+ +-------------------+
流程图
下面是 handleUserOperation
函数的流程图,描述了代码处理用户操作的步骤。
+-------------------------------------------+
| handleUserOperation |
+-------------------------------------------+
|
v
+-------------------------------------------+
| 1. 从参数中解构出所需的字段: |
| - content |
| - sourceRange |
| - action |
| - userMessageType |
| - acceleratorMetadata |
| - selectionMetadata |
+-------------------------------------------+
|
v
+-------------------------------------------+
| 2. 检查 textdocVersion 是否有效 |
| - 如果无效,则返回 |
+-------------------------------------------+
|
v
+-------------------------------------------+
| 3. 提取选中的文本和周围上下文 |
| - 如果有 sourceRange: |
| - 从文档内容中提取 selectedText |
| - 调用 extractSurroundingContext() |
| 获取 surroundingContext |
| - 否则: |
| - selectedText 为 undefined |
| - surroundingContext 为 null |
+-------------------------------------------+
|
v
+-------------------------------------------+
| 4. 调用 generatePrompt() 生成提示 |
| - 参数包括: |
| - textdocId |
| - type |
| - action |
| - selectedText |
| - surroundingContext |
+-------------------------------------------+
|
v
+-------------------------------------------+
| 5. 调用 sendPromptRequest() 发送生成的提示 |
| - 发送 prompt 到外部服务 |
| - 处理返回结果(此处未实现) |
+-------------------------------------------+
代码逻辑说明
处理用户操作:
handleUserOperation
是主函数,负责处理用户的操作。它接受用户的请求参数和当前的文本文档版本。首先,它从参数中提取必要的信息,如内容、选择范围、用户操作类型等。
检查文本文档版本是否有效,如果无效则不进行后续操作。
如果用户有选中的文本(即提供了
sourceRange
),则从文档内容中提取选中的文本和周围的上下文信息。调用
generatePrompt
函数,根据用户的操作类型和选择的文本生成相应的提示(Prompt)。最后,调用
sendPromptRequest
函数,将生成的提示发送到外部服务进行处理。
生成提示:
generatePrompt
函数根据用户的操作类型,调用对应的生成函数,如generateAskPrompt
、generateEditPrompt
等。每个生成函数都会根据传入的参数,生成特定格式的提示,包含上下文和指令信息。
辅助函数:
describeSelectionInContext
:用于描述用户选中的文本和上下文,生成上下文部分的内容。extractSurroundingContext
:根据用户选中的文本位置,从文档内容中提取前后的上下文,以便在提示中提供更完整的信息。additionalInstructions
和generateUpdateInstructions
:根据文本文档的类型和选择的内容,生成额外的指令,指导模型如何处理用户的请求。
外部方法:
sendPromptRequest
:负责将生成的提示发送到外部服务,例如 OpenAI 的 API,以获取模型的响应。该函数在代码中未实现,标注为需要外部实现的方法。
通过以上架构图和流程图,可以清晰地理解代码的整体结构和运行流程。代码主要是根据用户的操作类型和选中的文本,生成相应的提示,发送到外部服务进行处理,然后处理返回的结果。