105 lines
3.5 KiB
Python
105 lines
3.5 KiB
Python
from __future__ import annotations
|
||
|
||
import os
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
from typing import Optional
|
||
|
||
try:
|
||
from dotenv import load_dotenv
|
||
except Exception: # pragma: no cover
|
||
load_dotenv = None
|
||
|
||
|
||
@dataclass(slots=True)
|
||
class CoreAgentConfig:
|
||
model: str
|
||
api_key: Optional[str]
|
||
base_url: Optional[str]
|
||
timeout: float = 120.0
|
||
temperature: float = 0.2
|
||
max_iterations: int = 12
|
||
user_name: str = "User"
|
||
agent_name: str = "MeetingAgent"
|
||
|
||
|
||
def load_core_agent_env(env_dir: str | Path | None = None) -> Optional[Path]:
|
||
candidates = []
|
||
if env_dir:
|
||
candidates.append(Path(env_dir) / ".env")
|
||
candidates.append(Path.cwd() / ".env")
|
||
for path in candidates:
|
||
if not path.exists():
|
||
continue
|
||
if load_dotenv is not None:
|
||
load_dotenv(path, override=False)
|
||
else:
|
||
_load_env_without_dependency(path)
|
||
return path
|
||
return None
|
||
|
||
|
||
def apply_compat_env_aliases() -> None:
|
||
if not os.getenv("OPENAI_API_KEY") and os.getenv("API_KEY"):
|
||
os.environ["OPENAI_API_KEY"] = os.environ["API_KEY"]
|
||
if not os.getenv("OPENAI_BASE_URL") and os.getenv("BASE_URL"):
|
||
os.environ["OPENAI_BASE_URL"] = os.environ["BASE_URL"]
|
||
if not os.getenv("CORE_AGENT_MODEL") and os.getenv("MODEL_NAME"):
|
||
os.environ["CORE_AGENT_MODEL"] = os.environ["MODEL_NAME"]
|
||
|
||
|
||
def build_core_agent_config() -> CoreAgentConfig:
|
||
return CoreAgentConfig(
|
||
model=os.getenv("CORE_AGENT_MODEL") or os.getenv("MODEL_NAME") or os.getenv("MODEL") or "",
|
||
api_key=os.getenv("OPENAI_API_KEY") or os.getenv("API_KEY"),
|
||
base_url=os.getenv("OPENAI_BASE_URL") or os.getenv("BASE_URL"),
|
||
timeout=float(os.getenv("OPENAI_TIMEOUT", "120")),
|
||
temperature=float(os.getenv("OPENAI_TEMPERATURE", "0.2")),
|
||
max_iterations=int(os.getenv("CORE_AGENT_MAX_ITERATIONS", "12")),
|
||
user_name=os.getenv("USER_NAME", "User"),
|
||
agent_name=os.getenv("AGENT_NAME", "MeetingAgent"),
|
||
)
|
||
|
||
|
||
def require_model_config(config: CoreAgentConfig) -> None:
|
||
missing = []
|
||
if _is_missing_or_placeholder(config.api_key):
|
||
missing.append("OPENAI_API_KEY")
|
||
if _is_missing_or_placeholder(config.base_url):
|
||
missing.append("OPENAI_BASE_URL")
|
||
if _is_missing_or_placeholder(config.model):
|
||
missing.append("MODEL_NAME or CORE_AGENT_MODEL")
|
||
if missing:
|
||
joined = ", ".join(missing)
|
||
raise RuntimeError(
|
||
"缺少大模型配置:" + joined + "。请复制 .env.example 为 .env,并填写 OpenAI-compatible API 配置;"
|
||
"如果只想本地演示工具流程,请加 --offline。"
|
||
)
|
||
|
||
|
||
def _load_env_without_dependency(path: Path) -> None:
|
||
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
||
line = raw_line.strip()
|
||
if not line or line.startswith("#") or "=" not in line:
|
||
continue
|
||
key, value = line.split("=", 1)
|
||
key = key.strip()
|
||
value = value.strip().strip('"').strip("'")
|
||
if key and key not in os.environ:
|
||
os.environ[key] = value
|
||
|
||
|
||
def _is_missing_or_placeholder(value: Optional[str]) -> bool:
|
||
if value is None or not str(value).strip():
|
||
return True
|
||
normalized = str(value).strip().lower()
|
||
placeholders = {
|
||
"your-api-key-here",
|
||
"your-base-url-here",
|
||
"your-model-name-here",
|
||
"sk-xxx",
|
||
"xxx",
|
||
"changeme",
|
||
}
|
||
return normalized in placeholders or normalized.startswith("your-")
|