亚马逊AWS官方博客

基于 Amazon Bedrock + Claude3 快速构建 Serverless GenAI 应用

背景和目的

2024 年 3 月,OpenAI 的主要竞争对手之一 Anthropic 推出了最新的 Claude3 大语言模型系列:Claude 3 Haiku、Claude 3 Sonnet 和 Claude 3 Opus。Claude3 支持 20 万(200k)token 长度(相当于大约 15 万个单词,或超过 500 页的材料),作为多模态大模型,在推理、数学、编码、多语言理解以及视觉方面都有新的行业标杆作用。作为 Anthropic 的主要投资者,亚马逊云科技也在 Bedrock 服务里对 Claude3 迅速进行了集成。Anthropic 创立的初衷是为了创建世界上最安全、最强大的大型语言模型。Claude 是 Anthropic 开发的前沿、先进的大型语言模型,为企业提供速度、成本和上下文窗口等重要功能。

亚马逊云科技的 Bedrock 服务被视为云计算时代的”AI 操作系统”,集成了多种先进的大模型和工具链,并且第一时间对 Claude3 模型进行了集成。Claude3 的加入无疑将进一步增强 Bedrock 在企业级 AI 服务方面的竞争力。

为了让开发者可以快速体验和上手 GenAI 领域的最新成果,本文提供了一个易于上手的开源解决方案,为您揭示如何借助 Amazon Bedrock 和 Claude3 大语言模型,快速构建和部署一款功能强大的 GenAI(生成式人工智能)应用程序。通过提示工程(Prompt Engineering,简称 PE)并结合多个典型应用场景,充分挖掘 Claude3 强大的自然语言处理能力,包括智能聊天、文本翻译、内容汇总、代码编写、图片识别等应用场景,以充分体验 Claude3 的功能。

系统组成

该系统通过组合亚马逊云科技的服务,如 Amazon Bedrock,Amazon Lambda, Amazon APIGateway 和 Amazon CDK 来实现功能,主要包括:

  • WebUI 部分

前端采用 Gradio 库实现,这是一个用于构建机器学习模型交互界面的 Python 库。它使用纯 Python 编写,能快速创建易用且易于分享的应用程序。

  • 业务逻辑部分

后端使用 Lambda 来构建,实现与 Amazon Bedrock 的交互,LLM 默认使用 Anthropic Claude3 Sonnet and Haiku 模型,用户也可以更改其它 Claude 模型进行体验。

  • 对外服务接口部分

使用 Amazon API Gateway 为用户提供 API,并且为了方便原有 OpenAI 用户的使用便利性,用户可以使用 OpenAI 的接口格式,后台能够自动转换输入和输出格式,适配 Open AI 的格式。

  • 部署部分

整个方案可以使用 Amazon CDK 进行一键部署。

系统架构

部署指南

先决条件

  • AWS 账号和相应的 IAM 权限

安装 aws cli,具体步骤见官方文档

aws configuration,设置 AK/SK 和 Region
  • 安装 Node.js
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install –lts
node -v
npm -v

后端 API 部署说明

  • 克隆开源工程至本地
  • 进入工程目录
cd aws-claude3-ui 
  • 安装 Amazon CDK
npm install -g aws-cdk
  • 运行以下命令以验证安装是否成功。 AWS CDK CLI 应该输出版本号:
cdk --version
  • 安装依赖
npm i
  • 部署后端到您的默认 AWS 帐户/地区,获取输出 API 网关输出的 URL:
cdk deploy

前端应用部署说明

  • 编辑 ui 目录下的 .env 文件,配置 API_SERVER 变量为上述 Amazon ApiGateway 的服务地址,形如:
  • 编译 docker 镜像
    docker build -t claude-ui .
  • 运行容器
    docker run --env-file .env  -p 5006:5006  claude-ui
  • 应用运行时可以通过 Setting 页面修改 API server 以及 Model ID 参数

后端 Lambda 函数实现

Lambda 代码实现了一个中间层服务,可以接受 OpenAI 或 Anthropic 的 Claude 模型格式请求,并通过调用 AWS Bedrock Runtime 服务来执行模型推理并返回结果。它还提供了消息数据格式转换的功能,以支持不同的模型输入格式。

  1. openaiToClaudeParams 函数用于将 OpenAI 格式的消息数据转换为 Claude 格式。它会过滤掉 rolesystem 的消息对象,并将图像 URL 转换为 Base64 编码的图像数据。
  2. claudeToChatgptResponseStream 函数用于将 Claude 模型的响应结果转换为 OpenAI 的响应格式。
  3. 根据请求路径判断是 Claude 模型还是 OpenAI 模型。如果是 Claude 模型,则直接使用请求体中的消息数据;如果是 OpenAI 模型,则调用 openaiToClaudeParams 函数进行格式转换。
  4. 构建 InvokeModelCommandInput 对象,其中包含了模型 ID、请求内容类型、最大生成 token 数、温度等参数。
  5. 使用 BedrockRuntimeClient 调用 AWS Bedrock Runtime 服务,执行模型推理。
  6. 根据是否为 Claude 模型,调用 claudeToChatgptResponseStream 函数或直接返回 Claude 模型的响应结果。
    import {
      BedrockRuntimeClient,
      InvokeModelCommand,
      InvokeModelCommandInput,
    } from "@aws-sdk/client-bedrock-runtime";
    import { Handler } from "aws-lambda";
    
    function openaiToClaudeParams(messages) {
      messages = messages.filter((message) => message.role !== "system");
      messages.forEach((message) => {
        if (message.content && typeof message.content !== "string") {
          message.content.forEach((item) => {
            if (item.type === "image_url") {
              const imageUrl = item.image_url.url;
              const base64Image = imageUrl.substring(
                imageUrl.indexOf("{") + 1,
                imageUrl.indexOf("}")
              );
              item.type = "image";
              item.source = {
                type: "base64",
                media_type: "image/jpeg",
                data: base64Image,
              };
              delete item.image_url;
            }
          });
        }
      });
      return messages;
    }
    function claudeToChatgptResponseStream(claudeFormat) {
      const obj2Data = {
        choices: [
          {
            finish_reason: "stop",
            index: 0,
            message: {
              content: claudeFormat.content[0].text,
              role: claudeFormat.role,
            },
            logprobs: null,
          },
        ],
        created: Math.floor(Date.now() / 1000), // 使用当前时间作为创建时间,单位为秒
        id: claudeFormat.id,
        model: claudeFormat.model,
        object: "chat.completion",
        usage: {
          completion_tokens: claudeFormat.usage.output_tokens,
          prompt_tokens: claudeFormat.usage.input_tokens,
          total_tokens:
            claudeFormat.usage.input_tokens + claudeFormat.usage.output_tokens,
        },
      };
      return obj2Data;
    }
    
    export const handler: Handler = async (event, context) => {
      const path = event.path;
      const isClaude = path === "/v1/messages" ? true : false;
      const badResponse = {
        statusCode: 400,
        body: JSON.stringify("Invalid request!"),
      };
    
      if (event.body && event.body !== "") {
        let body = JSON.parse(event.body);
        if (body.model && body.messages && body.messages.length > 0) {
          let system = body.system;
          if (body.messages[0].role === "system") system = body.messages[0].content;
          let convertedMessages = isClaude
            ? body.messages
            : openaiToClaudeParams(body.messages);
          console.log("begin invoke message", convertedMessages);
          if (convertedMessages.length <= 0) return badResponse;
          let max_tokens = body.max_tokens || 1000;
          let top_p = body.top_p || 1;
          let top_k = body.top_k || 250;
          let modelId = "anthropic.claude-3-sonnet-20240229-v1:0";
          if (body.model.startsWith("anthropic")) modelId = body.model;
          let temperature = body.temperature || 0.5;
          const contentType = "application/json";
          const rockerRuntimeClient = new BedrockRuntimeClient({
            region: process.env.REGION,
          });
    
          const inputCommand: InvokeModelCommandInput = {
            modelId,
            contentType,
            accept: contentType,
            body: system
              ? JSON.stringify({
                  anthropic_version: "bedrock-2023-05-31",
                  max_tokens: max_tokens,
                  temperature: temperature,
                  top_k: top_k,
                  top_p: top_p,
                  system: system,
                  messages: convertedMessages,
                })
              : JSON.stringify({
                  anthropic_version: "bedrock-2023-05-31",
                  max_tokens: max_tokens,
                  temperature: temperature,
                  top_k: top_k,
                  top_p: top_p,
                  messages: convertedMessages,
                }),
          };
    
          const command = new InvokeModelCommand(inputCommand);
          const response = await rockerRuntimeClient.send(command);
          const result = {
            statusCode: 200,
            headers: {
              "Content-Type": `${contentType}`,
            },
            body: isClaude
              ? JSON.stringify(JSON.parse(new TextDecoder().decode(response.body)))
              : JSON.stringify(
                  claudeToChatgptResponseStream(
                    JSON.parse(new TextDecoder().decode(response.body))
                  ),
                  null,
                  2
                ),
          };
          console.log("invoke success response", result);
          return result;
        } else {
          return badResponse;
        }
      } else {
        return badResponse;
      }
    };
    

API 接口示例

HTTP 接口说明

请求 回应 场景

curl -X POST -k -H ‘Content-Type: application/json’ -i ‘https://api_gateway_url/v1/messages’ –data ‘{

“model”: “anthropic.claude-3-sonnet-20240229-v1:0”,

“max_tokens”: 1024,

“top_k”:1,

“temperature”:0.5,

“messages”: [

{“role”: “user”, “content”: “Hello, Claude”}

]

}’

{

“id”: “msg_01XyWaKwckzDSNhjSrpEA73p”,

“type”: “message”,

“role”: “assistant”,

“content”: [

{

“type”: “text”,

“text”: “Hello! It’s nice to meet you. How can I assist you today?”

}

],

“model”: “claude-3-sonnet-28k-20240229”,

“stop_reason”: “end_turn”,

“stop_sequence”: null,

“usage”: {

“input_tokens”: 20,

“output_tokens”: 19

}

}

以 claude 格式发送请求

curl -X POST -k -H ‘Content-Type: application/json’ -i ‘https://api_gateway_url/v1/chat/completions’ –data ‘{

“model”: “anthropic.claude-3-sonnet-20240229-v1:0”,

“max_tokens”: 1024,

“top_k”:1,

“temperature”:0.5,

“messages”: [

{“role”: “user”, “content”: “Hello, Claude”}

]

}’

{

“choices”: [

{

“finish_reason”: “stop”,

“index”: 0,

“message”: {

“content”: “Hello! It’s nice to meet you. How can I assist you today?”,

“role”: “assistant”

},

“logprobs”: null

}

],

“created”: 1709880244,

“id”: “msg_013gmWn1hnxqzAB2ku56u818”,

“model”: “claude-3-sonnet-28k-20240229”,

“object”: “chat.completion”,

“usage”: {

“completion_tokens”: 19,

“prompt_tokens”: 10,

“total_tokens”: 29

}

}

以 OpenAI 格式发送请求

GenAI 应用示例

我们采用 Gradio 快速构建了一个界面友好的 GenAI 演示程序 AIToolBox,让读者可以方便地体验 Claude3 系列模型的实用性,对 GenAI 应用开发有更直观的认识。

读者可以从代码库 aws-claude3-ui/ui/ 下获取本示例应用的代码,其中 llm 目录包含与 LLM 进行交互的函数实现,view 目录下包含基于 Gradio 构建的用户交互界面。

Chatbot 场景

在聊天机器人场景中,Claude3 系列模型减少了幻觉,并提高了回答的准确性。在“Chatbot”页面,我们构建了一个基于 Claude3 的聊天应用示例,用户可以通过发送文本或者图片与 Claude3 进行互动,运行效果如下图所示:

在实现上,默认对聊天的历史对话进行保存,以达到多轮会话的效果,用户可以通过“Forget All”按钮清除聊天记录以开启新一轮对话。

同时,我们利用 PE(提示词工程)实现了简单的风格化,通过在 system prompt 中添加语言风格描述,让 chatbot 的回复具备特定风格,如幽默、理性、可爱等。

文本处理场景

文本处理方面,Claude3 系列模型展现出了强大的多语言能力,在处理长文本方面的能力得到了显著提升。本示例应用中我们演示了 文本翻译,写作辅助,内容汇总 几个场景,详见“Translate”,“Rewrite”,“Summary”页面:

除此之外,Claude3 在文案创意、小说创作,论文撰写、商务文稿等方面都可以提供优质的文字输出和修改建议,提升生产效率,有待读者自行体验。

图片识别场景

图片识别方面,Claude3 作为多模态大模型,具有强大的“视觉能力”,在理解包括照片、图表、图形和技术示意图在内的各种视觉格式方面表现出很强的能力。

我们可以在示例应用的“Vision”页面进行体验,从本地上传任意一幅图片,然后在文本框里输入具体的任务描述作为 user prompt,让模型执行诸如识别图片内文字、场景理解、颜色分析、生成图像元数据等任务。

代码开发场景

代码开发场景中,在“Code”界面我们构建了一个开发助理应用,通过调用 Claude3  模型生成可以运行的代码,可以支持多语言编程,比如常用的 Python, JavaScript/TypeScript,Golang,Rust 等。

为了让 LLM 输出的代码质量更加稳定高效,我们进行了 PE(提示词工程)优化,让 LLM 分别扮演两个角色完成开发工作:先作为软件架构师,基于用户的原始需求生成代码框架,再作为经验丰富的开发人员基于代码框架编写最终的代码。

采用的 System prompt 详见本项目代码:aws-claude3-ui/ui/llm/code.py

# Define system prompt for software architect
    system_arch = """
        You are an experienced solution architect at a software company. 
        Your task is to help users design excellent code framework architectures as references for developers according to the human's requirements.
        """

# Define system prompt for coder
    system_coder = f"""
        You are an experienced developer in {program_language}.
        Your task is to generate high-quality code according to given instructions, and provide a concise explanation at the end.
        Make sure to include any imports required, and add comments for things that are non-obvious.
        NEVER write anything before the code.
        After you are done generating the code, check your work carefully to make sure there are no mistakes, errors, or inconsistencies. 
        If there are errors, list those errors in <error> tags, then generate a new version with those errors fixed. 
        If there are no errors, write "CHECKED: NO ERRORS" in <error> tags.

JSON 格式化

在实际应用中,我们经常需要 LLM 以 JSON 格式输出结果,通常需要为此进行大量的 PE 优化才能达到目的。Claude3 模型更擅长以 JSON 等格式生成流行的结构化输出,从而可以更轻松地指导 Claude 进行自然语言分类和情感分析等用例。

在示例应用的“Formatter”页面,构建了一个 JSON/YAML 转换器程序,让 Claude3 对输入的任意文本进行理解分析,从中提取出对象和属性,构造成结构化的 JSON 或者 YAML 格式进行输出。

最后

本文介绍了如何快速构建和部署一个基于 Amazon Bedrock + Claude3 模型的 GenAI 应用程序,并结合多个典型应用场景,介绍了 PE(提示工程)的最佳实践,让广大开发者可以充分利用 Claude3 大语言模型的能力,打造自己的生成式人工智能解决方案。

本篇作者

贺杨

亚马逊云科技解决方案架构师,具备 17 年 IT 专业服务经验,工作中担任过研发、开发经理、解决方案架构师等多种角色。在加入亚马逊云科技前,拥有多年外企研发和售前架构经验,在传统企业架构和中间件解决方案有深入的理解和丰富的实践经验。

肖学嵩

亚马逊云科技初创生态解决方案架构师,15 年 IT 从业经验,现主要负责云计算方案架构的咨询和设计,以技术赋能助力初创企业成长。拥有多年企业级数据中心系统架构设计和实施经验,曾任职于 Sinodata,IBM,Lenovo 等厂商,并先后在私有云和大数据领域有从 0 到 1 的创业经历。

Kelvin Guo

亚马逊云科技资深解决方案架构师。主要技术方向为 MLOps,DevOps,容器,数据分析。20+年软件开发,项目管理,敏捷思想落地,工程效能咨询和落地经验。