API Calls to v1/threads/{thread_id}/messages fails with [file] and [file_ids]

Since it is so confusing, here’s a practical example with all types of uploaded files you can put in a message, and the no-library method to run it.

import os, json, urllib3

apikey = os.environ.get("OPENAI_API_KEY")
headers = {"OpenAI-Beta": "assistants=v2",
           "Authorization": f"Bearer {apikey}"
           }

thread_id = "thread_zWknKmTcXoxcxcNgNEU0LGrL"
threads_url = f"https://api.openai.com/v1/threads/{thread_id}/messages"
new_user_message = {  # this message demonstrates all files you might add
  "role": "user",
  "content": [
    {  # text the user has input
      "type": "text",
      "text": ("Say OK if you see the picture. "  # string continues...
                  "List available python tool mount point files. "
                  "List file names that you can search with myfiles_browser."
               ),
    },
    {  # vision for image uploaded to storage, purpose "vision"
      "type": "image_file",
      "image_file": {
        "file_id": "file-y0dKBYzE57mXbYGaLc2MdmdP",
        "detail": "low"
      }
    }
  ],
  "attachments": [
    {  # python file attachment from storage, purpose "assistants"
      "file_id": "file-ESl8xolqhAPS7EzoS9eUrKHU",
      "tools": [{"type": "code_interpreter"}]
    },
    {  # add file to thread's vector store for search, purpose "assistants"
      "file_id": "file-hE850BH10qGiVu3ZlXea8omS",
      "tools": [{"type": "file_search"}]
    }
  ],
  #  add information for your own use
  "metadata": {"customer_id": "cust-3353"}
}
try:
    http = urllib3.PoolManager()
    encoded_body = json.dumps(new_user_message).encode('utf-8')
    response = http.request('POST', threads_url,
                            headers=headers,
                            body=encoded_body
                            )
    response = json.loads(response.data.decode('utf-8'))
    print(json.dumps(response, indent=3))
    message_id = response['id']
except Exception as e:
    print(e)
    raise

(the failure of file_search is the AI not knowing what is behind a search)

You must use your own thread ID and file IDs with the correct purpose.

Your actual new user message function must adapt to any number of images, code interpreter files, or file search files that could be added on user input.

I hope that illustration helps.