Arduino UNO Q のApp Labのexpose_api から AI 推論をトリガする

ChatGPT 5.2 Thinkingで、Arduino UNO QのApp Labの「expose_api」機能について調べていたところ、ChatGPT 5.2 Thinkingから、「次の質問候補」が複数提示された。その質問候補の中に、「expose_api から AI 推論をトリガ」という質問があったので、本記事では、この質問に対する回答を紹介する。

(注)本記事の内容は正確性を保証するものではない。

(2026年1月8日 追記)YouTube動画を追加しました。 https://youtu.be/2FqXwueC5HI

目次

「expose_api から AI 推論をトリガする」とは?

ChatGPT 5.2 Thinkingの回答

以下は、Arduino UNO Q × Arduino App Lab
「expose_api を叩く → AI 推論を実行 → 結果を Web UI に返し、必要なら MCU も制御する」
という 実務でそのまま使える完全例です。

全体像(今回やること)

  • ボード:Arduino UNO Q
  • 環境:Arduino App Lab
  • AI:AI Brick(Classifier系を想定)
Web UI(ボタン)
   ↓ HTTP POST
expose_api(Python)
   ↓
AI 推論(AI Brick)
   ↓
① 推論結果を Web UI に返す
② 必要なら MCU を制御(Bridge.call)

👉 ポイント

  • AI は「常時動かさない」
  • ユーザー操作やイベントで“明示的に推論”させる構成

デモ内容(完成形)

  • Web UI に 「判定する」ボタン
  • 押すと:
    1. 最新センサ値で AI 推論
    2. 結果(normal / anomaly)を表示
    3. anomaly の場合は MCU の LED を ON

1️⃣ MCU 側(Arduino)

※ センサ値取得+LED制御のみ(AIは一切知らない)

#include <Arduino_RouterBridge.h>

const int LED_PIN = 13;
float latest_value = 0.0;

void set_led(bool on) {
  digitalWrite(LED_PIN, on ? HIGH : LOW);
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Bridge.begin();
  Bridge.provide("set_led", set_led);
}

void loop() {
  // ダミーセンサ値(例:温度)
  latest_value = 20.0 + random(-50, 50) / 10.0;
  Bridge.notify("on_sample", latest_value);
  delay(1000);
}

2️⃣ Python 側(expose_api → AI 推論)

AI Brick の準備(例:Classifier)

from arduino.app_utils import App, Bridge
from arduino.app_bricks.web_ui import WebUI
from arduino.app_bricks.ai_classifier import Classifier

ui = WebUI()
model = Classifier(model="simple-anomaly")

latest = {"value": None}

MCU → Python(最新値を保持)

def on_sample(value: float):
    latest["value"] = value

Bridge.provide("on_sample", on_sample)

expose_api で AI 推論をトリガ

@ui.expose_api("POST", "/infer")
def api_infer():
    if latest["value"] is None:
        return {"ok": False, "error": "no data"}

    result = model.predict([latest["value"]])

    label = result["label"]          # "normal" / "anomaly"
    score = result.get("score", 0.0)

    # 異常なら MCU 制御
    if label == "anomaly":
        Bridge.call("set_led", True)
    else:
        Bridge.call("set_led", False)

    return {
        "ok": True,
        "value": latest["value"],
        "label": label,
        "score": score
    }

App.run()

ここが核心

  • AI 推論は expose_api の中でだけ実行
  • 推論結果は
    • Web UI に返す
    • MCU 制御にも使う
  • AI結果を bool / enum に落として MCU に渡す

3️⃣ Web UI(assets/index.html)

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>UNO Q AI Trigger</title>
</head>
<body>
  <h1>AI Inference</h1>

  <button onclick="infer()">判定する</button>

  <p>
    Value: <span id="value">--</span><br>
    Result: <strong><span id="label">--</span></strong><br>
    Score: <span id="score">--</span>
  </p>

  <script src="app.js"></script>
</body>
</html>

4️⃣ Web UI(assets/app.js)

function infer() {
  fetch("/infer", { method: "POST" })
    .then(r => r.json())
    .then(j => {
      if (!j.ok) {
        alert(j.error);
        return;
      }
      document.getElementById("value").textContent = j.value.toFixed(1);
      document.getElementById("label").textContent = j.label;
      document.getElementById("score").textContent = j.score.toFixed(2);
    });
}

5️⃣ 実行手順

  1. App Lab でプロジェクト作成
  2. WebUI Brick + AI Classifier Brick を追加
  3. 上記コードを配置
  4. Deploy
  5. ブラウザで開く
http://<UNO Q の IP>:7000/

✔ ボタンを押した瞬間だけ AI 推論
結果が画面表示
異常時は LED が点灯

設計のベストプラクティス(重要)

観点指針
推論トリガexpose_api で明示的に
AI結果bool / enum に変換
MCUAIを知らせない
UI「判定する」など意図的操作
安全Pythonで最終判断

よくある応用

  • Web UI の設定変更 → その条件で AI 推論
  • 定期タイマー + expose_api 併用
  • AI結果+理由(特徴量)を UI に表示
  • 異常時のみ推論(負荷削減)

まとめ

  • expose_api は
    「AI推論のスイッチ」として使うと最強
  • UNO Q では
    • AI判断:Python
    • 実行判断:Python
    • 実制御:MCU
      の分業が安定
  • AIを“必要なときだけ動かす設計”が実運用向き

次に進むなら:

1️⃣ AI 推論理由(特徴量)を Web UI に表示
2️⃣ AI 推論を非同期化(UIブロック防止)
3️⃣ 複数 AI モデル切替(expose_api で選択)

どれを見ますか?

次の質問として、「複数 AI モデル切替(expose_api で選択)」を選択予定だが、その前に上記AIの回答について追加質問を行った。


AI 推論は expose_api の中でだけ実行」のように構成する利点は何ですか

ChatGPT 5.2 Thinkingの回答

「AI 推論は expose_api の中でだけ実行(=ユーザー操作や外部要求が来たときだけ推論)」にする利点は、主に 負荷・安定性・UX・運用の4つです。UNO Q みたいに Linux(Python)+MCU を同居させる構成だと特に効きます。

1) CPU/電力/発熱を抑えられる(必要なときだけ重い処理)

AI 推論はたいてい CPU/GPU負荷が高いので、常時ループで回すと

  • 発熱・ファン・電力増
  • 他の処理(Web UI、ログ、通信)が詰まる
  • モデルによってはレイテンシが揺れる

expose_api トリガなら、押したとき/要求が来たときだけ実行なので、普段は軽く保てます。

2) Web UI が「意図したタイミング」で結果を得られる(UXが分かりやすい)

ボタンで POST /infer を叩く設計だと、

  • ユーザーが「今この状態を判定してほしい」という意図を明確に出せる
  • 返ってくるレスポンスがそのまま画面表示できる(成功/失敗/理由)

常時推論だと、画面上の数値が勝手に更新されて

  • 「いつの判定?」「何を根拠に?」が曖昧
  • 連続更新でUIが落ち着かない
    になりがちです。

3) 失敗時の扱いが明確(エラー処理が簡単)

expose_api 内だと、

  • 入力データがない(まだセンサ値が来てない)
  • モデルがロードできない
  • 推論が例外で落ちた

といった状況を HTTPレスポンス(ok: false, error: …)で返せるので、UI側も処理しやすいです。
常時推論だと、どこで失敗したかが分散し、原因切り分けが面倒になります。

4) セキュリティ/安全設計に向く(操作の入口を一本化)

推論→MCU制御(リレーON等)まで繋ぐなら、

  • 認可(LAN内限定、簡易トークン等)
  • レート制限(連打対策)
  • 入力の妥当性チェック(範囲、型)

expose_api が入口として一箇所に集約できます。
常時推論だと「いつ誰の要求で制御が動いたのか」が曖昧になりやすいです。

5) ログ・再現性・監査が取りやすい(運用が楽)

POST /infer を呼んだ瞬間に

  • 入力(センサ値、設定)
  • モデルバージョン
  • 出力(label/score)
  • その結果で何をしたか(LED ON等)

一括でログ化できます。
「いつ・何で・どう判断したか」が残せるので、改善サイクルが回しやすいです。

6) レイテンシの管理がしやすい(“重い処理”を隔離)

推論を常時ループに入れると、推論時間が伸びた瞬間に

  • Web UI配信(send_message)
  • センサ受信(Bridge)
  • API応答

が巻き込まれがちです。
expose_api トリガなら、推論の重さはそのAPI呼び出しに閉じるので、他の周期処理と干渉しにくいです。

ただしデメリットもあります(正直な話)

  • 自動監視(異常を即検知)には向かない
    → その場合は「定期タイマー推論」や「閾値超え時だけ推論」などのハイブリッドが良いです。

おすすめ構成(よく使う“いいとこ取り”)

  • 普段:MCUが軽く監視(簡易閾値・移動平均)
  • 必要時だけ:expose_api(“/infer”) でAI推論(精密判定)
  • 結果:Web UIに返す+必要ならMCU制御

これが一番トラブルが少なく、運用もしやすいです。

もし「自動監視もしたい」用途なら、
/infer(手動)に加えて、Python側で 一定間隔で推論するタイマーを入れた併用例も作れます。
用途は「異常を即通知」寄りですか?それとも「必要時に判定」寄りですか?


下記の文の意味を説明してください。

expose_api は「AI推論のスイッチ」として使うと最強

ChatGPT 5.2 Thinkingの回答

その文は、expose_api を 「AI推論を実行するタイミングを“意図的にON/OFFできる入口(スイッチ)”として設計すると、いちばん使い勝手が良くなる」という意味です。

何が“スイッチ”なのか

たとえば AI 推論を、

  • Python の loop() で常時回す(常時ON)
    ではなく、
  • POST /infer が来たときだけ推論する(必要時だけON)

にすると、/infer が スイッチになります。

@ui.expose_api("POST", "/infer")
def api_infer():
    result = model.predict(...)
    return result

Web UI のボタンがこの API を叩けば、

  • ボタン押下(スイッチON)→ 推論実行 → 結果返却
  • 押さない(スイッチOFF)→ 推論しない

という分かりやすい動きになります。

“最強”と言っている理由(得られるメリット)

1) 必要なときだけ動かせる(軽い・安定)

推論は重いので、普段は回さず 必要時だけ実行できる。

2) UI と相性がいい(結果がそのまま返る)

/infer のレスポンスをそのまま画面表示できて、「今の判定結果」が明確になる。

3) エラー処理が簡単(APIの成功/失敗で扱える)

データ不足・モデル未ロードなどを {“ok”: false, “error”: “…”}
みたいに返して UI で表示できる。

4) 制御まで一気通貫にできる

推論結果に応じて Bridge.call()MCU を動かす(LED、リレーなど)処理も、
同じAPIの中で完結できる。

具体例(Web UI からAI推論→MCU制御)

  • Web UI の「判定」ボタン → POST /infer
  • Python が推論
  • 結果が anomaly なら MCU に set_led(true) を送る
  • 返ってきた JSON を UI に表示

このとき POST /infer が AI推論のスイッチです。

まとめ

「expose_api は“AI推論のスイッチ”として使うと最強」=
推論を常時回すのではなく、API呼び出しをトリガにして必要時だけ推論し、結果をUIへ返し、必要ならMCU制御までまとめて行う設計が一番扱いやすい、という意味です。


下記の文の意味は、単にループ内に、AI推論コードを置いておくと、必要のない時でも実行されるが、expose_apiを使えば、POST /infer が来たときだけ推論させることができるということだと思いますが、例えば、if文の構文の中にAI推論コードを入れておけば、条件が整った時だけAI推論コードを実行させることができるのではないでしょうか。expose_apiを使ってもできるという程度の意味ですか。

推論は重いので、普段は回さず 必要時だけ実行できる

ChatGPT 5.2 Thinkingの回答

結論から言うと、あなたの理解(「ループに置くと不要時でも回るが、expose_api なら POST /infer のときだけ回せる」)は正しいです。
そしてご指摘の通り、if 文で条件が整ったときだけ推論することも当然できます。

ただし「expose_api を使う利点」は、単に “ifでもできることがAPIでもできる” という程度ではなく、“トリガの種類・責務分離・運用性”が変わる点にあります。

1) if 文トリガと expose_api トリガの違い(本質)

A. if 文(内部トリガ

  • ループ内で「条件成立したら推論」
  • トリガは プログラム内部の状態(センサ値、タイマー、閾値など)

向いている:
✅ 自動監視(異常を検知したら自動で判定)
✅ 周期処理(10秒ごと等)
✅ 完全スタンドアロン動作

弱い点:

  • 「ユーザーが今すぐ判定したい」という要求を取り込みにくい
  • 推論の実行タイミングがUIから見えにくい
  • “いつ・誰が・何で”推論したかの追跡がしにくい(作り込めば可能)

B. expose_api(外部トリガ

  • POST /infer などの外部要求が来たら推論
  • トリガは UI操作/外部システム(ボタン、スマホ、他機器、Webhook)

向いている:
✅ 手動判定(今この状態で判定したい)
✅ UIでの操作・設定変更と連動
外部連携(Node-RED、Home Assistant、別PCからの呼び出し等)

強い点:

  • 「推論要求」が明示的イベントになる(ログ化・監査・再現が簡単)
  • 入力(JSON)を一緒に渡せる(例:モード、閾値、対象センサ、推論オプション)
  • 結果(JSON)をその場で返せる(UIが表示しやすい/失敗理由も返せる)

2) 「重いので普段は回さず必要時だけ実行」の意味を、もう一段正確に言うと

単に「不要時に回らない」だけなら if 文でも実現可能です。
でも expose_api を“スイッチ”にする話の核は、

  • “必要時”の定義を「ユーザー/外部が要求した時」にできる
  • その要求に対して 同期的に「結果+エラー+付帯情報」を返せる

という点です。

3) if 文だけだと困りやすい典型例

例:UIの「判定する」ボタン

  • if 文トリガ:
    ボタンが押されたことを、結局どこかで受け取って状態変数を変える必要がある
    (=内部トリガに落とし込む手間が増える)
  • expose_api:
    ボタン押下 → POST /infer → 推論 → JSON返却
    で完結

例:入力が毎回違う(推論モード切替など)

  • if 文:入力受け渡し・整合性チェック・タイミング管理が増える
  • expose_api:POST /inferJSON に {“mode”:”fast”,”target”:”temp”} を一緒に渡せる

4) 実務的な結論:どちらが正解?

「どっちでもできる」というより、用途で使い分けるのが正解です。

  • 自動で常時監視したい → if(内部トリガ)が主役
  • 人がUIで“今だけ判定”したい/外部から呼びたい → expose_api(外部トリガ)が主役
  • よくある最適解 → ハイブリッド
    • 普段:if で軽い監視(閾値や簡易判定)
    • 必要時:POST /infer で重いAI推論(精密判定)

5) ハイブリッドのミニ例(考え方だけ)

  • if:センサが「怪しい」ときだけ need_infer=True にする(軽い条件)
  • expose_api:ユーザーが押したら即推論(手動)
  • 推論関数は共通 run_infer() にまとめる

こうすると、

  • 自動でも動く
  • 手動でも動く
  • 推論処理は1箇所

になって保守が楽です。

あなたのケースだと、目的はどちら寄りですか?

  • A) 自動監視(異常を見つけたら勝手に判定)
  • B) 手動判定(必要なときにボタンで判定)
  • C) 両方(普段は軽く監視+必要時に精密推論)

expose_apiでAI推論をトリガーすることにより、多くの利点が得られることが分かった。


複数 AI モデル切替(expose_api で選択)

「複数 AI モデル切替(expose_api で選択)」について説明してください

ChatGPT 5.2 Thinkingの回答

狙い:UNO Q の App Lab で、expose_api にモデル名やモードを渡して推論モデルを切り替える設計(安全策・キャッシュ・UI連携・MCU制御まで)を、日本語で動く雛形付きで説明します。
次に、REST設計(/infer と /models)と、実装パターン(事前ロード/遅延ロード)を整理します。

「複数 AI モデル切替(expose_api で選択)」は、Web UI(または外部)から API を叩くときに「どのモデルで推論するか」を指定して、UNO Q 側(Python)がそのモデルを選んで推論する設計です。

何が嬉しいのか(使いどころ)

  • 用途別にモデルを変える
    • 例:fast(軽い・速い)/accurate(重い・高精度)
  • 対象別にモデルを変える
    • 例:温度異常検知モデル/振動異常検知モデル
  • 実験・A/Bテスト
    • UIでモデルを切替えながら比較できる
  • フェイルセーフ
    • 既定モデルが失敗したら別モデルに切替える、など

設計パターン(おすすめ)

APIを2つ用意する

  1. モデル一覧(UIでプルダウンを作る)
  • GET /models → 利用可能モデルのリストを返す
  1. 推論(モデル指定して実行)
  • POST /infer(JSONで model を指定)
    • 例:{"model":"fast","input":[...],"action":"led"}

実装例(Python:WebUI + expose_api + モデル切替)

ここでは「Classifier系AI Brickがある」前提で、モデル切替の枠組みを示します。
実際の Brick 名や Classifier(model=...) の引数は、あなたの環境の Brick に合わせて差し替えてください。

from arduino.app_utils import App, Bridge
from arduino.app_bricks.web_ui import WebUI
# 例:Classifier Brick(名称は環境により違う可能性あり)
from arduino.app_bricks.ai_classifier import Classifier

ui = WebUI()

# ① 利用可能モデル(ホワイトリスト)
MODEL_CATALOG = {
    "fast":     {"model_id": "simple-anomaly-fast"},
    "accurate": {"model_id": "simple-anomaly-accurate"},
}

# ② モデルのキャッシュ(毎回ロードしない)
_model_cache = {}

def get_model(model_key: str) -> Classifier:
    if model_key not in MODEL_CATALOG:
        raise ValueError(f"unknown model: {model_key}")
    if model_key not in _model_cache:
        model_id = MODEL_CATALOG[model_key]["model_id"]
        _model_cache[model_key] = Classifier(model=model_id)
    return _model_cache[model_key]

# (例)MCUから最新値を受け取る
latest = {"value": None}
def on_sample(value: float):
    latest["value"] = float(value)
Bridge.provide("on_sample", on_sample)

# --- API: モデル一覧 ---
@ui.expose_api("GET", "/models")
def api_models():
    return {
        "ok": True,
        "models": [
            {"key": k, "model_id": v["model_id"]}
            for k, v in MODEL_CATALOG.items()
        ]
    }

# --- API: 推論(モデルを選択) ---
@ui.expose_api("POST", "/infer")
def api_infer(data=None):
    # data は POST の JSON body(実装により data が渡る/渡らない差があり得るので防御的に)
    data = data or {}
    model_key = data.get("model", "fast")  # 指定がなければ fast
    value = data.get("value", latest["value"])

    if value is None:
        return {"ok": False, "error": "no input value"}

    try:
        model = get_model(model_key)
        result = model.predict([float(value)])
    except Exception as e:
        return {"ok": False, "error": str(e), "model": model_key}

    # 結果の整形(例)
    label = result.get("label", "unknown")
    score = float(result.get("score", 0.0))

    # (任意)異常ならMCU制御
    if data.get("action") == "led":
        Bridge.call("set_led", label == "anomaly")

    return {
        "ok": True,
        "model": model_key,
        "input": float(value),
        "label": label,
        "score": score,
    }

App.run()

ここがポイント

  • MODEL_CATALOG(ホワイトリスト)で、UIから任意の文字列を受けても安全に制御
  • キャッシュで、モデルロードの重さを抑える
  • POST /infer は
    • model(どのモデル?)
    • value(入力)
    • action(推論後にMCUも動かす?)
      まとめて扱える

Web UI 例(モデル選択+推論ボタン)

assets/app.js(要点だけ)

async function loadModels() {
  const res = await fetch("/models");
  const j = await res.json();
  const sel = document.getElementById("model");
  sel.innerHTML = "";
  j.models.forEach(m => {
    const opt = document.createElement("option");
    opt.value = m.key;
    opt.textContent = m.key;
    sel.appendChild(opt);
  });
}

async function infer() {
  const model = document.getElementById("model").value;
  const value = parseFloat(document.getElementById("value").value);
  const res = await fetch("/infer", {
    method: "POST",
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify({ model, value, action: "led" })
  });
  const j = await res.json();
  document.getElementById("out").textContent = JSON.stringify(j, null, 2);
}

この assets/app.js は、ブラウザ上のWeb UIから
(1) 利用可能なモデル一覧を読み込んでプルダウンに入れる
(2) 選んだモデルで推論APIを呼んで結果を表示する という2つの処理をするコードです。

  • loadModels()
  • GET /models を呼び出して JSON を取得します。
  • 返ってきた j.models(モデル一覧)を使って、<select id="model"> の中身を空にし、option を順に追加します。
  • つまり「モデル選択のプルダウンを最新の一覧で埋める」処理です。
  • infer()
  • 画面から model(選択中のモデル)と value(入力値)を取り出します。
  • POST /infer{ model, value, action: "led" } をJSONで送ります。
  • 返ってきた推論結果JSONを受け取り、<pre id="out"> などの out 要素に整形して表示します。

要するに、UIでモデルを選んで数値を入れて「推論」ボタンを押すと、/infer に投げて結果を画面に表示するための最小コードです。

実装の選択肢(現場で悩むところ)

A) 事前ロード(起動時に全モデルをロード)

  • ✅ 最初の推論が速い
  • ❌ 起動が遅い・メモリを食う

B) 遅延ロード+キャッシュ(おすすめ)

  • ✅ 起動が軽い
  • ✅ 一度使ったモデルは速い
  • ❌ 初回だけロード待ちが出る

C) 毎回ロード(非推奨)

  • ✅ 実装は単純
  • ❌ 遅い・重い・不安定になりやすい

API設計のコツ(安全・拡張)

  • モデル指定は必ずホワイトリスト化
  • POST /infer の入力は
    • model(必須にしてもOK)
    • mode(fast/accurate 的な推論設定)
    • threshold(UI側設定値)
      をJSONで受けると後から増やしやすい
  • 結果は
    • label / score
    • model / model_version(入れられるなら)
      を返すと運用が楽

まとめ

「複数 AI モデル切替(expose_apiで選択)」=
POST /infer のJSONで model を指定して、UNO Q(Python)がモデルを選び、推論して、結果を返す(必要ならMCUも制御する)設計です。
UIのプルダウンと相性が良く、比較・運用・安全設計がやりやすくなります。

目次