-
Notifications
You must be signed in to change notification settings - Fork 572
Adds handlers for SDKs #60
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||
| package anthropic | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| "github.com/fasthttp/router" | ||||||||||||||||||||||||
| bifrost "github.com/maximhq/bifrost/core" | ||||||||||||||||||||||||
| "github.com/maximhq/bifrost/transports/bifrost-http/lib" | ||||||||||||||||||||||||
| "github.com/valyala/fasthttp" | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // AnthropicRouter holds route registrations for anthropic endpoints. | ||||||||||||||||||||||||
| type AnthropicRouter struct { | ||||||||||||||||||||||||
| client *bifrost.Bifrost | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // NewAnthropicRouter creates a new AnthropicRouter with the given bifrost client. | ||||||||||||||||||||||||
| func NewAnthropicRouter(client *bifrost.Bifrost) *AnthropicRouter { | ||||||||||||||||||||||||
| return &AnthropicRouter{client: client} | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // RegisterRoutes registers all anthropic routes on the given router. | ||||||||||||||||||||||||
| func (a *AnthropicRouter) RegisterRoutes(r *router.Router) { | ||||||||||||||||||||||||
| r.POST("/anthropic/v1/messages", a.handleChatCompletion) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // handleChatCompletion handles POST /anthropic/v1/messages | ||||||||||||||||||||||||
| func (a *AnthropicRouter) handleChatCompletion(ctx *fasthttp.RequestCtx) { | ||||||||||||||||||||||||
| var req ChatCompletionRequest | ||||||||||||||||||||||||
| if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { | ||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusBadRequest) | ||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(err) | ||||||||||||||||||||||||
| return | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| bifrostReq := req.ConvertToBifrostRequest("") | ||||||||||||||||||||||||
| bifrostCtx := lib.ConvertToBifrostContext(ctx) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| result, err := a.client.ChatCompletionRequest(*bifrostCtx, bifrostReq) | ||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusInternalServerError) | ||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(err) | ||||||||||||||||||||||||
| return | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+40
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Improve error response consistency. The error handling for internal server errors should also use structured error responses for consistency. if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
- json.NewEncoder(ctx).Encode(err)
+ ctx.SetContentType("application/json")
+ json.NewEncoder(ctx).Encode(map[string]string{"error": "Internal server error"})
return
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusOK) | ||||||||||||||||||||||||
| ctx.SetContentType("application/json") | ||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(result) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+1
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Address code duplication across router implementations. This router implementation is nearly identical to the Mistral router, differing only in package name, struct name, and endpoint path. Consider creating a generic router pattern to reduce duplication. Consider creating a common interface or base router: type IntegrationRouter interface {
RegisterRoutes(r *router.Router)
}
type BaseRouter struct {
client *bifrost.Bifrost
endpoint string
name string
}
func (b *BaseRouter) handleChatCompletion(ctx *fasthttp.RequestCtx, converter func([]byte) (*schemas.BifrostRequest, error)) {
// Common implementation
}This would eliminate the duplicate code across all integration routers. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package anthropic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import schemas "github.com/maximhq/bifrost/core/schemas" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ChatCompletionRequest represents the Anthropic messages API request. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only a subset of parameters are supported. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ChatCompletionRequest struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Model string `json:"model"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Messages []schemas.BifrostMessage `json:"messages"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Temperature *float64 `json:"temperature,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TopP *float64 `json:"top_p,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TopK *int `json:"top_k,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MaxTokens *int `json:"max_tokens,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StopSequences *[]string `json:"stop_sequences,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PresencePenalty *float64 `json:"presence_penalty,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Tools *[]schemas.Tool `json:"tools,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ToolChoice *schemas.ToolChoice `json:"tool_choice,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider refactoring to reduce code duplication. This struct definition is identical across all AI provider integrations (Anthropic, Mistral, LiteLLM, LangGraph). Consider extracting a common +// BaseChatCompletionRequest contains common fields across all AI providers
+type BaseChatCompletionRequest struct {
+ Model string `json:"model"`
+ Messages []schemas.BifrostMessage `json:"messages"`
+ Temperature *float64 `json:"temperature,omitempty"`
+ TopP *float64 `json:"top_p,omitempty"`
+ TopK *int `json:"top_k,omitempty"`
+ MaxTokens *int `json:"max_tokens,omitempty"`
+ StopSequences *[]string `json:"stop_sequences,omitempty"`
+ PresencePenalty *float64 `json:"presence_penalty,omitempty"`
+ FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
+ ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
+ Tools *[]schemas.Tool `json:"tools,omitempty"`
+ ToolChoice *schemas.ToolChoice `json:"tool_choice,omitempty"`
+}
// ChatCompletionRequest represents the Anthropic messages API request.
type ChatCompletionRequest struct {
- Model string `json:"model"`
- Messages []schemas.BifrostMessage `json:"messages"`
- Temperature *float64 `json:"temperature,omitempty"`
- TopP *float64 `json:"top_p,omitempty"`
- TopK *int `json:"top_k,omitempty"`
- MaxTokens *int `json:"max_tokens,omitempty"`
- StopSequences *[]string `json:"stop_sequences,omitempty"`
- PresencePenalty *float64 `json:"presence_penalty,omitempty"`
- FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
- ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
- Tools *[]schemas.Tool `json:"tools,omitempty"`
- ToolChoice *schemas.ToolChoice `json:"tool_choice,omitempty"`
+ BaseChatCompletionRequest
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ConvertToBifrostRequest converts the request to a BifrostRequest. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (r *ChatCompletionRequest) ConvertToBifrostRequest(model string) *schemas.BifrostRequest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if model == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model = r.Model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params := &schemas.ModelParameters{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Temperature: r.Temperature, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TopP: r.TopP, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TopK: r.TopK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MaxTokens: r.MaxTokens, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StopSequences: r.StopSequences, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PresencePenalty: r.PresencePenalty, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FrequencyPenalty: r.FrequencyPenalty, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ParallelToolCalls: r.ParallelToolCalls, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Tools: r.Tools, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ToolChoice: r.ToolChoice, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return &schemas.BifrostRequest{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Provider: schemas.Anthropic, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Model: model, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Input: schemas.RequestInput{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ChatCompletionInput: &r.Messages, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Params: params, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add validation for required fields. The conversion method doesn't validate required fields like // ConvertToBifrostRequest converts the request to a BifrostRequest.
-func (r *ChatCompletionRequest) ConvertToBifrostRequest(model string) *schemas.BifrostRequest {
+func (r *ChatCompletionRequest) ConvertToBifrostRequest(model string) (*schemas.BifrostRequest, error) {
if model == "" {
model = r.Model
}
+ if model == "" {
+ return nil, fmt.Errorf("model is required")
+ }
+ if len(r.Messages) == 0 {
+ return nil, fmt.Errorf("messages are required")
+ }
params := &schemas.ModelParameters{
Temperature: r.Temperature,
TopP: r.TopP,
TopK: r.TopK,
MaxTokens: r.MaxTokens,
StopSequences: r.StopSequences,
PresencePenalty: r.PresencePenalty,
FrequencyPenalty: r.FrequencyPenalty,
ParallelToolCalls: r.ParallelToolCalls,
Tools: r.Tools,
ToolChoice: r.ToolChoice,
}
return &schemas.BifrostRequest{
Provider: schemas.Anthropic,
Model: model,
Input: schemas.RequestInput{
ChatCompletionInput: &r.Messages,
},
Params: params,
- }
+ }, nil
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package anthropic | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| bifrost "github.com/maximhq/bifrost/core" | ||
| schemas "github.com/maximhq/bifrost/core/schemas" | ||
| ) | ||
|
|
||
| func TestConvertToBifrostRequest(t *testing.T) { | ||
| temp := 0.5 | ||
| req := ChatCompletionRequest{ | ||
| Model: "claude-test", | ||
| Messages: []schemas.BifrostMessage{ | ||
| {Role: schemas.ModelChatMessageRoleUser, Content: bifrost.Ptr("hi")}, | ||
| }, | ||
| Temperature: &temp, | ||
| } | ||
|
|
||
| bfReq := req.ConvertToBifrostRequest("") | ||
|
|
||
| if bfReq.Provider != schemas.Anthropic { | ||
| t.Errorf("expected provider %s, got %s", schemas.Anthropic, bfReq.Provider) | ||
| } | ||
| if bfReq.Model != "claude-test" { | ||
| t.Errorf("expected model claude-test, got %s", bfReq.Model) | ||
| } | ||
| if bfReq.Params == nil || bfReq.Params.Temperature == nil || *bfReq.Params.Temperature != temp { | ||
| t.Errorf("temperature not copied") | ||
| } | ||
| if bfReq.Input.ChatCompletionInput == nil || len(*bfReq.Input.ChatCompletionInput) != 1 { | ||
| t.Fatalf("expected 1 message, got %v", bfReq.Input.ChatCompletionInput) | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||||||||
| package langchain | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| "github.com/fasthttp/router" | ||||||||||||||||||||||||||||||||
| bifrost "github.com/maximhq/bifrost/core" | ||||||||||||||||||||||||||||||||
| "github.com/maximhq/bifrost/transports/bifrost-http/lib" | ||||||||||||||||||||||||||||||||
| "github.com/valyala/fasthttp" | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // LangChainRouter holds route registrations for langchain endpoints. | ||||||||||||||||||||||||||||||||
| type LangChainRouter struct { | ||||||||||||||||||||||||||||||||
| client *bifrost.Bifrost | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // NewLangChainRouter creates a new LangChainRouter with the given bifrost client. | ||||||||||||||||||||||||||||||||
| func NewLangChainRouter(client *bifrost.Bifrost) *LangChainRouter { | ||||||||||||||||||||||||||||||||
| return &LangChainRouter{client: client} | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // RegisterRoutes registers all langchain routes on the given router. | ||||||||||||||||||||||||||||||||
| func (l *LangChainRouter) RegisterRoutes(r *router.Router) { | ||||||||||||||||||||||||||||||||
| r.POST("/langchain/v1/chat/completions", l.handleChatCompletion) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Reduce duplication across SDK routers 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // handleChatCompletion handles POST /langchain/v1/chat/completions | ||||||||||||||||||||||||||||||||
| func (l *LangChainRouter) handleChatCompletion(ctx *fasthttp.RequestCtx) { | ||||||||||||||||||||||||||||||||
| var req ChatCompletionRequest | ||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { | ||||||||||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusBadRequest) | ||||||||||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(err) | ||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid exposing internal errors on malformed input 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| bifrostReq := req.ConvertToBifrostRequest("") | ||||||||||||||||||||||||||||||||
| bifrostCtx := lib.ConvertToBifrostContext(ctx) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Simplify context conversion signature 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| result, err := l.client.ChatCompletionRequest(*bifrostCtx, bifrostReq) | ||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusInternalServerError) | ||||||||||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(err) | ||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Apply consistent error handling for Bifrost client errors. Same issue as above - directly encoding internal errors can expose implementation details. - result, err := l.client.ChatCompletionRequest(*bifrostCtx, bifrostReq)
- if err != nil {
- ctx.SetStatusCode(fasthttp.StatusInternalServerError)
- json.NewEncoder(ctx).Encode(err)
- return
- }
+ result, err := l.client.ChatCompletionRequest(*bifrostCtx, bifrostReq)
+ if err != nil {
+ ctx.SetStatusCode(fasthttp.StatusInternalServerError)
+ ctx.SetContentType("application/json")
+ json.NewEncoder(ctx).Encode(map[string]string{
+ "error": "Internal server error",
+ })
+ return
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusOK) | ||||||||||||||||||||||||||||||||
| ctx.SetContentType("application/json") | ||||||||||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(result) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 🛠️ Refactor suggestion Significant code duplication across integration routers. The router implementations for LangChain, LangGraph, and LiteLLM are nearly identical. This violates the DRY principle and makes maintenance more difficult. Consider implementing a generic router pattern or base router that can be composed for different integrations. This would:
Would you like me to generate a refactored implementation that addresses these concerns? 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package langchain | ||
|
|
||
| import schemas "github.com/maximhq/bifrost/core/schemas" | ||
|
|
||
| // ChatCompletionRequest represents a LangChain API request. LangChain | ||
| // often proxies OpenAI style payloads, so the field set is similar. | ||
| type ChatCompletionRequest struct { | ||
| Model string `json:"model"` | ||
| Messages []schemas.BifrostMessage `json:"messages"` | ||
| Temperature *float64 `json:"temperature,omitempty"` | ||
| TopP *float64 `json:"top_p,omitempty"` | ||
| TopK *int `json:"top_k,omitempty"` | ||
| MaxTokens *int `json:"max_tokens,omitempty"` | ||
| StopSequences *[]string `json:"stop_sequences,omitempty"` | ||
| PresencePenalty *float64 `json:"presence_penalty,omitempty"` | ||
| FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` | ||
| ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"` | ||
| Tools *[]schemas.Tool `json:"tools,omitempty"` | ||
| ToolChoice *schemas.ToolChoice `json:"tool_choice,omitempty"` | ||
| } | ||
|
Comment on lines
+7
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Include explicit provider metadata in the request struct 🤖 Prompt for AI Agents |
||
|
|
||
| // ConvertToBifrostRequest converts the request to a BifrostRequest. | ||
| func (r *ChatCompletionRequest) ConvertToBifrostRequest(model string) *schemas.BifrostRequest { | ||
| if model == "" { | ||
| model = r.Model | ||
| } | ||
| params := &schemas.ModelParameters{ | ||
| Temperature: r.Temperature, | ||
| TopP: r.TopP, | ||
| TopK: r.TopK, | ||
| MaxTokens: r.MaxTokens, | ||
| StopSequences: r.StopSequences, | ||
| PresencePenalty: r.PresencePenalty, | ||
| FrequencyPenalty: r.FrequencyPenalty, | ||
| ParallelToolCalls: r.ParallelToolCalls, | ||
| Tools: r.Tools, | ||
| ToolChoice: r.ToolChoice, | ||
| } | ||
|
|
||
| return &schemas.BifrostRequest{ | ||
| Provider: schemas.OpenAI, | ||
| Model: model, | ||
|
Comment on lines
+40
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify hardcoding provider to OpenAI is appropriate. While the comment mentions LangChain often proxies OpenAI style payloads, hardcoding the provider to 🌐 Web query: 💡 Result: LangChain supports a wide range of model providers beyond just OpenAI. It offers integrations with numerous LLM and chat model providers through a standardized interface. Supported Model ProvidersLangChain integrates with many popular model providers including:
LangChain organizes these integrations into two categories:
Identifying Models in RequestsWhen working with different model providers in LangChain, you should identify them using a standardized approach:
Each provider typically has their own class naming convention, often prefixed with "Chat" for chat models (e.g., Custom Model IntegrationFor models not directly supported, you can create custom LLM classes that implement the required interface. This approach allows you to connect to models via wrappers and APIs with custom authentication methods like HMAC signatures[9]. LangChain's standardized interface helps address historical inconsistencies between providers, where some used Citations:
Avoid Hardcoding the Provider Field Hardcoding • File: transports/bifrost-http/integrations/langchain/types.go return &schemas.BifrostRequest{
// ❌ Always OpenAI
Provider: schemas.OpenAI,
Model: model,
…
}Suggestions:
Fixing this will ensure BifrostRequest accurately reflects the intended model provider. 🤖 Prompt for AI Agents |
||
| Input: schemas.RequestInput{ | ||
| ChatCompletionInput: &r.Messages, | ||
| }, | ||
| Params: params, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package langchain | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| bifrost "github.com/maximhq/bifrost/core" | ||
| schemas "github.com/maximhq/bifrost/core/schemas" | ||
| ) | ||
|
|
||
| func TestConvertToBifrostRequest(t *testing.T) { | ||
| temp := 0.5 | ||
| req := ChatCompletionRequest{ | ||
| Model: "gpt-test", | ||
| Messages: []schemas.BifrostMessage{ | ||
| {Role: schemas.ModelChatMessageRoleUser, Content: bifrost.Ptr("hi")}, | ||
| }, | ||
| Temperature: &temp, | ||
| } | ||
|
|
||
| bfReq := req.ConvertToBifrostRequest("override") | ||
|
|
||
| if bfReq.Provider != schemas.OpenAI { | ||
| t.Errorf("expected provider %s, got %s", schemas.OpenAI, bfReq.Provider) | ||
| } | ||
| if bfReq.Model != "override" { | ||
| t.Errorf("expected model override, got %s", bfReq.Model) | ||
| } | ||
| if bfReq.Params == nil || bfReq.Params.Temperature == nil || *bfReq.Params.Temperature != temp { | ||
| t.Errorf("temperature not copied") | ||
| } | ||
| if bfReq.Input.ChatCompletionInput == nil || len(*bfReq.Input.ChatCompletionInput) != 1 { | ||
| t.Fatalf("expected 1 message, got %v", bfReq.Input.ChatCompletionInput) | ||
| } | ||
| } | ||
|
Comment on lines
+10
to
+34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion LangChain conversion logic correctly tested. The test validates that LangChain requests properly map to The identical test structure across all integration files creates significant maintenance overhead. Consider implementing a table-driven test approach: +// In a shared test file or package
+func TestAllProviderConversions(t *testing.T) {
+ testCases := []struct {
+ name string
+ provider string
+ modelOverride string
+ expectedProvider schemas.Provider
+ expectedModel string
+ }{
+ {"OpenAI", "openai", "override", schemas.OpenAI, "override"},
+ {"Mistral", "mistral", "override", schemas.Mistral, "override"},
+ {"LiteLLM", "litellm", "", schemas.OpenAI, "gpt-test"},
+ {"LangChain", "langchain", "override", schemas.OpenAI, "override"},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ // Test implementation using provider factories
+ })
+ }
+}
🤖 Prompt for AI Agents |
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||||
| package langgraph | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| "github.com/fasthttp/router" | ||||||||||||||||||||||||||||
| bifrost "github.com/maximhq/bifrost/core" | ||||||||||||||||||||||||||||
| "github.com/maximhq/bifrost/transports/bifrost-http/lib" | ||||||||||||||||||||||||||||
| "github.com/valyala/fasthttp" | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // LangGraphRouter holds route registrations for langgraph endpoints. | ||||||||||||||||||||||||||||
| type LangGraphRouter struct { | ||||||||||||||||||||||||||||
| client *bifrost.Bifrost | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // NewLangGraphRouter creates a new LangGraphRouter with the given bifrost client. | ||||||||||||||||||||||||||||
| func NewLangGraphRouter(client *bifrost.Bifrost) *LangGraphRouter { | ||||||||||||||||||||||||||||
| return &LangGraphRouter{client: client} | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // RegisterRoutes registers all langgraph routes on the given router. | ||||||||||||||||||||||||||||
| func (l *LangGraphRouter) RegisterRoutes(r *router.Router) { | ||||||||||||||||||||||||||||
| r.POST("/langgraph/v1/chat/completions", l.handleChatCompletion) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // handleChatCompletion handles POST /langgraph/v1/chat/completions | ||||||||||||||||||||||||||||
| func (l *LangGraphRouter) handleChatCompletion(ctx *fasthttp.RequestCtx) { | ||||||||||||||||||||||||||||
| var req ChatCompletionRequest | ||||||||||||||||||||||||||||
| if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { | ||||||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusBadRequest) | ||||||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(err) | ||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve error handling to avoid exposing internal details. Directly encoding Go errors to JSON responses can expose internal implementation details and potentially sensitive information to clients. Consider creating structured error responses instead. - if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
- ctx.SetStatusCode(fasthttp.StatusBadRequest)
- json.NewEncoder(ctx).Encode(err)
- return
- }
+ if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
+ ctx.SetStatusCode(fasthttp.StatusBadRequest)
+ ctx.SetContentType("application/json")
+ json.NewEncoder(ctx).Encode(map[string]string{
+ "error": "Invalid request format",
+ })
+ return
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| bifrostReq := req.ConvertToBifrostRequest("") | ||||||||||||||||||||||||||||
| bifrostCtx := lib.ConvertToBifrostContext(ctx) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| result, err := l.client.ChatCompletionRequest(*bifrostCtx, bifrostReq) | ||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusInternalServerError) | ||||||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(err) | ||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Apply consistent error handling for Bifrost client errors. Same issue as above - directly encoding internal errors can expose implementation details. - if err != nil {
- ctx.SetStatusCode(fasthttp.StatusInternalServerError)
- json.NewEncoder(ctx).Encode(err)
- return
- }
+ if err != nil {
+ ctx.SetStatusCode(fasthttp.StatusInternalServerError)
+ ctx.SetContentType("application/json")
+ json.NewEncoder(ctx).Encode(map[string]string{
+ "error": "Internal server error",
+ })
+ return
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| ctx.SetStatusCode(fasthttp.StatusOK) | ||||||||||||||||||||||||||||
| ctx.SetContentType("application/json") | ||||||||||||||||||||||||||||
| json.NewEncoder(ctx).Encode(result) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package langgraph | ||
|
|
||
| import schemas "github.com/maximhq/bifrost/core/schemas" | ||
|
|
||
| // ChatCompletionRequest describes the payload expected by LangGraph's HTTP | ||
| // interface, which is largely compatible with OpenAI's API. | ||
| type ChatCompletionRequest struct { | ||
| Model string `json:"model"` | ||
| Messages []schemas.BifrostMessage `json:"messages"` | ||
| Temperature *float64 `json:"temperature,omitempty"` | ||
| TopP *float64 `json:"top_p,omitempty"` | ||
| TopK *int `json:"top_k,omitempty"` | ||
| MaxTokens *int `json:"max_tokens,omitempty"` | ||
| StopSequences *[]string `json:"stop_sequences,omitempty"` | ||
| PresencePenalty *float64 `json:"presence_penalty,omitempty"` | ||
| FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` | ||
| ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"` | ||
| Tools *[]schemas.Tool `json:"tools,omitempty"` | ||
| ToolChoice *schemas.ToolChoice `json:"tool_choice,omitempty"` | ||
| } | ||
|
|
||
| // ConvertToBifrostRequest converts the request to a BifrostRequest. | ||
| func (r *ChatCompletionRequest) ConvertToBifrostRequest(model string) *schemas.BifrostRequest { | ||
| if model == "" { | ||
| model = r.Model | ||
| } | ||
| params := &schemas.ModelParameters{ | ||
| Temperature: r.Temperature, | ||
| TopP: r.TopP, | ||
| TopK: r.TopK, | ||
| MaxTokens: r.MaxTokens, | ||
| StopSequences: r.StopSequences, | ||
| PresencePenalty: r.PresencePenalty, | ||
| FrequencyPenalty: r.FrequencyPenalty, | ||
| ParallelToolCalls: r.ParallelToolCalls, | ||
| Tools: r.Tools, | ||
| ToolChoice: r.ToolChoice, | ||
| } | ||
|
|
||
| return &schemas.BifrostRequest{ | ||
| Provider: schemas.OpenAI, | ||
| Model: model, | ||
| Input: schemas.RequestInput{ | ||
| ChatCompletionInput: &r.Messages, | ||
| }, | ||
| Params: params, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Improve error response formatting.
The current error encoding directly serializes the unmarshaling error, which may expose internal implementation details. Consider using a structured error response format.
if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) - json.NewEncoder(ctx).Encode(err) + ctx.SetContentType("application/json") + json.NewEncoder(ctx).Encode(map[string]string{"error": "Invalid request format"}) return }📝 Committable suggestion
🤖 Prompt for AI Agents