fix: refine bedrock discovery defaults (#1543) (thanks @fal3)
This commit is contained in:
@@ -7,6 +7,7 @@ Docs: https://docs.clawd.bot
|
|||||||
### Changes
|
### Changes
|
||||||
- CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it.
|
- CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it.
|
||||||
- CLI: add live auth probes to `clawdbot models status` for per-profile verification.
|
- CLI: add live auth probes to `clawdbot models status` for per-profile verification.
|
||||||
|
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1543) Thanks @fal3.
|
||||||
- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.
|
- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.
|
||||||
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
|
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
|
||||||
- Tlon: add Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a.
|
- Tlon: add Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a.
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ Config options live under `models.bedrockDiscovery`:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
region: "us-east-1",
|
region: "us-east-1",
|
||||||
providerFilter: ["anthropic", "amazon"],
|
providerFilter: ["anthropic", "amazon"],
|
||||||
refreshInterval: 3600
|
refreshInterval: 3600,
|
||||||
|
defaultContextWindow: 32000,
|
||||||
|
defaultMaxTokens: 4096
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +45,8 @@ Notes:
|
|||||||
- `region` defaults to `AWS_REGION` or `AWS_DEFAULT_REGION`, then `us-east-1`.
|
- `region` defaults to `AWS_REGION` or `AWS_DEFAULT_REGION`, then `us-east-1`.
|
||||||
- `providerFilter` matches Bedrock provider names (for example `anthropic`).
|
- `providerFilter` matches Bedrock provider names (for example `anthropic`).
|
||||||
- `refreshInterval` is seconds; set to `0` to disable caching.
|
- `refreshInterval` is seconds; set to `0` to disable caching.
|
||||||
|
- `defaultContextWindow` (default: `32000`) and `defaultMaxTokens` (default: `4096`)
|
||||||
|
are used for discovered models (override if you know your model limits).
|
||||||
|
|
||||||
## Setup (manual)
|
## Setup (manual)
|
||||||
|
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -311,9 +311,6 @@ importers:
|
|||||||
'@matrix-org/matrix-sdk-crypto-nodejs':
|
'@matrix-org/matrix-sdk-crypto-nodejs':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.0
|
||||||
version: 0.4.0
|
version: 0.4.0
|
||||||
clawdbot:
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../..
|
|
||||||
markdown-it:
|
markdown-it:
|
||||||
specifier: 14.1.0
|
specifier: 14.1.0
|
||||||
version: 14.1.0
|
version: 14.1.0
|
||||||
@@ -323,6 +320,13 @@ importers:
|
|||||||
music-metadata:
|
music-metadata:
|
||||||
specifier: ^11.10.6
|
specifier: ^11.10.6
|
||||||
version: 11.10.6
|
version: 11.10.6
|
||||||
|
zod:
|
||||||
|
specifier: ^4.3.5
|
||||||
|
version: 4.3.5
|
||||||
|
devDependencies:
|
||||||
|
clawdbot:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../..
|
||||||
|
|
||||||
extensions/mattermost: {}
|
extensions/mattermost: {}
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ describe("bedrock discovery", () => {
|
|||||||
name: "Claude 3.7 Sonnet",
|
name: "Claude 3.7 Sonnet",
|
||||||
reasoning: false,
|
reasoning: false,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
contextWindow: 128000,
|
contextWindow: 32000,
|
||||||
maxTokens: 8192,
|
maxTokens: 4096,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -93,4 +93,101 @@ describe("bedrock discovery", () => {
|
|||||||
});
|
});
|
||||||
expect(models).toHaveLength(0);
|
expect(models).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses configured defaults for context and max tokens", async () => {
|
||||||
|
const { discoverBedrockModels, resetBedrockDiscoveryCacheForTest } =
|
||||||
|
await import("./bedrock-discovery.js");
|
||||||
|
resetBedrockDiscoveryCacheForTest();
|
||||||
|
|
||||||
|
sendMock.mockResolvedValueOnce({
|
||||||
|
modelSummaries: [
|
||||||
|
{
|
||||||
|
modelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||||
|
modelName: "Claude 3.7 Sonnet",
|
||||||
|
providerName: "anthropic",
|
||||||
|
inputModalities: ["TEXT"],
|
||||||
|
outputModalities: ["TEXT"],
|
||||||
|
responseStreamingSupported: true,
|
||||||
|
modelLifecycle: { status: "ACTIVE" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const models = await discoverBedrockModels({
|
||||||
|
region: "us-east-1",
|
||||||
|
config: { defaultContextWindow: 64000, defaultMaxTokens: 8192 },
|
||||||
|
clientFactory,
|
||||||
|
});
|
||||||
|
expect(models[0]).toMatchObject({ contextWindow: 64000, maxTokens: 8192 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("caches results when refreshInterval is enabled", async () => {
|
||||||
|
const { discoverBedrockModels, resetBedrockDiscoveryCacheForTest } =
|
||||||
|
await import("./bedrock-discovery.js");
|
||||||
|
resetBedrockDiscoveryCacheForTest();
|
||||||
|
|
||||||
|
sendMock.mockResolvedValueOnce({
|
||||||
|
modelSummaries: [
|
||||||
|
{
|
||||||
|
modelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||||
|
modelName: "Claude 3.7 Sonnet",
|
||||||
|
providerName: "anthropic",
|
||||||
|
inputModalities: ["TEXT"],
|
||||||
|
outputModalities: ["TEXT"],
|
||||||
|
responseStreamingSupported: true,
|
||||||
|
modelLifecycle: { status: "ACTIVE" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await discoverBedrockModels({ region: "us-east-1", clientFactory });
|
||||||
|
await discoverBedrockModels({ region: "us-east-1", clientFactory });
|
||||||
|
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips cache when refreshInterval is 0", async () => {
|
||||||
|
const { discoverBedrockModels, resetBedrockDiscoveryCacheForTest } =
|
||||||
|
await import("./bedrock-discovery.js");
|
||||||
|
resetBedrockDiscoveryCacheForTest();
|
||||||
|
|
||||||
|
sendMock
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
modelSummaries: [
|
||||||
|
{
|
||||||
|
modelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||||
|
modelName: "Claude 3.7 Sonnet",
|
||||||
|
providerName: "anthropic",
|
||||||
|
inputModalities: ["TEXT"],
|
||||||
|
outputModalities: ["TEXT"],
|
||||||
|
responseStreamingSupported: true,
|
||||||
|
modelLifecycle: { status: "ACTIVE" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
modelSummaries: [
|
||||||
|
{
|
||||||
|
modelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||||
|
modelName: "Claude 3.7 Sonnet",
|
||||||
|
providerName: "anthropic",
|
||||||
|
inputModalities: ["TEXT"],
|
||||||
|
outputModalities: ["TEXT"],
|
||||||
|
responseStreamingSupported: true,
|
||||||
|
modelLifecycle: { status: "ACTIVE" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await discoverBedrockModels({
|
||||||
|
region: "us-east-1",
|
||||||
|
config: { refreshInterval: 0 },
|
||||||
|
clientFactory,
|
||||||
|
});
|
||||||
|
await discoverBedrockModels({
|
||||||
|
region: "us-east-1",
|
||||||
|
config: { refreshInterval: 0 },
|
||||||
|
clientFactory,
|
||||||
|
});
|
||||||
|
expect(sendMock).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
import type { BedrockDiscoveryConfig, ModelDefinitionConfig } from "../config/types.js";
|
import type { BedrockDiscoveryConfig, ModelDefinitionConfig } from "../config/types.js";
|
||||||
|
|
||||||
const DEFAULT_REFRESH_INTERVAL_SECONDS = 3600;
|
const DEFAULT_REFRESH_INTERVAL_SECONDS = 3600;
|
||||||
const DEFAULT_CONTEXT_WINDOW = 128000;
|
const DEFAULT_CONTEXT_WINDOW = 32000;
|
||||||
const DEFAULT_MAX_TOKENS = 8192;
|
const DEFAULT_MAX_TOKENS = 4096;
|
||||||
const DEFAULT_COST = {
|
const DEFAULT_COST = {
|
||||||
input: 0,
|
input: 0,
|
||||||
output: 0,
|
output: 0,
|
||||||
@@ -39,6 +39,8 @@ function buildCacheKey(params: {
|
|||||||
region: string;
|
region: string;
|
||||||
providerFilter: string[];
|
providerFilter: string[];
|
||||||
refreshIntervalSeconds: number;
|
refreshIntervalSeconds: number;
|
||||||
|
defaultContextWindow: number;
|
||||||
|
defaultMaxTokens: number;
|
||||||
}): string {
|
}): string {
|
||||||
return JSON.stringify(params);
|
return JSON.stringify(params);
|
||||||
}
|
}
|
||||||
@@ -69,12 +71,14 @@ function inferReasoningSupport(summary: BedrockModelSummary): boolean {
|
|||||||
return haystack.includes("reasoning") || haystack.includes("thinking");
|
return haystack.includes("reasoning") || haystack.includes("thinking");
|
||||||
}
|
}
|
||||||
|
|
||||||
function inferContextWindow(): number {
|
function resolveDefaultContextWindow(config?: BedrockDiscoveryConfig): number {
|
||||||
return DEFAULT_CONTEXT_WINDOW;
|
const value = Math.floor(config?.defaultContextWindow ?? DEFAULT_CONTEXT_WINDOW);
|
||||||
|
return value > 0 ? value : DEFAULT_CONTEXT_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
function inferMaxTokens(): number {
|
function resolveDefaultMaxTokens(config?: BedrockDiscoveryConfig): number {
|
||||||
return DEFAULT_MAX_TOKENS;
|
const value = Math.floor(config?.defaultMaxTokens ?? DEFAULT_MAX_TOKENS);
|
||||||
|
return value > 0 ? value : DEFAULT_MAX_TOKENS;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchesProviderFilter(summary: BedrockModelSummary, filter: string[]): boolean {
|
function matchesProviderFilter(summary: BedrockModelSummary, filter: string[]): boolean {
|
||||||
@@ -96,7 +100,10 @@ function shouldIncludeSummary(summary: BedrockModelSummary, filter: string[]): b
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toModelDefinition(summary: BedrockModelSummary): ModelDefinitionConfig {
|
function toModelDefinition(
|
||||||
|
summary: BedrockModelSummary,
|
||||||
|
defaults: { contextWindow: number; maxTokens: number },
|
||||||
|
): ModelDefinitionConfig {
|
||||||
const id = summary.modelId?.trim() ?? "";
|
const id = summary.modelId?.trim() ?? "";
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@@ -104,8 +111,8 @@ function toModelDefinition(summary: BedrockModelSummary): ModelDefinitionConfig
|
|||||||
reasoning: inferReasoningSupport(summary),
|
reasoning: inferReasoningSupport(summary),
|
||||||
input: mapInputModalities(summary),
|
input: mapInputModalities(summary),
|
||||||
cost: DEFAULT_COST,
|
cost: DEFAULT_COST,
|
||||||
contextWindow: inferContextWindow(),
|
contextWindow: defaults.contextWindow,
|
||||||
maxTokens: inferMaxTokens(),
|
maxTokens: defaults.maxTokens,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,10 +132,14 @@ export async function discoverBedrockModels(params: {
|
|||||||
Math.floor(params.config?.refreshInterval ?? DEFAULT_REFRESH_INTERVAL_SECONDS),
|
Math.floor(params.config?.refreshInterval ?? DEFAULT_REFRESH_INTERVAL_SECONDS),
|
||||||
);
|
);
|
||||||
const providerFilter = normalizeProviderFilter(params.config?.providerFilter);
|
const providerFilter = normalizeProviderFilter(params.config?.providerFilter);
|
||||||
|
const defaultContextWindow = resolveDefaultContextWindow(params.config);
|
||||||
|
const defaultMaxTokens = resolveDefaultMaxTokens(params.config);
|
||||||
const cacheKey = buildCacheKey({
|
const cacheKey = buildCacheKey({
|
||||||
region: params.region,
|
region: params.region,
|
||||||
providerFilter,
|
providerFilter,
|
||||||
refreshIntervalSeconds,
|
refreshIntervalSeconds,
|
||||||
|
defaultContextWindow,
|
||||||
|
defaultMaxTokens,
|
||||||
});
|
});
|
||||||
const now = params.now?.() ?? Date.now();
|
const now = params.now?.() ?? Date.now();
|
||||||
|
|
||||||
@@ -150,7 +161,12 @@ export async function discoverBedrockModels(params: {
|
|||||||
const discovered: ModelDefinitionConfig[] = [];
|
const discovered: ModelDefinitionConfig[] = [];
|
||||||
for (const summary of response.modelSummaries ?? []) {
|
for (const summary of response.modelSummaries ?? []) {
|
||||||
if (!shouldIncludeSummary(summary, providerFilter)) continue;
|
if (!shouldIncludeSummary(summary, providerFilter)) continue;
|
||||||
discovered.push(toModelDefinition(summary));
|
discovered.push(
|
||||||
|
toModelDefinition(summary, {
|
||||||
|
contextWindow: defaultContextWindow,
|
||||||
|
maxTokens: defaultMaxTokens,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return discovered.sort((a, b) => a.name.localeCompare(b.name));
|
return discovered.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -75,12 +75,12 @@ function resolveEnvSourceLabel(params: {
|
|||||||
return `${prefix}${params.label}`;
|
return `${prefix}${params.label}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveAwsSdkEnvVarName(): string | undefined {
|
export function resolveAwsSdkEnvVarName(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
||||||
if (process.env[AWS_BEARER_ENV]?.trim()) return AWS_BEARER_ENV;
|
if (env[AWS_BEARER_ENV]?.trim()) return AWS_BEARER_ENV;
|
||||||
if (process.env[AWS_ACCESS_KEY_ENV]?.trim() && process.env[AWS_SECRET_KEY_ENV]?.trim()) {
|
if (env[AWS_ACCESS_KEY_ENV]?.trim() && env[AWS_SECRET_KEY_ENV]?.trim()) {
|
||||||
return AWS_ACCESS_KEY_ENV;
|
return AWS_ACCESS_KEY_ENV;
|
||||||
}
|
}
|
||||||
if (process.env[AWS_PROFILE_ENV]?.trim()) return AWS_PROFILE_ENV;
|
if (env[AWS_PROFILE_ENV]?.trim()) return AWS_PROFILE_ENV;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ export async function resolveImplicitBedrockProvider(params: {
|
|||||||
const env = params.env ?? process.env;
|
const env = params.env ?? process.env;
|
||||||
const discoveryConfig = params.config?.models?.bedrockDiscovery;
|
const discoveryConfig = params.config?.models?.bedrockDiscovery;
|
||||||
const enabled = discoveryConfig?.enabled;
|
const enabled = discoveryConfig?.enabled;
|
||||||
const hasAwsCreds = resolveAwsSdkEnvVarName() !== undefined;
|
const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined;
|
||||||
if (enabled === false) return null;
|
if (enabled === false) return null;
|
||||||
if (enabled !== true && !hasAwsCreds) return null;
|
if (enabled !== true && !hasAwsCreds) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ export type BedrockDiscoveryConfig = {
|
|||||||
region?: string;
|
region?: string;
|
||||||
providerFilter?: string[];
|
providerFilter?: string[];
|
||||||
refreshInterval?: number;
|
refreshInterval?: number;
|
||||||
|
defaultContextWindow?: number;
|
||||||
|
defaultMaxTokens?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ModelsConfig = {
|
export type ModelsConfig = {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ export const BedrockDiscoverySchema = z
|
|||||||
region: z.string().optional(),
|
region: z.string().optional(),
|
||||||
providerFilter: z.array(z.string()).optional(),
|
providerFilter: z.array(z.string()).optional(),
|
||||||
refreshInterval: z.number().int().nonnegative().optional(),
|
refreshInterval: z.number().int().nonnegative().optional(),
|
||||||
|
defaultContextWindow: z.number().int().positive().optional(),
|
||||||
|
defaultMaxTokens: z.number().int().positive().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional();
|
.optional();
|
||||||
|
|||||||
Reference in New Issue
Block a user