VLM(Gemini)を活用して物体検出アノテーションを自動化するツールを作ってみた

お疲れ様です。

以前の記事でプロジェクトのセットアップを行っていた、VLMを使ったバウンディングボックス(BBox)のアノテーション補助ツールを実際に作ってみました。

fallpoke-tech.hatenadiary.jp



ソースコード

ソースコードはこちらのGitHubにあります。以前の記事でプロジェクトの初期化まで行ったリポジトリですね。
uvの環境設定を行っているため、uv環境さえあれば動作自体はできると思います。

github.com

実装

ソースコードの中身はプロジェクト本体を見ていただくとして、こちらには概要を載せておきます。 動作フローは以下のようになっています。

flowchart TD
  Start([開始: main.py 実行]) --> ParseArgs[CLI引数の解析\n-d/--data, -v/--visualize]
  ParseArgs --> LoadConfig[classes_info.json 読み込み\n対象クラス・説明の取得]
  LoadConfig --> InitAnnotator[VLMAnnotateAssistant 初期化\n- プロンプト生成\n- LLMモデルの取得]

  InitAnnotator --> LoopImages[画像ディレクトリ走査\n.jpg, .png, .jpeg, .bmp]

  subgraph ImageProcess [画像ごとの処理ループ]
    direction TB
    LoadImg[画像読み込み\nPIL.Image.open] --> ResizeBase64[前処理\n- 1000x1000にリサイズ\n- Base64エンコード]
    ResizeBase64 --> CallVLM[VLM呼び出し\nLangChain invoke\n構造化出力: ResponseFormat]
    CallVLM --> CheckVisual{可視化フラグ\n-v / --visualize?}
    CheckVisual -- True --> Vis[draw_detection_results\nMatplotlibでBBoxを描画・表示]
    CheckVisual -- False --> SaveXML
    Vis --> SaveXML[create_voc_xml\n- 正規化座標をピクセル座標へ変換\n- Pascal VOC XMLファイルを書き出し]
  end

  LoopImages --> ImageProcess
  SaveXML --> NextImage{次の画像あり?}
  NextImage -- Yes --> LoopImages
  NextImage -- No --> End([終了])

使用可能なLLMは今のところ、Gemini APIで使用可能なモデルかOllamaで使用可能なモデルです。 VLMや画像入力が可能なマルチモーダルLLMを使用することができます。 LangChainを使用しているため、他のサービスを利用したい場合も設定を追加すれば対応可能かと思います。

画像の入力方法については、過去にお試しで実装したやり方を使いました。 LangChainの「Chat***」クラスにBase64エンコードした画像を渡すという方法です。

fallpoke-tech.hatenadiary.jp

一応アノテーションの補助ツールという立ち位置なので、最終的にはユーザー自身がチェックして修正する想定です。 最終的な出力はPascal VOC形式のアノテーションファイルになっており、LabelImg等のアノテーションツールで読み込むことができます。 MS COCO形式は現在未対応ですが、必要があれば対応するかもしれません。 VOC形式をCOCO形式に変換するスクリプトなどは多く公開されているため、それらを使って変換するのも手です。

実行方法

設定の変更と実行方法について説明します。

まず、モデルは .env ファイルで指定します。 使用するサービスとモデル名、Geminiを使う場合はAPIキーを指定します。

model_setting

プロジェクト内の classes_info.json に検出したいクラスを設定します。 下記のように クラス名: 物体の説明 の形式で記載します。 これが、LLMに画像とともに入力されるプロンプトに埋め込まれます。

{
  "human": "画像内の人、人間",
  "car": "画像内の自動車",
  "bicycle": "画像内の自転車"
}

Pythonコマンドで実行します。 実行時にコマンドライン引数(argparse)で画像が入ったフォルダを指定し、その中の画像に対して物体検出を適用します。 出力されるアノテーションファイルも同じフォルダに格納されます。 また、検出結果の可視化の有効/無効もコマンドライン引数で設定します。

# コマンドライン引数の設定
parser = ArgumentParser()
parser.add_argument("-d", "--data", type=str, default="./data")
parser.add_argument("-v", "--visualize", action="store_true")
args = parser.parse_args()

python main.py -d ./data -v

出力結果

実際に動作させてみます。 使用するモデルにはGemini APIからgemini-3.5-flash-liteを選択しました。(VLMではないですが…。) また、検出クラスは人(human)、車(car)、自転車(bicycle)の3クラスとしています。

出力結果は以下のようになりました。 プロンプトの内容によるとは思いますが、Geminiだとかなり良い感じに出力してくれました。 今回のクラス指定がシンプルだった影響もあると思いますが、アノテーション補助ツールのつもりがそのまま使えそうなレベルの精度です。

output

出力したPascal VOC形式のアノテーションファイルもLabelImgで問題なく表示されました。

labelimg

所感

今回、VLMの勉強も兼ねてこのようなツールを作成してみました。 実際に試してみた感想ですが、プロンプトでかなり詳細に指示しないと意図した出力が得られないという印象を受けました。 このあたりは、もっとより良いやり方やプロンプトの工夫があるのかもしれません。

物体検出(BBox)のアノテーションだったため、難易度が高かったという側面もあるかもしれません。 今後は異常検知やOCRなども試してみたいところです。 業務内でVLMを活用したいという話も耳にするため、そのあたりの知見もより深めていければと思います。

今回はGeminiを使用しましたが、OllamaによるローカルVLMもいくつか試しています。 そちらについても、また別の機会に記事にしようと思います。

GitHub Copilotで話題のローカルLLM「Ornith-1.5」をOllama連携で動かしてみた【備忘録】

お疲れ様です。

最近は新たなローカルLLMが次々に出てきて楽しいですね…!
今回は、2026/8/19にリリースされたOrnith-1.5の動作を試してみたいと思います。 Agentでの利用に適しているとのことなので、Ollamaから起動してGitHub Copilot上で動かしてみます。

Ornith-1.5の概要は以下の通りです。(ChatGPTによる生成)

Ornith-1.5とは

Ornith-1.5は、Agent・コーディング用途に重点を置いたオープンウェイトLLMです。最大の特徴は、従来のように人間が用意したタスクだけで学習するのではなく、モデル自身が学習タスクや解法を生成し、その結果を強化学習で評価・改善する「自己改善型」の学習手法を採用している点です。([Hugging Face][1])

モデルは9B、35B-A3B、397Bの3サイズがあり、特に35B-A3Bは約35Bのパラメータを持ちながら、1トークンあたり約3Bのみを活性化するMoE構成です。9B版にはGGUF版も用意されており、ローカル環境での利用にも適しています。([Hugging Face][2])

性能面では、SWE-benchやTerminal-Benchなどのコーディング・Agent系ベンチマークで高い性能を示しており、単なるチャット用途よりも、コード生成・修正やツールを活用するAgentとしての利用が主な狙いです。([Hugging Face][2])

そのため、「LLM自身による自己改善」と「実用的なCoding Agent性能」を両立させたモデルとして注目される存在と言えます。

[1]: https://huggingface.co/ornith-ai/Ornith-1.5-9B?utm_source=chatgpt.com "ornith-ai/Ornith-1.5-9B · Hugging Face" [2]: https://huggingface.co/ornith-ai/Ornith-1.5-35B-A3B/blob/main/README.md?utm_source=chatgpt.com "README.md · ornith-ai/Ornith-1.5-35B-A3B at main"



Ollamaのインストール

Ollamaがインストールされていない場合は、先にインストールしてください。 以下からインストーラーをダウンロードできます。

ollama.com

インストール後、設定画面でコンテキスト長を最大にしておくと良いと思います。 コンテキスト長が短いと、GitHub Copilotで動作させた際にエラーが発生する可能性が高くなります。

ollama_setting

error

Ornithのダウンロード

次に、OllamaでOrnith-1.5のモデルをダウンロードします。 モデルサイズは「9B」「35B-A3B」「397B」の3種類ありますが、今回はGPU(VRAM)に乗るサイズの9Bを使用しました。

ollama run ornith-1.5:9b

ollama.com

GitHub Copilotでの設定

モデルのダウンロードが完了したら、GitHub Copilot側にOllamaを設定します。 「モデルの追加」からOllamaを選択して追加します。 追加すると、Ollamaでダウンロード済みのモデル一覧が表示されます。 これでGitHub Copilotのチャット上でOllamaのモデルを選択できるようになります。

add_model

  • 参考

note.com

コード生成を実行してみる

作成中のチャットボットにおいて、AI返答文のチャットバブルにメタデータを表示するアイコンを追加してもらいました。 1回の実行でも十分なクオリティのコードが出力された印象です。

run

その後、表示位置やデータの調整を重ねて以下のようになりました。

所感

まず、ローカルモデルでここまで動くのはなかなかすごいなというのが素直な感想です…! 今回試したのは最も軽量な9Bモデルですが、それでも十分な完成度だと感じました。 GitHub Copilotの機能はそのまま活用できますし、レート制限に達した際の代替モデルとしても活躍してくれそうです。

一方で、複数ファイルに跨る処理やチャットのターン数が増えた際に、無限ループに陥ってしまうことがありました。 これが9Bモデル特有の現象なのかは、他のサイズを試していないためまだ分かりませんが…。 また、プロンプトで指示を明確にしておかないと勝手に他のファイルを見に行ってしまうことがあり、そこも注意が必要でした。

そうした挙動に気をつければ十分に実用的なモデルだと感じたので、今後も活用していきたいと思います。

QwenシリーズのローカルLLM「Qwen3.8 27B」をお試し

お疲れ様です。

今回は昨日(2026年8月15日)リリースされたQwen3.8 27Bをお試しする回です。



モデルについて

  • HuggingFaceの公式モデルページ

huggingface.co

モデルはUnsloth AIが公開している量子化済みモデルを使用します。

  • Unsloth AIのモデルページ

huggingface.co

HuggingFaceで公開された量子化済みモデル(GGUF)は直接ダウンロードできます。今回は、私の環境のGPU(RTX 4060 Ti、16GB)でも動作するサイズのものを選び、Ollamaでダウンロードして利用しました。

note.com

gguf

上記を参考に量子化タイプごとのGGUFサイズを確認し、VRAM 16GBに収まるもの(Q3_K_M)を選択しました。 ユーザー名とリポジトリ名はモデルページのタイトルからコピー(unsloth/Qwen3.8-27B-GGUF)しています。 ダウンロードは次の形式で実行します。

ollama run hf.co/{ユーザー名}/{リポジトリ名}:{量子化タイプ}

今回は次のコマンドで取得しました。

ollama pull hf.co/unsloth/Qwen3.8-27B-GGUF:Q3_K_M

download

ダウンロードしたモデルはOllamaのアプリ上で問題なく動作しました。自作アプリやPythonプログラムでも動作を確認しています。

ollama_ui

動作チェック

通常の返答生成

自作のチャットボットを使って通常の応答生成を試しましたが、うまく動作しませんでした。約4分待っても回答が返ってこず、原因はまだ特定できていません。

output

ChatOllamaでreasoning=False(思考オフ)にすると応答は返ってきましたが、内容はかなり粗いものでした。

output_2

思考オフ時の生成にかかった時間は以下のとおりです。

time

画像入力

過去に作成したコードを使って画像入力も試しました。入力画像は同じものを使用しています。

こちらはreasoning=False等を指定していませんが、出力自体は問題なさそうでした。何らかのコンテキストを与えれば問題なく動作する、ということでしょうか。

input_image

output_3

所感

今回は手元のプログラムで動かしてみましたがなんとも微妙な結果でした…。

こちらのモデルもMuse Glimmerと同様にエージェントで使用して輝くモデルのようなので、エージェントで使用してみたいところですね。 その辺はどうするか考えてみようと思います。

日本語の性能が悪かったり、コード生成時におかしな挙動をする等の情報もみたのも気になりますが…。 カタログスペックはかなり良いみたいなので、今後の動向も注目していきたいところです。

Antigravity CLIでプロジェクト初期化SKILLを作成・設定してAgent Skillsに入門する

お疲れ様です。

業務でもコーディングエージェントを使う機会が増えてきており、そろそろ関連する技術も勉強しておきたいと思うようになりました。 そこで今回は、エージェント関連技術の1つであるAgent Skillsを試してみました。 入門編として、新規プロジェクトの初期化を行うSKILLを作成します。

今回、SKILLを実行するエージェントには「Antigravity CLI」を使用します。 インストールは下記から行いました。

  • Antigravity CLIのインストール

antigravity.google

また、Antigravity CLIでのSKILLの設定方法は下記2つのサイトを参考にしました。 基本的にこちらの内容で設定自体は可能ですが、SKILLを格納する場所が変わっている場合があるので、その点だけ注意してください。(後述)

antigravity.google

zenn.dev

SKILL.mdの作成

作成したSKILL.mdはこちらになります。長いため折りたたんでいます。 記事内だと少し見づらいため、後述のGitHubページから確認していただくのがよいかもしれません。

ざっくりとしたワークフローは以下のようになっています。 テンプレートリポジトリは自作したものを指定していますが、必要に応じて変更や追加が可能です。

  1. プロジェクト名の設定
  2. テンプレートリポジトリのclone
  3. プロジェクトの初期化
  4. 初期コミットの作成

SKILL.md

---
name: python-project-initializer
description: 標準テンプレートリポジトリから新しいPythonプロジェクトを初期化する。コードの作成は行わず初期化のみにとどめる。
---

# Python Project Initializer

## 目的

ユーザーが新しいPythonプロジェクトを作成したい場合に、このSkillを使用する。

**注意:** 本Skillの役割はプロジェクト構造と開発環境の初期化のみです。アプリケーションコードの作成や機能実装は行いません。

例:

- 新しいPythonプロジェクトを作成して
- uvでプロジェクトを開始したい
- AIプロジェクトを作りたい

---

## 重要なルール

1. **ワークフロー順の遵守(ステップ1・2の確実な実行)**:
   - 必ず **ステップ1(プロジェクトタイプの決定)** および **ステップ2(プロジェクト名と説明の決定)** を行ってからテンプレートのClone(ステップ3以降)に進むこと。
   - プロジェクト名や説明が指定されていない場合は、飛ばさずにユーザーへ確認するか決定すること。
2. **初期化処理のみを行い、アプリケーションコードは作成しない**:
   - 本Skillで実行するのは「プロジェクトタイプ/名前/説明の決定」「テンプレートClone」「Git初期化」「設定ファイルの更新」「`uv sync`」「初期コミット」の初期化処理のみとする。
   - ユーザーの要求に具体的な機能や処理の実装が含まれていても、本Skillではアプリケーションコード(Pythonスクリプト等)の作成・編集は行わない。
   - 初期化完了後はコード作成を行わずに完了報告をし、その後の実装についてユーザーの指示を待つ。

---

## ワークフロー

### 1. プロジェクトタイプを決定する

プロジェクトタイプを確認・決定する。ステップ3に進む前に必ず行うこと。

対応しているテンプレート:
- `python-base` (標準Pythonプロジェクト)

※ ユーザーの指定がない場合や不明な場合は、使用するテンプレートを確認・決定する。

---

### 2. プロジェクト名と説明を決定する

プロジェクト名および概要/説明文を確認・決定する。ステップ3でのClone時およびステップ5での設定更新に使用するため、飛ばさずに必ず決定すること。

- **プロジェクト名**: 指定されていない場合は、ユーザーに確認するか依頼内容に基づき決定する(例: `my-awesome-project`)。
- **プロジェクト説明**: 指定されていない場合は、ユーザーに確認するか適切な説明文を設定する。

例:
- プロジェクト名:`my-awesome-project`
- プロジェクト説明:`A new Python project created with python-project-initializer`

---

### 3. テンプレートリポジトリをCloneする

対応するテンプレートリポジトリをCloneする。

例:

python-base

```bash
git clone https://github.com/muetaek0321/python-project-template.git <PROJECT_NAME>
```

---

### 4. Gitリポジトリを初期化する

テンプレートをCloneした後、以下を実行する。

1. 既存の`.git`ディレクトリを削除する。

- Linux/macOS: `rm -rf .git`
- Windows (PowerShell): `Remove-Item -Recurse -Force .git`

2. 新しいGitリポジトリを初期化する。

例:

```bash
rm -rf .git

git init
```

新しいプロジェクトには、以下を引き継いではならない。

- テンプレートリポジトリのコミット履歴
- リモートリポジトリの設定(`origin`)
- テンプレートリポジトリのブランチ情報

---

### 5. プロジェクト設定を更新する

以下を変更・更新する。

- `pyproject.toml`
  - `{{PROJECT_NAME}}` を決定したプロジェクト名(例: `my-awesome-project`)に置換する。
  - `{{PROJECT_DESCRIPTION}}` を決定したプロジェクト説明文に置換する。

- `README.md`
  - プロジェクトタイトルおよび概要文を更新する。

---

### 6. 依存関係を同期する

以下を実行して仮想環境と依存関係を初期化する。

```bash
uv sync
```

### 7. 初期コミットを作成する

```bash
git add .

git commit -m "Initial commit"
```

1. プロジェクト内のすべてのファイルをステージングする。
2. 初期コミットを作成する。

---

### 8. 完了を報告する

コード作成は行わず、プロジェクトの初期化完了のみを報告して処理を終了する。

以下の内容をまとめて報告する。

- プロジェクト名
- 使用したテンプレート
- Pythonバージョン
- インストールされた依存関係
- 初期化完了の報告と、アプリケーションコードの実装準備が整った旨

Antigravity CLIのSKILLの設定

前述の通り、グローバル設定の場合のSKILLの配置場所は、参考サイトに記載されている内容とは少し異なります。 Agent自身に聞いてみると、C:\Users\[ユーザ名]\.gemini\config\skills\に置くのが正しいとのことです。 このあたりは今後も変わる可能性があるため、都度確認が必要そうです…。

skills_dir

このように配置しました。 SKILL.mdファイルは、スキル名のフォルダ内に配置するようにします。 スキル名はSKILL.md内の"name"にあたる部分をハイフン区切りにした文字列です。

skills_setting

SKILLの配置後、こちらも直接Agentに聞いてみたところ、正常に登録されていることが確認できました。 Antigravity CLIには組み込みのSKILLがいくつか用意されているようです。 このうち3番目のSKILLが、今回設定したものになります。

登録されているskillを教えて

skills_check

実際に試してみる

実際に動作させてみました。
プロンプトは「〇〇のプロジェクトを初期化して」といった内容で動作します。 〇〇の部分から名称や説明が十分に推測できる場合はそれを使ってくれるため、ワークフローの最初の部分をスキップしてテンプレートリポジトリのcloneから進めてくれます。

今回は下記のプロンプトで実行しました。

VLMを使って物体検出のアノテーション(BBox)を補助するツールを作成します。ツールのプロジェクトを初期化してください。

実行すると、ワークフローに沿って処理を進めてくれます。 コマンドの実行が必要な場合は承認要求が出るので、内容を確認して問題なければ許可していきます。

question

実行が終わると、作成されたプロジェクトの概要が表示されます。 今回はプロンプトにある程度具体的な説明を含めていたため、その内容からプロジェクト名と説明文を自動で生成してくれました。

run

repo

所感

初めてAgent Skillsに本格的に触れてみましたが、使いこなせれば非常に便利な機能だなと改めて実感しました。
実際に作成してみて、SKILL.mdの指示の書き方がなかなか難しいと感じました。 意図通りに動いてくれないことも多々あり、トライ&エラーで実行と修正を繰り返しながら作成する形になりました。 このあたりも徐々に慣れていきたいですね。 今回は汎用的に利用できるプロジェクト初期化のSkillを作成してみましたが、今後は特定のプロジェクト専用のSkillなども作ってみたいと思います。 普段はGitHub Copilotを使うことが多いので、Copilot側での設定方法も調べておきたいです。

共有(GitHub)

最後に、今回作成したリポジトリやファイルをまとめて共有しておきます。

  • 作成したSKILL.md

github.com

  • ベースに指定したPythonプロジェクトのテンプレートリポジトリ

github.com

  • 実際にSkillで作成したプロジェクト(今後開発を進めるため変更されている可能性があります)

github.com

MetaのローカルLLM「Muse Glimmer」をお試し

お疲れ様です。

MetaがMuse Glimmerという新しいローカルLLMをリリースしたので、手元の環境で動作させてみました。



リリース

research.meta.ai

モデルについて

モデルはOllamaやHuggingFaceから利用できます。
今回はOllamaから利用できる量子化済みモデルを使用しました。 量子化済みモデルでもサイズが18GBほどあります。

  • Ollama

ollama.com

  • HuggingFace

huggingface.co

動作チェック

通常の返答生成

動作確認には過去に作成したチャットボットを使用しました。 こちらに新たにMuse Glimmerが使用できるように設定を追加しています。

fallpoke-tech.hatenadiary.jp

実際に動作させた結果が以下のようになりました。 コンテキストなどは与えず素の知識のまま実行しています。 (そのため内容はいつも通りめちゃくちゃです。)

output

生成にかかった時間が大体90秒ほどでした。 一般的な返答生成としては遅いです。 私の実行環境はVRAM16GBなので、18GBあるこのモデルではぎりぎり乗り切らないためですね…。一応参考記録ということで。

time

実行時のリソースの使用状況がこんな感じ。 上述の通りモデルがGPUのVRAMに乗り切っていないです。

task_manager

画像入力

また、このモデルはマルチモーダルモデルでもあるので画像の入力も試してみます。

入力画像とプログラムは過去記事で使用したものを流用しています。

fallpoke-tech.hatenadiary.jp

input_image

出力が以下のようになりました。
日本の伝統的なスポーツの会場という文言があり、雰囲気をうまく捉えているようです。 過去記事で試した3つのモデルと比べても良い出力のようです。

image_description

所感

今回は手元で動かせるプログラムを流用して試してみました。 モデルの特徴としてエージェント的な処理もうまくできるようなので、これもいずれ試してみたいところです。

LangChainでローカル画像をbase64エンコードしてChatモデルに渡す実装メモ【備忘録】

お疲れ様です。

LangChainのChatでは、LLMが画像をサポートしていれば画像入力ができることを知ったので試してみました。

今回作成したコードは以下に置いていますので参考にしてください。

github.com

入力方法

早速ですが入力の方法について解説します。

LangChainのMessageを使用し、content"type": "image_url"を指定することで画像を入力することができます。 画像データはURLで渡すか、ローカルから読み込んだ画像ファイルをbase64エンコードした文字列を渡す形になります。 LangChainの場合、この2パターンが基本になります。 一部モデルの公式SDKでは画像データ(PIL.Image形式)を直接渡すことができるようですが、今回はLangChainで使う想定なので割愛します。

from langchain_core.messages import HumanMessage

message = HumanMessage(
    content=[
        {
            "type": "text",
            "text": PROMPT,
        },
        {
            "type": "image_url",
            "image_url": {"url": f"data:image/png;base64,{image_base64}"},
        },
    ]
)

ローカルの画像ファイルを入力したい場合が多いと思うので、base64エンコードをするコードを載せておきます。
一旦PIL.Imageで読み込んだ後に変換する形になります。

def image_to_bytes(image: Image.Image) -> str:
    buffer = BytesIO()

    if image.mode != "RGB":
        image = image.convert("RGB")

    image.save(buffer, format="PNG")

    image_base64 = base64.b64encode(buffer.getvalue()).decode()

    return image_base64

base64エンコードについて詳しくはこちらが参考になります。

qiita.com

動作確認

前述したGitHubリポジトリのコードを実際に動かしてみました。

使用するモデルは以下になります。

  • gemma4:31b(Gemini APIを使用して実行)
  • minimax-m3(Ollama Cloud APIを使用して実行)
  • qwen3.5:9b(ローカル環境のOllamaで実行)

入力する画像としてはこちらを使用しました。日本武道館ですね。

sample.jpg

また、画像と一緒にLLMに渡すプロンプトは以下の内容です。

PROMPT = """
添付された画像を詳細に観察し、以下の項目に沿って日本語でわかりやすく説明してください。
1. 主な被写体(何が写っているか)
2. 背景や周囲の状況(場所や設定)
3. 全体的な色合い・照明・雰囲気
4. 画像から伝わる特徴や特記事項(文字がある場合はその内容も含む)
"""

gemma4:31b

まずは、無料枠でもレートリミットが緩くて扱いやすいGemini APIのgemma4:31bから試してみました。 マルチモーダル対応で日本語性能が高いのが魅力ですね。ローカルLLMでは今一番よく使うモデルかもです。

日本武道館という固有名詞までは出てきていませんが、「アリーナ」や「イベント」というところは画像から読み取れているようです。

output_1

minimax-m3

次にOllama Cloud APIからminimax-m3を試してみました。 無料枠だと使えるモデルの種類がかなり制限されますが、Google系以外を試せるのがメリットでしょうか…。

なんとなくの雰囲気は読み取れていますが、ちょっと怪しいですね。看板部分に書かれている文字を間違えて、そこから間違った解釈で推論してしまっている感じです。

output_2

qwen3.5:9b

最後にローカル環境のOllamaで動くモデルとしてqwen3.5:9bを試してみました。 APIから利用できるモデルに比べるとモデルサイズが小さく、出力に時間がかかりますが、モデルが選び放題です。 今回はQwen3.5を使いましたが、他にも画像入力に対応したモデルはあります。

こちらもなんとなくの雰囲気は読み取れている感じです。写真の構図的に読み取れる情報が少ないということでしょうかね。 ともあれ、ローカルLLMでもある程度は実用的に画像入力も使えそうだなという印象は受けました。

output_3

【Next.js/FastAPI】ローカルLLMも動かせる自作チャットアプリのベースを作った話

お疲れ様です。

最近ひそかに作っていたチャットボットについて紹介します。

chat

このような感じで動作しています。画像ではGemma4:12Bに回答させています。 今はRAG等の実装ができておらず、そのままLLMに返答をさせているだけなので、回答がめちゃくちゃですが…。

ソースコードは以下のリポジトリに置いてあります。
チャットとして最低限必要になる機能部分を実装した時点のバージョンでタグ付けしています。 何かしらチャットのベースとして使用することはできるかと思います。

自分自身もこのバージョンからいろいろとカスタマイズしていこうと考えています。
今後新たに登場してくるであろうローカルLLMの検証環境としても良さそうです。

github.com

構成

本アプリケーションは、FastAPIを用いたバックエンドと、Next.jsを用いたフロントエンドで構成されています。

  • フロントエンド: Next.js(16.2)、React(19)、TypeScriptをベースに、UIコンポーネントライブラリとしてMUI (Material UI)を採用しています。その他、Tailwind CSSやテストフレームワークとしてVitest、React Testing Libraryなどを利用しています。
  • バックエンド: FastAPI を用いたAPIサーバーです。チャットデータや設定の保存、LLMによるテキスト生成を管理します。PydanticやUvicornを利用し、パッケージ管理にはuv、テストにPytestを使用しています。
  • AI/LLM: langchain-google-genai を用いたGemini APIの利用に加え、PyTorchやTransformers、llama.cpp、Ollamaなどを用いてGemmaといったローカルモデルを動かすことも想定した構成になっています。
  • ツール類: コード品質を保つためにESLint、Prettier、Ruffといったリンター・フォーマッターを導入しています。

機能

LLM (Gemini API、ローカルのGemmaモデル等) とチャットを行うための様々な機能を提供しています。

  • チャットの新規作成、履歴の表示・保存・削除(チャットのタイトルは最初のユーザー入力から自動的に設定されます)
  • ユーザーの入力に応じたAI (アシスタント) からの応答生成
  • システムプロンプトの確認および更新
  • チャットモデル(APIやローカルモデル)の切り替え
  • モデル生成パラメータの調整(思考時間など)