🐕
LLMエージェントの基礎実装
効率的なエージェントを開発するための考え方とパターンをまとめた資料(Building effective agents)をAnthropic 社が発表しました。この資料は、どういうタスクには対してはどういうエージェントが適しているかという観点から、エージェントの設計についての指針を提供しています。
本記事は、この資料を3つのストーリーポイントに分けてキャッチアップしたもののひとつになります。
近年、LLM(Large Language Model) を活用したエージェント技術が急速に発展しています。LLMエージェントとは、大規模言語モデルを基盤とし、外部ツールと連携しながら特定のタスクを自律的に実行するシステムです。本記事では、LLMエージェントの基本概念、構成要素、および実装方法をTypeScriptのコードスニペットを交えて解説します。
LLMエージェントの定義と分類
LLMエージェントは、動作の特性に応じて大きく以下の2つに分類されます。
① 自律的なシステム(Autonomous Agent)
- LLMが自ら意思決定を行い、外部ツールを活用しながらタスクを遂行します。
- 例:カスタマーサポートボット、ドキュメント要約システム。
② 定義済みワークフローに従うシステム(Scripted Workflow)
- 事前に定義された手順に従い、LLMがツールと連携しながらタスクを処理します。
- 例:特定の質問に対するFAQボット、データ抽出パイプライン。
エージェントの本質的な違いは、「動的な意思決定を行うか(エージェント型)」 vs 「事前に決められた手順を実行するか(ワークフロー型)」という点にあります。
LLMエージェントの主要構成要素
LLMエージェントは、主に以下の3つの機能を備えた「拡張LLM」を中心に設計されます。
🔍 検索(Retrieval)
- LLMが外部のデータベースやWeb検索エンジンを利用し、リアルタイムで情報を取得する。
- 例:Google検索API、ベクトルデータベース。
🛠 ツール連携(Tool Integration)
- LLMが特定のAPIやツールを活用し、計算・翻訳・データ処理などの機能を実行する。
- 例:電卓API、翻訳API、コード実行環境。
🧠 メモリ(Memory)
- 過去の会話履歴やタスク実行履歴を保持し、文脈を理解しながら対話や処理を進める。
- 例:短期記憶(セッション内履歴)、長期記憶(永続的データストレージ)。
代表的なワークフローの種類
LLMエージェントは、以下のようなワークフローと組み合わせて活用できます。
ワークフロー名 | 概要 | 適用例 |
---|---|---|
プロンプトチェーン | タスクを複数のステップに分解し、各ステップでLLMを活用 | 分析レポート生成、プログラム修正支援 |
ルーティング | ユーザー入力を分類し、適切な処理フローへ誘導 | FAQチャットボット、問い合わせ対応 |
並列化 | 複数のLLMが同時に異なる処理を実行し、結果を統合 | 大規模データ要約、マルチモーダル処理 |
オーケストレーター&ワーカー | 中央のLLMがタスクを分解し、サブエージェントに割り当て | 自動記事生成、プロジェクト管理 |
評価器&最適化器 | LLMの出力品質を評価し、フィードバックループで改善 | コードレビュー、文章校正 |
TypeScriptによるLLMエージェントの実装
以下のTypeScriptコードは、LLMと外部ツールを連携させるエージェントの基本構造を示したものです。
interface LLM {
generate(task: string): Promise<string>;
}
interface Tool {
name: string;
description: string;
execute: (input: string) => Promise<string>;
}
class LLMAgent {
private llm: LLM;
private tools: Tool[];
private parser: ToolCallParser;
constructor(llm: LLM, tools: Tool[], parser: ToolCallParser) {
this.llm = llm;
this.tools = tools;
this.parser = parser;
}
async run(task: string): Promise<string> {
let currentTask = task;
while (true) {
const response = await this.llm.generate(currentTask);
const toolCall = this.parser.parse(response);
if (toolCall) {
const tool = this.findTool(toolCall.name);
if (!tool) {
console.error(`Error: Tool "${toolCall.name}" not found.`);
return "Error: Tool not found."; // より具体的なエラーメッセージ
}
const result = await tool.execute(toolCall.input);
currentTask = this.updateTask(currentTask, result);
} else {
return response;
}
}
}
private updateTask(currentTask: string, result: string): string {
return `${currentTask} -> ツール実行結果: ${result}`;
}
private findTool(name: string): Tool | undefined {
return this.tools.find(tool => tool.name === name);
}
}
interface ToolCall {
name: string;
input: string;
}
interface ToolCallParser {
parse(response: string): ToolCall | null;
}
class JsonToolCallParser implements ToolCallParser {
parse(response: string): ToolCall | null {
try {
return JSON.parse(response);
} catch (e) {
console.error("Error parsing tool call", e);
return null;
}
}
}
const searchTool: Tool = {
name: "search",
description: "ウェブ検索を実行",
execute: async (input: string) => {
return `検索結果: ${input} に関する情報を取得しました。`;
}
};
const mockLLM: LLM = {
generate: async (task: string) => {
if (task.includes("search")) {
return `{"name": "search", "input": "LLMエージェント"}`;
}
return `タスク完了: ${task}`;
}
};
const jsonParser = new JsonToolCallParser();
const agent = new LLMAgent(mockLLM, [searchTool], jsonParser);
(async () => {
const result = await agent.run("LLMエージェントについて調べてください。");
console.log(result);
})();
この実装のポイント
- ✅ LLMの応答を解析し、必要に応じてツールを呼び出す。
- ✅ ツールの実行結果を考慮し、タスクを継続的に更新する。
- ✅ モックLLMを活用し、実際のAPIなしでもテスト可能。
LLMエージェント実装時の注意点
- コスト管理:APIの呼び出し回数が増えると、コストが急増する可能性がある。
- エラー処理:エージェントの意思決定ミスを防ぐため、エラー検出とフェイルセーフを設計する。
- ツールの安全性:エージェントが実行するツールが適切に制限されているか検証する。
まとめ
LLMエージェントは、検索・ツール連携・メモリ機能を備えた拡張LLMを活用し、柔軟にタスクを処理する強力なシステムです。本記事では、LLMエージェントの基本概念と構成要素を解説し、TypeScriptでの実装例を紹介しました。
Discussion