Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/blizzard9272_hello-agent.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
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.
2026-08-07 12:53:47 +08:00

1 line
11 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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 )"}}}}