Clineは、ユーザの指示を実現する「計画と実行」を行う自律型コーディングエージェントです。コーディングエージェントとしてはNo.1の地位を築いているのではないでしょうか。
このエントリでは、Clineの内部構造とその動作の流れを、ソースコードの実際の箇所を交えながら書いてみます。対象バージョンは今日リリースされたばかりのCline v3.1.0です。ホヤホヤですね。
概観
ClineはいくつかのToolを内部で定義しており、ユーザの指示を実現するためにどのToolをどのような順で使っていくかを「計画」し、実行すると言うのが基本的な流れになります。
ツール定義
利用可能なツールは`core/assistant-message/index.tsで定義されています。Toolの名前配列が都合よくあったので、抜き出すと以下。ファイル操作からリスト取得、コード解析まで多岐にわたり、ほぼすべての操作はこれらを通じて実現されます。
export const toolUseNames = [ "execute_command", "read_file", "write_to_file", "replace_in_file", "search_files", "list_files", "list_code_definition_names", "browser_action", "use_mcp_tool", "access_mcp_resource", "ask_followup_question", "attempt_completion", ] as const
処理の流れ
処理主体
タスク処理に関するコアな部分の実装は、core/Cline.ts
で行われています。
ここで定義されているクラスCline
は、どうもユーザの指示1つに対して1つ生成されるタイプの思想だと思われます。と言うのも、コンストラクタに渡されるtask
が、ユーザが入力した指示を示しているからですね。
Clineの処理の流れ
主要な処理ステップとその実装箇所を示します。
タスクの初期化
処理のエントリーポイントはstartTask
です。
続くinitializeTaskLoop
で、タスクの処理ループが開始されます。
ここでさらにrecursivelyMakeClineRequests
が呼び出されます。このメソッドこそが、タスクの計画と実行を担うコアとなるメソッドです。
recursivelyMakeClineRequests
でどうやってタスクの計画を行うのか
Clineでタスクを処理する時におそらく一番重要な処理を握るのはこのメソッドです。
いろんな処理をやっているんですが、まずはここをみてくれ。
const [parsedUserContent, environmentDetails] = await this.loadContext(userContent, includeFileDetails) userContent = parsedUserContent // add environment details as its own text block, separate from tool results userContent.push({ type: "text", text: environmentDetails })
大前提として、ここでいうuserContent
はユーザ指示を実現するために分解したサブタスク群が入っているArrayになっていて、型定義は次のとおり。
type UserContent = Array< Anthropic.TextBlockParam | Anthropic.ImageBlockParam | Anthropic.ToolUseBlockParam | Anthropic.ToolResultBlockParam>
ユーザの指示は最初Anthropic.TextBlockParam
型として1要素に格納された状態になっています。task
って言うのが、ユーザが最初に指示した内容で、以下のような構成。
{ type: "text", text: `<task>\n${task}\n</task>`, },
loadContext
って言うのは、ざっくりと以下のようなことをしてくれている。
@url
とか@file
とかそういうコンテキストを解析し、その結果をプロンプトに埋め込む。その内容をサブタスク配列parsedUserContent
として返却- VS Code上で開いているファイルの内容とかのコンテキスト情報を
environemtDetails
として返却
というわけで、コンテキストが埋め込まれたサブタスク配列が今userContent
に入っているわけですね。
とはいえ、まだuserContent
にはユーザ指示しか入っていない。これをサブタスク分割していくのがattemptApiRequestの呼び出しです。
const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk)
ツールを使うサブタスクに分割するためのtool use
サブタスク分割は、いわゆるtool_use(function calling)として行われます。実行場所はここ。
// tool useのプロンプトを作成 let systemPrompt = await SYSTEM_PROMPT(cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub) // (略) // この辺りで、`systemPrompt`に .clineRules`ファイルの内容とかが追加される const stream = this.api.createMessage(systemPrompt, truncatedConversationHistory)
createMessage
は、tool_use
のプロンプトを生成AIに投げ込んで、ユーザのタスクを実現するためにどのツールを使えば良いのかを計画します。
プロンプトの実体は`core/prompts/system.tsにあるので見ていただければと思いますが。冒頭部分は以下のような感じ1で、よくあるtool_use (function calling)のプロンプトになっています。
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. # Tool Use Formatting Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: <tool_name> <parameter1_name>value1</parameter1_name> <parameter2_name>value2</parameter2_name> ... </tool_name>
これを生成AIに投げ込むと、ユーザ指示を実行するにあたりツールをどう使っていくべきかを返却してくれるので、それをパースし、実行計画としてサブタスク配列に埋め込みます。
そしてこのサブタスクがpresentAssistantMessage
の呼び出しの中で個々のツール実装によって処理され、結果がユーザに提示されます。
その上で、残っているサブタスク配列を元にして、再度recursivelyMakeClineRequests
を呼び出す。これを繰り返していくことで、ユーザの指示が実現されていきます。
const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent)
まとめ
Clineは、ユーザの指示を実現するために、定義したツールを使ったサブタスク分割を行うことで処理を計画し、それを実行していくという流れで動いているようでした。 僕はtool_useのユースケースは「外部ツールの利用」だと思っていたのですが、実際にはClineの中で実装した処理をツールと見立てて処理を行っていたわけですね。なるほどなぁ。 最近VS Code APIの中でもtool use用のAPIも生えたので、僕の中で利用範囲が広がりました。
Clineで定義したツール個々の処理自体も面白いです。計画を立てる上では当然どのファイルでどういう処理が行われているのかを理解する必要があり、それをtree-sitter等を利用してどのように進めていくのかというのも興味深かったのですが、それはまた別エントリで。
- 読みやすいように改行だけ付与しました↩