11import { createDeepSeek } from '@ai-sdk/deepseek'
22import { createOpenAI } from '@ai-sdk/openai'
3+ import { APICallError } from 'ai'
34import * as ai from 'ai'
45import OpenAI from 'openai'
56
67import { BotContext } from './bot_context'
8+ import {
9+ EXCEED_TOKEN_QUOTA_LIMIT ,
10+ EXCEED_TOKEN_QUOTA_LIMIT_MESSAGE ,
11+ REQUEST_LLM_ERROR_CODE ,
12+ REQUEST_LLM_ERROR_MESSAGE
13+ } from './constant'
714import { McpClient } from './mcp'
815import { getAccessToken } from './tcb'
16+ import { extractWithLodash , safeJsonParse } from './utils'
917
1018const DEEPSEEK_PREFIX = 'deepseek'
1119
@@ -28,8 +36,9 @@ export interface IMsgResult {
2836 content : string ;
2937 finish_reason ?: string ;
3038 error ?: {
31- name : string ;
39+ name ? : string ;
3240 message : string ;
41+ code ?: string ;
3342 } ;
3443 tool_call ?: string ;
3544 usage : object ;
@@ -173,19 +182,31 @@ export class LLMCommunicator {
173182 messages : ChatCompletionMessage [ ] ,
174183 cb : ( streamPart : ai . TextStreamPart < ai . ToolSet > ) => void
175184 ) {
176- const { fullStream } = ai . streamText ( {
177- model : this . model ,
178- tools : await this . mcpClient ?. tools ( ) ,
179- maxSteps : 10 ,
180- messages : this . tarnsMessage ( [ ...messages ] ) ,
181- abortSignal : this . controller . signal ,
182- onFinish : ( ) => {
183- this . mcpClient ?. close ( )
185+ try {
186+ const { fullStream } = ai . streamText ( {
187+ model : this . model ,
188+ tools : await this . mcpClient ?. tools ( ) ,
189+ maxSteps : 10 ,
190+ messages : this . tarnsMessage ( [ ...messages ] ) ,
191+ abortSignal : this . controller . signal ,
192+ onFinish : ( ) => {
193+ this . mcpClient ?. close ( )
194+ }
195+ } )
196+
197+ for await ( const streamPart of fullStream ) {
198+ cb ( streamPart )
184199 }
185- } )
200+ } catch ( e ) {
201+ cb ?.( {
202+ type : 'error' ,
203+ content : '' ,
204+ step : 'error' ,
205+ error : e ,
206+ finishReason : 'error'
207+ } )
186208
187- for await ( const streamPart of fullStream ) {
188- cb ( streamPart )
209+ console . log ( 'streamText_error:' , JSON . stringify ( { error : e , msg : e . message } ) )
189210 }
190211 }
191212
@@ -296,15 +317,20 @@ export class LLMCommunicator {
296317 )
297318 } else if ( streamPart . type === 'error' ) {
298319 // 对话异常
320+ const gwLLMError = this . handlerAICallError (
321+ ( streamPart as any ) . error ?. lastError || ( streamPart as any ) . error
322+ )
299323 result = {
300324 ...result ,
301325 finish_reason : 'error' ,
302- error : {
303- name : 'LLMError' ,
304- message : streamPart . error as string
305- }
326+ error : gwLLMError
306327 }
307- error = streamPart . error
328+ error = ( streamPart as any ) . error
329+
330+ console . log ( 'stream_error:' , JSON . stringify ( {
331+ msg : ( streamPart as any ) . error ,
332+ errMsg : ( streamPart as any ) . error ?. message
333+ } ) )
308334
309335 callMsg . push ( result )
310336 this . botContext . bot . sseSender . send (
@@ -316,6 +342,9 @@ export class LLMCommunicator {
316342 }
317343 )
318344 } catch ( error ) {
345+ const gwLLMError = this . handlerAICallError (
346+ ( error as any ) ?. lastError || error
347+ )
319348 let result : IMsgResult = {
320349 type : 'error' ,
321350 created : Date . now ( ) ,
@@ -327,12 +356,10 @@ export class LLMCommunicator {
327356 result = {
328357 ...result ,
329358 finish_reason : 'error' ,
330- error : {
331- name : 'LLMError' ,
332- message : error as string
333- }
359+ error : gwLLMError
334360 }
335- // error = streamPart.error
361+
362+ console . log ( 'stream_error:' , JSON . stringify ( { error, msg : ( error as any ) ?. message } ) )
336363
337364 callMsg . push ( result )
338365 this . botContext . bot . sseSender . send ( `data: ${ JSON . stringify ( result ) } \n\n` )
@@ -369,8 +396,30 @@ export class LLMCommunicator {
369396 const generateTextRes = await ai . generateText ( data )
370397 return cb ( generateTextRes )
371398 } catch ( error ) {
372- console . log ( error )
373- return { }
399+ console . log ( 'generateText_error:' , JSON . stringify ( { error, msg : ( error as any ) ?. message } ) )
400+ const gwLLMError = this . handlerAICallError (
401+ ( error as any ) ?. lastError || error
402+ )
403+ return { error : gwLLMError }
404+ }
405+ }
406+
407+ /**
408+ * 解析 LLM 调用错误,转换为统一的内部错误码和友好消息
409+ */
410+ handlerAICallError ( callError : APICallError ) {
411+ const responseBody = safeJsonParse ( callError ?. responseBody )
412+ const result = extractWithLodash ( responseBody , [ 'message' , 'code' ] )
413+ const code = result ?. code ?. includes ( EXCEED_TOKEN_QUOTA_LIMIT )
414+ ? EXCEED_TOKEN_QUOTA_LIMIT
415+ : result ?. code ?. [ 0 ] || REQUEST_LLM_ERROR_CODE
416+ let message = result ?. message ?. [ 0 ] || REQUEST_LLM_ERROR_MESSAGE
417+ if ( code === EXCEED_TOKEN_QUOTA_LIMIT ) {
418+ message = EXCEED_TOKEN_QUOTA_LIMIT_MESSAGE
419+ }
420+ return {
421+ code,
422+ message
374423 }
375424 }
376425}
0 commit comments