163def _pick_model(model_manager, preferred: str |
None, session_model: str) -> tuple[str, bool, str |
None]:
164 """Return ``(model, would_switch, cheap_model)``.
166 Always returns *session_model* for the model (preserving prefix cache).
167 If *preferred* differs from *session_model*, ``would_switch=True`` and
168 *cheap_model* is set.
170 NOTE (plan 132 + 134): ``would_switch`` / ``cheap_model`` are NO LONGER a
171 sub-loop trigger, and sub-loops no longer run on ``cheap_model`` — the
172 interim model-lock runs them on the **session model** (no silent flash
173 out-of-band usage). These fields are retained only for the future
174 targeted, opt-in cheap-model offload redesign; nothing consumes
175 ``cheap_model`` to pick a model today.
177 if preferred
and _known(model_manager, preferred)
and preferred != session_model:
178 return session_model,
True, preferred
179 if _known(model_manager, session_model):
180 return session_model,
False,
None
181 keys = list(model_manager.known_models().keys())
182 fallback = keys[0]
if keys
else session_model
183 return fallback,
False,
None
232 base: int, token_profile: str, *, ceiling: int |
None =
None
234 """Scale completion ``max_tokens`` from ``/budget`` token profile.
236 *ceiling* bounds the ``deep`` profile. It defaults to the operator
237 ceiling; :func:`resolve_output_tokens` passes the tighter of that and the
238 model's own declared limit, so ``deep`` can never ask a provider for more
239 output than it says it can produce.
241 prof = (token_profile
or "balanced").strip().lower()
242 cap = ceiling
if ceiling
is not None else get_output_tokens_ceiling()
244 return max(1024, int(base * 0.62))
246 return min(cap, int(base * 1.18))
247 return min(cap, base)
251 model_manager: Any, model: str, bucket: str, token_profile: str
253 """Resolve this iteration's completion cap from the model's own capability.
255 Replaces the former hardcoded per-bucket literals (12_288 / 8192 / 6144 /
256 4096), which were sized for a small model and silently truncated a
257 large one. The budget is the model's declared ``max_output_tokens``,
258 bounded by the operator ceiling, then apportioned by bucket and scaled by
259 the ``/budget`` profile.
261 func =
"resolve_output_tokens"
262 ceiling = get_output_tokens_ceiling()
263 declared = int(
_caps(model_manager, model).get(
"max_output_tokens")
or 0)
264 budget = min(declared
if declared > 0
else UNDECLARED_OUTPUT_TOKENS, ceiling)
265 share = _BUCKET_OUTPUT_SHARE.get(bucket, 1.0)
266 base = max(_MIN_BUCKET_OUTPUT_TOKENS, int(budget * share))
272 _dbg(
"OC_CHAT.MAIN", func,
273 f
"bucket={bucket} model={model} declared={declared} ceiling={ceiling} "
274 f
"budget={budget} share={share} profile={token_profile} -> {out}", 3)
279 """Determine if a task is complex enough to warrant sub-loop usage."""
281 complexity_indicators = [
295 task_lower = task.lower()
296 has_complexity = any(indicator
in task_lower
for indicator
in complexity_indicators)
299 is_stuck = iteration_count > 8
302 recent_user_messages = [m
for m
in recent_messages
if m.get(
"role") ==
"user"]
303 has_repetition = len(recent_user_messages) > 3
and any(
305 for m
in recent_user_messages[-3:]
309 if iteration_count < 3:
313 sub_loop_used_recently = any(
315 for m
in recent_messages[-5:]
318 if sub_loop_used_recently:
322 if iteration_count > 15:
327 m
for m
in recent_messages[-10:]
328 if m.get(
"role") ==
"assistant" and
329 any(error
in _message_text(m).lower()
for error
in [
"error",
"failed",
"not found"])
332 if len(recent_errors) > 3:
335 return has_complexity
or is_stuck
or has_repetition
359 session_reasoning_effort: str,
362 is_finalize_nudge: bool,
363 messages: list[dict],
364 token_profile: str =
"balanced",
365) -> IterationRouting:
366 """Pick model / effort / thinking / max_tokens for this ReAct iteration."""
367 func =
"resolve_iteration_routing"
368 tp = token_profile
or "balanced"
369 provider = model_manager.provider
370 reasoning_model = provider.tier(_REASONING_TIER)
371 cheap_model = provider.tier(_CHEAP_TIER)
375 if iteration == 0
or is_finalize_nudge:
376 model, would_switch, cheap_model =
_pick_model(model_manager, reasoning_model, session_model)
381 if is_finalize_nudge:
385 pref = session_reasoning_effort
if session_reasoning_effort
in (
"low",
"medium",
"high")
else "high"
387 effort = e2
if e2
is not None else effort
390 _dbg(
"OC_CHAT.MAIN", func,
391 f
"planning/finalize: model={model} effort={effort} max_tokens={mt} finalize={is_finalize_nudge}", 3)
392 return IterationRouting(model=model, effort=effort, extra_body=extra, max_tokens=mt, would_switch_model=would_switch, cheap_model=cheap_model)
397 if bucket ==
"simple":
398 model, would_switch, cheap_model =
_pick_model(model_manager, cheap_model, session_model)
401 _dbg(
"OC_CHAT.MAIN", func,
402 f
"bucket=simple: model={model} effort={effort} max_tokens={mt}", 3)
403 return IterationRouting(model=model, effort=effort, extra_body=
None, max_tokens=mt, would_switch_model=would_switch, cheap_model=cheap_model)
405 if bucket ==
"simple_thinking":
406 model, would_switch, cheap_model =
_pick_model(model_manager, reasoning_model, session_model)
410 _dbg(
"OC_CHAT.MAIN", func,
411 f
"bucket=simple_thinking: model={model} effort={effort} max_tokens={mt}", 3)
412 return IterationRouting(model=model, effort=effort, extra_body=extra, max_tokens=mt, would_switch_model=would_switch, cheap_model=cheap_model)
414 if bucket ==
"thinking_write":
415 model, would_switch, cheap_model =
_pick_model(model_manager, reasoning_model, session_model)
419 _dbg(
"OC_CHAT.MAIN", func,
420 f
"bucket=thinking_write: model={model} effort={effort} max_tokens={mt}", 3)
421 return IterationRouting(model=model, effort=effort, extra_body=extra, max_tokens=mt, would_switch_model=would_switch, cheap_model=cheap_model)
424 model, would_switch, cheap_model =
_pick_model(model_manager, reasoning_model, session_model)
428 _dbg(
"OC_CHAT.MAIN", func,
429 f
"bucket=thinking: model={model} effort={effort} max_tokens={mt}", 3)
430 return IterationRouting(model=model, effort=effort, extra_body=extra, max_tokens=mt, would_switch_model=would_switch, cheap_model=cheap_model)
IterationRouting resolve_iteration_routing(*, str session_model, str session_reasoning_effort, model_manager, int iteration, bool is_finalize_nudge, list[dict] messages, str token_profile="balanced")