目次

1.はじめに
2.セットアップについて
3.チャット用スクリプトの実装と動作確認
4.コミットメッセージ生成用スクリプトの実装と動作確認
5.おわりに

1.はじめに

LiteRT-LM とは Google が公開しているエッジデバイス(スマートフォン、PC、IoT機器など)向けの LLM 実行環境です。

本記事執筆時点では Python・Kotlin・C++ の API が用意されており、本記事では LiteRT-LM Python API を取り上げます。

LiteRT-LM Python API 経由で Gemma 4(litert-community/gemma-4-E4B-it-litert-lm) を動かし、ターミナル上でのチャットやコミットメッセージ生成を試してみたので紹介します。

環境情報

  • macOS:Tahoe 26.3.1
  • チップ:Apple M4
  • メモリ:16GB
  • uv:0.11.7

2.セットアップについて

LiteRT-LM Python API の利用にあたり uv で以下を実行しています。
litert-lm をシステム全体のバイナリとしてインストール、どこからでも litert-lm コマンドが使えるようにします。

$ uv tool install litert-lm
Resolved 26 packages in 565ms
Prepared 5 packages in 5.72s
Installed 26 packages in 38ms
 + annotated-doc==0.0.4
 + anyio==4.13.0
 + certifi==2026.4.22
 + click==8.3.3
 + filelock==3.29.0
 + fsspec==2026.4.0
 + h11==0.16.0
 + hf-xet==1.4.3
 + httpcore==1.0.9
 + httpx==0.28.1
 + huggingface-hub==1.13.0
 + idna==3.13
 + litert-lm==0.11.0
 + litert-lm-api==0.11.0
 + markdown-it-py==4.0.0
 + mdurl==0.1.2
 + packaging==26.2
 + prompt-toolkit==3.0.52
 + pygments==2.20.0
 + pyyaml==6.0.3
 + rich==15.0.0
 + shellingham==1.5.4
 + tqdm==4.67.3
 + typer==0.25.1
 + typing-extensions==4.15.0
 + wcwidth==0.7.0
Installed 1 executable: litert-lm

続いてモデルインポートを実施します。
litert-community/gemma-4-E4B-it-litert-lm を HuggingFace からインポートします。
gemma-4-E4B-it.litertlm をダウンロード、ローカルでの管理名は gemma4-e4b にしています。(管理名の設定は任意です)

litert-lm import \
    --from-huggingface-repo litert-community/gemma-4-E4B-it-litert-lm \
    gemma-4-E4B-it.litertlm \
    gemma4-e4b

コマンドの実行結果は以下です。

$ litert-lm import \
>     --from-huggingface-repo litert-community/gemma-4-E4B-it-litert-lm \
>     gemma-4-E4B-it.litertlm \
>     gemma4-e4b
Downloading gemma-4-E4B-it.litertlm from litert-community/gemma-4-E4B-it-litert-lm...
gemma-4-E4B-it.litertlm: 100%|██████████████████████████████████████████████████████████████████████████████████████████| 3.66G/3.66G [01:37<00:00, 37.6MB/s]
Successfully imported model to /Users/xxx/.litert-lm/models/gemma4-e4b/model.litertlm
You can now run the model with 'litert-lm run gemma4-e4b'

モデルの配置場所は以下になっています。

/Users/xxx/.litert-lm/models/gemma4-e4b/model.litertlm

ディレクトリ名は設定した管理名(gemma4-e4b)が使われており、管理名を省略した場合 HuggingFace リポジトリ内の名称(gemma-4-E4B-it.litertlm) がそのままディレクトリ名となります。
上記パスは後述の Python スクリプトで使用します。

3.チャット用スクリプトの実装と動作確認

スクリプトの作成

インポートしてきたモデルを使いターミナル上でチャットができる Python スクリプトを作成します。
以下ディレクトリに chat.py を作成します。

~/.local/bin/
└── chat.py

~/.local/bin/ はユーザー固有の実行ファイルを置く標準的なディレクトリなので、ここを使用します。

チャット用スクリプト、後述するコミットメッセージ生成用スクリプトのどちらもモデルを使った処理が前提であり、ローカルにインポートしたモデルを読み込んで使える状態にする必要があります。

これを LiteRT-LM Python API(litert_lmライブラリ)で実現します。

litert_lm.Engine が API のエントリポイント、つまりモデルを利用する際の起点であり、litert_lm.Engine にモデルのパスを指定して初期化することでモデルを利用できる状態となります。

# 例
with litert_lm.Engine(
    "path/to/your/model.litertlm",
    backend=litert_lm.Backend.GPU
) as engine:

下記チャット用スクリプトの場合、スクリプトを実行するとモデルがプログラム上で動作し、会話セッションを通じてユーザーの入力を受け付け、応答をストリーミングで出力する形となります。
チャットは exit か quit、Ctrl+C で終了できます。
chat.py の内容は以下となります。

"""
chat.py
役割: ローカルLLMとターミナル上で会話する
使い方: uv tool run --from litert-lm python3 ~/.local/bin/chat.py
終了: Ctrl+C / exit / quit を入力
"""

import litert_lm
from pathlib import Path

MODEL_PATH = str(Path.home() / ".litert-lm/models/gemma4-e4b/model.litertlm")

litert_lm.set_min_log_severity(litert_lm.LogSeverity.ERROR)

print("💬 ローカルLLMとチャット(終了: Ctrl+C / exit / quit)\n")

messages = [
    {
        "role": "system",
        "content": [{"type": "text", "text": "結論を最初に述べ、補足説明は簡潔にまとめてください。"}],
    }
]

with litert_lm.Engine(MODEL_PATH, backend=litert_lm.Backend.GPU, enable_speculative_decoding=True) as engine:
    with engine.create_conversation(messages=messages) as conversation:
        while True:
            try:
                user_input = input(">>> ")
            except (KeyboardInterrupt, EOFError):
                print("\n終了します。")
                break

            if user_input.strip().lower() in ("exit", "quit"):
                print("終了します。")
                break

            if not user_input.strip():
                continue

            for chunk in conversation.send_message_async(user_input):
                print(chunk["content"][0]["text"], end="", flush=True)
            print()  # ストリーミング出力は改行なしで終わるため最後に改行を補う

実行コマンドは uv tool run --from litert-lm python3 ~/.local/bin/chat.py ですが、毎回入力するには長いためエイリアス設定しておきます。

# 設定ファイル(~/.bash_profile や ~/.zshrc)に追記
alias chat='uv tool run --from litert-lm python3 ~/.local/bin/chat.py'

動作確認

チャットなので質問に対する回答が返ってくるか、セッション内の会話が記憶されているかを確認します。

chat.py の実行結果は以下です。
ターミナルで chat と入力し起動、MySQL・SQLServer・PostgreSQL のポートについて質問し、最後に最初にした質問は何であったかを聞いています。

いずれも正しい回答が返っており、セッション内の会話が記憶されていることも確認できました。(セッションを終了したらやりとりの記憶はリセットされます)

$ chat
💬 ローカルLLMとチャット(終了: Ctrl+C / exit / quit)

>>> MySQLのポートは?
MySQLの標準ポートは**3306**です。

**補足:**
*   これはMySQLがデフォルトでリッスンするポート番号です。
*   インストールや設定によっては、このポート番号が変更されている場合があります。
*   接続時にポート番号を指定しない場合、クライアントは通常3306番ポートに接続しようとします。
>>> SQLServerは?
SQL Serverの標準ポートは**1433**です。

**補足:**
*   これはSQL Serverがデフォルトでリッスンするポート番号です。
*   SQL Serverのインスタンス名や設定によっては、このポート番号が変更されている場合があります。
*   複数のインスタンスを同じサーバー上で稼働させる場合、ポート番号の変更が一般的です。
>>> PostgreSQLは?
PostgreSQLの標準ポートは**5432**です。

**補足:**
*   これはPostgreSQLがデフォルトでリッスンするポート番号です。
*   設定ファイル(`postgresql.conf`など)を変更することで、このポート番号は変更可能です。
>>> 私の最初の質問は?
あなたの最初の質問は「**MySQLのポートは?**」でした。
>>> exit
終了します

4.コミットメッセージ生成用スクリプトの実装と動作確認

スクリプトの作成

~/.local/bin/ 配下に commit-message/ ディレクトリを作成し以下の2つのファイルを配置します。

~/.local/bin/commit-message/
├── get_diff.sh
└── commit_message.py

get_diff.sh の内容について、git diff の情報を取得し出力するシェルスクリプトになっています。

git add 済みの差分がある場合 STATUS: ready として git diff --staged の結果を出力、git add 前の変更のみの場合 STATUS: needs_add として git diff の結果を出力、変更がない場合 STATUS: no_changes を出力します。

このスクリプトは後述の commit_message.py から呼び出して使用します。
commit_message.py はこのスクリプトの出力を受け取り、STATUS に応じた処理を実行します。

#!/bin/bash

# =============================================================================
# get_diff.sh
# 役割: コミットメッセージ提案のためのdiff情報を取得するスクリプト
# 使い方: bash ~/.local/bin/commit-message/get_diff.sh
# =============================================================================

STAGED=$(git diff --staged)
UNSTAGED=$(git diff)
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)

echo "## Current branch: $CURRENT_BRANCH"
echo ""

# パターン1: staged あり → 正常フロー
if [ -n "$STAGED" ]; then
  echo "## STATUS: ready"
  echo ""
  echo "## Staged changes"
  echo "$STAGED"

# パターン2: staged なし・unstaged あり → git add を促す
elif [ -n "$UNSTAGED" ]; then
  echo "## STATUS: needs_add"
  echo ""
  echo "## git add前のファイル一覧"
  git status --short
  echo ""
  echo "## Unstaged diff (参考)"
  echo "$UNSTAGED"

# パターン3: 変更なし
else
  echo "## STATUS: no_changes"
  echo "コミットする変更がありません"
fi

続いて commit_message.py の内容について、get_diff.sh を呼び出して diff 情報を取得し、その内容をシステムプロンプトとともにモデルに渡しコミットメッセージを生成します。

get_diff.sh の実行結果が STATUS: no_changes だった場合、モデルの読み込みはスキップして早期終了する形としています。

コミットメッセージの生成ですが、システムプロンプトでモデルに指示しています。
STATUS の値に応じた回答をする形としており、STATUS: ready の場合 diff の内容を分析し prefix: 日本語 の形式でコミットメッセージを提案します。
STATUS: needs_add の場合 git add がまだであることを伝えつつ、diff の内容からコミットメッセージの予告提案を添える形としています。

#!/usr/bin/env python3
"""
commit_message.py
役割: git diffを取得しConventional Commits形式のコミットメッセージを提案する
使い方: uv tool run --from litert-lm python3 ~/.local/bin/commit-message/commit_message.py
"""

import subprocess
import sys
from pathlib import Path
import litert_lm

# ==============================================================================
# 設定
# ==============================================================================

MODEL_PATH = str(Path.home() / ".litert-lm/models/gemma4-e4b/model.litertlm")

# get_diff.sh のパス(このスクリプトと同じディレクトリ)
SCRIPT_DIR = Path(__file__).parent
GET_DIFF_SH = SCRIPT_DIR / "get_diff.sh"

# ==============================================================================
# システムプロンプト
# ==============================================================================

SYSTEM_PROMPT = """あなたはGitのコミットメッセージを提案するアシスタントです。
git diffの情報が渡されます。その内容を分析してConventional Commits形式・日本語でコミットメッセージを提案してください。
コミットの実行は絶対に行わない。提案のみ行うこと。

## STATUSによる分岐

入力の先頭に `## STATUS: <value>` が含まれます。

### STATUS: ready
staged diffを分析してコミットメッセージを提案する。

### STATUS: needs_add
git addが済んでいないことをユーザーに伝える。
unstaged diffを参考に予告提案を添える。

例:
```
git add がまだのようです。以下のファイルが変更されています:
  M  terraform/s3.tf

git add <ファイル> の後、もう一度お試しください。

変更内容からは feat: 〇〇 になりそうです。
```

### STATUS: no_changes
「コミットする変更がありません」と伝えて終了する。

## コミットメッセージのルール

### プレフィックス

| prefix | 用途 |
|---|---|
| feat | 新しい機能 |
| change | 既存機能への変更 |
| docs | ドキュメントのみの変更 |
| style | 空白、フォーマット、セミコロン追加など |
| refactor | 仕様に影響がないコード改善(リファクタ) |

### 基本フォーマット

```
<prefix>: <変更理由>ため<変更内容>を<動詞>
```

- 必ず日本語で書く
- 50文字以内を目安にする
- 変更が複数の種類にまたがる場合は最も主要な変更のprefixを使い、メッセージにはすべての変更内容を含める
- prefixの主従が判断できない場合のみ、各prefixで1案ずつコミットメッセージを提案する

## 出力フォーマット

基本は1案のみ提案する。
prefixの主従が判断できない場合のみ、各prefixで1案ずつ提案する。

```
## コミットメッセージの提案文

feat: 〇〇のため△△を追加

---
採用する案が決まったら以下のコマンドでコミットできます:

git commit -m 'feat: 〇〇のため△△を追加'
```

クォートは必ずシングルクォート(')を使う。
"""

# ==============================================================================
# メイン処理
# ==============================================================================

def get_diff() -> str:
    """get_diff.sh を実行してdiff情報を取得する"""
    if not GET_DIFF_SH.exists():
        print(f"エラー: {GET_DIFF_SH} が見つかりません", file=sys.stderr)
        sys.exit(1)

    result = subprocess.run(
        ["bash", str(GET_DIFF_SH)],
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        print(f"エラー: get_diff.sh の実行に失敗しました\n{result.stderr}", file=sys.stderr)
        sys.exit(1)

    return result.stdout


def generate_commit_message(diff_content: str) -> None:
    """コミットメッセージを生成して出力する"""

    litert_lm.set_min_log_severity(litert_lm.LogSeverity.ERROR)

    messages = [
        {
            "role": "system",
            "content": [{"type": "text", "text": SYSTEM_PROMPT}],
        }
    ]

    print("🔍 Diff情報を分析中...\n")

    with litert_lm.Engine(MODEL_PATH, backend=litert_lm.Backend.GPU, enable_speculative_decoding=True) as engine:
        with engine.create_conversation(messages=messages) as conversation:
            for chunk in conversation.send_message_async(diff_content):
                for item in chunk.get("content", []):
                    if item.get("type") == "text":
                        print(item["text"], end="", flush=True)
    print()  # ストリーミング出力は改行なしで終わるため最後に改行を補う


def main():
    diff_content = get_diff()

    # no_changes の場合はモデルを読み込まずに早期終了
    if "## STATUS: no_changes" in diff_content:
        print("コミットする変更がありません。")
        sys.exit(0)

    generate_commit_message(diff_content)


if __name__ == "__main__":
    main()


実行コマンドは uv tool run --from litert-lm python3 ~/.local/bin/commit-message/commit_message.py ですが、毎回入力するには長いためエイリアス設定しておきます。

# 設定ファイル(~/.bash_profile や ~/.zshrc)に追記
alias commitmsg='uv tool run --from litert-lm python3 ~/.local/bin/commit-message/commit_message.py'

動作確認

Terraform で AWS リソースを管理している環境を例に動作確認します。
既存のサブネット・セキュリティグループを流用する形でコードに ENI と EC2 を1台追加、git add まで実行済みの状態にしています。
この状態から commitmsg(commit_message.py 実行のエイリアス)をターミナルで実行してみます。

モデルに渡される git diff --staged の内容は以下です。
aws_network_interface と aws_instance が新規追加されるため、prefix は feat、メッセージは日本語でそれらのリソース追加を示す内容になるはずです。

$ git diff --staged
diff --git a/main/02_sysytem/compute.tf b/main/02_sysytem/compute.tf
index d55a2a7..9c74bab 100644
--- a/main/02_sysytem/compute.tf
+++ b/main/02_sysytem/compute.tf
@@ -400,6 +400,47 @@ EOF
   }
 }

+resource "aws_network_interface" "test3_eni" {
+  subnet_id       = data.terraform_remote_state.common.outputs.private_subnet_a_id
+  private_ips     = ["10.10.xxx.xxx"]
+  security_groups = [aws_security_group.test2_sg.id]
+
+  tags = {
+    Name = "${var.project_prefix}-${var.env}-test-03-eni"
+  }
+}
+
+resource "aws_instance" "test3" {
+  ami           = var.ami_id_test2
+  instance_type = var.instance_type_test2
+
+  network_interface {
+    network_interface_id  = aws_network_interface.test3_eni.id
+    device_index          = 0
+    delete_on_termination = false
+  }
+
+  iam_instance_profile = aws_iam_instance_profile.test_profile.name
+  key_name             = "your-key-pair"
+
+  disable_api_termination = false
+
+  root_block_device {
+    volume_size = 50
+    volume_type = "gp3"
+    iops        = 3000
+    throughput  = 125
+    encrypted   = true
+    tags = {
+      Name = "${var.project_prefix}-${var.env}-test-03-root"
+    }
+  }
+
+  tags = {
+    Name = "${var.project_prefix}-${var.env}-test-03"
+  }
+}
+
 resource "aws_ebs_volume" "data" {
   availability_zone = "ap-northeast-1a"
   size = 50

実行結果は以下です。
システムプロンプトで設定したフォーマットで出力されています。
期待通り prefix は feat、メッセージは日本語で ENI と EC2 の追加を示しており、git diff --staged 内のコードの test という命名からテスト環境用であることも汲み取った内容となっています。

$ commitmsg
🔍 Diff情報を分析中...

## コミットメッセージの提案文

feat: テスト環境用のネットワークインターフェースとインスタンスリソースを追加

---
採用する案が決まったら以下のコマンドでコミットできます:

git commit -m 'feat: テスト環境用のネットワークインターフェースとインスタンスリソースを追加'

5.おわりに

LiteRT-LM Python API を使ったスクリプトを動かしてみました。

今回試した内容はクラウド経由でモデルを利用する AI サービス・CLI ツールでも実現できますが、クラウド経由でのモデルに全ての処理を任せるとトークンコストがかさみ、利用制限(レートリミット)にも影響します。

ローカル LLM で処理できることはローカルで完結できれば、クラウド経由側のトークンコストを抑えつつレートリミットも温存できるので、ローカル LLM の可能性を把握しておくことは有用かと思います。