Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ async def setup(proxy: WsProxy):

@app.talk_session
async def talk_session(proxy: WsProxy):
# 聞くポーズ
await proxy.move_servo([(ServoMoveType.MOVE_Y, 80, 100)])

chat = client.chats.create(
model="gemini-3-flash-preview",
config=types.GenerateContentConfig(
Expand All @@ -55,6 +52,9 @@ async def talk_session(proxy: WsProxy):
)

while True:
# 聞くポーズ
await proxy.move_servo([(ServoMoveType.MOVE_Y, 80, 100)])

try:
# 音声認識
text = await proxy.listen()
Expand Down
80 changes: 80 additions & 0 deletions docs/create_your_claude_agent_sdk_apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Claude Agent SDKによるAIエージェントアプリケーション開発

[example_apps/claude_agent_sdk/app.py](../example_apps/claude_agent_sdk/app.py) をベースに改変を行い、Claude Agent SDKを利用したエージェントを作る手順を解説します。

> [!CAUTION]
> Claude Agent SDKでは、エージェントがファイルシステムの変更権限を持ちます。
> エージェントに与える指示によっては、意図しないファイルの編集や削除などが行われる可能性があります。
> エージェントに与える指示には十分注意したり、コンテナ環境に置くなどしてください。

## ディレクトリを決める

まず、コードのディレクトリと、ワークスペースとなるディレクトリを決めてください。

コードのディレクトリは、エージェントのプログラムを置くディレクトリです。
ワークスペースとは、Claude Agent SDKの作業ディレクトリです。
ワークスペースにある .claude/ の配下に、スキルやMCPの設定などを置きます。
ワークスペースは、Claude Codeで言うところの、起動ディレクトリに相当します。

## プログラムを作る

コードのディレクトリにて、uvでプロジェクトを初期化します。

```
uv init
```

本リポジトリをライブラリとして追加します。

```
uv add https://github.com/74th/websocket-control-stackchan.git
```

Claude Agent SDKのライブラリも追加します。

```
uv add claude-agent-sdk
```

[example_apps/claude_agent_sdk/app.py](../example_apps/claude_agent_sdk/app.py) を、コードのディレクトリ内にコピーします。

app.py を書き換えていきます。

WORKSPACE_DIR という変数の定義があるため、、ワークスペースのディレクトリを設定するように書き換えます。

```py
WORKSPACE_DIR = "/path/to/your_workspace"
```

## サーバの設定の.envファイルの準備

[./server_ja.md](./server_ja.md) で作成した .env ファイルを、コードのディレクトリにコピーしてください。

## サーバを起動する

app.py と言うファイル名で作成した場合、以下のコマンドでサーバを起動します。
ポート番号等は適宜変更してください。

```
uv run uvicorn app:app.fastapi --host 0.0.0.0 --port 8000
```

> [!INFORMATION]
> uvicorn.run()の第一引数は、`{Pythonモジュール名}:{モジュール内のFastAPIアプリケーションの変数名}` という形式になっています。
>
> app.py というファイルを作った場合、Pythonモジュール名も app になります。
> claude_agent_sdk など、既存モジュールと名前が被らないようにしてください。
>
> app.py 内で、app = StackChanApp() というコードがあるため、モジュール内のFastAPIアプリケーションの変数名は app になります。
> app.fastapi がFastAPIのアプリケーションオブジェクトになります。
>
> そのため、uvicorn.run()の第一引数は、`{ファイル名(拡張子なし)}:app.fastapi` という形式になります。

スタックチャンを起動して、「Disconnected」から「Idle」のステータス表示になれば接続されています。
「ハイ!スタックチャン!」と話しかけて、聞くポーズになることを確認して、話しかけてみてください。

## さらに作り込む

以下のClaude Agent SDKのドキュメントを参照して、スキルやMCPの追加など、さらにエージェントを作り込んでみてください。

> https://platform.claude.com/docs/ja/agent-sdk/python
6 changes: 3 additions & 3 deletions docs/run_sample_app_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ uv sync
その後、以下のコマンドでPythonサーバを起動します。

```
uv run uvicorn app.echo_with_move:app.fastapi --host 0.0.0.0 --port 8000
uv run uvicorn example_apps.echo_with_move:app.fastapi --host 0.0.0.0 --port 8000
```

スタックチャンを起動して、「Disconnected」から「Idle」のステータス表示になれば接続されています。
Expand All @@ -36,7 +36,7 @@ uv sync --group example-gemini
その後、以下のコマンドでPythonサーバを起動します。

```
uv run uvicorn app.gemini:app.fastapi --host 0.0.0.0 --port 8000
uv run uvicorn example_apps.gemini:app.fastapi --host 0.0.0.0 --port 8000
```

スタックチャンを起動して、「Disconnected」から「Idle」のステータス表示になれば接続されています。
Expand Down Expand Up @@ -86,7 +86,7 @@ Claude Agent SDKを利用するには、VertexAIを利用する場合、以下
その後、以下のコマンドでPythonサーバを起動します。

```
uv run uvicorn app.claude_agent_sdk:app.fastapi --host 0.0.0.0 --port 8000
uv run uvicorn example_apps.claude_agent_sdk.app:app.fastapi --host 0.0.0.0 --port 8000
```

スタックチャンを起動して、「Disconnected」から「Idle」のステータス表示になれば接続されています。
Expand Down
4 changes: 2 additions & 2 deletions docs/server_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ STACKCHAN_USE_GOOGLE_CLOUD_STT=1
STACKCHAN_GOOGLE_CLOUD_STT_LANGUAGE_CODE="ja-JP"
```

### Whisper.cppのwhisper-cliの設定
### (オプション)Whisper.cppのwhisper-cliの設定

(WIP)

Expand All @@ -59,7 +59,7 @@ STACKCHAN_WHISPER_CLI_MODEL_PATH="/path/to/whisper.cpp/ggml-small.bin"
STACKCHAN_WHISPER_CLI_VAD_MODEL_PATH="/path/to/whisper.cpp/ggml-silero-v5.1.2.bin"
```

### Whisper.cppのwhisper-serverの設定
### (オプション)Whisper.cppのwhisper-serverの設定

(WIP)

Expand Down
22 changes: 8 additions & 14 deletions docs/setup_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Dockerがインストールされていない場合は以下のページを参
Dockerがインストールできたら、リポジトリのディレクトリで以下のコマンドを実行してください。

```
docker compose run --rm --service-ports voicevox
docker compose -f ./misc/voicevox/docker-compose.yml up -d
```

以下のサイトにアクセスし、「VOICEVOX Engine」と表示されていれば成功です。
Expand All @@ -115,14 +115,14 @@ docker compose run --rm --service-ports voicevox
標準ではGoogle Cloud Speech-to-Textを利用して音声認識を行います。
無料で利用できるWhisper.cppのwhisper-cliも利用できます。

TODO
(TODO)

## (オプション)Whisper.cppのwhisper-serverのインストールと実行

標準ではGoogle Cloud Speech-to-Textを利用して音声認識を行います。
無料で利用できるWhisper.cppのwhisper-serverも利用できます。

TODO
(TODO)

## Python開発環境の構築

Expand All @@ -137,27 +137,21 @@ Pythonの環境構築の方法は、パッケージマネージャuvのページ

以下のページを参照して、サーバの設定を行ってください。

[./server_ja.md](./server_ja.md)
[server_ja.md](./server_ja.md)

## サンプルアプリケーションの実行

まずは、サンプルアプリケーションを実行してみましょう。

以下のページを参照して、サンプルアプリケーションの実行方法を確認してください。

[./run_sample_app_ja.md](./run_sample_app_ja.md)

## アプリケーションを作る

(WIP)

[example_apps/gemini.py](../example_apps/gemini.py) をベースに改変を行い、アプリケーションを作ってみましょう。
[run_sample_app_ja.md](./run_sample_app_ja.md)

## Claude Agent SDKによるエージェントの構築と実行
## Claude Agent SDKによるAIエージェントアプリケーションの開発

(WIP)
以下のページを参照して、ユーザ独自のコードで、Claude Agent SDKを利用したAIエージェントアプリケーションを起動する方法を確認してください。

[example_apps/claude_agent_sdk/claude_agent_sdk.py](../example_apps/claude_agent_sdk/claude_agent_sdk.py) をベースに改変を行い、Claude Agent SDKを利用したエージェントを作ってみましょう。
[docs/create_your_claude_agent_sdk_apps.md](../docs/create_your_claude_agent_sdk_apps.md)

## Claude Agent SDKをDocker環境で実行する

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@
import os
import pathlib
from logging import StreamHandler, getLogger
from typing import Any, Literal

from claude_agent_sdk import (
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
create_sdk_mcp_server,
tool,
)
from dotenv import load_dotenv
from pydantic import BaseModel

from stackchan_server.app import StackChanApp
from stackchan_server.ws_proxy import (
Expand All @@ -39,51 +35,17 @@
model = "claude-haiku-4-5@20251001"


# ツールの作成
class AirConRemoteInput(BaseModel):
room: Literal["寝室", "リビング"]
state: Literal["オフ", "暖房オン", "冷房オン"]


@tool(
"aircon-control",
"自宅のエアコンを操作する。寝室かリビングかを指定する。",
AirConRemoteInput.model_json_schema(),
)
async def aircon_remote(dict_args: dict[str, Any]):
args = AirConRemoteInput.model_validate(dict_args)
# 実際に実装が必要
print(f"🌳エアコンを操作します {args}")
return {"state": "success"}


# MCPサーバ化
home_remote_mcp = create_sdk_mcp_server(
name="home-remote",
version="1.0.0",
tools=[aircon_remote],
option = ClaudeAgentOptions(
model=model,
system_prompt="あなたは音声AIアシスタントのスタックチャンです。ユーザの質問に対して、3文程度の言葉で答えてください。音声案内であるため、マークダウンや絵文字等は用いずに、文字列だけで回答してください",
cwd=str(WORKSPACE_DIR),
setting_sources=["project"],
permission_mode="bypassPermissions",
)


def setup_claude_agent_sdk() -> ClaudeSDKClient:
option = ClaudeAgentOptions(
model=model,
system_prompt="あなたは音声AIアシスタントのスタックチャンです。ユーザの質問に対して、3文程度の言葉で答えてください。音声案内であるため、マークダウンや絵文字等は用いずに、文字列だけで回答してください",
cwd=str(WORKSPACE_DIR),
setting_sources=["project"],
# MCPサーバを登録
mcp_servers={"home-remote": home_remote_mcp},
# tools=["mcp__home-remote__aircon-control"],
# 全て許可
permission_mode="bypassPermissions",
)

return ClaudeSDKClient(
options=option,
)


client = setup_claude_agent_sdk()
client = ClaudeSDKClient(
options=option,
)


@app.setup
Expand Down Expand Up @@ -128,14 +90,3 @@ async def talk_session(proxy: WsProxy):
logger.info("AI: %s", message.result)
if message.result:
await proxy.speak(message.result)


if __name__ == "__main__":
import uvicorn

uvicorn.run(
"example_apps.claude_agent_sdk.claude_agent_sdk:app.fastapi",
host="0.0.0.0",
port=8000,
reload=True,
)
6 changes: 0 additions & 6 deletions example_apps/echo.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,3 @@ async def talk_session(proxy: WsProxy):
return
logger.info("Heard: %s", text)
await proxy.speak(text)


if __name__ == "__main__":
import uvicorn

uvicorn.run("example_apps.echo:app.fastapi", host="0.0.0.0", port=8000, reload=True)
40 changes: 20 additions & 20 deletions example_apps/echo_with_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,31 +35,31 @@ async def setup(proxy: WsProxy):
@app.talk_session
async def talk_session(proxy: WsProxy):
while True:
try:
await proxy.move_servo([(ServoMoveType.MOVE_Y, 80, 100)])
# listening pose
await proxy.move_servo([(ServoMoveType.MOVE_Y, 80, 100)])

try:
# voice recognition
text = await proxy.listen()

await proxy.move_servo(
[
(ServoMoveType.MOVE_Y, 100, 100),
(ServoWaitType.SLEEP, 200),
(ServoMoveType.MOVE_Y, 90, 100),
(ServoWaitType.SLEEP, 200),
(ServoMoveType.MOVE_Y, 100, 100),
(ServoWaitType.SLEEP, 200),
(ServoMoveType.MOVE_Y, 90, 100),
]
)

except EmptyTranscriptError:
# off pose
await proxy.move_servo([(ServoMoveType.MOVE_Y, 90, 100)])
return
logger.info("Heard: %s", text)
await proxy.speak(text)

# nod pose
await proxy.move_servo(
[
(ServoMoveType.MOVE_Y, 100, 100),
(ServoWaitType.SLEEP, 200),
(ServoMoveType.MOVE_Y, 90, 100),
(ServoWaitType.SLEEP, 200),
(ServoMoveType.MOVE_Y, 100, 100),
(ServoWaitType.SLEEP, 200),
(ServoMoveType.MOVE_Y, 90, 100),
]
)

if __name__ == "__main__":
import uvicorn
logger.info("Heard: %s", text)

uvicorn.run("example_apps.echo:app.fastapi", host="0.0.0.0", port=8000, reload=True)
# speaking
await proxy.speak(text)
Loading
Loading