From d805a89fa89303a518baae67f26a8db2d75558a1 Mon Sep 17 00:00:00 2001 From: CookSleep Date: Wed, 1 Apr 2026 15:52:11 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix(core):=20=E5=B0=86=E7=A9=BA=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E8=A7=86=E4=B8=BA=E5=A4=B1=E8=B4=A5=E5=B9=B6=E4=BA=A4?= =?UTF-8?q?=E7=BB=99=E6=A8=A1=E5=9E=8B=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/llm-core/platform/model.ts | 48 ++++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/core/src/llm-core/platform/model.ts b/packages/core/src/llm-core/platform/model.ts index 152ce65bd..c87550061 100644 --- a/packages/core/src/llm-core/platform/model.ts +++ b/packages/core/src/llm-core/platform/model.ts @@ -239,23 +239,28 @@ export class ChatLunaChatModel extends BaseChatModel { const latestTokenUsage = this._createTokenUsageTracker() let stream: AsyncGenerator | null = null let hasChunk = false + let hasResponse = false let hasToolCallChunk = false try { stream = await this._createStream(streamParams) for await (const chunk of stream) { - hasToolCallChunk = - this._handleStreamChunk( - chunk, - runManager, - latestTokenUsage - ) || hasToolCallChunk + const hasTool = this._handleStreamChunk( + chunk, + runManager, + latestTokenUsage + ) + hasToolCallChunk = hasTool || hasToolCallChunk hasChunk = true + hasResponse = + hasResponse || + hasTool || + getMessageContent(chunk.message.content).trim().length > 0 yield chunk } - this._ensureChunksReceived(hasChunk) + this._ensureChunksReceived(hasChunk, hasResponse) this._finalizeStream( hasToolCallChunk, latestTokenUsage, @@ -268,12 +273,12 @@ export class ChatLunaChatModel extends BaseChatModel { if ( this._shouldRethrowStreamError( error, - hasChunk, + hasResponse, attempt, maxRetries ) ) { - if (hasChunk) { + if (hasResponse) { logger.debug( 'Stream failed after yielding chunks, cannot retry' ) @@ -322,10 +327,11 @@ export class ChatLunaChatModel extends BaseChatModel { return hasToolCallChunk } - private _hasToolCallChunk(message?: AIMessageChunk): boolean { + private _hasToolCallChunk(message?: AIMessage | AIMessageChunk): boolean { return ( (message?.tool_calls?.length ?? 0) > 0 || - (message?.tool_call_chunks?.length ?? 0) > 0 || + ((message as AIMessageChunk | undefined)?.tool_call_chunks?.length ?? + 0) > 0 || (message?.invalid_tool_calls?.length ?? 0) > 0 ) } @@ -347,8 +353,8 @@ export class ChatLunaChatModel extends BaseChatModel { latestTokenUsage.output_token_details = usage.output_token_details } - private _ensureChunksReceived(hasChunk: boolean) { - if (hasChunk) { + private _ensureChunksReceived(hasChunk: boolean, hasResponse: boolean) { + if (hasChunk && hasResponse) { return } @@ -391,12 +397,12 @@ export class ChatLunaChatModel extends BaseChatModel { private _shouldRethrowStreamError( error: unknown, - hasChunk: boolean, + hasResponse: boolean, attempt: number, maxRetries: number ): boolean { return ( - this._isAbortError(error) || hasChunk || attempt === maxRetries - 1 + this._isAbortError(error) || hasResponse || attempt === maxRetries - 1 ) } @@ -495,6 +501,18 @@ export class ChatLunaChatModel extends BaseChatModel { }) } + if ( + getMessageContent(response.message.content).trim() + .length < 1 && + this._hasToolCallChunk( + response.message as AIMessage | AIMessageChunk + ) !== true + ) { + throw new ChatLunaError( + ChatLunaErrorCode.API_REQUEST_FAILED + ) + } + return response } catch (error) { if ( From 694e00f7d5dbab76fa34b60f748c3ce5e1429c72 Mon Sep 17 00:00:00 2001 From: CookSleep Date: Wed, 1 Apr 2026 16:17:26 +0800 Subject: [PATCH 2/4] =?UTF-8?q?style(core):=20=E8=B0=83=E6=95=B4=E7=A9=BA?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E6=A0=A1=E9=AA=8C=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/llm-core/platform/model.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/llm-core/platform/model.ts b/packages/core/src/llm-core/platform/model.ts index c87550061..9c8689237 100644 --- a/packages/core/src/llm-core/platform/model.ts +++ b/packages/core/src/llm-core/platform/model.ts @@ -256,7 +256,8 @@ export class ChatLunaChatModel extends BaseChatModel { hasResponse = hasResponse || hasTool || - getMessageContent(chunk.message.content).trim().length > 0 + getMessageContent(chunk.message.content).trim() + .length > 0 yield chunk } @@ -330,8 +331,8 @@ export class ChatLunaChatModel extends BaseChatModel { private _hasToolCallChunk(message?: AIMessage | AIMessageChunk): boolean { return ( (message?.tool_calls?.length ?? 0) > 0 || - ((message as AIMessageChunk | undefined)?.tool_call_chunks?.length ?? - 0) > 0 || + ((message as AIMessageChunk | undefined)?.tool_call_chunks + ?.length ?? 0) > 0 || (message?.invalid_tool_calls?.length ?? 0) > 0 ) } From 561717b0ba11b6260ba2ecc37c2cc7e6bcd20f28 Mon Sep 17 00:00:00 2001 From: dingyi Date: Sun, 5 Apr 2026 11:44:41 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(core):=20=E7=BB=9F=E4=B8=80=E7=A9=BA?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=88=A4=E5=AE=9A=E5=B9=B6=E4=BF=9D=E7=95=99?= =?UTF-8?q?=E6=B5=81=E5=BC=8F=E9=87=8D=E8=AF=95=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/llm-core/platform/model.ts | 49 ++++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/core/src/llm-core/platform/model.ts b/packages/core/src/llm-core/platform/model.ts index 9c8689237..059598266 100644 --- a/packages/core/src/llm-core/platform/model.ts +++ b/packages/core/src/llm-core/platform/model.ts @@ -255,13 +255,18 @@ export class ChatLunaChatModel extends BaseChatModel { hasChunk = true hasResponse = hasResponse || - hasTool || - getMessageContent(chunk.message.content).trim() - .length > 0 + this._hasResponse( + chunk.message as AIMessage | AIMessageChunk + ) yield chunk } - this._ensureChunksReceived(hasChunk, hasResponse) + if (!hasResponse) { + throw new ChatLunaError( + ChatLunaErrorCode.API_REQUEST_FAILED + ) + } + this._finalizeStream( hasToolCallChunk, latestTokenUsage, @@ -274,12 +279,12 @@ export class ChatLunaChatModel extends BaseChatModel { if ( this._shouldRethrowStreamError( error, - hasResponse, + hasChunk, attempt, maxRetries ) ) { - if (hasResponse) { + if (hasChunk) { logger.debug( 'Stream failed after yielding chunks, cannot retry' ) @@ -337,6 +342,20 @@ export class ChatLunaChatModel extends BaseChatModel { ) } + private _hasResponse(message?: AIMessage | AIMessageChunk): boolean { + const content = message?.content + + return ( + (typeof content === 'string' + ? content.trim().length > 0 + : Array.isArray(content) && content.length > 0) || + this._hasToolCallChunk(message) || + ((message?.additional_kwargs?.tool_calls as unknown[] | undefined) + ?.length ?? 0) > 0 || + message?.additional_kwargs?.function_call != null + ) + } + private _updateTokenUsageFromChunk( chunk: ChatGenerationChunk, latestTokenUsage: TokenUsageTracker @@ -354,14 +373,6 @@ export class ChatLunaChatModel extends BaseChatModel { latestTokenUsage.output_token_details = usage.output_token_details } - private _ensureChunksReceived(hasChunk: boolean, hasResponse: boolean) { - if (hasChunk && hasResponse) { - return - } - - throw new ChatLunaError(ChatLunaErrorCode.API_REQUEST_FAILED) - } - private _finalizeStream( hasToolCallChunk: boolean, latestTokenUsage: TokenUsageTracker, @@ -398,12 +409,12 @@ export class ChatLunaChatModel extends BaseChatModel { private _shouldRethrowStreamError( error: unknown, - hasResponse: boolean, + hasChunk: boolean, attempt: number, maxRetries: number ): boolean { return ( - this._isAbortError(error) || hasResponse || attempt === maxRetries - 1 + this._isAbortError(error) || hasChunk || attempt === maxRetries - 1 ) } @@ -503,11 +514,9 @@ export class ChatLunaChatModel extends BaseChatModel { } if ( - getMessageContent(response.message.content).trim() - .length < 1 && - this._hasToolCallChunk( + !this._hasResponse( response.message as AIMessage | AIMessageChunk - ) !== true + ) ) { throw new ChatLunaError( ChatLunaErrorCode.API_REQUEST_FAILED From e1855c30335b5c9b0eaf834ad1d52cd9510614c1 Mon Sep 17 00:00:00 2001 From: dingyi Date: Sun, 5 Apr 2026 12:23:27 +0800 Subject: [PATCH 4/4] fix(core): use exponential retry backoff --- packages/core/src/llm-core/platform/model.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/llm-core/platform/model.ts b/packages/core/src/llm-core/platform/model.ts index 059598266..03df0d9cf 100644 --- a/packages/core/src/llm-core/platform/model.ts +++ b/packages/core/src/llm-core/platform/model.ts @@ -295,7 +295,7 @@ export class ChatLunaChatModel extends BaseChatModel { logger.debug( `Stream failed before first chunk (attempt ${attempt + 1}/${maxRetries}), retrying...` ) - await sleep(2000) + await sleep(2000 * 2 ** attempt) } } } @@ -533,7 +533,7 @@ export class ChatLunaChatModel extends BaseChatModel { throw error } - await sleep(2000) + await sleep(2000 * 2 ** attempt) } }