{"repo": "binison/academic-research-agent", "files": {"llm_config.py": {"23548fdf7e28d2beedfa3a8e3a826e6388e39557": {"t": 1785746880.9571998, "n": 7314, "h": "9304787fb6aa7834", "c": "\"\"\"\n通用 LLM 配置模块\n支持 OpenAI 格式和 Anthropic 格式的所有第三方模型\n\"\"\"\n\nimport os\nfrom typing import Optional, Dict, Any\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass ModelConfig:\n \"\"\"模型配置\"\"\"\n name: str\n format: str # \"openai\" 或 \"anthropic\"\n api_key: str\n base_url: Optional[str] = None\n model: str = \"\"\n temperature: float = 0.3\n extra_headers: Optional[Dict[str, str]] = None\n\n\ndef get_model_config() -> ModelConfig:\n \"\"\"\n 从环境变量读取模型配置\n\n 支持的配置方式:\n\n 1. OpenAI 官方:\n OPENAI_API_KEY=sk-xxx\n OPENAI_MODEL=gpt-4o\n\n 2. OpenAI 兼容 (Ollama/Groq/OpenRouter/DeepSeek 等):\n LLM_FORMAT=openai\n LLM_API_KEY=xxx\n LLM_BASE_URL=https://api.deepseek.com/v1\n LLM_MODEL=deepseek-chat\n\n 3. Anthropic 官方:\n ANTHROPIC_API_KEY=sk-ant-xxx\n ANTHROPIC_MODEL=claude-3-5-sonnet-20241022\n\n 4. Anthropic 兼容 (代理服务):\n LLM_FORMAT=anthropic\n LLM_API_KEY=xxx\n LLM_BASE_URL=https://your-proxy.com\n LLM_MODEL=claude-3-5-sonnet\n \"\"\"\n\n # 首先检查通用配置\n llm_format = os.getenv(\"LLM_FORMAT\", \"\").lower()\n llm_api_key = os.getenv(\"LLM_API_KEY\", \"\")\n llm_base_url = os.getenv(\"LLM_BASE_URL\", \"\")\n llm_model = os.getenv(\"LLM_MODEL\", \"\")\n llm_temperature = float(os.getenv(\"LLM_TEMPERATURE\", \"0.3\"))\n\n # 构建额外请求头(某些服务需要)\n extra_headers = {}\n if os.getenv(\"LLM_HTTP_REFERER\"):\n extra_headers[\"HTTP-Referer\"] = os.getenv(\"LLM_HTTP_REFERER\")\n if os.getenv(\"LLM_X_TITLE\"):\n extra_headers[\"X-Title\"] = os.getenv(\"LLM_X_TITLE\")\n\n # 标准化 base_url\n if llm_base_url:\n llm_base_url = llm_base_url.rstrip('/')\n # 移除可能的 /v1/chat/completions 后缀\n for suffix in ['/v1/chat/completions', '/chat/completions', '/v1/messages']:\n if llm_base_url.endswith(suffix):\n llm_base_url = llm_base_url[:-len(suffix)]\n break\n\n # 方式1: 通用配置 (LLM_ 前缀)\n if llm_api_key and llm_format:\n # 设置默认模型名称\n if not llm_model:\n if llm_format == \"openai\":\n llm_model = \"gpt-4o\"\n else:\n llm_model = \"claude-3-5-sonnet-20241022\"\n\n return ModelConfig(\n name=llm_model,\n format=llm_format,\n api_key=llm_api_key,\n base_url=llm_base_url or None,\n model=llm_model,\n temperature=llm_temperature,\n extra_headers=extra_headers if extra_headers else None\n )\n\n # 方式2: OpenAI 官方\n openai_key = os.getenv(\"OPENAI_API_KEY\")\n if openai_key:\n openai_base = os.getenv(\"OPENAI_BASE_URL\", \"\")\n if openai_base:\n openai_base = openai_base.rstrip('/')\n for suffix in ['/v1/chat/completions', '/chat/completions']:\n if openai_base.endswith(suffix):\n openai_base = openai_base[:-len(suffix)]\n break\n\n return ModelConfig(\n name=os.getenv(\"OPENAI_MODEL\", \"gpt-4o\"),\n format=\"openai\",\n api_key=openai_key,\n base_url=openai_base or None,\n model=os.getenv(\"OPENAI_MODEL\", \"gpt-4o\"),\n temperature=llm_temperature,\n extra_headers=extra_headers if extra_headers else None\n )\n\n # 方式3: Anthropic 官方\n anthropic_key = os.getenv(\"ANTHROPIC_API_KEY\")\n if anthropic_key:\n anthropic_base = os.getenv(\"ANTHROPIC_BASE_URL\", \"\")\n if anthropic_base:\n anthropic_base = anthropic_base.rstrip('/')\n for suffix in ['/v1/messages', '/messages']:\n if anthropic_base.endswith(suffix):\n anthropic_base = anthropic_base[:-len(suffix)]\n break\n\n return ModelConfig(\n name=os.getenv(\"ANTHROPIC_MODEL\", \"claude-3-5-sonnet-20241022\"),\n format=\"anthropic\",\n api_key=anthropic_key,\n base_url=anthropic_base or None,\n model=os.getenv(\"ANTHROPIC_MODEL\", \"claude-3-5-sonnet-20241022\"),\n temperature=llm_temperature,\n extra_headers=extra_headers if extra_headers else None\n )\n\n raise ValueError(\n \"请配置以下环境变量之一:\\n\"\n \" 1. LLM_FORMAT + LLM_API_KEY + LLM_BASE_URL (通用配置)\\n\"\n \" 2. OPENAI_API_KEY (OpenAI 官方)\\n\"\n \" 3. ANTHROPIC_API_KEY (Anthropic 官方)\\n\"\n \"\\n示例 .env 文件:\\n\"\n \" LLM_FORMAT=openai\\n\"\n \" LLM_API_KEY=your-key\\n\"\n \" LLM_BASE_URL=https://api.deepseek.com/v1\\n\"\n \" LLM_MODEL=deepseek-chat\"\n )\n\n\ndef create_llm_client(config: Optional[ModelConfig] = None):\n \"\"\"\n 创建 LLM 客户端\n 支持 OpenAI 格式和 Anthropic 格式\n \"\"\"\n if config is None:\n config = get_model_config()\n\n if config.format == \"openai\":\n from langchain_openai import ChatOpenAI\n\n kwargs = {\n \"model\": config.model,\n \"temperature\": config.temperature,\n \"api_key\": config.api_key,\n }\n if config.base_url:\n kwargs[\"base_url\"] = config.base_url\n if config.extra_headers:\n kwargs[\"default_headers\"] = config.extra_headers\n\n return ChatOpenAI(**kwargs)\n\n elif config.format == \"anthropic\":\n from langchain_anthropic import ChatAnthropic\n\n kwargs = {\n \"model\": config.model,\n \"temperature\": config.temperature,\n \"api_key\": config.api_key,\n }\n if config.base_url:\n kwargs[\"base_url\"] = config.base_url\n if config.extra_headers:\n kwargs[\"default_headers\"] = config.extra_headers\n\n return ChatAnthropic(**kwargs)\n\n else:\n raise ValueError(f\"不支持的格式: {config.format}\")\n\n\n# 预定义的常见第三方配置(方便复制到 .env)\nPRESET_CONFIGS = {\n \"ollama\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"http://localhost:11434/v1\",\n \"LLM_API_KEY\": \"ollama\", # Ollama 需要任意字符串\n \"LLM_MODEL\": \"llama3.2\",\n },\n \"groq\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://api.groq.com/openai/v1\",\n \"LLM_API_KEY\": \"your-groq-key\",\n \"LLM_MODEL\": \"llama-3.2-90b-vision-preview\",\n },\n \"openrouter\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://openrouter.ai/api/v1\",\n \"LLM_API_KEY\": \"your-openrouter-key\",\n \"LLM_MODEL\": \"anthropic/claude-3.5-sonnet\",\n },\n \"deepseek\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://api.deepseek.com/v1\",\n \"LLM_API_KEY\": \"your-deepseek-key\",\n \"LLM_MODEL\": \"deepseek-chat\",\n },\n \"siliconflow\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://api.siliconflow.cn/v1\",\n \"LLM_API_KEY\": \"your-siliconflow-key\",\n \"LLM_MODEL\": \"deepseek-ai/DeepSeek-V2.5\",\n },\n \"dashscope\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"LLM_API_KEY\": \"your-dashscope-key\",\n \"LLM_MODEL\": \"qwen-max\",\n },\n}\n\n\ndef print_preset_configs():\n \"\"\"打印预设配置示例\"\"\"\n print(\"=\" * 60)\n print(\"常用第三方模型配置示例\")\n print(\"=\" * 60)\n\n for name, config in PRESET_CONFIGS.items():\n print(f\"\\n【{name.upper()}】\")\n for key, value in config.items():\n print(f\"{key}={value}\")\n\n print(\"\\n\" + \"=\" * 60)\n print(\"使用方式:将上述配置复制到 .env 文件中\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n print_preset_configs()\n"}, "6b9556420976905dd33f24b381a4340e981c7e43": {"t": 1785768409.5387626, "n": 7314, "h": "9304787fb6aa7834", "c": "\"\"\"\n通用 LLM 配置模块\n支持 OpenAI 格式和 Anthropic 格式的所有第三方模型\n\"\"\"\n\nimport os\nfrom typing import Optional, Dict, Any\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass ModelConfig:\n \"\"\"模型配置\"\"\"\n name: str\n format: str # \"openai\" 或 \"anthropic\"\n api_key: str\n base_url: Optional[str] = None\n model: str = \"\"\n temperature: float = 0.3\n extra_headers: Optional[Dict[str, str]] = None\n\n\ndef get_model_config() -> ModelConfig:\n \"\"\"\n 从环境变量读取模型配置\n\n 支持的配置方式:\n\n 1. OpenAI 官方:\n OPENAI_API_KEY=sk-xxx\n OPENAI_MODEL=gpt-4o\n\n 2. OpenAI 兼容 (Ollama/Groq/OpenRouter/DeepSeek 等):\n LLM_FORMAT=openai\n LLM_API_KEY=xxx\n LLM_BASE_URL=https://api.deepseek.com/v1\n LLM_MODEL=deepseek-chat\n\n 3. Anthropic 官方:\n ANTHROPIC_API_KEY=sk-ant-xxx\n ANTHROPIC_MODEL=claude-3-5-sonnet-20241022\n\n 4. Anthropic 兼容 (代理服务):\n LLM_FORMAT=anthropic\n LLM_API_KEY=xxx\n LLM_BASE_URL=https://your-proxy.com\n LLM_MODEL=claude-3-5-sonnet\n \"\"\"\n\n # 首先检查通用配置\n llm_format = os.getenv(\"LLM_FORMAT\", \"\").lower()\n llm_api_key = os.getenv(\"LLM_API_KEY\", \"\")\n llm_base_url = os.getenv(\"LLM_BASE_URL\", \"\")\n llm_model = os.getenv(\"LLM_MODEL\", \"\")\n llm_temperature = float(os.getenv(\"LLM_TEMPERATURE\", \"0.3\"))\n\n # 构建额外请求头(某些服务需要)\n extra_headers = {}\n if os.getenv(\"LLM_HTTP_REFERER\"):\n extra_headers[\"HTTP-Referer\"] = os.getenv(\"LLM_HTTP_REFERER\")\n if os.getenv(\"LLM_X_TITLE\"):\n extra_headers[\"X-Title\"] = os.getenv(\"LLM_X_TITLE\")\n\n # 标准化 base_url\n if llm_base_url:\n llm_base_url = llm_base_url.rstrip('/')\n # 移除可能的 /v1/chat/completions 后缀\n for suffix in ['/v1/chat/completions', '/chat/completions', '/v1/messages']:\n if llm_base_url.endswith(suffix):\n llm_base_url = llm_base_url[:-len(suffix)]\n break\n\n # 方式1: 通用配置 (LLM_ 前缀)\n if llm_api_key and llm_format:\n # 设置默认模型名称\n if not llm_model:\n if llm_format == \"openai\":\n llm_model = \"gpt-4o\"\n else:\n llm_model = \"claude-3-5-sonnet-20241022\"\n\n return ModelConfig(\n name=llm_model,\n format=llm_format,\n api_key=llm_api_key,\n base_url=llm_base_url or None,\n model=llm_model,\n temperature=llm_temperature,\n extra_headers=extra_headers if extra_headers else None\n )\n\n # 方式2: OpenAI 官方\n openai_key = os.getenv(\"OPENAI_API_KEY\")\n if openai_key:\n openai_base = os.getenv(\"OPENAI_BASE_URL\", \"\")\n if openai_base:\n openai_base = openai_base.rstrip('/')\n for suffix in ['/v1/chat/completions', '/chat/completions']:\n if openai_base.endswith(suffix):\n openai_base = openai_base[:-len(suffix)]\n break\n\n return ModelConfig(\n name=os.getenv(\"OPENAI_MODEL\", \"gpt-4o\"),\n format=\"openai\",\n api_key=openai_key,\n base_url=openai_base or None,\n model=os.getenv(\"OPENAI_MODEL\", \"gpt-4o\"),\n temperature=llm_temperature,\n extra_headers=extra_headers if extra_headers else None\n )\n\n # 方式3: Anthropic 官方\n anthropic_key = os.getenv(\"ANTHROPIC_API_KEY\")\n if anthropic_key:\n anthropic_base = os.getenv(\"ANTHROPIC_BASE_URL\", \"\")\n if anthropic_base:\n anthropic_base = anthropic_base.rstrip('/')\n for suffix in ['/v1/messages', '/messages']:\n if anthropic_base.endswith(suffix):\n anthropic_base = anthropic_base[:-len(suffix)]\n break\n\n return ModelConfig(\n name=os.getenv(\"ANTHROPIC_MODEL\", \"claude-3-5-sonnet-20241022\"),\n format=\"anthropic\",\n api_key=anthropic_key,\n base_url=anthropic_base or None,\n model=os.getenv(\"ANTHROPIC_MODEL\", \"claude-3-5-sonnet-20241022\"),\n temperature=llm_temperature,\n extra_headers=extra_headers if extra_headers else None\n )\n\n raise ValueError(\n \"请配置以下环境变量之一:\\n\"\n \" 1. LLM_FORMAT + LLM_API_KEY + LLM_BASE_URL (通用配置)\\n\"\n \" 2. OPENAI_API_KEY (OpenAI 官方)\\n\"\n \" 3. ANTHROPIC_API_KEY (Anthropic 官方)\\n\"\n \"\\n示例 .env 文件:\\n\"\n \" LLM_FORMAT=openai\\n\"\n \" LLM_API_KEY=your-key\\n\"\n \" LLM_BASE_URL=https://api.deepseek.com/v1\\n\"\n \" LLM_MODEL=deepseek-chat\"\n )\n\n\ndef create_llm_client(config: Optional[ModelConfig] = None):\n \"\"\"\n 创建 LLM 客户端\n 支持 OpenAI 格式和 Anthropic 格式\n \"\"\"\n if config is None:\n config = get_model_config()\n\n if config.format == \"openai\":\n from langchain_openai import ChatOpenAI\n\n kwargs = {\n \"model\": config.model,\n \"temperature\": config.temperature,\n \"api_key\": config.api_key,\n }\n if config.base_url:\n kwargs[\"base_url\"] = config.base_url\n if config.extra_headers:\n kwargs[\"default_headers\"] = config.extra_headers\n\n return ChatOpenAI(**kwargs)\n\n elif config.format == \"anthropic\":\n from langchain_anthropic import ChatAnthropic\n\n kwargs = {\n \"model\": config.model,\n \"temperature\": config.temperature,\n \"api_key\": config.api_key,\n }\n if config.base_url:\n kwargs[\"base_url\"] = config.base_url\n if config.extra_headers:\n kwargs[\"default_headers\"] = config.extra_headers\n\n return ChatAnthropic(**kwargs)\n\n else:\n raise ValueError(f\"不支持的格式: {config.format}\")\n\n\n# 预定义的常见第三方配置(方便复制到 .env)\nPRESET_CONFIGS = {\n \"ollama\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"http://localhost:11434/v1\",\n \"LLM_API_KEY\": \"ollama\", # Ollama 需要任意字符串\n \"LLM_MODEL\": \"llama3.2\",\n },\n \"groq\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://api.groq.com/openai/v1\",\n \"LLM_API_KEY\": \"your-groq-key\",\n \"LLM_MODEL\": \"llama-3.2-90b-vision-preview\",\n },\n \"openrouter\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://openrouter.ai/api/v1\",\n \"LLM_API_KEY\": \"your-openrouter-key\",\n \"LLM_MODEL\": \"anthropic/claude-3.5-sonnet\",\n },\n \"deepseek\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://api.deepseek.com/v1\",\n \"LLM_API_KEY\": \"your-deepseek-key\",\n \"LLM_MODEL\": \"deepseek-chat\",\n },\n \"siliconflow\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://api.siliconflow.cn/v1\",\n \"LLM_API_KEY\": \"your-siliconflow-key\",\n \"LLM_MODEL\": \"deepseek-ai/DeepSeek-V2.5\",\n },\n \"dashscope\": {\n \"LLM_FORMAT\": \"openai\",\n \"LLM_BASE_URL\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"LLM_API_KEY\": \"your-dashscope-key\",\n \"LLM_MODEL\": \"qwen-max\",\n },\n}\n\n\ndef print_preset_configs():\n \"\"\"打印预设配置示例\"\"\"\n print(\"=\" * 60)\n print(\"常用第三方模型配置示例\")\n print(\"=\" * 60)\n\n for name, config in PRESET_CONFIGS.items():\n print(f\"\\n【{name.upper()}】\")\n for key, value in config.items():\n print(f\"{key}={value}\")\n\n print(\"\\n\" + \"=\" * 60)\n print(\"使用方式:将上述配置复制到 .env 文件中\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n print_preset_configs()\n"}}}}