AI Agents#

pyRadPlan ships an optional ai_agents module that uses pydantic-ai to integrate large-language-model (LLM) reasoning into the treatment planning workflow. The agents follow the same pydantic data-model conventions as the rest of pyRadPlan and return fully-validated plan and structure-set objects, so their output can be fed directly into the rest of the pipeline.

Note

The ai_agents module is an optional feature. Before using it you need to install the optional dependencies and set an API key for your chosen LLM provider:

pip install pydantic-ai pydantic-settings python-dotenv

Then export your provider API key, for example:

export ANTHROPIC_API_KEY=sk-ant-...   # Anthropic / Claude
export OPENAI_API_KEY=sk-...          # OpenAI
export GOOGLE_API_KEY=...             # Google Gemini

API keys can also be stored in a .env file in the working directory.

Overview#

The module exposes two high-level helper functions and a settings class:

Symbol

Purpose

generate_beam_angles()

Ask the LLM to suggest gantry (and couch) angles for a given treatment site and radiation mode, then write them back into the Plan.

generate_voi_objectives()

Ask the LLM to propose optimization objectives for each VOI in a StructureSet based on the treatment site and prescribed dose.

AiSettings

Pydantic-settings class that reads the default model name from the environment variable PYRADPLAN_AI_MODEL or a .env file.

Model selection#

The default model is claude-sonnet-4-5. You can override it globally via the environment variable PYRADPLAN_AI_MODEL, or per-call via the model= keyword:

import os
os.environ["PYRADPLAN_AI_MODEL"] = "openai:gpt-4o-mini"

from pyRadPlan import ai_agents

# Check effective settings
print(ai_agents.AiSettings().model)   # openai:gpt-4o-mini

# Override for a single call
pln = ai_agents.generate_beam_angles(pln, "prostate", model="gemini-2.0-flash")

pydantic-ai uses the provider prefix (openai:, anthropic:, google-gla:, …) to select the backend automatically. When no prefix is given the model string is passed to the Anthropic backend.

Generating beam angles#

generate_beam_angles() populates pln.prop_stf with gantry_angles and matching couch_angles (all zeros by default):

from pyRadPlan import IonPlan, ai_agents

pln = IonPlan(
    radiation_mode="protons",
    machine="Generic",
    num_of_fractions=30,
    prescribed_dose=60,
)

pln = ai_agents.generate_beam_angles(pln, treatment_site="prostate")

print(pln.prop_stf["gantry_angles"])   # e.g. [0.0, 90.0, 180.0]

An optional additional_context string lets you pass extra clinical constraints:

pln = ai_agents.generate_beam_angles(
    pln,
    treatment_site="head and neck",
    additional_context="Patient has a hip replacement on the right side.",
)

Generating optimization objectives#

generate_voi_objectives() iterates over the VOIs in a StructureSet and attaches LLM-suggested optimization objectives to each structure. By default any previously assigned objectives are cleared first (clear_existing=True):

from pyRadPlan import load_tg119, IonPlan, ai_agents

ct, cst = load_tg119()
pln = IonPlan(
    radiation_mode="protons",
    machine="Generic",
    num_of_fractions=30,
    prescribed_dose=60,
)

cst = ai_agents.generate_voi_objectives(pln, cst, treatment_site="prostate")

for voi in cst.vois:
    if voi.objectives:
        print(voi.name, [type(o).__name__ for o in voi.objectives])

The agent currently supports the following objective types: SquaredDeviation, SquaredOverdosing, SquaredUnderdosing.

End-to-end example#

The script examples/utils_ai_agents.py shows a complete workflow:

  1. Load the TG119 phantom.

  2. Use generate_beam_angles to obtain gantry angles for a prostate plan.

  3. Use generate_voi_objectives to populate VOI objectives.

  4. Run the standard dose calculation and fluence optimization with the AI-generated settings.

  5. Visualize the result.

from dotenv import load_dotenv
from pyRadPlan import (
    IonPlan, load_tg119, ai_agents,
    generate_stf, calc_dose_influence, fluence_optimization,
    plot_slice, DVHCollection,
)

load_dotenv()   # load API key from .env

ct, cst = load_tg119()
pln = IonPlan(radiation_mode="protons", machine="Generic",
              num_of_fractions=30, prescribed_dose=60)
pln.prop_opt = {"solver": "scipy"}
pln.prop_dose_calc = {"dose_grid": ct.grid}

pln = ai_agents.generate_beam_angles(pln, treatment_site="prostate")
cst = ai_agents.generate_voi_objectives(pln, cst, treatment_site="prostate")

stf = generate_stf(ct, cst, pln)
dij = calc_dose_influence(ct, cst, stf, pln)
fluence = fluence_optimization(ct, cst, stf, dij, pln)

result = dij.compute_result_ct_grid(fluence)
dvhs = DVHCollection.from_structure_set(cst, result["physical_dose"])
plot_slice(ct=ct, cst=cst, overlay=result["physical_dose"])

Settings reference#

API reference#