Skip to content

Commit e3caa0c

Browse files
authored
Merge pull request #1 from kekoawong/kekoawong/open-ai-plugin
feat(openai plugin): Initial openai plugin
2 parents 5894b4e + 0d54e56 commit e3caa0c

7 files changed

Lines changed: 588 additions & 10 deletions

File tree

go/go.mod

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,18 @@ retract (
88
)
99

1010
require (
11-
cloud.google.com/go/aiplatform v1.68.0
1211
cloud.google.com/go/firestore v1.16.0
1312
cloud.google.com/go/logging v1.11.0
1413
firebase.google.com/go/v4 v4.14.1
1514
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.46.0
1615
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.22.0
1716
github.com/aymerick/raymond v2.0.2+incompatible
18-
github.com/google/generative-ai-go v0.19.0
1917
github.com/google/go-cmp v0.6.0
2018
github.com/invopop/jsonschema v0.12.0
2119
github.com/jba/slog v0.2.0
2220
github.com/lib/pq v1.10.9
2321
github.com/pgvector/pgvector-go v0.2.0
22+
github.com/stretchr/testify v1.9.0
2423
github.com/weaviate/weaviate v1.26.0-rc.1
2524
github.com/weaviate/weaviate-go-client/v4 v4.15.0
2625
github.com/wk8/go-ordered-map/v2 v2.1.8
@@ -34,14 +33,22 @@ require (
3433
golang.org/x/tools v0.23.0
3534
google.golang.org/api v0.197.0
3635
google.golang.org/genai v0.5.0
37-
google.golang.org/protobuf v1.34.2
3836
gopkg.in/yaml.v2 v2.4.0
3937
gopkg.in/yaml.v3 v3.0.1
4038
)
4139

40+
require (
41+
github.com/davecgh/go-spew v1.1.1 // indirect
42+
github.com/pmezard/go-difflib v1.0.0 // indirect
43+
github.com/tidwall/gjson v1.18.0 // indirect
44+
github.com/tidwall/match v1.1.1 // indirect
45+
github.com/tidwall/pretty v1.2.1 // indirect
46+
github.com/tidwall/sjson v1.2.5 // indirect
47+
google.golang.org/protobuf v1.34.2 // indirect
48+
)
49+
4250
require (
4351
cloud.google.com/go v0.116.0 // indirect
44-
cloud.google.com/go/ai v0.8.0 // indirect
4552
cloud.google.com/go/auth v0.9.3 // indirect
4653
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
4754
cloud.google.com/go/compute/metadata v0.5.0 // indirect
@@ -81,6 +88,7 @@ require (
8188
github.com/mailru/easyjson v0.7.7 // indirect
8289
github.com/mitchellh/mapstructure v1.5.0 // indirect
8390
github.com/oklog/ulid v1.3.1 // indirect
91+
github.com/openai/openai-go v0.1.0-alpha.65
8492
github.com/pkg/errors v0.9.1 // indirect
8593
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
8694
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect

go/go.sum

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
22
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
33
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
4-
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
5-
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
6-
cloud.google.com/go/aiplatform v1.68.0 h1:EPPqgHDJpBZKRvv+OsB3cr0jYz3EL2pZ+802rBPcG8U=
7-
cloud.google.com/go/aiplatform v1.68.0/go.mod h1:105MFA3svHjC3Oazl7yjXAmIR89LKhRAeNdnDKJczME=
84
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
95
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
106
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
@@ -149,8 +145,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
149145
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
150146
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
151147
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
152-
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
153-
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
154148
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
155149
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
156150
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -223,6 +217,12 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ
223217
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
224218
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
225219
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
220+
github.com/openai/openai-go v0.1.0-alpha.63 h1:bOlYE6NJcPdjG3OMl9rZnxJrxmwcG6zlEKOD1MHP9Is=
221+
github.com/openai/openai-go v0.1.0-alpha.63/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
222+
github.com/openai/openai-go v0.1.0-alpha.65 h1:G12sA6OaL+cVMElMO3m5RVFwKhhg40kmGeGhaYZIoYw=
223+
github.com/openai/openai-go v0.1.0-alpha.65/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
224+
github.com/openai/openai-go v0.1.0-alpha.66 h1:s+txdNJIsEVcDT+N3Ir+9vDlh1X0o2mdU5Of9l1xvCI=
225+
github.com/openai/openai-go v0.1.0-alpha.66/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
226226
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
227227
github.com/pgvector/pgvector-go v0.2.0 h1:NZdW4NxUxdSCzaev3LVHb9ORf+LdX+uZOQVqQ6s2Zyg=
228228
github.com/pgvector/pgvector-go v0.2.0/go.mod h1:OQpvU5QZGQOPI9quIXAyHaRZ5yGk/RGUDbs9C3DPUNE=
@@ -258,7 +258,19 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
258258
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
259259
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
260260
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
261+
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
262+
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
263+
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
264+
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
265+
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
266+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
267+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
261268
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
269+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
270+
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
271+
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
272+
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
273+
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
262274
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
263275
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
264276
github.com/uptrace/bun v1.1.12 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ=
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# OpenAI Plugin
2+
3+
This plugin provides a simple interface for using OpenAI's services.
4+
5+
## Prerequisites
6+
7+
- Go installed on your system
8+
- An OpenAI API key
9+
10+
## Running Tests
11+
12+
First, set your OpenAI API key as an environment variable:
13+
14+
```bash
15+
export OPENAI_API_KEY=<your-api-key>
16+
```
17+
18+
### Running All Tests
19+
To run all tests in the directory:
20+
```bash
21+
go test -v .
22+
```
23+
24+
### Running Tests from Specific Files
25+
To run tests from a specific file:
26+
```bash
27+
# Run only generate_live_test.go tests
28+
go test -run "^TestGenerator"
29+
30+
# Run only openai_live_test.go tests
31+
go test -run "^TestPlugin"
32+
```
33+
34+
### Running Individual Tests
35+
To run a specific test case:
36+
```bash
37+
# Run only the streaming test from openai_live_test.go
38+
go test -run "TestPlugin/streaming"
39+
40+
# Run only the Complete test from generate_live_test.go
41+
go test -run "TestGenerator_Complete"
42+
43+
# Run only the Stream test from generate_live_test.go
44+
go test -run "TestGenerator_Stream"
45+
```
46+
47+
### Test Output Verbosity
48+
Add the `-v` flag for verbose output:
49+
```bash
50+
go test -v -run "TestPlugin/streaming"
51+
```
52+
53+
Note: All live tests require the OPENAI_API_KEY environment variable to be set. Tests will be skipped if the API key is not provided.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package openai
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/firebase/genkit/go/ai"
8+
"github.com/openai/openai-go"
9+
)
10+
11+
// ModelGenerator handles OpenAI generation requests
12+
type ModelGenerator struct {
13+
client *openai.Client
14+
modelName string
15+
request *openai.ChatCompletionNewParams
16+
}
17+
18+
// NewModelGenerator creates a new ModelGenerator instance
19+
func NewModelGenerator(client *openai.Client, modelName string) *ModelGenerator {
20+
return &ModelGenerator{
21+
client: client,
22+
modelName: modelName,
23+
request: &openai.ChatCompletionNewParams{
24+
Model: openai.F(modelName),
25+
},
26+
}
27+
}
28+
29+
// WithMessages adds messages to the request
30+
func (g *ModelGenerator) WithMessages(messages []*ai.Message) *ModelGenerator {
31+
oaiMessages := make([]openai.ChatCompletionMessageParamUnion, 0, len(messages))
32+
for _, msg := range messages {
33+
content := g.concatenateContent(msg.Content)
34+
switch msg.Role {
35+
case ai.RoleSystem:
36+
oaiMessages = append(oaiMessages, openai.SystemMessage(content))
37+
case ai.RoleModel:
38+
oaiMessages = append(oaiMessages, openai.AssistantMessage(content))
39+
case ai.RoleTool:
40+
oaiMessages = append(oaiMessages, openai.ToolMessage("", content)) // TODO: Add tool ID if needed
41+
default:
42+
oaiMessages = append(oaiMessages, openai.UserMessage(content))
43+
}
44+
}
45+
g.request.Messages = openai.F(oaiMessages)
46+
return g
47+
}
48+
49+
// WithConfig adds configuration parameters from the model request
50+
func (g *ModelGenerator) WithConfig(config any) *ModelGenerator {
51+
if config != nil {
52+
// TODO: Implement configuration from model request
53+
// modelRequest.Config is any type
54+
}
55+
return g
56+
}
57+
58+
// WithTools adds tools to the request
59+
func (g *ModelGenerator) WithTools(tools []ai.Tool, choice ai.ToolChoice) *ModelGenerator {
60+
// TODO: Implement tools from model request
61+
// see vertex ai recent pr here for reference: https://github.com/firebase/genkit/pull/2259
62+
return g
63+
}
64+
65+
// Generate executes the generation request
66+
func (g *ModelGenerator) Generate(ctx context.Context, handleChunk func(context.Context, *ai.ModelResponseChunk) error) (*ai.ModelResponse, error) {
67+
if handleChunk != nil {
68+
return g.generateStream(ctx, handleChunk)
69+
}
70+
return g.generateComplete(ctx)
71+
}
72+
73+
// concatenateContent concatenates text content into a single string
74+
func (g *ModelGenerator) concatenateContent(parts []*ai.Part) string {
75+
content := ""
76+
for _, part := range parts {
77+
content += part.Text
78+
}
79+
return content
80+
}
81+
82+
// generateStream generates a streaming model response
83+
func (g *ModelGenerator) generateStream(ctx context.Context, handleChunk func(context.Context, *ai.ModelResponseChunk) error) (*ai.ModelResponse, error) {
84+
stream := g.client.Chat.Completions.NewStreaming(ctx, *g.request)
85+
defer stream.Close()
86+
87+
var fullResponse ai.ModelResponse
88+
fullResponse.Message = &ai.Message{
89+
Role: ai.RoleModel,
90+
Content: make([]*ai.Part, 0),
91+
}
92+
93+
// Initialize request and usage
94+
fullResponse.Request = &ai.ModelRequest{}
95+
fullResponse.Usage = &ai.GenerationUsage{
96+
InputTokens: 0,
97+
OutputTokens: 0,
98+
TotalTokens: 0,
99+
}
100+
101+
for stream.Next() {
102+
chunk := stream.Current()
103+
if len(chunk.Choices) > 0 {
104+
content := chunk.Choices[0].Delta.Content
105+
modelChunk := &ai.ModelResponseChunk{
106+
Content: []*ai.Part{ai.NewTextPart(content)},
107+
}
108+
109+
if err := handleChunk(ctx, modelChunk); err != nil {
110+
return nil, fmt.Errorf("callback error: %w", err)
111+
}
112+
113+
fullResponse.Message.Content = append(fullResponse.Message.Content, modelChunk.Content...)
114+
115+
// Update Usage
116+
fullResponse.Usage.InputTokens += int(chunk.Usage.PromptTokens)
117+
fullResponse.Usage.OutputTokens += int(chunk.Usage.CompletionTokens)
118+
fullResponse.Usage.TotalTokens += int(chunk.Usage.TotalTokens)
119+
}
120+
}
121+
122+
if err := stream.Err(); err != nil {
123+
return nil, fmt.Errorf("stream error: %w", err)
124+
}
125+
126+
return &fullResponse, nil
127+
}
128+
129+
// generateComplete generates a complete model response
130+
func (g *ModelGenerator) generateComplete(ctx context.Context) (*ai.ModelResponse, error) {
131+
completion, err := g.client.Chat.Completions.New(ctx, *g.request)
132+
if err != nil {
133+
return nil, fmt.Errorf("failed to create completion: %w", err)
134+
}
135+
136+
return &ai.ModelResponse{
137+
Message: &ai.Message{
138+
Role: ai.RoleModel,
139+
Content: []*ai.Part{
140+
ai.NewTextPart(completion.Choices[0].Message.Content),
141+
},
142+
},
143+
FinishReason: ai.FinishReason("stop"),
144+
Request: &ai.ModelRequest{},
145+
Usage: &ai.GenerationUsage{
146+
InputTokens: int(completion.Usage.PromptTokens),
147+
OutputTokens: int(completion.Usage.CompletionTokens),
148+
TotalTokens: int(completion.Usage.TotalTokens),
149+
},
150+
}, nil
151+
}

0 commit comments

Comments
 (0)