feat: stream reasoning summary deltas as reasoning_content for Muse Spark 1.3

This commit is contained in:
John Smith committed 2026-10-07 00:15:41 -04:00
1 parent c138a9c8f3
commit 799dcfc316
3 files changed
+121 -9

No files matched your search

+49 -6
View File
@@ -123,12 +123,17 @@ export function chatCompletionsToResponsesBody(body) {
if (typeof max_output_tokens === "number") responsesPayload.max_output_tokens = max_output_tokens;
else if (typeof max_tokens === "number") responsesPayload.max_output_tokens = max_tokens;
// Translate reasoning_effort (OpenAI Chat Completions) to reasoning: { effort } (Responses API)
// Translate reasoning_effort (OpenAI Chat Completions) to reasoning: { effort, summary } (Responses API)
const incomingEffort = body.reasoning_effort || (typeof body.reasoning === "object" ? body.reasoning?.effort : undefined);
if (incomingEffort && incomingEffort !== "auto" && incomingEffort !== "none") {
responsesPayload.reasoning = {
effort: incomingEffort
if (incomingEffort !== "none") {
const incomingSummary = typeof body.reasoning === "object" && body.reasoning?.summary ? body.reasoning.summary : "detailed";
const reasoningObj = {
summary: incomingSummary
};
if (incomingEffort && incomingEffort !== "auto") {
reasoningObj.effort = incomingEffort;
}
responsesPayload.reasoning = reasoningObj;
}
if (Array.isArray(tools)) {
@@ -171,11 +176,23 @@ export function responsesToChatCompletionsResponse(responsesData, requestedModel
const model = responsesData.model || requestedModel;
let textContent = "";
let reasoningContent = "";
const toolCalls = [];
const output = Array.isArray(responsesData.output) ? responsesData.output : [];
for (const item of output) {
if (item.type === "message") {
if (item.type === "reasoning") {
if (Array.isArray(item.summary)) {
const parts = item.summary.map(s => s.text || s.content || (typeof s === "string" ? s : "")).filter(Boolean);
if (parts.length > 0) {
reasoningContent = parts.join("\n\n");
}
} else if (typeof item.summary === "string") {
reasoningContent = item.summary;
} else if (typeof item.content === "string") {
reasoningContent = item.content;
}
} else if (item.type === "message") {
if (Array.isArray(item.content)) {
for (const part of item.content) {
if ((part.type === "output_text" || part.type === "text") && typeof part.text === "string") {
@@ -205,7 +222,8 @@ export function responsesToChatCompletionsResponse(responsesData, requestedModel
const message = {
role: "assistant",
content: textContent || (toolCalls.length ? null : "")
content: textContent || (toolCalls.length ? null : ""),
...(reasoningContent ? { reasoning_content: reasoningContent } : {})
};
if (toolCalls.length > 0) {
message.tool_calls = toolCalls;
@@ -280,6 +298,31 @@ export function translateResponsesEventToOpenAiChunk(eventString, state = { id:
choices: []
};
// Reasoning Summary / Reasoning Text Delta
if (
eventType === "response.reasoning_summary_text.delta" ||
eventType === "response.reasoning_text.delta" ||
data.type === "response.reasoning_summary_text.delta" ||
data.type === "response.reasoning_text.delta"
) {
let reasoningText = data.delta ?? data.text ?? data.reasoning_delta ?? "";
if (reasoningText) {
state.hasEmittedReasoning = true;
if (typeof data.summary_index === "number") {
if (state.lastSummaryIndex !== undefined && state.lastSummaryIndex !== data.summary_index) {
reasoningText = "\n\n" + reasoningText;
}
state.lastSummaryIndex = data.summary_index;
}
baseChunk.choices.push({
index: 0,
delta: { reasoning_content: reasoningText },
finish_reason: null
});
return `data: ${JSON.stringify(baseChunk)}\n\n`;
}
}
// Text Delta
if (
eventType === "response.output_text.delta" ||
+55
View File
@@ -83,10 +83,65 @@ server.listen(TEST_PORT, "127.0.0.1", async () => {
assert.equal(effortRes.status, 200, `Muse Spark with reasoning_effort should be 200 (was ${effortRes.status})`);
const effortJson = await effortRes.json();
const effortReply = effortJson.choices?.[0]?.message?.content?.trim();
const effortReasoning = effortJson.choices?.[0]?.message?.reasoning_content;
const effortDuration = ((Date.now() - effortStart) / 1000).toFixed(2);
console.log(`✔ Muse Spark with reasoning_effort='high' responded in ${effortDuration}s:`);
console.log(` Reasoning: "${effortReasoning ? effortReasoning.slice(0, 80) + '...' : '(none)'}"`);
console.log(` Output: "${effortReply}"`);
assert.ok(effortReply && /yes/i.test(effortReply), "Muse Spark should answer yes for prime 97");
assert.ok(effortReasoning && effortReasoning.length > 0, "Non-streaming message should include reasoning_content");
// 4. Verify Live Streaming Reasoning Tokens with Muse Spark 1.3 Contributor
console.log("\n[4/4] Verifying Live Streaming Reasoning Chunks (Muse Spark 1.3)...");
const streamStart = Date.now();
const streamRes = await fetch(`${baseUrl}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "muse-spark-1.3-contributor",
messages: [{ role: "user", content: "Which is bigger: 9.11 or 9.9?" }],
reasoning_effort: "high",
stream: true
})
});
assert.equal(streamRes.status, 200);
const reader = streamRes.body.getReader();
const decoder = new TextDecoder();
let streamBuf = "";
let reasoningTokens = "";
let contentTokens = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
streamBuf += decoder.decode(value, { stream: true });
const parts = streamBuf.split("\n\n");
streamBuf = parts.pop() || "";
for (const part of parts) {
for (const line of part.split("\n")) {
if (line.startsWith("data: ") && !line.includes("[DONE]")) {
try {
const parsed = JSON.parse(line.slice(6));
const delta = parsed.choices?.[0]?.delta;
if (delta?.reasoning_content) {
reasoningTokens += delta.reasoning_content;
}
if (delta?.content) {
contentTokens += delta.content;
}
} catch {}
}
}
}
}
const streamDuration = ((Date.now() - streamStart) / 1000).toFixed(2);
console.log(`✔ Stream completed in ${streamDuration}s:`);
console.log(` Streamed Reasoning: "${reasoningTokens.slice(0, 100)}..." (${reasoningTokens.length} chars)`);
console.log(` Streamed Content: "${contentTokens.slice(0, 100)}..." (${contentTokens.length} chars)`);
assert.ok(reasoningTokens.length > 0, "Should receive streaming reasoning_content tokens");
assert.ok(contentTokens.length > 0, "Should receive streaming content tokens");
console.log("\n==================================================");
console.log("✔ LIVE VERIFICATION PASSED FOR ALL MODELS & REASONING!");
+17 -3
View File
@@ -43,7 +43,7 @@ const effortHighReq = {
reasoning_effort: "high"
};
const effortHighPayload = chatCompletionsToResponsesBody(effortHighReq);
assert.deepEqual(effortHighPayload.reasoning, { effort: "high" });
assert.deepEqual(effortHighPayload.reasoning, { effort: "high", summary: "detailed" });
const effortXHighReq = {
model: "muse-spark-1.3-contributor",
@@ -51,7 +51,7 @@ const effortXHighReq = {
reasoning_effort: "xhigh"
};
const effortXHighPayload = chatCompletionsToResponsesBody(effortXHighReq);
assert.deepEqual(effortXHighPayload.reasoning, { effort: "xhigh" });
assert.deepEqual(effortXHighPayload.reasoning, { effort: "xhigh", summary: "detailed" });
const effortNoneReq = {
model: "muse-spark-1.3-contributor",
@@ -67,13 +67,17 @@ const effortNestedReq = {
reasoning: { effort: "low" }
};
const effortNestedPayload = chatCompletionsToResponsesBody(effortNestedReq);
assert.deepEqual(effortNestedPayload.reasoning, { effort: "low" });
assert.deepEqual(effortNestedPayload.reasoning, { effort: "low", summary: "detailed" });
// 3. Responses API non-streaming JSON -> Chat Completions JSON
const rawResponsesResp = {
id: "resp_1234567890",
model: "muse-spark-1.3-contributor",
output: [
{
type: "reasoning",
summary: [{ type: "summary_text", text: "Reasoning step 1" }, { type: "summary_text", text: "Reasoning step 2" }]
},
{
type: "message",
role: "assistant",
@@ -90,6 +94,7 @@ const rawResponsesResp = {
const chatResp = responsesToChatCompletionsResponse(rawResponsesResp, "muse-spark-1.3-contributor");
assert.equal(chatResp.id, "chatcmpl_1234567890");
assert.equal(chatResp.choices[0].message.content, "Hello from Responses API!");
assert.equal(chatResp.choices[0].message.reasoning_content, "Reasoning step 1\n\nReasoning step 2");
assert.equal(chatResp.choices[0].finish_reason, "stop");
assert.equal(chatResp.usage.prompt_tokens, 15);
assert.equal(chatResp.usage.completion_tokens, 8);
@@ -97,6 +102,15 @@ assert.equal(chatResp.usage.total_tokens, 23);
// 4. SSE translation
const state = { id: "", model: "muse-spark-1.3-contributor" };
// 4a. Reasoning summary delta SSE
const reasoningChunkRaw = `event: response.reasoning_summary_text.delta\ndata: {"type":"response.reasoning_summary_text.delta","delta":"Thinking through the problem..."}\n\n`;
const reasoningChunkOpenAi = translateResponsesEventToOpenAiChunk(reasoningChunkRaw, state);
assert.ok(reasoningChunkOpenAi.startsWith("data: "));
const parsedReasoningChunk = JSON.parse(reasoningChunkOpenAi.replace("data: ", "").trim());
assert.equal(parsedReasoningChunk.choices[0].delta.reasoning_content, "Thinking through the problem...");
// 4b. Text delta SSE
const textChunkRaw = `event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"Hello "}\n\n`;
const textChunkOpenAi = translateResponsesEventToOpenAiChunk(textChunkRaw, state);
assert.ok(textChunkOpenAi.startsWith("data: "));