mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-06 01:55:37 +00:00
fix(bedrock): preserve Mythos Preview thinking policy
This commit is contained in:
@@ -166,6 +166,70 @@ describe("bedrock discovery", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("marks known Fable inference profile fallbacks as reasoning capable", async () => {
|
||||
sendMock
|
||||
.mockResolvedValueOnce({
|
||||
modelSummaries: [],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
inferenceProfileSummaries: [
|
||||
{
|
||||
inferenceProfileId: "us.anthropic.claude-fable-5",
|
||||
inferenceProfileName: "US Claude Fable 5",
|
||||
status: "ACTIVE",
|
||||
type: "SYSTEM_DEFINED",
|
||||
models: [
|
||||
{
|
||||
modelArn: "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-fable-5",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const models = await discoverBedrockModels({ region: "us-east-1", clientFactory });
|
||||
|
||||
expect(models).toHaveLength(1);
|
||||
expectModelFields(models[0], {
|
||||
id: "us.anthropic.claude-fable-5",
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
thinkingLevelMap: { xhigh: "xhigh", max: "max" },
|
||||
});
|
||||
});
|
||||
|
||||
it("marks known Mythos Preview inference profile fallbacks as reasoning capable", async () => {
|
||||
sendMock
|
||||
.mockResolvedValueOnce({
|
||||
modelSummaries: [],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
inferenceProfileSummaries: [
|
||||
{
|
||||
inferenceProfileId: "us.anthropic.claude-mythos-preview",
|
||||
inferenceProfileName: "US Claude Mythos Preview",
|
||||
status: "ACTIVE",
|
||||
type: "SYSTEM_DEFINED",
|
||||
models: [
|
||||
{
|
||||
modelArn:
|
||||
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-mythos-preview",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const models = await discoverBedrockModels({ region: "us-east-1", clientFactory });
|
||||
const model = models[0] as Record<string, unknown> | undefined;
|
||||
|
||||
expectModelFields(model, {
|
||||
id: "us.anthropic.claude-mythos-preview",
|
||||
reasoning: true,
|
||||
});
|
||||
expect(model).not.toHaveProperty("thinkingLevelMap");
|
||||
});
|
||||
|
||||
it("normalizes region-prefixed versioned model ids when resolving context windows", async () => {
|
||||
sendMock
|
||||
.mockResolvedValueOnce({
|
||||
|
||||
@@ -157,6 +157,13 @@ function resolveKnownContextWindow(modelId: string): number | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isKnownClaudeMythosPreviewModelId(modelId: string): boolean {
|
||||
const stripped = modelId.replace(/^(?:us|eu|ap|apac|au|jp|global)\./, "");
|
||||
return [modelId, stripped].some((candidate) =>
|
||||
/(?:^|[/.:])anthropic\.claude-mythos-preview(?:$|[-.:/])/i.test(candidate),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveKnownThinkingLevelMap(
|
||||
modelId: string,
|
||||
): ModelDefinitionConfig["thinkingLevelMap"] | undefined {
|
||||
@@ -275,7 +282,10 @@ function mapInputModalities(summary: BedrockModelSummary): Array<"text" | "image
|
||||
}
|
||||
|
||||
function inferReasoningSupport(summary: BedrockModelSummary): boolean {
|
||||
if (supportsClaudeAdaptiveThinking({ id: summary.modelId })) {
|
||||
if (
|
||||
supportsClaudeAdaptiveThinking({ id: summary.modelId }) ||
|
||||
isKnownClaudeMythosPreviewModelId(summary.modelId ?? "")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const haystack = normalizeLowercaseStringOrEmpty(
|
||||
@@ -467,7 +477,8 @@ function resolveInferenceProfiles(
|
||||
name: profile.inferenceProfileName?.trim() || profile.inferenceProfileId,
|
||||
reasoning:
|
||||
baseModel?.reasoning ??
|
||||
supportsClaudeAdaptiveThinking({ id: baseModelId ?? profile.inferenceProfileId }),
|
||||
(supportsClaudeAdaptiveThinking({ id: baseModelId ?? profile.inferenceProfileId }) ||
|
||||
isKnownClaudeMythosPreviewModelId(baseModelId ?? profile.inferenceProfileId)),
|
||||
input: baseModel?.input ?? ["text"],
|
||||
cost: baseModel?.cost ?? DEFAULT_COST,
|
||||
contextWindow:
|
||||
|
||||
@@ -835,6 +835,44 @@ describe("amazon-bedrock provider plugin", () => {
|
||||
expect(payload.inferenceConfig).toEqual({});
|
||||
});
|
||||
|
||||
it("does not re-upgrade Mythos Preview max thinking in the final payload", async () => {
|
||||
const provider = await registerSingleProviderPlugin(amazonBedrockPlugin);
|
||||
const wrapped = provider.wrapStreamFn?.({
|
||||
provider: "amazon-bedrock",
|
||||
modelId: "us.anthropic.claude-mythos-preview",
|
||||
streamFn: spyStreamFn,
|
||||
thinkingLevel: "max",
|
||||
} as never);
|
||||
|
||||
const result = wrapped?.(
|
||||
{
|
||||
api: "bedrock-converse-stream",
|
||||
provider: "amazon-bedrock",
|
||||
id: "us.anthropic.claude-mythos-preview",
|
||||
name: "Claude Mythos Preview",
|
||||
reasoning: true,
|
||||
} as never,
|
||||
{ messages: [] } as never,
|
||||
{ reasoning: "max" } as never,
|
||||
) as Record<string, unknown> | undefined;
|
||||
|
||||
const payload = {
|
||||
inferenceConfig: { temperature: 0.2 },
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: "adaptive" },
|
||||
output_config: { effort: "high" },
|
||||
},
|
||||
};
|
||||
|
||||
await (result?.onPayload as ((p: Record<string, unknown>) => unknown) | undefined)?.(payload);
|
||||
|
||||
expect(payload.additionalModelRequestFields).toEqual({
|
||||
thinking: { type: "adaptive" },
|
||||
output_config: { effort: "high" },
|
||||
});
|
||||
expect(payload.inferenceConfig).toEqual({});
|
||||
});
|
||||
|
||||
it("classifies nested Bedrock deprecated-temperature validation as format failover", async () => {
|
||||
const provider = await registerSingleProviderPlugin(amazonBedrockPlugin);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { mergeImplicitBedrockProvider, resolveBedrockConfigApiKey } from "./disc
|
||||
import { bedrockMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js";
|
||||
import { streamBedrock, streamSimpleBedrock } from "./stream.runtime.js";
|
||||
import {
|
||||
isLatestAdaptiveBedrockModelRef,
|
||||
isOpus47OrNewerBedrockModelRef,
|
||||
resolveBedrockNativeThinkingLevelMap,
|
||||
resolveBedrockClaudeThinkingProfile,
|
||||
@@ -596,8 +597,10 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
currentPluginConfig?.discovery?.region;
|
||||
const mayNeedCacheInjection =
|
||||
isBedrockAppInferenceProfile(modelId) && !sharedRuntimeWouldInjectCachePoints(modelId);
|
||||
const shouldOmitTemperature = opus47OrNewer || fable5;
|
||||
const shouldOmitTemperature =
|
||||
opus47OrNewer || fable5 || isLatestAdaptiveBedrockModelRef(modelId, model?.params);
|
||||
const shouldPatchMaxThinking = supportsNativeMax && thinkingLevel === "max";
|
||||
const shouldPatchPayload = shouldOmitTemperature || shouldPatchMaxThinking;
|
||||
|
||||
// For known Anthropic models (heuristic match), enable injection immediately.
|
||||
// For opaque profile IDs, we'll resolve via GetInferenceProfile on first call.
|
||||
@@ -627,13 +630,17 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
context,
|
||||
withAwsCredentialRefreshOnPayload({
|
||||
...merged,
|
||||
...(shouldPatchMaxThinking
|
||||
...(shouldPatchPayload
|
||||
? {
|
||||
onPayload: (payload: unknown, payloadModel: unknown) => {
|
||||
if (payload && typeof payload === "object") {
|
||||
const payloadRecord = payload as Record<string, unknown>;
|
||||
patchMaxThinkingEffort(payloadRecord);
|
||||
omitUnsupportedClaudePayloadTemperature(payloadRecord);
|
||||
if (shouldPatchMaxThinking) {
|
||||
patchMaxThinkingEffort(payloadRecord);
|
||||
}
|
||||
if (shouldOmitTemperature) {
|
||||
omitUnsupportedClaudePayloadTemperature(payloadRecord);
|
||||
}
|
||||
}
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
|
||||
@@ -43,6 +43,28 @@ export function isOpus47OrNewerBedrockModelRef(modelRef: string): boolean {
|
||||
return isOpus47BedrockModelRef(modelRef) || isOpus48BedrockModelRef(modelRef);
|
||||
}
|
||||
|
||||
function isMythosPreviewBedrockModelRef(modelRef: string): boolean {
|
||||
return /(?:^|[/.:])(?:(?:us|eu|ap|apac|au|jp|global)\.)?(?:anthropic\.)?claude-mythos-preview(?:$|[-.:/])/i.test(
|
||||
modelRef,
|
||||
);
|
||||
}
|
||||
|
||||
/** Return whether a Bedrock Claude ref needs latest adaptive-thinking request shaping. */
|
||||
export function isLatestAdaptiveBedrockModelRef(
|
||||
modelId: string,
|
||||
params?: Record<string, unknown>,
|
||||
): boolean {
|
||||
const modelRef = { id: modelId, params };
|
||||
const canonicalModelId = resolveClaudeModelIdentity(modelRef);
|
||||
return (
|
||||
resolveClaudeFable5ModelIdentity(modelRef) !== undefined ||
|
||||
[modelId, canonicalModelId].some(
|
||||
(candidate) =>
|
||||
isOpus47OrNewerBedrockModelRef(candidate) || isMythosPreviewBedrockModelRef(candidate),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** Return whether a Bedrock Claude ref supports max effort. */
|
||||
export function supportsBedrockNativeMaxEffort(
|
||||
modelId: string,
|
||||
@@ -109,6 +131,12 @@ export function resolveBedrockClaudeThinkingProfile(
|
||||
defaultLevel: "adaptive",
|
||||
};
|
||||
}
|
||||
if (modelRefs.some(isMythosPreviewBedrockModelRef)) {
|
||||
return {
|
||||
levels: [...BASE_CLAUDE_THINKING_LEVELS, { id: "adaptive" }],
|
||||
defaultLevel: "adaptive",
|
||||
};
|
||||
}
|
||||
if (modelRefs.some((modelRef) => /claude-sonnet-4(?:\.|-)6(?:$|[-.])/i.test(modelRef))) {
|
||||
return {
|
||||
levels: [...BASE_CLAUDE_THINKING_LEVELS, { id: "adaptive" }],
|
||||
|
||||
Reference in New Issue
Block a user