feat(server): refactor provider interface (#11665)

fix AI-4
fix AI-18

better provider/model choose to allow fallback to similar models (e.g., self-hosted) when the provider is not fully configured
split functions of different output types
This commit is contained in:
darkskygit
2025-05-22 06:28:20 +00:00
parent a3b8aaff61
commit b388f92c96
36 changed files with 1465 additions and 903 deletions
@@ -12,10 +12,12 @@ import {
} from '../../../base';
import { CopilotProvider } from './provider';
import {
CopilotCapability,
CopilotChatOptions,
CopilotProviderType,
CopilotTextToTextProvider,
ModelConditions,
ModelFullConditions,
ModelInputType,
ModelOutputType,
PromptMessage,
} from './types';
import { chatToGPTMessage, CitationParser } from './utils';
@@ -46,17 +48,51 @@ const PerplexityErrorSchema = z.union([
type PerplexityError = z.infer<typeof PerplexityErrorSchema>;
export class PerplexityProvider
extends CopilotProvider<PerplexityConfig>
implements CopilotTextToTextProvider
{
export class PerplexityProvider extends CopilotProvider<PerplexityConfig> {
readonly type = CopilotProviderType.Perplexity;
readonly capabilities = [CopilotCapability.TextToText];
readonly models = [
'sonar',
'sonar-pro',
'sonar-reasoning',
'sonar-reasoning-pro',
{
name: 'Sonar',
id: 'sonar',
capabilities: [
{
input: [ModelInputType.Text],
output: [ModelOutputType.Text],
defaultForOutputType: true,
},
],
},
{
name: 'Sonar Pro',
id: 'sonar-pro',
capabilities: [
{
input: [ModelInputType.Text],
output: [ModelOutputType.Text],
},
],
},
{
name: 'Sonar Reasoning',
id: 'sonar-reasoning',
capabilities: [
{
input: [ModelInputType.Text],
output: [ModelOutputType.Text],
},
],
},
{
name: 'Sonar Reasoning Pro',
id: 'sonar-reasoning-pro',
capabilities: [
{
input: [ModelInputType.Text],
output: [ModelOutputType.Text],
},
],
},
];
#instance!: VercelPerplexityProvider;
@@ -73,18 +109,21 @@ export class PerplexityProvider
});
}
async generateText(
async text(
cond: ModelConditions,
messages: PromptMessage[],
model: string = 'sonar',
options: CopilotChatOptions = {}
): Promise<string> {
await this.checkParams({ messages, model, options });
const fullCond = { ...cond, outputType: ModelOutputType.Text };
await this.checkParams({ cond: fullCond, messages });
const model = this.selectModel(fullCond);
try {
metrics.ai.counter('chat_text_calls').add(1, { model });
metrics.ai.counter('chat_text_calls').add(1, { model: model.id });
const [system, msgs] = await chatToGPTMessage(messages, false);
const modelInstance = this.#instance(model);
const modelInstance = this.#instance(model.id);
const { text, sources } = await generateText({
model: modelInstance,
@@ -105,23 +144,26 @@ export class PerplexityProvider
result += parser.end();
return result;
} catch (e: any) {
metrics.ai.counter('chat_text_errors').add(1, { model });
metrics.ai.counter('chat_text_errors').add(1, { model: model.id });
throw this.handleError(e);
}
}
async *generateTextStream(
async *streamText(
cond: ModelConditions,
messages: PromptMessage[],
model: string = 'sonar',
options: CopilotChatOptions = {}
): AsyncIterable<string> {
await this.checkParams({ messages, model, options });
const fullCond = { ...cond, outputType: ModelOutputType.Text };
await this.checkParams({ cond: fullCond, messages });
const model = this.selectModel(fullCond);
try {
metrics.ai.counter('chat_text_stream_calls').add(1, { model });
metrics.ai.counter('chat_text_stream_calls').add(1, { model: model.id });
const [system, msgs] = await chatToGPTMessage(messages, false);
const modelInstance = this.#instance(model);
const modelInstance = this.#instance(model.id);
const stream = streamText({
model: modelInstance,
@@ -168,21 +210,21 @@ export class PerplexityProvider
}
}
} catch (e) {
metrics.ai.counter('chat_text_stream_errors').add(1, { model });
metrics.ai.counter('chat_text_stream_errors').add(1, { model: model.id });
throw e;
}
}
protected async checkParams({
model,
cond,
}: {
cond: ModelFullConditions;
messages?: PromptMessage[];
embeddings?: string[];
model: string;
options: CopilotChatOptions;
options?: CopilotChatOptions;
}) {
if (!(await this.isModelAvailable(model))) {
throw new CopilotPromptInvalid(`Invalid model: ${model}`);
if (!(await this.match(cond))) {
throw new CopilotPromptInvalid(`Invalid model: ${cond.modelId}`);
}
}