Add openai o-series support (#1238)

This commit is contained in:
Logan Yang 2025-02-11 15:35:57 -08:00 committed by GitHub
parent 7d4f0becd4
commit a596ffc313
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 188 additions and 96 deletions

85
package-lock.json generated
View file

@ -18,7 +18,7 @@
"@langchain/core": "^0.3.27",
"@langchain/google-genai": "^0.1.6",
"@langchain/groq": "^0.1.2",
"@langchain/openai": "^0.3.14",
"@langchain/openai": "^0.4.2",
"@orama/orama": "^3.0.0-rc-2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.2",
@ -4257,6 +4257,24 @@
}
}
},
"node_modules/@langchain/community/node_modules/@langchain/openai": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.17.tgz",
"integrity": "sha512-uw4po32OKptVjq+CYHrumgbfh4NuD7LqyE+ZgqY9I/LrLc6bHLMc+sisHmI17vgek0K/yqtarI0alPJbzrwyag==",
"license": "MIT",
"dependencies": {
"js-tiktoken": "^1.0.12",
"openai": "^4.77.0",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.3"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@langchain/core": ">=0.3.29 <0.4.0"
}
},
"node_modules/@langchain/community/node_modules/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
@ -4270,16 +4288,17 @@
}
},
"node_modules/@langchain/core": {
"version": "0.3.27",
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.27.tgz",
"integrity": "sha512-jtJKbJWB1NPU1YvtrExOB2rumvUFgkJwlWGxyjSIV9A6zcLVmUbcZGV8fCSuXgl5bbzOIQLJ1xcLYQmbW9TkTg==",
"version": "0.3.39",
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.39.tgz",
"integrity": "sha512-muXs4asy1A7qDtcdznxqyBfxf4N6qxofY/S0c95vbsWa0r9YAE2PttHIjcuxSy1q2jUiTkpCcgFEjNJRQRVhEw==",
"license": "MIT",
"dependencies": {
"@cfworker/json-schema": "^4.0.2",
"ansi-styles": "^5.0.0",
"camelcase": "6",
"decamelize": "1.2.0",
"js-tiktoken": "^1.0.12",
"langsmith": "^0.2.8",
"langsmith": ">=0.2.8 <0.4.0",
"mustache": "^4.2.0",
"p-queue": "^6.6.2",
"p-retry": "4",
@ -4357,6 +4376,24 @@
"@langchain/core": ">=0.2.21 <0.4.0"
}
},
"node_modules/@langchain/groq/node_modules/@langchain/openai": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.17.tgz",
"integrity": "sha512-uw4po32OKptVjq+CYHrumgbfh4NuD7LqyE+ZgqY9I/LrLc6bHLMc+sisHmI17vgek0K/yqtarI0alPJbzrwyag==",
"license": "MIT",
"dependencies": {
"js-tiktoken": "^1.0.12",
"openai": "^4.77.0",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.3"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@langchain/core": ">=0.3.29 <0.4.0"
}
},
"node_modules/@langchain/ollama": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@langchain/ollama/-/ollama-0.1.1.tgz",
@ -4387,13 +4424,13 @@
}
},
"node_modules/@langchain/openai": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.14.tgz",
"integrity": "sha512-lNWjUo1tbvsss45IF7UQtMu1NJ6oUKvhgPYWXnX9f/d6OmuLu7D99HQ3Y88vLcUo9XjjOy417olYHignMduMjA==",
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.4.2.tgz",
"integrity": "sha512-Cuj7qbVcycALTP0aqZuPpEc7As8cwiGaU21MhXRyZFs+dnWxKYxZ1Q1z4kcx6cYkq/I+CNwwmk+sP+YruU73Aw==",
"license": "MIT",
"dependencies": {
"js-tiktoken": "^1.0.12",
"openai": "^4.71.0",
"openai": "^4.77.0",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.3"
},
@ -4401,7 +4438,7 @@
"node": ">=18"
},
"peerDependencies": {
"@langchain/core": ">=0.2.26 <0.4.0"
"@langchain/core": ">=0.3.29 <0.4.0"
}
},
"node_modules/@langchain/textsplitters": {
@ -14792,6 +14829,24 @@
}
}
},
"node_modules/langchain/node_modules/@langchain/openai": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.17.tgz",
"integrity": "sha512-uw4po32OKptVjq+CYHrumgbfh4NuD7LqyE+ZgqY9I/LrLc6bHLMc+sisHmI17vgek0K/yqtarI0alPJbzrwyag==",
"license": "MIT",
"dependencies": {
"js-tiktoken": "^1.0.12",
"openai": "^4.77.0",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.3"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@langchain/core": ">=0.3.29 <0.4.0"
}
},
"node_modules/langchain/node_modules/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
@ -17036,9 +17091,9 @@
"integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ=="
},
"node_modules/openai": {
"version": "4.73.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.73.1.tgz",
"integrity": "sha512-nWImDJBcUsqrhy7yJScXB4+iqjzbUEgzfA3un/6UnHFdwWhjX24oztj69Ped/njABfOdLcO/F7CeWTI5dt8Xmg==",
"version": "4.83.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.83.0.tgz",
"integrity": "sha512-fmTsqud0uTtRKsPC7L8Lu55dkaTwYucqncDHzVvO64DKOpNTuiYwjbR/nVgpapXuYy8xSnhQQPUm+3jQaxICgw==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "^18.11.18",
@ -17053,9 +17108,13 @@
"openai": "bin/cli"
},
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}

View file

@ -79,7 +79,7 @@
"@langchain/core": "^0.3.27",
"@langchain/google-genai": "^0.1.6",
"@langchain/groq": "^0.1.2",
"@langchain/openai": "^0.3.14",
"@langchain/openai": "^0.4.2",
"@orama/orama": "^3.0.0-rc-2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.2",

View file

@ -7,23 +7,19 @@ import {
subscribeToModelKeyChange,
} from "@/aiParams";
import ChainFactory, { ChainType, Document } from "@/chainFactory";
import {
AI_SENDER,
BUILTIN_CHAT_MODELS,
USER_SENDER,
VAULT_VECTOR_STORE_STRATEGY,
} from "@/constants";
import { BUILTIN_CHAT_MODELS, USER_SENDER, VAULT_VECTOR_STORE_STRATEGY } from "@/constants";
import {
ChainRunner,
CopilotPlusChainRunner,
LLMChainRunner,
VaultQAChainRunner,
} from "@/LLMProviders/chainRunner";
import { logError, logInfo } from "@/logger";
import { HybridRetriever } from "@/search/hybridRetriever";
import VectorStoreManager from "@/search/vectorStoreManager";
import { getSettings, getSystemPrompt, subscribeToSettingsChange } from "@/settings/model";
import { ChatMessage } from "@/sharedState";
import { findCustomModel, isSupportedChain } from "@/utils";
import { findCustomModel, isOSeriesModel, isSupportedChain } from "@/utils";
import {
ChatPromptTemplate,
HumanMessagePromptTemplate,
@ -34,7 +30,6 @@ import { App, Notice } from "obsidian";
import ChatModelManager from "./chatModelManager";
import MemoryManager from "./memoryManager";
import PromptManager from "./promptManager";
import { logError, logInfo } from "@/logger";
export default class ChainManager {
private static chain: RunnableSequence;
@ -247,21 +242,19 @@ export default class ChainManager {
this.validateChainInitialization();
const chatModel = this.chatModelManager.getChatModel();
const modelName = (chatModel as any).modelName || (chatModel as any).model || "";
const isO1Model = modelName.startsWith("o1");
// Handle ignoreSystemMessage
if (ignoreSystemMessage || isO1Model) {
if (ignoreSystemMessage || isOSeriesModel(chatModel)) {
let effectivePrompt = ChatPromptTemplate.fromMessages([
new MessagesPlaceholder("history"),
HumanMessagePromptTemplate.fromTemplate("{input}"),
]);
// TODO: hack for o1 models, to be removed when they support system prompt
if (isO1Model) {
// Temporary fixfor o1-xx model need to covert systemMessage to aiMessage
// TODO: hack for o-series models, to be removed when langchainjs supports system prompt
// https://github.com/langchain-ai/langchain/issues/28895
if (isOSeriesModel(chatModel)) {
effectivePrompt = ChatPromptTemplate.fromMessages([
[AI_SENDER, getSystemPrompt() || ""],
[USER_SENDER, getSystemPrompt() || ""],
effectivePrompt,
]);
}

View file

@ -17,6 +17,7 @@ import {
extractYoutubeUrl,
formatDateTime,
getApiErrorMessage,
getMessageRole,
ImageContent,
ImageProcessor,
MessageContent,
@ -357,10 +358,13 @@ class CopilotPlusChainRunner extends BaseChainRunner {
"\n\nThe following is the relevant conversation history. Use this context to maintain consistency in your responses:";
}
// Add the combined system message
// Get chat model for role determination for O-series models
const chatModel = this.chainManager.chatModelManager.getChatModel();
// Add the combined system message with appropriate role
if (fullSystemMessage) {
messages.push({
role: "system",
role: getMessageRole(chatModel),
content: `${fullSystemMessage}\nIMPORTANT: Maintain consistency with previous responses in the conversation. If you've provided information about a person or topic before, use that same information in follow-up questions.`,
});
}

View file

@ -2,7 +2,7 @@ import { CustomModel, getModelKey, ModelConfig, setModelKey } from "@/aiParams";
import { BREVILABS_API_BASE_URL, BUILTIN_CHAT_MODELS, ChatModelProviders } from "@/constants";
import { getDecryptedKey } from "@/encryptionService";
import { getModelKeyFromModel, getSettings, subscribeToSettingsChange } from "@/settings/model";
import { err2String, safeFetch } from "@/utils";
import { err2String, isOSeriesModel, safeFetch } from "@/utils";
import { HarmBlockThreshold, HarmCategory } from "@google/generative-ai";
import { ChatAnthropic } from "@langchain/anthropic";
import { ChatCohere } from "@langchain/cohere";
@ -75,13 +75,11 @@ export default class ChatModelManager {
private async getModelConfig(customModel: CustomModel): Promise<ModelConfig> {
const settings = getSettings();
// Check if the model starts with "o1"
const modelName = customModel.name;
const isO1Model = modelName.startsWith("o1");
const baseConfig: ModelConfig = {
const isOSeries = isOSeriesModel(modelName);
const baseConfig: Omit<ModelConfig, "maxTokens" | "maxCompletionTokens"> = {
modelName: modelName,
temperature: customModel.temperature ?? settings.temperature,
maxTokens: customModel.maxTokens ?? settings.maxTokens,
streaming: customModel.stream ?? true,
maxRetries: 3,
maxConcurrency: 3,
@ -97,10 +95,9 @@ export default class ChatModelManager {
configuration: {
baseURL: customModel.baseUrl,
fetch: customModel.enableCors ? safeFetch : undefined,
organization: await getDecryptedKey(customModel.openAIOrgId || settings.openAIOrgId),
},
// @ts-ignore
openAIOrgId: getDecryptedKey(customModel.openAIOrgId || settings.openAIOrgId),
...this.handleOpenAIExtraArgs(isO1Model, settings.maxTokens, settings.temperature),
...this.handleOpenAIExtraArgs(isOSeries, settings.maxTokens, settings.temperature),
},
[ChatModelProviders.ANTHROPIC]: {
anthropicApiKey: await getDecryptedKey(customModel.apiKey || settings.anthropicApiKey),
@ -113,17 +110,18 @@ export default class ChatModelManager {
},
},
[ChatModelProviders.AZURE_OPENAI]: {
azureOpenAIApiKey: await getDecryptedKey(customModel.apiKey || settings.azureOpenAIApiKey),
azureOpenAIApiInstanceName:
customModel.azureOpenAIApiInstanceName || settings.azureOpenAIApiInstanceName,
azureOpenAIApiDeploymentName:
customModel.azureOpenAIApiDeploymentName || settings.azureOpenAIApiDeploymentName,
azureOpenAIApiVersion: customModel.azureOpenAIApiVersion || settings.azureOpenAIApiVersion,
modelName: modelName,
openAIApiKey: await getDecryptedKey(customModel.apiKey || settings.azureOpenAIApiKey),
configuration: {
baseURL: customModel.baseUrl,
baseURL:
customModel.baseUrl ||
`https://${customModel.azureOpenAIApiInstanceName || settings.azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${customModel.azureOpenAIApiDeploymentName || settings.azureOpenAIApiDeploymentName}`,
defaultQuery: {
"api-version": customModel.azureOpenAIApiVersion || settings.azureOpenAIApiVersion,
},
fetch: customModel.enableCors ? safeFetch : undefined,
},
...this.handleOpenAIExtraArgs(isO1Model, settings.maxTokens, settings.temperature),
...this.handleOpenAIExtraArgs(isOSeries, settings.maxTokens, settings.temperature),
},
[ChatModelProviders.COHEREAI]: {
apiKey: await getDecryptedKey(customModel.apiKey || settings.cohereApiKey),
@ -186,9 +184,9 @@ export default class ChatModelManager {
configuration: {
baseURL: customModel.baseUrl,
fetch: customModel.enableCors ? safeFetch : undefined,
dangerouslyAllowBrowser: true,
defaultHeaders: { "dangerously-allow-browser": "true" },
},
...this.handleOpenAIExtraArgs(isO1Model, settings.maxTokens, settings.temperature),
...this.handleOpenAIExtraArgs(isOSeries, settings.maxTokens, settings.temperature),
},
[ChatModelProviders.COPILOT_PLUS]: {
modelName: modelName,
@ -203,11 +201,22 @@ export default class ChatModelManager {
const selectedProviderConfig =
providerConfig[customModel.provider as keyof typeof providerConfig] || {};
return { ...baseConfig, ...selectedProviderConfig };
// Add token configuration separately to ensure they don't conflict
const tokenConfig = this.handleOpenAIExtraArgs(
isOSeries,
customModel.maxTokens ?? settings.maxTokens,
customModel.temperature ?? settings.temperature
);
return {
...baseConfig,
...selectedProviderConfig,
...tokenConfig,
};
}
private handleOpenAIExtraArgs(isO1Model: boolean, maxTokens: number, temperature: number) {
return isO1Model
private handleOpenAIExtraArgs(isOSeriesModel: boolean, maxTokens: number, temperature: number) {
const config = isOSeriesModel
? {
maxCompletionTokens: maxTokens,
temperature: 1,
@ -216,6 +225,7 @@ export default class ChatModelManager {
maxTokens: maxTokens,
temperature: temperature,
};
return config;
}
// Build a map of modelKey to model config
@ -327,10 +337,14 @@ export default class ChatModelManager {
const modelToTest = { ...model, enableCors };
const modelConfig = await this.getModelConfig(modelToTest);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { streaming, temperature, ...pingConfig } = modelConfig;
pingConfig.maxTokens = 10;
const { streaming, maxTokens, maxCompletionTokens, ...pingConfig } = modelConfig;
const isOSeries = isOSeriesModel(modelToTest.name);
const tokenConfig = this.handleOpenAIExtraArgs(isOSeries, 10, modelConfig.temperature);
const testModel = new (this.getProviderConstructor(modelToTest))(pingConfig);
const testModel = new (this.getProviderConstructor(modelToTest))({
...pingConfig,
...tokenConfig,
});
await testModel.invoke([{ role: "user", content: "hello" }], {
timeout: 3000,
});

View file

@ -208,7 +208,7 @@ export default class EmbeddingManager {
},
[EmbeddingModelProviders.OPENAI]: {
modelName,
openAIApiKey: await getDecryptedKey(customModel.apiKey || settings.openAIApiKey),
apiKey: await getDecryptedKey(customModel.apiKey || settings.openAIApiKey),
timeout: 10000,
configuration: {
baseURL: customModel.baseUrl,
@ -224,15 +224,15 @@ export default class EmbeddingManager {
apiKey: await getDecryptedKey(settings.googleApiKey),
},
[EmbeddingModelProviders.AZURE_OPENAI]: {
azureOpenAIApiKey: await getDecryptedKey(customModel.apiKey || settings.azureOpenAIApiKey),
azureOpenAIApiInstanceName:
customModel.azureOpenAIApiInstanceName || settings.azureOpenAIApiInstanceName,
azureOpenAIApiDeploymentName:
customModel.azureOpenAIApiEmbeddingDeploymentName ||
settings.azureOpenAIApiEmbeddingDeploymentName,
azureOpenAIApiVersion: customModel.azureOpenAIApiVersion || settings.azureOpenAIApiVersion,
modelName,
openAIApiKey: await getDecryptedKey(customModel.apiKey || settings.azureOpenAIApiKey),
configuration: {
baseURL: customModel.baseUrl,
baseURL:
customModel.baseUrl ||
`https://${customModel.azureOpenAIApiInstanceName || settings.azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${customModel.azureOpenAIApiEmbeddingDeploymentName || settings.azureOpenAIApiEmbeddingDeploymentName}`,
defaultQuery: {
"api-version": customModel.azureOpenAIApiVersion || settings.azureOpenAIApiVersion,
},
fetch: customModel.enableCors ? safeFetch : undefined,
},
},

View file

@ -40,6 +40,7 @@ export interface ModelConfig {
maxRetries: number;
maxConcurrency: number;
maxTokens?: number;
maxCompletionTokens?: number;
openAIApiKey?: string;
openAIOrgId?: string;
anthropicApiKey?: string;

View file

@ -23,7 +23,6 @@ export async function getStandaloneQuestion(
const response = await ChatModelManager.getInstance()
.getChatModel()
.invoke([
{ role: "system", content: condenseQuestionTemplate },
{
role: "user",
content: condenseQuestionTemplate

View file

@ -43,7 +43,8 @@ export enum ChatModels {
COPILOT_PLUS_FLASH = "copilot-plus-flash",
GPT_4o = "gpt-4o",
GPT_4o_mini = "gpt-4o-mini",
GPT_4_TURBO = "gpt-4-turbo",
O1_mini = "o1-mini",
O3_mini = "o3-mini",
GEMINI_PRO = "gemini-1.5-pro",
GEMINI_FLASH = "gemini-1.5-flash",
AZURE_OPENAI = "azure-openai",
@ -93,7 +94,13 @@ export const BUILTIN_CHAT_MODELS: CustomModel[] = [
core: true,
},
{
name: ChatModels.GPT_4_TURBO,
name: ChatModels.O1_mini,
provider: ChatModelProviders.OPENAI,
enabled: true,
isBuiltIn: true,
},
{
name: ChatModels.O3_mini,
provider: ChatModelProviders.OPENAI,
enabled: true,
isBuiltIn: true,
@ -147,15 +154,12 @@ export enum EmbeddingModelProviders {
OPENAI = "openai",
COHEREAI = "cohereai",
GOOGLE = "google",
// AZURE_OPENAI = "azure_openai",
AZURE_OPENAI = "azure openai",
OLLAMA = "ollama",
LM_STUDIO = "lm-studio",
OPENAI_FORMAT = "3rd party (openai-format)",
COPILOT_PLUS = "copilot-plus",
COPILOT_PLUS_JINA = "copilot-plus-jina",
// HUGGINGFACE = "huggingface",
// VOYAGEAI = "voyageai",
}
export enum EmbeddingModels {

View file

@ -1,6 +1,24 @@
import React, { useState } from "react";
import { useTab } from "@/contexts/TabContext";
import { getSettings } from "@/settings/model";
import { CustomModel } from "@/aiParams";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
ChatModelProviders,
DisplayKeyProviders,
@ -9,30 +27,12 @@ import {
ProviderMetadata,
ProviderSettingsKeyMap,
} from "@/constants";
import { CustomModel } from "@/aiParams";
import { useTab } from "@/contexts/TabContext";
import { getSettings } from "@/settings/model";
import { err2String, getProviderInfo, getProviderLabel, omit } from "@/utils";
import { Notice } from "obsidian";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button";
import { ChevronDown, Loader2 } from "lucide-react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { PasswordInput } from "@/components/ui/password-input";
import { Checkbox } from "@/components/ui/checkbox";
import { Notice } from "obsidian";
import React, { useState } from "react";
interface FormFieldProps {
label: string;
@ -426,7 +426,7 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
{Object.values(
isEmbeddingModel
? omit(EmbeddingModelProviders, ["COPILOT_PLUS", "COPILOT_PLUS_JINA"])
: ChatModelProviders
: omit(ChatModelProviders, ["COPILOT_PLUS"])
).map((provider) => (
<SelectItem key={provider} value={provider}>
{getProviderLabel(provider)}

View file

@ -7,6 +7,7 @@ import {
USER_SENDER,
} from "@/constants";
import { ChatMessage } from "@/sharedState";
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
import { MemoryVariables } from "@langchain/core/memory";
import { RunnableSequence } from "@langchain/core/runnables";
import { BaseChain, RetrievalQAChain } from "langchain/chains";
@ -867,3 +868,20 @@ export async function checkLatestVersion(): Promise<{
return { version: null, error: errorMessage };
}
}
export function isOSeriesModel(model: BaseChatModel | string): boolean {
if (typeof model === "string") {
return model.startsWith("o1") || model.startsWith("o3");
}
// For BaseChatModel instances
const modelName = (model as any).modelName || (model as any).model || "";
return modelName.startsWith("o1") || modelName.startsWith("o3");
}
export function getMessageRole(
model: BaseChatModel | string,
defaultRole: "system" | "human" = "system"
): "system" | "human" {
return isOSeriesModel(model) ? "human" : defaultRole;
}