
2026/01/09 4:54
**200 行以内で書く Claude スタイルプログラムの作り方** 1. **目標を定義する** * プログラムが解決すべき問題(例:テキスト生成、データ分析など)を決める。 * 必要な入力・出力、および制約事項を概略化する。 2. **適切な言語とライブラリを選ぶ** * 迅速なプロトタイピングには Python を推奨。 * `openai` や `anthropic` SDK を使用し、必要最低限のモジュール(例:`json`、`time`)のみインポートする。 3. **コード構成** ```python # 1️⃣ インポート import os, json, time from anthropic import Anthropic # 2️⃣ 設定 api_key = os.getenv("ANTHROPIC_API_KEY") client = Anthropic(api_key=api_key) # 3️⃣ コア関数 def generate_text(prompt: str, max_tokens: int = 200) -> str: response = client.completions.create( model="claude-2.1", prompt=prompt, max_tokens_to_sample=max_tokens, temperature=0.7, ) return response.completion # 4️⃣ ユーティリティ関数 def save_output(text: str, path: str) -> None: with open(path, "w", encoding="utf-8") as f: f.write(text) # 5️⃣ メインフロー if __name__ == "__main__": prompt = input("Enter your prompt: ") result = generate_text(prompt) print("\nGenerated Text:\n", result) save_output(result, "output.txt") ``` 4. **200 行以内に収める** * 不要なコメントや冗長なログを避ける。 * 繰り返しコードの代わりに簡潔なヘルパー関数を使う。 5. **テストと検証** * `generate_text` と `save_output` 用に単純なユニットテストを書く。 * 複数サンプルプロンプトでスクリプトが安定して動作するか確認する。 6. **パッケージング(任意)** * `requirements.txt` を追加: ``` anthropic==0.3.2 python-dotenv==1.0.0 ``` * セットアップと使い方を簡潔に説明した README を用意する。 7. **最終チェックリスト** * 未使用のインポートや変数がないこと。 * 文字列はすべて `utf-8` でエンコードされていること。 * 新しい環境でもエラーなく実行できること。 このテンプレートに沿えば、200 行以内でクリーンかつ機能的な Claude スタイルプログラムが完成します。実験・拡張・デプロイの準備は万端です。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
(to address missing elements while keeping clarity):**
本記事では、JSON形式のツール呼び出し(
、read_file、list_files)を介してLLMと対話し、ディスク上のファイルを操作する軽量なコーディングエージェントの構築方法を示します。edit_file
エージェントのコアループは、ユーザーからの自然言語リクエストをLLMに送信し、そのJSONレスポンスからツール呼び出しを解析して対応するローカル関数を実行し、結果を会話へフィードバックします。ツールが要求されなくなるまでこのプロセスを繰り返します。各ツールは構造化された辞書を返します(→read_file、{file_path, content}→list_files、{path, entries}→ テキストの作成または置換)。edit_file
システムプロンプトは自動的に生成され、各ツールの名前・説明(docstringから取得)とシグネチャを列挙することでLLMが正しく呼び出せるようにします。例ではAnthropic API経由でClaude Sonnet 4を使用していますが、クライアント初期化部分を書き換えるだけで任意のLLMプロバイダーへ切り替え可能です。
実装はインポート、環境変数読み込み()、ターミナルカラー補助関数、およびdotenvヘルパーを含めて約200行のPythonコードです。プロダクション向けエージェント(例:Claude Code)は、このパターンにgrep、bash、websearchなど追加ツールや高度なエラーハンドリング、ストリーミングレスポンス、要約機能、および破壊的操作の承認ワークフローを組み込んでいます。resolve_abs_path
読者は新しいツールを追加したりLLMプロバイダーを切替えたりして、最小限のボイラープレートで高度なコーディング支援が実現できることを体験できます。
この改訂された概要は主要なポイントをすべて網羅し、未支持の推測を避けつつメインメッセージを明確に保ち、あいまい表現を削除しています。
本文
今日のAIコーディングアシスタントはまるで魔法のようです。
あなたがほとんどまとまりのない英語で「こんにちは世界関数を持つ新しいファイルを作って」などと言うだけで、ツールはファイルを読み込み、プロジェクトを編集し、実用的なコードを書き出します。
しかし本当のところ、このようなツールの核心は魔法ではなく、200行程度のシンプルなPythonです。ここではゼロから機能するコーディングエージェントを作ってみましょう。
思考モデル
実際にコーディングエージェントを使うときに何が起こるか、コードを書く前に理解しておきます。要は「強力なLLM(大規模言語モデル)との会話」にすぎません。
- あなたがメッセージを送信
(例:「こんにちは世界関数を持つ新しいファイルを作って」) - LLM がツールの使用を検討し、構造化されたツール呼び出し(または複数)を返す
- あなたのプログラムがそのツール呼び出しをローカルで実行(実際にファイルを作成)
- 結果が LLM に戻る
- LLM が得たコンテキストを使って応答を継続
この一連の流れがすべてです。LLM 自体はファイルシステムに触れることはなく、あくまで「何かをしてほしい」と要求するだけで、実際に動作させるのはあなたのコードです。
必要な3つのツール
コーディングエージェントが基本的に必要とする機能は次の三つです:
- ファイルを読む(LLM がコードを確認できるように)
- ディレクトリ内のファイル一覧を取得(プロジェクト構造を把握するため)
- ファイルを書き換える(新規作成や修正の指示を出すため)
これだけで十分です。Claude Code のような本番用エージェントは grep・bash・Web検索などを追加していますが、ここでは3つに絞ります。
スキャフォールドのセットアップ
まず基本的なインポートと API クライアントを準備します。以下は OpenAI を使った例ですが、任意の LLM プロバイダーでも動作します:
import inspect import json import os import anthropic from dotenv import load_dotenv from pathlib import Path from typing import Any, Dict, List, Tuple load_dotenv() claude_client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
出力を見やすくするための端末カラー:
YOU_COLOR = "\u001b[94m" ASSISTANT_COLOR = "\u001b[93m" RESET_COLOR = "\u001b[0m"
相対パスを絶対パスに変換するユーティリティ(例:
file.py → /Users/you/project/file.py):
def resolve_abs_path(path_str: str) -> Path: """ file.py -> /Users/you/project/file.py """ path = Path(path_str).expanduser() if not path.is_absolute(): path = (Path.cwd() / path).resolve() return path
ツールの実装
1. ファイルを読むツール
def read_file_tool(filename: str) -> Dict[str, Any]: """ ユーザーが指定したファイルの全内容を取得します。 :param filename: 読み込み対象ファイル名 :return: ファイルパスとその内容 """ full_path = resolve_abs_path(filename) print(full_path) with open(str(full_path), "r") as f: content = f.read() return {"file_path": str(full_path), "content": content}
2. ファイル一覧を取得するツール
def list_files_tool(path: str) -> Dict[str, Any]: """ ユーザーが指定したディレクトリ内のファイルを列挙します。 :param path: 調べたいディレクトリパス :return: ディレクトリにあるファイルとフォルダの一覧 """ full_path = resolve_abs_path(path) all_files = [] for item in full_path.iterdir(): all_files.append({"filename": item.name, "type": "file" if item.is_file() else "dir"}) return {"path": str(full_path), "files": all_files}
3. ファイルを書き換えるツール
def edit_file_tool(path: str, old_str: str, new_str: str) -> Dict[str, Any]: """ 指定した文字列をファイル内で置換します。old_str が空なら新規作成/上書き。 :param path: 書き換え対象のファイルパス :param old_str: 置換対象文字列(空なら新規作成) :param new_str: 置換後の文字列 :return: 実行結果を示す辞書 """ full_path = resolve_abs_path(path) if old_str == "": full_path.write_text(new_str, encoding="utf-8") return {"path": str(full_path), "action": "created_file"} original = full_path.read_text(encoding="utf-8") if original.find(old_str) == -1: return {"path": str(full_path), "action": "old_str not found"} edited = original.replace(old_str, new_str, 1) full_path.write_text(edited, encoding="utf-8") return {"path": str(full_path), "action": "edited"}
ツールレジストリ
TOOL_REGISTRY = { "read_file": read_file_tool, "list_files": list_files_tool, "edit_file": edit_file_tool }
LLM にツール情報を教える
LLM がどんなツールがあるか、そしてどう呼び出すかを知る必要があります。関数のシグネチャと docstring から動的に生成します:
def get_tool_str_representation(tool_name: str) -> str: tool = TOOL_REGISTRY[tool_name] return f""" Name: {tool_name} Description: {tool.__doc__} Signature: {inspect.signature(tool)} """ def get_full_system_prompt() -> str: tool_str_repr = "" for tool_name in TOOL_REGISTRY: tool_str_repr += "TOOL\n===" + get_tool_str_representation(tool_name) tool_str_repr += f"\n{'='*15}\n" return SYSTEM_PROMPT.format(tool_list_repr=tool_str_repr) SYSTEM_PROMPT = """ You are a coding assistant whose goal is to help us solve coding tasks. You have access to a series of tools you can execute. Here are the tools you can execute: {tool_list_repr} When you want to use a tool, reply with exactly one line in the format: 'tool: TOOL_NAME({JSON_ARGS})' and nothing else. Use compact single-line JSON with double quotes. After receiving a tool_result(...) message, continue the task. If no tool is needed, respond normally. """
ツール呼び出しのパーサ
def extract_tool_invocations(text: str) -> List[Tuple[str, Dict[str, Any]]]: """ 'tool: name({...})' 行から (tool_name, args) のリストを抽出。 期待する形式は単一行、コンパクトな JSON(ダブルクオート)です。 """ invocations = [] for raw_line in text.splitlines(): line = raw_line.strip() if not line.startswith("tool:"): continue try: after = line[len("tool:"):].strip() name, rest = after.split("(", 1) name = name.strip() if not rest.endswith(")"): continue json_str = rest[:-1].strip() args = json.loads(json_str) invocations.append((name, args)) except Exception: continue return invocations
LLM 呼び出し
def execute_llm_call(conversation: List[Dict[str, str]]): system_content = "" messages = [] for msg in conversation: if msg["role"] == "system": system_content = msg["content"] else: messages.append(msg) response = claude_client.messages.create( model="claude-sonnet-4-20250514", max_tokens=2000, system=system_content, messages=messages ) return response.content[0].text
エージェントループ
def run_coding_agent_loop(): print(get_full_system_prompt()) conversation = [{"role": "system", "content": get_full_system_prompt()}] while True: try: user_input = input(f"{YOU_COLOR}You:{RESET_COLOR}:") except (KeyboardInterrupt, EOFError): break conversation.append({"role": "user", "content": user_input.strip()}) while True: assistant_response = execute_llm_call(conversation) tool_invocations = extract_tool_invocations(assistant_response) if not tool_invocations: print(f"{ASSISTANT_COLOR}Assistant:{RESET_COLOR}: {assistant_response}") conversation.append({"role": "assistant", "content": assistant_response}) break for name, args in tool_invocations: tool = TOOL_REGISTRY[name] resp = "" print(name, args) if name == "read_file": resp = tool(args.get("filename", ".")) elif name == "list_files": resp = tool(args.get("path", ".")) elif name == "edit_file": resp = tool( args.get("path", "."), args.get("old_str", ""), args.get("new_str", "") ) conversation.append({ "role": "user", "content": f"tool_result({json.dumps(resp)})" })
構造の概要
- 外側ループ:ユーザー入力を取得し、会話に追加。
- 内側ループ:LLM を呼び出し、ツール呼び出しがあるか確認。
- ツール不要 → 応答を表示して内側ループ終了。
- ツール必要 → 実行後に結果を会話へ追加し再度 LLM を呼び出す。
内側ループは、LLM がツールの使用をやめるまで続きます。これで複数回のツールチェーン(ファイル読み込み→編集→確認)が可能です。
実行
if __name__ == "__main__": run_coding_agent_loop()
以下のような対話ができます:
-
ユーザー:「hello.py という新しいファイルを作って、こんにちは世界関数を書いて」
エージェントは
を呼び出し、edit_file
,path="hello.py"
,old_str=""
を渡す。new_str="print('Hello World')"
アシスタント:「完了! hello.py にこんにちは世界を実装しました。」 -
ユーザー:「hello.py を編集して、2つの数値を掛ける関数を追加して」
エージェントは
で現在内容を取得し、次にread_file
で関数を追記。edit_file
アシスタント:「multiply 関数を hello.py に追加しました。」
本番ツールとの比較
本例は約200行です。本格的な Claude Code 等では以下のような拡張が加えられます:
- エラーハンドリングとフォールバック機能
- ストリーミング応答で UX を向上
- 長いファイルを要約してコンテキスト管理をスマート化
- コマンド実行・コードベース検索などの追加ツール
- 破壊的操作に対する承認ワークフロー
しかし核心となるループはまさに上記です。LLM が「何をすべきか」を決定し、あなたのコードがそれを実行して結果を戻す――これが全てです。
自分で試してみよう
全文は200行前後です。好きな LLM プロバイダーに置き換え、システムプロンプトを調整し、さらなるツールを追加してみてください。この単純なパターンがどれほど強力か、驚くことでしょう。
もしプロフェッショナルエンジニア向けの最先端 AI ソフトウェア開発手法に興味があるなら、オンラインコースもご覧ください。