From d8a4627355eb8b108e842534296b48c57c59a837 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Sun, 24 May 2026 13:44:38 +0000 Subject: [PATCH] Studio: widen scale type, preserve significant ws in stops, Kimi parity Round 2 reviewer feedback: - studio/frontend/src/features/chat/types/api.ts: `OpenAIChatCompletionsRequest.service_tier` did not include `"scale"`, so the request builder in chat-adapter.ts failed typecheck after the runtime ServiceTier union widened (`Type 'ServiceTier | undefined' is not assignable...`). Widen the type to match the SDK and keep the typecheck green. - studio/frontend/src/components/ui/stop-sequences-input.tsx: the chip editor used `draft.trim()` for storage, which silently mutated semantically meaningful stops like " End", "### ", and "\n\n". Keep the whitespace-only rejection (Anthropic 400s on those, OpenAI silently drops them) but persist the raw draft so leading/trailing whitespace inside otherwise-meaningful stops survives. - studio/backend/core/inference/external_provider.py: the Kimi web-search bypass dropped a single string `stop="\n\n"` via `stop.strip()` while the normal default OAI-compat path forwards it verbatim. Mirror the default path's behavior here so kimi-with-search and kimi-without-search apply the same rules (asymmetric provider-path fix flagged in round-2 review). --- studio/backend/core/inference/external_provider.py | 10 ++++++++-- .../src/components/ui/stop-sequences-input.tsx | 14 ++++++++++---- studio/frontend/src/features/chat/types/api.ts | 12 ++++++++++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/studio/backend/core/inference/external_provider.py b/studio/backend/core/inference/external_provider.py index 002e2548c6..d55f132487 100644 --- a/studio/backend/core/inference/external_provider.py +++ b/studio/backend/core/inference/external_provider.py @@ -912,9 +912,15 @@ class ExternalProviderClient: if seed is not None: body["seed"] = seed if stop: + # Mirror the default OAI-compat path's stop handling exactly + # so Kimi-with-search and Kimi-without-search apply the + # same rules — a single string is forwarded verbatim and + # lists are deduped + truncated to OpenAI's 4-entry cap. + # Earlier the bypass dropped whitespace-only strings here + # while the normal path forwarded them, which was an + # asymmetric provider-path fix. if isinstance(stop, str): - if stop.strip(): - body["stop"] = stop + body["stop"] = stop elif isinstance(stop, list): sequences = list( dict.fromkeys(s for s in stop if isinstance(s, str) and s) diff --git a/studio/frontend/src/components/ui/stop-sequences-input.tsx b/studio/frontend/src/components/ui/stop-sequences-input.tsx index e3c2d8a561..5d65fdd36a 100644 --- a/studio/frontend/src/components/ui/stop-sequences-input.tsx +++ b/studio/frontend/src/components/ui/stop-sequences-input.tsx @@ -38,14 +38,20 @@ export function StopSequencesInput({ const atCap = value.length >= maxEntries; function commitDraft() { - const trimmed = draft.trim(); - if (!trimmed) return; + // Reject chips that are empty or contain ONLY whitespace + // (Anthropic 400s on those and OpenAI silently drops them), but + // preserve significant leading/trailing whitespace inside otherwise + // -meaningful stops like " END", "### ", or "\n\n" — stop matching + // is exact, so stripping would silently change the semantics. The + // backend re-validates per-provider before the request hits the + // wire. + if (!draft || !draft.trim()) return; if (atCap) return; - if (value.includes(trimmed)) { + if (value.includes(draft)) { setDraft(""); return; } - onChange([...value, trimmed]); + onChange([...value, draft]); setDraft(""); } diff --git a/studio/frontend/src/features/chat/types/api.ts b/studio/frontend/src/features/chat/types/api.ts index e94f9dca7c..7c5b5bf99f 100644 --- a/studio/frontend/src/features/chat/types/api.ts +++ b/studio/frontend/src/features/chat/types/api.ts @@ -280,14 +280,22 @@ export interface OpenAIChatCompletionsRequest { stop?: string[]; /** * Provider service tier. Anthropic accepts `auto|standard_only`; - * OpenAI Chat accepts `auto|default|flex|priority|scale`; OpenAI - * Responses accepts `auto|default|flex|priority`. + * OpenAI Chat + Responses both accept + * `auto|default|flex|scale|priority` per the live `openai-python` + * SDK (`src/openai/types/responses/response_create_params.py` + * declares `Optional[Literal["auto", "default", "flex", "scale", + * "priority"]]`). The wire-side helper in + * `studio/backend/core/inference/external_provider.py` drops values + * that a given provider does not accept; this union stays permissive + * so the request-builder typechecks against + * `InferenceParams.serviceTier` without per-provider narrowing. */ service_tier?: | "auto" | "default" | "flex" | "priority" + | "scale" | "standard_only"; /** * Whether the provider may dispatch tool calls in parallel.