mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:30:43 +00:00
fix(deepseek): backfill v4 assistant reasoning replay
This commit is contained in:
@@ -176,4 +176,59 @@ describeLive("deepseek plugin live", () => {
|
||||
});
|
||||
expect(extractNonEmptyAssistantText(result.content).length).toBeGreaterThan(0);
|
||||
}, 60_000);
|
||||
|
||||
it("accepts V4 thinking replay after a prior plain assistant message", async () => {
|
||||
const context: Context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Say hello.",
|
||||
timestamp: Date.now() - 2,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
api: "openai-completions",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
content: [{ type: "text", text: "Hello." }],
|
||||
usage: ZERO_USAGE,
|
||||
stopReason: "stop",
|
||||
timestamp: Date.now() - 1,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Reply with exactly: ok",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
};
|
||||
let capturedPayload: Record<string, unknown> | undefined;
|
||||
const streamFn = createDeepSeekV4ThinkingWrapper(streamSimple, "high");
|
||||
expect(streamFn).toBeDefined();
|
||||
|
||||
const stream = streamFn?.(resolveDeepSeekV4LiveModel(), context, {
|
||||
apiKey: DEEPSEEK_KEY,
|
||||
maxTokens: 64,
|
||||
onPayload: (payload) => {
|
||||
capturedPayload = payload as Record<string, unknown>;
|
||||
},
|
||||
});
|
||||
expect(stream).toBeDefined();
|
||||
|
||||
const result = await (await stream!).result();
|
||||
if (result.stopReason === "error") {
|
||||
throw new Error(
|
||||
result.errorMessage || "DeepSeek V4 plain replay returned error with no message",
|
||||
);
|
||||
}
|
||||
|
||||
const messages = capturedPayload?.messages;
|
||||
expect(Array.isArray(messages)).toBe(true);
|
||||
expect((messages as Array<Record<string, unknown>>)[1]).toMatchObject({
|
||||
role: "assistant",
|
||||
content: "Hello.",
|
||||
reasoning_content: "",
|
||||
});
|
||||
expect(extractNonEmptyAssistantText(result.content).length).toBeGreaterThan(0);
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
@@ -309,6 +309,73 @@ describe("deepseek provider plugin", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("adds blank reasoning_content for replayed plain assistant messages", async () => {
|
||||
let capturedPayload: Record<string, unknown> | undefined;
|
||||
const model = {
|
||||
provider: "deepseek",
|
||||
id: "deepseek-v4-pro",
|
||||
name: "DeepSeek V4 Pro",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.deepseek.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 384_000,
|
||||
compat: {
|
||||
supportsUsageInStreaming: true,
|
||||
supportsReasoningEffort: true,
|
||||
maxTokensField: "max_tokens",
|
||||
},
|
||||
} as Model<"openai-completions">;
|
||||
const context = {
|
||||
messages: [
|
||||
{ role: "user", content: "hi", timestamp: 1 },
|
||||
{
|
||||
role: "assistant",
|
||||
api: "openai-completions",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
content: [{ type: "text", text: "Hello." }],
|
||||
usage: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
},
|
||||
stopReason: "stop",
|
||||
timestamp: 2,
|
||||
},
|
||||
{ role: "user", content: "next", timestamp: 3 },
|
||||
],
|
||||
} as Context;
|
||||
const baseStreamFn = (
|
||||
streamModel: Model<"openai-completions">,
|
||||
streamContext: Context,
|
||||
options?: { onPayload?: (payload: unknown, model: unknown) => unknown },
|
||||
) => {
|
||||
capturedPayload = buildOpenAICompletionsParams(streamModel, streamContext, {
|
||||
reasoning: "high",
|
||||
} as never);
|
||||
options?.onPayload?.(capturedPayload, streamModel);
|
||||
const stream = createAssistantMessageEventStream();
|
||||
queueMicrotask(() => stream.end());
|
||||
return stream;
|
||||
};
|
||||
|
||||
const wrapThinkingHigh = createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "high");
|
||||
expect(wrapThinkingHigh).toBeDefined();
|
||||
await wrapThinkingHigh?.(model, context, {});
|
||||
|
||||
expect((capturedPayload?.messages as Array<Record<string, unknown>>)[1]).toMatchObject({
|
||||
role: "assistant",
|
||||
content: "Hello.",
|
||||
reasoning_content: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("strips replayed reasoning_content when DeepSeek V4 thinking is disabled", async () => {
|
||||
let capturedPayload: Record<string, unknown> | undefined;
|
||||
const model = {
|
||||
|
||||
@@ -55,6 +55,7 @@ describe("venice provider plugin", () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{ role: "assistant", content: "done" },
|
||||
],
|
||||
};
|
||||
(options as { onPayload?: (payload: Record<string, unknown>) => void })?.onPayload?.(payload);
|
||||
@@ -87,6 +88,11 @@ describe("venice provider plugin", () => {
|
||||
],
|
||||
reasoning_content: "",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "done",
|
||||
reasoning_content: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -18,7 +18,7 @@ function ensureVeniceDeepSeekV4Replay(payload: Record<string, unknown>): void {
|
||||
continue;
|
||||
}
|
||||
const record = message as Record<string, unknown>;
|
||||
if (record.role === "assistant" && Array.isArray(record.tool_calls)) {
|
||||
if (record.role === "assistant") {
|
||||
record.reasoning_content ??= "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user