オレンジ色のケーブルが接続されたパッチパネル
設計と運用

LLM 0.32のログ設計はGitに学ぶ CI/CDでのLLMテスト運用を見直す

目次を見る

LLM API(OpenAIやAnthropicなどが提供する大規模言語モデルの呼び出しインターフェース)を使ったプロダクトのテストやログ運用を担当している方に向けた内容です。

Simon Willison氏が開発するコマンドラインツール兼Pythonライブラリ「LLM」の0.32が公開され、推論過程の可視化、サーバーサイドツール実行、そしてログ保存方式の刷新が加わりました。単なる新機能追加ではなく、LLMをCI/CDパイプラインに組み込む際のログ設計・再現性確保に関わる変更なので、整理しておく価値があります。

何が変わったのか

LLMは、コマンドラインからOpenAIやAnthropic、ローカルLLM(LM Studioなどで動かすモデル)に対して統一的にプロンプトを送れるツールです。0.32では大きく3つの変更が入りました。

1つ目は推論トレース(reasoning trace)の可視化です。GPT-5.6のような推論モデルは、最終回答を出す前に内部で思考過程を生成します。従来はこの過程が見えませんでしたが、0.32では標準エラー出力に思考過程がストリーミング表示されるようになりました。-R--hide-reasoning)オプションで非表示にもできます。

2つ目はサーバーサイドツール(server-side tools)の対応です。プロバイダ側でコード実行やWeb検索を完結させる仕組みで、たとえばOpenAIのコード実行環境は次のように呼び出せます。

llm --tool CodeInterpreter 'Show current python and SQLite versions'

Anthropic向けにはWebSearch・WebFetch・CodeExecution・AnthropicMCPが追加され、MCP(Model Context Protocol、外部データソースやツールをLLMに接続する共通規格)経由でDatasetteのようなサービスに直接問い合わせることも可能になりました。

3つ目が、テスト運用の観点でもっとも影響が大きいログ保存方式の刷新です。SQLiteベースのログが、Gitに似た「コンテンツアドレス方式(content-addressable)」のメッセージストアに変わりました。

ログ設計の変更を段階的に理解する

なぜログ設計が変わったのかを理解するには、LLM APIのリクエスト構造を押さえておく必要があります。

チャット形式のAPIは、会話が進むたびにこれまでのメッセージ履歴全体を毎回リクエストに含めます。3ターン目の会話なら、1〜2ターン目のメッセージも丸ごと再送されるということです。

これをそのままログに保存すると、ターンが増えるたびに重複したJSONがどんどん積み上がります。10ターンの会話なら、実質同じ内容を10回近く保存することになりかねません。

Gitはこの種の重複問題を「差分ではなく内容のハッシュで管理する」ことで解決しています。同じ内容のファイルは1つのオブジェクトとして共有され、コミットごとに全ファイルを複製したりはしません。

LLM 0.32のコンテンツアドレス方式のメッセージストアも同じ発想です。同一内容のメッセージは1つのレコードとして保存され、会話ごとにそれを参照する形になります。llm logsllm logs --jsonコマンドは、この内部形式を従来通りの読みやすい表示に変換して出力します。

Python API側にも変更があり、従来は会話オブジェクトを作ってメッセージを1つずつ送る形でしたが、0.32ではmodel.prompt(messages=[])で会話履歴をまとめて渡せるようになりました。

import llm
from llm import user, assistant, system

model = llm.get_model("gpt-5.6-luna")
response = model.prompt(messages=[
    system("You are a helpful pirate."),
    user("What is the capital of France?"),
    assistant("Paris, matey."),
    user("And Germany?"),
])
print(response.text())

さらにstream_events()という新しいストリーミングAPIが加わり、推論テキスト・出力テキスト・ツール呼び出しをイベント種別ごとに区別して受け取れます。

for event in model.prompt("Explain cats").stream_events():
    if event.type == "reasoning":
        print(f"[thinking] {event.chunk}", end="", flush=True)
    elif event.type == "text":
        print(event.chunk, end="", flush=True)
    else:
        print(f"Other event: {event}")

これらを組み合わせた成果として、OpenAI互換のチャット補完APIをローカルに立てられるllm-chat-completions-serverプラグインも公開されています。

llm install llm-chat-completions-server
llm chat-completions-server --port 9000

テスト・CI/CDの観点で見る影響

品質保証やテスト自動化を担当する立場から見ると、注目すべき点は3つあります。

1つ目はログの再現性です。従来のログ形式に依存したテスト(レスポンスの差分比較やリグレッションテストのスナップショット取得など)を組んでいる場合、SQLiteスキーマの変更によって既存のログ解析スクリプトが壊れる可能性があります。llm logs --jsonの出力構造が変わっていないか、CIで使っているパース処理を再確認する必要があります。

2つ目は推論トレースの扱いです。推論過程が標準エラー出力に流れるようになったため、CIログに大量の思考過程テキストが混入する懸念があります。テスト実行スクリプトで標準出力のみをアサーション対象にしている場合は影響が小さいですが、標準エラーも含めてログ収集している構成では、-Rオプションでの抑制を検討する価値があります。

3つ目はサーバーサイドツールのテスト戦略です。CodeInterpreterやWebSearchはプロバイダ側で実行されるため、ローカルでのモック化が難しくなります。E2Eテスト(実際のAPIを叩いて動作確認するテスト)と、ツール呼び出しをスタブに置き換えた単体テストを明確に分離しておくと、CI実行時間とコストの両方をコントロールしやすくなります。

今日確認できること

まず手元の環境でLLMのバージョンを確認します。

llm --version

0.32未満であれば、pip install -U llmまたはuv tool upgrade llmでアップデートできます。

次に、CI/CDパイプライン内でLLMのログやレスポンスをパースしている箇所を洗い出します。llm logsコマンドの出力形式に依存した正規表現やJSONパスの指定がないか、テストコードとログ解析スクリプトの両方を確認してください。

また、デフォルトモデルがGPT-5.6 Lunaに変更されている点も見逃せません。llm "prompt"のようにモデル指定を省略しているCIジョブがあれば、意図せず新しいモデルが使われることになります。再現性を重視するテストでは-mオプションでモデルを明示的に固定する運用が安全です。

サーバーサイドツールを使う予定があるなら、プロバイダ側の実行環境に依存するテストとそうでないテストを分けて、CIのジョブ構成に反映しておくと管理がしやすくなります。

まとめ

LLM 0.32は、推論トレースの可視化・サーバーサイドツール対応・Git的なコンテンツアドレス方式のログ刷新という3本柱の変更です。

品質保証の視点では、ログ解析スクリプトの互換性確認、標準エラー出力への推論トレース混入対策、モデルバージョンの明示的な固定が当面のチェックポイントになります。

まずはllm --versionでバージョンを確認し、CIパイプライン内のログパース処理とモデル指定箇所を点検するところから始めてみてください。

参考

New release of LLM adds support for reasoning traces, OpenAI Responses, server-side tools, and smarter logging

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。