Skip to content

Add RunnableConfig Access in Agent Middleware Functions #33726

@beastrog

Description

@beastrog

Checked other resources

  • This is a feature request, not a bug report or usage question.
  • I added a clear and descriptive title that summarizes the feature request.
  • I used the GitHub search to find a similar feature request and didn't find it.
  • I checked the LangChain documentation and API reference to see if this feature already exists.
  • This is not related to the langchain-community package.

Feature Description

When using create_agent with middleware functions (specifically before_model hooks), developers cannot access the RunnableConfig object that was passed during agent invocation. This prevents implementing context-aware middleware behavior based on configuration parameters like metadata, tags, or custom settings.

Current Limitation

from langchain.agents import create_agent
from langchain_core.runnables import RunnableConfig

def logging_middleware(runtime):
    # PROBLEM: Cannot access the RunnableConfig passed to agent.invoke()
    # Need: runtime.config or similar to access metadata, tags, etc.
    
    # Desired behavior:
    # config = runtime.config
    # if config.metadata.get("log_level") == "debug":
    #     enable_detailed_logging()
    pass

# Setup
agent = create_agent(llm=my_llm, tools=my_tools, middleware=[logging_middleware])

# The config here is invisible to middleware
config = RunnableConfig(metadata={"log_level": "debug", "user_id": "123"})
result = agent.invoke("Hello", config=config)


### Use Case


## **Use Case**
```markdown
I need to implement sophisticated middleware for production LangChain applications that requires context-aware behavior based on the configuration passed during agent invocation.

### Specific Use Cases:

1. **Context-Aware Logging**: Log different levels of detail based on `config.metadata.get("log_level")`
2. **User-Specific Behavior**: Implement different middleware logic based on `config.metadata.get("user_id")` or user roles
3. **Environment-Specific Processing**: Handle production vs development differently using `config.tags`
4. **Audit Trail**: Track operations with run names and metadata for compliance
5. **Conditional Security**: Apply different security measures based on config parameters

### Current Problem:
- Must use global state or external configuration management (bad practice)
- Cannot implement dynamic middleware behavior based on invocation context
- Limits the usefulness of middleware for advanced monitoring and debugging
- Forces workarounds that make code harder to maintain and test

### Business Impact:
This limitation prevents building production-ready LangChain applications that need sophisticated logging, monitoring, and conditional behavior in middleware layers.


### Proposed Solution

Extend the runtime context object passed to middleware functions to include a `config` property containing the `RunnableConfig` from agent invocation.

### Implementation Approach:
1. **Agent Invocation**: Capture the `RunnableConfig` in the `invoke()` method
2. **Runtime Context**: Extend the runtime object to include `config` property
3. **Middleware Execution**: Ensure config is propagated through the middleware chain
4. **Default Handling**: Provide empty `RunnableConfig()` when none is passed

### Key Files Likely to Change:
- `langchain/agents/agent.py` or similar agent implementation files
- Middleware execution logic
- Runtime context creation/management

### Test Cases to Validate:
```python
def test_middleware_can_access_config():
    captured_config = None
    
    def capture_config_middleware(runtime):
        nonlocal captured_config
        captured_config = runtime.config
        return runtime
    
    agent = create_agent(middleware=[capture_config_middleware])
    test_config = RunnableConfig(metadata={"test_key": "test_value"})
    
    agent.invoke("test input", config=test_config)
    
    assert captured_config is not None
    assert captured_config.metadata["test_key"] == "test_value"


### Alternatives Considered


## **Alternatives Considered**
```markdown
### 1. Global State Management
- **Tried**: Using global variables to store config
- **Problem**: Not thread-safe, makes testing difficult, violates clean architecture principles

### 2. Custom Middleware Base Class
- **Considered**: Creating a new middleware base class that accepts config
- **Problem**: Would break existing middleware, requires major API changes

### 3. Context Managers
- **Considered**: Using Python context managers to inject config
- **Problem**: Doesn't integrate well with LangChain's existing architecture

### 4. Callback-Based Approach
- **Considered**: Using callbacks to pass config to middleware
- **Problem**: More complex API, doesn't fit the current middleware pattern

### 5. Environment Variables
- **Tried**: Using environment variables for configuration
- **Problem**: Not dynamic per-invocation, doesn't support complex metadata structures

The proposed solution of extending the runtime context is the most elegant because it:
- Maintains backward compatibility
- Follows existing LangChain patterns
- Requires minimal API changes
- Provides clean access to all config properties


### Additional Context

### Related Issues:
- This addresses the limitation mentioned in issue #33721
- Similar to how other LangChain components provide access to configuration context

### Success Criteria:
1.Middleware can access `runtime.config` containing the `RunnableConfig` from `agent.invoke()`
2.All `RunnableConfig` properties (metadata, tags, run_name, etc.) are accessible
3.When no config is passed to `invoke()`, `runtime.config` returns `RunnableConfig()`
4.Multiple middleware functions in a chain all receive the same config
5.Existing middleware without config access continues to work
6.All test cases pass

### Implementation Complexity:
- **Difficulty**: Medium (3/5)
- **Estimated Time**: 2-3 hours for experienced contributor
- **Impact**: High - enables sophisticated middleware patterns for production applications

### Example Production Use Case:
```python
def audit_middleware(runtime):
    config = runtime.config
    user_id = config.metadata.get("user_id")
    operation = config.metadata.get("operation")
    
    # Log to audit system
    audit_logger.info(f"User {user_id} performing {operation}", 
                     extra={"run_name": config.run_name, "tags": config.tags})
    
    return runtime

def security_middleware(runtime):
    config = runtime.config
    if "sensitive" in (config.tags or []):
        # Apply additional security measures
        enable_data_redaction()
    
    return runtime

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestrequest for an enhancement / additional functionality

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions