代码之家  ›  专栏  ›  技术社区  ›  mustafa

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

  •  1
  • mustafa  · 技术社区  · 1 年前

    我正试图在我的vapor应用程序中向openai的语音转文本api发送请求。该api接受多部分/表单数据请求。我不知道如何使用vapor的客户端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
    
    0 回复  |  直到 1 年前
        1
  •  2
  •   mustafa    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)
    }