Coverage for agentos/agent/agent_builder.py: 78%

85 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-06 12:29 +0800

1""" 

2Agent Builder — 一键构建生产级 ToolAgent。 

3 

4自动发现所有 BaseTool 子类并通过 Bridge 注册到 ToolExecutor。 

5支持多种 LLM Provider 的自动探测。 

6""" 

7 

8from __future__ import annotations 

9 

10import importlib 

11import inspect 

12import os 

13import pkgutil 

14 

15from agentos.agent.tool_agent import AgentConfig, ToolAgent, ToolExecutor 

16from agentos.llm.base import LLMProvider 

17from agentos.tools.base import BaseTool 

18from agentos.tools.bridge import bridge_registry_to_executor 

19from agentos.tools.registry import ToolRegistry 

20 

21# ── Tool Discovery ── 

22 

23 

24def discover_tools(package_path: str = "agentos.tools") -> list[BaseTool]: 

25 """自动发现 agentos.tools 包下所有 BaseTool 子类并实例化。 

26 

27 排除已知不能独立执行的基础类。 

28 """ 

29 tools: list[BaseTool] = [] 

30 seen: set[str] = set() 

31 

32 package = importlib.import_module(package_path) 

33 package_dir = os.path.dirname(package.__file__) 

34 

35 for _, module_name, is_pkg in pkgutil.iter_modules([package_dir]): 

36 if module_name.startswith("_"): 

37 continue 

38 try: 

39 mod = importlib.import_module(f"{package_path}.{module_name}") 

40 except Exception: 

41 continue 

42 

43 for name, obj in inspect.getmembers(mod, inspect.isclass): 

44 if not issubclass(obj, BaseTool) or obj is BaseTool: 

45 continue 

46 if name in seen: 

47 continue 

48 # Skip abstract/helper base classes 

49 if getattr(obj, "name", None) is None: 

50 continue 

51 try: 

52 instance = obj() 

53 tools.append(instance) 

54 seen.add(name) 

55 except Exception: 

56 continue 

57 

58 return tools 

59 

60 

61# ── Provider Auto-Detection ── 

62 

63 

64def create_provider() -> LLMProvider: 

65 """自动探测可用的 LLM Provider。 

66 

67 优先级: DEEPSEEK_API_KEY > OPENAI_API_KEY > Mock (dev fallback) 

68 """ 

69 # DeepSeek 

70 if os.getenv("DEEPSEEK_API_KEY"): 

71 from agentos.llm.providers.deepseek import DeepSeekProvider 

72 

73 return DeepSeekProvider(model="deepseek-chat") 

74 

75 # OpenAI 

76 if os.getenv("OPENAI_API_KEY"): 

77 from agentos.llm.providers.openai import OpenAIProvider 

78 

79 return OpenAIProvider(model="gpt-4o-mini") 

80 

81 # Anthropic 

82 if os.getenv("ANTHROPIC_API_KEY"): 

83 from agentos.llm.providers.anthropic import AnthropicProvider 

84 

85 return AnthropicProvider(model="claude-3-5-sonnet-20241022") 

86 

87 # Mock (dev fallback) 

88 from agentos.agent.agent_builder import _MockProvider 

89 

90 return _MockProvider() 

91 

92 

93class _MockProvider(LLMProvider): 

94 """仅用于开发环境的 Mock Provider。""" 

95 

96 provider_name = "mock-dev" 

97 

98 def __init__(self): 

99 super().__init__(model="mock") 

100 

101 def chat(self, messages, **kwargs): 

102 return self._make("Mock provider: no API key configured. Set DEEPSEEK_API_KEY.") 

103 

104 async def achat(self, messages, **kwargs): 

105 return self.chat(messages, **kwargs) 

106 

107 def _make(self, content): 

108 from agentos.llm.base import ( 

109 CompletionChoice, 

110 CompletionResult, 

111 CompletionUsage, 

112 Message, 

113 MessageRole, 

114 ) 

115 

116 return CompletionResult( 

117 choices=[ 

118 CompletionChoice( 

119 index=0, 

120 message=Message(role=MessageRole.ASSISTANT, content=content), 

121 finish_reason="stop", 

122 ) 

123 ], 

124 usage=CompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0), 

125 model="mock", 

126 ) 

127 

128 

129# ── Builder ── 

130 

131 

132def build_agent( 

133 *, 

134 tools: list[BaseTool] | None = None, 

135 provider: LLMProvider | None = None, 

136 system_prompt: str | None = None, 

137 max_steps: int = 10, 

138 verbose: bool = False, 

139 discover_all: bool = True, 

140 include_skills: bool = True, 

141) -> ToolAgent: 

142 """一键构建生产级 ToolAgent。 

143 

144 Args: 

145 tools: 手动指定的工具列表。为 None 且 discover_all=True 时自动发现全部。 

146 provider: LLM Provider。为 None 时自动探测。 

147 system_prompt: 系统提示词。为 None 时使用默认中文提示。 

148 max_steps: 最大推理步数。 

149 verbose: 是否打印详细日志。 

150 discover_all: 是否自动发现所有 BaseTool。 

151 

152 Returns: 

153 配置完成的 ToolAgent 实例。 

154 

155 Example: 

156 agent = build_agent() 

157 result = agent.run("列出 /tmp 目录下的所有 .txt 文件") 

158 """ 

159 # Tools 

160 if tools is None and discover_all: 

161 tools = discover_tools() 

162 

163 # Skills — auto-discover and wrap as tools 

164 if include_skills: 

165 try: 

166 from agentos.tools.skill_tool import discover_skills 

167 

168 skill_tools = discover_skills() 

169 if tools is None: 

170 tools = list(skill_tools) 

171 else: 

172 tools = list(tools) + list(skill_tools) 

173 except Exception: 

174 pass 

175 

176 if tools is None: 

177 tools = [] 

178 

179 reg = ToolRegistry() 

180 for tool in tools: 

181 reg.register(tool) 

182 

183 executor = ToolExecutor() 

184 bridge_registry_to_executor(reg, executor) 

185 

186 # Provider 

187 if provider is None: 

188 provider = create_provider() 

189 

190 # System prompt 

191 if system_prompt is None: 

192 tool_names = ", ".join(t.name for t in tools) if tools else "无" 

193 system_prompt = ( 

194 "你是一个智能助手,可以使用工具来完成任务。\n" 

195 f"可用工具: {tool_names}\n" 

196 "当你可以给出最终答案时直接回答,不要再调用工具。\n" 

197 "用简洁的中文回答。" 

198 ) 

199 

200 agent = ToolAgent( 

201 provider=provider, 

202 tool_executor=executor, 

203 config=AgentConfig(max_steps=max_steps, verbose=verbose), 

204 system_prompt=system_prompt, 

205 ) 

206 

207 return agent