集成新的Beaver后端服务到应用实例中,替换原有的nanobot实现。 主要变更包括: - 在Dockerfile和环境配置中添加Beaver相关路径和配置变量 - 更新工作目录结构从.nanobot到.beaver - 实现Beaver引擎加载器,支持配置文件加载和工具组装 - 添加内置工具如ListDirectoryTool、ReadFileTool、SearchFilesTool - 更新消息处理流程,支持通道适配器和网关模式 - 重构技能系统,支持显式工具提示和嵌入式检索 - 改进错误处理和生命周期管理 此变更使应用实例能够使用统一的Beaver后端进行AI代理运行时管理。
128 lines
4.6 KiB
Python
128 lines
4.6 KiB
Python
"""Config loader for per-sandbox Beaver runtime settings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .schema import AgentDefaultsConfig, BeaverConfig, EmbeddingConfig, ProviderConfig
|
|
|
|
|
|
def default_config_path(*, workspace: str | Path | None = None) -> Path:
|
|
"""Resolve the default config path for a single-user sandbox instance.
|
|
|
|
Priority:
|
|
1. `BEAVER_CONFIG_PATH`
|
|
2. `NANOBOT_CONFIG_PATH` for compatibility during migration
|
|
3. `BEAVER_HOME/config.json`
|
|
4. `NANOBOT_HOME/config.json` for migration compatibility
|
|
5. `<workspace>/.beaver/config.json`
|
|
6. `./.beaver/config.json`
|
|
"""
|
|
|
|
explicit = os.getenv("BEAVER_CONFIG_PATH") or os.getenv("NANOBOT_CONFIG_PATH")
|
|
if explicit:
|
|
return Path(explicit).expanduser()
|
|
|
|
beaver_home = os.getenv("BEAVER_HOME")
|
|
if beaver_home:
|
|
return Path(beaver_home).expanduser() / "config.json"
|
|
|
|
nanobot_home = os.getenv("NANOBOT_HOME")
|
|
if nanobot_home:
|
|
return Path(nanobot_home).expanduser() / "config.json"
|
|
|
|
root = Path(workspace).expanduser() if workspace is not None else Path.cwd()
|
|
return root / ".beaver" / "config.json"
|
|
|
|
|
|
def load_config(
|
|
*,
|
|
workspace: str | Path | None = None,
|
|
config_path: str | Path | None = None,
|
|
) -> BeaverConfig:
|
|
"""Load backend config; missing config is treated as an empty config."""
|
|
|
|
path = Path(config_path).expanduser() if config_path is not None else default_config_path(workspace=workspace)
|
|
if not path.exists():
|
|
return BeaverConfig(config_path=path)
|
|
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
if not isinstance(data, dict):
|
|
raise ValueError(f"Beaver config must be a JSON object: {path}")
|
|
|
|
return BeaverConfig(
|
|
agents_defaults=_parse_agent_defaults(data),
|
|
providers=_parse_providers(data.get("providers")),
|
|
embedding=_parse_embedding(data),
|
|
config_path=path,
|
|
)
|
|
|
|
|
|
def _parse_agent_defaults(data: dict[str, Any]) -> AgentDefaultsConfig:
|
|
agents = _as_dict(data.get("agents"))
|
|
defaults = _as_dict(agents.get("defaults"))
|
|
return AgentDefaultsConfig(
|
|
workspace=_string(defaults.get("workspace") or data.get("workspace")),
|
|
model=_string(defaults.get("model") or data.get("model")),
|
|
provider=_string(defaults.get("provider") or data.get("provider")),
|
|
embedding_model=_string(defaults.get("embeddingModel") or defaults.get("embedding_model") or data.get("embeddingModel")),
|
|
)
|
|
|
|
|
|
def _parse_providers(raw: Any) -> dict[str, ProviderConfig]:
|
|
providers: dict[str, ProviderConfig] = {}
|
|
for name, payload in _as_dict(raw).items():
|
|
if not isinstance(payload, dict):
|
|
continue
|
|
providers[str(name)] = ProviderConfig(
|
|
api_key=_string(payload.get("apiKey") or payload.get("api_key")),
|
|
api_base=_string(payload.get("apiBase") or payload.get("api_base") or payload.get("baseUrl") or payload.get("base_url")),
|
|
extra_headers=_string_dict(payload.get("extraHeaders") or payload.get("extra_headers") or payload.get("headers")),
|
|
request_timeout_seconds=_float(
|
|
payload.get("requestTimeoutSeconds")
|
|
or payload.get("request_timeout_seconds")
|
|
or payload.get("timeout")
|
|
),
|
|
)
|
|
return providers
|
|
|
|
|
|
def _parse_embedding(data: dict[str, Any]) -> EmbeddingConfig:
|
|
raw = _as_dict(data.get("embedding") or data.get("embeddings"))
|
|
return EmbeddingConfig(
|
|
provider=_string(raw.get("provider") or raw.get("provider_name")),
|
|
model=_string(raw.get("model") or data.get("embeddingModel") or data.get("embedding_model")),
|
|
api_key=_string(raw.get("apiKey") or raw.get("api_key")),
|
|
api_base=_string(raw.get("apiBase") or raw.get("api_base") or raw.get("baseUrl") or raw.get("base_url")),
|
|
extra_headers=_string_dict(raw.get("extraHeaders") or raw.get("extra_headers") or raw.get("headers")),
|
|
request_timeout_seconds=_float(
|
|
raw.get("requestTimeoutSeconds") or raw.get("request_timeout_seconds") or raw.get("timeout")
|
|
),
|
|
)
|
|
|
|
|
|
def _as_dict(value: Any) -> dict[str, Any]:
|
|
return value if isinstance(value, dict) else {}
|
|
|
|
|
|
def _string(value: Any) -> str | None:
|
|
if value is None:
|
|
return None
|
|
value = str(value).strip()
|
|
return value or None
|
|
|
|
|
|
def _string_dict(value: Any) -> dict[str, str]:
|
|
if not isinstance(value, dict):
|
|
return {}
|
|
return {str(key): str(item) for key, item in value.items() if item is not None}
|
|
|
|
|
|
def _float(value: Any) -> float | None:
|
|
if value in (None, ""):
|
|
return None
|
|
return float(value)
|