From 1add4dbd0ed1c87c2228d7aa15fde60e89b76065 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Wed, 27 May 2026 14:32:11 +0000 Subject: [PATCH] Tighten comments across PR 5711 (no behaviour change) Audited every comment added by this PR; condensed multi-paragraph docstrings and inline blocks to 1-2 lines where the WHY survives. Files touched: 6 backend + 7 frontend. Net -148 lines. - external_provider.py: -80 (docstring + stop_sequences + compaction) - chat-settings-sheet.tsx: -70 (InfoHint tooltips + section headers) - routes/inference.py: -54 (parallel_tool_calls cap comments) - provider-capabilities.ts: -108 (stop-cap table, gemini bucket, service_tier resolver, max-output cap header) - chat-adapter.ts: -27 (sampling forwarding stanza) - providers.py: -24 (kimi reasoning class, deepseek aliases) - llama_cpp.py: -24 (payload builder shared header) - models/inference.py: -28 (Pydantic Field descriptions) - chat-settings-storage.ts: -23 (nullable handling comments) - stop-sequences-input.tsx: -23 (JSDoc + chip-key + draft-commit) - anthropic_compat.py: -9 (serial tool-call gate) - types/runtime.ts: -12 (DRY JSDoc, null convention header) - types/api.ts: -6 (service_tier JSDoc) Tightening rules applied: - Removed em-dashes everywhere a comment was rewritten. - Kept every external doc URL (Anthropic, OpenAI, vLLM, llama.cpp, Ollama, OpenRouter, Mistral, DeepSeek, Gemini, Kimi). - Collapsed "silently dropped on unsupported routes" prose since the bucket-comment at the top of each capability table already states it. - Removed restated rationale prose in comment blocks where the field name plus the master rule already encodes the WHY. 393/393 backend tests pass (test_sampling_params_routing 65, plus anthropic / openai / gemini / llama-server suites). Frontend tsc + vite build clean. --- .../core/inference/anthropic_compat.py | 9 +- .../core/inference/external_provider.py | 80 +++++-------- studio/backend/core/inference/llama_cpp.py | 24 ++-- studio/backend/core/inference/providers.py | 24 ++-- studio/backend/models/inference.py | 28 ++--- studio/backend/routes/inference.py | 54 +++------ .../components/ui/stop-sequences-input.tsx | 23 +--- .../src/features/chat/api/chat-adapter.ts | 27 ++--- .../src/features/chat/chat-settings-sheet.tsx | 70 ++++-------- .../features/chat/provider-capabilities.ts | 108 +++++++----------- .../frontend/src/features/chat/types/api.ts | 6 +- .../src/features/chat/types/runtime.ts | 12 +- .../chat/utils/chat-settings-storage.ts | 23 ++-- 13 files changed, 170 insertions(+), 318 deletions(-) diff --git a/studio/backend/core/inference/anthropic_compat.py b/studio/backend/core/inference/anthropic_compat.py index c2c1094806..cb2736a525 100644 --- a/studio/backend/core/inference/anthropic_compat.py +++ b/studio/backend/core/inference/anthropic_compat.py @@ -410,11 +410,10 @@ class AnthropicPassthroughEmitter: self._tool_call_states: dict = {} # delta index -> {block_index, id, name} self._usage: dict = {} self._stop_reason: str = "end_turn" - # When the caller opted out of parallel tool calls (Anthropic - # `tool_choice.disable_parallel_tool_use=true` mapped to local - # `parallel_tool_calls=false`), only emit the first tool-call - # index and drop later siblings. llama.cpp may not enforce the - # flag on every jinja template (ggml-org/llama.cpp#22043). + # parallel_tool_calls=False (Anthropic + # tool_choice.disable_parallel_tool_use=true): emit only the first + # tool-call index; llama.cpp's flag isn't enforced by every jinja + # template (ggml-org/llama.cpp#22043). self._serial_tool_calls: bool = parallel_tool_calls is False self._first_tool_call_idx: Optional[int] = None diff --git a/studio/backend/core/inference/external_provider.py b/studio/backend/core/inference/external_provider.py index c38a077627..bdb56c33ab 100644 --- a/studio/backend/core/inference/external_provider.py +++ b/studio/backend/core/inference/external_provider.py @@ -81,15 +81,10 @@ def _normalize_stop_for_provider( def _is_openai_family_cloud(base_url: Optional[str]) -> bool: """True iff ``base_url`` points at OpenAI cloud or Azure OpenAI Foundry. - Host-anchored to avoid subdomain-injection bypass - (https://evil.com/api.openai.com/v1, https://api.openai.com.attacker.com/v1). - Used to gate cloud-only Responses-API extensions - (prompt_cache_retention, context_management compaction, container - shell tool) that 400 on non-cloud OAI-compat servers. - - Azure Foundry uses .openai.azure.com; match via endswith - with the leading dot so the apex `openai.azure.com` can't slip - through (no apex Foundry endpoint exists). + Host-anchored against subdomain-injection (api.openai.com.attacker.com). + Gates Responses-API extensions (prompt_cache_retention, context_management, + container shell) that 400 on non-cloud OAI-compat servers. Azure Foundry + matches via .openai.azure.com suffix; leading dot blocks the apex. """ if not base_url: return False @@ -917,19 +912,14 @@ class ExternalProviderClient: For OpenAI-compatible providers, lines are forwarded verbatim. For Anthropic, the native Messages API SSE is translated to OpenAI format. - ``top_k`` and ``presence_penalty`` are forwarded only when the caller - supplies a value the provider accepts — the frontend's - provider-capability map already filters these per provider, so we - treat them as opt-in here. - + Optional sampling extras (``top_k``, ``presence_penalty``, ``frequency_penalty``, ``seed``, ``stop``, ``service_tier``, - ``parallel_tool_calls`` follow the same rule: the per-provider - stream helpers silently drop fields the upstream API does not - accept (e.g. Responses rejects all of seed / frequency / stop; - Anthropic does not implement seed / frequency / logprobs). + ``parallel_tool_calls``) are opt-in. Per-provider helpers silently + drop fields the upstream rejects (Responses: seed/freq/stop; + Anthropic: seed/freq/logprobs). - ``fast_mode`` only applies to Anthropic Opus 4.6 / 4.7 (silently - dropped elsewhere); adds the beta header and ``speed: "fast"``. + ``fast_mode``: Anthropic Opus 4.6/4.7 only; adds the beta header + and ``speed: "fast"``. """ # tool_choice="none" hard-disables hosted/builtin tools across # every provider so enabled_tools cannot accidentally bill or leak. @@ -1054,9 +1044,8 @@ class ExternalProviderClient: else: body["max_tokens"] = max_tokens - # Optional sampling extensions. Only forwarded when the caller - # passed a value. Per-provider rename / cap via `seed_field` / - # `stop_max`; `body_omit` strips fields the upstream rejects. + # Optional sampling extras; `seed_field` renames seed (Mistral), + # `body_omit` strips upstream-rejected fields. from core.inference.providers import get_provider_info provider_info = get_provider_info(self.provider_type) or {} @@ -1069,9 +1058,8 @@ class ExternalProviderClient: normalized_stop = _normalize_stop_for_provider(stop, provider_info) if normalized_stop: body["stop"] = normalized_stop - # service_tier is OpenAI Chat-only on the generic OAI-compat - # branch; opt-in via `accepts_service_tier=True` on the registry - # entry. Anthropic and Responses handle it in their own helpers. + # service_tier is OAI-Chat-only here (accepts_service_tier registry + # opt-in); Anthropic/Responses branches set it themselves. if service_tier is not None and provider_info.get( "accepts_service_tier", False ): @@ -1468,10 +1456,8 @@ class ExternalProviderClient: if max_tokens is not None: body["max_tokens"] = max_tokens - # The default OAI-compat body construction is skipped because - # this helper returns early. Apply the same provider-aware - # sampling / stop logic here so kimi-with-search matches - # kimi-without-search. + # Kimi-with-search returns early before the default OAI-compat + # body build; re-apply provider-aware sampling/stop here. from core.inference.providers import get_provider_info provider_info = get_provider_info(self.provider_type) or {} @@ -2141,27 +2127,17 @@ class ExternalProviderClient: if top_k is not None and top_k > 0 and not sampling_removed: body["top_k"] = top_k - # Optional sampling extensions. Anthropic has no - # frequency_penalty / seed / logprobs equivalents so they are - # never forwarded here. The two body-level knobs Anthropic - # accepts land here: - # stop -> stop_sequences (renamed, ws-stripped) - # service_tier -> service_tier (auto|standard_only only) - # parallel_tool_calls inversion is applied after the tools - # wiring below because Anthropic requires it nested under - # tool_choice. + # Anthropic body-knob mapping: stop -> stop_sequences (ws-stripped, + # dedup), service_tier (auto|standard_only). parallel_tool_calls is + # handled after tools wiring (Anthropic nests under tool_choice). if stop: sequences: list[str] if isinstance(stop, str): sequences = [stop] if stop.strip() else [] else: - # Dedupe + drop whitespace-only entries. Anthropic - # rejects any sequence with no non-whitespace char - # ("stop_sequences: each stop sequence must contain - # non-whitespace"), so "", " ", "\n", "\n\n" are all - # filtered. The 16-cap is a client-side guard; the - # docs do not publish a max, but every SDK treats 16 - # as a sane ceiling (Bedrock is the outlier at 8191). + # Anthropic rejects whitespace-only stop_sequences ("must + # contain non-whitespace"); 16-cap is a client-side guard + # (undocumented max; every SDK uses 16, Bedrock at 8191). sequences = list( dict.fromkeys(s for s in stop if isinstance(s, str) and s.strip()) ) @@ -2371,9 +2347,8 @@ class ExternalProviderClient: if anthropic_code_exec_container_id: body["container"] = anthropic_code_exec_container_id - # parallel_tool_calls=False maps to disable_parallel_tool_use=True - # nested under tool_choice (top-level placement is rejected). - # Without tools the flag is a no-op so we skip the block. + # parallel_tool_calls=False -> tool_choice.disable_parallel_tool_use=True + # (top-level 400s; no-op without tools). # https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use if parallel_tool_calls is False and body.get("tools"): tc = body.get("tool_choice") @@ -5435,11 +5410,8 @@ class ExternalProviderClient: "input": input_items, "stream": True, } - # Responses accepts auto|default|flex|priority per the live - # docs. The openai-python SDK type happens to include "scale" - # too but the public Responses reference does not, so drop it - # here to avoid a 400. Scale Tier is still selectable on Chat - # Completions backends. parallel_tool_calls default is true. + # Responses: auto|default|flex|priority (SDK type lists "scale" + # but server 400s; Scale Tier stays on Chat Completions). if service_tier in ("auto", "default", "flex", "priority"): body["service_tier"] = service_tier if parallel_tool_calls is not None: diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index 7fd6c44d83..0b3e426dc1 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -4316,10 +4316,8 @@ class LlamaCppBackend: else (self._effective_context_length or _DEFAULT_MAX_TOKENS_FLOOR) ) payload["t_max_predict_ms"] = _DEFAULT_T_MAX_PREDICT_MS - # Strip empty / non-string stop entries before forwarding to - # llama-server (matches `_normalize_stop_for_provider` for the - # external path). Without this a stale `stop=["", "END"]` can - # 400 the upstream. + # Strip empty/non-string stop entries (mirrors + # `_normalize_stop_for_provider`); stale `stop=["", "END"]` 400s. if stop: if isinstance(stop, str): payload["stop"] = stop @@ -4327,9 +4325,8 @@ class LlamaCppBackend: _cleaned = [s for s in stop if isinstance(s, str) and s] if _cleaned: payload["stop"] = _cleaned - # Each field gated `is not None` so explicit 0 / 0.0 / False - # values reach the wire. llama-server silently ignores fields - # it doesn't recognise. + # `is not None` gate so explicit 0/False reach the wire; + # llama-server ignores unknown fields. if frequency_penalty is not None: payload["frequency_penalty"] = frequency_penalty if seed is not None: @@ -5195,12 +5192,9 @@ class LlamaCppBackend: _accumulated_predicted_ms += _it.get("predicted_ms", 0) _accumulated_predicted_n += _it.get("predicted_n", 0) - # When the caller opted out of parallel tool calls - # (parallel_tool_calls=False), enforce at most one call - # per assistant turn even if llama-server emitted more. - # llama.cpp's parallel_tool_calls flag isn't enforced by - # every jinja template (see ggml-org/llama.cpp#22043), - # so this client-side cap is the only guarantee. + # parallel_tool_calls=False: client-side cap to 1 + # (llama.cpp flag isn't enforced by every jinja template, + # ggml-org/llama.cpp#22043). if parallel_tool_calls is False and tool_calls: tool_calls = tool_calls[:1] @@ -5414,8 +5408,8 @@ class LlamaCppBackend: _cleaned = [s for s in stop if isinstance(s, str) and s] if _cleaned: stream_payload["stop"] = _cleaned - # Match the per-iteration tool loop above so sampling behavior - # stays consistent when the cap-exhausted final-answer pass runs. + # Match per-iteration tool-loop sampling for the cap-exhausted + # final-answer pass. if frequency_penalty is not None: stream_payload["frequency_penalty"] = frequency_penalty if seed is not None: diff --git a/studio/backend/core/inference/providers.py b/studio/backend/core/inference/providers.py index 777d11b702..ed0047d8da 100644 --- a/studio/backend/core/inference/providers.py +++ b/studio/backend/core/inference/providers.py @@ -137,19 +137,17 @@ PROVIDER_REGISTRY: dict[str, dict[str, Any]] = { r"gemini-pro-latest|gemini-flash-latest|gemini-flash-lite-latest" r")$" ), - # Gemini's OpenAI-compatible layer inherits OpenAI's 4-stop cap - # (https://ai.google.dev/gemini-api/docs/openai). Without the - # explicit cap the default 16 leaks through and the upstream - # silently drops the overflow. + # Gemini OAI-compat inherits OpenAI's 4-stop cap; default 16 + # silently truncates upstream. + # https://ai.google.dev/gemini-api/docs/openai "stop_max": 4, }, "deepseek": { "display_name": "DeepSeek", "base_url": "https://api.deepseek.com/v1", - # Legacy aliases (deepseek-chat / deepseek-reasoner) retire - # 2026-07-24 per https://api-docs.deepseek.com/updates. Surface - # the new canonical ids (deepseek-v4-flash / deepseek-v4-pro) - # alongside so the picker keeps working on cutover. + # deepseek-chat / deepseek-reasoner retire 2026-07-24; list + # v4-pro / v4-flash alongside for cutover. + # https://api-docs.deepseek.com/updates "default_models": [ "deepseek-v4-pro", "deepseek-v4-flash", @@ -217,13 +215,9 @@ PROVIDER_REGISTRY: dict[str, dict[str, Any]] = { "auth_prefix": "Bearer ", "notes": "Moonshot API key. China: use base URL https://api.moonshot.cn/v1", "model_id_allowlist": re.compile(r"^kimi-k2\.[56]$"), - # Both k2.6 and k2.5 are reasoning-class. The API rejects - # custom sampling ("invalid temperature: only 1 is allowed for - # this model", same for top_p). frequency_penalty follows the - # same lock on those models. seed and parallel_tool_calls are - # not in Kimi's documented chat schema; strip them too so a - # stale client or direct API caller cannot smuggle them onto - # the wire and 400 the request. + # k2.5/k2.6 are reasoning-class: API locks temperature=1, top_p=1, + # frequency_penalty; seed and parallel_tool_calls are undocumented + # and 400. "body_omit": ( "temperature", "top_p", diff --git a/studio/backend/models/inference.py b/studio/backend/models/inference.py index 04daba30d0..e16a074a85 100644 --- a/studio/backend/models/inference.py +++ b/studio/backend/models/inference.py @@ -876,10 +876,8 @@ class ChatCompletionRequest(BaseModel): seed: Optional[int] = Field( None, description = ( - "Best-effort determinism seed. Forwarded to OpenAI Chat " - "Completions and OpenAI-compatible local backends. The " - "Responses family rejects it server-side and Anthropic does " - "not implement it, so it is silently dropped on those routes." + "Best-effort determinism seed. Forwarded to OpenAI Chat and " + "OAI-compat local backends; dropped on Anthropic and OpenAI Responses." ), ) service_tier: Optional[ @@ -887,22 +885,18 @@ class ChatCompletionRequest(BaseModel): ] = Field( None, description = ( - "Provider service tier. Anthropic accepts only `auto` and " - "`standard_only`; OpenAI Chat accepts " - "`auto|default|flex|priority|scale`; OpenAI Responses accepts " - "`auto|default|flex|priority`. Unsupported values per provider " - "are dropped in the per-provider stream helper instead of " - "422'ing here so a stale frontend never breaks a request." + "Provider service tier. Anthropic: auto|standard_only. " + "OpenAI Chat: auto|default|flex|priority|scale. " + "OpenAI Responses: auto|default|flex|priority. " + "Unsupported values are dropped per provider rather than 422'd here." ), ) parallel_tool_calls: Optional[bool] = Field( None, description = ( - "Whether the provider may dispatch tool calls in parallel. " - "OpenAI: forwarded as `parallel_tool_calls`. Anthropic: " - "inverted into `disable_parallel_tool_use` on the Messages " - "body. Default `None` preserves each provider's upstream " - "default (which is `true` everywhere today)." + "Allow parallel tool calls. Forwarded as `parallel_tool_calls` " + "on OpenAI; inverted to `disable_parallel_tool_use` on Anthropic. " + "None preserves upstream default (currently true everywhere)." ), ) typical_p: Optional[float] = Field( @@ -958,8 +952,8 @@ class ChatCompletionRequest(BaseModel): None, ge = 0.0, description = ( - "llama.cpp DRY multiplier. 0 disables the 4-field chain " - "(dry_base / dry_allowed_length / dry_penalty_last_n). Local only." + "llama.cpp DRY multiplier. 0 disables the dry_base / " + "dry_allowed_length / dry_penalty_last_n chain. Local only." ), ) dry_base: Optional[float] = Field( diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index 3dfaf398da..cc0e236b49 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -240,12 +240,9 @@ studio_router = APIRouter() def _clean_local_stop_list(stop) -> Optional[list[str]]: - """Strip empty / non-string entries from a stop sequence input. - - Mirrors `_normalize_stop_for_provider` (external_provider.py) so - local llama-server callers cannot ship `stop=["", "END"]` and - get a 400. Returns `None` when nothing survives, so the caller - can omit the field entirely. + """Strip empty/non-string stop entries; returns None when empty so + callers can omit. Mirrors `_normalize_stop_for_provider` so + `stop=["", "END"]` cannot 400 llama-server. """ if isinstance(stop, str): return [stop] if stop else None @@ -4185,9 +4182,7 @@ def _build_chat_request( chat_kwargs["top_p"] = payload.top_p if payload.max_output_tokens is not None: chat_kwargs["max_tokens"] = payload.max_output_tokens - # parallel_tool_calls is first-class on ChatCompletionRequest and - # the OpenAI-compat passthrough builder forwards it. Translate it - # so a Responses API caller's preference reaches llama-server. + # Forward parallel_tool_calls from Responses caller through to llama-server. if payload.parallel_tool_calls is not None: chat_kwargs["parallel_tool_calls"] = payload.parallel_tool_calls @@ -4250,9 +4245,8 @@ async def _responses_non_streaming( msg = choices[0].get("message", {}) or {} text = msg.get("content", "") or "" tool_calls = msg.get("tool_calls") or [] - # Match the cap applied on GGUF / Anthropic / safetensors tool - # paths: when the caller opted out of parallel tool calls, surface - # at most one. llama.cpp may not enforce the flag. + # parallel_tool_calls=False -> cap to 1 (llama.cpp flag isn't enforced; + # mirrors GGUF/Anthropic/safetensors paths). if payload.parallel_tool_calls is False and tool_calls: tool_calls = tool_calls[:1] @@ -4376,10 +4370,9 @@ async def _responses_stream( tool_call_state: dict[int, dict] = {} # Text message lives at output_index 0; tool calls claim 1, 2, ... next_output_index = 1 - # When the caller opted out of parallel tool calls, latch the - # first index we see and drop subsequent siblings — mirrors the - # GGUF agentic-loop / Anthropic-passthrough caps; llama.cpp may - # not enforce the upstream flag (ggml-org/llama.cpp#22043). + # parallel_tool_calls=False: latch the first tc index, drop the + # rest; llama.cpp flag isn't enforced by every jinja template + # (ggml-org/llama.cpp#22043). serial_tool_calls = payload.parallel_tool_calls is False first_serial_idx: Optional[int] = None @@ -4872,10 +4865,9 @@ async def anthropic_messages( if openai_tool_choice is None: openai_tool_choice = "auto" - # Anthropic nests `disable_parallel_tool_use` inside `tool_choice` - # (https://docs.claude.com/en/docs/agents-and-tools/tool-use/implement-tool-use). - # Flip it into the OpenAI-shaped `parallel_tool_calls` toggle so the - # local GGUF tool loop respects clients that opt out of parallel calls. + # Anthropic nests `disable_parallel_tool_use` under `tool_choice`; + # flip to OAI `parallel_tool_calls` so the local GGUF tool loop honors it. + # https://docs.claude.com/en/docs/agents-and-tools/tool-use/implement-tool-use anthropic_parallel_tool_calls: Optional[bool] = None if isinstance(payload.tool_choice, dict): _disable = payload.tool_choice.get("disable_parallel_tool_use") @@ -5393,10 +5385,8 @@ def _build_passthrough_payload( else (backend_ctx or _DEFAULT_MAX_TOKENS_FLOOR) ) body["t_max_predict_ms"] = _DEFAULT_T_MAX_PREDICT_MS - # Strip empty / non-string stop entries before forwarding to - # llama-server; the external-provider helper does this via - # `_normalize_stop_for_provider`, and the local path needs the same - # defensive shape so a stale `stop=["", "END"]` cannot 400 upstream. + # Strip empty stop entries (mirrors `_normalize_stop_for_provider`); + # stale `stop=["", "END"]` would 400 llama-server. if stop: if isinstance(stop, str): if stop: @@ -5412,12 +5402,9 @@ def _build_passthrough_payload( body["repeat_penalty"] = repetition_penalty if presence_penalty is not None: body["presence_penalty"] = presence_penalty - # llama-server's /v1/chat/completions accepts the standard OpenAI - # fields. parallel_tool_calls is a no-op on llama-server today but - # forwarded so a future release picks it up automatically. - # Each field below gated `is not None` so explicit 0 / False reach - # the wire; llama-server silently ignores unknown fields, Ollama's - # OAI translator drops everything outside the OAI subset. + # parallel_tool_calls is a no-op on llama-server today but forwarded + # for future support. `is not None` gate lets explicit 0/False through; + # llama-server ignores unknowns, Ollama drops non-OAI fields. if frequency_penalty is not None: body["frequency_penalty"] = frequency_penalty if seed is not None: @@ -5705,11 +5692,8 @@ async def _anthropic_passthrough_non_streaming( content_blocks.append(AnthropicResponseTextBlock(text = text)) tool_calls = message.get("tool_calls") or [] - # Mirror the GGUF agentic-loop client-side cap: when the caller - # opted out of parallel tool calls, surface at most one tool_use - # block even if llama-server returned more than one. llama.cpp may - # not enforce the flag on every jinja template (see - # ggml-org/llama.cpp#22043). + # parallel_tool_calls=False: cap to 1 tool_use block; llama.cpp flag + # isn't enforced by every jinja template (ggml-org/llama.cpp#22043). if parallel_tool_calls is False and tool_calls: tool_calls = tool_calls[:1] for tc in tool_calls: diff --git a/studio/frontend/src/components/ui/stop-sequences-input.tsx b/studio/frontend/src/components/ui/stop-sequences-input.tsx index 539571313a..e9adbc894f 100644 --- a/studio/frontend/src/components/ui/stop-sequences-input.tsx +++ b/studio/frontend/src/components/ui/stop-sequences-input.tsx @@ -7,14 +7,8 @@ import { cn } from "@/lib/utils"; import { XIcon } from "lucide-react"; import { type KeyboardEvent, useState } from "react"; -/** - * Chips editor for the `stop` / `stop_sequences` array. - * - * Commit a chip with Enter or comma; press Backspace on an empty input - * to delete the most recent chip. Caps at `maxEntries` -- OpenAI Chat - * Completions documents a hard cap of 4 stop sequences; Anthropic - * Messages accepts arbitrarily many. Pass `Infinity` to disable the cap. - */ +/** Chips editor for stop/stop_sequences. Enter or comma commits; Backspace + * on empty deletes the last. OpenAI Chat caps at 4; pass Infinity to disable. */ export interface StopSequencesInputProps { value: string[]; onChange: (next: string[]) => void; @@ -38,12 +32,9 @@ export function StopSequencesInput({ const atCap = value.length >= maxEntries; function commitDraft() { - // Reject only the empty draft; preserve whitespace exactly (stop - // matching is byte-exact). OpenAI-compat / llama-server backends - // accept whitespace-only stops like "\n\n" for blank-line halts; - // pasting such a value into the input should round-trip rather - // than be silently dropped. Anthropic's helper strips whitespace - // entries on the wire so a chip that's invalid there cannot 400. + // Preserve whitespace exactly (stops are byte-exact); llama-server + // accepts "\n\n" for blank-line halts. Anthropic strips whitespace + // entries on the wire. if (!draft) return; if (atCap) return; if (value.includes(draft)) { @@ -85,9 +76,7 @@ export function StopSequencesInput({ > {value.map((entry, index) => ( 0 ? { min_tokens: params.minTokens } : {}), - // Forward only when value diverges from upstream default; - // per-backend capability gating decides whether the wire - // even sees these. + // Forward only on non-default; per-backend cap-gates wire visibility. ...(params.skipSpecialTokens === false ? { skip_special_tokens: false } : {}), diff --git a/studio/frontend/src/features/chat/chat-settings-sheet.tsx b/studio/frontend/src/features/chat/chat-settings-sheet.tsx index 90246ed901..2abc423ae5 100644 --- a/studio/frontend/src/features/chat/chat-settings-sheet.tsx +++ b/studio/frontend/src/features/chat/chat-settings-sheet.tsx @@ -413,15 +413,11 @@ export function ChatSettingsPanel({ }: ChatSettingsPanelProps) { const isMobile = useIsMobile(); const isGguf = useChatRuntimeStore((s) => s.activeGgufVariant) != null; - // For non-external (local) models we show every knob — providerCapabilities - // is only consulted when `isExternalModel` is true. An external model with an - // unknown provider falls back to the OpenAI-compat shape via - // getProviderCapabilities, so these flags never undercount support. - // GGUF (llama-server) honours frequency_penalty/seed/stop/parallel_tool_calls; - // the safetensors / HF transformers path does not, so we hide those toggles - // on local non-GGUF backends to keep the UI honest. Anything still set in - // params from a prior GGUF session is harmlessly ignored by the safetensors - // worker, so this is purely a presentation gate. + // Local models show every knob (providerCapabilities only consulted + // when isExternalModel; unknown providers fall back to OPENAI_COMPAT_BASE). + // GGUF llama-server honours frequency_penalty/seed/stop/parallel_tool_calls; + // HF transformers path doesn't, so hide on local non-GGUF (stale params + // are harmlessly ignored by the safetensors worker). const localSamplerSupportsExtras = !isExternalModel ? isGguf : true; const showTemperature = !isExternalModel || Boolean(providerCapabilities?.temperature); @@ -446,9 +442,7 @@ export function ChatSettingsPanel({ const showParallelToolCalls = isExternalModel ? Boolean(providerCapabilities?.parallelToolCalls) : localSamplerSupportsExtras; - // Extended llama.cpp / vLLM / OpenRouter samplers. Same gate as the - // core knobs above: external → providerCapabilities flag; local → - // GGUF-only (safetensors transformers ignores these). + // Extended samplers: external uses cap flag, local is GGUF-only. const capAdv = (k: keyof ProviderCapabilities): boolean => isExternalModel ? Boolean(providerCapabilities?.[k]) @@ -484,9 +478,8 @@ export function ChatSettingsPanel({ postSamplingProbs: capAdv("postSamplingProbs"), }; const showAdvancedSamplingSection = Object.values(advCaps).some(Boolean); - // Per-provider stop cap from provider-capabilities.ts; backend - // re-trims on the wire if a stale UI sends more than the upstream - // accepts. + // Per-provider stop cap; backend re-trims on the wire if a stale + // UI sends more than the upstream accepts. const stopMaxEntries = getProviderStopMax(externalProviderType); const serviceTierOptions = getServiceTierOptions(externalProviderType); const hasModelContent = @@ -1379,10 +1372,9 @@ export function ChatSettingsPanel({ Seed - Best-effort determinism seed. Same seed + prompt = - same output (approximately). OpenAI Chat Completions - and OpenAI-compat local backends honor it; OpenAI - Responses and Anthropic silently drop it. + Best-effort determinism. OpenAI Chat and OAI-compat + local backends honor it; OpenAI Responses and Anthropic + silently drop it. - Strings that halt generation as soon as the model - emits them. Enter a value and press Enter or comma - to commit a chip. Backend translates to - `stop_sequences` on Anthropic and `stop` on OpenAI - Chat (capped at 4 entries). + Strings that halt generation. Enter or comma to commit. + Maps to `stop_sequences` (Anthropic) / `stop` (OpenAI, + cap 4). - Provider routing tier. `auto` (default) lets the - provider choose. `flex` / `priority` / `scale` route - to higher-latency-tolerant or premium queues on - OpenAI; `standard_only` opts out of Anthropic's - priority tier. + Provider routing tier. `auto` = provider default. + OpenAI: flex / priority / scale. Anthropic: + `standard_only` opts out of Priority Tier.