> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qredence.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Gotchas

> DSPy 3.3.0 pitfalls that cost time: retired assertions, model-string form, optimizer API shape, module conventions, streaming, and caching.

Distilled from the DSPy 3.3.0 research and verified against the installed package. Each item will cost real time if ignored.

## Assertions are gone — use `dspy.Refine`

`dspy.Assert`, `dspy.Suggest`, `dspy.constrain`, and `dspy.SoftAssert` **do not exist** in 3.x. There is also **no `dspy.BestofN`** in 3.3.0. The replacement is:

```python theme={null}
dspy.Refine(module, N, reward_fn, threshold)
```

Do not port assertion code.

## Model string

`deepseek-v4-flash` is addressed as `openai/deepseek-v4-flash` via the OpenAI-compatible endpoint — the same pattern DSPy uses for SGLang and local servers. `api_base` is required. Set `model_type="chat"` if the endpoint needs it.

Fleet Reasoner uses the repository's existing `OPENAI_*` environment variables (`OPENAI_MODEL`, `OPENAI_BASE_URL`, `OPENAI_API_KEY`). The old `INKLING_*` and `DEEPSEEK_API_KEY` conventions are gone. One model serves every tier.

If you ever switch to Gemini: the prefix is `gemini/`, not `google/`. A bare string silently defaults to Vertex AI and fails without GCP credentials. `vertex_ai/` is the GCP variant; use `vertex_project` and `vertex_location` — `project` and `location` are silently ignored.

## Optimizer API shape

* The metric goes on the **constructor**: `dspy.MIPROv2(metric=...)`, `dspy.BootstrapFewShot(metric=...)`.
* `trainset=` is **keyword-only** at `compile()` — `train_set=` fails with `TypeError`.
* `MIPROv2(auto="light"|"medium"|"heavy")` cannot be combined with explicit `num_candidates` or `num_trials` — that raises `ValueError`.
* `compile()` returns a **new copy**; the student is not mutated.

## Module conventions

* **No `dspy.Program` class.** `dspy.Module` is the base. Call `module(...)` or `module.acall(...)`, **never** `module.forward(...)` — that bypasses tracing and emits a deprecation warning.
* `super().__init__()` is **mandatory** (metaclass-enforced).
* **Never read `dspy.settings` in `__init__`.** Read it inside `forward()` so `dspy.context` overrides apply.
* Sub-module registration is attribute assignment (`self.predict = ...`). Only `dspy.Parameter` attributes are optimizer-visible.
* `Predict` and `ChainOfThought` accept **keyword args only** — `predict("q")` raises `ValueError`.

## Evaluation contract

* `dspy.Evaluate` calls the metric as `metric(example, prediction)` — exactly two positional args. `trace` is populated by optimizers, never by `Evaluate`.
* `EvaluationResult.score` is a **0–100 percentage**, not 0–1.
* Every devset example **must call `.with_inputs(...)`** or `program(**example.inputs())` crashes.
* Metric return: `bool`, `float`, or `dspy.Prediction(score, feedback)`. Feedback is read only by GEPA.

## Tools and retrieval

* `dspy.Tool` needs **valid type hints**. The docstring is the description; the type hints are the arg schema.
* `ReAct` and `ReActV2` **dedupe tools by name** — collisions silently overwrite. Keep names unique.
* `dspy.Retrieve` reads `dspy.settings.rm` **at call time**. `dspy.configure(rm=...)` first, or it raises `AssertionError("No RM is loaded.")`.
* Async tools need `acall()` or `dspy.configure(allow_tool_async_sync_conversion=True)`.

## 3.3.0-specific

* **`dspy.ReActV2` is experimental** and its prompt format differs from `ReAct` — reserved `submit` tool, `dspy.ToolCalls`, `termination_reason`. Fleet Reasoner uses it as the default with `ReAct` as a documented fallback.
* **`dspy.Flex` is experimental** and starts from a **signature**, not a composed `dspy.Module`. It runs optimizer-authored code in a `CodeInterpreter` sandbox. Because `ReasoningEngine` is a hand-structured module, `Flex` only applies to a future signature-first rewrite of the loop.
* **NumPy is optional** — install `dspy[numpy]` if any metric or visual code imports numpy.
* **Image / Audio / File constructors no longer do I/O.** Use `Image.from_path(path)` or `Image.from_url(url)`. `Image(path)` and `Image(url, download=True)` are gone. `Image.from_file()` and `from_PIL()` are deprecated aliases and are removed in 3.4.
* **GEPA result shapes changed** with `gepa[dspy]==0.1.1`: `candidates` are compiled modules, `best_candidate` returns a module, and `val_subscores` is keyed by validation instance id.
* **LM errors are normalized.** Catch `dspy.LMError` and its subclasses — `LMRateLimitError`, `ContextWindowExceededError`, `LMUnsupportedModelError`, `LMTimeoutError` — not provider-specific exceptions.
* **Typed LM boundary** (`dspy.LMRequest` / `dspy.LMResponse`, opt-in via `dspy.context(experimental=True)`) targets custom LM authors — irrelevant until you replace the built-in OpenAI-compatible provider.

## Streaming

* **`dspy.stream` does not exist in 3.3.0.** The streaming surface is `dspy.streamify(program, ...)` in `dspy.streaming`. It wraps any program and returns a callable whose result is an async generator of events, ending with the final `dspy.Prediction`.
* ReActV2 emits its final answer as `submit` tool-call arguments, so token-level streaming of `answer` does not apply. Surface tool activity as `status` events and the complete answer in the `done` frame.
* LM errors raised inside the stream are caught in the generator and emitted as `{"event": "error", "status": 429|413|502}` frames. The response has already started, so `HTTPException` is not an option mid-stream.

## Caching and concurrency

* **LM caching is ON by default.** Pass a unique `rollout_id` plus a non-zero temperature to force fresh calls. This is critical for `Refine` sampling.
* **`dspy.configure` has an owner-thread rule.** Configure once at startup and use `dspy.context` in request handlers and worker threads.
* **Save and load.** `program.save(path)` (state only) or `program.save(dir, save_program=True)` + `dspy.load(dir)`. `allow_pickle` defaults to `False`. API keys are never serialized.
