diff --git a/python/samples/02-agents/providers/openai/client_with_code_interpreter_files_download.py b/python/samples/02-agents/providers/openai/client_with_code_interpreter_files_download.py new file mode 100644 index 0000000000..079091e12a --- /dev/null +++ b/python/samples/02-agents/providers/openai/client_with_code_interpreter_files_download.py @@ -0,0 +1,165 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio +import os +from pathlib import Path + +from agent_framework import Agent +from agent_framework.openai import OpenAIChatClient +from dotenv import load_dotenv +from openai import AsyncOpenAI + +# Load environment variables from .env file +load_dotenv() + +""" +OpenAI Chat Client — Code Interpreter Graph Generation from CSV + +This sample demonstrates using code interpreter with the Responses API to: + 1. Upload a local CSV file via the Files API + 2. Pass the file to code interpreter via get_code_interpreter_tool(file_ids=[...]) + 3. Ask the model to generate matplotlib charts from the data + 4. Download generated chart images from container file citations + 5. Clean up the uploaded file + +The generated images are saved to the current working directory. +""" + + +def extract_file_citations(result) -> list[dict[str, str]]: + """Extract container file citation metadata from an AgentResponse. + + Returns a list of dicts with 'container_id', 'file_id', and 'filename' keys. + """ + citations = [] + for message in result.messages: + for content in message.contents: + if not content.annotations: + continue + for ann in content.annotations: + if ann.get("type") == "citation" and ann.get("file_id"): + props = ann.get("additional_properties", {}) + container_id = props.get("container_id") + if not container_id: + continue + citations.append({ + "container_id": container_id, + "file_id": ann["file_id"], + "filename": ann.get("url") or "", + }) + return citations + + +async def upload_csv(openai_client: AsyncOpenAI, csv_path: str) -> str: + """Upload a local CSV file for code interpreter use. + + Args: + openai_client: The underlying AsyncOpenAI client. + csv_path: Local path to the CSV file. + + Returns: + The uploaded file ID. + """ + with open(csv_path, "rb") as f: + uploaded = await openai_client.files.create(file=f, purpose="assistants") + print(f"Uploaded {csv_path} → {uploaded.id}") + return uploaded.id + + +async def cleanup_uploaded_file(openai_client: AsyncOpenAI, file_id: str) -> None: + """Delete an uploaded file.""" + await openai_client.files.delete(file_id) + print(f"Deleted uploaded file: {file_id}") + + +async def download_container_file( + openai_client: AsyncOpenAI, + container_id: str, + file_id: str, + output_path: str, +) -> str: + """Download a file from an OpenAI container and save it locally. + + Args: + openai_client: The underlying AsyncOpenAI client. + container_id: The container ID from the citation metadata. + file_id: The file ID from the citation metadata. + output_path: Local path to save the downloaded file. + + Returns: + The output path where the file was saved. + """ + response = await openai_client.containers.files.content.retrieve( + file_id=file_id, + container_id=container_id, + ) + with open(output_path, "wb") as f: + f.write(response.content) + print(f"Downloaded {output_path} ({len(response.content)} bytes)") + return output_path + + +async def main() -> None: + model = os.environ["AZURE_OPENAI_MODEL"] + endpoint = os.environ["AZURE_OPENAI_ENDPOINT"] + api_key = os.environ["AZURE_OPENAI_API_KEY"] + + client = OpenAIChatClient( + model=model, + azure_endpoint=endpoint, + api_key=api_key, + ) + + # Upload the CSV and configure code interpreter with the file + csv_path = str(Path(__file__).parents[3] / "shared" / "sample_assets" / "sample_city_populations.csv") + openai_client = client.client + uploaded_file_id = await upload_csv(openai_client, csv_path) + + agent = Agent( + client=client, + instructions=( + "You are a data visualization assistant. " + "When asked to create charts or graphs, use matplotlib to generate them. " + "Always save charts with plt.savefig before plt.show." + ), + tools=client.get_code_interpreter_tool(file_ids=[uploaded_file_id]), + ) + + query = ( + "Read the uploaded CSV file and create two charts: " + "1) A horizontal bar chart of the top 10 cities by population, " + "2) A pie chart showing population distribution by continent. " + "Use a nice color palette, add titles and labels." + ) + print(f"\nUser: {query}\n") + + result = await agent.run(query) + + # Print the text response + print(f"Assistant: {result.text}\n") + + # Print any generated Python code + for message in result.messages: + for content in message.contents: + if content.type == "code_interpreter_tool_call" and content.inputs: + print("Generated code:") + print(content.inputs[0].text) + print() + + # Download generated chart images from container file citations + citations = extract_file_citations(result) + for i, cite in enumerate(citations): + filename = cite["filename"] or f"output_{i}.png" + await download_container_file( + openai_client, + container_id=cite["container_id"], + file_id=cite["file_id"], + output_path=filename, + ) + + # Clean up + await cleanup_uploaded_file(openai_client, uploaded_file_id) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/samples/shared/sample_assets/sample_city_populations.csv b/python/samples/shared/sample_assets/sample_city_populations.csv new file mode 100644 index 0000000000..3e363b0080 --- /dev/null +++ b/python/samples/shared/sample_assets/sample_city_populations.csv @@ -0,0 +1,16 @@ +city,country,population_millions,area_sq_km,density_per_sq_km,continent +Tokyo,Japan,37.4,2194,17049,Asia +Delhi,India,32.9,1484,22177,Asia +Shanghai,China,29.2,6341,4607,Asia +São Paulo,Brazil,22.6,1521,14862,South America +Mumbai,India,21.7,603,36003,Asia +Cairo,Egypt,21.3,3085,6906,Africa +Beijing,China,20.9,16411,1274,Asia +Mexico City,Mexico,21.8,1485,14680,North America +Dhaka,Bangladesh,22.4,306,73275,Asia +Osaka,Japan,19.1,225,84889,Asia +New York,USA,18.8,783,24009,North America +Lagos,Nigeria,15.9,1171,13578,Africa +Istanbul,Turkey,15.6,5461,2856,Europe +Buenos Aires,Argentina,15.4,4758,3237,South America +Kolkata,India,15.1,1887,8003,Asia