From d79a76870c6516c3d44a541b535fed29eecafe3a Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 29 Aug 2024 13:30:31 -0500 Subject: [PATCH 1/9] feat: add ollama embedder to go plugin --- go/plugins/ollama/embed.go | 199 +++++++++++++++++++++++++++++++++++++ go/samples/rag/main.go | 11 +- 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 go/plugins/ollama/embed.go diff --git a/go/plugins/ollama/embed.go b/go/plugins/ollama/embed.go new file mode 100644 index 000000000..a2a000259 --- /dev/null +++ b/go/plugins/ollama/embed.go @@ -0,0 +1,199 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ollama + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strings" + + "github.com/firebase/genkit/go/ai" +) + +const ( + defaultEmbeddingModel = "all-minilm" + defaultOllamaAddress = "http://localhost:11434" +) + +var supportedEmbeddingModels = []string{ + "mxbai-embed-large", + "nomic-embed-text", + "all-minilm", // Default if not specified +} + +type EmbedOptions struct { + Model string `json:"model,omitempty"` +} + +type ollamaEmbedRequest struct { + Model string `json:"model"` + Input interface{} `json:"input"` // Change to interface{} to handle both string and []string + Options map[string]interface{} `json:"options,omitempty"` +} + +type ollamaEmbedResponse struct { + Embeddings [][]float32 `json:"embeddings"` +} + +// embed performs the actual embedding request to the Ollama server +func embed(ctx context.Context, serverAddress string, req *ai.EmbedRequest) (*ai.EmbedResponse, error) { + options, ok := req.Options.(*EmbedOptions) + if !ok && req.Options != nil { + return nil, fmt.Errorf("invalid options type: expected *EmbedOptions") + } + + model := getEmbeddingModel(options) + + if model == "" { + return nil, fmt.Errorf("invalid embedding model: model cannot be empty") + } + + if serverAddress == "" { + return nil, fmt.Errorf("invalid server address: address cannot be empty") + } + + ollamaReq := newOllamaEmbedRequest(model, req.Documents) + + jsonData, err := json.Marshal(ollamaReq) + if err != nil { + return nil, fmt.Errorf("failed to marshal embed request: %w", err) + } + + resp, err := sendEmbedRequest(ctx, serverAddress, jsonData) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + fmt.Printf("Ollama embed request failed with status code %d. Response body: %s\n", resp.StatusCode, string(body)) + return nil, fmt.Errorf("ollama embed request failed with status code %d", resp.StatusCode) + } + + var ollamaResp ollamaEmbedResponse + if err := json.NewDecoder(resp.Body).Decode(&ollamaResp); err != nil { + return nil, fmt.Errorf("failed to decode embed response: %w", err) + } + + return newEmbedResponse(ollamaResp.Embeddings), nil +} + +// getEmbeddingModel determines the appropriate embedding model to use +func getEmbeddingModel(options *EmbedOptions) string { + model := options.Model + for _, supportedModel := range supportedEmbeddingModels { + if model == supportedModel { + return model + } + } + return "" +} + +// sendEmbedRequest sends the actual HTTP request to the Ollama server +func sendEmbedRequest(ctx context.Context, serverAddress string, jsonData []byte) (*http.Response, error) { + client := &http.Client{} + httpReq, err := http.NewRequestWithContext(ctx, "POST", serverAddress+"/api/embed", bytes.NewBuffer(jsonData)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + httpReq.Header.Set("Content-Type", "application/json") + + return client.Do(httpReq) +} + +func newOllamaEmbedRequest(model string, documents []*ai.Document) ollamaEmbedRequest { + var input interface{} + if len(documents) == 1 { + input = concatenateText(documents[0]) + } else { + texts := make([]string, len(documents)) + for i, doc := range documents { + texts[i] = concatenateText(doc) + } + input = texts + } + + return ollamaEmbedRequest{ + Model: model, + Input: input, + } +} + +func newEmbedResponse(embeddings [][]float32) *ai.EmbedResponse { + resp := &ai.EmbedResponse{ + Embeddings: make([]*ai.DocumentEmbedding, len(embeddings)), + } + for i, embedding := range embeddings { + resp.Embeddings[i] = &ai.DocumentEmbedding{Embedding: embedding} + } + return resp +} + +// concatenateText combines all text content from a document into a single string. +func concatenateText(doc *ai.Document) string { + var builder strings.Builder + fmt.Println("Concatenating text for document:") + for _, part := range doc.Content { + builder.WriteString(part.Text) + } + result := builder.String() + fmt.Printf("Concatenated result: %s\n", result) + return result +} + +// DefineEmbedder defines an embedder with a given server address. +func DefineEmbedder(serverAddress string) ai.Embedder { + state.mu.Lock() + defer state.mu.Unlock() + if !state.initted { + panic("ollama.Init not called") + } + log.Printf("Defining embedder with server address: %s", serverAddress) + return defineEmbedder(serverAddress) +} + +// defineEmbedder creates and returns an ai.Embedder for the given server address +func defineEmbedder(serverAddress string) ai.Embedder { + log.Printf("Defining embedder function for server address: %s", serverAddress) + return ai.DefineEmbedder(provider, serverAddress, func(ctx context.Context, req *ai.EmbedRequest) (*ai.EmbedResponse, error) { + log.Printf("Embedding request received for server address: %s", serverAddress) + return embed(ctx, serverAddress, req) + }) +} + +// IsDefinedEmbedder reports whether the embedder with the given server address is defined by this plugin. +func IsDefinedEmbedder(serverAddress string) bool { + isDefined := ai.IsDefinedEmbedder(provider, serverAddress) + log.Printf("Checking if embedder is defined for server address %s: %v", serverAddress, isDefined) + return isDefined +} + +// Embedder returns the [ai.Embedder] with the given server address. +// It returns nil if the embedder was not defined. +func Embedder(serverAddress string) ai.Embedder { + embedder := ai.LookupEmbedder(provider, serverAddress) + if embedder == nil { + log.Printf("No embedder found for server address: %s", serverAddress) + } else { + log.Printf("Found embedder for server address: %s", serverAddress) + } + return embedder +} diff --git a/go/samples/rag/main.go b/go/samples/rag/main.go index 754548555..1bc4aba8a 100644 --- a/go/samples/rag/main.go +++ b/go/samples/rag/main.go @@ -44,6 +44,7 @@ import ( "github.com/firebase/genkit/go/plugins/dotprompt" "github.com/firebase/genkit/go/plugins/googleai" "github.com/firebase/genkit/go/plugins/localvec" + "github.com/firebase/genkit/go/plugins/ollama" "github.com/invopop/jsonschema" ) @@ -72,7 +73,15 @@ func main() { log.Fatal(err) } model := googleai.Model("gemini-1.0-pro") - embedder := googleai.Embedder("embedding-001") + + err = ollama.Init(context.Background(), &ollama.Config{ + ServerAddress: "http://localhost:11434", + }) + if err != nil { + log.Fatal(err) + } + embedder := ollama.DefineEmbedder("all-minilm") + if err := localvec.Init(); err != nil { log.Fatal(err) } From 0045eae9e39dbad8d48f73dc39d07bc361436af5 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 29 Aug 2024 13:58:39 -0500 Subject: [PATCH 2/9] remove extra log --- go/plugins/ollama/embed.go | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/go/plugins/ollama/embed.go b/go/plugins/ollama/embed.go index a2a000259..42a355ce4 100644 --- a/go/plugins/ollama/embed.go +++ b/go/plugins/ollama/embed.go @@ -19,8 +19,6 @@ import ( "context" "encoding/json" "fmt" - "io" - "log" "net/http" "strings" @@ -83,8 +81,6 @@ func embed(ctx context.Context, serverAddress string, req *ai.EmbedRequest) (*ai defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - fmt.Printf("Ollama embed request failed with status code %d. Response body: %s\n", resp.StatusCode, string(body)) return nil, fmt.Errorf("ollama embed request failed with status code %d", resp.StatusCode) } @@ -115,7 +111,6 @@ func sendEmbedRequest(ctx context.Context, serverAddress string, jsonData []byte return nil, fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") - return client.Do(httpReq) } @@ -150,12 +145,10 @@ func newEmbedResponse(embeddings [][]float32) *ai.EmbedResponse { // concatenateText combines all text content from a document into a single string. func concatenateText(doc *ai.Document) string { var builder strings.Builder - fmt.Println("Concatenating text for document:") for _, part := range doc.Content { builder.WriteString(part.Text) } result := builder.String() - fmt.Printf("Concatenated result: %s\n", result) return result } @@ -166,15 +159,7 @@ func DefineEmbedder(serverAddress string) ai.Embedder { if !state.initted { panic("ollama.Init not called") } - log.Printf("Defining embedder with server address: %s", serverAddress) - return defineEmbedder(serverAddress) -} - -// defineEmbedder creates and returns an ai.Embedder for the given server address -func defineEmbedder(serverAddress string) ai.Embedder { - log.Printf("Defining embedder function for server address: %s", serverAddress) return ai.DefineEmbedder(provider, serverAddress, func(ctx context.Context, req *ai.EmbedRequest) (*ai.EmbedResponse, error) { - log.Printf("Embedding request received for server address: %s", serverAddress) return embed(ctx, serverAddress, req) }) } @@ -182,18 +167,11 @@ func defineEmbedder(serverAddress string) ai.Embedder { // IsDefinedEmbedder reports whether the embedder with the given server address is defined by this plugin. func IsDefinedEmbedder(serverAddress string) bool { isDefined := ai.IsDefinedEmbedder(provider, serverAddress) - log.Printf("Checking if embedder is defined for server address %s: %v", serverAddress, isDefined) return isDefined } // Embedder returns the [ai.Embedder] with the given server address. // It returns nil if the embedder was not defined. func Embedder(serverAddress string) ai.Embedder { - embedder := ai.LookupEmbedder(provider, serverAddress) - if embedder == nil { - log.Printf("No embedder found for server address: %s", serverAddress) - } else { - log.Printf("Found embedder for server address: %s", serverAddress) - } - return embedder + return ai.LookupEmbedder(provider, serverAddress) } From e93b07132e320d295da840337ee55645faae6222 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 29 Aug 2024 18:30:52 -0500 Subject: [PATCH 3/9] remove default values, add tests --- go/plugins/ollama/embed.go | 5 --- go/plugins/ollama/embed_test.go | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 go/plugins/ollama/embed_test.go diff --git a/go/plugins/ollama/embed.go b/go/plugins/ollama/embed.go index 42a355ce4..5556281fc 100644 --- a/go/plugins/ollama/embed.go +++ b/go/plugins/ollama/embed.go @@ -25,11 +25,6 @@ import ( "github.com/firebase/genkit/go/ai" ) -const ( - defaultEmbeddingModel = "all-minilm" - defaultOllamaAddress = "http://localhost:11434" -) - var supportedEmbeddingModels = []string{ "mxbai-embed-large", "nomic-embed-text", diff --git a/go/plugins/ollama/embed_test.go b/go/plugins/ollama/embed_test.go new file mode 100644 index 000000000..5aae2da60 --- /dev/null +++ b/go/plugins/ollama/embed_test.go @@ -0,0 +1,66 @@ +package ollama + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/firebase/genkit/go/ai" +) + +func TestEmbedValidRequest(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(ollamaEmbedResponse{ + Embeddings: [][]float32{{0.1, 0.2, 0.3}}, + }) + })) + defer server.Close() + + req := &ai.EmbedRequest{ + Documents: []*ai.Document{ + ai.DocumentFromText("test", nil), + }, + Options: &EmbedOptions{Model: "all-minilm"}, + } + + resp, err := embed(context.Background(), server.URL, req) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(resp.Embeddings) != 1 { + t.Fatalf("expected 1 embedding, got %d", len(resp.Embeddings)) + } +} + +func TestEmbedInvalidModel(t *testing.T) { + req := &ai.EmbedRequest{ + Documents: []*ai.Document{ + ai.DocumentFromText("test", nil), + }, + Options: &EmbedOptions{Model: "invalid-model"}, + } + + _, err := embed(context.Background(), "http://localhost:11434", req) + if err == nil || !strings.Contains(err.Error(), "invalid embedding model") { + t.Fatalf("expected invalid model error, got %v", err) + } +} + +func TestEmbedInvalidServerAddress(t *testing.T) { + req := &ai.EmbedRequest{ + Documents: []*ai.Document{ + ai.DocumentFromText("test", nil), + }, + Options: &EmbedOptions{Model: "all-minilm"}, + } + + _, err := embed(context.Background(), "", req) + if err == nil || !strings.Contains(err.Error(), "invalid server address") { + t.Fatalf("expected invalid server address error, got %v", err) + } +} From 4ac245349273cacb6dd2381c6b3aa4869c896795 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 29 Aug 2024 21:03:11 -0500 Subject: [PATCH 4/9] nit --- go/plugins/ollama/embed.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/go/plugins/ollama/embed.go b/go/plugins/ollama/embed.go index 5556281fc..8dec5a3ea 100644 --- a/go/plugins/ollama/embed.go +++ b/go/plugins/ollama/embed.go @@ -28,7 +28,7 @@ import ( var supportedEmbeddingModels = []string{ "mxbai-embed-large", "nomic-embed-text", - "all-minilm", // Default if not specified + "all-minilm", } type EmbedOptions struct { @@ -37,7 +37,7 @@ type EmbedOptions struct { type ollamaEmbedRequest struct { Model string `json:"model"` - Input interface{} `json:"input"` // Change to interface{} to handle both string and []string + Input interface{} `json:"input"` // todo: using interface{} to handle both string and []string, figure out better solution Options map[string]interface{} `json:"options,omitempty"` } @@ -45,7 +45,6 @@ type ollamaEmbedResponse struct { Embeddings [][]float32 `json:"embeddings"` } -// embed performs the actual embedding request to the Ollama server func embed(ctx context.Context, serverAddress string, req *ai.EmbedRequest) (*ai.EmbedResponse, error) { options, ok := req.Options.(*EmbedOptions) if !ok && req.Options != nil { @@ -87,7 +86,6 @@ func embed(ctx context.Context, serverAddress string, req *ai.EmbedRequest) (*ai return newEmbedResponse(ollamaResp.Embeddings), nil } -// getEmbeddingModel determines the appropriate embedding model to use func getEmbeddingModel(options *EmbedOptions) string { model := options.Model for _, supportedModel := range supportedEmbeddingModels { @@ -98,7 +96,6 @@ func getEmbeddingModel(options *EmbedOptions) string { return "" } -// sendEmbedRequest sends the actual HTTP request to the Ollama server func sendEmbedRequest(ctx context.Context, serverAddress string, jsonData []byte) (*http.Response, error) { client := &http.Client{} httpReq, err := http.NewRequestWithContext(ctx, "POST", serverAddress+"/api/embed", bytes.NewBuffer(jsonData)) @@ -137,7 +134,6 @@ func newEmbedResponse(embeddings [][]float32) *ai.EmbedResponse { return resp } -// concatenateText combines all text content from a document into a single string. func concatenateText(doc *ai.Document) string { var builder strings.Builder for _, part := range doc.Content { From 164bb2d42941a308851c7959ff0ddc86e3089fe9 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 29 Aug 2024 21:07:11 -0500 Subject: [PATCH 5/9] revert rag sample changes --- go/samples/rag/main.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/go/samples/rag/main.go b/go/samples/rag/main.go index 1bc4aba8a..754548555 100644 --- a/go/samples/rag/main.go +++ b/go/samples/rag/main.go @@ -44,7 +44,6 @@ import ( "github.com/firebase/genkit/go/plugins/dotprompt" "github.com/firebase/genkit/go/plugins/googleai" "github.com/firebase/genkit/go/plugins/localvec" - "github.com/firebase/genkit/go/plugins/ollama" "github.com/invopop/jsonschema" ) @@ -73,15 +72,7 @@ func main() { log.Fatal(err) } model := googleai.Model("gemini-1.0-pro") - - err = ollama.Init(context.Background(), &ollama.Config{ - ServerAddress: "http://localhost:11434", - }) - if err != nil { - log.Fatal(err) - } - embedder := ollama.DefineEmbedder("all-minilm") - + embedder := googleai.Embedder("embedding-001") if err := localvec.Init(); err != nil { log.Fatal(err) } From 3833e8737dd17b8e633ac72310eccc634d913a4f Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 29 Aug 2024 21:09:20 -0500 Subject: [PATCH 6/9] fix format issue --- go/plugins/ollama/embed_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/go/plugins/ollama/embed_test.go b/go/plugins/ollama/embed_test.go index 5aae2da60..7441f6410 100644 --- a/go/plugins/ollama/embed_test.go +++ b/go/plugins/ollama/embed_test.go @@ -1,3 +1,17 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package ollama import ( From 34385ff86c9cd1d239e3b63b5fa99ec534ee0aac Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 13 Sep 2024 10:55:57 -0500 Subject: [PATCH 7/9] Update embedder to remove hardcoding models --- go/plugins/ollama/embed.go | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/go/plugins/ollama/embed.go b/go/plugins/ollama/embed.go index 8dec5a3ea..4f86f1775 100644 --- a/go/plugins/ollama/embed.go +++ b/go/plugins/ollama/embed.go @@ -25,12 +25,6 @@ import ( "github.com/firebase/genkit/go/ai" ) -var supportedEmbeddingModels = []string{ - "mxbai-embed-large", - "nomic-embed-text", - "all-minilm", -} - type EmbedOptions struct { Model string `json:"model,omitempty"` } @@ -50,18 +44,15 @@ func embed(ctx context.Context, serverAddress string, req *ai.EmbedRequest) (*ai if !ok && req.Options != nil { return nil, fmt.Errorf("invalid options type: expected *EmbedOptions") } - - model := getEmbeddingModel(options) - - if model == "" { - return nil, fmt.Errorf("invalid embedding model: model cannot be empty") + if options == nil || options.Model == "" { + return nil, fmt.Errorf("invalid embedding model: model must be specified") } if serverAddress == "" { return nil, fmt.Errorf("invalid server address: address cannot be empty") } - ollamaReq := newOllamaEmbedRequest(model, req.Documents) + ollamaReq := newOllamaEmbedRequest(options.Model, req.Documents) jsonData, err := json.Marshal(ollamaReq) if err != nil { @@ -86,16 +77,6 @@ func embed(ctx context.Context, serverAddress string, req *ai.EmbedRequest) (*ai return newEmbedResponse(ollamaResp.Embeddings), nil } -func getEmbeddingModel(options *EmbedOptions) string { - model := options.Model - for _, supportedModel := range supportedEmbeddingModels { - if model == supportedModel { - return model - } - } - return "" -} - func sendEmbedRequest(ctx context.Context, serverAddress string, jsonData []byte) (*http.Response, error) { client := &http.Client{} httpReq, err := http.NewRequestWithContext(ctx, "POST", serverAddress+"/api/embed", bytes.NewBuffer(jsonData)) @@ -144,13 +125,16 @@ func concatenateText(doc *ai.Document) string { } // DefineEmbedder defines an embedder with a given server address. -func DefineEmbedder(serverAddress string) ai.Embedder { +func DefineEmbedder(serverAddress string, model string) ai.Embedder { state.mu.Lock() defer state.mu.Unlock() if !state.initted { panic("ollama.Init not called") } return ai.DefineEmbedder(provider, serverAddress, func(ctx context.Context, req *ai.EmbedRequest) (*ai.EmbedResponse, error) { + if req.Options == nil { + req.Options = &EmbedOptions{Model: model} + } return embed(ctx, serverAddress, req) }) } From b417bb73efae17b3e32f487b610256436ed31106 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 13 Sep 2024 10:59:36 -0500 Subject: [PATCH 8/9] remove test --- go/plugins/ollama/embed_test.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/go/plugins/ollama/embed_test.go b/go/plugins/ollama/embed_test.go index 7441f6410..d5c0190a4 100644 --- a/go/plugins/ollama/embed_test.go +++ b/go/plugins/ollama/embed_test.go @@ -51,20 +51,6 @@ func TestEmbedValidRequest(t *testing.T) { } } -func TestEmbedInvalidModel(t *testing.T) { - req := &ai.EmbedRequest{ - Documents: []*ai.Document{ - ai.DocumentFromText("test", nil), - }, - Options: &EmbedOptions{Model: "invalid-model"}, - } - - _, err := embed(context.Background(), "http://localhost:11434", req) - if err == nil || !strings.Contains(err.Error(), "invalid embedding model") { - t.Fatalf("expected invalid model error, got %v", err) - } -} - func TestEmbedInvalidServerAddress(t *testing.T) { req := &ai.EmbedRequest{ Documents: []*ai.Document{ From 6cfc6596fa3cb93d49b4c093813a6b0ceb9fceb4 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 13 Sep 2024 12:00:27 -0500 Subject: [PATCH 9/9] Add check to see if model is empty --- go/plugins/ollama/embed.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/go/plugins/ollama/embed.go b/go/plugins/ollama/embed.go index 4f86f1775..051514420 100644 --- a/go/plugins/ollama/embed.go +++ b/go/plugins/ollama/embed.go @@ -26,7 +26,7 @@ import ( ) type EmbedOptions struct { - Model string `json:"model,omitempty"` + Model string `json:"model"` } type ollamaEmbedRequest struct { @@ -135,6 +135,9 @@ func DefineEmbedder(serverAddress string, model string) ai.Embedder { if req.Options == nil { req.Options = &EmbedOptions{Model: model} } + if req.Options.(*EmbedOptions).Model == "" { + req.Options.(*EmbedOptions).Model = model + } return embed(ctx, serverAddress, req) }) }