AI Agent

AI エージェント

AI Agent を分かりやすく

AI Agent(AI エージェント)は、単なる「質問に答える AI」から一歩進んだ、「自律的に行動する AI」です。

例え話をしましょう。あなたが「週末の旅行を計画して」と頼んだとします:

通常の AI: 「いいですね!どこに行きたいですか?予算は?」

  • 受動的で、すべてをユーザーに聞く
  • 一問一答形式

AI Agent: 「承知しました。まず、あなたの過去の旅行履歴を確認します...温泉が好きなようですね。次に、今週末の天気を調べます...関東地方は晴れです。予算データベースを確認すると、3万円程度ですね。それでは、箱根の温泉宿を検索して、予約可能な宿を3つ提案します...」

  • 能動的で、自分で情報を集める
  • 複数のステップを自律的に実行
  • ツールを使いこなす

AI Agent は、「目標」を与えられると、それを達成するために必要なステップを自分で考え、適切なツールを使って実行します。まるで有能な人間のアシスタントのように動くのです。

AI Agent のアーキテクチャ

AI Agent は通常、以下の要素で構成されます:

  1. Planner(計画者): タスクを分解して実行計画を立てる
  2. Executor(実行者): 計画に基づいてツールを実行
  3. Memory(記憶): 過去の会話や実行結果を記憶
  4. Tools(ツール): 検索、計算、API 呼び出しなどの機能

Next.js × TypeScript で AI Agent を実装する

ステップ 1: Agent の基本構造

// lib/agent/types.ts
export type AgentStep = {
  thought: string // AI の思考プロセス
  action: string // 実行するアクション
  actionInput: any // アクションへの入力
  observation: string // アクション実行後の観察結果
}

export type AgentResult = {
  finalAnswer: string
  steps: AgentStep[]
  iterations: number
}

export type Tool = {
  name: string
  description: string
  execute: (input: any) => Promise<any>
}

ステップ 2: ツールの定義

// lib/agent/tools.ts
import { Tool } from './types'

// ウェブ検索ツール(簡易実装)
export const searchTool: Tool = {
  name: 'web_search',
  description: 'インターネットで情報を検索します。最新情報や事実確認に使用してください。',
  execute: async (query: string) => {
    // 実際には Google Custom Search API などを使用
    return {
      results: [
        {
          title: '検索結果のタイトル',
          snippet: '検索結果の要約...',
          url: 'https://example.com',
        },
      ],
    }
  },
}

// 計算ツール
export const calculatorTool: Tool = {
  name: 'calculator',
  description: '数学的な計算を実行します。複雑な計算式も処理できます。',
  execute: async (expression: string) => {
    try {
      const result = Function('"use strict"; return (' + expression + ')')()
      return { result }
    } catch (error) {
      return { error: '計算エラー' }
    }
  },
}

// データベース検索ツール
export const databaseSearchTool: Tool = {
  name: 'database_search',
  description: 'データベースから情報を検索します。製品情報、ユーザー情報などを取得できます。',
  execute: async ({ table, query }: { table: string; query: string }) => {
    // 実際には Prisma や SQL を使用
    return {
      results: [
        { id: 1, name: 'サンプルデータ', description: '...' },
      ],
    }
  },
}

// 全てのツールを配列にまとめる
export const availableTools: Tool[] = [
  searchTool,
  calculatorTool,
  databaseSearchTool,
]

// ツール名から取得
export function getTool(name: string): Tool | undefined {
  return availableTools.find((tool) => tool.name === name)
}

ステップ 3: ReAct パターンの実装

ReAct(Reasoning + Acting)は、AI Agent の代表的なパターンです。

// lib/agent/react-agent.ts
import OpenAI from 'openai'
import { AgentStep, AgentResult, Tool } from './types'
import { availableTools, getTool } from './tools'

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
})

const REACT_PROMPT = `あなたは自律的に行動できる AI エージェントです。以下のツールを使用できます:

{tools}

以下の形式で考え、行動してください:

Thought: 何を考えているか
Action: 使用するツール名
Action Input: ツールへの入力
Observation: ツールの実行結果

このサイクルを繰り返し、最終的な答えが分かったら以下の形式で返してください:

Thought: 最終的な答えが分かった
Final Answer: [答え]

それでは、以下の質問に答えてください:
{question}`

export async function runReActAgent(
  question: string,
  maxIterations: number = 10
): Promise<AgentResult> {
  const steps: AgentStep[] = []
  let currentIteration = 0

  // ツール情報をプロンプトに埋め込む
  const toolsDescription = availableTools
    .map((tool) => `- ${tool.name}: ${tool.description}`)
    .join('\n')

  const initialPrompt = REACT_PROMPT.replace('{tools}', toolsDescription).replace(
    '{question}',
    question
  )

  let conversationHistory = initialPrompt
  let finalAnswer = ''

  while (currentIteration < maxIterations) {
    // AI に次のステップを考えさせる
    const response = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [
        {
          role: 'system',
          content: 'あなたは ReAct パターンに従って動作する AI エージェントです。',
        },
        {
          role: 'user',
          content: conversationHistory,
        },
      ],
      temperature: 0.3,
    })

    const agentResponse = response.choices[0].message.content || ''
    console.log(`\n=== Iteration ${currentIteration + 1} ===`)
    console.log(agentResponse)

    // Final Answer がある場合は終了
    if (agentResponse.includes('Final Answer:')) {
      finalAnswer = agentResponse.split('Final Answer:')[1].trim()
      break
    }

    // Thought、Action、Action Input を抽出
    const thoughtMatch = agentResponse.match(/Thought:(.*?)(?=Action:|$)/s)
    const actionMatch = agentResponse.match(/Action:(.*?)(?=Action Input:|$)/s)
    const actionInputMatch = agentResponse.match(/Action Input:(.*?)(?=Observation:|$)/s)

    if (!thoughtMatch || !actionMatch || !actionInputMatch) {
      throw new Error('AI の応答形式が正しくありません')
    }

    const thought = thoughtMatch[1].trim()
    const action = actionMatch[1].trim()
    const actionInput = actionInputMatch[1].trim()

    // ツールを実行
    const tool = getTool(action)
    if (!tool) {
      throw new Error(`ツール「${action}」が見つかりません`)
    }

    let observation: string
    try {
      const result = await tool.execute(actionInput)
      observation = JSON.stringify(result, null, 2)
    } catch (error) {
      observation = `エラー: ${error}`
    }

    // ステップを記録
    steps.push({
      thought,
      action,
      actionInput,
      observation,
    })

    // 会話履歴に追加
    conversationHistory += `\n\n${agentResponse}\nObservation: ${observation}\n`

    currentIteration++
  }

  if (!finalAnswer && currentIteration >= maxIterations) {
    finalAnswer = '最大反復回数に達しました。タスクを完了できませんでした。'
  }

  return {
    finalAnswer,
    steps,
    iterations: currentIteration,
  }
}

ステップ 4: Agent API の実装

// app/api/agent/run/route.ts
import { runReActAgent } from '@/lib/agent/react-agent'

export async function POST(req: Request) {
  try {
    const { question } = await req.json()

    if (!question || typeof question !== 'string') {
      return new Response('question が必要です', { status: 400 })
    }

    const result = await runReActAgent(question)

    return Response.json(result)
  } catch (error) {
    console.error('Agent Error:', error)
    return new Response('エラーが発生しました', { status: 500 })
  }
}

ステップ 5: フロントエンドの実装

// app/agent/page.tsx
'use client'

import { useState } from 'react'
import { AgentResult, AgentStep } from '@/lib/agent/types'

export default function AgentPage() {
  const [question, setQuestion] = useState('')
  const [result, setResult] = useState<AgentResult | null>(null)
  const [isLoading, setIsLoading] = useState(false)

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    if (!question.trim() || isLoading) return

    setIsLoading(true)
    setResult(null)

    try {
      const response = await fetch('/api/agent/run', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ question }),
      })

      if (!response.ok) {
        throw new Error('API エラー')
      }

      const data = await response.json()
      setResult(data)
    } catch (error) {
      console.error('Error:', error)
      alert('エラーが発生しました')
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div className="container mx-auto max-w-4xl p-4">
      <h1 className="text-3xl font-bold mb-6">AI Agent デモ</h1>

      {/* 説明 */}
      <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
        <h2 className="font-semibold mb-2">AI Agent ができること:</h2>
        <ul className="list-disc list-inside space-y-1 text-sm">
          <li>ウェブ検索で最新情報を取得</li>
          <li>複雑な計算を実行</li>
          <li>データベースから情報を検索</li>
          <li>複数のツールを組み合わせてタスクを完了</li>
        </ul>
        <p className="mt-2 text-sm text-gray-600">:2024年のノーベル物理学賞受賞者の年齢の合計を計算して」
        </p>
      </div>

      {/* 入力フォーム */}
      <form onSubmit={handleSubmit} className="mb-6">
        <textarea
          value={question}
          onChange={(e) => setQuestion(e.target.value)}
          placeholder="AI Agent に質問してください..."
          className="w-full border border-gray-300 rounded-lg px-4 py-2 mb-2"
          rows={4}
          disabled={isLoading}
        />
        <button
          type="submit"
          disabled={isLoading || !question.trim()}
          className="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 disabled:bg-gray-300"
        >
          {isLoading ? '実行中...' : 'Agent を実行'}
        </button>
      </form>

      {/* 結果表示 */}
      {result && (
        <div className="bg-white border border-gray-200 rounded-lg p-6">
          <h2 className="text-xl font-semibold mb-4">実行結果</h2>

          {/* 最終的な答え */}
          <div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
            <h3 className="font-semibold text-green-900 mb-2">最終的な答え:</h3>
            <p className="text-green-900">{result.finalAnswer}</p>
          </div>

          {/* 実行ステップ */}
          <div>
            <h3 className="font-semibold mb-3">
              実行ステップ({result.iterations} 回の反復):
            </h3>
            <div className="space-y-4">
              {result.steps.map((step, index) => (
                <div
                  key={index}
                  className="border border-gray-200 rounded-lg p-4 bg-gray-50"
                >
                  <h4 className="font-semibold text-sm text-gray-600 mb-2">
                    ステップ {index + 1}
                  </h4>

                  <div className="space-y-2 text-sm">
                    <div>
                      <span className="font-semibold">💭 Thought:</span>
                      <p className="text-gray-700 ml-4">{step.thought}</p>
                    </div>

                    <div>
                      <span className="font-semibold">🔧 Action:</span>
                      <code className="ml-2 bg-blue-100 px-2 py-1 rounded">
                        {step.action}
                      </code>
                    </div>

                    <div>
                      <span className="font-semibold">📥 Action Input:</span>
                      <pre className="ml-4 bg-gray-100 p-2 rounded overflow-x-auto text-xs">
                        {step.actionInput}
                      </pre>
                    </div>

                    <div>
                      <span className="font-semibold">👁️ Observation:</span>
                      <pre className="ml-4 bg-yellow-50 p-2 rounded overflow-x-auto text-xs">
                        {step.observation}
                      </pre>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

高度な AI Agent パターン

メモリ機能の追加

// lib/agent/memory.ts
export class AgentMemory {
  private conversationHistory: Array<{ role: string; content: string }> = []

  add(role: string, content: string) {
    this.conversationHistory.push({ role, content })
  }

  get() {
    return this.conversationHistory
  }

  clear() {
    this.conversationHistory = []
  }

  // 要約して長期記憶に保存
  async summarize() {
    // 会話が長くなった場合、要約して記憶を圧縮
    // 実装は省略
  }
}

まとめ

AI Agent は、単なるチャットボットを超えた、自律的に行動できる AI システムです。Next.js と TypeScript を組み合わせることで、型安全で拡張可能な AI Agent を構築できます。

重要なポイント:

  1. ReAct パターン: 思考→行動→観察のサイクルで動作
  2. ツール連携: 複数のツールを組み合わせてタスクを完了
  3. 自律性: ユーザーの指示を受けて、自分で実行計画を立てる
  4. メモリ: 過去の会話や実行結果を記憶
  5. 無限ループ防止: 最大反復回数を設定して暴走を防ぐ

AI Agent を活用することで、カスタマーサポート、データ分析、タスク自動化など、様々な用途で AI を実用化できます。