dspy.BaseLM¶
dspy.BaseLM(model, model_type='chat', temperature=None, max_tokens=None, cache=True, callbacks: list[BaseCallback] | None = None, num_retries: int = 3, **kwargs)
¶
Base class for DSPy language models.
Most users should use dspy.LM, which is a BaseLM subclass.
For advanced use cases, such as custom language model backends, users can
subclass BaseLM and implement forward().
DSPy is migrating forward() from the legacy OpenAI/LiteLLM-shaped
contract to a typed DSPy contract. During this migration, subclasses should
declare which contract they implement with forward_contract:
forward_contract = "typed_lm": implementforward(request: dspy.LMRequest) -> dspy.LMResponse. This is the preferred contract for new custom LMs.forward_contract = "legacy": implementforward(prompt=None, messages=None, **kwargs)and return an OpenAI-like provider response. This remains the default during the migration.
BaseLM.__call__() is the compatibility boundary. In DSPy 3.3 and 3.4,
ordinary calls preserve the legacy public return value, list[str | dict]:
Calls can flow internally through the typed LMRequest / LMResponse path
without changing the public return shape. The typed path is used when the
caller passes an explicit dspy.LMRequest, when
dspy.context(experimental=True) is active, or when the subclass declares
forward_contract = "typed_lm". It accepts richer direct-call inputs,
including dspy.System, dspy.User, dspy.Assistant, dspy.ToolResult,
content parts, and prior dspy.LMResponse objects. The public return value
remains legacy outputs unless the caller explicitly opts into typed output
with an LMRequest or experimental=True.
Example typed direct call:
with dspy.context(experimental=True):
response = lm(
dspy.System("You are concise."),
dspy.User("What is DSPy?"),
)
print(response.text)
LMResponse is designed to feel familiar to users of the legacy output
list while carrying substantially more structure, including typed outputs,
usage, cache status, provider metadata, tool calls, reasoning, citations,
and multimodal content.
LMs must be serializable as part of saved DSPy programs. The default
dump_state() and load_state() implementations support subclasses whose
persistent state is fully captured by BaseLM.__init__() arguments. If a
subclass stores additional persistent state, override both methods.
Examples:
Preferred typed custom LM:
import dspy
class EchoLM(dspy.BaseLM):
forward_contract = "typed_lm"
def forward(self, request: dspy.LMRequest) -> dspy.LMResponse:
return dspy.LMResponse.from_text("hello", model=request.model)
lm = EchoLM(model="test/echo")
with dspy.context(experimental=True):
response = lm(dspy.User("Say hello."))
print(response.text)
Legacy custom LM for an OpenAI-like provider:
from openai import OpenAI
import dspy
class MyLegacyLM(dspy.BaseLM):
forward_contract = "legacy"
def forward(self, prompt=None, messages=None, **kwargs):
client = OpenAI()
return client.chat.completions.create(
model=self.model,
messages=messages or [{"role": "user", "content": prompt}],
**self.kwargs,
**kwargs,
)
lm = MyLegacyLM(model="gpt-4o-mini")
dspy.configure(lm=lm)
print(dspy.Predict("q -> a")(q="Why did the chicken cross the kitchen?"))
Initialize a base language model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
The model identifier. |
required | |
model_type
|
The LM API type, such as |
'chat'
|
|
temperature
|
The default sampling temperature. |
None
|
|
max_tokens
|
The default maximum number of output tokens. |
None
|
|
cache
|
Whether requests should use DSPy’s cache by default. |
True
|
|
num_retries
|
int
|
The default number of provider request retries. |
3
|
callbacks
|
list[BaseCallback] | None
|
Optional instance-level callback handlers. |
None
|
**kwargs
|
Additional default request parameters stored in
|
{}
|
Source code in .venv/lib/python3.14/site-packages/dspy/clients/base_lm.py
Methods:¶
__call__(*items: Any, prompt: str | None = None, messages: list[dict[str, Any]] | None = None, request: LMRequest | None = None, **kwargs) -> LMResponse | list[dict[str, Any] | str]
¶
Call the language model synchronously.
The default call path preserves DSPy’s legacy behavior and returns list[str | dict]. Calls flow internally
through LMRequest / LMResponse when either:
request=is provided or the first positional argument is anLMRequest, ordspy.context(experimental=True)is active, or- the subclass declares
forward_contract = "typed_lm".
In the typed request path, positional items are normalized with LMRequest.from_call(). This supports
a single prompt string, direct message objects such as dspy.System(...), dspy.User(...), and
dspy.Assistant(...), dspy.ToolResult(...) tool messages, and prior LMResponse values as assistant turns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*items
|
Any
|
Optional direct-call inputs. In the legacy path this may contain at most one prompt string. In the
typed path it may contain normalized messages, message sequences, prior |
()
|
prompt
|
str | None
|
Optional prompt string. Do not combine with positional prompt input. |
None
|
messages
|
list[dict[str, Any]] | None
|
Optional OpenAI-chat-shaped messages. Do not combine with |
None
|
request
|
LMRequest | None
|
Optional explicit normalized request. Call kwargs override request config when provided. |
None
|
**kwargs
|
Per-call generation parameters. |
{}
|
Returns:
| Type | Description |
|---|---|
LMResponse | list[dict[str, Any] | str]
|
|
LMResponse | list[dict[str, Any] | str]
|
strings or dictionaries, even when a typed LM subclass uses the typed path internally. |
Source code in .venv/lib/python3.14/site-packages/dspy/clients/base_lm.py
acall(*items: Any, prompt: str | None = None, messages: list[dict[str, Any]] | None = None, request: LMRequest | None = None, **kwargs) -> LMResponse | list[dict[str, Any] | str]
async
¶
Asynchronously call the language model.
This is the async equivalent of __call__(). It preserves legacy outputs by default and returns
dspy.LMResponse for explicit LMRequest calls or experimental direct calls.
Source code in .venv/lib/python3.14/site-packages/dspy/clients/base_lm.py
aforward(prompt: str | None = None, messages: list[dict[str, Any]] | None = None, **kwargs)
async
¶
Async forward pass for the language model.
Subclasses that support async calls must implement this method according to forward_contract.
For forward_contract = "legacy", implement
aforward(prompt=None, messages=None, **kwargs) and return one of these OpenAI-like provider responses:
For forward_contract = "typed_lm", implement aforward(request: dspy.LMRequest) -> dspy.LMResponse.
Raises:
| Type | Description |
|---|---|
LMError
|
Base class for LM configuration, transport, provider,
and unsupported-feature failures. Notable subclasses include
|
Source code in .venv/lib/python3.14/site-packages/dspy/clients/base_lm.py
copy(**kwargs)
¶
Return a copy of the language model with updated parameters.
The default implementation makes a shallow runtime copy. Provider
clients, sessions, and local model handles are preserved by reference.
DSPy-owned mutable state is isolated for history, the callbacks
list, and the kwargs dict. Other attributes are shared by reference.
Subclasses with additional mutable DSPy-owned state should override this
method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Attribute or request-parameter updates to apply to the
copy. For example, |
{}
|
Returns:
| Type | Description |
|---|---|
|
A copied LM instance. |
Source code in .venv/lib/python3.14/site-packages/dspy/clients/base_lm.py
dump_state() -> dict[str, Any]
¶
Return a sanitized reconstruction state for this LM.
Subclasses whose state is captured by BaseLM.__init__ can use this
default. Subclasses with extra persistent state should override both
dump_state and load_state.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dictionary that can be passed to |
dict[str, Any]
|
excludes API keys. |
Source code in .venv/lib/python3.14/site-packages/dspy/clients/base_lm.py
forward(prompt: str | None = None, messages: list[dict[str, Any]] | None = None, **kwargs)
¶
Forward pass for the language model.
Subclasses must implement this method according to forward_contract.
For forward_contract = "legacy", implement
forward(prompt=None, messages=None, **kwargs) and return one of these OpenAI-like provider responses:
For forward_contract = "typed_lm", implement forward(request: dspy.LMRequest) -> dspy.LMResponse.
Raises:
| Type | Description |
|---|---|
LMError
|
Base class for LM configuration, transport, provider,
and unsupported-feature failures. Notable subclasses include
|
Source code in .venv/lib/python3.14/site-packages/dspy/clients/base_lm.py
inspect_history(n: int = 1, file: TextIO | None = None) -> None
¶
load_state(state: dict[str, Any], *, allow_custom_lm_class: bool = False) -> BaseLM
classmethod
¶
Reconstruct an LM from dump_state output.
Legacy states without a class marker load as dspy.LM. Custom LM
classes must be importable by their module-qualified class path and are
only loaded when allow_custom_lm_class=True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
dict[str, Any]
|
Serialized LM state produced by |
required |
allow_custom_lm_class
|
bool
|
If True, allow importing and loading custom
|
False
|
Returns:
| Type | Description |
|---|---|
BaseLM
|
The reconstructed LM instance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
ImportError
|
If the serialized LM class cannot be imported. |
TypeError
|
If the serialized class is not a |