@@ -2,30 +2,36 @@ import {
2
2
ChatCompletionFunctions ,
3
3
ChatCompletionRequestMessage ,
4
4
ChatCompletionResponseMessage , ChatCompletionResponseMessageRoleEnum ,
5
- Configuration ,
5
+ Configuration , CreateChatCompletionRequest , CreateImageRequest ,
6
6
OpenAIApi
7
7
} from "openai" ;
8
+ import { openAILog as log } from "./logging"
9
+
8
10
import { PluginBase } from "./plugins/PluginBase" ;
9
11
import { AiResponse , MessageData } from "./types" ;
10
12
11
- const configuration = new Configuration ( {
12
- apiKey : process . env [ 'OPENAI_API_KEY' ]
13
- } )
13
+ const apiKey = process . env [ 'OPENAI_API_KEY' ] ;
14
+ log . trace ( { apiKey} )
15
+
16
+ const configuration = new Configuration ( { apiKey } )
17
+
14
18
const openai = new OpenAIApi ( configuration )
15
19
16
20
const model = process . env [ 'OPENAI_MODEL_NAME' ] ?? 'gpt-3.5-turbo'
17
21
const max_tokens = Number ( process . env [ 'OPENAI_MAX_TOKENS' ] ?? 2000 )
18
22
const temperature = Number ( process . env [ 'OPENAI_TEMPERATURE' ] ?? 1 )
19
23
20
- const plugins : Record < string , PluginBase < any > > = { }
24
+ log . debug ( { model, max_tokens, temperature} )
25
+
26
+ const plugins : Map < string , PluginBase < any > > = new Map ( )
21
27
const functions : ChatCompletionFunctions [ ] = [ ]
22
28
23
29
/**
24
30
* Registers a plugin as a GPT function. These functions are sent to openAI when the user interacts with chatGPT.
25
31
* @param plugin
26
32
*/
27
33
export function registerChatPlugin ( plugin : PluginBase < any > ) {
28
- plugins [ plugin . key ] = plugin
34
+ plugins . set ( plugin . key , plugin )
29
35
functions . push ( {
30
36
name : plugin . key ,
31
37
description : plugin . description ,
@@ -48,27 +54,52 @@ export async function continueThread(messages: ChatCompletionRequestMessage[], m
48
54
message : 'Sorry, but it seems I found no valid response.'
49
55
}
50
56
57
+ // the number of rounds we're going to run at maximum
58
+ let maxChainLength = 7 ;
59
+
60
+ // check whether ChatGPT hallucinates a plugin name.
61
+ const missingPlugins = new Set < string > ( )
62
+
51
63
let isIntermediateResponse = true
52
- while ( isIntermediateResponse ) {
64
+ while ( isIntermediateResponse && maxChainLength -- > 0 ) {
53
65
const responseMessage = await createChatCompletion ( messages , functions )
66
+ log . trace ( responseMessage )
54
67
if ( responseMessage ) {
55
68
// if the function_call is set, we have a plugin call
56
69
if ( responseMessage . function_call && responseMessage . function_call . name ) {
70
+ const pluginName = responseMessage . function_call . name ;
71
+ log . trace ( { pluginName} )
57
72
try {
58
- const pluginResponse = await plugins [ responseMessage . function_call ! . name ! ] . runPlugin ( ( JSON . parse ( responseMessage . function_call ! . arguments ! ) ) , msgData )
59
-
60
- if ( pluginResponse . intermediate ) {
61
- messages . push ( {
62
- role : ChatCompletionResponseMessageRoleEnum . Function ,
63
- name : responseMessage . function_call ! . name ! ,
64
- content : pluginResponse . message
65
- } )
66
- continue
67
- }
73
+ const plugin = plugins . get ( pluginName ) ;
74
+ if ( plugin ) {
75
+ const pluginArguments = JSON . parse ( responseMessage . function_call . arguments ?? '[]' ) ;
76
+ log . trace ( { plugin, pluginArguments} )
77
+ const pluginResponse = await plugin . runPlugin ( pluginArguments , msgData )
78
+ log . trace ( { pluginResponse} )
68
79
69
- aiResponse = pluginResponse
80
+ if ( pluginResponse . intermediate ) {
81
+ messages . push ( {
82
+ role : ChatCompletionResponseMessageRoleEnum . Function ,
83
+ name : pluginName ,
84
+ content : pluginResponse . message
85
+ } )
86
+ continue
87
+ }
88
+ aiResponse = pluginResponse
89
+ } else {
90
+ if ( ! missingPlugins . has ( pluginName ) ) {
91
+ missingPlugins . add ( pluginName )
92
+ log . debug ( { error : 'Missing plugin ' + pluginName , pluginArguments : responseMessage . function_call . arguments } )
93
+ messages . push ( { role : 'system' , content : `There is no plugin named '${ pluginName } ' available. Try without using that plugin.` } )
94
+ continue
95
+ } else {
96
+ log . debug ( { messages } )
97
+ aiResponse . message = `Sorry, but it seems there was an error when using the plugin \`\`\`${ pluginName } \`\`\`.`
98
+ }
99
+ }
70
100
} catch ( e ) {
71
- aiResponse . message = `Sorry, but it seems there was an error when using the plugin \`\`\`${ responseMessage . function_call ! . name ! } \`\`\`.`
101
+ log . debug ( { messages, error : e } )
102
+ aiResponse . message = `Sorry, but it seems there was an error when using the plugin \`\`\`${ pluginName } \`\`\`.`
72
103
}
73
104
} else if ( responseMessage . content ) {
74
105
aiResponse . message = responseMessage . content
@@ -87,18 +118,22 @@ export async function continueThread(messages: ChatCompletionRequestMessage[], m
87
118
* @param functions Function calls which can be called by the openAI model
88
119
*/
89
120
export async function createChatCompletion ( messages : ChatCompletionRequestMessage [ ] , functions : ChatCompletionFunctions [ ] | undefined = undefined ) : Promise < ChatCompletionResponseMessage | undefined > {
90
- const options : any = {
121
+ const chatCompletionOptions : CreateChatCompletionRequest = {
91
122
model : model ,
92
123
messages : messages ,
93
124
max_tokens : max_tokens ,
94
125
temperature : temperature ,
95
126
}
96
127
if ( functions ) {
97
- options . functions = functions
98
- options . function_call = 'auto'
128
+ chatCompletionOptions . functions = functions
129
+ chatCompletionOptions . function_call = 'auto'
99
130
}
100
131
101
- const chatCompletion = await openai . createChatCompletion ( options )
132
+ log . trace ( { chatCompletionOptions} )
133
+
134
+ const chatCompletion = await openai . createChatCompletion ( chatCompletionOptions )
135
+
136
+ log . trace ( { chatCompletion} )
102
137
103
138
return chatCompletion . data ?. choices ?. [ 0 ] ?. message
104
139
}
@@ -108,12 +143,14 @@ export async function createChatCompletion(messages: ChatCompletionRequestMessag
108
143
* @param prompt The image description provided to DALL-E.
109
144
*/
110
145
export async function createImage ( prompt : string ) : Promise < string | undefined > {
111
- const image = await openai . createImage ( {
112
- prompt : prompt ,
146
+ const createImageOptions : CreateImageRequest = {
147
+ prompt,
113
148
n : 1 ,
114
149
size : '512x512' ,
115
- response_format : "b64_json"
116
- } )
117
-
150
+ response_format : 'b64_json'
151
+ } ;
152
+ log . trace ( { createImageOptions} )
153
+ const image = await openai . createImage ( createImageOptions )
154
+ log . trace ( { image} )
118
155
return image . data ?. data [ 0 ] ?. b64_json
119
156
}
0 commit comments