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
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@

- `example_apps/echo.py`: 音声をそのまま復唱
- `example_apps/echo_with_move.py`: 復唱 + サーボ動作
- `example_apps/gemini.py`: Gemini 応答を発話
- `example_apps/gemini.py`: Geminiで発話
- `example_apps/openai_compatible.py`: OpenAI互換APIで発話

## 起動時の目安

Expand Down
92 changes: 92 additions & 0 deletions example_apps/openai_compatible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import annotations

import os
from logging import StreamHandler, getLogger

from dotenv import load_dotenv
from openai import AsyncOpenAI
from openai.types.chat import (
ChatCompletionMessageParam,
)

from stackchan_server.app import StackChanApp
from stackchan_server.ws_proxy import (
EmptyTranscriptError,
ServoMoveType,
ServoWaitType,
WsProxy,
)

logger = getLogger(__name__)
logger.addHandler(StreamHandler())
logger.setLevel("DEBUG")

load_dotenv()

app = StackChanApp()

client = AsyncOpenAI(
base_url=os.getenv("OPENAI_BASE_URL", ""),
api_key=os.getenv("OPENAI_API_KEY", ""),
)

MODEL = os.getenv("OPENAI_MODEL", "")

SYSTEM_PROMPT = "あなたは親切な音声アシスタントです。音声で返答するため、マークダウンは記述せず、簡潔に答えてください。だいたい3文程度で答えてください。"


@app.setup
async def setup(proxy: WsProxy):
logger.info("WebSocket connected")


@app.talk_session
async def talk_session(proxy: WsProxy):
messages: list[ChatCompletionMessageParam] = [
{"role": "system", "content": SYSTEM_PROMPT},
]

while True:
# listening pose
await proxy.move_servo([(ServoMoveType.MOVE_Y, 80, 100)])

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

except EmptyTranscriptError:
# off pose
await proxy.move_servo([(ServoMoveType.MOVE_Y, 90, 100)])
return

# 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),
]
)

logger.info("Human: %s", text)

messages.append( {"role": "user", "content": text})

# generate response via OpenAI-compatible API (LM Studio)
resp = await client.chat.completions.create(
model=MODEL,
messages=messages,
)

reply = resp.choices[0].message.content or ""

messages.append( {"role": "assistant", "content": reply})

# speaking
logger.info("AI: %s", reply)
if reply:
await proxy.speak(reply)
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ dev = [
"ty>=0.0.17",
]
example-gemini = []
example-openai = [
"openai>=1.0.0",
]
example-claude-agent-sdk = [
"claude-agent-sdk>=0.1.39",
]
Expand Down
Loading
Loading