fix(server): better copilot error handle (#10509)
This commit is contained in:
@@ -37,6 +37,8 @@ import {
|
|||||||
Config,
|
Config,
|
||||||
CopilotFailedToGenerateText,
|
CopilotFailedToGenerateText,
|
||||||
CopilotSessionNotFound,
|
CopilotSessionNotFound,
|
||||||
|
InternalServerError,
|
||||||
|
mapAnyError,
|
||||||
mapSseError,
|
mapSseError,
|
||||||
metrics,
|
metrics,
|
||||||
NoCopilotProviderAvailable,
|
NoCopilotProviderAvailable,
|
||||||
@@ -148,8 +150,8 @@ export class CopilotController implements BeforeApplicationShutdown {
|
|||||||
if (!messageId || retry) {
|
if (!messageId || retry) {
|
||||||
// revert the latest message generated by the assistant
|
// revert the latest message generated by the assistant
|
||||||
// if messageId is provided, we will also revert latest user message
|
// if messageId is provided, we will also revert latest user message
|
||||||
await this.chatSession.revertLatestMessage(sessionId, !messageId);
|
await this.chatSession.revertLatestMessage(sessionId, !!messageId);
|
||||||
session.revertLatestMessage(!messageId);
|
session.revertLatestMessage(!!messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
@@ -209,31 +211,34 @@ export class CopilotController implements BeforeApplicationShutdown {
|
|||||||
@Param('sessionId') sessionId: string,
|
@Param('sessionId') sessionId: string,
|
||||||
@Query() params: Record<string, string | string[]>
|
@Query() params: Record<string, string | string[]>
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { messageId, retry } = this.prepareParams(params);
|
const info: any = { sessionId, params };
|
||||||
|
|
||||||
const provider = await this.chooseTextProvider(
|
|
||||||
user.id,
|
|
||||||
sessionId,
|
|
||||||
messageId
|
|
||||||
);
|
|
||||||
|
|
||||||
const session = await this.appendSessionMessage(
|
|
||||||
sessionId,
|
|
||||||
messageId,
|
|
||||||
retry
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
metrics.ai.counter('chat_calls').add(1, { model: session.model });
|
const { messageId, retry } = this.prepareParams(params);
|
||||||
const content = await provider.generateText(
|
|
||||||
session.finish(params),
|
const provider = await this.chooseTextProvider(
|
||||||
session.model,
|
user.id,
|
||||||
{
|
sessionId,
|
||||||
...session.config.promptConfig,
|
messageId
|
||||||
signal: this.getSignal(req),
|
|
||||||
user: user.id,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const session = await this.appendSessionMessage(
|
||||||
|
sessionId,
|
||||||
|
messageId,
|
||||||
|
retry
|
||||||
|
);
|
||||||
|
|
||||||
|
info.model = session.model;
|
||||||
|
metrics.ai.counter('chat_calls').add(1, { model: session.model });
|
||||||
|
const finalMessage = session.finish(params);
|
||||||
|
info.finalMessage = finalMessage;
|
||||||
|
|
||||||
|
const content = await provider.generateText(finalMessage, session.model, {
|
||||||
|
...session.config.promptConfig,
|
||||||
|
signal: this.getSignal(req),
|
||||||
|
user: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
session.push({
|
session.push({
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content,
|
content,
|
||||||
@@ -243,8 +248,13 @@ export class CopilotController implements BeforeApplicationShutdown {
|
|||||||
|
|
||||||
return content;
|
return content;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
metrics.ai.counter('chat_errors').add(1, { model: session.model });
|
metrics.ai.counter('chat_errors').add(1);
|
||||||
throw new CopilotFailedToGenerateText(e.message);
|
let error = mapAnyError(e);
|
||||||
|
if (error instanceof InternalServerError) {
|
||||||
|
error = new CopilotFailedToGenerateText(e.message);
|
||||||
|
}
|
||||||
|
error.log('CopilotChat', info);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,8 +286,10 @@ export class CopilotController implements BeforeApplicationShutdown {
|
|||||||
|
|
||||||
metrics.ai.counter('chat_stream_calls').add(1, { model: session.model });
|
metrics.ai.counter('chat_stream_calls').add(1, { model: session.model });
|
||||||
this.ongoingStreamCount$.next(this.ongoingStreamCount$.value + 1);
|
this.ongoingStreamCount$.next(this.ongoingStreamCount$.value + 1);
|
||||||
|
const finalMessage = session.finish(params);
|
||||||
|
info.finalMessage = finalMessage;
|
||||||
const source$ = from(
|
const source$ = from(
|
||||||
provider.generateTextStream(session.finish(params), session.model, {
|
provider.generateTextStream(finalMessage, session.model, {
|
||||||
...session.config.promptConfig,
|
...session.config.promptConfig,
|
||||||
signal: this.getSignal(req),
|
signal: this.getSignal(req),
|
||||||
user: user.id,
|
user: user.id,
|
||||||
|
|||||||
@@ -21,15 +21,24 @@ export type PerplexityConfig = {
|
|||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PerplexityErrorSchema = z.object({
|
const PerplexityErrorSchema = z.union([
|
||||||
detail: z.array(
|
z.object({
|
||||||
z.object({
|
detail: z.array(
|
||||||
loc: z.array(z.string()),
|
z.object({
|
||||||
msg: z.string(),
|
loc: z.array(z.string()),
|
||||||
|
msg: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
error: z.object({
|
||||||
|
message: z.string(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
})
|
code: z.number(),
|
||||||
),
|
}),
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
const PerplexityDataSchema = z.object({
|
const PerplexityDataSchema = z.object({
|
||||||
citations: z.array(z.string()),
|
citations: z.array(z.string()),
|
||||||
@@ -50,6 +59,8 @@ const PerplexityDataSchema = z.object({
|
|||||||
|
|
||||||
const PerplexitySchema = z.union([PerplexityDataSchema, PerplexityErrorSchema]);
|
const PerplexitySchema = z.union([PerplexityDataSchema, PerplexityErrorSchema]);
|
||||||
|
|
||||||
|
type PerplexityError = z.infer<typeof PerplexityErrorSchema>;
|
||||||
|
|
||||||
export class CitationParser {
|
export class CitationParser {
|
||||||
private readonly SQUARE_BRACKET_OPEN = '[';
|
private readonly SQUARE_BRACKET_OPEN = '[';
|
||||||
|
|
||||||
@@ -214,12 +225,8 @@ export class PerplexityProvider implements CopilotTextToTextProvider {
|
|||||||
params
|
params
|
||||||
);
|
);
|
||||||
const data = PerplexitySchema.parse(await response.json());
|
const data = PerplexitySchema.parse(await response.json());
|
||||||
if ('detail' in data) {
|
if ('detail' in data || 'error' in data) {
|
||||||
throw new CopilotProviderSideError({
|
throw this.convertError(data);
|
||||||
provider: this.type,
|
|
||||||
kind: 'unexpected_response',
|
|
||||||
message: data.detail[0].msg || 'Unexpected perplexity response',
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const citationParser = new CitationParser();
|
const citationParser = new CitationParser();
|
||||||
const { content } = data.choices[0].message;
|
const { content } = data.choices[0].message;
|
||||||
@@ -264,9 +271,9 @@ export class PerplexityProvider implements CopilotTextToTextProvider {
|
|||||||
this.config.endpoint || 'https://api.perplexity.ai/chat/completions',
|
this.config.endpoint || 'https://api.perplexity.ai/chat/completions',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
if (response.body) {
|
const errorHandler = this.convertError;
|
||||||
|
if (response.ok && response.body) {
|
||||||
const citationParser = new CitationParser();
|
const citationParser = new CitationParser();
|
||||||
const provider = this.type;
|
|
||||||
const eventStream = response.body
|
const eventStream = response.body
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
.pipeThrough(new EventSourceParserStream())
|
.pipeThrough(new EventSourceParserStream())
|
||||||
@@ -280,13 +287,8 @@ export class PerplexityProvider implements CopilotTextToTextProvider {
|
|||||||
const json = JSON.parse(chunk.data);
|
const json = JSON.parse(chunk.data);
|
||||||
if (json) {
|
if (json) {
|
||||||
const data = PerplexitySchema.parse(json);
|
const data = PerplexitySchema.parse(json);
|
||||||
if ('detail' in data) {
|
if ('detail' in data || 'error' in data) {
|
||||||
throw new CopilotProviderSideError({
|
throw errorHandler(data);
|
||||||
provider,
|
|
||||||
kind: 'unexpected_response',
|
|
||||||
message:
|
|
||||||
data.detail[0].msg || 'Unexpected perplexity response',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const { content } = data.choices[0].delta;
|
const { content } = data.choices[0].delta;
|
||||||
const { citations } = data;
|
const { citations } = data;
|
||||||
@@ -331,6 +333,24 @@ export class PerplexityProvider implements CopilotTextToTextProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private convertError(e: PerplexityError) {
|
||||||
|
function getErrMessage(e: PerplexityError) {
|
||||||
|
let err = 'Unexpected perplexity response';
|
||||||
|
if ('detail' in e) {
|
||||||
|
err = e.detail[0].msg || err;
|
||||||
|
} else if ('error' in e) {
|
||||||
|
err = e.error.message || err;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CopilotProviderSideError({
|
||||||
|
provider: this.type,
|
||||||
|
kind: 'unexpected_response',
|
||||||
|
message: getErrMessage(e),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private handleError(e: any) {
|
private handleError(e: any) {
|
||||||
if (e instanceof CopilotProviderSideError) {
|
if (e instanceof CopilotProviderSideError) {
|
||||||
return e;
|
return e;
|
||||||
|
|||||||
Reference in New Issue
Block a user