Skip to content

Commit

Permalink
#235: Extended the non-streaming chat error schema with new fields to…
Browse files Browse the repository at this point in the history
… give clients more context around the error
  • Loading branch information
roma-glushko committed May 5, 2024
1 parent 2db285c commit 99f5bb8
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 91 deletions.
35 changes: 13 additions & 22 deletions pkg/api/http/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package http

import (
"context"
"errors"
"sync"

"github.com/EinStack/glide/pkg/telemetry"
Expand Down Expand Up @@ -38,39 +37,33 @@ type Handler = func(c *fiber.Ctx) error
func LangChatHandler(routerManager *routers.RouterManager) Handler {
return func(c *fiber.Ctx) error {
if !c.Is("json") {
return c.Status(fiber.StatusBadRequest).JSON(ErrorSchema{
Message: "Glide accepts only JSON payloads",
})
return c.Status(fiber.StatusBadRequest).JSON(schemas.ErrUnsupportedMediaType)
}

// Unmarshal request body
var req *schemas.ChatRequest

err := c.BodyParser(&req)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(ErrorSchema{
Message: err.Error(),
})
return c.Status(fiber.StatusBadRequest).JSON(schemas.NewPayloadParseErr(err))
}

// Get router ID from path
routerID := c.Params("router")

router, err := routerManager.GetLangRouter(routerID)
if err != nil {
httpErr := schemas.FromErr(err)

if errors.Is(err, routers.ErrRouterNotFound) {
// Return not found error
return c.Status(fiber.StatusNotFound).JSON(ErrorSchema{
Message: err.Error(),
})
return c.Status(httpErr.Status).JSON(httpErr)
}

// Chat with router
resp, err := router.Chat(c.Context(), req)
if err != nil {
// Return internal server error
return c.Status(fiber.StatusInternalServerError).JSON(ErrorSchema{
Message: err.Error(),
})
httpErr := schemas.FromErr(err)

return c.Status(httpErr.Status).JSON(httpErr)
}

// Return chat response
Expand All @@ -85,9 +78,9 @@ func LangStreamRouterValidator(routerManager *routers.RouterManager) Handler {

_, err := routerManager.GetLangRouter(routerID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(ErrorSchema{
Message: err.Error(),
})
httpErr := schemas.FromErr(err)

return c.Status(httpErr.Status).JSON(httpErr)
}

return c.Next()
Expand Down Expand Up @@ -208,7 +201,5 @@ func HealthHandler(c *fiber.Ctx) error {
}

func NotFoundHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusNotFound).JSON(ErrorSchema{
Message: "The route is not found",
})
return c.Status(fiber.StatusNotFound).JSON(schemas.ErrRouteNotFound)
}
4 changes: 0 additions & 4 deletions pkg/api/http/schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ package http

import "github.com/EinStack/glide/pkg/routers"

type ErrorSchema struct {
Message string `json:"message"`
}

type HealthSchema struct {
Healthy bool `json:"healthy"`
}
Expand Down
22 changes: 7 additions & 15 deletions pkg/api/schemas/chat_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,14 @@ type (
Metadata = map[string]any
EventType = string
FinishReason = string
ErrorCode = string
)

var (
Complete FinishReason = "complete"
MaxTokens FinishReason = "max_tokens"
ContentFiltered FinishReason = "content_filtered"
ErrorReason FinishReason = "error"
OtherReason FinishReason = "other"
)

var (
NoModelConfigured ErrorCode = "no_model_configured"
ModelUnavailable ErrorCode = "model_unavailable"
AllModelsUnavailable ErrorCode = "all_models_unavailable"
UnknownError ErrorCode = "unknown_error"
ReasonComplete FinishReason = "complete"
ReasonMaxTokens FinishReason = "max_tokens"
ReasonContentFiltered FinishReason = "content_filtered"
ReasonError FinishReason = "error"
ReasonOther FinishReason = "other"
)

type StreamRequestID = string
Expand Down Expand Up @@ -70,7 +62,7 @@ type ChatStreamChunk struct {
}

type ChatStreamError struct {
ErrCode ErrorCode `json:"errCode"`
ErrCode ErrorName `json:"errCode"`
Message string `json:"message"`
FinishReason *FinishReason `json:"finishReason,omitempty"`
}
Expand All @@ -93,7 +85,7 @@ func NewChatStreamChunk(
func NewChatStreamError(
reqID StreamRequestID,
routerID string,
errCode ErrorCode,
errCode ErrorName,
errMsg string,
reqMetadata *Metadata,
finishReason *FinishReason,
Expand Down
82 changes: 82 additions & 0 deletions pkg/api/schemas/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package schemas

import (
"fmt"

"github.com/gofiber/fiber/v2"
)

type ErrorName = string

var (
// General API errors
UnsupportedMediaType ErrorName = "http.unsupported_media_type"
RouteNotFound ErrorName = "http.not_found"
PayloadParseError ErrorName = "http.payload_parse_error"
UnknownError ErrorName = "http.unknown_error"

// Router-specific errors
RouterNotFound ErrorName = "routers.not_found"
NoModelConfigured ErrorName = "routers.no_model_configured"
ModelUnavailable ErrorName = "routers.model_unavailable"
AllModelsUnavailable ErrorName = "routers.all_models_unavailable"
)

// Error / Error contains more context than the built-in error type,
// so we know information like error code and message that are useful to propagate to clients
type Error struct {
Status int `json:"-"`
Name string `json:"name"`
Message string `json:"message"`
}

var _ error = &Error{}

// Error returns the error message.
func (e Error) Error() string {
return fmt.Sprintf("Error (%s): %s", e.Name, e.Message)
}

func NewError(status int, name string, message string) Error {
return Error{Status: status, Name: name, Message: message}
}

var ErrUnsupportedMediaType = NewError(
fiber.StatusBadRequest,
UnsupportedMediaType,
"application/json is the only supported media type",
)

var ErrRouteNotFound = NewError(
fiber.StatusNotFound,
RouteNotFound,
"requested route is not found",
)

var ErrRouterNotFound = NewError(fiber.StatusNotFound, RouterNotFound, "router is not found")

var ErrNoModelAvailable = NewError(
503,
AllModelsUnavailable,
"all providers are unavailable",
)

func NewPayloadParseErr(err error) Error {
return NewError(
fiber.StatusBadRequest,
PayloadParseError,
err.Error(),
)
}

func FromErr(err error) Error {
if apiErr, ok := err.(Error); ok {
return apiErr
}

return NewError(
fiber.StatusInternalServerError,
UnknownError,
err.Error(),
)
}
8 changes: 4 additions & 4 deletions pkg/providers/cohere/finish_reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ func (m *FinishReasonMapper) Map(finishReason *string) *schemas.FinishReason {

switch strings.ToLower(*finishReason) {
case CompleteReason:
reason = &schemas.Complete
reason = &schemas.ReasonComplete
case MaxTokensReason:
reason = &schemas.MaxTokens
reason = &schemas.ReasonMaxTokens
case FilteredReason:
reason = &schemas.ContentFiltered
reason = &schemas.ReasonContentFiltered
default:
m.tel.Logger.Warn(
"Unknown finish reason, other is going to used",
zap.String("unknown_reason", *finishReason),
)

reason = &schemas.OtherReason
reason = &schemas.ReasonOther
}

return reason
Expand Down
8 changes: 4 additions & 4 deletions pkg/providers/openai/finish_reasons.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ func (m *FinishReasonMapper) Map(finishReason string) *schemas.FinishReason {

switch finishReason {
case CompleteReason:
reason = &schemas.Complete
reason = &schemas.ReasonComplete
case MaxTokensReason:
reason = &schemas.MaxTokens
reason = &schemas.ReasonMaxTokens
case FilteredReason:
reason = &schemas.ContentFiltered
reason = &schemas.ReasonContentFiltered
default:
m.tel.Logger.Warn(
"Unknown finish reason, other is going to used",
zap.String("unknown_reason", finishReason),
)

reason = &schemas.OtherReason
reason = &schemas.ReasonOther
}

return reason
Expand Down
6 changes: 3 additions & 3 deletions pkg/providers/testing/lang.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// RespMock mocks a chat response or a streaming chat chunk
type RespMock struct {
Msg string
Err *error
Err error
}

func (m *RespMock) Resp() *schemas.ChatResponse {
Expand Down Expand Up @@ -81,7 +81,7 @@ func (m *RespStreamMock) Recv() (*schemas.ChatStreamChunk, error) {
m.idx++

if chunk.Err != nil {
return nil, *chunk.Err
return nil, chunk.Err
}

return chunk.RespChunk(), nil
Expand Down Expand Up @@ -130,7 +130,7 @@ func (c *ProviderMock) Chat(_ context.Context, _ *schemas.ChatRequest) (*schemas
c.idx++

if response.Err != nil {
return nil, *response.Err
return nil, response.Err
}

return response.Resp(), nil
Expand Down
7 changes: 2 additions & 5 deletions pkg/routers/manager.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package routers

import (
"errors"

"github.com/EinStack/glide/pkg/api/schemas"
"github.com/EinStack/glide/pkg/telemetry"
)

var ErrRouterNotFound = errors.New("no router found with given ID")

type RouterManager struct {
Config *Config
tel *telemetry.Telemetry
Expand Down Expand Up @@ -48,5 +45,5 @@ func (r *RouterManager) GetLangRouter(routerID string) (*LangRouter, error) {
return router, nil
}

return nil, ErrRouterNotFound
return nil, &schemas.ErrRouterNotFound
}
15 changes: 6 additions & 9 deletions pkg/routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import (
"github.com/EinStack/glide/pkg/api/schemas"
)

var (
ErrNoModels = errors.New("no models configured for router")
ErrNoModelAvailable = errors.New("could not handle request because all providers are not available")
)
var ErrNoModels = errors.New("no models configured for router")

type RouterID = string

Expand Down Expand Up @@ -124,7 +121,7 @@ func (r *LangRouter) Chat(ctx context.Context, req *schemas.ChatRequest) (*schem
// if we reach this part, then we are in trouble
r.logger.Error("No model was available to handle chat request")

return nil, ErrNoModelAvailable
return nil, schemas.ErrNoModelAvailable
}

func (r *LangRouter) ChatStream(
Expand All @@ -139,7 +136,7 @@ func (r *LangRouter) ChatStream(
schemas.NoModelConfigured,
ErrNoModels.Error(),
req.Metadata,
&schemas.ErrorReason,
&schemas.ReasonError,
)

return
Expand Down Expand Up @@ -239,9 +236,9 @@ func (r *LangRouter) ChatStream(
respC <- schemas.NewChatStreamError(
req.ID,
r.routerID,
schemas.AllModelsUnavailable,
ErrNoModelAvailable.Error(),
schemas.ErrNoModelAvailable.Name,
schemas.ErrNoModelAvailable.Message,
req.Metadata,
&schemas.ErrorReason,
&schemas.ReasonError,
)
}
Loading

0 comments on commit 99f5bb8

Please sign in to comment.