wutongyuonce 4470932118
feat(extensions): allow constructor kwargs on config-declared middlewares (#5312)
* feat(extensions): allow constructor kwargs on config-declared middlewares

extensions.middlewares entries may be a class-path string or {class, kwargs}.
String entries keep the zero-argument constructor. Unknown fields and blank
class paths fail at config validation. Constructor errors still fail at
agent creation.

Fixes #5311

* fix(extensions): coerce middleware kwargs to JSON types

YAML timestamps became datetime objects while JSON kept strings, so
constructors and to_file_dict() json.dump saw different types. Validate
kwargs as JSON types at config load, stringify dates, reject NaN and
other non-JSON values, and cover the raw-dict loader branch.

* style(extensions): wrap middleware Field description for ruff E501

make lint failed: the middlewares description was 289 chars (limit 240).
Wrap it and run ruff format on the two files this PR last touched.

* docs: compact configured middleware guidance to satisfy size limit

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-12 10:57:40 +08:00

48 lines
1.9 KiB
Python

"""Config-declared agent middleware loading."""
import logging
from typing import TYPE_CHECKING, Any
from langchain.agents.middleware import AgentMiddleware
from deerflow.config.extensions_config import ConfiguredMiddlewareSpec
from deerflow.reflection import resolve_class
if TYPE_CHECKING:
from deerflow.config.app_config import AppConfig
logger = logging.getLogger(__name__)
def _middleware_constructor_args(entry: str | ConfiguredMiddlewareSpec | dict[str, Any]) -> tuple[str, dict[str, Any]]:
"""Return ``(class_path, kwargs)`` for one config entry."""
if isinstance(entry, str):
return entry, {}
if isinstance(entry, ConfiguredMiddlewareSpec):
return entry.class_path, dict(entry.kwargs)
parsed = ConfiguredMiddlewareSpec.model_validate(entry)
return parsed.class_path, dict(parsed.kwargs)
def load_configured_extension_middlewares(app_config: "AppConfig") -> list[AgentMiddleware]:
"""Instantiate config-declared agent middlewares.
Each entry is a ``module.path:ClassName`` string or a
``ConfiguredMiddlewareSpec`` (``class`` plus optional ``kwargs``).
Import, attribute, and subclass validation intentionally go through the
shared reflection resolver so failures carry the same actionable
dependency hints as models, tools, sandbox providers, and guardrail
providers. Constructor errors fail loudly at agent creation.
"""
middlewares: list[AgentMiddleware] = []
for entry in list(app_config.extensions.middlewares or []):
class_path, kwargs = _middleware_constructor_args(entry)
middleware_cls = resolve_class(class_path, AgentMiddleware)
try:
middleware = middleware_cls(**kwargs)
except Exception:
logger.exception("Failed to instantiate configured extension middleware %s", class_path)
raise
middlewares.append(middleware)
return middlewares