From 8bd7106d96ffcd7e8814b08cfa8b391b315b4df5 Mon Sep 17 00:00:00 2001 From: LzSkyline Date: Wed, 8 Jan 2025 14:42:27 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8C=96API=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 6 +-- README.md | 45 +++++++++----------- main.py | 118 +++++++++++++++++++++++++++++++++------------------ 3 files changed, 99 insertions(+), 70 deletions(-) diff --git a/.env.example b/.env.example index 45dcd86..c546124 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ -# Dify Model Configurations -# Format: JSON string in one line -MODEL_CONFIG={"claude-3-5-sonnet-v2":"app-xxxxxxxxxxxxxxxx","gemini-2.0-flash-thinking-exp-1219":"app-xxxxxxxxxxxxxxxx","o1-preview":"app-xxxxxxxxxxxxxxxx"} +# Dify API Keys Configuration +# Format: Comma-separated list of API keys +DIFY_API_KEYS=app-xxxxxxxxxxxxxxxx,app-yyyyyyyyyyyyyyyy,app-zzzzzzzzzzzzzzzz # Dify API Base URL DIFY_API_BASE="https://api.dify.example.com/v1" diff --git a/README.md b/README.md index 0e805c1..6b9a2a8 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ OpenDify 是一个将 Dify API 转换为 OpenAI API 格式的代理服务器。 - 支持多个模型配置 - 完整的错误处理和日志记录 - 兼容标准的 OpenAI API 客户端 -- 灵活的模型配置支持 +- 自动获取 Dify 应用信息 ## 支持的模型 -支持任意 Dify 模型,只需在配置文件中添加对应的 API Key 即可。 +支持任意 Dify 应用,系统会自动从 Dify API 获取应用名称和信息。只需在配置文件中添加应用的 API Key 即可。 ## 快速开始 @@ -45,13 +45,13 @@ cp .env.example .env - 发布应用 - 进入"访问 API"页面,生成 API 密钥 - > **重要说明**:Dify 不支持在请求时动态传入提示词、切换模型及其他参数。所有这些配置都需要在创建应用时设置好。Dify 会根据 API 密钥来确定使用哪个应用及其对应的配置。 + > **重要说明**:Dify 不支持在请求时动态传入提示词、切换模型及其他参数。所有这些配置都需要在创建应用时设置好。Dify 会根据 API 密钥来确定使用哪个应用及其对应的配置。系统会自动从 Dify API 获取应用的名称和描述信息。 -3. 在 `.env` 文件中配置你的 Dify 模型和 API Keys: +3. 在 `.env` 文件中配置你的 Dify API Keys: ```env -# Dify Model Configurations -# 注意:必须是单行的 JSON 字符串格式 -MODEL_CONFIG={"claude-3-5-sonnet-v2":"your-claude-api-key","custom-model":"your-custom-api-key"} +# Dify API Keys Configuration +# Format: Comma-separated list of API keys +DIFY_API_KEYS=app-xxxxxxxx,app-yyyyyyyy,app-zzzzzzzz # Dify API Base URL DIFY_API_BASE="https://your-dify-api-base-url/v1" @@ -61,16 +61,10 @@ SERVER_HOST="127.0.0.1" SERVER_PORT=5000 ``` -你可以根据需要添加或删除模型配置,但必须保持 JSON 格式在单行内。这是因为 python-dotenv 的限制。 - -每个模型配置的格式为:`"模型名称": "Dify应用的API密钥"`。其中: -- 模型名称:可以自定义,用于在 API 调用时识别不同的应用 -- API 密钥:从 Dify 平台获取的应用 API 密钥 - -例如,如果你在 Dify 上创建了一个使用 Claude 的翻译应用和一个使用 Gemini 的代码助手应用,可以这样配置: -```env -MODEL_CONFIG={"translator":"app-xxxxxx","code-assistant":"app-yyyyyy"} -``` +配置说明: +- `DIFY_API_KEYS`:以逗号分隔的 API Keys 列表,每个 Key 对应一个 Dify 应用 +- 系统会自动从 Dify API 获取每个应用的名称和信息 +- 无需手动配置模型名称和映射关系 ### 运行服务 @@ -101,23 +95,22 @@ print(models) "object": "list", "data": [ { - "id": "claude-3-5-sonnet-v2", + "id": "My Translation App", # Dify 应用名称 "object": "model", "created": 1704603847, "owned_by": "dify" }, { - "id": "gemini-2.0-flash-thinking-exp-1219", + "id": "Code Assistant", # 另一个 Dify 应用名称 "object": "model", "created": 1704603847, "owned_by": "dify" - }, - // ... 其他在 MODEL_CONFIG 中配置的模型 + } ] } ``` -只有在 `.env` 文件的 `MODEL_CONFIG` 中配置了 API Key 的模型才会出现在列表中。 +系统会自动从 Dify API 获取应用名称,并用作模型 ID。 ### Chat Completions @@ -128,7 +121,7 @@ openai.api_base = "http://127.0.0.1:5000/v1" openai.api_key = "any" # 可以使用任意值 response = openai.ChatCompletion.create( - model="claude-3-5-sonnet-v2", # 使用在 MODEL_CONFIG 中配置的模型名称 + model="My Translation App", # 使用 Dify 应用的名称 messages=[ {"role": "user", "content": "你好"} ], @@ -155,9 +148,9 @@ for chunk in response: ### 配置灵活性 -- 支持动态添加新模型 -- 支持 JSON 格式配置 -- 支持自定义模型名称 +- 自动获取应用信息 +- 简化的配置方式 +- 动态模型名称映射 ## 贡献指南 diff --git a/main.py b/main.py index bdebf6d..a8b8a73 100644 --- a/main.py +++ b/main.py @@ -21,51 +21,83 @@ logging.getLogger("httpx").setLevel(logging.DEBUG) # 加载环境变量 load_dotenv() -def parse_model_config(): - """ - 从环境变量解析模型配置 - 返回一个字典 {model_name: api_key} - """ - try: - config_str = os.getenv('MODEL_CONFIG', '{}') - # 尝试作为Python字典解析 +class DifyModelManager: + def __init__(self): + self.api_keys = [] + self.name_to_api_key = {} # 应用名称到API Key的映射 + self.api_key_to_name = {} # API Key到应用名称的映射 + self.load_api_keys() + + def load_api_keys(self): + """从环境变量加载API Keys""" + api_keys_str = os.getenv('DIFY_API_KEYS', '') + if api_keys_str: + self.api_keys = [key.strip() for key in api_keys_str.split(',') if key.strip()] + logger.info(f"Loaded {len(self.api_keys)} API keys") + + async def fetch_app_info(self, api_key): + """获取Dify应用信息""" try: - return ast.literal_eval(config_str) - except (SyntaxError, ValueError) as e: - logger.error(f"Failed to parse MODEL_CONFIG as Python dict: {e}") - try: - # 尝试作为JSON解析 - return json.loads(config_str) - except json.JSONDecodeError as e: - logger.error(f"Failed to parse MODEL_CONFIG as JSON: {e}") - return {} - except Exception as e: - logger.error(f"Error parsing MODEL_CONFIG: {e}") - return {} + async with httpx.AsyncClient() as client: + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + response = await client.get( + f"{DIFY_API_BASE}/info", + headers=headers, + params={"user": "default_user"} + ) + + if response.status_code == 200: + app_info = response.json() + return app_info.get("name", "Unknown App") + else: + logger.error(f"Failed to fetch app info for API key: {api_key[:8]}...") + return None + except Exception as e: + logger.error(f"Error fetching app info: {str(e)}") + return None -# 从环境变量获取配置 -MODEL_TO_API_KEY = parse_model_config() + async def refresh_model_info(self): + """刷新所有应用信息""" + self.name_to_api_key.clear() + self.api_key_to_name.clear() + + for api_key in self.api_keys: + app_name = await self.fetch_app_info(api_key) + if app_name: + self.name_to_api_key[app_name] = api_key + self.api_key_to_name[api_key] = app_name + logger.info(f"Mapped app '{app_name}' to API key: {api_key[:8]}...") -# 根据MODEL_TO_API_KEY自动生成模型信息 -AVAILABLE_MODELS = [ - { - "id": model_id, - "object": "model", - "created": int(time.time()), - "owned_by": "dify" - } - for model_id, api_key in MODEL_TO_API_KEY.items() - if api_key is not None # 只包含配置了API Key的模型 -] + def get_api_key(self, model_name): + """根据模型名称获取API Key""" + return self.name_to_api_key.get(model_name) -app = Flask(__name__) + def get_available_models(self): + """获取可用模型列表""" + return [ + { + "id": name, + "object": "model", + "created": int(time.time()), + "owned_by": "dify" + } + for name in self.name_to_api_key.keys() + ] + +# 创建模型管理器实例 +model_manager = DifyModelManager() # 从环境变量获取API基础URL DIFY_API_BASE = os.getenv("DIFY_API_BASE", "https://mify-be.pt.xiaomi.com/api/v1") +app = Flask(__name__) + def get_api_key(model_name): """根据模型名称获取对应的API密钥""" - api_key = MODEL_TO_API_KEY.get(model_name) + api_key = model_manager.get_api_key(model_name) if not api_key: logger.warning(f"No API key found for model: {model_name}") return api_key @@ -149,7 +181,7 @@ def chat_completions(): # 验证模型是否支持 api_key = get_api_key(model) if not api_key: - error_msg = f"Model {model} is not supported. Available models: {', '.join(MODEL_TO_API_KEY.keys())}" + error_msg = f"Model {model} is not supported. Available models: {', '.join(model_manager.name_to_api_key.keys())}" logger.error(error_msg) return { "error": { @@ -375,11 +407,12 @@ def chat_completions(): def list_models(): """返回可用的模型列表""" logger.info("Listing available models") - # 过滤掉没有API密钥的模型 - available_models = [ - model for model in AVAILABLE_MODELS - if MODEL_TO_API_KEY.get(model["id"]) - ] + + # 刷新模型信息 + asyncio.run(model_manager.refresh_model_info()) + + # 获取可用模型列表 + available_models = model_manager.get_available_models() response = { "object": "list", @@ -389,6 +422,9 @@ def list_models(): return response if __name__ == '__main__': + # 启动时初始化模型信息 + asyncio.run(model_manager.refresh_model_info()) + host = os.getenv("SERVER_HOST", "127.0.0.1") port = int(os.getenv("SERVER_PORT", 5000)) logger.info(f"Starting server on http://{host}:{port}")