> ## Documentation Index
> Fetch the complete documentation index at: https://docs.decimal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Gemini API

> Give an agent you build on the google-genai SDK its DecimalAI prompt and skills with agent_config(), one run per user message, with automatic function calling or a loop of your own, and trace each run.

`agent_config()` reads an agent's prompt and skills from DecimalAI and hands them over per user message, as a run: `run.system` (the saved prompt, then the agent's skill list) for `system_instruction`, `run.message` (the user turn) and `load_skill`, as a Python function for automatic function calling (`run.tools`) or as a function declaration for a loop of your own (`run.tool_params`). Each run is one trace, filed under the DecimalAI agent. Nothing in the google-genai SDK is patched. `decimalai.google_genai` needs SDK 0.25 or later; through 0.24, code that called Gemini itself used [`decimalai.agent_config()`](/sdk/python/frameworks#your-own-model-calls).

Building on Google ADK (`LlmAgent`) instead? See [Google ADK](/sdk/python/frameworks/adk.md).

## Install

```bash theme={null}
pip install "decimalai[google-genai]"
```

The extra installs `google-genai` and `openinference-instrumentation-google-genai`, which `agent_config()` uses to trace its runs. `genai.Client()` reads `GOOGLE_API_KEY` (or `GEMINI_API_KEY`).

## Use

```python theme={null}
from google import genai
from google.genai import types
from decimalai.google_genai import agent_config

client = genai.Client()
support = agent_config("support-agent", fallback="You are a support agent.")


def lookup_order(order_id: str) -> str:
    """Look up the status of an order."""
    return f"Order {order_id} shipped yesterday."


question = "Where is order A1001?"
with support.run(question) as run:      # one user message = one run = one trace
    chat = client.chats.create(model="gemini-3.5-flash", config=types.GenerateContentConfig(
        system_instruction=run.system,     # the saved prompt, then the skill list
        tools=[lookup_order, *run.tools],  # your functions, then load_skill
    ))
    answer = chat.send_message(run.message)
print(answer.text)
```

* **`agent_config("support-agent")`** reads the agent's saved prompt when it is called. It initializes the SDK from `DECIMAL_API_KEY` when `decimalai.init()` has not been called, and a name your workspace has no agent by is created, so a misspelled name makes a new agent rather than an error. `fallback=` is what runs when the agent has no saved prompt yet, when no DecimalAI key is set, or when DecimalAI cannot be reached at startup. With no key at all, the agent runs [without a DecimalAI key](/sdk/python/frameworks.md#without-a-decimalai-key): the skills your project lists in `.decimal/skills.toml`, with nothing sent.
* **`support.run(question)`** routes the agent's skill list on `question` and opens the run's trace. It calls no model: make the model calls inside the `with` block.
* **`run.system`** is the system instruction: the saved prompt, then the agent's skill list with one sentence telling the model to load a skill before using it.
* **`run.message`** is the user turn, as text. Send it instead of the bare question: in a [chat](#chats) it can carry more.
* **`run.tools`** holds `load_skill` as a Python function, which automatic function calling declares and calls like your own. The model calls it with a skill's name and gets the skill's full text back, which counts as *delivered* and *activated* on the [usage ladder](/guides/skills.md#the-usage-ladder). Its parameter is a plain string: when the agent's whole list fits, a name not on it is answered with the list and loads nothing.

google-genai recommends automatic function calling in a chat, as above, rather than in `generate_content`.

### Your own loop

Automatic function calling raises `KeyError` when the model asks for a function you did not pass, for example one your saved prompt names. A loop of your own can answer that call instead, and `decimalai init --framework google-genai` writes one. It takes `run.tool_params` (the same `load_skill` as a function declaration, whose parameter is limited to the listed skills) and answers each call with `run.tool_result(call)`, which returns the `function_response` part for `load_skill` and `None` for any other function:

```python theme={null}
with support.run(question) as run:
    config = types.GenerateContentConfig(system_instruction=run.system,
                                         tools=[*TOOLS, *run.tool_params])
    contents = [{"role": "user", "parts": [{"text": run.message}]}]
    while True:
        response = client.models.generate_content(model="gemini-3.5-flash",
                                                  contents=contents, config=config)
        if not response.function_calls:
            break
        contents.append(response.candidates[0].content)
        contents.append({"role": "user", "parts": [
            run.tool_result(call) or answer_my_function(call) for call in response.function_calls]})
print(response.text)
```

A refusal (an unknown name, an outage) comes back under the part's `error` key, as text the model can read. `run.load_skill(name)` returns the text alone, for a dispatcher of your own.

### Async

Open the run with `async with support.run(question) as run:` for `client.aio`: `run.tools` then holds an async function, and `await run.atool_result(call)` (`await run.aload_skill(name)` for the text alone) keeps a load off the event loop.

## Chats

A chat keeps one conversation, and a conversation's system instruction and `load_skill` stay what its first message got. A newly saved prompt or a newly assigned skill reaches the next conversation, not an open one.

```python theme={null}
conversation = support.conversation(variables={"customer": {"name": "Sam"}})   # one per chat

with conversation.run(question) as run:
    contents.append({"role": "user", "parts": [{"text": run.message}]})
    ...                                          # the loop above, on this chat's contents
saved = conversation.state                      # JSON: support.conversation(state=saved) resumes it
```

* **`run.message`** is the user turn to send: the question, plus, for an agent whose skill list is cut to fit, the skills routed for this message that the chat has not shown yet.
* **`conversation.state`** is the conversation as JSON-ready data. A server that keeps no chat in memory stores it beside the chat's history, and `support.conversation(state=saved)` resumes it in another process.
* **Variables** are the conversation's: they are filled once, at its first message.

## Change the prompt without redeploying

`agent_config()` keeps a local copy of the prompt and re-reads it in the background at most once a minute, so a version saved in the dashboard reaches the next run within a minute, with no redeploy. A saved prompt's variables, written `{{name}}`, are filled from `support.run(question, variables=...)`; a variable with no value stops the run before any model call, with a `MissingPromptVariable` error that names it.

## When an agent's skills are switched off

* **The tool's kill switch.** With `DECIMALAI_LOAD_SKILL_TOOL=0` or `decimalai.init(load_skill_tool=False)`, every run is routed and the text of the skills picked for it goes into `run.system` instead, and `run.tools` and `run.tool_params` are empty.
* **`decimalai.init(enabled=False)`** after `agent_config()` stops routing and skills for that config; the prompt it read keeps running.
* **`on_unavailable="fail"`.** By default a run whose skill list never came back answers without skills, and its trace says so. With `agent_config(..., on_unavailable="fail")` opening the run raises instead.

## Tracing

`agent_config()` turns on Gemini tracing (OpenInference's instrumentor, which wraps the SDK's classes for the process) on the same pipeline as `decimalai.init(google=True)`, and sends only its runs: each run is one trace, filed under `support.name`, with its model calls, the prompt version, the variable names it filled, and the skills offered, loaded and delivered. A Gemini call made outside a run is held back, and so are the app's own spans; `decimalai.init(google=True)` sends every Gemini call. Set up your own OpenTelemetry tracer provider, and `decimalai.init(otel=True)`, before the first `agent_config()`. `agent_config(..., on_trace=fn)` calls `fn` once per run with a copy of the finished trace.

> [!TIP]
>
> **Checkpoint:** run the agent from [Use](#use) once with a question that matches one of your skills, then `print(support)`. It names the prompt version the agent ran and the skills on its list, for example `GeminiAgentConfig(name='support-agent', prompt_version=3, skills=['refund-policy'])`, and the run appears on the [Traces page](https://app.decimal.ai/traces) under that name. No trace? Check that the model calls were made inside the `with` block.

## What's next

> **[OpenAI SDK](/sdk/python/frameworks/openai.md)**
>
> The same run for your own OpenAI Chat Completions or Responses API loop.

> **[Google ADK](/sdk/python/frameworks/adk.md)**
>
> The same prompt and skills for an agent built on Google ADK.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.