Skip to content

Commit f2ffa64

Browse files
Merge pull request #1556 from ksylvan/0628-migrate-to-official-openai-go
2 parents aa028a4 + 09e01ed commit f2ffa64

File tree

27 files changed

+343
-268
lines changed

27 files changed

+343
-268
lines changed

chat/chat.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package chat
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
)
7+
8+
const (
9+
ChatMessageRoleSystem = "system"
10+
ChatMessageRoleUser = "user"
11+
ChatMessageRoleAssistant = "assistant"
12+
ChatMessageRoleFunction = "function"
13+
ChatMessageRoleTool = "tool"
14+
ChatMessageRoleDeveloper = "developer"
15+
)
16+
17+
var ErrContentFieldsMisused = errors.New("can't use both Content and MultiContent properties simultaneously")
18+
19+
type ChatMessagePartType string
20+
21+
const (
22+
ChatMessagePartTypeText ChatMessagePartType = "text"
23+
ChatMessagePartTypeImageURL ChatMessagePartType = "image_url"
24+
)
25+
26+
type ChatMessageImageURL struct {
27+
URL string `json:"url,omitempty"`
28+
}
29+
30+
type ChatMessagePart struct {
31+
Type ChatMessagePartType `json:"type,omitempty"`
32+
Text string `json:"text,omitempty"`
33+
ImageURL *ChatMessageImageURL `json:"image_url,omitempty"`
34+
}
35+
36+
type FunctionCall struct {
37+
Name string `json:"name,omitempty"`
38+
Arguments string `json:"arguments,omitempty"`
39+
}
40+
41+
type ToolType string
42+
43+
const (
44+
ToolTypeFunction ToolType = "function"
45+
)
46+
47+
type ToolCall struct {
48+
Index *int `json:"index,omitempty"`
49+
ID string `json:"id,omitempty"`
50+
Type ToolType `json:"type"`
51+
Function FunctionCall `json:"function"`
52+
}
53+
54+
type ChatCompletionMessage struct {
55+
Role string `json:"role"`
56+
Content string `json:"content,omitempty"`
57+
Refusal string `json:"refusal,omitempty"`
58+
MultiContent []ChatMessagePart `json:"-"`
59+
Name string `json:"name,omitempty"`
60+
ReasoningContent string `json:"reasoning_content,omitempty"`
61+
FunctionCall *FunctionCall `json:"function_call,omitempty"`
62+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
63+
ToolCallID string `json:"tool_call_id,omitempty"`
64+
}
65+
66+
func (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {
67+
if m.Content != "" && m.MultiContent != nil {
68+
return nil, ErrContentFieldsMisused
69+
}
70+
if len(m.MultiContent) > 0 {
71+
msg := struct {
72+
Role string `json:"role"`
73+
Content string `json:"-"`
74+
Refusal string `json:"refusal,omitempty"`
75+
MultiContent []ChatMessagePart `json:"content,omitempty"`
76+
Name string `json:"name,omitempty"`
77+
ReasoningContent string `json:"reasoning_content,omitempty"`
78+
FunctionCall *FunctionCall `json:"function_call,omitempty"`
79+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
80+
ToolCallID string `json:"tool_call_id,omitempty"`
81+
}(m)
82+
return json.Marshal(msg)
83+
}
84+
85+
msg := struct {
86+
Role string `json:"role"`
87+
Content string `json:"content,omitempty"`
88+
Refusal string `json:"refusal,omitempty"`
89+
MultiContent []ChatMessagePart `json:"-"`
90+
Name string `json:"name,omitempty"`
91+
ReasoningContent string `json:"reasoning_content,omitempty"`
92+
FunctionCall *FunctionCall `json:"function_call,omitempty"`
93+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
94+
ToolCallID string `json:"tool_call_id,omitempty"`
95+
}(m)
96+
return json.Marshal(msg)
97+
}
98+
99+
func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {
100+
msg := struct {
101+
Role string `json:"role"`
102+
Content string `json:"content"`
103+
Refusal string `json:"refusal,omitempty"`
104+
MultiContent []ChatMessagePart
105+
Name string `json:"name,omitempty"`
106+
ReasoningContent string `json:"reasoning_content,omitempty"`
107+
FunctionCall *FunctionCall `json:"function_call,omitempty"`
108+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
109+
ToolCallID string `json:"tool_call_id,omitempty"`
110+
}{}
111+
112+
if err := json.Unmarshal(bs, &msg); err == nil {
113+
*m = ChatCompletionMessage(msg)
114+
return nil
115+
}
116+
multiMsg := struct {
117+
Role string `json:"role"`
118+
Content string
119+
Refusal string `json:"refusal,omitempty"`
120+
MultiContent []ChatMessagePart `json:"content"`
121+
Name string `json:"name,omitempty"`
122+
ReasoningContent string `json:"reasoning_content,omitempty"`
123+
FunctionCall *FunctionCall `json:"function_call,omitempty"`
124+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
125+
ToolCallID string `json:"tool_call_id,omitempty"`
126+
}{}
127+
if err := json.Unmarshal(bs, &multiMsg); err != nil {
128+
return err
129+
}
130+
*m = ChatCompletionMessage(multiMsg)
131+
return nil
132+
}

cli/flags.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import (
1010
"strconv"
1111
"strings"
1212

13+
"github.com/danielmiessler/fabric/chat"
1314
"github.com/danielmiessler/fabric/common"
1415
"github.com/jessevdk/go-flags"
15-
goopenai "github.com/sashabaranov/go-openai"
1616
"golang.org/x/text/language"
1717
"gopkg.in/yaml.v2"
1818
)
@@ -278,15 +278,15 @@ func (o *Flags) BuildChatRequest(Meta string) (ret *common.ChatRequest, err erro
278278
Meta: Meta,
279279
}
280280

281-
var message *goopenai.ChatCompletionMessage
281+
var message *chat.ChatCompletionMessage
282282
if len(o.Attachments) > 0 {
283-
message = &goopenai.ChatCompletionMessage{
284-
Role: goopenai.ChatMessageRoleUser,
283+
message = &chat.ChatCompletionMessage{
284+
Role: chat.ChatMessageRoleUser,
285285
}
286286

287287
if o.Message != "" {
288-
message.MultiContent = append(message.MultiContent, goopenai.ChatMessagePart{
289-
Type: goopenai.ChatMessagePartTypeText,
288+
message.MultiContent = append(message.MultiContent, chat.ChatMessagePart{
289+
Type: chat.ChatMessagePartTypeText,
290290
Text: strings.TrimSpace(o.Message),
291291
})
292292
}
@@ -309,16 +309,16 @@ func (o *Flags) BuildChatRequest(Meta string) (ret *common.ChatRequest, err erro
309309
dataURL := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Image)
310310
url = &dataURL
311311
}
312-
message.MultiContent = append(message.MultiContent, goopenai.ChatMessagePart{
313-
Type: goopenai.ChatMessagePartTypeImageURL,
314-
ImageURL: &goopenai.ChatMessageImageURL{
312+
message.MultiContent = append(message.MultiContent, chat.ChatMessagePart{
313+
Type: chat.ChatMessagePartTypeImageURL,
314+
ImageURL: &chat.ChatMessageImageURL{
315315
URL: *url,
316316
},
317317
})
318318
}
319319
} else if o.Message != "" {
320-
message = &goopenai.ChatCompletionMessage{
321-
Role: goopenai.ChatMessageRoleUser,
320+
message = &chat.ChatCompletionMessage{
321+
Role: chat.ChatMessageRoleUser,
322322
Content: strings.TrimSpace(o.Message),
323323
}
324324
}

common/domain.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package common
22

3-
import goopenai "github.com/sashabaranov/go-openai"
3+
import "github.com/danielmiessler/fabric/chat"
44

55
const ChatMessageRoleMeta = "meta"
66

@@ -9,7 +9,7 @@ type ChatRequest struct {
99
SessionName string
1010
PatternName string
1111
PatternVariables map[string]string
12-
Message *goopenai.ChatCompletionMessage
12+
Message *chat.ChatCompletionMessage
1313
Language string
1414
Meta string
1515
InputHasVars bool
@@ -29,7 +29,7 @@ type ChatOptions struct {
2929
}
3030

3131
// NormalizeMessages remove empty messages and ensure messages order user-assist-user
32-
func NormalizeMessages(msgs []*goopenai.ChatCompletionMessage, defaultUserMessage string) (ret []*goopenai.ChatCompletionMessage) {
32+
func NormalizeMessages(msgs []*chat.ChatCompletionMessage, defaultUserMessage string) (ret []*chat.ChatCompletionMessage) {
3333
// Iterate over messages to enforce the odd position rule for user messages
3434
fullMessageIndex := 0
3535
for _, message := range msgs {
@@ -39,8 +39,8 @@ func NormalizeMessages(msgs []*goopenai.ChatCompletionMessage, defaultUserMessag
3939
}
4040

4141
// Ensure, that each odd position shall be a user message
42-
if fullMessageIndex%2 == 0 && message.Role != goopenai.ChatMessageRoleUser {
43-
ret = append(ret, &goopenai.ChatCompletionMessage{Role: goopenai.ChatMessageRoleUser, Content: defaultUserMessage})
42+
if fullMessageIndex%2 == 0 && message.Role != chat.ChatMessageRoleUser {
43+
ret = append(ret, &chat.ChatCompletionMessage{Role: chat.ChatMessageRoleUser, Content: defaultUserMessage})
4444
fullMessageIndex++
4545
}
4646
ret = append(ret, message)

common/domain_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@ package common
33
import (
44
"testing"
55

6-
goopenai "github.com/sashabaranov/go-openai"
6+
"github.com/danielmiessler/fabric/chat"
77
"github.com/stretchr/testify/assert"
88
)
99

1010
func TestNormalizeMessages(t *testing.T) {
11-
msgs := []*goopenai.ChatCompletionMessage{
12-
{Role: goopenai.ChatMessageRoleUser, Content: "Hello"},
13-
{Role: goopenai.ChatMessageRoleAssistant, Content: "Hi there!"},
14-
{Role: goopenai.ChatMessageRoleUser, Content: ""},
15-
{Role: goopenai.ChatMessageRoleUser, Content: ""},
16-
{Role: goopenai.ChatMessageRoleUser, Content: "How are you?"},
11+
msgs := []*chat.ChatCompletionMessage{
12+
{Role: chat.ChatMessageRoleUser, Content: "Hello"},
13+
{Role: chat.ChatMessageRoleAssistant, Content: "Hi there!"},
14+
{Role: chat.ChatMessageRoleUser, Content: ""},
15+
{Role: chat.ChatMessageRoleUser, Content: ""},
16+
{Role: chat.ChatMessageRoleUser, Content: "How are you?"},
1717
}
1818

19-
expected := []*goopenai.ChatCompletionMessage{
20-
{Role: goopenai.ChatMessageRoleUser, Content: "Hello"},
21-
{Role: goopenai.ChatMessageRoleAssistant, Content: "Hi there!"},
22-
{Role: goopenai.ChatMessageRoleUser, Content: "How are you?"},
19+
expected := []*chat.ChatCompletionMessage{
20+
{Role: chat.ChatMessageRoleUser, Content: "Hello"},
21+
{Role: chat.ChatMessageRoleAssistant, Content: "Hi there!"},
22+
{Role: chat.ChatMessageRoleUser, Content: "How are you?"},
2323
}
2424

2525
actual := NormalizeMessages(msgs, "default")

core/chatter.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"os"
88
"strings"
99

10-
goopenai "github.com/sashabaranov/go-openai"
10+
"github.com/danielmiessler/fabric/chat"
1111

1212
"github.com/danielmiessler/fabric/common"
1313
"github.com/danielmiessler/fabric/plugins/ai"
@@ -110,7 +110,7 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
110110
message = summary
111111
}
112112

113-
session.Append(&goopenai.ChatCompletionMessage{Role: goopenai.ChatMessageRoleAssistant, Content: message})
113+
session.Append(&chat.ChatCompletionMessage{Role: chat.ChatMessageRoleAssistant, Content: message})
114114

115115
if session.Name != "" {
116116
err = o.db.Sessions.SaveSession(session)
@@ -131,7 +131,7 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
131131
}
132132

133133
if request.Meta != "" {
134-
session.Append(&goopenai.ChatCompletionMessage{Role: common.ChatMessageRoleMeta, Content: request.Meta})
134+
session.Append(&chat.ChatCompletionMessage{Role: common.ChatMessageRoleMeta, Content: request.Meta})
135135
}
136136

137137
// if a context name is provided, retrieve it from the database
@@ -149,8 +149,8 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
149149
// Double curly braces {{variable}} indicate template substitution
150150
// Ensure we have a message before processing
151151
if request.Message == nil {
152-
request.Message = &goopenai.ChatCompletionMessage{
153-
Role: goopenai.ChatMessageRoleUser,
152+
request.Message = &chat.ChatCompletionMessage{
153+
Role: chat.ChatMessageRoleUser,
154154
Content: " ",
155155
}
156156
}
@@ -206,26 +206,26 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
206206
// Handle MultiContent properly in raw mode
207207
if len(request.Message.MultiContent) > 0 {
208208
// When we have attachments, add the text as a text part in MultiContent
209-
newMultiContent := []goopenai.ChatMessagePart{
209+
newMultiContent := []chat.ChatMessagePart{
210210
{
211-
Type: goopenai.ChatMessagePartTypeText,
211+
Type: chat.ChatMessagePartTypeText,
212212
Text: finalContent,
213213
},
214214
}
215215
// Add existing non-text parts (like images)
216216
for _, part := range request.Message.MultiContent {
217-
if part.Type != goopenai.ChatMessagePartTypeText {
217+
if part.Type != chat.ChatMessagePartTypeText {
218218
newMultiContent = append(newMultiContent, part)
219219
}
220220
}
221-
request.Message = &goopenai.ChatCompletionMessage{
222-
Role: goopenai.ChatMessageRoleUser,
221+
request.Message = &chat.ChatCompletionMessage{
222+
Role: chat.ChatMessageRoleUser,
223223
MultiContent: newMultiContent,
224224
}
225225
} else {
226226
// No attachments, use regular Content field
227-
request.Message = &goopenai.ChatCompletionMessage{
228-
Role: goopenai.ChatMessageRoleUser,
227+
request.Message = &chat.ChatCompletionMessage{
228+
Role: chat.ChatMessageRoleUser,
229229
Content: finalContent,
230230
}
231231
}
@@ -235,7 +235,7 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
235235
}
236236
} else {
237237
if systemMessage != "" {
238-
session.Append(&goopenai.ChatCompletionMessage{Role: goopenai.ChatMessageRoleSystem, Content: systemMessage})
238+
session.Append(&chat.ChatCompletionMessage{Role: chat.ChatMessageRoleSystem, Content: systemMessage})
239239
}
240240
// If multi-part content, it is in the user message, and should be added.
241241
// Otherwise, we should only add it if we have not already used it in the systemMessage.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ require (
1919
github.com/jessevdk/go-flags v1.6.1
2020
github.com/joho/godotenv v1.5.1
2121
github.com/ollama/ollama v0.9.0
22+
github.com/openai/openai-go v1.8.2
2223
github.com/otiai10/copy v1.14.1
2324
github.com/pkg/errors v0.9.1
2425
github.com/samber/lo v1.50.0
25-
github.com/sashabaranov/go-openai v1.40.3
2626
github.com/sgaunet/perplexity-go/v2 v2.8.0
2727
github.com/stretchr/testify v1.10.0
2828
golang.org/x/text v0.26.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ github.com/ollama/ollama v0.9.0 h1:GvdGhi8G/QMnFrY0TMLDy1bXua+Ify8KTkFe4ZY/OZs=
172172
github.com/ollama/ollama v0.9.0/go.mod h1:aio9yQ7nc4uwIbn6S0LkGEPgn8/9bNQLL1nHuH+OcD0=
173173
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
174174
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
175+
github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=
176+
github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
175177
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
176178
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
177179
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
@@ -189,8 +191,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
189191
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
190192
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
191193
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
192-
github.com/sashabaranov/go-openai v1.40.3 h1:PkOw0SK34wrvYVOuXF1HZzuTBRh992qRZHil4kG3eYE=
193-
github.com/sashabaranov/go-openai v1.40.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
194194
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
195195
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
196196
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=

nix/pkgs/fabric/gomod2nix.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,6 @@ schema = 3
229229
[mod."github.com/samber/lo"]
230230
version = "v1.50.0"
231231
hash = "sha256-KDFks82BKu39sGt0f972IyOkohV2U0r1YvsnlNLdugY="
232-
[mod."github.com/sashabaranov/go-openai"]
233-
version = "v1.40.3"
234-
hash = "sha256-Q2+la99lgKwcpgGHuf5p23gH5hmhYKp77YDUtbt35eo="
235232
[mod."github.com/sergi/go-diff"]
236233
version = "v1.4.0"
237234
hash = "sha256-rs9NKpv/qcQEMRg7CmxGdP4HGuFdBxlpWf9LbA9wS4k="

0 commit comments

Comments
 (0)