Base class for DSPy language models.
Legacy subclasses implement forward(prompt=None, messages=None, **kwargs) and optionally aforward with the same arguments. Ordinary calls return lists of strings or dictionaries. The built-in LM also accepts explicit dspy.lm15.Request calls. The experimental setting does not change outputs.
Implementing custom LMs through forward()/aforward() is deprecated. The old subclass interface remains supported throughout DSPy 3.4 and is scheduled for removal in 3.5. Implement an engine with complete(Request) -> Response and pass it to dspy.LM(engine=…) instead. LegacyEngine and AsyncLegacyEngine are transition wrappers for 3.4 only and are also scheduled for removal in 3.5. OpenAI-style messages= dictionaries are deprecated too: use lm15.Request and Message objects instead. lm(“hello”) remains a list-returning convenience. See the migration guide.
Persistent custom state belongs in dump_state/load_state. Runtime clients are shared by copy(), while DSPy history, callbacks and kwargs are isolated.
Initialize a base language model.
Parameters:
| Name | Type | Description | Default |
model | | | required |
model_type | | The LM API type, such as "chat", "text", or "responses". | '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 self.kwargs. | {} |
Source code in dspy/clients/base_lm.py
| def __init__(
self,
model,
model_type="chat",
temperature=None,
max_tokens=None,
cache=True,
callbacks: list[BaseCallback] | None = None,
num_retries: int = 3,
**kwargs,
):
"""Initialize a base language model.
Args:
model: The model identifier.
model_type: The LM API type, such as `"chat"`, `"text"`, or
`"responses"`.
temperature: The default sampling temperature.
max_tokens: The default maximum number of output tokens.
cache: Whether requests should use DSPy's cache by default.
num_retries: The default number of provider request retries.
callbacks: Optional instance-level callback handlers.
**kwargs: Additional default request parameters stored in
`self.kwargs`.
"""
self.model = model
self.model_type = model_type
self.cache = cache
self.callbacks = list(callbacks or [])
self.num_retries = num_retries
self.kwargs = self._get_initial_kwargs(temperature=temperature, max_tokens=max_tokens, **kwargs)
self.history = []
self._warned_zero_temp_rollout = False
|
Methods:
__call__(prompt=None, *, messages=None, **kwargs)
Return legacy outputs, or one lm15.Response for an explicit Request.
Source code in dspy/clients/base_lm.py
| @with_callbacks
def __call__(self, prompt=None, *, messages=None, **kwargs):
"""Return legacy outputs, or one lm15.Response for an explicit Request."""
from dspy.clients.execution import execute, finalize, prepare
call = prepare(self, prompt, messages, kwargs)
if not call.managed:
warn_legacy_lm()
warn_openai_messages(self, messages)
return finalize(self, call, execute(self, call))
|
acall(prompt=None, *, messages=None, **kwargs) async
Async equivalent of call, with the same execution ownership.
Source code in dspy/clients/base_lm.py
| @with_callbacks
async def acall(self, prompt=None, *, messages=None, **kwargs):
"""Async equivalent of __call__, with the same execution ownership."""
import asyncio
from dspy.clients.execution import aexecute, finalize, prepare
# Canonical media snapshots may read local files. Context variables
# propagate to the worker; callbacks/finalization stay on the caller.
call = await asyncio.to_thread(prepare, self, prompt, messages, kwargs, asynchronous=True)
if not call.managed:
warn_legacy_lm()
warn_openai_messages(self, messages)
return finalize(self, call, await aexecute(self, call))
|
aforward(prompt=None, messages=None, **kwargs) async
Asynchronously return an OpenAI-shaped provider response.
Source code in dspy/clients/base_lm.py
| async def aforward(self, prompt=None, messages=None, **kwargs):
"""Asynchronously return an OpenAI-shaped provider response."""
raise NotImplementedError("Subclasses must implement this method.")
|
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, lm.copy(rollout_id=1, temperature=1.0) returns an LM whose requests use a different rollout ID at non-zero temperature to bypass cache collisions. | {} |
Returns:
Source code in dspy/clients/base_lm.py
| def copy(self, **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.
Args:
**kwargs: Attribute or request-parameter updates to apply to the
copy. For example, `lm.copy(rollout_id=1, temperature=1.0)`
returns an LM whose requests use a different rollout ID at
non-zero temperature to bypass cache collisions.
Returns:
A copied LM instance.
"""
new_instance = copy_module.copy(self)
new_instance.history = []
new_instance.callbacks = list(getattr(self, "callbacks", []) or [])
new_instance.kwargs = dict(getattr(self, "kwargs", {}) or {})
for key, value in kwargs.items():
if hasattr(new_instance, key):
setattr(new_instance, key, value)
if (key in new_instance.kwargs) or (not hasattr(self, key)):
if value is None:
new_instance.kwargs.pop(key, None)
else:
new_instance.kwargs[key] = value
if hasattr(new_instance, "_warned_zero_temp_rollout"):
new_instance._warned_zero_temp_rollout = False
return new_instance
|
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 BaseLM.load_state. The state |
dict[str, Any] | |
Source code in dspy/clients/base_lm.py
| def dump_state(self) -> 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:
A dictionary that can be passed to `BaseLM.load_state`. The state
excludes API keys.
"""
filtered_kwargs = {key: value for key, value in self.kwargs.items() if key not in ("api_key", LM_CLASS_STATE_KEY)}
return {
LM_CLASS_STATE_KEY: f"{type(self).__module__}.{type(self).__qualname__}",
"model": self.model,
"model_type": self.model_type,
"cache": self.cache,
"num_retries": getattr(self, "num_retries", 3),
**filtered_kwargs,
}
|
forward(prompt=None, messages=None, **kwargs)
Return an OpenAI-shaped provider response for a legacy LM call.
Source code in dspy/clients/base_lm.py
| def forward(self, prompt=None, messages=None, **kwargs):
"""Return an OpenAI-shaped provider response for a legacy LM call."""
raise NotImplementedError("Subclasses must implement this method.")
|
inspect_history(n: int = 1, file: TextIO | None = None) -> None
Source code in dspy/clients/base_lm.py
| def inspect_history(self, n: int = 1, file: "TextIO | None" = None) -> None:
pretty_print_history(self.history, n, file=file)
|
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 dump_state. | required |
allow_custom_lm_class | bool | If True, allow importing and loading custom BaseLM subclasses recorded in state. Enable only for trusted state. | False |
Returns:
| Type | Description |
BaseLM | The reconstructed LM instance. |
Raises:
| Type | Description |
ValueError | If state references a custom LM class and allow_custom_lm_class is False. |
ImportError | If the serialized LM class cannot be imported. |
TypeError | If the serialized class is not a BaseLM subclass. |
Source code in dspy/clients/base_lm.py
| @classmethod
def load_state(cls, state: dict[str, Any], *, allow_custom_lm_class: bool = False) -> "BaseLM":
"""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`.
Args:
state: Serialized LM state produced by `dump_state`.
allow_custom_lm_class: If True, allow importing and loading custom
`BaseLM` subclasses recorded in `state`. Enable only for trusted
state.
Returns:
The reconstructed LM instance.
Raises:
ValueError: If `state` references a custom LM class and
`allow_custom_lm_class` is False.
ImportError: If the serialized LM class cannot be imported.
TypeError: If the serialized class is not a `BaseLM` subclass.
"""
state = dict(state)
class_path = state.pop(LM_CLASS_STATE_KEY, None)
if cls is BaseLM:
if class_path == "dspy.clients.typesafe.TypeSafe":
from dspy.clients.typesafe import TypeSafe
return TypeSafe.load_state(state)
if class_path is None:
# Legacy saved programs did not record the concrete LM class.
from dspy.clients.lm import LM
return LM(**state)
if class_path != _BUILTIN_LM_CLASS_PATH and not allow_custom_lm_class:
raise ValueError(
f"Refusing to import custom serialized LM class `{class_path}`. "
"Pass allow_unsafe_lm_state=True when loading trusted files to enable custom LM classes."
)
lm_cls = _import_lm_class(class_path)
if not issubclass(lm_cls, BaseLM):
raise TypeError(f"Serialized LM class `{class_path}` must be a subclass of dspy.BaseLM.")
if "allow_custom_lm_class" in inspect.signature(lm_cls.load_state).parameters:
return lm_cls.load_state(state, allow_custom_lm_class=allow_custom_lm_class)
return lm_cls.load_state(state)
return cls(**state)
|
update_history(entry)
Source code in dspy/clients/base_lm.py
| def update_history(self, entry):
record_history(self, entry)
|