Ihre DSPy-Feldbedingungen erreichen das Modell nie
DSPy verwirft Pydantic-Field-Metadaten, bevor das Schema den Prozess verlässt. Kein Structured-Output-Backend sieht diese Bedingungen je. Was stattdessen hilft.
DSPy verwirft Pydantic-Field-Metadaten, bevor das Schema den Prozess verlässt. Kein Structured-Output-Backend sieht diese Bedingungen je. Was stattdessen hilft.
Wenn Sie diese DSPy-Signatur schreiben:
import dspy, pydantic
from typing import Annotated
class Score(dspy.Signature):
text: str = dspy.InputField()
score: float = dspy.OutputField(ge=0.0, le=1.0, multiple_of=0.25)
gehen Sie vermutlich davon aus, dass das Modell einen Score zwischen 0 und 1 in Schritten von 0,25 ausgeben muss. Das tut es nicht. Geben Sie das Schema aus, das DSPy tatsächlich für Structured Outputs ableitet:
from dspy.adapters.json_adapter import _get_structured_outputs_response_format
print(_get_structured_outputs_response_format(Score, True).model_json_schema())
# {'properties': {'score': {'title': 'Score', 'type': 'number'}}, ...}
ge, le und multiple_of sind verschwunden. Die Ableitung von der Signatur zum Modell verwirft in DSPy die Pydantic-Field-Metadaten, bevor das Schema Ihren Prozess verlässt. Kein Structured-Output-Backend sieht diese Bedingungen je, weder das von OpenAI noch das von vLLM noch irgendeine Grammar-Engine. Ihre Pipeline besteht die Typprüfung, Ihre Ausgaben lassen sich parsen, und ein score von 0.37 läuft durch, bis weiter unten etwas darüber stolpert. Wir sind darauf gestoßen, als wir GRID an DSPy angebunden haben, und haben es gegen dspy 3.2.1 verifiziert. Es betrifft alle Backends gleichermaßen.
Daraus folgen zwei Dinge für die Praxis:
tags: set[str] übersteht die Ableitung und wird zu uniqueItems. Literal["a", "b"] übersteht sie. Verschachtelte Pydantic-Modelle überstehen sie. OutputField(multiple_of=...) nicht.
GRID ist unsere Engine für Constrained Decoding (Apache-2.0). Ihr Grundsatz lautet, dass nichts still scheitert: Jede Bedingung in einem Schema wird entweder von der Token-Maske durchgesetzt, namentlich protokolliert, sodass Sie genau wissen, was Sie nachvalidieren müssen, oder von vornherein als nicht unterstützt ausgewiesen. Dieser Grundsatz erweist sich als das fehlende Stück für typisierte Pipelines:
pip install grid-guardrail dspy
from grid.integrations.dspy_adapter import GridJSONAdapter, assert_enforceable
adapter = GridJSONAdapter(strict=True)
dspy.configure(adapter=adapter)
class Extract(dspy.Signature):
text: str = dspy.InputField()
verdict: str = dspy.OutputField()
tags: set[str] = dspy.OutputField() # set -> uniqueItems
program = dspy.Predict(Extract)
assert_enforceable(program, adapter)
# SignatureNotEnforceable: strict: uniqueItems at $.tags
Diese Exception greift, während Sie das Programm bauen, nicht drei Wochen später, wenn ein doppelter tag einen Join beschädigt. Lassen Sie strict=True weg, erhalten Sie dieselbe Information als Daten statt als Fehler:
adapter = GridJSONAdapter()
adapter.recorded_paths_for(Extract) # {'$.tags': {'uniqueItems'}}
recorded_paths_for ist der Ehrlichkeitsgrundsatz als API. Die Methode liefert genau die benannten Bedingungen zurück, die GRID akzeptiert, aber nicht per Maske durchgesetzt hat, verortet an dem Ausgabefeld, zu dem sie gehören. Ihr Validierungscode prüft also $.tags auf Eindeutigkeit und sonst nichts. (recorded_for liefert dieselbe Information als flaches Set von Namen.) Bei den meisten aus Pydantic abgeleiteten Signaturen bleibt die Menge leer, denn Enum- und Literal-Felder, verschachtelte Modelle und Pflichtschlüssel liegen alle in der einfachen Region von JSON Schema. Ab diesem Punkt wird die Parse-Retry-Mechanik in Ihrem Framework zu totem Gewicht, weil typisierte Felder gar nicht fehlerhaft ankommen können.
Dieselbe Prüfung läuft repo-weit als CI-Gate:
$ python -m grid.integrations.dspy_check src/pipelines.py --strict
ENFORCEABLE Summarize
RECORDED Extract [$.tags: uniqueItems]
2 signature(s): 1 enforceable, 1 with recorded residue, 0 declared unsupported
$ echo $? # --strict: nonzero unless everything is mask-enforceable
1
Das set[str] einer Kollegin scheitert am PR, nicht drei Wochen später an der Pipeline.
Gegen einen Server mit GRID (unsere vLLM-Integration) verlagert ein einziges Argument die Durchsetzung auf die Serverseite:
GridJSONAdapter(mode="server") # fügt die kompilierte Grammatik bei jeder Anfrage hinzu
Der Client-Modus, die Voreinstellung, ändert nichts an Ihren Requests und funktioniert schon heute gegen jeden OpenAI-kompatiblen Endpoint.
Die Fälle, in denen man die Finger davon lässt, verdienen dieselbe Klarheit. Wenn Ihre Signaturen aus einer Handvoll Enum- und String-Formen bestehen, die Sie ohnehin end-to-end testen, reichen die nativen Structured Outputs Ihres Anbieters aus, und dieser Adapter fügt eine Abhängigkeit für eine leere Restmenge hinzu. Der Adapter verdient seinen Platz dort, wo Signaturen aus vielen Händen stammen, sich wöchentlich ändern und Systeme speisen, in denen "geparst" und "korrekt" zwei verschiedene Wörter sind.
Alles oben Genannte ist gemessen und eingecheckt. Die vollständigen JSONSchemaBench-Ergebnisse der Engine (11.306 reale Schemata, drei Engines, eine Maschine, mit Status pro Schema im Repository) finden Sie unter github.com/evolutionIdGmbH/grid, einschließlich der Zeilen, in denen wir verlieren.