后端开发

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