使用 Amazon Bedrock + Claude 3 打造个性化智能编程助手

人工智能
Amazon CodeWhisperer
Amazon Bedrock
0
0
最近,随着人工智能技术的迅速发展,代码助手已经成为软件开发领域备受关注的工具。像 [Amazon CodeWhisperer](https://aws.amazon.com/codewhisperer/https://aws.amazon.com/codewhisperer/) 和 Github Copilot 这样的工具可以在集成开发环境中帮助用户自动生成代码,极大地提高了开发效率。然而,这些助手通常缺乏直接执行代码的能力,需要额外集成开发环境来执行代码。为了解决这一问题,OpenAI 在 ChatGPT 中推出了code interpreter,微软也开发了开源项目 AutoGen 来实现代码执行能力,类似的工具还包括开源项目 Open Interpreter。这些工具允许我们直接执行大型语言模型生成的代码,但是它们提供的的代码解释器主要以 Python 为主,对其他开发语言比如 Nodejs,Golang,Rust,PHP 支持得并不好。 在日常工作中,我们发现许多客户经常需要使用 Amazon SDK 进行开发,通常涉及多种编程语言如 Go、Node.js 和 Rust,为此我们创建了”Bedrock-claude-codecoach”开源项目(项目链接:https://github.com/aws-samples/bedrock-claude-codecoach?trk=cndc-detail ),它是使用 Amazon Bedrock、Piston、LangChainJS 和 NextJS 开发的编程助手和代码解释器,支持 Anthropic Claude 2/2.1,Claude 3 和开源 Mistral 7B、Mixtral 8x7B 等模型。通过这个开源项目,我们旨在提供一个开箱即用的 Bedrock 编程助手,可以快速帮助用户进行 Amazon SDK 代码开发和调试。另外,我们还内置了一个提示词编辑工具,方便那些初次接触 Amazon Bedrock 的提示词工程师进行提示词调试。 ### 1. 架构介绍 CodeCoach 架构图如下: ![1.png](https://dev-media.amazoncloud.cn/f2485fed9dc140f59303c54685179946_1.png "1.png") 本项目最大的价值是提供方便快捷的部署方案,以较高的准确度生成代码并提供执行代码的各种运行环境。因此在项目中我们使用了 Amazon Bedrock 服务进行代码的生成。 Amazon Bedrock 是一个 MaaS 的平台,提供了各种业界翘楚的商业化模型(如:Athropic 的 Claude)和开源模型(如:Llama/Mistral),最大程度地减少了客户选择和使用基础模型的试错成本和时间周期,并且 Bedrock 上的基础模型会不断迭代更新,确保客户能始终 enable 最新最强的模型的功能,如最近新上架的 Athropic Claude v3 的模型,在各项指标评测中赶超 GPT-4,并且增加了多模态 VQA,Function Calling 等最新功能。 在本项目中,我们采用了 Bedrock 上的 Claude 3 和 Mistral 7B 模型进行代码生成,经测试其在各项代码生成中稳定性和功能均表现突出;用户验证信息和提示词模版等数据存储在 DynamoDB 中,支持多人访问; 此外项目还集成了 Piston 多语言代码执行引擎,通过 Piston 我们可以打造自己开源的 code interpreter,目前 Piston 支持 30 多种执行环境,在 CodeCoach 里面我们选择了常见的几种开发语言:Python,Golang,JavaScript/TypeScript,PHP 和 Rust,并且已经预集成了Amazon SDK 可以直接使用;同时为了简化部署过程,我们提供了 CloudFormation 一键部署模版(cf-template.yaml) 。 ### 2. 部署项目 CodeCoach 已经提供了 CloudFormation 模版(https://github.com/aws-samples/bedrock-claude-codecoach/blob/main/cf-template.yaml?trk=cndc-detail ),可以直接下载到本地后进行部署。 #### 方法一、通过 CloudFormation 服务控制界面进行部署 首先请登陆 亚马逊云科技(海外)控制台,进入 CloudFormation 界面,点击创建 Stack ![2.png](https://dev-media.amazoncloud.cn/16c392409a024587a8dfd65439c0212d_2.png "2.png") 选择上传模版文件, 上传 cf-template.yaml 模版 ![3.png](https://dev-media.amazoncloud.cn/232b11120bc1481e84a43aaa8547bfea_3.png "3.png") 设置 EC2 密钥对的名字,这样就可以直接登陆到 EC2 进行调试 ![4.png](https://dev-media.amazoncloud.cn/602888d257274b5492f57aa3f6595c6a_4.png "4.png") ![5.png](https://dev-media.amazoncloud.cn/e7245d781a5e405da9f995fd16a078ba_5.png "5.png") #### 方法二、使用命令行工具部署 CodeCoach ```js #请替换<你的 KeyPair 名字> 为实际使用的 Key Pair 名字 aws cloudformation create-stack --region us-west-2 \ --stack-name codecoach \ --template-body file://cf-template.yaml \ --parameters ParameterKey=SSHKeyName,ParameterValue=<你的 KeyPair 名字> \ --capabilities CAPABILITY_IAM ``` CloudFormation Stack 创建完成后会输出 Cloudfront 地址(下图红框提示处即访问地址),直接访问即可。 ![6.png](https://dev-media.amazoncloud.cn/85ea3161e92645e2a6f7ec7904ca2471_6.png "6.png") 访问该网页点击 Get Start 即可登录,初始用户密码( admin@demo.com/123456!@# ),首次登录后请点击右上角设置(齿轮图标),立即修改用户密码。 ![7.png](https://dev-media.amazoncloud.cn/7563e3e3be72416787c7dafeb2800c48_7.png "7.png") ![8.png](https://dev-media.amazoncloud.cn/f809fd132b4b454fbbb15f035e45ef07_8.png "8.png") 选择助手,点击模型下拉列表,选择合适的模型然后点击测试,测试通过后点击保存,项目默认是 Claude2,推荐使用最新的 Claude3 Sonnet。 ![9.png](https://dev-media.amazoncloud.cn/3d0218c0fd8941e4bb84ee88fc3717f6_9.png "9.png") ### 3. CodeCoach 功能介绍 #### 3.1 使用 Amazon SDK 进行程序开发 我们在日常工作中可能会需要使用各种语言的 Amazon SDK 代码,比如 Python(boto3),Golang,JavaScript/TypeScript,PHP,Rust 等。下面我们就以 Python(boto3)/Golang 为例来演示如何使用 CodeCoach 进行 Amazon SDK 开发。 ##### 3.1.1 使用 boto3 进行开发,打印某个区域的 EC2 实例列表,并可以返回运行状态 首先我们输入“使用 boto3 列出 us-east-1 的 ec2 实例”,然后等待 CodeCoach 返回,点击执行。 ![10.png](https://dev-media.amazoncloud.cn/ff8d1ee68ee94affb82714502a8e1013_10.png "10.png") UI 就会调出代码编辑界面,在这个界面点击执行即可,在 output 显示框我们可以看见 Amazon SDK 的返回结果,如果代码有问题,会出现“fix”按钮,我们可以要求 CodeCoach 修复错误,并进行说明。 ![11.png](https://dev-media.amazoncloud.cn/db126f377d094fa99c5c8debb090a7ea_11.png "11.png") ##### 3.1.2 使用亚马逊 Golang SDK 打印 S3 桶列表 输入“使用 Amazon Golang SDK 编写一个 list s3 bucket 代码”,点击执行,因为 Golang 需要编译,所以执行的时候需要比 Python 更久的时间,点击后请耐心等待。 ![12.png](https://dev-media.amazoncloud.cn/bf7300a1ebf8405c864e7a3376e8d9b9_12.png "12.png") ![13.png](https://dev-media.amazoncloud.cn/f84e61fe8ad84014b30f6591617bc403_13.png "13.png") #### 3.2 错误修复 如果生成的代码出现了错误,我们可以直接点击“修复助手”,CodeCoach 会调用大语言模型给出修复方法和说明。 ![14.png](https://dev-media.amazoncloud.cn/bd9230dbec2346678b044e78fb5e8b14_14.png "14.png") ![15.png](https://dev-media.amazoncloud.cn/278241a3d0f741d1bcaf7219957c8b31_15.png "15.png") 可以看到 Claude3 不仅帮助我们纠正了错误,而且还给出了关于错误的解释:返回值“没有 Instances”这个 Key,需要使用 “Reservations”。 #### 3.3 提示词模版工具 CodeCoach 支持自定义提示词,直接打开提示词,可以编写加载自己的提示词,我们分别使用 Claude2,Claude3 来编写一个翻译助手。 ##### 3.3.1 编写 Claude3 提示词 Claude3 不再需要 Human,Assistant 限定格式,可以直接进行编写,可以参考 [Anthropic Claude Message API](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html?trk=cndc-detail) 文档, 下面我们以 Claude3 为例编写一个翻译助手。 **1)不使用 System Role,直接在用户提示词里面编写任务** ```js 你是一个中英文翻译专家,你先要判断我的问题是中文还是英文 1.如果是英文请转换成中文 2.如果是中文请转换成英文 这里是我的问题: {query} 请返回 {"input": "question", "output":"你的答案“} ``` ![16.png](https://dev-media.amazoncloud.cn/57e5552a059b41518fdbed5c3a8a2565_16.png "16.png") 点击提交,Claude3 会返回{*“input”: “我想买一辆车.”, “output”: “I want to buy a car.”*} ,完全符合我们的预期。 后台查看 Message API 格式如下: ![17.png](https://dev-media.amazoncloud.cn/77912b5328db4a5fb18051be20eaadeb_17.png "17.png") **2)使用 Claude3 的 System Role,我们将原始提示词里面任务的定义填写到 Claude3 System Role 里面** ![18.png](https://dev-media.amazoncloud.cn/f9e149509e25458e903b7aa49f7cf349_18.png "18.png") 注意,这里整个 payload 就多了一个新 key “system”,这个就是 Claude3 最新的 Message API 定义 system role 的方法。 ![19.png](https://dev-media.amazoncloud.cn/baab8867253c43779db90805a2329024_19.png "19.png") ##### 3.3.2 编写 Claude2 提示词,Human,Assistant 分别代表了输入和模型的输出 ```js Human: 你是一个中英文翻译专家,你先要判断我的问题是中文还是英文 1.如果是英文请转换成中文 2.如果是中文请转换成英文 这里是我的问题: {query} 请返回 {"input": "question", "output":"你的答案“} Assistant: ``` ![20.png](https://dev-media.amazoncloud.cn/a956a29776dd46938190814972a9a34a_20.png "20.png") Mistral 7B 使用方式类似,在这里我们就不再赘述了。 其次在提示词调试工具里面我们可以定义任意的{变量}, 变量会自动生成文本输入框,通过 LangChainJS Prompt Template 进行加载,点击提交即可测试你的提示词, 点击保存就可以将模版保存在 Amazon DynamoDB 中方便下次使用。 ### 4. 如何自定义 Piston 执行环境 Piston 的开源地址为 https://github.com/engineer-man/piston?trk=cndc-detail ,目前已经集成了 112 种运行环境,参考 https://github.com/engineer-man/piston/releases/download/pkgs/index?trk=cndc-detail 。官方的 repo 中仅提供每种语言基础的运行环境,但是在实际的使用过程中,我们需要丰富运行环境,安装额外的依赖,例如我们需要为 python 环境增加 boto3 的 SDK,或者为 Bash 环境增加 Amazon CLI 等,因此我们需要在官方 repo 的基础上自定义 Piston 环境。 #### 4.1 复制全量执行环境(可选) 为了拥有全量的执行环境,我们先将官方的 repo 中的所有运行环境迁移到自己的 github 仓库中,我们可以 Fork 官方 git 仓库之后通过以下脚本批量的迁移 Release 中发布的所有环境。 ```js #!/bin/bash repo="piston" # GitHub Token token="xxxxxxx" # github owner code owner="xxx" # Release名 release_name="Packages" # 本地附件目录 attach_dir="/opt/pkgs" # ReleaseId release_id="" upload_assets(){ release_id=$1 file=$2 echo "-----" echo $release_id $file # 上传文件 upload_url=$(curl -H "Authorization: token $token" \ -H "Content-Type: application/gzip" \ https://uploads.github.com/repos/$owner/$repo/releases/$release_id/assets?name=$(basename $file) \ --data-binary @$file > /dev/null) # 添加为Release附件 curl -X POST -s -H "Authorization: token $token" \ -H "Content-Type: application/json" \ -d '{"name":"'$(basename $file)'","url":"'$upload_url'"}' \ https://api.github.com/repos/$owner/$repo/releases/$release_id/assets > /dev/null } release_to_github(){ # 获取 release_id if [ "x"${release_id} == "x" ]; then # 判断release是否存在 release_exist=$(curl -s -H "Authorization: token $token" https://api.github.com/repos/$owner/$repo/releases/tags/$release_name | jq '.id') if [ -z "$release_exist" ]; then # 不存在则创建 release_id=$(curl -s -X POST -H "Authorization: token $token" \ -d '{"tag_name":"'"$release_name"'","name":"'"$release_name"'"}' \ https://api.github.com/repos/$owner/$repo/releases | jq '.id') else # 存在则直接使用 release_id=$release_exist fi fi # 遍历附件目录上传文件 for file in $attach_dir/*.tar.gz; do echo "release: $file" upload_assets $release_id $file done } download_from_source(){ cd $attach_dir rm -fr index && curl -s -L https://github.com/engineer-man/piston/releases/download/pkgs/index -o index count=1 while read line; do if [ "x"$line == "x" ];then continue fi # 分割CSV字段 IFS=',' read -ra fields <<< "$line" # 下载文件 url="${fields[3]}" echo "${count}:${url}" filename=$(basename $url) curl -s -L -o "$filename" "$url" release_to_github rm -fr $filename ((count++)) done < ./index sed -i 's!https://github.com/engineer-man/piston/releases/download/pkgs/!https://github.com/yanjun-ios/piston/releases/download/Packages/!g' index # 合并index文件 mv index index_1 && curl -s -L https://github.com/$owner/piston/releases/download/Packages/index -o index_2 cat index_1 index_2 | sort | uniq > index echo "upload the index file !" # 上传 index 文件 upload_assets $release_id $attach_dir/index } download_from_source ``` #### 4.2 修改执行环境,重新构建执行环境的安装包 在 piston 的工程中的 piston/packages/ 目录下存放了所有执行环境的构建代码,每个执行环境主要包括 build.sh,environment,metadata.json,run,test 五个文件,其中 build.sh 中定义了执行环境的安装过程,environment 中定义了我们要暴露的环境变量,在自定义执行环境的时候我们需要修改这两个文件。 ![21.png](https://dev-media.amazoncloud.cn/3949f53bc98d410cbfca797c9872603c_21.png "21.png") 我们以 bash 执行环境中添加 awscli 为例,将 iston/packages/bash/5.2.0/build.sh 中内容改成下代码: ```js #!/usr/bin/env bash # Put instructions to build your package in here PREFIX=$(realpath $(dirname $0)) mkdir -p build cd build curl "https://ftp.gnu.org/gnu/bash/bash-5.2.tar.gz" -o bash.tar.gz tar xzf bash.tar.gz --strip-components=1 # === autoconf based === ./configure --prefix "$PREFIX" make -j$(nproc) make install -j$(nproc) cd ../ rm -rf build # install aws cli PREFIX=$PWD curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.0.30.zip" -o "awscliv2.zip" unzip awscliv2.zip ./aws/install -i $PREFIX/aws-cli -b $PREFIX/bin rm -fr awscliv2.zip rm -fr ./aws ``` 在 environment 中添加 awscli 的环境变量 ```js #!/usr/bin/env bash # Put 'export' statements here for environment variables export PATH=$PWD/bin:$PATH export PATH=$PWD/aws-cli:$PATH ``` 修改完执行环境,我们修改 piston/docker-compose.yaml 文件,从本地代码构建 docker 镜像,并启动容器 ```js services: api: build: api container_name: piston_api cap_add: - CAP_SYS_ADMIN restart: always ports: - 2000:2000 volumes: - ./data/piston/packages:/piston/packages environment: - PISTON_REPO_URL=https://github.com/@owner/piston/releases/download/Packages/index - PISTON_DISABLE_NETWORKING=false - PISTON_RUN_TIMEOUT=300000 - PISTON_OUTPUT_MAX_SIZE=102400 tmpfs: - /piston/jobs:exec,uid=1000,gid=1000,mode=711 ``` 启动并进入容器中,构建自定义执行环境 ```js docker compose up -d docker exec -it piston_repo bash cd piston/repo/ # 执行构建命令 sh build_package.sh bash=5.2.0 # 将构建好的安装包发布到github sh build_package.sh release ``` 其中 build_package.sh 代码如下: ```js #!/bin/bash #set -x # usage: sh build_package.sh python or sh build_package.sh python=2.7.18 repo="piston" # GitHub Token token="xxxxx" # github owner code owner="xxxx" # Release名 release_name="Packages" # 本地附件目录 attach_dir="/piston/repo/" # ReleaseId release_id="" all_assets=() upload_assets(){ release_id=$1 file=$2 echo "-----" echo "Upload File, Release Id: $release_id file name : $file" # 上传文件 upload_url=$(curl -H "Authorization: token $token" \ -H "Content-Type: application/gzip" \ https://uploads.github.com/repos/$owner/$repo/releases/$release_id/assets?name=$(basename $file) \ --data-binary @$file > /dev/null) # 添加为Release附件 curl -X POST -s -H "Authorization: token $token" \ -H "Content-Type: application/json" \ -d '{"name":"'$(basename $file)'","url":"'$upload_url'"}' \ https://api.github.com/repos/$owner/$repo/releases/$release_id/assets > /dev/null } # 获取所有的asset get_all_assets() { page=1 #release_id=128663575 assets_url="https://api.github.com/repos/$owner/$repo/releases/${release_id}/assets?per_page=100" while : do local assets assets=$(curl -fsS -H "Authorization: token ${token}" "${assets_url}&page=${page}" | jq -r '.[] | "\(.name)-\(.id)"') name=$(echo ${assets[@]}| grep "pkg.tar.gz") # result=$(echo $name | grep "=") if [ $? -ne 0 ];then break; fi # 将当前页面的assets添加到数组中 #mapfile -t assets < <(echo "$assets") for i in ${assets[*]} do # echo "this is i: "$i all_assets[${#all_assets[*]}]=${i} done ((page++)) done echo "全量数组长度:${#all_assets[@]}" } # 根据文件名删除附件 delete_release_asset(){ release_id=$1 file_name=$2 if [ ${#all_assets[@]} -eq 0 ]; then echo "assets 为空,请求全量assets" get_all_assets fi asset_id="" for element in "${all_assets[@]}"; do if [[ $element == *"$file_name"* ]]; then asset_id=$(echo $element | awk -F '-' '{print $NF}') break fi done echo "Delete File, file Name : $file_name asset_id :$asset_id" if [ "$asset_id" == "" ]; then echo "Delete failed, No asset found with filename: $filename" return 1 fi asset_url="https://api.github.com/repos/$owner/${repo}/releases/assets/${asset_id}" response=$(curl -s -X DELETE -H "Authorization: token ${token}" ${asset_url}) if echo "$response" | grep -q '204 No Content'; then echo "Failed to delete asset: $response" else echo "Asset deleted successfully" fi } # 发布到github release中 release_to_github(){ cd $attach_dir if [ ! -f *.tar.gz ];then echo "there is no packages to be released , exit 1" exit 1 fi # 获取 release_id if [ "x"${release_id} == "x" ]; then # 判断release是否存在 release_exist=$(curl -s -H "Authorization: token $token" https://api.github.com/repos/$owner/$repo/releases/tags/$release_name | jq '.id') if [ -z "$release_exist" ]; then # 不存在则创建 release_id=$(curl -s -X POST -H "Authorization: token $token" \ -d '{"tag_name":"'"$release_name"'","name":"'"$release_name"'"}' \ https://api.github.com/repos/$owner/$repo/releases | jq '.id') else # 存在则直接使用 release_id=$release_exist fi fi # 遍历 tar.gz 附件目录上传文件 for file in *.tar.gz; do echo "release: $file" delete_release_asset $release_id $file upload_assets $release_id $file done # 合并index文件 mv index index_1 && curl -s -L https://github.com/@owner/piston/releases/download/Packages/index -o index_2 if [ $? -ne 0 ];then echo "download index file failed,exit 1" exit 1 fi for file in *.tar.gz; do sed -i "/${file}$/d" index_2 done cat index_1 index_2 | sort | uniq > index echo "upload the index file !" # 上传 index 文件 delete_release_asset $release_id index upload_assets $release_id index } # 构建安装包 build_package(){ cd /piston/packages echo "build packages from args..." for pkg in "$@" do shift if [ ! -d `echo $pkg | awk -F'=' '{print $1}'` ];then echo "Packages not found for $pkg" continue fi result=$(echo $pkg | grep "=") if [ $? -eq 0 ];then echo "install $pkg" pkgname=$(echo ${pkg/=/-}) echo $pkgname make -j16 $pkgname.pkg.tar.gz PLATFORM=docker-debian else if [ -d "$pkg" ];then echo "install all version for $pkg" for version in $pkg/*;do version=$(echo $version | awk -F '/' '{print $2}') pkgname=${pkg}"-"${version} echo $pkgname make -j16 $pkgname.pkg.tar.gz PLATFORM=docker-debian done fi fi done if [ ! -f *.tar.gz ];then echo "there is no packages to be released , exit 1" exit 1 fi cd /piston/repo echo "Creating index" ./mkindex.sh echo "Index created" } if [ $1 == "release" ];then release_to_github else build_package $@ # release_to_github fi ``` 至此,我们已经在自己的 github 中拥有一个完全独立的 piston 的 repo,在使用时,我们只需要进入 CloudFormation 启动的 EC2,修改/root/bedrock-claude-codecoach/docker-compose.yaml 文件,通过环境变量的方式指定我们自定义 piston repo 中的 index 文件,重新执行 init.sh 即可。docker-compose 示例如下图: ![image.png](https://dev-media.amazoncloud.cn/b1b7b1b8134f4c588c32de89e56e678e_image.png "image.png") - Piston 启动配置请参考:<https://github.com/engineer-man/piston/blob/master/docs/configuration.md?trk=cndc-detail> - 完整的 piston自定义环境参考:<https://github.com/yanjun-ios/piston/tree/master?trk=cndc-detail> - Piston 启动配置请参考:https://github.com/engineer-man/piston/blob/master/docs/configuration.md?trk=cndc-detail - 完整的 piston自定义环境参考:https://github.com/yanjun-ios/piston/tree/master?trk=cndc-detail ### 5. 总结 我们可以通过 Amazon Bedrock 和 Claude 3,Mistral 7B 打造自己的代码助手,同时通过扩展 Piston 提供自定义安全可靠的执行环境,并且始终保持整个数据访问限定在企业内部,满足数据合规要求。项目后续考虑加入 Agent 和文档检索增强,利用 Amazon SDK 文档进一步提高 CodeCoach 的代码正确率,通过 Agent 来实现自动化测试。 ### 附录 1. [Anthropic’s Claude on Amazon Bedrock](https://aws.amazon.com/bedrock/claude/?trk=cndc-detail) 2. [Mistral AI on Amazon Bedrock](https://aws.amazon.com/bedrock/mistral/?trk=cndc-detail) 3. [Piston](https://github.com/engineer-man/piston?trk=cndc-detail)
0
目录
关闭