51
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude 3.5/3.7 Sonnetに追加されたText editor toolを使えばClineが自作できる(かもしれない)

Posted at

Claudeに新しいツール「テキストエディター」ツールが追加されました。

ユースケースとして以下のものが挙げられています。

  • コードのデバッグ
  • コードのリファクタリング
  • ドキュメント生成
  • テスト生成

ん?これは、今流行りのClineですね!

ということで、この新しいツールを使ってみて、Clineっぽいものができるのか、検証しました。

環境

ドキュメント記載内容に沿って進めることにしましたので、Anthropic SDKを使いつつ、呼び出すのはAmazon BedrockのClaude 3.7 Sonnetとします。

実装手順

まず、ライブラリーをインストールします

pip install anthropic boto3

まず、ドキュメントに従い、APIを呼び出す部分を作成します。

clientを生成する部分のみ、Bedrockを使用する形式に変更しました。

私のAWSアカウントはClaude 3.7 Sonnetのクオーターが低く設定されてしまっているため、リトライ回数を多めにしています

import anthropic

client = anthropic.AnthropicBedrock(aws_region="us-east-1", max_retries=10) # ※1

response = client.messages.create(
    model="claude-3-7-sonnet-20250219",
    max_tokens=1024,
    tools=[
        {
            "type": "text_editor_20250124",
            "name": "str_replace_editor"
        }
    ],
    messages=[
        {
            "role": "user", 
            "content": "There's a syntax error in my primes.py file. Can you help me fix it?"
        }
    ]
)

toolsの指定が見慣れない形をしていますね。

通常、ツールを指定する場合は以下のように定義情報を指定しますが、テキストエディターツールの場合はツール定義の代わりにtypenameを指定します。

通常のツール定義
tools=[
    {
        "name": "get_weather",
        "description": "Get the current weather in a given location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                }
            },
            "required": ["location"],
        },
    }
],

テキストエディターツールは、以下の5つの機能(コマンド)で構成されています。

  • viewコマンド
    ファイルの内容を要求するツール。オプションで取得する行数範囲が指定される場合がある
  • str_replaceコマンド
    ファイルの文字列置換を要求する機能
  • createコマンド
    ファイルの新規作成を要求する機能
  • insertコマンド
    ファイルの特定の位置への文字列追加を要求する機能
  • undo_editコマンド
    最後の修正をもとに戻すことを要求する機能

テキストエディターツールが正確に動作するように、Claudeのモデルのファインチューニングをしているのではないかな?と、推測しています。

Claudeがテキストエディターツールを使用すると判断した際には、以下のようなtool_useブロックがレスポンスに含まれます。

{
  "type": "tool_use",
  "id": "toolu_01AbCdEfGhIjKlMnOpQrStU",
  "name": "str_replace_editor",
  "input": {
    "command": "view",
    "path": "primes.py"
  }
}

ツール呼び出しを要求された後のツール処理は自前で実装する必要があります。
ドキュメントでは以下のようなhandle_editor_tool関数の記述があります。
が、

def handle_editor_tool(tool_call):
    input_params = tool_call.input
    command = input_params.get('command', '')
    file_path = input_params.get('path', '')
    
    if command == 'view':
        # Read and return file contents
        pass
    elif command == 'str_replace':
        # Replace text in file
        pass
    elif command == 'create':
        # Create new file
        pass
    elif command == 'insert':
        # Insert text at location
        pass
    elif command == 'undo_edit':
        # Restore from backup
        pass

。。。

なんと、肝心な部分の実装がありません😅

実装できなくはなさそうですが、なんやかんやめんどくさそう(?)なので、参考になるものを探しました。

で、見つけました!

AnthropicがGitHubに公開しているQuickstartsに、Computer useのデモが含まれています。

実はComputer useの機能の一部としてテキストエディターツールが含まれているため、その実装を拝借することにしました。

computer_use_demo/tools以下を丸ごとコピーしてきます。

tree
.
├── app.py
└── tools
    ├── base.py
    ├── bash.py
    ├── collection.py
    ├── computer.py
    ├── edit.py
    ├── groups.py
    ├── __init__.py
    └── run.py

handle_editor_tool関数を作成します。

from tools.edit import CLIResult, EditTool20250124, ToolResult

async def handle_editor_tool(tool_call) -> CLIResult | ToolResult:
    """
    テキストエディタツールを実行し、その結果を返します。
    """
    input_params = tool_call.input
    command = input_params.get("command", "")
    file_path = input_params.get("path", "")
    file_text = input_params.get("file_text")
    old_str = input_params.get("old_str")
    new_str = input_params.get("new_str")
    insert_line = input_params.get("insert_line")
    view_range = input_params.get("view_range")

    editor = EditTool20250124()

    try:
        result = await editor(
            command=command,
            path=file_path,
            file_text=file_text,
            old_str=old_str,
            new_str=new_str,
            insert_line=insert_line,
            view_range=view_range,
        )

        return result
    except Exception as e:
        return ToolResult(output=str(e))

あとは、ツール呼び出しが繰り返し要求されることを想定し、ループ処理を記述します。
最終、完成したのがこのようになります。

import asyncio

import anthropic

from tools.edit import CLIResult, EditTool20250124, ToolResult

client = anthropic.AnthropicBedrock(aws_region="us-east-1", max_retries=10)


async def handle_editor_tool(tool_call) -> CLIResult | ToolResult:
    """
    テキストエディタツールを実行し、その結果を返します。
    """
    input_params = tool_call.input
    command = input_params.get("command", "")
    file_path = input_params.get("path", "")
    file_text = input_params.get("file_text")
    old_str = input_params.get("old_str")
    new_str = input_params.get("new_str")
    insert_line = input_params.get("insert_line")
    view_range = input_params.get("view_range")

    editor = EditTool20250124()

    try:
        result = await editor(
            command=command,
            path=file_path,
            file_text=file_text,
            old_str=old_str,
            new_str=new_str,
            insert_line=insert_line,
            view_range=view_range,
        )
    return result
    except Exception as e:
        return ToolResult(output=str(e))


async def main(content):
    messages = []
    messages.append(
        {
            "role": "user"
            "content": content,
        }
    )

    while True:
        # Claude 3.7 Sonnetに呼び出し
        response = client.messages.create(
            model="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
            max_tokens=10240,
            tools=[
                {"type": "text_editor_20250124", "name": "str_replace_editor"},
            ],
            messages=messages,
        )

        # 応答がtool_useでない場合、出力を表示してループを終了します。
        if not response.stop_reason == "tool_use":

            # 出力してBreak
            for content in response.content:
                if content.type == "text":
                    print(content.text)
                if content.type == "thinking":
                    print(content.thinking)
                if content.type == "redacted_thinking":
                    print(content.data)

            break

        # tool_useの場合、ツールの結果を処理します。
        tool_result_message = {"role": "user", "content": []}

        for content in response.content:
            if content.type == "text":
                print(content.text)
            if content.type == "thinking":
                print(content.thinking)
            if content.type == "redacted_thinking":
                print(content.data)
            if content.type == "tool_use":
                # Execute the tool based on command
                result = await handle_editor_tool(content)

                print(result.output)

                # Return result to Claude
                tool_result = {
                    "type": "tool_result",
                    "tool_use_id": content.id,
                    "content": result.output,
                }}
                tool_result_message["content"].append(tool_result)

        messages.append({"role": "assistant", "content": response.content})
        messages.append(tool_result_message)


if __name__ == "__main__":

    prompt = "There's a syntax error in my primes.py file. Can you help me fix it?"

    print(f"入力: {prompt}")

    asyncio.run(main(prompt))

Clineっぽいことができるかな?

早速やってみましょう。

Promptを以下のように指定しました。

primes.pyにシンタックスエラーがあります。修正してください。現在のディレクトリは`{os.getcwd()}`です。

何度か試したところ、パスの情報を渡さないと期待動作にならないことがわかったので、カレントディレクトリを渡すようにしました

ちなみに仕込んだシンタックスエラーは以下の3つです。

  • Returnのインデントがおかしい
    image.png
  • JSONのカンマが不足している
    image.png
  • JSONの閉じ括弧が多い
    image.png

IDEのエラー表示がなかったら、目Grepでも結構しんどやつです。


実行してみましょう。

😃ユーザー入力:
primes.pyにシンタックスエラーがあります。修正してください。現在のディレクトリは/workspaces/2503_cline/claude-new-toolです。

🤖Claude:
シンタックスエラーがあるというprimes.pyファイルを確認し、修正するために、まずファイルの内容を確認しましょう。

Tool実行結果
Here's the result of running `cat -n` on /workspaces/2503_cline/claude-new-tool/primes.py:
     1  import asyncio
     2
     3  import anthropic
     4
     5  from tools.edit import CLIResult, EditTool20250124, ToolResult
     6
     7  client = anthropic.AnthropicBedrock(aws_region="us-east-1", max_retries=10)
     8
     9
    10  async def handle_editor_tool(tool_call) -> CLIResult | ToolResult:
    11      """
    12      テキストエディタツールを実行し、その結果を返します。
    13      """
    14      input_params = tool_call.input
    15      command = input_params.get("command", "")
    16      file_path = input_params.get("path", "")
    17      file_text = input_params.get("file_text")
    18      old_str = input_params.get("old_str")
    19      new_str = input_params.get("new_str")
    20      insert_line = input_params.get("insert_line")
    21      view_range = input_params.get("view_range")
    22
    23      editor = EditTool20250124()
    24
    25      try:
    26          result = await editor(
    27              command=command,
    28              path=file_path,
    29              file_text=file_text,
    30              old_str=old_str,
    31              new_str=new_str,
    32              insert_line=insert_line,
    33              view_range=view_range,
    34          )
    35      return result
    36      except Exception as e:
    37          return ToolResult(output=str(e))
    38
    39
    40  async def main(content):
    41      messages = []
    42      messages.append(
    43          {
    44              "role": "user"
    45              "content": content,
    46          }
    47      )
    48
    49      while True:
    50          # Claude 3.7 Sonnetに呼び出し
    51          response = client.messages.create(
    52              model="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
    53              max_tokens=10240,
    54              tools=[
    55                  {"type": "text_editor_20250124", "name": "str_replace_editor"},
    56              ],
    57              messages=messages,
    58          )
    59
    60          # 応答がtool_useでない場合、出力を表示してループを終了します。
    61          if not response.stop_reason == "tool_use":
    62
    63              # 出力してBreak
    64              for content in response.content:
    65                  if content.type == "text":
    66                      print(content.text)
    67                  if content.type == "thinking":
    68                      print(content.thinking)
    69                  if content.type == "redacted_thinking":
    70                      print(content.data)
    71
    72              break
    73
    74          # tool_useの場合、ツールの結果を処理します。
    75          tool_result_message = {"role": "user", "content": []}
    76
    77          for content in response.content:
    78              if content.type == "text":
    79                  print(content.text)
    80              if content.type == "thinking":
    81                  print(content.thinking)
    82              if content.type == "redacted_thinking":
    83                  print(content.data)
    84              if content.type == "tool_use":
    85                  # Execute the tool based on command
    86                  result = asyncio.run(handle_editor_tool(content))
    87
    88                  print(result.output)
    89
    90                  # Return result to Claude
    91                  tool_result = {
    92                      "type": "tool_result",
    93                      "tool_use_id": content.id,
    94                      "content": result.output,
    95                  }}
    96                  tool_result_message["content"].append(tool_result)
    97
    98          messages.append({"role": "assistant", "content": response.content})
    99          messages.append(tool_result_message)
   100
   101
   102  if __name__ == "__main__":
   103
   104      prompt = "There's a syntax error in my primes.py file. Can you help me fix it?"
   105
   106      print(f"入力: {prompt}")
   107
   108      asyncio.run(main(prompt))
   109

🤖Claude:
コードを確認したところ、いくつかのシンタックスエラーがあります。修正が必要な箇所は以下の通りです:

  1. 35行目と36行目の間でインデントエラー - tryブロックの後にreturnがきてからexceptが来ています。
  2. 44行目の"role": "user"の後にカンマが抜けています。
  3. 95行目の閉じ括弧が余分になっています。

これらのエラーを修正します:

Tool実行結果
The file /workspaces/2503_cline/claude-new-tool/primes.py has been edited. Here's the result of running `cat -n` on a snippet of /workspaces/2503_cline/claude-new-tool/primes.py:
    21      view_range = input_params.get("view_range")
    22
    23      editor = EditTool20250124()
    24
    25      try:
    26          result = await editor(
    27              command=command,
    28              path=file_path,
    29              file_text=file_text,
    30              old_str=old_str,
    31              new_str=new_str,
    32              insert_line=insert_line,
    33              view_range=view_range,
    34          )
    35          return result
    36      except Exception as e:
    37          return ToolResult(output=str(e))
    38
    39
    40  async def main(content):
    41      messages = []
Review the changes and make sure they are as expected. Edit the file again if necessary.
Tool実行結果
The file /workspaces/2503_cline/claude-new-tool/primes.py has been edited. Here's the result of running `cat -n` on a snippet of /workspaces/2503_cline/claude-new-tool/primes.py:
    38
    39
    40  async def main(content):
    41      messages = []
    42      messages.append(
    43          {
    44              "role": "user",
    45              "content": content,
    46          }
    47      )
    48
    49      while True:
    50          # Claude 3.7 Sonnetに呼び出し
    51          response = client.messages.create(
Review the changes and make sure they are as expected. Edit the file again if necessary.
Tool実行結果
The file /workspaces/2503_cline/claude-new-tool/primes.py has been edited. Here's the result of running `cat -n` on a snippet of /workspaces/2503_cline/claude-new-tool/primes.py:
    86                  result = asyncio.run(handle_editor_tool(content))
    87
    88                  print(result.output)
    89
    90                  # Return result to Claude
    91                  tool_result = {
    92                      "type": "tool_result",
    93                      "tool_use_id": content.id,
    94                      "content": result.output,
    95                  }
    96                  tool_result_message["content"].append(tool_result)
    97
    98          messages.append({"role": "assistant", "content": response.content})
    99          messages.append(tool_result_message)
   100
Review the changes and make sure they are as expected. Edit the file again if necessary.

🤖Claude:
primes.pyのシンタックスエラーを修正しました。以下の3つの問題を解決しました:

  1. インデントエラー(35-36行目):

    • tryブロック内でreturn resultが正しくインデントされていなかったため、tryexceptの間に来ていました
    • return resulttryブロック内に適切に配置しました
  2. カンマの欠落(44行目):

    • 辞書内の"role": "user"の後にカンマが欠けていました
    • カンマを追加して"role": "user",に修正しました
  3. 余分な閉じ括弧(95行目):

    • tool_result辞書定義の後に余分な閉じ括弧}がありました
    • 余分な閉じ括弧を削除しました

これらの修正により、コードのシンタックスエラーは解決されました。プログラムが正常に実行できるようになりました。

すごい!!!
ですが、Claude 3.7 Sonnetにとってはお茶の子さいさいなのかもしれませんね。

念の為お伝えしておくと、変なところを見つけただけじゃなくて、修正まで完璧です

ちなみに

Anthropic SDKを使わずConverse APIで利用する場合は以下のようになります。

toolConfigtoolsを指定するとバリデーションエラーになるので、additionalModelRequestFieldsで指定する必要があります。

import boto3

client = boto3.client("bedrock-runtime")

response = client.converse(
    modelId="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
    inferenceConfig={
        "maxTokens": 10240,
    },
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "text": "There's a syntax error in my primes.py file. Can you help me fix it?",
                }
            ],
        }
    ],
    additionalModelRequestFields={
        "tools": [
            {"type": "text_editor_20250124", "name": "str_replace_editor"},
        ],
    },
)
51
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?