diff --git a/API_KEY_SETUP_GUIDE.md b/API_KEY_SETUP_GUIDE.md new file mode 100644 index 0000000..77e5795 --- /dev/null +++ b/API_KEY_SETUP_GUIDE.md @@ -0,0 +1,238 @@ +# API密钥配置指南 + +## 📋 概述 + +本项目已将所有API密钥统一管理到 `config/api_keys.py` 文件中,提供更安全、更便捷的密钥管理方式。 + +## 🔧 配置步骤 + +### 1. 创建环境变量文件 + +在项目根目录创建 `.env` 文件: + +```bash +# 复制模板文件 +cp .env.template .env +``` + +### 2. 配置API密钥 + +编辑 `.env` 文件,填入您的API密钥: + +```env +# 千问大模型API密钥 (必需) +QWEN_API_KEY=sk-your-actual-qwen-api-key-here + +# OpenAI API密钥 (可选) +OPENAI_API_KEY=sk-your-actual-openai-api-key-here + +# Anthropic API密钥 (可选) +ANTHROPIC_API_KEY=sk-ant-your-actual-anthropic-api-key-here +``` + +### 3. 验证配置 + +运行以下命令验证API密钥配置: + +```bash +python -c "from config.api_keys import get_api_status_report; print(get_api_status_report())" +``` + +## 🏗️ 架构说明 + +### 统一API密钥管理 + +- **文件位置**: `config/api_keys.py` +- **管理类**: `APIKeyManager` +- **全局实例**: 通过 `get_api_key_manager()` 获取 + +### 支持的API提供商 + +| 提供商 | 环境变量 | 必需性 | 用途 | +|--------|----------|--------|------| +| 千问 | `QWEN_API_KEY` | ✅ 必需 | 主要AI分析服务 | +| OpenAI | `OPENAI_API_KEY` | ⚪ 可选 | 备用AI服务 | +| Anthropic | `ANTHROPIC_API_KEY` | ⚪ 可选 | 备用AI服务 | +| Google | `GOOGLE_API_KEY` | ⚪ 可选 | 扩展服务 | +| 百度 | `BAIDU_API_KEY` | ⚪ 可选 | 扩展服务 | +| 腾讯 | `TENCENT_API_KEY` | ⚪ 可选 | 扩展服务 | + +## 🔒 安全特性 + +### 1. 密钥验证 +- 自动验证API密钥格式 +- 检查密钥是否为空或无效 +- 提供详细的验证报告 + +### 2. 环境隔离 +- 所有密钥存储在环境变量中 +- `.env` 文件被 `.gitignore` 忽略 +- 不会意外提交到版本控制 + +### 3. 运行时管理 +- 支持运行时动态设置密钥 +- 提供密钥状态监控 +- 支持多提供商切换 + +## 📚 使用方法 + +### 基本用法 + +```python +from config.api_keys import get_qwen_key, get_openai_key + +# 获取千问API密钥 +qwen_key = get_qwen_key() +if qwen_key: + print("千问API密钥已配置") +else: + print("千问API密钥未配置") + +# 获取OpenAI API密钥 +openai_key = get_openai_key() +``` + +### 高级用法 + +```python +from config.api_keys import get_api_key_manager + +# 获取管理器实例 +manager = get_api_key_manager() + +# 检查提供商可用性 +if manager.is_available('qwen'): + print("千问服务可用") + +# 获取所有可用提供商 +available = manager.get_available_providers() +print(f"可用提供商: {available}") + +# 获取状态报告 +status = manager.get_status_report() +print(f"配置状态: {status}") +``` + +### 动态设置密钥 + +```python +from config.api_keys import get_api_key_manager + +manager = get_api_key_manager() + +# 运行时设置API密钥 +manager.set_key('qwen', 'sk-your-new-api-key') +``` + +## 🛠️ 开发工具 + +### 创建环境变量模板 + +```python +from config.api_keys import create_env_template + +# 创建 .env 文件模板 +create_env_template() +``` + +### 获取配置建议 + +```python +from config.api_keys import get_api_status_report + +status = get_api_status_report() +for recommendation in status['recommendations']: + print(f"💡 {recommendation}") +``` + +## 🔍 故障排除 + +### 常见问题 + +1. **API密钥未配置** + ``` + ValueError: 千问API密钥未配置,请在.env文件中设置QWEN_API_KEY + ``` + **解决方案**: 检查 `.env` 文件是否存在且包含正确的API密钥 + +2. **密钥格式错误** + ``` + ⚠️ 千问API密钥格式可能不正确 + ``` + **解决方案**: 确保API密钥以 `sk-` 开头(千问)或 `sk-ant-` 开头(Anthropic) + +3. **环境变量未加载** + ``` + ⚠️ 千问API密钥未配置 + ``` + **解决方案**: 确保 `.env` 文件在项目根目录,且格式正确 + +### 调试命令 + +```bash +# 检查环境变量 +python -c "import os; print('QWEN_API_KEY:', os.getenv('QWEN_API_KEY', 'Not set'))" + +# 测试API密钥管理 +python config/api_keys.py + +# 验证配置 +python -c "from config.api_keys import get_api_status_report; import json; print(json.dumps(get_api_status_report(), indent=2, ensure_ascii=False))" +``` + +## 📈 最佳实践 + +1. **密钥安全** + - 永远不要将API密钥硬编码在代码中 + - 使用环境变量或配置文件 + - 定期轮换API密钥 + +2. **错误处理** + - 始终检查API密钥是否可用 + - 提供友好的错误提示 + - 实现降级策略 + +3. **配置管理** + - 使用统一的配置管理 + - 提供配置验证 + - 支持多环境配置 + +## 🔄 迁移指南 + +如果您之前在其他文件中硬编码了API密钥,请按以下步骤迁移: + +1. **移除硬编码密钥** + ```python + # 旧方式 ❌ + api_key = "sk-hardcoded-key" + + # 新方式 ✅ + from config.api_keys import get_qwen_key + api_key = get_qwen_key() + ``` + +2. **更新导入语句** + ```python + # 添加统一导入 + from config.api_keys import get_qwen_key, get_openai_key, get_anthropic_key + ``` + +3. **添加错误处理** + ```python + api_key = get_qwen_key() + if not api_key: + raise ValueError("千问API密钥未配置") + ``` + +## 📞 支持 + +如果您在配置API密钥时遇到问题,请: + +1. 检查本指南的故障排除部分 +2. 运行调试命令验证配置 +3. 查看项目日志获取详细错误信息 +4. 提交Issue描述具体问题 + +--- + +**注意**: 请妥善保管您的API密钥,不要分享给他人或提交到公开仓库。 diff --git a/README.md b/README.md index 2be2f4c..e6b0db2 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,8 @@ diet_recommendation_app/ ### 2. 安装依赖 ```bash # 克隆项目 -git clone -cd diet_recommendation_app +git clone https://github.com/random-jason/diet_recommendation.git +cd diet_recommendation # 安装基础依赖 pip install -r requirements.txt @@ -131,7 +131,22 @@ pip install -r requirements.txt pip install pytesseract opencv-python paddleocr easyocr ``` -### 3. OCR引擎配置(可选) +### 3. 配置API密钥 +```bash +# 方法1: 使用自动生成的模板 +python -c "from config.api_keys import create_env_template; create_env_template()" + +# 方法2: 手动创建.env文件 +echo "QWEN_API_KEY=your_qwen_api_key_here" > .env +``` + +### 4. 验证配置 +```bash +# 检查API密钥配置状态 +python -c "from config.api_keys import get_api_status_report; print(get_api_status_report())" +``` + +### 5. OCR引擎配置(可选) #### Tesseract安装 - **Windows**: 下载 [Tesseract安装包](https://github.com/UB-Mannheim/tesseract/wiki) @@ -166,6 +181,37 @@ python main.py 5. 查看和编辑识别结果 6. 确认保存到餐食记录 +## 🔐 API密钥管理 + +### 统一密钥管理 +项目采用统一的API密钥管理系统,所有API密钥都通过 `config/api_keys.py` 进行管理: + +- **千问API密钥** (必需): 用于主要AI分析服务 +- **OpenAI API密钥** (可选): 备用AI服务 +- **Anthropic API密钥** (可选): 备用AI服务 +- **其他API密钥** (可选): Google、百度、腾讯等 + +### 配置方法 +```python +from config.api_keys import get_qwen_key, get_api_status_report + +# 获取API密钥 +qwen_key = get_qwen_key() + +# 检查配置状态 +status = get_api_status_report() +print(f"可用提供商: {status['available_list']}") +``` + +### 安全特性 +- ✅ 环境变量隔离 +- ✅ 密钥格式验证 +- ✅ 运行时状态监控 +- ✅ 多提供商支持 +- ✅ 自动配置建议 + +详细配置指南请参考:[API_KEY_SETUP_GUIDE.md](API_KEY_SETUP_GUIDE.md) + ## 🔄 工作流程 ### 1. 智能数据采集(5天) diff --git a/config/api_keys.py b/config/api_keys.py new file mode 100644 index 0000000..01cc6cc --- /dev/null +++ b/config/api_keys.py @@ -0,0 +1,318 @@ +""" +API密钥统一管理 +所有API密钥的集中管理和配置 +""" + +import os +from typing import Optional, Dict, Any +from dataclasses import dataclass +import logging + +# 尝试加载环境变量 +try: + from dotenv import load_dotenv + load_dotenv() +except Exception: + # 如果没有.env文件或加载失败,使用默认配置 + pass + +logger = logging.getLogger(__name__) + + +@dataclass +class APIKeys: + """API密钥配置类""" + + # 千问大模型API密钥 + qwen_api_key: Optional[str] = None + + # OpenAI API密钥 + openai_api_key: Optional[str] = None + + # Anthropic API密钥 + anthropic_api_key: Optional[str] = None + + # 其他API密钥(可扩展) + google_api_key: Optional[str] = None + baidu_api_key: Optional[str] = None + tencent_api_key: Optional[str] = None + + +class APIKeyManager: + """API密钥管理器""" + + def __init__(self): + self.keys = APIKeys() + self._load_from_env() + self._validate_keys() + + def _load_from_env(self): + """从环境变量加载API密钥""" + # 千问API密钥 + self.keys.qwen_api_key = os.getenv('QWEN_API_KEY') + + # OpenAI API密钥 + self.keys.openai_api_key = os.getenv('OPENAI_API_KEY') + + # Anthropic API密钥 + self.keys.anthropic_api_key = os.getenv('ANTHROPIC_API_KEY') + + # 其他API密钥 + self.keys.google_api_key = os.getenv('GOOGLE_API_KEY') + self.keys.baidu_api_key = os.getenv('BAIDU_API_KEY') + self.keys.tencent_api_key = os.getenv('TENCENT_API_KEY') + + logger.info("API密钥已从环境变量加载") + + def _validate_keys(self): + """验证API密钥格式""" + validation_results = {} + + # 验证千问API密钥 + if self.keys.qwen_api_key: + if self.keys.qwen_api_key.startswith('sk-'): + validation_results['qwen'] = True + logger.info("✅ 千问API密钥格式正确") + else: + validation_results['qwen'] = False + logger.warning("⚠️ 千问API密钥格式可能不正确") + else: + validation_results['qwen'] = False + logger.warning("⚠️ 千问API密钥未配置") + + # 验证OpenAI API密钥 + if self.keys.openai_api_key: + if self.keys.openai_api_key.startswith('sk-'): + validation_results['openai'] = True + logger.info("✅ OpenAI API密钥格式正确") + else: + validation_results['openai'] = False + logger.warning("⚠️ OpenAI API密钥格式可能不正确") + else: + validation_results['openai'] = False + logger.warning("⚠️ OpenAI API密钥未配置") + + # 验证Anthropic API密钥 + if self.keys.anthropic_api_key: + if self.keys.anthropic_api_key.startswith('sk-ant-'): + validation_results['anthropic'] = True + logger.info("✅ Anthropic API密钥格式正确") + else: + validation_results['anthropic'] = False + logger.warning("⚠️ Anthropic API密钥格式可能不正确") + else: + validation_results['anthropic'] = False + logger.warning("⚠️ Anthropic API密钥未配置") + + return validation_results + + def get_qwen_key(self) -> Optional[str]: + """获取千问API密钥""" + return self.keys.qwen_api_key + + def get_openai_key(self) -> Optional[str]: + """获取OpenAI API密钥""" + return self.keys.openai_api_key + + def get_anthropic_key(self) -> Optional[str]: + """获取Anthropic API密钥""" + return self.keys.anthropic_api_key + + def get_key(self, provider: str) -> Optional[str]: + """根据提供商获取API密钥""" + key_map = { + 'qwen': self.keys.qwen_api_key, + 'openai': self.keys.openai_api_key, + 'anthropic': self.keys.anthropic_api_key, + 'google': self.keys.google_api_key, + 'baidu': self.keys.baidu_api_key, + 'tencent': self.keys.tencent_api_key + } + return key_map.get(provider.lower()) + + def is_available(self, provider: str) -> bool: + """检查指定提供商的API密钥是否可用""" + key = self.get_key(provider) + return key is not None and len(key.strip()) > 0 + + def get_available_providers(self) -> list: + """获取所有可用的API提供商""" + providers = ['qwen', 'openai', 'anthropic', 'google', 'baidu', 'tencent'] + available = [] + + for provider in providers: + if self.is_available(provider): + available.append(provider) + + return available + + def get_all_keys(self) -> Dict[str, Optional[str]]: + """获取所有API密钥(用于调试,不包含实际密钥值)""" + return { + 'qwen': '***' if self.keys.qwen_api_key else None, + 'openai': '***' if self.keys.openai_api_key else None, + 'anthropic': '***' if self.keys.anthropic_api_key else None, + 'google': '***' if self.keys.google_api_key else None, + 'baidu': '***' if self.keys.baidu_api_key else None, + 'tencent': '***' if self.keys.tencent_api_key else None + } + + def set_key(self, provider: str, api_key: str): + """设置API密钥(运行时设置)""" + provider = provider.lower() + + if provider == 'qwen': + self.keys.qwen_api_key = api_key + elif provider == 'openai': + self.keys.openai_api_key = api_key + elif provider == 'anthropic': + self.keys.anthropic_api_key = api_key + elif provider == 'google': + self.keys.google_api_key = api_key + elif provider == 'baidu': + self.keys.baidu_api_key = api_key + elif provider == 'tencent': + self.keys.tencent_api_key = api_key + else: + raise ValueError(f"不支持的API提供商: {provider}") + + logger.info(f"API密钥已设置: {provider}") + + def create_env_file(self, file_path: str = ".env"): + """创建.env文件模板""" + env_content = """# API密钥配置文件 +# 请将下面的your-api-key-here替换为实际的API密钥 + +# 千问大模型API密钥 +QWEN_API_KEY=your-qwen-api-key-here + +# OpenAI API密钥(可选) +OPENAI_API_KEY=your-openai-api-key-here + +# Anthropic API密钥(可选) +ANTHROPIC_API_KEY=your-anthropic-api-key-here + +# Google API密钥(可选) +GOOGLE_API_KEY=your-google-api-key-here + +# 百度API密钥(可选) +BAIDU_API_KEY=your-baidu-api-key-here + +# 腾讯API密钥(可选) +TENCENT_API_KEY=your-tencent-api-key-here + +# 其他配置 +DEBUG=true +LOG_LEVEL=INFO +""" + + with open(file_path, 'w', encoding='utf-8') as f: + f.write(env_content) + + logger.info(f"环境变量文件已创建: {file_path}") + + def get_status_report(self) -> Dict[str, Any]: + """获取API密钥状态报告""" + available_providers = self.get_available_providers() + + return { + "total_providers": 6, + "available_providers": len(available_providers), + "available_list": available_providers, + "unavailable_providers": [p for p in ['qwen', 'openai', 'anthropic', 'google', 'baidu', 'tencent'] + if p not in available_providers], + "recommendations": self._get_recommendations() + } + + def _get_recommendations(self) -> list: + """获取配置建议""" + recommendations = [] + + if not self.is_available('qwen'): + recommendations.append("建议配置千问API密钥以获得最佳的中文AI分析体验") + + if not self.is_available('openai'): + recommendations.append("可选配置OpenAI API密钥作为备用AI服务") + + if not self.is_available('anthropic'): + recommendations.append("可选配置Anthropic API密钥作为备用AI服务") + + return recommendations + + +# 全局API密钥管理器实例 +_api_key_manager: Optional[APIKeyManager] = None + + +def get_api_key_manager() -> APIKeyManager: + """获取全局API密钥管理器实例""" + global _api_key_manager + if _api_key_manager is None: + _api_key_manager = APIKeyManager() + return _api_key_manager + + +# 便捷函数 +def get_qwen_key() -> Optional[str]: + """获取千问API密钥""" + return get_api_key_manager().get_qwen_key() + + +def get_openai_key() -> Optional[str]: + """获取OpenAI API密钥""" + return get_api_key_manager().get_openai_key() + + +def get_anthropic_key() -> Optional[str]: + """获取Anthropic API密钥""" + return get_api_key_manager().get_anthropic_key() + + +def get_api_key(provider: str) -> Optional[str]: + """根据提供商获取API密钥""" + return get_api_key_manager().get_key(provider) + + +def is_api_available(provider: str) -> bool: + """检查指定提供商的API是否可用""" + return get_api_key_manager().is_available(provider) + + +def get_available_providers() -> list: + """获取所有可用的API提供商""" + return get_api_key_manager().get_available_providers() + + +def create_env_template(file_path: str = ".env"): + """创建环境变量文件模板""" + return get_api_key_manager().create_env_file(file_path) + + +def get_api_status_report() -> Dict[str, Any]: + """获取API状态报告""" + return get_api_key_manager().get_status_report() + + +if __name__ == "__main__": + # 测试API密钥管理 + print("=== API密钥管理测试 ===") + + manager = get_api_key_manager() + + # 显示状态报告 + status = manager.get_status_report() + print(f"✅ 总提供商数量: {status['total_providers']}") + print(f"✅ 可用提供商数量: {status['available_providers']}") + print(f"✅ 可用提供商: {status['available_list']}") + print(f"✅ 不可用提供商: {status['unavailable_providers']}") + + # 显示建议 + if status['recommendations']: + print("\n💡 配置建议:") + for rec in status['recommendations']: + print(f" - {rec}") + + # 创建环境变量文件模板 + manager.create_env_file() + + print("\n✅ API密钥管理测试完成") diff --git a/config/settings.py b/config/settings.py index 76b056f..227c193 100644 --- a/config/settings.py +++ b/config/settings.py @@ -161,13 +161,16 @@ class UnifiedConfig: if os.getenv('DATABASE_PATH'): self.database.database_path = os.getenv('DATABASE_PATH') - # API配置 - self.api.qwen_api_key = os.getenv('QWEN_API_KEY') + # API配置 - 使用统一API密钥管理 + from config.api_keys import get_api_key_manager + api_manager = get_api_key_manager() + + self.api.qwen_api_key = api_manager.get_qwen_key() self.api.qwen_base_url = os.getenv('QWEN_BASE_URL', self.api.qwen_base_url) self.api.qwen_model = os.getenv('QWEN_MODEL', self.api.qwen_model) - self.api.openai_api_key = os.getenv('OPENAI_API_KEY') - self.api.anthropic_api_key = os.getenv('ANTHROPIC_API_KEY') + self.api.openai_api_key = api_manager.get_openai_key() + self.api.anthropic_api_key = api_manager.get_anthropic_key() # 应用配置 if os.getenv('DEBUG'): diff --git a/core/base_engine.py b/core/base_engine.py index 04eb859..62efe28 100644 --- a/core/base_engine.py +++ b/core/base_engine.py @@ -479,8 +479,20 @@ class AIAnalyzer(BaseEngine): import openai import anthropic - self.openai_client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY")) - self.anthropic_client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) + from config.api_keys import get_openai_key, get_anthropic_key + + openai_key = get_openai_key() + anthropic_key = get_anthropic_key() + + if openai_key: + self.openai_client = openai.OpenAI(api_key=openai_key) + else: + self.logger.warning("OpenAI API密钥未配置") + + if anthropic_key: + self.anthropic_client = anthropic.Anthropic(api_key=anthropic_key) + else: + self.logger.warning("Anthropic API密钥未配置") self._initialized = True self.logger.info("AI分析器初始化成功") diff --git a/llm_integration/qwen_client.py b/llm_integration/qwen_client.py index b3dce1e..f65c843 100644 --- a/llm_integration/qwen_client.py +++ b/llm_integration/qwen_client.py @@ -519,10 +519,15 @@ class QwenLLMClient: } -# 千问配置 - 从环境变量获取 +# 千问配置 - 使用统一API密钥管理 def get_qwen_config() -> LLMConfig: """获取千问配置""" - api_key = os.getenv('QWEN_API_KEY', 'sk-c0dbefa1718d46eaa897199135066f00') + from config.api_keys import get_qwen_key + + api_key = get_qwen_key() + if not api_key: + raise ValueError("千问API密钥未配置,请在.env文件中设置QWEN_API_KEY") + base_url = os.getenv('QWEN_BASE_URL', 'https://dashscope.aliyuncs.com/compatible-mode/v1') model = os.getenv('QWEN_MODEL', 'qwen-plus-latest') diff --git a/main.py b/main.py index 51485b2..a97b74b 100644 --- a/main.py +++ b/main.py @@ -48,19 +48,9 @@ class DietRecommendationApp: """加载配置""" config = BaseConfig() - # 从环境变量加载API密钥 - config.qwen_api_key = os.getenv('QWEN_API_KEY') - - # 从.env文件加载配置 - env_file = Path('.env') - if env_file.exists(): - try: - from dotenv import load_dotenv - load_dotenv() - config.qwen_api_key = os.getenv('QWEN_API_KEY') - except Exception: - # 如果.env文件有编码问题,跳过加载 - pass + # 使用统一API密钥管理 + from config.api_keys import get_qwen_key + config.qwen_api_key = get_qwen_key() return config