forked from coderabbitai/ai-pr-reviewer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbot.ts
128 lines (117 loc) · 3.56 KB
/
bot.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import './fetch-polyfill'
import {info, setFailed, warning} from '@actions/core'
import {
ChatGPTAPI,
ChatGPTError,
ChatMessage,
SendMessageOptions
// eslint-disable-next-line import/no-unresolved
} from 'chatgpt'
import pRetry from 'p-retry'
import {OpenAIOptions, Options} from './options'
// define type to save parentMessageId and conversationId
export interface Ids {
parentMessageId?: string
conversationId?: string
}
export class Bot {
private readonly api: ChatGPTAPI | null = null // not free
private readonly options: Options
constructor(options: Options, openaiOptions: OpenAIOptions) {
this.options = options
if (process.env.OPENAI_API_KEY) {
const currentDate = new Date().toISOString().split('T')[0]
const systemMessage = `${options.systemMessage}
Knowledge cutoff: ${openaiOptions.tokenLimits.knowledgeCutOff}
Current date: ${currentDate}
IMPORTANT: Entire response must be in the language with ISO code: ${options.language}
`
this.api = new ChatGPTAPI({
apiBaseUrl: options.apiBaseUrl,
systemMessage,
apiKey: process.env.OPENAI_API_KEY,
apiOrg: process.env.OPENAI_API_ORG ?? undefined,
debug: options.debug,
maxModelTokens: openaiOptions.tokenLimits.maxTokens,
maxResponseTokens: openaiOptions.tokenLimits.responseTokens,
completionParams: {
temperature: options.openaiModelTemperature,
model: openaiOptions.model
}
})
} else {
const err =
"Unable to initialize the OpenAI API, both 'OPENAI_API_KEY' environment variable are not available"
throw new Error(err)
}
}
chat = async (message: string, ids: Ids): Promise<[string, Ids]> => {
let res: [string, Ids] = ['', {}]
try {
res = await this.chat_(message, ids)
return res
} catch (e: unknown) {
if (e instanceof ChatGPTError) {
warning(`Failed to chat: ${e}, backtrace: ${e.stack}`)
}
return res
}
}
private readonly chat_ = async (
message: string,
ids: Ids
): Promise<[string, Ids]> => {
// record timing
const start = Date.now()
if (!message) {
return ['', {}]
}
let response: ChatMessage | undefined
if (this.api != null) {
const opts: SendMessageOptions = {
timeoutMs: this.options.openaiTimeoutMS
}
if (ids.parentMessageId) {
opts.parentMessageId = ids.parentMessageId
}
try {
response = await pRetry(() => this.api!.sendMessage(message, opts), {
retries: this.options.openaiRetries
})
} catch (e: unknown) {
if (e instanceof ChatGPTError) {
info(
`response: ${response}, failed to send message to openai: ${e}, backtrace: ${e.stack}`
)
}
}
const end = Date.now()
info(`response: ${JSON.stringify(response)}`)
info(
`openai sendMessage (including retries) response time: ${
end - start
} ms`
)
} else {
setFailed('The OpenAI API is not initialized')
}
let responseText = ''
if (response != null) {
responseText = response.text
} else {
warning('openai response is null')
}
// remove the prefix "with " in the response
if (responseText.startsWith('with ')) {
responseText = responseText.substring(5)
}
if (this.options.debug) {
info(`openai responses: ${responseText}`)
}
const newIds: Ids = {
parentMessageId: response?.id,
conversationId: response?.conversationId
}
return [responseText, newIds]
}
}