mirror of
https://github.com/Warky-Devs/vecna.git
synced 2026-05-05 01:26:58 +00:00
* Introduced ExtraMapConfig to allow multiple adapter configurations. * Updated server and handler to utilize extra maps for routing. * Added dashboard handler for metrics visualization.
176 lines
5.5 KiB
Go
176 lines
5.5 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/uptrace/bunrouter"
|
|
|
|
"github.com/Warky-Devs/vecna.git/pkg/adapter"
|
|
"github.com/Warky-Devs/vecna.git/pkg/embedclient"
|
|
)
|
|
|
|
// googleDispatch parses the wildcard "model:action" segment and routes to the
|
|
// correct Google handler. The colon is a literal method separator in the
|
|
// Google embedding API, not a bunrouter parameter prefix.
|
|
func (h *handler) googleDispatch(w http.ResponseWriter, req bunrouter.Request) error {
|
|
return h.googleDispatchWithAdapter(w, req, h.adapter, "")
|
|
}
|
|
|
|
func (h *handler) googleDispatchMapped(w http.ResponseWriter, req bunrouter.Request) error {
|
|
em, err := h.resolveExtraMap(req.Param("mapping"))
|
|
if err != nil {
|
|
return writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
|
}
|
|
return h.googleDispatchWithAdapter(w, req, em.Adapter, em.ForwardTarget)
|
|
}
|
|
|
|
func (h *handler) googleDispatchWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error {
|
|
modelaction := req.Param("modelaction") // e.g. "text-embedding-foo:embedContent"
|
|
idx := strings.LastIndex(modelaction, ":")
|
|
if idx < 0 {
|
|
return writeJSON(w, http.StatusNotFound, map[string]string{"error": "invalid Google API path"})
|
|
}
|
|
model := modelaction[:idx]
|
|
action := modelaction[idx+1:]
|
|
|
|
ctx := context.WithValue(req.Context(), modelKey, model)
|
|
req = req.WithContext(ctx)
|
|
|
|
switch action {
|
|
case "embedContent":
|
|
return h.googleEmbedContentWithAdapter(w, req, adp, targetOverride)
|
|
case "batchEmbedContents":
|
|
return h.googleBatchEmbedContentsWithAdapter(w, req, adp, targetOverride)
|
|
default:
|
|
return writeJSON(w, http.StatusNotFound, map[string]string{"error": "unknown Google API method: " + action})
|
|
}
|
|
}
|
|
|
|
// --- single embedContent ---
|
|
|
|
type googleEmbedContentRequest struct {
|
|
Content googleContent `json:"content"`
|
|
TaskType string `json:"taskType,omitempty"`
|
|
}
|
|
|
|
type googleContent struct {
|
|
Parts []googlePart `json:"parts"`
|
|
}
|
|
|
|
type googlePart struct {
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
type googleEmbedContentResponse struct {
|
|
Embedding googleEmbeddingValues `json:"embedding"`
|
|
}
|
|
|
|
type googleEmbeddingValues struct {
|
|
Values []float32 `json:"values"`
|
|
}
|
|
|
|
func (h *handler) googleEmbedContentWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error {
|
|
model, _ := req.Context().Value(modelKey).(string)
|
|
|
|
var body googleEmbedContentRequest
|
|
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
|
return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
|
}
|
|
|
|
texts := make([]string, len(body.Content.Parts))
|
|
for i, p := range body.Content.Parts {
|
|
texts[i] = p.Text
|
|
}
|
|
|
|
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 writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
}
|
|
trace.ForwardModel = embedResp.Model
|
|
trace.PromptTokens = embedResp.Usage.PromptTokens
|
|
trace.TotalTokens = embedResp.Usage.TotalTokens
|
|
|
|
t1 := time.Now()
|
|
var adapted []float32
|
|
if len(embedResp.Embeddings) > 0 {
|
|
adapted, err = adp.Adapt(embedResp.Embeddings[0])
|
|
if err != nil {
|
|
return writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
}
|
|
}
|
|
trace.TranslateDuration = time.Since(t1)
|
|
|
|
writeTraceHeaders(w, trace)
|
|
|
|
return writeJSON(w, http.StatusOK, googleEmbedContentResponse{
|
|
Embedding: googleEmbeddingValues{Values: adapted},
|
|
})
|
|
}
|
|
|
|
// --- batch batchEmbedContents ---
|
|
|
|
type googleBatchRequest struct {
|
|
Requests []googleEmbedContentRequest `json:"requests"`
|
|
}
|
|
|
|
type googleBatchResponse struct {
|
|
Embeddings []googleEmbeddingValues `json:"embeddings"`
|
|
}
|
|
|
|
func (h *handler) googleBatchEmbedContentsWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error {
|
|
model, _ := req.Context().Value(modelKey).(string)
|
|
|
|
var body googleBatchRequest
|
|
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
|
return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
|
}
|
|
|
|
var texts []string
|
|
for _, r := range body.Requests {
|
|
for _, p := range r.Content.Parts {
|
|
texts = append(texts, p.Text)
|
|
}
|
|
}
|
|
|
|
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 writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
}
|
|
trace.ForwardModel = embedResp.Model
|
|
trace.PromptTokens = embedResp.Usage.PromptTokens
|
|
trace.TotalTokens = embedResp.Usage.TotalTokens
|
|
|
|
t1 := time.Now()
|
|
result := make([]googleEmbeddingValues, len(embedResp.Embeddings))
|
|
for i, vec := range embedResp.Embeddings {
|
|
adapted, adaptErr := adp.Adapt(vec)
|
|
if adaptErr != nil {
|
|
return writeJSON(w, http.StatusInternalServerError, map[string]string{"error": adaptErr.Error()})
|
|
}
|
|
result[i] = googleEmbeddingValues{Values: adapted}
|
|
}
|
|
trace.TranslateDuration = time.Since(t1)
|
|
|
|
writeTraceHeaders(w, trace)
|
|
|
|
return writeJSON(w, http.StatusOK, googleBatchResponse{Embeddings: result})
|
|
}
|