-
Notifications
You must be signed in to change notification settings - Fork 2
/
aicontext.go
179 lines (154 loc) · 4.92 KB
/
aicontext.go
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package labeler
import (
"fmt"
"strings"
"github.com/ammario/prefixsuffix"
"github.com/google/go-github/v59/github"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
"github.com/tiktoken-go/tokenizer"
)
// aiContext contains and generates the GPT-4 aiContext used for label generation.
type aiContext struct {
allLabels []*github.Label
lastIssues []*github.Issue
targetIssue *github.Issue
}
func issueToText(issue *github.Issue) string {
var sb strings.Builder
fmt.Fprintf(&sb, "=== ISSUE %v ===\n", issue.GetNumber())
fmt.Fprintf(&sb, "author: %s (%s)\n", issue.GetUser().GetLogin(), issue.GetAuthorAssociation())
var labels []string
for _, label := range issue.Labels {
labels = append(labels, label.GetName())
}
fmt.Fprintf(&sb, "labels: %s\n", labels)
sb.WriteString("title: " + issue.GetTitle())
sb.WriteString("\n")
saver := prefixsuffix.Saver{
// Max 1000 characters per issue.
N: 500,
}
saver.Write([]byte(issue.GetBody()))
sb.Write(saver.Bytes())
fmt.Fprintf(&sb, "\n=== END ISSUE %v ===\n", issue.GetNumber())
return sb.String()
}
func countTokens(msgs ...openai.ChatCompletionMessage) int {
enc, err := tokenizer.Get(tokenizer.Cl100kBase)
if err != nil {
panic("oh oh")
}
var tokens int
for _, msg := range msgs {
ts, _, _ := enc.Encode(msg.Content)
tokens += len(ts)
for _, call := range msg.ToolCalls {
ts, _, _ = enc.Encode(call.Function.Arguments)
tokens += len(ts)
}
}
return tokens
}
// magicDisableString is deprecated as the original recommendation
// for disabling inscriptive labels.
const magicDisableString = "Only humans may set this"
// Request generates the messages to be used in the GPT-4 context.
func (c *aiContext) Request(
model string,
) openai.ChatCompletionRequest {
var labelsDescription strings.Builder
for _, label := range c.allLabels {
labelsDescription.WriteString(label.GetName())
labelsDescription.WriteString(": ")
labelsDescription.WriteString(label.GetDescription())
labelsDescription.WriteString("\n")
}
const labelFuncName = "setLabels"
request := openai.ChatCompletionRequest{
Model: model,
// We use LogProbs to determine level of confidence.
LogProbs: true,
// Want high determinism.
Temperature: 0,
ResponseFormat: &openai.ChatCompletionResponseFormat{
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{
Name: labelFuncName,
Description: `Label the GitHub issue with the given labels.`,
Schema: jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"reasoning": {
Description: "The reasoning for the labels. Maximum of one sentence per label.",
Type: jsonschema.String,
},
"labels": {
Type: jsonschema.Array,
Items: &jsonschema.Definition{Type: jsonschema.String},
},
},
Required: []string{"reasoning", "labels"},
AdditionalProperties: false,
},
Strict: true,
},
},
}
constructMsgs:
var msgs []openai.ChatCompletionMessage
// System message with instructions
msgs = append(msgs, openai.ChatCompletionMessage{
Role: "system",
Content: `You are a bot that helps label issues on GitHub using the "setLabels"
function. Do not apply labels that are meant for Pull Requests. Avoid applying labels when
the label description says something like "` + magicDisableString + `".
Only apply labels when absolutely certain they are correct. An accidental
omission of a label is better than an accidental addition.
Multiple labels can be applied to a single issue if appropriate.`,
})
// System message with label descriptions
msgs = append(msgs,
openai.ChatCompletionMessage{
Role: "system",
Content: "The labels available are: \n" + labelsDescription.String(),
},
)
// Create a single blob of past issues
var pastIssuesBlob strings.Builder
pastIssuesBlob.WriteString("Here are some examples of past issues and their labels:\n\n")
for _, issue := range c.lastIssues {
pastIssuesBlob.WriteString(issueToText(issue))
pastIssuesBlob.WriteString("\n\n")
}
// Add past issues blob as a system message
msgs = append(msgs, openai.ChatCompletionMessage{
Role: "system",
Content: pastIssuesBlob.String(),
})
// Add the target issue
msgs = append(msgs, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: issueToText(c.targetIssue),
})
modelTokenLimit := 128000
// Check token limit and adjust if necessary
if countTokens(msgs...) > modelTokenLimit && len(c.lastIssues) > 1 {
// Reduce the number of past issues
c.lastIssues = c.lastIssues[:len(c.lastIssues)/2]
goto constructMsgs
}
request.Messages = msgs
return request
}
func tokenize(text string) []string {
enc, err := tokenizer.Get(tokenizer.Cl100kBase)
if err != nil {
panic(err)
}
_, strs, err := enc.Encode(text)
if err != nil {
panic(err)
}
return strs
}