Skip to content

Commit b5e4c9d

Browse files
committed
feat(agents): add middleware dependency resolution
1 parent 52b1516 commit b5e4c9d

File tree

6 files changed

+958
-3
lines changed

6 files changed

+958
-3
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Agent middleware dependency resolution
2+
3+
LangChain agents support **middleware chaining** that can intercept model and tool
4+
execution. Middleware can now declare additional middleware dependencies and
5+
ordering requirements, enabling reusable building blocks that assemble the
6+
correct stack automatically.
7+
8+
This guide covers the dependency resolution process, the new
9+
`MiddlewareSpec`/`OrderingConstraints` helpers, and tips for understanding the
10+
resulting execution order.
11+
12+
## Declaring dependencies with `requires()`
13+
14+
Every `AgentMiddleware` subclass can override `requires()` and return a
15+
sequence of `MiddlewareSpec` objects. Each spec describes another middleware
16+
that should be inserted into the stack before the current middleware runs.
17+
18+
```python
19+
from langchain.agents.middleware.types import AgentMiddleware, MiddlewareSpec
20+
21+
22+
class RetryMiddleware(AgentMiddleware):
23+
def requires(self) -> list[MiddlewareSpec]:
24+
# Ensure requests are redacted before retries are attempted
25+
return [MiddlewareSpec(factory=PIIMiddleware)]
26+
```
27+
28+
When `create_agent` resolves middleware, it recursively flattens all declared
29+
dependencies. Each dependency runs before the middleware that requested it,
30+
and the dependency's own `requires()` declarations are resolved as well.
31+
32+
## `MiddlewareSpec` reference
33+
34+
`MiddlewareSpec` encapsulates dependency metadata:
35+
36+
| Field | Description |
37+
| --- | --- |
38+
| `factory` / `middleware` | Nullary callable that creates the dependency, or a pre-instantiated middleware. One of the two must be provided. |
39+
| `id` | Optional identifier override. Defaults to `middleware.id` or the class name. |
40+
| `priority` | Optional numeric priority. Higher numbers run earlier when order ties cannot be broken by constraints or user list order. |
41+
| `tags` | Optional sequence of tags for referencing in ordering constraints. |
42+
| `ordering` | Optional `OrderingConstraints` specifying additional before/after requirements. |
43+
| `merge_strategy` | Duplicate handling policy: `"first_wins"`, `"last_wins"`, or `"error"`. |
44+
45+
Dependencies that share an `id` are deduplicated according to their
46+
`merge_strategy`:
47+
48+
- `first_wins` (default): keep the first instance and merge subsequent ordering
49+
constraints and tags.
50+
- `last_wins`: replace the existing instance with the most recent dependency.
51+
- `error`: raise a `ValueError` if another dependency with the same `id` is
52+
encountered. This matches the pre-existing behaviour for user supplied
53+
duplicates.
54+
55+
!!! note
56+
Middleware supplied without an explicit `id` (either by the user or within
57+
a dependency `MiddlewareSpec`) receives an auto-generated identifier. The
58+
first instance keeps its class name for backwards compatibility; additional
59+
instances of the same class gain a deterministic module-qualified suffix
60+
(for example, `my.module.Middleware#2`). This preserves historical behaviour
61+
where multiple differently configured instances of the same middleware class
62+
can coexist without triggering duplicate-id errors.
63+
64+
## `OrderingConstraints`
65+
66+
Ordering constraints ensure dependencies line up with other middleware:
67+
68+
```python
69+
MiddlewareSpec(
70+
factory=AuthMiddleware,
71+
ordering=OrderingConstraints(
72+
after=("tag:session",),
73+
before=("retry-handler",),
74+
),
75+
)
76+
```
77+
78+
- `after` accepts middleware identifiers or `tag:<tag-name>` references that
79+
must execute **before** the dependency.
80+
- `before` accepts identifiers or tags that must execute **after** the
81+
dependency.
82+
- Tag references apply the constraint to every middleware with that tag.
83+
84+
Self references are not allowed. Referencing an unknown id or tag raises a
85+
`ValueError` during agent creation.
86+
87+
## Ordering semantics
88+
89+
Middleware resolution produces a deterministic order by applying the following
90+
rules:
91+
92+
1. **User order is the starting point.** Middleware passed to `create_agent`
93+
retains its relative order whenever no other constraint applies.
94+
2. **Dependencies run before their requestor.** Declared dependencies are
95+
inserted ahead of the middleware that required them.
96+
3. **Before/after constraints add graph edges.** Constraints from
97+
`OrderingConstraints` augment the ordering graph by `id` or `tag`.
98+
4. **Priority breaks ties.** When multiple nodes can execute next and the user
99+
order does not distinguish them, higher `priority` values win. If priorities
100+
are equal, the resolver uses the order dependencies were discovered.
101+
5. **Cycles fail fast.** If the resulting graph contains a cycle, a
102+
`MiddlewareOrderCycleError` is raised with a human-readable cycle trace.
103+
104+
## Example
105+
106+
```python
107+
from functools import partial
108+
109+
class AuditMiddleware(AgentMiddleware):
110+
id = "audit"
111+
tags = ("observability",)
112+
113+
def requires(self) -> list[MiddlewareSpec]:
114+
return [
115+
MiddlewareSpec(
116+
factory=partial(RateLimitMiddleware, limit=10),
117+
merge_strategy="first_wins",
118+
ordering=OrderingConstraints(after=("tag:auth",)),
119+
)
120+
]
121+
122+
123+
class AuthMiddleware(AgentMiddleware):
124+
id = "auth"
125+
tags = ("auth",)
126+
127+
128+
agent = create_agent(
129+
model="openai:gpt-4o",
130+
middleware=[AuthMiddleware(), AuditMiddleware()],
131+
)
132+
```
133+
134+
Resolution order:
135+
136+
1. `RateLimitMiddleware` (after everything tagged `auth`, before `audit`).
137+
2. `AuthMiddleware` (user supplied).
138+
3. `AuditMiddleware`.
139+
140+
If another middleware also requests the same `RateLimitMiddleware` with
141+
`merge_strategy="first_wins"`, the resolver reuses the original instance and
142+
adds the new ordering constraints.
143+
144+
## Troubleshooting
145+
146+
- **Duplicate id error** – Supply a unique `id` or configure `merge_strategy`
147+
(`"first_wins"`/`"last_wins"`).
148+
- **Cycle detected** – Review the cycle path in the
149+
`MiddlewareOrderCycleError` message and adjust `before`/`after` constraints.
150+
- **Unknown id/tag** – Ensure referenced identifiers are spelled correctly and
151+
that tagged middleware sets `tags` before resolution.
152+
- **Unexpected ordering** – Remember that higher `priority` values preempt
153+
lower ones when no other constraints apply. Adjust `priority` or add explicit
154+
ordering constraints.

0 commit comments

Comments
 (0)