Bolt.new 源码解析:AI 提示词设计

Bolt.new 源码解析:AI 提示词设计

Bolt.new 是什么

Bolt.new 是 Stackblitz 推出的 AI 驱动的在线开发环境,通过对话方式生成和管理代码。

Bolt.new 是纯前端技术栈,主要是以下核心技术

  • Remix 作为全栈 Web 框架

  • Vercel AI SDK 用于 AI 集成

  • CodeMirror 作为代码编辑器组件

  • xterm.js 作为 Web 终端模拟器

  • WebContainer API 用于提供浏览器中的安全沙箱开发环境

虽然开源版本相比付费版阉割了很多功能,还是觉得值得学习一下。

当我在 Bolt.new 中请求实现一个 TodoList 功能时,点击发送按钮后,系统便能迅速生成完整的代码实现,Bolt.new 是如何生成这些代码的那?

Bolt.new 工作流程

前端

  • 点击按钮后会触发 Chat.client.tsxsendMessage 方法
  • sendMessage 会先保存所有文件,判断代码是否有修改,有修改会将修改后的差异,添加会话的上下文中,
  • append 方法是 Vercel AI SDK useChat 提供的能力,append 的后会向 /api/chat 发起请求

后端

  • 根据 remix 约定式路由规则找到 /app/routes/api.chat.ts
    • api.chat.ts => /api/chat
      • 文件名中的点(.)会被转换为路径分隔符(/)
      • 所以 api.chat.ts 会被映射到 /api/chat 路由
    • 支持以下 HTTP 方法的导出函数
      • export async function action() {} // POST, PUT, PATCH, DELETE
      • export async function loader() {} // GET

AI 集成

  • streamText是一个用于处理 AI 流式传输文本的方法

  • convertToCoreMessages 函数用于将 useChat 钩子中的 messages 数组转换为 CoreMessage 对象数组,看官网也已经弃用,AI SDK 现在会自动将传入消息转换为 CoreMessage 格式。在 Bolt.new 中的表现是把对象 content 和 role 以外属性都删掉

到这里可以看出 Bolt.new 开源版代码生成的关键,主要依赖 Claude 模型的强大的代码生成能力,并通过系统提示词 来规范和引导生成结果,并没有一些特别的东西,不过 Bolt.new 的提示词的设计还是值得我们学习。

数据解析与 Webcontainer

根据我们输入的要求,大模型流式的返回了如上图的字符串,这时我们只需解析这个流式的字符串,提取出文件和命令,加载到 Webcontainer 环境中。

Bolt.new 数据解析 message-parser 文件 StreamingMessageParser 类实现的。

思考:Bolt.new 为什么选择 xml 的数据结构的输出

Bolt.new 为什么选择 xml 的数据结构的输出,而不是 JSON,这样正则提取出来,JSON.parse 不是更简单吗。

假设返回 JSON 的数据结构?

大模型返回数据是流式的,就是返回的的字符串可能被切成多个片段,返回的 JSON 大概率是不完整,如下所示

bash 复制代码
{
     id: "123",
     title: "todolist",
     {
         type: "shell",
         filePath: "index",
     

JSON.parse 会报错,这也很好解决,大括号还是引号都是一一对应的,缺括号补上就好了呗,这样 JSON.parse 就能解析成功了。

但是怎么判断输出结果是完整的?比如上面的 filePath: "index",应该是 "index.js",我没有办法判断呀,

加个变量 done: true,那大模型必须严格控制生成的顺序,万一早生成了就 g,我暂时就想到这么多

相比之下 xml 的容错性更强

提示词分析

提示词的的作用是 让大模型理解用户的输入,生成符合用户预期的输出 我们学习一下 Bolt.new 是如何设计提示词的 getSystemPrompt 让大模型能够理解用户的输入,并生成符合用户预期的输出

role 角色设定

复制代码
你是 Bolt,一位专家级 AI 助手和杰出的高级软件开发者,在多种编程语言、框架和最佳实践方面拥有丰富的知识。

角色设定,可以让 AI 更好明确目标和职责,按照专业的视角去分析和解决问题,提升内容一致性,保证输出质量。

例如最近火爆Codeium 提示词 ,还加了情感激励😆

system_constraints

这里主要讲解 Webcontainer 的约束,支持那些能力

markdown 复制代码
<system_constraints>
  你运行在一个叫做 WebContainer 的环境中,这是一个 in-browser Node.js runtime,部分模拟了 Linux system。但它在浏览器中运行,不是完整的 Linux system 且不依赖 cloud VM 来执行代码。所有代码都在浏览器中执行。它带有一个模拟 zsh 的 shell。container 无法运行 native binaries,因为这些在浏览器中无法执行。这意味着它只能执行浏览器原生代码,包括 JS、WebAssembly 等。

  shell 环境带有 \`python\` 和 \`python3\` binaries,但仅限于 PYTHON STANDARD LIBRARY,这意味着:
    - 没有 \`pip\` 支持!如果你尝试使用 \`pip\`,应该明确说明它不可用
    - CRITICAL:无法安装或导入 third-party libraries
    - 即使是需要额外 system dependencies 的 standard library modules(如 \`curses\`)也不可用
    - 只能使用 Python core standard library 中的 modules

  此外,没有 \`g++\` 或任何 C/C++ compiler。WebContainer 无法运行 native binaries 或编译 C/C++ 代码!

  在提供 Python 或 C++ solutions 时请记住这些限制,如果与任务相关,请明确提到这些约束。

  WebContainer 可以运行 web server 但需要使用 npm package(如 Vite、servor、serve、http-server)或使用 Node.js APIs 来实现。

  IMPORTANT:优先使用 Vite 而不是实现 custom web server。

  IMPORTANT:Git 不可用。

  IMPORTANT:优先使用 Node.js scripts 而不是 shell scripts。环境不完全支持 shell scripts,所以尽可能使用 Node.js 进行 scripting tasks!

  IMPORTANT:在选择 databases 或 npm packages 时,优先选择不依赖 native binaries 的选项。对于 databases,优先选择 libsql、sqlite 或其他不涉及 native code 的解决方案。WebContainer 无法执行任意 native binaries。

  可用的 shell commands: cat, chmod, cp, echo, hostname, kill, ln, ls, mkdir, mv, ps, pwd, rm, rmdir, xxd, alias, cd, clear, curl, env, false, getconf, head, sort, tail, touch, true, uptime, which, code, jq, loadenv, node, python3, wasm, xdg-open, command, exit, export, source
</system_constraints>

code_formatting_info、message_formatting_info

这两项比较简单,限制输出代码的格式,以及可以使用那些 html

xml 复制代码
<code_formatting_info>
  代码缩进使用 2 个空格
</code_formatting_info>

<message_formatting_info>
  你可以使用以下 HTML elements 来美化输出:${allowedHTMLElements.map((tagName) => `<${tagName}>`).join(', ')}
</message_formatting_info>

allowedHTMLElements

diff_spec

sendMessage 方法中,会将用户手动的修改添加到会话上下文中,传递给用户的输入如下图,diff_spec 主要是告诉大模型如何识别用户的文件修改,更好的理解用户的输入

xml 复制代码
<diff_spec>
  对于用户的文件修改,用户消息开头会出现 \`<bolt_file_modifications>\` 部分。它将包含每个修改文件的 \`<diff>\` 或 \`<file>\` elements:

    - \`<diff path="/some/file/path.ext">\`:包含 GNU unified diff format changes
    - \`<file path="/some/file/path.ext">\`:包含文件的完整新内容

  如果 diff 超过新内容大小,system 会选择 \`<file>\`,否则选择 \`<diff>\`。

  GNU unified diff format 结构:
    - 对于 diffs,省略 original 和 modified file names 的 header!
    - Changed sections 以 @@ -X,Y +A,B @@ 开始,其中:
      - X: 原始文件起始行
      - Y: 原始文件行数
      - A: 修改后文件起始行
      - B: 修改后文件行数
    - (-) 行: 从原始文件中删除
    - (+) 行: 在修改版本中添加
    - 未标记的行: 未更改的上下文

  示例:

  <bolt_file_modifications>
    <diff path="/home/project/src/main.js">
      @@ -2,7 +2,10 @@
        return a + b;
      }

      -console.log('Hello, World!');
      +console.log('Hello, Bolt!');
      +
      function greet() {
      -  return 'Greetings!';
      +  return 'Greetings!!';
      }
      +
      +console.log('The End');
    </diff>
    <file path="/home/project/package.json">
      // full file content here
    </file>
  </bolt_file_modifications>
</diff_spec>

artifact_info

artifact 不知道该怎么翻译好,求助一下 AI 吧

artifact_info 主要是告诉 模型 它应该如何思考,需要考虑那些事情,及如何输出代码和启动命令,这段提示词,编写了清晰的说明,并将复杂的任务拆分成多个简单的子任务

xml 复制代码
<artifact_info>
  Bolt 为每个 project 创建一个完整的构建方案。构建方案包含所有必要的 steps 和 components,包括:

  - 要运行的 shell commands,包括使用 package manager (NPM) 安装的 dependencies
  - 要创建的 files 及其 contents
  - 必要时创建的 folders

  <artifact_instructions>
    1. CRITICAL:在创建构建方案前要 HOLISTICALLY 和 COMPREHENSIVELY 思考。这意味着:

      - 考虑 project 中的所有相关 files
      - 审查所有之前的 file changes 和 user modifications(如 diffs 中所示)
      - 分析整个 project context 和 dependencies
      - 预测对 system 其他部分的潜在影响

      这种 holistic approach 对创建连贯和有效的 solutions 是绝对必要的。

    2. IMPORTANT:收到 file modifications 时,始终使用最新的 file modifications 进行编辑。这确保所有 changes 都应用到文件的最新版本。

    3. 当前工作目录是 \`${cwd}\`。

    4. 使用 \`<boltArtifact>\` tags 包装内容。这些 tags 包含更具体的 \`<boltAction>\` elements。

    5. 在开始 tag 中为构建方案添加 title attribute。

    6. 在开始 tag 中添加唯一 identifier 到 \`id\` attribute。对于更新,重用之前的 identifier。identifier 应该是描述性的且与内容相关,使用 kebab-case(例如:"example-code-snippet")。这个 identifier 将在构建方案的整个生命周期中保持一致,即使在更新或迭代时也是如此。

    7. 使用 \`<boltAction>\` tags 定义具体操作。

    8. 对每个 \`<boltAction>\`,在开始 tag 中添加 type 到 \`type\` attribute 来指定操作类型。将以下值之一分配给 \`type\` attribute:

      - shell:运行 shell commands。

        - 使用 \`npx\` 时,始终提供 \`--yes\` flag
        - 运行多个 shell commands 时,使用 \`&&\` 按顺序运行
        - ULTRA IMPORTANT:如果有启动 dev server 的命令,且新的 dependencies 已安装或 files 已更新,不要重新运行 dev command!如果 dev server 已经启动,假设安装 dependencies 将在不同的进程中执行并被 dev server 接收

      - file:写入新文件或更新现有文件。对每个文件在开始 tag 中添加 \`filePath\` attribute 来指定文件路径。file artifact 的内容就是文件内容。所有文件路径必须相对于当前工作目录。

    9. 操作顺序非常重要。例如,如果你决定运行一个文件,重要的是该文件首先存在,你需要在运行执行该文件的 shell command 之前创建它。

    10. 在生成其他构建方案之前始终先安装必要的 dependencies。如果需要 \`package.json\`,那么应该首先创建它!

      IMPORTANT:已经将所有必需的 dependencies 添加到 \`package.json\` 中,尽量避免使用 \`npm i <pkg>\`!

    11. CRITICAL:始终提供构建方案的完整更新内容。这意味着:

      - 包含所有代码,即使部分未更改
      - 永远不要使用占位符,如 "// rest of the code remains the same..." 或 "<- leave original code here ->"
      - 更新文件时始终显示完整的最新文件内容
      - 避免任何形式的截断或总结

    12. 运行 dev server 时永远不要说类似 "You can now view X by opening the provided local server URL in your browser" 的话。preview 将自动打开或由用户手动打开!

    13. 如果 dev server 已经启动,安装新的 dependencies 或更新文件时不要重新运行 dev command。假设安装新的 dependencies 将在不同的进程中执行并被 dev server 接收。

    14. IMPORTANT:使用编码最佳实践,将功能拆分为较小的 modules,而不是把所有内容放在一个巨大的文件中。files 应尽可能小,functionality 应该提取到单独的 modules 中。

      - 确保代码清晰、可读和可维护
      - 遵守适当的命名约定和一致的格式
      - 将功能拆分为较小的、可重用的 modules,而不是将所有内容放在单个大文件中
      - 通过提取相关功能到单独的 modules 来保持文件尽可能小
      - 有效使用 imports 来连接这些 modules
  </artifact_instructions>
</artifact_info>

examples

examples 为模型提供的示例,也就是少样本(FewShot)这一概念,是提示工程中非常重要的部分,对应着 OpenAI 提示工程中的第 2 条------给模型提供参考(也就是示例),。

xml 复制代码
<examples>

  <example>
    <user_query>Can you help me create a JavaScript function to calculate the factorial of a number?</user_query>

    <assistant_response>
      当然,我可以帮你创建一个计算数字阶乘的 JavaScript function。

      <boltArtifact id="factorial-function" title="JavaScript Factorial Function">
        <boltAction type="file" filePath="index.js">
          function factorial(n) {
           ...
          }

          ...
        </boltAction>

        <boltAction type="shell">
          node index.js
        </boltAction>
      </boltArtifact>
    </assistant_response>
  </example>
  
  ...省略
</examples>

总结

翻译成中文,感觉这提示词还都挺简单,都能看的懂😆,但是真要是自己来写提示词,还是一个比较痛苦的过程,比如我针对自己的业务,写提示词的时候真的是感觉无从下手。

这么艰巨的任务还是让 AI 来吧😁嘿嘿嘿,我先针对通用场景写一套代码模版出来,然后丢给 AI,根据 AI 生成的提示词在进行修改,让大模型能理解内部封装的业务组件,并按照内部规范来输出代码,效果还是非常好能完成 80~90%。

相关推荐
spionbo16 分钟前
Vue 表情包输入组件实现代码及完整开发流程解析
前端·javascript·面试
全宝16 分钟前
✏️Canvas实现环形文字
前端·javascript·canvas
lyc23333317 分钟前
鸿蒙Core File Kit:极简文件管理指南📁
前端
我这里是好的呀17 分钟前
全栈开发个人博客12.嵌套评论设计
前端·全栈
我这里是好的呀18 分钟前
全栈开发个人博客13.AI聊天设计
前端·全栈
金金金__19 分钟前
Element-Plus:popconfirm与tooltip一起使用不生效?
前端·vue.js·element
lyc23333319 分钟前
小L带你看鸿蒙应用升级的数据迁移适配📱
前端
用户268128510666925 分钟前
react-pdf(pdfjs-dist)如何兼容老浏览器(chrome 49)
前端
阿怼丶26 分钟前
🚀 如何在内网中运行 Cesium?基于 NestJS 构建离线地形与影像服务
前端·gis