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

6a0777b3c318fe3bf2260e4281363409.gif

最近,随着人工智能技术的迅速发展,代码助手已经成为软件开发领域备受关注的工具。比如像 Amazon CodeWhisperer 这样的工具可以在集成开发环境中帮助用户自动生成代码,极大地提高了开发效率。然而,这些助手通常缺乏直接执行代码的能力,需要额外集成开发环境来执行代码。为了解决这一问题,很多软件又开发了一些工具,能够允许我们直接执行大型语言模型生成的代码。但是它们提供的的代码解释器主要以 Python 为主,对其他开发语言比如 Nodejs,Golang,Rust,PHP 支持得并不好。

在日常工作中,我们发现许多客户经常需要使用 Amazon SDK 进行开发,通常涉及多种编程语言如 Go、Node.js 和 Rust,为此我们创建了“Bedrock-claude-codecoach”开源项目,

(项目链接:https://github.com/aws-samples/bedrock-claude-codecoach)

它是使用 Amazon Bedrock、Piston、LangChainJS 和 NextJS 开发的编程助手和代码解释器,支持 Anthropic Claude 2/2.1,Claude 3 和开源 Mistral 7B、Mixtral 8x7B 等模型。通过这个开源项目,我们旨在提供一个开箱即用的 Bedrock 编程助手,可以快速帮助用户进行 Amazon SDK 代码开发和调试。另外,我们还内置了一个提示词编辑工具,方便那些初次接触 Amazon Bedrock 的提示词工程师进行提示词调试。

01

架构介绍

CodeCoach 架构图如下:

c476b5a6767aa62072e2b8572f68f878.png

本项目最大的价值是提供方便快捷的部署方案,以较高的准确度生成代码并提供执行代码的各种运行环境。因此在项目中我们使用了 Amazon Bedrock 服务进行代码的生成。

Amazon Bedrock 是一个 MaaS 的平台,提供了各种业界翘楚的商业化模型(如:Anthropic 的 Claude)和开源模型(如:Llama/Mistral),最大程度地减少了客户选择和使用基础模型的试错成本和时间周期,并且 Amazon Bedrock 上的基础模型会不断迭代更新,确保客户能始终 enable 最新最强的模型的功能,如最近新上架的 Anthropic Claude v3 的模型,增加了多模态 VQA,Function Calling 等最新功能。

在本项目中,我们采用了 Claude 3 on Amazon Bedrock 和 Mistral 7B 模型进行代码生成,经测试其在各项代码生成中稳定性和功能均表现突出;用户验证信息和提示词模版等数据存储在 DynamoDB 中,支持多人访问;此外项目还集成了 Piston 多语言代码执行引擎,通过 Piston 我们可以打造自己开源的 code interpreter,目前 Piston 支持 30 多种执行环境,在 CodeCoach 里面我们选择了常见的几种开发语言:Python,Golang,JavaScript/TypeScript,PHP 和 Rust,并且已经预集成了 Amazon SDK 可以直接使用;同时为了简化部署过程,我们提供了 CloudFormation 一键部署模版(cf-template.yaml)。

02

部署项目

CodeCoach 已经提供了 CloudFormation 模版(https://github.com/aws-samples/bedrock-claude-codecoach/blob/main/cf-template.yaml),可以直接下载到本地后进行部署。

方法一:

通过 CloudFormation 服务控制界面进行部署

首先请登陆亚马逊云科技控制台,进入 CloudFormation 界面,点击创建 Stack

f839a5355f08759ba41b9faf2c9e009c.png

选择上传模版文件, 上传 cf-template.yaml 模版

16b3d14c7344f7c14fd4d2d336269b59.png

设置 EC2 密钥对的名字,这样就可以直接登陆到 EC2 进行调试

09066c2698a98910ad8adf5fb6adce31.png

daded2a15a1ae65a4b8a296f48f636f5.png

方法二:

使用命令行工具部署 CodeCoach

#请替换<你的 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 地址(下图红框提示处即访问地址),直接访问即可。

9bcdc78cf29c30aa15ff9f71d83f4812.png

访问该网页点击 Get Start 即可登录,初始用户密码(admin@demo.com/123456!@#),首次登录后请点击右上角设置(齿轮图标),立即修改用户密码。

5946e0be1b83505a06677433e9b50f80.png

51924af1f2d570b501664fcef37e63b4.png

选择助手,点击模型下拉列表,选择合适的模型然后点击测试,测试通过后点击保存,项目默认是 Claude2,推荐使用最新的 Claude3 Sonnet。

c072d8fdf4044f29bbed3df048549feb.png

03

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 返回,点击执行。

3f660f81cb576ae738281d7f495e5bf1.png

UI 就会调出代码编辑界面,在这个界面点击执行即可,在 output 显示框我们可以看见 Amazon SDK 的返回结果,如果代码有问题,会出现“fix”按钮,我们可以要求 CodeCoach 修复错误,并进行说明。

6780ab367e81c3f42c94616fecf5b01c.png

3.1.2 使用亚马逊 Golang SDK 打印 S3 桶列表

输入“使用 Amazon Golang SDK 编写一个 list s3 bucket 代码”,点击执行,因为 Golang 需要编译,所以执行的时候需要比 Python 更久的时间,点击后请耐心等待。

01de1340081f8910503a614b56c3d566.png

aa763d578cd0375259e02e652b1bdec3.png

3.2 错误修复

如果生成的代码出现了错误,我们可以直接点击“修复助手”,CodeCoach 会调用大语言模型给出修复方法和说明。

6c52156e9d0ba4af07b2040e4b0e3bcc.png

1082807160d8d7eab0f3b1843ba3302a.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

下面我们以 Claude3 为例编写一个翻译助手。

1)不使用 System Role,直接在用户提示词里面编写任务

你是一个中英文翻译专家,你先要判断我的问题是中文还是英文


1.如果是英文请转换成中文
2.如果是中文请转换成英文


这里是我的问题:
  {query}
请返回 {"input": "question", "output":"你的答案“}

ff40e442591f6eabc2cdf300e5f0443e.png

点击提交,Claude3 会返回{“input”: “我想买一辆车.”, “output”: “I want to buy a car.”} ,完全符合我们的预期。

后台查看 Message API 格式如下:

499f190229607082d820a58355b9b0a7.png

2)使用 Claude3 的 System Role,我们将原始提示词里面任务的定义填写到 Claude3 System Role 里面

058789dba5081d4266992bb9e59e57e5.png

注意,这里整个 payload 就多了一个新 key “system”,这个就是 Claude3 最新的 Message API 定义 system role 的方法。

d105bf1eb518ac62d0f53ecfed7c11b0.png

3.3.2 编写 Claude2 提示词,Human,Assistant 分别代表了输入和模型的输出

Human: 你是一个中英文翻译专家,你先要判断我的问题是中文还是英文


1.如果是英文请转换成中文
2.如果是中文请转换成英文




这里是我的问题:
  {query}
请返回 {"input": "question", "output":"你的答案“}


Assistant:

1e5a3b8fb4bd7ce7abfeb973776d18d5.png

Mistral 7B 使用方式类似,在这里我们就不再赘述了。

其次在提示词调试工具里面我们可以定义任意的{变量}, 变量会自动生成文本输入框,通过 LangChainJS Prompt Template 进行加载,点击提交即可测试你的提示词,点击保存就可以将模版保存在 Amazon DynamoDB 中方便下次使用。

04

如何自定义 Piston 执行环境

Piston 的开源地址为 https://github.com/engineer-man/piston,目前已经集成了 112 种运行环境,参考 https://github.com/engineer-man/piston/releases/download/pkgs/index。官方的 repo 中仅提供每种语言基础的运行环境,但是在实际的使用过程中,我们需要丰富运行环境,安装额外的依赖,例如我们需要为 python 环境增加 boto3 的 SDK,或者为 Bash 环境增加 Amazon CLI 等,因此我们需要在官方 repo 的基础上自定义 Piston 环境。

4.1 复制全量执行环境(可选)

为了拥有全量的执行环境,我们先将官方的 repo 中的所有运行环境迁移到自己的 github 仓库中,我们可以 Fork 官方 git 仓库之后通过以下脚本批量的迁移 Release 中发布的所有环境。

#!/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 中定义了我们要暴露的环境变量,在自定义执行环境的时候我们需要修改这两个文件。

26675aeb54d6229d4bad8b122aea47e5.png

我们以 Bash 执行环境中添加 awscli 为例,将 piston/packages/bash/5.2.0/build.sh 中内容改成下代码:

#!/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 的环境变量

#!/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 镜像,并启动容器

version: '3.2'


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

启动并进入容器中,构建自定义执行环境

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 代码如下:

#!/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 示例如下图:

ec80220d1e1bae30f36e9939171eefaf.png

  • Piston 启动配置请参考:

    https://github.com/engineer-man/piston/blob/master/docs/configuration.md

  • 完整的 Piston自定义环境参考:

    https://github.com/yanjun-ios/piston/tree/master

05

总结

我们可以通过 Claude 3 on Amazon Bedrock,Mistral 7B 打造自己的代码助手,同时通过扩展 Piston 提供自定义安全可靠的执行环境,并且始终保持整个数据访问限定在企业内部,满足数据合规要求。项目后续考虑加入 Agent 和文档检索增强,利用 Amazon SDK 文档进一步提高 CodeCoach 的代码正确率,通过 Agent 来实现自动化测试。

附录

  • Anthropic’s Claude on Amazon Bedrock

    https://aws.amazon.com/bedrock/claude/

  • Mistral AI on Amazon Bedrock

    https://aws.amazon.com/bedrock/mistral/

  • Piston

    https://github.com/engineer-man/piston

本篇作者

9d26085e0a6af46a4f37fa471604959a.jpeg

粟伟

亚马逊云科技资深解决方案架构师,专注游戏行业,开源项目爱好者,致力于云原生应用推广、落地。具有 15 年以上的信息技术行业专业经验,担任过高级软件工程师,系统架构师等职位,在加入亚马逊云科技之前曾就职于 Bea,Oracle,IBM 等公司。

be64ed29c8e06eb61c49c44b4e247206.jpeg

唐清原

亚马逊云科技高级解决方案架构师,负责 Data Analytic & AIML 产品服务架构设计以及解决方案。10+数据领域研发及架构设计经验,历任 IBM 咨询顾问,Oracle 高级咨询顾问,澳新银行数据部领域架构师职务。在大数据 BI,数据湖,推荐系统,MLOps 等平台项目有丰富实战经验。

c329c5b55abea7387a3bf7f4bd757c53.jpeg

严军

亚马逊云科技解决方案架构师,目前主要负责帮客户进行云架构设计和技术咨询,对容器化等技术方向有深入的了解,在云迁移方案设计和实施方面有丰富的经验。

180f7437e5b38ee7f5b3d1efac029e38.jpeg

高郁

亚马逊云科技解决方案架构师,主要负责企业客户上云,帮助客户进行云架构设计和技术咨询,专注于智能湖仓、AI/ML 等技术方向。

8751fa8eff04ec9e31ad5791896401cc.gif

星标不迷路,开发更极速!

关注后记得星标「亚马逊云开发者」

a9ed589b41c2c5db169d177dcff11b19.gif

听说,点完下面4个按钮

就不会碰到bug了!

d50a2266dac9442b6f39b2bd526fb771.gif

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值