Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for iam roles for bedrock client #2632

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 38 additions & 56 deletions frontend/src/components/LLMSelection/AwsBedrockLLMOptions/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { AWS_REGIONS } from "./regions";
import { useState } from "react";

export default function AwsBedrockLLMOptions({ settings }) {
const [useSessionToken, setUseSessionToken] = useState(
settings?.AwsBedrockLLMConnectionMethod === "sessionToken"
const [useIamRole, setUseIamRole] = useState(
settings?.AwsBedrockLLMConnectionMethod === "iam_role"
);

return (
Expand All @@ -14,7 +14,7 @@ export default function AwsBedrockLLMOptions({ settings }) {
<div className="gap-x-2 flex items-center">
<Info size={40} />
<p className="text-base">
You should use a properly defined IAM user for inferencing.
You should use a properly defined IAM role for inferencing.
<br />
<a
href="https://docs.anythingllm.com/setup/llm-configuration/cloud/aws-bedrock"
Expand All @@ -28,98 +28,81 @@ export default function AwsBedrockLLMOptions({ settings }) {
</div>
</div>
)}

<div className="flex flex-col gap-y-2">
<input
type="hidden"
name="AwsBedrockLLMConnectionMethod"
value={useSessionToken ? "sessionToken" : "iam"}
value={useIamRole ? "iam_role" : "iam_user"}
/>
<div className="flex flex-col w-full">
<label className="text-white text-sm font-semibold block mb-3">
Use session token
Use IAM Role
</label>
<p className="text-white/50 text-sm">
Select the method to authenticate with AWS Bedrock.
</p>
</div>
<div className="flex items-center justify-start gap-x-4 bg-zinc-900 p-2.5 rounded-lg w-fit">
<span
className={`text-sm ${!useSessionToken ? "text-white" : "text-white/50"}`}
className={`text-sm ${!useIamRole ? "text-white" : "text-white/50"}`}
>
IAM
Explicit Credentials
</span>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
className="sr-only peer"
checked={useSessionToken}
onChange={(e) => setUseSessionToken(e.target.checked)}
checked={useIamRole}
onChange={(e) => setUseIamRole(e.target.checked)}
/>
<div className="w-11 h-6 bg-zinc-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-button"></div>
</label>
<span
className={`text-sm ${useSessionToken ? "text-white" : "text-white/50"}`}
className={`text-sm ${useIamRole ? "text-white" : "text-white/50"}`}
>
Session Token
IAM Role
</span>
</div>
</div>

<div className="w-full flex items-center gap-[36px] my-1.5">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
AWS Bedrock IAM Access ID
</label>
<input
type="password"
name="AwsBedrockLLMAccessKeyId"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="AWS Bedrock IAM User Access ID"
defaultValue={
settings?.AwsBedrockLLMAccessKeyId ? "*".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 Bedrock IAM Access Key
</label>
<input
type="password"
name="AwsBedrockLLMAccessKey"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="AWS Bedrock IAM User Access Key"
defaultValue={
settings?.AwsBedrockLLMAccessKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
{useSessionToken && (
{!useIamRole && (
<div className="w-full flex items-center gap-[36px] my-1.5">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
AWS Bedrock Session Token
AWS Bedrock IAM Access ID
</label>
<input
type="password"
name="AwsBedrockLLMSessionToken"
name="AwsBedrockLLMAccessKeyId"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="AWS Bedrock Session Token"
placeholder="AWS Bedrock IAM User Access ID"
defaultValue={
settings?.AwsBedrockLLMSessionToken ? "*".repeat(20) : ""
settings?.AwsBedrockLLMAccessKeyId ? "*".repeat(20) : ""
}
required={true}
required={!useIamRole}
autoComplete="off"
spellCheck={false}
/>
</div>
)}
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
AWS Bedrock IAM Access Key
</label>
<input
type="password"
name="AwsBedrockLLMAccessKey"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="AWS Bedrock IAM User Access Key"
defaultValue={
settings?.AwsBedrockLLMAccessKey ? "*".repeat(20) : ""
}
required={!useIamRole}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
)}
<div className="w-full flex items-center gap-[36px] my-1.5">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
AWS region
Expand All @@ -140,7 +123,6 @@ export default function AwsBedrockLLMOptions({ settings }) {
</select>
</div>
</div>

<div className="w-full flex items-center gap-[36px] my-1.5">
{!settings?.credentialsOnly && (
<>
Expand Down
2 changes: 1 addition & 1 deletion server/models/systemSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ const SystemSettings = {
GenericOpenAiMaxTokens: process.env.GENERIC_OPEN_AI_MAX_TOKENS,

AwsBedrockLLMConnectionMethod:
process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam",
process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam_role",
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,
Expand Down
48 changes: 12 additions & 36 deletions server/utils/AiProviders/bedrock/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,12 @@ class AWSBedrockLLM {
];

constructor(embedder = null, modelPreference = null) {
if (!process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID)
throw new Error("No AWS Bedrock LLM profile id was set.");

if (!process.env.AWS_BEDROCK_LLM_ACCESS_KEY)
throw new Error("No AWS Bedrock LLM access key was set.");

if (!process.env.AWS_BEDROCK_LLM_REGION)
throw new Error("No AWS Bedrock LLM region was set.");

if (
process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD === "sessionToken" &&
!process.env.AWS_BEDROCK_LLM_SESSION_TOKEN
)
throw new Error(
"No AWS Bedrock LLM session token was set while using session token as the authentication method."
);

this.model =
modelPreference || process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE;
this.authMethod = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || 'iam_role';
this.limits = {
history: this.promptWindowLimit() * 0.15,
system: this.promptWindowLimit() * 0.15,
Expand All @@ -49,36 +36,25 @@ class AWSBedrockLLM {

this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;
this.#log(
`Loaded with model: ${this.model}. Will communicate with AWS Bedrock using ${this.authMethod} authentication.`
);
}

/**
* Get the authentication method for the AWS Bedrock LLM.
* There are only two valid values for this setting - anything else will default to "iam".
* @returns {"iam"|"sessionToken"}
*/
get authMethod() {
const method = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam";
if (!["iam", "sessionToken"].includes(method)) return "iam";
return method;
}

#bedrockClient({ temperature = 0.7 }) {
const { ChatBedrockConverse } = require("@langchain/aws");
return new ChatBedrockConverse({
const clientConfig = {
model: this.model,
region: process.env.AWS_BEDROCK_LLM_REGION,
credentials: {
temperature,
};

const authMethod = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || 'iam_role';

if (authMethod === 'iam_user' && process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID && process.env.AWS_BEDROCK_LLM_ACCESS_KEY) {
clientConfig.credentials = {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
...(this.authMethod === "sessionToken"
? { sessionToken: process.env.AWS_BEDROCK_LLM_SESSION_TOKEN }
: {}),
},
temperature,
});
};
}
return new ChatBedrockConverse(clientConfig);
}

// For streaming we use Langchain's wrapper to handle weird chunks
Expand Down
16 changes: 9 additions & 7 deletions server/utils/agents/aibitat/providers/ai-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const { ChatBedrockConverse } = require("@langchain/aws");
const { ChatOllama } = require("@langchain/community/chat_models/ollama");
const { toValidNumber } = require("../../../http");
const { getLLMProviderClass } = require("../../../helpers");
const { parseLMStudioBasePath } = require("../../../AiProviders/lmStudio");

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.";
Expand Down Expand Up @@ -117,15 +116,18 @@ class Provider {
...config,
});
case "bedrock":
return new ChatBedrockConverse({
const bedrockConfig = {
model: process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE,
region: process.env.AWS_BEDROCK_LLM_REGION,
credentials: {
...config,
};
if (process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID && process.env.AWS_BEDROCK_LLM_ACCESS_KEY) {
bedrockConfig.credentials = {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
},
...config,
});
};
}
return new ChatBedrockConverse(bedrockConfig);
case "fireworksai":
return new ChatOpenAI({
apiKey: process.env.FIREWORKS_AI_LLM_API_KEY,
Expand Down Expand Up @@ -170,7 +172,7 @@ class Provider {
case "lmstudio":
return new ChatOpenAI({
configuration: {
baseURL: parseLMStudioBasePath(process.env.LMSTUDIO_BASE_PATH),
baseURL: process.env.LMSTUDIO_BASE_PATH?.replace(/\/+$/, ""),
},
apiKey: "not-used", // Needs to be specified or else will assume OpenAI
...config,
Expand Down
69 changes: 45 additions & 24 deletions server/utils/agents/aibitat/providers/bedrock.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,59 @@ class AWSBedrockProvider extends InheritMultiple([Provider, UnTooled]) {

constructor(_config = {}) {
super();
this.verbose = true;
this._lastConfig = null;
this._lastAuthMethod = null;
this.refreshClient();
}

get client() {
if (this.shouldRefreshClient()) {
this.refreshClient();
}
return this._client;
}

getClientConfig() {
const model = process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE ?? null;
const client = new ChatBedrockConverse({
region: process.env.AWS_BEDROCK_LLM_REGION,
credentials: {
const region = process.env.AWS_BEDROCK_LLM_REGION;
const authMethod = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD;

const clientConfig = {
region,
model,
};

if (authMethod === 'iam_user' && process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID && process.env.AWS_BEDROCK_LLM_ACCESS_KEY) {
clientConfig.credentials = {
accessKeyId: process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
// If we're using a session token, we need to pass it in as a credential
// otherwise we must omit it so it does not conflict if using IAM auth
...(this.authMethod === "sessionToken"
? { sessionToken: process.env.AWS_BEDROCK_LLM_SESSION_TOKEN }
: {}),
},
model,
});
};
}

this._client = client;
this.model = model;
this.verbose = true;
return clientConfig;
}

/**
* Get the authentication method for the AWS Bedrock LLM.
* There are only two valid values for this setting - anything else will default to "iam".
* @returns {"iam"|"sessionToken"}
*/
get authMethod() {
const method = process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam";
if (!["iam", "sessionToken"].includes(method)) return "iam";
return method;
getCurrentAuthMethod() {
return process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || 'iam_role';
}

get client() {
shouldRefreshClient() {
if (!this._client || !this._lastConfig) return true;

const currentConfig = this.getClientConfig();
const currentAuthMethod = this.getCurrentAuthMethod();

return JSON.stringify(currentConfig) !== JSON.stringify(this._lastConfig) ||
currentAuthMethod !== this._lastAuthMethod;
}

refreshClient() {
const config = this.getClientConfig();
this._client = new ChatBedrockConverse(config);
this.model = config.model;
this._lastConfig = config;
this._lastAuthMethod = this.getCurrentAuthMethod();
return this._client;
}

Expand Down
13 changes: 3 additions & 10 deletions server/utils/helpers/updateENV.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,7 @@ const KEY_MAPPING = {
// AWS Bedrock LLM InferenceSettings
AwsBedrockLLMConnectionMethod: {
envKey: "AWS_BEDROCK_LLM_CONNECTION_METHOD",
checks: [
(input) =>
["iam", "sessionToken"].includes(input) ? null : "Invalid value",
],
checks: [(input) => ['iam_role','iam_user'].includes(input) ? null : "invalid Value"]
},
AwsBedrockLLMAccessKeyId: {
envKey: "AWS_BEDROCK_LLM_ACCESS_KEY_ID",
Expand Down Expand Up @@ -724,13 +721,9 @@ function validAnthropicModel(input = "") {
"claude-instant-1.2",
"claude-2.0",
"claude-2.1",
"claude-3-haiku-20240307",
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-opus-latest",
"claude-3-5-haiku-latest",
"claude-3-5-haiku-20241022",
"claude-3-5-sonnet-latest",
"claude-3-5-sonnet-20241022",
"claude-3-haiku-20240307",
"claude-3-5-sonnet-20240620",
];
return validModels.includes(input)
Expand Down