diff --git a/.gitignore b/.gitignore index d8b355e..ef154c9 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ -Config \ No newline at end of file +Config +tests/request.http diff --git a/README.md b/README.md index 859f296..1c80cd0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ -[中文](./README_CN.md) +# Bedrock Access Gateway with several models for fail safe -# Bedrock Access Gateway - -OpenAI-compatible RESTful APIs for Amazon Bedrock +**Modified from AWS Samples repository:** OpenAI-compatible RESTful APIs for Amazon Bedrock that will change models if it encounters the lately very recurrent error: +```bash +Code: 500 +Message: BedrockException: Rate Limit Error +``` ## Breaking Changes -The source code is refactored with the new [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) by bedrock which provides native support with tool calls. - -If you are facing any problems, please raise an issue. - +This function switches from a set of predefined Bedrock Models when it encounters the aforementioned error until it produces a 200 success code. ## Overview @@ -43,7 +42,15 @@ Supported Amazon Bedrock models family: You can call the `models` API to get the full list of model IDs supported. -> **Note:** The default model is set to `anthropic.claude-3-sonnet-20240229-v1:0` which can be changed via Lambda environment variables (`DEFAULT_MODEL`). +> **Note:** The default model list is set as follows using Lambda Environment variables, you can modify them after deployment in the Lambda function directly: +```bash + + "DEFAULT_MODEL" = "anthropic.claude-3-sonnet-20240229-v1:0" + "MODEL_2" = "anthropic.claude-3-5-sonnet-20240620-v1:0" + "MODEL_3" = "meta.llama3-1-405b-instruct-v1:0" + "MODEL_4" = "anthropic.claude-3-sonnet-20240229-v1:0" + +``` ## Get Started @@ -85,24 +92,20 @@ Please follow the steps below to deploy the Bedrock Proxy APIs into your AWS acc 5. Click "Create parameter". 6. Make a note of the parameter name you used (e.g., "BedrockProxyAPIKey"). You'll need this in the next step. -**Step 2: Deploy the CloudFormation stack** +**Step 2: Create your Docker Image and deploy to Amazon ECR** + +7. You can use the following script to create the Docker image and push it to ECR: `scripts/push-to-ecr.sh` -1. Sign in to AWS Management Console, switch to the region to deploy the CloudFormation Stack to. -2. Click the following button to launch the CloudFormation Stack in that region. Choose one of the following: - - **ALB + Lambda** +**Step 3: Create and deploy your CloudFormation template** - [![Launch Stack](assets/launch-stack.png)](https://console.aws.amazon.com/cloudformation/home#/stacks/create/template?stackName=BedrockProxyAPI&templateURL=https://aws-gcr-solutions.s3.amazonaws.com/bedrock-access-gateway/latest/BedrockProxy.template) - - **ALB + Fargate** - - [![Launch Stack](assets/launch-stack.png)](https://console.aws.amazon.com/cloudformation/home#/stacks/create/template?stackName=BedrockProxyAPI&templateURL=https://aws-gcr-solutions.s3.amazonaws.com/bedrock-access-gateway/latest/BedrockProxyFargate.template) -3. Click "Next". -4. On the "Specify stack details" page, provide the following information: +8. Use `deployment/BedrockProxy.template` or `deployment/BedrockProxyFargate.template` to deploy to AWS Cloudfront. +9. On the "Specify stack details" page, provide the following information: - Stack name: Change the stack name if needed. - ApiKeyParam (if you set up an API key in Step 1): Enter the parameter name you used for storing the API key (e.g., `BedrockProxyAPIKey`). If you did not set up an API key, leave this field blank. Click "Next". -5. On the "Configure stack options" page, you can leave the default settings or customize them according to your needs. -6. Click "Next". -7. On the "Review" page, review the details of the stack you're about to create. Check the "I acknowledge that AWS CloudFormation might create IAM resources" checkbox at the bottom. -8. Click "Create stack". +10. On the "Configure stack options" page, you can leave the default settings or customize them according to your needs. +11. Click "Next". +12. On the "Review" page, review the details of the stack you're about to create. Check the "I acknowledge that AWS CloudFormation might create IAM resources" checkbox at the bottom. +13. Click "Create stack". That is it! 🎉 Once deployed, click the CloudFormation stack and go to **Outputs** tab, you can find the API Base URL from `APIBaseUrl`, the value should look like `http://xxxx.xxx.elb.amazonaws.com/api/v1`. diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index 4556304..0000000 --- a/README_CN.md +++ /dev/null @@ -1,267 +0,0 @@ -[English](./README.md) - -# Bedrock Access Gateway - -使用兼容OpenAI的API访问Amazon Bedrock - -## 重大变更 - -项目源代码已使用Bedrock提供的新 [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) 进行了重构,该API对工具调用提供了原生支持。 - -如果您遇到任何问题,请提 Github Issue。 - -## 概述 - -Amazon Bedrock提供了广泛的基础模型(如Claude 3 Opus/Sonnet/Haiku、Llama 2/3、Mistral/Mixtral等),以及构建生成式AI应用程序的多种功能。更多详细信息,请查看[Amazon -Bedrock](https://aws.amazon.com/bedrock)。 - -有时,您可能已经使用OpenAI的API或SDK构建了应用程序,并希望在不修改代码的情况下试用Amazon -Bedrock的模型。或者,您可能只是希望在AutoGen等工具中评估这些基础模型的功能。 好消息是, 这里提供了一种方便的途径,让您可以使用 -OpenAI 的 API 或 SDK 无缝集成并试用 Amazon Bedrock 的模型,而无需对现有代码进行修改。 - -如果您觉得这个项目有用,请考虑给它点个一个免费的小星星 ⭐。 - -功能列表: - -- [x] 支持 server-sent events (SSE)的流式响应 -- [x] 支持 Model APIs -- [x] 支持 Chat Completion APIs -- [x] 支持 Tool Call (**new**) -- [x] 支持 Embedding API (**new**) -- [x] 支持 Multimodal API (**new**) - -请查看[使用指南](./docs/Usage_CN.md)以获取有关如何使用新API的更多详细信息。 - -> 注意: 不支持旧的 [text completion](https://platform.openai.com/docs/api-reference/completions) API,请更改为使用Chat Completion API。 - -支持的Amazon Bedrock模型家族: - -- Anthropic Claude 2 / 3 (Haiku/Sonnet/Opus) -- Meta Llama 2 / 3 -- Mistral / Mixtral -- Cohere Command R / R+ -- Cohere Embedding - -你可以先调用`models` API 获取支持的详细 model ID 列表。 - -> 注意: 默认模型为 `anthropic.claude-3-sonnet-20240229-v1:0`, 可以通过更改Lambda环境变量进行更改。 - -## 使用指南 - -### 前提条件 - -请确保您已满足以下先决条件: - -- 可以访问Amazon Bedrock基础模型。 - -如果您还没有获得模型访问权限,请参考[配置](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html)指南。 - -### 架构图 - -下图展示了本方案的参考架构。请注意,它还包括一个新的**VPC**,其中只有两个公共子网用于应用程序负载均衡器(ALB)。 - -![Architecture](assets/arch.svg) - -您也可以选择在 ALB 后面接 [AWS Fargate](https://aws.amazon.com/fargate/) 而不是 [AWS Lambda](https://aws.amazon.com/lambda/),主要区别在于流响应的首字节延迟(Fargate更低)。 - -或者,您可以使用 Lambda Function URL 来代替 ALB,请参阅[示例](https://github.com/awslabs/aws-lambda-web-adapter/tree/main/examples/fastapi-response-streaming) - -### 部署 - -请按以下步骤将Bedrock代理API部署到您的AWS账户中。仅支持Amazon Bedrock可用的区域(如us-west-2)。 部署预计用时**3-5分钟** 🕒。 - -**第一步: 自定义您的API Key (可选)** - -> 注意:这一步是使用任意字符串(不带空格)创建一个自定义的API Key(凭证),将用于后续访问代理API。此API Key不必与您实际的OpenAI -> Key一致,您甚至无需拥有OpenAI API Key。建议您执行此步操作并且请确保保管好此API Key。 - -1. 打开AWS管理控制台,导航到Systems Manager服务。 -2. 在左侧导航窗格中,单击"参数存储"。 -3. 单击"创建参数"按钮。 -4. 在"创建参数"窗口中,选择以下选项: - - 名称:输入参数的描述性名称(例如"BedrockProxyAPIKey")。 - - 描述:可选,为参数提供描述。 - - 层级:选择**标准**。 - - 类型:选择**SecureString**。 - - 值: 随意字符串(不带空格)。 -5. 单击"创建参数"。 -6. 记录您使用的参数名称(例如"BedrockProxyAPIKey")。您将在下一步中需要它。 - -**第二步: 部署CloudFormation堆栈** - -1. 登录AWS管理控制台,切换到要部署CloudFormation堆栈的区域。 -2. 单击以下按钮在该区域启动CloudFormation堆栈,选择一种方式部署。 - - **ALB + Lambda** - - [![Launch Stack](assets/launch-stack.png)](https://console.aws.amazon.com/cloudformation/home#/stacks/create/template?stackName=BedrockProxyAPI&templateURL=https://aws-gcr-solutions.s3.amazonaws.com/bedrock-access-gateway/latest/BedrockProxy.template) - - **ALB + Fargate** - - [![Launch Stack](assets/launch-stack.png)](https://console.aws.amazon.com/cloudformation/home#/stacks/create/template?stackName=BedrockProxyAPI&templateURL=https://aws-gcr-solutions.s3.amazonaws.com/bedrock-access-gateway/latest/BedrockProxyFargate.template) -3. 单击"下一步"。 -4. 在"指定堆栈详细信息"页面,提供以下信息: - - 堆栈名称: 可以根据需要更改名称。 - - ApiKeyParam(如果在步骤1中设置了API Key):输入您用于存储API密钥的参数名称(例如"BedrockProxyAPIKey"),否则,请将此字段留空。 - 单击"下一步"。 -5. 在"配置堆栈选项"页面,您可以保留默认设置或根据需要进行自定义。 -6. 单击"下一步"。 -7. 在"审核"页面,查看您即将创建的堆栈详细信息。勾选底部的"我确认,AWS CloudFormation 可能创建 IAM 资源。"复选框。 -8. 单击"创建堆栈"。 - -仅此而已 🎉 。部署完成后,点击CloudFormation堆栈,进入"输出"选项卡,你可以从"APIBaseUrl" -中找到API Base URL,它应该类似于`http://xxxx.xxx.elb.amazonaws.com/api/v1` 这样的格式。 - -### SDK/API使用 - -你只需要API Key和API Base URL。如果你没有设置自己的密钥,那么默认将使用API Key `bedrock`。 - -现在,你可以尝试使用代理API了。假设你想测试Claude 3 Sonnet模型,那么使用"anthropic.claude-3-sonnet-20240229-v1:0"作为模型ID。 - -- **API 使用示例** - -```bash -export OPENAI_API_KEY= -export OPENAI_BASE_URL= -# 旧版本请使用OPENAI_API_BASE -# https://github.com/openai/openai-python/issues/624 -export OPENAI_API_BASE= -``` - -```bash -curl $OPENAI_BASE_URL/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "anthropic.claude-3-sonnet-20240229-v1:0", - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }' -``` - -- **SDK 使用示例** - -```python -from openai import OpenAI - -client = OpenAI() -completion = client.chat.completions.create( - model="anthropic.claude-3-sonnet-20240229-v1:0", - messages=[{"role": "user", "content": "Hello!"}], -) - -print(completion.choices[0].message.content) -``` - -请查看[使用指南](./docs/Usage_CN.md)以获取有关如何使用Embedding API、多模态API和Tool Call的更多详细信息。 - -## 其他例子 - -### AutoGen - -例如在AutoGen studio配置和使用模型 - -![AutoGen Model](assets/autogen-model.png) - -### LangChain - -请确保使用的示`ChatOpenAI(...)` ,而不是`OpenAI(...)` - -```python -# pip install langchain-openai -import os - -from langchain.chains import LLMChain -from langchain.prompts import PromptTemplate -from langchain_openai import ChatOpenAI - -chat = ChatOpenAI( - model="anthropic.claude-3-sonnet-20240229-v1:0", - temperature=0, - openai_api_key=os.environ['OPENAI_API_KEY'], - openai_api_base=os.environ['OPENAI_BASE_URL'], -) - -template = """Question: {question} - -Answer: Let's think step by step.""" - -prompt = PromptTemplate.from_template(template) -llm_chain = LLMChain(prompt=prompt, llm=chat) - -question = "What NFL team won the Super Bowl in the year Justin Beiber was born?" -response = llm_chain.invoke(question) -print(response) - -``` - -## FAQs - -### 关于隐私 - -这个方案不会收集您的任何数据。而且,它默认情况下也不会记录任何请求或响应。 - -### 为什么没有使用API Gateway 而是使用了Application Load Balancer? - -简单的答案是API Gateway不支持 server-sent events (SSE) 用于流式响应。 - -### 支持哪些区域? - -只支持Amazon Bedrock可用的区域, 截至当前,包括以下区域: - -- 美国东部(弗吉尼亚北部):us-east-1 -- 美国西部(俄勒冈州):us-west-2 -- 亚太地区(新加坡):ap-southeast-1 -- 亚太地区(悉尼):ap-southeast-2 -- 亚太地区(东京):ap-northeast-1 -- 欧洲(法兰克福):eu-central-1 -- 欧洲(巴黎):eu-west-3 - -通常来说,所有Amazon Bedrock支持的区域都支持,如果不支持,请提个Github Issue。 - -注意,并非所有模型都在上面区可用。 - -### 我可以构建并使用自己的ECR镜像吗? - -是的,你可以克隆repo并自行构建容器镜像(src/Dockerfile),然后推送到你自己的ECR仓库。 脚本可以参考`scripts/push-to-ecr.sh`。 - -在部署之前,请在CloudFormation模板中替换镜像仓库URL。 - -### 我可以在本地运行吗? - -是的,你可以在本地运行,那么API Base URL应该类似于`http://localhost:8000/api/v1` - -### 使用代理API会有任何性能牺牲或延迟增加吗? - -与 AWS SDK 调用相比,本方案参考架构会在响应上会有额外的延迟,你可以自己部署并测试。 - -另外,你也可以使用 Lambda Web Adapter + Function URL ( -参见 [示例](https://github.com/awslabs/aws-lambda-web-adapter/tree/main/examples/fastapi-response-streaming))来代替 ALB -或使用 AWS Fargate 来代替 Lambda,以获得更好的流响应性能。 - -### 有计划支持SageMaker模型吗? - -目前没有支持SageMaker模型的计划。这取决于是否有客户需求。 - -### 有计划支持Bedrock自定义模型吗? - -不支持微调模型和设置了已预配吞吐量的模型。如有需要,你可以克隆repo并进行自定义。 - -### 如何升级? - -要使用最新功能,您无需重新部署CloudFormation堆栈。您只需拉取最新的镜像即可。 - -具体操作方式取决于您部署的版本: - -- **Lambda版本**: 进入AWS Lambda控制台,找到Lambda 函数,然后找到并单击`部署新映像`按钮,然后单击保存。 -- **Fargate版本**: 进入ECS控制台,单击ECS集群,转到`任务`选项卡,选择正在运行的唯一任务,然后点击`停止所选`菜单, ECS会自动启动新任务并且使用最新镜像。 - -## 安全 - -更多信息,请参阅[CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications)。 - -## 许可证 - -本项目根据MIT-0许可证获得许可。请参阅LICENSE文件。 diff --git a/src/api/models/bedrock.py b/src/api/models/bedrock.py index 8b01b0f..f88d8c1 100644 --- a/src/api/models/bedrock.py +++ b/src/api/models/bedrock.py @@ -5,6 +5,7 @@ import time from abc import ABC from typing import AsyncIterable, Iterable, Literal +from api.setting import CURRENT_MODEL import boto3 import numpy as np @@ -38,6 +39,8 @@ ) from api.setting import DEBUG, AWS_REGION +CURRENT_MODEL_INDEX = 0 + logger = logging.getLogger(__name__) bedrock_runtime = boto3.client( @@ -231,10 +234,22 @@ def _invoke_bedrock(self, chat_request: ChatRequest, stream=False): response = bedrock_runtime.converse(**args) except bedrock_runtime.exceptions.ValidationException as e: logger.error("Validation Error: " + str(e)) - raise HTTPException(status_code=400, detail=str(e)) + if CURRENT_MODEL_INDEX == len(CURRENT_MODEL) - 1: + # We've tried all models, raise the exception + raise HTTPException(status_code=400, detail=str(e)) + else: + # Try the next model + CURRENT_MODEL_INDEX += 1 + response = self._invoke_bedrock(chat_request,stream) except Exception as e: logger.error(e) - raise HTTPException(status_code=500, detail=str(e)) + if CURRENT_MODEL_INDEX == len(CURRENT_MODEL) - 1: + # We've tried all models, raise the exception + raise HTTPException(status_code=500, detail=str(e)) + else: + # Try the next model + CURRENT_MODEL_INDEX += 1 + response = self._invoke_bedrock(chat_request, stream) return response def chat(self, chat_request: ChatRequest) -> ChatResponse: diff --git a/src/api/routers/chat.py b/src/api/routers/chat.py index 1e48a48..4c1d65f 100644 --- a/src/api/routers/chat.py +++ b/src/api/routers/chat.py @@ -1,20 +1,19 @@ from typing import Annotated -from fastapi import APIRouter, Depends, Body +from fastapi import APIRouter, Depends, Body, HTTPException from fastapi.responses import StreamingResponse from api.auth import api_key_auth from api.models.bedrock import BedrockModel from api.schema import ChatRequest, ChatResponse, ChatStreamResponse -from api.setting import DEFAULT_MODEL +from api.setting import CURRENT_MODEL, CURRENT_MODEL_INDEX router = APIRouter( prefix="/chat", dependencies=[Depends(api_key_auth)], # responses={404: {"description": "Not found"}}, ) - - + @router.post("/completions", response_model=ChatResponse | ChatStreamResponse, response_model_exclude_unset=True) async def chat_completions( chat_request: Annotated[ @@ -33,13 +32,13 @@ async def chat_completions( ] ): if chat_request.model.lower().startswith("gpt-"): - chat_request.model = DEFAULT_MODEL + chat_request.model = CURRENT_MODEL[CURRENT_MODEL_INDEX] # Exception will be raised if model not supported. model = BedrockModel() model.validate(chat_request) if chat_request.stream: - return StreamingResponse( - content=model.chat_stream(chat_request), media_type="text/event-stream" - ) + return StreamingResponse( + content=model.chat_stream(chat_request), media_type="text/event-stream" + ) return model.chat(chat_request) diff --git a/src/api/setting.py b/src/api/setting.py index 408eff5..78e2b51 100644 --- a/src/api/setting.py +++ b/src/api/setting.py @@ -20,9 +20,13 @@ DEBUG = os.environ.get("DEBUG", "false").lower() != "false" AWS_REGION = os.environ.get("AWS_REGION", "us-west-2") -DEFAULT_MODEL = os.environ.get( - "DEFAULT_MODEL", "anthropic.claude-3-sonnet-20240229-v1:0" -) +CURRENT_MODEL = [ + os.environ.get("DEFAULT_MODEL", "anthropic.claude-3-sonnet-20240229-v1:0"), + os.environ.get("MODEL_2", "anthropic.claude-3-5-sonnet-20240620-v1:0"), + os.environ.get("MODEL_3", "meta.llama3-1-405b-instruct-v1:0"), + os.environ.get("MODEL_4", "anthropic.claude-3-sonnet-20240229-v1:0") +] +CURRENT_MODEL_INDEX = 0 DEFAULT_EMBEDDING_MODEL = os.environ.get( "DEFAULT_EMBEDDING_MODEL", "cohere.embed-multilingual-v3" )