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:
@@ -177,6 +177,10 @@ public static class DeviceHubEndpoint
|
|||||||
}, ct);
|
}, 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)
|
private static async Task SendHelloAckAsync(ActiveDevice active, Device device, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var enabled = device.Config?.EnabledToolsJson ?? "[]";
|
var enabled = device.Config?.EnabledToolsJson ?? "[]";
|
||||||
@@ -186,7 +190,8 @@ public static class DeviceHubEndpoint
|
|||||||
"model": "{{Escape(device.Config?.Model)}}",
|
"model": "{{Escape(device.Config?.Model)}}",
|
||||||
"system_prompt": "{{Escape(device.Config?.SystemPrompt)}}",
|
"system_prompt": "{{Escape(device.Config?.SystemPrompt)}}",
|
||||||
"idle_timeout_seconds": {{device.Config?.IdleTimeoutSeconds ?? 30}},
|
"idle_timeout_seconds": {{device.Config?.IdleTimeoutSeconds ?? 30}},
|
||||||
"enabled_tools": {{enabled}}
|
"enabled_tools": {{enabled}},
|
||||||
|
"server_version": "{{ServerVersion}}"
|
||||||
}
|
}
|
||||||
""")!.AsObject();
|
""")!.AsObject();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
return new JsonObject
|
||||||
{
|
{
|
||||||
["type"] = "session.update",
|
["type"] = "session.update",
|
||||||
@@ -38,11 +43,6 @@ public static class RealtimeEvents
|
|||||||
{
|
{
|
||||||
["input"] = new JsonObject
|
["input"] = new JsonObject
|
||||||
{
|
{
|
||||||
["format"] = new JsonObject
|
|
||||||
{
|
|
||||||
["type"] = "audio/pcm",
|
|
||||||
["rate"] = 24000,
|
|
||||||
},
|
|
||||||
["transcription"] = new JsonObject { ["model"] = "whisper-1" },
|
["transcription"] = new JsonObject { ["model"] = "whisper-1" },
|
||||||
["turn_detection"] = new JsonObject
|
["turn_detection"] = new JsonObject
|
||||||
{
|
{
|
||||||
@@ -50,9 +50,6 @@ public static class RealtimeEvents
|
|||||||
["threshold"] = 0.5,
|
["threshold"] = 0.5,
|
||||||
["prefix_padding_ms"] = 300,
|
["prefix_padding_ms"] = 300,
|
||||||
["silence_duration_ms"] = 500,
|
["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,
|
["create_response"] = true,
|
||||||
["interrupt_response"] = true,
|
["interrupt_response"] = true,
|
||||||
},
|
},
|
||||||
@@ -60,11 +57,6 @@ public static class RealtimeEvents
|
|||||||
["output"] = new JsonObject
|
["output"] = new JsonObject
|
||||||
{
|
{
|
||||||
["voice"] = cfg.Voice,
|
["voice"] = cfg.Voice,
|
||||||
["format"] = new JsonObject
|
|
||||||
{
|
|
||||||
["type"] = "audio/pcm",
|
|
||||||
["rate"] = 24000,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
["tools"] = tools,
|
["tools"] = tools,
|
||||||
|
|||||||
@@ -128,10 +128,18 @@ public class RealtimeSession
|
|||||||
if (evt is null && ct.IsCancellationRequested) return;
|
if (evt is null && ct.IsCancellationRequested) return;
|
||||||
if (evt is null)
|
if (evt is null)
|
||||||
{
|
{
|
||||||
// Upstream has gone silent. If null persists for >2s, log so
|
// Upstream has gone silent. Surface that to the device every
|
||||||
// we can spot a dead upstream that's not surfacing as a close.
|
// ~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)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
nullPolls = 0;
|
nullPolls = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user