Skip to content

feat(go): use GenkitError instead of regular error #2643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
15e72fe
Addition of error handling in go along with error status
Apr 3, 2025
37f73f3
Addition of error handling in go along with error status
Apr 4, 2025
91b75f7
addition of Genkit Error in error.go
Apr 4, 2025
c17ce9a
fix in error code
Apr 4, 2025
12694bf
fix in test case
Apr 6, 2025
9c683b4
merge with main
Apr 6, 2025
48e895e
addition of UserError and removal of HTTPError
Apr 7, 2025
e258baa
removal of Genkit Error from genkit-schema.json as not in used
Apr 7, 2025
bfc3025
removal of GenkitError from schemas.config
Apr 7, 2025
505c97a
reverted GenkitError in gen.go to fix the build issue
Apr 7, 2025
f7c98a1
fix auth.go error
Apr 7, 2025
12dc24c
merge with main and resolve conflicts
Apr 8, 2025
42632b6
fixing build issue
Apr 8, 2025
1a5eb17
Fixed naming and various status codes.
apascal07 Apr 15, 2025
4588196
Update generate.go
apascal07 Apr 15, 2025
9aa5f6d
Update error.go
apascal07 Apr 15, 2025
5e9bc26
merge with main
Apr 15, 2025
8b76068
Merge branch 'main' into sahdev/go-generic-errors
Apr 16, 2025
66a043f
Addition go NewGenkitError, also refined the debug stack capturing
Apr 16, 2025
31141e7
Merge branch 'main' into sahdev/go-generic-errors
Apr 16, 2025
8c39f0a
change in newgenkit error debug stack handling
Apr 16, 2025
6015519
Renamed error functions.
apascal07 Apr 16, 2025
ba33b41
Delete mcp.go
apascal07 Apr 16, 2025
419e63b
Define Userfacing error type
Apr 17, 2025
fa67c71
Define Userfacing error type
Apr 17, 2025
6e205c6
fix test case because of UserFacingError handling
Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions go/ai/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,23 +181,6 @@ type GenerationUsage struct {
TotalTokens int `json:"totalTokens,omitempty"`
}

type GenkitError struct {
Data *GenkitErrorData `json:"data,omitempty"`
Details any `json:"details,omitempty"`
Message string `json:"message,omitempty"`
Stack string `json:"stack,omitempty"`
}

type GenkitErrorData struct {
GenkitErrorDetails *GenkitErrorDetails `json:"genkitErrorDetails,omitempty"`
GenkitErrorMessage string `json:"genkitErrorMessage,omitempty"`
}

type GenkitErrorDetails struct {
Stack string `json:"stack,omitempty"`
TraceID string `json:"traceId,omitempty"`
}

type Media struct {
ContentType string `json:"contentType,omitempty"`
Url string `json:"url,omitempty"`
Expand Down
33 changes: 17 additions & 16 deletions go/ai/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func LookupModel(r *registry.Registry, provider, name string) Model {
// It returns an error if the model was not defined.
func LookupModelByName(r *registry.Registry, modelName string) (Model, error) {
if modelName == "" {
return nil, errors.New("ai.LookupModelByName: model not specified")
return nil, core.NewError(core.INVALID_ARGUMENT, "ai.LookupModelByName: model not specified")
}

provider, name, found := strings.Cut(modelName, "/")
Expand All @@ -165,9 +165,9 @@ func LookupModelByName(r *registry.Registry, modelName string) (Model, error) {
model := LookupModel(r, provider, name)
if model == nil {
if provider == "" {
return nil, fmt.Errorf("ai.LookupModelByName: no model named %q", name)
return nil, core.NewError(core.NOT_FOUND, "ai.LookupModelByName: model %q not found", name)
}
return nil, fmt.Errorf("ai.LookupModelByName: no model named %q for provider %q", name, provider)
return nil, core.NewError(core.NOT_FOUND, "ai.LookupModelByName: model %q by provider %q not found", name, provider)
}

return model, nil
Expand All @@ -180,7 +180,7 @@ func GenerateWithRequest(ctx context.Context, r *registry.Registry, opts *Genera
opts.Model = defaultModel
}
if opts.Model == "" {
return nil, errors.New("ai.GenerateWithRequest: model is required")
return nil, core.NewError(core.INVALID_ARGUMENT, "ai.GenerateWithRequest: model is required")
}
}

Expand All @@ -193,12 +193,12 @@ func GenerateWithRequest(ctx context.Context, r *registry.Registry, opts *Genera
toolDefMap := make(map[string]*ToolDefinition)
for _, t := range opts.Tools {
if _, ok := toolDefMap[t]; ok {
return nil, fmt.Errorf("ai.GenerateWithRequest: duplicate tool found: %q", t)
return nil, core.NewError(core.INVALID_ARGUMENT, "ai.GenerateWithRequest: duplicate tool %q", t)
}

tool := LookupTool(r, t)
if tool == nil {
return nil, fmt.Errorf("ai.GenerateWithRequest: tool not found: %q", t)
return nil, core.NewError(core.NOT_FOUND, "ai.GenerateWithRequest: tool %q not found", t)
}

toolDefMap[t] = tool.Definition()
Expand All @@ -210,7 +210,7 @@ func GenerateWithRequest(ctx context.Context, r *registry.Registry, opts *Genera

maxTurns := opts.MaxTurns
if maxTurns < 0 {
return nil, fmt.Errorf("ai.GenerateWithRequest: max turns must be greater than 0, got %d", maxTurns)
return nil, core.NewError(core.INVALID_ARGUMENT, "ai.GenerateWithRequest: max turns must be greater than 0, got %d", maxTurns)
}
if maxTurns == 0 {
maxTurns = 5 // Default max turns.
Expand Down Expand Up @@ -276,7 +276,8 @@ func GenerateWithRequest(ctx context.Context, r *registry.Registry, opts *Genera
resp.Message, err = formatHandler.ParseMessage(resp.Message)
if err != nil {
logger.FromContext(ctx).Debug("model failed to generate output matching expected schema", "error", err.Error())
return nil, fmt.Errorf("model failed to generate output matching expected schema: %w", err)
return nil, core.NewError(core.INTERNAL, "model failed to generate output matching expected schema: %v", err)

}
}

Expand All @@ -291,7 +292,7 @@ func GenerateWithRequest(ctx context.Context, r *registry.Registry, opts *Genera
}

if currentTurn+1 > maxTurns {
return nil, fmt.Errorf("exceeded maximum tool call iterations (%d)", maxTurns)
return nil, core.NewError(core.ABORTED, "exceeded maximum tool call iterations (%d)", maxTurns)
}

newReq, interruptMsg, err := handleToolRequests(ctx, r, req, resp, cb)
Expand All @@ -318,7 +319,7 @@ func Generate(ctx context.Context, r *registry.Registry, opts ...GenerateOption)
genOpts := &generateOptions{}
for _, opt := range opts {
if err := opt.applyGenerate(genOpts); err != nil {
return nil, fmt.Errorf("ai.Generate: error applying options: %w", err)
return nil, core.NewError(core.INVALID_ARGUMENT, "ai.Generate: error applying options: %v", err)
}
}

Expand Down Expand Up @@ -421,7 +422,7 @@ func (m *model) Name() string {
// Generate applies the [Action] to provided request.
func (m *model) Generate(ctx context.Context, req *ModelRequest, cb ModelStreamCallback) (*ModelResponse, error) {
if m == nil {
return nil, errors.New("Model.Generate: generate called on a nil model; check that all models are defined")
return nil, core.NewError(core.INVALID_ARGUMENT, "Model.Generate: generate called on a nil model; check that all models are defined")
}

return (*core.ActionDef[*ModelRequest, *ModelResponse, *ModelResponseChunk])(m).Run(ctx, req, cb)
Expand Down Expand Up @@ -478,12 +479,12 @@ func cloneMessage(m *Message) *Message {
panic(fmt.Sprintf("failed to marshal message: %v", err))
}

var copy Message
if err := json.Unmarshal(bytes, &copy); err != nil {
var msgCopy Message
if err := json.Unmarshal(bytes, &msgCopy); err != nil {
panic(fmt.Sprintf("failed to unmarshal message: %v", err))
}

return &copy
return &msgCopy
}

// handleToolRequests processes any tool requests in the response, returning
Expand Down Expand Up @@ -520,7 +521,7 @@ func handleToolRequests(ctx context.Context, r *registry.Registry, req *ModelReq
toolReq := p.ToolRequest
tool := LookupTool(r, toolReq.Name)
if tool == nil {
resultChan <- toolResult{idx, nil, fmt.Errorf("tool %q not found", toolReq.Name)}
resultChan <- toolResult{idx, nil, core.NewError(core.NOT_FOUND, "tool %q not found", toolReq.Name)}
return
}

Expand All @@ -538,7 +539,7 @@ func handleToolRequests(ctx context.Context, r *registry.Registry, req *ModelReq
resultChan <- toolResult{idx, nil, interruptErr}
return
}
resultChan <- toolResult{idx, nil, fmt.Errorf("tool %q failed: %w", toolReq.Name, err)}
resultChan <- toolResult{idx, nil, core.NewError(core.INTERNAL, "tool %q failed: %v", toolReq.Name, err)}
return
}

Expand Down
23 changes: 12 additions & 11 deletions go/ai/model_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"strconv"
"strings"

"github.com/firebase/genkit/go/core"
"github.com/firebase/genkit/go/core/logger"
)

Expand Down Expand Up @@ -101,28 +102,28 @@ func validateSupport(model string, info *ModelInfo) ModelMiddleware {
for _, msg := range input.Messages {
for _, part := range msg.Content {
if part.IsMedia() {
return nil, fmt.Errorf("model %q does not support media, but media was provided. Request: %+v", model, input)
return nil, core.NewError(core.INVALID_ARGUMENT, "model %q does not support media, but media was provided. Request: %+v", model, input)
}
}
}
}

if !info.Supports.Tools && len(input.Tools) > 0 {
return nil, fmt.Errorf("model %q does not support tool use, but tools were provided. Request: %+v", model, input)
return nil, core.NewError(core.INVALID_ARGUMENT, "model %q does not support tool use, but tools were provided. Request: %+v", model, input)
}

if !info.Supports.Multiturn && len(input.Messages) > 1 {
return nil, fmt.Errorf("model %q does not support multiple messages, but %d were provided. Request: %+v", model, len(input.Messages), input)
return nil, core.NewError(core.INVALID_ARGUMENT, "model %q does not support multiple messages, but %d were provided. Request: %+v", model, len(input.Messages), input)
}

if !info.Supports.ToolChoice && input.ToolChoice != "" && input.ToolChoice != ToolChoiceAuto {
return nil, fmt.Errorf("model %q does not support tool choice, but tool choice was provided. Request: %+v", model, input)
return nil, core.NewError(core.INVALID_ARGUMENT, "model %q does not support tool choice, but tool choice was provided. Request: %+v", model, input)
}

if !info.Supports.SystemRole {
for _, msg := range input.Messages {
if msg.Role == RoleSystem {
return nil, fmt.Errorf("model %q does not support system role, but system role was provided. Request: %+v", model, input)
return nil, core.NewError(core.INVALID_ARGUMENT, "model %q does not support system role, but system role was provided. Request: %+v", model, input)
}
}
}
Expand All @@ -140,7 +141,7 @@ func validateSupport(model string, info *ModelInfo) ModelMiddleware {
info.Supports.Constrained == ConstrainedSupportNone ||
(info.Supports.Constrained == ConstrainedSupportNoTools && len(input.Tools) > 0)) &&
input.Output != nil && input.Output.Constrained {
return nil, fmt.Errorf("model %q does not support native constrained output, but constrained output was requested. Request: %+v", model, input)
return nil, core.NewError(core.INVALID_ARGUMENT, "model %q does not support native constrained output, but constrained output was requested. Request: %+v", model, input)
}

if err := validateVersion(model, info.Versions, input.Config); err != nil {
Expand Down Expand Up @@ -176,14 +177,14 @@ func validateVersion(model string, versions []string, config any) error {

version, ok := versionVal.(string)
if !ok {
return fmt.Errorf("version must be a string, got %T", versionVal)
return core.NewError(core.INVALID_ARGUMENT, "version must be a string, got %T", versionVal)
}

if slices.Contains(versions, version) {
return nil
}

return fmt.Errorf("model %q does not support version %q, supported versions: %v", model, version, versions)
return core.NewError(core.INVALID_ARGUMENT, "model %q does not support version %q, supported versions: %v", model, version, versions)
}

// ContextItemTemplate is the default item template for context augmentation.
Expand Down Expand Up @@ -302,13 +303,13 @@ func DownloadRequestMedia(options *DownloadMediaOptions) ModelMiddleware {

resp, err := client.Get(mediaUrl)
if err != nil {
return nil, fmt.Errorf("HTTP error downloading media %q: %w", mediaUrl, err)
return nil, core.NewError(core.INVALID_ARGUMENT, "HTTP error downloading media %q: %v", mediaUrl, err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("HTTP error downloading media %q: %s", mediaUrl, string(body))
return nil, core.NewError(core.UNKNOWN, "HTTP error downloading media %q: %s", mediaUrl, string(body))
}

contentType := part.ContentType
Expand All @@ -324,7 +325,7 @@ func DownloadRequestMedia(options *DownloadMediaOptions) ModelMiddleware {
data, err = io.ReadAll(resp.Body)
}
if err != nil {
return nil, fmt.Errorf("error reading media %q: %v", mediaUrl, err)
return nil, core.NewError(core.UNKNOWN, "error reading media %q: %v", mediaUrl, err)
}

message.Content[j] = NewMediaPart(contentType, fmt.Sprintf("data:%s;base64,%s", contentType, base64.StdEncoding.EncodeToString(data)))
Expand Down
3 changes: 1 addition & 2 deletions go/core/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"reflect"
"time"

Expand Down Expand Up @@ -213,7 +212,7 @@ func (a *ActionDef[In, Out, Stream]) Run(ctx context.Context, input In, cb Strea
func (a *ActionDef[In, Out, Stream]) RunJSON(ctx context.Context, input json.RawMessage, cb StreamCallback[json.RawMessage]) (json.RawMessage, error) {
// Validate input before unmarshaling it because invalid or unknown fields will be discarded in the process.
if err := base.ValidateJSON(input, a.inputSchema); err != nil {
return nil, &base.HTTPError{Code: http.StatusBadRequest, Err: err}
return nil, NewError(INVALID_ARGUMENT, err.Error())
}
var in In
if input != nil {
Expand Down
Loading
Loading