
Dspy Primitives
- 7 installs
- 11 repo stars
- Updated June 28, 2026
- lebsral/dspy-programming-not-prompting-lms-skills
Helps with ai & agent building tasks.
About
dspy-primitives is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- dspy-primitives
- AI & Agent Building
- AI-coding skill
Dspy Primitives 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-primitivesAdd 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
DSPy Primitives
Guide the user through DSPy's built-in primitive types for multimodal inputs, code handling, and conversation history.
Step 1: Understand the task
Before using primitives, clarify:
1. What kind of non-text data? Images, audio, code, or conversation history — each has its own primitive type. 2. Does the LM support it natively? dspy.Image needs a vision model (GPT-4o, Claude 3+, Gemini). dspy.Audio needs an audio model (GPT-4o-audio-preview, Gemini). dspy.Code and dspy.History work with any LM. 3. Is the data an input, output, or both? All primitives work as both input and output fields, but some patterns are more natural (e.g., dspy.Code for code generation output, dspy.Image for image analysis input).
What are primitives
Primitives are DSPy's custom types that go beyond plain strings. They let you pass images, audio, code, files, and conversation history directly into signatures, and capture structured outputs like native reasoning traces. DSPy handles the formatting, encoding, and adapter logic so the LM receives the data in the right format for its provider.
The core primitives:
| Primitive | Purpose | Typical use case |
|---|---|---|
dspy.Image | Images from URLs, files, or bytes | Vision tasks, image analysis, multimodal Q&A |
dspy.Audio | Audio from files, URLs, or arrays | Transcription, audio classification |
dspy.Code | Code with language annotation | Code generation, code review, analysis |
dspy.History | Conversation turns | Chatbots, multi-turn dialogue, follow-up questions |
dspy.File | Files (PDFs, documents) from a path, bytes, or upload ID | Document Q&A, PDF summarization |
dspy.Reasoning | Native reasoning/thinking traces from reasoning models | Capturing o1/o3/R1/extended-thinking output |
dspy.Tool | Wraps a Python callable as a tool the LM can call | Agents, ReAct (see /dspy-tools) |
dspy.ToolCalls | The LM's tool-call requests as a structured output | Agents, ReAct (see /dspy-tools) |
Two more types are the core input/output containers: dspy.Example holds a single labeled input/output pair (for training and few-shot data) and dspy.Prediction is what a module returns. Use them constantly but rarely construct primitives — see /dspy-data for dspy.Example depth.
dspy.Image
Wraps an image from any source into a format the LM can process. DSPy normalizes the input into a base64 data URI or plain URL automatically.
Constructor
dspy.Image(url=<source>, download=False, verify=True)Parameters:
- `url` — the image source. Accepts:
str— HTTP/HTTPS URL, GS URL, or local file pathbytes— raw image bytesPIL.Image.Image— a PIL image instancedict—{"url": value}(legacy form)- An already-encoded data URI
- `download` (
bool, defaultFalse) — whether to download remote URLs to infer MIME type - `verify` (
bool, defaultTrue) — whether to verify SSL certificates. SetFalsefor self-signed certs.
Usage in signatures
import dspy
lm = dspy.LM("openai/gpt-4o") # or "anthropic/claude-sonnet-4-5-20250929", etc. (must be vision-capable)
dspy.configure(lm=lm)
class DescribeImage(dspy.Signature):
"""Describe what you see in the image."""
image: dspy.Image = dspy.InputField(desc="Image to analyze")
description: str = dspy.OutputField(desc="Detailed description of the image")
describer = dspy.Predict(DescribeImage)
# From a URL
result = describer(image=dspy.Image(url="https://example.com/photo.jpg"))
print(result.description)
# From a local file
result = describer(image=dspy.Image(url="/path/to/photo.png"))
# From PIL
from PIL import Image as PILImage
pil_img = PILImage.open("photo.png")
result = describer(image=dspy.Image(url=pil_img))Multiple images
class CompareImages(dspy.Signature):
"""Compare two images and describe the differences."""
image_a: dspy.Image = dspy.InputField(desc="First image")
image_b: dspy.Image = dspy.InputField(desc="Second image")
differences: str = dspy.OutputField(desc="Key differences between the images")dspy.Audio
Wraps audio data for LMs that support native audio input. Audio is encoded as base64 internally.
Creating Audio objects
# From a local file
audio = dspy.Audio.from_file("recording.wav")
# From a URL
audio = dspy.Audio.from_url("https://example.com/clip.mp3")
# From a numpy array (e.g., from a microphone or audio processing)
import numpy as np
audio = dspy.Audio.from_array(samples, sampling_rate=16000, format="wav")
# Direct instantiation with base64 data
audio = dspy.Audio(data="<base64-string>", audio_format="wav")Usage in signatures
import dspy
lm = dspy.LM("openai/gpt-4o-audio-preview") # or "google/gemini-2.0-flash", etc. (must be audio-capable)
dspy.configure(lm=lm)
class TranscribeAudio(dspy.Signature):
"""Transcribe the spoken content in the audio."""
audio: dspy.Audio = dspy.InputField(desc="Audio recording to transcribe")
transcript: str = dspy.OutputField(desc="Transcribed text")
transcriber = dspy.Predict(TranscribeAudio)
result = transcriber(audio=dspy.Audio.from_file("meeting.wav"))
print(result.transcript)Audio classification
from typing import Literal
class ClassifyAudio(dspy.Signature):
"""Classify the type of audio content."""
audio: dspy.Audio = dspy.InputField(desc="Audio clip to classify")
category: Literal["speech", "music", "ambient", "silence"] = dspy.OutputField()
language: str = dspy.OutputField(desc="Detected language if speech, else 'N/A'")dspy.Code
Wraps code with a language annotation. DSPy formats it as a markdown code block so the LM sees properly delimited, syntax-aware code.
Language specification
Use bracket notation to specify the language:
dspy.Code["python"] # Python code
dspy.Code["java"] # Java code
dspy.Code["sql"] # SQL code
dspy.Code["rust"] # Rust code
# ... any language string worksThe language tag tells DSPy to format the code as a fenced markdown block ( `python ... ` ) and guides the LM on syntax expectations.
Usage in signatures
Code generation (output):
import dspy
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
class GenerateCode(dspy.Signature):
"""Generate Python code that solves the given problem."""
problem: str = dspy.InputField(desc="Problem description")
code: dspy.Code["python"] = dspy.OutputField(desc="Working Python solution")
generator = dspy.Predict(GenerateCode)
result = generator(problem="Write a function that checks if a string is a palindrome")
print(result.code)Code analysis (input):
class ReviewCode(dspy.Signature):
"""Review the code for bugs, performance issues, and style problems."""
code: dspy.Code["python"] = dspy.InputField(desc="Code to review")
issues: list[str] = dspy.OutputField(desc="List of issues found")
severity: Literal["clean", "minor", "major", "critical"] = dspy.OutputField()
reviewer = dspy.ChainOfThought(ReviewCode)
result = reviewer(code="def fib(n):\n if n <= 1: return n\n return fib(n-1) + fib(n-2)")
print(result.issues) # ["No memoization — exponential time complexity", ...]
print(result.severity) # "major"Code transformation (input and output):
class ConvertCode(dspy.Signature):
"""Convert the Python code to equivalent Java code."""
python_code: dspy.Code["python"] = dspy.InputField(desc="Python source code")
java_code: dspy.Code["java"] = dspy.OutputField(desc="Equivalent Java code")dspy.History
Represents conversation history as a list of message turns. Use it to build multi-turn chatbots and follow-up interactions in DSPy.
Creating History objects
# From prior conversation turns
history = dspy.History(messages=[
{"role": "user", "content": "What is the capital of France?"},
{"role": "assistant", "content": "Paris"},
])
# Using field names that match your signature
history = dspy.History(messages=[
{"question": "What is the capital of France?", "answer": "Paris"},
{"question": "What is the capital of Germany?", "answer": "Berlin"},
])History objects are immutable (frozen). Create a new History to add turns.
Usage in signatures
import dspy
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
class Chat(dspy.Signature):
"""Answer the user's question given the conversation history."""
history: dspy.History = dspy.InputField(desc="Prior conversation turns")
question: str = dspy.InputField(desc="Current user question")
answer: str = dspy.OutputField(desc="Response to the user")
chatbot = dspy.Predict(Chat)Building conversation incrementally
Capture each response and append it to history for the next turn:
chatbot = dspy.Predict(Chat)
# Turn 1
result = chatbot(
history=dspy.History(messages=[]),
question="What is the capital of France?"
)
print(result.answer) # Paris
# Turn 2 — include previous turn in history
history = dspy.History(messages=[
{"question": "What is the capital of France?", "answer": result.answer}
])
result = chatbot(
history=history,
question="What is its population?"
)
print(result.answer) # About 2.1 million in the city proper...
# Turn 3 — append again
history = dspy.History(messages=[
{"question": "What is the capital of France?", "answer": "Paris"},
{"question": "What is its population?", "answer": result.answer},
])
result = chatbot(
history=history,
question="How does that compare to London?"
)Helper pattern for managing history
class Chatbot(dspy.Module):
def __init__(self):
self.respond = dspy.ChainOfThought(Chat)
self.turns = []
def forward(self, question):
history = dspy.History(messages=self.turns)
result = self.respond(history=history, question=question)
self.turns.append({"question": question, "answer": result.answer})
return resultdspy.File
Wraps a file (PDF, document, etc.) so you can pass it to an LM that supports file inputs. DSPy encodes the file as a base64 data URI (data:<mime_type>;base64,<...>) following the OpenAI file content-part spec. Added in DSPy 3.1.
Creating File objects
# From a local file path (auto-detects MIME type)
file = dspy.File.from_path("./research.pdf")
# From raw bytes
file = dspy.File.from_bytes(pdf_bytes, filename="research.pdf", mime_type="application/pdf")
# From a previously uploaded file (referenced by provider file ID)
file = dspy.File.from_file_id("file-abc123", filename="research.pdf")
# Direct instantiation with a data URI
file = dspy.File(file_data="data:application/pdf;base64,<base64-string>")Usage in signatures
import dspy
lm = dspy.LM("openai/gpt-4o") # or "anthropic/claude-sonnet-4-5-20250929", etc. (must support file inputs)
dspy.configure(lm=lm)
class SummarizeDoc(dspy.Signature):
"""Summarize the key findings in the document."""
file: dspy.File = dspy.InputField(desc="Document to summarize")
summary: str = dspy.OutputField(desc="Concise summary of the document")
summarizer = dspy.Predict(SummarizeDoc)
result = summarizer(file=dspy.File.from_path("./research.pdf"))
print(result.summary)Use dspy.File when you need to feed a whole document to the LM (PDF Q&A, contract review, report summarization) rather than extracting and pasting its text. The model must support file inputs.
dspy.Reasoning
Captures the native reasoning/thinking trace emitted by reasoning models (o1/o3, DeepSeek-R1, Claude extended thinking) as a structured output field. When the configured model supports native reasoning, DSPy pulls the reasoning trace directly from the response; otherwise it falls back to a generated reasoning field, so the same signature works across reasoning and non-reasoning models. Added in DSPy 3.1.
dspy.Reasoning behaves like a string (you can index, slice, concatenate, and call string methods on it) while also being a typed primitive.
Usage in signatures
import dspy
lm = dspy.LM("openai/o3-mini") # or "anthropic/claude-sonnet-4-5-20250929" with thinking, "deepseek/deepseek-reasoner", etc.
dspy.configure(lm=lm)
class SolveProblem(dspy.Signature):
"""Solve the math problem."""
problem: str = dspy.InputField()
reasoning: dspy.Reasoning = dspy.OutputField(desc="The model's native reasoning")
answer: str = dspy.OutputField()
solver = dspy.Predict(SolveProblem)
result = solver(problem="If a train travels 60 km in 45 minutes, what is its speed in km/h?")
print(result.reasoning) # the model's native thinking trace
print(result.answer) # 80Use dspy.Reasoning when you want access to a reasoning model's native thinking as a first-class output. For a plain generated rationale that works on any LM, use dspy.ChainOfThought instead — see /dspy-chain-of-thought.
dspy.Tool and dspy.ToolCalls
dspy.Tool wraps a Python callable so the LM can invoke it as a tool, and dspy.ToolCalls represents the LM's tool-call requests as a structured output type. These are the building blocks for agents and tool use (ReAct). They are covered in depth — including registration, argument schemas, and execution loops — in /dspy-tools; reach for that skill rather than constructing these primitives directly.
Combining primitives in signatures
You can mix primitives with regular typed fields in the same signature:
class AnalyzeScreenshot(dspy.Signature):
"""Analyze a UI screenshot and generate test code for the visible elements."""
screenshot: dspy.Image = dspy.InputField(desc="Screenshot of the UI")
framework: str = dspy.InputField(desc="Test framework to use, e.g. 'playwright'")
test_code: dspy.Code["python"] = dspy.OutputField(desc="Generated test code")
element_count: int = dspy.OutputField(desc="Number of interactive elements found")class AudioChat(dspy.Signature):
"""Respond to a user's audio message in a conversation."""
history: dspy.History = dspy.InputField(desc="Prior conversation turns")
audio_message: dspy.Audio = dspy.InputField(desc="User's spoken message")
response: str = dspy.OutputField(desc="Text response to the user")Provider requirements
Not all LM providers support all primitives natively:
| Primitive | Requires |
|---|---|
dspy.Image | A vision-capable model (GPT-4o, Claude 3+, Gemini, etc.) |
dspy.Audio | An audio-capable model (GPT-4o-audio-preview, Gemini, etc.) |
dspy.Code | Any LM (formatted as markdown code blocks) |
dspy.History | Any LM (formatted as conversation turns) |
dspy.File | A model that supports file inputs (GPT-4o, Claude, Gemini, etc.) |
dspy.Reasoning | Best with a reasoning model (o1/o3, DeepSeek-R1, Claude extended thinking); falls back to a generated field on others |
DSPy's adapter system handles the provider-specific formatting. You write the signature once; DSPy translates it for the target LM.
Gotchas
1. Claude uses the deprecated `dspy.Image.from_file()` / `from_url()` class methods. Use dspy.Image(url="path-or-url") directly instead — the constructor accepts file paths, URLs, bytes, and PIL images via the url parameter. 2. Claude passes raw strings where a primitive is expected. If a signature field is typed as dspy.Code["python"], pass a string directly — DSPy's validate_input coerces it. But for dspy.Image and dspy.Audio, you must construct the primitive object explicitly. Raw strings will not be auto-converted. 3. Claude uses `role`/`content` keys in History messages instead of signature field names. History messages should use keys matching the signature fields (e.g., {"question": ..., "answer": ...}), not the generic role/content format. Using role/content works but produces worse prompt formatting because DSPy cannot map the history entries to the right signature fields. 4. Claude forgets that History is frozen (immutable). You cannot append to an existing History object. Create a new dspy.History(messages=[...old_turns, new_turn]) each time. Attempting to mutate raises a ValidationError. 5. Claude uses `dspy.Image` with a non-vision model. If the configured LM does not support vision (e.g., GPT-4o-mini, older Claude models), image inputs are silently ignored or cause errors. Always verify the model supports the primitive type.
Additional resources
- dspy.Image API docs
- dspy.Audio API docs
- dspy.Code API docs
- dspy.History API docs
- DSPy primitives API index (Tool, ToolCalls, Example, Prediction)
- dspy.File and dspy.Reasoning — covered in the Adapters guide under "Custom type wrappers" (no dedicated API page yet)
- For API details, see reference.md
- For worked examples, see examples.md
Cross-references
Install any skill: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill <name>- Defining signatures — see
/dspy-signatures - Using signatures with modules — see
/dspy-modules,/dspy-predict,/dspy-chain-of-thought - Tools and agents (`dspy.Tool`, `dspy.ToolCalls`) — see
/dspy-tools - `dspy.Example` and training data — see
/dspy-data - Building chatbots with History — see
/ai-building-chatbots - 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
[
{
"prompt": "I want to build a DSPy module that takes a product image and generates a catalog description. How do I pass the image?",
"expected_output": "Uses dspy.Image in a signature with dspy.InputField. Shows the constructor dspy.Image(url=...) not deprecated from_file/from_url methods. Uses a vision-capable model.",
"assertions": [
"Uses dspy.Image as the type annotation for an InputField",
"Uses dspy.Image(url=...) constructor, not deprecated from_file or from_url",
"Configures a vision-capable LM (GPT-4o, Claude 3+, Gemini, etc.)",
"LM configuration is provider-agnostic with alternative comment",
"Does not pass a raw image URL string where dspy.Image is expected"
]
},
{
"prompt": "How do I build a multi-turn chatbot in DSPy that remembers previous conversation turns?",
"expected_output": "Uses dspy.History with messages list. Shows building history incrementally by creating new History objects each turn. Uses signature field names as message keys.",
"assertions": [
"Uses dspy.History as the type annotation for an InputField",
"Creates History with messages matching signature field names (e.g. question/answer), not role/content",
"Shows creating a new History object each turn (does not try to mutate)",
"Mentions that History is frozen/immutable",
"Does not fabricate a History.append() or History.add() method"
]
},
{
"prompt": "I want DSPy to generate Python code and also review existing code. How do I use typed code fields?",
"expected_output": "Uses dspy.Code with bracket notation for language. Shows Code as both InputField and OutputField. Explains that raw strings are auto-coerced for Code fields.",
"assertions": [
"Uses dspy.Code['python'] bracket notation for language specification",
"Shows Code as OutputField for code generation",
"Shows Code as InputField for code review/analysis",
"Does not require wrapping raw code strings in dspy.Code() for input fields",
"LM configuration is provider-agnostic with alternative comment"
]
}
]
DSPy Primitives — Examples
1. Image analysis with dspy.Image
Analyze product images to generate catalog descriptions automatically.
import dspy
from typing import Literal
# Use a vision-capable model
lm = dspy.LM("openai/gpt-4o") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
class CatalogEntry(dspy.Signature):
"""Generate a product catalog entry from the product image."""
image: dspy.Image = dspy.InputField(desc="Product photo")
category: str = dspy.InputField(desc="Product category for context")
title: str = dspy.OutputField(desc="Short product title, max 10 words")
description: str = dspy.OutputField(desc="Marketing description, 2-3 sentences")
color: str = dspy.OutputField(desc="Primary color of the product")
tags: list[str] = dspy.OutputField(desc="Search tags for the product")
cataloger = dspy.ChainOfThought(CatalogEntry)
# From a URL
result = cataloger(
image=dspy.Image(url="https://example.com/products/red-sneaker.jpg"),
category="footwear",
)
print(result.title) # "Classic Red Canvas Sneaker"
print(result.description) # "A bold red canvas sneaker with white rubber sole..."
print(result.color) # "red"
print(result.tags) # ["sneaker", "canvas", "red", "casual", "footwear"]
# From a local file
result = cataloger(
image=dspy.Image(url="/photos/products/blue-jacket.png"),
category="outerwear",
)Image comparison
class QualityCheck(dspy.Signature):
"""Compare a reference product image to a manufacturing sample and flag defects."""
reference: dspy.Image = dspy.InputField(desc="Reference product image")
sample: dspy.Image = dspy.InputField(desc="Manufacturing sample photo")
passes_qc: bool = dspy.OutputField(desc="Whether the sample matches the reference")
defects: list[str] = dspy.OutputField(desc="List of defects found, empty if none")
checker = dspy.ChainOfThought(QualityCheck)
result = checker(
reference=dspy.Image(url="/images/reference/widget-v2.png"),
sample=dspy.Image(url="/images/samples/widget-batch-42-001.png"),
)
if not result.passes_qc:
print(f"QC failed. Defects: {result.defects}")2. Code review with dspy.Code
Analyze code for bugs, security issues, and suggest improvements.
import dspy
from typing import Literal
from pydantic import BaseModel
lm = dspy.LM("openai/gpt-4o-mini") # or any LiteLLM-supported provider
dspy.configure(lm=lm)
class Issue(BaseModel):
line_hint: str
severity: str # "info", "warning", "error"
message: str
suggestion: str
class CodeReview(dspy.Signature):
"""Review the code for bugs, security vulnerabilities, and style issues.
Focus on actionable feedback a developer can fix immediately."""
code: dspy.Code["python"] = dspy.InputField(desc="Code to review")
context: str = dspy.InputField(desc="What this code is supposed to do")
issues: list[Issue] = dspy.OutputField(desc="List of issues found")
overall: Literal["approve", "request_changes"] = dspy.OutputField()
reviewer = dspy.ChainOfThought(CodeReview)
source_code = """
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
result = db.execute(query)
return result.fetchone()
def process_payment(amount, card_number):
print(f"Processing payment of {amount} with card {card_number}")
# TODO: implement actual payment
return True
"""
result = reviewer(
code=source_code,
context="User lookup and payment processing for an e-commerce backend"
)
for issue in result.issues:
print(f"[{issue.severity}] {issue.message}")
print(f" Suggestion: {issue.suggestion}")
print()
print(f"Verdict: {result.overall}")
# [error] SQL injection vulnerability in get_user
# Suggestion: Use parameterized queries: db.execute("SELECT * FROM users WHERE id = ?", (user_id,))
#
# [error] Logging sensitive card number in process_payment
# Suggestion: Mask the card number before logging: card_number[-4:]
# ...
# Verdict: request_changesCode generation and transformation
class PortToTypeScript(dspy.Signature):
"""Port the Python code to idiomatic TypeScript.
Preserve the logic and add proper type annotations."""
python_code: dspy.Code["python"] = dspy.InputField(desc="Python source to port")
typescript_code: dspy.Code["typescript"] = dspy.OutputField(desc="Equivalent TypeScript")
notes: list[str] = dspy.OutputField(desc="Differences or caveats in the port")
porter = dspy.ChainOfThought(PortToTypeScript)
result = porter(python_code="def fibonacci(n: int) -> list[int]:\n a, b = 0, 1\n seq = []\n for _ in range(n):\n seq.append(a)\n a, b = b, a + b\n return seq")
print(result.typescript_code)
# function fibonacci(n: number): number[] {
# let a = 0, b = 1;
# const seq: number[] = [];
# for (let i = 0; i < n; i++) {
# seq.push(a);
# [a, b] = [b, a + b];
# }
# return seq;
# }
print(result.notes)
# ["Python tuple unpacking replaced with destructuring assignment", ...]3. Conversation with dspy.History
Build a multi-turn customer support chatbot that remembers context.
import dspy
lm = dspy.LM("openai/gpt-4o-mini") # or any LiteLLM-supported provider
dspy.configure(lm=lm)
class SupportChat(dspy.Signature):
"""You are a helpful customer support agent for an e-commerce store.
Use the conversation history to maintain context across turns.
Be concise and helpful."""
history: dspy.History = dspy.InputField(desc="Prior conversation turns")
question: str = dspy.InputField(desc="Customer's current message")
answer: str = dspy.OutputField(desc="Support agent response")
class SupportBot(dspy.Module):
def __init__(self):
self.respond = dspy.ChainOfThought(SupportChat)
self.turns = []
def chat(self, message: str) -> str:
history = dspy.History(messages=self.turns)
result = self.respond(history=history, question=message)
self.turns.append({"question": message, "answer": result.answer})
return result.answer
def reset(self):
self.turns = []
# Usage
bot = SupportBot()
print(bot.chat("I ordered a blue jacket last week but haven't received it yet."))
# "I'm sorry to hear that. Could you provide your order number so I can look into it?"
print(bot.chat("It's ORDER-12345."))
# "Thanks! Let me check on ORDER-12345 for you..."
print(bot.chat("Can I change the shipping address?"))
# The bot remembers the order context from previous turnsHistory with structured data
from pydantic import BaseModel
from typing import Literal, Optional
class OrderLookup(dspy.Signature):
"""Look up order details and answer the customer's question.
Use conversation history for context about which order is being discussed."""
history: dspy.History = dspy.InputField(desc="Prior conversation")
question: str = dspy.InputField(desc="Customer question")
order_data: str = dspy.InputField(desc="Order data from the database, if available")
answer: str = dspy.OutputField(desc="Helpful response")
action: Optional[Literal["escalate", "refund", "reship", "none"]] = dspy.OutputField(
desc="Action to take, if any"
)
agent = dspy.ChainOfThought(OrderLookup)
# Simulate a conversation with database lookups
history = dspy.History(messages=[
{"question": "Where is my order?", "answer": "Could you share your order number?"},
{"question": "ORDER-789", "answer": "I found it. It shipped on March 10."},
])
result = agent(
history=history,
question="It's been a week and I still don't have it. This is unacceptable.",
order_data="ORDER-789: shipped 2026-03-10, carrier: UPS, tracking: stuck at hub since 03-12"
)
print(result.answer) # Empathetic response about the delay
print(result.action) # "reship" or "escalate"DSPy Primitives API Reference
Condensed from dspy.ai/api/primitives/. Verify against upstream for latest.
dspy.Image
Constructor
dspy.Image(url=None, *, download=False, verify=True)| Parameter | Type | Default | Description |
|---|---|---|---|
url | Any | None | Image source: HTTP/HTTPS URL, GS URL, local file path, raw bytes, PIL.Image.Image, dict {"url": value}, or data URI |
download | bool | False | Download remote URLs to infer MIME type |
verify | bool | True | Verify SSL certificates when downloading |
Key Methods
| Method | Description |
|---|---|
format() | Returns formatted image as list of dicts or string (cached) |
serialize_model() | Serializes with custom type identifiers |
Deprecated Class Methods
These still work but use the constructor instead:
| Method | Replacement |
|---|---|
from_url(url, download=False) | dspy.Image(url=url, download=download) |
from_file(file_path) | dspy.Image(url=file_path) |
from_PIL(pil_image) | dspy.Image(url=pil_image) |
dspy.Audio
Constructor
dspy.Audio(data="<base64-string>", audio_format="wav")| Parameter | Type | Default | Description |
|---|---|---|---|
data | str | required | Base64-encoded audio data |
audio_format | str | required | Audio format (e.g., "wav", "mp3") |
Class Methods (preferred for creating Audio objects)
| Method | Signature | Description |
|---|---|---|
from_file(file_path) | file_path: str -> Audio | Read local audio file and encode as base64 |
from_url(url) | url: str -> Audio | Download audio from URL and encode as base64 |
from_array(array, sampling_rate, format="wav") | array: Any, sampling_rate: int, format: str -> Audio | Encode numpy array as base64 audio. Requires soundfile library |
Key Methods
| Method | Description |
|---|---|
format() | Returns list of dicts with type: "input_audio" and base64 data |
validate_input(values) | Accepts Audio instances or dicts with data and audio_format keys |
dspy.Code
Bracket Notation
dspy.Code["python"] # Python code type
dspy.Code["java"] # Java code type
dspy.Code["sql"] # SQL code type
# Any language string worksThe bracket notation creates a parameterized type. DSPy formats it as a fenced markdown code block with the language tag.
Input Coercion
dspy.Code accepts multiple input forms via validate_input:
| Input type | Behavior |
|---|---|
str | Treated as raw code string |
dict with "code" key | Extracts the code string |
Code instance | Returned as-is |
Key Methods
| Method | Description |
|---|---|
description() | Returns description indicating markdown code block format with language |
format() | Returns formatted code string |
parse_lm_response(response) | Parses LM response into Code object |
dspy.History
Constructor
dspy.History(messages=[...])| Parameter | Type | Default | Description |
|---|---|---|---|
messages | list[dict[str, Any]] | required | List of message dicts. Keys should match signature field names |
Configuration
- Frozen (
frozen=True) — instances are immutable after creation - Strict (
extra='forbid') — rejects undefined fields - Whitespace stripped (
str_strip_whitespace=True)
Message Format
Messages should use keys matching the signature fields:
# Good — keys match signature fields (question, answer)
dspy.History(messages=[
{"question": "What is DSPy?", "answer": "A framework for programming LMs."},
])
# Works but less optimal — generic role/content format
dspy.History(messages=[
{"role": "user", "content": "What is DSPy?"},
{"role": "assistant", "content": "A framework for programming LMs."},
])dspy.File
Added in DSPy 3.1. Wraps a file (PDF, document, etc.) as a base64 data URI following the OpenAI file content-part spec.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
file_data | `str \ | None` | None |
file_id | `str \ | None` | None |
filename | `str \ | None` | None |
At least one of file_data, file_id, or filename must be set.
Class Methods (preferred for creating File objects)
| Method | Signature | Description |
|---|---|---|
from_path(file_path, filename=None, mime_type=None) | -> File | Read a local file, auto-detect MIME type via mimetypes.guess_type(), encode as base64 data URI |
from_bytes(file_bytes, filename=None, mime_type="application/octet-stream") | -> File | Encode raw bytes as a base64 data URI |
from_file_id(file_id, filename=None) | -> File | Reference a provider-uploaded file by ID |
dspy.Reasoning
Added in DSPy 3.1. Captures the native reasoning/thinking trace from reasoning models as a structured, str-like output type.
Field
| Field | Type | Default | Description |
|---|---|---|---|
content | str | required | The reasoning text |
The validator accepts a plain str (converted to {"content": data}), a Reasoning instance, or a dict with a content key.
Behavior
- String-like — implements
__str__,__len__,__getitem__,__contains__,__iter__,__add__,__radd__, and delegates string methods via__getattr__. adapt_to_native_lm_feature()setsreasoning_effortand removes the field from the signature when the LM supports native reasoning.parse_lm_response()extracts reasoning from areasoning_contentfield;parse_stream_chunk()pulls it from streaming chunks.
When no native reasoning is available, DSPy falls back to a generated reasoning field so the same signature works on any LM.
dspy.Tool and dspy.ToolCalls
dspy.Tool wraps a Python callable so the LM can invoke it as a tool; dspy.ToolCalls represents the LM's tool-call requests as a structured output type. Both underpin agents and ReAct. See the /dspy-tools skill for full API details (registration, argument schemas, execution loops).
Base Class: Type
All primitives inherit from dspy.Type (which extends pydantic.BaseModel). Common inherited methods:
| Method | Description |
|---|---|
adapt_to_native_lm_feature(signature, field_name, lm, lm_kwargs) | Adapts signature for native LM features |
parse_lm_response(response) | Parses LM response into the primitive type |
is_streamable() | Returns False for all primitives |