跳到主要内容

上下文工程 - Context Engineering

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

随着 Agent 技术的广泛应用,LLM(大语言模型)面临的任务和问题也越来越复杂。单一的提示词已经难以满足复杂场景的需求,需要提供更加精准且合适的上下文作为支撑。上下文信息过少会导致模型无法正确理解任务,而过多或混乱的信息(尤其在上下文窗口不断扩大的情况下)则会增加模型产生幻觉的风险。因此,在设计 Agent 时,如何高效地组织和优化上下文已成为当前的核心挑战。

所以上下文工程的概念被提了出来。

Shopify CEO:Context Engineering

Shopify CEO 的观点:相比"提示工程"(prompt engineering),我更喜欢用"上下文工程"(context engineering)这个术语。它更精准地描述了这项核心技能的本质:为任务提供所有必要背景信息,使大语言模型(LLM)能够合理地解决该任务的艺术。

前特斯拉 AI 总监 Andrej Karpathy 有相似的观点:上下文工程是一门科学,也是门艺术。

图源宝玉翻译

如何理解上下文工程

如果你看过我的Cline 源码浅析系列肯定更容易理解上下文工程的概念:

  1. Cline System Prompt 定义了丰富的工具以及确定了工具的格式。
  2. Prompt 是根据用户环境信息动态组装的。
  3. 历史消息传递至上下文(短期记忆)。
  4. 模块化的 Prompt 设计:Command、Workflow、Mention、Memery Bank。
  5. 上下文窗口的管理(下一篇源码分析系列)

正因为有了完善的上下文设计,才能让优秀的模型接收正确的信息做出正确的任务处理结果。

就像人类一样,沟通至关重要!

和提示词工程有何不同

这里我们直接引用 LangChain 博客的观点。

为什么要从“提示”转向“上下文”?最初,开发者通过巧妙的提示措辞来获得更好的答案。然而,随着应用程序的复杂性增加,为 AI 提供完整而结构化的上下文显然比任何巧妙的措辞都更为重要。

我认为提示工程属于上下文工程的一部分。即便拥有所有的上下文,如何在提示中进行组装仍然至关重要。区别在于你需要处理一组动态数据并对其进行正确格式化,而不是仅仅为单一数据集设计提示。

我还要强调,上下文的一个关键部分通常是关于大语言模型如何表现的核心指令。这通常是提示工程的重要组成部分。你是否认为为智能体提供清晰详细的行为指令属于上下文工程还是提示工程?我认为两者都有涉及。

总结

正如 Andrej Karpathy 提到的那样:

“上下文工程”是围绕 LLM 应用,还在逐渐兴起一层厚重、复杂的软件基础设施,它的任务是协调、优化各种 LLM 调用和相关操作,组合出真正有效、有价值的 LLM 应用。


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

Cline 源码浅析 - 递归执行引擎

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

前面我们聊了 Cline 从输入到输出的流程MCP 的调用以及Prompt 设计,这篇文章,我们再回过头看一下 Cline 内很重要的一部分 - 递归执行引擎

如果你用过 Cline,你会发现一次模型的交互并不会完成任务(聊天界面会多次展示 API Request...),而是需要多次和模型多轮交互,每次交互都会带上之前的结果(用户反馈、工具调用结果),直到模型确认任务完成,才会结束。这就是递归执行引擎

Prompt 设计

我们还是先来看下 Cline 的 Prompt 设计,如何保证模型有条不紊的执行的每次循环。

这部分内容在 System Prompt 内,src/core/prompts/system.ts

首先 Cline 对模型强调了工具的使用规则:

您可以访问一组工具,这些工具会在用户批准后执行。您每次消息中只能使用一个工具,并将在用户的回复中收到该工具使用的执行结果。您会逐步使用工具来完成给定任务,每次工具的使用都会基于前一个工具使用的结果进行决策。

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.

  1. 工具需要用户批准进行
  2. 每次消息只能使用一个工具
  3. 根据用户反馈,逐步使用工具完成任务

对于复杂任务,这就保证了模型不必一次性完成任务,而是可以在多轮会话中使用多种工具共同协作完成任务。

那模型在任务完成后如何告知我们呢?这就是 attempt_completion 工具的作用了,Prompt 如下:

## 尝试完成  
描述:每次使用工具后,用户会回复该工具的执行结果,即是否成功以及失败的原因。一旦您收到了工具使用的执行结果,并可以确认任务已经完成,请使用此工具向用户展示您的工作成果。您可以选择性地提供一个 CLI 命令来展示您的工作成果。如果用户对结果不满意并给出反馈,您可以根据反馈进行改进并再次尝试。

重要提示:在确认用户之前的工具使用都已成功之前,**不能**使用此工具。否则会导致代码损坏和系统故障。在使用此工具之前,您必须在 `\<thinking\>\<\/thinking\>` 标签中自问:是否已从用户那里确认所有先前的工具使用都是成功的?如果不是,则**不要**使用此工具。

参数:
- **result**(必填):任务的结果。请以最终形式表述这个结果,不需要用户进一步输入。不要以问题或提供进一步帮助的提议结尾。
- **command**(可选):一条 CLI 命令,用于向用户实时演示结果。例如,使用 `open index.html` 来显示创建的 HTML 网站,或者使用 `open localhost:3000` 来显示本地运行的开发服务器。但**不要**使用像 `echo``cat` 这类只打印文本的命令。该命令应适用于当前操作系统,并确保格式正确,不包含任何有害指令。

Usage:
\<attempt_completion\>
\<result\>
Your final result description here
\<\/result\>
\<command\>Command to demonstrate result (optional)\<\/command\>
\<\/attempt_completion\>

模型确认任务完成后,调用 attempt_completion 这个工具,根据我们之前聊过的,Cline 会从模型的消息中解析工具的使用,在解析到模型消息中包含 attempt_completion 这个工具的使用时,我们可以确认任务完成了。

Task

后续就是 Cline 对模型消息的处理和递归的逻辑了,这部分代码在 src/core/task/index.ts 文件下。

recursivelyMakeClineRequests,看名字就知道是在做递归调用,主要是做一些 User Prompts 的拼装、聊天消息的组装,处理模型请求的流式消息等,只要模型没明确调用 attempt_completion ,就一直会调用自身。

关于 attempt_completion 工具的真实调用还是在 presentAssistantMessage 方法内,这里就不展开了。

下面是一个输出示例,使用 Cline 将截图转为 React 代码,任务完成后,模型输出如下:

\<thinking\>
Now I'll complete the task by summarizing what I've done and presenting the final result.
\<\/thinking\>

\<attempt_completion\>
\<result\>
I've successfully converted the provided module screenshot into a React component structure following all the specified requirements. Here's what I've created:

....
....

The implementation closely follows the visual design from the provided screenshot, with proper styling, layout, and component structure.
\<\/result\>
\<\/attempt_completion\>

总结

再次感叹,模型的如何通过 Prompt 和 Tool 让模型发挥出更大价值才是 Agent 最重要的事情。

至此,Cline 源码浅析系列关于模型相关的到此为止了,后面有计划继续探索下 Cline 工程相关的内容,比如检查点、VSCode 插件这些内容。


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

为什么随着 AI 能力的提升,人类意图变得更加重要

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

发烧两天,终于恢复,继续更新。

我发现 Cline 的官方博客真的是宝藏,虽然其实很多博客都是在宣传自身或者自身的一个 feature,但确实有很多很深刻又很有启发的观点,忍不住想分享给大家。比如今天这篇:为什么随着 AI 能力的提升,人类意图变得更加重要。配合前文 Cline 源码浅析 - Prompt 设计 食用更加。


还记得当 AI 编码只是简单的自动补全吗?那时,人类完成了大部分工作:浏览代码库、寻找正确的文件、定位要编辑的位置、开始输入,然后 AI 才能提供一些建议。人类是主导者,而 AI 只是辅助。

如今的智能 AI 能够搜索代码库、读取文件、编写完整模块、重构系统,并在多个文件中进行复杂的变更。借助像 Cline 这样的工具,AI 几乎接管了整个编码过程。这就产生了一个有趣的问题:如果 AI 几乎能做所有事情,人类还能做什么?

答案其实很简单:意图

最后一段路是最重要的

随着 AI 能力的增强,人类的角色并没有减少,而是发生了转变。当 AI 能够处理编码的细节时,我们需要思考:我们在构建什么,以及为什么要构建它?

可以将这过程视为一张工作量的饼图。在自动补全时代:

  • 人类工作量: 95%(导航、规划、输入、调试)
  • AI 工作量:5%(提供建议)

在智能 AI 时代:

  • AI 工作量:95%(搜索、阅读、写作、重构)
  • 人类工作量:5%(意图)

而这 5% 就是决定性因素。它决定了 AI 是完美地构建错误的东西,还是构建完全符合需求的东西。

为什么我们创建了计划-行动模式

问题在于:AI 充满热情。一旦赋予它读写文件的工具,它就会立即开始编码。这就像一个才华横溢的开发者在完全理解需求之前就开始动手。

这种急切并不是缺陷,而是这些模型工作的特性。它们被设计为提供帮助、采取行动、解决问题。但如果没有明确的意图,行动可能会走偏。

计划-行动模式不是为了给 AI 设置不同的“模式”,而是为了认识到有效合作的自然阶段。在计划阶段,我们为相互理解创造了一个专门的空间。我们让 AI 尽可能地关注人类的意图。不是因为 AI 能力不足,而是因为意图是唯一无法自动化的东西。

接下来是行动阶段,我们将其放手。AI 的能力不变,但现在优化为不间断的执行。这是我们互动方式的模式转变,而不是性格的变化。

反角色哲学 (为何计划-行动不是“模式”)

你可能见过一些编码助手,拥有专门的“智能体”:比如调试器、架构师和代码审阅者。我们刻意避开这种方法,原因如下图:

我们设计 Cline 是为了展现模型的潜力,而不是按照我们的需求来操控它们。

图中的英文翻译:第二个要从这个痛苦的教训中学到的要点是:心灵的实际内容是极其复杂的,无法挽回的;我们应该停止尝试找到简单的方法来思考心灵的内容,比如思考空间、物体、多个智能体或对称性的简单方法。所有这些都是任意的、本质上复杂的外界的一部分。由于它们的复杂性是无穷的,因此不应内置其中;相反,我们应该仅构建能够发现和捕捉这种任意复杂性的元方法。

Richard Sutton 在 AI 研究中的“苦涩教训”指出,利用计算的通用方法往往优于专业化的人工设计解决方案。当深蓝击败 Kasparov 时,它并没有使用大师级策略,而是依靠蛮力搜索。当 AlphaGo 征服围棋时,它并没有遵循古老的智慧,而是使用通用学习算法。

现代语言模型,例如 Claude Sonnet 4 和 Geminin 2.5 Pro,经过完整的编码过程训练,已经见识过数百万个架构决策、调试会话和代码审阅的案例。人工角色的创建会限制它们自然的能力。

这点很关键:计划和行动不是不同 AI 个性或能力的“模式”。我们没有在“计划 AI”和“编码 AI”之间切换。它始终是同一个 AI,能力不变。

变化的是交互模式。在计划阶段,我们优化意图收集;AI 会提问,探索上下文,确保理解。在行动阶段,我们优化执行;AI 不间断地发挥其全部能力。这是关于识别人类与 AI 合作的自然节奏,而不是限定 AI 的角色。

可以将其类比于对话与专注工作。你不会认为开发者在需求会议与编码时有不同的“模式”。他们是同一个人,只是在不同的工作阶段中。

不要打扰 AI

我们发现一个有趣的现象:大语言模型是出色的叙述者。它们是为构建连贯故事而设计的预测文本引擎:先发生这个,然后是那个,再然后是另一个。

如果你在 AI 执行任务时中途打断它来纠正方向,就会破坏这种叙述的流畅性。模型需要调整你的干预与之前的路径,通常会导致混乱或不理想的结果。

斯坦福大学的“迷失在中间”研究 (Liu et al., 2023) 表明,当处理被打断的上下文时,大语言模型的性能会显著下降。研究发现,当相关信息出现在长上下文的中间而不是开始时,模型的准确率下降了近 20%。重新开始,虽然看似低效,但通常效果更好,因为模型能够从清晰的意图构建完整的叙述,而不需要在受损的中间上下文信息中挣扎。

这就是为什么重新开始并提供更清晰的指令通常比纠正方向更有效。保存当前状态,让 AI 继续工作,然后接受结果或者重新开始,而不是纠结于修正。

随着推理成本下降,我们甚至可以同时进行多次尝试,并选择最佳结果,就像拉动老虎机一样,每次拉杆的成功概率都是独立的。

让 AI 自主运作

在计划阶段确定了你的意图后,接下来的原则同样重要:让 AI 自由发挥。

这与我们的反角色哲学直接相关。我们不将 AI 限制为“调试器”或“架构师”等特定角色,同样,我们也不会在执行过程中限制它。行动阶段的重点是让模型充分发挥其所有能力:架构决策、调试、重构、优化,作为一个整体智能体。

我们注意到,最新的 Claude 4 模型在自动批准模式下表现优异。每次中断都会打断其叙述流程。最有效的情况是 AI 能够不受干扰地进行写作。

需要注意的是,模型的最佳表现来自不受干扰的构建。然而,结果可能并不总是与你的意图完全一致。因此,如果你从事复杂的工程工作,我们建议你仍然保持参与,更重要的是,详细制定你的计划。

这可能看起来有些违反直觉。难道我们不需要控制、监督和干预的能力吗?

是的,但不需要实时进行。这就是我们构建以下功能的原因:

  • 检查点(Checkpoints):保存状态,随时可以返回。
  • 全面可见性(Full visibility):了解 AI 的全部操作。
  • 撤销功能(Undo capabilities):撤销任何更改。
  • 安全边界(Safety boundaries):定义 AI 能触及和不能触及的范围。

关键在于时机。先收集需求,共享意图,设定边界,然后让 AI 工作。之后再进行审查和迭代,而不是实时进行。


编码的未来不在于人类与 AI 竞争或通过不断细化专业领域来保持重要性。而在于人类发挥独特的作用:提供意图、意义和目标,而由 AI 来执行具体操作。

这不仅不是一个减弱的角色,而是所有角色中最为重要的。


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

什么是 Coding Agent?

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

本文翻译自 Cline 博客 What Makes a Coding Agent?

在我写完Cline 源码浅析 - 从输入到输出后,我就看到了这篇博客,整个博客的内容和我结尾的观点不谋而合。


当开发者首次接触到 Cline 时,他们常常称之为自己的“通用人工智能 (AGI) 时刻”——那一瞬间,他们意识到 AI 已经从一个简单的建议工具发展成为真正的编码伙伴。那么,究竟是什么让一个真正的编码 AI 智能体与众多 AI 驱动的开发工具有所不同呢?答案在于理解“智能体”这个词的确切含义。

Agent 定义

OpenAI 将智能体定义为“一个能够独立为你完成任务的系统”。Anthropic 则更进一步,描述智能体为“一个系统,其中的大语言模型 (LLM) 能够动态地指挥自身的流程和工具使用,掌控任务的完成方式”。虽然各家公司可能在细节上有所不同,但在基本架构上达成了共识:智能体由三个重要部分组成:(1) 模型,(2) 工具,和 (3) 指令

这种区别比你想象的更为重要。以 ChatGPT 为例。虽然 ChatGPT 技术先进,但它并不是真正的 AI 智能体;它是为聊天应用精心调整的模型。它可以根据你的提示进行回应,但无法独立组织复杂的工作流程或自主决定如何完成多步骤任务。

相比之下,Cline 被构建为一个真正的 AI 智能体,因为它通过三个重要的组成部分自主生成代码。接下来,我们来探讨每个组件的工作原理,以及为什么这种架构能够实现本质上不同的能力。

自主编码的三大基础

1. 模型:大脑

模型是智能体的核心决策者,可以说是“大脑”。当你配置 Cline 时,其实是在选择哪个 AI 模型来驱动你的编码伙伴。Cline 支持多个提供商,包括 Anthropic、OpenAI 和 Google,并提供具体的模型,比如 Claude 3.5 Sonnet、GPT-4.1 或 Gemini 2.5 Pro。

这种不依赖特定模型的方法不仅仅是关于选择,更是关于赋予使用者更多的能力。许多竞争对手会将你锁定在特定模型上,或者施加限制来保护他们的推理利润,而 Cline 则让你可以完全使用所选模型的全部功能。无论你需要强大的前沿模型来进行复杂的架构决策,还是选择像 DeepSeek-V3-0324 这样的经济型模型来完成日常任务,Cline 都为你提供了无障碍的途径。

2. 工具:双手

工具是 AI 智能体执行任务时使用的外部功能,可以把它们理解为动词,比如“搜索”、“编辑”和“阅读”。Cline 提供了一个全面的工具包,其中包括文件操作、终端命令、浏览器自动化,以及通过模型上下文协议 (MCP) 与外部服务的整合。

这是自主性的重要体现。传统软件通常按照预设的操作步骤执行,而 Cline 的智能体架构则让模型自主决定使用哪些工具以及使用的时机。面对复杂的编码任务,Cline 可以自主选择是先探索代码库、阅读文档、运行测试,还是直接开始实现。这种动态决策过程就是人们所说的智能体“自主性”的真正含义。

3. 指令:指引

第三个重要组成部分是明确的准则,它们定义了智能体的行为方式。Cline 通过精心设计的指令和系统提示,来确定其如何解决问题、与代码库进行交互以及保持编码标准。

这些指令不仅仅是简单的聊天回复。它们包含了复杂的推理模式、错误处理策略和协作工作流程,这使得 Cline 能够真正成为你的编码伙伴。

递归执行引擎

理解这些组成部分只是开始。真正的强大之处在于它们如何通过递归执行协同工作。当你发送一个提示给 Cline 时,系统背后会发生一些奇妙的事情。

Cline 首先会根据你的工作空间、计算机环境和你提供的自定义指令来扩展其系统提示。这个增强后的提示,连同你整个对话历史,被传送到所选的模型中。接下来就是智能体的协调过程。 Cline 会自主地在递归循环中调用工具;它会不断调用自己,直到确定任务完成。模型会自行决定做什么、使用哪些工具,以及何时完成编码。

我们来看一个具体的例子。假设你告诉 Cline“在主页上添加一个新按钮。”接下来会发生以下事情:

步骤 1: 探索 模型可能首先调用 list_files 工具,以了解你的代码库结构。Cline 发送请求后,会收到一个目录列表。

步骤 2: 调查 拥有文件结构后,Cline 可能会递归地调用 read_file 工具,检查 homepage.tsx 文件,理解当前的实现,并确定在哪里添加按钮。

步骤 3: 实施 在掌握上下文后,Cline 调用 write_to_file 工具,生成所需的代码更改,添加具有适当样式和功能的按钮。

步骤 4: 完成 完成更改并确认没有错误后,Cline 调用 attempt_completion 工具,结束递归循环。

在实际应用中,Cline 常常进行比这个简化示例更多的递归循环。关键在于,每个决策(例如使用什么工具、如何解释结果、何时停止)都由模型根据当前的上下文和对任务的理解自主做出。

Cline 的开源透明性增加了一个重要的维度。不同于那些将决策过程隐藏的“黑箱”工具,Cline 提供了实时的透明度,可以查看每个文件的读取、每个工具的调用和每个决策。这种透明性不仅仅关乎信任,更关乎在人类对日益复杂的自动化进行监督和控制。


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

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 和生活。

Mastra - TypeScript AI Agent 框架

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

简介

之前我们讨论过什么是 Agent,都是一些概念性的闲聊,今天介绍的 Mastra,是一个难得的(一般都是 Python 框架) TS 的 AI Agent 框架,可以让你快手上手开发一个 Agent,目前已经有 13.2K 的 Star。

前端人狂喜

Mastra 提供了 Agent 所需的所有要素:

  1. 大模型:基于 Vercel AI SDK。
  2. Tools:工具。
  3. Workflow:工作流,以支持更加复杂的工作。
  4. RAG:检索增强生成,以帮助构建知识库。
  5. Memery:各种存储方式集成。
  6. Agent:最终的 Agent 构建。

接下来,我们按照官网的快速开始,在 Next.js 框架中接入 Mastra。

快速开始

  1. 初始化 Next.js 结构,参照官网即可。

  2. lib 目录下 新建 mastra 目录,在这个目录下开发 Agent 相关的内容。

  3. 新建 tools 目录,扩展大模型能力的工具,比如建一个根据城市获取天气的工具。tools 目录下新建 weather-tool.ts 文件。

import { createTool } from '@mastra/core/tools';
import { z } from 'zod';

interface WeatherResponse {
current: {
time: string;
temperature_2m: number;
apparent_temperature: number;
relative_humidity_2m: number;
wind_speed_10m: number;
wind_gusts_10m: number;
weather_code: number;
};
}

export const weatherTool = createTool({
id: 'get-weather',
description: 'Get current weather for a location',
inputSchema: z.object({
location: z.string().describe("City name"),
}),
outputSchema: z.object({
temperature: z.number(),
feelsLike: z.number(),
humidity: z.number(),
windSpeed: z.number(),
windGust: z.number(),
conditions: z.string(),
location: z.string(),
}),
execute: async ({ context }) => {
return await getWeather(context.location);
},
})

const getWeather = async (location: string) => {
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`;
const geocodingResponse = await fetch(geocodingUrl);
const geocodingData = await geocodingResponse.json();

if (!geocodingData.results?.[0]) {
throw new Error(`Location '${location}' not found`);
}

const { latitude, longitude, name } = geocodingData.results[0];

const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`;

const response = await fetch(weatherUrl);
const data: WeatherResponse = await response.json();

return {
temperature: data.current.temperature_2m,
feelsLike: data.current.apparent_temperature,
humidity: data.current.relative_humidity_2m,
windSpeed: data.current.wind_speed_10m,
windGust: data.current.wind_gusts_10m,
conditions: getWeatherCondition(data.current.weather_code),
location: name,
};
};

function getWeatherCondition(code: number): string {
const conditions: Record<number, string> = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Foggy",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
56: "Light freezing drizzle",
57: "Dense freezing drizzle",
61: "Slight rain",
63: "Moderate rain",
65: "Heavy rain",
66: "Light freezing rain",
67: "Heavy freezing rain",
71: "Slight snow fall",
73: "Moderate snow fall",
75: "Heavy snow fall",
77: "Snow grains",
80: "Slight rain showers",
81: "Moderate rain showers",
82: "Violent rain showers",
85: "Slight snow showers",
86: "Heavy snow showers",
95: "Thunderstorm",
96: "Thunderstorm with slight hail",
99: "Thunderstorm with heavy hail",
};
return conditions[code] || "Unknown";
}
  1. mastra 目录下新建 agents 目录,agent 目录下新建 weather-agent.ts 文件。

可以看到这个天气 Agent 主要功能就是:如果用户提问天气相关的内容,模型则调用上述工具获取天气。

import { Agent } from '@mastra/core/agent';
import { weatherTool } from '../tools/weather-tool';
import { createAIModel } from '@/lib/utils';

export const weatherAgent = new Agent({
name: 'Weather Agent',
instructions: `You are a helpful weather assistant that provides accurate weather information.

Your primary function is to help users get weather details for specific locations. When responding:
- Always ask for a location if none is provided
- If the location name isn’t in English, please translate it
- Include relevant details like humidity, wind conditions, and precipitation
- Keep responses concise but informative

Use the weatherTool to fetch current weather data.`,
model: createAIModel('gpt-4o-mini-0718'),
tools: { weatherTool },
});

模型使用的 Vercel AI SDK,如果你是用的 OpenAI 或者 Claude 等模型,可以直接使用 @ai-sdk/openai 这些包。如果你和我一样,使用的服务商统一包装的兼容 OpenAI 的接口,则可以这样:

import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

export const createAIModel = (modelName: string) => {
return createOpenAICompatible({
name: 'custom model',
baseURL: 'https://your-base-url/api/openai/v1/',
// api key 最好配置到 .env 文件中
apiKey: 'your-api-key'
})(modelName);
}

然后我们建一个 agent 的入口文件 mastra/index.ts

import { Mastra } from '@mastra/core';
import { PinoLogger } from '@mastra/loggers';
import { LibSQLStore } from '@mastra/libsql';
import { weatherAgent } from './agents/weather-agent';

export const mastra = new Mastra({
agents: { weatherAgent },
storage: new LibSQLStore({
// stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
url: ':memory:',
}),
logger: new PinoLogger({
name: 'Mastra',
level: 'info',
}),
});
  1. 一个简单的 Agent 就完成了,我们开发一个接口测试一下:

在 app 目录下新建 /(chat)/api/chat/route.ts

import { mastra } from '@/lib/mastra';
import { NextResponse } from 'next/server';

export async function POST(req: Request) {
const { city } = await req.json();
const agent = mastra.getAgent('weatherAgent');

const result = await agent.stream(`What's the weather like in ${city}?`);

return result.toDataStreamResponse();
}
  1. 使用 Postman 访问 http://localhost:3000/api/talk 接口测试一下:

流式返回了天气信息!!

f:{"messageId":"msg-xZkgBuDjmIEtRWqu0wB6kNFw"}
9:{"toolCallId":"call_iQt37jB3orK3HhY0ZGBWUClB","toolName":"weatherTool","args":{"location":"New York"}}
a:{"toolCallId":"call_iQt37jB3orK3HhY0ZGBWUClB","result":{"temperature":9.2,"feelsLike":6.5,"humidity":88,"windSpeed":13.2,"windGust":36.7,"conditions":"Overcast","location":"New York"}}
e:{"finishReason":"tool-calls","usage":{"promptTokens":99,"completionTokens":4},"isContinued":false}
f:{"messageId":"msg-Bhwx7fT1X0zQTxBnKAeb00xi"}
0:"The"
0:" current"
0:" weather"
0:" in"
0:" New"
0:" York"
0:" is"
0:" as"
0:" follows"
0:":\n\n"
0:"-"
0:" **"
0:"Temperature"
0:":**"
0:" "
0:"9"
0:"."
0:"2"
0:"°C"
0:" ("
0:"Feels"
0:" like"
0:" "
0:"6"
0:"."
0:"5"
0:"°C"
0:")\n"
0:"-"
0:" **"
0:"Humidity"
0:":**"
0:" "
0:"88"
0:"%\n"
0:"-"
0:" **"
0:"Wind"
0:" Speed"
0:":**"
0:" "
0:"13"
0:"."
0:"2"
0:" km"
0:"/h"
0:"\n"
0:"-"
0:" **"
0:"Wind"
0:" Gust"
0:"s"
0:":**"
0:" "
0:"36"
0:"."
0:"7"
0:" km"
0:"/h"
0:"\n"
0:"-"
0:" **"
0:"Conditions"
0:":**"
0:" Over"
0:"cast"
0:"\n\n"
0:"If"
0:" you"
0:" need"
0:" more"
0:" details"
0:" or"
0:" information"
0:" about"
0:" another"
0:" location"
0:","
0:" feel"
0:" free"
0:" to"
0:" ask"
0:"!"
e:{"finishReason":"stop","usage":{"promptTokens":145,"completionTokens":88},"isContinued":false}
d:{"finishReason":"stop","usage":{"promptTokens":244,"completionTokens":92}}
  1. 如果有更复杂的需求,可以尝试 Workflow ,使用不同的模型处理多个任务,最终组合称为一个更好的 Agent。这里就不展开了。

写在后面

很欣喜能看到一个 TS 的 Agent 框架,而且从文档代码来看,完成度和质量都很高。

但我们应该意识到,除了 Agent 开发工具,更重要的是找到合适的落地场景


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

标签:

DeepWiki - 阅读开源代码的神器

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

以后推荐开源项目: ❌ https://github.com/xx/yyhttps://deepwiki.com/xx/yy


Devin (就是那个每月 500 刀的 AI 程序员)团队推出了 DeepWiki(https://deepwiki.org/),从此阅读源码更方便了。

只需要将开源项目链接的 github.com 替换为 deepwiki.com,就可以得到一份详尽的项目文档,比如:https://deepwiki.com/vercel/ai-chatbot。

(比源文档详细的多:https://chat-sdk.dev/docs/getting-started/overview)

甚至文档内还有图表:

并且可以基于文档进行问答:

快去试试吧!

最后祝大家五一假期快乐!


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

标签: