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-")
|