# Chat

> Pošlete zprávu modelu nebo asistentovi a získejte odpověď, vcelku nebo jako stream.

Chat pokrývá jeden endpoint: pošlete zprávu uživatele, AYETO spustí model nebo
asistenta (včetně AI nástrojů, které používá, jako je vyhledávání na webu nebo
generování obrázků), uloží obě zprávy do konverzace a vrátí odpověď. Odpověď přijde
jako jeden objekt zprávy, nebo jako [stream](streaming.md), pokud o něj požádáte.

## Odeslání zprávy

| | |
|---|---|
| Endpoint | `POST /api/v3/chat` |
| Rozsah | `ayeto.chat` |
| Limit požadavků | [default](conventions.md#limity-pozadavku) |
| Odpověď | [Zpráva](#odpoved), nebo [stream](streaming.md), když je `stream` `true` |

### Konverzace

Každá zpráva patří do konverzace určené pomocí `conversation_id`. **Id vytváříte vy**:
vygenerujte na své straně UUID (verze 4) a pošlete ho s první zprávou. Pokud
konverzace s tímto id ještě neexistuje, AYETO ji vytvoří; pokud existuje a patří
uživateli klíče, zpráva se do ní přidá a model vidí předchozí zprávy.

Odpověď id konverzace neobsahuje. Pokud `conversation_id` vynecháte, pro tuto jednu
zprávu se vytvoří nová konverzace a nemáte jak v ní pokračovat, proto vždy posílejte
id, které si uchováte.

Konverzace je vázaná na to, s čím byla vytvořena:

- **Asistent.** Konverzaci vytvořenou s `assistant_id` odpovídá po celou dobu její
  existence tento asistent (jeho instrukce, nástroje, znalosti a model). Pozdější
  odeslání jiného `assistant_id` nebo `model` ji na jiného asistenta nepřepne.
  Konverzace vytvořená s `model` zůstává prostou konverzací s modelem.
- **Organizace.** Konverzace vytvořená v organizaci v ní zůstává. Navazující zpráva,
  která žádnou organizaci neuvádí, běží v organizaci konverzace; uvedení jiné
  organizace se odmítne (viz [Organizace a kredity](#organizace-a-kredity)).

Pro výpis, čtení nebo mazání konverzací použijte API [Konverzace](conversations.md).

### Hlavičky požadavku

| Hlavička | Povinná | Popis |
|---|---|---|
| `uni-api-key` | ano | Váš [API klíč](authentication.md). |
| `Content-Type` | ano | `application/json` |
| `language` | ne | Kód jazyka uživatele, například `EN`, `CZ` nebo `FR`. Model se jazyk dozví s každou zprávou, takže v něm může odpovědět. Výchozí `EN`. |

### Požadavek

| Pole | Typ | Povinné | Popis |
|---|---|---|---|
| `message` | řetězec | ano | Zpráva uživatele, Markdown nebo prostý text. Zpráva může aktivovat dovednost asistenta pomocí `/skill-slug` (viz níže). |
| `conversation_id` | UUID | ne | Konverzace, do které se zpráva přidá; když neexistuje, vytvoří se. Vygenerujte ho sami, viz [Konverzace](#konverzace). Výchozí: nové náhodné id. |
| `assistant_id` | UUID | jedno z `assistant_id`, `model` | Asistent, se kterým mluvíte. Musí to být váš vlastní asistent nebo asistent sdílený s uživatelem klíče. Když je nastaveno, `model` se ignoruje a odpovídá model asistenta. |
| `model` | řetězec | jedno z `assistant_id`, `model` | Id modelu, se kterým mluvíte přímo, bez asistenta, například `gpt-5-mini`. Id uvádí [Modely a nástroje](models-and-tools.md). Povinné, když není nastaveno `assistant_id`. |
| `organization_id` | UUID | ne | Spustit zprávu v této organizaci: platí ji kredity organizace a její soubory se započítávají do úložiště organizace. Uživatel klíče musí být administrátor nebo člen (ne host) organizace. Výchozí: organizace asistenta, pokud asistent nějaké patří, jinak organizace konverzace, pokud se pokračuje v konverzaci vytvořené v organizaci, jinak žádná (osobní účet). |
| `stream` | boolean | ne | `true` vrací odpověď jako [stream](streaming.md) průběžně během generování. Výchozí `false`. |
| `runner_version` | řetězec | ne | Formát streamu: `"1"` streamuje prostý text, `"2"` streamuje strukturované události (text, uvažování, činnost nástrojů, stav). Použije se jen, když je `stream` `true`. Výchozí `"1"`. Viz [Streamování](streaming.md#formaty-streamu-chatu). |
| `attachments` | pole [Příloh](#prilohy) | ne | Soubory odeslané se zprávou (dokumenty, obrázky). |
| `max_tokens` | celé číslo | ne | Horní limit tokenů, které model smí vygenerovat v jednom volání modelu. Musí být mezi 1 a vlastním maximem modelu. Výchozí: výchozí hodnota modelu. |
| `dynamic_tools` | boolean | ne | Jen konverzace s modelem: dát modelu standardní sadu AI nástrojů (vyhledávání na webu, generování obrázků, tvorba dokumentů a další). U asistentů, jejichž nástroje se nastavují na asistentovi, se ignoruje. Výchozí `false`. |
| `use_vision` | boolean | ne | Nechat model prohlížet přiložené obrázky (modely s viděním). S `false` se model o přiložených obrázcích dozví, ale nemůže se na ně podívat. Výchozí `true`. |
| `relevant_history` | boolean | ne | Poslat modelu jen tu část historie konverzace, která je pro novou zprávu relevantní, vybranou dalším voláním modelu. Platí jen pro modely, které to podporují, a konverzace s aspoň 3 zprávami; u reasoning modelů, které používají nástroje, se ignoruje. Výchozí `false`. |
| `remove_tool_calls` | boolean | ne | Odstranit z `content` odpovědi [bloky volání nástrojů](#volani-nastroju-v-odpovedi), takže zůstane jen vlastní text modelu. Na streamy se nepoužije. Výchozí `false`. |
| `follow_options` | boolean | ne | Požádat model, aby svou odpověď, když se to hodí, zakončil ohraničeným blokem kódu označeným `ayeto-follow-options`: objekt JSON s volitelnou `question`, volitelným `multi_select` a seznamem `options` (`label`, `prompt`, volitelný `description`), ze kterých si uživatel může vybrat další zprávu. Užitečné jen, pokud ho váš klient vykreslí. Výchozí `false`. |

**Dovednosti asistenta.** Slovo ve tvaru `/slug` (malá písmena, číslice a pomlčky,
oddělené mezerami) v `message` aktivuje dovednost asistenta s tímto slugem, pokud ji
uživatel klíče vlastní nebo je s ním sdílená a patří do stejného kontextu organizace
jako požadavek. Instrukce a nástroje dovednosti platí pro tuto zprávu. Neznámé slugy
zůstávají prostým textem.

### Přílohy

Každá příloha je soubor zakódovaný do těla požadavku.

| Pole | Typ | Povinné | Popis |
|---|---|---|---|
| `filename` | řetězec | ano | Název souboru s příponou, například `report.pdf`. |
| `data` | řetězec | ano | Obsah zakódovaný v base64 jako data URI, například `data:application/pdf;base64,JVBERi0xLjcK...`. Přijímá se i prostý base64 bez prefixu `data:...;base64,`. Bílé znaky, například zalomení řádků, se v textu base64 ignorují; jakýkoli jiný znak mimo abecedu base64 nebo chybné zarovnání (padding) požadavek odmítne s `422`. |
| `mime_type` | řetězec | ano | Typ MIME, například `application/pdf` nebo `image/png`. U obrázků se typ zjišťuje z obsahu (JPEG, PNG, WebP) a pokud se liší, opraví se. |

Uložená velikost se bere z dekódovaného obsahu. Pole `size`, které posílají starší
klienti, se ignoruje.

Přílohy se ukládají jako soubory uživatele klíče (nebo organizace, viz
`organization_id`) a uchovávají se s konverzací; smazáním konverzace se smažou.

- **Limit velikosti.** Každý soubor je omezen limitem pro nahrávání daného nasazení,
  ve výchozím stavu 50 MB. Větší soubor se odmítne s `422 file is too large`.
- **Kvóta úložiště.** Přílohy se započítávají do kvóty úložiště uživatele nebo
  organizace. Když je plná, požadavek selže s `422 storage quota exceeded` nebo
  `422 organization storage quota exceeded`.
- **Kontrolují se jako první, všechno, nebo nic.** Přílohy se dekódují a uloží dřív,
  než se uloží cokoli jiného z požadavku. Když se některá odmítne (neplatný base64,
  příliš velká, nad kvótou úložiště), požadavek selže s `422`, přílohy téhož
  požadavku, které už byly uloženy, se zase smažou a konverzace zůstane, jak byla:
  zpráva uživatele se nepřidá a nová konverzace se nevytvoří. Opravte přílohu
  a pošlete zprávu znovu se stejným `conversation_id`.
- **Jak je model čte.** Model se dozví, které soubory jsou přiloženy, a čte je
  vestavěnými nástroji: dokumenty jako extrahovaný text, obrázky pomocí vidění (když ho
  model podporuje a `use_vision` je `true`; velké obrázky se pro model zmenší). Přílohy
  jsou proto užitečné jen s modely, které podporují AI nástroje. Extrakce textu pokrývá
  běžné kancelářské formáty, PDF a textové formáty, viz
  [Soubory a média](files-and-media.md).

### Odpověď

Bez `stream` je odpovědí odpověď asistenta na zprávu.

| Pole | Typ | Popis |
|---|---|---|
| `id` | UUID | Id zprávy s odpovědí v rámci konverzace. |
| `timestamp` | časové razítko | Kdy zpráva s odpovědí vznikla (milisekundy od epochy). |
| `role` | řetězec | `assistant`. |
| `content` | řetězec | Odpověď v Markdownu. Obsahuje [bloky volání nástrojů](#volani-nastroju-v-odpovedi), pokud `remove_tool_calls` není `true`. |
| `reasoning_content` | řetězec nebo null | Uvažování modelu nebo shrnutí jeho přemýšlení, u modelů, které ho zpřístupňují; jinak prázdné nebo `null`. |
| `model` | řetězec nebo null | Id modelu, který odpověď vytvořil. U asistenta, který používá automatický model, je to model skutečně vybraný pro danou zprávu. |
| `attachments` | pole [Příloh odpovědi](#prilohy-odpovedi) | Soubory vytvořené pro uživatele během odpovídání, například vygenerované obrázky. |
| `tool_runs` | objekt | Odpovědi sub-asistentů volaných jako nástroje, viz [Běhy sub-asistentů](#behy-sub-asistentu). Prázdný objekt, když žádné nejsou. |

### Přílohy odpovědi

| Pole | Typ | Popis |
|---|---|---|
| `filename` | řetězec | Název souboru. |
| `content_type` | řetězec | Typ MIME, například `image/png`. |
| `file_url` | řetězec | Odkaz, který soubor stáhne. Funguje bez API klíče, proto s ním zacházejte jako s tajným údajem. |
| `name` | řetězec nebo null | Volitelný zobrazovaný název. |
| `description` | řetězec nebo null | Volitelný popis. |

### Běhy sub-asistentů

Asistent může volat jiné asistenty jako nástroje. Co takový sub-asistent odpověděl,
se vrací v `tool_runs` pod klíčem, kterým je pozice volání nástroje mezi bloky volání
nástrojů v `content`, zapsaná jako řetězec: `"0"` je první volání nástroje, `"1"`
druhé. Záznam mají jen volání nástrojů, na která odpověděl sub-asistent.

| Pole | Typ | Popis |
|---|---|---|
| `content` | řetězec | Odpověď sub-asistenta s jeho vlastními bloky volání nástrojů. |
| `tool_runs` | objekt | Běhy uvnitř vlastních volání nástrojů sub-asistenta, se stejnou strukturou. |

### Volání nástrojů v odpovědi

Když model při odpovídání používá AI nástroje, každé volání nástroje se zapíše do
`content` jako blok mezi dvěma pevnými značkami, za kterým následuje další text modelu:

```text
Let me check the current exchange rate.

### calling AI tool ...
### Scraping URL: https://example.com/rates
Looking up today's EUR/CZK rate
Successfully scraped 1 page(s)

***
The current rate is 24.35 CZK per euro.
```

Otevírací značka je přesně řetězec `"\n\n### calling AI tool ...\n"` a uzavírací
značka je `"\n***\n"`. Mezi nimi je text průběhu, který nástroj hlásil (Markdown).
Nastavte `remove_tool_calls` na `true`, chcete-li `content` bez těchto bloků, nebo
použijte strukturovaný [stream událostí](streaming.md#stream-udalosti-verze-2), který
činnost nástrojů hlásí jako samostatné události.

### Vygenerované obrázky a soubory

Nástroje, které pro uživatele vytvářejí soubory (generování obrázků, tvorba
dokumentů), hlásí soubor dvěma způsoby:

- blok volání nástroje v `content` obsahuje odkaz v Markdownu, u obrázku
  `![image.png](https://ayeto.ai/api/v1/public/file/read?id=...&secret=...)`;
- vygenerované obrázky jsou uvedeny v `attachments` odpovědi.

Generování obrázků je k dispozici, když má asistent nástroj pro generování obrázků,
nebo v konverzaci s modelem s `dynamic_tools` nastaveným na `true`. O obrázek
požádejte v `message`; žádný samostatný příznak pro obrázky neexistuje.

### Organizace a kredity

Každé volání modelu se účtuje v kreditech, uživateli klíče, nebo, když požadavek běží
v organizaci, z kreditu uživatele v této organizaci. Požadavek běží v organizaci, když
je nastaveno `organization_id`, když asistent patří organizaci, nebo když pokračuje
konverzace vytvořená v organizaci.

Konverzace vytvořená v organizaci v ní zůstává. Navazující zpráva odeslaná bez
`organization_id` (a bez asistenta jiné organizace) běží v organizaci konverzace:
účtuje se tam, její přílohy se započítávají do úložiště organizace a uživatel klíče
v ní stále musí být administrátorem nebo členem. Navazující zpráva, která uvádí jinou
organizaci, přímo nebo přes `assistant_id`, se odmítne
s `422 the conversation belongs to another organization` dřív, než se cokoli uloží
nebo vyúčtuje. Na konverzaci
vytvořenou bez organizace se takové pravidlo nevztahuje.

Před každým voláním modelu AYETO odhadne jeho cenu a volání odmítne, když zůstatek
nestačí. Během generování odpovědi se odpověď utne, jakmile by její cena přesáhla
zůstatek. V obou případech je chybou `422` s detailem `not enough user credit` nebo
`not enough organization credit`. Volání modelu, která už proběhla, se účtují,
a odpověď utnutá uprostřed se v konverzaci uchová.

Jak číst zůstatek kreditu, viz [Účet](account.md).

### Chyby

Chyby se vracejí jako `{"detail": "..."}` se stavem HTTP uvedeným níže. Když je `stream`
nastaveno na `true`, chyby označené *během* nastanou až po zahájení streamu: stav HTTP
je `200` a chyba přijde jako [chybový chunk](streaming.md#chyby-ve-streamu) se stejným
stavem a detailem. Chyby označené *před* se v obou režimech vracejí jako běžná chybová
odpověď.

| Stav | `detail` | Kdy | Příčina |
|---|---|---|---|
| `401` | `API key is invalid` | před | Viz [Autentizace](authentication.md#chyby-autentizace). |
| `403` | `permission denied` | před | Uživatel klíče nesmí používat chat, nebo nesmí používat daného asistenta. |
| `403` | `not shared with user` | před | Asistent není vlastní asistent uživatele ani s ním není sdílený. |
| `403` | `You are not allowed to chat in this conversation` | před | `conversation_id` patří jinému uživateli. |
| `403` | `User is not a member of the organization` | před | `organization_id` (nebo organizace asistenta či organizace konverzace) není mezi organizacemi uživatele. |
| `403` | `User does not have write permissions in the organization` | před | Uživatel je v organizaci host. |
| `404` | `Assistant not found` | před | Žádný asistent s `assistant_id`. |
| `404` | `model not found` | před | Žádný model s id v `model` (nebo model asistenta). |
| `422` | seznam chyb polí | před | Neplatné tělo, například není zadáno ani `assistant_id`, ani `model` (`Either assistant_id or model is required`). |
| `422` | `max_tokens must be between 1 and the maximum allowed tokens for the model` | před | `max_tokens` mimo rozsah. |
| `422` | `the conversation belongs to another organization` | před | Konverzace byla vytvořena v organizaci a požadavek uvádí jinou (`organization_id` nebo organizaci `assistant_id`). |
| `422` | `invalid base64 data in attachment '<filename>'` | před | `data` přílohy nejsou platný base64. |
| `422` | `invalid data URI in attachment '<filename>'` | před | `data` přílohy začínají na `data:`, ale před obsahem nemají `,`. |
| `422` | `file is too large` | před | Příloha překračuje limit pro nahrávání. |
| `422` | `storage quota exceeded`, `organization storage quota exceeded` | před | Příloha se nevejde do kvóty úložiště. |
| `422` | `Assistant skill '/<slug>' requires unavailable tools: ...` | před | Aktivovaná dovednost potřebuje nástroj, který není k dispozici. |
| `422` | `model is deprecated` | před | Model (nebo model asistenta) je vyřazený. |
| `422` | `model is disabled` | před | Model (nebo model asistenta) vypnul administrátor. |
| `422` | `model is not available` | před | Model (nebo model asistenta) je automatický model a žádný z modelů, ze kterých vybírá, není k dispozici. |
| `422` | `model does not have the required capability` | během | Model není chatovací model. |
| `422` | `not enough user credit`, `not enough organization credit` | během | Viz [Organizace a kredity](#organizace-a-kredity). |
| `422` | `max iterations reached` | během | Model volal nástroje i po vyčerpání povoleného počtu kol. |
| `403` | `llm: refusal from provider` | během | Poskytovatel modelu odmítl odpovědět. |
| `404` | `organization membership not found` | během | Uživatel opustil organizaci, ve které požadavek běží. |
| `429` | zpráva limitu požadavků | před | Příliš mnoho požadavků, viz [Limity požadavků](conventions.md#limity-pozadavku). |
| `500` | `llm: error on provider side`, `llm: error in chat stream`, `llm: max token reached` | během | Poskytovatel modelu selhal, nebo odpověď narazila na limit výstupních tokenů. |
| `529` | `llm: provider overload error` | během | Poskytovatel modelu je přetížený; zkuste to později. |
| `500` | `An error occurred during chat processing` | během | Neočekávaná chyba (bez `stream`). |

Pokud chyba odpověď přeruší, dosud vygenerovaná část se v konverzaci uchová.

### Příklady

Položte modelu otázku a pokračujte ve stejné konverzaci:

```bash
curl -X POST "https://ayeto.ai/api/v3/chat" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "3f6c1a9e-8b2d-4e57-9a1c-2d7e5b8f4a10",
    "model": "gpt-5-mini",
    "message": "Summarize the benefits of unit tests in three bullet points."
  }'
```

```json
{
  "id": "b1e4c7d2-5a3f-4c89-8e6b-0f2a9d1c7e35",
  "timestamp": 1759406412345,
  "role": "assistant",
  "content": "- **Catch regressions early** ...\n- **Document behaviour** ...\n- **Make refactoring safe** ...",
  "reasoning_content": "",
  "model": "gpt-5-mini",
  "attachments": [],
  "tool_runs": {}
}
```

```bash
curl -X POST "https://ayeto.ai/api/v3/chat" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "3f6c1a9e-8b2d-4e57-9a1c-2d7e5b8f4a10",
    "model": "gpt-5-mini",
    "message": "Now give an example for the second point in Python."
  }'
```

Mluvte s asistentem v organizaci a přiložte dokument:

```bash
curl -X POST "https://ayeto.ai/api/v3/chat" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "8d2f0b6a-1c4e-4f7a-b3d9-6e5a2c1f0b87",
    "assistant_id": "c7a9e2f1-4b6d-4d38-9f0e-1a2b3c4d5e6f",
    "organization_id": "5e8b1d3c-7a2f-4c6e-8d9b-0a1f2e3d4c5b",
    "message": "List the payment terms in this contract.",
    "remove_tool_calls": true,
    "attachments": [
      {
        "filename": "contract.txt",
        "data": "data:text/plain;base64,UGF5bWVudCBkdWUgd2l0aGluIDMwIGRheXMu",
        "mime_type": "text/plain"
      }
    ]
  }'
```

Vygenerujte obrázek v konverzaci s modelem:

```bash
curl -X POST "https://ayeto.ai/api/v3/chat" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "0a7c3e5f-9b1d-4a26-8c4e-7f6d5b3a2c19",
    "model": "gpt-5-mini",
    "dynamic_tools": true,
    "message": "Draw a watercolor lighthouse at sunset."
  }'
```

```json
{
  "id": "e2d4f6a8-0b1c-4d3e-9f5a-7b6c8d9e0f12",
  "timestamp": 1759406530112,
  "role": "assistant",
  "content": "\n\n### calling AI tool ...\n### Generating image\n...\n![image.png](https://ayeto.ai/api/v1/public/file/read?id=9c1e...&secret=4f7a...)\n\n***\nHere is your watercolor lighthouse at sunset.",
  "reasoning_content": "",
  "model": "gpt-5-mini",
  "attachments": [
    {
      "filename": "image.png",
      "content_type": "image/png",
      "file_url": "https://ayeto.ai/api/v1/public/file/read?id=9c1e...&secret=4f7a...",
      "name": null,
      "description": null
    }
  ],
  "tool_runs": {}
}
```

Python (`requests`):

```python
import base64
import os
import uuid

import requests

API = "https://ayeto.ai/api/v3"
HEADERS = {"uni-api-key": os.environ["AYETO_API_KEY"]}

conversation_id = str(uuid.uuid4())  # keep it to continue the conversation


def chat(message, attachments=None):
    response = requests.post(
        f"{API}/chat",
        headers=HEADERS,
        json={
            "conversation_id": conversation_id,
            "model": "gpt-5-mini",
            "message": message,
            "remove_tool_calls": True,
            "attachments": attachments,
        },
        timeout=600,  # answers that use tools can take minutes
    )
    if response.status_code != 200:
        raise RuntimeError(f"{response.status_code}: {response.json()['detail']}")
    return response.json()


with open("invoice.pdf", "rb") as f:
    pdf = f.read()

answer = chat(
    "What is the total amount of this invoice?",
    attachments=[{
        "filename": "invoice.pdf",
        "data": "data:application/pdf;base64," + base64.b64encode(pdf).decode(),
        "mime_type": "application/pdf",
    }],
)
print(answer["content"])
print(chat("And the due date?")["content"])
```

JavaScript (`fetch`):

```javascript
const API = "https://ayeto.ai/api/v3";
const conversationId = crypto.randomUUID(); // keep it to continue the conversation

async function chat(message) {
  const response = await fetch(`${API}/chat`, {
    method: "POST",
    headers: {
      "uni-api-key": process.env.AYETO_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      conversation_id: conversationId,
      assistant_id: "c7a9e2f1-4b6d-4d38-9f0e-1a2b3c4d5e6f",
      message,
    }),
  });
  const body = await response.json();
  if (!response.ok) {
    throw new Error(`${response.status}: ${JSON.stringify(body.detail)}`);
  }
  return body;
}

const answer = await chat("Draft a short reply to a customer asking about delivery times.");
console.log(answer.content);
```

Chcete-li odpověď přijímat průběžně během generování, nastavte `"stream": true`
a čtěte odpověď podle popisu ve [Streamování](streaming.md#cteni-streamu).
