Update openai package and use developer role message for o1

This commit is contained in:
Matt Rubens
2025-01-11 00:05:42 -05:00
parent 15513f4031
commit 6e90bcf4a3
4 changed files with 133 additions and 21 deletions

8
package-lock.json generated
View File

@@ -31,7 +31,7 @@
"isbinaryfile": "^5.0.2", "isbinaryfile": "^5.0.2",
"mammoth": "^1.8.0", "mammoth": "^1.8.0",
"monaco-vscode-textmate-theme-converter": "^0.1.7", "monaco-vscode-textmate-theme-converter": "^0.1.7",
"openai": "^4.73.1", "openai": "^4.78.1",
"os-name": "^6.0.0", "os-name": "^6.0.0",
"p-wait-for": "^5.0.2", "p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
@@ -12546,9 +12546,9 @@
} }
}, },
"node_modules/openai": { "node_modules/openai": {
"version": "4.76.0", "version": "4.78.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.76.0.tgz", "resolved": "https://registry.npmjs.org/openai/-/openai-4.78.1.tgz",
"integrity": "sha512-QBGIetjX1C9xDp5XGa/3mPnfKI9BgAe2xHQX6PmO98wuW9qQaurBaumcYptQWc9LHZZq7cH/Y1Rjnsr6uUDdVw==", "integrity": "sha512-drt0lHZBd2lMyORckOXFPQTmnGLWSLt8VK0W9BhOKWpMFBEoHMoz5gxMPmVq5icp+sOrsbMnsmZTVHUlKvD1Ow==",
"dependencies": { "dependencies": {
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4", "@types/node-fetch": "^2.6.4",

View File

@@ -227,7 +227,7 @@
"isbinaryfile": "^5.0.2", "isbinaryfile": "^5.0.2",
"mammoth": "^1.8.0", "mammoth": "^1.8.0",
"monaco-vscode-textmate-theme-converter": "^0.1.7", "monaco-vscode-textmate-theme-converter": "^0.1.7",
"openai": "^4.73.1", "openai": "^4.78.1",
"os-name": "^6.0.0", "os-name": "^6.0.0",
"p-wait-for": "^5.0.2", "p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",

View File

@@ -60,6 +60,13 @@ jest.mock('openai', () => {
describe('OpenAiNativeHandler', () => { describe('OpenAiNativeHandler', () => {
let handler: OpenAiNativeHandler; let handler: OpenAiNativeHandler;
let mockOptions: ApiHandlerOptions; let mockOptions: ApiHandlerOptions;
const systemPrompt = 'You are a helpful assistant.';
const messages: Anthropic.Messages.MessageParam[] = [
{
role: 'user',
content: 'Hello!'
}
];
beforeEach(() => { beforeEach(() => {
mockOptions = { mockOptions = {
@@ -86,14 +93,6 @@ describe('OpenAiNativeHandler', () => {
}); });
describe('createMessage', () => { describe('createMessage', () => {
const systemPrompt = 'You are a helpful assistant.';
const messages: Anthropic.Messages.MessageParam[] = [
{
role: 'user',
content: 'Hello!'
}
];
it('should handle streaming responses', async () => { it('should handle streaming responses', async () => {
const stream = handler.createMessage(systemPrompt, messages); const stream = handler.createMessage(systemPrompt, messages);
const chunks: any[] = []; const chunks: any[] = [];
@@ -109,15 +108,126 @@ describe('OpenAiNativeHandler', () => {
it('should handle API errors', async () => { it('should handle API errors', async () => {
mockCreate.mockRejectedValueOnce(new Error('API Error')); mockCreate.mockRejectedValueOnce(new Error('API Error'));
const stream = handler.createMessage(systemPrompt, messages); const stream = handler.createMessage(systemPrompt, messages);
await expect(async () => { await expect(async () => {
for await (const chunk of stream) { for await (const chunk of stream) {
// Should not reach here // Should not reach here
} }
}).rejects.toThrow('API Error'); }).rejects.toThrow('API Error');
}); });
it('should handle missing content in response for o1 model', async () => {
// Use o1 model which supports developer role
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: 'o1'
});
mockCreate.mockResolvedValueOnce({
choices: [{ message: { content: null } }],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
});
const generator = handler.createMessage(systemPrompt, messages);
const results = [];
for await (const result of generator) {
results.push(result);
}
expect(results).toEqual([
{ type: 'text', text: '' },
{ type: 'usage', inputTokens: 0, outputTokens: 0 }
]);
// Verify developer role is used for system prompt with o1 model
expect(mockCreate).toHaveBeenCalledWith({
model: 'o1',
messages: [
{ role: 'developer', content: systemPrompt },
{ role: 'user', content: 'Hello!' }
]
});
});
});
describe('streaming models', () => {
beforeEach(() => {
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: 'gpt-4o',
});
});
it('should handle streaming response', async () => {
const mockStream = [
{ choices: [{ delta: { content: 'Hello' } }], usage: null },
{ choices: [{ delta: { content: ' there' } }], usage: null },
{ choices: [{ delta: { content: '!' } }], usage: { prompt_tokens: 10, completion_tokens: 5 } },
];
mockCreate.mockResolvedValueOnce(
(async function* () {
for (const chunk of mockStream) {
yield chunk;
}
})()
);
const generator = handler.createMessage(systemPrompt, messages);
const results = [];
for await (const result of generator) {
results.push(result);
}
expect(results).toEqual([
{ type: 'text', text: 'Hello' },
{ type: 'text', text: ' there' },
{ type: 'text', text: '!' },
{ type: 'usage', inputTokens: 10, outputTokens: 5 },
]);
expect(mockCreate).toHaveBeenCalledWith({
model: 'gpt-4o',
temperature: 0,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: 'Hello!' },
],
stream: true,
stream_options: { include_usage: true },
});
});
it('should handle empty delta content', async () => {
const mockStream = [
{ choices: [{ delta: {} }], usage: null },
{ choices: [{ delta: { content: null } }], usage: null },
{ choices: [{ delta: { content: 'Hello' } }], usage: { prompt_tokens: 10, completion_tokens: 5 } },
];
mockCreate.mockResolvedValueOnce(
(async function* () {
for (const chunk of mockStream) {
yield chunk;
}
})()
);
const generator = handler.createMessage(systemPrompt, messages);
const results = [];
for await (const result of generator) {
results.push(result);
}
expect(results).toEqual([
{ type: 'text', text: 'Hello' },
{ type: 'usage', inputTokens: 10, outputTokens: 5 },
]);
});
}); });
describe('completePrompt', () => { describe('completePrompt', () => {

View File

@@ -23,14 +23,16 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler
} }
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
switch (this.getModel().id) { const modelId = this.getModel().id
switch (modelId) {
case "o1": case "o1":
case "o1-preview": case "o1-preview":
case "o1-mini": { case "o1-mini": {
// o1 doesnt support streaming, non-1 temp, or system prompt // o1-preview and o1-mini don't support streaming, non-1 temp, or system prompt
// o1 doesnt support streaming or non-1 temp but does support a developer prompt
const response = await this.client.chat.completions.create({ const response = await this.client.chat.completions.create({
model: this.getModel().id, model: modelId,
messages: [{ role: "user", content: systemPrompt }, ...convertToOpenAiMessages(messages)], messages: [{ role: modelId === "o1" ? "developer" : "user", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
}) })
yield { yield {
type: "text", type: "text",
@@ -93,7 +95,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler
case "o1": case "o1":
case "o1-preview": case "o1-preview":
case "o1-mini": case "o1-mini":
// o1 doesn't support non-1 temp or system prompt // o1 doesn't support non-1 temp
requestOptions = { requestOptions = {
model: modelId, model: modelId,
messages: [{ role: "user", content: prompt }] messages: [{ role: "user", content: prompt }]