diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index e227053118..b26ddaf1cf 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -5202,6 +5202,14 @@ class LlamaCppBackend: stream_payload["t_max_predict_ms"] = _DEFAULT_T_MAX_PREDICT_MS if stop: stream_payload["stop"] = stop + # Match the per-iteration tool loop above so sampling behavior + # stays consistent when the cap-exhausted final-answer pass runs. + if frequency_penalty is not None: + stream_payload["frequency_penalty"] = frequency_penalty + if seed is not None: + stream_payload["seed"] = seed + if parallel_tool_calls is not None: + stream_payload["parallel_tool_calls"] = parallel_tool_calls stream_payload["stream_options"] = {"include_usage": True} cumulative = "" diff --git a/studio/backend/core/inference/providers.py b/studio/backend/core/inference/providers.py index 6701b27ba9..026d19217b 100644 --- a/studio/backend/core/inference/providers.py +++ b/studio/backend/core/inference/providers.py @@ -312,6 +312,9 @@ PROVIDER_REGISTRY: dict[str, dict[str, Any]] = { }, "notes": "Unified gateway to 300+ models across all major providers. HTTP-Referer and X-Title headers sent for attribution.", "model_list_mode": "curated", + # OpenRouter normalises to OpenAI's chat schema and inherits + # the 4-entry stop cap. + "stop_max": 4, }, } diff --git a/studio/backend/tests/test_sampling_params_routing.py b/studio/backend/tests/test_sampling_params_routing.py index 99cb3a8993..0cfda6b7d1 100644 --- a/studio/backend/tests/test_sampling_params_routing.py +++ b/studio/backend/tests/test_sampling_params_routing.py @@ -607,6 +607,34 @@ def test_kimi_web_search_uses_kimi_stop_cap_5(monkeypatch): assert body["stop"] == ["S0", "S1", "S2", "S3", "S4"], body +def test_openrouter_stop_cap_is_4(monkeypatch): + """OpenRouter normalises to OpenAI's chat schema and inherits the + 4-entry stop cap; the default 16-cap is too permissive for it.""" + captured = _install_mock(monkeypatch, sse_payload = _oai_done_payload()) + + async def run(): + client = ExternalProviderClient( + provider_type = "openrouter", + base_url = "https://openrouter.ai/api/v1", + api_key = "or-test", + ) + async for _ in client.stream_chat_completion( + messages = [{"role": "user", "content": "hi"}], + model = "openai/gpt-4o", + temperature = 0.5, + top_p = 0.9, + max_tokens = 64, + stop = [f"S{i}" for i in range(10)], + ): + pass + await client.close() + + _drive(run()) + body = captured["body"] + assert len(body.get("stop", [])) == 4, body + assert body["stop"] == ["S0", "S1", "S2", "S3"], body + + def test_kimi_drops_stop_strings_over_32_bytes(monkeypatch): """Kimi limits each stop string to <= 32 bytes per https://platform.kimi.ai/docs/api/chat. Drop overlong entries diff --git a/studio/frontend/src/features/chat/provider-capabilities.ts b/studio/frontend/src/features/chat/provider-capabilities.ts index 7b439fb5ab..1565516dd8 100644 --- a/studio/frontend/src/features/chat/provider-capabilities.ts +++ b/studio/frontend/src/features/chat/provider-capabilities.ts @@ -64,12 +64,13 @@ export interface ProviderCapabilities { * Per-provider stop-sequence max count. Resolved by * `getProviderStopMax(providerType)`. Mirrors the backend's * `provider_info.stop_max` for the same provider type. - * - openai: 4 (Chat Completions hard cap; Responses drops stop) - * - anthropic: 16 (client-side guard; docs publish no max) - * - kimi: 5 (https://platform.kimi.ai/docs/api/chat) - * - deepseek: 16 (https://api-docs.deepseek.com/api/create-chat-completion) - * - mistral: 16 (no documented max; widen to permissive default) - * - default: 16 (covers ollama, vllm, llama.cpp, openrouter, custom) + * - openai: 4 (Chat Completions hard cap; Responses drops stop) + * - anthropic: 16 (client-side guard; docs publish no max) + * - kimi: 5 (https://platform.kimi.ai/docs/api/chat) + * - deepseek: 16 (https://api-docs.deepseek.com/api/create-chat-completion) + * - mistral: 16 (no documented max; widen to permissive default) + * - openrouter: 4 (normalises to OpenAI's chat schema) + * - default: 16 (covers ollama, vllm, llama.cpp, custom) */ const PROVIDER_STOP_MAX: Record = { openai: 4, @@ -77,6 +78,7 @@ const PROVIDER_STOP_MAX: Record = { kimi: 5, deepseek: 16, mistral: 16, + openrouter: 4, }; export function getProviderStopMax(