- HOME >
- Jamstack用語集 >
- Function Calling
Function Calling
AI の関数呼び出し
Function Calling を分かりやすく
Function Calling(関数呼び出し)は、AI に「特殊能力」を与える技術です。
例え話をしましょう。あなたが秘書を雇ったとします:
通常の秘書: 知識と経験だけで答える
- 「今日の天気は晴れだと思います」(記憶や推測に基づく)
- 「株価は上がっているはずです」(不確実)
Function Calling を使う秘書: 必要に応じてツールを使う
- 「天気 API を確認します...今日の天気は晴れで、気温は 25 度です」(正確)
- 「株価 API を確認します...現在の株価は 1,234 円です」(リアルタイム)
Function Calling を使うと、AI は単なる「知識の塊」から、「実際に行動できるアシスタント」に変わります。データベースを検索したり、計算をしたり、メールを送ったり、API を呼び出したり――人間のアシスタントができることを、AI もできるようになるのです。
Function Calling の仕組み
Function Calling は以下のフローで動作します:
- 関数の定義: AI が使えるツール(関数)を定義
- AI が判断: ユーザーの質問に対して、どの関数を使うべきか AI が決定
- パラメータ抽出: 関数に渡すべきパラメータを AI が抽出
- 関数実行: アプリケーション側で実際に関数を実行
- 結果を返す: 実行結果を AI に渡して、最終的な回答を生成
Next.js × TypeScript で実装する
ステップ 1: 関数の定義
// lib/functions.ts
import { z } from 'zod'
// 天気情報を取得する関数
export const getWeatherFunction = {
name: 'get_weather',
description: '指定された都市の現在の天気情報を取得します',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '都市名(例: 東京、大阪、福岡)',
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: '温度の単位',
},
},
required: ['city'],
},
}
// データベースから製品を検索する関数
export const searchProductsFunction = {
name: 'search_products',
description: '製品データベースからキーワードで製品を検索します',
parameters: {
type: 'object',
properties: {
keyword: {
type: 'string',
description: '検索キーワード',
},
category: {
type: 'string',
description: '製品カテゴリ(オプション)',
enum: ['electronics', 'clothing', 'food', 'books'],
},
maxResults: {
type: 'number',
description: '最大検索結果数',
default: 10,
},
},
required: ['keyword'],
},
}
// 計算を実行する関数
export const calculateFunction = {
name: 'calculate',
description: '数式を計算します(加減乗除、累乗など)',
parameters: {
type: 'object',
properties: {
expression: {
type: 'string',
description: '計算式(例: "2 + 2", "10 * 5 + 3")',
},
},
required: ['expression'],
},
}
// 全ての関数を配列にまとめる
export const availableFunctions = [getWeatherFunction, searchProductsFunction, calculateFunction]
ステップ 2: 実際の関数実装
// lib/function-implementations.ts
type WeatherData = {
city: string
temperature: number
condition: string
humidity: number
}
// 天気情報取得(ダミー実装、実際は外部 API を使用)
export async function getWeather(city: string, unit: string = 'celsius'): Promise<WeatherData> {
// 実際には OpenWeatherMap などの API を呼び出す
return {
city,
temperature: unit === 'celsius' ? 25 : 77,
condition: '晴れ',
humidity: 60,
}
}
type Product = {
id: string
name: string
price: number
category: string
}
// 製品検索
export async function searchProducts(
keyword: string,
category?: string,
maxResults: number = 10
): Promise<Product[]> {
// 実際にはデータベースやElasticsearchを使用
const mockProducts: Product[] = [
{ id: '1', name: 'iPhone 15', price: 120000, category: 'electronics' },
{ id: '2', name: 'MacBook Pro', price: 250000, category: 'electronics' },
{ id: '3', name: 'AirPods Pro', price: 35000, category: 'electronics' },
]
let results = mockProducts.filter((p) => p.name.toLowerCase().includes(keyword.toLowerCase()))
if (category) {
results = results.filter((p) => p.category === category)
}
return results.slice(0, maxResults)
}
// 計算実行
export function calculate(expression: string): number {
// 安全な計算(eval は使わない)
try {
// 実際には math.js などのライブラリを使用
const result = Function('"use strict"; return (' + expression + ')')()
return result
} catch (error) {
throw new Error('無効な計算式です')
}
}
// 関数名から実装へのマッピング
export async function executeFunctionCall(functionName: string, args: any): Promise<any> {
switch (functionName) {
case 'get_weather':
return await getWeather(args.city, args.unit)
case 'search_products':
return await searchProducts(args.keyword, args.category, args.maxResults)
case 'calculate':
return calculate(args.expression)
default:
throw new Error(`Unknown function: ${functionName}`)
}
}
ステップ 3: Function Calling API の実装
// app/api/chat/function-calling/route.ts
import OpenAI from 'openai'
import { availableFunctions } from '@/lib/functions'
import { executeFunctionCall } from '@/lib/function-implementations'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
export async function POST(req: Request) {
try {
const { messages } = await req.json()
// 最初のAI呼び出し(関数を使うかどうか判断)
let response = await openai.chat.completions.create({
model: 'gpt-4',
messages,
functions: availableFunctions,
function_call: 'auto', // AI が自動で判断
})
let responseMessage = response.choices[0].message
// Function Calling が必要な場合
if (responseMessage.function_call) {
const functionName = responseMessage.function_call.name
const functionArgs = JSON.parse(responseMessage.function_call.arguments)
console.log(`AI が関数を呼び出しました: ${functionName}`, functionArgs)
// 関数を実行
const functionResult = await executeFunctionCall(functionName, functionArgs)
console.log(`関数の実行結果:`, functionResult)
// 関数の結果を AI に渡して最終的な回答を生成
const secondResponse = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
...messages,
responseMessage,
{
role: 'function',
name: functionName,
content: JSON.stringify(functionResult),
},
],
})
return Response.json({
message: secondResponse.choices[0].message.content,
functionCalled: functionName,
functionArgs,
functionResult,
})
}
// Function Calling が不要な場合は通常の回答
return Response.json({
message: responseMessage.content,
functionCalled: null,
})
} catch (error) {
console.error('Function Calling Error:', error)
return new Response('エラーが発生しました', { status: 500 })
}
}
ステップ 4: フロントエンドの実装
// app/function-calling/page.tsx
'use client'
import { useState } from 'react'
type Message = {
role: 'user' | 'assistant' | 'system'
content: string
functionCalled?: string
functionResult?: any
}
export default function FunctionCallingPage() {
const [messages, setMessages] = useState<Message[]>([])
const [input, setInput] = useState('')
const [isLoading, setIsLoading] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!input.trim() || isLoading) return
const userMessage: Message = {
role: 'user',
content: input,
}
setMessages((prev) => [...prev, userMessage])
setInput('')
setIsLoading(true)
try {
const response = await fetch('/api/chat/function-calling', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [...messages, userMessage].map((m) => ({
role: m.role,
content: m.content,
})),
}),
})
const data = await response.json()
const assistantMessage: Message = {
role: 'assistant',
content: data.message,
functionCalled: data.functionCalled,
functionResult: data.functionResult,
}
setMessages((prev) => [...prev, assistantMessage])
} catch (error) {
console.error('Error:', error)
} finally {
setIsLoading(false)
}
}
return (
<div className="container mx-auto max-w-4xl p-4">
<h1 className="text-3xl font-bold mb-6">Function Calling デモ</h1>
{/* 使用可能な機能の説明 */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<h2 className="font-semibold mb-2">AI が使える機能:</h2>
<ul className="list-disc list-inside space-y-1 text-sm">
<li>天気情報の取得(例: 「東京の天気を教えて」)</li>
<li>製品検索(例: 「iPhone を検索して」)</li>
<li>計算(例: 「125 × 47 を計算して」)</li>
</ul>
</div>
{/* メッセージ表示 */}
<div className="bg-gray-50 rounded-lg p-4 h-[500px] overflow-y-auto mb-4">
{messages.map((message, index) => (
<div
key={index}
className={`mb-4 ${message.role === 'user' ? 'text-right' : 'text-left'}`}
>
<div
className={`inline-block rounded-lg px-4 py-2 max-w-[80%] ${
message.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-white border border-gray-200'
}`}
>
<p className="text-xs mb-1">{message.role === 'user' ? 'あなた' : 'AI'}</p>
<p className="whitespace-pre-wrap">{message.content}</p>
{/* 関数呼び出し情報を表示 */}
{message.functionCalled && (
<div className="mt-2 pt-2 border-t border-gray-200">
<p className="text-xs text-gray-500">
🔧 使用した機能: <code>{message.functionCalled}</code>
</p>
<details className="mt-1">
<summary className="text-xs text-gray-500 cursor-pointer">実行結果を表示</summary>
<pre className="text-xs bg-gray-100 p-2 rounded mt-1 overflow-x-auto">
{JSON.stringify(message.functionResult, null, 2)}
</pre>
</details>
</div>
)}
</div>
</div>
))}
</div>
{/* 入力フォーム */}
<form onSubmit={handleSubmit} className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メッセージを入力..."
className="flex-1 border border-gray-300 rounded-lg px-4 py-2"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 disabled:bg-gray-300"
>
{isLoading ? '送信中...' : '送信'}
</button>
</form>
</div>
)
}
高度な Function Calling パターン
複数の関数を連続で呼び出す
// AI が複数の関数を順番に呼び出すケース
export async function POST(req: Request) {
const { messages } = await req.json()
const conversationMessages = [...messages]
let iterations = 0
const maxIterations = 5 // 無限ループ防止
while (iterations < maxIterations) {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: conversationMessages,
functions: availableFunctions,
function_call: 'auto',
})
const responseMessage = response.choices[0].message
// Function Calling がない場合は終了
if (!responseMessage.function_call) {
return Response.json({
message: responseMessage.content,
iterations,
})
}
// Function Calling を実行
const functionName = responseMessage.function_call.name
const functionArgs = JSON.parse(responseMessage.function_call.arguments)
const functionResult = await executeFunctionCall(functionName, functionArgs)
// 会話履歴に追加
conversationMessages.push(responseMessage)
conversationMessages.push({
role: 'function',
name: functionName,
content: JSON.stringify(functionResult),
})
iterations++
}
return new Response('最大反復回数に達しました', { status: 500 })
}
まとめ
Function Calling は、AI を「実際に行動できるアシスタント」に変える強力な技術です。Next.js と TypeScript を組み合わせることで、型安全で拡張可能な Function Calling システムを構築できます。
重要なポイント:
- 関数定義の明確化: 各関数の目的とパラメータを詳細に記述
- エラーハンドリング: 関数実行の失敗を適切に処理
- セキュリティ: 危険な関数の実行を制限
- ログ記録: どの関数がいつ呼ばれたかを記録
- 無限ループ防止: 関数呼び出しの回数を制限
Function Calling を活用することで、データベース統合、外部 API 連携、計算処理など、AI アプリケーションの可能性が大きく広がります。