Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
11 KiB
JSON
1 line
11 KiB
JSON
{"repo": "blizzard9272/hello-agent", "files": {"HelloAgents/core/llm.py": {"b0a284dd647c549b7800cc0fe5a45b331ff0b97a": {"t": 1786068461.9822125, "n": 8566, "h": "bd4c7830a1da255c", "c": "\"\"\"HelloAgents统一LLM接口 - 支持OpenAI、Anthropic、Gemini等多种接口\"\"\"\n\nimport os\nimport asyncio\nfrom typing import Optional, Iterator, List, Dict, Union, Any, AsyncIterator\n\nfrom .exceptions import HelloAgentsException\nfrom .llm_response import LLMResponse, StreamStats, LLMToolResponse\nfrom .llm_adapters import create_adapter, BaseLLMAdapter\n\n\nclass HelloAgentsLLM:\n \"\"\"\n HelloAgents统一LLM客户端\n\n 设计理念:\n - 统一配置:只需 LLM_MODEL_ID、LLM_API_KEY、LLM_BASE_URL、LLM_TIMEOUT\n - 自动适配:根据base_url自动选择适配器(OpenAI/Anthropic/Gemini)\n - 统计信息:返回token使用量、耗时等信息,方便日志记录\n - Thinking Model:自动识别并处理推理过程(o1、deepseek-reasoner等)\n\n 支持的接口:\n - OpenAI及所有兼容接口(DeepSeek、Qwen、Kimi、智谱、Ollama等)\n - Anthropic Claude\n - Google Gemini\n \"\"\"\n\n def __init__(\n self,\n model: Optional[str] = None,\n api_key: Optional[str] = None,\n base_url: Optional[str] = None,\n temperature: float = 0.7,\n max_tokens: Optional[int] = None,\n timeout: Optional[int] = None,\n **kwargs\n ):\n \"\"\"\n 初始化LLM客户端\n\n 参数优先级:传入参数 > 环境变量\n\n Args:\n model: 模型名称,默认从 LLM_MODEL_ID 读取\n api_key: API密钥,默认从 LLM_API_KEY 读取\n base_url: 服务地址,默认从 LLM_BASE_URL 读取\n temperature: 温度参数,默认0.7\n max_tokens: 最大token数\n timeout: 超时时间(秒),默认从 LLM_TIMEOUT 读取,默认60秒\n \"\"\"\n # 加载配置\n self.model = model or os.getenv(\"LLM_MODEL_ID\")\n self.api_key = api_key or os.getenv(\"LLM_API_KEY\")\n self.base_url = base_url or os.getenv(\"LLM_BASE_URL\")\n self.timeout = timeout or int(os.getenv(\"LLM_TIMEOUT\", \"60\"))\n\n self.temperature = temperature\n self.max_tokens = max_tokens\n self.kwargs = kwargs\n\n # 验证必要参数\n if not self.model:\n raise HelloAgentsException(\"必须提供模型名称(model参数或LLM_MODEL_ID环境变量)\")\n if not self.api_key:\n raise HelloAgentsException(\"必须提供API密钥(api_key参数或LLM_API_KEY环境变量)\")\n if not self.base_url:\n raise HelloAgentsException(\"必须提供服务地址(base_url参数或LLM_BASE_URL环境变量)\")\n\n # 创建适配器(自动检测)\n self._adapter: BaseLLMAdapter = create_adapter(\n api_key=self.api_key,\n base_url=self.base_url,\n timeout=self.timeout,\n model=self.model\n )\n\n # 最后一次调用的统计信息(用于流式调用)\n self.last_call_stats: Optional[StreamStats] = None\n\n def think(self, messages: List[Dict[str, str]], temperature: Optional[float] = None) -> Iterator[str]:\n \"\"\"\n 调用大语言模型进行思考,并返回流式响应。\n 这是主要的调用方法,默认使用流式响应以获得更好的用户体验。\n\n Args:\n messages: 消息列表\n temperature: 温度参数,如果未提供则使用初始化时的值\n\n Yields:\n str: 流式响应的文本片段\n\n Note:\n 流式调用结束后,可通过 llm.last_call_stats 获取统计信息\n \"\"\"\n print(f\"🧠 正在调用 {self.model} 模型...\")\n\n # 准备参数\n kwargs = {\n \"temperature\": temperature if temperature is not None else self.temperature,\n }\n if self.max_tokens:\n kwargs[\"max_tokens\"] = self.max_tokens\n\n try:\n print(\"✅ 大语言模型响应成功:\")\n for chunk in self._adapter.stream_invoke(messages, **kwargs):\n print(chunk, end=\"\", flush=True)\n yield chunk\n print() # 换行\n\n # 保存统计信息\n if hasattr(self._adapter, 'last_stats'):\n self.last_call_stats = self._adapter.last_stats\n\n except Exception as e:\n print(f\"❌ 调用LLM API时发生错误: {e}\")\n raise\n\n def invoke(self, messages: List[Dict[str, str]], **kwargs) -> LLMResponse:\n \"\"\"\n 非流式调用LLM,返回完整响应对象。\n\n Args:\n messages: 消息列表\n **kwargs: 其他参数(temperature, max_tokens等)\n\n Returns:\n LLMResponse: 包含内容、统计信息、推理过程(thinking model)的响应对象\n\n Example:\n response = llm.invoke([{\"role\": \"user\", \"content\": \"你好\"}])\n print(response.content) # 回复内容\n print(response.usage) # token使用量\n print(response.latency_ms) # 耗时\n if response.reasoning_content: # thinking model的推理过程\n print(response.reasoning_content)\n \"\"\"\n # 合并参数\n call_kwargs = {\n \"temperature\": kwargs.pop(\"temperature\", self.temperature),\n }\n if self.max_tokens:\n call_kwargs[\"max_tokens\"] = kwargs.pop(\"max_tokens\", self.max_tokens)\n call_kwargs.update(kwargs)\n\n return self._adapter.invoke(messages, **call_kwargs)\n\n def stream_invoke(self, messages: List[Dict[str, str]], **kwargs) -> Iterator[str]:\n \"\"\"\n 流式调用LLM的别名方法,与think方法功能相同。\n 保持向后兼容性。\n\n Args:\n messages: 消息列表\n **kwargs: 其他参数\n\n Yields:\n str: 流式响应的文本片段\n\n Note:\n 流式调用结束后,可通过 llm.last_call_stats 获取统计信息\n \"\"\"\n temperature = kwargs.pop(\"temperature\", None)\n\n # 准备参数\n call_kwargs = {}\n if self.max_tokens:\n call_kwargs[\"max_tokens\"] = kwargs.pop(\"max_tokens\", self.max_tokens)\n call_kwargs.update(kwargs)\n\n for chunk in self._adapter.stream_invoke(messages, temperature=temperature, **call_kwargs):\n yield chunk\n\n # 保存统计信息\n if hasattr(self._adapter, 'last_stats'):\n self.last_call_stats = self._adapter.last_stats\n\n def invoke_with_tools(\n self,\n messages: List[Dict],\n tools: List[Dict],\n tool_choice: Union[str, Dict] = \"auto\",\n **kwargs\n ) -> LLMToolResponse:\n \"\"\"\n 调用 LLM 并支持工具调用(Function Calling)\n\n 这是支持 OpenAI Function Calling 的核心方法,用于结构化工具调用。\n\n Args:\n messages: 消息列表,格式为 [{\"role\": \"user\", \"content\": \"...\"}]\n tools: 工具 schema 列表,格式为 OpenAI Function Calling 规范\n tool_choice: 工具选择策略\n - \"auto\": 让模型自动决定是否调用工具(默认)\n - \"none\": 强制不调用工具\n - \"required\": 强制调用工具\n - {\"type\": \"function\", \"function\": {\"name\": \"tool_name\"}}: 强制调用指定工具\n **kwargs: 其他参数(temperature, max_tokens 等)\n\n Returns:\n 统一的工具调用响应对象 (LLMToolResponse)\n\n Raises:\n HelloAgentsException: 当 LLM 调用失败时\n \"\"\"\n # 合并参数\n call_kwargs = {\n \"temperature\": kwargs.pop(\"temperature\", self.temperature),\n \"tool_choice\": tool_choice,\n }\n if self.max_tokens:\n call_kwargs[\"max_tokens\"] = kwargs.pop(\"max_tokens\", self.max_tokens)\n call_kwargs.update(kwargs)\n\n return self._adapter.invoke_with_tools(messages, tools, **call_kwargs)\n\n # ==================== 异步方法 ====================\n\n async def ainvoke(self, messages: List[Dict[str, str]], **kwargs) -> LLMResponse:\n \"\"\"\n 异步非流式调用 LLM\n\n 在线程池中运行同步 invoke 方法,避免阻塞事件循环\n\n Args:\n messages: 消息列表\n **kwargs: 其他参数(temperature, max_tokens等)\n\n Returns:\n LLMResponse: 包含内容、统计信息的响应对象\n\n Example:\n response = await llm.ainvoke([{\"role\": \"user\", \"content\": \"你好\"}])\n print(response.content)\n \"\"\"\n loop = asyncio.get_event_loop()\n return await loop.run_in_executor(\n None,\n lambda: self.invoke(messages, **kwargs)\n )\n\n async def astream_invoke(\n self,\n messages: List[Dict[str, str]],\n **kwargs\n ) -> AsyncIterator[str]:\n \"\"\"\n 真正的异步流式调用 LLM(使用 adapter 的异步实现)\n\n Args:\n messages: 消息列表\n **kwargs: 其他参数\n\n Yields:\n str: 流式响应的文本片段(实时返回)\n\n Example:\n async for chunk in llm.astream_invoke(messages):\n print(chunk, end=\"\", flush=True)\n \"\"\"\n # 使用 adapter 的异步流式方法\n async for chunk in self._adapter.astream_invoke(messages, **kwargs):\n yield chunk\n\n # 保存统计信息\n if hasattr(self._adapter, 'last_stats'):\n self.last_call_stats = self._adapter.last_stats\n\n async def ainvoke_with_tools(\n self,\n messages: List[Dict],\n tools: List[Dict],\n tool_choice: Union[str, Dict] = \"auto\",\n **kwargs\n ) -> LLMToolResponse:\n \"\"\"\n 异步调用 LLM 并支持工具调用(Function Calling)\n\n Args:\n messages: 消息列表\n tools: 工具 schema 列表\n tool_choice: 工具选择策略\n **kwargs: 其他参数\n\n Returns:\n 统一的工具调用响应对象 (LLMToolResponse)\n \"\"\"\n loop = asyncio.get_event_loop()\n return await loop.run_in_executor(\n None,\n lambda: self.invoke_with_tools(messages, tools, tool_choice, **kwargs)\n )"}}}} |