mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🆕 feat: Enhanced Title Generation Config Options (#8580)
* 🏗️ refactor: Extract reasoning key logic into separate function
* refactor: Ensure `overrideProvider` is always defined in `getProviderConfig` result, and only used in `initializeAgent` if different from `agent.provider`
* feat: new title configuration options across services
- titlePrompt
- titleEndpoint
- titlePromptTemplate
- new "completion" titleMethod (new default)
* chore: update @librechat/agents and conform openai version to prevent SDK errors
* chore: add form-data package as a dependency and override to v4.0.4 to address CVE-2025-7783
* feat: add support for 'all' endpoint configuration in AppService and corresponding tests
* refactor: replace HttpsProxyAgent with ProxyAgent from undici for improved proxy handling in assistant initialization
* chore: update frontend review workflow to limit package paths to data-provider
* chore: update backend review workflow to include all package paths
This commit is contained in:
parent
aec1777a90
commit
14660d75ae
21 changed files with 2666 additions and 196 deletions
2
.github/workflows/backend-review.yml
vendored
2
.github/workflows/backend-review.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
||||||
- release/*
|
- release/*
|
||||||
paths:
|
paths:
|
||||||
- 'api/**'
|
- 'api/**'
|
||||||
- 'packages/api/**'
|
- 'packages/**'
|
||||||
jobs:
|
jobs:
|
||||||
tests_Backend:
|
tests_Backend:
|
||||||
name: Run Backend unit tests
|
name: Run Backend unit tests
|
||||||
|
|
|
||||||
2
.github/workflows/frontend-review.yml
vendored
2
.github/workflows/frontend-review.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
||||||
- release/*
|
- release/*
|
||||||
paths:
|
paths:
|
||||||
- 'client/**'
|
- 'client/**'
|
||||||
- 'packages/**'
|
- 'packages/data-provider/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests_frontend_ubuntu:
|
tests_frontend_ubuntu:
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
"@langchain/google-vertexai": "^0.2.13",
|
"@langchain/google-vertexai": "^0.2.13",
|
||||||
"@langchain/openai": "^0.5.18",
|
"@langchain/openai": "^0.5.18",
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@librechat/agents": "^2.4.63",
|
"@librechat/agents": "^2.4.67",
|
||||||
"@librechat/api": "*",
|
"@librechat/api": "*",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@node-saml/passport-saml": "^5.0.0",
|
"@node-saml/passport-saml": "^5.0.0",
|
||||||
|
|
@ -71,6 +71,7 @@
|
||||||
"express-static-gzip": "^2.2.0",
|
"express-static-gzip": "^2.2.0",
|
||||||
"file-type": "^18.7.0",
|
"file-type": "^18.7.0",
|
||||||
"firebase": "^11.0.2",
|
"firebase": "^11.0.2",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
"googleapis": "^126.0.1",
|
"googleapis": "^126.0.1",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
|
|
@ -93,7 +94,7 @@
|
||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"nodemailer": "^6.9.15",
|
"nodemailer": "^6.9.15",
|
||||||
"ollama": "^0.5.0",
|
"ollama": "^0.5.0",
|
||||||
"openai": "^4.96.2",
|
"openai": "^5.10.1",
|
||||||
"openai-chat-tokens": "^0.2.8",
|
"openai-chat-tokens": "^0.2.8",
|
||||||
"openid-client": "^6.5.0",
|
"openid-client": "^6.5.0",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ const {
|
||||||
Callback,
|
Callback,
|
||||||
Providers,
|
Providers,
|
||||||
GraphEvents,
|
GraphEvents,
|
||||||
|
TitleMethod,
|
||||||
formatMessage,
|
formatMessage,
|
||||||
formatAgentMessages,
|
formatAgentMessages,
|
||||||
getTokenCountForMessage,
|
getTokenCountForMessage,
|
||||||
|
|
@ -1009,7 +1010,7 @@ class AgentClient extends BaseClient {
|
||||||
}
|
}
|
||||||
const { handleLLMEnd, collected: collectedMetadata } = createMetadataAggregator();
|
const { handleLLMEnd, collected: collectedMetadata } = createMetadataAggregator();
|
||||||
const { req, res, agent } = this.options;
|
const { req, res, agent } = this.options;
|
||||||
const endpoint = agent.endpoint;
|
let endpoint = agent.endpoint;
|
||||||
|
|
||||||
/** @type {import('@librechat/agents').ClientOptions} */
|
/** @type {import('@librechat/agents').ClientOptions} */
|
||||||
let clientOptions = {
|
let clientOptions = {
|
||||||
|
|
@ -1017,17 +1018,32 @@ class AgentClient extends BaseClient {
|
||||||
model: agent.model_parameters.model,
|
model: agent.model_parameters.model,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { getOptions, overrideProvider, customEndpointConfig } =
|
let titleProviderConfig = await getProviderConfig(endpoint);
|
||||||
await getProviderConfig(endpoint);
|
|
||||||
|
|
||||||
/** @type {TEndpoint | undefined} */
|
/** @type {TEndpoint | undefined} */
|
||||||
const endpointConfig = req.app.locals[endpoint] ?? customEndpointConfig;
|
const endpointConfig =
|
||||||
|
req.app.locals.all ?? req.app.locals[endpoint] ?? titleProviderConfig.customEndpointConfig;
|
||||||
if (!endpointConfig) {
|
if (!endpointConfig) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'[api/server/controllers/agents/client.js #titleConvo] Error getting endpoint config',
|
'[api/server/controllers/agents/client.js #titleConvo] Error getting endpoint config',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (endpointConfig?.titleEndpoint && endpointConfig.titleEndpoint !== endpoint) {
|
||||||
|
try {
|
||||||
|
titleProviderConfig = await getProviderConfig(endpointConfig.titleEndpoint);
|
||||||
|
endpoint = endpointConfig.titleEndpoint;
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(
|
||||||
|
`[api/server/controllers/agents/client.js #titleConvo] Error getting title endpoint config for ${endpointConfig.titleEndpoint}, falling back to default`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
// Fall back to original provider config
|
||||||
|
endpoint = agent.endpoint;
|
||||||
|
titleProviderConfig = await getProviderConfig(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
endpointConfig &&
|
endpointConfig &&
|
||||||
endpointConfig.titleModel &&
|
endpointConfig.titleModel &&
|
||||||
|
|
@ -1036,7 +1052,7 @@ class AgentClient extends BaseClient {
|
||||||
clientOptions.model = endpointConfig.titleModel;
|
clientOptions.model = endpointConfig.titleModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = await getOptions({
|
const options = await titleProviderConfig.getOptions({
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
optionsOnly: true,
|
optionsOnly: true,
|
||||||
|
|
@ -1045,7 +1061,7 @@ class AgentClient extends BaseClient {
|
||||||
endpointOption: { model_parameters: clientOptions },
|
endpointOption: { model_parameters: clientOptions },
|
||||||
});
|
});
|
||||||
|
|
||||||
let provider = options.provider ?? overrideProvider ?? agent.provider;
|
let provider = options.provider ?? titleProviderConfig.overrideProvider ?? agent.provider;
|
||||||
if (
|
if (
|
||||||
endpoint === EModelEndpoint.azureOpenAI &&
|
endpoint === EModelEndpoint.azureOpenAI &&
|
||||||
options.llmConfig?.azureOpenAIApiInstanceName == null
|
options.llmConfig?.azureOpenAIApiInstanceName == null
|
||||||
|
|
@ -1078,16 +1094,23 @@ class AgentClient extends BaseClient {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (provider === Providers.GOOGLE) {
|
if (
|
||||||
|
provider === Providers.GOOGLE &&
|
||||||
|
(endpointConfig?.titleMethod === TitleMethod.FUNCTIONS ||
|
||||||
|
endpointConfig?.titleMethod === TitleMethod.STRUCTURED)
|
||||||
|
) {
|
||||||
clientOptions.json = true;
|
clientOptions.json = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const titleResult = await this.run.generateTitle({
|
const titleResult = await this.run.generateTitle({
|
||||||
provider,
|
provider,
|
||||||
|
clientOptions,
|
||||||
inputText: text,
|
inputText: text,
|
||||||
contentParts: this.contentParts,
|
contentParts: this.contentParts,
|
||||||
clientOptions,
|
titleMethod: endpointConfig?.titleMethod,
|
||||||
|
titlePrompt: endpointConfig?.titlePrompt,
|
||||||
|
titlePromptTemplate: endpointConfig?.titlePromptTemplate,
|
||||||
chainOptions: {
|
chainOptions: {
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
callbacks: [
|
callbacks: [
|
||||||
|
|
|
||||||
730
api/server/controllers/agents/client.test.js
Normal file
730
api/server/controllers/agents/client.test.js
Normal file
|
|
@ -0,0 +1,730 @@
|
||||||
|
const { Providers } = require('@librechat/agents');
|
||||||
|
const { Constants, EModelEndpoint } = require('librechat-data-provider');
|
||||||
|
const AgentClient = require('./client');
|
||||||
|
|
||||||
|
jest.mock('@librechat/agents', () => ({
|
||||||
|
...jest.requireActual('@librechat/agents'),
|
||||||
|
createMetadataAggregator: () => ({
|
||||||
|
handleLLMEnd: jest.fn(),
|
||||||
|
collected: [],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AgentClient - titleConvo', () => {
|
||||||
|
let client;
|
||||||
|
let mockRun;
|
||||||
|
let mockReq;
|
||||||
|
let mockRes;
|
||||||
|
let mockAgent;
|
||||||
|
let mockOptions;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset all mocks
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Mock run object
|
||||||
|
mockRun = {
|
||||||
|
generateTitle: jest.fn().mockResolvedValue({
|
||||||
|
title: 'Generated Title',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock agent - with both endpoint and provider
|
||||||
|
mockAgent = {
|
||||||
|
id: 'agent-123',
|
||||||
|
endpoint: EModelEndpoint.openAI, // Use a valid provider as endpoint for getProviderConfig
|
||||||
|
provider: EModelEndpoint.openAI, // Add provider property
|
||||||
|
model_parameters: {
|
||||||
|
model: 'gpt-4',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock request and response
|
||||||
|
mockReq = {
|
||||||
|
app: {
|
||||||
|
locals: {
|
||||||
|
[EModelEndpoint.openAI]: {
|
||||||
|
// Match the agent endpoint
|
||||||
|
titleModel: 'gpt-3.5-turbo',
|
||||||
|
titlePrompt: 'Custom title prompt',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePromptTemplate: 'Template: {{content}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
id: 'user-123',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
model: 'gpt-4',
|
||||||
|
endpoint: EModelEndpoint.openAI,
|
||||||
|
key: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mockRes = {};
|
||||||
|
|
||||||
|
// Mock options
|
||||||
|
mockOptions = {
|
||||||
|
req: mockReq,
|
||||||
|
res: mockRes,
|
||||||
|
agent: mockAgent,
|
||||||
|
endpointTokenConfig: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create client instance
|
||||||
|
client = new AgentClient(mockOptions);
|
||||||
|
client.run = mockRun;
|
||||||
|
client.responseMessageId = 'response-123';
|
||||||
|
client.conversationId = 'convo-123';
|
||||||
|
client.contentParts = [{ type: 'text', text: 'Test content' }];
|
||||||
|
client.recordCollectedUsage = jest.fn().mockResolvedValue(); // Mock as async function that resolves
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('titleConvo method', () => {
|
||||||
|
it('should throw error if run is not initialized', async () => {
|
||||||
|
client.run = null;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.titleConvo({ text: 'Test', abortController: new AbortController() }),
|
||||||
|
).rejects.toThrow('Run not initialized');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use titlePrompt from endpoint config', async () => {
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
titlePrompt: 'Custom title prompt',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use titlePromptTemplate from endpoint config', async () => {
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
titlePromptTemplate: 'Template: {{content}}',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use titleMethod from endpoint config', async () => {
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: Providers.OPENAI,
|
||||||
|
titleMethod: 'structured',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use titleModel from endpoint config when provided', async () => {
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Check that generateTitle was called with correct clientOptions
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('gpt-3.5-turbo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing endpoint config gracefully', async () => {
|
||||||
|
// Remove endpoint config
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI] = undefined;
|
||||||
|
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
titlePrompt: undefined,
|
||||||
|
titlePromptTemplate: undefined,
|
||||||
|
titleMethod: undefined,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use agent model when titleModel is not provided', async () => {
|
||||||
|
// Remove titleModel from config
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI].titleModel;
|
||||||
|
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('gpt-4'); // Should use agent's model
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use titleModel when it equals CURRENT_MODEL constant', async () => {
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titleModel = Constants.CURRENT_MODEL;
|
||||||
|
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('gpt-4'); // Should use agent's model
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass all required parameters to generateTitle', async () => {
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith({
|
||||||
|
provider: expect.any(String),
|
||||||
|
inputText: text,
|
||||||
|
contentParts: client.contentParts,
|
||||||
|
clientOptions: expect.objectContaining({
|
||||||
|
model: 'gpt-3.5-turbo',
|
||||||
|
}),
|
||||||
|
titlePrompt: 'Custom title prompt',
|
||||||
|
titlePromptTemplate: 'Template: {{content}}',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
chainOptions: expect.objectContaining({
|
||||||
|
signal: abortController.signal,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should record collected usage after title generation', async () => {
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(client.recordCollectedUsage).toHaveBeenCalledWith({
|
||||||
|
model: 'gpt-3.5-turbo',
|
||||||
|
context: 'title',
|
||||||
|
collectedUsage: expect.any(Array),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the generated title', async () => {
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
const result = await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(result).toBe('Generated Title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors gracefully and return undefined', async () => {
|
||||||
|
mockRun.generateTitle.mockRejectedValue(new Error('Title generation failed'));
|
||||||
|
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
const result = await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass titleEndpoint configuration to generateTitle', async () => {
|
||||||
|
// Mock the API key just for this test
|
||||||
|
const originalApiKey = process.env.ANTHROPIC_API_KEY;
|
||||||
|
process.env.ANTHROPIC_API_KEY = 'test-api-key';
|
||||||
|
|
||||||
|
// Add titleEndpoint to the config
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titleEndpoint = EModelEndpoint.anthropic;
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titleMethod = 'structured';
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titlePrompt = 'Custom title prompt';
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titlePromptTemplate = 'Custom template';
|
||||||
|
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify generateTitle was called with the custom configuration
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
titleMethod: 'structured',
|
||||||
|
provider: Providers.ANTHROPIC,
|
||||||
|
titlePrompt: 'Custom title prompt',
|
||||||
|
titlePromptTemplate: 'Custom template',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore the original API key
|
||||||
|
if (originalApiKey) {
|
||||||
|
process.env.ANTHROPIC_API_KEY = originalApiKey;
|
||||||
|
} else {
|
||||||
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use all config when endpoint config is missing', async () => {
|
||||||
|
// Remove endpoint-specific config
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI].titleModel;
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI].titlePrompt;
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI].titleMethod;
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI].titlePromptTemplate;
|
||||||
|
|
||||||
|
// Set 'all' config
|
||||||
|
mockReq.app.locals.all = {
|
||||||
|
titleModel: 'gpt-4o-mini',
|
||||||
|
titlePrompt: 'All config title prompt',
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titlePromptTemplate: 'All config template: {{content}}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify generateTitle was called with 'all' config values
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titlePrompt: 'All config title prompt',
|
||||||
|
titlePromptTemplate: 'All config template: {{content}}',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the model was set from 'all' config
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('gpt-4o-mini');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize all config over endpoint config for title settings', async () => {
|
||||||
|
// Set both endpoint and 'all' config
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titleModel = 'gpt-3.5-turbo';
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titlePrompt = 'Endpoint title prompt';
|
||||||
|
mockReq.app.locals[EModelEndpoint.openAI].titleMethod = 'structured';
|
||||||
|
// Remove titlePromptTemplate from endpoint config to test fallback
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI].titlePromptTemplate;
|
||||||
|
|
||||||
|
mockReq.app.locals.all = {
|
||||||
|
titleModel: 'gpt-4o-mini',
|
||||||
|
titlePrompt: 'All config title prompt',
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titlePromptTemplate: 'All config template',
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = 'Test conversation text';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify 'all' config takes precedence over endpoint config
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titlePrompt: 'All config title prompt',
|
||||||
|
titlePromptTemplate: 'All config template',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the model was set from 'all' config
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('gpt-4o-mini');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use all config with titleEndpoint and verify provider switch', async () => {
|
||||||
|
// Mock the API key for the titleEndpoint provider
|
||||||
|
const originalApiKey = process.env.ANTHROPIC_API_KEY;
|
||||||
|
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
|
||||||
|
|
||||||
|
// Remove endpoint-specific config to test 'all' config
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI];
|
||||||
|
|
||||||
|
// Set comprehensive 'all' config with all new title options
|
||||||
|
mockReq.app.locals.all = {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'claude-3-haiku-20240307',
|
||||||
|
titleMethod: 'completion', // Testing the new default method
|
||||||
|
titlePrompt: 'Generate a concise, descriptive title for this conversation',
|
||||||
|
titlePromptTemplate: 'Conversation summary: {{content}}',
|
||||||
|
titleEndpoint: EModelEndpoint.anthropic, // Should switch provider to Anthropic
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = 'Test conversation about AI and machine learning';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify all config values were used
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: Providers.ANTHROPIC, // Critical: Verify provider switched to Anthropic
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titlePrompt: 'Generate a concise, descriptive title for this conversation',
|
||||||
|
titlePromptTemplate: 'Conversation summary: {{content}}',
|
||||||
|
inputText: text,
|
||||||
|
contentParts: client.contentParts,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the model was set from 'all' config
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('claude-3-haiku-20240307');
|
||||||
|
|
||||||
|
// Verify other client options are set correctly
|
||||||
|
expect(generateTitleCall.clientOptions).toMatchObject({
|
||||||
|
model: 'claude-3-haiku-20240307',
|
||||||
|
// Note: Anthropic's getOptions may set its own maxTokens value
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore the original API key
|
||||||
|
if (originalApiKey) {
|
||||||
|
process.env.ANTHROPIC_API_KEY = originalApiKey;
|
||||||
|
} else {
|
||||||
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should test all titleMethod options from all config', async () => {
|
||||||
|
// Test each titleMethod: 'completion', 'functions', 'structured'
|
||||||
|
const titleMethods = ['completion', 'functions', 'structured'];
|
||||||
|
|
||||||
|
for (const method of titleMethods) {
|
||||||
|
// Clear previous calls
|
||||||
|
mockRun.generateTitle.mockClear();
|
||||||
|
|
||||||
|
// Remove endpoint config
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.openAI];
|
||||||
|
|
||||||
|
// Set 'all' config with specific titleMethod
|
||||||
|
mockReq.app.locals.all = {
|
||||||
|
titleModel: 'gpt-4o-mini',
|
||||||
|
titleMethod: method,
|
||||||
|
titlePrompt: `Testing ${method} method`,
|
||||||
|
titlePromptTemplate: `Template for ${method}: {{content}}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = `Test conversation for ${method} method`;
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify the correct titleMethod was used
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
titleMethod: method,
|
||||||
|
titlePrompt: `Testing ${method} method`,
|
||||||
|
titlePromptTemplate: `Template for ${method}: {{content}}`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Azure-specific title generation', () => {
|
||||||
|
let originalEnv;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset mocks
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Save original environment variables
|
||||||
|
originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
// Mock Azure API keys
|
||||||
|
process.env.AZURE_OPENAI_API_KEY = 'test-azure-key';
|
||||||
|
process.env.AZURE_API_KEY = 'test-azure-key';
|
||||||
|
process.env.EASTUS_API_KEY = 'test-eastus-key';
|
||||||
|
process.env.EASTUS2_API_KEY = 'test-eastus2-key';
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore environment variables
|
||||||
|
process.env = originalEnv;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use OPENAI provider for Azure serverless endpoints', async () => {
|
||||||
|
// Set up Azure endpoint with serverless config
|
||||||
|
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockAgent.provider = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.app.locals[EModelEndpoint.azureOpenAI] = {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'grok-3',
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titlePrompt: 'Azure serverless title prompt',
|
||||||
|
streamRate: 35,
|
||||||
|
modelGroupMap: {
|
||||||
|
'grok-3': {
|
||||||
|
group: 'Azure AI Foundry',
|
||||||
|
deploymentName: 'grok-3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupMap: {
|
||||||
|
'Azure AI Foundry': {
|
||||||
|
apiKey: '${AZURE_API_KEY}',
|
||||||
|
baseURL: 'https://test.services.ai.azure.com/models',
|
||||||
|
version: '2024-05-01-preview',
|
||||||
|
serverless: true,
|
||||||
|
models: {
|
||||||
|
'grok-3': {
|
||||||
|
deploymentName: 'grok-3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockReq.body.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.body.model = 'grok-3';
|
||||||
|
|
||||||
|
const text = 'Test Azure serverless conversation';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify provider was switched to OPENAI for serverless
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: Providers.OPENAI, // Should be OPENAI for serverless
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titlePrompt: 'Azure serverless title prompt',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use AZURE provider for Azure endpoints with instanceName', async () => {
|
||||||
|
// Set up Azure endpoint
|
||||||
|
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockAgent.provider = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.app.locals[EModelEndpoint.azureOpenAI] = {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-4o',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Azure instance title prompt',
|
||||||
|
streamRate: 35,
|
||||||
|
modelGroupMap: {
|
||||||
|
'gpt-4o': {
|
||||||
|
group: 'eastus',
|
||||||
|
deploymentName: 'gpt-4o',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupMap: {
|
||||||
|
eastus: {
|
||||||
|
apiKey: '${EASTUS_API_KEY}',
|
||||||
|
instanceName: 'region-instance',
|
||||||
|
version: '2024-02-15-preview',
|
||||||
|
models: {
|
||||||
|
'gpt-4o': {
|
||||||
|
deploymentName: 'gpt-4o',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockReq.body.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.body.model = 'gpt-4o';
|
||||||
|
|
||||||
|
const text = 'Test Azure instance conversation';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify provider remains AZURE with instanceName
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: Providers.AZURE,
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Azure instance title prompt',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Azure titleModel with CURRENT_MODEL constant', async () => {
|
||||||
|
// Set up Azure endpoint
|
||||||
|
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockAgent.provider = EModelEndpoint.azureOpenAI;
|
||||||
|
mockAgent.model_parameters.model = 'gpt-4o-latest';
|
||||||
|
mockReq.app.locals[EModelEndpoint.azureOpenAI] = {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: Constants.CURRENT_MODEL,
|
||||||
|
titleMethod: 'functions',
|
||||||
|
streamRate: 35,
|
||||||
|
modelGroupMap: {
|
||||||
|
'gpt-4o-latest': {
|
||||||
|
group: 'region-eastus',
|
||||||
|
deploymentName: 'gpt-4o-mini',
|
||||||
|
version: '2024-02-15-preview',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupMap: {
|
||||||
|
'region-eastus': {
|
||||||
|
apiKey: '${EASTUS2_API_KEY}',
|
||||||
|
instanceName: 'test-instance',
|
||||||
|
version: '2024-12-01-preview',
|
||||||
|
models: {
|
||||||
|
'gpt-4o-latest': {
|
||||||
|
deploymentName: 'gpt-4o-mini',
|
||||||
|
version: '2024-02-15-preview',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockReq.body.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.body.model = 'gpt-4o-latest';
|
||||||
|
|
||||||
|
const text = 'Test Azure current model';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify it uses the correct model when titleModel is CURRENT_MODEL
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
// When CURRENT_MODEL is used with Azure, the model gets mapped to the deployment name
|
||||||
|
// In this case, 'gpt-4o-latest' is mapped to 'gpt-4o-mini' deployment
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('gpt-4o-mini');
|
||||||
|
// Also verify that CURRENT_MODEL constant was not passed as the model
|
||||||
|
expect(generateTitleCall.clientOptions.model).not.toBe(Constants.CURRENT_MODEL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Azure with multiple model groups', async () => {
|
||||||
|
// Set up Azure endpoint
|
||||||
|
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockAgent.provider = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.app.locals[EModelEndpoint.azureOpenAI] = {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'o1-mini',
|
||||||
|
titleMethod: 'completion',
|
||||||
|
streamRate: 35,
|
||||||
|
modelGroupMap: {
|
||||||
|
'gpt-4o': {
|
||||||
|
group: 'eastus',
|
||||||
|
deploymentName: 'gpt-4o',
|
||||||
|
},
|
||||||
|
'o1-mini': {
|
||||||
|
group: 'region-eastus',
|
||||||
|
deploymentName: 'o1-mini',
|
||||||
|
},
|
||||||
|
'codex-mini': {
|
||||||
|
group: 'codex-mini',
|
||||||
|
deploymentName: 'codex-mini',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupMap: {
|
||||||
|
eastus: {
|
||||||
|
apiKey: '${EASTUS_API_KEY}',
|
||||||
|
instanceName: 'region-eastus',
|
||||||
|
version: '2024-02-15-preview',
|
||||||
|
models: {
|
||||||
|
'gpt-4o': {
|
||||||
|
deploymentName: 'gpt-4o',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'region-eastus': {
|
||||||
|
apiKey: '${EASTUS2_API_KEY}',
|
||||||
|
instanceName: 'region-eastus2',
|
||||||
|
version: '2024-12-01-preview',
|
||||||
|
models: {
|
||||||
|
'o1-mini': {
|
||||||
|
deploymentName: 'o1-mini',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'codex-mini': {
|
||||||
|
apiKey: '${AZURE_API_KEY}',
|
||||||
|
baseURL: 'https://example.cognitiveservices.azure.com/openai/',
|
||||||
|
version: '2025-04-01-preview',
|
||||||
|
serverless: true,
|
||||||
|
models: {
|
||||||
|
'codex-mini': {
|
||||||
|
deploymentName: 'codex-mini',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockReq.body.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.body.model = 'o1-mini';
|
||||||
|
|
||||||
|
const text = 'Test Azure multi-group conversation';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify correct model and provider are used
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: Providers.AZURE,
|
||||||
|
titleMethod: 'completion',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const generateTitleCall = mockRun.generateTitle.mock.calls[0][0];
|
||||||
|
expect(generateTitleCall.clientOptions.model).toBe('o1-mini');
|
||||||
|
expect(generateTitleCall.clientOptions.maxTokens).toBeUndefined(); // o1 models shouldn't have maxTokens
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use all config as fallback for Azure endpoints', async () => {
|
||||||
|
// Set up Azure endpoint with minimal config
|
||||||
|
mockAgent.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockAgent.provider = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.body.endpoint = EModelEndpoint.azureOpenAI;
|
||||||
|
mockReq.body.model = 'gpt-4';
|
||||||
|
|
||||||
|
// Remove Azure-specific config
|
||||||
|
delete mockReq.app.locals[EModelEndpoint.azureOpenAI];
|
||||||
|
|
||||||
|
// Set 'all' config as fallback with a serverless Azure config
|
||||||
|
mockReq.app.locals.all = {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-4',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Fallback title prompt from all config',
|
||||||
|
titlePromptTemplate: 'Template: {{content}}',
|
||||||
|
modelGroupMap: {
|
||||||
|
'gpt-4': {
|
||||||
|
group: 'default-group',
|
||||||
|
deploymentName: 'gpt-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupMap: {
|
||||||
|
'default-group': {
|
||||||
|
apiKey: '${AZURE_API_KEY}',
|
||||||
|
baseURL: 'https://default.openai.azure.com/',
|
||||||
|
version: '2024-02-15-preview',
|
||||||
|
serverless: true,
|
||||||
|
models: {
|
||||||
|
'gpt-4': {
|
||||||
|
deploymentName: 'gpt-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = 'Test Azure with all config fallback';
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
await client.titleConvo({ text, abortController });
|
||||||
|
|
||||||
|
// Verify all config is used
|
||||||
|
expect(mockRun.generateTitle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: Providers.OPENAI, // Should be OPENAI when no instanceName
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Fallback title prompt from all config',
|
||||||
|
titlePromptTemplate: 'Template: {{content}}',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -157,6 +157,10 @@ const AppService = async (app) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (endpoints?.all) {
|
||||||
|
endpointLocals.all = endpoints.all;
|
||||||
|
}
|
||||||
|
|
||||||
app.locals = {
|
app.locals = {
|
||||||
...defaultLocals,
|
...defaultLocals,
|
||||||
fileConfig: config?.fileConfig,
|
fileConfig: config?.fileConfig,
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,206 @@ describe('AppService', () => {
|
||||||
expect(process.env.IMPORT_USER_MAX).toEqual('initialUserMax');
|
expect(process.env.IMPORT_USER_MAX).toEqual('initialUserMax');
|
||||||
expect(process.env.IMPORT_USER_WINDOW).toEqual('initialUserWindow');
|
expect(process.env.IMPORT_USER_WINDOW).toEqual('initialUserWindow');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should correctly configure endpoint with titlePrompt, titleMethod, and titlePromptTemplate', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
endpoints: {
|
||||||
|
[EModelEndpoint.openAI]: {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-3.5-turbo',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Custom title prompt for conversation',
|
||||||
|
titlePromptTemplate: 'Summarize this conversation: {{conversation}}',
|
||||||
|
},
|
||||||
|
[EModelEndpoint.assistants]: {
|
||||||
|
titleMethod: 'functions',
|
||||||
|
titlePrompt: 'Generate a title for this assistant conversation',
|
||||||
|
titlePromptTemplate: 'Assistant conversation template: {{messages}}',
|
||||||
|
},
|
||||||
|
[EModelEndpoint.azureOpenAI]: {
|
||||||
|
groups: azureGroups,
|
||||||
|
titleConvo: true,
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titleModel: 'gpt-4',
|
||||||
|
titlePrompt: 'Azure title prompt',
|
||||||
|
titlePromptTemplate: 'Azure conversation: {{context}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
// Check OpenAI endpoint configuration
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.openAI);
|
||||||
|
expect(app.locals[EModelEndpoint.openAI]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-3.5-turbo',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Custom title prompt for conversation',
|
||||||
|
titlePromptTemplate: 'Summarize this conversation: {{conversation}}',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check Assistants endpoint configuration
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.assistants);
|
||||||
|
expect(app.locals[EModelEndpoint.assistants]).toMatchObject({
|
||||||
|
titleMethod: 'functions',
|
||||||
|
titlePrompt: 'Generate a title for this assistant conversation',
|
||||||
|
titlePromptTemplate: 'Assistant conversation template: {{messages}}',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check Azure OpenAI endpoint configuration
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.azureOpenAI);
|
||||||
|
expect(app.locals[EModelEndpoint.azureOpenAI]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
titleConvo: true,
|
||||||
|
titleMethod: 'completion',
|
||||||
|
titleModel: 'gpt-4',
|
||||||
|
titlePrompt: 'Azure title prompt',
|
||||||
|
titlePromptTemplate: 'Azure conversation: {{context}}',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure Agent endpoint with title generation settings', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
endpoints: {
|
||||||
|
[EModelEndpoint.agents]: {
|
||||||
|
disableBuilder: false,
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-4',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Generate a descriptive title for this agent conversation',
|
||||||
|
titlePromptTemplate: 'Agent conversation summary: {{content}}',
|
||||||
|
recursionLimit: 15,
|
||||||
|
capabilities: [AgentCapabilities.tools, AgentCapabilities.actions],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.agents);
|
||||||
|
expect(app.locals[EModelEndpoint.agents]).toMatchObject({
|
||||||
|
disableBuilder: false,
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-4',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Generate a descriptive title for this agent conversation',
|
||||||
|
titlePromptTemplate: 'Agent conversation summary: {{content}}',
|
||||||
|
recursionLimit: 15,
|
||||||
|
capabilities: expect.arrayContaining([AgentCapabilities.tools, AgentCapabilities.actions]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing title configuration options with defaults', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
endpoints: {
|
||||||
|
[EModelEndpoint.openAI]: {
|
||||||
|
titleConvo: true,
|
||||||
|
// titlePrompt and titlePromptTemplate are not provided
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.openAI);
|
||||||
|
expect(app.locals[EModelEndpoint.openAI]).toMatchObject({
|
||||||
|
titleConvo: true,
|
||||||
|
});
|
||||||
|
// Check that the optional fields are undefined when not provided
|
||||||
|
expect(app.locals[EModelEndpoint.openAI].titlePrompt).toBeUndefined();
|
||||||
|
expect(app.locals[EModelEndpoint.openAI].titlePromptTemplate).toBeUndefined();
|
||||||
|
expect(app.locals[EModelEndpoint.openAI].titleMethod).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly configure titleEndpoint when specified', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
endpoints: {
|
||||||
|
[EModelEndpoint.openAI]: {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-3.5-turbo',
|
||||||
|
titleEndpoint: EModelEndpoint.anthropic,
|
||||||
|
titlePrompt: 'Generate a concise title',
|
||||||
|
},
|
||||||
|
[EModelEndpoint.agents]: {
|
||||||
|
titleEndpoint: 'custom-provider',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
// Check OpenAI endpoint has titleEndpoint
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.openAI);
|
||||||
|
expect(app.locals[EModelEndpoint.openAI]).toMatchObject({
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-3.5-turbo',
|
||||||
|
titleEndpoint: EModelEndpoint.anthropic,
|
||||||
|
titlePrompt: 'Generate a concise title',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check Agents endpoint has titleEndpoint
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.agents);
|
||||||
|
expect(app.locals[EModelEndpoint.agents]).toMatchObject({
|
||||||
|
titleEndpoint: 'custom-provider',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly configure all endpoint when specified', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
endpoints: {
|
||||||
|
all: {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-4o-mini',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Default title prompt for all endpoints',
|
||||||
|
titlePromptTemplate: 'Default template: {{conversation}}',
|
||||||
|
titleEndpoint: EModelEndpoint.anthropic,
|
||||||
|
streamRate: 50,
|
||||||
|
},
|
||||||
|
[EModelEndpoint.openAI]: {
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-3.5-turbo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
// Check that 'all' endpoint config is loaded
|
||||||
|
expect(app.locals).toHaveProperty('all');
|
||||||
|
expect(app.locals.all).toMatchObject({
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-4o-mini',
|
||||||
|
titleMethod: 'structured',
|
||||||
|
titlePrompt: 'Default title prompt for all endpoints',
|
||||||
|
titlePromptTemplate: 'Default template: {{conversation}}',
|
||||||
|
titleEndpoint: EModelEndpoint.anthropic,
|
||||||
|
streamRate: 50,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that OpenAI endpoint has its own config
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.openAI);
|
||||||
|
expect(app.locals[EModelEndpoint.openAI]).toMatchObject({
|
||||||
|
titleConvo: true,
|
||||||
|
titleModel: 'gpt-3.5-turbo',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('AppService updating app.locals and issuing warnings', () => {
|
describe('AppService updating app.locals and issuing warnings', () => {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ const initializeAgent = async ({
|
||||||
|
|
||||||
agent.endpoint = provider;
|
agent.endpoint = provider;
|
||||||
const { getOptions, overrideProvider } = await getProviderConfig(provider);
|
const { getOptions, overrideProvider } = await getProviderConfig(provider);
|
||||||
if (overrideProvider) {
|
if (overrideProvider !== agent.provider) {
|
||||||
agent.provider = overrideProvider;
|
agent.provider = overrideProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const OpenAI = require('openai');
|
const OpenAI = require('openai');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { ProxyAgent } = require('undici');
|
||||||
const { ErrorTypes, EModelEndpoint } = require('librechat-data-provider');
|
const { ErrorTypes, EModelEndpoint } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getUserKeyValues,
|
getUserKeyValues,
|
||||||
|
|
@ -59,7 +59,10 @@ const initializeClient = async ({ req, res, endpointOption, version, initAppClie
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PROXY) {
|
if (PROXY) {
|
||||||
opts.httpAgent = new HttpsProxyAgent(PROXY);
|
const proxyAgent = new ProxyAgent(PROXY);
|
||||||
|
opts.fetchOptions = {
|
||||||
|
dispatcher: proxyAgent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OPENAI_ORGANIZATION) {
|
if (OPENAI_ORGANIZATION) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// const OpenAI = require('openai');
|
// const OpenAI = require('openai');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { ProxyAgent } = require('undici');
|
||||||
const { ErrorTypes } = require('librechat-data-provider');
|
const { ErrorTypes } = require('librechat-data-provider');
|
||||||
const { getUserKey, getUserKeyExpiry, getUserKeyValues } = require('~/server/services/UserService');
|
const { getUserKey, getUserKeyExpiry, getUserKeyValues } = require('~/server/services/UserService');
|
||||||
const initializeClient = require('./initalize');
|
const initializeClient = require('./initalize');
|
||||||
|
|
@ -107,6 +107,7 @@ describe('initializeClient', () => {
|
||||||
const res = {};
|
const res = {};
|
||||||
|
|
||||||
const { openai } = await initializeClient({ req, res });
|
const { openai } = await initializeClient({ req, res });
|
||||||
expect(openai.httpAgent).toBeInstanceOf(HttpsProxyAgent);
|
expect(openai.fetchOptions).toBeDefined();
|
||||||
|
expect(openai.fetchOptions.dispatcher).toBeInstanceOf(ProxyAgent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const OpenAI = require('openai');
|
const OpenAI = require('openai');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { ProxyAgent } = require('undici');
|
||||||
const { constructAzureURL, isUserProvided, resolveHeaders } = require('@librechat/api');
|
const { constructAzureURL, isUserProvided, resolveHeaders } = require('@librechat/api');
|
||||||
const { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } = require('librechat-data-provider');
|
const { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
|
|
@ -158,7 +158,10 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PROXY) {
|
if (PROXY) {
|
||||||
opts.httpAgent = new HttpsProxyAgent(PROXY);
|
const proxyAgent = new ProxyAgent(PROXY);
|
||||||
|
opts.fetchOptions = {
|
||||||
|
dispatcher: proxyAgent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OPENAI_ORGANIZATION) {
|
if (OPENAI_ORGANIZATION) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// const OpenAI = require('openai');
|
// const OpenAI = require('openai');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { ProxyAgent } = require('undici');
|
||||||
const { ErrorTypes } = require('librechat-data-provider');
|
const { ErrorTypes } = require('librechat-data-provider');
|
||||||
const { getUserKey, getUserKeyExpiry, getUserKeyValues } = require('~/server/services/UserService');
|
const { getUserKey, getUserKeyExpiry, getUserKeyValues } = require('~/server/services/UserService');
|
||||||
const initializeClient = require('./initialize');
|
const initializeClient = require('./initialize');
|
||||||
|
|
@ -107,6 +107,7 @@ describe('initializeClient', () => {
|
||||||
const res = {};
|
const res = {};
|
||||||
|
|
||||||
const { openai } = await initializeClient({ req, res });
|
const { openai } = await initializeClient({ req, res });
|
||||||
expect(openai.httpAgent).toBeInstanceOf(HttpsProxyAgent);
|
expect(openai.fetchOptions).toBeDefined();
|
||||||
|
expect(openai.fetchOptions.dispatcher).toBeInstanceOf(ProxyAgent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,13 @@ const providerConfigMap = {
|
||||||
* @param {string} provider - The provider string
|
* @param {string} provider - The provider string
|
||||||
* @returns {Promise<{
|
* @returns {Promise<{
|
||||||
* getOptions: Function,
|
* getOptions: Function,
|
||||||
* overrideProvider?: string,
|
* overrideProvider: string,
|
||||||
* customEndpointConfig?: TEndpoint
|
* customEndpointConfig?: TEndpoint
|
||||||
* }>}
|
* }>}
|
||||||
*/
|
*/
|
||||||
async function getProviderConfig(provider) {
|
async function getProviderConfig(provider) {
|
||||||
let getOptions = providerConfigMap[provider];
|
let getOptions = providerConfigMap[provider];
|
||||||
let overrideProvider;
|
let overrideProvider = provider;
|
||||||
/** @type {TEndpoint | undefined} */
|
/** @type {TEndpoint | undefined} */
|
||||||
let customEndpointConfig;
|
let customEndpointConfig;
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ async function getProviderConfig(provider) {
|
||||||
overrideProvider = Providers.OPENAI;
|
overrideProvider = Providers.OPENAI;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKnownCustomProvider(overrideProvider || provider) && !customEndpointConfig) {
|
if (isKnownCustomProvider(overrideProvider) && !customEndpointConfig) {
|
||||||
customEndpointConfig = await getCustomEndpointConfig(provider);
|
customEndpointConfig = await getCustomEndpointConfig(provider);
|
||||||
if (!customEndpointConfig) {
|
if (!customEndpointConfig) {
|
||||||
throw new Error(`Provider ${provider} not supported`);
|
throw new Error(`Provider ${provider} not supported`);
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ function assistantsConfigSetup(config, assistantsEndpoint, prevConfig = {}) {
|
||||||
privateAssistants: parsedConfig.privateAssistants,
|
privateAssistants: parsedConfig.privateAssistants,
|
||||||
timeoutMs: parsedConfig.timeoutMs,
|
timeoutMs: parsedConfig.timeoutMs,
|
||||||
streamRate: parsedConfig.streamRate,
|
streamRate: parsedConfig.streamRate,
|
||||||
|
titlePrompt: parsedConfig.titlePrompt,
|
||||||
|
titleMethod: parsedConfig.titleMethod,
|
||||||
|
titleModel: parsedConfig.titleModel,
|
||||||
|
titleEndpoint: parsedConfig.titleEndpoint,
|
||||||
|
titlePromptTemplate: parsedConfig.titlePromptTemplate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
1782
package-lock.json
generated
1782
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -118,6 +118,7 @@
|
||||||
"@langchain/openai": "0.5.18",
|
"@langchain/openai": "0.5.18",
|
||||||
"axios": "1.8.2",
|
"axios": "1.8.2",
|
||||||
"elliptic": "^6.6.1",
|
"elliptic": "^6.6.1",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
"mdast-util-gfm-autolink-literal": "2.0.0",
|
"mdast-util-gfm-autolink-literal": "2.0.0",
|
||||||
"remark-gfm": {
|
"remark-gfm": {
|
||||||
"mdast-util-gfm-autolink-literal": "2.0.0"
|
"mdast-util-gfm-autolink-literal": "2.0.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@librechat/api",
|
"name": "@librechat/api",
|
||||||
"version": "1.2.7",
|
"version": "1.2.8",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"description": "MCP services for LibreChat",
|
"description": "MCP services for LibreChat",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@langchain/core": "^0.3.62",
|
"@langchain/core": "^0.3.62",
|
||||||
"@librechat/agents": "^2.4.63",
|
"@librechat/agents": "^2.4.67",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.13.3",
|
"@modelcontextprotocol/sdk": "^1.13.3",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,28 @@ const customProviders = new Set([
|
||||||
Providers.OPENROUTER,
|
Providers.OPENROUTER,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export function getReasoningKey(
|
||||||
|
provider: Providers,
|
||||||
|
llmConfig: t.RunLLMConfig,
|
||||||
|
agentEndpoint?: string | null,
|
||||||
|
): 'reasoning_content' | 'reasoning' {
|
||||||
|
let reasoningKey: 'reasoning_content' | 'reasoning' = 'reasoning_content';
|
||||||
|
if (provider === Providers.GOOGLE) {
|
||||||
|
reasoningKey = 'reasoning';
|
||||||
|
} else if (
|
||||||
|
llmConfig.configuration?.baseURL?.includes(KnownEndpoints.openrouter) ||
|
||||||
|
(agentEndpoint && agentEndpoint.toLowerCase().includes(KnownEndpoints.openrouter))
|
||||||
|
) {
|
||||||
|
reasoningKey = 'reasoning';
|
||||||
|
} else if (
|
||||||
|
(llmConfig as OpenAIClientOptions).useResponsesApi === true &&
|
||||||
|
(provider === Providers.OPENAI || provider === Providers.AZURE)
|
||||||
|
) {
|
||||||
|
reasoningKey = 'reasoning';
|
||||||
|
}
|
||||||
|
return reasoningKey;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Run instance with custom handlers and configuration.
|
* Creates a new Run instance with custom handlers and configuration.
|
||||||
*
|
*
|
||||||
|
|
@ -69,21 +91,7 @@ export async function createRun({
|
||||||
llmConfig.usage = true;
|
llmConfig.usage = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reasoningKey: 'reasoning_content' | 'reasoning' = 'reasoning_content';
|
const reasoningKey = getReasoningKey(provider, llmConfig, agent.endpoint);
|
||||||
if (provider === Providers.GOOGLE) {
|
|
||||||
reasoningKey = 'reasoning';
|
|
||||||
} else if (
|
|
||||||
llmConfig.configuration?.baseURL?.includes(KnownEndpoints.openrouter) ||
|
|
||||||
(agent.endpoint && agent.endpoint.toLowerCase().includes(KnownEndpoints.openrouter))
|
|
||||||
) {
|
|
||||||
reasoningKey = 'reasoning';
|
|
||||||
} else if (
|
|
||||||
(llmConfig as OpenAIClientOptions).useResponsesApi === true &&
|
|
||||||
(provider === Providers.OPENAI || provider === Providers.AZURE)
|
|
||||||
) {
|
|
||||||
reasoningKey = 'reasoning';
|
|
||||||
}
|
|
||||||
|
|
||||||
const graphConfig: StandardGraphConfig = {
|
const graphConfig: StandardGraphConfig = {
|
||||||
signal,
|
signal,
|
||||||
llmConfig,
|
llmConfig,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.900",
|
"version": "0.7.901",
|
||||||
"description": "data services for librechat apps",
|
"description": "data services for librechat apps",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
|
|
@ -62,7 +62,6 @@
|
||||||
"@types/winston": "^2.4.4",
|
"@types/winston": "^2.4.4",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"openai": "^4.76.3",
|
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"rollup": "^4.22.4",
|
"rollup": "^4.22.4",
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,12 @@ export const baseEndpointSchema = z.object({
|
||||||
baseURL: z.string().optional(),
|
baseURL: z.string().optional(),
|
||||||
titlePrompt: z.string().optional(),
|
titlePrompt: z.string().optional(),
|
||||||
titleModel: z.string().optional(),
|
titleModel: z.string().optional(),
|
||||||
|
titleConvo: z.boolean().optional(),
|
||||||
|
titleMethod: z
|
||||||
|
.union([z.literal('completion'), z.literal('functions'), z.literal('structured')])
|
||||||
|
.optional(),
|
||||||
|
titleEndpoint: z.string().optional(),
|
||||||
|
titlePromptTemplate: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TBaseEndpoint = z.infer<typeof baseEndpointSchema>;
|
export type TBaseEndpoint = z.infer<typeof baseEndpointSchema>;
|
||||||
|
|
@ -225,8 +231,6 @@ export const assistantEndpointSchema = baseEndpointSchema.merge(
|
||||||
userIdQuery: z.boolean().optional(),
|
userIdQuery: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
titleConvo: z.boolean().optional(),
|
|
||||||
titleMethod: z.union([z.literal('completion'), z.literal('functions')]).optional(),
|
|
||||||
headers: z.record(z.any()).optional(),
|
headers: z.record(z.any()).optional(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -279,8 +283,6 @@ export const endpointSchema = baseEndpointSchema.merge(
|
||||||
fetch: z.boolean().optional(),
|
fetch: z.boolean().optional(),
|
||||||
userIdQuery: z.boolean().optional(),
|
userIdQuery: z.boolean().optional(),
|
||||||
}),
|
}),
|
||||||
titleConvo: z.boolean().optional(),
|
|
||||||
titleMethod: z.union([z.literal('completion'), z.literal('functions')]).optional(),
|
|
||||||
summarize: z.boolean().optional(),
|
summarize: z.boolean().optional(),
|
||||||
summaryModel: z.string().optional(),
|
summaryModel: z.string().optional(),
|
||||||
forcePrompt: z.boolean().optional(),
|
forcePrompt: z.boolean().optional(),
|
||||||
|
|
@ -315,6 +317,8 @@ export const azureEndpointSchema = z
|
||||||
titleConvo: true,
|
titleConvo: true,
|
||||||
titleMethod: true,
|
titleMethod: true,
|
||||||
titleModel: true,
|
titleModel: true,
|
||||||
|
titlePrompt: true,
|
||||||
|
titlePromptTemplate: true,
|
||||||
summarize: true,
|
summarize: true,
|
||||||
summaryModel: true,
|
summaryModel: true,
|
||||||
customOrder: true,
|
customOrder: true,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import type OpenAI from 'openai';
|
|
||||||
import type { InfiniteData } from '@tanstack/react-query';
|
import type { InfiniteData } from '@tanstack/react-query';
|
||||||
import type {
|
import type {
|
||||||
TBanner,
|
TBanner,
|
||||||
|
|
@ -14,8 +13,6 @@ import type { SettingDefinition } from './generate';
|
||||||
import type { TMinimalFeedback } from './feedback';
|
import type { TMinimalFeedback } from './feedback';
|
||||||
import type { Agent } from './types/assistants';
|
import type { Agent } from './types/assistants';
|
||||||
|
|
||||||
export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam;
|
|
||||||
|
|
||||||
export * from './schemas';
|
export * from './schemas';
|
||||||
|
|
||||||
export type TMessages = TMessage[];
|
export type TMessages = TMessage[];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue