mirror of
https://github.com/Warky-Devs/vecna.git
synced 2026-05-05 01:26:58 +00:00
fix(server): handle router setup panic and return error
* return error from New function if route registration panics * add googleDispatch to handle model:action routing
This commit is contained in:
@@ -58,7 +58,10 @@ func runServe(cmd *cobra.Command, _ []string) error {
|
|||||||
reg = metrics.New()
|
reg = metrics.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
router := server.New(cfg, clients, adp, reg, logger)
|
router, err := server.New(cfg, clients, adp, reg, logger)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("build router: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
|
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/uptrace/bunrouter"
|
"github.com/uptrace/bunrouter"
|
||||||
@@ -10,6 +12,31 @@ import (
|
|||||||
"github.com/Warky-Devs/vecna.git/pkg/embedclient"
|
"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 {
|
||||||
|
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.googleEmbedContent(w, req)
|
||||||
|
case "batchEmbedContents":
|
||||||
|
return h.googleBatchEmbedContents(w, req)
|
||||||
|
default:
|
||||||
|
return writeJSON(w, http.StatusNotFound, map[string]string{"error": "unknown Google API method: " + action})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- single embedContent ---
|
// --- single embedContent ---
|
||||||
|
|
||||||
type googleEmbedContentRequest struct {
|
type googleEmbedContentRequest struct {
|
||||||
@@ -34,7 +61,7 @@ type googleEmbeddingValues struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) googleEmbedContent(w http.ResponseWriter, req bunrouter.Request) error {
|
func (h *handler) googleEmbedContent(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
model := req.Param("model")
|
model, _ := req.Context().Value(modelKey).(string)
|
||||||
|
|
||||||
var body googleEmbedContentRequest
|
var body googleEmbedContentRequest
|
||||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
||||||
@@ -89,7 +116,7 @@ type googleBatchResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) googleBatchEmbedContents(w http.ResponseWriter, req bunrouter.Request) error {
|
func (h *handler) googleBatchEmbedContents(w http.ResponseWriter, req bunrouter.Request) error {
|
||||||
model := req.Param("model")
|
model, _ := req.Context().Value(modelKey).(string)
|
||||||
|
|
||||||
var body googleBatchRequest
|
var body googleBatchRequest
|
||||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
||||||
|
|||||||
@@ -18,14 +18,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New builds and returns a configured bunrouter.Router.
|
// New builds and returns a configured bunrouter.Router.
|
||||||
|
// Returns an error if route registration panics (e.g. conflicting routes).
|
||||||
func New(
|
func New(
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
clients map[string]embedclient.Client,
|
clients map[string]embedclient.Client,
|
||||||
adp adapter.Adapter,
|
adp adapter.Adapter,
|
||||||
reg *metrics.Registry,
|
reg *metrics.Registry,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) *bunrouter.Router {
|
) (router *bunrouter.Router, err error) {
|
||||||
router := bunrouter.New(
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Error("panic during router setup", zap.Any("recover", r))
|
||||||
|
err = fmt.Errorf("router setup panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
router = bunrouter.New(
|
||||||
bunrouter.WithMiddleware(authMiddleware(cfg.Server.APIKeys)),
|
bunrouter.WithMiddleware(authMiddleware(cfg.Server.APIKeys)),
|
||||||
bunrouter.WithMiddleware(traceMiddleware()),
|
bunrouter.WithMiddleware(traceMiddleware()),
|
||||||
bunrouter.WithMiddleware(metricsMiddleware(reg, adp)),
|
bunrouter.WithMiddleware(metricsMiddleware(reg, adp)),
|
||||||
@@ -35,8 +43,10 @@ func New(
|
|||||||
h := &handler{cfg: cfg, clients: clients, adapter: adp, logger: logger}
|
h := &handler{cfg: cfg, clients: clients, adapter: adp, logger: logger}
|
||||||
|
|
||||||
router.POST("/v1/embeddings", h.openAIEmbeddings)
|
router.POST("/v1/embeddings", h.openAIEmbeddings)
|
||||||
router.POST("/v1/models/:model:embedContent", h.googleEmbedContent)
|
// Google API uses a literal colon as a method separator (e.g. /v1/models/foo:embedContent).
|
||||||
router.POST("/v1/models/:model:batchEmbedContents", h.googleBatchEmbedContents)
|
// bunrouter can't distinguish two routes with the same :param prefix, so a single wildcard
|
||||||
|
// captures the full "model:action" segment and dispatches internally.
|
||||||
|
router.POST("/v1/models/*modelaction", h.googleDispatch)
|
||||||
|
|
||||||
// OpenAPI spec + docs
|
// OpenAPI spec + docs
|
||||||
router.GET("/openapi.yaml", spec.SpecHandler())
|
router.GET("/openapi.yaml", spec.SpecHandler())
|
||||||
@@ -59,7 +69,7 @@ func New(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return router
|
return router, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// authMiddleware rejects requests without a valid Bearer token when api_keys is configured.
|
// authMiddleware rejects requests without a valid Bearer token when api_keys is configured.
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import (
|
|||||||
|
|
||||||
type contextKey int
|
type contextKey int
|
||||||
|
|
||||||
const traceKey contextKey = iota
|
const (
|
||||||
|
traceKey contextKey = iota
|
||||||
|
modelKey
|
||||||
|
)
|
||||||
|
|
||||||
// RequestTrace holds per-request timing data populated by handlers and middleware.
|
// RequestTrace holds per-request timing data populated by handlers and middleware.
|
||||||
type RequestTrace struct {
|
type RequestTrace struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user