
Dspy Adapters
- 7 installs
- 11 repo stars
- Updated June 28, 2026
- lebsral/dspy-programming-not-prompting-lms-skills
Helps with ai & agent building tasks.
About
dspy-adapters is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- dspy-adapters
- AI & Agent Building
- AI-coding skill
Dspy Adapters by the numbers
- 7 all-time installs (skills.sh)
- Ranked #12,545 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lebsral/dspy-programming-not-prompting-lms-skills --skill dspy-adaptersAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 7 |
|---|---|
| repo stars | ★ 11 |
| Last updated | June 28, 2026 |
| Repository | lebsral/dspy-programming-not-prompting-lms-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Control Prompt Formatting with DSPy Adapters
Adapters sit between your DSPy modules and the language model. They control how signatures get turned into prompts and how LM responses get parsed back into typed Python objects. Most of the time the default adapter just works -- but when you need tighter control over structured output, or you are working with reasoning models that struggle with formatting, adapters give you that control.
What adapters do
Every time a DSPy module calls an LM, an adapter handles two jobs:
1. Format -- Convert the signature, few-shot demos, and inputs into a prompt (system message + user/assistant messages). 2. Parse -- Extract the output fields from the LM's raw text response and cast them to the declared Python types.
You never call adapters directly. You configure one globally or per-module, and DSPy uses it behind the scenes.
The four built-in adapters
| Adapter | How it formats | How it parses | Best for |
|---|---|---|---|
ChatAdapter | Field markers like [[ ## field_name ## ]] | Splits on field headers | General use (default) |
JSONAdapter | Requests JSON output with field schema | json_repair + type casting | Reliable structured output |
XMLAdapter | XML tags like <field_name>value</field_name> | Regex on XML tags | Models that handle XML well |
TwoStepAdapter | Natural language prompt (no formatting constraints) | Sends raw response to a second LM for extraction | Reasoning models (o1, o3) |
ChatAdapter (the default)
ChatAdapter is what DSPy uses unless you say otherwise. It formats prompts with field delimiters and parses responses by looking for those same delimiters in the output.
import dspy
# ChatAdapter is used automatically -- no configuration needed
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
classify = dspy.ChainOfThought("text -> label")
result = classify(text="Great product!")
print(result.label)Constructor parameters
dspy.ChatAdapter(
callbacks=None, # Optional list of BaseCallback objects
use_native_function_calling=False, # Use native function calling features
native_response_types=None, # Output field types handled natively by the LM
use_json_adapter_fallback=True, # Fall back to JSONAdapter on parse failure
)Key behavior
- Formats each field with
[[ ## field_name ## ]]delimiters in the prompt. - Few-shot demos become alternating user/assistant message pairs.
- If parsing fails, it automatically retries using
JSONAdapter(unless you setuse_json_adapter_fallback=False). - Works well with most models out of the box.
When to use ChatAdapter explicitly
You rarely need to instantiate ChatAdapter yourself. Do it when you want to:
- Disable the JSON fallback:
dspy.ChatAdapter(use_json_adapter_fallback=False) - Enable native function calling:
dspy.ChatAdapter(use_native_function_calling=True)
adapter = dspy.ChatAdapter(use_json_adapter_fallback=False)
dspy.configure(lm=lm, adapter=adapter)JSONAdapter
JSONAdapter extends ChatAdapter and instructs the LM to respond with a JSON object matching your output fields. It uses the provider's native structured output mode when available, falling back to response_format: {"type": "json_object"}.
import dspy
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
adapter = dspy.JSONAdapter()
dspy.configure(lm=lm, adapter=adapter)
# Now all modules request JSON output from the LM
classify = dspy.Predict("text -> label: str, confidence: float")
result = classify(text="Great product!")
print(result.label) # positive
print(result.confidence) # 0.95Constructor parameters
dspy.JSONAdapter(
callbacks=None, # Optional list of BaseCallback objects
use_native_function_calling=True, # Enabled by default (unlike ChatAdapter)
)Key behavior
- Tells the LM to respond with a JSON object whose keys match the output field names.
- Includes type hints in the prompt (e.g.,
"(must be formatted as a valid Python str)"). - Parses responses with
json_repairfor resilience against minor formatting errors. - Validates that all required output fields are present and casts values to their annotated types.
- Raises
AdapterParseErrorif the response cannot be parsed as a JSON object.
When to use JSONAdapter
Use JSONAdapter when you need more reliable structured output, especially with:
- Complex Pydantic output types (nested models, lists of objects)
- Models that sometimes break the
[[ ## field ## ]]format - Applications where parse failures are costly (production APIs, batch pipelines)
from pydantic import BaseModel
class Invoice(BaseModel):
vendor: str
total: float
line_items: list[dict]
class ExtractInvoice(dspy.Signature):
"""Extract invoice details from the document text."""
document: str = dspy.InputField()
invoice: Invoice = dspy.OutputField()
lm = dspy.LM("openai/gpt-4o") # or "anthropic/claude-sonnet-4-5-20250929", etc.
adapter = dspy.JSONAdapter()
dspy.configure(lm=lm, adapter=adapter)
extractor = dspy.Predict(ExtractInvoice)
result = extractor(document="Invoice #1234 from Acme Corp. Total: $1,250.00 ...")
print(result.invoice.vendor) # Acme Corp
print(result.invoice.total) # 1250.0
print(result.invoice.line_items) # [...]XMLAdapter
XMLAdapter extends ChatAdapter and wraps input/output fields in XML tags like <field_name>value</field_name> instead of [[ ## field ## ]] delimiters. Some models respond more reliably to XML-structured prompts.
import dspy
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
adapter = dspy.XMLAdapter()
dspy.configure(lm=lm, adapter=adapter)
classify = dspy.Predict("text -> label: str, confidence: float")
result = classify(text="Great product!")Constructor parameters
dspy.XMLAdapter(
callbacks=None, # Optional list of BaseCallback objects
)Key behavior
- Formats fields with XML tags:
<field_name>value</field_name>. - Parses responses by matching XML tag patterns with regex.
- Falls back to
JSONAdapteron parse failure (likeChatAdapter). - Simpler constructor than ChatAdapter -- only takes
callbacks.
When to use XMLAdapter
Consider XMLAdapter when:
- Your model handles XML-structured prompts better than field delimiters.
- You want a lighter-weight alternative to JSONAdapter that does not require native structured output support.
TwoStepAdapter
TwoStepAdapter is designed for reasoning models (like OpenAI's o1 and o3 series) that produce better answers when they are not forced into a rigid output format. It splits the work into two steps:
1. Step 1 -- The main LM gets a natural language prompt with no formatting constraints. It can think freely. 2. Step 2 -- A smaller, cheaper extraction model reads the main LM's response and extracts the structured output fields.
import dspy
# Main LM: a reasoning model that struggles with structured output
main_lm = dspy.LM("openai/o3-mini", max_tokens=16000, temperature=1.0) # or another reasoning model
# Extraction model: a fast, cheap model for parsing
extraction_lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-haiku-3-5-20241022", etc.
adapter = dspy.TwoStepAdapter(extraction_model=extraction_lm)
dspy.configure(lm=main_lm, adapter=adapter)
# The reasoning model thinks freely; gpt-4o-mini extracts the answer
solver = dspy.ChainOfThought("question -> answer")
result = solver(question="What is the sum of the first 100 prime numbers?")
print(result.answer)Constructor parameters
dspy.TwoStepAdapter(
extraction_model=dspy.LM("openai/gpt-4o-mini"), # Required: cheap LM for extraction
)Key behavior
- The main LM receives a simplified, natural language prompt -- no field delimiters or JSON instructions.
- The extraction model uses
ChatAdapterinternally to parse the main LM's freeform response into structured fields. - Adds cost (two LM calls per prediction) but improves quality for reasoning models.
When to use TwoStepAdapter
Use TwoStepAdapter when:
- Your main LM is a reasoning model (o1, o3, o3-mini) that performs worse when forced to follow formatting rules.
- You want the best reasoning quality and can tolerate the extra latency/cost of a second LM call.
- The extraction step is straightforward (the reasoning is the hard part, not the formatting).
Decision table: which adapter to use
| Situation | Adapter | Why |
|---|---|---|
| General use, most models | ChatAdapter (default) | Works out of the box, no config needed |
| Need reliable JSON/Pydantic output | JSONAdapter | Stricter parsing, native structured output support |
| Complex nested output types | JSONAdapter | Better at complex schemas than field-delimiter parsing |
| Model responds better to XML structure | XMLAdapter | XML tags instead of field delimiters |
| Using reasoning models (o1, o3) | TwoStepAdapter | Reasoning models perform worse with format constraints |
| Parse failures in production | JSONAdapter | More resilient parsing with json_repair |
| Fastest iteration, prototyping | ChatAdapter (default) | Zero config, good enough for most tasks |
Configuring adapters
Global configuration
Set the adapter for all modules at once:
import dspy
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
adapter = dspy.JSONAdapter()
dspy.configure(lm=lm, adapter=adapter)Temporary override with dspy.context
Switch adapters for a block of code without changing the global config:
json_adapter = dspy.JSONAdapter()
# Use JSONAdapter just for this block
with dspy.context(adapter=json_adapter):
result = extractor(document=text)
# Back to the default ChatAdapter outside the blockPer-module adapter assignment
Assign different adapters to different modules using set_adapter():
class Pipeline(dspy.Module):
def __init__(self):
self.classify = dspy.Predict("text -> label")
self.extract = dspy.Predict(ExtractDetails)
def forward(self, text):
# Classification is simple -- default ChatAdapter is fine
label = self.classify(text=text)
return self.extract(text=text)
pipeline = Pipeline()
# Use JSONAdapter only for the extraction step
pipeline.extract.set_adapter(dspy.JSONAdapter())Other adapters
- BamlAdapter (
dspy.adapters.baml_adapter) — exists in the DSPy source but is undocumented and likely experimental. It integrates with the BAML structured output framework. Do not use in production until it has official documentation.
Custom adapters
You can build your own adapter by subclassing the base Adapter class. This is advanced -- only needed if the built-in adapters do not fit your use case.
import dspy
from dspy.adapters import Adapter
class MyAdapter(Adapter):
def format(self, signature, demos, inputs, messages=None):
"""Convert signature + inputs into a list of messages for the LM."""
system_msg = {"role": "system", "content": f"Task: {signature.instructions}"}
user_msg = {"role": "user", "content": str(inputs)}
return [system_msg, user_msg]
def parse(self, signature, completion):
"""Extract output fields from the LM's raw response text."""
# Your custom parsing logic here
fields = {}
for field_name in signature.output_fields:
fields[field_name] = completion.strip()
return fields
adapter = MyAdapter()
dspy.configure(lm=lm, adapter=adapter)Override format() to control how prompts are built and parse() to control how responses are read. The return from parse() should be a dict mapping output field names to their values.
Common patterns
Fallback chain: ChatAdapter with JSON fallback (default)
By default, ChatAdapter already falls back to JSONAdapter on parse failure. This gives you the best of both worlds -- fast field-delimiter parsing most of the time, with JSON as a safety net.
# This is the default behavior -- you get it for free
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
# ChatAdapter is used, with automatic JSONAdapter fallback on failureTo disable this fallback:
adapter = dspy.ChatAdapter(use_json_adapter_fallback=False)
dspy.configure(lm=lm, adapter=adapter)Reasoning model setup
# Full setup for reasoning models
reasoning_lm = dspy.LM("openai/o3-mini", max_tokens=16000, temperature=1.0) # or another reasoning model
extraction_lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-haiku-3-5-20241022", etc.
adapter = dspy.TwoStepAdapter(extraction_model=extraction_lm)
dspy.configure(lm=reasoning_lm, adapter=adapter)Mixed adapter pipeline
import dspy
lm = dspy.LM("openai/gpt-4o") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm) # default ChatAdapter
class MixedPipeline(dspy.Module):
def __init__(self):
self.summarize = dspy.ChainOfThought("document -> summary")
self.extract = dspy.Predict(ExtractInvoice)
def forward(self, document):
summary = self.summarize(document=document)
return self.extract(document=document)
pipeline = MixedPipeline()
# ChatAdapter for summarization (freeform text is fine)
# JSONAdapter for extraction (need reliable structured output)
pipeline.extract.set_adapter(dspy.JSONAdapter())Gotchas
- Claude switches to JSONAdapter for every task. JSONAdapter is not always better — it adds JSON formatting constraints to the prompt that can degrade reasoning quality on open-ended tasks like summarization or creative writing. Use the default ChatAdapter for freeform text output and reserve JSONAdapter for tasks that need reliable structured output (Pydantic models, typed fields, API responses).
- Claude forgets that ChatAdapter already falls back to JSONAdapter. By default,
ChatAdapterautomatically retries withJSONAdapterwhen parsing fails. If you are only switching to JSONAdapter to fix occasional parse errors, the default fallback may already handle it. Only switch globally when you need JSON for every call. - Claude uses TwoStepAdapter with a cheap extraction model that is too weak. The extraction model must be capable enough to reliably parse the reasoning model output into structured fields. GPT-4o-mini works well for most extraction; do not use a model weaker than that or you trade reasoning quality for parse failures.
- Claude configures TwoStepAdapter but does not set the main LM to a reasoning model. TwoStepAdapter adds latency and cost (two LM calls per prediction). It only makes sense when the main LM is a reasoning model (o1, o3) that performs worse with formatting constraints. For standard models like GPT-4o or Claude, use ChatAdapter or JSONAdapter directly.
- Claude does not use `set_adapter()` for per-module overrides. When a pipeline has steps that need different adapters (e.g., freeform summarization + structured extraction), Claude sets the adapter globally instead of per-module. Use
pipeline.extract.set_adapter(dspy.JSONAdapter())to override only the modules that need it.
Additional resources
- dspy.ChatAdapter API docs
- dspy.JSONAdapter API docs
- dspy.XMLAdapter API docs
- dspy.TwoStepAdapter API docs
- Adapter base class API docs
- reference.md — constructor parameters, key methods, behavior details
- examples.md — JSONAdapter with Pydantic, TwoStepAdapter for reasoning models, per-module adapter switching
Cross-references
Install any skill: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill <name>- `/dspy-signatures` -- Define the input/output fields that adapters format and parse
- `/dspy-lm` -- Configure the language model that adapters communicate with
- `/dspy-modules` -- Modules that use adapters under the hood (Predict, ChainOfThought, etc.)
- Install `/ai-do` if you do not have it — it routes any AI problem to the right skill and is the fastest way to work:
npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill ai-do
last_audit:
date: 2026-05-01
score: 38/38
versions:
dspy: 3.2.1
[
{
"prompt": "My DSPy program extracts invoice data into a Pydantic model but sometimes the default adapter fails to parse the output correctly. How do I fix this?",
"expected_output": "Switch to JSONAdapter for more reliable structured output parsing",
"assertions": [
"Recommends dspy.JSONAdapter for reliable Pydantic/structured output",
"Shows how to configure it: dspy.configure(lm=lm, adapter=dspy.JSONAdapter())",
"Explains that ChatAdapter already falls back to JSONAdapter by default, so this makes it the primary parser",
"Does NOT recommend TwoStepAdapter for this use case (standard models do not need it)"
]
},
{
"prompt": "I am using OpenAI o3-mini for a complex reasoning task but the model keeps breaking the output format. How do I get structured output from a reasoning model?",
"expected_output": "Use TwoStepAdapter with a cheap extraction model",
"assertions": [
"Recommends dspy.TwoStepAdapter for reasoning models",
"Shows extraction_model as a separate, cheaper LM (e.g., gpt-4o-mini)",
"Explains that reasoning models perform worse when forced to follow formatting constraints",
"Shows the two-step process: reasoning model thinks freely, extraction model parses",
"Sets temperature=1.0 and max_tokens appropriately for the reasoning model"
]
},
{
"prompt": "I have a DSPy pipeline where one step does summarization and another extracts structured metadata. The summarization works fine with the default adapter but the metadata extraction needs more reliable JSON parsing. How do I use different adapters for different steps?",
"expected_output": "Use set_adapter() to assign JSONAdapter to just the extraction module",
"assertions": [
"Uses module.set_adapter(dspy.JSONAdapter()) for per-module override",
"Does NOT change the global adapter (summarization works fine with ChatAdapter)",
"Explains that set_adapter() overrides the global adapter for that module only",
"Alternatively mentions dspy.context(adapter=...) for temporary block-level overrides"
]
}
]
dspy-adapters -- Worked Examples
Example 1: Using JSONAdapter for reliable structured output
Extract structured product reviews into a Pydantic model with JSONAdapter for reliable parsing. This pattern is useful when you need guaranteed valid JSON from the LM -- for example, writing results to a database or returning them from an API.
import dspy
from pydantic import BaseModel, Field
from typing import Literal, Optional
class ReviewAnalysis(BaseModel):
sentiment: Literal["positive", "negative", "mixed"]
key_topics: list[str] = Field(description="Main topics mentioned in the review")
purchase_intent: bool = Field(description="Whether the reviewer would buy again")
summary: str = Field(description="One-sentence summary of the review")
class AnalyzeReview(dspy.Signature):
"""Analyze a product review and extract structured insights."""
review_text: str = dspy.InputField(desc="Raw product review text")
analysis: ReviewAnalysis = dspy.OutputField()
# --- Usage ---
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
adapter = dspy.JSONAdapter()
dspy.configure(lm=lm, adapter=adapter)
analyze = dspy.Predict(AnalyzeReview)
# Single review
result = analyze(
review_text="I've been using this keyboard for 3 months now. The mechanical switches "
"feel great and the build quality is solid. Battery life is disappointing though -- "
"I have to charge it every 4 days. Would still recommend it for the price."
)
review = result.analysis
print(review.sentiment) # mixed
print(review.key_topics) # ["mechanical switches", "build quality", "battery life", "price"]
print(review.purchase_intent) # True
print(review.summary) # "Good keyboard with great switches and build quality but disappointing battery life."
# Batch processing -- JSONAdapter gives consistent structure across all items
reviews = [
"Absolute garbage. Broke after one week. Returning immediately.",
"Best purchase I've made this year. Works exactly as advertised.",
"It's fine. Does what it says. Nothing special but no complaints either.",
]
for text in reviews:
r = analyze(review_text=text)
a = r.analysis
print(f"[{a.sentiment}] intent={a.purchase_intent} -- {a.summary}")Key points:
JSONAdapterinstructs the LM to respond with a JSON object matching the Pydantic schema- The adapter uses
json_repairunder the hood, so minor formatting issues in the LM response are fixed automatically - Nested Pydantic models, lists, and Literal types all work reliably with JSON output
- If you are hitting parse errors with the default
ChatAdapter, switching toJSONAdapteris often the fix
Example 2: TwoStepAdapter for complex extraction with a reasoning model
Use a reasoning model (o3-mini) for a hard analytical task, then extract structured output with a cheap model. This pattern works well when the thinking is the hard part -- the extraction is easy once the reasoning is done.
import dspy
from pydantic import BaseModel
from typing import Literal
class ContractRisk(BaseModel):
clause: str
risk_level: Literal["low", "medium", "high", "critical"]
explanation: str
recommended_action: str
class ContractAnalysis(BaseModel):
overall_risk: Literal["low", "medium", "high", "critical"]
risks: list[ContractRisk]
missing_clauses: list[str]
recommendation: str
class AnalyzeContract(dspy.Signature):
"""Analyze a contract for legal risks, missing protections, and problematic clauses.
Be thorough -- identify every potential issue."""
contract_text: str = dspy.InputField(desc="Full text of the contract")
party_name: str = dspy.InputField(desc="Name of the party we represent")
analysis: ContractAnalysis = dspy.OutputField()
# --- Usage ---
# Reasoning model for deep analysis
reasoning_lm = dspy.LM("openai/o3-mini", max_tokens=16000, temperature=1.0) # or another reasoning model
# Cheap model just for extracting structure from the reasoning output
extraction_lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-haiku-3-5-20241022", etc.
adapter = dspy.TwoStepAdapter(extraction_model=extraction_lm)
dspy.configure(lm=reasoning_lm, adapter=adapter)
analyze = dspy.ChainOfThought(AnalyzeContract)
result = analyze(
contract_text="""
SERVICE AGREEMENT between Acme Corp ("Provider") and ClientCo ("Client").
1. TERM: This agreement is effective for 36 months with automatic renewal.
2. PAYMENT: Client shall pay $10,000/month, due within 30 days of invoice.
3. TERMINATION: Provider may terminate with 30 days notice. Client may terminate
with 90 days notice and payment of remaining contract value.
4. LIABILITY: Provider's total liability shall not exceed fees paid in the
prior 3 months.
5. IP: All work product created during the engagement belongs to Provider.
""",
party_name="ClientCo",
)
analysis = result.analysis
print(f"Overall risk: {analysis.overall_risk}")
for risk in analysis.risks:
print(f"\n[{risk.risk_level.upper()}] {risk.clause}")
print(f" Issue: {risk.explanation}")
print(f" Action: {risk.recommended_action}")
print(f"\nMissing clauses: {analysis.missing_clauses}")
print(f"Recommendation: {analysis.recommendation}")Key points:
- The reasoning model (o3-mini) gets a natural language prompt with no formatting constraints -- it can think freely
- The extraction model (gpt-4o-mini) reads the reasoning output and extracts the structured
ContractAnalysisobject - This costs two LM calls per prediction, but the reasoning quality is significantly better than forcing o3-mini to output JSON directly
- Use
ChainOfThoughtso the reasoning model has space to work through the problem step by step
Example 3: Switching adapters per module in a pipeline
Use different adapters for different steps in a pipeline. Here, a summarization step uses the default ChatAdapter (freeform text output is fine), while a metadata extraction step uses JSONAdapter for reliable structured output.
import dspy
from pydantic import BaseModel
from typing import Literal
class ArticleMetadata(BaseModel):
category: Literal["tech", "business", "science", "politics", "sports", "other"]
entities: list[str]
key_dates: list[str]
sentiment: Literal["positive", "negative", "neutral"]
class Summarize(dspy.Signature):
"""Write a concise 2-3 sentence summary of the article."""
article: str = dspy.InputField(desc="Full article text")
summary: str = dspy.OutputField(desc="2-3 sentence summary")
class ExtractMetadata(dspy.Signature):
"""Extract structured metadata from the article."""
article: str = dspy.InputField(desc="Full article text")
metadata: ArticleMetadata = dspy.OutputField()
class ArticleProcessor(dspy.Module):
"""Process articles: summarize and extract metadata."""
def __init__(self):
self.summarize = dspy.ChainOfThought(Summarize)
self.extract = dspy.Predict(ExtractMetadata)
def forward(self, article):
summary = self.summarize(article=article)
metadata = self.extract(article=article)
return dspy.Prediction(
summary=summary.summary,
metadata=metadata.metadata,
)
# --- Usage ---
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
processor = ArticleProcessor()
# ChatAdapter (default) for summarization -- freeform text is fine
# JSONAdapter for extraction -- need reliable structured output
processor.extract.set_adapter(dspy.JSONAdapter())
article = """
Apple announced its new M4 chip today at a special event in Cupertino. The chip
delivers 2x faster CPU performance and 3x faster GPU performance compared to M3.
CEO Tim Cook called it "the most powerful chip we've ever created for Mac."
The new MacBook Pro models featuring M4 will be available starting November 8
at prices starting from $1,599. Analysts expect strong holiday quarter sales,
with Morgan Stanley raising its price target to $240.
"""
result = processor(article=article)
print("Summary:")
print(result.summary)
print("\nMetadata:")
meta = result.metadata
print(f" Category: {meta.category}")
print(f" Entities: {meta.entities}")
print(f" Key dates: {meta.key_dates}")
print(f" Sentiment: {meta.sentiment}")Key points:
set_adapter()on a module overrides the global adapter for that module only- The summarization step uses
ChatAdapter(the default) because it outputs plain text -- no need for JSON - The extraction step uses
JSONAdapterbecause it outputs a complex Pydantic model and needs reliable parsing - You can also use
dspy.context(adapter=...)for temporary overrides instead ofset_adapter() - This pattern scales to any pipeline -- use the simplest adapter that works for each step
Condensed from dspy.ai/api/adapters/, ChatAdapter/, JSONAdapter/, XMLAdapter/, and TwoStepAdapter/. Verify against upstream for latest.
DSPy Adapters — API Reference
dspy.Adapter (base class)
dspy.Adapter(
callbacks=None, # list[BaseCallback] | None
use_native_function_calling=False, # bool
native_response_types=None, # list[type] | None — defaults to [Citations]
)| Parameter | Type | Default | Description |
|---|---|---|---|
callbacks | `list[BaseCallback] | None` | None |
use_native_function_calling | bool | False | Enable native LM function calling when inputs contain dspy.Tool types. |
native_response_types | `list[type] | None` | None (defaults to [Citations]) |
Abstract class. Subclass and implement format_field_description, format_field_structure, format_task_description, parse, format_user_message_content, and format_assistant_message_content.
Key methods
| Method | Signature | Description |
|---|---|---|
__call__ | (lm, lm_kwargs, signature, demos, inputs) -> list[dict] | Format → LM call → parse pipeline. |
acall | async variant of __call__ | Async pipeline. |
format | (signature, demos, inputs) -> list[dict] | Convert inputs + demos into multiturn LM messages. |
parse | (signature, completion) -> dict | Extract output fields from raw LM response (abstract). |
format_system_message | (signature) -> str | Combine field descriptions, structure, and task instructions. |
format_demos | (signature, demos) -> list[dict] | Transform few-shot examples into user/assistant message pairs. |
format_conversation_history | (signature, ...) -> list[dict] | Format historical messages from History field. |
---
dspy.ChatAdapter
dspy.ChatAdapter(
callbacks=None, # list[BaseCallback] | None
use_native_function_calling=False, # bool
native_response_types=None, # list[type] | None
use_json_adapter_fallback=True, # bool
)| Parameter | Type | Default | Description |
|---|---|---|---|
callbacks | `list[BaseCallback] | None` | None |
use_native_function_calling | bool | False | Enable native function calling features of the LM provider. |
native_response_types | `list[type] | None` | None |
use_json_adapter_fallback | bool | True | Automatically retry with JSONAdapter when parsing fails (except on context window exceeded). |
Inheritance: Extends Adapter.
Key methods
| Method | Signature | Description |
|---|---|---|
__call__ | (lm, lm_kwargs, signature, demos, inputs) -> list[dict] | Execute adapter with fallback logic. |
acall | async variant of __call__ | Async execution with fallback. |
format | (signature, demos, inputs) -> list[dict] | Convert DSPy signature + inputs into chat messages. |
parse | (signature, completion) -> dict | Extract output fields from LM response text. |
format_system_message | (signature) -> str | Generate the system message. |
format_demos | (signature, demos) -> list[dict] | Format few-shot examples as user/assistant message pairs. |
format_finetune_data | (signature, demos, inputs, outputs) -> dict | Prepare data in OpenAI fine-tuning format. |
---
dspy.JSONAdapter
dspy.JSONAdapter(
callbacks=None, # list[BaseCallback] | None
use_native_function_calling=True, # bool (note: True by default, unlike ChatAdapter)
)| Parameter | Type | Default | Description |
|---|---|---|---|
callbacks | `list[BaseCallback] | None` | None |
use_native_function_calling | bool | True | Use native function calling (enabled by default). |
Inheritance: Extends ChatAdapter.
Key methods
Inherits all ChatAdapter methods. Overrides:
| Method | Description |
|---|---|
__call__ | Attempts structured output format first, falls back to JSON mode on failure. |
parse | Extracts and validates JSON from LM responses using json_repair. |
format_field_structure | Generates JSON schema instructions instead of field delimiters. |
format_user_message_content | Adds JSON formatting instructions to user messages. |
format_assistant_message_content | Formats assistant responses as JSON. |
Key behavior
- Instructs the LM to respond with a JSON object matching output field names and types.
- Uses provider native structured output mode when available, falls back to
response_format: {"type": "json_object"}. - Parses with
json_repairfor resilience against minor formatting errors. - Raises
AdapterParseErrorif response cannot be parsed.
---
dspy.XMLAdapter
dspy.XMLAdapter(
callbacks=None, # list[BaseCallback] | None
)| Parameter | Type | Default | Description |
|---|---|---|---|
callbacks | `list[BaseCallback] | None` | None |
Inheritance: Extends ChatAdapter.
Key methods
Inherits all ChatAdapter methods. Overrides:
| Method | Description |
|---|---|
format_field_structure | Generates XML tag instructions instead of field delimiters. |
format_user_message_content | Wraps input fields in XML tags. |
format_assistant_message_content | Formats assistant responses with XML tags. |
parse | Extracts field values from XML tags using regex pattern matching. |
user_message_output_requirements | Specifies XML tag format requirements. |
Key behavior
- Wraps fields in XML tags:
<field_name>value</field_name>. - Falls back to
JSONAdapteron parse failure (like ChatAdapter). - Simpler constructor than ChatAdapter — only takes
callbacks.
---
dspy.TwoStepAdapter
dspy.TwoStepAdapter(
extraction_model, # dspy.BaseLM (required)
**kwargs, # passed to parent Adapter
)| Parameter | Type | Default | Description |
|---|---|---|---|
extraction_model | dspy.BaseLM | required | Smaller LM for extracting structured data from the main LM response. Must be a BaseLM instance. |
**kwargs | Additional arguments passed to the parent Adapter class. |
Inheritance: Extends Adapter.
Raises: ValueError if extraction_model is not a BaseLM instance.
Key methods
| Method | Signature | Description |
|---|---|---|
__call__ | (lm, lm_kwargs, signature, demos, inputs) -> list[dict] | Two-stage pipeline: format → call main LM → extract with extraction_model. |
acall | async variant | Async two-stage pipeline. |
format | (signature, demos, inputs) -> list[dict] | Formats natural language prompt (no formatting constraints) for the main LM. |
parse | (signature, completion) -> dict | Uses extraction_model with ChatAdapter to structure the main LM raw text. |
Key behavior
- Main LM receives a simplified, natural language prompt — no field delimiters or JSON instructions.
- Extraction model uses ChatAdapter internally to parse freeform response into structured fields.
- Preserves
tool_callsandlogprobsfrom main LM responses. - Two LM calls per prediction (main + extraction).
---
Configuration methods
| Method | Where | Description |
|---|---|---|
dspy.configure(adapter=...) | Global | Set adapter for all modules. |
dspy.context(adapter=...) | Block | Temporary override within a with block. |
module.set_adapter(adapter) | Per-module | Override adapter for a specific module only. |