Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
99427f8
fix(avatar): remove deprecated prefix args from internal signatures (…
nullhack May 16, 2026
661a612
fix: correct mismatched closing code fence in README (#9771)
abhicris May 17, 2026
52efe77
Add exact adapter format message tests (#9791)
MaximeRivest May 21, 2026
6409a72
chore(deps): bump zizmorcore/zizmor-action from 0.5.3 to 0.5.5 (#9787)
dependabot[bot] May 21, 2026
6fb3f96
Expand exact adapter format message coverage (#9792)
MaximeRivest May 22, 2026
c827fed
Add core language model types (#9786)
MaximeRivest May 22, 2026
b16d109
docs: fix FAQ learn guide link (#9798)
cosmopolitan033 May 22, 2026
efb7fee
Isolate DSPy cache in tests (#9795)
isaacbmiller May 22, 2026
ee2c580
Introduce normalized LM boundary for adapters (#9802)
MaximeRivest May 23, 2026
b2c6ca4
refactor(adapter): introduce adapter call plan runtime
isaacbmiller May 24, 2026
4b00ae5
feat(adapter): add canonical tool result values
isaacbmiller May 24, 2026
81765e6
refactor(adapter): move type rendering to default hooks
isaacbmiller May 24, 2026
c3bf0c5
feat(adapter): add type feature handlers
isaacbmiller May 24, 2026
f5d24d6
refactor(adapter): add normalized render helpers
isaacbmiller May 24, 2026
a2086ed
refactor(adapter): route requests through call plans
isaacbmiller May 24, 2026
4575760
refactor(adapter): render normalized request messages
isaacbmiller May 24, 2026
556f04c
refactor(adapter): replay history through call plans
isaacbmiller May 24, 2026
52d0fd8
refactor(adapter): render demos through call plans
isaacbmiller May 24, 2026
f756df6
feat(adapter): render type features as LM parts
isaacbmiller May 24, 2026
2e9be53
feat(adapter): normalize history values
isaacbmiller May 24, 2026
fcce0ea
refactor(adapter): update two-step and streaming hooks
isaacbmiller May 24, 2026
f77af4d
feat(predict): add ReActV2 runtime
isaacbmiller May 24, 2026
28a5606
Move LM capabilities into BaseLM
MaximeRivest May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5
- name: Run actionlint
uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ To install the very latest from `main`:

```bash
pip install git+https://github.com/stanfordnlp/dspy.git
````
```



Expand Down
1 change: 0 additions & 1 deletion docs/docs/api/experimental/Citations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
handler: python
options:
members:
- adapt_to_native_lm_feature
- description
- extract_custom_type_from_annotation
- format
Expand Down
1 change: 0 additions & 1 deletion docs/docs/api/experimental/Document.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
handler: python
options:
members:
- adapt_to_native_lm_feature
- description
- extract_custom_type_from_annotation
- format
Expand Down
1 change: 0 additions & 1 deletion docs/docs/api/primitives/Audio.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
handler: python
options:
members:
- adapt_to_native_lm_feature
- description
- extract_custom_type_from_annotation
- format
Expand Down
1 change: 0 additions & 1 deletion docs/docs/api/primitives/Code.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
handler: python
options:
members:
- adapt_to_native_lm_feature
- description
- extract_custom_type_from_annotation
- format
Expand Down
1 change: 0 additions & 1 deletion docs/docs/api/primitives/Image.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
handler: python
options:
members:
- adapt_to_native_lm_feature
- description
- extract_custom_type_from_annotation
- format
Expand Down
1 change: 0 additions & 1 deletion docs/docs/api/primitives/Tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
members:
- __call__
- acall
- adapt_to_native_lm_feature
- description
- extract_custom_type_from_annotation
- format
Expand Down
1 change: 0 additions & 1 deletion docs/docs/api/primitives/ToolCalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
handler: python
options:
members:
- adapt_to_native_lm_feature
- description
- extract_custom_type_from_annotation
- format
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/faqs.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The **DSPy** philosophy and abstraction differ significantly from other librarie

## Basic Usage

**How should I use DSPy for my task?** We wrote a [eight-step guide](learn/index.md) on this. In short, using DSPy is an iterative process. You first define your task and the metrics you want to maximize, and prepare a few example inputs — typically without labels (or only with labels for the final outputs, if your metric requires them). Then, you build your pipeline by selecting built-in layers (`modules`) to use, giving each layer a `signature` (input/output spec), and then calling your modules freely in your Python code. Lastly, you use a DSPy `optimizer` to compile your code into high-quality instructions, automatic few-shot examples, or updated LM weights for your LM.
**How should I use DSPy for my task?** We wrote an [eight-step guide](/learn/) on this. In short, using DSPy is an iterative process. You first define your task and the metrics you want to maximize, and prepare a few example inputs — typically without labels (or only with labels for the final outputs, if your metric requires them). Then, you build your pipeline by selecting built-in layers (`modules`) to use, giving each layer a `signature` (input/output spec), and then calling your modules freely in your Python code. Lastly, you use a DSPy `optimizer` to compile your code into high-quality instructions, automatic few-shot examples, or updated LM weights for your LM.

**How do I convert my complex prompt into a DSPy pipeline?** See the same answer above.

Expand Down
20 changes: 18 additions & 2 deletions dspy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,25 @@

from dspy.evaluate import Evaluate # isort: skip
from dspy.clients import * # isort: skip
from dspy.adapters import Adapter, ChatAdapter, JSONAdapter, XMLAdapter, TwoStepAdapter, Image, Audio, File, History, Type, Tool, ToolCalls, Code, Reasoning # isort: skip
from dspy.adapters import Adapter, ChatAdapter, JSONAdapter, XMLAdapter, TwoStepAdapter, Image, Audio, File, History, Type, Tool, ToolCalls, ToolCallResults, Code, Reasoning # isort: skip
from dspy.primitives.sandbox_serializable import SandboxSerializable # isort: skip
from dspy.utils.exceptions import ContextWindowExceededError
from dspy.utils.exceptions import (
ContextWindowExceededError,
DSPyError,
LMAuthError,
LMBillingError,
LMConfigurationError,
LMError,
LMInvalidRequestError,
LMNotConfiguredError,
LMProviderError,
LMRateLimitError,
LMServerError,
LMTimeoutError,
LMTransportError,
LMUnsupportedFeatureError,
LMUnsupportedModelError,
)
from dspy.utils.logging_utils import configure_dspy_loggers, disable_logging, enable_logging
from dspy.utils.asyncify import asyncify
from dspy.utils.syncify import syncify
Expand Down
14 changes: 13 additions & 1 deletion dspy/adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@
from dspy.adapters.chat_adapter import ChatAdapter
from dspy.adapters.json_adapter import JSONAdapter
from dspy.adapters.two_step_adapter import TwoStepAdapter
from dspy.adapters.types import Audio, Code, File, History, Image, Reasoning, Tool, ToolCalls, Type
from dspy.adapters.types import (
Audio,
Code,
File,
History,
Image,
Reasoning,
Tool,
ToolCallResults,
ToolCalls,
Type,
)
from dspy.adapters.xml_adapter import XMLAdapter

__all__ = [
Expand All @@ -19,5 +30,6 @@
"TwoStepAdapter",
"Tool",
"ToolCalls",
"ToolCallResults",
"Reasoning",
]
145 changes: 145 additions & 0 deletions dspy/adapters/_legacy_type_markers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""Private compatibility helpers for legacy `dspy.Type` marker serialization."""

from __future__ import annotations

import json
import re
from typing import Any

import json_repair

from dspy.adapters.types.base_type import CUSTOM_TYPE_END_IDENTIFIER, CUSTOM_TYPE_START_IDENTIFIER
from dspy.core.types import LMAudioPart, LMBinaryPart, LMDocumentPart, LMImagePart, LMMessage, LMPart, LMTextPart


def _expand_legacy_custom_type_markers_in_chat_message(message: dict[str, Any]) -> dict[str, Any]:
"""Expand legacy marker payloads in an OpenAI-chat-shaped user message."""
if message.get("role") != "user" or not isinstance(message.get("content"), str):
return message
content = message["content"]
if CUSTOM_TYPE_START_IDENTIFIER not in content:
return message
return {**message, "content": _split_legacy_custom_type_text_to_blocks(content)}


def _expand_legacy_custom_type_markers_in_lm_message(message: LMMessage) -> LMMessage:
"""Expand legacy marker payloads in user `LMMessage`s into normalized parts."""
if message.role != "user":
return message
expanded_parts: list[LMPart] = []
changed = False
for part in message.parts:
if not isinstance(part, LMTextPart) or CUSTOM_TYPE_START_IDENTIFIER not in part.text:
expanded_parts.append(part)
continue
changed = True
expanded_parts.extend(_split_legacy_custom_type_text_to_parts(part.text))
return message.model_copy(update={"parts": expanded_parts}) if changed else message


def _split_legacy_custom_type_text_to_blocks(text: str) -> list[dict[str, Any]]:
blocks: list[dict[str, Any]] = []
last_end = 0
for match in re.finditer(_marker_pattern(), text, re.DOTALL):
start, end = match.span()
if start > last_end:
blocks.append({"type": "text", "text": text[last_end:start]})
blocks.extend(_legacy_custom_type_payload_to_blocks(match.group(1).strip()))
last_end = end
if last_end < len(text):
blocks.append({"type": "text", "text": text[last_end:]})
return blocks


def _split_legacy_custom_type_text_to_parts(text: str) -> list[LMPart]:
parts: list[LMPart] = []
last_end = 0
for match in re.finditer(_marker_pattern(), text, re.DOTALL):
start, end = match.span()
if start > last_end:
parts.append(LMTextPart(text=text[last_end:start]))
parts.extend(_legacy_custom_type_payload_to_parts(match.group(1).strip()))
last_end = end
if last_end < len(text):
parts.append(LMTextPart(text=text[last_end:]))
return parts


def _legacy_custom_type_payload_to_blocks(payload: str) -> list[dict[str, Any]]:
parsed = _parse_legacy_payload(payload)
if isinstance(parsed, list):
return [block if isinstance(block, dict) else {"type": "text", "text": str(block)} for block in parsed]
return [{"type": "text", "text": payload}]


def _legacy_custom_type_payload_to_parts(payload: str) -> list[LMPart]:
parsed = _parse_legacy_payload(payload)
if not isinstance(parsed, list):
return [LMTextPart(text=payload)]
return [_legacy_content_block_to_lm_part(block) for block in parsed]


def _parse_legacy_payload(payload: str) -> Any:
for parse_fn in (json.loads, _parse_doubly_quoted_json, json_repair.loads):
try:
return parse_fn(payload)
except Exception:
continue
return None


def _parse_doubly_quoted_json(value: str) -> Any:
# Legacy `Type` payloads can be JSON-encoded twice when the serialized
# marker string is nested inside a larger JSON value, e.g. list[Image].
return json.loads(json.loads(f'"{value}"'))


def _legacy_content_block_to_lm_part(block: Any) -> LMPart:
if not isinstance(block, dict):
return LMTextPart(text=str(block))

block_type = block.get("type")
if block_type == "text":
return LMTextPart(text=block.get("text", ""))
if block_type == "image_url":
image_url = block.get("image_url", {})
source = image_url.get("url") if isinstance(image_url, dict) else image_url
if isinstance(source, str) and source.startswith("data:") and "," in source:
media_type, data = _split_data_uri(source)
return LMImagePart(data=data, media_type=media_type)
return LMImagePart(url=source or "")
if block_type == "input_audio":
audio = block.get("input_audio", {})
return LMAudioPart(data=audio.get("data", ""), media_type=f"audio/{audio.get('format', 'wav')}")
if block_type == "file":
file = block.get("file", {})
if file.get("file_data") is not None:
media_type, data = _split_data_uri(file["file_data"])
return LMBinaryPart(data=data, media_type=media_type, filename=file.get("filename"), metadata={"legacy_content_block": block})
return LMBinaryPart(file_id=file.get("file_id", ""), filename=file.get("filename"), metadata={"legacy_content_block": block})
if block_type == "document":
source = block.get("source", {})
return LMDocumentPart(
source=source if isinstance(source, dict) else {"type": "text", "data": str(source)},
citations=block.get("citations") or {"enabled": True},
title=block.get("title"),
context=block.get("context"),
)

# Keep unknown provider-shaped blocks losslessly for the current BaseLM
# compatibility path. `openai_format.part_to_openai_blocks()` rehydrates
# this metadata before the legacy provider-shaped call. A later normalized
# type pass should replace this with an explicit opaque part or remove it
# when marker-based custom type rendering is retired.
return LMTextPart(text="", metadata={"legacy_content_block": block})


def _marker_pattern() -> str:
return rf"{CUSTOM_TYPE_START_IDENTIFIER}(.*?){CUSTOM_TYPE_END_IDENTIFIER}"


def _split_data_uri(value: str) -> tuple[str, str]:
if value.startswith("data:") and "," in value:
header, data = value.split(",", 1)
return header.removeprefix("data:").split(";", 1)[0], data
return "application/octet-stream", value
Loading
Loading