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

# A Python function

> Deploy any Python agent as a plain function that returns or yields its reply, with its own tool calls and steps if you want them.

## What is supported

Any Python 3.11 or 3.12 code. A function is what Onecortex calls when the object is not a framework it recognises, so it is also how you deploy an agent on a framework not listed here, such as AutoGen or Pydantic AI: wrap its run call.

## A complete agent

```yaml agent.yml theme={null}
apiVersion: v1
runtime: python3.12
entrypoint: agent.py:agent
dependencies: requirements.txt
```

```text requirements.txt theme={null}
# No dependencies. List any you add here.
```

```python agent.py theme={null}
def agent(prompt: str) -> str:
    return f"You said: {prompt}"
```

Call any model you like from inside the function, with its key added as a secret on the agent's **Config** tab. See [Configuration and secrets](/build/configuration).

<Card title="A complete A Python function example" icon="github" href="https://github.com/onecortex-io/examples/tree/main/echo">
  `echo`: The quickstart's agent: one tool call of its own, then a reply, with no dependencies.
</Card>

## The prompt and the reply

The function receives the prompt. What it may be:

| Shape | Reply |
| - | - |
| `def agent(prompt)` returning a string | The string, once |
| A generator, `yield` strings | Each string as it is yielded |
| `async def`, returning a string | The string, once |
| An async generator | Each string as it is yielded |
| A callable class instance, or an object with `invoke(prompt)` | As above, by what it returns |

Declare a second positional parameter to receive the session ID: `def agent(prompt, session_id)`. Declare a parameter named `request` to receive the whole request: `{"prompt", "sessionId", "params", "surface", "messages"}`.

## Events it reports

| Events | From |
| - | - |
| `text` | Each string you return or yield |
| Any event | Yield a dict with a `type`, and it is sent as that event, checked first |
| `done` or `error` | The end of the run: always exactly one |

See [Streaming and events](/build/streaming).

## The caller's fields

In `request["params"]`, for a function that declares `request`:

```python theme={null}
def agent(prompt: str, request: dict) -> str:
    model = request["params"].get("model", "gpt-5")
    return call_my_model(model, prompt)
```

## Report tool calls and steps

Yield event dicts alongside your text, and they reach every caller as real events:

```python theme={null}
import json


def agent(prompt: str):
    yield {"type": "tool_call_start", "id": "call_1", "name": "word_count"}
    yield {"type": "tool_call_args", "id": "call_1", "delta": json.dumps({"text": prompt})}
    yield {"type": "tool_call_end", "id": "call_1"}
    yield {"type": "tool_result", "id": "call_1", "output": str(len(prompt.split()))}
    yield f"You said: {prompt}"
```

Each dict needs the fields of its [event type](/build/streaming). `v` is filled in for you, and Onecortex adds the closing `done`. A dict that is not a valid event ends the run with `Your agent yielded an event Onecortex cannot read:` and the field that is wrong.

## Known limits

* A dict with a `type` key is always read as an event. Yield a string, or JSON text, to send data that has a `type` field.

## Troubleshooting

| You see | Do this |
| - | - |
| `Your agent yielded an event Onecortex cannot read: <problem>.` | Fix the field it names. Every event's fields are on [Streaming and events](/build/streaming). |

More on [Troubleshooting](/production/troubleshooting) and [Errors](/production/errors).
