Run a MedGemma agent with prompt-based tool calling. The agent can use Python tools (BMI calculation, drug interactions, lab value interpretation, etc.) through the tool_code/tool_output protocol. Use when you need medical AI that can compute, look up data, or call functions.
Run MedGemma as a tool-calling agent that can execute Python functions to answer medical questions requiring computation, data lookup, or multi-step reasoning.
Run the agent harness script, passing the user's query and optionally specifying tools:
python3 "$(dirname "$0")/medgemma_agent_harness.py" --query "$ARGUMENTS"
If the harness script does not exist yet or needs custom tools, create/edit it based on the template below.
MedGemma does NOT support native Ollama tool calling. Instead, use prompt-based tool calling:
```tool_code blocks when it wants to call a function```tool_output blocksWhen creating or modifying the agent harness, follow this pattern:
import json, re, io, inspect, requests, sys
from contextlib import redirect_stdout
from typing import Callable
OLLAMA_URL = "http://localhost:11434"
MODEL = "hf.co/unsloth/medgemma-27b-it-GGUF:Q4_K_M"
# ── Tool Registry ──
TOOL_REGISTRY: dict[str, Callable] = {}
def register_tool(func: Callable) -> Callable:
TOOL_REGISTRY[func.__name__] = func
return func
def get_tool_descriptions() -> str:
lines = []
for name, func in TOOL_REGISTRY.items():
source = inspect.getsource(func)
source_lines = [l for l in source.split('\n') if '@register_tool' not in l]
lines.append('\n'.join(source_lines))
return '\n\n'.join(lines)
# ── Define Your Tools ──
@register_tool
def calculate_bmi(weight_kg: float, height_m: float) -> str:
"""Calculate Body Mass Index. Args: weight_kg, height_m"""
bmi = weight_kg / (height_m ** 2)
cat = "Underweight" if bmi < 18.5 else "Normal" if bmi < 25 else "Overweight" if bmi < 30 else "Obese"
return f"BMI: {bmi:.1f} ({cat})"
@register_tool
def drug_interaction_check(drug_a: str, drug_b: str) -> str:
"""Check for known interactions between two drugs. Args: drug_a, drug_b"""
interactions = {
("warfarin", "aspirin"): "HIGH RISK: Increased bleeding risk.",
("metformin", "contrast dye"): "MODERATE RISK: Hold metformin 48h before/after contrast.",
("lisinopril", "potassium"): "MODERATE RISK: Risk of hyperkalemia.",
}
key = (drug_a.lower(), drug_b.lower())
rev = (drug_b.lower(), drug_a.lower())
return interactions.get(key) or interactions.get(rev) or f"No known major interactions between {drug_a} and {drug_b}."
# ── System Prompt ──
def build_system_prompt() -> str:
return f"""You are a helpful medical assistant with access to tools.
At each turn, if you decide to invoke any of the function(s), it should be wrapped with ```tool_code```.
The python methods described below are imported and available. You can only use defined methods.
The response to a method will be wrapped in ```tool_output``` — use it to generate a helpful response.
The following Python methods are available:
```python
{get_tool_descriptions()}
```"""
# ── Tool Executor ──
def extract_and_execute(text: str) -> str | None:
match = re.search(r"```tool_code\s*(.*?)\s*```", text, re.DOTALL)
if not match:
return None
code = match.group(1).strip()
namespace = dict(TOOL_REGISTRY)
try:
f = io.StringIO()
with redirect_stdout(f):
result = eval(code, namespace)
output = f.getvalue()
return f"```tool_output\n{result if output == '' else output}\n```"
except Exception as e:
return f"```tool_output\nError: {e}\n```"
# ── Agent Loop ──
def run_agent(query: str, max_rounds: int = 3) -> str:
messages = [
{"role": "system", "content": build_system_prompt()},
{"role": "user", "content": query},
]
for _ in range(max_rounds):
resp = requests.post(f"{OLLAMA_URL}/api/chat",
json={"model": MODEL, "stream": False, "messages": messages})
resp.raise_for_status()
content = resp.json()["message"]["content"]
tool_output = extract_and_execute(content)
if tool_output is None:
return content
messages.append({"role": "assistant", "content": content})
messages.append({"role": "user", "content": f"Results:\n{tool_output}"})
return content
if __name__ == "__main__":
query = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else sys.argv[0]
print(run_agent(query))
To add a new tool, define a function with the @register_tool decorator:
@register_tool
def calculate_egfr(creatinine: float, age: int, is_female: bool) -> str:
"""Estimate glomerular filtration rate using CKD-EPI equation.
Args: creatinine (mg/dL), age (years), is_female (bool)"""
# CKD-EPI 2021 equation (race-free)
k = 0.7 if is_female else 0.9
alpha = -0.241 if is_female else -0.302
scr_k = creatinine / k
egfr = 142 * (min(scr_k, 1) ** alpha) * (max(scr_k, 1) ** -1.200) * (0.9938 ** age)
if is_female:
egfr *= 1.012
stage = "Normal" if egfr >= 90 else "Mild" if egfr >= 60 else "Moderate" if egfr >= 30 else "Severe" if egfr >= 15 else "Kidney Failure"
return f"eGFR: {egfr:.1f} mL/min/1.73m2 ({stage})"
The model will automatically see the new tool in the system prompt and can call it.
To use this pattern with the OpenAI Agents SDK (v0.8.1+):
from agents import Agent, Runner, function_tool
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
model = OpenAIChatCompletionsModel(model="hf.co/unsloth/medgemma-27b-it-GGUF:Q4_K_M", openai_client=client)
# Note: MedGemma does NOT support native tool calling via the SDK.
# The SDK passes tools to the API which returns HTTP 400.
# For SDK integration, use a basic Agent without tools and handle
# tool_code/tool_output parsing in a wrapper around Runner.run().
eval() — only register trusted functions