hotspot.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. """
  2. 热点宝工具
  3. 提供抖音视频画像数据分析功能。
  4. """
  5. import json
  6. from typing import Optional, Dict, Any
  7. import httpx
  8. from agent.tools import tool, ToolResult
  9. # API 基础配置
  10. HOTSPOT_API = "http://your-hotspot-api.com" # 替换为实际API地址
  11. DEFAULT_TIMEOUT = 60.0
  12. @tool(
  13. display={
  14. "zh": {
  15. "name": "获取视频画像数据",
  16. "params": {
  17. "video_url": "抖音视频链接",
  18. "platform_video_id": "平台视频ID"
  19. }
  20. }
  21. }
  22. )
  23. async def hotspot_get_video_profile(
  24. video_url: Optional[str] = None,
  25. platform_video_id: Optional[str] = None,
  26. ) -> ToolResult:
  27. """
  28. 获取抖音视频的受众画像数据
  29. Args:
  30. video_url: 抖音视频链接(二选一)
  31. platform_video_id: 平台视频ID(二选一)
  32. Returns:
  33. ToolResult 包含画像数据:
  34. {
  35. "code": 0,
  36. "message": "success",
  37. "data": {
  38. "video_id": "7123456789",
  39. "basic_stats": {
  40. "like_count": 12700,
  41. "comment_count": 856,
  42. "share_count": 432,
  43. "play_count": 125000
  44. },
  45. "audience_profile": {
  46. "age_distribution": {
  47. "18-24": 0.15,
  48. "25-34": 0.25,
  49. "35-44": 0.20,
  50. "45-54": 0.25,
  51. "55+": 0.15
  52. },
  53. "gender_distribution": {
  54. "male": 0.45,
  55. "female": 0.55
  56. },
  57. "region_distribution": {
  58. "一线城市": 0.30,
  59. "二线城市": 0.35,
  60. "三线及以下": 0.35
  61. },
  62. "device_distribution": {
  63. "ios": 0.40,
  64. "android": 0.60
  65. }
  66. },
  67. "engagement_metrics": {
  68. "avg_watch_time": 145,
  69. "completion_rate": 0.78,
  70. "interaction_rate": 0.12
  71. },
  72. "content_tags": ["历史", "教育", "感动", "正能量"],
  73. "emotional_analysis": {
  74. "primary_emotion": "感动",
  75. "emotion_scores": {
  76. "感动": 0.85,
  77. "敬佩": 0.70,
  78. "悲伤": 0.45
  79. }
  80. }
  81. }
  82. }
  83. """
  84. try:
  85. if not video_url and not platform_video_id:
  86. return ToolResult(
  87. title="参数错误",
  88. output="",
  89. error="必须提供 video_url 或 platform_video_id 之一"
  90. )
  91. url = f"{HOTSPOT_API}/api/video/profile"
  92. payload = {}
  93. if video_url:
  94. payload["video_url"] = video_url
  95. if platform_video_id:
  96. payload["platform_video_id"] = platform_video_id
  97. async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
  98. response = await client.post(
  99. url,
  100. json=payload,
  101. headers={"Content-Type": "application/json"},
  102. )
  103. response.raise_for_status()
  104. data = response.json()
  105. video_id = data.get("data", {}).get("video_id", "Unknown")
  106. return ToolResult(
  107. title=f"视频画像: {video_id}",
  108. output=json.dumps(data, ensure_ascii=False, indent=2),
  109. long_term_memory=f"Retrieved video profile for {video_id}"
  110. )
  111. except httpx.HTTPStatusError as e:
  112. return ToolResult(
  113. title="获取画像失败",
  114. output="",
  115. error=f"HTTP error {e.response.status_code}: {e.response.text}"
  116. )
  117. except Exception as e:
  118. return ToolResult(
  119. title="获取画像失败",
  120. output="",
  121. error=str(e)
  122. )
  123. @tool(
  124. display={
  125. "zh": {
  126. "name": "分析受众匹配度",
  127. "params": {
  128. "video_profile": "视频画像数据",
  129. "target_audience": "目标受众定义"
  130. }
  131. }
  132. }
  133. )
  134. async def hotspot_analyze_audience(
  135. video_profile: Dict[str, Any],
  136. target_audience: Dict[str, Any],
  137. ) -> ToolResult:
  138. """
  139. 分析视频受众与目标受众的匹配度
  140. Args:
  141. video_profile: 视频画像数据(从 hotspot_get_video_profile 获取)
  142. target_audience: 目标受众定义,例如:
  143. {
  144. "age_range": ["45-54", "55+"],
  145. "min_age_percentage": 0.30,
  146. "primary_emotion": "感动",
  147. "min_engagement_rate": 0.10
  148. }
  149. Returns:
  150. ToolResult 包含匹配度分析:
  151. {
  152. "code": 0,
  153. "message": "success",
  154. "data": {
  155. "match_score": 0.85,
  156. "match_level": "高度匹配",
  157. "details": {
  158. "age_match": {
  159. "score": 0.90,
  160. "target_percentage": 0.40,
  161. "actual_percentage": 0.40,
  162. "status": "符合"
  163. },
  164. "emotion_match": {
  165. "score": 0.85,
  166. "target_emotion": "感动",
  167. "actual_emotion": "感动",
  168. "emotion_score": 0.85,
  169. "status": "符合"
  170. },
  171. "engagement_match": {
  172. "score": 0.80,
  173. "target_rate": 0.10,
  174. "actual_rate": 0.12,
  175. "status": "符合"
  176. }
  177. },
  178. "recommendation": "该视频高度符合目标受众特征,建议引入"
  179. }
  180. }
  181. """
  182. try:
  183. # 这里实现匹配度计算逻辑
  184. # 实际项目中可以调用API或本地计算
  185. audience_data = video_profile.get("audience_profile", {})
  186. age_dist = audience_data.get("age_distribution", {})
  187. emotional_data = video_profile.get("emotional_analysis", {})
  188. engagement_data = video_profile.get("engagement_metrics", {})
  189. # 计算年龄匹配度
  190. target_age_ranges = target_audience.get("age_range", [])
  191. target_age_percentage = sum(age_dist.get(age, 0) for age in target_age_ranges)
  192. min_age_percentage = target_audience.get("min_age_percentage", 0.30)
  193. age_match = target_age_percentage >= min_age_percentage
  194. # 计算情感匹配度
  195. target_emotion = target_audience.get("primary_emotion", "")
  196. actual_emotion = emotional_data.get("primary_emotion", "")
  197. emotion_score = emotional_data.get("emotion_scores", {}).get(target_emotion, 0)
  198. emotion_match = actual_emotion == target_emotion or emotion_score >= 0.70
  199. # 计算互动匹配度
  200. actual_engagement = engagement_data.get("interaction_rate", 0)
  201. min_engagement = target_audience.get("min_engagement_rate", 0.10)
  202. engagement_match = actual_engagement >= min_engagement
  203. # 综合评分
  204. match_score = (
  205. (0.90 if age_match else 0.50) * 0.4 +
  206. (emotion_score if emotion_match else 0.50) * 0.4 +
  207. (0.80 if engagement_match else 0.50) * 0.2
  208. )
  209. match_level = "高度匹配" if match_score >= 0.80 else "中度匹配" if match_score >= 0.60 else "低度匹配"
  210. result = {
  211. "code": 0,
  212. "message": "success",
  213. "data": {
  214. "match_score": round(match_score, 2),
  215. "match_level": match_level,
  216. "details": {
  217. "age_match": {
  218. "score": 0.90 if age_match else 0.50,
  219. "target_percentage": min_age_percentage,
  220. "actual_percentage": round(target_age_percentage, 2),
  221. "status": "符合" if age_match else "不符合"
  222. },
  223. "emotion_match": {
  224. "score": round(emotion_score, 2),
  225. "target_emotion": target_emotion,
  226. "actual_emotion": actual_emotion,
  227. "emotion_score": round(emotion_score, 2),
  228. "status": "符合" if emotion_match else "不符合"
  229. },
  230. "engagement_match": {
  231. "score": 0.80 if engagement_match else 0.50,
  232. "target_rate": min_engagement,
  233. "actual_rate": round(actual_engagement, 2),
  234. "status": "符合" if engagement_match else "不符合"
  235. }
  236. },
  237. "recommendation": f"该视频{match_level},{'建议引入' if match_score >= 0.70 else '建议进一步评估'}"
  238. }
  239. }
  240. return ToolResult(
  241. title=f"受众匹配分析: {match_level}",
  242. output=json.dumps(result, ensure_ascii=False, indent=2),
  243. long_term_memory=f"Analyzed audience match, score: {match_score:.2f}"
  244. )
  245. except Exception as e:
  246. return ToolResult(
  247. title="分析失败",
  248. output="",
  249. error=str(e)
  250. )