mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-15 17:16:16 +08:00
feat(server): adapt gemini3.1 preview (#14583)
#### PR Dependency Tree * **PR #14583** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Gemini 3.1 Pro Preview support (text, image, audio) and new GPT‑5 variants as defaults; centralized persistent telemetry state for more reliable client identity. * **UX** * Improved model submenu placement in chat preferences. * More robust mindmap parsing, preview, regeneration and replace behavior. * **Chores** * Bumped AI SDK and related dependencies. * **Tests** * Expanded/updated tests and increased timeouts for flaky flows. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -230,9 +230,8 @@ defineModuleConfig('copilot', {
|
||||
chat: 'gemini-2.5-flash',
|
||||
embedding: 'gemini-embedding-001',
|
||||
image: 'gpt-image-1',
|
||||
rerank: 'gpt-4.1',
|
||||
coding: 'claude-sonnet-4-5@20250929',
|
||||
complex_text_generation: 'gpt-4o-2024-08-06',
|
||||
complex_text_generation: 'gpt-5-mini',
|
||||
quick_decision_making: 'gpt-5-mini',
|
||||
quick_text_generation: 'gemini-2.5-flash',
|
||||
polish_and_summarize: 'gemini-2.5-flash',
|
||||
|
||||
@@ -15,6 +15,10 @@ import {
|
||||
import { PromptService } from '../prompt/service';
|
||||
import { CopilotProviderFactory } from '../providers/factory';
|
||||
import type { CopilotProvider } from '../providers/provider';
|
||||
import {
|
||||
DEFAULT_RERANK_MODEL,
|
||||
normalizeRerankModel,
|
||||
} from '../providers/rerank';
|
||||
import {
|
||||
type ModelFullConditions,
|
||||
ModelInputType,
|
||||
@@ -114,10 +118,16 @@ class ProductionEmbeddingClient extends EmbeddingClient {
|
||||
if (!prompt) {
|
||||
throw new CopilotPromptNotFound({ name: RERANK_PROMPT });
|
||||
}
|
||||
const provider = await this.getProvider({ modelId: prompt.model });
|
||||
const rerankModel = normalizeRerankModel(prompt.model);
|
||||
if (prompt.model !== rerankModel) {
|
||||
this.logger.warn(
|
||||
`Unsupported rerank model "${prompt.model}" configured, falling back to "${DEFAULT_RERANK_MODEL}".`
|
||||
);
|
||||
}
|
||||
const provider = await this.getProvider({ modelId: rerankModel });
|
||||
|
||||
const ranks = await provider.rerank(
|
||||
{ modelId: prompt.model },
|
||||
{ modelId: rerankModel },
|
||||
embeddings.map(e => prompt.finish({ query, doc: e.content })),
|
||||
{ signal }
|
||||
);
|
||||
@@ -171,7 +181,7 @@ class ProductionEmbeddingClient extends EmbeddingClient {
|
||||
);
|
||||
|
||||
try {
|
||||
// 4.1 mini's context windows large enough to handle all embeddings
|
||||
// The rerank prompt is expected to handle the full deduped candidate list.
|
||||
const ranks = await this.getEmbeddingRelevance(
|
||||
query,
|
||||
sortedEmbeddings,
|
||||
|
||||
@@ -34,7 +34,6 @@ export const Scenario = {
|
||||
'Remove background',
|
||||
'Upscale image',
|
||||
],
|
||||
rerank: ['Rerank results'],
|
||||
coding: [
|
||||
'Apply Updates',
|
||||
'Code Artifact',
|
||||
@@ -124,7 +123,7 @@ const workflows: Prompt[] = [
|
||||
{
|
||||
name: 'workflow:presentation:step2',
|
||||
action: 'workflow:presentation:step2',
|
||||
model: 'gpt-4o-2024-08-06',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -143,7 +142,7 @@ const workflows: Prompt[] = [
|
||||
{
|
||||
name: 'workflow:presentation:step4',
|
||||
action: 'workflow:presentation:step4',
|
||||
model: 'gpt-4o-2024-08-06',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -187,7 +186,7 @@ const workflows: Prompt[] = [
|
||||
{
|
||||
name: 'workflow:brainstorm:step2',
|
||||
action: 'workflow:brainstorm:step2',
|
||||
model: 'gpt-4o-2024-08-06',
|
||||
model: 'gpt-5-mini',
|
||||
config: {
|
||||
frequencyPenalty: 0.5,
|
||||
presencePenalty: 0.5,
|
||||
@@ -197,7 +196,8 @@ const workflows: Prompt[] = [
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `You are the creator of the mind map. You need to analyze and expand on the input and output it according to the indentation formatting template given below without redundancy.\nBelow is an example of indentation for a mind map, the title and content needs to be removed by text replacement and not retained. Please strictly adhere to the hierarchical indentation of the template and my requirements, bold, headings and other formatting (e.g. #, **) are not allowed, a maximum of five levels of indentation is allowed, and the last node of each node should make a judgment on whether to make a detailed statement or not based on the topic:\nexmaple:\n- {topic}\n - {Level 1}\n - {Level 2}\n - {Level 3}\n - {Level 4}\n - {Level 1}\n - {Level 2}\n - {Level 3}\n - {Level 1}\n - {Level 2}\n - {Level 3}`,
|
||||
content:
|
||||
'Use the Markdown nested unordered list syntax without any extra styles or plain text descriptions to analyze and expand the input into a mind map. Regardless of the content, the first-level list should contain only one item, which acts as the root. Each node label must be plain text only. Do not output markdown links, footnotes, citations, URLs, headings, bold text, code fences, or any explanatory text outside the nested list. A maximum of five levels of indentation is allowed.',
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
@@ -381,7 +381,11 @@ const textActions: Prompt[] = [
|
||||
name: 'Transcript audio',
|
||||
action: 'Transcript audio',
|
||||
model: 'gemini-2.5-flash',
|
||||
optionalModels: ['gemini-2.5-flash', 'gemini-2.5-pro'],
|
||||
optionalModels: [
|
||||
'gemini-2.5-flash',
|
||||
'gemini-2.5-pro',
|
||||
'gemini-3.1-pro-preview',
|
||||
],
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -417,7 +421,7 @@ Convert a multi-speaker audio recording into a structured JSON format by transcr
|
||||
{
|
||||
name: 'Rerank results',
|
||||
action: 'Rerank results',
|
||||
model: 'gpt-4.1',
|
||||
model: 'gpt-5.2',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -432,7 +436,7 @@ Convert a multi-speaker audio recording into a structured JSON format by transcr
|
||||
{
|
||||
name: 'Generate a caption',
|
||||
action: 'Generate a caption',
|
||||
model: 'gpt-5-mini',
|
||||
model: 'gemini-2.5-flash',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
@@ -448,7 +452,7 @@ Convert a multi-speaker audio recording into a structured JSON format by transcr
|
||||
{
|
||||
name: 'Conversation Summary',
|
||||
action: 'Conversation Summary',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -473,7 +477,7 @@ Return only the summary text—no headings, labels, or commentary.`,
|
||||
{
|
||||
name: 'Summary',
|
||||
action: 'Summary',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -504,7 +508,7 @@ You are an assistant helping summarize a document. Use this format, replacing te
|
||||
{
|
||||
name: 'Summary as title',
|
||||
action: 'Summary as title',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -521,7 +525,7 @@ You are an assistant helping summarize a document. Use this format, replacing te
|
||||
{
|
||||
name: 'Summary the webpage',
|
||||
action: 'Summary the webpage',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
@@ -533,7 +537,7 @@ You are an assistant helping summarize a document. Use this format, replacing te
|
||||
{
|
||||
name: 'Explain this',
|
||||
action: 'Explain this',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -576,7 +580,7 @@ A concise paragraph that captures the article's main argument and key conclusion
|
||||
{
|
||||
name: 'Explain this image',
|
||||
action: 'Explain this image',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gemini-2.5-flash',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -727,7 +731,7 @@ You are a highly accomplished professional translator, demonstrating profound pr
|
||||
{
|
||||
name: 'Summarize the meeting',
|
||||
action: 'Summarize the meeting',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -752,7 +756,7 @@ You are an assistant helping summarize a document. Use this format, replacing te
|
||||
{
|
||||
name: 'Find action for summary',
|
||||
action: 'Find action for summary',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -774,7 +778,7 @@ You are an assistant helping find actions of meeting summary. Use this format, r
|
||||
{
|
||||
name: 'Write an article about this',
|
||||
action: 'Write an article about this',
|
||||
model: 'gemini-2.5-flash',
|
||||
model: 'gemini-2.5-pro',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -829,7 +833,7 @@ You are an assistant helping find actions of meeting summary. Use this format, r
|
||||
{
|
||||
name: 'Write a twitter about this',
|
||||
action: 'Write a twitter about this',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gemini-2.5-flash',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -915,7 +919,7 @@ You are an assistant helping find actions of meeting summary. Use this format, r
|
||||
{
|
||||
name: 'Write a blog post about this',
|
||||
action: 'Write a blog post about this',
|
||||
model: 'gemini-2.5-flash',
|
||||
model: 'gemini-2.5-pro',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1005,7 +1009,7 @@ You are an assistant helping find actions of meeting summary. Use this format, r
|
||||
{
|
||||
name: 'Change tone to',
|
||||
action: 'Change tone',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gemini-2.5-flash',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1096,12 +1100,12 @@ You are an assistant helping find actions of meeting summary. Use this format, r
|
||||
{
|
||||
name: 'Brainstorm mindmap',
|
||||
action: 'Brainstorm mindmap',
|
||||
model: 'gpt-4o-2024-08-06',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'Use the Markdown nested unordered list syntax without any extra styles or plain text descriptions to brainstorm the questions or topics provided by user for a mind map. Regardless of the content, the first-level list should contain only one item, which acts as the root. Do not wrap everything into a single code block.',
|
||||
'Use the Markdown nested unordered list syntax without any extra styles or plain text descriptions to brainstorm the questions or topics provided by user for a mind map. Regardless of the content, the first-level list should contain only one item, which acts as the root. Each node label must be plain text only. Do not output markdown links, footnotes, citations, URLs, headings, bold text, code fences, or any explanatory text outside the nested list.',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
@@ -1113,12 +1117,12 @@ You are an assistant helping find actions of meeting summary. Use this format, r
|
||||
{
|
||||
name: 'Expand mind map',
|
||||
action: 'Expand mind map',
|
||||
model: 'gpt-4o-2024-08-06',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'You are a professional writer. Use the Markdown nested unordered list syntax without any extra styles or plain text descriptions to brainstorm the questions or topics provided by user for a mind map.',
|
||||
'You are a professional writer. Use the Markdown nested unordered list syntax without any extra styles or plain text descriptions to expand the selected node in a mind map. The output must be exactly one subtree: the first bullet must repeat the selected node text as the subtree root, and it must include at least one new nested child bullet beneath it. Each node label must be plain text only. Do not output markdown links, footnotes, citations, URLs, headings, bold text, code fences, or any explanatory text outside the nested list.',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
@@ -1190,7 +1194,7 @@ The output must be perfect. Adherence to every detail of these instructions is n
|
||||
{
|
||||
name: 'Improve grammar for it',
|
||||
action: 'Improve grammar for it',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1259,7 +1263,7 @@ The output must be perfect. Adherence to every detail of these instructions is n
|
||||
{
|
||||
name: 'Find action items from it',
|
||||
action: 'Find action items from it',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1283,7 +1287,7 @@ If there are items in the content that can be used as to-do tasks, please refer
|
||||
{
|
||||
name: 'Check code error',
|
||||
action: 'Check code error',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1343,7 +1347,7 @@ If there are items in the content that can be used as to-do tasks, please refer
|
||||
{
|
||||
name: 'Create a presentation',
|
||||
action: 'Create a presentation',
|
||||
model: 'gpt-4o-2024-08-06',
|
||||
model: 'gpt-5-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1518,7 +1522,7 @@ When sent new notes, respond ONLY with the contents of the html file.`,
|
||||
{
|
||||
name: 'Continue writing',
|
||||
action: 'Continue writing',
|
||||
model: 'gemini-2.5-flash',
|
||||
model: 'gemini-2.5-pro',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1904,6 +1908,7 @@ const CHAT_PROMPT: Omit<Prompt, 'name'> = {
|
||||
optionalModels: [
|
||||
'gemini-2.5-flash',
|
||||
'gemini-2.5-pro',
|
||||
'gemini-3.1-pro-preview',
|
||||
'claude-sonnet-4-5@20250929',
|
||||
],
|
||||
messages: [
|
||||
@@ -2074,7 +2079,11 @@ Below is the user's query. Please respond in the user's preferred language witho
|
||||
'codeArtifact',
|
||||
'blobRead',
|
||||
],
|
||||
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
|
||||
proModels: [
|
||||
'gemini-2.5-pro',
|
||||
'gemini-3.1-pro-preview',
|
||||
'claude-sonnet-4-5@20250929',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
import type { GoogleVertexProvider } from '@ai-sdk/google-vertex';
|
||||
import {
|
||||
AISDKError,
|
||||
type EmbeddingModel,
|
||||
embedMany,
|
||||
generateObject,
|
||||
generateText,
|
||||
@@ -43,6 +44,34 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
| GoogleGenerativeAIProvider
|
||||
| GoogleVertexProvider;
|
||||
|
||||
private getThinkingConfig(
|
||||
model: string,
|
||||
options: { includeThoughts: boolean; useDynamicBudget?: boolean }
|
||||
): NonNullable<GoogleGenerativeAIProviderOptions['thinkingConfig']> {
|
||||
if (this.isGemini3Model(model)) {
|
||||
return {
|
||||
includeThoughts: options.includeThoughts,
|
||||
thinkingLevel: 'high',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
includeThoughts: options.includeThoughts,
|
||||
thinkingBudget: options.useDynamicBudget ? -1 : 12000,
|
||||
};
|
||||
}
|
||||
|
||||
private getEmbeddingModel(model: string) {
|
||||
const provider = this.instance as typeof this.instance & {
|
||||
embeddingModel?: (modelId: string) => EmbeddingModel;
|
||||
textEmbeddingModel?: (modelId: string) => EmbeddingModel;
|
||||
};
|
||||
|
||||
return (
|
||||
provider.embeddingModel?.(model) ?? provider.textEmbeddingModel?.(model)
|
||||
);
|
||||
}
|
||||
|
||||
private handleError(e: any) {
|
||||
if (e instanceof UserFriendlyError) {
|
||||
return e;
|
||||
@@ -122,10 +151,10 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
schema,
|
||||
providerOptions: {
|
||||
google: {
|
||||
thinkingConfig: {
|
||||
thinkingBudget: -1,
|
||||
thinkingConfig: this.getThinkingConfig(model.id, {
|
||||
includeThoughts: false,
|
||||
},
|
||||
useDynamicBudget: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
abortSignal: options.signal,
|
||||
@@ -234,7 +263,10 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
.counter('generate_embedding_calls')
|
||||
.add(1, { model: model.id });
|
||||
|
||||
const modelInstance = this.instance.textEmbeddingModel(model.id);
|
||||
const modelInstance = this.getEmbeddingModel(model.id);
|
||||
if (!modelInstance) {
|
||||
throw new Error(`Embedding model is not available for ${model.id}`);
|
||||
}
|
||||
|
||||
const embeddings = await Promise.allSettled(
|
||||
messages.map(m =>
|
||||
@@ -286,15 +318,18 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
private getGeminiOptions(options: CopilotChatOptions, model: string) {
|
||||
const result: GoogleGenerativeAIProviderOptions = {};
|
||||
if (options?.reasoning && this.isReasoningModel(model)) {
|
||||
result.thinkingConfig = {
|
||||
thinkingBudget: 12000,
|
||||
result.thinkingConfig = this.getThinkingConfig(model, {
|
||||
includeThoughts: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private isGemini3Model(model: string) {
|
||||
return model.startsWith('gemini-3');
|
||||
}
|
||||
|
||||
private isReasoningModel(model: string) {
|
||||
return model.startsWith('gemini-2.5');
|
||||
return model.startsWith('gemini-2.5') || this.isGemini3Model(model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,25 +20,6 @@ export class GeminiGenerativeProvider extends GeminiProvider<GeminiGenerativeCon
|
||||
override readonly type = CopilotProviderType.Gemini;
|
||||
|
||||
readonly models = [
|
||||
{
|
||||
name: 'Gemini 2.0 Flash',
|
||||
id: 'gemini-2.0-flash-001',
|
||||
capabilities: [
|
||||
{
|
||||
input: [
|
||||
ModelInputType.Text,
|
||||
ModelInputType.Image,
|
||||
ModelInputType.Audio,
|
||||
],
|
||||
output: [
|
||||
ModelOutputType.Text,
|
||||
ModelOutputType.Object,
|
||||
ModelOutputType.Structured,
|
||||
],
|
||||
defaultForOutputType: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Gemini 2.5 Flash',
|
||||
id: 'gemini-2.5-flash',
|
||||
@@ -75,6 +56,24 @@ export class GeminiGenerativeProvider extends GeminiProvider<GeminiGenerativeCon
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Gemini 3.1 Pro Preview',
|
||||
id: 'gemini-3.1-pro-preview',
|
||||
capabilities: [
|
||||
{
|
||||
input: [
|
||||
ModelInputType.Text,
|
||||
ModelInputType.Image,
|
||||
ModelInputType.Audio,
|
||||
],
|
||||
output: [
|
||||
ModelOutputType.Text,
|
||||
ModelOutputType.Object,
|
||||
ModelOutputType.Structured,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Gemini Embedding',
|
||||
id: 'gemini-embedding-001',
|
||||
|
||||
@@ -50,6 +50,24 @@ export class GeminiVertexProvider extends GeminiProvider<GeminiVertexConfig> {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Gemini 3.1 Pro Preview',
|
||||
id: 'gemini-3.1-pro-preview',
|
||||
capabilities: [
|
||||
{
|
||||
input: [
|
||||
ModelInputType.Text,
|
||||
ModelInputType.Image,
|
||||
ModelInputType.Audio,
|
||||
],
|
||||
output: [
|
||||
ModelOutputType.Text,
|
||||
ModelOutputType.Object,
|
||||
ModelOutputType.Structured,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Gemini Embedding',
|
||||
id: 'gemini-embedding-001',
|
||||
|
||||
@@ -18,6 +18,12 @@ import {
|
||||
import type { NodeTextMiddleware } from '../config';
|
||||
import { buildNativeRequest, NativeProviderAdapter } from './native';
|
||||
import { CopilotProvider } from './provider';
|
||||
import {
|
||||
normalizeRerankModel,
|
||||
OPENAI_RERANK_MAX_COMPLETION_TOKENS,
|
||||
OPENAI_RERANK_TOP_LOGPROBS_LIMIT,
|
||||
usesRerankReasoning,
|
||||
} from './rerank';
|
||||
import type {
|
||||
CopilotChatOptions,
|
||||
CopilotChatTools,
|
||||
@@ -33,6 +39,30 @@ import { chatToGPTMessage } from './utils';
|
||||
|
||||
export const DEFAULT_DIMENSIONS = 256;
|
||||
|
||||
const GPT_5_SAMPLING_UNSUPPORTED_MODELS = /^(gpt-5(?:$|[.-]))/;
|
||||
|
||||
export function normalizeOpenAIOptionsForModel<
|
||||
T extends {
|
||||
frequencyPenalty?: number | null;
|
||||
presencePenalty?: number | null;
|
||||
temperature?: number | null;
|
||||
topP?: number | null;
|
||||
},
|
||||
>(options: T, model: string): T {
|
||||
if (!GPT_5_SAMPLING_UNSUPPORTED_MODELS.test(model)) {
|
||||
return options;
|
||||
}
|
||||
|
||||
const normalizedOptions = { ...options };
|
||||
|
||||
delete normalizedOptions.frequencyPenalty;
|
||||
delete normalizedOptions.presencePenalty;
|
||||
delete normalizedOptions.temperature;
|
||||
delete normalizedOptions.topP;
|
||||
|
||||
return normalizedOptions;
|
||||
}
|
||||
|
||||
export type OpenAIConfig = {
|
||||
apiKey: string;
|
||||
baseURL?: string;
|
||||
@@ -252,6 +282,34 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'GPT 5.2',
|
||||
id: 'gpt-5.2',
|
||||
capabilities: [
|
||||
{
|
||||
input: [ModelInputType.Text, ModelInputType.Image],
|
||||
output: [
|
||||
ModelOutputType.Text,
|
||||
ModelOutputType.Object,
|
||||
ModelOutputType.Structured,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'GPT 5.2 2025-12-11',
|
||||
id: 'gpt-5.2-2025-12-11',
|
||||
capabilities: [
|
||||
{
|
||||
input: [ModelInputType.Text, ModelInputType.Image],
|
||||
output: [
|
||||
ModelOutputType.Text,
|
||||
ModelOutputType.Object,
|
||||
ModelOutputType.Structured,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'GPT 5 Nano',
|
||||
id: 'gpt-5-nano',
|
||||
@@ -435,10 +493,14 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
||||
metrics.ai.counter('chat_text_calls').add(1, this.metricLabels(model.id));
|
||||
const tools = await this.getTools(options, model.id);
|
||||
const middleware = this.getActiveProviderMiddleware();
|
||||
const normalizedOptions = normalizeOpenAIOptionsForModel(
|
||||
options,
|
||||
model.id
|
||||
);
|
||||
const { request } = await buildNativeRequest({
|
||||
model: model.id,
|
||||
messages,
|
||||
options,
|
||||
options: normalizedOptions,
|
||||
tools,
|
||||
include: options.webSearch ? ['citations'] : undefined,
|
||||
reasoning: this.getReasoning(options, model.id),
|
||||
@@ -472,10 +534,14 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
||||
.add(1, this.metricLabels(model.id));
|
||||
const tools = await this.getTools(options, model.id);
|
||||
const middleware = this.getActiveProviderMiddleware();
|
||||
const normalizedOptions = normalizeOpenAIOptionsForModel(
|
||||
options,
|
||||
model.id
|
||||
);
|
||||
const { request } = await buildNativeRequest({
|
||||
model: model.id,
|
||||
messages,
|
||||
options,
|
||||
options: normalizedOptions,
|
||||
tools,
|
||||
include: options.webSearch ? ['citations'] : undefined,
|
||||
reasoning: this.getReasoning(options, model.id),
|
||||
@@ -508,10 +574,14 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
||||
.add(1, this.metricLabels(model.id));
|
||||
const tools = await this.getTools(options, model.id);
|
||||
const middleware = this.getActiveProviderMiddleware();
|
||||
const normalizedOptions = normalizeOpenAIOptionsForModel(
|
||||
options,
|
||||
model.id
|
||||
);
|
||||
const { request } = await buildNativeRequest({
|
||||
model: model.id,
|
||||
messages,
|
||||
options,
|
||||
options: normalizedOptions,
|
||||
tools,
|
||||
include: options.webSearch ? ['citations'] : undefined,
|
||||
reasoning: this.getReasoning(options, model.id),
|
||||
@@ -542,10 +612,14 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
||||
metrics.ai.counter('chat_text_calls').add(1, { model: model.id });
|
||||
const tools = await this.getTools(options, model.id);
|
||||
const middleware = this.getActiveProviderMiddleware();
|
||||
const normalizedOptions = normalizeOpenAIOptionsForModel(
|
||||
options,
|
||||
model.id
|
||||
);
|
||||
const { request, schema } = await buildNativeRequest({
|
||||
model: model.id,
|
||||
messages,
|
||||
options,
|
||||
options: normalizedOptions,
|
||||
tools,
|
||||
reasoning: this.getReasoning(options, model.id),
|
||||
middleware,
|
||||
@@ -576,15 +650,21 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
||||
const scores = await Promise.all(
|
||||
chunkMessages.map(async messages => {
|
||||
const [system, msgs] = await chatToGPTMessage(messages);
|
||||
const rerankModel = normalizeRerankModel(model.id);
|
||||
const response = await this.requestOpenAIJson(
|
||||
'/chat/completions',
|
||||
{
|
||||
model: model.id,
|
||||
model: rerankModel,
|
||||
messages: this.toOpenAIChatMessages(system, msgs),
|
||||
temperature: 0,
|
||||
max_tokens: 16,
|
||||
logprobs: true,
|
||||
top_logprobs: 16,
|
||||
top_logprobs: OPENAI_RERANK_TOP_LOGPROBS_LIMIT,
|
||||
...(usesRerankReasoning(rerankModel)
|
||||
? {
|
||||
reasoning_effort: 'none' as const,
|
||||
max_completion_tokens: OPENAI_RERANK_MAX_COMPLETION_TOKENS,
|
||||
}
|
||||
: { max_tokens: OPENAI_RERANK_MAX_COMPLETION_TOKENS }),
|
||||
},
|
||||
options.signal
|
||||
);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const GPT_4_RERANK_MODELS = /^(gpt-4(?:$|[.-]))/;
|
||||
const GPT_5_RERANK_LOGPROBS_MODELS = /^(gpt-5\.2(?:$|-))/;
|
||||
|
||||
export const DEFAULT_RERANK_MODEL = 'gpt-5.2';
|
||||
export const OPENAI_RERANK_TOP_LOGPROBS_LIMIT = 5;
|
||||
export const OPENAI_RERANK_MAX_COMPLETION_TOKENS = 16;
|
||||
|
||||
export function supportsRerankModel(model: string): boolean {
|
||||
return (
|
||||
GPT_4_RERANK_MODELS.test(model) || GPT_5_RERANK_LOGPROBS_MODELS.test(model)
|
||||
);
|
||||
}
|
||||
|
||||
export function usesRerankReasoning(model: string): boolean {
|
||||
return GPT_5_RERANK_LOGPROBS_MODELS.test(model);
|
||||
}
|
||||
|
||||
export function normalizeRerankModel(model?: string | null): string {
|
||||
if (model && supportsRerankModel(model)) {
|
||||
return model;
|
||||
}
|
||||
return DEFAULT_RERANK_MODEL;
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import { GoogleVertexProviderSettings } from '@ai-sdk/google-vertex';
|
||||
import { GoogleVertexAnthropicProviderSettings } from '@ai-sdk/google-vertex/anthropic';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import {
|
||||
CoreAssistantMessage,
|
||||
CoreUserMessage,
|
||||
AssistantModelMessage,
|
||||
FilePart,
|
||||
ImagePart,
|
||||
TextPart,
|
||||
TextStreamPart,
|
||||
UserModelMessage,
|
||||
} from 'ai';
|
||||
import { GoogleAuth, GoogleAuthOptions } from 'google-auth-library';
|
||||
import z, { ZodType } from 'zod';
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
import { CustomAITools } from '../tools';
|
||||
import { PromptMessage, StreamObject } from './types';
|
||||
|
||||
type ChatMessage = CoreUserMessage | CoreAssistantMessage;
|
||||
type ChatMessage = UserModelMessage | AssistantModelMessage;
|
||||
|
||||
const ATTACHMENT_MAX_BYTES = 20 * 1024 * 1024;
|
||||
const ATTACH_HEAD_PARAMS = { timeoutMs: OneMinute / 12, maxRedirects: 3 };
|
||||
|
||||
Reference in New Issue
Block a user