这篇帖子会教你如何使用Amazon Bedrock创建一个原汁原味的iOS应用,利用Swift来实现AI驱动的聊天和图像功能。我们会用到无服务器资源,包括AWS Lambda和API Gateway这两个工具,作为后端支持。
示例应用程序包括以下内容:
- 一个使用 Swift 开发的移动应用
- 将 Amazon Bedrock 集成,使用模型
amazon.titan-image-generator-v1
和ai21.j2-mid-v1
- 使用 AWS Lambda 和 TypeScript 实现无服务器后端处理
- 通过 Amazon API Gateway 实现 RESTful API 通信
- 使用 Amazon CloudWatch Logs 监控 AWS Lambda 函数并查看日志信息
最终你将得到的应用程序如下:
前提条件
请确保你已经安装了以下软件,再开始。
- 一个 AWS 账户(Account)
- Node.js v18 或更新版本
- Serverless Framework、AWS SAM 或 AWS CDK(取决于你是否想使用基础设施即代码。我会用 Serverless Framework)
- 包管理器(比如我会使用 Yarn)
- Xcode 版本 15 或更新版本
建筑
- 用户通过他们的移动设备访问应用程序,应用程序随后向 Amazon API Gateway 发送请求。
- API Gateway 将请求路由到
ImageFunction
或TextFunction
(Lambda 函数)。 - AWS Lambda 与 Amazon Bedrock 模型进行通信,并以 JSON 格式检索生成的响应。
- 处理后的响应被发送回应用程序,以便显示并启用内容或聊天交互。
要访问Amazon Bedrock,请按照以下步骤操作
- 登录您的 AWS 控制台
- 转到 Amazon Bedrock
- 在左侧导航中点击“模型访问”
- 请求访问,请注意
body
可能会根据您选择的模型有所不同。我将使用Titan Image Generator G1
模型来生成图像,以及使用ai21.j2-mid-v1
模型来生成文字
使用 TypeScript 和 AWS Lambda 进行 Serverless 后端处理
你可以选择你喜欢的工具来部署 Lambda 函数(或 Lambda 函数部署工具),比如我会提供必要的代码来创建这些函数。
文字 Lambda
请留意以下几点哦:
- 需要先导入
client-bedrock-runtime
包。 - 还需要添加 modelId 参数。
- prompt 就是从你 API 提供的搜索文本。
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';
const client = new BedrockRuntimeClient({ region: 'us-east-1' });
export async function handler(event: any) {
const prompt = JSON.parse(event.body).prompt;
const input = {
modelId: 'ai21.j2-mid-v1',
contentType: 'application/json',
accept: '*/*',
headers: {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Methods': 'POST'
},
body: JSON.stringify({
prompt: prompt,
maxTokens: 200,
temperature: 0.7,
topP: 1,
stopSequences: [],
countPenalty: { scale: 0 },
presencePenalty: { scale: 0 },
frequencyPenalty: { scale: 0 }
})
};
try {
const data = await client.send(new InvokeModelCommand(input));
const jsonString = Buffer.from(data.body).toString('utf8');
const parsedData = JSON.parse(jsonString);
const text = parsedData.completions[0].data.text;
return text;
} catch (error) {
console.error(error);
}
}
全屏/退出全屏
图像Lambda
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';
const client = new BedrockRuntimeClient({ region: 'us-east-1' });
export async function handler(event: any) {
const prompt = JSON.parse(event.body).prompt;
const input = {
modelId: 'amazon.titan-image-generator-v1',
contentType: 'application/json',
accept: 'application/json',
headers: {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Methods': 'POST'
},
body: JSON.stringify({
textToImageParams: {
text: prompt
},
taskType: 'TEXT_IMAGE',
imageGenerationConfig: {
cfgScale: 10,
seed: 0,
width: 512,
height: 512,
numberOfImages: 1
}
})
};
try {
const command = new InvokeModelCommand(input);
const response = await client.send(command);
const blobAdapter = response.body;
const textDecoder = new TextDecoder('utf-8');
const jsonString = textDecoder.decode(blobAdapter.buffer);
try {
const parsedData = JSON.parse(jsonString);
return parsedData.images[0];
} catch (error) {
console.error(`解析 JSON 出错: ${error}`);
return 'TextError';
}
} catch (error) {
console.error(error);
}
}
进入全屏,退出全屏
现在部署你的Lambda,如果你使用Serverless框架的话,可以使用以下配置:
service: aws-bedrock-ts
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs18.x
timeout: 30
iam:
role:
statements:
- Effect: '授权'
Action:
- 'bedrock:InvokeModel'
Resource: '*'
functions:
bedrockText:
handler: src/bedrock/text.handler
name: 'aws-bedrock-text'
events:
- httpApi:
path: /bedrock/text
method: post
bedrockImage:
handler: src/bedrock/image.handler
name: 'aws-bedrock-image'
events:
- httpApi:
path: /bedrock/image
method: post
点击进入全屏模式 按退出全屏模式
请保存好你的Lambda API端点。
开发你的 iOS 应用程序吧
搭建项目
- 首先,在 Xcode 中创建一个新的 Swift 项目。
- 将项目命名为你的应用名(比如叫 BedrockSwift)。
聊天消息模型
首先,定义一个模型来存储聊天消息,比如说,这些消息可以是文本或图片。我将这个类命名为ChatMessage.swift:
import UIKit
struct ChatMessage: Equatable {
var text: String?
var image: UIImage?
var isImage: Bool
var isUser: Bool
}
注:以上代码片段为未翻译的 Swift 代码。
全屏模式 退出全屏
API请求处理
此服务负责管理API交互,包括将提示发送到您的Lambda函数并处理响应。请确保将端点更新到您的Lambda函数。我将我的类命名为APIService.swift,
import UIKit
class APIService: ObservableObject {
@Published var messages: [ChatMessage] = []
private func getEndpointURL(for type: String) -> URL? {
let baseURL = "https://example.execute-api.region.amazonaws.com/bedrock"
switch type {
case "text":
return URL(string: "\(baseURL)/text")
case "image":
return URL(string: "\(baseURL)/image")
default:
return nil
}
}
func addUserPrompt(_ prompt: String) {
messages.append(ChatMessage(text: prompt, image: nil, isImage: false, isUser: true))
}
func sendRequest(prompt: String, type: String, completion: @escaping () -> Void) {
guard let url = getEndpointURL(for: type) else {
print("无效的URL类型: \(type)")
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let parameters: [String: Any] = ["prompt": prompt]
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("错误: \(error)")
return
}
guard let data = data else { return }
print(data)
DispatchQueue.main.async {
if type == "text" {
if let responseString = String(data: data, encoding: .utf8) {
let trimmedResponse = responseString.trimmingCharacters(in: .whitespacesAndNewlines)
self.messages.append(ChatMessage(text: trimmedResponse, image: nil, isImage: false, isUser: false))
}
} else {
DispatchQueue.main.async {
if let base64String = String(data: data, encoding: .utf8),
let imageData = Data(base64Encoded: base64String, options: .ignoreUnknownCharacters),
let image = UIImage(data: imageData) {
DispatchQueue.main.async {
self.messages.append(ChatMessage(text: nil, image: image, isImage: true, isUser: false))
}
}
}
}
completion()
}
}.resume()
}
}
全屏,退出全屏
聊天界面查看
现在,创建一个主要负责处理UI并显示聊天消息的视图。接下来,我将文件命名为BedrockView.swift。
import SwiftUI
struct BedrockView: View {
@StateObject var apiService = APIService()
@State private var prompt: String = ""
@State private var selectedType = 0
@State private var isLoading = false
var body: some View {
VStack {
ScrollViewReader { scrollViewProxy in
ScrollView {
VStack {
ForEach(apiService.messages.indices, id: \.self) { index in
if apiService.messages[index].isImage, let image = apiService.messages[index].image {
HStack {
Spacer()
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(height: 200)
.frame(maxWidth: .infinity, alignment: .leading)
.cornerRadius(10)
.padding(.vertical, 5)
}
} else if let text = apiService.messages[index].text {
HStack {
if apiService.messages[index].isUser {
Spacer()
Text(text)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.background(Color.blue.opacity(0.2))
.cornerRadius(10)
.frame(maxWidth: .infinity, alignment: .trailing)
} else {
Text(text)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(.vertical, 1)
}
}
if isLoading {
ProgressView("加载中...")
.padding(.vertical, 20)
}
}
.padding(.horizontal)
.id("BOTTOM")
}
.onChange(of: apiService.messages) { _ in
withAnimation {
scrollViewProxy.scrollTo("BOTTOM", anchor: .bottom)
}
}
}
VStack {
TextField("输入提示内容...", text: $prompt)
.textFieldStyle(.roundedBorder)
.padding(.horizontal)
.padding(.vertical, 10)
HStack {
Picker(selection: $selectedType, label: Text("选择类型")) {
Text("文本").tag(0)
Text("图片").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.frame(maxWidth: .infinity)
.padding(.leading, 10)
Button(action: {
if prompt.isEmpty { return }
apiService.addUserPrompt(prompt)
let type = selectedType == 0 ? "text" : "image"
isLoading = true
apiService.sendRequest(prompt: prompt, type: type) {
isLoading = false
}
prompt = ""
}) {
Text("发送消息")
.frame(width: 100, height: 2)
.padding()
.background(Color.primary)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding(.horizontal)
}
.padding()
}
}
}
全屏 和 退出全屏
应用启动页面
打开你的文件:YourAppNameApp.swift,并更新你在创建 SwiftUI 项目时设置的默认入口点。我的默认入口点命名为 BedrockView,如前所述。
import SwiftUI
@main
struct BedrockSwiftApp: App {
// 主体结构定义
var body: some Scene {
// 定义窗口组
WindowGroup {
// 显示BedrockView视图
BedrockView()
}
}
}
点击全屏模式。点击退出全屏。
在 Xcode 中启动应用
现在你可以启动你的应用了!请按照以下步骤在Xcode中启动应用。
- 从工具栏中选择一个模拟器或连接的设备作为您的设备。
- 点击运行按钮(或按 Cmd + R)来构建并运行应用。
这将在你选择的设备上启动应用,让你可以与Amazon Bedrock的聊天和图像生成功能互动。
有几点要注意哦
由于这是一个用于本地测试的应用,我已经将 Access-Control-Allow-Origin
设置为 *
。此外,你可能还需要在 API Gateway 中调整 CORS 设置。
请注意,API 调用可能会产生小额费用。有关详细定价信息参阅Amazon Bedrock 定价页面。
GitHub 存储库
该项目的源代码已托管在GitHub上:
- 后端代码: amazon-bedrock-back
- iOS App: amazon-bedrock-app
结论部分
在这篇文章中,我带您了解如何使用原生的Swift语言以及无服务器的AWS服务(如AWS Lambda和API Gateway等)来构建一个简单的AI聊天应用程序。通过将Amazon Bedrock的生成式AI模型与这些服务集成,我们创建了一个流畅的解决方案,充分利用了AWS的功能,并提供原生的移动体验。请注意,我在应用程序中尽力只使用了原生组件,尽管还有改进的空间。此外,保护您的API使用令牌至关重要;我将在接下来的文章中详细讨论这个话题。
共同學習,寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章