Quellcode durchsuchen

feat: 需求理解阶段 tool 返回瘦身

jihuaqiang vor 1 Monat
Ursprung
Commit
1caa197eac

+ 15 - 9
agent/core/runner.py

@@ -104,6 +104,7 @@ class RunConfig:
     temperature: float = 0.3
     temperature: float = 0.3
     max_iterations: int = 200
     max_iterations: int = 200
     tools: Optional[List[str]] = None          # None = 全部已注册工具
     tools: Optional[List[str]] = None          # None = 全部已注册工具
+    include_builtin_tools: bool = True         # tools 非空时,是否自动叠加内置工具 schema
     side_branch_max_turns: int = 5             # 侧分支最大轮次(压缩/反思)
     side_branch_max_turns: int = 5             # 侧分支最大轮次(压缩/反思)
     goal_compression: Literal["none", "on_complete", "on_overflow"] = "on_overflow"  # Goal 压缩模式
     goal_compression: Literal["none", "on_complete", "on_overflow"] = "on_overflow"  # Goal 压缩模式
 
 
@@ -538,7 +539,7 @@ class AgentRunner:
         task_name = config.name or await self._generate_task_name(messages)
         task_name = config.name or await self._generate_task_name(messages)
 
 
         # 准备工具 Schema
         # 准备工具 Schema
-        tool_schemas = self._get_tool_schemas(config.tools)
+        tool_schemas = self._get_tool_schemas(config.tools, include_builtin_tools=config.include_builtin_tools)
 
 
         trace_obj = Trace(
         trace_obj = Trace(
             trace_id=trace_id,
             trace_id=trace_id,
@@ -989,7 +990,7 @@ class AgentRunner:
     ) -> AsyncIterator[Union[Trace, Message]]:
     ) -> AsyncIterator[Union[Trace, Message]]:
         """ReAct 循环"""
         """ReAct 循环"""
         trace_id = trace.trace_id
         trace_id = trace.trace_id
-        tool_schemas = self._get_tool_schemas(config.tools)
+        tool_schemas = self._get_tool_schemas(config.tools, include_builtin_tools=config.include_builtin_tools)
 
 
         # 当前主路径头节点的 sequence(用于设置 parent_sequence)
         # 当前主路径头节点的 sequence(用于设置 parent_sequence)
         head_seq = trace.head_sequence
         head_seq = trace.head_sequence
@@ -2114,22 +2115,27 @@ class AgentRunner:
         )
         )
         return messages
         return messages
 
 
-    def _get_tool_schemas(self, tools: Optional[List[str]]) -> List[Dict]:
+    def _get_tool_schemas(self, tools: Optional[List[str]], include_builtin_tools: bool = True) -> List[Dict]:
         """
         """
         获取工具 Schema
         获取工具 Schema
 
 
         - tools=None: 使用 registry 中全部已注册工具(含内置 + 外部注册的)
         - tools=None: 使用 registry 中全部已注册工具(含内置 + 外部注册的)
-        - tools=["a", "b"]: 在 BUILTIN_TOOLS 基础上追加指定工具
+        - tools=["a", "b"] 且 include_builtin_tools=True: 在 BUILTIN_TOOLS 基础上追加指定工具
+        - tools=["a", "b"] 且 include_builtin_tools=False: 仅使用显式指定工具
         """
         """
         if tools is None:
         if tools is None:
             # 全部已注册工具
             # 全部已注册工具
             tool_names = self.tools.get_tool_names()
             tool_names = self.tools.get_tool_names()
         else:
         else:
-            # BUILTIN_TOOLS + 显式指定的额外工具
-            tool_names = BUILTIN_TOOLS.copy()
-            for t in tools:
-                if t not in tool_names:
-                    tool_names.append(t)
+            if include_builtin_tools:
+                # BUILTIN_TOOLS + 显式指定的额外工具
+                tool_names = BUILTIN_TOOLS.copy()
+                for t in tools:
+                    if t not in tool_names:
+                        tool_names.append(t)
+            else:
+                # 仅显式指定工具,适用于强约束的业务任务
+                tool_names = list(dict.fromkeys(tools))
         return self.tools.get_schemas(tool_names)
         return self.tools.get_schemas(tool_names)
 
 
     # 默认 system prompt 前缀(当 config.system_prompt 和前端都未提供 system message 时使用)
     # 默认 system prompt 前缀(当 config.system_prompt 和前端都未提供 system message 时使用)

+ 7 - 1
examples/content_finder/content_finder.md

@@ -17,6 +17,12 @@ $system$
 ## 推理与表达
 ## 推理与表达
 在调用工具前后用自然语言说明意图与结论:行动前简要说明为何调用、期望得到什么;返回后解读要点与是否调整策略;做阶段性决策或者内容保留/淘汰时写明依据(可结合画像占比与 TGI)。`think_and_plan` 仅作阶段结构化备忘,**不能替代**上述对话中的推理说明。
 在调用工具前后用自然语言说明意图与结论:行动前简要说明为何调用、期望得到什么;返回后解读要点与是否调整策略;做阶段性决策或者内容保留/淘汰时写明依据(可结合画像占比与 TGI)。`think_and_plan` 仅作阶段结构化备忘,**不能替代**上述对话中的推理说明。
 
 
+## 成本优先(强约束)
+- 需求阶段仅允许最小必要动作:特征归类 + `get_goodcase_topic_point` + `hot_topic_search`(限 1 次)。  
+- `hot_topic_search` 仅使用 `metadata.top_topics` / `metadata.blocks` / `metadata.next_cursor` 做决策,**禁止**复述或依赖全量原始榜单。  
+- 在完成首轮搜索前,**不要**调用 `goal`、`get_current_context`;`think_and_plan` 仅在阶段切换时调用 1 次。  
+- 达到“可进入下一阶段”的条件后立即推进,避免在需求阶段反复总结。
+
 ## 可用工具
 ## 可用工具
 - 高赞选题点case查询:`get_goodcase_topic_point`, 传入原始的[下层特征]词,禁止修改或添加其他词语。
 - 高赞选题点case查询:`get_goodcase_topic_point`, 传入原始的[下层特征]词,禁止修改或添加其他词语。
 - 热门话题(搜索词优化):`hot_topic_search`,传入[实质特征]分词列表。
 - 热门话题(搜索词优化):`hot_topic_search`,传入[实质特征]分词列表。
@@ -35,7 +41,7 @@ $system$
 - 执行日志在 OUTPUT_DIR 下;上下文不足时可结合日志与当次 `trace_id` 目录排查。  
 - 执行日志在 OUTPUT_DIR 下;上下文不足时可结合日志与当次 `trace_id` 目录排查。  
 
 
 ## 工作方式与技能
 ## 工作方式与技能
-整体建议按「理解需求 → 找内容 → 备选数量达到筛选标准 → 筛选 → 通过数量达标 → 优质账号扩展 → 落盘 → 自检 → 入库 → 爬取计划」推进,但**不必**像流水线逐步打卡;可在中间自由推理、穿插工具。**细则与示例以技能为准**,请在执行中按需遵循:
+整体建议按「需求理解 → 找内容 → 备选数量达到筛选标准 → 筛选 → 通过数量达标 → 优质账号扩展 → 写入output.json → 自检 → 入库 → 爬取计划」推进,但**不必**像流水线逐步打卡;可在中间自由推理、穿插工具。**细则与示例以技能为准**,请在执行中按需遵循:
 
 
 | 技能 | 用途 |
 | 技能 | 用途 |
 |------|------|
 |------|------|

+ 15 - 3
examples/content_finder/core.py

@@ -95,9 +95,9 @@ from tools import (
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 # 默认搜索词
 # 默认搜索词
-DEFAULT_QUERY = "贪腐案例,赃物"
-DEFAULT_SUGGESTION = "用户想了解贪腐案例中的涉案财物和金额"
-DEFAULT_DEMAND_ID = 21820
+DEFAULT_QUERY = "养生干预手段"
+DEFAULT_SUGGESTION = "用户想学习具体的养生保健方法"
+DEFAULT_DEMAND_ID = 22655
 
 
 
 
 def extract_assistant_text(message: Message) -> str:
 def extract_assistant_text(message: Message) -> str:
@@ -180,6 +180,9 @@ async def run_agent(
     store = FileSystemTraceStore(base_path=str(trace_dir_path))
     store = FileSystemTraceStore(base_path=str(trace_dir_path))
 
 
     allowed_tools = [
     allowed_tools = [
+        "read_file",
+        "write_file",
+        "edit_file",
         "douyin_search",
         "douyin_search",
         "douyin_search_tikhub",
         "douyin_search_tikhub",
         "douyin_user_videos",
         "douyin_user_videos",
@@ -206,6 +209,15 @@ async def run_agent(
         goal_compression = "none",
         goal_compression = "none",
         force_side_branch = None,
         force_side_branch = None,
         max_iterations=max_iterations,
         max_iterations=max_iterations,
+        include_builtin_tools=False,
+        skills=[
+            "demand_analysis",
+            "content_finding_strategy",
+            "content_filtering_strategy",
+            "output_schema",
+            "high_quality_account",
+            "aigc_platform_plan",
+        ],
         tools=allowed_tools,
         tools=allowed_tools,
         extra_llm_params={"max_tokens": 8192},
         extra_llm_params={"max_tokens": 8192},
         knowledge=KnowledgeConfig(
         knowledge=KnowledgeConfig(

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

@@ -54,5 +54,6 @@ description: 内容筛选方法论
 ## 原则
 ## 原则
 
 
 标准来自当次需求;分阶段是为了省调用;数据不足时承认不确定,不硬编高分。
 标准来自当次需求;分阶段是为了省调用;数据不足时承认不确定,不硬编高分。
+**不能为了完成任务硬凑数量,明显质量不好就马上舍弃,继续寻找**
 
 
 最终 JSON 字段与命名须严格符合 `output_schema`,禁止中文 key 与自创字段。
 最终 JSON 字段与命名须严格符合 `output_schema`,禁止中文 key 与自创字段。

+ 17 - 11
examples/content_finder/tools/hot_topic_search.py

@@ -226,6 +226,7 @@ async def hot_topic_search(
     cursor: int = 1,
     cursor: int = 1,
     feature_keywords: Optional[List[str]] = None,
     feature_keywords: Optional[List[str]] = None,
     timeout: Optional[float] = None,
     timeout: Optional[float] = None,
+    include_raw_data: bool = False,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
     检索每日热点话题(今日热榜)
     检索每日热点话题(今日热榜)
@@ -236,6 +237,7 @@ async def hot_topic_search(
         feature_keywords: 词语列表(list[str])。传入时对每条话题标题逐词判断规范化后的包含关系,
         feature_keywords: 词语列表(list[str])。传入时对每条话题标题逐词判断规范化后的包含关系,
             至少命中一词则保留;不传入则不做过滤(返回全部话题)。
             至少命中一词则保留;不传入则不做过滤(返回全部话题)。
         timeout: 超时时间(秒),默认 60
         timeout: 超时时间(秒),默认 60
+        include_raw_data: 是否在 metadata 中包含原始 API 返回。默认 False 以减少 token 消耗。
 
 
     Returns:
     Returns:
         ToolResult:
         ToolResult:
@@ -247,7 +249,7 @@ async def hot_topic_search(
             - metadata.top_topics: Top3 话题明细(含 score、matched_keywords)
             - metadata.top_topics: Top3 话题明细(含 score、matched_keywords)
             - metadata.matched_total: 实际返回的命中话题总数(<=3)
             - metadata.matched_total: 实际返回的命中话题总数(<=3)
             - metadata.feature_keywords: 本次参与匹配的词语(清洗/去重后)
             - metadata.feature_keywords: 本次参与匹配的词语(清洗/去重后)
-            - metadata.raw_data: 原始 API 返回
+            - metadata.raw_data: 原始 API 返回(仅 include_raw_data=True 时提供)
     """
     """
     start_time = time.time()
     start_time = time.time()
     request_timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
     request_timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
@@ -257,6 +259,7 @@ async def hot_topic_search(
         "cursor": cursor,
         "cursor": cursor,
         "feature_keywords": cleaned_keywords,
         "feature_keywords": cleaned_keywords,
         "timeout": request_timeout,
         "timeout": request_timeout,
+        "include_raw_data": include_raw_data,
     }
     }
 
 
     if not isinstance(sort_type, str) or not sort_type.strip():
     if not isinstance(sort_type, str) or not sort_type.strip():
@@ -330,20 +333,23 @@ async def hot_topic_search(
         },
         },
     )
     )
 
 
+    metadata: Dict[str, Any] = {
+        "has_more": has_more,
+        "next_cursor": next_cursor,
+        "blocks": blocks,
+        "topics_by_source": parsed.get("topics_by_source", {}),
+        "top_topics": parsed.get("top_topics", []),
+        "matched_total": matched_total,
+        "feature_keywords": cleaned_keywords,
+    }
+    if include_raw_data:
+        metadata["raw_data"] = raw
+
     out = ToolResult(
     out = ToolResult(
         title=f"今日热榜热点话题({sort_type},cursor={cursor})",
         title=f"今日热榜热点话题({sort_type},cursor={cursor})",
         output=summary,
         output=summary,
         long_term_memory=f"Fetched hot topics sort_type='{sort_type}' cursor={cursor}",
         long_term_memory=f"Fetched hot topics sort_type='{sort_type}' cursor={cursor}",
-        metadata={
-            "raw_data": raw,
-            "has_more": has_more,
-            "next_cursor": next_cursor,
-            "blocks": blocks,
-            "topics_by_source": parsed.get("topics_by_source", {}),
-            "top_topics": parsed.get("top_topics", []),
-            "matched_total": matched_total,
-            "feature_keywords": cleaned_keywords,
-        },
+        metadata=metadata,
         include_metadata_in_llm=True,
         include_metadata_in_llm=True,
     )
     )
     log_tool_call(_LOG_LABEL, call_params, json.dumps(out.metadata.get("topics_by_source", {}), ensure_ascii=False))
     log_tool_call(_LOG_LABEL, call_params, json.dumps(out.metadata.get("topics_by_source", {}), ensure_ascii=False))