Fix Anthropic empty thinking signature replay

closes #4464
This commit is contained in:
Mario Zechner
2026-05-28 12:02:54 +02:00
parent 3e9f717445
commit 458a7bc27c
10 changed files with 252 additions and 18 deletions

View File

@@ -9307,7 +9307,7 @@ export const MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 204800,
contextWindow: 262144,
maxTokens: 8192,
} satisfies Model<"openai-completions">,
"minimax/minimax-m2.7": {
@@ -11915,13 +11915,13 @@ export const MODELS = {
reasoning: true,
input: ["text"],
cost: {
input: 0.06599999999999999,
output: 0.26,
cacheRead: 0.029,
input: 0.063,
output: 0.21,
cacheRead: 0.020999999999999998,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 262144,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"thedrummer/rocinante-12b": {
id: "thedrummer/rocinante-12b",

View File

@@ -177,6 +177,7 @@ function getAnthropicCompat(
sendSessionAffinityHeaders:
model.compat?.sendSessionAffinityHeaders ?? !!(isFireworks || isCloudflareAiGatewayAnthropic),
supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? !isFireworks,
allowEmptySignature: model.compat?.allowEmptySignature ?? false,
};
}
@@ -895,7 +896,13 @@ function buildParams(
const { cacheControl } = getCacheControl(model, options?.cacheRetention);
const params: MessageCreateParamsStreaming = {
model: model.id,
messages: convertMessages(context.messages, model, isOAuthToken, cacheControl),
messages: convertMessages(
context.messages,
model,
isOAuthToken,
cacheControl,
getAnthropicCompat(model).allowEmptySignature,
),
max_tokens: options?.maxTokens ?? model.maxTokens,
stream: true,
};
@@ -1001,6 +1008,7 @@ function convertMessages(
model: Model<"anthropic-messages">,
isOAuthToken: boolean,
cacheControl?: CacheControlEphemeral,
allowEmptySignature = false,
): MessageParam[] {
const params: MessageParam[] = [];
@@ -1069,13 +1077,21 @@ function convertMessages(
}
if (block.thinking.trim().length === 0) continue;
// If thinking signature is missing/empty (e.g., from aborted stream),
// convert to plain text block without <thinking> tags to avoid API rejection
// and prevent Claude from mimicking the tags in responses
// convert to plain text for Anthropic. Some compatible providers emit
// and accept empty signatures, so let marked models preserve the block.
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
blocks.push({
type: "text",
text: sanitizeSurrogates(block.thinking),
});
blocks.push(
allowEmptySignature
? {
type: "thinking",
thinking: sanitizeSurrogates(block.thinking),
signature: "",
}
: {
type: "text",
text: sanitizeSurrogates(block.thinking),
},
);
} else {
blocks.push({
type: "thinking",

View File

@@ -451,6 +451,8 @@ export interface AnthropicMessagesCompat {
* Default: false.
*/
forceAdaptiveThinking?: boolean;
/** Whether to replay empty thinking signatures as `signature: ""` instead of converting thinking to text. Default: false. */
allowEmptySignature?: boolean;
}
/**