@@ -19,6 +19,64 @@ The response you provided was either in an incorrect/unparsable format or was in
1919Please provide a valid response that matches the expected output format exactly.
2020` ;
2121
22+ /**
23+ * Resolves model configuration with field-by-field merge
24+ *
25+ * Precedence: userConfig > AGENT_CONFIG defaults
26+ * Each field is resolved independently (first defined value wins)
27+ */
28+ function resolveModelConfig (
29+ agentActionName : AgentActionKey ,
30+ userConfig ?: ModelConfig ,
31+ ) : ModelConfig {
32+ const defaultConfig = AGENT_CONFIG [ agentActionName ] ;
33+
34+ const merged : ModelConfig = {
35+ name : userConfig ?. name ?? defaultConfig . name ,
36+ reasoning_effort : userConfig ?. reasoning_effort ?? defaultConfig . reasoning_effort ,
37+ max_tokens : userConfig ?. max_tokens ?? defaultConfig . max_tokens ,
38+ temperature : userConfig ?. temperature ?? defaultConfig . temperature ,
39+ fallbackModel : userConfig ?. fallbackModel ?? defaultConfig . fallbackModel ,
40+ } ;
41+
42+ // Validate model name - try userConfig first, then default
43+ const modelCandidates = [ userConfig ?. name , defaultConfig . name ]
44+ . filter ( ( n ) : n is AIModels | string => n !== undefined ) ;
45+
46+ let validModelName : AIModels | string | undefined ;
47+ for ( const candidate of modelCandidates ) {
48+ if ( ! isValidAIModel ( candidate ) ) {
49+ logger . warn ( `Model ${ candidate } not valid, trying next` ) ;
50+ continue ;
51+ }
52+ const check = validateAgentConstraints ( agentActionName , candidate ) ;
53+ if ( check . constraintEnabled && ! check . valid ) {
54+ logger . warn ( `Model ${ candidate } violates constraints for ${ agentActionName } ` ) ;
55+ continue ;
56+ }
57+ validModelName = candidate ;
58+ break ;
59+ }
60+
61+ if ( ! validModelName ) {
62+ logger . warn ( `No valid model found for ${ agentActionName } , using default` ) ;
63+ validModelName = defaultConfig . name ;
64+ }
65+ merged . name = validModelName ;
66+
67+ // Validate fallback model
68+ if ( merged . fallbackModel ) {
69+ const fallbackCheck = validateAgentConstraints ( agentActionName , merged . fallbackModel ) ;
70+ if ( fallbackCheck . constraintEnabled && ! fallbackCheck . valid ) {
71+ logger . warn ( `Fallback ${ merged . fallbackModel } violates constraints, using default` ) ;
72+ merged . fallbackModel = defaultConfig . fallbackModel ;
73+ }
74+ }
75+
76+ logger . info ( `Resolved config for ${ agentActionName } : model=${ merged . name } , fallback=${ merged . fallbackModel } ` ) ;
77+ return merged ;
78+ }
79+
2280/**
2381 * Helper function to execute AI inference with consistent error handling
2482 * @param params Parameters for the inference operation
@@ -39,7 +97,6 @@ interface InferenceParamsBase {
3997 onChunk : ( chunk : string ) => void ;
4098 } ;
4199 reasoning_effort ?: ReasoningEffort ;
42- modelConfig ?: ModelConfig ;
43100 context : InferenceContext ;
44101 onAssistantMessage ?: ( message : Message ) => Promise < void > ;
45102 completionConfig ?: CompletionConfig ;
@@ -72,98 +129,23 @@ export async function executeInference<T extends z.AnyZodObject>( {
72129 agentActionName,
73130 format,
74131 modelName,
75- modelConfig,
76132 context,
77133 onAssistantMessage,
78134 completionConfig,
79135} : InferenceParamsBase & {
80136 schema ?: T ;
81137 format ?: SchemaFormat ;
82138} ) : Promise < InferResponseString | InferResponseObject < T > | null > {
83- let conf : ModelConfig | undefined ;
84-
85- if ( modelConfig ) {
86- // Use explicitly provided model config
87- conf = modelConfig ;
88- } else if ( context ?. metadata . userId && context ?. userModelConfigs ) {
89- // Try to get user-specific configuration from context cache
90- conf = context . userModelConfigs [ agentActionName ] ;
91- if ( conf ) {
92- logger . info ( `Using user configuration for ${ agentActionName } : ${ JSON . stringify ( conf ) } ` ) ;
93- } else {
94- logger . info ( `No user configuration for ${ agentActionName } , using AGENT_CONFIG defaults` , context . userModelConfigs , context ) ;
95- }
96-
97- // If conf.name is not a valid AIModels enum value, fall back to defaults
98- if ( conf && ! isValidAIModel ( conf . name ) ) {
99- logger . warn ( `User config model ${ conf . name } not in defined AIModels, falling back to defaults` ) ;
100- conf = undefined ;
101- }
102-
103- // If conf.name violates agent constraints, fall back to defaults
104- if ( conf && conf . name ) {
105- const constraintCheck = validateAgentConstraints ( agentActionName , conf . name ) ;
139+ // Resolve config with clear precedence: userConfig > defaults
140+ const resolvedConfig = resolveModelConfig (
141+ agentActionName ,
142+ context ?. userModelConfigs ?. [ agentActionName ] ,
143+ ) ;
106144
107- if ( constraintCheck . constraintEnabled && ! constraintCheck . valid ) {
108- logger . warn (
109- `User config model ${ conf . name } violates constraints for ${ agentActionName } , falling back to defaults. ` +
110- `Allowed models: ${ constraintCheck . allowedModels ?. join ( ', ' ) } `
111- ) ;
112- conf = undefined ; // Trigger fallback to AGENT_CONFIG[agentActionName]
113- }
114- }
115-
116- // Validate fallback model too (if exists in conf)
117- if ( conf && conf . fallbackModel ) {
118- const fallbackCheck = validateAgentConstraints ( agentActionName , conf . fallbackModel ) ;
119-
120- if ( fallbackCheck . constraintEnabled && ! fallbackCheck . valid ) {
121- logger . warn (
122- `User config fallback model ${ conf . fallbackModel } violates constraints for ${ agentActionName } , removing fallback`
123- ) ;
124- conf . fallbackModel = undefined ; // Remove invalid fallback
125- }
126- }
127-
128- // If conf.name is not in defined AIModels, fall back to defaults
129- if ( conf && ! ( conf . name in AIModels ) ) {
130- logger . warn ( `User config model ${ conf . name } not in defined AIModels, falling back to defaults` ) ;
131- conf = undefined ;
132- }
133-
134- // If conf.name violates agent constraints, fall back to defaults
135- if ( conf && conf . name ) {
136- const constraintCheck = validateAgentConstraints ( agentActionName , conf . name ) ;
137-
138- if ( constraintCheck . constraintEnabled && ! constraintCheck . valid ) {
139- logger . warn (
140- `User config model ${ conf . name } violates constraints for ${ agentActionName } , falling back to defaults. ` +
141- `Allowed models: ${ constraintCheck . allowedModels ?. join ( ', ' ) } `
142- ) ;
143- conf = undefined ; // Trigger fallback to AGENT_CONFIG[agentActionName]
144- }
145- }
146-
147- // Validate fallback model too (if exists in conf)
148- if ( conf && conf . fallbackModel ) {
149- const fallbackCheck = validateAgentConstraints ( agentActionName , conf . fallbackModel ) ;
150-
151- if ( fallbackCheck . constraintEnabled && ! fallbackCheck . valid ) {
152- logger . warn (
153- `User config fallback model ${ conf . fallbackModel } violates constraints for ${ agentActionName } , removing fallback`
154- ) ;
155- conf . fallbackModel = undefined ; // Remove invalid fallback
156- }
157- }
158- }
159-
160- // Use the final config or fall back to AGENT_CONFIG defaults
161- const finalConf = conf || AGENT_CONFIG [ agentActionName ] ;
162-
163- modelName = modelName || finalConf . name ;
164- temperature = temperature || finalConf . temperature || 0.2 ;
165- maxTokens = maxTokens || finalConf . max_tokens || 16000 ;
166- reasoning_effort = reasoning_effort || finalConf . reasoning_effort ;
145+ modelName = modelName || resolvedConfig . name ;
146+ temperature = temperature ?? resolvedConfig . temperature ?? 0.2 ;
147+ maxTokens = maxTokens || resolvedConfig . max_tokens || 16000 ;
148+ reasoning_effort = reasoning_effort || resolvedConfig . reasoning_effort ;
167149
168150 // Exponential backoff for retries
169151 const backoffMs = ( attempt : number ) => Math . min ( 500 * Math . pow ( 2 , attempt ) , 10000 ) ;
@@ -237,20 +219,13 @@ export async function executeInference<T extends z.AnyZodObject>( {
237219 messages . push ( createAssistantMessage ( error . response ) ) ;
238220 messages . push ( createUserMessage ( responseRegenerationPrompts ) ) ;
239221 useCheaperModel = true ;
240-
241- // If this was a repetition error, apply a frequency penalty to the retry
242- if ( error . message . toLowerCase ( ) . includes ( 'repetition' ) ) {
243- logger . info ( 'Applying frequency penalty to retry due to repetition' ) ;
244- // Create a temporary config override for this retry
245- conf = {
246- ...finalConf ,
247- frequency_penalty : 0.5 // Apply moderate penalty
248- } ;
249- }
250222 }
251223 } else {
252- // Try using fallback model if available
253- modelName = conf ?. fallbackModel || modelName ;
224+ // Switch to fallback model if available
225+ if ( resolvedConfig . fallbackModel && resolvedConfig . fallbackModel !== modelName ) {
226+ logger . info ( `Switching to fallback model: ${ resolvedConfig . fallbackModel } ` ) ;
227+ modelName = resolvedConfig . fallbackModel ;
228+ }
254229 }
255230
256231 if ( ! isLastAttempt ) {
0 commit comments