mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 19:04:45 +00:00
fix(agent): retry empty anthropic-compatible replies
This commit is contained in:
@@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents: retry empty final turns for generic `anthropic-messages` providers instead of limiting non-visible recovery to Kimi, so custom/proxied Anthropic-compatible routes can recover with a visible answer. Addresses #46080. Thanks @wmgx, @w1tv, and @iFwu.
|
||||
- Control UI: rotate browser service-worker caches per build so updated Gateways are less likely to keep serving stale dashboard bundles that trigger protocol mismatch errors.
|
||||
- Discord: report unresolved configured bot-token SecretRefs during startup instead of treating the account as unconfigured. (#82009) Thanks @giodl73-repo.
|
||||
- CLI/config: preserve numeric-looking object keys such as Discord guild IDs during `config patch` recursive merges. (#81999) Thanks @giodl73-repo.
|
||||
|
||||
@@ -817,6 +817,75 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
|
||||
expectWarnMessageWith("empty response detected");
|
||||
});
|
||||
|
||||
it("retries empty Anthropic-compatible stop turns even when the provider is not Kimi", async () => {
|
||||
mockedClassifyFailoverReason.mockReturnValue(null);
|
||||
mockedResolveModelAsync.mockResolvedValue({
|
||||
model: {
|
||||
id: "claude-opus-4-7",
|
||||
provider: "sub2api",
|
||||
contextWindow: 200000,
|
||||
api: "anthropic-messages",
|
||||
},
|
||||
error: null,
|
||||
authStorage: {
|
||||
setRuntimeApiKey: vi.fn(),
|
||||
},
|
||||
modelRegistry: {},
|
||||
});
|
||||
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
||||
makeAttemptResult({
|
||||
assistantTexts: [],
|
||||
lastAssistant: {
|
||||
role: "assistant",
|
||||
api: "anthropic-messages",
|
||||
stopReason: "stop",
|
||||
provider: "sub2api",
|
||||
model: "claude-opus-4-7",
|
||||
content: [],
|
||||
usage: {
|
||||
input: 2048,
|
||||
output: 3100,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 5148,
|
||||
},
|
||||
} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
|
||||
}),
|
||||
);
|
||||
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
||||
makeAttemptResult({
|
||||
assistantTexts: ["Visible Anthropic-compatible answer."],
|
||||
lastAssistant: {
|
||||
role: "assistant",
|
||||
api: "anthropic-messages",
|
||||
stopReason: "stop",
|
||||
provider: "sub2api",
|
||||
model: "claude-opus-4-7",
|
||||
content: [{ type: "text", text: "Visible Anthropic-compatible answer." }],
|
||||
usage: {
|
||||
input: 2300,
|
||||
output: 8,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 2308,
|
||||
},
|
||||
} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
|
||||
}),
|
||||
);
|
||||
|
||||
await runEmbeddedPiAgent({
|
||||
...overflowBaseRunParams,
|
||||
provider: "sub2api",
|
||||
model: "claude-opus-4-7",
|
||||
runId: "run-empty-anthropic-compatible-stop-continuation",
|
||||
});
|
||||
|
||||
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
||||
const secondCall = runAttemptCall(1);
|
||||
expect(secondCall.prompt).toContain(EMPTY_RESPONSE_RETRY_INSTRUCTION);
|
||||
expectWarnMessageWith("empty response detected");
|
||||
});
|
||||
|
||||
it("surfaces an error after exhausting empty-response retries", async () => {
|
||||
mockedClassifyFailoverReason.mockReturnValue(null);
|
||||
mockedRunEmbeddedAttempt.mockResolvedValue(
|
||||
|
||||
@@ -131,7 +131,6 @@ const GEMINI_INCOMPLETE_TURN_MODEL_ID_PATTERN = /^gemini(?:[.-]|$)/;
|
||||
// Ollama native `/api/chat` can finish with only thinking/internal blocks when
|
||||
// constrained, but it should not inherit the stricter planning-only/ack prompts.
|
||||
const OLLAMA_INCOMPLETE_TURN_PROVIDER_ID_PATTERN = /^ollama(?:-|$)/;
|
||||
const KIMI_INCOMPLETE_TURN_PROVIDER_ID_PATTERN = /^kimi(?:-|$)/;
|
||||
const DEFAULT_PLANNING_ONLY_RETRY_LIMIT = 1;
|
||||
const STRICT_AGENTIC_PLANNING_ONLY_RETRY_LIMIT = 2;
|
||||
// Allow one immediate continuation plus one follow-up continuation before
|
||||
@@ -620,14 +619,9 @@ function shouldApplyNonVisibleTurnRetryGuard(params: {
|
||||
if (shouldApplyPlanningOnlyRetryGuard(params)) {
|
||||
return true;
|
||||
}
|
||||
if (normalizeLowercaseStringOrEmpty(params.modelApi ?? "") === "openai-completions") {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
normalizeLowercaseStringOrEmpty(params.modelApi ?? "") === "anthropic-messages" &&
|
||||
KIMI_INCOMPLETE_TURN_PROVIDER_ID_PATTERN.test(
|
||||
normalizeLowercaseStringOrEmpty(params.provider ?? ""),
|
||||
)
|
||||
normalizeLowercaseStringOrEmpty(params.modelApi ?? "") === "openai-completions" ||
|
||||
normalizeLowercaseStringOrEmpty(params.modelApi ?? "") === "anthropic-messages"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user