この記事の対象読者
- Pythonの基本文法(関数、クラス、import)を理解している方
- ChatGPTやClaudeなどのLLMを使ったことがある方
- 「AIエージェント」という言葉を聞いて「結局何が違うの?」と感じている方
この記事で得られること
- 概念の理解: AIエージェント、AIワークフロー、AI組み込みシステムの違いが明確になる
- 実装スキル: 各アーキテクチャを実際にPythonで実装できるようになる
- 設計判断力: 自分のプロジェクトにどのアプローチが適切か判断できるようになる
この記事で扱わないこと
- LLMの内部アーキテクチャ(Transformer、Attention機構)の詳細
- 特定のフレームワーク(LangChain、AutoGen)の詳細な使い方
- マルチエージェントシステムの高度な協調パターン
1. AIエージェントとの出会い
「このLLM、賢いんだけど、言われたことしかやってくれないんだよな...」
ChatGPTを使い込んでいて、そう感じたことはありませんか?私は何度もありました。
質問すれば答えてくれる。コードを書いてと言えば書いてくれる。でも、「このバグを直して、テストを実行して、問題なければコミットしておいて」という一連の作業は、自分で一つずつ指示しないといけない。まるで優秀だけど受け身な新人のようです。
そんな中、2024年末にAnthropicが公開した「Building Effective Agents」というガイドを読んで、目から鱗が落ちました。彼らはこう定義していたんです。
「Workflows」are systems where LLMs and tools are orchestrated through predefined code paths.
「Agents」are systems where LLMs dynamically direct their own processes and tool usage.ワークフローは、LLMとツールが事前定義されたコードパスで連携するシステム。
エージェントは、LLMが自ら処理とツール使用を動的に決定するシステム。— Anthropic「Building Effective Agents」(2024年12月)
つまり、AIエージェントとは「自分で考えて、自分で行動を決められるAI」 ということ。これは単なる「賢いチャットボット」とは根本的に違うアプローチなのです。
ここまでで、AIエージェントがどんなものか、なんとなくイメージできたでしょうか。次は、この技術が生まれた背景と、混同されやすい概念との違いを整理していきましょう。
2. 前提知識の確認
本題に入る前に、この記事で使う用語を整理しておきます。
2.1 LLM(Large Language Model)とは
大量のテキストデータで学習した言語モデルです。ChatGPT、Claude、Geminiなどが代表例です。与えられた文脈から「次に来るべき単語」を予測することで、人間のような文章を生成します。
2.2 プロンプトとは
LLMに与える指示文のことです。「このコードのバグを見つけて」「この文章を要約して」といった自然言語での命令を指します。
2.3 ツール(Tool)/ 関数呼び出し(Function Calling)とは
LLMが外部システムと連携するための仕組みです。LLM単体では「今の天気」を知ることはできませんが、天気APIを「ツール」として与えることで、必要に応じてAPIを呼び出し、その結果を踏まえた回答ができるようになります。
2.4 コンテキストウィンドウとは
LLMが一度に処理できるテキストの上限です。GPT-4なら約128Kトークン、Claudeなら最大200Kトークンまで。この範囲内で「会話の履歴」や「参照資料」を保持します。
これらの用語が押さえられたら、次に進みましょう。
3. AIシステムの3つのアーキテクチャ
AI活用には主に3つのアプローチがあります。それぞれの特徴を理解することで、「どれを使うべきか」の判断ができるようになります。
3.1 AI組み込みシステム(LLM-based Application)
最もシンプルな形態です。従来のアプリケーションにLLMの機能を「追加」したものです。
特徴:
- LLMは「部品の一つ」として機能する
- 処理フローはアプリケーション側が制御する
- 単一タスク(翻訳、要約、分類など)に特化
具体例: メールアプリの自動返信機能、検索エンジンのAI要約、コードエディタの補完機能
ユーザー入力 → [アプリのロジック] → [LLM呼び出し] → [アプリのロジック] → 出力
↑ ↓
プログラムが制御 LLMは「呼ばれたら答える」だけ
3.2 AIワークフロー(Agentic Workflow)
複数のLLM呼び出しを事前に定義されたパターンで連携させるシステムです。
特徴:
- 処理の流れは人間が設計する
- 各ステップでLLMが専門的な判断を行う
- 予測可能で、デバッグしやすい
具体例: RAG(検索拡張生成)、コード生成→レビュー→修正のパイプライン
入力 → [LLM-1: 分析] → [LLM-2: 生成] → [LLM-3: 検証] → 出力
↓ ↓ ↓
固定された流れ 固定された流れ 固定された流れ
3.3 AIエージェント(AI Agent)
LLMが自ら次の行動を決定するシステムです。これが今回の主役です。
特徴:
- LLMが「何をすべきか」を自分で判断する
- 状況に応じて動的にツールを選択・実行する
- ゴール達成まで自律的にループする
具体例: Devin(AIソフトウェアエンジニア)、AutoGPT、Claude Codeのエージェントモード
ゴール設定 → [LLM: 計画立案] → [LLM: 行動選択] → [ツール実行] → [LLM: 結果評価]
↑ ↓
└──────────── 目標達成まで自律的にループ ─────────┘
これら3つの違いを表にまとめると、以下のようになります。
| 観点 | AI組み込みシステム | AIワークフロー | AIエージェント |
|---|---|---|---|
| 制御主体 | アプリケーション | 人間が設計したフロー | LLM自身 |
| 処理パターン | 単発の呼び出し | 固定されたパイプライン | 動的なループ |
| 柔軟性 | 低い | 中程度 | 高い |
| 予測可能性 | 高い | 高い | 低い |
| デバッグ難易度 | 容易 | 中程度 | 困難 |
| コスト | 低い | 中程度 | 高い |
| 適用領域 | 単一タスクの効率化 | 定型的な複合タスク | 複雑・不定形なタスク |
Anthropicのガイドでは、こう忠告しています。
When building applications with LLMs, we recommend finding the simplest solution possible, and only increasing complexity when needed.
LLMアプリケーションを構築する際は、可能な限りシンプルな解決策を見つけ、必要な場合にのみ複雑さを増すことを推奨します。
つまり、いきなりエージェントを目指す必要はないということ。多くの場合、単純なLLM呼び出しか、設計されたワークフローで十分なのです。
抽象的な概念がわかったところで、これらを具体的なコードで実装していきましょう。
4. 実装で理解する3つのアーキテクチャ
4.1 環境構築
まず、必要なパッケージをインストールします。
# 必要なパッケージのインストール
pip install anthropic python-dotenv
4.2 設定ファイルの準備
以下の3種類の設定ファイルを用意しています。用途に応じて選択してください。
開発環境用(config.yaml)
# config.yaml - 開発環境用(このままコピーして使える)
# APIキーは環境変数から読み込む
llm:
provider: "anthropic"
model: "claude-sonnet-4-20250514"
max_tokens: 1024
temperature: 0.7
agent:
max_iterations: 10
timeout_seconds: 60
verbose: true
logging:
level: "DEBUG"
format: "%(asctime)s - %(levelname)s - %(message)s"
本番環境用(config.production.yaml)
# config.production.yaml - 本番環境用
# 安定性とコスト効率を重視した設定
llm:
provider: "anthropic"
model: "claude-sonnet-4-20250514"
max_tokens: 2048
temperature: 0.3 # 安定性重視で低めに設定
agent:
max_iterations: 20
timeout_seconds: 120
verbose: false
logging:
level: "INFO"
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
file: "/var/log/ai_agent/app.log"
monitoring:
enable_metrics: true
metrics_endpoint: "/metrics"
テスト環境用(config.test.yaml)
# config.test.yaml - テスト・CI/CD環境用
# 高速実行と再現性を重視した設定
llm:
provider: "anthropic"
model: "claude-sonnet-4-20250514" # テストでもSonnetで十分
max_tokens: 512 # テスト時は短めに
temperature: 0.0 # 再現性のため完全に固定
agent:
max_iterations: 5 # テスト時間短縮
timeout_seconds: 30
verbose: true
logging:
level: "DEBUG"
format: "%(levelname)s: %(message)s"
test:
mock_external_apis: true
use_cached_responses: true
4.3 AI組み込みシステムの実装
最もシンプルな形態から始めましょう。メール返信の自動生成機能です。
"""
AI組み込みシステムのサンプル: メール返信生成
使い方: python embedded_ai.py
必要: pip install anthropic python-dotenv
"""
import os
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
def generate_email_reply(original_email: str, tone: str = "professional") -> str:
"""
受信メールに対する返信を自動生成する
Args:
original_email: 受信したメールの本文
tone: 返信のトーン(professional, friendly, formal)
Returns:
生成された返信メール
"""
client = Anthropic()
# LLMは「部品の一つ」として単発で呼び出される
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[
{
"role": "user",
"content": f"""以下のメールに対する{tone}な返信を作成してください。
【受信メール】
{original_email}
【返信】"""
}
]
)
return message.content[0].text
if __name__ == "__main__":
sample_email = """
件名: プロジェクト進捗の確認
お疲れ様です。田中です。
先日お願いしたAPIの設計書について、進捗はいかがでしょうか?
来週の月曜日までに初版をいただければ幸いです。
何か問題があればお知らせください。
"""
reply = generate_email_reply(sample_email)
print("【生成された返信】")
print(reply)
実行結果
上記のコードを実行すると、以下のような出力が得られます:
【生成された返信】
田中様
お疲れ様です。
ご連絡ありがとうございます。
API設計書の件、承知いたしました。
現在、基本設計は完了しており、エンドポイントの詳細仕様を
ドキュメント化している段階です。
来週月曜日までに初版をお送りできる見込みです。
何かご不明点があればお気軽にご連絡ください。
よろしくお願いいたします。
これが最もシンプルな「AI組み込み」パターンです。アプリケーションが制御し、LLMは単発で呼び出されるだけ。予測可能で、デバッグも簡単です。
基本的な使い方を押さえたところで、次はより複雑なワークフローパターンを見ていきましょう。
4.4 AIワークフローの実装
次に、複数のLLM呼び出しを連携させるワークフローを実装します。ここでは「コード生成→レビュー→改善」のパイプラインを作ります。
"""
AIワークフローのサンプル: コード生成パイプライン
使い方: python workflow_ai.py
必要: pip install anthropic python-dotenv
"""
import os
from dataclasses import dataclass
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
@dataclass
class CodeReviewResult:
"""コードレビューの結果を格納するクラス"""
is_approved: bool
issues: list[str]
suggestions: list[str]
class CodeGenerationWorkflow:
"""
コード生成ワークフロー
処理フロー(固定):
1. 要件からコードを生成
2. 生成されたコードをレビュー
3. レビュー結果に基づいて改善
"""
def __init__(self):
self.client = Anthropic()
self.model = "claude-sonnet-4-20250514"
def _call_llm(self, system: str, prompt: str) -> str:
"""LLMを呼び出すヘルパーメソッド"""
message = self.client.messages.create(
model=self.model,
max_tokens=2048,
system=system,
messages=[{"role": "user", "content": prompt}]
)
return message.content[0].text
def step1_generate_code(self, requirement: str) -> str:
"""ステップ1: 要件からコードを生成"""
print("📝 ステップ1: コード生成中...")
system = "あなたはPythonの専門家です。要件に基づいて、クリーンで効率的なコードを生成してください。"
prompt = f"以下の要件を満たすPythonコードを生成してください:\n\n{requirement}"
return self._call_llm(system, prompt)
def step2_review_code(self, code: str) -> CodeReviewResult:
"""ステップ2: コードをレビュー"""
print("🔍 ステップ2: コードレビュー中...")
system = """あなたはシニアソフトウェアエンジニアです。
コードをレビューし、以下の形式で回答してください:
承認: [YES/NO]
問題点:
- (あれば列挙)
改善提案:
- (あれば列挙)"""
prompt = f"以下のコードをレビューしてください:\n\n```python\n{code}\n```"
review_text = self._call_llm(system, prompt)
# レビュー結果をパース(簡易版)
is_approved = "承認: YES" in review_text or "承認:YES" in review_text
issues = []
suggestions = []
lines = review_text.split("\n")
current_section = None
for line in lines:
if "問題点:" in line:
current_section = "issues"
elif "改善提案:" in line:
current_section = "suggestions"
elif line.strip().startswith("- "):
item = line.strip()[2:]
if current_section == "issues":
issues.append(item)
elif current_section == "suggestions":
suggestions.append(item)
return CodeReviewResult(is_approved, issues, suggestions)
def step3_improve_code(self, original_code: str, review: CodeReviewResult) -> str:
"""ステップ3: レビューに基づいてコードを改善"""
print("✨ ステップ3: コード改善中...")
if review.is_approved and not review.suggestions:
return original_code
system = "あなたはPythonの専門家です。レビューフィードバックに基づいてコードを改善してください。"
feedback = ""
if review.issues:
feedback += "【問題点】\n" + "\n".join(f"- {i}" for i in review.issues) + "\n\n"
if review.suggestions:
feedback += "【改善提案】\n" + "\n".join(f"- {s}" for s in review.suggestions)
prompt = f"""以下のコードを改善してください:
```python
{original_code}
{feedback}
改善後のコードのみを出力してください。"""
return self._call_llm(system, prompt)
def run(self, requirement: str) -> str:
"""ワークフロー全体を実行(固定されたパイプライン)"""
print("=" * 50)
print("🚀 コード生成ワークフロー開始")
print("=" * 50)
# ステップ1: コード生成
code = self.step1_generate_code(requirement)
print(f"生成されたコード:\n{code[:200]}...")
# ステップ2: レビュー
review = self.step2_review_code(code)
print(f"レビュー結果: 承認={review.is_approved}")
if review.issues:
print(f" 問題点: {len(review.issues)}件")
# ステップ3: 改善(必要な場合)
final_code = self.step3_improve_code(code, review)
print("=" * 50)
print("✅ ワークフロー完了")
print("=" * 50)
return final_code
if name == "main":
workflow = CodeGenerationWorkflow()
requirement = """
ファイルの内容を読み込み、単語の出現頻度をカウントする関数を作成してください。
- エラーハンドリングを含むこと
- 大文字小文字を区別しないこと
- 結果は出現頻度の降順でソートすること
"""
final_code = workflow.run(requirement)
print("\n【最終コード】")
print(final_code)
### 実行結果
==================================================
🚀 コード生成ワークフロー開始
📝 ステップ1: コード生成中...
生成されたコード:
from collections import Counter
from pathlib import Path
def count_word_frequency(file_path: str) -> list[tuple[str, int]]:
"""
ファイルから単語の出現頻度をカウントする...
🔍 ステップ2: コードレビュー中...
レビュー結果: 承認=YES
問題点: 0件
✨ ステップ3: コード改善中...
✅ ワークフロー完了
ワークフローのポイントは、**処理の流れが固定されている**ことです。ステップ1→2→3と、必ずこの順序で実行されます。これにより予測可能性が高く、デバッグも容易になります。
ワークフローの仕組みが理解できたところで、いよいよAIエージェントの実装に進みましょう。
---
### 4.5 AIエージェントの実装
いよいよ本題です。AIエージェントは**自分で次の行動を決定**します。ここでは、ファイル操作ができる簡単なエージェントを実装します。
```python
"""
AIエージェントのサンプル: ファイル操作エージェント
使い方: python agent_ai.py
必要: pip install anthropic python-dotenv
"""
import os
import json
from pathlib import Path
from typing import Any
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
class FileOperationAgent:
"""
ファイル操作を自律的に行うAIエージェント
ワークフローとの違い:
- 処理フローは固定されていない
- LLMが状況を見て次の行動を自分で決める
- ゴール達成まで自律的にループする
"""
def __init__(self, workspace: str = "./workspace"):
self.client = Anthropic()
self.model = "claude-sonnet-4-20250514"
self.workspace = Path(workspace)
self.workspace.mkdir(exist_ok=True)
self.max_iterations = 10
# 使用可能なツールを定義
self.tools = [
{
"name": "list_files",
"description": "ワークスペース内のファイル一覧を取得する",
"input_schema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "read_file",
"description": "指定したファイルの内容を読み込む",
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "読み込むファイル名"
}
},
"required": ["filename"]
}
},
{
"name": "write_file",
"description": "指定したファイルに内容を書き込む",
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "書き込むファイル名"
},
"content": {
"type": "string",
"description": "書き込む内容"
}
},
"required": ["filename", "content"]
}
},
{
"name": "task_complete",
"description": "タスクが完了したことを報告する",
"input_schema": {
"type": "object",
"properties": {
"summary": {
"type": "string",
"description": "完了したタスクの要約"
}
},
"required": ["summary"]
}
}
]
def _execute_tool(self, name: str, input_data: dict) -> str:
"""ツールを実行して結果を返す"""
if name == "list_files":
files = list(self.workspace.glob("*"))
if not files:
return "ワークスペースにファイルはありません。"
return "ファイル一覧:\n" + "\n".join(f"- {f.name}" for f in files)
elif name == "read_file":
filepath = self.workspace / input_data["filename"]
if not filepath.exists():
return f"エラー: {input_data['filename']} が見つかりません。"
return filepath.read_text(encoding="utf-8")
elif name == "write_file":
filepath = self.workspace / input_data["filename"]
filepath.write_text(input_data["content"], encoding="utf-8")
return f"成功: {input_data['filename']} に書き込みました。"
elif name == "task_complete":
return f"TASK_COMPLETE: {input_data['summary']}"
return f"エラー: 不明なツール {name}"
def run(self, goal: str) -> str:
"""
ゴールを達成するまでエージェントを実行
これがワークフローとの最大の違い:
- 何をするかはLLMが決める
- 完了するまで自律的にループする
"""
print("=" * 60)
print(f"🤖 エージェント起動")
print(f"📎 ゴール: {goal}")
print("=" * 60)
system = """あなたはファイル操作を行うAIエージェントです。
与えられたゴールを達成するために、利用可能なツールを使って自律的に作業を進めてください。
重要なルール:
1. まず現状を把握してから行動を決める
2. 一度に一つのツールを使用する
3. タスクが完了したら必ずtask_completeを呼ぶ
4. エラーが発生したら、別のアプローチを試す"""
messages = [{"role": "user", "content": f"ゴール: {goal}"}]
for iteration in range(self.max_iterations):
print(f"\n--- イテレーション {iteration + 1}/{self.max_iterations} ---")
# LLMに次の行動を決めさせる(ここがエージェントの核心)
response = self.client.messages.create(
model=self.model,
max_tokens=1024,
system=system,
tools=self.tools,
messages=messages
)
# レスポンスを処理
assistant_content = response.content
messages.append({"role": "assistant", "content": assistant_content})
# ツール呼び出しがあるかチェック
tool_use_blocks = [
block for block in assistant_content
if block.type == "tool_use"
]
if not tool_use_blocks:
# ツール呼び出しなし = テキスト応答のみ
text_blocks = [
block.text for block in assistant_content
if hasattr(block, "text")
]
print(f"💬 エージェントの応答: {' '.join(text_blocks)[:100]}...")
continue
# ツールを実行
tool_results = []
for tool_use in tool_use_blocks:
print(f"🔧 ツール実行: {tool_use.name}")
print(f" 入力: {json.dumps(tool_use.input, ensure_ascii=False)[:100]}")
result = self._execute_tool(tool_use.name, tool_use.input)
print(f" 結果: {result[:100]}...")
# タスク完了チェック
if result.startswith("TASK_COMPLETE:"):
print("\n" + "=" * 60)
print("✅ タスク完了!")
print(result.replace("TASK_COMPLETE: ", ""))
print("=" * 60)
return result.replace("TASK_COMPLETE: ", "")
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result
})
messages.append({"role": "user", "content": tool_results})
return "最大イテレーション数に達しました。タスクは完了していません。"
if __name__ == "__main__":
agent = FileOperationAgent()
# エージェントにゴールを与える(「何をするか」は指定しない!)
goal = """
以下のタスクを完了してください:
1. ワークスペースにどんなファイルがあるか確認する
2. hello.txt というファイルを作成し、「Hello, AI Agent!」と書き込む
3. 作成したファイルの内容を確認する
"""
result = agent.run(goal)
実行結果
============================================================
🤖 エージェント起動
📎 ゴール: 以下のタスクを完了してください...
============================================================
--- イテレーション 1/10 ---
🔧 ツール実行: list_files
入力: {}
結果: ワークスペースにファイルはありません。...
--- イテレーション 2/10 ---
🔧 ツール実行: write_file
入力: {"filename": "hello.txt", "content": "Hello, AI Agent!"}
結果: 成功: hello.txt に書き込みました。...
--- イテレーション 3/10 ---
🔧 ツール実行: read_file
入力: {"filename": "hello.txt"}
結果: Hello, AI Agent!...
--- イテレーション 4/10 ---
🔧 ツール実行: task_complete
入力: {"summary": "ワークスペースの確認、hello.txtの作成と内容確認を完了"}
結果: TASK_COMPLETE: ...
============================================================
✅ タスク完了!
ワークスペースの確認、hello.txtの作成と内容確認を完了
============================================================
ワークフローとの決定的な違い
見てください。コードのどこにも「最初にlist_filesを呼び、次にwrite_fileを呼び...」という固定的なフローは書かれていません。LLMが状況を見て、自分で次のアクションを決めているのです。
これがAIエージェントの本質です。
実装パターンが理解できたところで、次は「いつどのアプローチを使うべきか」という判断基準を見ていきましょう。
4.6 よくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
AuthenticationError |
APIキー未設定 |
.envファイルにANTHROPIC_API_KEYを設定 |
RateLimitError |
API呼び出し制限 | リトライロジックを追加、または待機時間を設ける |
InvalidRequestError: max_tokens |
トークン数超過 |
max_tokensを調整、または入力を分割 |
| エージェントが無限ループ | 終了条件が不明確 |
task_completeツールの説明を明確化、max_iterationsを設定 |
| ツールが呼ばれない | ツール定義が不適切 |
descriptionをより具体的に記述 |
5. どのアーキテクチャを選ぶべきか?
ここまで3つのアプローチを見てきました。では、実際のプロジェクトでどれを選ぶべきでしょうか?
5.1 選定フローチャート
タスクは単一か複合か?
│
├─ 単一タスク → AI組み込みシステム
│ 例: 翻訳、要約、分類
│
└─ 複合タスク
│
処理フローは事前に定義できるか?
│
├─ YES → AIワークフロー
│ 例: RAG、定型的なパイプライン
│
└─ NO → AIエージェント
例: 調査タスク、創造的問題解決
5.2 判断基準の詳細
| 判断ポイント | AI組み込み | ワークフロー | エージェント |
|---|---|---|---|
| タスクの複雑さ | 単純 | 中程度 | 複雑 |
| 処理パターン | 固定 | ほぼ固定 | 動的 |
| 許容できるレイテンシ | 低 | 中 | 高 |
| 許容できるコスト | 低 | 中 | 高 |
| 必要な信頼性 | 高 | 高 | 中 |
| デバッグ容易性の要求 | 高 | 高 | 低 |
5.3 私の経験から
正直に言うと、最初からエージェントを選ぶ必要はほとんどありません。
私がRTX 5090を搭載したWindowsマシンを組んで最初にやりたかったのは「ローカルでAIエージェントを動かす」ことでした。でも実際にやってみると、ほとんどのタスクは単純なLLM呼び出しか、設計されたワークフローで十分だと気づきました。
エージェントが真価を発揮するのは、「何をすべきか事前にわからない」場合だけです。
例えば、「このバグを調査して直して」というタスク。バグの原因がわからない段階では、どのファイルを見るべきか、どんなテストを実行すべきか、事前に決められません。こういうときにエージェントが役立ちます。
逆に、「毎日9時にレポートを生成してSlackに送る」のような定型タスクには、エージェントは過剰です。普通のワークフローで十分です。
実践的な判断基準を押さえたところで、次は具体的なユースケースとコード例を見ていきましょう。
6. ユースケース別ガイド
6.1 ユースケース1: カスタマーサポートの自動応答
想定読者: SaaSプロダクトの開発者、カスタマーサポート担当者
推奨構成: AIワークフロー(問い合わせ分類→回答生成→品質チェック)
選定理由: 処理フローが定型化でき、品質の一貫性が重要なため
"""
カスタマーサポート自動応答ワークフロー
"""
from anthropic import Anthropic
from enum import Enum
class TicketCategory(Enum):
BILLING = "billing"
TECHNICAL = "technical"
ACCOUNT = "account"
OTHER = "other"
class CustomerSupportWorkflow:
def __init__(self):
self.client = Anthropic()
# カテゴリ別の知識ベース(実際はDBから取得)
self.knowledge_base = {
TicketCategory.BILLING: "返金は購入後30日以内であれば可能です。プラン変更は即座に反映されます。",
TicketCategory.TECHNICAL: "ログインできない場合はパスワードリセットをお試しください。APIエラーはドキュメントを参照してください。",
TicketCategory.ACCOUNT: "アカウント削除は設定画面から可能です。データのエクスポートは事前に行ってください。",
}
def step1_classify(self, inquiry: str) -> TicketCategory:
"""ステップ1: 問い合わせを分類"""
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=50,
messages=[{
"role": "user",
"content": f"""以下の問い合わせを分類してください。
カテゴリ: billing, technical, account, other
問い合わせ: {inquiry}
カテゴリ名のみを回答:"""
}]
)
category_str = response.content[0].text.strip().lower()
try:
return TicketCategory(category_str)
except ValueError:
return TicketCategory.OTHER
def step2_generate_response(self, inquiry: str, category: TicketCategory) -> str:
"""ステップ2: カテゴリに応じた回答を生成"""
context = self.knowledge_base.get(category, "")
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=500,
system=f"あなたはカスタマーサポート担当者です。参考情報: {context}",
messages=[{
"role": "user",
"content": f"以下の問い合わせに丁寧に回答してください:\n\n{inquiry}"
}]
)
return response.content[0].text
def step3_quality_check(self, response: str) -> tuple[bool, str]:
"""ステップ3: 回答の品質をチェック"""
check_response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=100,
messages=[{
"role": "user",
"content": f"""以下の回答を評価してください:
{response}
評価項目:
1. 丁寧な言葉遣いか
2. 具体的な情報を含むか
3. 誤解を招く表現がないか
問題あり: YES/NO
理由: (一行で)"""
}]
)
text = check_response.content[0].text
has_issue = "問題あり: YES" in text
return not has_issue, text
def run(self, inquiry: str) -> str:
"""ワークフローを実行"""
# 固定されたパイプライン
category = self.step1_classify(inquiry)
response = self.step2_generate_response(inquiry, category)
is_ok, _ = self.step3_quality_check(response)
if not is_ok:
# 品質チェック失敗時は再生成
response = self.step2_generate_response(inquiry, category)
return response
# 使用例
if __name__ == "__main__":
workflow = CustomerSupportWorkflow()
inquiry = "先月の請求が二重になっているようなのですが、確認していただけますか?"
response = workflow.run(inquiry)
print(response)
6.2 ユースケース2: コードベースの調査・分析
想定読者: ソフトウェアエンジニア、テックリード
推奨構成: AIエージェント
選定理由: どのファイルを調べるべきか事前にわからないため
"""
コードベース調査エージェント
"""
import os
from pathlib import Path
from anthropic import Anthropic
class CodeInvestigationAgent:
def __init__(self, project_root: str):
self.client = Anthropic()
self.project_root = Path(project_root)
self.max_iterations = 15
self.tools = [
{
"name": "list_directory",
"description": "ディレクトリの内容を一覧表示",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "相対パス"}
},
"required": ["path"]
}
},
{
"name": "read_file",
"description": "ファイルの内容を読み込む",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "相対パス"}
},
"required": ["path"]
}
},
{
"name": "search_code",
"description": "コード内をキーワード検索",
"input_schema": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "検索キーワード"}
},
"required": ["keyword"]
}
},
{
"name": "report_findings",
"description": "調査結果を報告して終了",
"input_schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"details": {"type": "string"},
"recommendations": {"type": "string"}
},
"required": ["summary", "details", "recommendations"]
}
}
]
def _execute_tool(self, name: str, input_data: dict) -> str:
"""ツール実行"""
if name == "list_directory":
target = self.project_root / input_data["path"]
if not target.exists():
return f"エラー: {input_data['path']} が存在しません"
items = list(target.iterdir())[:20] # 上限20件
return "\n".join(f"{'[DIR]' if i.is_dir() else '[FILE]'} {i.name}" for i in items)
elif name == "read_file":
target = self.project_root / input_data["path"]
if not target.exists():
return f"エラー: ファイルが存在しません"
content = target.read_text(encoding="utf-8", errors="ignore")
return content[:3000] # 上限3000文字
elif name == "search_code":
results = []
for py_file in self.project_root.rglob("*.py"):
try:
content = py_file.read_text(encoding="utf-8", errors="ignore")
if input_data["keyword"].lower() in content.lower():
results.append(str(py_file.relative_to(self.project_root)))
except Exception:
pass
return f"検索結果 ({len(results)}件):\n" + "\n".join(results[:10])
elif name == "report_findings":
return f"COMPLETE:{input_data['summary']}\n\n詳細:\n{input_data['details']}\n\n推奨:\n{input_data['recommendations']}"
return "不明なツール"
def investigate(self, question: str) -> str:
"""質問に基づいてコードベースを調査"""
system = """あなたはコードベースを調査するエージェントです。
質問に答えるために、ディレクトリを探索し、関連ファイルを読み、検索を活用してください。
調査が完了したらreport_findingsで結果を報告してください。"""
messages = [{"role": "user", "content": f"調査依頼: {question}"}]
for _ in range(self.max_iterations):
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=system,
tools=self.tools,
messages=messages
)
messages.append({"role": "assistant", "content": response.content})
tool_uses = [b for b in response.content if b.type == "tool_use"]
if not tool_uses:
continue
results = []
for tool in tool_uses:
result = self._execute_tool(tool.name, tool.input)
if result.startswith("COMPLETE:"):
return result[9:]
results.append({"type": "tool_result", "tool_use_id": tool.id, "content": result})
messages.append({"role": "user", "content": results})
return "調査が完了しませんでした"
# 使用例
if __name__ == "__main__":
agent = CodeInvestigationAgent("./my_project")
result = agent.investigate("このプロジェクトで認証処理はどこで行われていますか?")
print(result)
6.3 ユースケース3: ドキュメント要約(シンプルな単発処理)
想定読者: ドキュメントを大量に処理する必要がある人
推奨構成: AI組み込みシステム(単発のLLM呼び出し)
選定理由: タスクが単純で、複雑なフローが不要なため
"""
ドキュメント要約(シンプルなAI組み込み)
"""
from anthropic import Anthropic
from pathlib import Path
def summarize_document(text: str, max_length: int = 200) -> str:
"""
ドキュメントを要約する(シンプルな単発呼び出し)
エージェントやワークフローは不要。
単純なタスクには単純なソリューションを。
"""
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=500,
messages=[{
"role": "user",
"content": f"""以下のテキストを{max_length}文字以内で要約してください。
重要なポイントを箇条書きで含めてください。
テキスト:
{text}
要約:"""
}]
)
return response.content[0].text
def summarize_files(directory: str) -> dict[str, str]:
"""複数ファイルを要約(バッチ処理)"""
results = {}
dir_path = Path(directory)
for file_path in dir_path.glob("*.txt"):
content = file_path.read_text(encoding="utf-8")
summary = summarize_document(content)
results[file_path.name] = summary
print(f"✓ {file_path.name} を要約しました")
return results
# 使用例
if __name__ == "__main__":
sample_text = """
人工知能(AI)の発展は、過去10年間で急速に進んでいます。
特に深層学習の登場により、画像認識、自然言語処理、音声認識などの
分野で人間を超える性能を達成するようになりました。
2022年以降は大規模言語モデル(LLM)が注目を集め、
ChatGPTの登場により一般消費者にもAIが身近な存在となりました。
今後はマルチモーダルAIやAIエージェントの発展が期待されています。
"""
summary = summarize_document(sample_text)
print("【要約】")
print(summary)
ユースケースを把握できたところで、この記事を読んだ後の学習パスを確認しましょう。
7. 学習ロードマップ
この記事を読んだ後、次のステップとして以下をおすすめします。
初級者向け(まずはここから)
-
Anthropic公式ドキュメントを読む
- Claude API Documentation - APIの基本的な使い方
- Tool Use (Function Calling) - ツール定義の詳細
-
この記事のサンプルコードを動かす
- AI組み込みシステムのサンプルから始める
- 次にワークフローを試す
- 最後にエージェントを動かしてみる
中級者向け(実践に進む)
-
Anthropicの「Building Effective Agents」を精読する
- Building Effective Agents
- 5つのワークフローパターンを理解する
-
自分のプロジェクトに組み込む
- まずは単純なAI組み込みから始める
- 必要に応じてワークフローに発展させる
- 本当に必要な場合のみエージェント化を検討
-
Claude Codeを使ってみる
- エージェント的な動作を体験できる
- 実際のコーディングタスクで有用性を確認
上級者向け(さらに深く)
-
マルチエージェントシステムを学ぶ
-
評価とモニタリングを実装する
- エージェントの成功率を測定
- コストとレイテンシを最適化
-
本番環境へのデプロイ
- エラーハンドリングの強化
- セキュリティ対策(プロンプトインジェクション対策など)
8. まとめ
この記事では、AIエージェントについて以下を解説しました:
-
3つのアーキテクチャの違い
- AI組み込みシステム: LLMを部品として使う(制御はアプリ側)
- AIワークフロー: 複数のLLM呼び出しを固定パターンで連携
- AIエージェント: LLMが自ら次の行動を決定(自律的ループ)
-
選定基準
- シンプルなタスク → AI組み込みシステム
- 定型的な複合タスク → AIワークフロー
- 不定形な複雑タスク → AIエージェント
-
実装のポイント
- まずは最もシンプルなアプローチから始める
- 必要に応じて複雑さを増していく
- エージェントは「本当に必要な場合」のみ選択
私の所感
AIエージェントは確かに魅力的な技術です。「自律的に動くAI」という響きには、SF映画のような未来を感じさせるものがあります。
でも、実際に触ってみると、多くの場合は「普通のLLM呼び出し」や「設計されたワークフロー」で十分だと気づきます。Anthropicが「最もシンプルな解決策を見つけろ」と言っているのは、まさにその通りだと実感しました。
エージェントを使うべきタイミングは、「何をすべきか事前にわからない」ときだけ。それ以外は、シンプルに保つ方が、デバッグも楽だし、コストも安いし、予測可能性も高い。
2025年は「エージェントの年」と言われていますが、私は「エージェントが必要かどうか見極める年」だと思っています。流行に流されず、本当に必要な場面で適切に使えるようになりたいものですね。