zhangliang 1 день тому
батько
коміт
b89d754ea0

+ 1 - 0
examples/content_finder/.env.example

@@ -1,5 +1,6 @@
 # LLM 配置
 OPEN_ROUTER_API_KEY=your-api-key-here
+TIKHUB_API_KEY=hb8FH+kMgkuLlk7ORbWzzknwPRSSerhe3i7c4n+BW9m8mW6fI1CgVQi9CQ==
 MODEL=anthropic/claude-sonnet-4.5
 TEMPERATURE=0.3
 MAX_ITERATIONS=30

+ 21 - 1
examples/content_finder/README.md

@@ -29,6 +29,7 @@ content_finder/
 ├── tools/                         # 自定义工具
 │   ├── __init__.py
 │   ├── douyin_search.py           # 抖音关键词搜索
+│   ├── douyin_search_tikhub.py    # 抖音关键词搜索(Tikhub)
 │   ├── douyin_user_videos.py      # 账号作品列表
 │   └── hotspot_profile.py         # 热点宝画像数据
 ├── skills/                        # Agent 方法论(注入 System Prompt)
@@ -80,6 +81,7 @@ python examples/content_finder/server.py
 | 变量 | 默认值 | 说明 |
 |------|--------|------|
 | `OPEN_ROUTER_API_KEY` | 必填 | OpenRouter API Key |
+| `TIKHUB_API_KEY` | 必填(使用 `douyin_search_tikhub` 时) | Tikhub API Key |
 | `MODEL` | `anthropic/claude-sonnet-4.6` | 使用的模型 |
 | `TEMPERATURE` | `0.3` | 模型温度 |
 | `MAX_ITERATIONS` | `30` | Agent 最大迭代轮数 |
@@ -146,7 +148,7 @@ Authorization: Bearer {SCHEDULE_QUERY_API_KEY}
 
 ## 工具说明
 
-Agent 只允许调用以下 4 个工具,其他工具(包括浏览器工具)均被禁止:
+Agent 只允许调用以下 5 个工具,其他工具(包括浏览器工具)均被禁止:
 
 ### douyin_search
 
@@ -163,6 +165,24 @@ Agent 只允许调用以下 4 个工具,其他工具(包括浏览器工具
 
 结果通过 `metadata.search_results` 获取结构化数据。
 
+### douyin_search_tikhub
+
+通过关键词搜索抖音视频(Tikhub 接口)。
+
+`douyin_search` 失败后使用 `douyin_search_tikhub`,结果通过 `metadata.search_results` 获取。
+
+| 参数 | 必填 | 默认值 | 说明 |
+|------|------|--------|------|
+| `keyword` | ✅ | — | 搜索关键词 |
+| `cursor` | | `0` | 翻页游标(首次请求传 0,翻页时使用上次响应的 cursor) |
+| `sort_type` | | `0` | 排序方式:`0` 综合排序,`1` 最多点赞,`2` 最新发布 |
+| `publish_time` | | `0` | 发布时间筛选:`0` 不限,`1` 最近一天,`7` 最近一周,`180` 最近半年 |
+| `filter_duration` | | `0` | 视频时长筛选:`0` 不限,`0-1` 1 分钟以内,`1-5` 1-5 分钟,`5-10000` 5 分钟以上 |
+| `content_type` | | `0` | 内容类型筛选:`0` 不限,`1` 视频,`2` 图片,`3` 文章 |
+| `search_id` | | `""` | 搜索ID(分页时使用,从上一次响应获取) |
+| `backtrace` | | `""` | 翻页回溯标识(分页时使用,从上一次响应获取) |
+| `timeout` | | `60` | 超时秒数 |
+
 ### douyin_user_videos
 
 获取账号历史作品列表。

+ 3 - 1
examples/content_finder/content_finder.md

@@ -17,6 +17,7 @@ $system$
 ## 可用工具(按目的)
 - 获取高赞视频的选题点: `get_video_topic`
 - 抖音视频搜索:`douyin_search`
+- 抖音视频搜索(Tikhub):`douyin_search_tikhub`
 - 抖音作者作品搜索:`douyin_user_videos`
 - 数据库作者检索(按搜索词找历史优质作者):`find_authors_from_db`
 - 作品画像获取:`get_content_fans_portrait`
@@ -51,7 +52,8 @@ $system$
 ### 画像工具必须调用
 对每条候选内容,**必须**按以下顺序获取画像:
 1. 先调用 `get_content_fans_portrait`,检查 `metadata.has_portrait`。
-2. 若 `has_portrait=False`,如果是 `douyin_search` 获取到的视频,再调用 `get_account_fans_portrait` 兜底,如果是`douyin_user_videos`则不需要再次调用`get_account_fans_portrait`。
+2. 若 `has_portrait=False`,如果是 `douyin_search` 或 `douyin_search_tikhub` 获取到的视频,再调用 `get_account_fans_portrait` 兜底,如果是`douyin_user_videos`则不需要再次调用`get_account_fans_portrait`。
+补充:`douyin_search` 失败后再调用 `douyin_search_tikhub` 作为兜底。
 3. **不允许跳过画像获取直接输出**
 
 ### 输出字段必须严格遵循 Schema

+ 2 - 0
examples/content_finder/core.py

@@ -45,6 +45,7 @@ from agent.tools.builtin.knowledge import KnowledgeConfig
 # 导入工具(确保工具被注册)
 from tools import (
     douyin_search,
+    douyin_search_tikhub,
     douyin_user_videos,
     get_content_fans_portrait,
     get_account_fans_portrait,
@@ -136,6 +137,7 @@ async def run_agent(
 
     allowed_tools = [
         "douyin_search",
+        "douyin_search_tikhub",
         "douyin_user_videos",
         "get_content_fans_portrait",
         "get_account_fans_portrait",

+ 1 - 0
examples/content_finder/render_log_html.py

@@ -61,6 +61,7 @@ DEFAULT_COLLAPSE_KEYWORDS = ["调用参数", "返回内容"]
 TOOL_DESCRIPTION_MAP: dict[str, str] = {
     "think_and_plan": "系统化记录思考、计划与下一步行动(只记录不获取新信息)。",
     "douyin_search": "通过关键词在抖音上搜索视频内容。",
+    "douyin_search_tikhub": "通过关键词在抖音上搜索视频内容(Tikhub 接口)。",
     "douyin_user_videos": "通过账号/作者 sec_uid 获取其历史作品列表。",
     "get_content_fans_portrait": "获取视频点赞用户画像(热点宝),判断 metadata.has_portrait。",
     "get_account_fans_portrait": "获取作者粉丝画像(热点宝),用于内容画像缺失兜底。",

+ 1 - 0
examples/content_finder/server.py

@@ -230,6 +230,7 @@ async def create_task(request: TaskRequest):
 
                 allowed_tools = [
                     "douyin_search",
+                    "douyin_search_tikhub",
                     "douyin_user_videos",
                     "get_content_fans_portrait",
                     "get_account_fans_portrait",

+ 1 - 2
examples/content_finder/skills/content_filtering_strategy.md

@@ -28,7 +28,7 @@ description: 内容筛选方法论
 
 在未获取画像前,仅允许用以下字段做需求对齐判断:
 - `title`(若有)
-- `desc`(来自 `douyin_search` 的搜索结果)
+- `desc`(来自 `douyin_search` / `douyin_search_tikhub` 的搜索结果)
 - 或候选对象里可见的简介/摘要文本(若检索来源不同,请只用已有字段)
 
 ### 需求对齐判定规则(可直接执行)
@@ -132,4 +132,3 @@ description: 内容筛选方法论
 
 
 输出格式严格遵循 `output_schema` 中定义的 JSON Schema,禁止自创字段名或使用中文 key。
-

+ 1 - 0
examples/content_finder/skills/content_finding_strategy.md

@@ -26,6 +26,7 @@ description: 内容搜索方法论
 - 账号作品从 `metadata.user_videos` 获取
 - 数据库作者从 `find_authors_from_db` 的 `metadata.authors` 获取(优先使用其中的 `author_sec_uid`)
 **分页策略**:第一次使用默认 cursor(`"0"` 或 `""`),需要更多时使用返回的 cursor 继续获取。
+**兜底策略**:`douyin_search` 失败或无结果时,使用 `douyin_search_tikhub`。
 
 ### 备选:历史优质作者扩展(备选策略)
 当关键词搜索结果质量不稳定、或需要更贴近目标人群的内容时,可走“作者→作品”的扩展路径:

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

@@ -3,6 +3,7 @@
 """
 
 from .douyin_search import douyin_search
+from .douyin_search_tikhub import douyin_search_tikhub
 from .douyin_user_videos import douyin_user_videos
 from .hotspot_profile import get_content_fans_portrait, get_account_fans_portrait
 from .store_results_mysql import store_results_mysql
@@ -13,6 +14,7 @@ from .get_video_topic import get_video_topic
 
 __all__ = [
     "douyin_search",
+    "douyin_search_tikhub",
     "douyin_user_videos",
     "get_content_fans_portrait",
     "get_account_fans_portrait",

+ 14 - 4
examples/content_finder/tools/douyin_search_fallback.py → examples/content_finder/tools/douyin_search_tikhub.py

@@ -5,15 +5,20 @@
 """
 import asyncio
 import logging
+import os
 import time
+from pathlib import Path
 from typing import Optional
 
 import requests
 
 from agent.tools import tool, ToolResult
+from dotenv import load_dotenv
 
 logger = logging.getLogger(__name__)
 
+load_dotenv(dotenv_path=Path(__file__).resolve().parent.parent / ".env.example", override=False)
+
 
 # 解析工具:从 business_data 的单条记录中安全提取 aweme_info
 def _get_aweme_info(item: object) -> dict:
@@ -29,11 +34,12 @@ def _get_aweme_info(item: object) -> dict:
 # API 基础配置
 DOUYIN_SEARCH_API = "https://api.tikhub.io/api/v1/douyin/search/fetch_video_search_v2"
 DEFAULT_TIMEOUT = 60.0
+TIKHUB_API_KEY_ENV = "TIKHUB_API_KEY"
 
 
 
-@tool(description="通过关键词搜索抖音视频内容兜底接口")
-async def douyin_search(
+@tool(description="通过关键词搜索抖音视频内容接口")
+async def douyin_search_tikhub(
     keyword: str,
     content_type: str = "0",
     sort_type: str = "0",
@@ -99,6 +105,10 @@ async def douyin_search(
     start_time = time.time()
 
     try:
+        api_key = os.getenv(TIKHUB_API_KEY_ENV, "").strip()
+        if not api_key:
+            raise ValueError(f"missing {TIKHUB_API_KEY_ENV} in env")
+
         payload = {
             "keyword": keyword,
             "cursor": cursor,
@@ -116,7 +126,7 @@ async def douyin_search(
             DOUYIN_SEARCH_API,
             json=payload,
             headers={"Content-Type": "application/json",
-                     "Authorization": "Bearer hb8FH+kMgkuLlk7ORbWzzknwPRSSerhe3i7c4n+BW9m8mW6fI1CgVQi9CQ=="},
+                     "Authorization": f"Bearer {api_key}"},
             timeout=request_timeout
         )
         response.raise_for_status()
@@ -271,7 +281,7 @@ async def douyin_search(
 
 
 async def main():
-    result = await douyin_search(
+    result = await douyin_search_tikhub(
         keyword="养老政策",
     )
     print(result.output)