前言
上个月,写了本小册 —— 扣子:Al Bots 通关指南 ,销量破千,里面提到:“2023 年是大模型底层技术研发‘百团大战’之年,2024 年则是大模型应用赋能千行百业‘百花齐放’之年”。
当然,借助像扣子这样平台,我们可以做到“傻瓜式”创建 AIGC 应用。但对于程序员来说,这样似乎缺少点“技术范”?实际上,扣子平台有其底层模型较单一的局限性;且由于无需写代码对接大模型(配置、传参、调用等实操),我们对大模型的认识上算是一个“黑盒”。即使能成功创建一个应用,但是并没有充分利用大模型的一些特性,特别是同一模型下的不同版本设计。所以,如果想要打开个这个黑盒进去一探究竟,我们就必须得深入到代码中、深入构建过程中、深入大模型中,或许黑盒只是眼前的“初极狭、才通人”,只需复行“数十步”,便能豁然看朗,看到桃花源~
本篇以 Claude 模型举例,实战 Claude3 + GO + AWS,深入构建 AIGC 应用,发现 Claude3 不同版本的设计机制。
Anthropic Claude 被誉为是 ChatGPT 最强对手之一,基准测试结果甚至优于 GPT-4。它包括 3 个主要版本: Claude3 Haiku、Claude3 Sonnet 和 Claude3 Opus,其中 Haiku 响应迅速、Sonnet 有更好的创作能力、Opus 则能处理高度复杂的任务;Claude3 是多模态的,可以接受文本或图像作为输入,可以执行复杂的多模态分析。
简单上手
开始之前要先安装好 GO,配置 Amazon Bedrock 访问以及获取 IAM 权限;
让我们先从一个简单的示例开始:AWS SDK for Go (v2) ;
运行操作如下:
git clone https://github.com/abhirockzz/claude3-bedrock-go
cd claude3-bedrock-go
go run basic/main.go
运行结果参考如下:
request payload: {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [{
"type": "text",
"text": "Hello, what's your name?"
}]
}]
}
response payload: {
"id": "msg_015e3SJ99WF6p1yhGTkc4HbK",
"type": "message",
"role": "assistant",
"content": [{
"type": "text",
"text": "My name is Claude. It's nice to meet you!"
}],
"model": "claude-3-sonnet-28k-20240229",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 14,
"output_tokens": 15
}
}
response string: My name is Claude.It 's nice to meet you!'
稍微来解释一下代码:
- 首先定义一条用户输入的消息
msg
。 - 构造一个
Claude3Request
类型的请求负载payload
,包括模型版本、最大令牌数和包含用户消息的消息列表。 - 将
payload
序列化为 JSON 格式,结果存储在payloadBytes
中,便于后续发送给生成式 AI 模型。
msg := "Hello, what's your name?"
payload := Claude3Request{
AnthropicVersion: "bedrock-2023-05-31",
MaxTokens: 1024,
Messages: []Message{
{
Role: "user",
Content: []Content{
{
Type: "text",
Text: msg,
},
},
},
},
}
payloadBytes, err := json.Marshal(payload)
这样做的目的是准备好格式化的请求数据,以便通过网络传输发送给生成式 AI 模型进行处理。
//....
output, err := brc.InvokeModel(context.Background(), &bedrockruntime.InvokeModelInput{
Body: payloadBytes,
ModelId: aws.String(modelID),
ContentType: aws.String("application/json"),
})
var resp Claude3Response
err = json.Unmarshal(output.Body, &resp)
log.Println("response string:\n", resp.ResponseContent[0].Text)
InvokeModel 用于模型调用,传递包含请求负载、模型 ID 和内容类型的输入参数;检查调用过程中是否发生错误,如果调用成功,将返回的 JSON 响应体反序列化为 Claude3Response
类型的变量。
流式输出
为了体验更丝滑,我们还要保证客户端应用不用等生成完整的响应内容后再反馈给用户,需要添加一个流动输出的处理步骤。
可以运行如下代码:
go run chat-streaming/main.go
流动输出所涉及的要点较多:首先,我们要使用:InvokeModelWithResponseStream
方法;
output, err := brc.InvokeModelWithResponseStream(context.Background(), &bedrockruntime.InvokeModelWithResponseStreamInput{
Body: payloadBytes,
ModelId: aws.String(modelID),
ContentType: aws.String("application/json"),
})
resp, err := processStreamingOutput(output, func(ctx context.Context, part []byte) error {
fmt.Print(string(part))
return nil
})
再将上述代码的输出继续处理:
resp, err := processStreamingOutput(output, func(ctx context.Context, part []byte) error {
fmt.Print(string(part))
return nil
})
//...
processStreamingOutput
函数是重点,函数用于处理从InvokeModelWithResponseStreamOutput
接收到的流式输出,并调用一个处理函数 StreamingOutputHandler
来处理部分响应。
func processStreamingOutput(output *bedrockruntime.InvokeModelWithResponseStreamOutput, handler StreamingOutputHandler) (Claude3Response, error) {
var combinedResult string
var resp Claude3Response // 最终的响应结构
for event := range output.GetStream().Events() {
switch v := event.(type) {
case *types.ResponseStreamMemberChunk:
var pr PartialResponse
// 解码事件中的JSON数据到部分响应结构
err := json.NewDecoder(bytes.NewReader(v.Value.Bytes)).Decode(&pr)
if err != nil {
return resp, err
}
if pr.Type == partialResponseTypeContentBlockDelta {
// 如果是内容块增量类型
handler(context.Background(), []byte(pr.Delta.Text))
combinedResult += pr.Delta.Text
} else if (pr.Type == partialResponseTypeMessageStart) {
// 如果是消息开始类型
resp.ID = pr.Message.ID
resp.Usage.InputTokens = pr.Message.Usage.InputTokens
} else if (pr.Type == partialResponseTypeMessageDelta) {
// 如果是消息增量类型
resp.StopReason = pr.Delta.StopReason
resp.Usage.OutputTokens = pr.Message.Usage.OutputTokens
}
}
}
// 将累积的结果文本存储在响应中
resp.ResponseContent[0].Text = combinedResult
return resp, nil
}
这段代码是一个用于处理流式输出的函数。它接受一个指向流式输出的指针 output
和一个流式输出处理器 handler
,并返回一个 Claude3Response
结构。
解析图片
虽然 Claude3 的3个版本都支持将图像作为输入,但是 Haiku 版本更擅长处理 OCR、理解图像。
我们尝试用 StackOverflow 问题截图,并给予提示“提取图像中的代码,只输出代码”;
运行如下:main.go
go run images/main.go
Claude3 Haiku 输出:
func test() {
cluster := gocql.NewCluster("hostIP")
cluster.ProtoVersion = 4
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: "<username>",
Password: "<password>",
}
cluster.Keyspace = "myspace"
cluster.Consistency = gocql.One
session, err := cluster.CreateSession()
if err != nil {
panic(err)
}
defer session.Close()
}
然后,我们尝试继续追问:你能解答这个代码问题吗?
回答如下:
根据图片中显示的错误信息,问题似乎与使用 gocql 驱动程序(Go: 1.8.3)连接到 Cassandra 数据库有关。错误表明应用程序无法拨号控制连接、创建会话或在超时时间内连接到初始主机。
可以尝试以下方法来排查此问题:
1. 验证连接详细信息(主机、端口、用户名、密码)是否正确,并与 Cassandra 集群配置匹配。
2. 确保 Cassandra 集群正在运行,并且应用程序主机可以访问该集群。
3. 检查防火墙设置,确保应用程序主机能够在所需端口上连接到 Cassandra 集群。
4. 检查 Cassandra 服务器日志,寻找任何与连接问题相关的错误或线索。
5. 尝试增加 gocql 配置中的超时值,因为当前的超时可能对 Cassandra 集群响应来说太短。
6. 确保 gocql 版本(1.8.3)与您使用的 Cassandra 版本(3.10.0)兼容。
7. 考虑在代码中添加一些错误处理和重试逻辑,以优雅地处理连接失败并尝试重新连接。
在没有更多关于具体设置和环境的信息的情况下,很难精确定位问题。然而,上述步骤应能帮助您调查问题并找到解决方案。
特别要说明的是,在 main.go 中,imageContents
属性是图像 base64
编码后的内容,其计算方式如下:
func readImageAsBase64(filePath string) (string, error) {
imageFile, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
encodedString := base64.StdEncoding.EncodeToString(imageFile)
return encodedString, nil
}
文本和图片
大模型对话中并非只能是文本,或者只能是图片,有一种消息结构能够兼容二者结构,那就是灵活的 JSON。
运行代码如下:main.go
go run multi-modal-chat-streaming/main.go
这个示例中可以混合匹配文本+图片的消息;
结果:
AWS 服务
以上测试不必依赖 Python 来构建,借助 AWS 工具包,选择你喜欢的语言(本篇以 GO 语言为示例),最后在 Amazon Bedrock 中集成。
Amazon Bedrock 是完全托管的服务,可以调用来自Anthropic、Meta、Stability AI 和 Amazon 等高性能大模型,由于它是是无服务器的,所以你无需管理任何基础设施,使用已经熟悉的 AWS 服务,即可将生成式人工智能功能安全地集成和部署到应用程序中。
Amazon Bedrock 是亚马逊云生成式人工智能模块下的产品,最近,亚马逊云科技中国峰会 5月29日-30日 将在上海世博中心举办,是每年都会举行的、国内比较顶尖的科技峰会 —— 传送门;有机会去到现场看看,或者可以到 官方网站、注册免费享云服务、测试一下如何手动部署各种大模型应用。—— 粉丝专享报名通道:web 端 和 小程序 冲就完事!!