Technical Insights

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.

Posted by
Mohsen Arjmandi
Posted date
August 11, 2026

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:

  1. Bedingungen gehören in die Annotation, nicht in Field-kwargs. tags: set[str] übersteht die Ableitung und wird zu uniqueItems. Literal["a", "b"] übersteht sie. Verschachtelte Pydantic-Modelle überstehen sie. OutputField(multiple_of=...) nicht.
  2. Das sollten Sie zur Build-Zeit erfahren, nicht in der Produktion. Genau das haben wir gebaut.

Der Adapter: nicht durchsetzbare Signaturen scheitern beim Build

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.

Wann Sie das nicht einsetzen sollten

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.

Über uns
Seit mehr als zwei Jahrzehnten unterstützt evolutionID Organisationen dabei, Klarheit und Kontrolle in ihre Identitäts‑ und Zugangsprozesse zu bringen. Wir konzentrieren uns auf das Wesentliche: sichere, verlässliche Abläufe, die einfach zu bedienen sind und langfristig bestehen.W

ir verbinden Physical Identity & Access Management (PIAM), Karten‑ und Mitarbeitermanagement sowie RFID‑gestützte Workflows zu einem stimmigen Gesamtkonzept. Unsere modularen Bausteine ermöglichen es, Identitäts‑ und Zugangssysteme Schritt für Schritt weiterzuentwickeln – ohne funktionierende Prozesse zu unterbrechen. Das Ergebnis: weniger Komplexität, mehr Transparenz und mehr Sicherheit im täglichen Betrieb.

Als langfristiger Partner begleiten wir unsere Kunden Schritt für Schritt – von Analyse und Architektur über Implementierung und Migration bis hin zu langfristigem Support. Mit Teams in München, Bonn und Frankfurt arbeiten wir eng mit Organisationen in der gesamten DACH‑Region zusammen, um Zugangsstrukturen zu schaffen, die sicher, stabil und bereit für alles sind, was kommt.