Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions pipeline/core/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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."""
Expand Down
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]


Expand Down Expand Up @@ -133,3 +134,9 @@ ignore = [

[tool.ruff.lint.pydocstyle]
convention = "google"


[tool.mypy]
exclude = [
"src/plugins/middleware_visualization/generate_middleware_diagrams.py",
]
19 changes: 19 additions & 0 deletions src/oss/langchain/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).
</Tip>
:::

### 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:

<iframe
src="/plugins/middleware_visualization/index.html"
style={{ width: "100%", height: "600px", border: "none" }}
title="Interactive Middleware Visualizer"
/>

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

<Tip>
For detailed documentation on each hook type and how to implement custom middleware, see [Middleware](/oss/langchain/middleware).
</Tip>
36 changes: 22 additions & 14 deletions src/oss/langchain/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
:::

<Tip>
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).
</Tip>

**Example: Logging middleware**

:::python
Expand Down Expand Up @@ -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).
Expand Down
63 changes: 63 additions & 0 deletions src/plugins/middleware_visualization/README.md
Original file line number Diff line number Diff line change
@@ -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
<iframe
src="/plugins/middleware_visualization/index.html"
style={{ width: "100%", height: "600px", border: "none" }}
title="Interactive Middleware Visualizer"
/>
```

**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 `<script src>`.
1 change: 1 addition & 0 deletions src/plugins/middleware_visualization/diagrams_js.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/plugins/middleware_visualization/diagrams_python.js

Large diffs are not rendered by default.

Loading