如何使用vapor客户端发送多部分表单数据请求

问题描述 投票:0回答:1

我正在尝试在我的 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
swift multipartform-data vapor openai-whisper
1个回答
0
投票

我最终创建了一个小函数来创建多部分表单数据。我找不到任何有关 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)
}
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.