最近,随着人工智能技术的迅速发展,代码助手已经成为软件开发领域备受关注的工具。像 Amazon CodeWhisperer 和 Github Copilot 这样的工具可以在集成开发环境中帮助用户自动生成代码,极大地提高了开发效率。然而,这些助手通常缺乏直接执行代码的能力,需要额外集成开发环境来执行代码。为了解决这一问题,OpenAI 在 ChatGPT 中推出了code interpreter,微软也开发了开源项目 AutoGen 来实现代码执行能力,类似的工具还包括开源项目 Open Interpreter。这些工具允许我们直接执行大型语言模型生成的代码,但是它们提供的的代码解释器主要以 Python 为主,对其他开发语言比如 Nodejs,Golang,Rust,PHP 支持得并不好。
在日常工作中,我们发现许多客户经常需要使用 Amazon Web Service(AWS)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 编程助手,可以快速帮助用户进行 AWS SDK 代码开发和调试。另外,我们还内置了一个提示词编辑工具,方便那些初次接触 Amazon Bedrock 的提示词工程师进行提示词调试。
1. 架构介绍
CodeCoach 架构图如下:
本项目最大的价值是提供方便快捷的部署方案,以较高的准确度生成代码并提供执行代码的各种运行环境。因此在项目中我们使用了 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,并且已经预集成了AWS SDK 可以直接使用;同时为了简化部署过程,我们提供了 CloudFormation 一键部署模版(cf-template.yaml) 。
2. 部署项目
CodeCoach 已经提供了 CloudFormation 模版(https://github.com/aws-samples/bedrock-claude-codecoach/blob/main/cf-template.yaml),可以直接下载到本地后进行部署。
方法一、通过 CloudFormation 服务控制界面进行部署
首先请登陆 AWS(海外)控制台,进入 CloudFormation 界面,点击创建 Stack
选择上传模版文件, 上传 cf-template.yaml 模版
设置 EC2 密钥对的名字,这样就可以直接登陆到 EC2 进行调试
方法二、使用命令行工具部署 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 地址(下图红框提示处即访问地址),直接访问即可。
访问该网页点击 Get Start 即可登录,初始用户密码(admin@demo.com/123456!@#),首次登录后请点击右上角设置(齿轮图标),立即修改用户密码。
选择助手,点击模型下拉列表,选择合适的模型然后点击测试,测试通过后点击保存,项目默认是 Claude2,推荐使用最新的 Claude3 Sonnet。
3. CodeCoach 功能介绍
3.1 使用 AWS SDK 进行程序开发
我们在日常工作中可能会需要使用各种语言的 AWS SDK 代码,比如 Python(boto3),Golang,JavaScript/TypeScript,PHP,Rust 等。下面我们就以 Python(boto3)/Golang 为例来演示如何使用 CodeCoach 进行 AWS SDK 开发。
3.1.1 使用 boto3 进行开发,打印某个区域的 EC2 实例列表,并可以返回运行状态
首先我们输入“使用 boto3 列出 us-east-1 的 ec2 实例”,然后等待 CodeCoach 返回,点击执行。
UI 就会调出代码编辑界面,在这个界面点击执行即可,在 output 显示框我们可以看见 AWS SDK 的返回结果,如果代码有问题,会出现“fix”按钮,我们可以要求 CodeCoach 修复错误,并进行说明。
3.1.2 使用亚马逊 Golang SDK 打印 S3 桶列表
输入“使用 AWS Golang SDK 编写一个 list s3 bucket 代码”,点击执行,因为 Golang 需要编译,所以执行的时候需要比 Python 更久的时间,点击后请耐心等待。
3.2 错误修复
如果生成的代码出现了错误,我们可以直接点击“修复助手”,CodeCoach 会调用大语言模型给出修复方法和说明。
可以看到 Claude3 不仅帮助我们纠正了错误,而且还给出了关于错误的解释:返回值“没有 Instances”这个 Key,需要使用 “Reservations”。
3.3 提示词模版工具
CodeCoach 支持自定义提示词,直接打开提示词,可以编写加载自己的提示词,我们分别使用 Claude2,Claude3 来编写一个翻译助手。
3.3.1 编写 Claude3 提示词
Claude3 不再需要 Human,Assistant 限定格式,可以直接进行编写,可以参考 Anthropic Claude Message API 文档, 下面我们以 Claude3 为例编写一个翻译助手。
1)不使用 System Role,直接在用户提示词里面编写任务
你是一个中英文翻译专家,你先要判断我的问题是中文还是英文
1.如果是英文请转换成中文
2.如果是中文请转换成英文
这里是我的问题:
{query}
请返回 {"input": "question", "output":"你的答案“}
点击提交,Claude3 会返回{“input”: “我想买一辆车.”, “output”: “I want to buy a car.”} ,完全符合我们的预期。
后台查看 Message API 格式如下:
2)使用 Claude3 的 System Role,我们将原始提示词里面任务的定义填写到 Claude3 System Role 里面
注意,这里整个 payload 就多了一个新 key “system”,这个就是 Claude3 最新的 Message API 定义 system role 的方法。
3.3.2 编写 Claude2 提示词,Human,Assistant 分别代表了输入和模型的输出
Human: 你是一个中英文翻译专家,你先要判断我的问题是中文还是英文
1.如果是英文请转换成中文
2.如果是中文请转换成英文
这里是我的问题:
{query}
请返回 {"input": "question", "output":"你的答案“}
Assistant:
Mistral 7B 使用方式类似,在这里我们就不再赘述了。
其次在提示词调试工具里面我们可以定义任意的{变量}, 变量会自动生成文本输入框,通过 LangChainJS Prompt Template 进行加载,点击提交即可测试你的提示词, 点击保存就可以将模版保存在 Amazon DynamoDB 中方便下次使用。
4. 如何自定义 Piston 执行环境
Piston 的开源地址为 https://github.com/engineer-man/piston,目前已经集成了 112 种运行环境,参考 https://github.com/engineer-man/piston/releases/download/pkgs/index。官方的 repo 中仅提供每种语言基础的运行环境,但是在实际的使用过程中,我们需要丰富运行环境,安装额外的依赖,例如我们需要为 python 环境增加 boto3 的 SDK,或者为 Bash 环境增加 AWS 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 中定义了我们要暴露的环境变量,在自定义执行环境的时候我们需要修改这两个文件。
我们以 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 示例如下图:
Piston 启动配置请参考:https://github.com/engineer-man/piston/blob/master/docs/configuration.md
完整的 piston自定义环境参考:https://github.com/yanjun-ios/piston/tree/master
5. 总结
我们可以通过 Amazon Bedrock 和 Claude 3,Mistral 7B 打造自己的代码助手,同时通过扩展 Piston 提供自定义安全可靠的执行环境,并且始终保持整个数据访问限定在企业内部,满足数据合规要求。项目后续考虑加入 Agent 和文档检索增强,利用 AWS SDK 文档进一步提高 CodeCoach 的代码正确率,通过 Agent 来实现自动化测试。
*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。
附录
- Anthropic’s Claude on Amazon Bedrock
- Mistral AI on Amazon Bedrock
- Piston
本篇作者