Fix Studio auto-titles for reasoning models (#7098)

This commit is contained in:
Long Yixing 2026-07-14 02:13:50 +08:00 committed by GitHub
commit a337c72753
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 6 deletions

View file

@ -77,6 +77,7 @@ const pendingRunStartReadyByMessageId = new Map<string, Promise<void>>();
type TitleResponse = {
choices?: Array<{
finish_reason?: string | null;
message?: {
content?: string;
};
@ -474,6 +475,8 @@ async function generateTitleWithModel(payload: {
max_tokens: 24,
top_k: 20,
repetition_penalty: 1.0,
enable_thinking: false,
reasoning_effort: "none",
messages: [
{
role: "system",
@ -489,8 +492,10 @@ async function generateTitleWithModel(payload: {
.json()
.catch(() => null)) as TitleResponse | null;
if (!response.ok) return null;
const raw: string | undefined = body?.choices?.[0]?.message?.content;
if (!raw) return null;
const choice = body?.choices?.[0];
if (choice?.finish_reason === "length") return null;
const raw: string | undefined = choice?.message?.content;
if (!raw || /<\/?think>/i.test(raw)) return null;
return normalizeTitle(raw);
}

View file

@ -65,6 +65,8 @@ def test_title_model_payload_includes_optional_assistant_reply():
assert "if (assistant)" in block
assert "parts.push(`Assistant: ${assistant}`);" in block
assert 'parts.join("\\n")' in block
assert "enable_thinking: false" in block
assert 'reasoning_effort: "none"' in block
def test_generate_title_passes_first_assistant_reply_after_first_user():
@ -81,6 +83,25 @@ def test_generate_title_passes_first_assistant_reply_after_first_user():
assert "assistantText," in block
def test_tool_call_only_first_assistant_still_uses_first_user_message():
source = RUNTIME_TSX.read_text()
extract_block = " ".join(_balanced_block(source, "function extractTextParts").split())
generate_block = " ".join(_balanced_block(source, "async generateTitle(remoteId").split())
assert (
'.filter((p): p is Extract<typeof p, { type: "text" }> => p.type === "text")'
in extract_block
)
assert (
"const userText = extractTextParts(firstUser) || defaultTitle; const assistantText = extractTextParts(firstAssistant);"
in generate_block
)
assert (
"(await generateTitleWithModel({ userText, assistantText, })) || fallbackTitleFromUserText(userText);"
in generate_block
)
def test_auto_title_disabled_uses_deterministic_user_text_fallback():
block = _balanced_block(
RUNTIME_TSX.read_text(),
@ -93,12 +114,18 @@ def test_auto_title_disabled_uses_deterministic_user_text_fallback():
def test_model_failure_still_falls_back_to_user_text():
block = _balanced_block(
RUNTIME_TSX.read_text(),
"async generateTitle(remoteId",
source = RUNTIME_TSX.read_text()
model_block = _source_until(
source,
"async function generateTitleWithModel",
"\nconst inflightTitleByKey",
)
generate_block = _balanced_block(source, "async generateTitle(remoteId")
assert "})) || fallbackTitleFromUserText(userText);" in block
assert "finish_reason?: string | null;" in source
assert 'if (choice?.finish_reason === "length") return null;' in model_block
assert r"if (!raw || /<\/?think>/i.test(raw)) return null;" in model_block
assert "})) || fallbackTitleFromUserText(userText);" in generate_block
def test_title_normalizer_still_enforces_output_constraints():
@ -113,3 +140,4 @@ def test_title_normalizer_still_enforces_output_constraints():
assert 'replace(/[.!?:;,]+/g, " ")' in block
assert 'title.split(" ").filter(Boolean).slice(0, 6)' in block
assert "joined.length > 60" in block
assert "return normalizeTitle(raw);" in block