run.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. """
  2. 生产级 Agent 模板
  3. 这是一个可直接用于生产环境的 Agent 模板,包含:
  4. - 完整的错误处理
  5. - 日志记录
  6. - 配置管理
  7. - 工具注册
  8. - Skills 加载
  9. - 结果输出
  10. """
  11. import asyncio
  12. import logging
  13. import sys
  14. import os
  15. from pathlib import Path
  16. from typing import Optional, List, Dict, Any
  17. from datetime import datetime
  18. # 添加项目根目录到 Python 路径
  19. sys.path.insert(0, str(Path(__file__).parent.parent.parent))
  20. from dotenv import load_dotenv
  21. load_dotenv()
  22. from agent import (
  23. AgentRunner,
  24. RunConfig,
  25. FileSystemTraceStore,
  26. Trace,
  27. Message,
  28. tool,
  29. ToolResult,
  30. ToolContext,
  31. )
  32. from agent.llm import create_openrouter_llm_call
  33. # 配置日志
  34. logging.basicConfig(
  35. level=logging.INFO,
  36. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
  37. handlers=[
  38. logging.FileHandler('.cache/agent.log'),
  39. logging.StreamHandler()
  40. ]
  41. )
  42. logger = logging.getLogger(__name__)
  43. # ===== 配置管理 =====
  44. class AgentConfig:
  45. """Agent 配置"""
  46. def __init__(self):
  47. # LLM 配置
  48. self.model = os.getenv("MODEL", "anthropic/claude-sonnet-4.5")
  49. self.api_key = os.getenv("OPENROUTER_API_KEY")
  50. self.temperature = float(os.getenv("TEMPERATURE", "0.3"))
  51. self.max_iterations = int(os.getenv("MAX_ITERATIONS", "30"))
  52. # 存储配置
  53. self.trace_dir = os.getenv("TRACE_DIR", ".cache/traces")
  54. self.output_dir = os.getenv("OUTPUT_DIR", ".cache/output")
  55. # Skills 配置
  56. self.skills_dir = os.getenv("SKILLS_DIR", "./skills")
  57. self.enabled_skills = os.getenv("ENABLED_SKILLS", "").split(",") if os.getenv("ENABLED_SKILLS") else None
  58. # 验证配置
  59. self._validate()
  60. def _validate(self):
  61. """验证配置"""
  62. if not self.api_key:
  63. raise ValueError("OPENROUTER_API_KEY 未设置,请在 .env 文件中配置")
  64. # 创建必要的目录
  65. Path(self.trace_dir).mkdir(parents=True, exist_ok=True)
  66. Path(self.output_dir).mkdir(parents=True, exist_ok=True)
  67. # ===== 自定义工具 =====
  68. @tool(description="示例工具:处理数据")
  69. async def process_data(
  70. data: str,
  71. operation: str = "transform",
  72. ctx: ToolContext = None,
  73. ) -> ToolResult:
  74. """
  75. 处理数据的示例工具
  76. Args:
  77. data: 要处理的数据
  78. operation: 操作类型(transform/validate/analyze)
  79. ctx: 工具上下文
  80. """
  81. try:
  82. logger.info(f"处理数据: operation={operation}, data_length={len(data)}")
  83. # 你的业务逻辑
  84. if operation == "transform":
  85. result = data.upper()
  86. elif operation == "validate":
  87. result = f"数据有效: {len(data)} 字符"
  88. elif operation == "analyze":
  89. result = f"分析结果: 包含 {len(data.split())} 个单词"
  90. else:
  91. return ToolResult(
  92. title="操作失败",
  93. output=f"不支持的操作: {operation}",
  94. error=True,
  95. )
  96. return ToolResult(
  97. title="处理成功",
  98. output=result,
  99. data={"operation": operation, "result": result},
  100. )
  101. except Exception as e:
  102. logger.error(f"处理数据失败: {e}", exc_info=True)
  103. return ToolResult(
  104. title="处理失败",
  105. output=str(e),
  106. error=True,
  107. )
  108. @tool(description="示例工具:查询外部 API")
  109. async def query_api(
  110. endpoint: str,
  111. params: Optional[Dict[str, Any]] = None,
  112. ctx: ToolContext = None,
  113. ) -> ToolResult:
  114. """
  115. 查询外部 API 的示例工具
  116. Args:
  117. endpoint: API 端点
  118. params: 查询参数
  119. ctx: 工具上下文
  120. """
  121. try:
  122. logger.info(f"查询 API: endpoint={endpoint}, params={params}")
  123. # 模拟 API 调用
  124. # 实际使用时替换为真实的 API 调用
  125. await asyncio.sleep(0.5)
  126. result = {
  127. "endpoint": endpoint,
  128. "params": params,
  129. "data": {"status": "success", "message": "API 调用成功"},
  130. }
  131. return ToolResult(
  132. title="API 查询成功",
  133. output=f"成功查询 {endpoint}",
  134. data=result,
  135. )
  136. except Exception as e:
  137. logger.error(f"API 查询失败: {e}", exc_info=True)
  138. return ToolResult(
  139. title="API 查询失败",
  140. output=str(e),
  141. error=True,
  142. )
  143. # ===== Agent 类 =====
  144. class ProductionAgent:
  145. """生产级 Agent"""
  146. def __init__(self, config: AgentConfig):
  147. self.config = config
  148. self.runner = self._create_runner()
  149. def _create_runner(self) -> AgentRunner:
  150. """创建 AgentRunner"""
  151. # 初始化存储
  152. trace_store = FileSystemTraceStore(base_path=self.config.trace_dir)
  153. # 初始化 LLM
  154. llm_call = create_openrouter_llm_call(
  155. model=self.config.model,
  156. api_key=self.config.api_key,
  157. )
  158. # 创建 Runner
  159. runner = AgentRunner(
  160. llm_call=llm_call,
  161. trace_store=trace_store,
  162. skills_dir=self.config.skills_dir if Path(self.config.skills_dir).exists() else None,
  163. )
  164. logger.info(f"AgentRunner 已创建: model={self.config.model}")
  165. return runner
  166. async def run(
  167. self,
  168. task: str,
  169. trace_id: Optional[str] = None,
  170. ) -> Dict[str, Any]:
  171. """
  172. 运行 Agent
  173. Args:
  174. task: 任务描述
  175. trace_id: Trace ID(用于续跑)
  176. Returns:
  177. 执行结果
  178. """
  179. logger.info(f"开始执行任务: {task[:100]}...")
  180. # 构建运行配置
  181. run_config = RunConfig(
  182. model=self.config.model,
  183. temperature=self.config.temperature,
  184. max_iterations=self.config.max_iterations,
  185. trace_id=trace_id,
  186. skills=self.config.enabled_skills,
  187. )
  188. # 执行 Agent
  189. final_response = None
  190. current_trace_id = None
  191. messages_count = 0
  192. try:
  193. async for item in self.runner.run(
  194. messages=[{"role": "user", "content": task}],
  195. config=run_config,
  196. ):
  197. if isinstance(item, Trace):
  198. current_trace_id = item.trace_id
  199. logger.info(f"Trace ID: {current_trace_id}")
  200. elif isinstance(item, Message):
  201. messages_count += 1
  202. if item.role == "assistant" and item.content:
  203. final_response = item.content
  204. logger.info(f"收到 Assistant 消息 #{messages_count}")
  205. logger.info(f"任务执行完成: trace_id={current_trace_id}, messages={messages_count}")
  206. # 保存结果
  207. result = {
  208. "trace_id": current_trace_id,
  209. "task": task,
  210. "response": final_response,
  211. "messages_count": messages_count,
  212. "timestamp": datetime.now().isoformat(),
  213. }
  214. self._save_result(result)
  215. return result
  216. except Exception as e:
  217. logger.error(f"任务执行失败: {e}", exc_info=True)
  218. return {
  219. "error": str(e),
  220. "trace_id": current_trace_id,
  221. "task": task,
  222. }
  223. def _save_result(self, result: Dict[str, Any]):
  224. """保存执行结果"""
  225. output_file = Path(self.config.output_dir) / f"{result['trace_id']}.txt"
  226. with open(output_file, 'w', encoding='utf-8') as f:
  227. f.write(f"Trace ID: {result['trace_id']}\n")
  228. f.write(f"时间: {result['timestamp']}\n")
  229. f.write(f"任务: {result['task']}\n")
  230. f.write(f"\n{'='*60}\n\n")
  231. f.write(f"结果:\n{result['response']}\n")
  232. logger.info(f"结果已保存: {output_file}")
  233. # ===== 主函数 =====
  234. async def main():
  235. """主函数"""
  236. try:
  237. # 加载配置
  238. config = AgentConfig()
  239. # 创建 Agent
  240. agent = ProductionAgent(config)
  241. # 执行任务
  242. task = """
  243. 请帮我完成以下任务:
  244. 1. 使用 process_data 工具处理文本 "hello world"
  245. 2. 使用 query_api 工具查询 /api/users 端点
  246. 3. 总结执行结果
  247. """
  248. result = await agent.run(task)
  249. # 输出结果
  250. print("\n" + "="*60)
  251. print("执行结果:")
  252. print("="*60)
  253. print(f"Trace ID: {result.get('trace_id')}")
  254. print(f"消息数: {result.get('messages_count')}")
  255. print("\n响应:")
  256. print(result.get('response', result.get('error')))
  257. print("="*60)
  258. except Exception as e:
  259. logger.error(f"程序执行失败: {e}", exc_info=True)
  260. sys.exit(1)
  261. if __name__ == "__main__":
  262. asyncio.run(main())