Upgrade to Pro — share decks privately, control downloads, hide ads and more …

速習 Spring AI Gemini CLIのようなLLM CLIを自作しよう

Avatar for roronya roronya
November 14, 2025
110

速習 Spring AI Gemini CLIのようなLLM CLIを自作しよう

JJUG CCC 2025 Fallでの登壇資料です。

今年の5月に Spring AI が GA になりました。
Springユーザーとして、既存のSpringによる実装を再利用したMCPサーバー化や、JavaでのLLMアプリケーション開発について、気になっている人は多いのではないでしょうか。
このセッションでは
・LLMアプリケーションフレームワーク選定の観点
・Spring AIの各機能について網羅的に解説
・サンプルアプリケーションによるデモ
について解説します。

Avatar for roronya

roronya

November 14, 2025
Tweet

Transcript

  1. はじめに • 今年の5月に Spring AI が GA になった • 気になっているひとは多いのではないか

    ◦ LLMアプリケーション開発そのものについて ◦ 既存のSpringによる実装を再利用したMCPサーバー化 2
  2. 金井 優希 Yuki KanAI @roronya • 元々はデータサイエンティストして入社 ◦ 宿予約サービスの検索用機械学習モデルの作成など •

    2年目以降エンジニア転向 ◦ 効果的に機械学習を取り入れるためには 適切にメンテナンスできるアーキテクチャが必要 ◦ LLMに関しても同様のアーキテクチャ上の課題感 5 所属: 株式会社リクルート 担当: 飲食店の注文管理システム 役割: TechLead
  3. LLM FWの分布とSpring AIのポジショニング 12 Java実装では他にLangChain4jが有名 軽量 重量 LiteLLMなど 各LLMベンダーAPI汎用化 AWS

    Bedrock クラウドインフラとの統合 (PaaS) LangChain LlamaIndex Spring AIなど より多機能なFW Python実装が多いが 他言語でも実装され始めている
  4. 1. 基本的な使い方 2. 各LLMベンダーのChatModelの汎化 3. ChatMemory 4. PromptTemplate 5. Structured

    Output 6. ToolCalling 7. MCP Client 8. MCP Server 9. Advisor 10. Observability 11. その他 14
  5. (1/11) 基本的な使い方 15 private final List<Message> history = new ArrayList<>();

    String response = chatClient .prompt() .system(systemPrompt) .user(userPrompt) .tools(new CommandTool(), new TimeTool()) .messages(history) .call() .content(); history.add(new UserMessage(userPrompt)); history.add(new AssistantMessage(response));
  6. (2/11) 各LLMベンダのChatModelの汎化 16 ChatClient ChatModel OpenAIChatModel OllamaChatModel VertexAiGeminiChatModel etc... •

    LLMベンダーごとの差異を ChatModelインターフェースで吸収 • ユーザーはChatClientの使い方のみ知れば良い
  7. 各LLMベンダのChatModelの汎化: 使い方 17 • build.gradleにstarterの依存を追加 • 各LLMベンダー用の設定をapplication.ymlに書く implementation 'org.springframework.ai:spring-ai-starter-model-openai' ai:

    openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini @Autowired private final OpenAiChatModel openAiChatModel; • 使いたいコンポーネントで@Autowiredする
  8. (4/11) PromptTemplate • Promptをテンプレートエンジンを使って書ける ◦ {}でくくって、Mapをわたす 20 String systemPrompt =

    new PromptTemplate("{country}の首都はどこ?") .create(Map.of("country", "日本")) .getContents();
  9. Structured Output: 動作例 24 1. Response型を指定しConverterを作成 2. Converterが出力形式指定プロンプトを生成 3. PromptTemplateで埋め込む

    BeanOutputConverter<Response> converter = new BeanOutputConverter<>(Response.class); String format = converter.getFormat(); String systemPrompt = new PromptTemplate(promptResource) .create(Map.of("format", format)) .getContents();
  10. Structured Output: 動作例 25 実際に生成される出力指定プロンプト Your response should be in

    JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. Do not include markdown code blocks in your response. Remove the ```json markdown from the output. Here is the JSON Schema instance your output must adhere to: ```{ "$schema" : "https://json-schema.org/draft/2020-12/schema", "type" : "object", "properties" : { "content" : { "type" : "string" }, "reason" : { "type" : "string" } }, "additionalProperties" : false }``` BeanOutputConverterに指定した クラスに則ったJSON Schemaを指定 してくれる
  11. Structured Output: 注意 26 • モデルが提供するStructured Outputとは異なる ◦ Structured Output機能を

    持ってないLLMにもある程度有効 ◦ モデルが提供するStructured Outputも使える • 100%のJSON出力を保証するものではない ◦ プロンプトで指定しているだけなので > The StructuredOutputConverter is a best effort to convert the model output into a structured output. https://docs.spring.io/spring-ai/reference/api/structured-output-converter.html
  12. Tool Calling: 現在時刻を返すToolの例 28 @Tool( name = "get_now", description =

    "return current date as String" ) public String getNow() { return LocalDateTime .now() .atZone(LocaleContextHolder.getTimeZone().toZoneId()) .toString(); }
  13. Tool Calling: 特定の日付の文字列から曜日を返す例 29 多少複雑な引数もLLMが指定してくれる formatPattern="yyyy/MM/dd"なども作ってくれる @Tool( name = "get_day_of_week",

    description = """ return day of week. Arguments are dateString and formatPattern. """ ) public String getDayOfWeek(String dateString, String formatPattern) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern); LocalDate date = LocalDate.parse(dateString, formatter); DayOfWeek dayOfWeek = date.getDayOfWeek(); return dayOfWeek.getDisplayName(TextStyle.FULL, Locale.JAPANESE); }
  14. (7/11) MCP Client • デフォルトではMCP Clientとしては機能しない • MCP Client用のstarterの依存を追加する •

    • 設定ファイルにMCPサーバーのアドレス書くと アプリ起動時に問い合わせてToolとして登録してくれる (あとはTool Callingと一緒) 31 implementation 'org.springframework.ai:spring-ai-starter-mcp-client'
  15. MCP Server: 現在時刻を返すMCPサーバー 33 ToolCallbackにロジックをラップし Beanに登録するだけでMCPサーバーが立つ @Bean // 現在時刻を返すだけの Tool

    public ToolCallback getCurrentDateTime() { return FunctionToolCallback.builder( "getCurrentDateTime", () -> LocalDateTime .now() .atZone(LocaleContextHolder.getTimeZone().toZoneId()) .toString()) .description("Get the current date and time in the user's timezone") .build(); }
  16. Advisor: LLM呼び出し前後にログを仕込む例 36 public class CustomLogAdvisor implements CallAdvisor { @Override

    public String getName() { return "CustomLogAdvisor"; } @Override public int getOrder() { return 0; } @Override public ChatClientResponse adviseCall( ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain ) { System.out.println("BEFORE =========================="); System.out.println(chatClientRequest.prompt()); ChatClientResponse response = callAdvisorChain.nextCall(chatClientRequest); System.out.println("AFTER =========================="); System.out.println(response.chatResponse().getResult().getOutput().getText()); return response; } }
  17. Advisor: Recursive Advisor 37 11/13にリリースされた 1.1.0の目玉機能 • 再帰的にChatModelを呼び出せる • 使用例

    ◦ Structured OutputのValidate ◦ レピュテーションリスクチェック Advisorを仕込める粒度が細かくなり ToolCallAdvisorも仕込める
  18. LLM CLIを作るために必要なもの 1. LLM CLI用プロンプト => Gemini CLIから拝借 2. LLM

    FWの基本的機能 => Spring AI 3. コマンド実行Tool => 自作 4. REPL => 自作 43
  19. LLM CLIを作るために必要なもの ①LLM CLI用プロンプト 44 • google-gemini/gemini-cliの packages/core/src/core/prompts.ts を参考にカスタマイズするのがおすすめ •

    Coding Agentとして期待する振る舞いが書かれ ている ◦ 「コードの読解や修正を行う際は、必ず既存プロジェクトのコーディング規約を厳格に遵 守せよ。まずは周辺のコード、テストケース、および設定ファイルを確認すること。」 ◦ 「リクエストの明確な範囲を超えて重大な変更を加える場合は、必ずユーザーと確認を 取ってください。」
  20. ①LLM CLI用プロンプト: コツ 45 • 動作例を豊富に与える • Tool Callingの使い道を教える <例>

    ユーザー: pythonでHello Worldを表示するプログラムを作成し実行せ よ モデル: [tool_call: command_executer echo -e "if __name__ == '__main__': print('Hello World')" > hello.py] [tool_call: command_executer python3 hello.py] </例>
  21. ③コマンド実行Tool: 実装例 48 @Tool( name = "command_executor", description = "Executes

    the given shell command as bash -c argument") public String execute(String command) { System.out.println("Executing command: bash -c " + command); try { ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", command); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String output = reader.lines() .collect(Collectors.joining(System.lineSeparator())); process.waitFor(); return output; } catch (IOException | InterruptedException e) { return errorMessage; } }
  22. ③コマンド実行Tool: 注意点 49 LLMが危ないコマンドを実行する場合がある 対応策 • Allow-List方式 • chroot/コンテナ化 •

    作業ディレクトリの隔離 • Auditログ • Cgroup • Human-in-the-Loop $ rm -rf / $ curl -X POST xxxx $ chmod 777 /etc/shadow
  23. サマリ 55 • Spring AIは中量級のLLM FW ◦ プロダクト実装のために必要な機能を提供 ◦ 既存実装を再利用したりMCPサーバー化可能

    ◦ オープンソースでコミット可能 • Spring AIの基本的機能を解説 • playgroundとしてのLLM CLI自作のすすめ
  24. 参考文献 • Spring AI Reference ◦ https://docs.spring.io/spring-ai/reference/index.html • Spring AI

    SourceCode ◦ https://github.com/spring-projects/spring-ai • Spring AI Examples ◦ https://github.com/spring-projects/spring-ai-examples • Spring I/O Keynote ◦ https://www.youtube.com/watch?v=oUK1Np4OvnM • IK.AM: Java on Azure Day 2025で"Spring AIで学ぶMCPの活用"について話してきました。 ◦ https://ik.am/entries/858 • Grafana Labs: Auto-instrumenting a Java Spring Boot application for traces and logs using OpenTelemetry and Grafana Tempo ◦ https://grafana.com/blog/2021/02/03/auto-instrumenting-a-java-spring-boot-application-for-traces-and-logs-using-op entelemetry-and-grafana-tempo/ • Google Gemini SourceCode ◦ https://github.com/google-gemini/gemini-cli 57