瀏覽代碼

Merge branch 'feature_content_finder_agent_v2' of https://git.yishihui.com/howard/Agent into feature_content_finder_agent_v2

zhangliang 1 天之前
父節點
當前提交
5415d06f9b

+ 2 - 1
examples/content_finder/content_finder.md

@@ -15,6 +15,7 @@ $system$
 5. **`think_and_plan` 用于结构化记录**:`think_and_plan` 仍然用于记录计划和关键节点,但它不能替代你在对话中直接输出的思考文本。两者互补,缺一不可。
 
 ## 可用工具(按目的)
+- 获取高赞视频的选题点: `get_video_topic`
 - 抖音视频搜索:`douyin_search`
 - 抖音作者作品搜索:`douyin_user_videos`
 - 数据库作者检索(按搜索词找历史优质作者):`find_authors_from_db`
@@ -36,7 +37,7 @@ $system$
 - 核心指标:分享率、DAU
 
 ## 执行流程(按顺序,禁止跳步)
-1. **需求理解阶段**: 按 `demand_analysis` 执行
+1. **需求理解阶段**: 按 `demand_analysis` 执行,输出的内容用于后续的流程
 1. **内容寻找和筛选阶段**:按 `content_finding_strategy` 执行
 2. **筛选阶段**:按 `content_filtering_strategy` 执行,并且将 `demand_analysis` 的结果(判别目标/关键点/形式规则)用于“需求对齐打分”和淘汰理由生成
 3. **优质账号扩展**: 对于筛选阶段获取到用户画像的优质作者,按`high_quality_analysis`执行

+ 2 - 0
examples/content_finder/core.py

@@ -53,6 +53,7 @@ from tools import (
     store_results_mysql,
     think_and_plan,
     find_authors_from_db,
+    get_video_topic,
 )
 
 logger = logging.getLogger(__name__)
@@ -143,6 +144,7 @@ async def run_agent(
         "create_crawler_plan_by_douyin_content_id",
         "create_crawler_plan_by_douyin_account_id",
         "think_and_plan",
+        "get_video_topic",
     ]
 
     runner = AgentRunner(

+ 9 - 14
examples/content_finder/skills/content_finding_strategy.md

@@ -9,34 +9,30 @@ description: 内容搜索方法论
 
 ---
 
-## 第一步:需求分析与关键词提取
+## 第一步:关键词提取
 
-- 从用户需求中提取核心关键词和扩展关键词,优先使用用户原话
-- 按相关性排序:用户明确说的 > 用户暗示的 > 推测的
+- 从`需求分析`中提取若干搜索词。
 - 确定目标数量 **M**(如"找10条",则 M = 10)
 
 ---
 
 ## 第二步:串行关键词搜索
 
+### 优先:抖音搜索
+**搜索词限制**: 仅搜索第一步中输出的搜索词,严谨联想或者扩展其他词搜索。
 **数量控制**:只搜索 **N = M × 2** 条,搜到后立即停止,不超出此限制。
+**数据读取规则**:
+- 搜索结果从 `metadata.search_results` 获取,**不要解析工具的 output 文本**
+- 账号作品从 `metadata.user_videos` 获取
+- 数据库作者从 `find_authors_from_db` 的 `metadata.authors` 获取(优先使用其中的 `author_sec_uid`)
+**分页策略**:第一次使用默认 cursor(`"0"` 或 `""`),需要更多时使用返回的 cursor 继续获取。
 
 ### 备选:历史优质作者扩展(备选策略)
-
 当关键词搜索结果质量不稳定、或需要更贴近目标人群的内容时,可走“作者→作品”的扩展路径:
-
 - 先调用 `find_authors_from_db(query)`:从数据库历史沉淀中按搜索词找到相关优质作者(返回 `author_sec_uid`)
 - 再对 Top 作者调用 `douyin_user_videos(account_id=author_sec_uid)` 拉作品,作为候选池补充
-
 **仍需遵守数量控制**:作者扩展拿到的作品也计入候选数量,总量不要超过 **N = M × 2**。
 
-**数据读取规则**:
-- 搜索结果从 `metadata.search_results` 获取,**不要解析工具的 output 文本**
-- 账号作品从 `metadata.user_videos` 获取
-- 数据库作者从 `find_authors_from_db` 的 `metadata.authors` 获取(优先使用其中的 `author_sec_uid`)
-
-**分页策略**:第一次使用默认 cursor(`"0"` 或 `""`),需要更多时使用返回的 cursor 继续获取。
-
 ---
 
 ## 第三步:数据真实性规范(严格遵守)
@@ -45,7 +41,6 @@ description: 内容搜索方法论
 
 ### 字段完整性要求
 - `author.sec_uid`:约 80 字符,必须**逐字符完整复制**,不能截断或修改
-  - 格式校验:必须以 `MS4wLjABAAAA` 开头,后跟约 68 个字符
 - `aweme_id`、作者名、热度数据必须来自**同一条记录**,不能混用
 
 ### 正确做法

+ 6 - 4
examples/content_finder/skills/demand_analysis.md

@@ -35,13 +35,13 @@ description: 需求分析
 
 ---
 
-## 二、双起点策略(case出发 / 特征出发)
+## 二、双起点策略(高赞case出发 / 特征出发)
 
-### A. case出发(优先用于下层特征)
+### A. 高赞case出发(优先用于下层特征)
 
-适用:需求里已有具象表达,或需要从案例中补全搜索词。  
+适用:需求里已有具象表达,或需要从案例中补全/完善搜索词。  
 动作:
-1. 调用**查看当前输入特征关联的 goodcase 视频选题内容**工具
+1. 调用**`get_video_topic`**工具
 2. 将工具返回的选题点按用途拆分:
    - `灵感点` -> 用于构建**搜索词包**(写入寻找清单的候选词)
    - `目的点` -> 用于构建**判别目标**(写入判别清单的“该对齐什么”)
@@ -50,6 +50,8 @@ description: 需求分析
    - `寻找清单_case`:由 `灵感点` 扩展出的即时搜索词(允许 3-5 个同义/上下位词)
    - `判别清单_case`:由 `目的点` + `关键点` 形成的打分点与淘汰条件草案
 
+注意:高赞视频仅用于根据选题点扩展/判别,不能作为输出
+
 ### B. 特征出发(优先用于上层特征)
 
 适用:需求偏抽象,先建立主题覆盖框架。  

+ 2 - 0
examples/content_finder/tools/__init__.py

@@ -9,6 +9,7 @@ from .store_results_mysql import store_results_mysql
 from .aigc_platform_api import create_crawler_plan_by_douyin_content_id, create_crawler_plan_by_douyin_account_id
 from .think_and_plan import think_and_plan
 from .find_authors_from_db import find_authors_from_db
+from .get_video_topic import get_video_topic
 
 __all__ = [
     "douyin_search",
@@ -20,4 +21,5 @@ __all__ = [
     "create_crawler_plan_by_douyin_account_id",
     "think_and_plan",
     "find_authors_from_db",
+    "get_video_topic",
 ]

+ 25 - 0
examples/content_finder/tools/aigc_platform_api.py

@@ -16,6 +16,8 @@ from db import update_content_plan_ids
 
 logger = logging.getLogger(__name__)
 
+USE_REAL_API = False
+
 AIGC_BASE_URL = "https://aigc-api.aiddit.com"
 CRAWLER_PLAN_CREATE_URL = f"{AIGC_BASE_URL}/aigc/crawler/plan/save"
 GET_PRODUCE_PLAN_DETAIL_BY_ID = f"{AIGC_BASE_URL}/aigc/produce/plan/detail"
@@ -230,6 +232,29 @@ async def create_crawler_plan_by_douyin_content_id(
     Note:
         - 建议从 metadata.result 获取结构化数据,而非解析 output 文本
     """
+    # 先临时返回创建成功,不要真实创建
+    if USE_REAL_API == False:
+        return ToolResult(
+            title="根据抖音内容创建爬取计划",
+            output="",
+            metadata={
+                "result": {
+                    "crawler_info": {
+                        "crawler_plan_id": "1234567890",
+                        "crawler_plan_name": "抖音视频直接抓取",
+                    },
+                    "produce_plan_infos": [
+                        {
+                            "produce_plan_id": "1234567890",
+                            "produce_plan_name": "抖音视频直接抓取",
+                            "is_success": "绑定成功",
+                            "msg": "成功",
+                        }
+                    ]
+                }
+            },
+            long_term_memory="Create crawler plan by DouYin Content IDs",
+        )
     if not trace_id or not isinstance(trace_id, str):
         logger.error(f"create_crawler_plan_by_douyin_content_id invalid trace_id: {trace_id}")
         return ToolResult(

+ 73 - 0
examples/content_finder/tools/get_video_topic.py

@@ -0,0 +1,73 @@
+"""
+根据特征匹配高赞视频的选题解构信息(占位版)。
+
+当前阶段没有真实接口:先把“工具签名 + 返回结构”固定,内部临时返回空列表。
+后续接入数据源时,只需要填充 metadata.videos 的内容,不改调用方。
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional
+
+from agent.tools import ToolResult, tool
+
+JsonDict = Dict[str, Any]
+
+
+@dataclass(frozen=True)
+class VideoTopicItem:
+    """
+    单条视频的选题点结构(仅保留三类列表)。
+
+    - inspiration_points: 灵感点列表
+    - goal_points: 目的点列表
+    - key_points: 关键点列表
+    """
+
+    inspiration_points: List[str]
+    goal_points: List[str]
+    key_points: List[str]
+
+    def to_dict(self) -> JsonDict:
+        return {
+            "inspiration_points": self.inspiration_points,
+            "goal_points": self.goal_points,
+            "key_points": self.key_points,
+        }
+
+
+def _empty_videos() -> List[JsonDict]:
+    # 约定:返回“视频列表”,但当前无接口先返回空。
+    return []
+
+
+@tool(description="根据特征匹配高赞视频,并返回每个视频的灵感点/目的点/关键点列表(当前占位返回空)")
+async def get_video_topic(
+    features: Optional[List[str]] = None,
+    limit: int = 20,
+) -> ToolResult:
+    """
+    Args:
+        features: 特征/关键词列表(可空)
+        limit: 期望返回的最大视频数(当前占位实现不使用)
+
+    Returns:
+        ToolResult:
+          - metadata.videos: List[{
+              "inspiration_points": [...],
+              "goal_points": [...],
+              "key_points": [...]
+            }]
+    """
+
+    _ = features
+    _ = limit
+
+    videos = _empty_videos()
+    return ToolResult(
+        title="选题解构(占位)",
+        output=f"当前无可用接口,临时返回空视频列表(videos=0)。",
+        metadata={"videos": videos, "features": features or [], "limit": limit},
+        long_term_memory="Get video topic decomposition (placeholder, empty result).",
+    )