""" 人群定向工具 — 定向策略与人群包管理 """ import logging from typing import Any, Dict, List, Optional from agent.tools import tool from agent.tools.models import ToolResult from examples.auto_put_ad.tools.ad_api import _post, _check, DEFAULT_ACCOUNT_ID logger = logging.getLogger(__name__) # 本业务常用的年龄区间定向配置 AGE_TARGETING_PRESETS = { "18-24": [{"min": 18, "max": 24}], "25-30": [{"min": 25, "max": 30}], "25-35": [{"min": 25, "max": 35}], "31-40": [{"min": 31, "max": 40}], "35-45": [{"min": 35, "max": 45}], "18-35": [{"min": 18, "max": 35}], "25-45": [{"min": 25, "max": 35}, {"min": 35, "max": 45}], } @tool(description="生成定向设置(targeting结构体),根据人群包和年龄段组合生成可直接传入ad_create的targeting参数") async def audience_build_targeting( custom_audience_ids: Optional[List[int]] = None, excluded_audience_ids: Optional[List[int]] = None, age_preset: Optional[str] = None, age_ranges: Optional[List[Dict[str, int]]] = None, gender: Optional[str] = None, geo_regions: Optional[List[int]] = None, user_os: Optional[List[str]] = None, ) -> ToolResult: """生成腾讯广告 targeting 结构体,直接用于 ad_create 的 targeting 参数。 Args: custom_audience_ids: 自有人群包ID列表(定向投放给这些人群) excluded_audience_ids: 排除人群包ID列表(不投给这些人) age_preset: 预设年龄区间名称,可选: "18-24", "25-30", "25-35", "31-40", "35-45", "18-35", "25-45" age_ranges: 自定义年龄区间,如 [{"min": 25, "max": 35}] (age_preset 和 age_ranges 二选一,age_ranges 优先) gender: 性别定向 "MALE" / "FEMALE",不传则不限 geo_regions: 地域ID列表(省市区县,不传则全国) user_os: 操作系统 ["IOS"] / ["ANDROID"] / ["IOS", "ANDROID"],不传则不限 Returns: targeting 字典,可直接传给 ad_create(targeting=...) """ targeting: Dict[str, Any] = {} if custom_audience_ids: targeting["custom_audience"] = custom_audience_ids if excluded_audience_ids: targeting["excluded_custom_audience"] = excluded_audience_ids # 年龄定向 if age_ranges: targeting["age"] = age_ranges elif age_preset: if age_preset not in AGE_TARGETING_PRESETS: return ToolResult( title="audience_build_targeting 失败", output=f"不支持的 age_preset: {age_preset},可选:{list(AGE_TARGETING_PRESETS.keys())}" ) targeting["age"] = AGE_TARGETING_PRESETS[age_preset] if gender: targeting["gender"] = gender if geo_regions: targeting["geo_location"] = {"regions": geo_regions} if user_os: targeting["user_os"] = user_os if not targeting: return ToolResult(title="audience_build_targeting", output="警告:targeting 为空(宽泛定向),将投放给全量用户") # 生成可读描述 desc_parts = [] if custom_audience_ids: desc_parts.append(f"人群包: {custom_audience_ids}") if excluded_audience_ids: desc_parts.append(f"排除: {excluded_audience_ids}") if targeting.get("age"): ages = targeting["age"] desc_parts.append(f"年龄: {'-'.join(str(a.get('min', '')) + '~' + str(a.get('max', '')) for a in ages)}") if gender: desc_parts.append(f"性别: {'男' if gender == 'MALE' else '女'}") if geo_regions: desc_parts.append(f"地域: {len(geo_regions)}个地区") if user_os: desc_parts.append(f"系统: {'/'.join(user_os)}") return ToolResult( title="定向设置已生成", output="定向参数:" + ",".join(desc_parts), metadata={"targeting": targeting}, ) @tool(description="查询可用人群包并推荐最优定向组合(基于历史效果数据)") async def audience_recommend_targeting( optimization_goal: str = "OPTIMIZATIONGOAL_PAGE_VIEW", account_id: int = 0, ) -> ToolResult: """根据优化目标,推荐效果最好的人群包和定向组合方案。 Args: optimization_goal: 优化目标,影响推荐策略 OPTIMIZATIONGOAL_PAGE_VIEW(关键页面访问) OPTIMIZATIONGOAL_CLICK(点击) account_id: 广告主账号ID """ from examples.auto_put_ad.tools.ad_api import audience_get_list # 查询账户下所有可用人群包 result = await audience_get_list(account_id=account_id) if "失败" in result.title: return result audiences = (result.metadata or {}).get("list", []) if not audiences: return ToolResult( title="人群推荐", output="账户下暂无自定义人群包,建议先上传人群包后再进行精准定向。\n" "过渡期可使用宽泛定向(仅年龄+地域),让系统自动探索最优人群。" ) # 按人数排序,推荐人数较大的人群包 audiences_sorted = sorted(audiences, key=lambda x: x.get("user_count", 0), reverse=True) recommendations = [] for ag in audiences_sorted[:5]: recommendations.append( f"- [{ag['audience_id']}] {ag.get('name', '未命名')} " f"| 覆盖人数: {ag.get('user_count', 0):,}" ) age_presets = ["25-35", "18-35"] if optimization_goal == "OPTIMIZATIONGOAL_CLICK" else ["25-45", "31-40"] output = ( f"推荐定向方案(优化目标: {optimization_goal}):\n\n" f"📦 推荐人群包(按覆盖量排序):\n" + "\n".join(recommendations) + "\n\n" f"📅 推荐年龄区间:{' / '.join(age_presets)}\n\n" f"💡 建议:先用 1-2 个大人群包测试,确认效果后再细分" ) return ToolResult(title="人群定向推荐", output=output)