Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chasen-liao_lanobot.json
T

1 line
47 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": "chasen-liao/lanobot", "files": {"config.json": {"5ab2c91813e035a444f9a51d9778402f9d6ed946": {"t": 1785746769.0461369, "n": 401, "h": "56e38729bd56d0bc", "c": "{\n \"llm\": {\n \"provider\": \"siliconflow\",\n \"model\": \"deepseek-ai/DeepSeek-V3.2\",\n \"api_key\": \"sk-wlyxwslviscdgdgsfqelmsyvhjbsjloxdvvoritvjnremboi\",\n \"base_url\": \"https://api.siliconflow.cn/v1\",\n \"temperature\": 0.1,\n \"max_tokens\": 4096\n },\n \"channels\": {\n \"feishu_enabled\": false,\n \"feishu_app_id\": \"\",\n \"feishu_app_secret\": \"\"\n },\n \"logging\": {\n \"level\": \"INFO\"\n }\n}\n"}, "5256bc5495634f4de1a9f0cdb886ba412b5012b8": {"t": 1785767628.2883065, "n": 401, "h": "56e38729bd56d0bc", "c": "{\n \"llm\": {\n \"provider\": \"siliconflow\",\n \"model\": \"deepseek-ai/DeepSeek-V3.2\",\n \"api_key\": \"sk-wlyxwslviscdgdgsfqelmsyvhjbsjloxdvvoritvjnremboi\",\n \"base_url\": \"https://api.siliconflow.cn/v1\",\n \"temperature\": 0.1,\n \"max_tokens\": 4096\n },\n \"channels\": {\n \"feishu_enabled\": false,\n \"feishu_app_id\": \"\",\n \"feishu_app_secret\": \"\"\n },\n \"logging\": {\n \"level\": \"INFO\"\n }\n}\n"}, "9fd5b3cf984fd747e434f2e271b666e5a8476cb6": {"t": 1785767629.5318918, "n": 405, "h": "6ce613680ed1d585", "c": "{\n \"llm\": {\n \"provider\": \"siliconflow\",\n \"model\": \"Pro/deepseek-ai/DeepSeek-V3.2\",\n \"api_key\": \"sk-wlyxwslviscdgdgsfqelmsyvhjbsjloxdvvoritvjnremboi\",\n \"base_url\": \"https://api.siliconflow.cn/v1\",\n \"temperature\": 0.1,\n \"max_tokens\": 4096\n },\n \"channels\": {\n \"feishu_enabled\": false,\n \"feishu_app_id\": \"\",\n \"feishu_app_secret\": \"\"\n },\n \"logging\": {\n \"level\": \"INFO\"\n }\n}\n"}}, "framework/README.md": {"5ab2c91813e035a444f9a51d9778402f9d6ed946": {"t": 1785747232.4867244, "n": 11902, "h": "84b0062b2a605be4", "c": "# Lanobot 架构设计文档\n\n## 1. 整体架构\n\n```mermaid\nflowchart TB\n subgraph Channels[\"渠道层 (channels/)\"]\n TG[Telegram]\n SL[Slack]\n DD[钉钉]\n FS[飞书]\n DC[Discord]\n WA[WhatsApp]\n QQ[QQ]\n EM[Email]\n end\n\n subgraph Bus[\"消息总线 (bus/)\"]\n MB[MessageBus]\n BE[事件定义]\n end\n\n subgraph Core[\"核心Agent层 (agent/)\"]\n AG[AgentGraph<br/>LangGraph StateGraph]\n SN[State Nodes]\n ED[Edges]\n end\n\n subgraph Tools[\"工具层 (tools/)\"]\n FS[文件系统]\n SH[Shell执行]\n WB[Web搜索/抓取]\n MS[Message发送]\n SP[Spawn子代理]\n CR[Cron定时]\n MC[MCP工具]\n end\n\n subgraph Memory[\"记忆层 (memory/)\"]\n CP[Checkpointer<br/>短期记忆]\n LS[Long-term Store<br/>长期记忆]\n HM[History Manager]\n end\n\n subgraph Providers[\"LLM提供商 (providers/)\"]\n OP[OpenAI]\n AN[Anthropic]\n AZ[Azure OpenAI]\n DS[DeepSeek]\n OR[OpenRouter]\n LM[本地模型<br/>Ollama/vLLM]\n end\n\n subgraph Config[\"配置层 (config/)\"]\n SC[Schema<br/>Pydantic]\n LD[Loader]\n end\n\n subgraph Session[\"会话管理 (session/)\"]\n SM[SessionManager]\n SH[Session历史]\n end\n\n Channels --> MB\n MB --> AG\n AG --> Tools\n AG --> AN\n Tools -->|结果| AG\n AG <--> CP\n AG <--> LS\n AG <--> HM\n AG <--> MB\n MB --> Channels\n```\n\n## 2. 模块职责\n\n### 2.1 渠道层 (channels/)\n\n**职责**:对接各种IM/通讯平台的API,统一消息格式\n\n**保留原因**:与业务无关,纯粹适配外部接口\n\n```python\n# channels/base.py\nclass ChannelAdapter(ABC):\n \"\"\"渠道适配器基类\"\"\"\n\n @abstractmethod\n async def start(self):\n \"\"\"启动渠道监听\"\"\"\n\n @abstractmethod\n async def send(self, chat_id: str, content: str, metadata: dict = None):\n \"\"\"发送消息\"\"\"\n\n @abstractmethod\n async def stop(self):\n \"\"\"停止渠道\"\"\"\n```\n\n**支持的渠道**:\n- Telegram, Slack, Discord\n- 钉钉, 飞书, 企业微信\n- WhatsApp, QQ, Email, Matrix\n\n### 2.2 消息总线 (bus/)\n\n**职责**:异步消息队列,解耦各模块\n\n**保留原因**:架构清晰,状态无关\n\n```python\n# bus/queue.py\nclass MessageBus:\n \"\"\"Async message queue for decoupled channel-agent communication\"\"\"\n\n def __init__(self):\n self.inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()\n self.outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()\n\n async def publish_inbound(self, msg: InboundMessage):\n await self.inbound.put(msg)\n\n async def consume_inbound(self) -> InboundMessage:\n return await self.inbound.get()\n\n async def publish_outbound(self, msg: OutboundMessage):\n await self.outbound.put(msg)\n```\n\n### 2.3 Agent核心层 (agent/) - LangChain 1.x重构\n\n**职责**:Agent推理循环,工具调用协调\n\n**重构方案**:使用LangChain 1.x的 `create_agent` API(基于LangGraph)\n\n> ⚠️ **重要更新**: LangGraph v1 废弃了 `create_react_agent`,推荐使用 LangChain 1.x 的 `create_agent`\n\n```python\n# agent/graph.py\nfrom langchain.agents import create_agent\nfrom langgraph.checkpoint.memory import InMemorySaver\n\nclass AgentGraph:\n \"\"\"LangChain 1.x Agent构建器\"\"\"\n\n def __init__(self, model, tools: list, checkpointer=None, middleware=None):\n self.graph = create_agent(\n model, # 模型实例或模型名\n tools, # 工具列表\n system_prompt=\"你是lanobot,一个超轻量级AI助手。\", # 注意:不再是prompt\n checkpointer=checkpointer,\n middleware=middleware, # 可选:中间件列表\n )\n\n async def invoke(self, message: str, thread_id: str):\n config = {\"configurable\": {\"thread_id\": thread_id}}\n return await self.graph.ainvoke(\n {\"messages\": [{\"role\": \"user\", \"content\": message}]},\n config=config\n )\n```\n\n#### Middleware系统\n\n```python\n# agent/middleware.py\nfrom langchain.agents.middleware import HumanInTheLoopMiddleware\n\n# 人机协作中间件 - 敏感操作需要审批\nhuman_middleware = HumanInTheLoopMiddleware(\n interrupt_on={\n \"send_message\": True, # 发送消息需要审批\n \"exec_command\": True, # 执行命令需要审批\n \"write_file\": True, # 写文件需要审批\n }\n)\n\n# 创建带中间件的Agent\nagent = create_agent(\n model,\n tools,\n system_prompt=\"...\",\n middleware=[human_middleware],\n checkpointer=InMemorySaver(),\n)\n```\n\n### 2.4 工具层 (tools/) - LangChain化\n\n**职责**:Agent可调用的外部能力\n\n**重构方案**:使用`@tool`装饰器或`StructuredTool`\n\n```python\n# tools/filesystem.py\nfrom langchain_core.tools import tool, StructuredTool\nfrom pydantic import BaseModel, Field\n\n# 方式1:使用@tool装饰器(简单场景)\n@tool\ndef read_file(path: str) -> str:\n \"\"\"读取文件内容\n\n Args:\n path: 文件路径(绝对路径或相对于workspace的路径)\n\n Returns:\n 文件内容字符串\n \"\"\"\n # 实现逻辑\n pass\n\n@tool\ndef write_file(path: str, content: str) -> str:\n \"\"\"写入文件内容\n\n Args:\n path: 文件路径\n content: 要写入的内容\n \"\"\"\n pass\n\n@tool\ndef list_directory(path: str = \".\") -> str:\n \"\"\"列出目录内容\"\"\"\n pass\n\n@tool\ndef edit_file(path: str, old: str, new: str) -> str:\n \"\"\"编辑文件内容(替换指定文本)\"\"\"\n pass\n```\n\n```python\n# tools/shell.py\n@tool\ndef exec_command(command: str, timeout: int = 60) -> str:\n \"\"\"执行Shell命令\n\n Args:\n command: 要执行的命令\n timeout: 超时时间(秒),默认60秒\n \"\"\"\n pass\n```\n\n```python\n# tools/web.py\n@tool\ndef web_search(query: str, max_results: int = 5) -> str:\n \"\"\"Web搜索\"\"\"\n pass\n\n@tool\ndef web_fetch(url: str) -> str:\n \"\"\"抓取网页内容\"\"\"\n pass\n\n@tool\ndef message_send(channel: str, chat_id: str, content: str) -> str:\n \"\"\"发送消息到指定渠道\"\"\"\n pass\n\n@tool\ndef spawn_subagent(task: str, model: str = None) -> str:\n \"\"\"Spawn a sub-agent to handle subtasks\"\"\"\n pass\n```\n\n### 2.5 记忆层 (memory/) - LangChain化\n\n**职责**:短期会话记忆 + 长期记忆\n\n**重构方案**:LangGraph Checkpointer + LangChain Store\n\n> ⚠️ **注意**: LangChain 1.x 使用 `InMemorySaver` 替代了 `MemorySaver`\n\n```python\n# memory/checkpointer.py\nfrom langgraph.checkpoint.memory import InMemorySaver\n\n# 开发环境\ncheckpointer = InMemorySaver()\n\n# 生产环境\n# from langgraph.checkpoint.postgres import PostgresSaver\n# checkpointer = PostgresSaver.from_conn_string(conn_string)\n```\n\n```python\n# memory/store.py\nfrom langchain.storage import InMemoryStore\nfrom langgraph.store.postgres import PostgresStore\n\nclass MemoryStore:\n \"\"\"长期记忆存储\"\"\"\n\n def __init__(self, use_postgres: bool = False):\n if use_postgres:\n self.store = PostgresStore.from_conn_string(conn_string)\n else:\n self.store = InMemoryStore()\n\n async def search_memory(self, query: str, k: int = 5) -> list:\n \"\"\"语义检索相关记忆\"\"\"\n pass\n\n async def save_memory(self, key: str, value: str, metadata: dict = None):\n \"\"\"保存记忆\"\"\"\n pass\n```\n\n### 2.6 Providers层 (providers/)\n\n**职责**:LLM接口适配\n\n**保留原因**:对接各种API,但包装为LangChain接口\n\n```python\n# providers/langchain_wrapper.py\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_openai import ChatOpenAI\nfrom langchain_community.chat_models import ChatLiteLLM\n\ndef create_llm(provider: str, model: str, api_key: str = None, api_base: str = None):\n \"\"\"创建LangChain LLM实例\"\"\"\n\n if provider == \"anthropic\":\n return ChatAnthropic(\n model=model,\n api_key=api_key,\n base_url=api_base\n )\n elif provider == \"openai\":\n return ChatOpenAI(\n model=model,\n api_key=api_key,\n base_url=api_base\n )\n # LiteLLM支持所有OpenAI兼容的API\n elif provider == \"litellm\":\n return ChatLiteLLM(\n model=model,\n api_key=api_key,\n base_url=api_base\n )\n```\n\n### 2.7 配置层 (config/)\n\n**职责**:YAML/Env配置解析\n\n**保留**:Pydantic配置系统已经很完善\n\n```python\n# config/schema.py\nclass Config(BaseSettings):\n agents: AgentsConfig\n channels: ChannelsConfig\n providers: ProvidersConfig\n gateway: GatewayConfig\n tools: ToolsConfig\n```\n\n### 2.8 会话管理 (session/)\n\n**职责**:会话历史持久化\n\n**保留**:Session管理逻辑与具体Agent实现解耦\n\n```python\n# session/manager.py\nclass SessionManager:\n \"\"\"会话管理器\"\"\"\n\n def __init__(self, workspace: Path):\n self.sessions_dir = workspace / \"sessions\"\n\n def get_or_create(self, session_key: str) -> Session:\n \"\"\"获取或创建会话\"\"\"\n\n def save(self, session: Session):\n \"\"\"保存会话\"\"\"\n\n def get_history(self, session_key: str, max_messages: int = None) -> list:\n \"\"\"获取会话历史\"\"\"\n```\n\n## 3. 数据流\n\n### 3.1 消息处理流程\n\n```mermaid\nsequenceDiagram\n participant C as Channel\n participant B as MessageBus\n participant G as AgentGraph\n participant T as Tools\n participant P as Provider\n participant M as Memory\n participant O as Outbound\n\n C->>B: publish_inbound(msg)\n B->>G: 消费消息\n G->>P: LLM调用(带工具定义)\n P-->>G: LLM响应(含tool_calls)\n G->>T: 执行工具\n T-->>G: 工具结果\n G->>M: 保存checkpointer状态\n G->>P: 继续LLM调用\n P-->>G: 最终响应\n G->>B: publish_outbound(response)\n B->>O: 推送响应\n O->>C: 发送消息\n```\n\n### 3.2 记忆系统流程\n\n```mermaid\nflowchart LR\n A[用户消息] --> B{检查token限制}\n B -->|超限| C[consolidate_messages]\n B -->|正常| D[直接处理]\n C --> E[LLM摘要]\n E --> F[保存到MEMORY.md]\n F --> D\n D --> G[返回响应]\n```\n\n## 4. 目录结构\n\n```\nlanobot/\n├── main.py # 入口点\n├── pyproject.toml # 项目配置\n├── CLAUDE.md # Claude Code指导\n│\n├── config/ # 配置层\n│ ├── schema.py # Pydantic配置模型\n│ ├── loader.py # 配置加载\n│ └── paths.py # 路径工具\n│\n├── bus/ # 消息总线\n│ ├── queue.py # MessageBus实现\n│ └── events.py # 事件定义\n│\n├── channels/ # 渠道层(保留)\n│ ├── base.py # 渠道基类\n│ ├── manager.py # 渠道管理\n│ ├── telegram.py\n│ ├── slack.py\n│ ├── feishu.py\n│ ├── dingtalk.py\n│ ├── discord.py\n│ ├── whatsapp.py\n│ ├── qq.py\n│ ├── wecom.py\n│ ├── email.py\n│ └── matrix.py\n│\n├── providers/ # LLM提供商\n│ ├── base.py # 基础接口(可保留)\n│ ├── registry.py # 提供商注册\n│ ├── langchain_wrapper.py # LangChain包装器\n│ ├── openai_provider.py\n│ ├── anthropic_provider.py\n│ ├── azure_openai_provider.py\n│ ├── litellm_provider.py\n│ └── custom_provider.py\n│\n├── agent/ # Agent核心层(LangChain化)\n│ ├── __init__.py\n│ ├── state.py # AgentState定义\n│ ├── graph.py # LangGraph构建\n│ ├── nodes.py # 节点函数\n│ ├── edges.py # 边定义\n│ └── middleware.py # 中间件\n│\n├── tools/ # 工具层(LangChain化)\n│ ├── __init__.py\n│ ├── registry.py # 工具注册\n│ ├── filesystem.py # 文件操作\n│ ├── shell.py # Shell执行\n│ ├── web.py # Web工具\n│ ├── message.py # 消息发送\n│ ├── spawn.py # 子代理\n│ ├── cron.py # 定时任务\n│ └── mcp.py # MCP集成\n│\n├── memory/ # 记忆层(LangChain化)\n│ ├── checkpointer.py # LangGraph Checkpointer\n│ ├── store.py # 长期记忆\n│ └── history.py # 历史记录管理\n│\n├── session/ # 会话管理\n│ ├── __init__.py\n│ ├── manager.py # 会话管理器\n│ └── types.py # 会话类型\n│\n├── cron/ # 定时任务\n│ ├── service.py\n│ └── types.py\n│\n├── heartbeat/ # 心跳服务\n│ └── service.py\n│\n├── cli/ # CLI命令\n│ ├── commands.py\n│ └── __init__.py\n│\n└── utils/ # 工具函数\n └── helpers.py\n```\n\n## 5. 重构计划\n\n### Phase 1: 基础架构\n- [ ] 创建agent/state.py - AgentState定义\n- [ ] 创建agent/graph.py - LangGraph构建\n- [ ] 创建providers/langchain_wrapper.py - LLM包装\n- [ ] 集成现有MessageBus\n\n### Phase 2: 工具迁移\n- [ ] 将tools/filesystem.py转为@tool\n- [ ] 将tools/shell.py转为@tool\n- [ ] 将tools/web.py转为@tool\n- [ ] 集成MCP工具\n\n### Phase 3: 记忆系统\n- [ ] 集成LangGraph Checkpointer\n- [ ] 实现长期记忆Store\n- [ ] 迁移会话历史\n\n### Phase 4: 渠道适配\n- [ ] 适配现有渠道到新Agent接口\n- [ ] 测试各渠道连通性\n\n### Phase 5: CLI和配置\n- [ ] 更新CLI命令\n- [ ] 验证配置系统\n\n## 6. 关键设计决策\n\n### 6.1 为什么用混合架构?\n- **保留**:渠道、配置、业务无关的模块已经稳定\n- **重构**:Agent核心需要LangChain的标准化和生态\n\n### 6.2 LangGraph vs 纯LangChain\n- 使用`create_agent`快速构建\n- 后续可扩展为自定义StateGraph\n\n### 6.3 记忆策略\n- **短期**:LangGraph Checkpointer (thread_id级别)\n- **长期**:LangChain Store (跨会话)\n- **历史**:文件存储 (可grep搜索)\n\n### 6.4 Provider策略\n- 保留原有provider注册机制\n- 新增LangChain包装层\n- 渐进式迁移"}, "771d048f6878219b0a134244862568223f15fd53": {"t": 1785767627.4940884, "n": 11902, "h": "84b0062b2a605be4", "c": "# Lanobot 架构设计文档\n\n## 1. 整体架构\n\n```mermaid\nflowchart TB\n subgraph Channels[\"渠道层 (channels/)\"]\n TG[Telegram]\n SL[Slack]\n DD[钉钉]\n FS[飞书]\n DC[Discord]\n WA[WhatsApp]\n QQ[QQ]\n EM[Email]\n end\n\n subgraph Bus[\"消息总线 (bus/)\"]\n MB[MessageBus]\n BE[事件定义]\n end\n\n subgraph Core[\"核心Agent层 (agent/)\"]\n AG[AgentGraph<br/>LangGraph StateGraph]\n SN[State Nodes]\n ED[Edges]\n end\n\n subgraph Tools[\"工具层 (tools/)\"]\n FS[文件系统]\n SH[Shell执行]\n WB[Web搜索/抓取]\n MS[Message发送]\n SP[Spawn子代理]\n CR[Cron定时]\n MC[MCP工具]\n end\n\n subgraph Memory[\"记忆层 (memory/)\"]\n CP[Checkpointer<br/>短期记忆]\n LS[Long-term Store<br/>长期记忆]\n HM[History Manager]\n end\n\n subgraph Providers[\"LLM提供商 (providers/)\"]\n OP[OpenAI]\n AN[Anthropic]\n AZ[Azure OpenAI]\n DS[DeepSeek]\n OR[OpenRouter]\n LM[本地模型<br/>Ollama/vLLM]\n end\n\n subgraph Config[\"配置层 (config/)\"]\n SC[Schema<br/>Pydantic]\n LD[Loader]\n end\n\n subgraph Session[\"会话管理 (session/)\"]\n SM[SessionManager]\n SH[Session历史]\n end\n\n Channels --> MB\n MB --> AG\n AG --> Tools\n AG --> AN\n Tools -->|结果| AG\n AG <--> CP\n AG <--> LS\n AG <--> HM\n AG <--> MB\n MB --> Channels\n```\n\n## 2. 模块职责\n\n### 2.1 渠道层 (channels/)\n\n**职责**:对接各种IM/通讯平台的API,统一消息格式\n\n**保留原因**:与业务无关,纯粹适配外部接口\n\n```python\n# channels/base.py\nclass ChannelAdapter(ABC):\n \"\"\"渠道适配器基类\"\"\"\n\n @abstractmethod\n async def start(self):\n \"\"\"启动渠道监听\"\"\"\n\n @abstractmethod\n async def send(self, chat_id: str, content: str, metadata: dict = None):\n \"\"\"发送消息\"\"\"\n\n @abstractmethod\n async def stop(self):\n \"\"\"停止渠道\"\"\"\n```\n\n**支持的渠道**:\n- Telegram, Slack, Discord\n- 钉钉, 飞书, 企业微信\n- WhatsApp, QQ, Email, Matrix\n\n### 2.2 消息总线 (bus/)\n\n**职责**:异步消息队列,解耦各模块\n\n**保留原因**:架构清晰,状态无关\n\n```python\n# bus/queue.py\nclass MessageBus:\n \"\"\"Async message queue for decoupled channel-agent communication\"\"\"\n\n def __init__(self):\n self.inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()\n self.outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()\n\n async def publish_inbound(self, msg: InboundMessage):\n await self.inbound.put(msg)\n\n async def consume_inbound(self) -> InboundMessage:\n return await self.inbound.get()\n\n async def publish_outbound(self, msg: OutboundMessage):\n await self.outbound.put(msg)\n```\n\n### 2.3 Agent核心层 (agent/) - LangChain 1.x重构\n\n**职责**:Agent推理循环,工具调用协调\n\n**重构方案**:使用LangChain 1.x的 `create_agent` API(基于LangGraph)\n\n> ⚠️ **重要更新**: LangGraph v1 废弃了 `create_react_agent`,推荐使用 LangChain 1.x 的 `create_agent`\n\n```python\n# agent/graph.py\nfrom langchain.agents import create_agent\nfrom langgraph.checkpoint.memory import InMemorySaver\n\nclass AgentGraph:\n \"\"\"LangChain 1.x Agent构建器\"\"\"\n\n def __init__(self, model, tools: list, checkpointer=None, middleware=None):\n self.graph = create_agent(\n model, # 模型实例或模型名\n tools, # 工具列表\n system_prompt=\"你是lanobot,一个超轻量级AI助手。\", # 注意:不再是prompt\n checkpointer=checkpointer,\n middleware=middleware, # 可选:中间件列表\n )\n\n async def invoke(self, message: str, thread_id: str):\n config = {\"configurable\": {\"thread_id\": thread_id}}\n return await self.graph.ainvoke(\n {\"messages\": [{\"role\": \"user\", \"content\": message}]},\n config=config\n )\n```\n\n#### Middleware系统\n\n```python\n# agent/middleware.py\nfrom langchain.agents.middleware import HumanInTheLoopMiddleware\n\n# 人机协作中间件 - 敏感操作需要审批\nhuman_middleware = HumanInTheLoopMiddleware(\n interrupt_on={\n \"send_message\": True, # 发送消息需要审批\n \"exec_command\": True, # 执行命令需要审批\n \"write_file\": True, # 写文件需要审批\n }\n)\n\n# 创建带中间件的Agent\nagent = create_agent(\n model,\n tools,\n system_prompt=\"...\",\n middleware=[human_middleware],\n checkpointer=InMemorySaver(),\n)\n```\n\n### 2.4 工具层 (tools/) - LangChain化\n\n**职责**:Agent可调用的外部能力\n\n**重构方案**:使用`@tool`装饰器或`StructuredTool`\n\n```python\n# tools/filesystem.py\nfrom langchain_core.tools import tool, StructuredTool\nfrom pydantic import BaseModel, Field\n\n# 方式1:使用@tool装饰器(简单场景)\n@tool\ndef read_file(path: str) -> str:\n \"\"\"读取文件内容\n\n Args:\n path: 文件路径(绝对路径或相对于workspace的路径)\n\n Returns:\n 文件内容字符串\n \"\"\"\n # 实现逻辑\n pass\n\n@tool\ndef write_file(path: str, content: str) -> str:\n \"\"\"写入文件内容\n\n Args:\n path: 文件路径\n content: 要写入的内容\n \"\"\"\n pass\n\n@tool\ndef list_directory(path: str = \".\") -> str:\n \"\"\"列出目录内容\"\"\"\n pass\n\n@tool\ndef edit_file(path: str, old: str, new: str) -> str:\n \"\"\"编辑文件内容(替换指定文本)\"\"\"\n pass\n```\n\n```python\n# tools/shell.py\n@tool\ndef exec_command(command: str, timeout: int = 60) -> str:\n \"\"\"执行Shell命令\n\n Args:\n command: 要执行的命令\n timeout: 超时时间(秒),默认60秒\n \"\"\"\n pass\n```\n\n```python\n# tools/web.py\n@tool\ndef web_search(query: str, max_results: int = 5) -> str:\n \"\"\"Web搜索\"\"\"\n pass\n\n@tool\ndef web_fetch(url: str) -> str:\n \"\"\"抓取网页内容\"\"\"\n pass\n\n@tool\ndef message_send(channel: str, chat_id: str, content: str) -> str:\n \"\"\"发送消息到指定渠道\"\"\"\n pass\n\n@tool\ndef spawn_subagent(task: str, model: str = None) -> str:\n \"\"\"Spawn a sub-agent to handle subtasks\"\"\"\n pass\n```\n\n### 2.5 记忆层 (memory/) - LangChain化\n\n**职责**:短期会话记忆 + 长期记忆\n\n**重构方案**:LangGraph Checkpointer + LangChain Store\n\n> ⚠️ **注意**: LangChain 1.x 使用 `InMemorySaver` 替代了 `MemorySaver`\n\n```python\n# memory/checkpointer.py\nfrom langgraph.checkpoint.memory import InMemorySaver\n\n# 开发环境\ncheckpointer = InMemorySaver()\n\n# 生产环境\n# from langgraph.checkpoint.postgres import PostgresSaver\n# checkpointer = PostgresSaver.from_conn_string(conn_string)\n```\n\n```python\n# memory/store.py\nfrom langchain.storage import InMemoryStore\nfrom langgraph.store.postgres import PostgresStore\n\nclass MemoryStore:\n \"\"\"长期记忆存储\"\"\"\n\n def __init__(self, use_postgres: bool = False):\n if use_postgres:\n self.store = PostgresStore.from_conn_string(conn_string)\n else:\n self.store = InMemoryStore()\n\n async def search_memory(self, query: str, k: int = 5) -> list:\n \"\"\"语义检索相关记忆\"\"\"\n pass\n\n async def save_memory(self, key: str, value: str, metadata: dict = None):\n \"\"\"保存记忆\"\"\"\n pass\n```\n\n### 2.6 Providers层 (providers/)\n\n**职责**:LLM接口适配\n\n**保留原因**:对接各种API,但包装为LangChain接口\n\n```python\n# providers/langchain_wrapper.py\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_openai import ChatOpenAI\nfrom langchain_community.chat_models import ChatLiteLLM\n\ndef create_llm(provider: str, model: str, api_key: str = None, api_base: str = None):\n \"\"\"创建LangChain LLM实例\"\"\"\n\n if provider == \"anthropic\":\n return ChatAnthropic(\n model=model,\n api_key=api_key,\n base_url=api_base\n )\n elif provider == \"openai\":\n return ChatOpenAI(\n model=model,\n api_key=api_key,\n base_url=api_base\n )\n # LiteLLM支持所有OpenAI兼容的API\n elif provider == \"litellm\":\n return ChatLiteLLM(\n model=model,\n api_key=api_key,\n base_url=api_base\n )\n```\n\n### 2.7 配置层 (config/)\n\n**职责**:YAML/Env配置解析\n\n**保留**:Pydantic配置系统已经很完善\n\n```python\n# config/schema.py\nclass Config(BaseSettings):\n agents: AgentsConfig\n channels: ChannelsConfig\n providers: ProvidersConfig\n gateway: GatewayConfig\n tools: ToolsConfig\n```\n\n### 2.8 会话管理 (session/)\n\n**职责**:会话历史持久化\n\n**保留**:Session管理逻辑与具体Agent实现解耦\n\n```python\n# session/manager.py\nclass SessionManager:\n \"\"\"会话管理器\"\"\"\n\n def __init__(self, workspace: Path):\n self.sessions_dir = workspace / \"sessions\"\n\n def get_or_create(self, session_key: str) -> Session:\n \"\"\"获取或创建会话\"\"\"\n\n def save(self, session: Session):\n \"\"\"保存会话\"\"\"\n\n def get_history(self, session_key: str, max_messages: int = None) -> list:\n \"\"\"获取会话历史\"\"\"\n```\n\n## 3. 数据流\n\n### 3.1 消息处理流程\n\n```mermaid\nsequenceDiagram\n participant C as Channel\n participant B as MessageBus\n participant G as AgentGraph\n participant T as Tools\n participant P as Provider\n participant M as Memory\n participant O as Outbound\n\n C->>B: publish_inbound(msg)\n B->>G: 消费消息\n G->>P: LLM调用(带工具定义)\n P-->>G: LLM响应(含tool_calls)\n G->>T: 执行工具\n T-->>G: 工具结果\n G->>M: 保存checkpointer状态\n G->>P: 继续LLM调用\n P-->>G: 最终响应\n G->>B: publish_outbound(response)\n B->>O: 推送响应\n O->>C: 发送消息\n```\n\n### 3.2 记忆系统流程\n\n```mermaid\nflowchart LR\n A[用户消息] --> B{检查token限制}\n B -->|超限| C[consolidate_messages]\n B -->|正常| D[直接处理]\n C --> E[LLM摘要]\n E --> F[保存到MEMORY.md]\n F --> D\n D --> G[返回响应]\n```\n\n## 4. 目录结构\n\n```\nlanobot/\n├── main.py # 入口点\n├── pyproject.toml # 项目配置\n├── CLAUDE.md # Claude Code指导\n│\n├── config/ # 配置层\n│ ├── schema.py # Pydantic配置模型\n│ ├── loader.py # 配置加载\n│ └── paths.py # 路径工具\n│\n├── bus/ # 消息总线\n│ ├── queue.py # MessageBus实现\n│ └── events.py # 事件定义\n│\n├── channels/ # 渠道层(保留)\n│ ├── base.py # 渠道基类\n│ ├── manager.py # 渠道管理\n│ ├── telegram.py\n│ ├── slack.py\n│ ├── feishu.py\n│ ├── dingtalk.py\n│ ├── discord.py\n│ ├── whatsapp.py\n│ ├── qq.py\n│ ├── wecom.py\n│ ├── email.py\n│ └── matrix.py\n│\n├── providers/ # LLM提供商\n│ ├── base.py # 基础接口(可保留)\n│ ├── registry.py # 提供商注册\n│ ├── langchain_wrapper.py # LangChain包装器\n│ ├── openai_provider.py\n│ ├── anthropic_provider.py\n│ ├── azure_openai_provider.py\n│ ├── litellm_provider.py\n│ └── custom_provider.py\n│\n├── agent/ # Agent核心层(LangChain化)\n│ ├── __init__.py\n│ ├── state.py # AgentState定义\n│ ├── graph.py # LangGraph构建\n│ ├── nodes.py # 节点函数\n│ ├── edges.py # 边定义\n│ └── middleware.py # 中间件\n│\n├── tools/ # 工具层(LangChain化)\n│ ├── __init__.py\n│ ├── registry.py # 工具注册\n│ ├── filesystem.py # 文件操作\n│ ├── shell.py # Shell执行\n│ ├── web.py # Web工具\n│ ├── message.py # 消息发送\n│ ├── spawn.py # 子代理\n│ ├── cron.py # 定时任务\n│ └── mcp.py # MCP集成\n│\n├── memory/ # 记忆层(LangChain化)\n│ ├── checkpointer.py # LangGraph Checkpointer\n│ ├── store.py # 长期记忆\n│ └── history.py # 历史记录管理\n│\n├── session/ # 会话管理\n│ ├── __init__.py\n│ ├── manager.py # 会话管理器\n│ └── types.py # 会话类型\n│\n├── cron/ # 定时任务\n│ ├── service.py\n│ └── types.py\n│\n├── heartbeat/ # 心跳服务\n│ └── service.py\n│\n├── cli/ # CLI命令\n│ ├── commands.py\n│ └── __init__.py\n│\n└── utils/ # 工具函数\n └── helpers.py\n```\n\n## 5. 重构计划\n\n### Phase 1: 基础架构\n- [ ] 创建agent/state.py - AgentState定义\n- [ ] 创建agent/graph.py - LangGraph构建\n- [ ] 创建providers/langchain_wrapper.py - LLM包装\n- [ ] 集成现有MessageBus\n\n### Phase 2: 工具迁移\n- [ ] 将tools/filesystem.py转为@tool\n- [ ] 将tools/shell.py转为@tool\n- [ ] 将tools/web.py转为@tool\n- [ ] 集成MCP工具\n\n### Phase 3: 记忆系统\n- [ ] 集成LangGraph Checkpointer\n- [ ] 实现长期记忆Store\n- [ ] 迁移会话历史\n\n### Phase 4: 渠道适配\n- [ ] 适配现有渠道到新Agent接口\n- [ ] 测试各渠道连通性\n\n### Phase 5: CLI和配置\n- [ ] 更新CLI命令\n- [ ] 验证配置系统\n\n## 6. 关键设计决策\n\n### 6.1 为什么用混合架构?\n- **保留**:渠道、配置、业务无关的模块已经稳定\n- **重构**:Agent核心需要LangChain的标准化和生态\n\n### 6.2 LangGraph vs 纯LangChain\n- 使用`create_agent`快速构建\n- 后续可扩展为自定义StateGraph\n\n### 6.3 记忆策略\n- **短期**:LangGraph Checkpointer (thread_id级别)\n- **长期**:LangChain Store (跨会话)\n- **历史**:文件存储 (可grep搜索)\n\n### 6.4 Provider策略\n- 保留原有provider注册机制\n- 新增LangChain包装层\n- 渐进式迁移"}}, "tests/test_providers/test_registry.py": {"5ab2c91813e035a444f9a51d9778402f9d6ed946": {"t": 1785747510.7227652, "n": 6737, "h": "808c7305dbb7dd54", "c": "\"\"\"Provider 注册表测试.\"\"\"\n\nimport pytest\n\nfrom lanobot.providers.registry import (\n PROVIDERS,\n find_by_model,\n find_by_name,\n find_gateway,\n get_default_provider,\n get_provider,\n list_providers,\n)\n\n\nclass TestProviderSpec:\n \"\"\"ProviderSpec 数据类测试.\"\"\"\n\n def test_create_spec(self):\n \"\"\"测试创建 Provider 规范.\"\"\"\n from lanobot.providers.registry import ProviderSpec\n\n spec = ProviderSpec(\n name=\"test\",\n keywords=(\"test\",),\n env_key=\"TEST_API_KEY\",\n display_name=\"Test Provider\",\n litellm_prefix=\"test\",\n )\n assert spec.name == \"test\"\n assert spec.litellm_prefix == \"test\"\n assert spec.label == \"Test Provider\"\n\n def test_label_property(self):\n \"\"\"测试 label 属性.\"\"\"\n spec = get_provider(\"siliconflow\")\n assert spec.label == \"SiliconFlow\"\n\n\nclass TestProviderRegistry:\n \"\"\"Provider 注册表测试.\"\"\"\n\n def test_providers_not_empty(self):\n \"\"\"测试注册表不为空.\"\"\"\n assert len(PROVIDERS) > 0\n\n def test_list_providers(self):\n \"\"\"测试列出所有 Provider.\"\"\"\n providers = list_providers()\n assert isinstance(providers, list)\n assert len(providers) > 0\n\n def test_gateway_providers(self):\n \"\"\"测试网关 Provider.\"\"\"\n # OpenRouter - 通过 API Key 前缀检测\n spec = get_provider(\"openrouter\")\n assert spec.name == \"openrouter\"\n assert spec.is_gateway is True\n assert spec.detect_by_key_prefix == \"sk-or-\"\n assert spec.default_api_base == \"https://openrouter.ai/api/v1\"\n\n # SiliconFlow - 通过 URL 关键字检测\n spec = get_provider(\"siliconflow\")\n assert spec.name == \"siliconflow\"\n assert spec.is_gateway is True\n assert \"siliconflow\" in spec.detect_by_base_keyword\n\n # VolcEngine\n spec = get_provider(\"volcengine\")\n assert spec.name == \"volcengine\"\n assert spec.is_gateway is True\n assert \"volces\" in spec.detect_by_base_keyword\n\n def test_standard_providers(self):\n \"\"\"测试标准 Provider.\"\"\"\n # DeepSeek\n spec = get_provider(\"deepseek\")\n assert spec.name == \"deepseek\"\n assert spec.is_gateway is False\n assert spec.litellm_prefix == \"deepseek\"\n assert spec.env_key == \"DEEPSEEK_API_KEY\"\n\n # OpenAI\n spec = get_provider(\"openai\")\n assert spec.name == \"openai\"\n assert spec.litellm_prefix == \"\"\n\n # Anthropic\n spec = get_provider(\"anthropic\")\n assert spec.name == \"anthropic\"\n assert spec.supports_prompt_caching is True\n\n def test_local_providers(self):\n \"\"\"测试本地 Provider.\"\"\"\n # Ollama\n spec = get_provider(\"ollama\")\n assert spec.name == \"ollama\"\n assert spec.is_local is True\n assert spec.default_api_base == \"http://localhost:11434\"\n\n # vLLM\n spec = get_provider(\"vllm\")\n assert spec.name == \"vllm\"\n assert spec.is_local is True\n\n def test_direct_providers(self):\n \"\"\"测试直接调用 Provider.\"\"\"\n spec = get_provider(\"custom\")\n assert spec.name == \"custom\"\n assert spec.is_direct is True\n\n spec = get_provider(\"azure_openai\")\n assert spec.name == \"azure_openai\"\n assert spec.is_direct is True\n\n def test_oauth_providers(self):\n \"\"\"测试 OAuth Provider.\"\"\"\n spec = get_provider(\"openai_codex\")\n assert spec.name == \"openai_codex\"\n assert spec.is_oauth is True\n assert spec.env_key == \"\"\n\n def test_chinese_providers(self):\n \"\"\"测试中文 Provider.\"\"\"\n # Zhipu (智谱)\n spec = get_provider(\"zhipu\")\n assert spec.name == \"zhipu\"\n assert \"glm\" in spec.keywords\n assert \"zhi\" in spec.display_name.lower()\n\n # DashScope (阿里云)\n spec = get_provider(\"dashscope\")\n assert spec.name == \"dashscope\"\n assert \"qwen\" in spec.keywords\n\n # Moonshot (月之暗面)\n spec = get_provider(\"moonshot\")\n assert spec.name == \"moonshot\"\n assert \"kimi\" in spec.keywords\n\n # MiniMax\n spec = get_provider(\"minimax\")\n assert spec.name == \"minimax\"\n assert spec.default_api_base == \"https://api.minimax.io/v1\"\n\n def test_unknown_provider_raises_error(self):\n \"\"\"测试未知 Provider 抛出错误.\"\"\"\n with pytest.raises(ValueError) as exc:\n get_provider(\"unknown_provider\")\n assert \"Unknown provider\" in str(exc.value)\n\n def test_default_provider(self):\n \"\"\"测试获取默认 Provider (第一个非 Direct).\"\"\"\n default = get_default_provider()\n # 第一个非 Direct 应该是 OpenRouter (Gateway)\n assert default.is_gateway is True\n\n def test_find_by_name(self):\n \"\"\"测试按名称查找.\"\"\"\n spec = find_by_name(\"deepseek\")\n assert spec is not None\n assert spec.name == \"deepseek\"\n\n # 不存在的名称返回 None\n assert find_by_name(\"nonexistent\") is None\n\n def test_find_gateway_by_key_prefix(self):\n \"\"\"测试通过 API Key 前缀检测网关.\"\"\"\n # sk-or- -> OpenRouter\n spec = find_gateway(\n provider_name=None,\n api_key=\"sk-or-v1xxxxx\",\n api_base=None,\n )\n assert spec is not None\n assert spec.name == \"openrouter\"\n\n def test_find_gateway_by_base_keyword(self):\n \"\"\"测试通过 URL 关键字检测网关.\"\"\"\n # siliconflow in URL -> SiliconFlow\n spec = find_gateway(\n provider_name=None,\n api_key=\"sk-xxxx\",\n api_base=\"https://api.siliconflow.cn/v1\",\n )\n assert spec is not None\n assert spec.name == \"siliconflow\"\n\n def test_find_by_model(self):\n \"\"\"测试通过模型名称查找 Provider.\"\"\"\n # gpt-* -> OpenAI\n spec = find_by_model(\"gpt-4o\")\n assert spec is not None\n assert spec.name == \"openai\"\n\n # claude-* -> Anthropic\n spec = find_by_model(\"claude-3-opus\")\n assert spec is not None\n assert spec.name == \"anthropic\"\n\n # deepseek-chat -> DeepSeek\n spec = find_by_model(\"deepseek-chat\")\n assert spec is not None\n assert spec.name == \"deepseek\"\n\n # qwen-max -> DashScope\n spec = find_by_model(\"qwen-max\")\n assert spec is not None\n assert spec.name == \"dashscope\"\n\n # kimi-k2.5 -> Moonshot\n spec = find_by_model(\"kimi-k2.5\")\n assert spec is not None\n assert spec.name == \"moonshot\"\n\n def test_model_overrides(self):\n \"\"\"测试模型参数覆盖.\"\"\"\n spec = get_provider(\"moonshot\")\n assert spec.model_overrides is not None\n # kimi-k2.5 有 temperature 覆盖\n has_override = any(m == \"kimi-k2.5\" for m, _ in spec.model_overrides)\n assert has_override is True\n\n\nif __name__ == \"__main__\":\n pytest.main([__file__, \"-v\"])"}, "9fd5b3cf984fd747e434f2e271b666e5a8476cb6": {"t": 1785767628.1130886, "n": 6737, "h": "808c7305dbb7dd54", "c": "\"\"\"Provider 注册表测试.\"\"\"\n\nimport pytest\n\nfrom lanobot.providers.registry import (\n PROVIDERS,\n find_by_model,\n find_by_name,\n find_gateway,\n get_default_provider,\n get_provider,\n list_providers,\n)\n\n\nclass TestProviderSpec:\n \"\"\"ProviderSpec 数据类测试.\"\"\"\n\n def test_create_spec(self):\n \"\"\"测试创建 Provider 规范.\"\"\"\n from lanobot.providers.registry import ProviderSpec\n\n spec = ProviderSpec(\n name=\"test\",\n keywords=(\"test\",),\n env_key=\"TEST_API_KEY\",\n display_name=\"Test Provider\",\n litellm_prefix=\"test\",\n )\n assert spec.name == \"test\"\n assert spec.litellm_prefix == \"test\"\n assert spec.label == \"Test Provider\"\n\n def test_label_property(self):\n \"\"\"测试 label 属性.\"\"\"\n spec = get_provider(\"siliconflow\")\n assert spec.label == \"SiliconFlow\"\n\n\nclass TestProviderRegistry:\n \"\"\"Provider 注册表测试.\"\"\"\n\n def test_providers_not_empty(self):\n \"\"\"测试注册表不为空.\"\"\"\n assert len(PROVIDERS) > 0\n\n def test_list_providers(self):\n \"\"\"测试列出所有 Provider.\"\"\"\n providers = list_providers()\n assert isinstance(providers, list)\n assert len(providers) > 0\n\n def test_gateway_providers(self):\n \"\"\"测试网关 Provider.\"\"\"\n # OpenRouter - 通过 API Key 前缀检测\n spec = get_provider(\"openrouter\")\n assert spec.name == \"openrouter\"\n assert spec.is_gateway is True\n assert spec.detect_by_key_prefix == \"sk-or-\"\n assert spec.default_api_base == \"https://openrouter.ai/api/v1\"\n\n # SiliconFlow - 通过 URL 关键字检测\n spec = get_provider(\"siliconflow\")\n assert spec.name == \"siliconflow\"\n assert spec.is_gateway is True\n assert \"siliconflow\" in spec.detect_by_base_keyword\n\n # VolcEngine\n spec = get_provider(\"volcengine\")\n assert spec.name == \"volcengine\"\n assert spec.is_gateway is True\n assert \"volces\" in spec.detect_by_base_keyword\n\n def test_standard_providers(self):\n \"\"\"测试标准 Provider.\"\"\"\n # DeepSeek\n spec = get_provider(\"deepseek\")\n assert spec.name == \"deepseek\"\n assert spec.is_gateway is False\n assert spec.litellm_prefix == \"deepseek\"\n assert spec.env_key == \"DEEPSEEK_API_KEY\"\n\n # OpenAI\n spec = get_provider(\"openai\")\n assert spec.name == \"openai\"\n assert spec.litellm_prefix == \"\"\n\n # Anthropic\n spec = get_provider(\"anthropic\")\n assert spec.name == \"anthropic\"\n assert spec.supports_prompt_caching is True\n\n def test_local_providers(self):\n \"\"\"测试本地 Provider.\"\"\"\n # Ollama\n spec = get_provider(\"ollama\")\n assert spec.name == \"ollama\"\n assert spec.is_local is True\n assert spec.default_api_base == \"http://localhost:11434\"\n\n # vLLM\n spec = get_provider(\"vllm\")\n assert spec.name == \"vllm\"\n assert spec.is_local is True\n\n def test_direct_providers(self):\n \"\"\"测试直接调用 Provider.\"\"\"\n spec = get_provider(\"custom\")\n assert spec.name == \"custom\"\n assert spec.is_direct is True\n\n spec = get_provider(\"azure_openai\")\n assert spec.name == \"azure_openai\"\n assert spec.is_direct is True\n\n def test_oauth_providers(self):\n \"\"\"测试 OAuth Provider.\"\"\"\n spec = get_provider(\"openai_codex\")\n assert spec.name == \"openai_codex\"\n assert spec.is_oauth is True\n assert spec.env_key == \"\"\n\n def test_chinese_providers(self):\n \"\"\"测试中文 Provider.\"\"\"\n # Zhipu (智谱)\n spec = get_provider(\"zhipu\")\n assert spec.name == \"zhipu\"\n assert \"glm\" in spec.keywords\n assert \"zhi\" in spec.display_name.lower()\n\n # DashScope (阿里云)\n spec = get_provider(\"dashscope\")\n assert spec.name == \"dashscope\"\n assert \"qwen\" in spec.keywords\n\n # Moonshot (月之暗面)\n spec = get_provider(\"moonshot\")\n assert spec.name == \"moonshot\"\n assert \"kimi\" in spec.keywords\n\n # MiniMax\n spec = get_provider(\"minimax\")\n assert spec.name == \"minimax\"\n assert spec.default_api_base == \"https://api.minimax.io/v1\"\n\n def test_unknown_provider_raises_error(self):\n \"\"\"测试未知 Provider 抛出错误.\"\"\"\n with pytest.raises(ValueError) as exc:\n get_provider(\"unknown_provider\")\n assert \"Unknown provider\" in str(exc.value)\n\n def test_default_provider(self):\n \"\"\"测试获取默认 Provider (第一个非 Direct).\"\"\"\n default = get_default_provider()\n # 第一个非 Direct 应该是 OpenRouter (Gateway)\n assert default.is_gateway is True\n\n def test_find_by_name(self):\n \"\"\"测试按名称查找.\"\"\"\n spec = find_by_name(\"deepseek\")\n assert spec is not None\n assert spec.name == \"deepseek\"\n\n # 不存在的名称返回 None\n assert find_by_name(\"nonexistent\") is None\n\n def test_find_gateway_by_key_prefix(self):\n \"\"\"测试通过 API Key 前缀检测网关.\"\"\"\n # sk-or- -> OpenRouter\n spec = find_gateway(\n provider_name=None,\n api_key=\"sk-or-v1xxxxx\",\n api_base=None,\n )\n assert spec is not None\n assert spec.name == \"openrouter\"\n\n def test_find_gateway_by_base_keyword(self):\n \"\"\"测试通过 URL 关键字检测网关.\"\"\"\n # siliconflow in URL -> SiliconFlow\n spec = find_gateway(\n provider_name=None,\n api_key=\"sk-xxxx\",\n api_base=\"https://api.siliconflow.cn/v1\",\n )\n assert spec is not None\n assert spec.name == \"siliconflow\"\n\n def test_find_by_model(self):\n \"\"\"测试通过模型名称查找 Provider.\"\"\"\n # gpt-* -> OpenAI\n spec = find_by_model(\"gpt-4o\")\n assert spec is not None\n assert spec.name == \"openai\"\n\n # claude-* -> Anthropic\n spec = find_by_model(\"claude-3-opus\")\n assert spec is not None\n assert spec.name == \"anthropic\"\n\n # deepseek-chat -> DeepSeek\n spec = find_by_model(\"deepseek-chat\")\n assert spec is not None\n assert spec.name == \"deepseek\"\n\n # qwen-max -> DashScope\n spec = find_by_model(\"qwen-max\")\n assert spec is not None\n assert spec.name == \"dashscope\"\n\n # kimi-k2.5 -> Moonshot\n spec = find_by_model(\"kimi-k2.5\")\n assert spec is not None\n assert spec.name == \"moonshot\"\n\n def test_model_overrides(self):\n \"\"\"测试模型参数覆盖.\"\"\"\n spec = get_provider(\"moonshot\")\n assert spec.model_overrides is not None\n # kimi-k2.5 有 temperature 覆盖\n has_override = any(m == \"kimi-k2.5\" for m, _ in spec.model_overrides)\n assert has_override is True\n\n\nif __name__ == \"__main__\":\n pytest.main([__file__, \"-v\"])"}}}}