Trim GA session.update + add deploy marker + surface upstream silence

The previous run showed session_started, then total silence from the
relay for the entire conversation — no events forwarded, no errors,
nothing. Two changes to disambiguate:

1. Drop the explicit audio.input.format / audio.output.format objects.
   24 kHz PCM16 mono is the documented GA default for both directions
   and the only thing OpenAI ever advertised; an unrecognised value
   inside the format object is the most likely reason OpenAI silently
   ignores incoming audio.

2. Forward 'upstream silent NxN250ms' debug envelopes every ~2 s when
   ReceiveJsonAsync keeps returning null, and add a 'server_version'
   field to hello_ack so the Pi journal proves which build is running.
This commit is contained in:
2026-06-12 07:04:53 +00:00
parent a42038b825
commit 12be767325
3 changed files with 22 additions and 17 deletions
+6 -1
View File
@@ -177,6 +177,10 @@ public static class DeviceHubEndpoint
}, ct);
}
// Bump on every backend change so we can verify a Coolify deploy landed
// by looking at the hello_ack config.server_version field in the Pi journal.
public const string ServerVersion = "plan3-debug-2026-06-12-09";
private static async Task SendHelloAckAsync(ActiveDevice active, Device device, CancellationToken ct)
{
var enabled = device.Config?.EnabledToolsJson ?? "[]";
@@ -186,7 +190,8 @@ public static class DeviceHubEndpoint
"model": "{{Escape(device.Config?.Model)}}",
"system_prompt": "{{Escape(device.Config?.SystemPrompt)}}",
"idle_timeout_seconds": {{device.Config?.IdleTimeoutSeconds ?? 30}},
"enabled_tools": {{enabled}}
"enabled_tools": {{enabled}},
"server_version": "{{ServerVersion}}"
}
""")!.AsObject();
+5 -13
View File
@@ -26,6 +26,11 @@ public static class RealtimeEvents
});
}
// Minimal GA session.update. We drop explicit audio.input/output.format
// objects (the GA shape accepts them but OpenAI defaults to 24 kHz PCM16
// mono on both directions, which is what we use). Removing the explicit
// format objects matches the documented minimal session config and
// eliminates a known source of OpenAI silently ignoring the audio.
return new JsonObject
{
["type"] = "session.update",
@@ -38,11 +43,6 @@ public static class RealtimeEvents
{
["input"] = new JsonObject
{
["format"] = new JsonObject
{
["type"] = "audio/pcm",
["rate"] = 24000,
},
["transcription"] = new JsonObject { ["model"] = "whisper-1" },
["turn_detection"] = new JsonObject
{
@@ -50,9 +50,6 @@ public static class RealtimeEvents
["threshold"] = 0.5,
["prefix_padding_ms"] = 300,
["silence_duration_ms"] = 500,
// GA requires both explicitly — without them the
// model never auto-generates a reply after VAD
// detects speech_stopped.
["create_response"] = true,
["interrupt_response"] = true,
},
@@ -60,11 +57,6 @@ public static class RealtimeEvents
["output"] = new JsonObject
{
["voice"] = cfg.Voice,
["format"] = new JsonObject
{
["type"] = "audio/pcm",
["rate"] = 24000,
},
},
},
["tools"] = tools,
+11 -3
View File
@@ -128,10 +128,18 @@ public class RealtimeSession
if (evt is null && ct.IsCancellationRequested) return;
if (evt is null)
{
// Upstream has gone silent. If null persists for >2s, log so
// we can spot a dead upstream that's not surfacing as a close.
// Upstream has gone silent. Surface that to the device every
// ~2s so we can tell "OpenAI is up but emitting nothing" from
// "the upstream WS was closed" — Coolify logs aren't reachable.
if (++nullPolls % 8 == 0)
_logger.LogDebug("upstream silent ({Polls} x 250ms polls)", nullPolls);
{
_logger.LogInformation("upstream silent ({Polls} x 250ms polls)", nullPolls);
await _output.WriteEnvelopeAsync(new JsonObject
{
["type"] = "debug",
["message"] = "upstream silent " + nullPolls + "x250ms",
}, ct);
}
continue;
}
nullPolls = 0;