fix: refine bedrock discovery defaults (#1543) (thanks @fal3)
This commit is contained in:
@@ -7,6 +7,7 @@ Docs: https://docs.clawd.bot
|
||||
### Changes
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -32,7 +32,9 @@ Config options live under `models.bedrockDiscovery`:
|
||||
enabled: true,
|
||||
region: "us-east-1",
|
||||
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`.
|
||||
- `providerFilter` matches Bedrock provider names (for example `anthropic`).
|
||||
- `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)
|
||||
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -311,9 +311,6 @@ importers:
|
||||
'@matrix-org/matrix-sdk-crypto-nodejs':
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
clawdbot:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
markdown-it:
|
||||
specifier: 14.1.0
|
||||
version: 14.1.0
|
||||
@@ -323,6 +320,13 @@ importers:
|
||||
music-metadata:
|
||||
specifier: ^11.10.6
|
||||
version: 11.10.6
|
||||
zod:
|
||||
specifier: ^4.3.5
|
||||
version: 4.3.5
|
||||
devDependencies:
|
||||
clawdbot:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
|
||||
extensions/mattermost: {}
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ describe("bedrock discovery", () => {
|
||||
name: "Claude 3.7 Sonnet",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
contextWindow: 32000,
|
||||
maxTokens: 4096,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,4 +93,101 @@ describe("bedrock discovery", () => {
|
||||
});
|
||||
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";
|
||||
|
||||
const DEFAULT_REFRESH_INTERVAL_SECONDS = 3600;
|
||||
const DEFAULT_CONTEXT_WINDOW = 128000;
|
||||
const DEFAULT_MAX_TOKENS = 8192;
|
||||
const DEFAULT_CONTEXT_WINDOW = 32000;
|
||||
const DEFAULT_MAX_TOKENS = 4096;
|
||||
const DEFAULT_COST = {
|
||||
input: 0,
|
||||
output: 0,
|
||||
@@ -39,6 +39,8 @@ function buildCacheKey(params: {
|
||||
region: string;
|
||||
providerFilter: string[];
|
||||
refreshIntervalSeconds: number;
|
||||
defaultContextWindow: number;
|
||||
defaultMaxTokens: number;
|
||||
}): string {
|
||||
return JSON.stringify(params);
|
||||
}
|
||||
@@ -69,12 +71,14 @@ function inferReasoningSupport(summary: BedrockModelSummary): boolean {
|
||||
return haystack.includes("reasoning") || haystack.includes("thinking");
|
||||
}
|
||||
|
||||
function inferContextWindow(): number {
|
||||
return DEFAULT_CONTEXT_WINDOW;
|
||||
function resolveDefaultContextWindow(config?: BedrockDiscoveryConfig): number {
|
||||
const value = Math.floor(config?.defaultContextWindow ?? DEFAULT_CONTEXT_WINDOW);
|
||||
return value > 0 ? value : DEFAULT_CONTEXT_WINDOW;
|
||||
}
|
||||
|
||||
function inferMaxTokens(): number {
|
||||
return DEFAULT_MAX_TOKENS;
|
||||
function resolveDefaultMaxTokens(config?: BedrockDiscoveryConfig): number {
|
||||
const value = Math.floor(config?.defaultMaxTokens ?? DEFAULT_MAX_TOKENS);
|
||||
return value > 0 ? value : DEFAULT_MAX_TOKENS;
|
||||
}
|
||||
|
||||
function matchesProviderFilter(summary: BedrockModelSummary, filter: string[]): boolean {
|
||||
@@ -96,7 +100,10 @@ function shouldIncludeSummary(summary: BedrockModelSummary, filter: string[]): b
|
||||
return true;
|
||||
}
|
||||
|
||||
function toModelDefinition(summary: BedrockModelSummary): ModelDefinitionConfig {
|
||||
function toModelDefinition(
|
||||
summary: BedrockModelSummary,
|
||||
defaults: { contextWindow: number; maxTokens: number },
|
||||
): ModelDefinitionConfig {
|
||||
const id = summary.modelId?.trim() ?? "";
|
||||
return {
|
||||
id,
|
||||
@@ -104,8 +111,8 @@ function toModelDefinition(summary: BedrockModelSummary): ModelDefinitionConfig
|
||||
reasoning: inferReasoningSupport(summary),
|
||||
input: mapInputModalities(summary),
|
||||
cost: DEFAULT_COST,
|
||||
contextWindow: inferContextWindow(),
|
||||
maxTokens: inferMaxTokens(),
|
||||
contextWindow: defaults.contextWindow,
|
||||
maxTokens: defaults.maxTokens,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,10 +132,14 @@ export async function discoverBedrockModels(params: {
|
||||
Math.floor(params.config?.refreshInterval ?? DEFAULT_REFRESH_INTERVAL_SECONDS),
|
||||
);
|
||||
const providerFilter = normalizeProviderFilter(params.config?.providerFilter);
|
||||
const defaultContextWindow = resolveDefaultContextWindow(params.config);
|
||||
const defaultMaxTokens = resolveDefaultMaxTokens(params.config);
|
||||
const cacheKey = buildCacheKey({
|
||||
region: params.region,
|
||||
providerFilter,
|
||||
refreshIntervalSeconds,
|
||||
defaultContextWindow,
|
||||
defaultMaxTokens,
|
||||
});
|
||||
const now = params.now?.() ?? Date.now();
|
||||
|
||||
@@ -150,7 +161,12 @@ export async function discoverBedrockModels(params: {
|
||||
const discovered: ModelDefinitionConfig[] = [];
|
||||
for (const summary of response.modelSummaries ?? []) {
|
||||
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));
|
||||
})();
|
||||
|
||||
@@ -75,12 +75,12 @@ function resolveEnvSourceLabel(params: {
|
||||
return `${prefix}${params.label}`;
|
||||
}
|
||||
|
||||
export function resolveAwsSdkEnvVarName(): string | undefined {
|
||||
if (process.env[AWS_BEARER_ENV]?.trim()) return AWS_BEARER_ENV;
|
||||
if (process.env[AWS_ACCESS_KEY_ENV]?.trim() && process.env[AWS_SECRET_KEY_ENV]?.trim()) {
|
||||
export function resolveAwsSdkEnvVarName(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
||||
if (env[AWS_BEARER_ENV]?.trim()) return AWS_BEARER_ENV;
|
||||
if (env[AWS_ACCESS_KEY_ENV]?.trim() && env[AWS_SECRET_KEY_ENV]?.trim()) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ export async function resolveImplicitBedrockProvider(params: {
|
||||
const env = params.env ?? process.env;
|
||||
const discoveryConfig = params.config?.models?.bedrockDiscovery;
|
||||
const enabled = discoveryConfig?.enabled;
|
||||
const hasAwsCreds = resolveAwsSdkEnvVarName() !== undefined;
|
||||
const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined;
|
||||
if (enabled === false) return null;
|
||||
if (enabled !== true && !hasAwsCreds) return null;
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ export type BedrockDiscoveryConfig = {
|
||||
region?: string;
|
||||
providerFilter?: string[];
|
||||
refreshInterval?: number;
|
||||
defaultContextWindow?: number;
|
||||
defaultMaxTokens?: number;
|
||||
};
|
||||
|
||||
export type ModelsConfig = {
|
||||
|
||||
@@ -65,6 +65,8 @@ export const BedrockDiscoverySchema = z
|
||||
region: z.string().optional(),
|
||||
providerFilter: z.array(z.string()).optional(),
|
||||
refreshInterval: z.number().int().nonnegative().optional(),
|
||||
defaultContextWindow: z.number().int().positive().optional(),
|
||||
defaultMaxTokens: z.number().int().positive().optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
Reference in New Issue
Block a user