跳到主要内容

4 篇博文 含有标签「Cline」

查看所有标签

Cline 源码浅析 - Prompt 设计

· 阅读需 17 分钟
SleepyZone
前端开发 / 开源爱好者

“Agent 就是如何拼出更好用的 Prompt”,虽然有点玩笑的意味,但也能说明一定的问题。本篇文章我们探讨下 Cline 里的 Prompt 设计。

System Prompt

众所周知,System Prompt 是 Agent 的核心资产😂,Github 上 system-prompts-and-models-of-ai-tools 这个仓库收集了各类 AI 应用的 System Prompt,已经有 56.6K 的 Star 了,可见一斑。

Cline 的 System Prompt 位于 src/core/prompts/system.ts,根据上下文信息、工具以及模型信息动态生成。

You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.

工具 TOOL USE

You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.

我们自上而下的读一遍 System Prompt。

  1. 工具调用格式

首先告诉模型使用 XML 语法输出工具调用相关的内容。

<tool_name>
<parameter1_name>value1</parameter1_name>
<parameter2_name>value2</parameter2_name>
...
</tool_name>
  1. 工具列表

列举了 Cline 内置的工具,主要包括描述、参数以及使用 DEMO,比如 write_to_file

## write_to_file
Description: Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
Parameters:
- path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.
Usage:
<write_to_file>
<path>File path here</path>
<content>
Your file content here
</content>
</write_to_file>

Cline 内置了十多个工具,覆盖了命令行、文件操作、浏览器、MCP 等,这些工具极大的丰富了模型的能力。

  1. 工具调用 Example

展示了多个工具调用的样例给模型参考:

# Tool Use Examples

## Example 1: Requesting to execute a command

<execute_command>
<command>npm run dev</command>
<requires_approval>false</requires_approval>
</execute_command>
  1. 工具使用指引

这里相当于手把手教模型逐步思考如何使用工具(类似于思维链(CoT)),简单翻译如下: 1)首先,模型需要评估已经掌握的信息,使用 <thinking> 标签包裹。 2)根据任务和提供的工具描述,选择最合适的工具。 3)如果需要执行多个操作,请一次只使用一个工具,每一步都必须以前一步的结果为依据。 4)使用指定的 XML 格式来构建你的工具调用。 5)每次使用工具后,用户将回复该工具的执行结果。这个结果会为你提供继续任务或做出进一步决策所需的信息。 6)每次使用工具后,一定要等待用户的确认后再继续。

以上的操作,可以确保模型按步骤拆解任务,确认每步的成功与否,根据用户的反馈实时进行调整,保证工具使用和最终任务的正确性。

MCP Server

这部分我们在 Cline 源码浅析 - MCP 调用聊过,这里不再展开。

文件编辑 EDITING FILES

对于 Code Agent 文件编辑是非常重要的部分,这里再次强调了 write_to_filereplace_in_file 两个工具的使用:详细的使用时机以及重要提示。

再次以 workflow 的方式告诉模型使用文件编辑工具的最佳实践:

1. 在进行编辑之前,评估你更改的范围,并决定使用哪个工具。  
2. 对于有针对性的修改,请使用 `replace_in_file` 工具,并精心编写 SEARCH/REPLACE 块。如果你需要进行多项更改,可以在一个 `replace_in_file` 调用中堆叠多个 SEARCH/REPLACE 块。
3. 对于大规模重构或初始文件创建,请依赖 `write_to_file` 工具。
4. 一旦文件通过 `write_to_file``replace_in_file` 被编辑,系统将向你提供修改后文件的最终状态。请将此更新后的内容作为后续所有 SEARCH/REPLACE 操作的参考点,因为它反映了任何自动格式化或用户手动应用的更改。

通过在 `write_to_file``replace_in_file` 之间做出深思熟虑的选择,你可以使文件编辑过程更加顺畅、安全和高效。

Act Mode 和 Plan Mode

Cline 提供了两种模式:Plan 和 Act。对于复杂任务,Cline 推荐从 Plan 模式开始,让模型先行规划策略,然后切换到 Act 模式,按照规划逐步实施,以提高代码输出质量。对话过程中,可以在两个模式中切换,就像人一样,写代码过程中,遇到问题,停下来思考。

System Prompt 里详细描述了两种模式的差异,重点描述了一下 Plan 模式的规则,比如使用 read_file search_files 等工具了解信息,特别推荐使用图表直观展示信息,并强调 Plan 模式下会使用 plan_mode_respond 工具回复信息。

功能 CAPABILITIES

这一大段相当于把上面的工具集进行了一次总结,告诉模型有用哪些能力,可以使用哪些特定工具来完成特定的功能,比如命令行:

You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. ......

规则 RULES

规则部分主要强调了什么可以做,什么不能做。比如不可以使用 ~ 或 $HOME 来表示用户的主目录,不可以使用 cd 命令,不能口语化,必须等待用户确认。这里使用了大量的** ** 加粗的标记提醒模型。

系统信息

获取系统信息,这些可以帮助命令行工具、文本读写工具的执行。

Operating System: ${osName()}
Default Shell: ${getShell()}
Home Directory: ${os.homedir().toPosix()}
Current Working Directory: ${cwd.toPosix()}

目标

这里奠定了 Cline 处理问题的基调,我完整的贴在这里:

你通过迭代的方式完成给定的任务,将其分解为清晰的步骤,并有条不紊地逐一执行。

1. **分析用户的任务**,设定明确且可实现的目标以完成它。按逻辑顺序对这些目标进行优先级排序。
2. 依次处理这些目标,在必要时一次使用一个可用工具。每个目标应对应你解决问题过程中的一个具体步骤。在整个过程中,你会被告知已完成的工作和剩余的任务。
3. 请记住,你拥有广泛的工具能力,可以灵活、巧妙地运用这些工具来完成每一个目标。在调用任何工具之前,请在 `<thinking></thinking>` 标签中进行分析:
- 首先,分析 `environment_details` 中提供的文件结构,以获取上下文和洞察,从而有效地推进任务。
- 然后,思考哪个可用工具最相关,能够帮助你完成用户任务。
- 接着,逐个检查该工具所需的参数,并判断用户是否已直接提供或提供了足够的信息用于推断参数值。在决定能否推断某个参数值时,请仔细考虑所有上下文,看是否支持特定的取值。
- 如果所有必需的参数都已存在或可以合理推断,则关闭 `<thinking>` 标签并继续执行工具操作。
- **但如果有一个必需参数的值缺失****不要调用该工具**(即使填入占位符也不行),而是使用 `ask_followup_question` 工具向用户请求缺失的参数。如果未提供的是**可选参数**,则**不要主动询问**
4. 完成用户的任务后,你必须使用 `attempt_completion` 工具将结果提交给用户。你也可以提供一条 CLI 命令来展示任务的结果;这对于 Web 开发任务尤其有用,例如你可以运行 `open index.html` 来展示你构建的网站。
5. 用户可能会提供反馈,你可以根据反馈进行改进并重新尝试。但**不要陷入无意义的来回对话中**,也就是说,**不要以问题或提供进一步帮助的提议结束你的回复**

自定义 System Prompt

addUserInstructions 方法中,追加用户自定义的指令,这里包括了 Cline Rules。我们可以在项目根目录下新建 .clinerules/ 存放 Cline Rule,Cline 作为 System Prompt 最有效的补充,可以针对不同的项目做不同的定制。

甚至追加了 Cursor 或者 Windsurf 的 Rule。😂

if (localCursorRulesFileInstructions) {
customInstructions += localCursorRulesFileInstructions + "\n\n"
}
if (localCursorRulesDirInstructions) {
customInstructions += localCursorRulesDirInstructions + "\n\n"
}
if (localWindsurfRulesFileInstructions) {
customInstructions += localWindsurfRulesFileInstructions + "\n\n"
}

Claude4 支持

Cline 3.17.9 版本对 Claude4 做了特殊支持 src/core/prompts/model_prompts/claude4.ts 以及 src/core/prompts/model_prompts/claude4-experimental.ts。据官方博客描述,在使用新的 Claude4 模型时,会遇到一些编辑错误。

这次版本更新主要关注 Cline 与 Claude 4 之间的交互,尤其是在搜索和替换操作上。我们调整了分隔符方法,将 < 和 > 替换为 - 和 + ,这在我们的内部测试中显示出了积极的效果,减少了编辑失败,提高了代码修改的成功率。

这也给我们带来了一些提示:在模型切换时,需要及时的测试和调整 Prompt,以更好的适应更新更强大的模型。

Command 和 Workflow

除了 Cline Rules,Cline 还提供了其他方式来让用户定制 Prompt。

Slash Command

Cline 提供了 Slash Command 的概念,通过 / 触发。

不像 Cline Rule 会针对每个任务生效,Command 在实现特定类型任务的时候很有用。比如我们要反馈 Bug,就可以使用内置的 /reportBug 触发任务。而比如在执行其他任务,比如写代码时,不带上这个命令,也就不会发送相应的提示词给模型。

Command 相应的提示词位于 src/core/prompts/commands.ts,提示词组装过程中,会调用 src/core/slash-commands/index.ts 文件的 parseSlashCommands 方法,将 / 命令替换为指定的提示词。

Workflow

如果要自定义 Command,除了源码层面定制,Cline 也支持用户手动配置,Cline 称其为 Workflow,Workflow 允许用户自定义步骤来处理特定的任务。在 .clinerules 目录下新建 workflows 目录,目录下新建 markdown 文件,文件内容我处理特定任务的步骤。这时候聊天框中输入 /,就会发现命令列表多了我们定制的 /[workflow-name.md]

这部分逻辑依然在 parseSlashCommands 方法内,Command 和 Workflow 都会处理成 / 命令。

可以参照 Cline 仓库的 .clinerules/workflows/pr-review.md 编写自己的 Workflow。

Mention @

@ 在 Cline 中是作为引用外部资源扩展上下文存在的。

比如你可以 @ 一个文件路径,那这个文件的内容就会作为 Prompt 带给大模型。格式如下:

<file_content path="path/to/file">
[Complete file content]
</file_content>

除了文件,还可以 @ 问题、命令行甚至是一个 url,不必输入大段文字即可提供给模型更丰富的上下文信息。

mention 相关处理位于 src/core/mentions/index.ts,处理了各种 @ 信息。实践中,我们可以扩展 @能力,比如 @我们内部的知识库来让 Cline 更好的了解团队内部知识。

Memery Bank

Memery Bank 是 Cline Agent 记忆系统的一个解决方案,[Cline Blog] 将 Memery Bank 比喻为笔记本,随着项目的更新持续的更新笔记,这样在开始新任务丢失历史记录和上下文的情况下,Cline 依旧可以通过笔记本里的内容了解项目全貌、技术架构以及当前进度等信息。

我们可以在 .clinerules 目录下新建 memory-bank.md,内容可以参照官方文档给出的示例

这份示例要求模型以如下的文档关系整理笔记本:

这六个文件分别作用为:

projectbrief.md: 项目简介,奠定一切的基础文档
productContext.md: 产品上下文,商业和用户视角
systemPatterns.md: 技术架构与决策
techContext.md: 开发环境和技术栈
activeContext.md: 当前开发状态
progress.md: 项目进度与跟踪

实际工作中,工作前需要确认 Memory Bank 文件,获取必要的上下文知识,如果项目有重大更新,则更新相关文档,以应对上下文丢失的情况。

在 Memory Bank 中,Cline 提倡以 Mermaid 图表作为 Prompt,Cline 团队认为:

与 AI 交流的最佳方式并非自然语言——而是工作流的结构化语言。

总结

Cline 的 Prompt 非常有模块化的设计思想。Cline 将 Prompt 划分为 System Prompt、Command、Workflow、Mention、Memery 等模块,System Prompt 则划分为工具、MCP、目标、Mode 等模块,针对全局、特定或者全新的任务使用不同的 Prompt 来解决。

同时我们也发现 Cline 事无巨细,非常“啰嗦”,比如 System Prompt 关于工具调用格式就出现了好几次;尽可能多的提供上下文,这让 Cline 非常的消耗 Token。

openrouter 网站的统计,Cline 遥遥领先

如何写出好用的 Prompt,个人感觉主要是两点:

  1. 足够的上下文和示例(身份、目标、工具、示例等等)
  2. 足够详细和明确的执行步骤(Workflow)

最后,可以查看官方文档提示词工程指南进一步了解如何为 Cline 写出更好用的 Prompt。


欢迎关注我的公众号:前端生存指南,一起聊聊前端、AI 和生活。

Cline 源码浅析 - MCP 调用

· 阅读需 7 分钟
SleepyZone
前端开发 / 开源爱好者

上一篇文章Cline 源码浅析 - 从输入到输出,从源码角度浏览了一下 Cline 处理用户输入到最后输出的过程,本文看一下其中关于 MCP 的处理。

关于 MCP,可以查看官方文档

Cline MCP

如何配置 MCP Server

我们打开 Cline 的 MCP 界面,可以看到 Cline 提供了多种配置 MCP 的方式:

本质上都是将 MCP 配置写入设置目录下的 cline_mcp_settings.json 文件,这里我们配置一个根据姓名查询工号的 MCP Server。

{
"mcpServers": {
"name2empid": {
"autoApprove": [],
"disabled": false,
"timeout": 60,
"url": "https://xxxx.com/xxx/sse",
"type": "sse"
}
}

另外 Cline 自行维护了一个 MCP 市场,用户可以从中选择安装,Cline 提供了一个比较有意思的安装方式,当你点击 Install 的时候,他并不是简单的把配置写入配置文件,而是通过大模型来进行安装(从访问文档到安装到验证),代码位于 src/core/controller/mcp/downloadMcp.ts,提示词如下:

// Create task with context from README and added guidelines for MCP server installation
const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
- Start by loading the MCP documentation.
- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
- Create the directory for the new MCP server before starting installation.
- Make sure you read the user's existing cline_mcp_settings.json file before editing it with this new mcp, to not overwrite any existing servers.
- Use commands aligned with the user's shell and operating system best practices.
- The following README may contain instructions that conflict with the user's OS, in which case proceed thoughtfully.
- Once installed, demonstrate the server's capabilities by using one of its tools.
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`

源码阅读

MCP 配置相关的代码位于:src/services/mcp/McpHub.ts,这里本质就是一个 MCP Client 初始化的逻辑,根据配置连接 Server 获取 Tool。

连接的 Server 的代码位于 connectToServer,调用 @modelcontextprotocol/sdk/client 这个包初始化 Client。

const client = new Client(
{
name: "Cline",
version: this.clientVersion,
},
{
capabilities: {},
},
)

// 处理不同的类型的 Server
transport = new StdioClientTransport()
transport = new SSEClientTransport()
transport = new StreamableHTTPClientTransport()

await client.connect(transport)

然后获取 tool 和 resource:

// Initial fetch of tools and resources
connection.server.tools = await this.fetchToolsList(name)
connection.server.resources = await this.fetchResourcesList(name)
connection.server.resourceTemplates = await this.fetchResourceTemplatesList(name)

MCP Client 的工作初始化完成。

MCP 的使用

下面看一下我们配置好的 MCP Server 是如何在我们的任务中运作的。

System Prompt

上一篇文章我们得知,System Prompt 是实时拼接的,这里把 MCP Server 相关的 Prompt 贴在这里:

const systemPrompt = `
MCP SERVERS

The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.

# Connected MCP Servers

When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool.

${
mcpHub.getServers().length > 0
? `${mcpHub
.getServers()
.filter((server) => server.status === "connected")
.map((server) => {
const tools = server.tools
?.map((tool) => {
const schemaStr = tool.inputSchema
? ` Input Schema:
${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}`
: ""

return `- ${tool.name}: ${tool.description}\n${schemaStr}`
})
.join("\n\n")

const templates = server.resourceTemplates
?.map((template) => `- ${template.uriTemplate} (${template.name}): ${template.description}`)
.join("\n")

const resources = server.resources
?.map((resource) => `- ${resource.uri} (${resource.name}): ${resource.description}`)
.join("\n")

const config = JSON.parse(server.config)

return (
`## ${server.name} (\`${config.command}${config.args && Array.isArray(config.args) ? ` ${config.args.join(" ")}` : ""}\`)` +
(tools ? `\n\n### Available Tools\n${tools}` : "") +
(templates ? `\n\n### Resource Templates\n${templates}` : "") +
(resources ? `\n\n### Direct Resources\n${resources}` : "")
)
})
.join("\n\n")}`
: "(No MCP servers currently connected)"
}`

MCP Server 的相关提示词在 System Prompt Tool Use Guidelines 部分,通过遍历 McpHub 内所有 server,将 server 内的 tool 格式化的拼接到 Prompt 中。大致格式为:

## name2empid(node xxx.ts)
### Available Tools
- get_empid_by_name: 根据员工姓名查询工号
Input Schema: {"name": "string"}

同时还提供给模型一个调用 MCP Server Tool 的示例:

## Example 5: Requesting to use an MCP tool

<use_mcp_tool>
<server_name>weather-server</server_name>
<tool_name>get_forecast</tool_name>
<arguments>
{
"city": "San Francisco",
"days": 5
}
</arguments>
</use_mcp_tool>

涉及到 MCP 调用统一使用 use_mcp_tool 工具,然后再指定是哪个 MCP 工具。

模型调用

比如我们问“查询张三的工号”,模型输出类似下面这样:

<thinking>
我需要查询"张三"的工号。根据任务描述,我需要使用name2empid MCP服务器提供的工具来获取员工的工号。

我可以使用`use_mcp_tool`工具来调用name2empid服务器的`get_empid_by_name`工具,该工具可以根据员工姓名获取工号。

需要的参数是:
- server_name: "name2empid"
- tool_name: "get_empid_by_name"
- arguments: 包含员工姓名的JSON对象

我已经有了所有必要的信息,可以直接调用这个工具。
</thinking>

<use_mcp_tool>
<server_name>name2empid</server_name>
<tool_name>get_empid_by_name</tool_name>
<arguments>
{
"name": "张三"
}
</arguments>
</use_mcp_tool>

经过 parseAssistantMessageV2presentAssistantMessage 两个方法,会从模型输出中解析出使用的 MCP Tool 以及参数,和内置的 Tool 逻辑基本类似。

name = 'use_mcp_tool'
params = {server_name: 'name2empid', tool_name: 'get_empid_by_name', arguments: '{\n "name": "张三"\n}'}
arguments = '{\n "name": "张三"\n}'
server_name = 'name2empid'
tool_name = 'get_empid_by_name'

此时,Cline 会执行 McpHub 内的 callTool 方法获取结果,后面再将结果交给模型,得到最终输出,自此任务结束。

总结

如果觉得 Cline 源码太复杂,可以直接看 MCP 官方的 Client DEMO,比较清晰。

MCP 大大扩展了模型能力,可以在让模型发挥更好的价值。Cline 在 MCP 的管理和调用上设计的都很巧妙,尤其是 System Prompt,下一篇文章我们将探讨一下 Cline 的 Prompt 设计。


欢迎关注我的公众号:前端生存指南,一起聊聊前端、AI 和生活。

Cline 源码浅析 - 从输入到输出

· 阅读需 14 分钟
SleepyZone
前端开发 / 开源爱好者

我们在上一篇文章:现在开始使用 Cline Rules 里简单介绍过 Cline,本篇文章从源码角度来看一下从我们输入指令到 Cline 输出的过程到底是怎样的,这样对我们后续使用 Cline 会有更好的帮助。

启动 Cline

访问 Cline 的 Github 仓库 README:https://github.com/cline/cline?tab=readme-ov-file#contributing:

git clone https://github.com/cline/cline.git
code cline
npm run install:all

在 VSCode 里 F5 或者 Run->Start Debugging 开始调试 Cline VSCode 插件。此时会新开一个 VSCode 窗口,这个窗口里我们可以愉快的调试 VSCode。

这里可能遇到一些问题,一个是仓库里提到的,如果构建有问题,需要安装 esbuild Problem Matchers插件;另外项目的 Webview 里需要安装 electron 这个巨无霸,可能会很慢,可以设置为国内源:

electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
shamefully-hoist=true

Step by Step Debug

VSCode 对自家插件的调试做的非常好,不管是 VSCode 主进程还是 Webview 内容,下面就一步一步的开始调试。

前端输入

我们准备一段简单的输入:创建一个新任务,实现快速排序算法,并插入到 utils 文件内。

/newtask 实现快速排序算法,写入 @/src/utils/index.ts 文件

这里使用了 Cline 的内置的两个功能:内置命令 Command /newtask 以及 mention @ 功能指定了写入的文件。Command 可以理解为指定特定任务的 prompts,方便执行一些高频操作的任务,Cline 内置了诸如新建任务、创建 Rule、创建 Github Issue 等 Command。@ 功能可以指定模型特别关注的上下文,比如指定的文件内容,指定的目录内容。

输入框内输入上述内容,点击发送,代码文件:webview-ui/src/components/chat/ChatView.tsx,发送消息的方法为 handleSendMessage,Webview 内只负责收集输入信息,实际任务处理的逻辑都会发送至 VSCode 的主进程内,VSCode 主进程处理过程中,会实时的发送消息回 Webview。

// 文件位置 webview-ui/src/services/grpc-client-base.ts
window.addEventListener("message", handleResponse)

// Send the request
const encodedRequest = encodeRequest(request)

vscode.postMessage({
type: "grpc_request",
grpc_request: {
service: service.fullName,
method: method.name, // method 为 newTask
message: encodedRequest, // message 为输入的文本、图片、文件消息等
request_id: requestId,
is_streaming: false,
},
})

message 的格式:

files =(0) []
images =(0) []
text ='/newtask 创建一个快速排序算法,写入 @/src/utils/index.ts 文件'

VSCode 主进程接收 grpc_request 消息进行处理。

VSCode 主进程接收消息

主进程处理的内容位于:src/core/controller/grpc-handler.ts

	/**
* Handle a gRPC request from the webview
* @param service The service name
* @param method The method name
* @param message The request message
* @param requestId The request ID for response correlation
* @param isStreaming Whether this is a streaming request
* @returns The response message or error for unary requests, void for streaming requests
*/
async handleRequest(
service: string,
method: string,
message: any,
requestId: string,
isStreaming: boolean = false,
): Promise<{
message?: any
error?: string
request_id: string
} | void> {}

可以看到,参数和 Webview 传过来的一致。

任务初始化

经过各种封装,最终处理的任务落到了 src/core/controller/index.tsinitTask 方法内一个超级大的 Task 类的初始化:

this.task = new Task(
this.context,
this.mcpHub,
this.workspaceTracker,
(historyItem) => this.updateTaskHistory(historyItem),
() => this.postStateToWebview(),
(message) => this.postMessageToWebview(message),
(taskId) => this.reinitExistingTaskFromId(taskId),
() => this.cancelTask(),
apiConfiguration,
autoApprovalSettings,
browserSettings,
chatSettings,
shellIntegrationTimeout,
terminalReuseEnabled ?? true,
enableCheckpointsSetting ?? true,
customInstructions,
task,
images,
files,
historyItem,
)

Task 类位于 src/core/task/index.ts

在一系列的初始化后:

初始化的代码没有过度深究,从类名来看,包括了后续任务执行中可能使用到的工具:浏览器、命令行、Url、DiffView 等;mcp 的配置;autoApproval 的配置等等。

终于进入到实际的逻辑处理:

首先,根据用户配置的模型提供商信息创建 AI 的 api handler:

// Now that taskId is initialized, we can build the API handler
this.api = buildApiHandler(effectiveApiConfiguration)

然后,进入到 startTask 方法:

say 方法会在聊天面板新增一条消息,比如下面的代码,将用户的输入信息显示在聊天面板:

await this.say("text", task, images, files)

构建 User Prompt

接下来,构建 userContent:

// 为用户输入的消息添加一个 task 标签。
let userContent: UserContent = [
{
type: "text",
text: `<task>\n${task}\n</task>`,
},
...imageBlocks,
]

/**
输出:
text =
'<task>\n/newtask 创建一个快速排序算法,写入 @/src/utils/index.ts 文件\n</task>'
type =
'text'
*/

如果用户提供了文件(Cline 支持用户上传文本文件),背后其实是读完文本文本,也用来构造 userContent。

if (files && files.length > 0) {
const fileContentString = await processFilesIntoText(files)
if (fileContentString) {
userContent.push({
type: "text",
text: fileContentString,
})
}
}

继续构造 Prompts

用过 Cline 的话,我们会知道 Cline 会循环处理任务,所以 User Prompts 构造完成后,进入到 initiateTaskLoop 方法,真正执行任务的方法为 recursivelyMakeClineRequests

进一步处理用户的输入信息:

const [parsedUserContent, environmentDetails, clinerulesError] = await this.loadContext(userContent, includeFileDetails)

这样处理之后,用户输入重新组装为 parsedUserContent,内容比较长,这里不贴了,大致格式为:

<explicit_instructions type="new_task"></explicit_instructions>
<task>用户输入</task>
<fileContent>src/utils/index.ts内的文件</fileContent>

再次看一下我们的输入:/newtask 实现快速排序算法,写入 @/src/utils/index.ts 文件,上面三段一一对应:\<explicit_instructions type="new_task\>" 对应 /newtask 命令,这是 Cline 的内置 prompt,@/src/utils/index.ts 添加了该文件内容作为上下文。

同时将 environmentDetails 也添加到 userContent,environmentDetails 有工作目录、当前时间、当前打开的 Tab 等。

userContent = parsedUserContent
// add environment details as its own text block, separate from tool results
userContent.push({ type: "text", text: environmentDetails })

怪不得 Cline 这么耗 Token!

模型请求

继续进入 attemptApiRequest 方法,首先构造 System prompt:

const isClaude4ModelFamily = await this.isClaude4ModelFamily()
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsBrowserUse, this.mcpHub, this.browserSettings, isClaude4ModelFamily)

比较有意思的是,Cline 针对 Claude4 定制了不同的 System Prompt。

System Prompt 里会告诉模型我们当前已经提供了哪些工具以及这些工具该如何调用(参数和规则),比如本次任务要将模型生成的代码写入已有文件,那么就需要使用到 replace_in_file 这个工具,这里贴一下 System Prompt 里关于 replace_in_file 的内容。

## replace_in_file
Description: Request to replace sections of content in an existing file using SEARCH/REPLACE blocks that define exact changes to specific parts of the file. This tool should be used when you need to make targeted changes to specific parts of a file.
Parameters:
- path: (required) The path of the file to modify (relative to the current working directory ${cwd.toPosix()})
- diff: (required) One or more SEARCH/REPLACE blocks following this exact format:
\`\`\`
------- SEARCH
[exact content to find]
=======
[new content to replace with]
+++++++ REPLACE
\`\`\`
Critical rules:
1. SEARCH content must match the associated file section to find EXACTLY:
* Match character-for-character including whitespace, indentation, line endings
* Include all comments, docstrings, etc.
2. SEARCH/REPLACE blocks will ONLY replace the first match occurrence.
* Including multiple unique SEARCH/REPLACE blocks if you need to make multiple changes.
* Include *just* enough lines in each SEARCH section to uniquely match each set of lines that need to change.
* When using multiple SEARCH/REPLACE blocks, list them in the order they appear in the file.
3. Keep SEARCH/REPLACE blocks concise:
* Break large SEARCH/REPLACE blocks into a series of smaller blocks that each change a small portion of the file.
* Include just the changing lines, and a few surrounding lines if needed for uniqueness.
* Do not include long runs of unchanging lines in SEARCH/REPLACE blocks.
* Each line must be complete. Never truncate lines mid-way through as this can cause matching failures.
4. Special operations:
* To move code: Use two SEARCH/REPLACE blocks (one to delete from original + one to insert at new location)
* To delete code: Use empty REPLACE section
Usage:
<replace_in_file>
<path>File path here</path>
<diff>
Search and replace blocks here
</diff>
</replace_in_file>

同时,System Prompt 也继续追加了用户的偏好语言,方便按照用户偏好的语言进行输出。

接下来发送请求:

let stream = this.api.createMessage(systemPrompt, contextManagementMetadata.truncatedConversationHistory)

针对模型的输出,需要进一步处理,是直接展示在聊天面板,还是需要调用工具处理,这部分逻辑在 src/core/assistant-message/parse-assistant-message.tsparseAssistantMessageV2 方法内。

针对我们本次创建排序算法的任务,下面是一个模型输出样例:

<thinking>\n我需要在`src/utils/index.ts`文件中添加一个快速排序算法。首先,我已经看到了文件的内容,这是一个工具函数集合文件,包含了各种实用函数,如rpx转换、vw计算、类型检查、日志记录等。\n\n我需要在这个文件中添加一个快速排序算法实现。快速排序是一种高效的排序算法,基于分治法的思想。我将实现一个通用的快速排序函数,可以对数组进行排序。\n\n我将使用`replace_in_file`工具来修改文件,在文件末尾添加快速排序算法的实现。我需要确保添加的代码与现有代码风格保持一致,并提供适当的类型注解和注释。\n\n我将在文件末尾添加快速排序算法的实现,包括:\n1. 一个主快速排序函数,接受一个数组和可选的比较函数\n2. 一个辅助函数用于分区操作\n\n让我使用`replace_in_file`工具来修改文件。\n</thinking>\n\n<replace_in_file>\n<path>src/utils/index.ts</path>\n<diff>\n------- SEARCH\nexport const jump2UpdateApp = () => {\n  if (is… (a, b) => {\n  if (a < b) return -1;\n  if (a > b) return 1;\n  return 0;\n}): T[] => {\n  // 创建数组副本,不修改原数组\n  const result = [...arr];\n  \n  // 内部快速排序实现\n  const _quickSort = (arr: T[], left: number, right: number) => {\n    if (left < right) {\n      const pivotIndex = partition(arr, left, right);\n      _quickSort(arr, left, pivotIndex - 1);\n      _quickSort(arr, pivotIndex + 1, right);\n    }\n  };\n  \n  // 分区操作\n  const partition = (arr: T[], left: number, right: number): number => {\n    // 选择最右边的元素作为基

parseAssistantMessageV2 方法会解析上述输出内容,主要是从模型输出中解析出使用哪些工具以及工具的参数,比如这里我们发现模型输出中有 <replace_in_file>,那就说明这次需要使用 replace_in_file 工具。

接下来调用 presentAssistantMessage 方法真正将模型消息展示给用户,一方面展示文本消息,类似下图:

一方面执行上一步解析出的 tool,将模型生成代码以 diff 的方式写入文件,这里借助 VSCode 的能力创建 diffView。自此这个任务算是完成了。

总结

抛开 Cline 中用户交互和工程处理的部分,我们发现最核心的其实只有两件事情:

  1. prompt 组装。这其中包括了 System Prompt、VSCode 和系统的环境信息、内置 Command、内置的 @ 引用(文件、目录信息)、用户的输入(文本、图片、文本文件),当然还有我们之前介绍的 Rules 以及我们提到过的 Workflow(本质是用户自定义的 Command)。这些内容共同组装称为 Prompt 发送给模型,力求给模型足够多的上下文信息,获得更加准确的结果。

  2. tools 调用。首先在 System Prompt 定义了模型可以调用的 Tool(包括内置的和 MCP Server 定义的),在模型输出时指定调用的 Tool 和需要的参数,以此斤进一步增强模型的能力。

两者共同合作完成用户的编码的任务。

由此我们可以看到 Agent 开发中最重要的三点:

  1. 模型的使用
  2. 丰富的创意
  3. 丝滑的体验

最后,本文是在我边调试边写作的过程中完成的,可能表达还不够清晰,欢迎有兴趣的同学一起交流指正。


欢迎关注我的公众号:前端生存指南,一起聊聊前端、AI 和生活。

现在开始使用 Cline Rules

· 阅读需 5 分钟
SleepyZone
前端开发 / 开源爱好者

写在前面

最近在业务(面向用户的 C 端业务)中较多的使用了 Cline,总体感觉非常丝滑(尤其是看着他在默默地生成代码并且自己排查错误的时候,但 Token 确实也消耗的很快😂)。

Cline 算是 VS Code AI 开发插件中的佼佼者了,并且是一个开源项目,他的一个 fork 分支 RooCode 热度也很高。

openrouter 网站的统计,Cline 遥遥领先

开始配置 Cline Rules

作为一个通用的编程助手,如何能针对不同的项目进行更好的开发,除了 Cline 本身对代码的理解之外,作为用户如果能提供更多的指引,可以让 Cline 发挥更好的作用。

一般来说,Cline、Cursor 及其他代码 Agent 都会提供 rules 这个概念,在项目根目录下新建 rules 目录,目录下根据自己的项目可以新建各种规则文件。

your-project/
├── .clinerules
├── project.md
├── api.md
├── src/
├── docs/
└── ...

如果你的规则全局通用,可以将规则放置到 Documents/Cline/Rules 目录。

Cline Rules 优势

放在项目内好处很明显:

  1. 可以针对每个项目配置不同的规则;
  2. 作为项目源代码的一部分,进行版本控制,持续维护,跟随项目发展设置长远的 rule;
  3. 保证团队成员使用 AI 的行为是一致的。

Cline Rules 的原则

  1. 清晰简洁:使用简单语言,避免歧义。
  2. 关注期望结果:描述你想要的结果,而不是具体步骤。
  3. 测试和迭代:试验找出最适合你的工作流程的方法。

rules 本质上是自定义 prompts,和 Cline 的 system prompts 互为补充,共同为项目服务。(system promps:https://github.com/cline/cline/blob/main/src/core/prompts/model_prompts/claude4.ts)

关于提示词,我之前有总结过谷歌的提示词最佳实践,可以看一下。

高级用法 Rules Bank

Rules Bank 顾名思义,就是存放 Rules 的地方,如果你的项目很复杂,又有前端、又有后端、又有不同的技术栈,这个时候就很有用。

your-project/
├── .clinerules/ # Active rules - automatically applied
│ ├── 01-coding.md
│ └── client-a.md

├── clinerules-bank/ # Repository of available but inactive rules
│ ├── clients/ # Client-specific rule sets
│ │ ├── client-a.md
│ │ └── client-b.md
│ ├── frameworks/ # Framework-specific rules
│ │ ├── react.md
│ │ └── vue.md
│ └── project-types/ # Project type standards
│ ├── api-service.md
│ └── frontend-app.md
└── ...

比如当你在开发 React 技术栈的时候,从 Rules Bank 中复制 React Rule 到 Cline Rules 内。

# Switch to React
rm .clinerules/vue.md
cp clinerules-bank/clients/react.md .clinerules/

总之一切文本放到代码仓库中,提高可持续性,并且可以跟随项目保持最新的迭代!

如何管理

Cline 提供了一种方便的方式管理 Rules,在输入框下方一个天平的小标志内,可以打开 Rule 管理器,新增删除或者启用禁用 Rule。

最后

十分建议你在平时工作中使用 Cline,并使用 Cline Rule 增强开发体验。后面会持续更新 Cline 的相关内容。

最后,假期快乐!


欢迎关注我的公众号:前端生存指南,一起聊聊前端、AI 和生活。