Build a Power Grid Analysis Agent

Build a solver-grounded AI agent that analyzes power grids using real simulations.

Introduction

30 Second Summary

Every time you flip a light switch, an electrical grid is balancing voltage levels and line loads across thousands of connections. When engineers ask AI to help analyze these networks, the AI fabricates numbers that sound precise but are completely wrong.

In this project, you will build a Power Grid Analysis Agent that answers plain-English questions about power networks using real simulation results from pandapower. Your agent connects an LLM to real power system solvers through the Google Antigravity SDK, so every number it reports comes from an actual computation.

What You'll Build

Type "Check for voltage violations in my network" and watch your agent build a power grid model, run a real simulation, and report back with verified results you can trust.

By the end of this project, you'll have:

  • Ask a plain-English question about a power grid and get back real simulation results that come from actual computations.
  • Watch your AI agent use tool calling to autonomously create a network, run power flow, check voltages, and inspect line loading.
  • Compare LLM-only answers (fabricated) against solver-grounded answers (computed) on the same question to see the difference firsthand.
  • Secret Mission: Add a what-if scenario tool so the agent can answer questions like "What happens if I add a 30 MW solar plant at bus 3?".

Are there any prerequisites?

You'll need Python 3.10 or higher and Google Antigravity installed on your machine. You'll also need a Google account to grab a free Gemini API key.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Environment

Your agent needs two core ingredients to work. It needs a power system simulator that computes real results, and it needs an LLM brain that can orchestrate those computations.

In this step, you'll install the Python packages that provide both of those ingredients. You'll also grab a free API key for Gemini so your agent has a language model to power its reasoning.

In this step, get ready to:
  • Install pandapower and the Google Antigravity SDK.
  • Get a free Gemini API key from Google AI Studio.
  • Verify both installations work.
Install pandapower and the Google Antigravity SDK

Your agent will rely on two Python packages. pandapower is an open-source power system analysis library that runs real simulations. google-antigravity is the SDK that lets you build AI agents with tool calling and Gemini integration.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Install both packages by running these commands:
pip install pandapower==3.5.4
pip install google-antigravity==0.1.12

What do these packages do?

pandapower is a power system analysis library used by researchers and engineers worldwide. It builds network models and runs Newton-Raphson power flow simulations to compute real voltage and loading values.

google-antigravity is the Google Antigravity SDK. It provides the Agent class that connects to Gemini and lets you register plain Python functions as tools the agent can call.

  • Wait for both installs to finish. You should see Successfully installed at the end of each command's output.

Seeing a permission error?

  • Try running the commands with pip install --user instead. This installs the package for your user account without requiring admin permissions.
  • If you have multiple Python versions, make sure you're using pip for Python 3.10 or higher. You can check with python --version.
  • help me fix a pip install error for pandapower or google-antigravity
Get a free Gemini API key

Your agent needs an API key to communicate with Gemini. Google AI Studio provides free API keys that work with the Antigravity SDK.

  • Open Google AI Studio in your browser.
  • Sign in with your Google account.
  • Click Create API key.
  • Copy the API key that appears. Save it somewhere safe for now.

Why is this free?

The Gemini API has a generous free tier. The usage in this project stays well within the free allowance, so you will not be charged.

Now set the API key as an environment variable so the SDK can find it.

  • Return to your terminal and set the GEMINI_API_KEY environment variable by running the command for your operating system:

macOS

export GEMINI_API_KEY="your-api-key-here"
  • Replace your-api-key-here with the API key you just copied.

On Linux?

The same export command works in Linux terminals too.

Windows

The command depends on which terminal you are using. Right-click the title bar of your terminal window to check.

  • If the title bar says Windows PowerShell, set the key by running:
$env:GEMINI_API_KEY="your-api-key-here"
  • If the title bar says Command Prompt, run this instead:
set GEMINI_API_KEY=your-api-key-here
  • Replace your-api-key-here with the API key you just copied.

This key only lasts for the session

Setting an environment variable this way only persists for the current terminal session. If you close and reopen your terminal, you will need to set GEMINI_API_KEY again.

Verify your installations

Both packages are installed and your API key is set. Before you move on, take a moment to predict what will happen when you run the verification commands below.

  • What do you think each command will print if everything is set up correctly?
  • Verify your pandapower installation by running:
python -c "import pandapower; print(pandapower.__version__)"
  • You should see 3.5.4 printed in your terminal.
  • Verify the Google Antigravity SDK by running:
python -c "from google.antigravity import Agent; print('SDK ready')"
  • You should see SDK ready printed in your terminal.

Seeing a ModuleNotFoundError?

  • Make sure you ran both pip install commands from earlier in the same terminal session.
  • Check that python points to Python 3.10 or higher. Run python --version to confirm. On some systems, you may need to use python3 instead of python.
  • If the pandapower version printed but the SDK import failed, try reinstalling with pip install google-antigravity==0.1.12.
  • help me troubleshoot a ModuleNotFoundError when importing pandapower or google.antigravity

Your environment is locked and loaded. Next up, you'll build a real power network model and see pandapower compute actual voltage and loading values.

Build and Run a Power Network

Your environment is set up with pandapower and the Antigravity SDK installed. Now you need the most important ingredient for a solver-grounded agent: a real solver with real data.

In this step, you'll build a 5-bus electrical power network using pandapower and run a power flow simulation. The computed results from this simulation are the ground truth your AI agent will rely on later.

In this step, get ready to:
  • Build a 5-bus power network with buses, lines, a transformer, and solar generation.
  • Run a Newton-Raphson power flow simulation and observe real computed results.
  • Record the actual voltage values for comparison in the next step.
Create your project folder and grid_demo.py

Every power network starts with buses. Buses are connection points where components like generators, loads, and transformers attach. Your network will have five buses at two voltage levels: 110 kV for high-voltage transmission and 20 kV for medium-voltage distribution.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir power-grid-agent
cd power-grid-agent
  • Confirm the folder exists by running:
ls

You should see an empty folder with no files listed yet.

  • In Google Antigravity, open the power-grid-agent folder from your Desktop. Click File in the top menu bar.
  • Click Open Folder and select the power-grid-agent folder on your Desktop.
  • Create a new file by clicking File then New File. Name it grid_demo.py.
  • Add the following code to import pandapower and create five buses:
import pandapower as pp

# Create an empty power network
net = pp.create_empty_network()

# Define five buses at two voltage levels
# b0 and b1 are 110 kV high-voltage transmission buses
# b2, b3, and b4 are 20 kV medium-voltage distribution buses
b0 = pp.create_bus(net, vn_kv=110)
b1 = pp.create_bus(net, vn_kv=110)
b2 = pp.create_bus(net, vn_kv=20)
b3 = pp.create_bus(net, vn_kv=20)
b4 = pp.create_bus(net, vn_kv=20)

What does this code do?

pp.create_empty_network() initializes a blank pandapower network. Think of it as an empty canvas for your power grid.

pp.create_bus() adds a bus (connection point) at a given voltage level. Buses b0 and b1 operate at 110 kV (transmission level). Buses b2, b3, and b4 operate at 20 kV (distribution level).

  • Save grid_demo.py.
  • Run the script in your terminal to confirm there are no errors:
python grid_demo.py

The script should exit cleanly with no output and no errors. This confirms your pandapower import and bus creation work correctly.

Seeing an import error?

  • Make sure you installed pandapower in Step 1 with pip install pandapower==3.5.4. If you used a virtual environment, make sure it is activated.
  • Check that your file is named exactly grid_demo.py and is saved inside the power-grid-agent folder.
  • Still stuck? Help me debug my pandapower import error
Connect the network components

Buses alone are just isolated points. To form an actual power grid, you need to connect them with lines, transformers, and an external power source.

  • In grid_demo.py, add the following code below the bus definitions:
# Connect bus 0 to the external grid (infinite power source)
pp.create_ext_grid(net, b0)
# Add a 110 kV transmission line between bus 0 and bus 1
pp.create_line(net, b0, b1, length_km=10, std_type="149-AL1/24-ST1A 110.0")
# Step down voltage from 110 kV to 20 kV with a transformer
pp.create_transformer(net, b1, b2, std_type="25 MVA 110/20 kV")
# Add two 20 kV distribution lines
pp.create_line(net, b2, b3, length_km=10, std_type="184-AL1/30-ST1A 20.0")
pp.create_line(net, b2, b4, length_km=10, std_type="184-AL1/30-ST1A 20.0")

# Add a 15 MW load (electricity consumer) at bus 3
pp.create_load(net, b3, p_mw=15.0, q_mvar=10.0, name="Load 1")
# Add a 20 MW solar generator at bus 4
pp.create_sgen(net, b4, p_mw=20.0, q_mvar=0.15, name="Solar Gen 1")

What does this code do?

  • pp.create_ext_grid() connects bus 0 to the external grid. This is an infinite power source that keeps the network balanced.
  • pp.create_line() adds a physical power line between two buses. The std_type parameter selects a real cable specification from pandapower's built-in library.
  • pp.create_transformer() steps the voltage down from 110 kV to 20 kV, connecting the transmission and distribution networks.
  • pp.create_load() adds a 15 MW electricity consumer at bus 3. The q_mvar value represents reactive power demand.
  • pp.create_sgen() adds a 20 MW solar generator at bus 4. This is a static generator that feeds power into the grid.
Run the power flow and observe real results

The network is fully defined. Now pandapower can solve the physics: it calculates the actual voltage at every bus and the loading on every line using a Newton-Raphson algorithm.

  • In grid_demo.py, add the following code below the solar generator line:
# Run Newton-Raphson power flow analysis
pp.runpp(net)

# Print the simulation results
print("=== Bus Results ===")
print(net.res_bus)
print("\n=== Line Loading ===")
print(net.res_line[["loading_percent"]])
print("\n=== Transformer Loading ===")
print(net.res_trafo[["loading_percent"]])

What does this code do?

pp.runpp(net) runs a full AC power flow analysis using the Newton-Raphson method. It solves for the real voltage magnitude and angle at every bus.

  • net.res_bus holds the solved bus voltages. The vm_pu column is the voltage magnitude in per-unit (values near 1.0 mean the bus is healthy).
  • net.res_line[["loading_percent"]] shows how much of each line's capacity is being used. Higher values mean the line is carrying more power relative to its limit.
  • net.res_trafo[["loading_percent"]] shows the transformer's loading as a percentage of its rated capacity.
  • Save grid_demo.py.

Before you run the script, what do you think the voltage magnitudes will be for a well-functioning power grid? Will they be close to 0, close to 1, or much higher?

  • Run the script in your terminal:
python grid_demo.py

You should see three tables printed in your terminal: bus results with vm_pu values close to 1.0, line loading percentages, and transformer loading percentage.

What should I see?

The vm_pu column shows voltage magnitude in per-unit. Values near 1.0 mean the bus voltage is close to its rated value. This is what a healthy grid looks like.

The loading_percent columns show how hard each line and transformer is working. These are real computed values from pandapower's Newton-Raphson solver. They are not estimates or approximations.

Seeing a convergence error?

  • Double-check that your std_type strings are copied exactly. The line types "149-AL1/24-ST1A 110.0" and "184-AL1/30-ST1A 20.0" must match character-for-character.
  • Confirm the transformer type is exactly "25 MVA 110/20 kV". A typo in any standard type string causes pandapower to fail.
  • Make sure you have pp.create_ext_grid(net, b0) in your file. Without an external grid, the power flow has no reference voltage and cannot converge.
  • Still stuck? Help me fix my pandapower convergence error

Write down your vm_pu values

Note the actual vm_pu values from the Bus Results table. In the next step, you will ask an LLM the same question about this network and compare its fabricated answer against these real computed values.

✔️ Awesome, I've got everything!

Great. Double check you've saved grid_demo.py and make sure your terminal output shows all three result tables.

ⓧ I'd like to double check the full code

Here is the complete grid_demo.py file. Compare it against your code line by line:

import pandapower as pp

net = pp.create_empty_network()

b0 = pp.create_bus(net, vn_kv=110)
b1 = pp.create_bus(net, vn_kv=110)
b2 = pp.create_bus(net, vn_kv=20)
b3 = pp.create_bus(net, vn_kv=20)
b4 = pp.create_bus(net, vn_kv=20)

pp.create_ext_grid(net, b0)
pp.create_line(net, b0, b1, length_km=10, std_type="149-AL1/24-ST1A 110.0")
pp.create_transformer(net, b1, b2, std_type="25 MVA 110/20 kV")
pp.create_line(net, b2, b3, length_km=10, std_type="184-AL1/30-ST1A 20.0")
pp.create_line(net, b2, b4, length_km=10, std_type="184-AL1/30-ST1A 20.0")

pp.create_load(net, b3, p_mw=15.0, q_mvar=10.0, name="Load 1")
pp.create_sgen(net, b4, p_mw=20.0, q_mvar=0.15, name="Solar Gen 1")

pp.runpp(net)

print("=== Bus Results ===")
print(net.res_bus)
print("\n=== Line Loading ===")
print(net.res_line[["loading_percent"]])
print("\n=== Transformer Loading ===")
print(net.res_trafo[["loading_percent"]])

You now have a working power system model that produces real, computed results. Next up, you'll ask an LLM the exact same question about this network and see how its answer compares to the real numbers you just computed.

Ask the LLM Without Tools

In Step 2, you built a working power network and got real computed results from pandapower's Newton-Raphson solver. You noted down those bus voltages and line loading values.

Now it's time for an experiment. What happens if you describe the exact same network to an LLM and ask it for those same numbers? In this step, you'll put the LLM to the test.

In this step, get ready to:
  • Create ask_llm.py with a basic agent that has no tools.
  • Run the script and compare the LLM's numbers to your real pandapower results.
Create the LLM-only script

The Antigravity SDK lets you create an agent with a simple LocalAgentConfig. When you pass no tools, the agent relies entirely on the LLM's own knowledge to answer questions.

  • Create a new file called ask_llm.py in the same folder as grid_demo.py.
  • Add the following code to ask_llm.py:
import asyncio
from google.antigravity import Agent, LocalAgentConfig

async def main():
    # Create a basic agent with no tools
    config = LocalAgentConfig()
    async with Agent(config) as agent:
        # Describe the exact same network from grid_demo.py
        question = (
            "I have a 5-bus power network with a 110/20 kV transformer. "
            "Bus 0 and Bus 1 are 110 kV, Buses 2-4 are 20 kV. "
            "Bus 3 has a 15 MW, 10 MVAr load and Bus 4 has a 20 MW solar generator. "
            "Lines are 10 km each using standard types. "
            "What are the exact voltage magnitudes at each bus in per-unit "
            "and the line loading percentages after running a power flow?"
        )
        print(f"Question: {question}\n")
        # Send the question and print the LLM's response
        response = await agent.chat(question)
        print(f"LLM Response:\n{await response.text()}")

if __name__ == "__main__":
    asyncio.run(main())

What does this code do?

  • LocalAgentConfig() creates a bare-bones agent configuration with no tools. The agent can only rely on the LLM's training data to answer.
  • The question string describes the exact same 5-bus network you built in grid_demo.py. It asks for the same voltage magnitudes and line loading percentages.
  • agent.chat(question) sends the question to the Gemini LLM. The await response.text() call retrieves the LLM's full text response.
  • Save ask_llm.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete ask_llm.py file for comparison:

import asyncio
from google.antigravity import Agent, LocalAgentConfig


async def main():
    config = LocalAgentConfig()
    async with Agent(config) as agent:
        question = (
            "I have a 5-bus power network with a 110/20 kV transformer. "
            "Bus 0 and Bus 1 are 110 kV, Buses 2-4 are 20 kV. "
            "Bus 3 has a 15 MW, 10 MVAr load and Bus 4 has a 20 MW solar generator. "
            "Lines are 10 km each using standard types. "
            "What are the exact voltage magnitudes at each bus in per-unit "
            "and the line loading percentages after running a power flow?"
        )
        print(f"Question: {question}\n")
        response = await agent.chat(question)
        print(f"LLM Response:\n{await response.text()}")


if __name__ == "__main__":
    asyncio.run(main())
Run the script and compare the results

Before you run this, think about it: do you expect the LLM to give you the same voltage values you got from pandapower in Step 2?

  • Run the script by entering this command in your terminal:
python ask_llm.py

The LLM responds with specific voltage magnitudes and line loading percentages. It sounds confident and the numbers look plausible.

Seeing an error instead of a response?

  • If you see A Gemini API key is required, your GEMINI_API_KEY environment variable is not set. Re-run the export command from Step 1 in the same terminal session you are using now.
  • If you see ModuleNotFoundError, the Antigravity SDK is not installed in this Python environment. Run pip install google-antigravity again.

Still stuck? Help me debug why ask_llm.py is throwing an error when I run it.

  • Now compare the LLM's numbers against the real output from grid_demo.py that you noted down in Step 2.
  • Look at the bus voltage magnitudes (vm_pu values) the LLM reported. Do they match the real pandapower values?
  • Check the line loading percentages too. The LLM's numbers will be different from your real results.

The numbers do not match. The LLM invented values that sound reasonable but are numerically wrong. It has no access to a power flow solver, so it cannot actually compute the results.

Why did the LLM get the numbers wrong?

The LLM was trained on text about power systems, so it knows what voltage magnitudes and loading percentages look like. It produces numbers that are in the right ballpark. But it never ran a Newton-Raphson solver on your specific network topology, loads, and generators.

This is the core limitation that solver-grounded agents fix. Instead of letting the LLM guess, you give it tools that call real solvers. The LLM decides which tool to call. The solver computes the actual answer.

You have proven the problem: the LLM fabricates engineering results it cannot compute. In the next step, you will fix this by giving the agent tools that call pandapower directly.

Create Your First Tool-Calling Agent

In the last step, you saw the LLM confidently produce voltage and loading numbers that were completely wrong. It had no way to actually compute a power flow. It just guessed.

The fix is to give the LLM access to real tools. Instead of guessing, the agent will call your pandapower functions directly, get computed results, and report those back. This is the tool calling pattern at the heart of solver-grounded AI agents.

In this step, get ready to:
  • Write two tool functions that wrap pandapower operations for the agent to call.
  • Wire both tools into an interactive agent with system instructions.
  • Test the agent and confirm it returns real computed values that match your Step 2 output.
Create the tool functions

The agent needs Python functions it can call when it decides to act. Each function wraps a pandapower operation and returns a string the agent can read and relay to you.

What is tool calling?

Tool calling lets an LLM invoke real functions instead of generating an answer from memory. The LLM reads the function's name and docstring, decides when to call it, and uses the returned result in its response.

The LLM never runs Python itself. The SDK handles the actual execution and passes results back. The LLM orchestrates and explains. The tools compute.

  • Create a new file called grid_agent.py in the same folder as your other project files.
  • Add the imports, a global network variable, and the first tool function by pasting this code:
import asyncio
import pandapower as pp
from google.antigravity import Agent, LocalAgentConfig

net = None  # Holds the network between tool calls

def create_test_network() -> str:
    """Creates a 5-bus test power network with a 110/20 kV transformer, lines, a load, and a solar generator."""
    global net
    net = pp.create_empty_network()
    # Create 5 buses: two at 110 kV, three at 20 kV
    b0 = pp.create_bus(net, vn_kv=110)
    b1 = pp.create_bus(net, vn_kv=110)
    b2 = pp.create_bus(net, vn_kv=20)
    b3 = pp.create_bus(net, vn_kv=20)
    b4 = pp.create_bus(net, vn_kv=20)
    # Connect with external grid, lines, and transformer
    pp.create_ext_grid(net, b0)
    pp.create_line(net, b0, b1, length_km=10, std_type="149-AL1/24-ST1A 110.0")
    pp.create_transformer(net, b1, b2, std_type="25 MVA 110/20 kV")
    pp.create_line(net, b2, b3, length_km=10, std_type="184-AL1/30-ST1A 20.0")
    pp.create_line(net, b2, b4, length_km=10, std_type="184-AL1/30-ST1A 20.0")
    # Add load and solar generation
    pp.create_load(net, b3, p_mw=15.0, q_mvar=10.0, name="Load 1")
    pp.create_sgen(net, b4, p_mw=20.0, q_mvar=0.15, name="Solar Gen 1")
    # Return a summary for the agent to relay to the user
    return (
        f"Created a 5-bus network with {len(net.bus)} buses, "
        f"{len(net.line)} lines, {len(net.trafo)} transformer, "
        f"{len(net.load)} load, and {len(net.sgen)} solar generator."
    )

What does this code do?

  • net = None is a global variable that stores the network object. Both tool functions need access to the same network, so it lives at the module level.
  • create_test_network() builds the exact same 5-bus network you created in grid_demo.py. The docstring tells the LLM what the function does, so it knows when to call it.
  • The function returns a summary string instead of a dataframe. The agent reads text, not pandas objects.
  • Add the second tool function directly below create_test_network() by pasting this code:

def run_power_flow() -> str:
    """Runs an AC power flow analysis on the current network using the Newton-Raphson method and returns bus voltage results."""
    global net
    if net is None:
        return "Error: No network created yet. Call create_test_network first."
    # Run Newton-Raphson power flow
    pp.runpp(net)
    # Extract voltage magnitude and angle for each bus
    bus_results = net.res_bus[["vm_pu", "va_degree"]].to_string()
    return (
        "Power flow converged successfully.\n\n"
        "Bus Results (voltage magnitude in p.u. and angle in degrees):\n"
        f"{bus_results}"
    )

What does this function do?

  • The safety check if net is None prevents the agent from running power flow before a network exists. The error message tells the LLM to call create_test_network first.
  • pp.runpp(net) runs the same Newton-Raphson solver you used in grid_demo.py. The results are formatted as a string so the agent can read and explain them.
  • Save grid_agent.py.
  • Verify that the first tool function works by running this command:
python -c "from grid_agent import create_test_network; print(create_test_network())"

You should see a summary line like Created a 5-bus network with 5 buses, 3 lines, 1 transformer, 1 load, and 1 solar generator.

Seeing an import error?

  • Make sure you saved grid_agent.py before running the command.
  • Confirm your terminal is in the same folder as grid_agent.py. Run ls and check that the file appears in the listing.
  • Check that the from google.antigravity import Agent, LocalAgentConfig line matches exactly. A typo in the module path will cause an import failure.

Help me debug this import error in grid_agent.py

Wire up the interactive agent

The two tool functions are ready. Now the agent needs a configuration that tells it who it is, what rules to follow, and which tools it has access to.

  • Add the agent configuration and interactive loop at the bottom of grid_agent.py, below the run_power_flow() function:

async def main():
    # Configure the agent with instructions and tools
    config = LocalAgentConfig(
        system_instructions=(
            "You are a power systems analysis agent. You help engineers analyze "
            "electrical power networks. Always use your tools to get real computed "
            "results. Never guess or estimate numerical values. When asked about "
            "a network, first create it, then run power flow, then analyze results."
        ),
        tools=[
            create_test_network,
            run_power_flow,
        ],
    )
    # Start an interactive chat loop
    async with Agent(config) as agent:
        print("Power Grid Analysis Agent ready. Type your questions (Ctrl+C to exit).\n")
        while True:
            try:
                question = input("You: ")
                if not question.strip():
                    continue
                response = await agent.chat(question)
                print(f"\nAgent: {await response.text()}\n")
            except KeyboardInterrupt:
                print("\nGoodbye!")
                break

if __name__ == "__main__":
    asyncio.run(main())

What does this code do?

  • system_instructions tells the agent its role and rules. The key instruction is "Never guess or estimate numerical values." This forces the agent to use tools instead of fabricating answers like the bare LLM did in Step 3.
  • tools=[create_test_network, run_power_flow] registers both functions. The SDK reads each function's name, type hints, and docstring to tell the LLM what tools are available.
  • The while True loop creates an interactive chat session. You type a question, the agent processes it (calling tools as needed), and prints the response. Press Ctrl+C to exit.
  • Save grid_agent.py.

Seeing a syntax error on save?

  • Make sure the async def main(): line is at the top level (no indentation). It should be aligned with the def run_power_flow(): line above it.
  • Check that the closing parenthesis on the LocalAgentConfig(...) call is present. A missing ) will produce an unexpected indent or EOF error.

Help me fix a syntax error in my grid_agent.py

Test the agent with a real question

Your agent has two tools and instructions telling it to always use them. Time to see if it actually calls the solver instead of guessing.

Before you run the agent, think about what you expect to happen. Will the agent call one tool or both? Will the voltage values match the numbers from grid_demo.py?

  • Start the agent by running:
python grid_agent.py

You should see Power Grid Analysis Agent ready. Type your questions (Ctrl+C to exit).

  • Type this question at the You: prompt and press Enter:
Create the test network and run a power flow analysis

The agent calls both tools autonomously. It first creates the 5-bus network, then runs the Newton-Raphson power flow, and reports the computed bus voltages and angles.

  • Compare the vm_pu values the agent returns against the bus results you saw when you ran grid_demo.py in Step 2. They should match exactly.
  • Press Ctrl+C to exit the agent when you are done.

Why do the numbers match now?

In Step 3, the LLM made up numbers because it had no access to a power flow solver. Now the agent calls pp.runpp(net) through your run_power_flow() tool. The same solver produces the same results every time.

This is the solver-grounded agent pattern: the LLM decides what to do and explains the results, but the actual numbers always come from a trusted computation. No more fabrication.

Agent not calling the tools?

  • Check that your GEMINI_API_KEY environment variable is still set in this terminal session. If you opened a new terminal, you need to set it again.
  • Verify that both functions are listed in the tools=[create_test_network, run_power_flow] list. If a function is missing, the agent cannot call it.
  • Make sure each tool function has a docstring. The SDK uses the docstring to tell the LLM what the function does. Without it, the agent does not know when to call the tool.

Help me debug why my agent is not calling tools

✔️ Awesome, I've got everything!

Great. Double check you have saved grid_agent.py and that the agent returned real voltage values matching your Step 2 output.

ⓧ I'd like to double check the full code

The full grid_agent.py file is built from three chunks. Scroll up and confirm each one is present in this order:

  • Chunk 1: Imports (asyncio, pandapower, Agent, LocalAgentConfig), net = None, and the create_test_network() function.
  • Chunk 2: The run_power_flow() function.
  • Chunk 3: The async def main() function with LocalAgentConfig, the interactive loop, and asyncio.run(main()) at the bottom.

Your agent now computes real answers instead of guessing. Next up, you will give it diagnostic intelligence so it can flag voltage violations and overloaded lines on its own.

Add Diagnostic Intelligence

Your agent can now create a power network and compute raw results on demand. That is a big step forward from the fabricated numbers you saw in Step 3.

However, raw voltage tables and loading percentages are not the same as actionable insights. A real power systems assistant needs to flag problems automatically. In this step, you will add two diagnostic tools that check for voltage violations and overloaded lines, so the agent can report exactly what needs attention.

In this step, get ready to:
  • Add a voltage violation checker that flags buses outside safe limits.
  • Add a line loading diagnostic that warns about overloaded lines.
  • Test the agent chaining all four tools for a full network analysis.
Add a voltage violation checker

In power systems, bus voltages must stay within a safe range of 0.95 to 1.05 per-unit (p.u.). Voltages outside this range can damage equipment or destabilize the grid.

  • In grid_agent.py, scroll down to the run_power_flow() function.
  • Add the following new function below it:
def check_voltage_violations() -> str:
    """Checks all buses for voltage violations outside the acceptable range of 0.95 to 1.05 p.u."""
    global net
    if net is None or net.res_bus.empty:
        return "Error: Run power flow first."
    under = net.res_bus[net.res_bus.vm_pu < 0.95]
    over = net.res_bus[net.res_bus.vm_pu > 1.05]
    if under.empty and over.empty:
        all_voltages = net.res_bus[["vm_pu"]].to_string()
        return (
            "All bus voltages are within acceptable limits (0.95 - 1.05 p.u.).\n\n"
            f"Bus Voltages:\n{all_voltages}"
        )
    violations = net.res_bus[
        (net.res_bus.vm_pu < 0.95) | (net.res_bus.vm_pu > 1.05)
    ]
    result = violations[["vm_pu"]].to_string()
    return f"VOLTAGE VIOLATIONS DETECTED at {len(violations)} bus(es):\n{result}"

What does this code do?

  • The function first checks whether power flow results exist. Without results, there is nothing to inspect.
  • It filters net.res_bus.vm_pu for buses below 0.95 p.u. or above 1.05 p.u. These are the standard voltage limits in power engineering.
  • If all buses are within limits, it returns an all-clear report with the actual voltage values. If any violations exist, it returns a clear warning identifying exactly which buses are affected.

The function is written, but the agent does not know it exists yet. You need to register it in the agent's tool list.

  • Scroll down to the tools list inside the main() function.
  • Find this code:
        tools=[
            create_test_network,
            run_power_flow,
        ],
  • Add check_voltage_violations to the list, so it now looks like this:
        tools=[
            create_test_network,
            run_power_flow,
            check_voltage_violations,
        ],
  • Save grid_agent.py.
  • Run the agent by running this command:
python grid_agent.py
  • Type this prompt and press Enter:
Create the test network, run power flow, and check for voltage violations

You should see the agent call three tools in sequence: create_test_network, run_power_flow, and then check_voltage_violations. It will report whether any buses have voltage violations.

  • Press Ctrl+C to exit the agent.

Agent not calling the voltage check tool?

  • Make sure check_voltage_violations appears in the tools list. If it is not registered, the agent cannot call it.
  • Check that the function is defined before main(). Python needs the function to exist before it is referenced.
  • Ask for help: Why isn't my agent calling the check_voltage_violations tool?
Add a line loading diagnostic

Voltage is only half the picture. Lines carrying too much current can overheat and fail. A loading above 80% signals that a line is under stress.

  • In grid_agent.py, find the check_voltage_violations() function you just added.
  • Add the following new function below it:
def get_line_loading() -> str:
    """Returns the loading percentage of all lines and flags any lines loaded above 80 percent."""
    global net
    if net is None or net.res_line.empty:
        return "Error: Run power flow first."
    loading = net.res_line[["loading_percent"]].to_string()
    overloaded = net.res_line[net.res_line.loading_percent > 80]
    warning = ""
    if not overloaded.empty:
        warning = f"\n\nWARNING: {len(overloaded)} line(s) loaded above 80%!"
    return f"Line Loading Results:\n{loading}{warning}"

What does this code do?

  • The function reads net.res_line[["loading_percent"]] to get the current loading of every line in the network.
  • It then checks if any line exceeds 80% loading. If so, it appends a warning to the results so the agent can call attention to the stressed lines.
  • Scroll down to the tools list inside main().
  • Find this code:
        tools=[
            create_test_network,
            run_power_flow,
            check_voltage_violations,
        ],
  • Add get_line_loading to the list, so it now looks like this:
        tools=[
            create_test_network,
            run_power_flow,
            check_voltage_violations,
            get_line_loading,
        ],
  • Save grid_agent.py.
Run a full network analysis

Your agent now has four tools: create the network, run power flow, check voltage violations, and inspect line loading. Time to see it chain all four in one request.

Before you run this, think about what you expect to happen. How many tools will the agent call, and in what order?

  • Run the agent by running this command:
python grid_agent.py
  • Type this prompt and press Enter:
Run a full analysis of the network - check voltages and line loading

The agent calls all four tools in sequence: it creates the network, runs power flow, checks for voltage violations, and inspects line loading. It then summarizes everything in a single response, reporting whether any buses have voltage violations or any lines are overloaded.

Why is four-tool chaining a big deal?

You did not write any code to tell the agent which tools to call or in what order. The LLM reads the tool descriptions, understands the dependencies (you need a network before you can run power flow, and you need power flow results before you can check voltages), and orchestrates the sequence autonomously.

This is the solver-grounded agent pattern: the LLM decides what to do and explains the results, while the trusted tools compute the actual numbers.

  • Press Ctrl+C to exit the agent.

Agent skipping some tools?

  • Check that all four functions (create_test_network, run_power_flow, check_voltage_violations, get_line_loading) are in the tools list. A missing entry means the agent cannot call that tool.
  • Try rephrasing your prompt to be more explicit, such as "Create the network, run power flow, check for voltage violations, and show line loading."
  • Ask for help: Why is my agent not calling all four tools when I ask for a full analysis?

✔️ Awesome, I've got everything!

Great. Double check you have saved grid_agent.py.

ⓧ I'd like to double check the full code

Your grid_agent.py should contain these sections in order. Scroll up and confirm each one is present:

  • Imports: asyncio, pandapower as pp, Agent and LocalAgentConfig from google.antigravity.
  • Global: net = None.
  • Function 1: create_test_network() (from Step 4).
  • Function 2: run_power_flow() (from Step 4).
  • Function 3: check_voltage_violations() (added in this step).
  • Function 4: get_line_loading() (added in this step).
  • main(): LocalAgentConfig with system_instructions and a tools list containing all four functions.

Your agent now interprets results, not just computes them. It flags voltage violations and stressed lines without you having to read raw tables. Next up, you will take on a secret mission to add what-if scenario analysis.

Secret mission

Add a What-If Scenario Tool

Your agent can analyze a network's current state, but real power engineers ask "what if?" questions. Can you add a tool that lets the agent modify the network, re-run the solver, and compare outcomes?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally with free tools. The Gemini API free tier has no charges, so there are no ongoing costs to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Python files: grid_demo.py, ask_llm.py, and grid_agent.py.
  • Pip packages: pandapower and google-antigravity.
  • Gemini API key in Google AI Studio.

Keep everything running

No action needed. Choose this if you want to keep experimenting with your power grid agent or extend it with new tools.

  • Your three Python files stay on your machine ready to run.
  • The pandapower and google-antigravity packages remain installed for future projects.
  • Your Gemini API key stays active in Google AI Studio at no cost.
  • Your GEMINI_API_KEY environment variable is session-scoped. It clears automatically when you close your terminal. You will need to set it again next time.

Pause - I'll come back to this later

Stop any running processes to free up your terminal. Your files and packages stay for later.

  • If your agent is still running, press Ctrl+C in the terminal to stop it.
  • Your Python files and installed packages stay in place.
  • Your GEMINI_API_KEY environment variable clears when you close the terminal. Set it again when you return.

Delete - I don't want to use this again

Remove all project files, uninstall the packages, and delete your API key.

  • If your agent is still running, press Ctrl+C in the terminal to stop it.
  • Navigate to the folder where you created your project files and delete them. On macOS/Linux, run:
rm grid_demo.py ask_llm.py grid_agent.py

On Windows, run:

del grid_demo.py, ask_llm.py, grid_agent.py
  • Uninstall the pip packages by running:
pip uninstall pandapower google-antigravity -y

Nice Work!

Nice Work!

Nice work! You just built a solver-grounded AI agent that answers power systems questions using real computed results from pandapower simulations, not fabricated numbers.

You've learned how to:

  • Expose the core limitation of LLMs for engineering by comparing fabricated numbers against real power flow results, then fix it with the solver-grounded agent pattern where trusted tools compute and the LLM orchestrates.
  • Build a tool-calling AI agent using the Google Antigravity SDK with four custom tools that create networks, run simulations, check voltage violations, and inspect line loading.
  • Watch the agent autonomously chain tool calls to answer multi-step analysis questions, deciding on its own which tools to call and in what order.
  • Secret Mission: Added what-if scenario analysis to your agent, enabling it to modify the network with new generators and compare before-and-after outcomes.

Ready to quiz yourself?