---
title: "DSPy — Автоматична генерація промптів"
url: "https://romankryvolapov.com/uk/dspy/"
description: "DSPy: як замінити ручний підбір промптів сигнатурами, метрикою та налаштуванням MIPROv2, щоб під час зміни LLM моделі промпт налаштовувався заново, а не переписувався руками."
language: uk
published: 2025-09-22
updated: 2026-11-05
---
Багато AI інженерів і не тільки, що працюють з LLM та агентами, стикаються із завданнями, коли коректність роботи агента багато в чому залежить від промпту.

Для простих завдань достатньо промпту в стилі "Ти розумний агент, відповідай на питання користувача". Але якщо агент має робити складні виклики функцій і обробляти нестандартні запити, написати стабільний промпт для викликів і для остаточної відповіді непросто, а під час зміни LLM моделі на іншу промпт, що раніше працював, може перестати працювати. Як писати такі промпти руками, розібрано в [статті про промпт-інжиніринг](/uk/prompt-engineering/), а як агент викликає інструменти — у [статті про RAG і виклик функцій](/uk/rag-and-function-calling/).

[DSPy](https://dspy.ai) ([GitHub](https://github.com/stanfordnlp/dspy)) перетворює це з написання тексту на вимірюваний процес. Що він робить простими словами:

- Замість довгого тексту ви описуєте завдання як форму: які є входи і які потрібні виходи.
- Правила виклику інструментів фіксуються кодом, а не «словами» в промпті.
- Якість вимірюється метрикою, і DSPy добирає формулювання під цю метрику.
- Під час зміни моделі або функцій ви запускаєте налаштування заново, а не переписуєте все руками.

Що це дає:

- Масштабування: простіше додавати інструменти та нові формати відповідей.
- Дешеву зміну моделі: вона перетворюється на повторний запуск налаштування, з оцінкою до і після.
- Повторюваність: промпт зберігається як артефакт і відновлюється.
- Контроль: поведінка задається структурою та метрикою, а не «магічним текстом».

Ціна — набір даних і час на налаштування; про них нижче. Приклад — агент, який шукає в локальній базі даних: так видно, що агенти можуть бути досить складними.

## Сигнатура та інструменти

Сигнатура каже, які поля приходять агенту і що він має повернути. Інструменти — звичайні функції з чіткими входами й виходами:

```python
import sqlite3
from contextlib import closing

import dspy

# A local model in LM Studio; any model LiteLLM can reach will do.
dspy.configure(
    lm=dspy.LM(
        "openai/google/gemma-3n-e4b",
        api_base="http://localhost:1234/v1",
        api_key="lm-studio",
        temperature=0,
    )
)


def search_kb(query: str) -> list[str]:
    """Searches the knowledge base and returns the matching fragments."""
    with closing(sqlite3.connect("kb.db")) as db:
        rows = db.execute(
            "SELECT text FROM articles WHERE text LIKE ? LIMIT 5", (f"%{query}%",)
        ).fetchall()
    return [row[0] for row in rows]


class AnswerFromKb(dspy.Signature):
    """Answer the question using only the knowledge base."""

    question: str = dspy.InputField()
    answer: str = dspy.OutputField(desc="a short answer, no explanations")


agent = dspy.ReAct(AnswerFromKb, tools=[search_kb], max_iters=5)
```

## Дані

Набір даних — це список питань із правильними відповідями:

```json
[
  {
    "question": "...",
    "answer": "..."
  }
]
```

За [документацією DSPy](https://dspy.ai/learn/optimization/overview/), 30 прикладів часто вже дають помітну користь, але орієнтуватися варто щонайменше на 300, а для MIPROv2 вона [радить](https://dspy.ai/learn/optimization/optimizers/) від 200, щоб уникнути перенавчання. Важливо, щоб у них було різноманіття.

Розбиття протилежне до звичного за нейромережами: 20 відсотків на навчання і 80 на валідацію, бо оптимізатори промптів легко перенавчаються на маленькій навчальній вибірці. Зверху додається відкладена тестова вибірка, якої оптимізатор ніколи не бачить, і довіряти варто саме її оцінці:

```python
import json
import random

with open("dataset.json", encoding="utf-8") as f:
    examples = [dspy.Example(**row).with_inputs("question") for row in json.load(f)]

random.Random(0).shuffle(examples)
holdout = len(examples) // 5
testset, rest = examples[:holdout], examples[holdout:]
split = len(rest) // 5
trainset, valset = rest[:split], rest[split:]
```

## Метрика та налаштування

MIPROv2 запускає агента, щоб зібрати вдалі траси, пропонує варіанти інструкцій і прикладів few-shot та шукає серед них найкращий за метрикою:

```python
def metric(example, prediction, trace=None):
    return example.answer.lower() in prediction.answer.lower()


evaluate = dspy.Evaluate(devset=testset, metric=metric, num_threads=4)
print("before:", evaluate(agent))

optimizer = dspy.MIPROv2(metric=metric, auto="light", num_threads=4)
tuned = optimizer.compile(agent, trainset=trainset, valset=valset)

print("after:", evaluate(tuned))
tuned.save("agent.json")
```

Запуск не безкоштовний. З `auto="light"` цей агент проходить 20 спроб на мінібатчах по 35 прикладів, плюс кожні 5 спроб повний прогін по валідаційній вибірці, до 100 прикладів, а кожен запуск агента — це кілька звернень до моделі. На локальній моделі це довге очікування, на хмарній — рахунок. Пропозиції варіантів можна віддати сильнішій моделі, ніж та, на якій працює агент, — через `prompt_model` і `task_model`.

## Використання результату

`agent.json` — це артефакт: він лежить у репозиторії й завантажується без жодного налаштування. Під час зміни даних або моделі запускаємо налаштування заново й порівнюємо оцінки на тестовій вибірці.

```python
agent = dspy.ReAct(AnswerFromKb, tools=[search_kb], max_iters=5)
agent.load("agent.json")
print(agent(question="How long is the refund period?").answer)
```
