Skip to content

Commit a86d732

Browse files
authored
Merge pull request #48 from traPtitech/feat/deepwiki-mcp-config
feat: deep wiki mcpを追加
2 parents 46c8373 + af2ae5d commit a86d732

File tree

3 files changed

+378
-62
lines changed

3 files changed

+378
-62
lines changed

internal/gpt/gpt.go

Lines changed: 121 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import (
99
"time"
1010

1111
"github.com/traPtitech/BOT_GPT/internal/bot"
12+
"github.com/traPtitech/BOT_GPT/internal/gpt/tooling"
1213
"github.com/traPtitech/BOT_GPT/internal/repository"
1314

1415
"github.com/openai/openai-go/v2"
1516
"github.com/openai/openai-go/v2/option"
17+
"github.com/openai/openai-go/v2/responses"
1618
)
1719

1820
type FinishReason int
@@ -26,15 +28,25 @@ const (
2628
var (
2729
blobs = [...]string{":blob_bongo:", ":blob_crazy_happy:", ":blob_grin:", ":blob_hype:", ":blob_love:", ":blob_lurk:", ":blob_pyon:", ":blob_pyon_inverse:", ":blob_slide:", ":blob_snowball_1:", ":blob_snowball_2:", ":blob_speedy_roll:", ":blob_speedy_roll_inverse:", ":blob_thinking:", ":blob_thinking_fast:", ":blob_thinking_portal:", ":blob_thinking_upsidedown:", ":blob_thonkang:", ":blob_thumbs_up:", ":blobblewobble:", ":blobenjoy:", ":blobglitch:", ":blobbass:", ":blobjam:", ":blobkeyboard:", ":bloblamp:", ":blobmaracas:", ":blobmicrophone:", ":blobthinksmart:", ":blobwobwork:", ":conga_party_thinking_blob:", ":Hyperblob:", ":party_blob:", ":partyparrot_blob:", ":partyparrot_blob_cat:"}
2830
amazed = [...]string{":amazed_fuzzy:", ":amazed_amazed_fuzzy:", ":amazed_god_enel:", ":amazed_hamutaro:"}
29-
blobsAndAmazed = append(blobs[:], amazed[:]...)
3031
warnings = [...]string{":warning:", ":ikura-hamu_shooting_warning:"}
3132
apiKey string
3233
baseURL string
33-
DefaultSystemRoleMessage = "あなたは日本の学生サークルである東京科学大学デジタル創作同好会traPの部内SNS「traQ」のユーザーを、楽しませる娯楽用途や勉強するための学習用途として、作られた対話型AIです。身内しかいないSNSで、ユーザーに緩く接してください。そして、ユーザーの言う事に出来る限り従うようにしてください。特定の指示がなければ、数式は\\[は使わずに$$で括った上で、\n - \\begin{align}(やequation,eqnarray,split等)は\\[は使わずに$$で括った上で、\\begin{aligned}を使う\n - \\newlineは\\\\等を使う\n - \\mboxは\\textを使う\n - \\(は使わずに$を使う\nようにしてください。"
34-
ChannelMessages = make(map[string][]Message)
34+
DefaultSystemRoleMessage = "あなたは日本の学生サークルである東京科学大学デジタル創作同好会traPの部内SNS「traQ」のユーザーを、楽しませる娯楽用途や勉強するための学習用途として、作られた対話型AIです。身内しかいないSNSで、ユーザーに緩く接してください。そして、ユーザーの言う事に出来る限り従うようにしてください。特定の指示がなければ、数式は\\[は使わずに$$で括った上で、\n - \\begin{align}(やequation,eqnarray,split等)は\\[は使わずに$$で括った上で、\\begin{aligned}を使う\n - \\newlineは\\\\等を使う\n - \\mboxは\\textを使う\n - \\(は使わずに$を使う\nようにしてください。"
35+
ChannelMessages = make(map[string]Message)
36+
toolProvider tooling.Provider = tooling.NewStaticProvider(tooling.DefaultSpecs())
3537
)
3638

37-
type Message = openai.ChatCompletionMessageParamUnion
39+
func SetToolProvider(p tooling.Provider) {
40+
if p == nil {
41+
toolProvider = tooling.NewStaticProvider(tooling.DefaultSpecs())
42+
43+
return
44+
}
45+
46+
toolProvider = p
47+
}
48+
49+
type Message = []responses.ResponseInputItemUnionParam
3850

3951
const SystemString = "FirstSystemMessageを変更しました。/gptsys showで確認できます。\nFirstSystemMessageとは、常に履歴の一番最初に入り、最初にgptに情報や状況を説明するのに使用する文字列です"
4052

@@ -51,11 +63,46 @@ func InitGPT() {
5163
if err != nil {
5264
log.Printf("Failed to load messages for channel %s: %v", channelID, err)
5365
// Initialize empty slice on error
54-
ChannelMessages[channelID] = make([]Message, 0)
66+
ChannelMessages[channelID] = make(Message, 0)
5567
} else {
56-
ChannelMessages[channelID] = messages
68+
// 保存されたメッセージをResponse API形式に変換
69+
convertedMessages := convertChatMessagesToResponseItems(messages)
70+
ChannelMessages[channelID] = convertedMessages
71+
}
72+
}
73+
}
74+
75+
// convertChatMessagesToResponseItems converts v2 ChatCompletionMessageParamUnion to Response API format
76+
func convertChatMessagesToResponseItems(messages []openai.ChatCompletionMessageParamUnion) []responses.ResponseInputItemUnionParam {
77+
var result []responses.ResponseInputItemUnionParam
78+
79+
for _, msg := range messages {
80+
if userMsg := msg.OfUser; userMsg != nil {
81+
if textContent := userMsg.Content.OfString; textContent.Valid() {
82+
result = append(result, responses.ResponseInputItemParamOfMessage(textContent.Value, "user"))
83+
} else if len(userMsg.Content.OfArrayOfContentParts) > 0 {
84+
var contentParams responses.ResponseInputMessageContentListParam
85+
for _, part := range userMsg.Content.OfArrayOfContentParts {
86+
if textPart := part.OfText; textPart != nil {
87+
contentParams = append(contentParams, responses.ResponseInputContentParamOfInputText(textPart.Text))
88+
} else if imagePart := part.OfImageURL; imagePart != nil {
89+
contentParams = append(contentParams, responses.ResponseInputContentParamOfInputText("[image]"))
90+
}
91+
}
92+
result = append(result, responses.ResponseInputItemParamOfMessage(contentParams, "user"))
93+
}
94+
} else if assistantMsg := msg.OfAssistant; assistantMsg != nil {
95+
if textContent := assistantMsg.Content.OfString; textContent.Valid() {
96+
result = append(result, responses.ResponseInputItemParamOfMessage(textContent.Value, "assistant"))
97+
}
98+
} else if systemMsg := msg.OfSystem; systemMsg != nil {
99+
if textContent := systemMsg.Content.OfString; textContent.Valid() {
100+
result = append(result, responses.ResponseInputItemParamOfMessage(textContent.Value, "system"))
101+
}
57102
}
58103
}
104+
105+
return result
59106
}
60107

61108
func getAPIKey() string {
@@ -84,68 +131,71 @@ func getRandomAmazed() string {
84131
return amazed[rand.Intn(len(amazed))]
85132
}
86133

87-
func getRandomBlobAndAmazed() string {
88-
return blobsAndAmazed[rand.Intn(len(blobsAndAmazed))]
89-
}
90-
91134
func getRandomWarning() string {
92135
return warnings[rand.Intn(len(warnings))]
93136
}
94137

95-
func OpenAIStream(messages []Message, model string, do func(string)) (responseMessage string, finishReason FinishReason, err error) {
138+
func OpenAIStream(messages Message, model string, do func(string)) (responseMessage string, finishReason FinishReason, err error) {
96139
c := openai.NewClient(
97140
option.WithAPIKey(apiKey),
98141
option.WithBaseURL(baseURL),
99142
)
100143
ctx := context.Background()
101144

102-
req := openai.ChatCompletionNewParams{
103-
Model: openai.ChatModel(model),
104-
Messages: messages,
145+
tools, toolErr := toolProvider.Tools(ctx)
146+
if toolErr != nil {
147+
return "", errorHappen, fmt.Errorf("resolve tools: %w", toolErr)
148+
}
149+
150+
// Response APIで全メッセージ履歴を使用
151+
req := responses.ResponseNewParams{
152+
Model: openai.ChatModel(model),
153+
Input: responses.ResponseNewParamsInputUnion{
154+
OfInputItemList: responses.ResponseInputParam(messages),
155+
},
156+
Tools: tools,
105157
}
106-
stream := c.Chat.Completions.NewStreaming(ctx, req)
158+
stream := c.Responses.NewStreaming(ctx, req)
107159
if stream.Err() != nil {
108160
return "", errorHappen, stream.Err()
109161
}
110162
defer stream.Close()
111163

112-
deltaTime := 500 * time.Millisecond
113-
lastDoTime := time.Now()
114164
for stream.Next() {
115-
response := stream.Current()
165+
ev := stream.Current()
116166
if stream.Err() != nil {
117167
do(responseMessage + getRandomWarning() + ":blobglitch: Error: " + fmt.Sprint(stream.Err()))
118168
finishReason = errorHappen
119169

120170
break
121171
}
122172

123-
if len(response.Choices) > 0 {
124-
choice := response.Choices[0]
125-
126-
if choice.FinishReason != "" {
127-
if choice.FinishReason == "stop" {
128-
time.Sleep(200 * time.Millisecond)
129-
do(responseMessage)
130-
finishReason = stop
131-
132-
break
133-
} else if choice.FinishReason == "length" {
134-
do(responseMessage + "\n" + getRandomAmazed() + "トークン(履歴を含む文字数)が上限に達しました。/resetを実行してください。")
135-
finishReason = length
136-
137-
break
138-
}
139-
}
140-
141-
if choice.Delta.Content != "" {
142-
responseMessage += choice.Delta.Content
173+
switch ev.Type {
174+
case "response.output_text.delta":
175+
textDelta := ev.AsResponseOutputTextDelta()
176+
responseMessage += textDelta.Delta
177+
do(responseMessage)
178+
case "response.completed":
179+
finishReason = stop
180+
do(responseMessage)
181+
case "response.failed":
182+
failedEvent := ev.AsResponseFailed()
183+
do(responseMessage + getRandomAmazed() + ":blobglitch: Failed: " + failedEvent.Response.Error.Message)
184+
finishReason = errorHappen
185+
case "response.incomplete":
186+
incompleteEvent := ev.AsResponseIncomplete()
187+
// トークン上限の場合 (max_output_tokensはトークン上限を示す)
188+
if incompleteEvent.Response.IncompleteDetails.Reason == "max_output_tokens" {
189+
do(responseMessage + "\n" + getRandomAmazed() + "トークン(履歴を含む文字数)が上限に達しました。/resetを実行してください。")
190+
finishReason = length
191+
} else {
192+
do(responseMessage + getRandomWarning() + ":blobglitch: Incomplete: " + incompleteEvent.Response.IncompleteDetails.Reason)
193+
finishReason = errorHappen
143194
}
144-
}
145-
146-
if time.Since(lastDoTime) >= deltaTime {
147-
lastDoTime = time.Now()
148-
do(getRandomBlobAndAmazed() + responseMessage + ":loading:")
195+
case "error":
196+
errorEvent := ev.AsError()
197+
do(responseMessage + getRandomWarning() + ":blobglitch: Error: " + errorEvent.Message)
198+
finishReason = errorHappen
149199
}
150200
}
151201

@@ -160,7 +210,7 @@ func OpenAIStream(messages []Message, model string, do func(string)) (responseMe
160210
func Chat(channelID, newMessageText string, imageBase64 []string) {
161211
_, exist := ChannelMessages[channelID]
162212
if !exist {
163-
ChannelMessages[channelID] = make([]Message, 0)
213+
ChannelMessages[channelID] = make(Message, 0)
164214
}
165215
addSystemMessageIfNotExist(channelID, DefaultSystemRoleMessage)
166216

@@ -210,7 +260,7 @@ func ChatShowSystemMessage(channelID string) {
210260

211261
func ChatReset(channelID string) {
212262
msg := bot.PostMessage(channelID, ":blobnom::loading:")
213-
ChannelMessages[channelID] = make([]Message, 0)
263+
ChannelMessages[channelID] = make(Message, 0)
214264
err := bot.EditMessageWithErr(msg.Id, ":done:")
215265
if err != nil {
216266
bot.EditMessage(msg.Id, "Error: "+fmt.Sprint(err))
@@ -222,16 +272,29 @@ func ChatReset(channelID string) {
222272
}
223273

224274
func addMessageAsUser(channelID, message string) {
225-
newMessage := openai.UserMessage(message)
226-
ChannelMessages[channelID] = append(ChannelMessages[channelID], newMessage)
275+
userMessage := responses.ResponseInputItemParamOfMessage(message, "user")
276+
ChannelMessages[channelID] = append(ChannelMessages[channelID], userMessage)
227277

228278
index := len(ChannelMessages[channelID]) - 1
229-
if err := repository.SaveMessage(channelID, index, newMessage); err != nil {
279+
if err := repository.SaveMessage(channelID, index, openai.UserMessage(message)); err != nil {
230280
fmt.Printf("Failed to save user message: %v\n", err)
231281
}
232282
}
233283

234284
func addImageAndTextAsUser(channelID, message string, imageDataBase64 []string) {
285+
// 現時点では画像もテキストとして扱う
286+
var content responses.ResponseInputMessageContentListParam
287+
content = append(content, responses.ResponseInputContentParamOfInputText(message))
288+
if len(imageDataBase64) > 0 {
289+
imageText := fmt.Sprintf("[%d images attached]", len(imageDataBase64))
290+
content = append(content, responses.ResponseInputContentParamOfInputText(imageText))
291+
}
292+
293+
userMessage := responses.ResponseInputItemParamOfMessage(content, "user")
294+
ChannelMessages[channelID] = append(ChannelMessages[channelID], userMessage)
295+
296+
index := len(ChannelMessages[channelID]) - 1
297+
// repository用に旧形式で保存
235298
var parts []openai.ChatCompletionContentPartUnionParam
236299

237300
parts = append(parts, openai.TextContentPart(message))
@@ -243,39 +306,35 @@ func addImageAndTextAsUser(channelID, message string, imageDataBase64 []string)
243306
})
244307
parts = append(parts, imagePart)
245308
}
246-
247-
newMessage := openai.UserMessage(parts)
248-
ChannelMessages[channelID] = append(ChannelMessages[channelID], newMessage)
249-
250-
index := len(ChannelMessages[channelID]) - 1
251-
if err := repository.SaveMessage(channelID, index, newMessage); err != nil {
309+
if err := repository.SaveMessage(channelID, index, openai.UserMessage(parts)); err != nil {
252310
fmt.Printf("Failed to save image message: %v\n", err)
253311
}
254312
}
255313

256314
func addMessageAsAssistant(channelID, message string) {
257-
newMessage := openai.AssistantMessage(message)
258-
ChannelMessages[channelID] = append(ChannelMessages[channelID], newMessage)
315+
assistantMessage := responses.ResponseInputItemParamOfMessage(message, "assistant")
316+
ChannelMessages[channelID] = append(ChannelMessages[channelID], assistantMessage)
259317

260318
index := len(ChannelMessages[channelID]) - 1
261-
if err := repository.SaveMessage(channelID, index, newMessage); err != nil {
319+
if err := repository.SaveMessage(channelID, index, openai.AssistantMessage(message)); err != nil {
262320
fmt.Printf("Failed to save assistant message: %v\n", err)
263321
}
264322
}
265323

266324
func addSystemMessageIfNotExist(channelID, message string) {
325+
// システムメッセージが既に存在するかチェック
267326
for _, msg := range ChannelMessages[channelID] {
268-
if msg.OfSystem != nil {
327+
if msg.GetRole() != nil && *msg.GetRole() == "system" {
269328
return
270329
}
271330
}
272331

273-
systemMessage := openai.SystemMessage(message)
274-
275-
ChannelMessages[channelID] = append([]Message{systemMessage}, ChannelMessages[channelID]...)
332+
// システムメッセージが存在しない場合のみ先頭に追加
333+
systemMessage := responses.ResponseInputItemParamOfMessage(message, "system")
334+
ChannelMessages[channelID] = append([]responses.ResponseInputItemUnionParam{systemMessage}, ChannelMessages[channelID]...)
276335

277336
index := 0
278-
if err := repository.SaveMessage(channelID, index, systemMessage); err != nil {
337+
if err := repository.SaveMessage(channelID, index, openai.SystemMessage(message)); err != nil {
279338
fmt.Printf("Failed to save system message: %v\n", err)
280339
}
281340
}

0 commit comments

Comments
 (0)