diff --git a/README.md b/README.md index 7a853d2..bcab1f0 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ The broker fetches HA's tools at startup and registers them on the Realtime sess |---|---|---| | `OPENAI_API_KEY` | — | required | | `MODEL` | `gpt-realtime` | Realtime model | +| `REASONING_EFFORT` | unset | reasoning effort for reasoning-line models (`gpt-realtime-2.1`+): `minimal`/`low`/`medium`/`high`/`xhigh`; leave unset for non-reasoning models | | `VOICE` | `marin` | Realtime voice | | `INSTRUCTIONS` | generic | system prompt / persona | | `WS_HOST` / `WS_PORT` | `0.0.0.0` / `8765` | where the device connects | diff --git a/broker/realtime_broker/agent.py b/broker/realtime_broker/agent.py index 93babd7..00fb282 100644 --- a/broker/realtime_broker/agent.py +++ b/broker/realtime_broker/agent.py @@ -16,8 +16,10 @@ AudioConfiguration, AudioInput, AudioOutput, + ClientEvent, InputAudioTranscription, SessionProperties, + SessionUpdateEvent, TurnDetection, ) from pipecat.services.openai.realtime.llm import OpenAIRealtimeLLMService @@ -42,11 +44,27 @@ class VoicePERealtimeService(OpenAIRealtimeLLMService): its own response.create). """ + def __init__(self, *args, reasoning_effort: str | None = None, **kwargs): + super().__init__(*args, **kwargs) + self._reasoning_effort = reasoning_effort + async def _handle_context(self, context: LLMContext) -> None: self._context = context self._llm_needs_conversation_setup = False await self._process_completed_function_calls(send_new_results=True) + async def send_client_event(self, event: ClientEvent) -> None: + # Pipecat 0.0.97's SessionProperties has no `reasoning` field and + # pydantic serializes by declared type, so a subclass field would be + # dropped — inject into the wire dict instead. Applies to every + # session.update (initial setup and mid-session). + if self._reasoning_effort and isinstance(event, SessionUpdateEvent): + dump = event.model_dump(exclude_none=True) + dump["session"]["reasoning"] = {"effort": self._reasoning_effort} + await self._ws_send(dump) + return + await super().send_client_event(event) + # Custom broker tools, registered with handlers by the server. CUSTOM_TOOLS = [ { @@ -206,6 +224,7 @@ async def build_agent(config: Config, mcp: MCPClient | None) -> OpenAIRealtimeLL model=config.model, session_properties=session, start_audio_paused=False, + reasoning_effort=config.reasoning_effort, ) if mcp is not None and tools_schema is not None: diff --git a/broker/realtime_broker/config.py b/broker/realtime_broker/config.py index 2c55e08..ddaf96d 100644 --- a/broker/realtime_broker/config.py +++ b/broker/realtime_broker/config.py @@ -14,6 +14,11 @@ class Config: model: str = "gpt-realtime" voice: str = "marin" instructions: str = "You are a helpful voice assistant." + # Reasoning effort for reasoning-line Realtime models (gpt-realtime-2.1+): + # minimal/low/medium/high/xhigh. The server default (low) makes the model + # deliberate and pad its spoken replies; "minimal" suits command-and-control. + # None = field not sent, required for non-reasoning models (gpt-realtime). + reasoning_effort: str | None = None ws_host: str = "0.0.0.0" ws_port: int = 8765 @@ -80,6 +85,7 @@ def from_env(cls) -> "Config": openai_api_key=api_key, model=os.environ.get("MODEL", "gpt-realtime"), voice=os.environ.get("VOICE", "marin"), + reasoning_effort=os.environ.get("REASONING_EFFORT") or None, instructions=os.environ.get("INSTRUCTIONS", cls.instructions), ws_host=os.environ.get("WS_HOST", "0.0.0.0"), ws_port=int(os.environ.get("WS_PORT", "8765")),