后端开发
AI Function Calling生産運用完全ガイド:2026年版
2026年、Function CallingはLLMアプリケーション開発のインフラレベルの機能となりました。しかし「デモが動く」と「本番で動く」の間には大きな溝があります。
Function Callingの本質は「AIに関数を呼ばせること」ではなく、LLMに自由テキストではなく構造化JSONを出力させることです。関数が実際に実行されるかは、あなたのコードが決定します。
本質:構造化出力であり実行ではない
ユーザー質問 → LLM推論 → JSON出力(関数名+引数)→ あなたのコードが実行可否を判断 → 結果をLLMに返す → 最終回答
重要:LLMは決して関数を直接実行しません。 どの関数を呼ぶべきか、どんな引数を渡すべきかを「提案」するだけです。
プロトコル比較:OpenAI / Anthropic / Google
| 項目 | OpenAI | Anthropic | Google Gemini |
|---|---|---|---|
| Schemaフィールド | parameters |
input_schema |
parameters |
| 型フォーマット | JSON Schema | JSON Schema | Gemini Schema |
| 並列呼び出し | ✅ | ✅ | ✅ |
| 強制呼び出し | tool_choice: "required" |
tool_choice: {type, name} |
function_calling_config |
Spring AI + Function Calling実践
自動関数登録
@Configuration
public class ToolConfig {
@Bean
@Description("指定都市の現在の天気情報を取得")
public Function<WeatherRequest, WeatherResponse> getWeather() {
return request -> weatherService.getCurrentWeather(request.city(), request.unit());
}
@Bean
@Description("ユーザーの最近の注文履歴を照会")
public Function<OrderQueryRequest, OrderQueryResponse> queryOrders() {
return request -> orderService.queryRecentOrders(request.userId(), request.limit());
}
}
Spring AIの魔法:
Function<Req, Resp>Beanに@Descriptionを付けるだけで、フレームワークが自動的にLLMツールとして登録し、JSON Schemaを生成し、呼び出しループを処理します。
チャットサービス
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("あなたはスマートカスタマーサポートアシスタントです。")
.defaultFunctions("getWeather", "queryOrders", "createTicket")
.build();
}
public String chat(String userMessage) {
return chatClient.prompt().user(userMessage).call().content();
}
}
LangChain4j Toolアノテーション駆動開発
@Tool("ナレッジベースから関連ドキュメントを検索")
public List<DocumentResult> searchKnowledge(
@P("検索クエリ") String query,
@P("結果数、デフォルト5") int topK
) {
return vectorStore.similaritySearch(query, topK);
}
@Tool("読み取り専用SQLクエリを実行")
public QueryResult executeQuery(@P("SQLクエリ、SELECTのみ") String sql) {
if (!sql.trim().toUpperCase().startsWith("SELECT")) {
throw new IllegalArgumentException("SELECTクエリのみ許可");
}
return databaseService.executeQuery(sql);
}
並列ツール呼び出し:スケジューリング戦略
| 戦略 | ユースケース | メリット | デメリット |
|---|---|---|---|
| 完全直列 | 厳密な依存関係 | シンプルで信頼性高い | 遅い |
| 完全並列 | 依存なし | 最速 | ビジネスルール違反の可能性 |
| DAGスケジューリング | 混合依存 | パフォーマンス+正確性 | 実装複雑 |
エラー処理とリトライ:優雅なデグラデーション
public class ResilientToolExecutor {
public ToolResult executeWithResilience(ToolCall call, String userId) {
// レイヤ1:サーキットブレーカーチェック
if (circuitBreakers.isOpen(call.name())) {
return ToolResult.fallback(call.name(), "サービスは一時的に利用不可");
}
// レイヤ2:パラメータバリデーション
ValidationResult validation = registry.validate(call.name(), call.arguments());
if (!validation.isValid()) {
return ToolResult.error(call.name(), "バリデーション失敗");
}
// レイヤ3:権限チェック
if (!registry.hasPermission(call.name(), userId)) {
return ToolResult.error(call.name(), "権限なし");
}
// レイヤ4:リトライ実行
try {
return retryPolicy.execute(() -> registry.execute(call.name(), call.arguments(), userId));
} catch (RetryExhaustedException e) {
circuitBreakers.recordFailure(call.name());
return ToolResult.fallback(call.name(), getFallbackMessage(call.name()));
}
}
}
セキュリティ:権限制御とサンドボックス実行
権限モデル
ToolPermissionManager.builder()
.role("USER", Set.of("getWeather", "queryOrders", "searchKnowledge"))
.role("VIP", Set.of("getWeather", "queryOrders", "searchKnowledge", "createTicket", "processRefund"))
.role("ADMIN", Set.of("*"))
.dangerousOperations(Set.of("processRefund", "deleteAccount"))
.build();
入力サニタイズ
- SQLインジェクション防止:UNION、DROP等のキーワード検出
- パストラバーサル防止:
../等のパス遷移を阻止 - コマンドインジェクション防止:シェル特殊文字のフィルタリング
- 長さ制限:文字列上限1000文字
生産ケース:スマートカスタマーサービスの多ターン編成
ユーザー:「昨日の注文がまだ発送されていない。今日発送されなければ返金したい。」
ターン1:LLM → queryOrders(userId, "yesterday")
ターン2:LLM分析 → processRefund(orderId)
ターン3:LLM → createTicket(userId, "返金申請", ...)
ターン4:LLM → 最終応答生成
@Service
public class SmartCustomerService {
public CustomerServiceResponse handle(String userMessage, String userId, String sessionId) {
var response = chatClient.prompt()
.system(buildSystemPrompt(userId))
.messages(conversationStore.getHistory(sessionId))
.user(userMessage)
.functions("queryOrders", "processRefund", "createTicket", "sendNotification")
.advisors(
new LoggingAdvisor(),
new RateLimitAdvisor(),
new SafetyCheckAdvisor(),
new MetricsAdvisor()
)
.call();
conversationStore.append(sessionId, userMessage, response.content());
return new CustomerServiceResponse(response.content(), extractToolUsage(response));
}
}
デバッグ:Function Callingトレーシング
┌──────────────────────────────────────────────────┐
│ Session: sess_abc123 User: user_456 │
├──────────────────────────────────────────────────┤
│ Turn 1: User → "注文未発送、返金希望" │
│ ├─ LLM → queryOrders(userId, days=1) │
│ │ └─ Result: [Order#1234 未発送] 120ms │
│ ├─ LLM → processRefund(orderId="1234") │
│ │ └─ Result: 返金成功 850ms │
│ └─ LLM → "注文1234の返金を処理しました..." │
│ │
│ Total: 2 tool calls, 970ms tool time │
└──────────────────────────────────────────────────┘
まとめ
| チェック項目 | 優先度 | 説明 |
|---|---|---|
| 関数記述の精度 | ⭐⭐⭐ | LLMは記述に完全に依存 |
| パラメータバリデーション | ⭐⭐⭐ | LLM出力を決して信頼しない |
| 権限制御 | ⭐⭐⭐ | ロールベースの関数アクセス |
| サーキットブレーカー | ⭐⭐ | 優雅なデグラデーション |
| ターン制限 | ⭐⭐ | 無限ループ防止(5-10ターン) |
| ログトレーシング | ⭐⭐ | フルtraceIdチェーン |
最重要原則:Function Callingは「提案」であり「指示」ではない。実行前に必ずバリデーション、認可、デグラデーションを行う——LLMの出力を決して信頼しない。
ブラウザローカルツールを無料で試す →
#Function Calling#AI Agent#LLM#工具调用#Spring AI