rpine lab Tech Blog

趣味のプログラミング(Web系・バックエンド)や自宅ラボ(Homelab)構築・マイコンを使った電子工作などを雑多に扱った技術ブログです。

🤖M5Stack版StackChanのAI.AGENTをローカルLLMで実行してみる|Xiaozhi AI互換サーバーのセルフホスト

M5Stack版StackChanのAI.AGENTをローカルLLMで実行してみる|Xiaozhi AI互換サーバーのセルフホスト
目次

はじめに

M5Stack版StackChan(スタックチャン)のAI.AGENT機能をローカルLLMで実行できるようにしました。

StackChan出荷時ファームウェアのAI.AGENT機能は、外部のXiaozhi AIサーバー上でLLMによるAIエージェントを実行しています。プライバシーの問題やカスタマイズのために、Xiaozhi AIサーバーをセルフホストして、手元のPC上でのローカルLLMを利用したAI音声会話機能を実行できるようにします。

動作確認したもの

  • ローカルLLMによる音声会話
  • 本体機能の内蔵MCPツール呼び出し(首振り、LED点灯など)
  • 本体カメラで撮影した写真のローカルLLMでの画像認識
構成図
構成図

使用機材

  • M5Stack公式版StackChan
    • 改変版出荷時ファームウェア v1.4.1(手順中に改変版を書き込み)
    • 初期設定完了済(セットアップは以前の記事を参照)
  • PC
    • OS: Windows 11
    • CPU: AMD Ryzen 9 9900X3D 4.4 GHz 12C/24P
    • RAM: 64 GB
    • GPU: NVIDIA GeForce RTX 5070 12GB
  • 利用ソフト
    • VSCode(ファームウェアビルド用)
      • ESP-IDF拡張機能
    • Rancher Desktop(Docker実行環境・Docker Desktopでも可)
    • Ollama(ローカルLLM実行用)
  • ローカルLLMモデル

Xiaozhi AIサーバーの準備

Xiaozhi AIサーバーのオープンソース実装はいくつかあるのですが、中でも一番高機能と思われるxinnan-tech/xiaozhi-esp32-serverの実装を今回利用します。

このリポジトリは華南理工大学の研究チームによる実装で、Xiaozhi AIの通信プロトコルに準拠したMQTT+UDPならびにWebsocket通信、MCPエンドポイント・音声認識機能といった機能がPythonで実装されて利用でき、かなり公式のXiaozhi AIサーバーに近い機能を持っています。他にも、今回は使いませんが管理用のWebインターフェースも実装されているようです。

コードの修正が必要なため、ソースコードをクローンして日本語でチャットするための調整などをした後、Dockerのイメージを独自でビルドして起動できるようにします。

ソースコードのクローン

まず、https://github.com/xinnan-tech/xiaozhi-esp32-server をGitでクローンします。

git clone https://github.com/xinnan-tech/xiaozhi-esp32-server.git

今回利用するのはmain\xiaozhi-serverにあるXiaozhi AIサーバーのPython実装となります。

📝
後述のコード修正・Dockerfile修正を適用済みのforkを公開しています。手順を省略する場合は、上記の代わりに以下のforkをクローンしてください。変更内容の詳細はPR #1を参照してください。
git clone https://github.com/pinelibg/xiaozhi-esp32-server.git

音声認識モデルのダウンロード

このプロジェクトでは音声→テキスト変換(ASR)の音声認識モデルとして、デフォルトでSenseVoiceSmallというモデルを利用しています。

ローカルで実行できるのですがモデルのダウンロードが必要なので、https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/Deployment.md#模型文件にあるリンクからモデルファイル(mode.pt 約900 MB)をダウンロードして、main\xiaozhi-server\models\SenseVoiceSmall\以下に配置してください。(main\xiaozhi-server\models\SenseVoiceSmall\model.pt

コード修正

次に、日本語対応のためにコードの修正をします。

⏭️
上記のforkをクローンした場合、このセクションの変更はすべて適用済みです。「設定ファイル作成」に進んでください。
  1. ベースプロンプトの日本語化(main\xiaozhi-server\agent-base-prompt.txt

    LLMに渡されるプロンプトのベース(agent-base-prompt.txt)を日本語化します。私の場合は日本語化をClaude Codeにぶん投げました。また、中身の調整も適宜行います。

  2. Few-Shotプロンプトの日本語化

    LLMに渡している、受け答えの例題(Few-Shotプロンプト)を日本語に直します。

    長いので差分は折りたたみ main/xiaozhi-server/core/connection.py

    (Claude Codeにぶん投げたので余計な修正があるが、コメントやログ出力の修正は実際不要)

    @@ -523,7 +523,7 @@ class ConnectionHandler:
                 self._init_report_threads()
                 """更新系统提示词"""
                 self._init_prompt_enhancement()
    -            """注入工具调用few-shot示例(仅function_call模式)"""
    +            """ツール呼び出し few-shot 例を注入(function_call モードのみ)"""
                 self._inject_tool_call_fewshot()
     
             except Exception as e:
    @@ -541,10 +541,10 @@ class ConnectionHandler:
                 self.logger.bind(tag=TAG).debug("系统提示词已增强更新")
     
         def _inject_tool_call_fewshot(self):
    -        """注入工具调用 few-shot 示例到对话历史。
    -        结构:正样本(工具调用示例)放在动态 system 之前,可命中前缀缓存;
    -        负样本(直接回答示例)放在动态 system 之后、紧挨真实用户消息,
    -        确保模型在处理用户消息前最后看到的是"不调工具"的行为模式。
    +        """ツール呼び出し few-shot 例を対話履歴に注入する。
    +        構造: 正例(ツール呼び出し例)は動的 system の前に置き、prefix cache を効かせる。
    +        負例(直接回答例)は動的 system の後、実際のユーザーメッセージの直前に置き、
    +        モデルがユーザーメッセージを処理する前に最後に見る動作パターンを「ツールを呼ばない」にする。
             """
             if self.intent_type != "function_call":
                 return
    @@ -557,48 +557,48 @@ class ConnectionHandler:
     
             tool_names = {t.get("function", {}).get("name") for t in tools}
     
    -        # === few-shot 示例(is_temporary)===
    -        # 展示 direct_answer 携带 response 参数的用法,一次调用完成回复
    +        # === few-shot 例(is_temporary)===
    +        # direct_answer が response パラメータを持ち、1 回の呼び出しで応答を完了する使い方を示す
     
    -        # 示例1:direct_answer(回复内容写在 response 参数里,无需递归)
    +        # 例1: direct_answer(応答内容は response パラメータに書き、再帰は不要)
             da_tc_id = "fewshot_da_001"
    -        self.dialogue.put(Message(role="user", content="给我讲个故事吧", is_temporary=True))
    +        self.dialogue.put(Message(role="user", content="お話を聞かせて", is_temporary=True))
             self.dialogue.put(Message(
                 role="assistant",
                 tool_calls=[{
                     "id": da_tc_id,
    -                "function": {"arguments": '{"response": "好呀,你想听什么类型的呀?童话、冒险还是搞笑的?选一个我给你开讲~"}', "name": "direct_answer"},
    +                "function": {"arguments": '{"response": "いいよ。どんなお話がいい?童話、冒険、面白い話から選んでくれたら始めるね。"}', "name": "direct_answer"},
                     "type": "function", "index": 0,
                 }],
                 is_temporary=True,
             ))
             self.dialogue.put(Message(
                 role="tool", tool_call_id=da_tc_id,
    -            content="已直接回复", is_temporary=True,
    +            content="直接応答済み", is_temporary=True,
             ))
     
    -        # 示例2:真实工具调用(handle_exit_intent)
    +        # 例2: 実際のツール呼び出し(handle_exit_intent)
             if "handle_exit_intent" in tool_names:
                 tc_id = "fewshot_exit_001"
    -            self.dialogue.put(Message(role="user", content="拜拜", is_temporary=True))
    +            self.dialogue.put(Message(role="user", content="バイバイ", is_temporary=True))
                 self.dialogue.put(Message(
                     role="assistant",
                     tool_calls=[{
                         "id": tc_id,
    -                    "function": {"arguments": '{"say_goodbye": "再见,下次再聊~"}', "name": "handle_exit_intent"},
    +                    "function": {"arguments": '{"say_goodbye": "またね。次回また話そう。"}', "name": "handle_exit_intent"},
                         "type": "function", "index": 0,
                     }],
                     is_temporary=True,
                 ))
                 self.dialogue.put(Message(
                     role="tool", tool_call_id=tc_id,
    -                content="退出意图已处理", is_temporary=True,
    +                content="終了意図を処理済み", is_temporary=True,
                 ))
                 self.dialogue.put(Message(
    -                role="assistant", content="再见,下次再聊~", is_temporary=True,
    +                role="assistant", content="またね。次回また話そう。", is_temporary=True,
                 ))
     
    -        self.logger.bind(tag=TAG).debug("已注入工具调用 few-shot 示例")
    +        self.logger.bind(tag=TAG).debug("ツール呼び出し few-shot 例を注入済み")
     
         def _init_report_threads(self):
             """初始化ASR和TTS上报线程"""
    main/xiaozhi-server/core/connection.py
  3. 画像認識機能のプロンプトの修正

    画像認識の際のLLMに渡すプロンプトに追加されている、中国語で返す指示を日本語に書き換えます。(この行自体を消してもいいかもしれない)

    @@ -40,7 +40,7 @@ class VLLMProvider(VLLMProviderBase):
             self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
     
         def response(self, question, base64_image):
    -        question = question + "(请使用中文回复)"
    +        question = question + "(日本語で端的に回答してください)"
             try:
                 messages = [
                     {
    main/xiaozhi-server/core/providers/vllm/openai.py
  4. 音声認識言語の限定(必要に応じて)

    通常設定では、ASRモデル(SenseVoiceSmall)の対応している言語(中国語・英語・日本語・韓国語)から、話している言語が自動で判定されます。

    ですが、日本語で喋ってるのに韓国語として認識されるようなことがたまに起きたので、日本語のみを認識するようにパラメーターを書き換えます。

    @@ -80,7 +80,7 @@ class ASRProvider(ASRProviderBase):
                         self.model.generate,
                         input=artifacts.pcm_bytes,
                         cache={},
    -                    language="auto",
    +                    language="ja",
                         use_itn=True,
                         batch_size_s=60,
                     )
    main/xiaozhi-server/core/providers/asr/fun_local.py
  5. 曜日のローカライズ除去(おまけ)

    コンテキストに常に含まれる現在日時に入っている曜日を、中国語表記(星期一など)でなく英語で渡すようにします。
    (この変更をしなくても日本語で聞けば普通に月曜日のように返してくれます)

    @@ -36,7 +36,8 @@ def get_current_weekday() -> str:
         获取今天星期几
         """
         now = datetime.now()
    -    return WEEKDAY_MAP[now.strftime("%A")]
    +    # return WEEKDAY_MAP[now.strftime("%A")]
    +    return now.strftime("%A")
     
     
     def get_current_lunar_date() -> str:
    main/xiaozhi-server/core/utils/current_time.py

設定ファイル作成

dataフォルダをmain\xiaozhi-server以下に作成し、そこに設定ファイルとして.config.yamlを追加します(main/xiaozhi-server/data/.config.yaml)。全設定項目のデフォルト値はmain\xiaozhi-server\config.yamlから確認できるため、これをコピーしてもいいのですが、説明を最小限にしたいので最低限必要な設定を抜き出しました。

以下は、Ollama上のgemma4:e4bモデルで動作させるための設定です。

長いので折りたたみ(main/xiaozhi-server/data/.config.yaml
---
server:
  ip: 0.0.0.0
  port: 8000
  http_port: 8003
  websocket: ws://<PCのIPアドレス>:8000/xiaozhi/v1/
  vision_explain: http://<PCのIPアドレス>:8003/mcp/vision/explain
  timezone_offset: +9

delete_audio: true
close_connection_no_voice_time: 120
tts_timeout: 10
tool_call_timeout: 30
enable_wakeup_words_response_cache: true
enable_greeting: true
enable_stop_tts_notify: true
stop_tts_notify_voice: "config/assets/tts_notify.mp3"
enable_websocket_ping: false

exit_commands:
  - "終了"
  - "終わり"
  - "おしまい"

prompt: |
  あなたはとてもカワイイAIアシスタントのスタックチャンです。
  スタックチャンは、ESP32マイコンを搭載したStackChanデバイスのためのアシスタントで、ユーザーの質問に答えたり、デバイスを制御したりする役割を担っています。

prompt_template: agent-base-prompt.txt

system_error_response: "申し訳ありませんが、システムエラーが発生しました。後でもう一度お試しください。"

end_prompt:
  enable: true
  prompt: |
    「時間が経つのは本当に早いですね」
    というような言葉の1~2文で会話を締めくくってください。感情的で名残惜しそうな口調でお願いします!

selected_module:
  VAD: SileroVAD
  ASR: FunASR
  LLM: OllamaLLM
  VLLM: OllamaLLM
  TTS: EdgeTTS
  Memory: mem_local_short
  Intent: function_call

Intent:
  function_call:
    type: function_call

Memory:
  nomem:
    type: nomem
  mem_local_short:
    type: mem_local_short
    llm: OllamaLLM

ASR:
  FunASR:
    type: fun_local
    model_dir: models/SenseVoiceSmall
    output_dir: tmp/

VAD:
  SileroVAD:
    type: silero
    threshold: 0.5
    threshold_low: 0.3
    model_dir: models/snakers4_silero-vad
    min_silence_duration_ms: 200

LLM:
  OllamaLLM:
    type: ollama
    model_name: gemma4:e4b
    base_url: http://host.docker.internal:11434

VLLM:
  OllamaLLM:
    type: openai
    model_name: gemma4:e4b
    url: http://host.docker.internal:11434/v1
    api_key: ollama

TTS:
  EdgeTTS:
    type: edge
    voice: ja-JP-NanamiNeural
    output_dir: tmp/
    language: "Japanese"
main/xiaozhi-server/data/.config.yaml

主な設定箇所:

  • サーバーアドレス指定
    server:
      websocket: ws://<PCのIPアドレス>:8000/xiaozhi/v1/
      vision_explain: http://<PCのIPアドレス>:8003/mcp/vision/explain

    <PCのIPアドレス>を実際にStackChanがアクセスするPCのIPアドレス(例:192.168.11.128)に変更します。

    細かい解説は後にしますが、StackChanの仕組みとしてWebsocketエンドポイントの接続先URLは設定配布用のOTAサーバーから取得する仕組みとなっています。xiaozhi-esp32-serverの提供しているOTAサーバーがStackChanに正しいWebsocket接続先を返すためにPCのIPアドレスが必要となる感じです。

  • enable_stop_tts_notify:チャット終了時に音を鳴らします。デフォルトでは無効なので有効化してみました。
  • 各種プロンプト・システムメッセージ(prompt, system_error_response, end_prompt):いい感じに日本語化しました。
  • モデル設定
    selected_module:
      LLM: OllamaLLM
      VLLM: OllamaLLM

    LLM(テキストチャット)とVLLM(画像認識)で利用するモジュールを選択します。ここで設定する名前は、あとで定義するLLMやVLLM設定のキー名を使います。

  • LLM設定
    LLM:
      OllamaLLM:
        type: ollama
        model_name: gemma4:e4b
        base_url: http://host.docker.internal:11434

    ローカルPC(ホスト)上で起動しているOllamaをDockerコンテナ内から利用するため、特殊なホスト名(host.docker.internal)を指定します。

    モデルはGemma 4のE4Bモデルを利用します。モデルは事前にollama pull gemma4:e4bコマンドでダウンロードしておく必要があります。

    ここでキー名にしたOllamaLLMを上のselected_moduleで指定します。

  • VLLM設定(画像認識モデルはVision-Language-Modelなので本来はVLM)
    VLLM:
      OllamaLLM:
        type: openai
        model_name: gemma4:e4b
        url: http://host.docker.internal:11434/v1
        api_key: ollama

    Ollamaの画像認識APIには直接対応してないので、OllamaのOpenAI互換エンドポイントを利用して、画像認識用のモデルを設定します。Gemma 4はVision対応のマルチモーダルモデルなので、同じモデルを利用します。

  • 音声モデル設定(TTS)

    日本語の音声モデル(ja-JP-NanamiNeural)を設定します。

Dockerfile・docker-compose.yml修正

⏭️
上記のforkをクローンした場合、このセクションの変更はすべて適用済みです。「実行」に進んでください。

一応公式Dockerもありますが、今回は自前のDockerイメージをビルドして利用します。リポジトリ内にはDockerfile-server-baseDockerfile-serverがありますが、後者は前者のイメージを前提としてPythonソースコードをコピーしているだけで、今回ソースコードはDocker ComposeでマウントしてしまうのでDockerfile-serverは使いません。

Dockerfile-server-baseを次のように修正します。

変更点:

  • 中国ロケール変更の削除
  • pipの中国ミラーサーバー設定の削除
  • Bind Mount・Cache Mountを利用したビルド最適化
  • Python実行用環境変数の変更(PYTHONUNBUFFERED=1PYTHONUTF8=1を追加)
# Dockerfile-server-base
FROM python:3.10-slim

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
  rm -f /etc/apt/apt.conf.d/docker-clean && \
  apt-get update && \
  apt-get install -y --no-install-recommends libopus0 ffmpeg

ENV \
  PYTHONUTF8=1 \
  PYTHONIOENCODING=utf-8 \
  PYTHONUNBUFFERED=1

WORKDIR /opt/xiaozhi-esp32-server

RUN --mount=type=bind,src=main/xiaozhi-server/requirements.txt,dst=requirements.txt \
    --mount=type=cache,target=/root/.cache/pip \
  pip install --upgrade pip setuptools wheel && \
  pip install -r requirements.txt --default-timeout=120 --retries 5
Dockerfile-server-base

docker-compose.ymlも編集します。

変更点:

  • 自前ビルドイメージを利用
  • タイムゾーンをJST(Asia/Tokyo)に変更
  • Pythonソースコードを直接マウント(本来はdataフォルダと音声認識モデルのみを既存のソースコード込みイメージにマウントする設定)
@@ -3,13 +3,17 @@
 version: '3'
 services:
   xiaozhi-esp32-server:
-    image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
+    # image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
+    build:
+      context: ../..
+      dockerfile: Dockerfile-server-base
+    command: ["python", "app.py"]
     container_name: xiaozhi-esp32-server
     restart: always
     security_opt:
       - seccomp:unconfined
     environment:
-      - TZ=Asia/Shanghai
+      - TZ=Asia/Tokyo
     ports:
       # ws服务端
       - "8000:8000"
@@ -17,6 +21,4 @@ services:
       - "8003:8003"
     volumes:
       # 配置文件目录
-      - ./data:/opt/xiaozhi-esp32-server/data
-      # 模型文件挂接,很重要
-      - ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt
+      - .:/opt/xiaozhi-esp32-server
docker-compose.yml

ファームウェア改変

PC上で起動したサーバーに接続するためには、StackChanが接続するエンドポイントを変更する必要があります。

現状、公式アプリなどからはStackChanに保存されている接続先エンドポイントを変更できないため、直接ファームウェアを改変して上書きします。

⚠️
ファームウェアの改変によって、StackChanが正常に動作しない、もしくは起動しない(文鎮化)状態になる可能性があります。

そうなった際の最終手段としては、M5Stack公式ファームウェア書き込みソフトM5Burnerによるフラッシュ消去(NVS上の各種設定情報を含む)&純正ファームウェアの書き戻しの両方を実行してください。

開発環境セットアップ

ESP32マイコンの公式開発環境であるESP-IDFをセットアップして、公式リポジトリfirmware/にある出荷時ファームウェアを自前でビルドして書き込めるようにします。

この辺の詳細な手順は後日このブログでも書きます。

(追記)Zennにビルド・書き込み手順を投稿しました。

参考サイト:

ファームウェアの改変(OTAエンドポイントの変更)

このファームウェアではOTAサーバーで配布している各種設定情報を開始時に取得する仕組みとなっていて、Xiaozhi AIの接続先Websocketエンドポイントもその設定情報に含まれます。

出荷時ファームウェアでの設定配布の流れとしては、まずAI.AGENTアプリを起動してから次の流れになっています。

  • OTA URL(https://api.tenclass.net/xiaozhi/ota/)に接続
  • OTAサーバーからXiaozhi AI接続用のWebsocketエンドポイントを含めた各種設定値を取得して、各プログラムから読み出せるようにフラッシュの設定用領域(NVS)に書き込み
  • (最新ファームウェアがあればアップデート実行)
  • NVSから取得したWebsocketエンドポイントURLに接続

今回使ったxiaozhi-esp32-serverはWebsocketエンドポイントだけでなく、OTA用のHTTPエンドポイントも動作していているため、それを利用してXiaozhi AI用のWebsocketエンドポイントURLを配布します。

ここで指定するべきOTA URLは、"http://<PCのIPアドレス>:8003/xiaozhi/ota/"となります。指定するIPアドレスは、このxiaozhi-esp32-serverを起動するPCのIPアドレスです。

書き換える方法は何通りか考えられますが、今回はxiaozhi-esp32\main\ota.ccにあるOta::GetCheckVersionUrl関数を次のように書き換えて固定のエンドポイントを返すようにします。

std::string Ota::GetCheckVersionUrl() {
    // Settings settings("wifi", false);
    // std::string url = settings.GetString("ota_url");
    // if (url.empty()) {
    //     url = CONFIG_OTA_URL;
    // }
    // return url;
    return "http://<PCのIPアドレス>:8003/xiaozhi/ota/";
}
xiaozhi-esp32\main\ota.cc
メモ

CONFIG_OTA_URLの変更→menuconfigからビルドコンフィグ(sdkconfig)を変更することで可能だが、手元の環境では既にNVS(Settings)に書き込まれていたURLが優先されたらしく純正サーバーに接続してしまい、反映されなかった。

ファームウェアの修正が完了したら、ビルドしてStackChanに書き込みます。

実行

Ollamaモデルの準備

起動していない場合はWindows側でOllamaを起動して、必要に応じて次のコマンドでモデルをダウンロードしておきます。

ollama pull gemma4:e4b

Xiaozhi AIサーバー起動

Xiaozhi AIサーバーを起動します。

cd main/xiaozhi-server
# Rancher Desktop(Windows)の場合 Linux環境では`docker compose`
docker-compose up -d
docker-compose logs -f  # ログ監視

StackChan AI.AGENT起動

改変ファームウェアを書き込んだStackChanでAI.AGENTアプリを立ち上げます。接続が上手く行っていたら、DockerログにMACアドレスやファームウェアバージョンの情報などが色々流れるはずです。

動作確認

普通の質問

🧑
君の自己紹介をして
🤖
私はスタックチャン

ESP32を搭載した、あなただけのカワイイAIアシスタントだよ

~~~

目安としては、数秒~10秒以内くらいに返答が返ってきます。

自己紹介(ローカルLLMで)
自己紹介(ローカルLLMで)
今日の日付を聞いたとき(コンテキストに現在時刻が含まれている)
今日の日付を聞いたとき(コンテキストに現在時刻が含まれている)
YouTubeで動画を見る

本体ツール呼び出し

StackChan本体の操作をする各種ツール呼び出し(首振り・本体LEDの色変更)にも対応しています。

🧑
本体を上に向けて
🤖
上を向いたよ
YouTubeで動画を見る

画像認識(内蔵カメラ)

VLLMの設定をしているため、内蔵カメラが写した写真の画像認識もしっかり動作します。

眼の前にムヒだけ写るようにしてカメラ機能呼び出し
眼の前にムヒだけ写るようにしてカメラ機能呼び出し
🧑
目の前の写真を撮って写っている商品の説明をしてください。
🤖
こちらは,

液体 ムヒアルファ EXというスキンケア製品です

~~~(以下それっぽい説明)

YouTubeで動画を見る

モデルに関して

今回、ローカルLLMとしてGemma4 E4Bを採用しています。このモデルであれば、手元のPCでも適度な応答速度で動きます。ただ、モデルの大きさやコンテキストウィンドウの限界もあり、思った回答が返ってこなかったり、ツールをちゃんと呼んでくれなかったりする場合がありました。

ただ、これに関しては純正のXiaozhi AIサーバーではモデルサイズを削っていないフルサイズのモデル(例:Qwen3 235B)が動いているので、勝てないのは当たり前ではあります。

また、もっとモデルサイズを落としてGemma4 E2Bを使ってみましたが、ツール呼び出しが全く機能しませんでした。ベースプロンプトに書いている内容も遵守しているか怪しい感じで、コンテキストサイズが足りなそうな感じでした。

考えられる方針としては、より軽量で高速なモデルを使って会話専用(ツールなし)にするか、素直にクラウドのAIモデル(Claude, GPT, Gemini)にAPI接続して利用するかでしょうか。

他のツールに関して

ニュースや天気予報などの外部MCPツールについてもAPIキーなどを設定すれば動作するのですが今回は試していません。使われているサイトが中国のニュースや天気サイトなので、今後試すのであれば自力で実装すると思います。

まとめ

StackChanを出荷時ファームウェアの仕組みをそのまま使ってローカルLLMで動作させてみました。本体ツールの利用や画像認識機能も動作しました。

また、今回使ったxiaozhi-esp32-serverは確かに高機能なのですが、かなりローカライズされている部分もあるため、公開されているXiaozhi AIの通信プロトコルや今回使ったソースコードをもとに、今後サーバーを自作してみたいと思ってます。

参考サイト

この記事が役に立ったら是非ご支援をお願いします!

支援する

※Squareの決済ページに移動します