Skip to content

CLI commands for creating OpenAI Assistants #359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/api/oaiapi/assistant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package oaiapi

import (
"github.com/jlewi/foyle/app/api"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
AssistantGVK = schema.FromAPIVersionAndKind(OAIGroup+"/"+api.Version, "Assistant")
)

// Assistant based off https://platform.openai.com/docs/api-reference/assistants/create
type Assistant struct {
Metadata api.Metadata `json:"metadata" yaml:"metadata"`
Spec AssistantSpec `json:"spec" yaml:"spec"`
}

type AssistantSpec struct {
// Model is the name of the model to use
Model string `json:"model" yaml:"model"`
// Instructions is the instructions for the assistant
Instructions string `json:"instructions" yaml:"instructions"`
// VectorStoreIDs is the IDs of the vector stores to use
VectorStoreIDs []string `json:"vectorStoreIDs" yaml:"vectorStoreIDs"`
Description string `json:"description" yaml:"description"`
}
7 changes: 7 additions & 0 deletions app/api/oaiapi/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package oaiapi

import "github.com/jlewi/foyle/app/api"

const (
OAIGroup = "oai." + api.Group
)
25 changes: 25 additions & 0 deletions app/api/oaiapi/filesync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package oaiapi

import (
"github.com/jlewi/foyle/app/api"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
FileSyncGVK = schema.FromAPIVersionAndKind(OAIGroup+"/"+api.Version, "FileSync")
)

// FileSync based off https://platform.openai.com/docs/api-reference/vector-stores/create
type FileSync struct {
Metadata api.Metadata `json:"metadata" yaml:"metadata"`
Spec FileSyncSpec `json:"spec" yaml:"spec"`
}

type FileSyncSpec struct {
// Source is the source glob to match
Source string `json:"source" yaml:"source"`
// VectorStoreID is the ID of the vector store to sync the files to
VectorStoreID string `json:"vectorStoreID" yaml:"vectorStoreID"`
VectorStoreName string `json:"vectorStoreName" yaml:"vectorStoreName"`
BaseURL string `json:"baseURL" yaml:"baseURL"`
}
20 changes: 20 additions & 0 deletions app/api/oaiapi/vectorstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package oaiapi

import (
"github.com/jlewi/foyle/app/api"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
VectorStoreGVK = schema.FromAPIVersionAndKind(OAIGroup+"/"+api.Version, "VectorStore")
)

// VectorStore based off https://platform.openai.com/docs/api-reference/vector-stores/create
type VectorStore struct {
Metadata api.Metadata `json:"metadata" yaml:"metadata"`
Spec VectorStoreSpec `json:"spec" yaml:"spec"`
}

type VectorStoreSpec struct {
// TODO(jeremy): Should add actual fields.
}
10 changes: 8 additions & 2 deletions app/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ go 1.22.1

replace (
github.com/jlewi/foyle/protos/go => ../protos/go
// TODO(jeremy): Remove this if https://github.com/sashabaranov/go-openai/pull/919 ever gets merged
github.com/sashabaranov/go-openai => github.com/jlewi/go-openai v0.0.0-20250102163401-3f27fc7109d1
// TODO(jeremy): We can get rid of this replace; we should no longer need to use a jlewi branch.
github.com/stateful/runme/v3 => github.com/jlewi/runme/v3 v3.0.0-20240524044247-2657f0b08e0f

k8s.io/client-go => k8s.io/client-go v0.27.3
)

Expand Down Expand Up @@ -37,7 +38,7 @@ require (
github.com/oklog/ulid/v2 v2.1.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/pkg/errors v0.9.1
github.com/sashabaranov/go-openai v1.30.3
github.com/sashabaranov/go-openai v1.36.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
github.com/stateful/runme/v3 v3.3.1-0.20240515132033-7fd1591498c6
Expand Down Expand Up @@ -157,6 +158,7 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/openai/openai-go v0.1.0-alpha.41 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
Expand Down Expand Up @@ -184,6 +186,10 @@ require (
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/timtadh/data-structures v0.6.1 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
Expand Down
14 changes: 14 additions & 0 deletions app/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jlewi/go-openai v0.0.0-20250102163401-3f27fc7109d1 h1:+YZfNrujCqjwWmfBxU0x/d0MpZb5tP5oCyVhBbZVS9I=
github.com/jlewi/go-openai v0.0.0-20250102163401-3f27fc7109d1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/jlewi/hydros v0.0.7-0.20240503183011-8f99ead373fb h1:2G2k606S3Qcg40czr7gnkeIG5KgQ2wXJ1BMxAuC+P3I=
github.com/jlewi/hydros v0.0.7-0.20240503183011-8f99ead373fb/go.mod h1:4fV+JUCnexPY2ZbKzdfV/RsyrfralN832MsUSq/7FqE=
github.com/jlewi/monogo v0.0.0-20240123191147-401afe194d74 h1:pbOw/rOMs0AZ494bGnI6DieGKwqoJQEjHWaJZrvxsJo=
Expand Down Expand Up @@ -494,6 +496,8 @@ github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/openai/openai-go v0.1.0-alpha.41 h1:OPRT5YfNKlENfipMtolMWnKbCR1iQDc9hCRsUkhMaK8=
github.com/openai/openai-go v0.1.0-alpha.41/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
Expand Down Expand Up @@ -624,6 +628,16 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/timtadh/data-structures v0.6.1 h1:76eDpwngj2rEi9r/qvdH6YL7wMXGsoFFzhEylo/IacA=
github.com/timtadh/data-structures v0.6.1/go.mod h1:uYUnI1cQi/5yMCc7s23I+x8Mn8BCMf4WgK+7/4QSEk4=
github.com/timtadh/getopt v1.0.0/go.mod h1:L3EL6YN2G0eIAhYBo9b7SB9d/kEQmdnwthIlMJfj210=
Expand Down
2 changes: 1 addition & 1 deletion app/pkg/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func Test_StreamingClient(t *testing.T) {
log := zapr.NewLogger(newLog)
// This is code to help us test streaming with the connect protocol
addr := "http://127.0.0.1:8877/api"
//addr := "http://127.0.0.1:9977/api"
//addr :=x "http://127.0.0.1:9977/api"

log.Info("Server started")
if err := runClient(addr); err != nil {
Expand Down
24 changes: 24 additions & 0 deletions app/pkg/application/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"github.com/jlewi/foyle/app/api/oaiapi"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -271,6 +272,29 @@ func (a *App) SetupRegistry() error {
return err
}

vs, err := oai.NewVSController(*a.Config)
if err != nil {
return err
}
if err := a.Registry.Register(oaiapi.VectorStoreGVK, vs); err != nil {
return err
}

fileSyncer, err := oai.NewFileSyncer(*a.Config)
if err != nil {
return err
}
if err := a.Registry.Register(oaiapi.FileSyncGVK, fileSyncer); err != nil {
return err
}

assistant, err := oai.NewAssistantController(*a.Config)
if err != nil {
return err
}
if err := a.Registry.Register(oaiapi.AssistantGVK, assistant); err != nil {
return err
}
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion app/pkg/logs/matchers/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package matchers
import "strings"

const (
OAIComplete = "github.com/jlewi/foyle/app/pkg/oai.(*Completer).Complete"
OAIComplete = "github.com/jlewi/foyle/app/pkg/oaiapi.(*Completer).Complete"
AnthropicComplete = "github.com/jlewi/foyle/app/pkg/anthropic.(*Completer).Complete"
LogEvents = "github.com/jlewi/foyle/app/pkg/agent.(*Agent).LogEvents"
StreamGenerate = "github.com/jlewi/foyle/app/pkg/agent.(*Agent).StreamGenerate"
Expand Down
2 changes: 1 addition & 1 deletion app/pkg/logs/matchers/test/names_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// package test is a hacky way to avoid circular imports in the test.
// The test imports some packages (e.g. anthropic/oai) that also import matchers
// The test imports some packages (e.g. anthropic/oaiapi) that also import matchers
// so if we don't use a separate package we end up with a circular import.
package test

Expand Down
87 changes: 87 additions & 0 deletions app/pkg/oai/assistant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package oai

import (
"context"
"github.com/jlewi/foyle/app/api/oaiapi"
"github.com/jlewi/foyle/app/pkg/config"
"github.com/jlewi/foyle/app/pkg/logs"
"github.com/pkg/errors"
"github.com/sashabaranov/go-openai"
"google.golang.org/protobuf/proto"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

// AssistantController is a controller for OpenAI assistant
type AssistantController struct {
cfg config.Config
client *openai.Client
}

// NewAssistantController creates a new controller for OpenAI assistant
func NewAssistantController(cfg config.Config) (*AssistantController, error) {
return &AssistantController{cfg: cfg}, nil
}

// ReconcileNode reconciles the state of the resource.
func (a *AssistantController) ReconcileNode(ctx context.Context, node *yaml.RNode) error {
s := &oaiapi.Assistant{}
if err := node.YNode().Decode(s); err != nil {
return errors.Wrap(err, "Failed to decode Assistant")
}

return a.Apply(ctx, s)
}

func (a *AssistantController) Apply(ctx context.Context, s *oaiapi.Assistant) error {
log := logs.FromContext(ctx)
if a.client == nil {
client, err := NewClient(a.cfg)
if err != nil {
return errors.Wrap(err, "Failed to create OpenAI client")
}
a.client = client
}

client := a.client
tools := []openai.AssistantTool{
{
Type: openai.AssistantToolTypeFileSearch,
},
}
req := &openai.AssistantRequest{
Model: s.Spec.Model,
Name: proto.String(s.Metadata.Name),
Description: proto.String(s.Spec.Description),
Instructions: proto.String(s.Spec.Instructions),
Tools: tools,
ToolResources: &openai.AssistantToolResource{
FileSearch: &openai.AssistantToolFileSearch{VectorStoreIDs: s.Spec.VectorStoreIDs},
},
}
resp, err := client.CreateAssistant(ctx, *req)

if err != nil {
return errors.Wrapf(err, "Failed to create assistant %v", s.Metadata.Name)
}

log.Info("Created assistant", "name", s.Metadata.Name, "id", resp.ID)
return nil
}

type Assistant struct {
cfg config.Config
client *openai.Client
}

func (a *Assistant) Assist(ctx context.Context) error {
if a.client == nil {
client, err := NewClient(a.cfg)
if err != nil {
return errors.Wrap(err, "Failed to create OpenAI client")
}
a.client = client
}

client := a.client
client.CreateThreadAndRun()
}
Loading
Loading