Nette Documentation Preview

syntax
Konverzace s AI modelem
***********************

.[perex]
Konverzace je posloupnost zpráv, kterou si knihovna drží sama. Naučíš se vést vícekolový dialog, nastavit modelu roli systémovou instrukcí, přečíst z odpovědi víc než jen text a poskládat historii ručně, když potřebuješ navázat na dřívější rozhovor.


Model si nic nepamatuje
=======================

Tohle je první věc, která překvapí skoro každého: jazykový model nemá paměť. Každé volání API je samostatné a model o předchozí otázce neví nic. Iluze rozhovoru vzniká tím, že se s každým dotazem posílá celá dosavadní historie znovu.

Právě proto tu je objekt konverzace. Stará se o historii za tebe:

```php
$chat = $client->createChat('gpt-5.6-luna');

$chat->sendMessage('Jaké je hlavní město Francie?');
$response = $chat->sendMessage('A jaká je tam známá památka?');

echo $response->getText();
```

Druhá otázka nezmiňuje Paříž ani slovem, a přesto model odpoví správně, protože spolu s ní odešla i první výměna. Kdyby ses ptal přes dvě samostatná volání, druhá odpověď by byla nesmysl.

Má to jeden důsledek, na který je dobré myslet dopředu: **dlouhá konverzace je drahá**. S každým kolem roste vstup, a vstup se platí. Historii proto někdy stojí za to zkrátit nebo začít znovu. A když stejný dotaz posíláš na stovky vstupů a nespěcháš, vyjde levněji [dávkové zpracování |batch]:

```php
$chat->clearMessages();
```


Systémová instrukce
===================

Systémová instrukce říká modelu, jakou roli má hrát a jakých pravidel se držet. Platí pro celou konverzaci a modely jí přikládají větší váhu než běžné zprávě, takže sem patří pokyny, které mají platit i za deset kol.

```php
$chat->setSystemInstruction('Jsi zkušený PHP vývojář. Odpovídej stručně a v příkladech používej Nette.');
```

Dobrá systémová instrukce je konkrétní. Místo "buď stručný" napiš "odpovídej nejvýš třemi větami"; místo "buď přesný" napiš "když si nejsi jistý, řekni to místo hádání". Model nemá jak poznat, co si pod obecným pokynem představuješ.

Systémová instrukce se posílá s každým požadavkem, takže se za ni platí pokaždé. Když je dlouhá a konverzace má hodně kol, sáhni po providerově cache; kolik se z ní načetlo, poznáš podle `cacheReadTokens` ve [spotřebě |#Co všechno je v odpovědi].


Historie sestavená ručně
========================

Někdy potřebuješ modelu předložit rozhovor, který se takhle neodehrál. Typicky když obnovuješ konverzaci uloženou v databázi, nebo když chceš ukázat pár příkladů správných odpovědí, což je technika známá jako few-shot prompting.

```php
use AIAccess\Chat\Role;

$chat = $client->createChat('gpt-5.6-luna');
$chat->addMessage('Jaké je hlavní město Francie?', Role::User);
$chat->addMessage('Paříž.', Role::Model);
$chat->addMessage('A jaká je tam známá památka?', Role::User);

$response = $chat->sendMessage(); // bez argumentu: pokračuj z historie
```

`addMessage()` zprávu jen přidá do historie a nic neodesílá. `sendMessage()` bez argumentu pak odešle konverzaci tak, jak je.

Role jsou tři. `Role::User` je uživatel, `Role::Model` je model a `Role::Tool` nese výsledky [volání nástrojů |tools]. Knihovna přitom používá vlastní pojmenování: Gemini téže roli říká `model`, zatímco ostatní `assistant`, a na jméno, které zrovna ten který provider očekává, si ho knihovna přeloží sama.

Celou historii si kdykoli vyžádáš zpátky:

```php
foreach ($chat->getMessages() as $message) {
	echo $message->getRole()->value, ': ', $message->getText(), "\n";
}
```


Co všechno je v odpovědi
========================

`sendMessage()` nevrací řetězec, ale objekt, protože samotný text je jen část toho, co se stalo.

```php
$response = $chat->sendMessage('Napiš mi povídku o PHP.');

echo $response->getText();
```

Když je text prázdný, nemusí jít o chybu. Model mohl odmítnout odpovědět, mohl narazit na limit dřív, než stihl napsat první slovo, nebo si místo odpovědi řekl o nástroj. Která z těch věcí to byla, prozradí důvod ukončení.


Proč model přestal psát
-----------------------

Odpověď nekončí vždycky proto, že model dopověděl. Někdy ho zastaví limit tokenů, jindy bezpečnostní filtr a jindy čeká, až mu něco doplníš. Rozlišovat to potřebuješ, protože v každém z těch případů se zachováš jinak. Slouží k tomu `getFinishReason()`, který vrací jednu z hodnot výčtu `FinishReason`:

| Hodnota           | Co se stalo                                                 | Jak se zachovat                                                               |
|-------------------|-------------------------------------------------------------|-------------------------------------------------------------------------------|
| `Complete`        | Model řekl všechno, co chtěl, a sám skončil.                | Nic, tohle je ten dobrý případ.                                               |
| `TokenLimit`      | Odpověď je useknutá uprostřed, došel povolený počet tokenů. | Zvyš limit v [nastavení |options], nebo si vyžádej kratší odpověď.            |
| `ContentFiltered` | Model odpověď odmítl.                                       | Text bude prázdný. Přeformuluj dotaz; u OpenAI důvod prozradí `getRefusal()`. |
| `ToolCall`        | Model si řekl o zavolání [nástroje |tools].                 | Zavolej ho a výsledek mu pošli zpátky.                                        |
| `Cancelled`       | [Stream |streaming] jsi přerušil sám.                       | Máš jen část odpovědi, a je to v pořádku.                                     |
| `Unknown`         | Provider poslal důvod, který do téhle škály nezapadá.       | Původní hodnotu najdeš v `getRawFinishReason()`.                              |

V kódu to vypadá takhle:

```php
use AIAccess\Chat\FinishReason;

if ($response->getFinishReason() === FinishReason::TokenLimit) {
	echo 'Odpověď je useknutá, model narazil na limit.';
}
```

Uvažovací modely navíc můžou vrátit svůj myšlenkový postup. Nikdy není součástí `getText()`, protože do výstupu aplikace nepatří, ale přečíst si ho můžeš:

```php
if ($reasoning = $response->getReasoning()) {
	echo "Model uvažoval takto:\n", $reasoning;
}
```

A když ti abstrakce nestačí, `getRawResponse()` ti vrátí kompletní dekódovanou odpověď providera přesně tak, jak přišla. Sjednocené rozhraní je pohodlí, ne klec.


Kam dál
=======

- [Nastavení a reasoning effort |options] - kolik přemýšlení si od modelu vyžádáš
- [Streamování |streaming] - odpověď čti, zatímco ji model teprve píše
- [Volání nástrojů |tools] - když má model sáhnout do tvé aplikace
- [Strukturovaný výstup |structured-output] - když potřebuješ data, ne prózu
- [Embeddingy |embeddings] - když má model odpovídat nad tvými vlastními texty

Konverzace s AI modelem

Konverzace je posloupnost zpráv, kterou si knihovna drží sama. Naučíš se vést vícekolový dialog, nastavit modelu roli systémovou instrukcí, přečíst z odpovědi víc než jen text a poskládat historii ručně, když potřebuješ navázat na dřívější rozhovor.

Model si nic nepamatuje

Tohle je první věc, která překvapí skoro každého: jazykový model nemá paměť. Každé volání API je samostatné a model o předchozí otázce neví nic. Iluze rozhovoru vzniká tím, že se s každým dotazem posílá celá dosavadní historie znovu.

Právě proto tu je objekt konverzace. Stará se o historii za tebe:

$chat = $client->createChat('gpt-5.6-luna');

$chat->sendMessage('Jaké je hlavní město Francie?');
$response = $chat->sendMessage('A jaká je tam známá památka?');

echo $response->getText();

Druhá otázka nezmiňuje Paříž ani slovem, a přesto model odpoví správně, protože spolu s ní odešla i první výměna. Kdyby ses ptal přes dvě samostatná volání, druhá odpověď by byla nesmysl.

Má to jeden důsledek, na který je dobré myslet dopředu: dlouhá konverzace je drahá. S každým kolem roste vstup, a vstup se platí. Historii proto někdy stojí za to zkrátit nebo začít znovu. A když stejný dotaz posíláš na stovky vstupů a nespěcháš, vyjde levněji dávkové zpracování:

$chat->clearMessages();

Systémová instrukce

Systémová instrukce říká modelu, jakou roli má hrát a jakých pravidel se držet. Platí pro celou konverzaci a modely jí přikládají větší váhu než běžné zprávě, takže sem patří pokyny, které mají platit i za deset kol.

$chat->setSystemInstruction('Jsi zkušený PHP vývojář. Odpovídej stručně a v příkladech používej Nette.');

Dobrá systémová instrukce je konkrétní. Místo „buď stručný“ napiš „odpovídej nejvýš třemi větami“; místo „buď přesný“ napiš „když si nejsi jistý, řekni to místo hádání“. Model nemá jak poznat, co si pod obecným pokynem představuješ.

Systémová instrukce se posílá s každým požadavkem, takže se za ni platí pokaždé. Když je dlouhá a konverzace má hodně kol, sáhni po providerově cache; kolik se z ní načetlo, poznáš podle cacheReadTokens ve spotřebě.

Historie sestavená ručně

Někdy potřebuješ modelu předložit rozhovor, který se takhle neodehrál. Typicky když obnovuješ konverzaci uloženou v databázi, nebo když chceš ukázat pár příkladů správných odpovědí, což je technika známá jako few-shot prompting.

use AIAccess\Chat\Role;

$chat = $client->createChat('gpt-5.6-luna');
$chat->addMessage('Jaké je hlavní město Francie?', Role::User);
$chat->addMessage('Paříž.', Role::Model);
$chat->addMessage('A jaká je tam známá památka?', Role::User);

$response = $chat->sendMessage(); // bez argumentu: pokračuj z historie

addMessage() zprávu jen přidá do historie a nic neodesílá. sendMessage() bez argumentu pak odešle konverzaci tak, jak je.

Role jsou tři. Role::User je uživatel, Role::Model je model a Role::Tool nese výsledky volání nástrojů. Knihovna přitom používá vlastní pojmenování: Gemini téže roli říká model, zatímco ostatní assistant, a na jméno, které zrovna ten který provider očekává, si ho knihovna přeloží sama.

Celou historii si kdykoli vyžádáš zpátky:

foreach ($chat->getMessages() as $message) {
	echo $message->getRole()->value, ': ', $message->getText(), "\n";
}

Co všechno je v odpovědi

sendMessage() nevrací řetězec, ale objekt, protože samotný text je jen část toho, co se stalo.

$response = $chat->sendMessage('Napiš mi povídku o PHP.');

echo $response->getText();

Když je text prázdný, nemusí jít o chybu. Model mohl odmítnout odpovědět, mohl narazit na limit dřív, než stihl napsat první slovo, nebo si místo odpovědi řekl o nástroj. Která z těch věcí to byla, prozradí důvod ukončení.

Proč model přestal psát

Odpověď nekončí vždycky proto, že model dopověděl. Někdy ho zastaví limit tokenů, jindy bezpečnostní filtr a jindy čeká, až mu něco doplníš. Rozlišovat to potřebuješ, protože v každém z těch případů se zachováš jinak. Slouží k tomu getFinishReason(), který vrací jednu z hodnot výčtu FinishReason:

Hodnota Co se stalo Jak se zachovat
Complete Model řekl všechno, co chtěl, a sám skončil. Nic, tohle je ten dobrý případ.
TokenLimit Odpověď je useknutá uprostřed, došel povolený počet tokenů. Zvyš limit v nastavení, nebo si vyžádej kratší odpověď.
ContentFiltered Model odpověď odmítl. Text bude prázdný. Přeformuluj dotaz; u OpenAI důvod prozradí getRefusal().
ToolCall Model si řekl o zavolání nástroje. Zavolej ho a výsledek mu pošli zpátky.
Cancelled Stream jsi přerušil sám. Máš jen část odpovědi, a je to v pořádku.
Unknown Provider poslal důvod, který do téhle škály nezapadá. Původní hodnotu najdeš v getRawFinishReason().

V kódu to vypadá takhle:

use AIAccess\Chat\FinishReason;

if ($response->getFinishReason() === FinishReason::TokenLimit) {
	echo 'Odpověď je useknutá, model narazil na limit.';
}

Uvažovací modely navíc můžou vrátit svůj myšlenkový postup. Nikdy není součástí getText(), protože do výstupu aplikace nepatří, ale přečíst si ho můžeš:

if ($reasoning = $response->getReasoning()) {
	echo "Model uvažoval takto:\n", $reasoning;
}

A když ti abstrakce nestačí, getRawResponse() ti vrátí kompletní dekódovanou odpověď providera přesně tak, jak přišla. Sjednocené rozhraní je pohodlí, ne klec.

Kam dál