mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
fix: openaiContentGenerator
- remove `metadata` when using unspported models/providers - use `qwen3-code-plus` as default, fix picking wrong model when refresh auth
This commit is contained in:
@@ -4,6 +4,9 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export const DEFAULT_QWEN_MODEL = 'qwen3-coder-plus';
|
||||||
|
export const DEFAULT_QWEN_FLASH_MODEL = 'qwen3-coder-flash';
|
||||||
|
|
||||||
export const DEFAULT_GEMINI_MODEL = 'qwen3-coder-plus';
|
export const DEFAULT_GEMINI_MODEL = 'qwen3-coder-plus';
|
||||||
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
|
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
|
||||||
export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite';
|
export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
GoogleGenAI,
|
GoogleGenAI,
|
||||||
} from '@google/genai';
|
} from '@google/genai';
|
||||||
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
|
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
|
||||||
import { DEFAULT_GEMINI_MODEL } from '../config/models.js';
|
import { DEFAULT_GEMINI_MODEL, DEFAULT_QWEN_MODEL } from '../config/models.js';
|
||||||
import { Config } from '../config/config.js';
|
import { Config } from '../config/config.js';
|
||||||
import { getEffectiveModel } from './modelCheck.js';
|
import { getEffectiveModel } from './modelCheck.js';
|
||||||
import { UserTierId } from '../code_assist/types.js';
|
import { UserTierId } from '../code_assist/types.js';
|
||||||
@@ -136,7 +136,9 @@ export function createContentGeneratorConfig(
|
|||||||
// For Qwen OAuth, we'll handle the API key dynamically in createContentGenerator
|
// For Qwen OAuth, we'll handle the API key dynamically in createContentGenerator
|
||||||
// Set a special marker to indicate this is Qwen OAuth
|
// Set a special marker to indicate this is Qwen OAuth
|
||||||
contentGeneratorConfig.apiKey = 'QWEN_OAUTH_DYNAMIC_TOKEN';
|
contentGeneratorConfig.apiKey = 'QWEN_OAUTH_DYNAMIC_TOKEN';
|
||||||
contentGeneratorConfig.model = config.getModel() || DEFAULT_GEMINI_MODEL;
|
|
||||||
|
// Prefer to use qwen3-coder-plus as the default Qwen model if QWEN_MODEL is not set.
|
||||||
|
contentGeneratorConfig.model = process.env.QWEN_MODEL || DEFAULT_QWEN_MODEL;
|
||||||
|
|
||||||
return contentGeneratorConfig;
|
return contentGeneratorConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2384,4 +2384,605 @@ describe('OpenAIContentGenerator', () => {
|
|||||||
consoleSpy.mockRestore();
|
consoleSpy.mockRestore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('metadata control', () => {
|
||||||
|
it('should include metadata when authType is QWEN_OAUTH', async () => {
|
||||||
|
const qwenConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'qwen-oauth',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const qwenGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'qwen-turbo',
|
||||||
|
qwenConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'qwen-turbo',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'qwen-turbo',
|
||||||
|
};
|
||||||
|
|
||||||
|
await qwenGenerator.generateContent(request, 'test-prompt-id');
|
||||||
|
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
metadata: {
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
promptId: 'test-prompt-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include metadata when baseURL is dashscope openai compatible mode', async () => {
|
||||||
|
// Mock environment to set dashscope base URL BEFORE creating the generator
|
||||||
|
vi.stubEnv(
|
||||||
|
'OPENAI_BASE_URL',
|
||||||
|
'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||||
|
);
|
||||||
|
|
||||||
|
const dashscopeConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'openai', // Not QWEN_OAUTH
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('dashscope-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const dashscopeGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'qwen-turbo',
|
||||||
|
dashscopeConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Debug: Check if the client was created with the correct baseURL
|
||||||
|
expect(vi.mocked(OpenAI)).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock the client's baseURL property to return the expected value
|
||||||
|
Object.defineProperty(dashscopeGenerator['client'], 'baseURL', {
|
||||||
|
value: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'qwen-turbo',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'qwen-turbo',
|
||||||
|
};
|
||||||
|
|
||||||
|
await dashscopeGenerator.generateContent(request, 'dashscope-prompt-id');
|
||||||
|
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
metadata: {
|
||||||
|
sessionId: 'dashscope-session-id',
|
||||||
|
promptId: 'dashscope-prompt-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT include metadata for regular OpenAI providers', async () => {
|
||||||
|
const regularConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'openai',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('regular-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const regularGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'gpt-4',
|
||||||
|
regularConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
await regularGenerator.generateContent(request, 'regular-prompt-id');
|
||||||
|
|
||||||
|
// Should NOT include metadata
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
metadata: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT include metadata for other auth types', async () => {
|
||||||
|
const otherAuthConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'gemini-api-key',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('other-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const otherGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'gpt-4',
|
||||||
|
otherAuthConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
await otherGenerator.generateContent(request, 'other-prompt-id');
|
||||||
|
|
||||||
|
// Should NOT include metadata
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
metadata: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT include metadata for other base URLs', async () => {
|
||||||
|
// Mock environment to set a different base URL
|
||||||
|
vi.stubEnv('OPENAI_BASE_URL', 'https://api.openai.com/v1');
|
||||||
|
|
||||||
|
const otherBaseUrlConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'openai',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('other-base-url-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const otherBaseUrlGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'gpt-4',
|
||||||
|
otherBaseUrlConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
await otherBaseUrlGenerator.generateContent(
|
||||||
|
request,
|
||||||
|
'other-base-url-prompt-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should NOT include metadata
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
metadata: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include metadata in streaming requests when conditions are met', async () => {
|
||||||
|
const qwenConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'qwen-oauth',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('streaming-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const qwenGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'qwen-turbo',
|
||||||
|
qwenConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockStream = [
|
||||||
|
{
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
delta: { content: 'Hello' },
|
||||||
|
finish_reason: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
delta: { content: ' there!' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue({
|
||||||
|
async *[Symbol.asyncIterator]() {
|
||||||
|
for (const chunk of mockStream) {
|
||||||
|
yield chunk;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'qwen-turbo',
|
||||||
|
};
|
||||||
|
|
||||||
|
const stream = await qwenGenerator.generateContentStream(
|
||||||
|
request,
|
||||||
|
'streaming-prompt-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify metadata was included in the streaming request
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
metadata: {
|
||||||
|
sessionId: 'streaming-session-id',
|
||||||
|
promptId: 'streaming-prompt-id',
|
||||||
|
},
|
||||||
|
stream: true,
|
||||||
|
stream_options: { include_usage: true },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Consume the stream to complete the test
|
||||||
|
const responses = [];
|
||||||
|
for await (const response of stream) {
|
||||||
|
responses.push(response);
|
||||||
|
}
|
||||||
|
expect(responses).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT include metadata in streaming requests when conditions are not met', async () => {
|
||||||
|
const regularConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'openai',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('regular-streaming-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const regularGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'gpt-4',
|
||||||
|
regularConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockStream = [
|
||||||
|
{
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
delta: { content: 'Hello' },
|
||||||
|
finish_reason: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
delta: { content: ' there!' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue({
|
||||||
|
async *[Symbol.asyncIterator]() {
|
||||||
|
for (const chunk of mockStream) {
|
||||||
|
yield chunk;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const stream = await regularGenerator.generateContentStream(
|
||||||
|
request,
|
||||||
|
'regular-streaming-prompt-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify metadata was NOT included in the streaming request
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
metadata: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Consume the stream to complete the test
|
||||||
|
const responses = [];
|
||||||
|
for await (const response of stream) {
|
||||||
|
responses.push(response);
|
||||||
|
}
|
||||||
|
expect(responses).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined sessionId gracefully', async () => {
|
||||||
|
const qwenConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'qwen-oauth',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue(undefined), // Undefined session ID
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const qwenGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'qwen-turbo',
|
||||||
|
qwenConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'qwen-turbo',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'qwen-turbo',
|
||||||
|
};
|
||||||
|
|
||||||
|
await qwenGenerator.generateContent(
|
||||||
|
request,
|
||||||
|
'undefined-session-prompt-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
metadata: {
|
||||||
|
sessionId: undefined,
|
||||||
|
promptId: 'undefined-session-prompt-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined baseURL gracefully', async () => {
|
||||||
|
// Ensure no base URL is set
|
||||||
|
vi.stubEnv('OPENAI_BASE_URL', '');
|
||||||
|
|
||||||
|
const noBaseUrlConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: 'openai',
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('no-base-url-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const noBaseUrlGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'gpt-4',
|
||||||
|
noBaseUrlConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
await noBaseUrlGenerator.generateContent(
|
||||||
|
request,
|
||||||
|
'no-base-url-prompt-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should NOT include metadata when baseURL is empty
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
metadata: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined authType gracefully', async () => {
|
||||||
|
const undefinedAuthConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
authType: undefined, // Undefined auth type
|
||||||
|
enableOpenAILogging: false,
|
||||||
|
}),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('undefined-auth-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const undefinedAuthGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'gpt-4',
|
||||||
|
undefinedAuthConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
await undefinedAuthGenerator.generateContent(
|
||||||
|
request,
|
||||||
|
'undefined-auth-prompt-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should NOT include metadata when authType is undefined
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
metadata: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined config gracefully', async () => {
|
||||||
|
const undefinedConfig = {
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue(undefined), // Undefined config
|
||||||
|
getSessionId: vi.fn().mockReturnValue('undefined-config-session-id'),
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const undefinedConfigGenerator = new OpenAIContentGenerator(
|
||||||
|
'test-key',
|
||||||
|
'gpt-4',
|
||||||
|
undefinedConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
id: 'chatcmpl-123',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: 'assistant', content: 'Response' },
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created: 1677652288,
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const request: GenerateContentParameters = {
|
||||||
|
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
||||||
|
model: 'gpt-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
await undefinedConfigGenerator.generateContent(
|
||||||
|
request,
|
||||||
|
'undefined-config-prompt-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should NOT include metadata when config is undefined
|
||||||
|
expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
metadata: expect.any(Object),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
FunctionCall,
|
FunctionCall,
|
||||||
FunctionResponse,
|
FunctionResponse,
|
||||||
} from '@google/genai';
|
} from '@google/genai';
|
||||||
import { ContentGenerator } from './contentGenerator.js';
|
import { AuthType, ContentGenerator } from './contentGenerator.js';
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
import { logApiResponse } from '../telemetry/loggers.js';
|
import { logApiResponse } from '../telemetry/loggers.js';
|
||||||
import { ApiResponseEvent } from '../telemetry/types.js';
|
import { ApiResponseEvent } from '../telemetry/types.js';
|
||||||
@@ -185,6 +185,46 @@ export class OpenAIContentGenerator implements ContentGenerator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if metadata should be included in the request.
|
||||||
|
* Only include the `metadata` field if the provider is QWEN_OAUTH
|
||||||
|
* or the baseUrl is 'https://dashscope.aliyuncs.com/compatible-mode/v1'.
|
||||||
|
* This is because some models/providers do not support metadata or need extra configuration.
|
||||||
|
*
|
||||||
|
* @returns true if metadata should be included, false otherwise
|
||||||
|
*/
|
||||||
|
private shouldIncludeMetadata(): boolean {
|
||||||
|
const authType = this.config.getContentGeneratorConfig?.()?.authType;
|
||||||
|
// baseUrl may be undefined; default to empty string if so
|
||||||
|
const baseUrl = this.client?.baseURL || '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
authType === AuthType.QWEN_OAUTH ||
|
||||||
|
baseUrl === 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build metadata object for OpenAI API requests.
|
||||||
|
*
|
||||||
|
* @param userPromptId The user prompt ID to include in metadata
|
||||||
|
* @returns metadata object if shouldIncludeMetadata() returns true, undefined otherwise
|
||||||
|
*/
|
||||||
|
private buildMetadata(
|
||||||
|
userPromptId: string,
|
||||||
|
): { metadata: { sessionId?: string; promptId: string } } | undefined {
|
||||||
|
if (!this.shouldIncludeMetadata()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
sessionId: this.config.getSessionId?.(),
|
||||||
|
promptId: userPromptId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async generateContent(
|
async generateContent(
|
||||||
request: GenerateContentParameters,
|
request: GenerateContentParameters,
|
||||||
userPromptId: string,
|
userPromptId: string,
|
||||||
@@ -205,10 +245,7 @@ export class OpenAIContentGenerator implements ContentGenerator {
|
|||||||
model: this.model,
|
model: this.model,
|
||||||
messages,
|
messages,
|
||||||
...samplingParams,
|
...samplingParams,
|
||||||
metadata: {
|
...(this.buildMetadata(userPromptId) || {}),
|
||||||
sessionId: this.config.getSessionId?.(),
|
|
||||||
promptId: userPromptId,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.config?.tools) {
|
if (request.config?.tools) {
|
||||||
@@ -339,10 +376,7 @@ export class OpenAIContentGenerator implements ContentGenerator {
|
|||||||
...samplingParams,
|
...samplingParams,
|
||||||
stream: true,
|
stream: true,
|
||||||
stream_options: { include_usage: true },
|
stream_options: { include_usage: true },
|
||||||
metadata: {
|
...(this.buildMetadata(userPromptId) || {}),
|
||||||
sessionId: this.config.getSessionId?.(),
|
|
||||||
promptId: userPromptId,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.config?.tools) {
|
if (request.config?.tools) {
|
||||||
|
|||||||
Reference in New Issue
Block a user