77
web/test/text-reasoning-stream.test.ts
Normal file
77
web/test/text-reasoning-stream.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test";
|
||||
import React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { GenerationToolCard } from "../src/components/ai/generation-tool-card";
|
||||
import type { ModelProtocol } from "../src/lib/model-protocols";
|
||||
import { requestImageQuestion } from "../src/services/api/image";
|
||||
import { createModelChannel, defaultConfig, type AiConfig } from "../src/stores/use-config-store";
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
function reasoningConfig(protocol: ModelProtocol | undefined, apiFormat: "openai" | "gemini" = "openai"): AiConfig {
|
||||
const channel = createModelChannel({
|
||||
id: "reasoning",
|
||||
name: "Reasoning",
|
||||
baseUrl: "https://reasoning.example/v1",
|
||||
apiKey: "synthetic-test-key",
|
||||
apiFormat,
|
||||
interfaceType: apiFormat === "gemini" ? undefined : protocol,
|
||||
models: ["reasoner"],
|
||||
modelCosts: [{ model: "reasoner", capability: "text", ...(protocol ? { protocol } : {}), billingMode: "fixed_request", unitPriceMicrocredits: 0 }],
|
||||
});
|
||||
return { ...defaultConfig, channels: [channel], model: "reasoning::reasoner", textModel: "reasoning::reasoner" };
|
||||
}
|
||||
|
||||
function streamResponse(events: unknown[]) {
|
||||
const body = `${events.map((event) => `data: ${JSON.stringify(event)}`).join("\n\n")}\n\n`;
|
||||
return new Response(body, { headers: { "Content-Type": "text/event-stream" } });
|
||||
}
|
||||
|
||||
async function requestWithReasoning(config: AiConfig, events: unknown[]) {
|
||||
globalThis.fetch = (async () => streamResponse(events)) as typeof fetch;
|
||||
let content = "";
|
||||
let reasoning = "";
|
||||
const result = await requestImageQuestion(config, [{ role: "user", content: "请回答" }], (value) => { content = value; }, { onReasoning: (value) => { reasoning = value; } });
|
||||
return { result, content, reasoning };
|
||||
}
|
||||
|
||||
describe("text reasoning streams", () => {
|
||||
test("separates Responses reasoning summaries from answer text", async () => {
|
||||
const output = await requestWithReasoning(reasoningConfig("openai-response"), [
|
||||
{ type: "response.reasoning_summary_text.delta", delta: "先分析" },
|
||||
{ type: "response.output_text.delta", delta: "最终答案" },
|
||||
]);
|
||||
|
||||
expect(output).toEqual({ result: "最终答案", content: "最终答案", reasoning: "先分析" });
|
||||
});
|
||||
|
||||
test("separates Chat Completions reasoning_content from answer text", async () => {
|
||||
const output = await requestWithReasoning(reasoningConfig("chat-completion"), [
|
||||
{ choices: [{ delta: { reasoning_content: "推理过程" } }] },
|
||||
{ choices: [{ delta: { content: "回答内容" } }] },
|
||||
]);
|
||||
|
||||
expect(output).toEqual({ result: "回答内容", content: "回答内容", reasoning: "推理过程" });
|
||||
});
|
||||
|
||||
test("does not duplicate Gemini thought parts into visible content", async () => {
|
||||
const output = await requestWithReasoning(reasoningConfig(undefined, "gemini"), [
|
||||
{ candidates: [{ content: { parts: [{ text: "内部思考", thought: true }, { text: "可见回答" }] } }] },
|
||||
]);
|
||||
|
||||
expect(output).toEqual({ result: "可见回答", content: "可见回答", reasoning: "内部思考" });
|
||||
});
|
||||
|
||||
test("keeps single results open and collapses completed bulk results on restore", () => {
|
||||
const single = renderToStaticMarkup(React.createElement(GenerationToolCard, { status: "completed", heading: "图像生成" }, React.createElement("span", null, "单图结果")));
|
||||
const bulk = renderToStaticMarkup(React.createElement(GenerationToolCard, { status: "completed", isBulk: true, heading: "图像生成" }, React.createElement("span", null, "批量结果")));
|
||||
|
||||
expect(single).toContain("单图结果");
|
||||
expect(bulk).not.toContain("批量结果");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user