Bedrock + Claude3 快速构建 Serverless GenAI 应用

目录

背景和目的

系统组成

系统架构

部署指南

先决条件

后端 API 部署说明

前端应用部署说明

后端 Lambda 函数实现

API 接口示例

HTTP 接口说明

GenAI 应用示例

Chatbot 场景

文本处理场景

图片识别场景

代码开发场景

JSON 格式化


背景和目的

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 部署说明

  • 克隆开源工程至本地
git clone GitHub - aws-samples/aws-bedrock-claude3-ui
  • 进入工程目录
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 的服务地址,形如:
    API_SERVER=https://xxxxxx.excute-api.xxxxxxx.amazonaws.com/prod/v1/messages
  • 编译 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 格式。它会过滤掉 role 为 system 的消息对象,并将图像 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 格式进行输出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值