@@ -824,6 +824,12 @@ func (al *AgentLoop) runLLMIteration(
824824 iteration := 0
825825 var finalContent string
826826
827+ // Determine effective model tier for this conversation turn.
828+ // selectCandidates evaluates routing once and the decision is sticky for
829+ // all tool-follow-up iterations within the same turn so that a multi-step
830+ // tool chain doesn't switch models mid-way through.
831+ activeCandidates , activeModel := al .selectCandidates (agent , opts .UserMessage , messages )
832+
827833 for iteration < agent .MaxIterations {
828834 iteration ++
829835
@@ -842,7 +848,7 @@ func (al *AgentLoop) runLLMIteration(
842848 map [string ]any {
843849 "agent_id" : agent .ID ,
844850 "iteration" : iteration ,
845- "model" : agent . Model ,
851+ "model" : activeModel ,
846852 "messages_count" : len (messages ),
847853 "tools_count" : len (providerToolDefs ),
848854 "max_tokens" : agent .MaxTokens ,
@@ -858,7 +864,7 @@ func (al *AgentLoop) runLLMIteration(
858864 "tools_json" : formatToolsForLog (providerToolDefs ),
859865 })
860866
861- // Call LLM with fallback chain if candidates are configured.
867+ // Call LLM with fallback chain if multiple candidates are configured.
862868 var response * providers.LLMResponse
863869 var err error
864870
@@ -879,10 +885,10 @@ func (al *AgentLoop) runLLMIteration(
879885 }
880886
881887 callLLM := func () (* providers.LLMResponse , error ) {
882- if len (agent . Candidates ) > 1 && al .fallback != nil {
888+ if len (activeCandidates ) > 1 && al .fallback != nil {
883889 fbResult , fbErr := al .fallback .Execute (
884890 ctx ,
885- agent . Candidates ,
891+ activeCandidates ,
886892 func (ctx context.Context , provider , model string ) (* providers.LLMResponse , error ) {
887893 return agent .Provider .Chat (ctx , messages , providerToolDefs , model , llmOpts )
888894 },
@@ -900,7 +906,7 @@ func (al *AgentLoop) runLLMIteration(
900906 }
901907 return fbResult .Response , nil
902908 }
903- return agent .Provider .Chat (ctx , messages , providerToolDefs , agent . Model , llmOpts )
909+ return agent .Provider .Chat (ctx , messages , providerToolDefs , activeModel , llmOpts )
904910 }
905911
906912 // Retry loop for context/token errors
@@ -1169,6 +1175,44 @@ func (al *AgentLoop) runLLMIteration(
11691175 return finalContent , iteration , nil
11701176}
11711177
1178+ // selectCandidates returns the model candidates and resolved model name to use
1179+ // for a conversation turn. When model routing is configured and the incoming
1180+ // message scores below the complexity threshold, it returns the light model
1181+ // candidates instead of the primary ones.
1182+ //
1183+ // The returned (candidates, model) pair is used for all LLM calls within one
1184+ // turn — tool follow-up iterations use the same tier as the initial call so
1185+ // that a multi-step tool chain doesn't switch models mid-way.
1186+ func (al * AgentLoop ) selectCandidates (
1187+ agent * AgentInstance ,
1188+ userMsg string ,
1189+ history []providers.Message ,
1190+ ) (candidates []providers.FallbackCandidate , model string ) {
1191+ if agent .Router == nil || len (agent .LightCandidates ) == 0 {
1192+ return agent .Candidates , agent .Model
1193+ }
1194+
1195+ _ , usedLight , score := agent .Router .SelectModel (userMsg , history , agent .Model )
1196+ if ! usedLight {
1197+ logger .DebugCF ("agent" , "Model routing: primary model selected" ,
1198+ map [string ]any {
1199+ "agent_id" : agent .ID ,
1200+ "score" : score ,
1201+ "threshold" : agent .Router .Threshold (),
1202+ })
1203+ return agent .Candidates , agent .Model
1204+ }
1205+
1206+ logger .InfoCF ("agent" , "Model routing: light model selected" ,
1207+ map [string ]any {
1208+ "agent_id" : agent .ID ,
1209+ "light_model" : agent .Router .LightModel (),
1210+ "score" : score ,
1211+ "threshold" : agent .Router .Threshold (),
1212+ })
1213+ return agent .LightCandidates , agent .Router .LightModel ()
1214+ }
1215+
11721216// maybeSummarize triggers summarization if the session history exceeds thresholds.
11731217func (al * AgentLoop ) maybeSummarize (agent * AgentInstance , sessionKey , channel , chatID string ) {
11741218 newHistory := agent .Sessions .GetHistory (sessionKey )
0 commit comments