steep-balloon-41261
02/17/2026, 6:25 PMtall-airline-16756
02/17/2026, 6:26 PMtall-airline-16756
02/17/2026, 6:26 PMlk.response.text can be empty in Cloud even when transcription and LLM invocation occur, and give a focused, minimal set of server-side logs you can add to pinpoint the failure.
Quick acknowledgement
• You said: audio and transcripts arrive, llm_node is invoked in production, but lk.response.text is always an empty string and there are no explicit LLM errors. I’ll focus on where an empty response can originate and show minimal logs to reveal which part of the pipeline is producing the empty output.
Why lk.response.text can be empty (concise)
• The LLM may be returning empty/structured output that your node never extracts into plain text. Some LLMs return deltas or JSON that need to be parsed into a final text field.
• The llm_node stream may be producing empty/undefined chunks (e.g., delta: { content: "" }), so downstream TTS/response sees no text.
• A stream-processing bug inside your llm_node override could swallow or replace valid content with an empty string (for example error handling or an incorrect mapping).
• The LLM call could be succeeding but returning only metadata (or a role-only chunk) that your pipeline doesn't translate into lk.response.text.
Minimal, focused logging to add (very small changes you can deploy immediately)
• Add logs only in three places: right after receiving the LLM stream, for each chunk produced by the stream, and when you build the final lk.response. These will show whether the LLM produced text and whether your processing turned it into an empty response.
1) Log stream creation and final response
• At the start of your llm_node override, log that the node ran and include correlation IDs (session, request) if available:
console.log('llm_node: invoked', { sessionId: chatCtx.sessionId, turnId: chatCtx.turnId });
• When you assemble the final lk.response (before sending/returning), log its shape and text length:
console.log('llm_node: final lk.response', {
text: lkResponse?.text,
text_length: lkResponse?.text?.length ?? 0,
metadata_keys: Object.keys(lkResponse ?? {})
});
2) Log every chunk from the LLM stream (small, safe JSON)
• Wrap the returned ReadableStream so you can inspect each chunk without changing pipeline behavior:
const stream = await Agent.default.llmNode(this, chatCtx, toolCtx, modelSettings);
if (!stream) return null;
return new ReadableStream({
async start(controller) {
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('llm_node: stream complete');
controller.close();
break;
}
// Minimal, safe inspect: stringify only small fields to avoid huge logs
try {
const preview = (typeof value === 'string')
? value.slice(0, 256)
: JSON.stringify({
role: value?.role,
contentPreview: (value?.content ?? '')?.slice?.(0, 256),
keys: Object.keys(value ?? {}).slice(0, 10)
});
console.log('llm_node: chunk', preview);
} catch (err) {
console.log('llm_node: chunk stringify error', String(err));
}
controller.enqueue(value);
}
}
});
• This shows whether the LLM is emitting non-empty text, whether content is nested (e.g., JSON) and whether chunks are empty deltas.
3) If you use structured/JSON LLM output, log parsed extraction
• If you expect the model to return something like { reply: "..." }, add one small log after parsing:
console.log('llm_node: parsed reply', { reply_length: reply?.length ?? 0, reply_sample: reply?.slice?.(0,200) });
What the logs will tell you (how to interpret results)
• You see non-empty chunk previews: the LLM is returning text — the bug is in your parsing/assembly into lk.response.
• You see empty chunk previews or only role/metadata: the LLM returned nothing usable — check model prompts/parameters or Azure OpenAI settings (temperature, response format).
• You see JSON payloads (content looks like JSON): you must parse the JSON and extract the verbal field before returning lk.response.text.
• You see errors in stringify or stream completion without content: stream handling likely swallowed content; check for try/catch that returns empty strings.
Next quick checks (one-liners to run locally or in cloud logs)
• Confirm you can reproduce the same stream output locally with identical model settings; compare the chunk previews.
• Add a short synthetic fallback in the llm_node just to test: if final text is empty, set a visible placeholder like "[NO_RESPONSE]" and log that — this confirms whether downstream services receive an empty string or the placeholder.
What I can help with next
• If you paste 2–3 sample chunk logs (the exact console output from the chunk preview), I can say whether the model emitted usable text or if it emitted structured/empty content and recommend the precise parsing fix.
• If you want, I can provide a minimal complete llm_node wrapper you can drop into your agent to both log and normalize structured outputs into lk.response.text.
If you want that wrapper or to paste the chunk logs, I’ll analyze them and give the exact small code change to fix lk.response.text.
Sources: Pipeline nodes and hooks | LiveKit Documentation | Tool definition and use | LiveKit Documentation | LLM-Powered Content Filter | LiveKit Documentation | Streamline troubleshooting with Agent Observability