Blog
AI20 min czytania

DSPy — programowanie pipeline'ów LLM zamiast ręcznego promptowania

Framework ze Stanford NLP, w którym prompt przestaje być kodem źródłowym i staje się artefaktem kompilacji. Deklarujesz sygnaturę wejścia i wyjścia, komponujesz moduły jak warstwy w PyTorchu, definiujesz metrykę — a optymalizator sam dobiera instrukcje i przykłady pod tę metrykę. Rozbieramy sygnatury, moduły, jedenaście optymalizatorów wraz z przewodnikiem wyboru, realne koszty przebiegu i mechanizm cache, który potrafi zmylić przy eksperymentach. Plus trzy sposoby wykorzystania tego w zespole, który pisze w PHP.

Inżynieria promptów w praktyce wygląda tak. Piszemy prompt, sprawdzamy na trzech przykładach, działa. Wchodzi nowsza wersja modelu i przestaje działać, więc dopisujemy „ODPOWIADAJ WYŁĄCZNIE POPRAWNYM JSON-em". Ktoś zgłasza, że daty wychodzą w złym formacie — dochodzi akapit o formacie daty. Ktoś inny narzeka na zbyt długie odpowiedzi — dochodzi zdanie o limicie. Do tego trzy przykłady few-shot, bo pomogły.

Po dwóch miesiącach w kodzie siedzi stała znakowa na cztery tysiące znaków, której nikt nie odważy się skrócić, bo nikt nie wie, która część jest tam z jakiego powodu. Nikt też nie wie, czy jakakolwiek zmiana pomogła — bo nie ma metryki. Jest wrażenie, że „chyba lepiej", oparte na obejrzeniu pięciu wyników.

DSPy (github.com/stanfordnlp/dspy) odwraca ten proces. Deklarujemy co ma się stać, a nie jak o to poprosić — a właściwe brzmienie promptu jest wynikiem kompilacji pod zadaną metrykę. Nazwa rozwija się jako Declarative Self-improving Python, a hasło projektu to „framework do programowania, a nie promptowania modeli językowych".

Poniżej: trzy pojęcia, na których stoi całość, jak to wygląda w kodzie, jedenaście dostępnych optymalizatorów z przewodnikiem wyboru, realne koszty przebiegu — oraz co z tego może wziąć zespół, który pisze w PHP i nie zamierza przenosić produkcji na Pythona.

Stan projektu

Dane z API GitHuba na 19 września 2026:

  • 37 873 gwiazdki i 3294 forki,
  • repozytorium założone 9 stycznia 2023, kod w Pythonie, licencja MIT,
  • najnowsze wydanie 3.3.1 z 21 sierpnia 2026, a przed nim 3.3.0 z 3 sierpnia i 3.2.1 z 5 maja — kadencja to wydanie minor raz na kilka tygodni,
  • ostatni commit z 5 września 2026, 660 otwartych zgłoszeń.

Rzecz, która wyróżnia ten projekt na tle większości narzędzi AI: stoi za nim seria prac naukowych z Stanford NLP, nie tylko README. Od Demonstrate-Search-Predict z grudnia 2022, przez główną pracę DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines przyjętą na ICLR 2024, po GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning z lipca 2025. Optymalizatory w bibliotece odpowiadają konkretnym publikacjom, a nie intuicjom autorów.

Trzy pojęcia

Cała biblioteka stoi na trzech abstrakcjach i warto je zrozumieć w tej kolejności, bo każda następna zakłada poprzednią.

Sygnatura — deklaracja zachowania

Sygnatura to deklaratywna specyfikacja zachowania wejście–wyjście. Mówi modelowi, co ma zrobić, nie jak o to poprosić. W najkrótszej formie to zwykły łańcuch znaków:

"question -> answer"                                    # domyślnie oba str
"sentence -> sentiment: bool"                            # klasyfikacja
"document -> summary"                                    # streszczenie
"context: list[str], question: str -> answer: str"       # RAG
"question, choices: list[str] -> reasoning: str, selection: int"

Istotny szczegół, który łatwo przeoczyć: nazwy pól mają znaczenie semantyczne. Dokumentacja podkreśla, że question to nie to samo co answer, a sql_query nie to samo co python_code — model dostaje z tych nazw informację o roli pola. Jednocześnie pada rada, którą warto potraktować poważnie: nie dostrajaj słów kluczowych w sygnaturze ręcznie. Do streszczenia równie dobrze nadaje się "document -> summary", "text -> gist" i "long_context -> tldr"; dobór brzmienia to praca dla kompilatora, nie dla nas.

Do bardziej złożonych zadań jest wariant klasowy, z docstringiem opisującym naturę zadania i typami wymuszającymi kształt wyjścia:

from typing import Literal

class Emotion(dspy.Signature):
    """Klasyfikuj emocję."""

    sentence: str = dspy.InputField()
    sentiment: Literal['sadness', 'joy', 'love', 'anger', 'fear', 'surprise'] = dspy.OutputField()

classify = dspy.Predict(Emotion)
classify(sentence="i started feeling a little vulnerable...")
# Prediction(sentiment='fear')

Literal w typie pola wyjściowego zastępuje cały akapit instrukcji „odpowiedz jednym z następujących słów i niczym więcej". Zamiast prosić model o format, deklarujemy go.

Moduł — technika promptowania jako klasa

Każdy wbudowany moduł to uogólniona technika promptowania, działająca z dowolną sygnaturą. Najbardziej podstawowy jest dspy.Predict — i wszystkie pozostałe są wewnętrznie na nim zbudowane:

classify = dspy.Predict('sentence -> sentiment: bool')
classify(sentence="it's a charming and often affecting journey.").sentiment
# True

Podmiana modułu na inny zmienia technikę bez zmiany sygnatury. dspy.ChainOfThought rozszerza sygnaturę pod spodem, dokładając pole reasoning, w którym model rozumuje przed wygenerowaniem właściwego wyjścia:

summarize = dspy.ChainOfThought('document -> summary', temperature=0.7)
response = summarize(document=document)

print(response.summary)     # streszczenie
print(response.reasoning)   # tok rozumowania, dodany przez moduł

Dokumentacja mówi wprost, że w wielu przypadkach samo podmienienie dspy.Predict na dspy.ChainOfThought poprawia jakość. Poza tym są dspy.ReAct do pętli agentowych z narzędziami i dspy.ProgramOfThought dla zadań wymagających kodu.

Moduły mają parametry uczone — czyli te elementy promptu i wag modelu, które da się dostroić — i komponują się w większe programy. Inspiracja jest wprost z modułów PyTorcha, tylko zastosowana do programów na modelach językowych. Kompozycja to zwykły Python z dowolnym przepływem sterowania:

class Hop(dspy.Module):
    def __init__(self, num_docs=10, num_hops=4):
        self.num_docs, self.num_hops = num_docs, num_hops
        self.generate_query = dspy.ChainOfThought('claim, notes -> query')
        self.append_notes = dspy.ChainOfThought(
            'claim, notes, context -> new_notes: list[str], titles: list[str]'
        )

    def forward(self, claim: str) -> list[str]:
        notes, titles = [], []

        for _ in range(self.num_hops):
            query = self.generate_query(claim=claim, notes=notes).query
            context = search(query, k=self.num_docs)
            prediction = self.append_notes(claim=claim, notes=notes, context=context)
            notes.extend(prediction.new_notes)
            titles.extend(prediction.titles)

        return dspy.Prediction(notes=notes, titles=list(set(titles)))

To jest wieloetapowe wyszukiwanie z pętlą i akumulacją notatek — napisane jako zwykła klasa, bez ani jednego promptu w kodzie. Magia zaczyna się dopiero przy kompilacji, gdy DSPy śledzi wywołania modelu wewnątrz tej pętli.

Metryka — bez niej to nie działa

Tu jest miejsce, w którym DSPy różni się od bibliotek do promptowania najmocniej i w którym najczęściej się z niego rezygnuje. DSPy jest frameworkiem uczenia maszynowego, więc wymaga automatycznej metryki — do śledzenia postępu i do optymalizacji.

Metryka to funkcja przyjmująca przykład i predykcję, zwracająca liczbę:

def validate_answer(example, pred, trace=None):
    return example.answer.lower() == pred.answer.lower()

Trzeci argument jest sprytnym detalem, który warto zrozumieć, bo pozwala tej samej funkcji obsłużyć dwa różne zastosowania:

def validate_context_and_answer(example, pred, trace=None):
    answer_match = example.answer.lower() == pred.answer.lower()
    context_match = any((pred.answer.lower() in c) for c in pred.context)

    if trace is None:   # ewaluacja albo optymalizacja — wartość ciągła
        return (answer_match + context_match) / 2.0
    else:               # bootstrapping demonstracji — decyzja tak/nie
        return answer_match and context_match

Przy ewaluacji chcemy wiedzieć jak dobrze, przy generowaniu demonstracji — czy w ogóle nadaje się jako przykład. Jedna funkcja, dwa tryby, rozróżniane obecnością śladu wykonania.

Do prostych zadań wystarczy dokładne dopasowanie albo F1. Do długich wyjść dokumentacja radzi rzecz, która na początku wygląda dziwnie, a przy dłuższym myśleniu jest jedyną sensowną: metryka sama powinna być małym programem DSPy, sprawdzającym kilka właściwości wyniku, prawdopodobnie z użyciem modelu w roli sędziego. I dodaje uczciwie, że trafienie w dobrą metrykę za pierwszym razem jest nieprawdopodobne — trzeba zacząć od czegoś prostego i iterować.

Do samego liczenia jest dspy.Evaluate, obsługujące zrównoleglenie na wątkach i pokazujące próbkę wejść, wyjść oraz wyników metryki.

Optymalizatory

Optymalizator to algorytm dostrajający parametry programu — prompty, wagi modelu albo jedno i drugie — pod zadaną metrykę. Przyjmuje trzy rzeczy: program, metrykę i kilka przykładów wejściowych. To ostatnie warto podkreślić: kilka. Dokumentacja mówi o pięciu albo dziesięciu przykładach, w tym niepełnych — samych wejściach, bez etykiet.

Mechanizm działania na przykładzie MIPROv2, opisany w dokumentacji w trzech etapach:

  1. Bootstrapping — uruchamia nieoptymalizowany program wielokrotnie na różnych wejściach, zbierając ślady zachowania każdego modułu, i zatrzymuje tylko te, które metryka oceniła wysoko,
  2. Propozycje z kontekstem — przegląda kod programu, dane i zebrane ślady, a na tej podstawie tworzy wiele kandydujących instrukcji dla każdego promptu w programie,
  3. Wyszukiwanie dyskretne — losuje mini-partie ze zbioru treningowego, proponuje kombinacje instrukcji i demonstracji, ocenia kandydata na mini-partii i optymalizacją bayesowską poprawia kolejne propozycje.

Dostępnych optymalizatorów jest jedenaście, w pięciu grupach:

  • Automatyczne uczenie few-shotLabeledFewShot (przykłady z danych z etykietami), BootstrapFewShot (moduł-nauczyciel generuje pełne demonstracje dla każdego etapu, walidowane metryką), BootstrapFewShotWithRandomSearch (to samo z losowym przeszukiwaniem i wyborem najlepszego programu), KNNFewShot (najbliżsi sąsiedzi jako demonstracje dla danego wejścia),
  • Automatyczna optymalizacja instrukcjiCOPRO (wspinaczka po zboczu), MIPROv2 (instrukcje i przykłady, optymalizacja bayesowska), SIMBA (próbkuje trudne przykłady o dużej zmienności wyjścia i każe modelowi introspekcyjnie przeanalizować własne porażki, tworząc reguły poprawy), GEPA (refleksja nad śladem wykonania programu z możliwością wykorzystania tekstowej informacji zwrotnej z domeny),
  • Automatyczne dostrajanie wagBootstrapFinetune, destylujący program oparty o prompty w aktualizacje wag. Wynikiem jest program o tych samych krokach, ale wykonywanych dostrojonym modelem zamiast promptowanym,
  • Transformacje programuEnsemble,
  • MetaoptymalizatoryBetterTogether, łączący optymalizację promptów i wag w konfigurowalnych sekwencjach (prompt → wagi → prompt). Uzasadnienie jest ładne: optymalizacja promptów odkrywa skuteczne dekompozycje zadania i strategie rozumowania, a optymalizacja wag specjalizuje model w ich wykonywaniu.

Optymalizatory się komponują — wynik MIPROv2 można podać ponownie do MIPROv2 albo do BootstrapFinetune, albo wyciągnąć pięć najlepszych kandydatów i zbudować z nich Ensemble. Dokumentacja opisuje to jako możliwość skalowania zarówno obliczeń w czasie wnioskowania, jak i obliczeń przed wnioskowaniem, czyli budżetu optymalizacji.

Który wybrać

Dokumentacja podaje przewodnik, który oszczędza pierwszą rundę zgadywania — i zaczyna go od uczciwego zastrzeżenia, że znalezienie właściwego optymalizatora i konfiguracji wymaga eksperymentowania:

  • około 10 przykładówBootstrapFewShot,
  • 50 przykładów lub więcejBootstrapFewShotWithRandomSearch,
  • chcesz zostać przy promptcie zero-shotMIPROv2 w konfiguracji dla zera przykładów, czyli sama optymalizacja instrukcji,
  • gotów na dłuższy przebieg (40+ prób) i masz 200+ przykładów, żeby nie przeuczyć → MIPROv2,
  • masz działający wynik na dużym modelu i potrzebujesz taniego w produkcjiBootstrapFinetune na małym modelu.

Samo użycie jest krótkie i wszystkie optymalizatory dzielą interfejs:

config = dict(max_bootstrapped_demos=4, max_labeled_demos=4,
              num_candidate_programs=10, num_threads=4)

optimizer = dspy.BootstrapFewShotWithRandomSearch(metric=moja_metryka, **config)
optimized = optimizer.compile(moj_program, trainset=zbior_treningowy)

Ile to kosztuje

Rzadka uczciwość, którą warto zacytować, bo w dokumentacjach narzędzi AI prawie nie występuje: typowy prosty przebieg optymalizacji kosztuje rzędu 2 USD i trwa około dziesięciu minut. Dokumentacja dodaje jednak ostrzeżenie: przy bardzo dużych modelach albo bardzo dużych zbiorach danych przebiegi mogą kosztować od kilku centów do dziesiątek dolarów.

Praktyczna konsekwencja przy wycenie: optymalizacja nie jest darmowa, ale jest jednorazowa. Płacimy raz za skompilowanie programu, a potem uruchamiamy w produkcji wynik. To zupełnie inny profil kosztowy niż pętla agentowa płacąca za każdy obrót.

Wynik jest plikiem, który da się przeczytać

Ten szczegół decyduje o tym, czy DSPy jest narzędziem inżynierskim, czy czarną skrzynką. Skompilowany program zapisuje się jednym wywołaniem:

optimized.save('artifacts/klasyfikator-zgloszen.json')

Powstały plik jest zwykłym JSON-em w czystym tekście, zawierającym wszystkie parametry i kroki programu źródłowego. Dokumentacja zaznacza wprost: zawsze można go przeczytać i zobaczyć, co optymalizator wygenerował. Wczytanie to instancja klasy programu i jedno wywołanie:

loaded = MojProgram()
loaded.load(path='artifacts/klasyfikator-zgloszen.json')

Wniosek jest ważniejszy niż samo API. Skompilowany prompt staje się artefaktem w repozytorium — wersjonowanym, podlegającym review i możliwym do porównania między wersjami. Zamiast czterotysięcznoznakowej stałej, której historii nikt nie zna, mamy plik, o którym wiadomo: który program go wygenerował, na jakiej metryce i na jakich danych.

Agenci z narzędziami

DSPy nie ogranicza się do jednokrokowych predykcji — ma wsparcie dla agentów używających narzędzi, w dwóch trybach.

Tryb pierwszy: dspy.ReAct, czyli w pełni zarządzany agent realizujący wzorzec Reasoning and Acting. Narzędzia to zwykłe funkcje Pythona z podpowiedziami typów i docstringiem — dokładnie tak, jak w innych frameworkach agentowych:

def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"The weather in {city} is sunny and 75°F"

def search_web(query: str) -> str:
    """Search the web for information."""
    ...

react_agent = dspy.ReAct(
    signature="question -> answer",
    tools=[get_weather, search_web],
    max_iters=5,
)

result = react_agent(question="What's the weather like in Tokyo?")
print(result.answer)
print("Wywołane narzędzia:", result.trajectory)

Trzy rzeczy w tym przykładzie mają znaczenie praktyczne. max_iters jest bezpiecznikiem budżetowym — tym samym, o którym pisaliśmy przy maxTurns w OpenAI Agents SDK, i z tego samego powodu. Pole trajectory w wyniku daje pełną historię rozumowania i wywołań narzędzi, więc diagnostyka „dlaczego agent zrobił to, co zrobił" nie wymaga dopisywania logowania. A wbudowana obsługa błędów oznacza, że nieudane wywołanie narzędzia nie przewraca całej pętli.

Najciekawsze jest jednak to, że agent z narzędziami też podlega optymalizacji. Dokumentacja pokazuje minimalny, w pełni uruchamialny przykład: agent dspy.ReAct odpowiadający na pytania przez wyszukiwanie w Wikipedii, optymalizowany MIPROv2 w tanim trybie light na pięciuset parach pytanie–odpowiedź. Innymi słowy: instrukcje, którymi agent decyduje, kiedy sięgnąć po narzędzie, są dostrajane pod metrykę, a nie pisane ręcznie.

Tryb drugi to ręczna obsługa narzędzi przez typy dspy.Tool i dspy.ToolCalls oraz własne sygnatury — dla przypadków, w których chcemy sami decydować o pętli wykonania. Metoda ToolCall.execute() jest przy tym dostępna od wersji 3.0.4b2, o czym dokumentacja uprzedza osobną notką.

Do tego dochodzi obsługa Model Context Protocol — po instalacji pip install -U "dspy[mcp]" narzędzia z dowolnego serwera MCP konwertuje się na narzędzia DSPy jedną linią:

async with Client("http://localhost:8000/mcp") as client:
    response = await client.list_tools()
    dspy_tools = [dspy.Tool.from_mcp_tool(client, tool) for tool in response.tools]

Warto zauważyć podział odpowiedzialności: DSPy nie zarządza połączeniami z serwerami MCP. Klienta dostarcza biblioteka mcp, a DSPy tylko adaptuje jej narzędzia — obsługując przy tym obie główne wersje jej SDK. To jest właściwa granica: jedna biblioteka nie próbuje być wszystkim.

Osobna uwaga dla osób wracających do DSPy po przerwie: dspy.Assert i mechanizm asercji są wycofane i niewspierane. Do wymuszania ograniczeń na wyjściu służy dziś dspy.Refine. Stara dokumentacja asercji nadal krąży po internecie i opisuje API, którego nie należy używać.

Rzeczy operacyjne, które decydują w praktyce

Cztery elementy spoza głównej narracji, o które potyka się każdy przy pierwszym poważnym użyciu.

Model wskazuje się łańcuchem dostawca/model, bo pod spodem jest LiteLLM. Działa OpenAI, Anthropic, Gemini, Vertex AI i kilkadziesiąt innych dostawców:

import dspy

lm = dspy.LM('anthropic/claude-sonnet-4-5-20250929')
dspy.configure(lm=lm)

Cache jest domyślnie włączony — i to jest pułapka numer jeden przy eksperymentowaniu. Powtórzone wywołanie z tymi samymi wejściami zwraca ten sam wynik, więc „sprawdziłem jeszcze raz i wyszło tak samo" nie jest żadnym dowodem. Do zróżnicowanych wyników służy rollout_id razem z niezerową temperaturą; DSPy haszuje wejścia wspólnie z tym identyfikatorem, więc różne wartości wymuszają nowe zapytanie, a kolejne wywołania z tą samą parą nadal korzystają z cache'u. Ważny detal: zmiana samego rollout_id przy temperature=0 nie zmieni wyniku. Cache można też wyłączyć wprost przez cache=False.

lm("Say this is a test!", rollout_id=1, temperature=1.0)

predict = dspy.Predict("question -> answer")
predict(question="What is 1 + 52?", config={"rollout_id": 5, "temperature": 1.0})

Modele przełącza się globalnie albo blokowo, przez dspy.configure i dspy.context — i oba mechanizmy są bezpieczne wątkowo, co przy zrównoleglonej ewaluacji nie jest szczegółem:

with dspy.context(lm=dspy.LM('openai/gpt-4o-mini')):
    response = qa(question="...")

Historia wywołań zawiera koszt. Każdy obiekt modelu trzyma pełną historię interakcji — wejścia, wyjścia, zużycie tokenów, koszt w dolarach, znacznik czasu i metadane. Rachunek za eksperyment jest więc widoczny w kodzie, a nie tylko w panelu dostawcy:

len(lm.history)
lm.history[-1].keys()
# dict_keys(['prompt', 'messages', 'kwargs', 'response', 'outputs',
#            'usage', 'cost', 'timestamp', 'uuid', 'model', ...])

Do tego strukturalne wyjątki: dspy.LMError jako nadklasa wszystkich awarii modelu i konkretne podklasy w rodzaju dspy.ContextWindowExceededError, pozwalające obsłużyć przekroczenie okna kontekstu inaczej niż awarię sieci.

Co z tego może wziąć zespół pracujący w PHP

DSPy jest w Pythonie i nie ma odpowiednika w PHP — więc trzeba to powiedzieć wprost i podać realistyczne warianty, a nie udawać, że da się to wpiąć w Laravela.

1. Narzędzie warsztatowe, nie produkcyjne

To jest wariant najtańszy i w większości przypadków wystarczający. Optymalizujemy prompt lokalnie, w Pythonie, a do aplikacji przenosimy wynik. Skompilowany program jest czytelnym JSON-em, więc wyciągnięcie z niego wygenerowanej instrukcji i wybranych przykładów few-shot jest kwestią przeczytania pliku.

Zysk: prompt w naszej aplikacji w PHP nie jest już wynikiem czyjejś intuicji, a wynikiem przebiegu optymalizacji pod metrykę, na naszych danych — z zapisanym śladem, skąd się wziął. Koszt: dwa dolary i dziesięć minut na przebieg, plus jednorazowe postawienie środowiska w Pythonie.

2. Osobna usługa dla złożonych pipeline'ów

Gdy program jest wieloetapowy — RAG z kilkoma przeskokami, agent z narzędziami, klasyfikacja z odwołaniem do bazy wiedzy — przenoszenie wyniku do PHP przestaje mieć sens, bo przenieślibyśmy tylko prompty, a nie logikę przepływu. Wtedy rozwiązaniem jest usługa w Pythonie wołana z Laravela po HTTP albo przez kolejkę, dokładnie tak, jak opisywaliśmy przy OpenAI Agents SDK: zadanie kolejkowe, jawny timeout, wyjątek na niezerowym kodzie wyjścia.

3. Metryka jako brama jakości w CI

To jest najbardziej wartościowa część do skopiowania i jedyna, która nie wymaga Pythona w ogóle. Sedno DSPy nie polega na optymalizatorach, a na jednym założeniu: jeśli nie masz metryki, nie masz inżynierii promptów — masz zgadywanie.

Ten sam pomysł da się wprowadzić w każdym stacku: zbiór przykładów z oczekiwanymi wynikami w repozytorium, test przechodzący przez nie w CI, próg, poniżej którego build nie przechodzi. W Laravelu to zwykły test z dostawcą danych, wywołujący nasz serwis modelu i sumujący trafienia. Kosztuje kilka godzin, a zamienia „zmieniłem prompt, chyba jest lepiej" na liczbę w pull requeście.

Uwaga praktyczna do takiego testu: wywołania modelu w CI kosztują i są niedeterministyczne, więc warto trzymać je w osobnej grupie testów, uruchamianej świadomie, a nie przy każdym commicie — i nagrywać odpowiedzi tam, gdzie sprawdzamy tylko logikę wokół modelu.

Pułapki

  • To framework uczenia maszynowego, nie biblioteka do promptów. Bez metryki i bez choćby kilkunastu przykładów DSPy nie da nic poza nieco czystszym kodem. To jest najczęstszy powód porzucania tego narzędzia po dwóch dniach,
  • Cache domyślnie włączony myli przy eksperymentach. Powtórzenie tego samego wywołania nie testuje niczego; do porównań potrzebny jest rollout_id z niezerową temperaturą albo cache=False,
  • Optymalizacja kosztuje. Dwa dolary za typowy przebieg, ale dziesiątki dolarów przy dużych modelach i zbiorach. Warto to policzyć, zanim ktoś odpali czterdzieści prób na najdroższym modelu,
  • Zmiana modelu to powód do ponownej kompilacji. Przenośność skompilowanych promptów między modelami jest jedną z obietnic DSPy, ale to obietnica lepszej przenośności niż promptów pisanych ręcznie — nie gwarancja, że stary artefakt jest nadal optymalny,
  • Ręczne dostrajanie sygnatur niweczy sens narzędzia. Dobór brzmienia jest zadaniem optymalizatora; nasze jest nazwać role pól sensownie i zostawić resztę,
  • Wersje główne przynosiły przebudowy API. Jesteśmy w linii 3.x, a nazewnictwo wciąż się porządkuje — dokumentacja odnotowuje trwającą zmianę nazwy z „teleprompter" na „optimizer". Przypnij wersję,
  • 660 otwartych zgłoszeń przy szybkim rozwoju oznacza, że przy nietypowym dostawcy modelu albo egzotycznym typie wyjścia warto poszukać w zgłoszeniach przed debugowaniem,
  • Brak odpowiednika w PHP. Każdy z trzech opisanych wariantów zakłada Pythona przynajmniej na etapie kompilacji promptu.

Podsumowanie

DSPy jest odpowiedzią na problem, który w większości projektów z modelami językowymi nie został nawet nazwany: prompt jest kodem, którego nikt nie testuje, nie wersjonuje sensownie i nie umie ocenić. Propozycja jest inżynierska — deklaruj zachowanie, komponuj moduły, zdefiniuj metrykę i pozwól kompilatorowi dobrać brzmienie. Co z tego wynika:

  • Projekt jest dojrzały i naukowo ugruntowany — 37 873 gwiazdki, licencja MIT, wersja 3.3.1 z 21 sierpnia 2026, seria publikacji ze Stanford NLP od 2022 roku,
  • Zacznij od metryki, nie od optymalizatora. Bez niej reszta narzędzia jest bezużyteczna, a z nią zysk pojawia się jeszcze przed pierwszą optymalizacją,
  • Sygnatury nazywaj semantycznie i nie dostrajaj ręcznieLiteral w typie wyjścia zastępuje akapit instrukcji o formacie,
  • Podmiana Predict na ChainOfThought jest najtańszą możliwą poprawą jakości i nie wymaga zmiany sygnatury,
  • Przy dziesięciu przykładach użyj BootstrapFewShot, przy pięćdziesięciu BootstrapFewShotWithRandomSearch, a MIPROv2 zostaw na przebiegi z 200+ przykładami,
  • Policz koszt przebiegu — około 2 USD i dziesięć minut typowo, dziesiątki dolarów w skrajnych konfiguracjach. Koszt jest jednorazowy, nie za każde wywołanie w produkcji,
  • Traktuj skompilowany program jak artefakt repozytorium — to czytelny JSON, więc podlega review i porównaniu między wersjami,
  • Pamiętaj o cache'u przy każdym eksperymencie i o tym, że lm.history pokazuje koszt w dolarach,
  • W stacku PHP użyj DSPy jako narzędzia warsztatowego — kompiluj prompt lokalnie, przenoś wynik do aplikacji; osobną usługę stawiaj tylko dla naprawdę wieloetapowych pipeline'ów,
  • Agenci też podlegają optymalizacjidspy.ReAct z narzędziami można kompilować pod metrykę, a pole trajectory daje pełną historię wywołań bez dopisywania logowania,
  • Nie używaj dspy.Assert — asercje są wycofane, zastąpił je dspy.Refine, a stara dokumentacja nadal krąży po internecie,
  • Metrykę jako bramę jakości w CI wprowadź niezależnie od DSPy. To najlepszy pojedynczy pomysł z tego frameworka i jedyny, który nie wymaga Pythona.

Licencja: DSPy jest rozpowszechniany na licencji MIT — permisywnej, bez copyleftu, bez warunków przy komercyjnym użyciu poza zachowaniem noty o prawach autorskich. Wolno go używać w projektach komercyjnych, modyfikować, wbudowywać w zamknięte produkty i sprzedawać rozwiązania na nim oparte, bez obowiązku publikowania czegokolwiek. Warto natomiast pamiętać o dwóch rzeczach, których licencja nie obejmuje. Pierwsza to koszty modeli: DSPy jest darmowy, a każde wywołanie w trakcie optymalizacji i w produkcji jest płatne u dostawcy według jego cennika. Druga to dane, które przechodzą przez optymalizację — zbiór treningowy jest wysyłany do modelu wielokrotnie, w wielu wariantach promptu, więc jeśli zawiera dane osobowe, to optymalizacja jest ich przetwarzaniem na skalę, której nie widać z komendy compile(). Przy takich danych warto zacząć od anonimizacji zbioru albo od modelu uruchamianego lokalnie, a nie od pierwszego przebiegu na cudzym API. Osobno godna wzmianki jest praktyka projektu: prace naukowe stojące za każdym optymalizatorem są publicznie dostępne, więc przy ocenie, czy dany algorytm ma sens dla naszego zadania, nie jesteśmy skazani na marketing.