LLM アプリ開発 #10 AI エージェントを作る
第6回で、Claude がツールを一度呼び、私たちが結果を返す流れを見ました。エージェントはこれを一段引き上げたものです。Claude が目標を受け取り、自分でどのツールをどの順で使うかを判断し、複数の段階を踏んで仕事を最後まで処理します。今回はそのエージェントを作ります。
エージェントとは #
これまでは私たちが流れを決めていました。「検索して、その結果で答えよ」のように、順序がコードに埋め込まれていました。エージェントは違います。私たちはツールと目標だけを与え、何を先にするか、次に何をするかは Claude が決めます。ツールを使い、結果を見て、次の行動を決め、またツールを使うことを、目標を達成するまで繰り返します。
この繰り返しをエージェントループと呼びます。じつは第6回のツール実行ループと構造が同じです。違うのは、ツールが複数で、段階が何度もあり、順序を Claude が決めるという点です。
複数のツールを与える #
エージェントにはふつう、ツールを複数与えます。たとえば文書検索と計算ツールを一緒に与えると、Claude は質問に応じて二つを自分で組み合わせます。
tools = [
{
"name": "search_docs",
"description": "社内文書から質問に関連する内容を検索する。",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
{
"name": "calculate",
"description": "数式を計算する。例: '1200 * 0.1'",
"input_schema": {
"type": "object",
"properties": {"expression": {"type": "string"}},
"required": ["expression"],
},
},
]
def run_tool(name: str, args: dict) -> str:
if name == "search_docs":
return search_docs(args["query"]) # 第8回の検索を再利用
if name == "calculate":
return str(eval(args["expression"])) # 例。実際には安全な計算機を使う
return "不明なツールです。"「返金手数料はいくら?」と尋ねると、Claude はまず search_docs で手数料の規定を探し、計算が必要なら calculate を続けて呼びます。この順序を私たちが組まなくても、Claude が決めます。
エージェントループ #
ループ自体は第6回と同じですが、安全のため最大の繰り返し回数を設けます。エージェントがツールを際限なく呼んで止まらない状況を防ぐためです。
import anthropic
client = anthropic.Anthropic()
def run_agent(goal: str, max_steps: int = 10) -> str:
messages = [{"role": "user", "content": goal}]
for _ in range(max_steps):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
return next(b.text for b in response.content if b.type == "text")
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
return "最大ステップ数に達しました。"
print(run_agent("返金手数料の規定を探し、5万ウォンの注文の手数料を計算してください。"))Claude がツールを呼ぶのをやめて end_turn で答えを終えると、結果を返します。そうでなければツールを実行し、結果を入れ直してループを続けます。max_steps は安全装置です。何らかの理由でエージェントが終えられないとき、決めた回数で止まります。
エージェントが合う仕事 #
エージェントが常に正解ではありません。段階が事前にすべてわかる仕事なら、コードで順序を組むほうが速くて予測しやすいです。エージェントは、事前に順序を決めにくい仕事に合います。質問によって必要なツールと段階が変わり、途中の結果を見て次を決める必要がある場合です。
費用とリスクも合わせて見ます。エージェントは複数回呼び出すので、単一の呼び出しより高く、遅いです。また誤ったツールを呼んだり誤った判断をしたりしうるので、取り消しにくい作業(決済、削除など)には人の確認をはさむのが安全です。
よくつまずくところ #
- 終了条件がない —
max_stepsのような上限がないと、エージェントが同じツールを繰り返して止まらず、費用が暴走しかねません。必ず上限を設けます。 - 危険なツールをそのまま任せる —
evalのような危険な実行や、決済・削除のようなツールは、そのまま自動実行してはいけません。入力を検証し、必要なら実行前に人の承認を取ります。 - ツールの説明が重なる — 複数ツールの説明が似ていると、Claude がどれを使うか迷います。各ツールの用途を明確に分けて書きます。
まとめ #
今回は、Claude が自分でツールを選び、複数の段階を処理するエージェントを作りました。
- エージェントはツールと目標だけ与えれば、順序を自分で決めて目標を達成するまでツールの使用を繰り返します。
- ループは第6回と同じですが、安全のため最大の繰り返し回数を設けます。
- 事前に順序を決めにくい仕事に合い、高く危険になりうるので、終了条件と人の確認を設けます。
ここまではツールを私たちが直接定義してつなぎました。次回の「LLM アプリ開発 #11 MCP でツールを接続する」では、ツールを接続する標準である MCP を扱います。毎回ツールを手で書く代わりに、すでに作られたツールサーバーに Claude をつなぐ方法です。