7.1 环境变量管理
🎯 小白理解:什么是"环境变量"?
想象你开了一家餐厅,有些信息不能写在菜单上:
- 菜单(代码):写"使用 API 获取数据"
- 保险箱(环境变量):存放真正的 API 密钥
sk-xxxxx为什么不能直接把密钥写在代码里?
python# ❌ 错误做法!密钥会被上传到 GitHub api_key = "sk-abc123xyz456" # ✅ 正确做法!密钥存在环境变量里 api_key = os.getenv("OPENAI_API_KEY")环境变量的好处:
优点 解释 安全 密钥不会被提交到 Git 灵活 不改代码就能换配置 分离 开发/测试/生产用不同的值 类比:环境变量就像密码管理器——你的程序知道去哪里取密钥,但密钥本身不会暴露在代码里。
环境变量基础
🎯 小白理解:
os.getenv()怎么用?pythonos.getenv("变量名") # 获取变量,没有则返回 None os.getenv("变量名", "默认值") # 获取变量,没有则返回默认值就像从保险箱取东西:有就拿出来,没有就用备用方案。
python
import os
from pathlib import Path
# 读取环境变量
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("MODEL_NAME", "gpt-4") # 带默认值
# 设置环境变量(当前进程)
os.environ["DEBUG"] = "true"
# 检查环境变量是否存在
if "OPENAI_API_KEY" in os.environ:
print("API Key 已配置")
else:
print("警告: API Key 未配置").env 文件管理
🎯 小白理解:什么是
.env文件?
.env文件就是一个专门存放环境变量的"配置文件":文件名: .env(以点开头,是隐藏文件) 位置: 放在项目根目录 格式: 变量名=值(一行一个)为什么用 .env 文件?
- 不用每次在终端设置环境变量
- 方便团队共享配置(但不共享真实密钥!)
- 使用
python-dotenv库自动加载⚠️ 重要规则:
.env文件绝对不能提交到 Git(加入.gitignore)- 创建
.env.example作为模板(只写变量名,不写真实值)
安装 python-dotenv
bash
pip install python-dotenv创建 .env 文件
env
# .env 文件内容
OPENAI_API_KEY=sk-xxx
ANTHROPIC_API_KEY=sk-ant-xxx
MODEL_NAME=gpt-4
TEMPERATURE=0.7
MAX_TOKENS=2000
DEBUG=false
LOG_LEVEL=INFO
DATABASE_URL=postgresql://user:pass@localhost/db加载环境变量
python
from dotenv import load_dotenv
import os
from pathlib import Path
# 方法 1: 从当前目录加载
load_dotenv()
# 方法 2: 指定 .env 文件路径
env_path = Path(".") / ".env"
load_dotenv(dotenv_path=env_path)
# 方法 3: 覆盖已有环境变量
load_dotenv(override=True)
# 使用环境变量
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("MODEL_NAME")
temperature = float(os.getenv("TEMPERATURE", 0.7))
debug = os.getenv("DEBUG", "false").lower() == "true"
print(f"模型: {model}")
print(f"温度: {temperature}")
print(f"调试模式: {debug}")配置管理类
🎯 小白理解:为什么要写"配置管理类"?
直接用
os.getenv()有几个问题:
- 每次都要写
os.getenv("OPENAI_API_KEY"),很长- 容易打错变量名(字符串没有代码补全)
- 类型不安全(
os.getenv()返回的都是字符串)配置类的好处:
python# 没有配置类 api_key = os.getenv("OPENAI_API_KEY") # 每次都写这么长 temp = float(os.getenv("TEMPERATURE", "0.7")) # 还要手动转类型 # 有配置类 config = Config.from_env() api_key = config.openai_api_key # 代码补全! temp = config.temperature # 已经是 float 类型!配置类就像一个"助手",帮你把所有环境变量整理好、转换好类型、验证好格式。
python
from typing import Optional, Dict, Any
from dataclasses import dataclass, field
from pathlib import Path
import os
from dotenv import load_dotenv
@dataclass
class Config:
"""应用配置管理"""
# API 配置
openai_api_key: str
anthropic_api_key: Optional[str] = None
# 模型配置
model_name: str = "gpt-4"
temperature: float = 0.7
max_tokens: int = 2000
# 应用配置
debug: bool = False
log_level: str = "INFO"
# 数据库配置
database_url: Optional[str] = None
@classmethod
def from_env(cls, env_file: str = ".env") -> "Config":
"""从环境变量加载配置"""
# 加载 .env 文件
if Path(env_file).exists():
load_dotenv(env_file)
# 读取配置
return cls(
openai_api_key=os.getenv("OPENAI_API_KEY", ""),
anthropic_api_key=os.getenv("ANTHROPIC_API_KEY"),
model_name=os.getenv("MODEL_NAME", "gpt-4"),
temperature=float(os.getenv("TEMPERATURE", "0.7")),
max_tokens=int(os.getenv("MAX_TOKENS", "2000")),
debug=os.getenv("DEBUG", "false").lower() == "true",
log_level=os.getenv("LOG_LEVEL", "INFO"),
database_url=os.getenv("DATABASE_URL")
)
def validate(self) -> bool:
"""验证配置"""
if not self.openai_api_key:
raise ValueError("OPENAI_API_KEY 未配置")
if not self.openai_api_key.startswith("sk-"):
raise ValueError("OPENAI_API_KEY 格式错误")
if self.temperature < 0 or self.temperature > 2:
raise ValueError("temperature 必须在 0-2 之间")
return True
def to_dict(self) -> Dict[str, Any]:
"""转换为字典(隐藏敏感信息)"""
return {
"openai_api_key": "sk-***" if self.openai_api_key else None,
"anthropic_api_key": "sk-ant-***" if self.anthropic_api_key else None,
"model_name": self.model_name,
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"debug": self.debug,
"log_level": self.log_level,
"database_url": "***" if self.database_url else None
}
# 使用示例
config = Config.from_env()
config.validate()
print(config.to_dict())多环境配置
python
from enum import Enum
from pathlib import Path
import os
class Environment(Enum):
"""环境类型"""
DEVELOPMENT = "development"
STAGING = "staging"
PRODUCTION = "production"
class EnvironmentConfig:
"""多环境配置管理"""
def __init__(self):
self.env = self._detect_environment()
self._load_config()
def _detect_environment(self) -> Environment:
"""检测当前环境"""
env_name = os.getenv("ENV", "development").lower()
if env_name in ["prod", "production"]:
return Environment.PRODUCTION
elif env_name in ["stage", "staging"]:
return Environment.STAGING
else:
return Environment.DEVELOPMENT
def _load_config(self):
"""加载对应环境的配置"""
# 加载基础配置
base_env = Path(".env")
if base_env.exists():
load_dotenv(base_env)
# 加载环境特定配置
env_file = Path(f".env.{self.env.value}")
if env_file.exists():
load_dotenv(env_file, override=True)
@property
def is_production(self) -> bool:
return self.env == Environment.PRODUCTION
@property
def is_development(self) -> bool:
return self.env == Environment.DEVELOPMENT
def get_config(self) -> Config:
"""获取配置"""
return Config.from_env()
# 使用示例
env_config = EnvironmentConfig()
print(f"当前环境: {env_config.env.value}")
print(f"是否生产环境: {env_config.is_production}")
config = env_config.get_config()Agent 配置系统
python
from typing import Optional, List, Dict, Any
from dataclasses import dataclass, field
from pathlib import Path
import os
from dotenv import load_dotenv
@dataclass
class AgentConfig:
"""Agent 配置"""
# 基础配置
name: str
description: str
# LLM 配置
model: str = "gpt-4"
temperature: float = 0.7
max_tokens: int = 2000
# 工具配置
tools: List[str] = field(default_factory=list)
# 系统提示词
system_prompt: str = "你是一个有用的助手。"
# API 配置
api_key: Optional[str] = None
api_base: Optional[str] = None
# 高级配置
streaming: bool = True
retry_attempts: int = 3
timeout: float = 30.0
@classmethod
def from_env(cls, agent_name: str) -> "AgentConfig":
"""从环境变量加载 Agent 配置"""
load_dotenv()
# 读取通用配置
prefix = f"{agent_name.upper()}_"
return cls(
name=agent_name,
description=os.getenv(f"{prefix}DESCRIPTION", ""),
model=os.getenv(f"{prefix}MODEL", os.getenv("MODEL_NAME", "gpt-4")),
temperature=float(os.getenv(f"{prefix}TEMPERATURE",
os.getenv("TEMPERATURE", "0.7"))),
max_tokens=int(os.getenv(f"{prefix}MAX_TOKENS",
os.getenv("MAX_TOKENS", "2000"))),
tools=os.getenv(f"{prefix}TOOLS", "").split(",") if os.getenv(f"{prefix}TOOLS") else [],
system_prompt=os.getenv(f"{prefix}SYSTEM_PROMPT", "你是一个有用的助手。"),
api_key=os.getenv("OPENAI_API_KEY"),
api_base=os.getenv("OPENAI_API_BASE"),
streaming=os.getenv(f"{prefix}STREAMING", "true").lower() == "true",
retry_attempts=int(os.getenv(f"{prefix}RETRY_ATTEMPTS", "3")),
timeout=float(os.getenv(f"{prefix}TIMEOUT", "30.0"))
)
# .env 示例
"""
# 通用配置
OPENAI_API_KEY=sk-xxx
MODEL_NAME=gpt-4
TEMPERATURE=0.7
# Research Agent 配置
RESEARCH_DESCRIPTION=专业的研究助手
RESEARCH_MODEL=gpt-4
RESEARCH_TOOLS=search,calculator,wikipedia
RESEARCH_SYSTEM_PROMPT=你是一个专业的研究助手,擅长查找和分析信息。
# Writer Agent 配置
WRITER_DESCRIPTION=专业的写作助手
WRITER_MODEL=gpt-3.5-turbo
WRITER_TEMPERATURE=0.9
WRITER_SYSTEM_PROMPT=你是一个创意写作助手,风格优美简洁。
"""
# 使用示例
research_config = AgentConfig.from_env("research")
writer_config = AgentConfig.from_env("writer")
print(f"Research Agent: {research_config.model}, 工具: {research_config.tools}")
print(f"Writer Agent: {writer_config.model}, 温度: {writer_config.temperature}")密钥管理最佳实践
🎯 小白理解:为什么需要"密钥管理"?
API 密钥就像你家的钥匙:
- 泄露了 = 别人可以用你的钱调用 API
- 格式错了 = 程序会报错
- 没配置 = 程序无法运行
SecretManager 能帮你:
功能 作用 get_api_key()安全获取密钥,没有就报错 validate_api_key()检查密钥格式是否正确 mask_secret()隐藏密钥(打印日志时用) 为什么要隐藏密钥?
python# ❌ 危险!密钥会出现在日志里 print(f"使用密钥: {api_key}") # ✅ 安全!只显示前几位 print(f"使用密钥: {mask_secret(api_key)}") # 输出: sk-a***
python
import os
from pathlib import Path
from typing import Optional
import secrets
class SecretManager:
"""密钥管理器"""
@staticmethod
def generate_secret_key(length: int = 32) -> str:
"""生成随机密钥"""
return secrets.token_urlsafe(length)
@staticmethod
def get_api_key(key_name: str, required: bool = True) -> Optional[str]:
"""安全获取 API Key"""
key = os.getenv(key_name)
if required and not key:
raise ValueError(f"环境变量 {key_name} 未配置")
if key and not SecretManager.validate_api_key(key):
raise ValueError(f"API Key 格式错误: {key_name}")
return key
@staticmethod
def validate_api_key(key: str) -> bool:
"""验证 API Key 格式"""
if not key:
return False
# OpenAI Key 格式
if key.startswith("sk-"):
return len(key) > 20
# Anthropic Key 格式
if key.startswith("sk-ant-"):
return len(key) > 30
return True
@staticmethod
def mask_secret(secret: str, visible_chars: int = 4) -> str:
"""隐藏密钥(仅显示前几位)"""
if not secret:
return "None"
if len(secret) <= visible_chars:
return "*" * len(secret)
return f"{secret[:visible_chars]}***"
# 使用示例
manager = SecretManager()
# 获取 API Key
try:
openai_key = manager.get_api_key("OPENAI_API_KEY", required=True)
anthropic_key = manager.get_api_key("ANTHROPIC_API_KEY", required=False)
print(f"OpenAI Key: {manager.mask_secret(openai_key)}")
print(f"Anthropic Key: {manager.mask_secret(anthropic_key) if anthropic_key else 'Not configured'}")
except ValueError as e:
print(f"配置错误: {e}")
# 生成新密钥
new_secret = manager.generate_secret_key()
print(f"新密钥: {manager.mask_secret(new_secret)}").gitignore 最佳实践
gitignore
# 环境变量文件
.env
.env.*
!.env.example
# Python
__pycache__/
*.py[cod]
*.so
.Python
# 虚拟环境
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
# 日志
*.log
logs/
# 数据库
*.db
*.sqlite3
# 密钥和证书
*.pem
*.key
*.cert关键要点
- 永远不要提交 .env 文件到 Git
- 使用 .env.example 作为模板
- 验证所有配置值
- 在日志中隐藏敏感信息
- 使用类型安全的配置类