Skip to content

Commit b70d3b7

Browse files
committed
chore: refactor endpoints to use same inferencing path, add automatic retrial mechanism in case of errors
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent d8161bf commit b70d3b7

9 files changed

Lines changed: 1260 additions & 655 deletions

File tree

core/backend/llm.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type TokenUsage struct {
3838
TimingTokenGeneration float64
3939
}
4040

41+
// ModelInferenceFunc is a test-friendly indirection to call model inference logic.
42+
// Tests can override this variable to provide a stub implementation.
43+
var ModelInferenceFunc = ModelInference
44+
4145
func ModelInference(ctx context.Context, s string, messages schema.Messages, images, videos, audios []string, loader *model.ModelLoader, c *config.ModelConfig, cl *config.ModelConfigLoader, o *config.ApplicationConfig, tokenCallback func(string, TokenUsage) bool, tools string, toolChoice string, logprobs *int, topLogprobs *int, logitBias map[string]float64, metadata map[string]string) (func() (LLMResponse, error), error) {
4246
modelFile := c.Model
4347

core/http/endpoints/anthropic/messages.go

Lines changed: 28 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/mudler/LocalAI/core/backend"
1111
"github.com/mudler/LocalAI/core/config"
1212
mcpTools "github.com/mudler/LocalAI/core/http/endpoints/mcp"
13+
openaiEndpoint "github.com/mudler/LocalAI/core/http/endpoints/openai"
1314
"github.com/mudler/LocalAI/core/http/middleware"
1415
"github.com/mudler/LocalAI/core/schema"
1516
"github.com/mudler/LocalAI/core/templates"
@@ -197,54 +198,25 @@ func handleAnthropicNonStream(c echo.Context, id string, input *schema.Anthropic
197198
xlog.Debug("Anthropic MCP re-templating", "iteration", mcpIteration, "prompt_len", len(predInput))
198199
}
199200

200-
images := []string{}
201-
for _, m := range openAIReq.Messages {
202-
images = append(images, m.StringImages...)
203-
}
201+
// Populate openAIReq fields for ComputeChoices
202+
openAIReq.Tools = convertFuncsToOpenAITools(funcs)
203+
openAIReq.ToolsChoice = input.ToolChoice
204+
openAIReq.Metadata = input.Metadata
204205

205-
toolsJSON := ""
206-
if len(funcs) > 0 {
207-
openAITools := make([]functions.Tool, len(funcs))
208-
for i, f := range funcs {
209-
openAITools[i] = functions.Tool{Type: "function", Function: f}
210-
}
211-
if toolsBytes, err := json.Marshal(openAITools); err == nil {
212-
toolsJSON = string(toolsBytes)
213-
}
214-
}
215-
toolChoiceJSON := ""
216-
if input.ToolChoice != nil {
217-
if toolChoiceBytes, err := json.Marshal(input.ToolChoice); err == nil {
218-
toolChoiceJSON = string(toolChoiceBytes)
219-
}
206+
var result string
207+
cb := func(s string, c *[]schema.Choice) {
208+
result = s
220209
}
221-
222-
predFunc, err := backend.ModelInference(
223-
input.Context, predInput, openAIReq.Messages, images, nil, nil, ml, cfg, cl, appConfig, nil, toolsJSON, toolChoiceJSON, nil, nil, nil, input.Metadata)
210+
choices, tokenUsage, chatDeltas, err := openaiEndpoint.ComputeChoices(openAIReq, predInput, cfg, cl, appConfig, ml, cb, nil)
224211
if err != nil {
225212
xlog.Error("Anthropic model inference failed", "error", err)
226213
return sendAnthropicError(c, 500, "api_error", fmt.Sprintf("model inference failed: %v", err))
227214
}
228-
229-
const maxEmptyRetries = 5
230-
var prediction backend.LLMResponse
231-
var result string
232-
for attempt := 0; attempt <= maxEmptyRetries; attempt++ {
233-
prediction, err = predFunc()
234-
if err != nil {
235-
xlog.Error("Anthropic prediction failed", "error", err)
236-
return sendAnthropicError(c, 500, "api_error", fmt.Sprintf("prediction failed: %v", err))
237-
}
238-
result = backend.Finetune(*cfg, predInput, prediction.Response)
239-
if result != "" || !shouldUseFn {
240-
break
241-
}
242-
xlog.Warn("Anthropic: retrying prediction due to empty backend response", "attempt", attempt+1, "maxRetries", maxEmptyRetries)
243-
}
215+
_ = choices
244216

245217
// Try pre-parsed tool calls from C++ autoparser first, fall back to text parsing
246218
var toolCalls []functions.FuncCallResults
247-
if deltaToolCalls := functions.ToolCallsFromChatDeltas(prediction.ChatDeltas); len(deltaToolCalls) > 0 {
219+
if deltaToolCalls := functions.ToolCallsFromChatDeltas(chatDeltas); len(deltaToolCalls) > 0 {
248220
xlog.Debug("[ChatDeltas] Anthropic: using pre-parsed tool calls", "count", len(deltaToolCalls))
249221
toolCalls = deltaToolCalls
250222
} else {
@@ -350,8 +322,8 @@ func handleAnthropicNonStream(c echo.Context, id string, input *schema.Anthropic
350322
StopReason: &stopReason,
351323
Content: contentBlocks,
352324
Usage: schema.AnthropicUsage{
353-
InputTokens: prediction.Usage.Prompt,
354-
OutputTokens: prediction.Usage.Completion,
325+
InputTokens: tokenUsage.Prompt,
326+
OutputTokens: tokenUsage.Completion,
355327
},
356328
}
357329

@@ -397,12 +369,6 @@ func handleAnthropicStream(c echo.Context, id string, input *schema.AnthropicReq
397369
xlog.Debug("Anthropic MCP stream re-templating", "iteration", mcpIteration)
398370
}
399371

400-
openAIMessages := openAIReq.Messages
401-
images := []string{}
402-
for _, m := range openAIMessages {
403-
images = append(images, m.StringImages...)
404-
}
405-
406372
// Track accumulated content for tool call detection
407373
accumulatedContent := ""
408374
currentBlockIndex := 0
@@ -481,38 +447,19 @@ func handleAnthropicStream(c echo.Context, id string, input *schema.AnthropicReq
481447
return true
482448
}
483449

484-
toolsJSON := ""
485-
if len(funcs) > 0 {
486-
openAITools := make([]functions.Tool, len(funcs))
487-
for i, f := range funcs {
488-
openAITools[i] = functions.Tool{Type: "function", Function: f}
489-
}
490-
if toolsBytes, err := json.Marshal(openAITools); err == nil {
491-
toolsJSON = string(toolsBytes)
492-
}
493-
}
494-
toolChoiceJSON := ""
495-
if input.ToolChoice != nil {
496-
if toolChoiceBytes, err := json.Marshal(input.ToolChoice); err == nil {
497-
toolChoiceJSON = string(toolChoiceBytes)
498-
}
499-
}
450+
// Populate openAIReq fields for ComputeChoices
451+
openAIReq.Tools = convertFuncsToOpenAITools(funcs)
452+
openAIReq.ToolsChoice = input.ToolChoice
453+
openAIReq.Metadata = input.Metadata
500454

501-
predFunc, err := backend.ModelInference(
502-
input.Context, predInput, openAIMessages, images, nil, nil, ml, cfg, cl, appConfig, tokenCallback, toolsJSON, toolChoiceJSON, nil, nil, nil, input.Metadata)
455+
_, tokenUsage, chatDeltas, err := openaiEndpoint.ComputeChoices(openAIReq, predInput, cfg, cl, appConfig, ml, func(s string, c *[]schema.Choice) {}, tokenCallback)
503456
if err != nil {
504457
xlog.Error("Anthropic stream model inference failed", "error", err)
505458
return sendAnthropicError(c, 500, "api_error", fmt.Sprintf("model inference failed: %v", err))
506459
}
507460

508-
prediction, err := predFunc()
509-
if err != nil {
510-
xlog.Error("Anthropic stream prediction failed", "error", err)
511-
return sendAnthropicError(c, 500, "api_error", fmt.Sprintf("prediction failed: %v", err))
512-
}
513-
514461
// Also check chat deltas for tool calls
515-
if deltaToolCalls := functions.ToolCallsFromChatDeltas(prediction.ChatDeltas); len(deltaToolCalls) > 0 && len(collectedToolCalls) == 0 {
462+
if deltaToolCalls := functions.ToolCallsFromChatDeltas(chatDeltas); len(deltaToolCalls) > 0 && len(collectedToolCalls) == 0 {
516463
collectedToolCalls = deltaToolCalls
517464
}
518465

@@ -595,7 +542,7 @@ func handleAnthropicStream(c echo.Context, id string, input *schema.AnthropicReq
595542
StopReason: &stopReason,
596543
},
597544
Usage: &schema.AnthropicUsage{
598-
OutputTokens: prediction.Usage.Completion,
545+
OutputTokens: tokenUsage.Completion,
599546
},
600547
})
601548

@@ -613,6 +560,14 @@ func handleAnthropicStream(c echo.Context, id string, input *schema.AnthropicReq
613560
return nil
614561
}
615562

563+
func convertFuncsToOpenAITools(funcs functions.Functions) []functions.Tool {
564+
tools := make([]functions.Tool, len(funcs))
565+
for i, f := range funcs {
566+
tools[i] = functions.Tool{Type: "function", Function: f}
567+
}
568+
return tools
569+
}
570+
616571
func sendAnthropicSSE(c echo.Context, event schema.AnthropicStreamEvent) {
617572
data, err := json.Marshal(event)
618573
if err != nil {

0 commit comments

Comments
 (0)