diff --git a/api/streaming.py b/api/streaming.py index 7460fefe..20a78e47 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -1209,17 +1209,23 @@ def _is_minimax_route(provider: str = '', model: str = '', base_url: str = '') - return 'minimax' in text or 'minimaxi.com' in text -def _aux_title_configured() -> bool: - """Return True when any auxiliary title_generation config field is meaningfully set.""" +def _get_aux_title_config() -> dict: + """Return title_generation auxiliary config, or an empty dict on errors.""" try: from agent.auxiliary_client import _get_auxiliary_task_config tg = _get_auxiliary_task_config('title_generation') - provider = tg.get('provider', '') or '' - model = tg.get('model', '') or '' - base_url = tg.get('base_url', '') or '' - return bool(model or base_url or (provider and provider.lower() != 'auto')) + return tg if isinstance(tg, dict) else {} except Exception: - return False + return {} + + +def _aux_title_configured() -> bool: + """Return True when any auxiliary title_generation config field is meaningfully set.""" + tg = _get_aux_title_config() + provider = tg.get('provider', '') or '' + model = tg.get('model', '') or '' + base_url = tg.get('base_url', '') or '' + return bool(model or base_url or (provider and provider.lower() != 'auto')) def _aux_title_timeout(default: float = 15.0) -> float: """Return the configured timeout (seconds) for auxiliary title generation. @@ -1229,8 +1235,7 @@ def _aux_title_timeout(default: float = 15.0) -> float: so mis-configurations are visible in server output. """ try: - from agent.auxiliary_client import _get_auxiliary_task_config - tg = _get_auxiliary_task_config('title_generation') + tg = _get_aux_title_config() raw = tg.get('timeout') if raw is None: return default @@ -1363,6 +1368,12 @@ def generate_title_raw_via_aux( if not user_text or not assistant_text: return None, 'missing_exchange' qa, prompts = _title_prompts(user_text, assistant_text) + configured = _get_aux_title_config() + provider = provider or configured.get('provider', '') or '' + if str(provider).strip().lower() == 'auto': + provider = '' + model = model or configured.get('model', '') or '' + base_url = base_url or configured.get('base_url', '') or '' base_max_tokens = _title_completion_budget(provider, model, base_url) reasoning_extra = {"reasoning": {"enabled": False}} if _is_minimax_route(provider, model, base_url): diff --git a/tests/test_title_aux_routing.py b/tests/test_title_aux_routing.py index c9a1552d..f3c7cb82 100644 --- a/tests/test_title_aux_routing.py +++ b/tests/test_title_aux_routing.py @@ -109,6 +109,51 @@ class TestGenerateTitleRawViaAuxTimeout(unittest.TestCase): 30.0, ) + def test_configured_provider_model_and_base_url_are_passed_to_aux_client(self): + """Regression for #2235: task config must select the first title model. + + If generate_title_raw_via_aux leaves provider/model/base_url as None, + agent.auxiliary_client.call_llm can fall back to the main chat model and + WebUI titles look like local first-message placeholders or unrelated + chat-model output instead of using auxiliary.title_generation.model. + """ + from api.streaming import generate_title_raw_via_aux + + mock_resp = types.SimpleNamespace( + choices=[ + types.SimpleNamespace( + message=types.SimpleNamespace(content='Configured Model Title'), + finish_reason='stop', + ) + ] + ) + captured = {} + + def fake_call_llm(**kwargs): + captured.update(kwargs) + return mock_resp + + tg_config = { + 'provider': 'openrouter', + 'model': 'anthropic/claude-haiku-title', + 'base_url': 'https://openrouter.ai/api/v1', + 'timeout': 22.0, + } + with _patch_tg_config(tg_config): + with patch('agent.auxiliary_client.call_llm', side_effect=fake_call_llm, create=True): + result, status = generate_title_raw_via_aux( + user_text='Explain why the moon has phases.', + assistant_text='The moon has phases because we see different sunlit portions.', + ) + + self.assertEqual(result, 'Configured Model Title') + self.assertEqual(status, 'llm_aux') + self.assertEqual(captured.get('task'), 'title_generation') + self.assertEqual(captured.get('provider'), 'openrouter') + self.assertEqual(captured.get('model'), 'anthropic/claude-haiku-title') + self.assertEqual(captured.get('base_url'), 'https://openrouter.ai/api/v1') + self.assertEqual(captured.get('timeout'), 22.0) + def test_integer_timeout_from_config(self): """Config timeout as int is coerced to float.""" self._run_with_config(