feature: Support for AWS Bedrock API Keys (#4651)

* feat: add AWS Bedrock API Key option to settings panel

* feat: Bedrock API key auth method

* fix: hide IAM note when using bedrock api key

* move to camcelCase identifier for bedrock api key use
linting

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
Chetan Sarva 2025-11-20 18:38:45 -05:00 committed by GitHub
parent 2eb5384e27
commit c169193fc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 164 additions and 81 deletions

View File

@ -115,6 +115,9 @@ GID='1000'
# AWS_BEDROCK_LLM_CONNECTION_METHOD=iam
# AWS_BEDROCK_LLM_MAX_OUTPUT_TOKENS=4096
# AWS_BEDROCK_LLM_SESSION_TOKEN= # Only required if CONNECTION_METHOD is 'sessionToken'
# or even use Short and Long Term API keys
# AWS_BEDROCK_LLM_CONNECTION_METHOD="apiKey"
# AWS_BEDROCK_LLM_API_KEY=
# LLM_PROVIDER='fireworksai'
# FIREWORKS_AI_LLM_API_KEY='my-fireworks-ai-key'

View File

@ -7,10 +7,9 @@ export default function AwsBedrockLLMOptions({ settings }) {
settings?.AwsBedrockLLMConnectionMethod ?? "iam"
);
console.log("connectionMethod", connectionMethod);
return (
<div className="w-full flex flex-col">
{!settings?.credentialsOnly && (
{!settings?.credentialsOnly && connectionMethod !== "apiKey" && (
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
<div className="gap-x-2 flex items-center">
<Info size={40} />
@ -21,6 +20,7 @@ export default function AwsBedrockLLMOptions({ settings }) {
href="https://docs.anythingllm.com/setup/llm-configuration/cloud/aws-bedrock"
target="_blank"
className="underline flex gap-x-1 items-center"
rel="noreferrer"
>
Read more on how to use AWS Bedrock in AnythingLLM
<ArrowSquareOut size={14} />
@ -38,7 +38,7 @@ export default function AwsBedrockLLMOptions({ settings }) {
/>
<div className="flex flex-col w-full">
<label className="text-theme-text-primary text-sm font-semibold block mb-3">
Use session token
Authentication Method
</label>
<p className="text-theme-text-secondary text-sm">
Select the method to authenticate with AWS Bedrock.
@ -56,6 +56,7 @@ export default function AwsBedrockLLMOptions({ settings }) {
Session Token (Temporary Credentials)
</option>
<option value="iam_role">IAM Role (Implied Credentials)</option>
<option value="apiKey">Bedrock API Key</option>
</select>
</div>
@ -117,6 +118,23 @@ export default function AwsBedrockLLMOptions({ settings }) {
/>
</div>
)}
{connectionMethod === "apiKey" && (
<div className="flex flex-col w-60">
<label className="text-theme-text-primary text-sm font-semibold block mb-3">
AWS Bedrock API Key
</label>
<input
type="password"
name="AwsBedrockLLMAPIKey"
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="AWS Bedrock API Key"
defaultValue={settings?.AwsBedrockLLMAPIKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
)}
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
AWS region

View File

@ -122,6 +122,9 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long.
# AWS_BEDROCK_LLM_CONNECTION_METHOD=iam
# AWS_BEDROCK_LLM_MAX_OUTPUT_TOKENS=4096
# AWS_BEDROCK_LLM_SESSION_TOKEN= # Only required if CONNECTION_METHOD is 'sessionToken'
# or even use Short and Long Term API keys
# AWS_BEDROCK_LLM_CONNECTION_METHOD="apiKey"
# AWS_BEDROCK_LLM_API_KEY=
# LLM_PROVIDER='apipie'
# APIPIE_LLM_API_KEY='sk-123abc'

View File

@ -582,6 +582,7 @@ const SystemSettings = {
AwsBedrockLLMAccessKeyId: !!process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
AwsBedrockLLMAccessKey: !!process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
AwsBedrockLLMSessionToken: !!process.env.AWS_BEDROCK_LLM_SESSION_TOKEN,
AwsBedrockLLMAPIKey: !!process.env.AWS_BEDROCK_LLM_API_KEY,
AwsBedrockLLMRegion: process.env.AWS_BEDROCK_LLM_REGION,
AwsBedrockLLMModel: process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE,
AwsBedrockLLMTokenLimit:

View File

@ -1,5 +1,4 @@
const {
BedrockRuntimeClient,
ConverseCommand,
ConverseStreamCommand,
} = require("@aws-sdk/client-bedrock-runtime");
@ -15,9 +14,11 @@ const { v4: uuidv4 } = require("uuid");
const {
DEFAULT_MAX_OUTPUT_TOKENS,
DEFAULT_CONTEXT_WINDOW_TOKENS,
SUPPORTED_CONNECTION_METHODS,
getImageFormatFromMime,
base64ToUint8Array,
createBedrockCredentials,
createBedrockRuntimeClient,
getBedrockAuthMethod,
} = require("./utils");
class AWSBedrockLLM {
@ -42,7 +43,7 @@ class AWSBedrockLLM {
*/
constructor(embedder = null, modelPreference = null) {
const requiredEnvVars = [
...(this.authMethod !== "iam_role"
...(!["iam_role", "apiKey"].includes(this.authMethod)
? [
// required for iam and sessionToken
"AWS_BEDROCK_LLM_ACCESS_KEY_ID",
@ -55,6 +56,12 @@ class AWSBedrockLLM {
"AWS_BEDROCK_LLM_SESSION_TOKEN",
]
: []),
...(this.authMethod === "apiKey"
? [
// required for bedrock api key
"AWS_BEDROCK_LLM_API_KEY",
]
: []),
"AWS_BEDROCK_LLM_REGION",
"AWS_BEDROCK_LLM_MODEL_PREFERENCE",
];
@ -75,10 +82,10 @@ class AWSBedrockLLM {
user: Math.floor(contextWindowLimit * 0.7),
};
this.bedrockClient = new BedrockRuntimeClient({
region: process.env.AWS_BEDROCK_LLM_REGION,
credentials: this.credentials,
});
this.bedrockClient = createBedrockRuntimeClient(
this.authMethod,
this.credentials
);
this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;
@ -92,26 +99,7 @@ class AWSBedrockLLM {
* @returns {object} The credentials object.
*/
get credentials() {
switch (this.authMethod) {
case "iam": // explicit credentials
return {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
};
case "sessionToken": // Session token is used for temporary credentials
return {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
sessionToken: process.env.AWS_BEDROCK_LLM_SESSION_TOKEN,
};
// IAM role is used for long-term credentials implied by system process
// is filled by the AWS SDK automatically if we pass in no credentials
// returning undefined will allow this to happen
case "iam_role":
return undefined;
default:
return undefined;
}
return createBedrockCredentials(this.authMethod);
}
/**
@ -120,8 +108,7 @@ class AWSBedrockLLM {
* @returns {"iam" | "iam_role" | "sessionToken"} The authentication method.
*/
get authMethod() {
const method = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam";
return SUPPORTED_CONNECTION_METHODS.includes(method) ? method : "iam";
return getBedrockAuthMethod();
}
/**
@ -287,7 +274,7 @@ class AWSBedrockLLM {
#generateContent({ userPrompt = "", attachments = [] }) {
const content = [];
// Add text block if prompt is not empty
if (!!userPrompt?.trim()?.length) content.push({ text: userPrompt });
if (userPrompt?.trim()?.length) content.push({ text: userPrompt });
// Validate attachments and add valid attachments to content
const validAttachments = this.#validateAttachments(attachments);
@ -384,7 +371,7 @@ class AWSBedrockLLM {
if (reasoningBlock) {
const reasoningText =
reasoningBlock.reasoningContent.reasoningText.text.trim();
if (!!reasoningText?.length)
if (reasoningText?.length)
textResponse = `<think>${reasoningText}</think>${textResponse}`;
}
return textResponse;

View File

@ -1,3 +1,7 @@
const { BedrockRuntimeClient } = require("@aws-sdk/client-bedrock-runtime");
const { fromStatic } = require("@aws-sdk/token-providers");
const { ChatBedrockConverse } = require("@langchain/aws");
/** @typedef {'jpeg' | 'png' | 'gif' | 'webp'} */
const SUPPORTED_BEDROCK_IMAGE_FORMATS = ["jpeg", "png", "gif", "webp"];
@ -7,8 +11,95 @@ const DEFAULT_MAX_OUTPUT_TOKENS = 4096;
/** @type {number} */
const DEFAULT_CONTEXT_WINDOW_TOKENS = 8191;
/** @type {'iam' | 'iam_role' | 'sessionToken'} */
const SUPPORTED_CONNECTION_METHODS = ["iam", "iam_role", "sessionToken"];
/** @type {'iam' | 'iam_role' | 'sessionToken' | 'apiKey'} */
const SUPPORTED_CONNECTION_METHODS = [
"iam",
"iam_role",
"sessionToken",
"apiKey",
];
/**
* Gets the AWS Bedrock authentication method from the environment variables.
* @returns {"iam" | "iam_role" | "sessionToken" | "apiKey"} The authentication method.
*/
function getBedrockAuthMethod() {
const method = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam";
return SUPPORTED_CONNECTION_METHODS.includes(method) ? method : "iam";
}
/**
* Creates the AWS Bedrock credentials object based on the authentication method.
* @param {"iam" | "iam_role" | "sessionToken" | "apiKey"} authMethod - The authentication method.
* @returns {object | undefined} The credentials object.
*/
function createBedrockCredentials(authMethod) {
switch (authMethod) {
case "iam": // explicit credentials
return {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
};
case "sessionToken": // Session token is used for temporary credentials
return {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
sessionToken: process.env.AWS_BEDROCK_LLM_SESSION_TOKEN,
};
// IAM role is used for long-term credentials implied by system process
// is filled by the AWS SDK automatically if we pass in no credentials
// returning undefined will allow this to happen
case "iam_role":
return undefined;
case "apiKey":
return fromStatic({
token: { token: process.env.AWS_BEDROCK_LLM_API_KEY },
});
default:
return undefined;
}
}
/**
* Creates the AWS Bedrock runtime client based on the authentication method.
* @param {"iam" | "iam_role" | "sessionToken" | "apiKey"} authMethod - The authentication method.
* @param {object | undefined} credentials - The credentials object.
* @returns {BedrockRuntimeClient} The runtime client.
*/
function createBedrockRuntimeClient(authMethod, credentials) {
const clientOpts = {
region: process.env.AWS_BEDROCK_LLM_REGION,
};
if (authMethod === "apiKey") {
clientOpts.token = credentials;
clientOpts.authSchemePreference = ["httpBearerAuth"];
} else {
clientOpts.credentials = credentials;
}
return new BedrockRuntimeClient(clientOpts);
}
/**
* Creates the AWS Bedrock chat client based on the authentication method.
* Used explicitly by the agent provider for the AWS Bedrock provider.
* @param {object} config - The configuration object.
* @param {"iam" | "iam_role" | "sessionToken" | "apiKey"} authMethod - The authentication method.
* @param {object | undefined} credentials - The credentials object.
* @param {string | null} model - The model to use.
* @returns {ChatBedrockConverse} The chat client.
*/
function createBedrockChatClient(config = {}, authMethod, credentials, model) {
authMethod ||= getBedrockAuthMethod();
credentials ||= createBedrockCredentials(authMethod);
model ||= process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE ?? null;
const client = createBedrockRuntimeClient(authMethod, credentials);
return new ChatBedrockConverse({
region: process.env.AWS_BEDROCK_LLM_REGION,
client,
model,
...config,
});
}
/**
* Parses a MIME type string (e.g., "image/jpeg") to extract and validate the image format
@ -64,4 +155,8 @@ module.exports = {
DEFAULT_CONTEXT_WINDOW_TOKENS,
getImageFormatFromMime,
base64ToUint8Array,
getBedrockAuthMethod,
createBedrockCredentials,
createBedrockRuntimeClient,
createBedrockChatClient,
};

View File

@ -13,7 +13,6 @@
const { v4 } = require("uuid");
const { ChatOpenAI } = require("@langchain/openai");
const { ChatAnthropic } = require("@langchain/anthropic");
const { ChatBedrockConverse } = require("@langchain/aws");
const { ChatOllama } = require("@langchain/community/chat_models/ollama");
const { toValidNumber, safeJsonParse } = require("../../../http");
const { getLLMProviderClass } = require("../../../helpers");
@ -22,6 +21,9 @@ const { parseFoundryBasePath } = require("../../../AiProviders/foundry");
const {
SystemPromptVariables,
} = require("../../../../models/systemPromptVariables");
const {
createBedrockChatClient,
} = require("../../../AiProviders/bedrock/utils");
const DEFAULT_WORKSPACE_PROMPT =
"You are a helpful ai assistant who can assist the user and use tools available to help answer the users prompts and questions.";
@ -151,20 +153,7 @@ class Provider {
...config,
});
case "bedrock":
// Grab just the credentials from the bedrock provider
// using a closure to avoid circular dependency + to avoid instantiating the provider
const credentials = (() => {
const AWSBedrockProvider = require("./bedrock");
const bedrockProvider = new AWSBedrockProvider();
return bedrockProvider.credentials;
})();
return new ChatBedrockConverse({
model: process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE,
region: process.env.AWS_BEDROCK_LLM_REGION,
credentials: credentials,
...config,
});
return createBedrockChatClient(config);
case "fireworksai":
return new ChatOpenAI({
apiKey: process.env.FIREWORKS_AI_LLM_API_KEY,

View File

@ -1,10 +1,11 @@
const {
SUPPORTED_CONNECTION_METHODS,
createBedrockCredentials,
getBedrockAuthMethod,
createBedrockChatClient,
} = require("../../../AiProviders/bedrock/utils.js");
const Provider = require("./ai-provider.js");
const InheritMultiple = require("./helpers/classes.js");
const UnTooled = require("./helpers/untooled.js");
const { ChatBedrockConverse } = require("@langchain/aws");
const {
HumanMessage,
SystemMessage,
@ -20,11 +21,12 @@ class AWSBedrockProvider extends InheritMultiple([Provider, UnTooled]) {
constructor(_config = {}) {
super();
const model = process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE ?? null;
const client = new ChatBedrockConverse({
region: process.env.AWS_BEDROCK_LLM_REGION,
credentials: this.credentials,
model,
});
const client = createBedrockChatClient(
{},
this.authMethod,
this.credentials,
model
);
this._client = client;
this.model = model;
@ -36,25 +38,7 @@ class AWSBedrockProvider extends InheritMultiple([Provider, UnTooled]) {
* @returns {object} The credentials object.
*/
get credentials() {
switch (this.authMethod) {
case "iam": // explicit credentials
return {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
};
case "sessionToken": // Session token is used for temporary credentials
return {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
sessionToken: process.env.AWS_BEDROCK_LLM_SESSION_TOKEN,
};
// IAM role is used for long-term credentials implied by system process
// is filled by the AWS SDK automatically if we pass in no credentials
case "iam_role":
return undefined;
default:
return undefined;
}
return createBedrockCredentials(this.authMethod);
}
/**
@ -63,8 +47,7 @@ class AWSBedrockProvider extends InheritMultiple([Provider, UnTooled]) {
* @returns {"iam" | "iam_role" | "sessionToken"} The authentication method.
*/
get authMethod() {
const method = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam";
return SUPPORTED_CONNECTION_METHODS.includes(method) ? method : "iam";
return getBedrockAuthMethod();
}
get client() {

View File

@ -235,16 +235,20 @@ const KEY_MAPPING = {
},
AwsBedrockLLMAccessKeyId: {
envKey: "AWS_BEDROCK_LLM_ACCESS_KEY_ID",
checks: [isNotEmpty],
checks: [],
},
AwsBedrockLLMAccessKey: {
envKey: "AWS_BEDROCK_LLM_ACCESS_KEY",
checks: [isNotEmpty],
checks: [],
},
AwsBedrockLLMSessionToken: {
envKey: "AWS_BEDROCK_LLM_SESSION_TOKEN",
checks: [],
},
AwsBedrockLLMAPIKey: {
envKey: "AWS_BEDROCK_LLM_API_KEY",
checks: [],
},
AwsBedrockLLMRegion: {
envKey: "AWS_BEDROCK_LLM_REGION",
checks: [isNotEmpty],