@@ -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
1820type FinishReason int
@@ -26,15 +28,25 @@ const (
2628var (
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
3951const SystemString = "FirstSystemMessageを変更しました。/gptsys showで確認できます。\n FirstSystemMessageとは、常に履歴の一番最初に入り、最初に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
61108func 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-
91134func 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
160210func 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
211261func 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
224274func 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
234284func 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
256314func 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
266324func 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