Adaptoare și ieșiri tipizate: ChatAdapter, JSONAdapter, XMLAdapter în DSPy & Programare Algoritmică LLM

Adaptorul transformă semnătura, exemplele și intrările în mesaje pentru model și convertește răspunsul înapoi în valori Python de tipul declarat.

Sintaxă & invocare
dspy.configure(adapter=dspy.JSONAdapter())
with dspy.context(adapter=dspy.XMLAdapter()):
    ...

Adaptorul este stratul dintre semnătură și model. Ciclul lui are cinci pași: pregătire (folosește funcții native ale modelului, ca apelul de unelte), formatare (mesajul de sistem cu descrierea câmpurilor și instrucțiunile, apoi exemplele și intrările), apelul la model, post-procesare și parsare. Parsarea convertește fiecare câmp în tipul declarat cu Pydantic, deci ieșirile pot fi bool, float, Literal, list[str], modele Pydantic sau tipuri DSPy.

ChatAdapter delimitează câmpurile cu marcaje [[ ## nume ## ]] și, implicit, repetă cererea prin JSONAdapter dacă răspunsul nu poate fi parsat. JSONAdapter cere un obiect JSON și folosește, când modelul îl acceptă, modul de ieșire structurată response_format cu schemă JSON. XMLAdapter folosește etichete <camp>...</camp>. TwoStepAdapter lasă modelul principal să răspundă liber și folosește un al doilea model pentru extragerea câmpurilor.

Adaptorul implicit diferă între cod și documentație: în dspy 3.4.1 (PyPI), Predict folosește ChatAdapter dacă nu ai configurat altul, iar documentația de pe dspy.ai, construită din ramura principală, descrie deja XMLAdapter(use_native_function_calling=True) ca implicit. Dacă formatul contează, setează adaptorul explicit cu dspy.configure(adapter=...) sau dspy.context(adapter=...). DSPy nu alege automat adaptorul după model.

Concepte cheie și caracteristici

Concept / Parametru Descriere practică
dspy.ChatAdapter(use_native_function_calling=False, use_json_adapter_fallback=True) Marcaje [[ ## camp ## ]]; la eroare de parsare reîncearcă o dată cu JSONAdapter.
dspy.JSONAdapter(use_native_function_calling=True) Ieșire JSON; folosește ieșirea structurată a furnizorului când e disponibilă.
dspy.XMLAdapter(...) Etichete XML pentru câmpuri, <item> pentru liste.
dspy.TwoStepAdapter(extraction_model) Două apeluri: răspuns liber, apoi extragerea câmpurilor cu extraction_model.
dspy.Image Intrare de tip imagine: URL, data URI, bytes sau PIL. Fișierele locale se citesc doar cu dspy.Image.from_path(), iar descărcarea se face cu from_url().
dspy.History Istoricul conversației, ca listă de dicționare cu cheile câmpurilor din semnătură; adaptorul îl transformă în mesaje separate.
dspy.Reasoning Câmp de raționament care folosește modul nativ de raționament al modelului, dacă există.

Exemple practice și scenarii reale

Exemplul 1: Ieșire structurată cu un model Pydantic

import dspy
from pydantic import BaseModel

class Factura(BaseModel):
    furnizor: str
    total: float
    moneda: str

class ExtrageFactura(dspy.Signature):
    """Extrage datele facturii din text."""
    text: str = dspy.InputField()
    factura: Factura = dspy.OutputField()

dspy.configure(lm=dspy.LM("openai/gpt-6-luna"), adapter=dspy.JSONAdapter())
extrage = dspy.Predict(ExtrageFactura)
rez = extrage(text="Factura nr. 381 de la Alfa SRL, total de plată 1.250,40 lei.")
print(rez.factura.total)

Exemplul 2: Alt adaptor doar pentru un bloc și verificarea promptului

with dspy.context(adapter=dspy.XMLAdapter()):
    rez = extrage(text="...")
dspy.inspect_history(n=1)   # mesajele exacte trimise modelului

Exemplul 3: Imagine ca intrare

descrie = dspy.Predict("imagine: dspy.Image -> descriere")
rez = descrie(imagine=dspy.Image.from_path("bon.jpg"))
print(rez.descriere)

Exemplul 4: Conversație cu dspy.History

După tutorialul oficial: fiecare tură adaugă în istoric intrările și ieșirile ei.

class QA(dspy.Signature):
    question: str = dspy.InputField()
    history: dspy.History = dspy.InputField()
    answer: str = dspy.OutputField()

predict = dspy.Predict(QA)
history = dspy.History(messages=[])

for question in ["Ce este TCP?", "Și prin ce diferă de UDP?"]:
    outputs = predict(question=question, history=history)
    history.messages.append({"question": question, **outputs})

Greșeli frecvente și capcane

  • Scrii dspy.Image("poza.jpg") ca în codul vechi. Din DSPy 3.3, constructorul nu mai citește fișiere și nu mai descarcă URL-uri; folosește Image.from_path() sau Image.from_url().
  • Nu observi că o eroare de parsare dublează apelurile. ChatAdapter reîncearcă implicit cu JSONAdapter; în teste, setează use_json_adapter_fallback=False ca să vezi eroarea.
  • Presupui un anumit adaptor implicit. Versiunea publicată și documentația curentă diferă; setează adaptorul explicit.
  • Folosești TwoStepAdapter fără să socotești costul: fiecare predicție face două apeluri.

Noțiuni și module înrudite