我正在尝试在我的 Vapor 应用程序中向 openai 的语音到文本 api 发送请求。该 API 接受多部分/表单数据请求。我不知道如何使用 steam 的客户端 api。对于 json 请求,可以相当容易地发送请求。
let resp = try await client.post(
"https://api.openai.com/v1/chat/completions",
headers: HTTPHeaders([
("Content-Type", "application/json"),
("Authorization", "Bearer \(memoKey)")
]),
content: reqData
)
对于多部分表单数据,我已经尝试过,但 api 给出了
Could not parse multipart form
错误
struct SpeechToTextRequest: Content {
var model = "whisper-1"
var file: Data
}
func makeSpeechToTextRequest(
client: Client,
audio: Data
) async throws {
let result = try await client.post(
"https://api.openai.com/v1/audio/transcriptions",
headers: [
"Content-Type": "multipart/form-data",
"Authorization": "Bearer \(memoKey)"
],
beforeSend: { req in
let encoder = FormDataEncoder()
let encoded = try encoder.encode(
SpeechToTextRequest(file: audio),
boundary: ""
)
req.body = ByteBuffer(string: encoded)
}
)
print(result)
}
这里是请求的curl字符串作为参考
curl --request POST \
--url https://api.openai.com/v1/audio/transcriptions \
--header "Authorization: Bearer $OPENAI_API_KEY" \
--header 'Content-Type: multipart/form-data' \
--form file=@/path/to/file/openai.mp3 \
--form model=whisper-1
我最终创建了一个小函数来创建多部分表单数据。我找不到任何有关 Vapor 的 MultiPartKit 库的文档。
以下是请求的构造方式:
func makeSpeechToTextRequest(
client: Client,
audio: Data
) async throws -> SpeechToTextResponse {
let result = try await client.post(
"https://api.openai.com/v1/audio/transcriptions",
headers: [
"Authorization": "Bearer \(memoKey)"
],
beforeSend: { req in
let (body, contentType) = createMultipartFormData(from: [
.file(fileName: "speech.mp3", fileType: "audio/mp3", fileData: audio),
.string(name: "model", value: "whisper-1"),
.string(name: "response_format", value: "verbose_json"),
.string(name: "timestamp_granularities[]", value: "word")
])
req.body = body
req.headers.contentType = contentType
}
)
return try result.content.decode(SpeechToTextResponse.self)
}
这是辅助函数:
private enum MultipartField {
case string(name: String, value: String)
case file(fileName: String, fileType: String, fileData: Data)
}
private func createMultipartFormData(from fields: [MultipartField]) -> (ByteBuffer, HTTPMediaType) {
let boundary = UUID().uuidString
var buffer = ByteBuffer()
for field in fields {
switch field {
case let .file(fileName, fileType, fileData):
buffer.writeString("--\(boundary)\r\n")
buffer.writeString("Content-Disposition: form-data; name=\"file\"; filename=\"\(fileName)\"\r\n")
buffer.writeString("Content-Type: \(fileType)\r\n\r\n")
buffer.writeData(fileData)
buffer.writeString("\r\n")
case let .string(name, value):
buffer.writeString("--\(boundary)\r\n")
buffer.writeString("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n")
buffer.writeString("\(value)\r\n")
}
}
buffer.writeString("--\(boundary)--\r\n")
let mediaType = HTTPMediaType(
type: "multipart",
subType: "form-data",
parameters: ["boundary": boundary]
)
return (buffer, mediaType)
}