foropenai icon indicating copy to clipboard operation
foropenai copied to clipboard

Bug in request function of http-client package

Open gha3mi opened this issue 2 years ago • 4 comments

There is a bug with the request function of http-client when working with multipart/form-data in subroutine create_transcription.

The issue arises when there are no spaces before the "name" attribute in the form variable for the second and following pairs. Here's an example:

response = request(url=this%url, method=HTTP_POST , header=req_header, form=form_data, file=file_data)

And here's the problematic form definition:

form_data = [&
   pair_type('model', trim(this%model)),&
   pair_type(' language', trim(this%language)),&
   pair_type(' response_format', trim(this%response_format)),&
   pair_type(' prompt', trim(this%prompt)),&
   pair_type(' temperature', trim(tempereture_str))&
   ]

gha3mi avatar Sep 17 '23 19:09 gha3mi

Hello @gha3mi, I've tested the code with my own data, and it appears to be functioning correctly. Could you please specify if you are encountering any errors or receiving unexpected output? Additionally, sharing your code and the output you're experiencing would be helpful. I've personally tested this example, and it's functioning as expected.

program main

  use http, only: response_type, request, HTTP_POST, pair_type
  implicit none
  type(response_type) :: response
  type(pair_type), allocatable :: form_data(:), req_header(:)
  type(pair_type) :: file_data
  character(:), allocatable :: str1, str2, str3

  ! Storing form data in a array of pair_type object, each pair_type object 
  ! represent a single form field
  file_data = pair_type('file.txt', './data/file.txt')
  str1 = ' value1 '
  str2 = ' value2 '
  str3 = ' value3 '
  req_header = [pair_type('content-type', 'multipart/form-data')]
  form_data = [pair_type('param1', trim(str1)), pair_type('param2', trim(str2)), pair_type('param3', trim(str3))]

  response = request(url='https://httpbin.org/post', method=HTTP_POST, file=file_data, form=form_data, header=req_header)
  if(.not. response%ok) then
      print *,'Error message : ', response%err_msg
  else
      print *, 'Response Code    : ', response%status_code
      print *, 'Response Length  : ', response%content_length
      print *, 'Response Method  : ', response%method
      print *, 'Response Content : ', response%content
  end if

end program main

rajkumardongre avatar Sep 18 '23 20:09 rajkumardongre

Thanks, @rajkumardongre. I've tested your program, and it functions correctly.

However, in this case, it requires a space:

form_data = [&
   pair_type('model', trim(this%model)),&
   pair_type(' language', trim(this%language)),&
   pair_type(' response_format', trim(this%response_format)),&
   pair_type(' prompt', trim(this%prompt)),&
   pair_type(' temperature', trim(tempereture_str))&
   ]

The test code can be found in test/test3.f90 and utilizes the create_transcription subroutine from the foropenai_Transcription module. create_transcription subroutine is available here: https://github.com/gha3mi/foropenai/blob/a80f469ff4588b13229f2f7f89a2a5fd388e1727/src/foropenai_Transcription.f90#L366C1-L418C84

The result of test3 is as follows: text: FORTRAN stands for Formula Translation.

Note: An OpenAI API key is required to use this program. If your account has credit, you can obtain an API key from this link and make sure to set it in the foropenai.json config file.

gha3mi avatar Sep 18 '23 22:09 gha3mi

@rajkumardongre This is the equivalent curl command that functions correctly:

curl https://api.openai.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: multipart/form-data" \
  -F file="@test/audio.mp3" \
  -F model="whisper-1" \
  -F language="en" \
  -F response_format="json" \
  -F prompt=""\
  -F temperature="0.0"

When using http-client:

program test_transcription

   use http, only: response_type, request, HTTP_POST, pair_type

   type(pair_type),    allocatable :: req_header(:), form_data(:), file_data
   character(len=:),   allocatable :: api_key
   type(response_type)             :: response

   api_key = 'OPENAI_API_KEY'

   req_header = [&
      pair_type('Authorization', 'Bearer '//trim(api_key)),&
      pair_type('Content-Type', 'multipart/form-data')&
      ]

   ! This format doesn't work:
   form_data = [&
      pair_type('model'          , "whisper-1"),&
      pair_type('language'       , "en"),&
      pair_type('response_format', "json"),&
      pair_type('prompt'         , ""),&
      pair_type('temperature'    , "0.0")&
      ]

   ! This format functions correctly:
   ! form_data = [&
   !    pair_type('model', 'whisper-1'),&
   !    pair_type(' language', 'en'),&
   !    pair_type(' response_format', 'json'),&
   !    pair_type(' prompt', ''),&
   !    pair_type(' temperature', '0.0')&
   !    ]

   ! This format functions correctly:
   ! form_data = [&
   !    pair_type('model', 'whisper-1'),&
   !    pair_type('Language', 'en'),& ! Language instead of language
   !    pair_type('response_format', 'json'),&
   !    pair_type('prompt', ''),&
   !    pair_type('Temperature', '0.0')& ! Temperature instead of temperature
   !    ]

   file_data = pair_type('file', 'test/audio.mp3')

   response = request(&
      url    = "https://api.openai.com/v1/audio/transcriptions",&
      method = HTTP_POST,&
      header = req_header,&
      form   = form_data,&
      file   = file_data)

   print*, response%content

end program test_transcription

Edit: Updated test program.

gha3mi avatar Sep 19 '23 10:09 gha3mi

@rajkumardongre, here's another issue with multipart/form-data when using the http-client, whereas the curl command functions correctly. Issue #33.

gha3mi avatar Sep 19 '23 15:09 gha3mi