Added call to embedding openai

This commit is contained in:
Radu Macocian (admac)
2026-06-23 14:36:34 +02:00
parent 8599de09de
commit 05b1f53f93
8 changed files with 458 additions and 181 deletions
+37 -27
View File
@@ -16,9 +16,9 @@ import (
const COMPLETIONS_API = "/v1/chat/completions"
const MODELS_API = "/v1/models"
type LLMClient struct {
type ChatClient struct {
Cfg config.Config
Logger slog.Logger
logger slog.Logger
}
// Request Types
@@ -52,22 +52,29 @@ type Usage struct {
TotalTokens int `json:"total_tokens"`
}
func (client LLMClient) Models() ([]string, error) {
resp, err := http.Get(client.Cfg.Api.Url + MODELS_API)
func NewLLMClient(cfg config.Config) *ChatClient {
return &ChatClient{
Cfg: cfg,
logger: *slog.Default().With("Component", "ChatClient"),
}
}
func (cl ChatClient) Models() ([]string, error) {
resp, err := http.Get(cl.Cfg.Api.Url + MODELS_API)
if err != nil {
client.Logger.Error("There was an error during the network request", "error", err.Error())
cl.logger.Error("There was an error during the network request", "error", err.Error())
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
client.Logger.Error("There was an error reading the response body", "error", err.Error())
cl.logger.Error("There was an error reading the response body", "error", err.Error())
return nil, err
}
if resp.StatusCode != 200 && resp.StatusCode != 202 {
client.Logger.Error("Call returned non 200 status",
cl.logger.Error("Call returned non 200 status",
"statusCode", resp.StatusCode,
"status", resp.Status)
return nil,
@@ -75,18 +82,18 @@ func (client LLMClient) Models() ([]string, error) {
strconv.Itoa(resp.StatusCode) +
resp.Status)
}
client.Logger.Debug("Received data", "data", data)
cl.logger.Debug("Received data", "data", data)
var response struct{
Data []struct{
var response struct {
Data []struct {
Id string
}
}
if err := json.Unmarshal(data, &response); err != nil {
client.Logger.Error("There was an error parsing the response", "error", err.Error())
cl.logger.Error("There was an error parsing the response", "error", err.Error())
return nil, err
}
client.Logger.Debug("Unmarshal response", "data", response)
cl.logger.Debug("Unmarshal response", "data", response)
var ret []string
for _, m := range response.Data {
ret = append(ret, m.Id)
@@ -94,43 +101,46 @@ func (client LLMClient) Models() ([]string, error) {
return ret, nil
}
func (client LLMClient) Call(query string, messages []model.Message) ([]model.Message, error) {
func (cl ChatClient) Call(query string, messages []model.Message) ([]model.Message, error) {
requestBody := ChatRequest{
Model: client.Cfg.Llm.Model,
Model: cl.Cfg.Llm.Model,
Messages: messages,
Tools: config.AvailableTools(),
}
serialized, err := json.Marshal(requestBody)
if err != nil {
cl.logger.Error("There was an error marshelling the request body",
"request", requestBody,
"error", err)
return []model.Message{}, err
}
body := bytes.NewBuffer(serialized)
req, err := http.NewRequest("POST", client.Cfg.Api.Url + COMPLETIONS_API, body)
req, err := http.NewRequest("POST", cl.Cfg.Api.Url+COMPLETIONS_API, body)
if err != nil {
client.Logger.Error("There was an error creating the request", "error", err.Error())
cl.logger.Error("There was an error creating the request", "error", err.Error())
return []model.Message{}, err
}
req.Header.Set("Content-Type", "application/json")
if client.Cfg.Api.Key != "" {
req.Header.Set("Authorization", "Bearer "+client.Cfg.Api.Key)
if cl.Cfg.Api.Key != "" {
req.Header.Set("Authorization", "Bearer "+cl.Cfg.Api.Key)
}
client.Logger.Debug("Executing llm call", "req", req)
cl.logger.Debug("Executing llm call", "req", req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
client.Logger.Error("There was an error during http call", "error", err.Error())
cl.logger.Error("There was an error during http call", "error", err.Error())
return []model.Message{}, err
}
client.Logger.Debug("LLM Call returned", "response", resp)
defer resp.Body.Close()
cl.logger.Debug("LLM Call returned", "response", resp)
data, err := io.ReadAll(resp.Body)
if err != nil {
client.Logger.Error("There was an error reading the response body", "error", err.Error())
cl.logger.Error("There was an error reading the response body", "error", err.Error())
return []model.Message{}, err
}
if resp.StatusCode != 200 && resp.StatusCode != 202 {
client.Logger.Error("Call returned non 200 status",
cl.logger.Error("Call returned non 200 status",
"statusCode", resp.StatusCode,
"status", resp.Status)
return []model.Message{},
@@ -139,20 +149,20 @@ func (client LLMClient) Call(query string, messages []model.Message) ([]model.Me
resp.Status)
}
client.Logger.Debug("Received data", "data", data)
cl.logger.Debug("Received data", "data", data)
var response ChatResponse
if err := json.Unmarshal(data, &response); err != nil {
client.Logger.Error("There was an error parsing the response", "error", err.Error())
cl.logger.Error("There was an error parsing the response", "error", err.Error())
return []model.Message{}, err
}
var result []model.Message
if len(response.Choices) == 0 {
client.Logger.Error("The call returned an empty response")
cl.logger.Error("The call returned an empty response")
return []model.Message{}, errors.New("The call returned an empty response")
}
for _, choice := range response.Choices {
client.Logger.Debug("Appending response message", "message", choice.Message)
cl.logger.Debug("Appending response message", "message", choice.Message)
result = append(result, choice.Message)
}
+133
View File
@@ -0,0 +1,133 @@
package llm
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
config "git.estatecloud.org/radumaco/souvenir/config"
)
const EMBEDDINGS_API = "/v1/embeddings"
type Embedder struct {
Cfg config.EmbeddingConfig
logger slog.Logger
id string
}
type EmbedRequest struct {
Input []string `json:"input"`
Model string `json:"model"`
EncodingFormat string `json:"encoding_format"`
}
type EmbedResponse struct {
Data []Embedding `json:"data"`
Model string `json:"model"`
Object string `json:"object"`
Usage Usage `json:"usage"`
}
type Embedding struct {
Embedding []float32 `json:"embeddgin"`
Index int `json:"index"`
Object string `json:"object"`
}
func NewEmbedder(cfg config.EmbeddingConfig) *Embedder {
return &Embedder{
Cfg: cfg,
logger: *slog.Default().With("Component", "Embedder"),
}
}
func (e Embedder) ID() string {
if e.id == "" {
e.id = strings.Map(func(r rune) rune {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r <= '0' && r <= '9':
return r
default:
return '_'
}
}, e.Cfg.Model)
}
return e.id
}
func (e Embedder) EmbedBatch(text []string) ([][]float32, error) {
requestBody := EmbedRequest{
Model: e.Cfg.Model,
Input: text,
EncodingFormat: "float",
}
serialized, err := json.Marshal(requestBody)
if err != nil {
e.logger.Error("There was an error marshelling the request body",
"request", requestBody,
"error", err)
return [][]float32{}, err
}
body := bytes.NewBuffer(serialized)
req, err := http.NewRequest("POST", e.Cfg.Url+EMBEDDINGS_API, body)
if err != nil {
e.logger.Error("There was an error creating the request", "error", err.Error())
return [][]float32{}, err
}
req.Header.Set("Content-Type", "application/json")
if e.Cfg.Key != "" {
req.Header.Set("Authorization", "Bearer "+e.Cfg.Key)
}
e.logger.Debug("Executing embedding call", "req", req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
e.logger.Error("There was an error during http call", "error", err.Error())
return [][]float32{}, err
}
defer resp.Body.Close()
e.logger.Debug("LLM Call returned", "response", resp)
data, err := io.ReadAll(resp.Body)
if err != nil {
e.logger.Error("There was an error reading the response body", "error", err.Error())
return [][]float32{}, err
}
if resp.StatusCode != 200 && resp.StatusCode != 202 {
e.logger.Error("Call returned non 200 status",
"statusCode", resp.StatusCode,
"status", resp.Status)
return [][]float32{},
errors.New("Call returned invalid status " +
resp.Status)
}
e.logger.Debug("Received data", "data", data)
var response EmbedResponse
if err := json.Unmarshal(data, &response); err != nil {
e.logger.Error("There was an error parsing the response", "error", err.Error())
return [][]float32{}, err
}
if len(response.Data) == 0 {
e.logger.Error("The call returned empty data")
return [][]float32{}, errors.New("The call returned empty data")
}
result := make([][]float32, len(text))
for _, embed := range response.Data {
if embed.Index < 0 || embed.Index >= len(result) {
return nil, fmt.Errorf("Embedding index out of range. Index:%d Length:%d",
embed.Index, len(result))
}
e.logger.Debug("Appening embbeding", "embed", embed.Embedding)
result[embed.Index] = embed.Embedding
}
return result, nil
}