refactor(reasoning)!: remove Node-RED engine and integrations

BREAKING CHANGE: NODE_RED reasoning is no longer supported. Unsupported
mode values fall back to PROXY. PROXY and OPENAI remain available.
This commit is contained in:
2026-10-06 18:58:54 -03:00
parent 77b60622e9
commit e0304b2bdb
31 changed files with 129 additions and 1138 deletions
+5 -8
View File
@@ -5,7 +5,7 @@ The [React browser client](../../s2s-frontend) uses the
`start_ui.sh` serves Vite on the existing public port `8420` and FastAPI on
loopback port `8421`. Vite forwards backend routes and provides hot reload;
Uvicorn reloads Python edits. The launcher supervises both processes.
The speech pipeline and optional Node-RED reasoning service run separately.
The speech pipeline runs separately.
Runtime configuration comes from environment variables and the root `.env`.
```mermaid
@@ -14,7 +14,7 @@ flowchart LR
Vite[Vite on public port 8420]
Server[Client FastAPI server]
Speech[Realtime speech backend]
Reasoning[Node-RED or HTTP reasoning provider]
Reasoning[OpenAI-compatible HTTP reasoning provider]
Tools[ApiPilot backend]
Browser <-->|Frontend and HMR| Vite
Vite -->|HTTP configuration and proxies| Server
@@ -58,7 +58,6 @@ audio. It is not an unconditional byte-for-byte tunnel.
| [components](../../s2s-frontend/src/components) | Chat, caller profile, settings and voice components |
| [audio](../../s2s-frontend/src/audio) | TypeScript capture/playback worklets, PCM codec, and visualizer |
| [legacy-theme.css](../../s2s-frontend/src/styles/legacy-theme.css) | Retained themes and responsive visual styles alongside Tailwind |
| [flows.json](../../s2s-reasoning-engine/flows.json) | Node-RED reasoning and assistant-response logging |
## Server routes
@@ -66,7 +65,7 @@ audio. It is not an unconditional byte-for-byte tunnel.
|---|---|
| `GET /api/config` | Connection settings, primary prompt, reasoning/theme/start modes, and feature flags |
| `WS /v1/realtime` | Local realtime proxy to `S2S_PROXY_TARGET` |
| `POST /api/reasoning` | Node-RED or OpenAI-compatible HTTP reasoning; disabled in `PROXY` mode |
| `POST /api/reasoning` | OpenAI-compatible HTTP reasoning; disabled in `PROXY` mode |
| `GET /api/user/phone` | Configured caller profile lookup |
| `GET /api/user/reports` | Configured scheduled report lookup, subject to report-injection controls |
| `GET /api/apipilot/tools` | Tool discovery with optional authorization and cache refresh |
@@ -104,10 +103,8 @@ and cancellation model is documented in [realtime API](../speech/realtime-api.md
## Reasoning and context
The browser triggers external reasoning on completed user transcripts only in
`OPENAI` or `NODE_RED` mode. Node-RED `log_only=true` responses leave native speech
output running; an answer-producing response uses the external-answer path.
The server can independently forward assistant text to Node-RED for paired
conversation logs. These HTTP operations supplement the realtime audio path.
`OPENAI` mode. A non-empty answer replaces the native response through the
external-answer path. These HTTP operations supplement the realtime audio path.
Read [reasoning](reasoning.md), [context injection](context-injection.md), and
[tool calling](tool-calling.md) for the conditions and limitations of each path.
+3 -1
View File
@@ -62,11 +62,13 @@ existing error shapes; there is no new common error envelope.
## Compatibility evidence
`tests/fixtures/legacy-contracts.json` was captured from the original monolithic
server at commit `3d42df5`, before refactoring. Tests compare 86 HTTP scenarios,
server at commit `3d42df5`, before refactoring. Tests compare 82 HTTP scenarios,
including exact response text, statuses, selected headers, and outgoing mock
HTTP requests. They also compare request OpenAPI definitions and representative
realtime frames. Additional tests cover response validation and permissive
upstream values; the original local HTTP/WebSocket tests remain in the suite.
Four captures belonging exclusively to the retired workflow reasoning service
were removed with that integration; all retained captures are unchanged.
The captures deliberately preserve unknown fields, omitted fields, unusual JSON
values, queue beacon edge cases, permissive tool arguments, and known failures.
-1
View File
@@ -37,4 +37,3 @@ names shown in a deployment are configuration examples, not definitions.
| Claim | The transition from a waiting ticket to an allocated realtime session |
| At capacity | A full waiting queue that cannot accept another ticket |
| Barge-in | User speech interrupts an active assistant response, coordinating playback clearing and backend cancellation |
| Log-only reasoning | External transcript processing that observes/logs the turn without replacing native speech output |
+11 -34
View File
@@ -1,17 +1,15 @@
# Reasoning modes
Reasoning determines the assistant's response text. The realtime backend always
provides the speech channel; optional HTTP reasoning can observe a transcript or
produce a separate answer. Set `S2S_REASONING_TYPE` to select the mode.
provides the speech channel; optional HTTP reasoning produces a separate answer
from a transcript. Set `S2S_REASONING_TYPE` to select the mode.
| Mode | Response owner | HTTP hook |
|---|---|---|
| `PROXY` (default) | Realtime backend | Browser does not call `/api/reasoning`; that endpoint returns 409 |
| `NODE_RED` | Realtime backend for `log_only=true`; external answer otherwise | Server forwards to `S2S_REASONING_NODE_RED_URL/api/reasoning` |
| `OPENAI` | OpenAI-compatible Chat Completions response | Server calls `OPENAI_BASE_URL/chat/completions` |
The mode is normalized case-insensitively. `NODERED` and `NODE-RED` resolve to
`NODE_RED`; unknown values resolve to `PROXY`.
The mode is normalized case-insensitively. Unknown values resolve to `PROXY`.
## Shared instruction assembly
@@ -40,30 +38,6 @@ Using a direct browser URL bypasses local WebSocket proxy processing. Browser
instructions still apply, but server-side session context and marker execution
require the local `/v1/realtime` route.
## Node-RED reasoning
Configure a Node-RED base URL reachable by the UI server:
```env
S2S_REASONING_TYPE=NODE_RED
S2S_REASONING_NODE_RED_URL=http://localhost:1881
```
`VoiceController.reason()` in [controller.ts](../../s2s-frontend/src/realtime/controller.ts)
handles both external modes. It runs after a complete user transcript and posts a body with
`transcript`, `instructions`, `language`, `user_context`, `phoneNumber`, and
`episode_id`. The server builds context and forwards the reasoning fields to
Node-RED with a 30-second timeout.
The checked-in Node-RED flow exposes `POST /api/reasoning` and
`POST /api/log/system-response`. Its reasoning response uses `log_only=true` and
an empty answer. The browser returns early in that case, preserving the native
backend's response. Assistant-text logging is forwarded separately by the relay
and is not an audio transport.
An answer-producing flow must return a non-empty `answer` and omit `log_only` or
set it to `false`. That activates the external-answer behavior below.
## OpenAI-compatible HTTP reasoning
```env
@@ -76,13 +50,17 @@ OPENAI_REASONING_MODEL=gpt-4.1-mini
`OPENAI_ORGANIZATION` and `OPENAI_PROJECT` are optional headers. The model value
is a runtime default, not a recommendation about provider availability.
`VoiceController.reason()` in [controller.ts](../../s2s-frontend/src/realtime/controller.ts)
runs after a complete user transcript and posts a body with `transcript`,
`instructions`, `language`, `user_context`, `phoneNumber`, and `episode_id`.
The server combines instructions and context into model messages, requests a
final answer, and returns `answer` with available usage metadata. ApiPilot can
final answer with a 30-second timeout, and returns `answer`. ApiPilot can
append discovery instructions, execute a detected call marker, and request a
second response based on the tool result. The endpoint does not forward the
browser's native `session.tools` catalog as OpenAI Chat Completions `tools`.
second response based on the tool result, including available `usage_pass2`
metadata. The endpoint does not forward the browser's native `session.tools`
catalog as OpenAI Chat Completions `tools`.
For a non-log-only answer, the browser cancels/suppresses the active native
For an external answer, the browser cancels/suppresses the active native
response and appends the external assistant text. It uses browser
`SpeechSynthesis` when available. The React client adds the answer to the realtime
conversation as an assistant item; without browser speech synthesis it remains
@@ -101,5 +79,4 @@ realtime backend instead.
| Change browser context assembly | `buildInstructions()` in `s2s-frontend/src/lib/context.ts` |
| Change HTTP provider messages | `reasoning()` in `s2s-client/s2s_client/services/reasoning.py` |
| Change external-answer rendering | `VoiceController.reason()` and `RealtimeClient.renderExternalAnswer()` |
| Change Node-RED processing | `s2s-reasoning-engine/flows.json` |
| Change native model/tool generation | `speech_to_speech/LLM` and realtime handlers |
+1 -3
View File
@@ -109,9 +109,7 @@ before changing audio timing based on those observations.
## External reasoning and startup
`/api/reasoning` returning 409 is expected in `PROXY` mode. In `NODE_RED`, check
that `S2S_REASONING_NODE_RED_URL` is reachable from the server and whether the
response is `log_only=true`. An empty answer is valid only in log-only mode.
`/api/reasoning` returning 409 is expected in `PROXY` mode.
In `OPENAI`, check the configured endpoint/model/key and provider logs; HTTP
reasoning does not use the native session's tool array.