run.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. """
  2. 内容寻找 Agent - MVP 版本
  3. 功能:
  4. - 根据用户需求智能搜索抖音视频内容
  5. - 基于画像数据筛选符合老年人群体的内容
  6. - 深度挖掘优质作者的其他作品
  7. 使用示例:
  8. python run.py
  9. """
  10. import asyncio
  11. import logging
  12. import sys
  13. import os
  14. from pathlib import Path
  15. from typing import Optional, Dict, Any
  16. from datetime import datetime
  17. # 添加项目根目录到 Python 路径
  18. sys.path.insert(0, str(Path(__file__).parent.parent.parent))
  19. from dotenv import load_dotenv
  20. load_dotenv()
  21. from agent import (
  22. AgentRunner,
  23. RunConfig,
  24. FileSystemTraceStore,
  25. Trace,
  26. Message,
  27. )
  28. from agent.llm import create_openrouter_llm_call
  29. # 导入工具(确保工具被注册)
  30. from tools import (
  31. douyin_search,
  32. douyin_user_videos,
  33. get_video_audience_profile,
  34. get_user_fans_profile,
  35. )
  36. # 配置日志
  37. log_dir = Path(__file__).parent / '.cache'
  38. log_dir.mkdir(exist_ok=True)
  39. logging.basicConfig(
  40. level=logging.INFO,
  41. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
  42. handlers=[
  43. logging.FileHandler(log_dir / 'agent.log'),
  44. logging.StreamHandler()
  45. ]
  46. )
  47. logger = logging.getLogger(__name__)
  48. # ===== 配置管理 =====
  49. class ContentFinderConfig:
  50. """内容寻找 Agent 配置"""
  51. def __init__(self):
  52. # LLM 配置
  53. self.model = os.getenv("MODEL", "anthropic/claude-sonnet-4.5")
  54. self.api_key = os.getenv("OPEN_ROUTER_API_KEY")
  55. self.temperature = float(os.getenv("TEMPERATURE", "0.3"))
  56. self.max_iterations = int(os.getenv("MAX_ITERATIONS", "30"))
  57. # 存储配置
  58. self.trace_dir = os.getenv("TRACE_DIR", ".cache/traces")
  59. self.output_dir = os.getenv("OUTPUT_DIR", ".cache/output")
  60. # Skills 配置 - 使用绝对路径
  61. default_skills_dir = str(Path(__file__).parent / "skills")
  62. self.skills_dir = os.getenv("SKILLS_DIR", default_skills_dir)
  63. # 验证配置
  64. self._validate()
  65. def _validate(self):
  66. """验证配置"""
  67. if not self.api_key:
  68. raise ValueError("OPEN_ROUTER_API_KEY 未设置,请在 .env 文件中配置")
  69. # 创建必要的目录
  70. Path(self.trace_dir).mkdir(parents=True, exist_ok=True)
  71. Path(self.output_dir).mkdir(parents=True, exist_ok=True)
  72. Path('.cache').mkdir(exist_ok=True)
  73. # ===== Agent 封装 =====
  74. class ContentFinderAgent:
  75. """内容寻找 Agent"""
  76. def __init__(self, config: ContentFinderConfig):
  77. self.config = config
  78. # 初始化 Trace Store
  79. self.trace_store = FileSystemTraceStore(base_path=config.trace_dir)
  80. # 初始化 LLM
  81. self.llm_call = create_openrouter_llm_call(model=config.model)
  82. # 初始化 Runner
  83. self.runner = AgentRunner(
  84. llm_call=self.llm_call,
  85. trace_store=self.trace_store,
  86. skills_dir=config.skills_dir,
  87. )
  88. logger.info(f"ContentFinderAgent 初始化完成")
  89. logger.info(f" 模型: {config.model}")
  90. logger.info(f" Skills 目录: {config.skills_dir}")
  91. async def find_content(
  92. self,
  93. user_request: str,
  94. trace_id: Optional[str] = None,
  95. ) -> Dict[str, Any]:
  96. """
  97. 根据用户需求寻找内容
  98. Args:
  99. user_request: 用户需求描述
  100. trace_id: 可选的 trace_id,用于恢复之前的会话
  101. Returns:
  102. 包含执行结果的字典
  103. """
  104. logger.info(f"开始处理用户需求: {user_request}")
  105. # 构建系统提示
  106. system_prompt = """你是一个专业的内容寻找助手,擅长根据用户需求在抖音平台上寻找合适的视频内容。
  107. ⚠️ 重要提示:当前所有工具返回的都是模拟数据,仅用于测试和演示。
  108. - 在分析和推荐时,请明确说明这是基于模拟数据的演示
  109. - 不要给出过于自信的评分和推荐
  110. - 提醒用户实际使用时需要对接真实API
  111. 你的工作流程:
  112. 1. 理解用户需求,提取关键词和目标受众特征
  113. 2. 使用 douyin_search 工具搜索相关内容(返回模拟数据)
  114. 3. 使用画像工具分析内容是否符合目标受众(返回模拟数据)
  115. 4. 对于优质内容,使用 douyin_user_videos 获取作者的其他作品(返回模拟数据)
  116. 5. 综合评估并推荐最合适的内容,但要说明这是基于模拟数据的演示
  117. 重点关注:
  118. - 内容是否符合老年人群体的偏好(年龄分布、偏好度)
  119. - 内容热度(点赞量、评论量、分享量)
  120. - 互动率(评论率、分享率)
  121. - 时效性(发布时间)
  122. - 作者的内容质量和稳定性
  123. 请按照 content_finding_strategy 和 content_filtering_strategy 中的策略执行。"""
  124. # 构建配置
  125. config = RunConfig(
  126. model=self.config.model,
  127. temperature=self.config.temperature,
  128. max_iterations=self.config.max_iterations,
  129. agent_type="content_finder",
  130. trace_id=trace_id,
  131. )
  132. # 构建消息
  133. messages = [
  134. {"role": "system", "content": system_prompt},
  135. {"role": "user", "content": user_request},
  136. ]
  137. # 执行
  138. result = {
  139. "trace_id": None,
  140. "status": "unknown",
  141. "messages_count": 0,
  142. "response": "",
  143. "error": None,
  144. }
  145. try:
  146. last_assistant_message = None
  147. message_count = 0
  148. async for item in self.runner.run(messages=messages, config=config):
  149. if isinstance(item, Trace):
  150. result["trace_id"] = item.trace_id
  151. result["status"] = item.status
  152. logger.info(f"Trace 状态: {item.status}")
  153. elif isinstance(item, Message):
  154. message_count += 1
  155. if item.role == "assistant":
  156. last_assistant_message = item
  157. # 实时输出助手响应
  158. content = item.content
  159. if isinstance(content, str):
  160. print(f"\n{content}")
  161. elif isinstance(content, dict):
  162. text = content.get("text", "")
  163. if text:
  164. print(f"\n{text}")
  165. result["messages_count"] = message_count
  166. # 提取最后的助手响应
  167. if last_assistant_message:
  168. content = last_assistant_message.content
  169. if isinstance(content, str):
  170. result["response"] = content
  171. elif isinstance(content, dict):
  172. result["response"] = content.get("text", "")
  173. logger.info(f"执行完成,共 {message_count} 条消息")
  174. except Exception as e:
  175. logger.error(f"执行失败: {e}", exc_info=True)
  176. result["error"] = str(e)
  177. result["status"] = "failed"
  178. return result
  179. # ===== 主函数 =====
  180. async def main():
  181. """主函数"""
  182. try:
  183. # 加载配置
  184. config = ContentFinderConfig()
  185. # 创建 Agent
  186. agent = ContentFinderAgent(config)
  187. # 用户需求
  188. user_request = """
  189. 孩子军抗日,让人感动。找这样的视频。
  190. 要求:
  191. - 内容要感人,有情感共鸣
  192. - 适合老年人观看
  193. - 热度要高,质量要好
  194. """
  195. print("\n" + "="*60)
  196. print("内容寻找 Agent - MVP 版本")
  197. print("="*60)
  198. print(f"\n用户需求:\n{user_request}")
  199. print("\n" + "="*60)
  200. print("开始执行...\n")
  201. # 执行任务
  202. result = await agent.find_content(user_request)
  203. # 输出结果摘要
  204. print("\n" + "="*60)
  205. print("执行结果摘要")
  206. print("="*60)
  207. print(f"Trace ID: {result.get('trace_id')}")
  208. print(f"状态: {result.get('status')}")
  209. print(f"消息数: {result.get('messages_count')}")
  210. if result.get('error'):
  211. print(f"错误: {result.get('error')}")
  212. print("="*60 + "\n")
  213. except Exception as e:
  214. logger.error(f"程序执行失败: {e}", exc_info=True)
  215. sys.exit(1)
  216. if __name__ == "__main__":
  217. asyncio.run(main())