-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from ferrislucas/use-system-messages
Use system messages to shape the model output
- Loading branch information
Showing
9 changed files
with
231 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export default class SystemMessage { | ||
|
||
static systemMessages() { | ||
return [{ role: "system", content: `You are a creative and helpful software engineer. | ||
Your response should be entirely valid json with no other content outside of the json. | ||
Do not include file contents or any other words before or after the json. | ||
Do not respond with anything but json. | ||
The json should be an object with an "operations" key. | ||
The "operations" key should be an array of objects. | ||
Each object should represent a file that should be created, updated, or deleted. | ||
Each object should have three keys: "crudOperation", "filePath", and "fileContents". | ||
The "crudOperation" value should contain the operation that you would like to perform for the given file. The "crudOperation" value should be "create", "update", or "delete". | ||
The "filePath" value should contain the path to the file. | ||
The "fileContents" value should be the contents of the file if the file is being created or updated - if the file is being deleted then the "fileContents" key can be omitted. | ||
Make sure that the "fileContents" value is delimitted correctly as a json string. | ||
Only include changed files in your response. | ||
Don't abbreviate file contents - include the whole file for the "fileContents" value.` }] | ||
} | ||
|
||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import assert from 'assert'; | ||
import sinon from 'sinon'; | ||
import OpenAiGptService from '../src/services/OpenAiGptService.js'; | ||
import { Configuration, OpenAIApi } from 'openai'; | ||
import ConfigService from '../src/services/configService.js'; | ||
import CliState from '../src/cliState.js'; | ||
import SystemMessage from '../src/services/SystemMessage.js'; | ||
|
||
describe('OpenAiGptService', () => { | ||
beforeEach(() => { | ||
CliState.init([], '') | ||
}); | ||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
it('should return the result from the model', async () => { | ||
const prompt = 'What is the capital of France?'; | ||
const expectedResult = 'The capital of France is Paris.'; | ||
const model = 'gpt-4'; | ||
|
||
const configStub = sinon.stub(ConfigService, 'retrieveConfig').resolves({ api: { temperature: 0.5 } }); | ||
const openaiStub = sinon.stub(OpenAIApi.prototype, 'createChatCompletion').resolves({ | ||
data: { | ||
choices: [ | ||
{ message: { content: expectedResult } } | ||
] | ||
} | ||
}); | ||
|
||
const result = await OpenAiGptService.call(prompt, model); | ||
|
||
assert.strictEqual(result, expectedResult); | ||
sinon.assert.calledOnce(configStub); | ||
sinon.assert.calledOnce(openaiStub); | ||
}); | ||
|
||
it('should return null when the response does not contain choices', async () => { | ||
const prompt = 'What is the capital of France?'; | ||
const model = 'gpt-4'; | ||
|
||
const configStub = sinon.stub(ConfigService, 'retrieveConfig').resolves({ api: { temperature: 0.5 } }); | ||
const openaiStub = sinon.stub(OpenAIApi.prototype, 'createChatCompletion').resolves({ | ||
data: {} | ||
}); | ||
|
||
const result = await OpenAiGptService.call(prompt, model); | ||
|
||
assert.strictEqual(result, null); | ||
sinon.assert.calledOnce(configStub); | ||
sinon.assert.calledOnce(openaiStub); | ||
}); | ||
|
||
it('should append system messages in the call to openai.createChatCompletion when requestJsonOutput is true', async () => { | ||
const prompt = 'What is the capital of France?'; | ||
const expectedResult = 'The capital of France is Paris.'; | ||
const model = 'gpt-4'; | ||
const configStub = sinon.stub(ConfigService, 'retrieveConfig').resolves({ api: { temperature: 0.5 } }) | ||
const openaiStub = sinon.stub(OpenAIApi.prototype, 'createChatCompletion').resolves({ | ||
data: { | ||
choices: [ | ||
{ message: { content: expectedResult } } | ||
] | ||
} | ||
}); | ||
|
||
await OpenAiGptService.call(prompt, model, true); | ||
|
||
sinon.assert.calledOnce(configStub); | ||
sinon.assert.calledWith(openaiStub, sinon.match({ | ||
messages: sinon.match.array.deepEquals([ | ||
{ role: 'user', content: prompt }, | ||
...SystemMessage.systemMessages() | ||
]) | ||
})); | ||
}); | ||
|
||
it('should not append system messages in the call to openai.createChatCompletion when requestJsonOutput is false', async () => { | ||
const prompt = 'What is the capital of France?'; | ||
const expectedResult = 'The capital of France is Paris.'; | ||
const model = 'gpt-4'; | ||
const configStub = sinon.stub(ConfigService, 'retrieveConfig').resolves({ api: { temperature: 0.5 } }) | ||
const openaiStub = sinon.stub(OpenAIApi.prototype, 'createChatCompletion').resolves({ | ||
data: { | ||
choices: [ | ||
{ message: { content: expectedResult } } | ||
] | ||
} | ||
}); | ||
|
||
await OpenAiGptService.call(prompt, model, false); | ||
|
||
sinon.assert.calledOnce(configStub); | ||
sinon.assert.calledWith(openaiStub, sinon.match({ | ||
messages: sinon.match.array.deepEquals([ | ||
{ role: 'user', content: prompt } | ||
]) | ||
})); | ||
}); | ||
|
||
it('should pass the correct model value to openai.createChatCompletion', async () => { | ||
const prompt = 'What is the capital of France?'; | ||
const expectedResult = 'The capital of France is Paris.'; | ||
const models = ['gpt3', 'gpt4']; | ||
const expectedModels = ['gpt-3.5-turbo', 'gpt-4']; | ||
|
||
const configStub = sinon.stub(ConfigService, 'retrieveConfig').resolves({ api: { temperature: 0.5 } }); | ||
const openaiStub = sinon.stub(OpenAIApi.prototype, 'createChatCompletion').resolves({ | ||
data: { | ||
choices: [ | ||
{ message: { content: expectedResult } } | ||
] | ||
} | ||
}); | ||
|
||
for (let i = 0; i < models.length; i++) { | ||
await OpenAiGptService.call(prompt, models[i]); | ||
sinon.assert.calledWith(openaiStub.getCall(i), sinon.match({ model: expectedModels[i] })); | ||
} | ||
|
||
sinon.assert.callCount(openaiStub, models.length); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.