feat(memory): add hybrid gateway configuration

This commit is contained in:
2026-06-15 11:05:23 +08:00
parent 25e7dfba88
commit f4bdfc0717
4 changed files with 207 additions and 0 deletions

View File

@ -7,6 +7,8 @@ from .schema import (
BackendIdentityConfig,
BeaverConfig,
EmbeddingConfig,
MemoryConfig,
MemoryGatewayConfig,
MCPServerConfig,
ProviderConfig,
ToolsConfig,
@ -18,6 +20,8 @@ __all__ = [
"BackendIdentityConfig",
"BeaverConfig",
"EmbeddingConfig",
"MemoryConfig",
"MemoryGatewayConfig",
"MCPServerConfig",
"ProviderConfig",
"ToolsConfig",

View File

@ -15,6 +15,8 @@ from .schema import (
BeaverConfig,
ChannelConfig,
EmbeddingConfig,
MemoryConfig,
MemoryGatewayConfig,
MCPServerConfig,
ProviderConfig,
ToolsConfig,
@ -76,6 +78,7 @@ def load_config(
authz=_parse_authz(data.get("authz")),
channels=_parse_channels(data.get("channels")),
backend_identity=_parse_backend_identity(data.get("backend_identity") or data.get("backendIdentity")),
memory=_parse_memory(data),
config_path=path,
)
@ -251,6 +254,50 @@ def _parse_backend_identity(raw: Any) -> BackendIdentityConfig:
)
def _parse_memory(data: dict[str, Any]) -> MemoryConfig:
explicit = "memory" in data
raw = _as_dict(data.get("memory"))
mode = (_string(raw.get("mode")) or "hybrid").lower()
if mode not in {"curated", "hybrid"}:
raise ValueError("memory.mode must be 'curated' or 'hybrid'")
gateway_raw = _as_dict(raw.get("gateway"))
parsed_top_k = _int(_first_config_value(gateway_raw.get("topK"), gateway_raw.get("top_k")))
parsed_timeout = _float(
_first_config_value(gateway_raw.get("timeoutSeconds"), gateway_raw.get("timeout_seconds"))
)
gateway = MemoryGatewayConfig(
base_url=_string(gateway_raw.get("baseUrl") or gateway_raw.get("base_url")) or "",
user_id=_string(gateway_raw.get("userId") or gateway_raw.get("user_id")) or "",
user_key=_string(gateway_raw.get("userKey") or gateway_raw.get("user_key")) or "",
app_id=_string(gateway_raw.get("appId") or gateway_raw.get("app_id")) or "default",
project_id=_string(gateway_raw.get("projectId") or gateway_raw.get("project_id")) or "default",
scope=_string_list(gateway_raw.get("scope")) or ["current_chat", "resources"],
top_k=8 if parsed_top_k is None else parsed_top_k,
timeout_seconds=10.0 if parsed_timeout is None else parsed_timeout,
)
if mode == "hybrid" and explicit:
missing: list[str] = []
if not gateway.base_url:
missing.append("baseUrl")
if not gateway.user_id:
missing.append("userId")
if not gateway.user_key:
missing.append("userKey")
if missing:
raise ValueError(f"Explicit hybrid memory requires gateway fields: {', '.join(missing)}")
allowed_scopes = {"current_chat", "resources", "all_user_memory"}
if not gateway.scope or any(scope not in allowed_scopes for scope in gateway.scope):
raise ValueError("memory.gateway.scope contains an unsupported value")
if gateway.top_k < 1 or gateway.top_k > 100:
raise ValueError("memory.gateway.topK must be between 1 and 100")
if gateway.timeout_seconds <= 0:
raise ValueError("memory.gateway.timeoutSeconds must be positive")
return MemoryConfig(mode=mode, explicit=explicit, gateway=gateway)
def _as_dict(value: Any) -> dict[str, Any]:
return value if isinstance(value, dict) else {}

View File

@ -115,6 +115,33 @@ class BackendIdentityConfig:
public_base_url: str = ""
@dataclass(slots=True)
class MemoryGatewayConfig:
"""Fixed Memory Gateway settings for one Beaver instance."""
base_url: str = ""
user_id: str = ""
user_key: str = field(default="", repr=False)
app_id: str = "default"
project_id: str = "default"
scope: list[str] = field(default_factory=lambda: ["current_chat", "resources"])
top_k: int = 8
timeout_seconds: float = 10.0
@property
def is_configured(self) -> bool:
return bool(_clean(self.base_url) and _clean(self.user_id) and _clean(self.user_key))
@dataclass(slots=True)
class MemoryConfig:
"""Curated baseline plus optional Memory Gateway layer."""
mode: str = "hybrid"
explicit: bool = False
gateway: MemoryGatewayConfig = field(default_factory=MemoryGatewayConfig)
@dataclass(slots=True)
class BeaverConfig:
"""Config loaded once per backend sandbox instance."""
@ -126,6 +153,7 @@ class BeaverConfig:
authz: AuthzConfig = field(default_factory=AuthzConfig)
channels: dict[str, ChannelConfig] = field(default_factory=dict)
backend_identity: BackendIdentityConfig = field(default_factory=BackendIdentityConfig)
memory: MemoryConfig = field(default_factory=MemoryConfig)
config_path: Path | None = None
@property