# Multi-turn LLM conversations

Hold multi-turn conversations whose history the platform keeps and trims.

The `chat` method is `invoke` with memory - the platform keeps each conversation's history under an id you choose, and every call sends the preceding turns along, so the model receives what was already said. Your service stays stateless, and the same conversation can continue from any server and across restarts.

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SupportChat(Service):

    name = 'demo.llm.support-chat'

    def handle(self):

        conn = self.llm['My Claude']

        # The chat id groups this user's turns into one conversation -
        # any string works, e.g. a user id or a session id.
        chat_id = self.request.payload['user_id']
        text = self.request.payload['text']

        response = conn.chat(text, chat_id=chat_id)

        self.response.payload = response['text']
```

The response is the same dictionary that [invoke](https://zato.io/docs/ai/llm/invoke.html) returns - `text`, `usage` and `raw`.

## History storage {#history-storage}

- Each chat's history is stored under its id, with the expiry the [connection](https://zato.io/docs/ai/llm/connections.html) configures - one day by default, counted from the chat's last message. A chat that stays quiet longer than that starts afresh.
- History survives server restarts and is shared across all servers of an environment, so a conversation is never pinned to the server that started it.
- An unknown or expired chat id starts a new conversation - there is nothing to create first.

## History trimming {#history-trimming}

Only the last **max history turns** of the conversation are sent with each call - a turn is one user message plus the assistant's reply, and the limit is set on the connection, 20 turns by default. Older turns stay in the history until it expires but never leave the platform, which keeps long conversations from growing the token bill without a ceiling.

A named [skill](https://zato.io/docs/ai/skills/index.html#use-a-skill) accompanies every call of the conversation:

```python
response = conn.chat(text, chat_id=chat_id, skill='support-agent')
```

## Concurrent calls {#concurrent-calls}

Two calls to the same chat at the same time would race over its history, so the platform serializes them - a per-chat lock makes the second call wait until the first one's reply is saved. Calls to different chats never wait for each other.

## Without a chat id {#without-a-chat-id}

A `chat` call without a `chat_id` loads nothing and saves nothing - it is exactly `invoke`. This lets one code path serve both cases, as in a service that receives an optional session id:

```python
# An empty chat_id makes this a one-shot call
response = conn.chat(text, chat_id=chat_id_or_empty)
```

## See also {#see-also}

- [Invoking LLMs](https://zato.io/docs/ai/llm/invoke.html) - The one-shot call chat builds on, and its response dictionary
- [LLM connections](https://zato.io/docs/ai/llm/connections.html) - Where max history turns and chat expiry are set
- [Skills](https://zato.io/docs/ai/skills/index.html) - Instructions that accompany every call of a conversation
- [Chat with memory](https://zato.io/docs/ai/examples/chat-with-memory.html) - A complete per-user chat service with trimming and expiry
