package orchestration import ( "context" "encoding/json" "fmt" "strings" coretypes "github.com/enmanuel/agents/pkg/llm" "github.com/enmanuel/agents/pkg/orchestration" ) // routeInitial asks the LLM which bot should handle the question first. func (o *Orchestrator) routeInitial(ctx context.Context, question string, participants []string) (orchestration.RoutingDecision, error) { systemPrompt := strings.ReplaceAll(o.routingPrompt, "{{PARTICIPANTS}}", o.buildParticipantsList(participants, "")) resp, err := o.llm(ctx, coretypes.CompletionRequest{ Model: o.cfg.LLM.Primary.Model, MaxTokens: o.cfg.LLM.Primary.MaxTokens, Temperature: o.cfg.LLM.Primary.Temperature, SystemPrompt: systemPrompt, Messages: []coretypes.Message{ {Role: coretypes.RoleUser, Content: question}, }, }) if err != nil { return orchestration.RoutingDecision{}, fmt.Errorf("LLM routing call: %w", err) } var rd orchestration.RoutingDecision if err := json.Unmarshal([]byte(strings.TrimSpace(resp.Content)), &rd); err != nil { o.logger.Warn("failed to parse routing response, raw", "content", resp.Content, "err", err) return orchestration.RoutingDecision{}, fmt.Errorf("parse routing decision: %w", err) } // Validate the chosen bot is actually a participant if !contains(participants, rd.TargetBotID) { o.logger.Warn("LLM chose unknown bot, falling back to first", "chosen", rd.TargetBotID) rd.TargetBotID = participants[0] rd.Confidence = 0.5 rd.Reason = "fallback: LLM chose unknown bot" } return rd, nil } // routeRefinement asks the LLM which bot should improve the response, // excluding the last respondent. func (o *Orchestrator) routeRefinement( ctx context.Context, question string, responses []orchestration.BotResponse, participants []string, excludeBot string, ) (orchestration.RoutingDecision, error) { lastResponse := "" if len(responses) > 0 { lastResponse = responses[len(responses)-1].Text } systemPrompt := strings.ReplaceAll(o.refinementPrompt, "{{PARTICIPANTS}}", o.buildParticipantsList(participants, excludeBot)) systemPrompt = strings.ReplaceAll(systemPrompt, "{{LAST_RESPONSE}}", lastResponse) userContent := fmt.Sprintf("Original question: %s\n\nCurrent response that needs improvement:\n%s", question, lastResponse) resp, err := o.llm(ctx, coretypes.CompletionRequest{ Model: o.cfg.LLM.Primary.Model, MaxTokens: o.cfg.LLM.Primary.MaxTokens, Temperature: o.cfg.LLM.Primary.Temperature, SystemPrompt: systemPrompt, Messages: []coretypes.Message{ {Role: coretypes.RoleUser, Content: userContent}, }, }) if err != nil { return orchestration.RoutingDecision{}, fmt.Errorf("LLM refinement call: %w", err) } var rd orchestration.RoutingDecision if err := json.Unmarshal([]byte(strings.TrimSpace(resp.Content)), &rd); err != nil { o.logger.Warn("failed to parse refinement response", "content", resp.Content, "err", err) return orchestration.RoutingDecision{}, fmt.Errorf("parse refinement decision: %w", err) } // Validate: must be a participant and not the excluded bot if rd.TargetBotID == excludeBot || !contains(participants, rd.TargetBotID) { // Pick first available that isn't excluded for _, p := range participants { if p != excludeBot { rd.TargetBotID = p rd.Reason = "fallback: LLM chose excluded or unknown bot" break } } } return rd, nil } func contains(ss []string, s string) bool { for _, v := range ss { if v == s { return true } } return false }