audience_tools.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. """
  2. 人群定向工具 — 定向策略与人群包管理
  3. """
  4. import logging
  5. from typing import Any, Dict, List, Optional
  6. from agent.tools import tool
  7. from agent.tools.models import ToolResult
  8. from examples.auto_put_ad.tools.ad_api import _post, _check, DEFAULT_ACCOUNT_ID
  9. logger = logging.getLogger(__name__)
  10. # 本业务常用的年龄区间定向配置
  11. AGE_TARGETING_PRESETS = {
  12. "18-24": [{"min": 18, "max": 24}],
  13. "25-30": [{"min": 25, "max": 30}],
  14. "25-35": [{"min": 25, "max": 35}],
  15. "31-40": [{"min": 31, "max": 40}],
  16. "35-45": [{"min": 35, "max": 45}],
  17. "18-35": [{"min": 18, "max": 35}],
  18. "25-45": [{"min": 25, "max": 35}, {"min": 35, "max": 45}],
  19. }
  20. @tool(description="生成定向设置(targeting结构体),根据人群包和年龄段组合生成可直接传入ad_create的targeting参数")
  21. async def audience_build_targeting(
  22. custom_audience_ids: Optional[List[int]] = None,
  23. excluded_audience_ids: Optional[List[int]] = None,
  24. age_preset: Optional[str] = None,
  25. age_ranges: Optional[List[Dict[str, int]]] = None,
  26. gender: Optional[str] = None,
  27. geo_regions: Optional[List[int]] = None,
  28. user_os: Optional[List[str]] = None,
  29. ) -> ToolResult:
  30. """生成腾讯广告 targeting 结构体,直接用于 ad_create 的 targeting 参数。
  31. Args:
  32. custom_audience_ids: 自有人群包ID列表(定向投放给这些人群)
  33. excluded_audience_ids: 排除人群包ID列表(不投给这些人)
  34. age_preset: 预设年龄区间名称,可选:
  35. "18-24", "25-30", "25-35", "31-40", "35-45", "18-35", "25-45"
  36. age_ranges: 自定义年龄区间,如 [{"min": 25, "max": 35}]
  37. (age_preset 和 age_ranges 二选一,age_ranges 优先)
  38. gender: 性别定向 "MALE" / "FEMALE",不传则不限
  39. geo_regions: 地域ID列表(省市区县,不传则全国)
  40. user_os: 操作系统 ["IOS"] / ["ANDROID"] / ["IOS", "ANDROID"],不传则不限
  41. Returns:
  42. targeting 字典,可直接传给 ad_create(targeting=...)
  43. """
  44. targeting: Dict[str, Any] = {}
  45. if custom_audience_ids:
  46. targeting["custom_audience"] = custom_audience_ids
  47. if excluded_audience_ids:
  48. targeting["excluded_custom_audience"] = excluded_audience_ids
  49. # 年龄定向
  50. if age_ranges:
  51. targeting["age"] = age_ranges
  52. elif age_preset:
  53. if age_preset not in AGE_TARGETING_PRESETS:
  54. return ToolResult(
  55. title="audience_build_targeting 失败",
  56. output=f"不支持的 age_preset: {age_preset},可选:{list(AGE_TARGETING_PRESETS.keys())}"
  57. )
  58. targeting["age"] = AGE_TARGETING_PRESETS[age_preset]
  59. if gender:
  60. targeting["gender"] = gender
  61. if geo_regions:
  62. targeting["geo_location"] = {"regions": geo_regions}
  63. if user_os:
  64. targeting["user_os"] = user_os
  65. if not targeting:
  66. return ToolResult(title="audience_build_targeting", output="警告:targeting 为空(宽泛定向),将投放给全量用户")
  67. # 生成可读描述
  68. desc_parts = []
  69. if custom_audience_ids:
  70. desc_parts.append(f"人群包: {custom_audience_ids}")
  71. if excluded_audience_ids:
  72. desc_parts.append(f"排除: {excluded_audience_ids}")
  73. if targeting.get("age"):
  74. ages = targeting["age"]
  75. desc_parts.append(f"年龄: {'-'.join(str(a.get('min', '')) + '~' + str(a.get('max', '')) for a in ages)}")
  76. if gender:
  77. desc_parts.append(f"性别: {'男' if gender == 'MALE' else '女'}")
  78. if geo_regions:
  79. desc_parts.append(f"地域: {len(geo_regions)}个地区")
  80. if user_os:
  81. desc_parts.append(f"系统: {'/'.join(user_os)}")
  82. return ToolResult(
  83. title="定向设置已生成",
  84. output="定向参数:" + ",".join(desc_parts),
  85. metadata={"targeting": targeting},
  86. )
  87. @tool(description="查询可用人群包并推荐最优定向组合(基于历史效果数据)")
  88. async def audience_recommend_targeting(
  89. optimization_goal: str = "OPTIMIZATIONGOAL_PAGE_VIEW",
  90. account_id: int = 0,
  91. ) -> ToolResult:
  92. """根据优化目标,推荐效果最好的人群包和定向组合方案。
  93. Args:
  94. optimization_goal: 优化目标,影响推荐策略
  95. OPTIMIZATIONGOAL_PAGE_VIEW(关键页面访问)
  96. OPTIMIZATIONGOAL_CLICK(点击)
  97. account_id: 广告主账号ID
  98. """
  99. from examples.auto_put_ad.tools.ad_api import audience_get_list
  100. # 查询账户下所有可用人群包
  101. result = await audience_get_list(account_id=account_id)
  102. if "失败" in result.title:
  103. return result
  104. audiences = (result.metadata or {}).get("list", [])
  105. if not audiences:
  106. return ToolResult(
  107. title="人群推荐",
  108. output="账户下暂无自定义人群包,建议先上传人群包后再进行精准定向。\n"
  109. "过渡期可使用宽泛定向(仅年龄+地域),让系统自动探索最优人群。"
  110. )
  111. # 按人数排序,推荐人数较大的人群包
  112. audiences_sorted = sorted(audiences, key=lambda x: x.get("user_count", 0), reverse=True)
  113. recommendations = []
  114. for ag in audiences_sorted[:5]:
  115. recommendations.append(
  116. f"- [{ag['audience_id']}] {ag.get('name', '未命名')} "
  117. f"| 覆盖人数: {ag.get('user_count', 0):,}"
  118. )
  119. age_presets = ["25-35", "18-35"] if optimization_goal == "OPTIMIZATIONGOAL_CLICK" else ["25-45", "31-40"]
  120. output = (
  121. f"推荐定向方案(优化目标: {optimization_goal}):\n\n"
  122. f"📦 推荐人群包(按覆盖量排序):\n" + "\n".join(recommendations) + "\n\n"
  123. f"📅 推荐年龄区间:{' / '.join(age_presets)}\n\n"
  124. f"💡 建议:先用 1-2 个大人群包测试,确认效果后再细分"
  125. )
  126. return ToolResult(title="人群定向推荐", output=output)