From f932e886ba6acd529af4a107b1fb8f3a8cb91276 Mon Sep 17 00:00:00 2001 From: Casey Quinn Date: Sun, 16 Nov 2025 12:54:45 +0000 Subject: [PATCH] docs(middleware): add dependency resolution guide --- .../middleware/dependency-resolution.mdx | 158 ++++++++++++++++++ src/oss/langchain/middleware/overview.mdx | 3 + 2 files changed, 161 insertions(+) create mode 100644 src/oss/langchain/middleware/dependency-resolution.mdx diff --git a/src/oss/langchain/middleware/dependency-resolution.mdx b/src/oss/langchain/middleware/dependency-resolution.mdx new file mode 100644 index 0000000000..33cf4024a9 --- /dev/null +++ b/src/oss/langchain/middleware/dependency-resolution.mdx @@ -0,0 +1,158 @@ +--- +title: Dependency resolution +description: Configure and understand middleware dependencies, ordering, and deduplication +--- + +LangChain middleware can declare other middleware they depend on. Dependency resolution +ensures the correct stack is assembled automatically, merges duplicate identifiers, and +honors ordering constraints. + +This guide explains how middleware dependencies work, how to configure +`MiddlewareSpec`, and how the resolver determines execution order. + +## Declaring dependencies with `requires()` + +Each `AgentMiddleware` subclass can override `requires()` and return a +sequence of `MiddlewareSpec` objects. Every spec describes another middleware +to insert before the requesting middleware runs. + +```python +from langchain.agents.middleware import AgentMiddleware, MiddlewareSpec + + +class RetryMiddleware(AgentMiddleware): + def requires(self) -> list[MiddlewareSpec]: + # Ensure requests are redacted before retries are attempted + return [MiddlewareSpec(factory=PIIMiddleware)] +``` + +When `create_agent` resolves middleware, it recursively flattens dependencies. +Each dependency executes before the middleware that requested it, and their own +`requires()` declarations are resolved as well. + +## `MiddlewareSpec` fields + +`MiddlewareSpec` encapsulates dependency metadata: + +| Field | Description | +| --- | --- | +| `factory` / `middleware` | Nullary callable that creates the dependency, or a pre-instantiated middleware. One of the two must be provided. | +| `id` | Optional identifier override. Defaults to the dependency's id or class name. | +| `priority` | Optional numeric priority. Higher values run earlier when order ties cannot be broken by other rules. | +| `tags` | Optional sequence of tags for referencing in ordering constraints. | +| `ordering` | Optional [`OrderingConstraints`](#orderingconstraints) specifying additional ordering relationships. | +| `merge_strategy` | Duplicate handling policy: `"first_wins"`, `"last_wins"`, or `"error"`. | + +Dependencies that share an `id` are deduplicated according to the merge +strategy: + +- `first_wins` (default): keep the first instance and merge subsequent ordering + constraints and tags. +- `last_wins`: replace the existing instance with the latest dependency. +- `error`: raise a `ValueError` if another dependency with the same `id` + is encountered. This mirrors user-supplied duplicates without an explicit strategy. + +:::note +Middleware supplied without an explicit `id` receives an auto-generated identifier. +The first instance keeps its class name for backwards compatibility; additional +instances of the same class gain a deterministic module-qualified suffix (for +example, `my.module.Middleware#2`). This allows multiple differently configured +instances of the same middleware class to coexist without triggering duplicate +identifier errors. +::: + +## `OrderingConstraints` + +Ordering constraints ensure dependencies line up with other middleware: + +```python +from langchain.agents.middleware import OrderingConstraints + + +MiddlewareSpec( + factory=AuthMiddleware, + ordering=OrderingConstraints( + after=("tag:session",), + before=("retry-handler",), + ), +) +``` + +- `after` accepts middleware identifiers or `tag:` references that must + execute before the dependency. +- `before` accepts identifiers or tags that must execute after the dependency. +- Tag references apply the constraint to every middleware with that tag. + +Self references are not allowed. Referencing an unknown id or tag raises a +`ValueError` during agent creation. + +## Ordering semantics + +Middleware resolution produces a deterministic order by applying these rules: + +1. **User order is the starting point.** Middleware passed to `create_agent` + retains its relative order when no other constraint applies. +2. **Dependencies run before their requestor.** Declared dependencies are + inserted ahead of the middleware that required them. +3. **Before/after constraints build a directed graph.** Constraints from + `OrderingConstraints` add graph edges between middleware identifiers or tags. +4. **Priority breaks ties.** When multiple candidates can execute next and user + order does not distinguish them, higher `priority` values win. If priorities + are equal, the resolver uses discovery order. +5. **Cycles fail fast.** If constraints introduce a cycle, a + `MiddlewareOrderCycleError` is raised with a human-readable cycle trace. + +## Example + +```python +from functools import partial +from langchain.agents import create_agent +from langchain.agents.middleware import AgentMiddleware, MiddlewareSpec, OrderingConstraints + + +class AuditMiddleware(AgentMiddleware): + id = "audit" + tags = ("observability",) + + def requires(self) -> list[MiddlewareSpec]: + return [ + MiddlewareSpec( + factory=partial(RateLimitMiddleware, limit=10), + merge_strategy="first_wins", + ordering=OrderingConstraints(after=("tag:auth",)), + ) + ] + + +class AuthMiddleware(AgentMiddleware): + id = "auth" + tags = ("auth",) + + +agent = create_agent( + model="openai:gpt-4o", + middleware=[AuthMiddleware(), AuditMiddleware()], +) +``` + +Resolution order: + +1. `RateLimitMiddleware` (after everything tagged `auth`, before `audit`). +2. `AuthMiddleware` (user-supplied middleware). +3. `AuditMiddleware`. + +If another middleware also requests the same `RateLimitMiddleware` with +`merge_strategy="first_wins"`, the resolver reuses the original instance and +adds the new ordering constraints. + +## Troubleshooting + +- **Duplicate id error** – Supply a unique `id` or configure `merge_strategy` + (`"first_wins"`/`"last_wins"`). +- **Cycle detected** – Review the cycle path in the + `MiddlewareOrderCycleError` message and adjust `before`/`after` constraints. +- **Unknown id/tag** – Ensure referenced identifiers are spelled correctly and + that tagged middleware assigns `tags` before resolution. +- **Unexpected ordering** – Remember that higher `priority` values preempt + lower ones when no other constraints apply. Adjust `priority` or add explicit + ordering constraints. diff --git a/src/oss/langchain/middleware/overview.mdx b/src/oss/langchain/middleware/overview.mdx index 471914a30b..fc298fad12 100644 --- a/src/oss/langchain/middleware/overview.mdx +++ b/src/oss/langchain/middleware/overview.mdx @@ -76,6 +76,9 @@ Middleware exposes hooks before and after each of those steps: Build your own middleware with hooks and decorators. + + Manage middleware dependencies, ordering, and deduplication. + Complete API reference for middleware.