アーカイブ済み(2026-09-18): 本ドキュメントは../ui-brain-protocol.mdの再設計(Issue #8)に伴いアーカイブした。
adjust_volumeの意味論(2節)・.env永続化方針(4節)は実装済みで有効な内容のため、新しい付録文書を作る際の参照元として使うこと。接続方式(3節)は#8の結論待ちのまま保留されていた内容であり、そのまま採用しないこと。
ステータス: adjust_volumeの意味論(2節)は実装済み。接続方式(3節以降)は保留 — Issue #8参照
本書はui-brain-protocol.mdで定義される一般プロトコルの付録であり、UI層が音声応答UI(AiChatリポジトリ)である場合に固有の仕様・実装判断を定義する。一般プロトコルで規定されない事項(control個々の意味、リトライ方針、失敗時の振る舞い等)はここに書く。
関連: AiChatリポジトリ issue #8(音量の音声指示機能)。過去の中間検討資料はarchive/参照。
issue #8はもともと.envのAUDIO_OUTPUT_VOLUMEによる静的な倍率設定だったが、「ユーザーが音声で指示したら音量が変わる」機能へスコープを変更した。「もう少し大きく」「うるさいから下げて」のような言い回しの揺れを吸収するため、既存の天気・Web検索と同じLLM tool-calling方式で意図認識する。
操作対象はこのアプリケーション内部の再生倍率(_apply_volume_pcm16によるPCM16スケーリング)であり、Windows OS自体のマスター音量ではない。
controlアクション: adjust_volume一般プロトコルのcontrol配列に現れる、音声UI固有のアクション種別。
{"type": "adjust_volume", "direction": "up" | "down"}
direction)のみを判定して返す。数値・現在の音量・ステップ幅など、実装依存の値は一切持たない。_VOLUME_STEP = 0.1)。0.0〜2.0にクランプする(既存AUDIO_OUTPUT_VOLUMEの検証範囲と揃える)。conversation)を信用せず上書きする(Brainは現在値を知らないため、既に上限でも「上げました」と言ってしまう可能性がある)。例: 「これ以上大きくできません」。type・不正なdirectionはログに残して無視する(例外にしない)。controlはconversationを読み上げる前に必ず適用する(新しい音量で読み上げ自体が再生されるように)。conversationが空文字になり得る設計上、既存の「回答が空なら会話を終了する」という分岐は、control適用より先に評価してはならない。無音の成功でもcontrolの適用自体は行い、そのターンを継続する。
conversation, control = self.chat.ask(text)
override = self._apply_control_actions(control)
speak_text = override or conversation
if speak_text:
self._play_ack_tone()
self._speak(speak_text)
microphone.clear()
start_timeout_sec = self.settings.conversation_timeout_sec
_VOLUME_STEP = 0.1
def _apply_control_actions(self, control: list[dict]) -> str | None:
"""controlを適用する。クランプにより実際は変化しなかった場合、代わりに読み上げるべき文言を返す。"""
override: str | None = None
for action in control:
if action.get("type") != "adjust_volume":
logger.warning("未知のcontrolアクションです: %s", action)
continue
direction = action.get("direction")
if direction not in ("up", "down"):
logger.warning("不正なdirectionです: %s", action)
continue
step = _VOLUME_STEP if direction == "up" else -_VOLUME_STEP
before = self.settings.audio_output_volume
after = max(0.0, min(2.0, before + step))
self.settings.audio_output_volume = after
if after == before:
override = "これ以上大きくできません" if direction == "up" else "これ以上小さくできません"
return override
adjust_volumeツール定義(参考。実装場所: AiChatWls側tools.py)Brain側のツール定義そのものだが、その存在理由は音声UIにしかないため、参考としてここに載せる。
def adjust_volume(direction: str) -> dict[str, object]:
if direction not in ("up", "down"):
raise ToolError(f"未知の方向です: {direction}")
return {"type": "adjust_volume", "direction": direction}
ToolSpec(
name="adjust_volume",
description=(
"ユーザーが音量を上げてほしい/下げてほしいと発話した時に呼ぶ"
"(例:「もっと大きく」「うるさいから下げて」「音量下げて」)。"
"具体的な数値は分からないので、directionだけを渡す。"
),
parameters={
"type": "object",
"properties": {
"direction": {
"type": "string",
"enum": ["up", "down"],
"description": "上げるならup、下げるならdown",
},
},
"required": ["direction"],
},
handler=adjust_volume,
is_client_action=True,
)
現状app.pyのAssistant.__init__はローカルのOllamaChatを直接構築しており、Brainの/askを一切呼んでいない。adjust_volumeをBrain経由の役割分担(Brainが意図認識、音声UIが状態適用)で動かすには、この接続(OllamaChat→RemoteChat、wsl-brain-separation-plan.mdのステージ1本体カットオーバー)が必要になる。
2026-09-18に一度、音量機能を実現するためのカットオーバー方式(フルカットオーバー、RemoteChat実装、Windows側tools.py削除、リトライ・タイムアウト方針)をここで検討・確定させたが、これは音量という単一機能の要請から場当たり的に決めるべき話ではなく、複数UI層を見据えたUI層-Brain層間の構造・インタフェース設計そのものの課題だと整理し直した。この検討はIssue #8に分離し、本節(3節)以降の確定は#8での設計確立後に行う。
.env書き戻し、確定)当初の受け入れ条件「変更は次回起動時にも引き継がれることが望ましい」に基づき、.envのAUDIO_OUTPUT_VOLUMEへ書き戻す方式を採用する。
_apply_control_actionsで音量が実際に変化した場合(クランプにより変化しなかった場合を除く)、その場で.envファイルのAUDIO_OUTPUT_VOLUME行を新しい値で更新する。python-dotenvのset_key()(または同等の部分置換)を使い、.env内の他の行・コメントを壊さないようにする。生の読み書き(openして全文再構成)は避ける。self.settings.audio_output_volumeは書き込み成否に関わらずそのまま適用を続ける(永続化の失敗で音量調整機能自体を止めない)。.env読み込み(config.py)は変更不要(次回起動時に更新済みの値がそのまま読まれる前提)。def _persist_volume(self, value: float) -> None:
try:
set_key(self.settings.env_path, "AUDIO_OUTPUT_VOLUME", str(value))
except OSError:
logger.warning(".envへの音量書き戻しに失敗しました(プロセス内の値は維持します)", exc_info=True)
_apply_control_actions内、self.settings.audio_output_volume = afterの直後にself._persist_volume(after)を呼ぶ(クランプでafter == beforeの場合は呼ばない)。
本節の内容自体は音量固有で#8の結論に左右されないが、_apply_control_actionsがどこから呼ばれるか(=Brainとどう繋がるか)は#8で決まる接続方式に依存するため、実際の組み込みは#8確立後になる。
_apply_control_actionsによるクランプ(0.0〜2.0)と、クランプ時のoverride文言。controlの適用が読み上げ(_speak)より先に行われること。directionを安全に無視すること。.env書き戻し: 値の更新、他の行・コメントが保持されること、書き込み失敗時にプロセス継続すること。/ask経由で音量指示→適用→.env反映までを通しで確認する。adjust_volumeツール、AskResult、dedup、フォールバック修正): AiChatWls側 実装・テスト済み・push済み。_apply_control_actions、.env書き戻し): 未実装。3節(接続方式)はIssue #8の結論待ち。