Let’s call for my PDF generation locally.
In a demo script for non-streaming, I have a parser that understands the response.output
list items. I haven’t written it to fully offer up files translated to a storage download.
[0]: Content found to return
[1]: Output list item of type ‘code_interpreter_call’ skipped
[2]: Content found to return
To create a one-page PDF with 1 cm randomly-colored squares, I will:
- Define the page size (e.g., A4).
- Calculate how many 1 cm squares fit horizontally and vertically.
- Draw each square with a random color.
I’ll generate the PDF now.
I have created the one-page PDF with 1 cm randomly-colored squares. You can download it here:
[random_colored_squares.pdf](sandbox:/mnt/data/random_colored_squares.pdf)
So in the AI output, you have a link that should be captured and rewritten.
But with what?
Let’s look at that code interpreter output more closely.
"output": [
{
"id": "msg_6833e",
"content": [
{
"annotations": [],
"text": "To create a one-page PDF with 1 cm randomly-colored squares, I will:\n\n1. Define the page size (e.g., A4).\n2. Calculate how many 1 cm squares fit horizontally and vertically.\n3. Draw each square with a random color.\n\nI'll generate the PDF now.",
"type": "output_text"
}
],
"role": "assistant",
"status": "completed",
"type": "message"
},
There was the AI writing about its intention to use the tool. AI can produce text as well as calling functions in a response, this isn’t just iteration done internally.
Then we get our next item returned. Responses abstracts away that this was the same “assistant” generation.
{
"id": "ci_6833e6",
"code": "from reportlab.lib.pagesizes import A4\nfrom reportlab.pdfgen import canvas\nfrom reportlab.lib.units import cm\nimport random\n\n# Page size\nwidth, height = A4\n\n# Square size in points (1 cm)\nsquare_size = 1 * cm\n\n# Number of squares horizontally and vertically\nnum_squares_x = int(width // square_size)\nnum_squares_y = int(height // square_size)\n\n# Create PDF\npdf_path = \"/mnt/data/random_colored_squares.pdf\"\nc = canvas.Canvas(pdf_path, pagesize=A4)\n\n# Draw squares\nfor i in range(num_squares_x):\n for j in range(num_squares_y):\n # Random color\n r = random.random()\n g = random.random()\n b = random.random()\n c.setFillColorRGB(r, g, b)\n \n # Draw square\n x = i * square_size\n y = j * square_size\n c.rect(x, y, square_size, square_size, fill=1, stroke=0)\n\nc.showPage()\nc.save()\n\npdf_path",
"results": null,
"status": "completed",
"type": "code_interpreter_call",
"container_id": "cntr_6833e",
"outputs": [
{
"type": "logs",
"logs": "'/mnt/data/random_colored_squares.pdf'"
}
]
},
This object is a result of include=["code_interpreter_call.outputs",]
- along with four other strings that can go in that list to receive other stuff.
You can see the written code, the output produced and returned to the AI from that code. And more, you will get multiple if there are internal “steps”, such as the AI looping in writing bad code and trying again.
The display is useful for a UI, not needed for our file mapping to storage.
We now get to the AI output after it is finished with coding.
{
"id": "msg_6833e6",
"content": [
{
"annotations": [
{
"file_id": "cfile_6833e6",
"index": null,
"type": "container_file_citation",
"container_id": "cntr_6833e6",
"end_index": 168,
"filename": "random_colored_squares.pdf",
"start_index": 124
}
],
"text": "I have created the one-page PDF with 1 cm randomly-colored squares. You can download it here:\n\n[random_colored_squares.pdf](sandbox:/mnt/data/random_colored_squares.pdf)",
"type": "output_text"
}
],
"role": "assistant",
"status": "completed",
"type": "message"
}
]
End of the “output” list from the API response object.
Beside the text response for display (or that might stream), we have an annotation
. You can see it is type "container_file_citation"
- and has both the autogenerated code container ID, a file ID, and also, a plain file name, the same as the mount point file.
Then you’ve just got go grabbing and downloading, and fast, and retain for the user beyond the quick expiry of the code interpreter “session” container.
You can see that the playground doesn’t look back at a stream and rewrite the annotation into it. If you are not streaming, it is easy for you to not need to hold back markdown URLs or to rewrite the UI: you can just regex the text, with where you want to serve the file from.
Here’s your API Reference link for retrieving out of “Containers”: https://platform.openai.com/docs/api-reference/container-files/retrieveContainerFile