fix: handle null deltas from Kimi 2.5 API (#4143)
- Add explicit null/undefined detection for streaming deltas - Log warnings when null values are encountered - Throw clear error after consecutive null deltas - Provide actionable feedback to users - Track consecutiveNullDeltas in subscription state Fixes #4143
This commit is contained in:
parent
6af205a13a
commit
8792303ad8
@ -54,8 +54,57 @@ export function handleMessageUpdate(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const delta = typeof assistantRecord?.delta === "string" ? assistantRecord.delta : "";
|
// Explicit null/undefined detection (Issue #4143)
|
||||||
const content = typeof assistantRecord?.content === "string" ? assistantRecord.content : "";
|
// Some providers (e.g., Kimi 2.5) may return null deltas, causing silent hangs
|
||||||
|
const deltaRaw = assistantRecord?.delta;
|
||||||
|
const contentRaw = assistantRecord?.content;
|
||||||
|
const MAX_CONSECUTIVE_NULLS = 5;
|
||||||
|
|
||||||
|
// Check for null/undefined delta in streaming events
|
||||||
|
if (evtType === "text_delta" && (deltaRaw === null || deltaRaw === undefined)) {
|
||||||
|
ctx.state.consecutiveNullDeltas++;
|
||||||
|
ctx.log.warn(
|
||||||
|
`Received null delta in text_delta event (count: ${ctx.state.consecutiveNullDeltas}/${MAX_CONSECUTIVE_NULLS}). ` +
|
||||||
|
`This may indicate an API issue with the model provider.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ctx.state.consecutiveNullDeltas >= MAX_CONSECUTIVE_NULLS) {
|
||||||
|
const error = new Error(
|
||||||
|
`Model provider returned ${ctx.state.consecutiveNullDeltas} consecutive null deltas. ` +
|
||||||
|
`This typically indicates an API issue or incompatibility. ` +
|
||||||
|
`Try a different model or contact the provider.`,
|
||||||
|
);
|
||||||
|
error.name = "NullDeltaError";
|
||||||
|
|
||||||
|
// Emit error event for better user feedback
|
||||||
|
emitAgentEvent({
|
||||||
|
runId: ctx.params.runId,
|
||||||
|
stream: "error",
|
||||||
|
data: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return; // Skip processing this null delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset counter on valid delta
|
||||||
|
if (evtType === "text_delta" && typeof deltaRaw === "string" && deltaRaw.length > 0) {
|
||||||
|
ctx.state.consecutiveNullDeltas = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for null content in text_end
|
||||||
|
if (evtType === "text_end" && contentRaw === null && !deltaRaw) {
|
||||||
|
ctx.log.warn(
|
||||||
|
`Received null content in text_end event with no delta. ` +
|
||||||
|
`The model may have failed to generate a response.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = typeof deltaRaw === "string" ? deltaRaw : "";
|
||||||
|
const content = typeof contentRaw === "string" ? contentRaw : "";
|
||||||
|
|
||||||
appendRawStream({
|
appendRawStream({
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
|
|||||||
@ -58,6 +58,9 @@ export type EmbeddedPiSubscribeState = {
|
|||||||
messagingToolSentTargets: MessagingToolSend[];
|
messagingToolSentTargets: MessagingToolSend[];
|
||||||
pendingMessagingTexts: Map<string, string>;
|
pendingMessagingTexts: Map<string, string>;
|
||||||
pendingMessagingTargets: Map<string, MessagingToolSend>;
|
pendingMessagingTargets: Map<string, MessagingToolSend>;
|
||||||
|
|
||||||
|
// Track consecutive null deltas for hang detection (Issue #4143)
|
||||||
|
consecutiveNullDeltas: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmbeddedPiSubscribeContext = {
|
export type EmbeddedPiSubscribeContext = {
|
||||||
|
|||||||
@ -65,6 +65,7 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
messagingToolSentTargets: [],
|
messagingToolSentTargets: [],
|
||||||
pendingMessagingTexts: new Map(),
|
pendingMessagingTexts: new Map(),
|
||||||
pendingMessagingTargets: new Map(),
|
pendingMessagingTargets: new Map(),
|
||||||
|
consecutiveNullDeltas: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const assistantTexts = state.assistantTexts;
|
const assistantTexts = state.assistantTexts;
|
||||||
@ -96,6 +97,7 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
state.lastAssistantTextNormalized = undefined;
|
state.lastAssistantTextNormalized = undefined;
|
||||||
state.lastAssistantTextTrimmed = undefined;
|
state.lastAssistantTextTrimmed = undefined;
|
||||||
state.assistantTextBaseline = nextAssistantTextBaseline;
|
state.assistantTextBaseline = nextAssistantTextBaseline;
|
||||||
|
state.consecutiveNullDeltas = 0; // Reset null delta counter (Issue #4143)
|
||||||
};
|
};
|
||||||
|
|
||||||
const rememberAssistantText = (text: string) => {
|
const rememberAssistantText = (text: string) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user