duck-chat-api 0.2.1
DuckDuckGo Chat AI client
Requires Python: >=3.13

# 🦆 Duck Chat

> Библиотека для программного взаимодействия с AI-моделями через DuckDuckGo AI Chat

Эта библиотека предоставляет Python API для работы с DuckDuckGo AI Chat и включает встроенный сервис для запуска локального HTTP-сервера. Теперь вы можете использовать мощные AI-модели напрямую в своем коде или через REST API.

---

## 🚀 Ключевые возможности

- **Прямой доступ** к AI-моделям DuckDuckGo через Python API
- **Встроенный FastAPI-сервер** для удобной интеграции с любыми приложениями
- **Автоматическое управление заголовками** запросов (через Playwright)
- **Поддержка изображений** в запросах
- **Web-поиск** в ответах моделей
- **Совместимость** с различными AI-моделями (GPT-4o, Claude, Llama и др.)

---

## 📦 Установка

### Установка основной библиотеки:

```bash
pip install git+https://github.com/paranoik1/duck-chat-api
```

### Установка с дополнительными зависимостями для API-сервиса:

```bash
pip install "duck-chat-api[api-service]"
```

### Установка для разработки:

```bash
git clone https://github.com/paranoik1/duck-local-chat-api
cd duck-local-chat-api
poetry install --with api-service,dev
```

> **Важно:** Для работы с API-сервисом необходимы системные зависимости:
> ```bash
> sudo apt install xvfb
> ```

---

## 💻 Использование библиотеки

### Базовое использование:

```python
import asyncio
from duck_chat_api import DuckChat, ModelType
from duck_chat_api.utils.headers import get_headers

async def main():
    headers = await get_headers()
    async with DuckChat(headers, model=ModelType.Gpt4OMini) as chat:
        async for part in chat.ask_question("Привет! Кто ты?"):
            if hasattr(part, 'text'):
                print(part.text, end="", flush=True)
        print()

asyncio.run(main())
```

### Расширенное использование с параметрами:

```python
import asyncio
from duck_chat_api import DuckChat, PartText, PartImage
from duck_chat_api.utils.headers import get_headers
import base64


async def chat_with_image():
    headers = await get_headers()
    async with DuckChat(headers, model="gpt-4o-mini") as chat:
        # Подготовка изображения
        with open("image.jpg", "rb") as img_file:
            image_data = base64.b64encode(img_file.read()).decode()
        
        # Формирование запроса с изображением
        query = [
            PartText.create("Что изображено на картинке?"),
            PartImage.create(image_data)
        ]
        
        # Отправка запроса с включенным веб-поиском
        async for part in chat.ask_question(query, web_search=True):
            if hasattr(part, 'text'):
                print(part.text, end="", flush=True)
        print()

asyncio.run(chat_with_image())
```
> **Важно:** Библиотека рассчитана на самостоятельное управление заголовками запросов. Пользователи должны самостоятельно получать (`get_headers()` или другими методами) и обновлять заголовки (`chat.set_headers(headers)`). 

---

## 🌐 Запуск API-сервиса

### Через CLI:

```bash
duck-api-service --host 0.0.0.0 --port 8000 --log-level info
```

### Программно:

```python
from duck_chat_api.service import app
import uvicorn

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
```

После запуска сервиса доступны эндпоинты:
- `POST /chat` — отправка запроса к AI-модели
- `GET /docs` — интерактивная документация Swagger UI
- `GET /redoc` — альтернативная документация ReDoc

### Пример запроса к API:

```bash
curl -X POST http://localhost:8000/chat \
  -F "content=Что на этой картинке?" \
  -F "model=gpt-4o-mini" \
  -F "web_search=true" \
  -F "file=@/path/to/image.jpg"
```

---

## 🖥️ Архитектура библиотеки

```
duck_chat_api/
├── __init__.py           # Основные импорты
├── api.py                # Класс DuckChat для взаимодействия с API
├── event.py              # Обработка событий от DuckDuckGo
├── exceptions.py         # Пользовательские исключения
├── model_type.py         # Типы поддерживаемых моделей
├── parts.py              # Структуры данных для частей сообщений
├── request_data.py       # Формирование запросов к API
├── service/              # Модуль для запуска API-сервиса
│   ├── __main__.py       # Точка входа для CLI
│   ├── headers_manager.py# Управление заголовками запросов
│   ├── service.py        # FastAPI приложение
│   └── utils.py          # Вспомогательные функции
└── utils/                # Утилиты для работы с заголовками и моделями
    ├── headers.py        # Получение валидных заголовков через Playwright
    └── models.py         # Парсинг доступных моделей
```

---

## 🔧 Доступные модели

Библиотека автоматически определяет доступные модели при запуске. Основные поддерживаемые модели:

- `gpt-4o-mini`
- `gpt-5-mini`
- `claude-3-5-haiku-latest`
- `meta-llama/Llama-4-Scout-17B-16E-Instruct`
- `mistralai/Mistral-Small-24B-Instruct-2501`
- `openai/gpt-oss-120b`

Для получения актуального списка моделей используйте:

```python
from duck_chat_api.utils.models import get_models_page_html, parse_models
from duck_chat_api.service.utils import generate_models
import asyncio
from pprint import pprint

async def print_models():
    html = await get_models_page_html()
    models = parse_models(html)
    pprint(models)
    # ИЛИ же
    models = await generate_models()
    pprint(models)

asyncio.run(print_models())
```

## 🧩 Работа с частями сообщений (Parts)

Библиотека `duck_chat_api` использует концепцию "частей сообщений" (parts) для гибкого формирования запросов и обработки ответов. Все части сообщений наследуются от базового класса `Part` и имеют специфическую структуру.

### Доступные типы частей сообщений

#### `Part` (базовый класс)
- Абстрактный базовый класс для всех типов частей
- Содержит поле `type` для идентификации типа части
- Атрибут `IS_SAVE` определяет, нужно ли сохранять часть в истории диалога

#### `PartText`
- Представляет текстовую часть сообщения
- Создание: `PartText.create("Ваш текст здесь")`
- Поля:
  - `text`: содержимое текста

#### `PartImage` 
- Представляет изображение в запросе
- Создание: `PartImage.create(base64_encoded_image, mime_type="image/jpeg")`
- Поля:
  - `mime_type`: MIME-тип изображения (по умолчанию `image/webp`)
  - `image`: изображение в формате data URL (`data:{mime_type};base64,{base64}`)

#### `PartTool`
- Используется моделью для вызова инструментов (автоматически)
- Не предназначен для ручного создания в запросах
- Представляет либо вызов инструмента, либо результат его выполнения
- Поля:
  - `tool_call_id`: идентификатор вызова
  - `state`: состояние (`call` или `result`)
  - `tool_arguments`, `tool_name`: параметры вызова (при `state=call`)
  - `result`: результат выполнения (при `state=result`)

#### `PartSource`
- Представляет источники информации в ответах модели
- Не сохраняется в истории диалога (`IS_SAVE = False`)
- Поля:
  - `source`: объект с информацией об источнике

### Правила использования

| Тип части | Можно использовать в запросе | Может вернуться в ответе | Примечание |
|-----------|-------------------------------|--------------------------|------------|
| `PartText` | ✅ | ✅ | Основной тип для текстовых запросов и ответов |
| `PartImage` | ✅ | ❌ | Только для отправки изображений в запросах |
| `PartTool` | ❌ | ✅ | Автоматически обрабатывается моделью |
| `PartSource` | ❌ | ✅ | Источники информации в результатах поиска |

### Примеры использования

```python
import asyncio
from duck_chat_api import DuckChat, PartText, PartImage, PartSource
from duck_chat_api.utils.headers import get_headers
import base64

async def chat_with_image():
    # Получение валидных заголовков для запросов к API
    headers = await get_headers()
    
    async with DuckChat(headers, model="gpt-4o-mini") as chat:
        # Подготовка изображения
        with open("image.jpg", "rb") as img_file:
            image_data = base64.b64encode(img_file.read()).decode()
        
        # Формирование запроса с изображением
        query = [
            PartText.create("Что изображено на картинке?"),
            PartImage.create(image_data)
        ]
        
        # Отправка запроса с включенным веб-поиском
        async for part in chat.ask_question(query, web_search=True):
            if isinstance(part, PartText):
                print(part.text, end="", flush=True)
            elif isinstance(part, PartSource):
                print("\n" + part.source.title + ": " + part.source.url)
            elif isinstance(part, PartTool):
              if part.state == "call" and part.tool_name == "WebSearch":
                  print("\n[Выполняется поиск в интернете...]")
        print()

asyncio.run(chat_with_image())
```

Важно: при создании запросов вы можете использовать **только** `PartText` и `PartImage`. Все остальные типы частей (`PartTool`, `PartSource`) генерируются автоматически моделью при формировании ответа и не должны создаваться вручную в запросах.

---

## ⚠️ Ограничения и требования

- **Только Linux (если получение заголовков требуется в headless режиме)** (из-за зависимости `xvfbwrapper`)
- **Python 3.13+** (требуется для всех функций библиотеки)
- **Chromium** должен быть установлен в системе
- **Срок действия заголовков** ограничен (обычно 5-10 запросов)
- Не предназначен для массового использования (следуйте правилам duck.ai)
- Некоторые модели могут требовать аккаунта DuckDuckGo Pro (не поддерживается проектом)

---

## 🙏 Благодарности

- [mrgick/duck_chat](https://github.com/mrgick/duck_chat) — за основу реализации
- [patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright) — за решение по обходу проверок на ботов
- [FastAPI](https://fastapi.tiangolo.com/) и [Playwright](https://playwright.dev/) — за отличные инструменты

---

> 🦆 Утки не возвращаются, но API — да.

> Пользуйтесь с умом и соблюдайте правила использования DuckDuckGo AI.