ADR-020: LangChain Adapter Eager Validation at FastAPI Startup¶
Status: Accepted
Date: 2026-05-14
Implements: Sprint 1, item S1-6
Related: agentic_v2/adapters/registry.py, agentic_v2/server/app.py
Context¶
The langchain adapter requires the [langchain] optional extras (langchain, langgraph, and related packages). The package intentionally guards all adapter imports with try/except ImportError so that a bare pip install -e ".[dev,server]" succeeds without the optional deps. This is the correct design for dependency weight.
The problem is when the error surfaces. Prior to S1-6:
- The FastAPI server starts successfully regardless of whether the LangChain extras are installed.
- On the first call to
POST /api/runwithadapter=langchain(or any workflow that defaults to the langchain adapter),AdapterRegistry.get_adapter("langchain")triggers the deferred import, which raisesImportErrorinside the request handler. - The server logs a traceback and returns
501 Not Implementedor, depending on exception handling,500 Internal Server Error. - The operator sees the failure mid-workflow — after clients have connected, after resources were allocated, potentially after a billed LLM call has started.
This is especially problematic for the AGENTIC_DEFAULT_ADAPTER setting (default langchain), where every workflow run will fail until the extras are installed. The error should be surfaced at boot time, not at request time.
Decision¶
Add AdapterRegistry.validate_selected(name: str) -> None and call it during FastAPI's lifespan startup sequence.
validate_selected(name)checks whether the named adapter is importable and fully initialised. For thelangchainadapter, this means attempting the import that was previously deferred. If the import fails, the method raisesConfigurationErrorwith a human-readable message that includes the install hint:AGENTIC_DEFAULT_ADAPTER(defaultlangchain) selects which adapter is validated. Setting it tonativeskips the LangChain extras check.- Lifespan integration: The call is placed in the FastAPI
@asynccontextmanagerlifespan function inserver/app.py. AConfigurationErrorraised in the lifespan start block propagates as a server startup failure —uvicornlogs the error and exits with a non-zero code. - Probe exemption: The optional
probe_and_update_tier_defaults()function, which also attempts LangChain-related imports, retains its owntry/exceptguard. It is intended to be advisory (update tier defaults if possible, but do not block startup if extras are absent).validate_selected()is the authoritative startup gate.
Consequences¶
Positive¶
- Misconfigured deployments fail fast at boot with a clear, actionable error message rather than mid-workflow with a traceback.
- Operators and CI pipelines catch missing extras immediately on server start rather than on first workflow execution.
- The fix is non-breaking for correctly configured deployments: if LangChain extras are installed, startup proceeds as before.
- The separation between
validate_selected()(strict gate) and the probe function (advisory) makes the intent of each clear.
Negative¶
- The
nativeadapter path is unaffected — operators who use only the native engine and setAGENTIC_DEFAULT_ADAPTER=nativeare not protected against a future native-adapter initialisation bug by this mechanism. A more generalvalidate_all_registered()option exists if needed. - The
validate_selected()call happens synchronously in the lifespan, not in an async context. If validation ever becomes async (e.g., if it needs to make a network call), the implementation must be updated. - CLI path:
agentic run --adapter langchainresolves the adapter outside the FastAPI lifespan. The startup gate does not protect CLI users — they still encounter the deferredImportErrorat adapter resolution time. A pre-flight check in the CLI is a follow-on task.
Alternatives Considered¶
| Alternative | Disposition |
|---|---|
| Defer validation to first import (prior behavior) | Retained as the fallback for the probe function, where advisory discovery is the goal. Not acceptable for the server startup path where a failed boot is preferable to a mid-workflow crash. |
| Require all adapters to be installed | Rejected. The optional-extras design is correct — it keeps the installation footprint minimal for users who only need the native engine. The gate should validate what is configured, not mandate all optional packages. |
Validate at register() time |
Considered. Rejected because registration happens at import time (module level), before the server has read configuration. The gate needs to know which adapter is selected, not which adapters are registered. |
| Emit a startup warning instead of raising | Rejected for the server path. A warning that is scrolled past in logs provides no protection. For the probe function, a warning-on-failure is the correct behavior — adopted there. |
Implementation References¶
- New method:
agentic_v2/adapters/registry.py(AdapterRegistry.validate_selected) - Lifespan hook:
agentic_v2/server/app.py(lifespan@asynccontextmanager) - Error type:
agentic_v2/core/errors.py(ConfigurationError) - Probe function (advisory, not gated):
agentic_v2/server/app.py(probe_and_update_tier_defaults) - Configuration:
AGENTIC_DEFAULT_ADAPTERenvironment variable (default"langchain") - Known limitations:
KNOWN_LIMITATIONS.md§2.2
Review Cadence¶
Re-evaluate when:
- The CLI gains a pre-flight adapter check that covers the
agentic run --adapter langchainpath. - The adapter registry grows to support more than two adapters, and a
validate_all_registered()pattern becomes warranted. - The native adapter requires its own optional dependency (at which point
validate_selected()would need to cover that case too).