diff --git a/pipeline/core/builder.py b/pipeline/core/builder.py
index a0ec66207..8185dd755 100644
--- a/pipeline/core/builder.py
+++ b/pipeline/core/builder.py
@@ -51,6 +51,7 @@ def __init__(self, src_dir: Path, build_dir: Path) -> None:
".yaml",
".css",
".js",
+ ".html",
}
# Mapping of language codes to full names for URLs
@@ -152,8 +153,8 @@ def rewrite_link(match: re.Match) -> str:
url = match.group(2) # The URL
post = match.group(3) # Everything after the URL
- # Only rewrite absolute /oss/ paths that don't contain 'images'
- if url.startswith("/oss/") and "images" not in url:
+ # Only rewrite absolute /oss/ paths that don't contain 'images' or 'plugins'
+ if url.startswith("/oss/") and "images" not in url and "plugins" not in url:
parts = url.split("/")
# Insert full language name after "oss"
parts.insert(2, self.language_url_names[target_language])
@@ -739,8 +740,11 @@ def is_shared_file(self, file_path: Path) -> bool:
if "snippets" in relative_path.parts:
return True
+ if "plugins" in relative_path.parts:
+ return True
+
# JavaScript and CSS files should be shared (used for custom scripts/styles)
- return file_path.suffix.lower() in {".js", ".css"}
+ return file_path.suffix.lower() in {".js", ".css", ".html", ".json"}
def _copy_shared_files(self) -> None:
"""Copy files that should be shared between versions."""
diff --git a/pyproject.toml b/pyproject.toml
index 4231dbe5f..b9abdd8cf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -70,7 +70,8 @@ asyncio_default_fixture_loop_scope = "function"
line-length = 88
extend-exclude = [
"scripts/update_mdx.py",
- "pipeline/tools/notebook/convert.py"
+ "pipeline/tools/notebook/convert.py",
+ "src/plugins/middleware_visualization/generate_middleware_diagrams.py"
]
@@ -133,3 +134,9 @@ ignore = [
[tool.ruff.lint.pydocstyle]
convention = "google"
+
+
+[tool.mypy]
+exclude = [
+ "src/plugins/middleware_visualization/generate_middleware_diagrams.py",
+]
diff --git a/src/oss/langchain/agents.mdx b/src/oss/langchain/agents.mdx
index 32d14582b..a6ae27480 100644
--- a/src/oss/langchain/agents.mdx
+++ b/src/oss/langchain/agents.mdx
@@ -746,3 +746,22 @@ For comprehensive middleware documentation including decorators like `@before_mo
For comprehensive middleware documentation including hooks like `beforeModel`, `afterModel`, and `wrapToolCall`, see [Middleware](/oss/langchain/middleware).
:::
+
+### Visualizing agents
+
+The interactive widget below shows how middleware hooks integrate into the agent execution flow. Toggle different hook combinations to see how they affect the agent's graph structure:
+
+
+
+This visualization helps you understand:
+- Where each middleware hook executes in the agent lifecycle
+- How multiple middleware components interact
+- The execution order of before/after hooks vs wrap-style hooks
+
+
+For detailed documentation on each hook type and how to implement custom middleware, see [Middleware](/oss/langchain/middleware).
+
diff --git a/src/oss/langchain/middleware.mdx b/src/oss/langchain/middleware.mdx
index 273557c8d..19f1fcb33 100644
--- a/src/oss/langchain/middleware.mdx
+++ b/src/oss/langchain/middleware.mdx
@@ -971,22 +971,28 @@ agent = create_agent(
#### Node-style hooks
-Run at specific points in the execution flow:
+Run at specific points in the execution flow. Each hook receives the current state and runtime, and can optionally return state updates or jump commands.
+
+**Available hooks:**
:::python
-- `before_agent` - Before agent starts (once per invocation)
-- `before_model` - Before each model call
-- `after_model` - After each model response
-- `after_agent` - After agent completes (up to once per invocation)
+- `before_agent` - Runs once when the agent starts, before any processing begins. Use for initialization, logging, or validating inputs.
+- `before_model` - Runs before each model call. Use for context injection, message trimming, or pre-processing prompts.
+- `after_model` - Runs after each model response. Use for validation, content filtering, or logging model outputs.
+- `after_agent` - Runs once when the agent completes, after all processing is done. Use for cleanup, final validation, or result transformation.
:::
:::js
-- `beforeAgent` - Before agent starts (once per invocation)
-- `beforeModel` - Before each model call
-- `afterModel` - After each model response
-- `afterAgent` - After agent completes (up to once per invocation)
+- `beforeAgent` - Runs once when the agent starts, before any processing begins. Use for initialization, logging, or validating inputs.
+- `beforeModel` - Runs before each model call. Use for context injection, message trimming, or pre-processing prompts.
+- `afterModel` - Runs after each model response. Use for validation, content filtering, or logging model outputs.
+- `afterAgent` - Runs once when the agent completes, after all processing is done. Use for cleanup, final validation, or result transformation.
:::
+
+Want to see how these hooks fit into the agent execution flow? Check out the [interactive visualization in the agents documentation](/oss/langchain/agents#visualizing-agents).
+
+
**Example: Logging middleware**
:::python
@@ -1072,16 +1078,18 @@ const createMessageLimitMiddleware = (maxMessages: number = 50) => {
#### Wrap-style hooks
-Intercept execution and control when the handler is called:
+Intercept execution and control when the handler is called. These hooks wrap around the actual execution and give you complete control over whether and how the handler runs.
+
+**Available hooks:**
:::python
-- `wrap_model_call` - Around each model call
-- `wrap_tool_call` - Around each tool call
+- `wrap_model_call` - Wraps around each model call. Receives a `ModelRequest` and handler function. Use for retry logic, caching, dynamic model selection, or request/response transformation.
+- `wrap_tool_call` - Wraps around each tool call. Receives a `ToolCallRequest` and handler function. Use for tool error handling, logging tool execution, or modifying tool inputs/outputs.
:::
:::js
-- `wrapModelCall` - Around each model call
-- `wrapToolCall` - Around each tool call
+- `wrapModelCall` - Wraps around each model call. Receives a request object and handler function. Use for retry logic, caching, dynamic model selection, or request/response transformation.
+- `wrapToolCall` - Wraps around each tool call. Receives a request object and handler function. Use for tool error handling, logging tool execution, or modifying tool inputs/outputs.
:::
You decide if the handler is called zero times (short-circuit), once (normal flow), or multiple times (retry logic).
diff --git a/src/plugins/middleware_visualization/README.md b/src/plugins/middleware_visualization/README.md
new file mode 100644
index 000000000..d2a1c70a4
--- /dev/null
+++ b/src/plugins/middleware_visualization/README.md
@@ -0,0 +1,63 @@
+# Middleware Hooks Visualizer
+
+Minimal interactive visualizer for LangChain middleware hooks and agent graphs.
+
+## Features
+
+- **Language-specific diagrams**: Automatically switches between Python (snake_case) and JavaScript (camelCase) naming based on active tab
+- **Interactive tooltips**: Hover over checkboxes to see what each hook does
+- **Compact layout**: Optimized spacing for better visual hierarchy
+- **Mintlify integration**: Uses Mintlify design tokens for consistent styling
+
+## Usage
+
+The `index.html` file can be embedded directly into Mintlify documentation:
+
+```html
+
+```
+
+**Binary naming scheme (5 bits):**
+```
+Bit 0: tools
+Bit 1: before_agent
+Bit 2: before_model
+Bit 3: after_model
+Bit 4: after_agent
+```
+
+Example: `10110` = tools + before_model + after_model
+
+## Files
+
+- `index.html` - Embeddable widget with interactive controls and diagram rendering
+- `diagrams_python.js` - Python version with snake_case naming (e.g., `before_agent`)
+- `diagrams_js.js` - JavaScript version with camelCase naming (e.g., `beforeAgent`)
+- `generate_middleware_diagrams.py` - Diagram generator script
+
+## Regenerating Diagrams
+
+To regenerate the diagrams after making changes to middleware hooks:
+
+```bash
+uv run python src/plugins/middleware_visualization/generate_middleware_diagrams.py
+```
+
+This generates three files:
+- `diagrams_python.js` - Python diagrams with snake_case hooks
+- `diagrams_js.js` - JavaScript diagrams with camelCase hooks
+
+### Diagram Configuration
+
+The generator creates compact diagrams with:
+- `nodeSpacing: 30` - Horizontal spacing between nodes
+- `rankSpacing: 40` - Vertical spacing between ranks
+- `padding: 10` - Diagram padding
+
+Adjust these values in `generate_middleware_diagrams.py` line 144-150 to modify diagram height.
+
+**Note**: Mintlify's dev server doesn't serve JSON files, so we convert the data to JavaScript files that can be loaded via `
+
+
diff --git a/tests/unit_tests/test_builder.py b/tests/unit_tests/test_builder.py
index 6ce5d874e..e7d485d2a 100644
--- a/tests/unit_tests/test_builder.py
+++ b/tests/unit_tests/test_builder.py
@@ -37,6 +37,7 @@ def test_builder_initialization() -> None:
".yaml",
".css",
".js",
+ ".html",
}