From ca8d9709e01609c8b89cd76ad8a28305bffc7664 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A1ssio=20de=20Freitas=20e=20Silva?=
 <ksfreitas@gmail.com>
Date: Wed, 3 Jul 2024 05:27:58 -0300
Subject: [PATCH] feat: add support for Meta LLMs in AWS Bedrock (#960)

---
 .../modules/llms/available_llms/bedrock.md    |   6 +-
 packages/community/README.md                  |   1 +
 packages/community/src/llm/bedrock/base.ts    | 218 ++----------------
 .../community/src/llm/bedrock/provider.ts     |  59 +++++
 .../src/llm/bedrock/providers/anthropic.ts    | 154 +++++++++++++
 .../src/llm/bedrock/providers/index.ts        |   9 +
 .../src/llm/bedrock/providers/meta.ts         |  69 ++++++
 packages/community/src/llm/bedrock/types.ts   |  22 +-
 packages/community/src/llm/bedrock/utils.ts   |  81 +++++++
 9 files changed, 418 insertions(+), 201 deletions(-)
 create mode 100644 packages/community/src/llm/bedrock/provider.ts
 create mode 100644 packages/community/src/llm/bedrock/providers/anthropic.ts
 create mode 100644 packages/community/src/llm/bedrock/providers/index.ts
 create mode 100644 packages/community/src/llm/bedrock/providers/meta.ts

diff --git a/apps/docs/docs/modules/llms/available_llms/bedrock.md b/apps/docs/docs/modules/llms/available_llms/bedrock.md
index 07b934dae..56c65c8fd 100644
--- a/apps/docs/docs/modules/llms/available_llms/bedrock.md
+++ b/apps/docs/docs/modules/llms/available_llms/bedrock.md
@@ -15,7 +15,7 @@ Settings.llm = new Bedrock({
 });
 ```
 
-Currently only supports Anthropic models:
+Currently only supports Anthropic and Meta models:
 
 ```ts
 ANTHROPIC_CLAUDE_INSTANT_1 = "anthropic.claude-instant-v1";
@@ -25,6 +25,10 @@ ANTHROPIC_CLAUDE_3_SONNET = "anthropic.claude-3-sonnet-20240229-v1:0";
 ANTHROPIC_CLAUDE_3_HAIKU = "anthropic.claude-3-haiku-20240307-v1:0";
 ANTHROPIC_CLAUDE_3_OPUS = "anthropic.claude-3-opus-20240229-v1:0"; // available on us-west-2
 ANTHROPIC_CLAUDE_3_5_SONNET = "anthropic.claude-3-5-sonnet-20240620-v1:0";
+META_LLAMA2_13B_CHAT = "meta.llama2-13b-chat-v1";
+META_LLAMA2_70B_CHAT = "meta.llama2-70b-chat-v1";
+META_LLAMA3_8B_INSTRUCT = "meta.llama3-8b-instruct-v1:0";
+META_LLAMA3_70B_INSTRUCT = "meta.llama3-70b-instruct-v1:0";
 ```
 
 Sonnet, Haiku and Opus are multimodal, image_url only supports base64 data url format, e.g. `data:image/jpeg;base64,SGVsbG8sIFdvcmxkIQ==`
diff --git a/packages/community/README.md b/packages/community/README.md
index b759df607..5a2f0fa7a 100644
--- a/packages/community/README.md
+++ b/packages/community/README.md
@@ -5,6 +5,7 @@
 ## Current Features:
 
 - Bedrock support for the Anthropic Claude Models [usage](https://ts.llamaindex.ai/modules/llms/available_llms/bedrock)
+- Bedrock support for the Meta LLama 2 and 3 Models [usage](https://ts.llamaindex.ai/modules/llms/available_llms/bedrock)
 
 ## LICENSE
 
diff --git a/packages/community/src/llm/bedrock/base.ts b/packages/community/src/llm/bedrock/base.ts
index a3d5b3405..b50f6f1c2 100644
--- a/packages/community/src/llm/bedrock/base.ts
+++ b/packages/community/src/llm/bedrock/base.ts
@@ -1,53 +1,34 @@
 import {
   BedrockRuntimeClient,
+  type BedrockRuntimeClientConfig,
   InvokeModelCommand,
   InvokeModelWithResponseStreamCommand,
-  ResponseStream,
-  type BedrockRuntimeClientConfig,
-  type InvokeModelCommandInput,
-  type InvokeModelWithResponseStreamCommandInput,
 } from "@aws-sdk/client-bedrock-runtime";
 import type {
-  BaseTool,
   ChatMessage,
   ChatResponse,
-  ChatResponseChunk,
   CompletionResponse,
   LLMChatParamsNonStreaming,
   LLMChatParamsStreaming,
   LLMCompletionParamsNonStreaming,
   LLMCompletionParamsStreaming,
   LLMMetadata,
-  PartialToolCall,
-  ToolCall,
   ToolCallLLMMessageOptions,
 } from "llamaindex";
-import { ToolCallLLM, streamConverter, wrapLLMEvent } from "llamaindex";
-import type {
-  AnthropicNoneStreamingResponse,
-  AnthropicTextContent,
-  StreamEvent,
-  ToolBlock,
-  ToolChoice,
-} from "./types.js";
+import { streamConverter, ToolCallLLM, wrapLLMEvent } from "llamaindex";
 import {
-  mapBaseToolsToAnthropicTools,
-  mapChatMessagesToAnthropicMessages,
-  mapMessageContentToMessageContentDetails,
-  toUtf8,
-} from "./utils.js";
-
-export type BedrockAdditionalChatOptions = { toolChoice: ToolChoice };
+  type BedrockAdditionalChatOptions,
+  type BedrockChatStreamResponse,
+  Provider,
+} from "./provider";
+import { PROVIDERS } from "./providers";
+import { mapMessageContentToMessageContentDetails } from "./utils.js";
 
 export type BedrockChatParamsStreaming = LLMChatParamsStreaming<
   BedrockAdditionalChatOptions,
   ToolCallLLMMessageOptions
 >;
 
-export type BedrockChatStreamResponse = AsyncIterable<
-  ChatResponseChunk<ToolCallLLMMessageOptions>
->;
-
 export type BedrockChatParamsNonStreaming = LLMChatParamsNonStreaming<
   BedrockAdditionalChatOptions,
   ToolCallLLMMessageOptions
@@ -151,174 +132,6 @@ export const TOOL_CALL_MODELS = [
   BEDROCK_MODELS.ANTHROPIC_CLAUDE_3_5_SONNET,
 ];
 
-abstract class Provider<ProviderStreamEvent extends {} = {}> {
-  abstract getTextFromResponse(response: Record<string, any>): string;
-
-  abstract getToolsFromResponse<T extends {} = {}>(
-    response: Record<string, any>,
-  ): T[];
-
-  getStreamingEventResponse(
-    response: Record<string, any>,
-  ): ProviderStreamEvent | undefined {
-    return response.chunk?.bytes
-      ? (JSON.parse(toUtf8(response.chunk?.bytes)) as ProviderStreamEvent)
-      : undefined;
-  }
-
-  async *reduceStream(
-    stream: AsyncIterable<ResponseStream>,
-  ): BedrockChatStreamResponse {
-    yield* streamConverter(stream, (response) => {
-      return {
-        delta: this.getTextFromStreamResponse(response),
-        raw: response,
-      };
-    });
-  }
-
-  getTextFromStreamResponse(response: Record<string, any>): string {
-    return this.getTextFromResponse(response);
-  }
-
-  abstract getRequestBody<T extends ChatMessage>(
-    metadata: LLMMetadata,
-    messages: T[],
-    tools?: BaseTool[],
-    options?: BedrockAdditionalChatOptions,
-  ): InvokeModelCommandInput | InvokeModelWithResponseStreamCommandInput;
-}
-
-class AnthropicProvider extends Provider<StreamEvent> {
-  getResultFromResponse(
-    response: Record<string, any>,
-  ): AnthropicNoneStreamingResponse {
-    return JSON.parse(toUtf8(response.body));
-  }
-
-  getToolsFromResponse<AnthropicToolContent>(
-    response: Record<string, any>,
-  ): AnthropicToolContent[] {
-    const result = this.getResultFromResponse(response);
-    return result.content
-      .filter((item) => item.type === "tool_use")
-      .map((item) => item as AnthropicToolContent);
-  }
-
-  getTextFromResponse(response: Record<string, any>): string {
-    const result = this.getResultFromResponse(response);
-    return result.content
-      .filter((item) => item.type === "text")
-      .map((item) => (item as AnthropicTextContent).text)
-      .join(" ");
-  }
-
-  getTextFromStreamResponse(response: Record<string, any>): string {
-    const event = this.getStreamingEventResponse(response);
-    if (event?.type === "content_block_delta") {
-      if (event.delta.type === "text_delta") return event.delta.text;
-      if (event.delta.type === "input_json_delta")
-        return event.delta.partial_json;
-    }
-    return "";
-  }
-
-  async *reduceStream(
-    stream: AsyncIterable<ResponseStream>,
-  ): BedrockChatStreamResponse {
-    let collecting = [];
-    let tool: ToolBlock | undefined = undefined;
-    // #TODO this should be broken down into a separate consumer
-    for await (const response of stream) {
-      const event = this.getStreamingEventResponse(response);
-      if (
-        event?.type === "content_block_start" &&
-        event.content_block.type === "tool_use"
-      ) {
-        tool = event.content_block;
-        continue;
-      }
-
-      if (
-        event?.type === "content_block_delta" &&
-        event.delta.type === "input_json_delta"
-      ) {
-        collecting.push(event.delta.partial_json);
-      }
-
-      let options: undefined | ToolCallLLMMessageOptions = undefined;
-      if (tool && collecting.length) {
-        const input = collecting.filter((item) => item).join("");
-        // We have all we need to parse the tool_use json
-        if (event?.type === "content_block_stop") {
-          options = {
-            toolCall: [
-              {
-                id: tool.id,
-                name: tool.name,
-                input: JSON.parse(input),
-              } as ToolCall,
-            ],
-          };
-          // reset the collection/tool
-          collecting = [];
-          tool = undefined;
-        } else {
-          options = {
-            toolCall: [
-              {
-                id: tool.id,
-                name: tool.name,
-                input,
-              } as PartialToolCall,
-            ],
-          };
-        }
-      }
-      const delta = this.getTextFromStreamResponse(response);
-      if (!delta && !options) continue;
-
-      yield {
-        delta,
-        options,
-        raw: response,
-      };
-    }
-  }
-
-  getRequestBody<T extends ChatMessage<ToolCallLLMMessageOptions>>(
-    metadata: LLMMetadata,
-    messages: T[],
-    tools?: BaseTool[],
-    options?: BedrockAdditionalChatOptions,
-  ): InvokeModelCommandInput | InvokeModelWithResponseStreamCommandInput {
-    const extra: Record<string, unknown> = {};
-    if (options?.toolChoice) {
-      extra["tool_choice"] = options?.toolChoice;
-    }
-    const mapped = mapChatMessagesToAnthropicMessages(messages);
-    return {
-      modelId: metadata.model,
-      contentType: "application/json",
-      accept: "application/json",
-      body: JSON.stringify({
-        anthropic_version: "bedrock-2023-05-31",
-        messages: mapped,
-        tools: mapBaseToolsToAnthropicTools(tools),
-        max_tokens: metadata.maxTokens,
-        temperature: metadata.temperature,
-        top_p: metadata.topP,
-        ...extra,
-      }),
-    };
-  }
-}
-
-// Other providers could go here
-const PROVIDERS: { [key: string]: Provider } = {
-  anthropic: new AnthropicProvider(),
-};
-
 const getProvider = (model: string): Provider => {
   const providerName = model.split(".")[0];
   if (!(providerName in PROVIDERS)) {
@@ -373,6 +186,10 @@ export class Bedrock extends ToolCallLLM<BedrockAdditionalChatOptions> {
     this.temperature = temperature ?? DEFAULT_BEDROCK_PARAMS.temperature;
     this.topP = topP ?? DEFAULT_BEDROCK_PARAMS.topP;
     this.client = new BedrockRuntimeClient(params);
+
+    if (!this.supportToolCall) {
+      console.warn(`The model "${this.model}" doesn't support ToolCall`);
+    }
   }
 
   get supportToolCall(): boolean {
@@ -402,10 +219,13 @@ export class Bedrock extends ToolCallLLM<BedrockAdditionalChatOptions> {
     );
     const command = new InvokeModelCommand(input);
     const response = await this.client.send(command);
-    const tools = this.provider.getToolsFromResponse(response);
-    const options: ToolCallLLMMessageOptions = tools.length
-      ? { toolCall: tools }
-      : {};
+    let options: ToolCallLLMMessageOptions = {};
+    if (this.supportToolCall) {
+      const tools = this.provider.getToolsFromResponse(response);
+      if (tools.length) {
+        options = { toolCall: tools };
+      }
+    }
     return {
       raw: response,
       message: {
diff --git a/packages/community/src/llm/bedrock/provider.ts b/packages/community/src/llm/bedrock/provider.ts
new file mode 100644
index 000000000..99b615354
--- /dev/null
+++ b/packages/community/src/llm/bedrock/provider.ts
@@ -0,0 +1,59 @@
+import {
+  type InvokeModelCommandInput,
+  type InvokeModelWithResponseStreamCommandInput,
+  ResponseStream,
+} from "@aws-sdk/client-bedrock-runtime";
+import {
+  type BaseTool,
+  type ChatMessage,
+  type ChatResponseChunk,
+  type LLMMetadata,
+  streamConverter,
+  type ToolCallLLMMessageOptions,
+} from "llamaindex";
+import type { ToolChoice } from "./types";
+import { toUtf8 } from "./utils";
+
+export type BedrockAdditionalChatOptions = { toolChoice: ToolChoice };
+
+export type BedrockChatStreamResponse = AsyncIterable<
+  ChatResponseChunk<ToolCallLLMMessageOptions>
+>;
+
+export abstract class Provider<ProviderStreamEvent extends {} = {}> {
+  abstract getTextFromResponse(response: Record<string, any>): string;
+
+  abstract getToolsFromResponse<T extends {} = {}>(
+    response: Record<string, any>,
+  ): T[];
+
+  getStreamingEventResponse(
+    response: Record<string, any>,
+  ): ProviderStreamEvent | undefined {
+    return response.chunk?.bytes
+      ? (JSON.parse(toUtf8(response.chunk?.bytes)) as ProviderStreamEvent)
+      : undefined;
+  }
+
+  async *reduceStream(
+    stream: AsyncIterable<ResponseStream>,
+  ): BedrockChatStreamResponse {
+    yield* streamConverter(stream, (response) => {
+      return {
+        delta: this.getTextFromStreamResponse(response),
+        raw: response,
+      };
+    });
+  }
+
+  getTextFromStreamResponse(response: Record<string, any>): string {
+    return this.getTextFromResponse(response);
+  }
+
+  abstract getRequestBody<T extends ChatMessage>(
+    metadata: LLMMetadata,
+    messages: T[],
+    tools?: BaseTool[],
+    options?: BedrockAdditionalChatOptions,
+  ): InvokeModelCommandInput | InvokeModelWithResponseStreamCommandInput;
+}
diff --git a/packages/community/src/llm/bedrock/providers/anthropic.ts b/packages/community/src/llm/bedrock/providers/anthropic.ts
new file mode 100644
index 000000000..d68c1b721
--- /dev/null
+++ b/packages/community/src/llm/bedrock/providers/anthropic.ts
@@ -0,0 +1,154 @@
+import {
+  type InvokeModelCommandInput,
+  type InvokeModelWithResponseStreamCommandInput,
+  ResponseStream,
+} from "@aws-sdk/client-bedrock-runtime";
+import type {
+  BaseTool,
+  ChatMessage,
+  LLMMetadata,
+  PartialToolCall,
+  ToolCall,
+  ToolCallLLMMessageOptions,
+} from "llamaindex";
+import {
+  type BedrockAdditionalChatOptions,
+  type BedrockChatStreamResponse,
+  Provider,
+} from "../provider";
+import type {
+  AnthropicNoneStreamingResponse,
+  AnthropicStreamEvent,
+  AnthropicTextContent,
+  ToolBlock,
+} from "../types";
+import {
+  mapBaseToolsToAnthropicTools,
+  mapChatMessagesToAnthropicMessages,
+  toUtf8,
+} from "../utils";
+
+export class AnthropicProvider extends Provider<AnthropicStreamEvent> {
+  getResultFromResponse(
+    response: Record<string, any>,
+  ): AnthropicNoneStreamingResponse {
+    return JSON.parse(toUtf8(response.body));
+  }
+
+  getToolsFromResponse<AnthropicToolContent>(
+    response: Record<string, any>,
+  ): AnthropicToolContent[] {
+    const result = this.getResultFromResponse(response);
+    return result.content
+      .filter((item) => item.type === "tool_use")
+      .map((item) => item as AnthropicToolContent);
+  }
+
+  getTextFromResponse(response: Record<string, any>): string {
+    const result = this.getResultFromResponse(response);
+    return result.content
+      .filter((item) => item.type === "text")
+      .map((item) => (item as AnthropicTextContent).text)
+      .join(" ");
+  }
+
+  getTextFromStreamResponse(response: Record<string, any>): string {
+    const event = this.getStreamingEventResponse(response);
+    if (event?.type === "content_block_delta") {
+      if (event.delta.type === "text_delta") return event.delta.text;
+      if (event.delta.type === "input_json_delta")
+        return event.delta.partial_json;
+    }
+    return "";
+  }
+
+  async *reduceStream(
+    stream: AsyncIterable<ResponseStream>,
+  ): BedrockChatStreamResponse {
+    let collecting = [];
+    let tool: ToolBlock | undefined = undefined;
+    // #TODO this should be broken down into a separate consumer
+    for await (const response of stream) {
+      const event = this.getStreamingEventResponse(response);
+      if (
+        event?.type === "content_block_start" &&
+        event.content_block.type === "tool_use"
+      ) {
+        tool = event.content_block;
+        continue;
+      }
+
+      if (
+        event?.type === "content_block_delta" &&
+        event.delta.type === "input_json_delta"
+      ) {
+        collecting.push(event.delta.partial_json);
+      }
+
+      let options: undefined | ToolCallLLMMessageOptions = undefined;
+      if (tool && collecting.length) {
+        const input = collecting.filter((item) => item).join("");
+        // We have all we need to parse the tool_use json
+        if (event?.type === "content_block_stop") {
+          options = {
+            toolCall: [
+              {
+                id: tool.id,
+                name: tool.name,
+                input: JSON.parse(input),
+              } as ToolCall,
+            ],
+          };
+          // reset the collection/tool
+          collecting = [];
+          tool = undefined;
+        } else {
+          options = {
+            toolCall: [
+              {
+                id: tool.id,
+                name: tool.name,
+                input,
+              } as PartialToolCall,
+            ],
+          };
+        }
+      }
+      const delta = this.getTextFromStreamResponse(response);
+      if (!delta && !options) continue;
+
+      yield {
+        delta,
+        options,
+        raw: response,
+      };
+    }
+  }
+
+  getRequestBody<T extends ChatMessage<ToolCallLLMMessageOptions>>(
+    metadata: LLMMetadata,
+    messages: T[],
+    tools?: BaseTool[],
+    options?: BedrockAdditionalChatOptions,
+  ): InvokeModelCommandInput | InvokeModelWithResponseStreamCommandInput {
+    const extra: Record<string, unknown> = {};
+    if (options?.toolChoice) {
+      extra["tool_choice"] = options?.toolChoice;
+    }
+    const mapped = mapChatMessagesToAnthropicMessages(messages);
+    return {
+      modelId: metadata.model,
+      contentType: "application/json",
+      accept: "application/json",
+      body: JSON.stringify({
+        anthropic_version: "bedrock-2023-05-31",
+        messages: mapped,
+        tools: mapBaseToolsToAnthropicTools(tools),
+        max_tokens: metadata.maxTokens,
+        temperature: metadata.temperature,
+        top_p: metadata.topP,
+        ...extra,
+      }),
+    };
+  }
+}
diff --git a/packages/community/src/llm/bedrock/providers/index.ts b/packages/community/src/llm/bedrock/providers/index.ts
new file mode 100644
index 000000000..01ba640d5
--- /dev/null
+++ b/packages/community/src/llm/bedrock/providers/index.ts
@@ -0,0 +1,9 @@
+import { Provider } from "../provider";
+import { AnthropicProvider } from "./anthropic";
+import { MetaProvider } from "./meta";
+
+// Other providers should go here
+export const PROVIDERS: { [key: string]: Provider } = {
+  anthropic: new AnthropicProvider(),
+  meta: new MetaProvider(),
+};
diff --git a/packages/community/src/llm/bedrock/providers/meta.ts b/packages/community/src/llm/bedrock/providers/meta.ts
new file mode 100644
index 000000000..26eacf086
--- /dev/null
+++ b/packages/community/src/llm/bedrock/providers/meta.ts
@@ -0,0 +1,69 @@
+import type {
+  InvokeModelCommandInput,
+  InvokeModelWithResponseStreamCommandInput,
+} from "@aws-sdk/client-bedrock-runtime";
+import type { ChatMessage, LLMMetadata } from "llamaindex";
+import type { MetaNoneStreamingResponse, MetaStreamEvent } from "../types";
+import {
+  mapChatMessagesToMetaLlama2Messages,
+  mapChatMessagesToMetaLlama3Messages,
+  toUtf8,
+} from "../utils";
+
+import { Provider } from "../provider";
+
+export class MetaProvider extends Provider<MetaStreamEvent> {
+  constructor() {
+    super();
+  }
+
+  getResultFromResponse(
+    response: Record<string, any>,
+  ): MetaNoneStreamingResponse {
+    return JSON.parse(toUtf8(response.body));
+  }
+
+  getToolsFromResponse(_response: Record<string, any>): never {
+    throw new Error("Not supported by this provider.");
+  }
+
+  getTextFromResponse(response: Record<string, any>): string {
+    const result = this.getResultFromResponse(response);
+    return result.generation;
+  }
+
+  getTextFromStreamResponse(response: Record<string, any>): string {
+    const event = this.getStreamingEventResponse(response);
+    if (event?.generation) {
+      return event.generation;
+    }
+    return "";
+  }
+
+  getRequestBody<T extends ChatMessage>(
+    metadata: LLMMetadata,
+    messages: T[],
+  ): InvokeModelCommandInput | InvokeModelWithResponseStreamCommandInput {
+    let promptFunction: (messages: ChatMessage[]) => string;
+
+    if (metadata.model.startsWith("meta.llama3")) {
+      promptFunction = mapChatMessagesToMetaLlama3Messages;
+    } else if (metadata.model.startsWith("meta.llama2")) {
+      promptFunction = mapChatMessagesToMetaLlama2Messages;
+    } else {
+      throw new Error(`Meta model ${metadata.model} is not supported`);
+    }
+
+    return {
+      modelId: metadata.model,
+      contentType: "application/json",
+      accept: "application/json",
+      body: JSON.stringify({
+        prompt: promptFunction(messages),
+        max_gen_len: metadata.maxTokens,
+        temperature: metadata.temperature,
+        top_p: metadata.topP,
+      }),
+    };
+  }
+}
diff --git a/packages/community/src/llm/bedrock/types.ts b/packages/community/src/llm/bedrock/types.ts
index 8a02d5db4..a72554c73 100644
--- a/packages/community/src/llm/bedrock/types.ts
+++ b/packages/community/src/llm/bedrock/types.ts
@@ -79,7 +79,7 @@ export type ToolChoice =
   | { type: "auto" }
   | { type: "tool"; name: string };
 
-export type StreamEvent =
+export type AnthropicStreamEvent =
   | { type: "message_start"; message: Message }
   | ContentBlockStart
   | ContentBlockDelta
@@ -93,6 +93,8 @@ export type AnthropicContent =
   | AnthropicToolContent
   | AnthropicToolResultContent;
 
+export type MetaTextContent = string;
+
 export type AnthropicTextContent = {
   type: "text";
   text: string;
@@ -133,6 +135,11 @@ export type AnthropicMessage = {
   content: AnthropicContent[];
 };
 
+export type MetaMessage = {
+  role: "user" | "assistant" | "system";
+  content: MetaTextContent;
+};
+
 export type AnthropicNoneStreamingResponse = {
   id: string;
   type: "message";
@@ -143,3 +150,16 @@ export type AnthropicNoneStreamingResponse = {
   stop_sequence?: string;
   usage: { input_tokens: number; output_tokens: number };
 };
+
+type MetaResponse = {
+  generation: string;
+  prompt_token_count: number;
+  generation_token_count: number;
+  stop_reason: "stop" | "length";
+};
+
+export type MetaStreamEvent = MetaResponse & {
+  "amazon-bedrock-invocationMetrics": InvocationMetrics;
+};
+
+export type MetaNoneStreamingResponse = MetaResponse;
diff --git a/packages/community/src/llm/bedrock/utils.ts b/packages/community/src/llm/bedrock/utils.ts
index 64bbdda1e..c301d16b2 100644
--- a/packages/community/src/llm/bedrock/utils.ts
+++ b/packages/community/src/llm/bedrock/utils.ts
@@ -4,6 +4,7 @@ import type {
   JSONObject,
   MessageContent,
   MessageContentDetail,
+  MessageContentTextDetail,
   ToolCallLLMMessageOptions,
   ToolMetadata,
 } from "llamaindex";
@@ -13,6 +14,7 @@ import type {
   AnthropicMediaTypes,
   AnthropicMessage,
   AnthropicTextContent,
+  MetaMessage,
 } from "./types.js";
 
 const ACCEPTED_IMAGE_MIME_TYPES = [
@@ -148,6 +150,85 @@ export const mapChatMessagesToAnthropicMessages = <
   return mergeNeighboringSameRoleMessages(mapped);
 };
 
+export const mapChatMessagesToMetaMessages = <T extends ChatMessage>(
+  messages: T[],
+): MetaMessage[] => {
+  return messages.map((msg) => {
+    let content: string;
+    if (typeof msg.content === "string") {
+      content = msg.content;
+    } else {
+      content = (msg.content[0] as MessageContentTextDetail).text;
+    }
+    return {
+      role:
+        msg.role === "assistant"
+          ? "assistant"
+          : msg.role === "user"
+            ? "user"
+            : "system",
+      content,
+    };
+  });
+};
+
+/**
+ * Documentation at https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3
+ */
+export const mapChatMessagesToMetaLlama3Messages = <T extends ChatMessage>(
+  messages: T[],
+): string => {
+  const mapped = mapChatMessagesToMetaMessages(messages).map((message) => {
+    const text = message.content;
+    return `<|start_header_id|>${message.role}<|end_header_id|>\n${text}\n<|eot_id|>\n`;
+  });
+  return (
+    "<|begin_of_text|>" +
+    mapped.join("\n") +
+    "\n<|start_header_id|>assistant<|end_header_id|>\n"
+  );
+};
+
+/**
+ * Documentation at https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-2
+ */
+export const mapChatMessagesToMetaLlama2Messages = <T extends ChatMessage>(
+  messages: T[],
+): string => {
+  const mapped = mapChatMessagesToMetaMessages(messages);
+  let output = "<s>";
+  let insideInst = false;
+  let needsStartAgain = false;
+  for (const message of mapped) {
+    if (needsStartAgain) {
+      output += "<s>";
+      needsStartAgain = false;
+    }
+    const text = message.content;
+    if (message.role === "system") {
+      if (!insideInst) {
+        output += "[INST] ";
+        insideInst = true;
+      }
+      output += `<<SYS>>\n${text}\n<</SYS>>\n`;
+    } else if (message.role === "user") {
+      output += text;
+      if (insideInst) {
+        output += " [/INST]";
+        insideInst = false;
+      }
+    } else if (message.role === "assistant") {
+      if (insideInst) {
+        output += " [/INST]";
+        insideInst = false;
+      }
+      output += ` ${text} </s>\n`;
+      needsStartAgain = true;
+    }
+  }
+  return output;
+};
+
 export const mapTextContent = (text: string): AnthropicTextContent => {
   return { type: "text", text };
 };
-- 
GitLab