Skip to content

Commit be54430

Browse files
committed
feat(agents): add middleware dependency resolution
1 parent 8a3bb73 commit be54430

File tree

6 files changed

+907
-3
lines changed

6 files changed

+907
-3
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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+
## `OrderingConstraints`
56+
57+
Ordering constraints ensure dependencies line up with other middleware:
58+
59+
```python
60+
MiddlewareSpec(
61+
factory=AuthMiddleware,
62+
ordering=OrderingConstraints(
63+
after=("tag:session",),
64+
before=("retry-handler",),
65+
),
66+
)
67+
```
68+
69+
- `after` accepts middleware identifiers or `tag:<tag-name>` references that
70+
must execute **before** the dependency.
71+
- `before` accepts identifiers or tags that must execute **after** the
72+
dependency.
73+
- Tag references apply the constraint to every middleware with that tag.
74+
75+
Self references are not allowed. Referencing an unknown id or tag raises a
76+
`ValueError` during agent creation.
77+
78+
## Ordering semantics
79+
80+
Middleware resolution produces a deterministic order by applying the following
81+
rules:
82+
83+
1. **User order is the starting point.** Middleware passed to `create_agent`
84+
retains its relative order whenever no other constraint applies.
85+
2. **Dependencies run before their requestor.** Declared dependencies are
86+
inserted ahead of the middleware that required them.
87+
3. **Before/after constraints add graph edges.** Constraints from
88+
`OrderingConstraints` augment the ordering graph by `id` or `tag`.
89+
4. **Priority breaks ties.** When multiple nodes can execute next and the user
90+
order does not distinguish them, higher `priority` values win. If priorities
91+
are equal, the resolver uses the order dependencies were discovered.
92+
5. **Cycles fail fast.** If the resulting graph contains a cycle, a
93+
`MiddlewareOrderCycleError` is raised with a human-readable cycle trace.
94+
95+
## Example
96+
97+
```python
98+
from functools import partial
99+
100+
class AuditMiddleware(AgentMiddleware):
101+
id = "audit"
102+
tags = ("observability",)
103+
104+
def requires(self) -> list[MiddlewareSpec]:
105+
return [
106+
MiddlewareSpec(
107+
factory=partial(RateLimitMiddleware, limit=10),
108+
merge_strategy="first_wins",
109+
ordering=OrderingConstraints(after=("tag:auth",)),
110+
)
111+
]
112+
113+
114+
class AuthMiddleware(AgentMiddleware):
115+
id = "auth"
116+
tags = ("auth",)
117+
118+
119+
agent = create_agent(
120+
model="openai:gpt-4o",
121+
middleware=[AuthMiddleware(), AuditMiddleware()],
122+
)
123+
```
124+
125+
Resolution order:
126+
127+
1. `RateLimitMiddleware` (after everything tagged `auth`, before `audit`).
128+
2. `AuthMiddleware` (user supplied).
129+
3. `AuditMiddleware`.
130+
131+
If another middleware also requests the same `RateLimitMiddleware` with
132+
`merge_strategy="first_wins"`, the resolver reuses the original instance and
133+
adds the new ordering constraints.
134+
135+
## Troubleshooting
136+
137+
- **Duplicate id error** – Supply a unique `id` or configure `merge_strategy`
138+
(`"first_wins"`/`"last_wins"`).
139+
- **Cycle detected** – Review the cycle path in the
140+
`MiddlewareOrderCycleError` message and adjust `before`/`after` constraints.
141+
- **Unknown id/tag** – Ensure referenced identifiers are spelled correctly and
142+
that tagged middleware sets `tags` before resolution.
143+
- **Unexpected ordering** – Remember that higher `priority` values preempt
144+
lower ones when no other constraints apply. Adjust `priority` or add explicit
145+
ordering constraints.

0 commit comments

Comments
 (0)