Skip to content

Commit ee96e5e

Browse files
authored
chore: refactor endpoints to use same inferencing path, add automatic retrial mechanism in case of errors (#9029)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 3d9ccd1 commit ee96e5e

9 files changed

Lines changed: 1256 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: 27 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,24 @@ 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+
_, 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
}
228215

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-
}
244-
245216
// Try pre-parsed tool calls from C++ autoparser first, fall back to text parsing
246217
var toolCalls []functions.FuncCallResults
247-
if deltaToolCalls := functions.ToolCallsFromChatDeltas(prediction.ChatDeltas); len(deltaToolCalls) > 0 {
218+
if deltaToolCalls := functions.ToolCallsFromChatDeltas(chatDeltas); len(deltaToolCalls) > 0 {
248219
xlog.Debug("[ChatDeltas] Anthropic: using pre-parsed tool calls", "count", len(deltaToolCalls))
249220
toolCalls = deltaToolCalls
250221
} else {
@@ -350,8 +321,8 @@ func handleAnthropicNonStream(c echo.Context, id string, input *schema.Anthropic
350321
StopReason: &stopReason,
351322
Content: contentBlocks,
352323
Usage: schema.AnthropicUsage{
353-
InputTokens: prediction.Usage.Prompt,
354-
OutputTokens: prediction.Usage.Completion,
324+
InputTokens: tokenUsage.Prompt,
325+
OutputTokens: tokenUsage.Completion,
355326
},
356327
}
357328

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

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

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-
}
449+
// Populate openAIReq fields for ComputeChoices
450+
openAIReq.Tools = convertFuncsToOpenAITools(funcs)
451+
openAIReq.ToolsChoice = input.ToolChoice
452+
openAIReq.Metadata = input.Metadata
500453

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)
454+
_, tokenUsage, chatDeltas, err := openaiEndpoint.ComputeChoices(openAIReq, predInput, cfg, cl, appConfig, ml, func(s string, c *[]schema.Choice) {}, tokenCallback)
503455
if err != nil {
504456
xlog.Error("Anthropic stream model inference failed", "error", err)
505457
return sendAnthropicError(c, 500, "api_error", fmt.Sprintf("model inference failed: %v", err))
506458
}
507459

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-
514460
// Also check chat deltas for tool calls
515-
if deltaToolCalls := functions.ToolCallsFromChatDeltas(prediction.ChatDeltas); len(deltaToolCalls) > 0 && len(collectedToolCalls) == 0 {
461+
if deltaToolCalls := functions.ToolCallsFromChatDeltas(chatDeltas); len(deltaToolCalls) > 0 && len(collectedToolCalls) == 0 {
516462
collectedToolCalls = deltaToolCalls
517463
}
518464

@@ -595,7 +541,7 @@ func handleAnthropicStream(c echo.Context, id string, input *schema.AnthropicReq
595541
StopReason: &stopReason,
596542
},
597543
Usage: &schema.AnthropicUsage{
598-
OutputTokens: prediction.Usage.Completion,
544+
OutputTokens: tokenUsage.Completion,
599545
},
600546
})
601547

@@ -613,6 +559,14 @@ func handleAnthropicStream(c echo.Context, id string, input *schema.AnthropicReq
613559
return nil
614560
}
615561

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

0 commit comments

Comments
 (0)