GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-03-20Advanced

Build an AI Data Analysis Agent with Gemini API — Combining Code Execution, Function Calling, and Structured Output

Learn how to build a production-ready AI data analysis agent in Python that combines Gemini API's Code Execution, Function Calling, and Structured Output to automatically analyze CSV/Excel data, generate visualizations, and produce structured reports.

gemini102gemini-api277python104data-analysis5code-execution4function-calling20structured-output22agent10automation51pandas

The Problem This Agent Solves

Data analysis typically involves a manual, multi-step process: data cleaning, exploratory analysis, visualization, and report generation. By combining three powerful Gemini API features — Code Execution, Function Calling, and Structured Output — you can build an AI agent that automates this entire pipeline with simple natural language instructions.

The agent below is built in Python from scratch. Upload a CSV or Excel file, ask a question in plain English, and it fetches the data, runs the statistics, draws the charts, and returns structured insights — without a single hand-written Pandas query.

What you'll learn:

  • How to use Gemini API's Code Execution to run Python in a sandboxed environment
  • Designing Function Calling tools to connect your agent to external data sources
  • Extracting type-safe JSON results with Structured Output
  • Integrating all three features into a complete data analysis pipeline

Prerequisites: Familiarity with Gemini API basics and intermediate Python development experience.


Environment Setup

Requirements

  • Python 3.10+
  • Google AI Python SDK (google-genai v1.55.0+)
  • A Gemini API key (available from Google AI Studio)

Installation

pip install google-genai pandas openpyxl matplotlib

Set your API key as an environment variable:

export GOOGLE_API_KEY="your-api-key-here"

Initialize the SDK in Python:

from google import genai
 
client = genai.Client()
 
# Code Execution-compatible model
MODEL_ID = "gemini-3-flash"

Model selection tip: Code Execution works with both gemini-3-flash and gemini-3-pro. Use Flash for cost efficiency and speed; choose Pro when your analysis requires complex multi-step reasoning.


Concepts and Design Philosophy

Role of Each Feature

The agent uses three Gemini API features, each serving a distinct purpose:

FeatureRoleExample
Code ExecutionData processing, statistics, visualizationPandas aggregations, Matplotlib charts
Function CallingExternal resource accessFile loading, DB queries, API calls
Structured OutputResult formattingJSON-schema-compliant insight reports

Architecture Overview

User (natural language query)
    ↓
Gemini API Agent
    ├── Function Calling → Fetch data from files/databases
    ├── Code Execution  → Analyze with Pandas/Matplotlib
    └── Structured Output → Format results as typed JSON
    ↓
Structured report + chart images

With this design, a user can simply say "Analyze last month's sales data and identify the top 5 categories by growth rate," and the agent will autonomously handle every step from data ingestion to visualization.


Step-by-Step Implementation

Step 1: Running Data Analysis with Code Execution

Let's start with the fundamentals. Code Execution lets Gemini generate and run Python code in a sandboxed environment, iterating until it reaches a solution.

from google import genai
from google.genai import types
 
client = genai.Client()
 
# Sample CSV data (in practice, you'd upload a file)
csv_data = """date,product,revenue,quantity
2026-01-01,Widget A,15000,120
2026-01-02,Widget A,18000,145
2026-01-03,Widget B,22000,90
2026-01-04,Widget A,16500,130
2026-01-05,Widget B,25000,105
2026-01-06,Widget C,8000,200
2026-01-07,Widget A,19000,155
2026-01-08,Widget B,21000,88
2026-01-09,Widget C,9500,220
2026-01-10,Widget A,17500,140"""
 
response = client.models.generate_content(
    model="gemini-3-flash",
    contents=f"""Analyze the following CSV data:
1. Calculate total revenue and average unit price per product
2. Create a daily revenue trend chart using Matplotlib
3. Identify the top-performing product and explain why
 
Data:
{csv_data}""",
    config=types.GenerateContentConfig(
        tools=[types.Tool(code_execution=types.ToolCodeExecution)]
    )
)
 
# Response contains text, code, execution results, and images
for part in response.candidates[0].content.parts:
    if part.text:
        print("📝 Text:", part.text)
    elif part.executable_code:
        print("💻 Code:", part.executable_code.code)
    elif part.code_execution_result:
        print("📊 Result:", part.code_execution_result.output)
    elif part.inline_data:
        # Matplotlib chart image (base64-encoded)
        print("🖼️ Chart image received")

Expected output:

💻 Code: import pandas as pd
import matplotlib.pyplot as plt
...
📊 Result:
Revenue by product:
  Widget A: $86,000 (avg. unit price: $124)
  Widget B: $68,000 (avg. unit price: $240)
  Widget C: $17,500 (avg. unit price: $42)
🖼️ Chart image received
📝 Text: Widget A leads in total revenue...

Key point: The Code Execution sandbox comes pre-loaded with Pandas, NumPy, Matplotlib, scikit-learn, and 50+ other libraries. You cannot install additional packages. Only Matplotlib is supported for chart rendering.

Step 2: Connecting Data Sources with Function Calling

Next, add Function Calling so the agent can access external data sources — files, databases, or APIs.

from google.genai import types
import pandas as pd
import json
 
# --- Tool implementations ---
 
def load_csv_file(file_path: str) -> str:
    """Load a CSV file and return a preview with statistics"""
    df = pd.read_csv(file_path)
    summary = {
        "rows": len(df),
        "columns": list(df.columns),
        "dtypes": {col: str(dtype) for col, dtype in df.dtypes.items()},
        "preview": df.head(20).to_csv(index=False),
        "describe": df.describe().to_json()
    }
    return json.dumps(summary, ensure_ascii=False)
 
def query_database(sql: str) -> str:
    """Execute a SQL query and return results as CSV"""
    import sqlite3
    conn = sqlite3.connect("sales.db")
    df = pd.read_sql_query(sql, conn)
    conn.close()
    return df.to_csv(index=False)
 
def save_report(filename: str, content: str) -> str:
    """Save an analysis report as a Markdown file"""
    with open(filename, "w", encoding="utf-8") as f:
        f.write(content)
    return f"Report saved to {filename}"
 
# --- Function declarations for Gemini ---
 
tools = types.Tool(
    function_declarations=[
        types.FunctionDeclaration(
            name="load_csv_file",
            description="Load a CSV file and return statistics and a preview",
            parameters=types.Schema(
                type="OBJECT",
                properties={
                    "file_path": types.Schema(
                        type="STRING",
                        description="Path to the CSV file"
                    )
                },
                required=["file_path"]
            )
        ),
        types.FunctionDeclaration(
            name="query_database",
            description="Execute a SQL query against an SQLite database",
            parameters=types.Schema(
                type="OBJECT",
                properties={
                    "sql": types.Schema(
                        type="STRING",
                        description="The SQL query to execute"
                    )
                },
                required=["sql"]
            )
        ),
        types.FunctionDeclaration(
            name="save_report",
            description="Save an analysis report to a file",
            parameters=types.Schema(
                type="OBJECT",
                properties={
                    "filename": types.Schema(
                        type="STRING",
                        description="Output filename"
                    ),
                    "content": types.Schema(
                        type="STRING",
                        description="Report content in Markdown format"
                    )
                },
                required=["filename", "content"]
            )
        )
    ]
)

Step 3: Getting Type-Safe Results with Structured Output

Add Structured Output to ensure analysis results conform to a well-defined JSON schema.

from google.genai import types
 
# Define the analysis report schema
analysis_schema = types.Schema(
    type="OBJECT",
    properties={
        "summary": types.Schema(
            type="STRING",
            description="Executive summary of the analysis (3 sentences max)"
        ),
        "key_metrics": types.Schema(
            type="ARRAY",
            items=types.Schema(
                type="OBJECT",
                properties={
                    "name": types.Schema(type="STRING"),
                    "value": types.Schema(type="STRING"),
                    "trend": types.Schema(
                        type="STRING",
                        enum=["up", "down", "stable"]
                    )
                }
            ),
            description="List of key performance metrics"
        ),
        "insights": types.Schema(
            type="ARRAY",
            items=types.Schema(
                type="OBJECT",
                properties={
                    "title": types.Schema(type="STRING"),
                    "description": types.Schema(type="STRING"),
                    "confidence": types.Schema(
                        type="STRING",
                        enum=["high", "medium", "low"]
                    ),
                    "action_item": types.Schema(type="STRING")
                }
            ),
            description="Discovered insights with recommended actions"
        ),
        "anomalies": types.Schema(
            type="ARRAY",
            items=types.Schema(type="STRING"),
            description="Detected anomalies or outliers"
        )
    },
    required=["summary", "key_metrics", "insights"]
)
 
# Get structured analysis results
response = client.models.generate_content(
    model="gemini-3-flash",
    contents="Structure the sales data analysis results from above",
    config=types.GenerateContentConfig(
        response_mime_type="application/json",
        response_schema=analysis_schema
    )
)
 
import json
result = json.loads(response.text)
print(json.dumps(result, indent=2))

Expected output:

{
  "summary": "Widget A leads in total revenue at 49% market share. Widget B shows the highest unit price strategy. Widget C captures the high-volume, low-price segment.",
  "key_metrics": [
    {"name": "Total Revenue", "value": "$171,500", "trend": "up"},
    {"name": "Daily Average", "value": "$17,150", "trend": "stable"},
    {"name": "Peak Day", "value": "Jan 5 ($25,000)", "trend": "up"}
  ],
  "insights": [
    {
      "title": "Widget B's premium pricing strategy is effective",
      "description": "Despite the lowest unit count, Widget B commands the highest unit price, suggesting strong profit margins",
      "confidence": "high",
      "action_item": "Conduct a detailed margin analysis on Widget B to optimize pricing strategy"
    }
  ],
  "anomalies": ["Widget C revenue spike on Jan 9 (+18.75% day-over-day)"]
}

Step 4: The Complete Agent Class

Now let's bring everything together into a production-ready agent.

from google import genai
from google.genai import types
import pandas as pd
import json
import base64
from pathlib import Path
 
class DataAnalysisAgent:
    """AI Data Analysis Agent powered by Gemini API"""
 
    def __init__(self, model: str = "gemini-3-flash"):
        self.client = genai.Client()
        self.model = model
        self._setup_tools()
 
    def _setup_tools(self):
        """Configure Function Calling + Code Execution tools"""
        self.function_tool = types.Tool(
            function_declarations=[
                types.FunctionDeclaration(
                    name="load_csv_file",
                    description="Load a CSV file and return stats",
                    parameters=types.Schema(
                        type="OBJECT",
                        properties={
                            "file_path": types.Schema(
                                type="STRING",
                                description="Path to the CSV file"
                            )
                        },
                        required=["file_path"]
                    )
                )
            ]
        )
        self.code_exec_tool = types.Tool(
            code_execution=types.ToolCodeExecution
        )
 
    def _handle_function_call(self, fn_call) -> str:
        """Dispatch function calls to implementations"""
        name = fn_call.name
        args = dict(fn_call.args)
 
        if name == "load_csv_file":
            df = pd.read_csv(args["file_path"])
            return json.dumps({
                "rows": len(df),
                "columns": list(df.columns),
                "preview": df.head(20).to_csv(index=False),
                "stats": df.describe().to_json()
            })
        return '{"error": "Unknown function"}'
 
    def analyze(self, file_path: str, question: str) -> dict:
        """
        Analyze a file and return structured results.
 
        Args:
            file_path: Path to a CSV/Excel file
            question: Analysis question or instruction
 
        Returns:
            dict: Structured analysis with metrics, insights, and charts
        """
        config = types.GenerateContentConfig(
            tools=[self.function_tool, self.code_exec_tool],
            system_instruction="""You are a data analysis expert.
Follow these steps:
1. Use load_csv_file to read the data
2. Use Code Execution to analyze and visualize the data
3. Provide detailed insights about your findings"""
        )
 
        prompt = f"""Analyze the following file:
File: {file_path}
Question: {question}
 
Steps:
1. Load the data using load_csv_file
2. Analyze and visualize with Python code
3. Summarize your findings"""
 
        contents = [types.Content(
            role="user",
            parts=[types.Part(text=prompt)]
        )]
 
        graphs = []
        max_turns = 5
 
        for _ in range(max_turns):
            response = self.client.models.generate_content(
                model=self.model,
                contents=contents,
                config=config
            )
 
            fn_calls = [
                p for p in response.candidates[0].content.parts
                if p.function_call
            ]
 
            if fn_calls:
                contents.append(response.candidates[0].content)
                fn_responses = []
                for fc in fn_calls:
                    result = self._handle_function_call(fc.function_call)
                    fn_responses.append(
                        types.Part(function_response=types.FunctionResponse(
                            name=fc.function_call.name,
                            response=json.loads(result)
                        ))
                    )
                contents.append(types.Content(
                    role="user", parts=fn_responses
                ))
                continue
 
            for part in response.candidates[0].content.parts:
                if part.inline_data:
                    graphs.append(part.inline_data)
            break
 
        # Phase 2: Structure the results
        analysis_text = response.text if response.text else ""
 
        structured_response = self.client.models.generate_content(
            model=self.model,
            contents=f"Structure these analysis results:\n\n{analysis_text}",
            config=types.GenerateContentConfig(
                response_mime_type="application/json",
                response_schema=types.Schema(
                    type="OBJECT",
                    properties={
                        "summary": types.Schema(type="STRING"),
                        "key_metrics": types.Schema(
                            type="ARRAY",
                            items=types.Schema(
                                type="OBJECT",
                                properties={
                                    "name": types.Schema(type="STRING"),
                                    "value": types.Schema(type="STRING"),
                                    "trend": types.Schema(type="STRING")
                                }
                            )
                        ),
                        "insights": types.Schema(
                            type="ARRAY",
                            items=types.Schema(
                                type="OBJECT",
                                properties={
                                    "title": types.Schema(type="STRING"),
                                    "description": types.Schema(type="STRING"),
                                    "action_item": types.Schema(type="STRING")
                                }
                            )
                        )
                    },
                    required=["summary", "key_metrics", "insights"]
                )
            )
        )
 
        result = json.loads(structured_response.text)
        result["graphs"] = [
            {
                "mime_type": g.mime_type,
                "data": base64.b64encode(g.data).decode()
            }
            for g in graphs
        ]
        return result
 
    def save_graphs(self, result: dict, output_dir: str = "./output"):
        """Save chart images from analysis results to disk"""
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        for i, graph in enumerate(result.get("graphs", [])):
            ext = graph["mime_type"].split("/")[-1]
            path = Path(output_dir) / f"chart_{i}.{ext}"
            with open(path, "wb") as f:
                f.write(base64.b64decode(graph["data"]))
            print(f"💾 Chart saved: {path}")

Usage example:

agent = DataAnalysisAgent(model="gemini-3-flash")
 
result = agent.analyze(
    file_path="sales_2026_q1.csv",
    question="Analyze quarterly sales trends and identify the fastest-growing product categories"
)
 
print("📊 Summary:", result["summary"])
for metric in result["key_metrics"]:
    print(f"  {metric['name']}: {metric['value']} ({metric['trend']})")
 
for insight in result["insights"]:
    print(f"\n💡 {insight['title']}")
    print(f"   {insight['description']}")
    print(f"   → Action: {insight['action_item']}")
 
agent.save_graphs(result, output_dir="./reports/charts")

Advanced Patterns

Pattern 1: Multi-File Comparative Analysis

Load and compare multiple datasets in a single analysis session.

result = agent.analyze(
    file_path="sales_q1.csv",
    question="""Compare Q1 and Q2 sales data.
    Additional data: sales_q2.csv
    Compare:
    - Revenue growth rates
    - Category market share shifts
    - Seasonal patterns"""
)

Pattern 2: Automated Scheduled Reports

Combine the agent with a scheduler for recurring reports.

import schedule
import time
from datetime import datetime
 
def daily_report():
    agent = DataAnalysisAgent()
    today = datetime.now().strftime("%Y-%m-%d")
 
    result = agent.analyze(
        file_path=f"/data/daily/{today}.csv",
        question="Generate a daily sales summary with day-over-day comparisons"
    )
 
    report = f"# Daily Report ({today})\n\n"
    report += f"## Summary\n{result['summary']}\n\n"
    report += "## Key Metrics\n"
    for m in result["key_metrics"]:
        report += f"- **{m['name']}**: {m['value']} ({m['trend']})\n"
 
    with open(f"reports/{today}.md", "w") as f:
        f.write(report)
 
schedule.every().day.at("09:00").do(daily_report)

Pattern 3: Anomaly Detection Pipeline

Leverage scikit-learn within Code Execution for automated anomaly detection.

result = agent.analyze(
    file_path="sensor_data.csv",
    question="""Detect anomalies in this sensor data using:
    - Isolation Forest for outlier detection
    - Moving average deviation analysis
    - List the top 10 highest anomaly scores
    Include a chart with anomalies highlighted in red."""
)

Troubleshooting

Common Issues and Solutions

1. Code Execution timeout (30-second limit)

Large datasets can exceed the execution time limit.

# ❌ Processing millions of rows directly
"Analyze all 1 million rows in this CSV"
 
# ✅ Sample or aggregate first
"Randomly sample 10,000 rows and perform statistical analysis"

2. Conflicts between Function Calling and Code Execution

When both tools are enabled, the model may be uncertain which to use.

# Use System Instructions to clarify roles
system_instruction = """
Always use load_csv_file for data loading.
Always use Code Execution for analysis, calculations, and charts.
Never confuse these two roles.
"""

3. Structured Output schema violations

Overly complex schemas may cause incomplete results.

# ❌ Deeply nested schemas with many required fields
# ✅ Keep schemas flat, add complexity incrementally

Cost and Performance Considerations

Token Cost Estimates

OperationInput TokensOutput TokensFlash Cost (est.)
CSV load (1,000 rows)~5,000~500~$0.001
Statistical analysis + code execution~2,000~3,000~$0.003
Chart generation (Matplotlib)~1,000~2,000~$0.002
Structured Output~3,000~1,000~$0.002
Total (single analysis)~11,000~6,500~$0.008

Performance Optimization Tips

  1. Two-phase approach: Start with a data preview to understand the structure, then run targeted detailed analysis only where needed.
  2. Context Caching: When asking multiple questions about the same dataset, use Context Caching to avoid re-sending input tokens.
  3. Flash vs. Pro: Use Flash for routine analysis (lower cost, faster response). Reserve Pro for complex reasoning tasks.
# Efficient multi-question analysis with Context Caching
from google.genai import types
 
cache = client.caches.create(
    model="gemini-3-flash",
    contents=[types.Content(
        role="user",
        parts=[types.Part(text=f"Dataset for analysis:\n{large_csv_data}")]
    )],
    config=types.CreateCachedContentConfig(
        display_name="sales-data-q1",
        ttl="3600s"
    )
)
 
# Ask multiple questions using the cache (saves input tokens)
for question in ["What are the sales trends?", "Any anomalies?", "Build a forecast model"]:
    response = client.models.generate_content(
        model="gemini-3-flash",
        contents=question,
        config=types.GenerateContentConfig(
            cached_content=cache.name,
            tools=[types.Tool(code_execution=types.ToolCodeExecution)]
        )
    )

Conclusion

The agent we just built combines Gemini API's Code Execution, Function Calling, and Structured Output to automatically analyze CSV/Excel data from natural language instructions.

Key takeaways:

  • Code Execution runs Pandas and Matplotlib in a sandboxed environment, ideal for data processing and visualization
  • Function Calling abstracts external data access, extending the agent's capabilities to files, databases, and APIs
  • Structured Output ensures analysis results are returned as type-safe JSON, ready for downstream processing
  • Combining all three features enables sophisticated automated analysis pipelines that would be difficult to build with any single feature alone

To take this further, explore [Gemini API Streaming]((/articles/gemini-api/streaming-and-chat) for real-time analysis progress updates, the [Interactions API]((/articles/gemini-api/gemini-interactions-api-guide) for long-running research tasks, and [Function Calling production patterns]((/articles/gemini-api/gemini-function-calling-production) to improve agent reliability.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-07-14
When Gemini's executed result and its prose disagree on a number — a gate that trusts only code_execution_result
Gemini Code Execution returns the value it actually computed and the sentence describing it as separate parts. Trust the prose and you can inherit a hallucinated number. Here is a verification gate, in working code, that extracts the executed result as the single source of truth and rejects prose that disagrees.
API / SDK2026-07-13
When responseSchema Can't Do $ref: Handling Recursive Schemas in Production with responseJsonSchema
Gemini's responseSchema is an OpenAPI subset with no $ref or $defs, so it can't express shared definitions or recursion. Here's how I moved to responseJsonSchema to reuse localized fields and handle a recursive category tree in production.
API / SDK2026-06-17
Moving My Automation Off the Gemini CLI Before the June 18 Shutdown
On June 18, the Gemini CLI stops responding for hosted plans. Here is how I moved unattended scripts that called gemini from the shell over to the google-genai SDK, with structured output, retries, and cost measurement built in.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →