# AI Agent

Build an assistant that talks to your customers in your own words. You configure an agent once — the model, the instructions, the knowledge it may consult, the voice it speaks with — and then hold conversations with it over three small endpoints.

The split is deliberate. The **agent** is configuration and never changes during a conversation. The **chat** is one conversation thread and holds the history. The **message** is a single turn: you post what the user said and the same call returns what the agent answered.

Nothing here is a black box you have to trust blindly. Point the agent at an [AI Knowledge Base](https://docs.starkinfra.com/get-started/ai-knowledge-base.md) and it answers from your own documentation. Declare a `metadataSchema` and every reply also comes back as structured data your backend can route on. Give it an [AI Voice](https://docs.starkinfra.com/get-started/ai-voice.md) and every reply comes back with a spoken version ready to synthesize.

NOTE: Read [Core Concepts](https://docs.starkinfra.com/get-started/core-concepts.md) before continuing this guide.

**RESOURCE SUMMARY**

**AI Agent**The assistant's configuration: model, instructions, knowledge bases and voice.**AI Chat**One conversation thread with an agent. Holds the history the agent reads.**AI Message**One turn of a chat. You post the user's text and get the agent's answer back.

## Available Languages

Code samples on this page are in Python. The same content is available with samples in:

- [Python](https://docs.starkinfra.com/get-started/ai-agent-python.md)
- [Node.js](https://docs.starkinfra.com/get-started/ai-agent-node.md)
- [PHP](https://docs.starkinfra.com/get-started/ai-agent-php.md)
- [Java](https://docs.starkinfra.com/get-started/ai-agent-java.md)
- [Ruby](https://docs.starkinfra.com/get-started/ai-agent-ruby.md)
- [Elixir](https://docs.starkinfra.com/get-started/ai-agent-elixir.md)
- [.NET](https://docs.starkinfra.com/get-started/ai-agent-dotnet.md)
- [Go](https://docs.starkinfra.com/get-started/ai-agent-go.md)
- [Clojure](https://docs.starkinfra.com/get-started/ai-agent-clojure.md)
- [cURL](https://docs.starkinfra.com/get-started/ai-agent-curl.md)

## Setup

For each environment (Sandbox or Production):

1. Create a workspace at Stark Infra and generate your ECDSA keys.

2. Get in touch with your account manager to enable the AI products on your workspace.

3. Optional, but this is where the product earns its keep: create an [AI Knowledge Base](https://docs.starkinfra.com/get-started/ai-knowledge-base.md) over your documentation, and an [AI Voice](https://docs.starkinfra.com/get-started/ai-voice.md) if the agent should speak.

## Typical flow

**1.** Create an AI Agent with the model and the instructions that define its persona. Save the returned `id` — you will reference it as `agentId`.

**2.** Open an AI Chat pointing at that agent, once per conversation. Save the returned `id` — you will reference it as `chatId`.

**3.** Post the user's text as an AI Message. The response carries two messages: the one you sent and the agent's reply.

**4.** Keep posting to the same chat for as long as the conversation lasts. The agent reads the last 20 messages of the thread, so context is carried for you.

**5.** Read the reply's `text` to show it, its `metadata` to route it, and its `speech` to synthesize it.

## One reply, three payloads

Every answer the agent produces is returned in three parallel forms, and each one has a job.

**text** — the full reply, in Markdown. This is what you render in a chat window.

**speech** — the same answer rewritten to be heard instead of read: plain conversational text, no Markdown, no URLs, no code, at most two sentences. It is only filled when the agent has a `voiceId`; feed it to `POST /v2/ai-speech` to get audio.

**metadata** — the structured fields you declared in the agent's `metadataSchema`, filled from the conversation. This is how you turn a conversation into a decision your backend can act on: an intent to route by, a sentiment to escalate on, an order number to look up.

## Use cases

**Support triage:** answer the common questions from your documentation and hand the rest to a human, using a `metadataSchema` with an `isEscalation` flag as the switch.

**Onboarding assistant:** walk a new customer through your integration, answering from the same documentation your developers read.

**Structured intake:** let the customer describe the problem in their own words and have the agent extract the fields your ticketing system needs.

**Voice channel:** transcribe the caller, post the text, and read the reply's `speech` back in your own cloned voice.

## AI Agent Overview

Here we show you how to configure an agent and how to sharpen it afterwards. An agent is cheap to change: chats already pointing at it pick up the new configuration on their next message.

### Creating an AI Agent

`POST /v2/ai-agent`

Only `name` and `model` are mandatory. Pick `bender-1.0` for everyday conversations and `prime-1.0` when the agent has to reason harder — you can switch later, or per message.

The `systemPrompt` is where the agent becomes yours. Write it as instructions to a new colleague: who they are, who they are talking to, what they must never promise. It cannot override the platform's own rules, so an agent cannot be talked into leaking its prompt or breaking its output shape.

**Request**

```python
import starkinfra

agent = starkinfra.aiagent.create(
    starkinfra.AiAgent(
        name="Support Assistant",
        model="bender-1.0",
        system_prompt="You are the support assistant of an online store. Answer in the language the customer wrote in. Never invent policies, prices or delivery dates. When you do not know something, say so and offer to open a ticket."
    )
)

print(agent)
```

**Response**

```python
AiAgent(
    created=2022-01-01 00:00:00,
    id=4242424242424242,
    knowledge_base_ids=[],
    knowledge_bases=None,
    metadata_schema=None,
    model=bender-1.0,
    name=Support Assistant,
    system_prompt=You are the support assistant of an online store. Answer in the language the customer wrote in. Never invent policies, prices or delivery dates. When you do not know something, say so and offer to open a ticket.,
    updated=2022-01-01 00:00:00,
    voice_id=
)
```

### Attaching knowledge and a voice

`PATCH /v2/ai-agent/:id`

Send `knowledgeBaseIds` to let the agent answer from your own documentation. Before every reply it retrieves the passages most relevant to the question and reads only those, which is what keeps answers grounded instead of invented.

Send `voiceId` to make the agent speak. From then on every reply also carries a `speech` string, already stripped of Markdown and URLs, ready for `POST /v2/ai-speech`.

Both lists replace the current ones, so send the full set you want the agent to end up with.

**Send `knowledgeBaseIds` on every update to this agent, even when you are only changing something else.** Leaving it out of the body does not preserve the current list — it clears it.

**Request**

```python
import starkinfra

agent = starkinfra.aiagent.update(
    "4242424242424242",
    knowledge_base_ids=["6767676767676767"],
    voice_id="5656565656565656"
)

print(agent)
```

**Response**

```python
AiAgent(
    created=2022-01-01 00:00:00,
    id=4242424242424242,
    knowledge_base_ids=['6767676767676767'],
    knowledge_bases=None,
    metadata_schema=None,
    model=bender-1.0,
    name=Support Assistant,
    system_prompt=You are the support assistant of an online store...,
    updated=2022-01-02 00:00:00,
    voice_id=5656565656565656
)
```

### Extracting structured data

`PATCH /v2/ai-agent/:id`

A `metadataSchema` turns every reply into a record your backend can act on. Declare the fields you need and the agent fills them from the conversation, alongside its written answer.

Each field takes a `type` — `string`, `integer`, `number`, `boolean` or `array` — and an optional `description` telling the agent what to look for. A `string` field can also declare an `enum`, which is the reliable way to get a value you can switch on.

Keep the schema small: at most 20 fields and 8192 bytes. Field descriptions describe data to extract, never behavior — instructions belong in the `systemPrompt`.

**Request**

```python
import starkinfra

agent = starkinfra.aiagent.update(
    "4242424242424242",
    metadata_schema={
        "intent": {
            "type": "string",
            "enum": ["order", "refund", "shipping", "other"],
            "description": "What the customer is trying to do."
        },
        "orderNumber": {
            "type": "string",
            "description": "Order number mentioned by the customer, or an empty string when none was."
        },
        "isEscalation": {
            "type": "boolean",
            "description": "Whether the conversation should be handed to a human."
        }
    }
)

print(agent)
```

**Response**

```python
AiAgent(
    created=2022-01-01 00:00:00,
    id=4242424242424242,
    knowledge_base_ids=['6767676767676767'],
    knowledge_bases=None,
    metadata_schema={'intent': {'description': 'What the customer is trying to do.', 'enum': ['order', 'refund', 'shipping', 'other'], 'type': 'string'}, 'isEscalation': {'description': 'Whether the conversation should be handed to a human.', 'type': 'boolean'}, 'orderNumber': {'description': 'Order number mentioned by the customer, or an empty string when none was.', 'type': 'string'}},
    model=bender-1.0,
    name=Support Assistant,
    system_prompt=You are the support assistant of an online store...,
    updated=2022-01-03 00:00:00,
    voice_id=5656565656565656
)
```

## AI Chat Overview

A chat is one conversation thread. Open one per conversation and keep it for as long as the conversation lasts — that is what gives the agent continuity, and what gives you a clean slate when you want one.

### Opening a chat

`POST /v2/ai-chat`

Point the chat at an agent and save the returned `id`. Every message you post afterwards references it as `chatId`.

The `title` is optional. Leave it out and the first message posted to the chat generates one from what the user wrote — send `expand=chatName` on that first message to read it back.

**Request**

```python
import starkinfra

chat = starkinfra.aichat.create(
    starkinfra.AiChat(
        agent_id="4242424242424242"
    )
)

print(chat)
```

**Response**

```python
AiChat(
    agent_id=4242424242424242,
    agent_name=None,
    id=3131313131313131,
    title=None,
    updated=2022-01-01 00:00:00
)
```

### Handing a chat to another agent

`PATCH /v2/ai-chat/:id`

Send a new `agentId` to change who answers from now on. The history stays, so the incoming agent reads everything that was said before.

This is how you escalate without losing the thread: a triage agent takes the first questions, and a specialist agent with a different knowledge base and a stricter prompt takes over when the metadata says so.

**Request**

```python
import starkinfra

chat = starkinfra.aichat.update(
    "3131313131313131",
    agent_id="5050505050505050"
)

print(chat)
```

**Response**

```python
AiChat(
    agent_id=5050505050505050,
    agent_name=None,
    id=3131313131313131,
    title=Order 1234 not delivered,
    updated=2022-01-01 00:05:00
)
```

## AI Message Overview

Posting a message is the whole conversation loop: one call sends the user's text and returns the agent's answer. There is no polling and no webhook — the call runs the model and waits.

### Sending a message

`POST /v2/ai-message`

The `messages` array holds two entries in order: the message you sent and the reply the agent produced. Read the reply's `text` to show it, `metadata` to route it and `speech` to synthesize it.

Send `model` to override the agent's model for this turn only — a cheap way to escalate one hard question to `prime-1.0` without reconfiguring the agent.

This call is slower than the rest of the API because it runs the model and, when the agent has knowledge bases, a retrieval pass before it. Size your client timeouts accordingly.

**Request**

```python
import starkinfra

messages = starkinfra.aimessage.create(
    starkinfra.AiMessage(
        chat_id="3131313131313131",
        text="My order 1234 was supposed to arrive yesterday and it did not. What can I do?"
    ),
    expand=["chat_name"]
)

for message in messages:
    print(message)
```

**Response**

```python
AiMessage(
    chat_id=3131313131313131,
    chat_name=Order 1234 not delivered,
    created=2022-01-01 00:00:00,
    id=2020202020202020,
    metadata={},
    model=bender-1.0,
    sender=user,
    speech=My order 1234 was supposed to arrive yesterday and it did not. What can I do?,
    text=My order 1234 was supposed to arrive yesterday and it did not. What can I do?
)
AiMessage(
    chat_id=3131313131313131,
    chat_name=Order 1234 not delivered,
    created=2022-01-01 00:00:00.001000,
    id=2121212121212121,
    metadata={'intent': 'shipping', 'isEscalation': False, 'orderNumber': '1234'},
    model=bender-1.0,
    sender=system,
    speech=I am sorry about the delay. I can open a delivery check for order 1234 right now.,
    text=I am sorry about the delay on order **1234**.

    Here is what we can do:

    - Open a delivery check with the carrier, which takes up to 2 business days.
    - Reship the order at no cost if the carrier confirms the package was lost.

    Would you like me to open the delivery check now?
)
```

### Reading the history

`GET /v2/ai-message`

Get the messages of a chat, newest first, in chunks of at most 100. Use it to render a conversation your customer is coming back to.

You do not need this call to keep context: the agent already reads the last 20 messages of the thread on every turn.

**Request**

```python
import starkinfra

messages = starkinfra.aimessage.query(
    "3131313131313131",
    limit=20
)

for message in messages:
    print(message)
```

**Response**

```python
AiMessage(
    chat_id=3131313131313131,
    chat_name=None,
    created=2022-01-01 00:00:00.001000,
    id=2121212121212121,
    metadata={'intent': 'shipping', 'isEscalation': False, 'orderNumber': '1234'},
    model=bender-1.0,
    sender=system,
    speech=I am sorry about the delay. I can open a delivery check for order 1234 right now.,
    text=I am sorry about the delay on order **1234**.
)
AiMessage(
    chat_id=3131313131313131,
    chat_name=None,
    created=2022-01-01 00:00:00,
    id=2020202020202020,
    metadata={},
    model=bender-1.0,
    sender=user,
    speech=My order 1234 was supposed to arrive yesterday and it did not. What can I do?,
    text=My order 1234 was supposed to arrive yesterday and it did not. What can I do?
)
```
