package server import ( "encoding/json" "fmt" "net/http" "time" "github.com/uptrace/bunrouter" "github.com/Warky-Devs/vecna.git/pkg/adapter" "github.com/Warky-Devs/vecna.git/pkg/embedclient" ) type openAIEmbedRequest struct { Input interface{} `json:"input"` // string or []string Model string `json:"model"` } type openAIEmbedResponse struct { Object string `json:"object"` Data []openAIEmbedDatum `json:"data"` Model string `json:"model"` Usage openAIUsage `json:"usage"` } type openAIEmbedDatum struct { Object string `json:"object"` Embedding []float32 `json:"embedding"` Index int `json:"index"` } type openAIUsage struct { PromptTokens int `json:"prompt_tokens"` TotalTokens int `json:"total_tokens"` } func (h *handler) openAIEmbeddings(w http.ResponseWriter, req bunrouter.Request) error { return h.openAIEmbeddingsWithAdapter(w, req, h.adapter, "") } func (h *handler) openAIEmbeddingsMapped(w http.ResponseWriter, req bunrouter.Request) error { em, err := h.resolveExtraMap(req.Param("mapping")) if err != nil { return writeError(w, http.StatusNotFound, err.Error()) } return h.openAIEmbeddingsWithAdapter(w, req, em.Adapter, em.ForwardTarget) } // openAIEmbeddingsGet is a GET convenience variant (e.g. for browser/curl testing): // ?input=foo&input=bar&model=name in place of a JSON body. func (h *handler) openAIEmbeddingsGet(w http.ResponseWriter, req bunrouter.Request) error { return h.openAIEmbeddingsGetWithAdapter(w, req, h.adapter, "") } func (h *handler) openAIEmbeddingsGetMapped(w http.ResponseWriter, req bunrouter.Request) error { em, err := h.resolveExtraMap(req.Param("mapping")) if err != nil { return writeError(w, http.StatusNotFound, err.Error()) } return h.openAIEmbeddingsGetWithAdapter(w, req, em.Adapter, em.ForwardTarget) } func (h *handler) openAIEmbeddingsGetWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error { q := req.URL.Query() texts := q["input"] if len(texts) == 0 { return writeError(w, http.StatusBadRequest, "query parameter \"input\" is required") } return h.processEmbeddings(w, req, adp, targetOverride, texts, q.Get("model")) } func (h *handler) openAIEmbeddingsWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error { var body openAIEmbedRequest if err := json.NewDecoder(req.Body).Decode(&body); err != nil { return writeError(w, http.StatusBadRequest, "invalid request body") } texts, err := toStringSlice(body.Input) if err != nil { return writeError(w, http.StatusBadRequest, err.Error()) } return h.processEmbeddings(w, req, adp, targetOverride, texts, body.Model) } func (h *handler) processEmbeddings(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string, texts []string, model string) error { client, targetName, targetURL := h.resolveClientOverride(targetOverride, model) trace := TraceFromContext(req.Context()) trace.ForwardTarget = targetName trace.ForwardURL = targetURL t0 := time.Now() embedResp, err := client.Embed(req.Context(), embedclient.Request{Texts: texts, Model: model}) trace.ForwardDuration = time.Since(t0) if err != nil { return writeError(w, http.StatusBadGateway, err.Error()) } trace.ForwardModel = embedResp.Model trace.PromptTokens = embedResp.Usage.PromptTokens trace.TotalTokens = embedResp.Usage.TotalTokens t1 := time.Now() data := make([]openAIEmbedDatum, len(embedResp.Embeddings)) for i, vec := range embedResp.Embeddings { adapted, adaptErr := adp.Adapt(vec) if adaptErr != nil { return writeError(w, http.StatusInternalServerError, adaptErr.Error()) } data[i] = openAIEmbedDatum{Object: "embedding", Embedding: adapted, Index: i} } trace.TranslateDuration = time.Since(t1) writeTraceHeaders(w, trace) return writeJSON(w, http.StatusOK, openAIEmbedResponse{ Object: "list", Data: data, Model: embedResp.Model, Usage: openAIUsage{PromptTokens: embedResp.Usage.PromptTokens, TotalTokens: embedResp.Usage.TotalTokens}, }) } // toStringSlice accepts a JSON string or array of strings. func toStringSlice(v interface{}) ([]string, error) { switch val := v.(type) { case string: return []string{val}, nil case []interface{}: out := make([]string, len(val)) for i, item := range val { s, ok := item.(string) if !ok { return nil, fmt.Errorf("input array element %d is not a string", i) } out[i] = s } return out, nil default: return nil, fmt.Errorf("input must be a string or array of strings") } }