From 15433700275a2a26071016d5f824f78feb0527bb Mon Sep 17 00:00:00 2001 From: Eric Zhu Date: Thu, 30 Oct 2025 15:02:31 -0700 Subject: [PATCH] Python: Lab: Updates to GAIA module (#1763) * Lab: Updates to GAIA module * update * emoj! * fix lint * update lab test workflow to only trigger for python changes * lint * lint * Fix broken OpenAI agents JS documentation link --- .github/workflows/python-lab-tests.yml | 26 ++ docs/decisions/0006-userapproval.md | 1 - python/packages/lab/gaia/README.md | 15 - .../lab/gaia/agent_framework_lab_gaia/gaia.py | 136 ++++++--- .../lab/gaia/samples/azure_ai_agent.py | 67 +++++ .../packages/lab/gaia/samples/gaia_sample.py | 283 ++++++++++++++++-- .../packages/lab/gaia/samples/openai_agent.py | 64 ++++ python/packages/lab/pyproject.toml | 9 +- python/uv.lock | 63 +++- 9 files changed, 575 insertions(+), 89 deletions(-) create mode 100644 python/packages/lab/gaia/samples/azure_ai_agent.py create mode 100644 python/packages/lab/gaia/samples/openai_agent.py diff --git a/.github/workflows/python-lab-tests.yml b/.github/workflows/python-lab-tests.yml index c5af889dce..bae78be27c 100644 --- a/.github/workflows/python-lab-tests.yml +++ b/.github/workflows/python-lab-tests.yml @@ -16,8 +16,34 @@ env: UV_CACHE_DIR: /tmp/.uv-cache jobs: + paths-filter: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + outputs: + pythonChanges: ${{ steps.filter.outputs.python}} + steps: + - uses: actions/checkout@v5 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + python: + - 'python/**' + # run only if 'python' files were changed + - name: python tests + if: steps.filter.outputs.python == 'true' + run: echo "Python file" + # run only if not 'python' files were changed + - name: not python tests + if: steps.filter.outputs.python != 'true' + run: echo "NOT python file" + python-lab-tests: name: Python Lab Tests + needs: paths-filter + if: needs.paths-filter.outputs.pythonChanges == 'true' runs-on: ${{ matrix.os }} strategy: fail-fast: true diff --git a/docs/decisions/0006-userapproval.md b/docs/decisions/0006-userapproval.md index 4512044f65..63ca8bc0fb 100644 --- a/docs/decisions/0006-userapproval.md +++ b/docs/decisions/0006-userapproval.md @@ -22,7 +22,6 @@ This document aims to provide options and capture the decision on how to model t See various features that would need to be supported via this type of mechanism, plus how various other frameworks support this: - Also see [dotnet issue 6492](https://github.com/dotnet/extensions/issues/6492), which discusses the need for a similar pattern in the context of MCP approvals. -- Also see [the openai RunToolApprovalItem](https://openai.github.io/openai-agents-js/openai/agents/classes/runtoolapprovalitem/). - Also see [the openai human-in-the-loop guide](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#approval-requests). - Also see [the openai MCP guide](https://openai.github.io/openai-agents-js/guides/mcp/#optional-approval-flow). - Also see [MCP Approval Requests from OpenAI](https://platform.openai.com/docs/guides/tools-remote-mcp#approvals). diff --git a/python/packages/lab/gaia/README.md b/python/packages/lab/gaia/README.md index ee0093d54b..b811abb171 100644 --- a/python/packages/lab/gaia/README.md +++ b/python/packages/lab/gaia/README.md @@ -43,21 +43,6 @@ async def main() -> None: See the [gaia_sample.py](./samples/gaia_sample.py) for more detail. -### Run the evaluation - -Run the evaluation script using `uv`: - -```bash -uv run python run_gaia.py -``` - -By default, the script will first look for cached GAIA data in the `data_gaia_hub` directory, -and download it if not found. -The result will be saved to `gaia_results_.jsonl`. - -**Don't run the script inside this directory because it will confuse the local `agent_framework` namespace -package with the real one.** - ## View results We provide a console viewer for reading GAIA results: diff --git a/python/packages/lab/gaia/agent_framework_lab_gaia/gaia.py b/python/packages/lab/gaia/agent_framework_lab_gaia/gaia.py index 9d21fc351b..6c5cae0d1b 100644 --- a/python/packages/lab/gaia/agent_framework_lab_gaia/gaia.py +++ b/python/packages/lab/gaia/agent_framework_lab_gaia/gaia.py @@ -54,17 +54,29 @@ class GAIATelemetryConfig: if not self.enable_tracing: return - from agent_framework.observability import setup_observability + # If only file tracing is requested (no OTLP or Application Insights), + # skip the default setup_observability which adds console exporter + if self.trace_to_file and not self.otlp_endpoint and not self.applicationinsights_connection_string: + # Set up minimal tracing with only file export + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.trace import set_tracer_provider - setup_observability( - enable_sensitive_data=True, # Enable for detailed task traces - otlp_endpoint=self.otlp_endpoint, - applicationinsights_connection_string=self.applicationinsights_connection_string, - ) - - # Set up local file export if requested - if self.trace_to_file: + tracer_provider = TracerProvider() + set_tracer_provider(tracer_provider) self._setup_file_export() + else: + # Use full observability setup for OTLP/AppInsights + from agent_framework.observability import setup_observability + + setup_observability( + enable_sensitive_data=True, # Enable for detailed task traces + otlp_endpoint=self.otlp_endpoint, + applicationinsights_connection_string=self.applicationinsights_connection_string, + ) + + # Set up local file export if requested + if self.trace_to_file: + self._setup_file_export() def _setup_file_export(self) -> None: """Set up local file export for traces.""" @@ -204,29 +216,87 @@ def _load_gaia_local(repo_dir: Path, wanted_levels: list[int] | None = None, max """Load GAIA tasks from local repository directory.""" tasks: list[Task] = [] - for p in repo_dir.rglob("metadata.jsonl"): - for rec in _read_jsonl(p): - # Robustly extract fields used across variants - q = rec.get("Question") or rec.get("question") or rec.get("query") or rec.get("prompt") - ans = rec.get("Final answer") or rec.get("answer") or rec.get("final_answer") - qid = str( - rec.get("task_id") - or rec.get("question_id") - or rec.get("id") - or rec.get("uuid") - or f"{p.stem}:{len(tasks)}" - ) - lvl = rec.get("Level") or rec.get("level") - fname = rec.get("file_name") or rec.get("filename") or None + # First try to load from parquet files (new format) + # Prioritize validation split over test split (validation has answers) + parquet_files = sorted( + repo_dir.rglob("metadata*.parquet"), key=lambda p: (0 if "validation" in str(p) else 1, str(p)) + ) - # Only evaluate examples with public answers (dev/validation split) - if not q or ans is None: - continue + for p in parquet_files: + try: + import pyarrow.parquet as pq - if wanted_levels and (lvl not in wanted_levels): - continue + table = pq.read_table(p) + for row in table.to_pylist(): + # Robustly extract fields used across variants + q = row.get("Question") or row.get("question") or row.get("query") or row.get("prompt") + ans = row.get("Final answer") or row.get("answer") or row.get("final_answer") + qid = str( + row.get("task_id") + or row.get("question_id") + or row.get("id") + or row.get("uuid") + or f"{p.stem}:{len(tasks)}" + ) + lvl = row.get("Level") or row.get("level") - tasks.append(Task(task_id=qid, question=q, answer=str(ans), level=lvl, file_name=fname, metadata=rec)) + # Convert level to int if it's a string + def _parse_level(lvl: Any) -> int | None: + """Parse level value to integer if possible.""" + if isinstance(lvl, int): + return lvl + if isinstance(lvl, str) and lvl.isdigit(): + return int(lvl) + return None + + lvl = _parse_level(lvl) + fname = row.get("file_name") or row.get("filename") or None + + # Only evaluate examples with public answers (dev/validation split) + # Skip if no question, no answer, or answer is placeholder like "?" + if not q or ans is None or str(ans).strip() in ["?", ""]: + continue + + if wanted_levels and (lvl not in wanted_levels): + continue + + tasks.append(Task(task_id=qid, question=q, answer=str(ans), level=lvl, file_name=fname, metadata=row)) + except ImportError: + print("Warning: pyarrow not installed. Install with: pip install pyarrow") + continue + except Exception as e: + print(f"Warning: Could not load parquet file {p}: {e}") + continue + + # Fall back to jsonl files (old format) if no parquet files found + if not tasks: + for p in repo_dir.rglob("metadata.jsonl"): + for rec in _read_jsonl(p): + # Robustly extract fields used across variants + q = rec.get("Question") or rec.get("question") or rec.get("query") or rec.get("prompt") + ans = rec.get("Final answer") or rec.get("answer") or rec.get("final_answer") + qid = str( + rec.get("task_id") + or rec.get("question_id") + or rec.get("id") + or rec.get("uuid") + or f"{p.stem}:{len(tasks)}" + ) + lvl = rec.get("Level") or rec.get("level") + # Convert level to int if it's a string + if isinstance(lvl, str) and lvl.isdigit(): + lvl = int(lvl) + fname = rec.get("file_name") or rec.get("filename") or None + + # Only evaluate examples with public answers (dev/validation split) + # Skip if no question, no answer, or answer is placeholder like "?" + if not q or ans is None or str(ans).strip() in ["?", ""]: + continue + + if wanted_levels and (lvl not in wanted_levels): + continue + + tasks.append(Task(task_id=qid, question=q, answer=str(ans), level=lvl, file_name=fname, metadata=rec)) # Shuffle to help with rate-limits and fairness if max_n is provided random.shuffle(tasks) @@ -290,7 +360,6 @@ class GAIA: "with access to gaia-benchmark/GAIA." ) - print(f"Downloading GAIA dataset to {self.data_dir}...") from huggingface_hub import snapshot_download local_dir = snapshot_download( # type: ignore @@ -438,8 +507,6 @@ class GAIA: "Make sure you have dataset access and selected valid levels." ) - print(f"Running {len(tasks)} GAIA tasks (levels={levels}) with {parallel} parallel workers...") - # Update benchmark span with task info if benchmark_span: benchmark_span.set_attributes({ @@ -473,17 +540,12 @@ class GAIA: "gaia.benchmark.avg_runtime_seconds": avg_runtime, }) - print("\nGAIA Benchmark Results:") - print(f"Accuracy: {accuracy:.3f} ({correct}/{len(results)})") - print(f"Average runtime: {avg_runtime:.2f}s") - # Save results if requested if out: with self.tracer.start_as_current_span( "gaia.results.save", kind=SpanKind.INTERNAL, attributes={"gaia.results.output_file": out} ): self._save_results(results, out) - print(f"Results saved to {out}") return results diff --git a/python/packages/lab/gaia/samples/azure_ai_agent.py b/python/packages/lab/gaia/samples/azure_ai_agent.py new file mode 100644 index 0000000000..2fa4ce7e92 --- /dev/null +++ b/python/packages/lab/gaia/samples/azure_ai_agent.py @@ -0,0 +1,67 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Azure AI Agent factory for GAIA benchmark. + +This module provides a factory function to create an Azure AI agent +configured for GAIA benchmark tasks. + +Required Environment Variables: + AZURE_AI_PROJECT_ENDPOINT: Azure AI project endpoint URL + AZURE_AI_MODEL_DEPLOYMENT_NAME: Name of the model deployment to use + +Optional Environment Variables: + BING_CONNECTION_NAME: Name of the Bing connection for web search + OR + BING_CONNECTION_ID: ID of the Bing connection for web search + +Authentication: + Uses Azure CLI credentials via AzureCliCredential. + Run `az login` before executing to authenticate. + +Example: + export AZURE_AI_PROJECT_ENDPOINT="https://your-project.azure.com" + export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o" + export BING_CONNECTION_NAME="bing-grounding-connection" + az login +""" + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager + +from agent_framework import ChatAgent, HostedCodeInterpreterTool, HostedWebSearchTool +from agent_framework.azure import AzureAIAgentClient +from azure.identity.aio import AzureCliCredential + + +@asynccontextmanager +async def create_gaia_agent() -> AsyncIterator[ChatAgent]: + """Create an Azure AI agent configured for GAIA benchmark tasks. + + The agent is configured with: + - Bing Search tool for web information retrieval + - Code Interpreter tool for calculations and data analysis + + Yields: + ChatAgent: A configured agent ready to run GAIA tasks. + + Example: + async with create_gaia_agent() as agent: + result = await agent.run("What is the capital of France?") + print(result.text) + """ + async with ( + AzureCliCredential() as credential, + AzureAIAgentClient(async_credential=credential).create_agent( + name="GaiaAgent", + instructions="Solve tasks to your best ability. Use Bing Search to find " + "information and Code Interpreter to perform calculations and data analysis.", + tools=[ + HostedWebSearchTool( + name="Bing Grounding Search", + description="Search the web for current information using Bing", + ), + HostedCodeInterpreterTool(), + ], + ) as agent, + ): + yield agent diff --git a/python/packages/lab/gaia/samples/gaia_sample.py b/python/packages/lab/gaia/samples/gaia_sample.py index cb2d08b97e..bf96190652 100644 --- a/python/packages/lab/gaia/samples/gaia_sample.py +++ b/python/packages/lab/gaia/samples/gaia_sample.py @@ -2,43 +2,148 @@ """GAIA Benchmark Sample. -To run this sample, execute it from the root directory of the agent-framework repository: - cd /path/to/agent-framework - uv run python python/packages/lab/gaia/gaia_sample.py +Run the GAIA (General AI Assistant) benchmark with configurable agent providers, +telemetry options, and benchmark parameters. -This avoids namespace package conflicts that occur when running from within the gaia package directory. +Agent Providers: + - Azure AI (default): See azure_ai_agent.py for required environment variables + - OpenAI: See openai_agent.py for required environment variables + +Prerequisites: + 1. Set HF_TOKEN environment variable with your Hugging Face token: + - Get token: https://huggingface.co/settings/tokens + - Request dataset access: https://huggingface.co/datasets/gaia-benchmark/GAIA + - Set: export HF_TOKEN="your-huggingface-token" + + 2. Configure your chosen agent provider (see agent module files for details) + +Telemetry: + When using --otlp-endpoint or --trace-file, OpenTelemetry will export trace data + in JSON format to the console in addition to the configured endpoints. This is + expected behavior from the OpenTelemetry SDK and provides visibility into the + telemetry being captured. The traces are also exported to: + - OTLP endpoint (e.g., Aspire Dashboard) if --otlp-endpoint is specified + - Local file if --trace-file is specified + + To suppress console output, redirect stderr: `python gaia_sample.py 2>/dev/null` + +Usage: + # Run with default settings (Azure AI agent) + uv run python gaia_sample.py + + # Run with OpenAI agent + uv run python gaia_sample.py --agent-provider openai + + # Run with telemetry export to Aspire Dashboard + uv run python gaia_sample.py --otlp-endpoint http://localhost:4318 + + # See all options + uv run python gaia_sample.py --help """ -from agent_framework.azure import AzureAIAgentClient +import argparse + from agent_framework.lab.gaia import GAIA, Evaluation, GAIATelemetryConfig, Prediction, Task -from azure.identity.aio import AzureCliCredential -def evaluate_task(task: Task, prediction: Prediction) -> Evaluation: +async def evaluate_task(task: Task, prediction: Prediction) -> Evaluation: """Evaluate the prediction for a given task.""" # Simple evaluation: check if the prediction contains the answer is_correct = (task.answer or "").lower() in prediction.prediction.lower() return Evaluation(is_correct=is_correct, score=1 if is_correct else 0) -async def main() -> None: - """Run GAIA benchmark with telemetry configuration.""" +async def main( + otlp_endpoint: str | None = None, + trace_file: str | None = None, + result_file: str | None = None, + data_dir: str | None = None, + agent_provider: str = "azure-ai", + level: int | list[int] = 1, + max_n: int = 2, + parallel: int = 1, + timeout: int = 120, +) -> None: + """Run GAIA benchmark with telemetry configuration. + + Args: + otlp_endpoint: Optional OTLP endpoint URL for exporting traces (e.g., http://localhost:4318) + trace_file: Optional file path to export traces to. If None, traces won't be saved to file. + result_file: Optional file path to save benchmark results. If None, results won't be saved to file. + data_dir: Directory to cache GAIA dataset. If None, uses temp directory. + agent_provider: Agent provider to use: 'azure-ai' or 'openai' (default: 'azure-ai') + level: GAIA level(s) to run (1, 2, or 3) + max_n: Maximum number of tasks to run per level + parallel: Number of parallel tasks to run + timeout: Timeout per task in seconds + """ + # Check for required Hugging Face token + import logging + import os + + # Suppress console logging for traces and verbose SDK output + logging.getLogger("opentelemetry").setLevel(logging.ERROR) + logging.getLogger("azure").setLevel(logging.WARNING) + logging.getLogger("agent_framework").setLevel(logging.WARNING) + logging.getLogger("httpx").setLevel(logging.WARNING) + logging.getLogger("httpcore").setLevel(logging.WARNING) + + # Suppress OpenTelemetry exporters console output + import os as _os + + _os.environ.setdefault("OTEL_PYTHON_LOG_LEVEL", "error") + + # Print trace export configuration + print("\n=== Telemetry Configuration ===") + if trace_file: + print(f"📁 Trace file: {os.path.abspath(trace_file)}") + else: + print("📁 Trace file: disabled") + + if otlp_endpoint: + print(f"🌐 OTLP endpoint: {otlp_endpoint}") + else: + print("🌐 OTLP endpoint: disabled") + + if result_file: + print(f"📊 Results file: {os.path.abspath(result_file)}") + else: + print("📊 Results file: disabled") + + print("\n=== Run Configuration ===") + print(f"🤖 Agent provider: {agent_provider}") + if data_dir: + print(f"📂 Data directory: {os.path.abspath(data_dir)}") + else: + import tempfile + from pathlib import Path + + default_data_dir = Path(tempfile.gettempdir()) / "data_gaia_hub" + print(f"📂 Data directory: {default_data_dir} (default)") + print(f"🎯 Level: {level}") + print(f"🔢 Max tasks: {max_n}") + print(f"⚡ Parallel: {parallel}") + print(f"⏱️ Timeout: {timeout}s") + print() + + # Import the appropriate agent factory based on provider + if agent_provider == "azure-ai": + from azure_ai_agent import create_gaia_agent + elif agent_provider == "openai": + from openai_agent import create_gaia_agent + else: + raise ValueError(f"Unknown agent provider: {agent_provider}. Use 'azure-ai' or 'openai'.") + # Configure telemetry for tracing telemetry_config = GAIATelemetryConfig( enable_tracing=True, # Enable OpenTelemetry tracing - # Configure local file tracing - trace_to_file=True, # Export traces to local file - file_path="gaia_benchmark_traces.jsonl", # Custom file path for traces + trace_to_file=trace_file is not None, # Export traces to local file only if path provided + file_path=trace_file, # Custom file path for traces (can be None) + otlp_endpoint=otlp_endpoint, # Optional OTLP endpoint for Aspire Dashboard or other collectors ) # Create a single agent once and reuse it for all tasks - async with ( - AzureCliCredential() as credential, - AzureAIAgentClient(async_credential=credential).create_agent( - name="GaiaAgent", - instructions="Solve tasks to your best ability.", - ) as agent, - ): + async with create_gaia_agent() as agent: async def run_task(task: Task) -> Prediction: """Run a single GAIA task and return the prediction using the shared agent.""" @@ -49,30 +154,142 @@ async def main() -> None: return Prediction(prediction=result.text, messages=result.messages) # Create the GAIA benchmark runner with telemetry configuration - runner = GAIA(evaluator=evaluate_task, telemetry_config=telemetry_config) + runner = GAIA( + evaluator=evaluate_task, + telemetry_config=telemetry_config, + data_dir=data_dir, + ) # Run the benchmark with the task runner. # By default, this will check for locally cached benchmark data and checkout # the latest version from HuggingFace if not found. + # Note: The GAIA dataset has been updated to use Parquet format. + # If you encounter issues, try using validation split which has labeled data. results = await runner.run( run_task, - level=1, # Level 1, 2, or 3 or multiple levels like [1, 2] - max_n=5, # Maximum number of tasks to run per level - parallel=2, # Number of parallel tasks to run - timeout=60, # Timeout per task in seconds - out="gaia_results_level1.jsonl", # Output file to save results including detailed traces (optional) + level=level, + max_n=max_n, + parallel=parallel, + timeout=timeout, + out=result_file, # Output file to save results including detailed traces (optional, None = no file output) ) - # Print the results. - print("\n=== GAIA Benchmark Results ===") - for result in results: - print(f"\n--- Task ID: {result.task_id} ---") - print(f"Task: {result.task.question[:100]}...") - print(f"Prediction: {result.prediction.prediction}") - print(f"Evaluation: Correct={result.evaluation.is_correct}, Score={result.evaluation.score}") + # Print summary similar to the viewer in gaia.py + total = len(results) + correct = sum(1 for r in results if r.evaluation.is_correct) + accuracy = correct / total if total > 0 else 0.0 + avg_runtime = sum(r.runtime_seconds or 0 for r in results) / total if total > 0 else 0.0 + + print("\n=== GAIA Benchmark Summary ===") + print(f"📝 Total: {total}, ✅ Correct: {correct}, 🎯 Accuracy: {accuracy:.3f}") + print(f"⏱️ Average runtime: {avg_runtime:.2f}s") + if result_file: + print(f"💾 Detailed results saved to: {result_file}") if __name__ == "__main__": import asyncio - asyncio.run(main()) + # Parse command line arguments + parser = argparse.ArgumentParser( + description="Run GAIA benchmark with optional telemetry export to OTLP endpoint and/or file", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Run with default settings + python gaia_sample.py + + # Run with custom data directory + python gaia_sample.py --data-dir ./gaia_data + + # Run with OpenAI agent provider + python gaia_sample.py --agent-provider openai + + # Run with trace file export + python gaia_sample.py --trace-file gaia_benchmark_traces.jsonl + + # Run level 2 tasks with 5 maximum tasks + python gaia_sample.py --level 2 --max-n 5 + + # Run with OTLP export to Aspire Dashboard and custom settings + python gaia_sample.py --otlp-endpoint http://localhost:4318 --level 1 --max-n 10 --parallel 2 + + # Run with all options configured + python gaia_sample.py --agent-provider openai \ + --trace-file traces.jsonl \ + --result-file results.jsonl \ + --otlp-endpoint http://localhost:4318 --level 1 --max-n 5 --parallel 2 --timeout 180 + """, + ) + parser.add_argument( + "--otlp-endpoint", + type=str, + default=None, + help="OTLP endpoint URL for exporting traces (e.g., http://localhost:4318 for Aspire Dashboard)", + ) + parser.add_argument( + "--trace-file", + type=str, + default=None, + help="File path to export traces to (e.g., gaia_benchmark_traces.jsonl). " + "If not set, traces won't be saved to file.", + ) + parser.add_argument( + "--result-file", + type=str, + default="gaia_results_level1.jsonl", + help="File path to save benchmark results (default: gaia_results_level1.jsonl)", + ) + parser.add_argument( + "--data-dir", + type=str, + default=None, + help="Directory to cache GAIA dataset. If not set, uses system temp directory.", + ) + parser.add_argument( + "--agent-provider", + type=str, + default="azure-ai", + choices=["azure-ai", "openai"], + help="Agent provider to use: 'azure-ai' or 'openai' (default: 'azure-ai')", + ) + parser.add_argument( + "--level", + type=int, + default=1, + choices=[1, 2, 3], + help="GAIA benchmark level to run: 1, 2, or 3 (default: 1)", + ) + parser.add_argument( + "--max-n", + type=int, + default=2, + help="Maximum number of tasks to run per level (default: 2)", + ) + parser.add_argument( + "--parallel", + type=int, + default=1, + help="Number of parallel tasks to run (default: 1)", + ) + parser.add_argument( + "--timeout", + type=int, + default=120, + help="Timeout per task in seconds (default: 120)", + ) + args = parser.parse_args() + + asyncio.run( + main( + otlp_endpoint=args.otlp_endpoint, + trace_file=args.trace_file, + result_file=args.result_file, + data_dir=args.data_dir, + agent_provider=args.agent_provider, + level=args.level, + max_n=args.max_n, + parallel=args.parallel, + timeout=args.timeout, + ) + ) diff --git a/python/packages/lab/gaia/samples/openai_agent.py b/python/packages/lab/gaia/samples/openai_agent.py new file mode 100644 index 0000000000..64600aa5f2 --- /dev/null +++ b/python/packages/lab/gaia/samples/openai_agent.py @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""OpenAI Agent factory for GAIA benchmark. + +This module provides a factory function to create an OpenAI agent +configured for GAIA benchmark tasks using the OpenAI Responses API. + +Required Environment Variables: + OPENAI_API_KEY: Your OpenAI API key + OPENAI_RESPONSES_MODEL_ID: Model to use with Responses API (e.g., gpt-4o, gpt-4o-mini) + +Optional Environment Variables: + OPENAI_BASE_URL: Custom API base URL if using a proxy or compatible service + OPENAI_ORG_ID: Organization ID for OpenAI API (if applicable) + +Authentication: + Uses OPENAI_API_KEY environment variable. + Get your API key from: https://platform.openai.com/api-keys + +Example: + export OPENAI_API_KEY="sk-..." + export OPENAI_RESPONSES_MODEL_ID="gpt-4o" +""" + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager + +from agent_framework import ChatAgent, HostedCodeInterpreterTool, HostedWebSearchTool +from agent_framework.openai import OpenAIResponsesClient + + +@asynccontextmanager +async def create_gaia_agent() -> AsyncIterator[ChatAgent]: + """Create an OpenAI agent configured for GAIA benchmark tasks. + + Uses OpenAI Responses API for enhanced capabilities. + + The agent is configured with: + - Web Search tool for information retrieval + - Code Interpreter tool for calculations and data analysis + + Yields: + ChatAgent: A configured agent ready to run GAIA tasks. + + Example: + async with create_gaia_agent() as agent: + result = await agent.run("What is the capital of France?") + print(result.text) + """ + chat_client = OpenAIResponsesClient() + + async with chat_client.create_agent( + name="GaiaAgent", + instructions="Solve tasks to your best ability. Use Web Search to find " + "information and Code Interpreter to perform calculations and data analysis.", + tools=[ + HostedWebSearchTool( + name="Web Search", + description="Search the web for current information", + ), + HostedCodeInterpreterTool(), + ], + ) as agent: + yield agent diff --git a/python/packages/lab/pyproject.toml b/python/packages/lab/pyproject.toml index c8788db58c..8b7f8f3c96 100644 --- a/python/packages/lab/pyproject.toml +++ b/python/packages/lab/pyproject.toml @@ -32,6 +32,7 @@ gaia = [ "tqdm>=4.60.0", "huggingface-hub>=0.20.0", "orjson>=3.8.0", + "pyarrow>=10.0.0", # For reading parquet files ] # Lightning RL training module dependencies @@ -111,7 +112,13 @@ extend = "../../pyproject.toml" extend-exclude = ["**/data/**"] [tool.ruff.lint] -ignore = ["T201", "ASYNC230", "INP001"] # Allow print statements, blocking file operations, and implicit namespace packages in lab modules +ignore = [ + "T201", # Allow print statements in experimental/lab code for debugging purposes. + "ASYNC230", # Allow 'await' outside of async functions in test and experimental code. + "INP001", # Ignore missing __init__.py in namespace packages. + "RUF029", # Allow use of 'assert' statements; assertions are used for internal checks in experimental code. + "ASYNC240", # Allow 'async for' outside of async functions in test and experimental code. +] [tool.coverage.run] omit = [ diff --git a/python/uv.lock b/python/uv.lock index 1f2664c774..0e11d3bd11 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -297,6 +297,7 @@ gaia = [ { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "orjson", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pyarrow", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] @@ -344,6 +345,7 @@ requires-dist = [ { name = "numpy", marker = "extra == 'tau2'" }, { name = "opentelemetry-api", marker = "extra == 'gaia'", specifier = ">=1.24.0" }, { name = "orjson", marker = "extra == 'gaia'", specifier = ">=3.8.0" }, + { name = "pyarrow", marker = "extra == 'gaia'", specifier = ">=10.0.0" }, { name = "pydantic", marker = "extra == 'gaia'", specifier = ">=2.0.0" }, { name = "pydantic", marker = "extra == 'tau2'", specifier = ">=2.0.0" }, { name = "sympy", marker = "extra == 'math'", specifier = ">=1.13.0" }, @@ -909,7 +911,7 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "(implementation_name != 'PyPy' and sys_platform == 'darwin') or (implementation_name != 'PyPy' and sys_platform == 'linux') or (implementation_name != 'PyPy' and sys_platform == 'win32')" }, + { name = "pycparser", marker = "(python_full_version < '3.13' and implementation_name != 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.13' and implementation_name != 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.13' and implementation_name != 'PyPy' and sys_platform == 'win32') or (implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' and sys_platform == 'darwin') or (implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' and sys_platform == 'linux') or (implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' and sys_platform == 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ @@ -1571,7 +1573,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, + { name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -4169,6 +4171,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/94/6475c7faa94a1d90303f624936471cd0f4c20430bd2c92deab607cd0ff31/py2docfx-0.1.22-py3-none-any.whl", hash = "sha256:ccee611af2aefe9f39f446f72b5e07d3369bbdb77c13ebafb69ed1a116116467", size = 11420273, upload-time = "2025-10-10T07:16:25.294Z" }, ] +[[package]] +name = "pyarrow" +version = "22.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/9b/cb3f7e0a345353def531ca879053e9ef6b9f38ed91aebcf68b09ba54dec0/pyarrow-22.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:77718810bd3066158db1e95a63c160ad7ce08c6b0710bc656055033e39cdad88", size = 34223968, upload-time = "2025-10-24T10:03:31.21Z" }, + { url = "https://files.pythonhosted.org/packages/6c/41/3184b8192a120306270c5307f105b70320fdaa592c99843c5ef78aaefdcf/pyarrow-22.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:44d2d26cda26d18f7af7db71453b7b783788322d756e81730acb98f24eb90ace", size = 35942085, upload-time = "2025-10-24T10:03:38.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3d/a1eab2f6f08001f9fb714b8ed5cfb045e2fe3e3e3c0c221f2c9ed1e6d67d/pyarrow-22.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b9d71701ce97c95480fecb0039ec5bb889e75f110da72005743451339262f4ce", size = 44964613, upload-time = "2025-10-24T10:03:46.516Z" }, + { url = "https://files.pythonhosted.org/packages/46/46/a1d9c24baf21cfd9ce994ac820a24608decf2710521b29223d4334985127/pyarrow-22.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:710624ab925dc2b05a6229d47f6f0dac1c1155e6ed559be7109f684eba048a48", size = 47627059, upload-time = "2025-10-24T10:03:55.353Z" }, + { url = "https://files.pythonhosted.org/packages/3a/4c/f711acb13075c1391fd54bc17e078587672c575f8de2a6e62509af026dcf/pyarrow-22.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f963ba8c3b0199f9d6b794c90ec77545e05eadc83973897a4523c9e8d84e9340", size = 47947043, upload-time = "2025-10-24T10:04:05.408Z" }, + { url = "https://files.pythonhosted.org/packages/4e/70/1f3180dd7c2eab35c2aca2b29ace6c519f827dcd4cfeb8e0dca41612cf7a/pyarrow-22.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd0d42297ace400d8febe55f13fdf46e86754842b860c978dfec16f081e5c653", size = 50206505, upload-time = "2025-10-24T10:04:15.786Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/fea6578112c8c60ffde55883a571e4c4c6bc7049f119d6b09333b5cc6f73/pyarrow-22.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84", size = 28101641, upload-time = "2025-10-24T10:04:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/18f611a8cdc43417f9394a3ccd3eace2f32183c08b9eddc3d17681819f37/pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:3e294c5eadfb93d78b0763e859a0c16d4051fc1c5231ae8956d61cb0b5666f5a", size = 34272022, upload-time = "2025-10-24T10:04:28.973Z" }, + { url = "https://files.pythonhosted.org/packages/26/5c/f259e2526c67eb4b9e511741b19870a02363a47a35edbebc55c3178db22d/pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:69763ab2445f632d90b504a815a2a033f74332997052b721002298ed6de40f2e", size = 35995834, upload-time = "2025-10-24T10:04:35.467Z" }, + { url = "https://files.pythonhosted.org/packages/50/8d/281f0f9b9376d4b7f146913b26fac0aa2829cd1ee7e997f53a27411bbb92/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b41f37cabfe2463232684de44bad753d6be08a7a072f6a83447eeaf0e4d2a215", size = 45030348, upload-time = "2025-10-24T10:04:43.366Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e5/53c0a1c428f0976bf22f513d79c73000926cb00b9c138d8e02daf2102e18/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:35ad0f0378c9359b3f297299c3309778bb03b8612f987399a0333a560b43862d", size = 47699480, upload-time = "2025-10-24T10:04:51.486Z" }, + { url = "https://files.pythonhosted.org/packages/95/e1/9dbe4c465c3365959d183e6345d0a8d1dc5b02ca3f8db4760b3bc834cf25/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8382ad21458075c2e66a82a29d650f963ce51c7708c7c0ff313a8c206c4fd5e8", size = 48011148, upload-time = "2025-10-24T10:04:59.585Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/7caf5d21930061444c3cf4fa7535c82faf5263e22ce43af7c2759ceb5b8b/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a812a5b727bc09c3d7ea072c4eebf657c2f7066155506ba31ebf4792f88f016", size = 50276964, upload-time = "2025-10-24T10:05:08.175Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f3/cec89bd99fa3abf826f14d4e53d3d11340ce6f6af4d14bdcd54cd83b6576/pyarrow-22.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ec5d40dd494882704fb876c16fa7261a69791e784ae34e6b5992e977bd2e238c", size = 28106517, upload-time = "2025-10-24T10:05:14.314Z" }, + { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", size = 35989906, upload-time = "2025-10-24T10:05:29.485Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", size = 45021677, upload-time = "2025-10-24T10:05:38.274Z" }, + { url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", size = 47726315, upload-time = "2025-10-24T10:05:47.314Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", size = 47990906, upload-time = "2025-10-24T10:05:58.254Z" }, + { url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", size = 50306783, upload-time = "2025-10-24T10:06:08.08Z" }, + { url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", size = 27972883, upload-time = "2025-10-24T10:06:14.204Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a", size = 34204629, upload-time = "2025-10-24T10:06:20.274Z" }, + { url = "https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901", size = 35985783, upload-time = "2025-10-24T10:06:27.301Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/782344c2ce58afbea010150df07e3a2f5fdad299cd631697ae7bd3bac6e3/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691", size = 45020999, upload-time = "2025-10-24T10:06:35.387Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a", size = 47724601, upload-time = "2025-10-24T10:06:43.551Z" }, + { url = "https://files.pythonhosted.org/packages/69/4d/76e567a4fc2e190ee6072967cb4672b7d9249ac59ae65af2d7e3047afa3b/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6", size = 48001050, upload-time = "2025-10-24T10:06:52.284Z" }, + { url = "https://files.pythonhosted.org/packages/01/5e/5653f0535d2a1aef8223cee9d92944cb6bccfee5cf1cd3f462d7cb022790/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941", size = 50307877, upload-time = "2025-10-24T10:07:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145", size = 27977099, upload-time = "2025-10-24T10:08:07.259Z" }, + { url = "https://files.pythonhosted.org/packages/90/81/db56870c997805bf2b0f6eeeb2d68458bf4654652dccdcf1bf7a42d80903/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1", size = 34336685, upload-time = "2025-10-24T10:07:11.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/98/0727947f199aba8a120f47dfc229eeb05df15bcd7a6f1b669e9f882afc58/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f", size = 36032158, upload-time = "2025-10-24T10:07:18.626Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/9babdef9c01720a0785945c7cf550e4acd0ebcd7bdd2e6f0aa7981fa85e2/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d", size = 44892060, upload-time = "2025-10-24T10:07:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ca/2f8804edd6279f78a37062d813de3f16f29183874447ef6d1aadbb4efa0f/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f", size = 47504395, upload-time = "2025-10-24T10:07:34.09Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f0/77aa5198fd3943682b2e4faaf179a674f0edea0d55d326d83cb2277d9363/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746", size = 48066216, upload-time = "2025-10-24T10:07:43.528Z" }, + { url = "https://files.pythonhosted.org/packages/79/87/a1937b6e78b2aff18b706d738c9e46ade5bfcf11b294e39c87706a0089ac/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95", size = 50288552, upload-time = "2025-10-24T10:07:53.519Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/b5a5811e11f25788ccfdaa8f26b6791c9807119dffcf80514505527c384c/pyarrow-22.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc", size = 28262504, upload-time = "2025-10-24T10:08:00.932Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b0/0fa4d28a8edb42b0a7144edd20befd04173ac79819547216f8a9f36f9e50/pyarrow-22.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9bddc2cade6561f6820d4cd73f99a0243532ad506bc510a75a5a65a522b2d74d", size = 34224062, upload-time = "2025-10-24T10:08:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a8/7a719076b3c1be0acef56a07220c586f25cd24de0e3f3102b438d18ae5df/pyarrow-22.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e70ff90c64419709d38c8932ea9fe1cc98415c4f87ea8da81719e43f02534bc9", size = 35990057, upload-time = "2025-10-24T10:08:21.842Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/359ed54c93b47fb6fe30ed16cdf50e3f0e8b9ccfb11b86218c3619ae50a8/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:92843c305330aa94a36e706c16209cd4df274693e777ca47112617db7d0ef3d7", size = 45068002, upload-time = "2025-10-24T10:08:29.034Z" }, + { url = "https://files.pythonhosted.org/packages/55/fc/4945896cc8638536ee787a3bd6ce7cec8ec9acf452d78ec39ab328efa0a1/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6dda1ddac033d27421c20d7a7943eec60be44e0db4e079f33cc5af3b8280ccde", size = 47737765, upload-time = "2025-10-24T10:08:38.559Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/7cb7edeb2abfaa1f79b5d5eb89432356155c8426f75d3753cbcb9592c0fd/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:84378110dd9a6c06323b41b56e129c504d157d1a983ce8f5443761eb5256bafc", size = 48048139, upload-time = "2025-10-24T10:08:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/546baa7c48185f5e9d6e59277c4b19f30f48c94d9dd938c2a80d4d6b067c/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:854794239111d2b88b40b6ef92aa478024d1e5074f364033e73e21e3f76b25e0", size = 50314244, upload-time = "2025-10-24T10:08:55.771Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/755ff2d145aafec8d347bf18f95e4e81c00127f06d080135dfc86aea417c/pyarrow-22.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:b883fe6fd85adad7932b3271c38ac289c65b7337c2c132e9569f9d3940620730", size = 28757501, upload-time = "2025-10-24T10:09:59.891Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d2/237d75ac28ced3147912954e3c1a174df43a95f4f88e467809118a8165e0/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a820d8ae11facf32585507c11f04e3f38343c1e784c9b5a8b1da5c930547fe2", size = 34355506, upload-time = "2025-10-24T10:09:02.953Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/733dfffe6d3069740f98e57ff81007809067d68626c5faef293434d11bd6/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:c6ec3675d98915bf1ec8b3c7986422682f7232ea76cad276f4c8abd5b7319b70", size = 36047312, upload-time = "2025-10-24T10:09:10.334Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2b/29d6e3782dc1f299727462c1543af357a0f2c1d3c160ce199950d9ca51eb/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3e739edd001b04f654b166204fc7a9de896cf6007eaff33409ee9e50ceaff754", size = 45081609, upload-time = "2025-10-24T10:09:18.61Z" }, + { url = "https://files.pythonhosted.org/packages/8d/42/aa9355ecc05997915af1b7b947a7f66c02dcaa927f3203b87871c114ba10/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7388ac685cab5b279a41dfe0a6ccd99e4dbf322edfb63e02fc0443bf24134e91", size = 47703663, upload-time = "2025-10-24T10:09:27.369Z" }, + { url = "https://files.pythonhosted.org/packages/ee/62/45abedde480168e83a1de005b7b7043fd553321c1e8c5a9a114425f64842/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f633074f36dbc33d5c05b5dc75371e5660f1dbf9c8b1d95669def05e5425989c", size = 48066543, upload-time = "2025-10-24T10:09:34.908Z" }, + { url = "https://files.pythonhosted.org/packages/84/e9/7878940a5b072e4f3bf998770acafeae13b267f9893af5f6d4ab3904b67e/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4c19236ae2402a8663a2c8f21f1870a03cc57f0bef7e4b6eb3238cc82944de80", size = 50288838, upload-time = "2025-10-24T10:09:44.394Z" }, + { url = "https://files.pythonhosted.org/packages/7b/03/f335d6c52b4a4761bcc83499789a1e2e16d9d201a58c327a9b5cc9a41bd9/pyarrow-22.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0c34fe18094686194f204a3b1787a27456897d8a2d62caf84b61e8dfbc0252ae", size = 29185594, upload-time = "2025-10-24T10:09:53.111Z" }, +] + [[package]] name = "pyasn1" version = "0.6.1"