






















@@ -58,8 +58,12 @@ const LIVE_TEST_TIMEOUT_MS = Math.max(
5858toInt(process.env.OPENCLAW_LIVE_TEST_TIMEOUT_MS, 60 * 60 * 1000),
5959);
6060const DEFAULT_LIVE_MODEL_CONCURRENCY = 20;
61-const LIVE_MODEL_CONCURRENCY = resolveLiveModelConcurrency();
62-const LIVE_MODELS_JSON_TIMEOUT_MS = resolveLiveModelsJsonTimeoutMs();
61+const LIVE_MODEL_CONCURRENCY = resolveLiveModelConcurrency(
62+process.env.OPENCLAW_LIVE_MODEL_CONCURRENCY,
63+);
64+const LIVE_MODELS_JSON_TIMEOUT_MS = resolveLiveModelsJsonTimeoutMs(
65+process.env.OPENCLAW_LIVE_MODELS_JSON_TIMEOUT_MS,
66+);
6367const LIVE_FILE_PROBE_ENABLED = isLiveModelProbeEnabled(process.env, LIVE_MODEL_FILE_PROBE_ENV);
6468const LIVE_IMAGE_PROBE_ENABLED = isLiveModelProbeEnabled(process.env, LIVE_MODEL_IMAGE_PROBE_ENV);
6569@@ -318,13 +322,13 @@ function toInt(value: string | undefined, fallback: number): number {
318322return Number.isFinite(parsed) ? parsed : fallback;
319323}
320324321-function resolveLiveModelConcurrency(raw = process.env.OPENCLAW_LIVE_MODEL_CONCURRENCY): number {
325+function resolveLiveModelConcurrency(raw?: string): number {
322326return Math.max(1, toInt(raw, DEFAULT_LIVE_MODEL_CONCURRENCY));
323327}
324328325329describe("resolveLiveModelConcurrency", () => {
326330it("defaults direct-model probes to 20-way concurrency", () => {
327-expect(resolveLiveModelConcurrency(undefined)).toBe(20);
331+expect(resolveLiveModelConcurrency()).toBe(20);
328332});
329333330334it("accepts explicit concurrency overrides", () => {
@@ -334,7 +338,7 @@ describe("resolveLiveModelConcurrency", () => {
334338});
335339336340function resolveLiveModelsJsonTimeoutMs(
337-modelsJsonTimeoutRaw = process.env.OPENCLAW_LIVE_MODELS_JSON_TIMEOUT_MS,
341+modelsJsonTimeoutRaw?: string,
338342setupTimeoutMs = LIVE_SETUP_TIMEOUT_MS,
339343): number {
340344return Math.max(setupTimeoutMs, toInt(modelsJsonTimeoutRaw, 120_000));
@@ -491,6 +495,102 @@ async function completeOkWithRetry(params: {
491495return await runOnce(256);
492496}
493497498+function isDeepSeekV4Model(model: Pick<Model<Api>, "id" | "provider">): boolean {
499+return (
500+model.provider === "deepseek" &&
501+(model.id === "deepseek-v4-flash" || model.id === "deepseek-v4-pro")
502+);
503+}
504+505+async function runDeepSeekV4ReplayRegression(params: {
506+model: Model<Api>;
507+apiKey: string;
508+timeoutMs: number;
509+progressLabel: string;
510+}) {
511+const noopTool = {
512+name: "noop",
513+description: "Return ok.",
514+parameters: Type.Object({}, { additionalProperties: false }),
515+};
516+let firstUser = {
517+role: "user" as const,
518+content: "Call the tool `noop` with {}. Do not write any other text.",
519+timestamp: Date.now(),
520+};
521+let first = await completeSimpleWithTimeout(
522+params.model,
523+{ messages: [firstUser], tools: [noopTool] },
524+{
525+apiKey: params.apiKey,
526+reasoning: resolveTestReasoning(params.model),
527+maxTokens: 256,
528+},
529+params.timeoutMs,
530+`${params.progressLabel}: DeepSeek V4 replay first call`,
531+);
532+let toolCall = first.content.find((block) => block.type === "toolCall");
533+534+for (let i = 0; i < 2 && !toolCall; i += 1) {
535+firstUser = {
536+role: "user" as const,
537+content: "Call the tool `noop` with {}. IMPORTANT: respond with the tool call.",
538+timestamp: Date.now(),
539+};
540+first = await completeSimpleWithTimeout(
541+params.model,
542+{ messages: [firstUser], tools: [noopTool] },
543+{
544+apiKey: params.apiKey,
545+reasoning: resolveTestReasoning(params.model),
546+maxTokens: 256,
547+},
548+params.timeoutMs,
549+`${params.progressLabel}: DeepSeek V4 replay retry ${i + 1}`,
550+);
551+toolCall = first.content.find((block) => block.type === "toolCall");
552+}
553+554+expect(toolCall).toBeTruthy();
555+if (!toolCall || toolCall.type !== "toolCall") {
556+throw new Error("expected DeepSeek V4 tool call");
557+}
558+559+const second = await completeSimpleWithTimeout(
560+params.model,
561+{
562+messages: [
563+firstUser,
564+first,
565+{
566+role: "toolResult",
567+toolCallId: toolCall.id,
568+toolName: "noop",
569+content: [{ type: "text", text: "ok" }],
570+isError: false,
571+timestamp: Date.now(),
572+},
573+{
574+role: "user",
575+content: "Reply with the word ok.",
576+timestamp: Date.now(),
577+},
578+],
579+},
580+{
581+apiKey: params.apiKey,
582+reasoning: resolveTestReasoning(params.model),
583+maxTokens: 256,
584+},
585+params.timeoutMs,
586+`${params.progressLabel}: DeepSeek V4 replay followup`,
587+);
588+if (second.stopReason === "error") {
589+throw new Error(second.errorMessage || "DeepSeek V4 replay followup returned error");
590+}
591+expect(extractAssistantText(second).length).toBeGreaterThan(0);
592+}
593+494594async function runExtraTurnProbes(params: {
495595model: Model<Api>;
496596apiKey: string;
@@ -849,6 +949,24 @@ describeLive("live models (profile keys)", () => {
849949break;
850950}
851951952+if (isDeepSeekV4Model(model)) {
953+logProgress(`${progressLabel}: DeepSeek V4 replay regression`);
954+await runDeepSeekV4ReplayRegression({
955+ model,
956+ apiKey,
957+timeoutMs: perModelTimeoutMs,
958+ progressLabel,
959+});
960+await runExtraTurnProbes({
961+ model,
962+ apiKey,
963+timeoutMs: perModelTimeoutMs,
964+ progressLabel,
965+});
966+logProgress(`${progressLabel}: done`);
967+break;
968+}
969+852970logProgress(`${progressLabel}: prompt`);
853971const ok = await completeOkWithRetry({
854972 model,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。