|
@@ -26,7 +26,7 @@ from typing import AsyncIterator, Optional, Dict, Any, List, Callable, Literal,
|
|
|
from agent.trace.models import Trace, Message
|
|
from agent.trace.models import Trace, Message
|
|
|
from agent.trace.protocols import TraceStore
|
|
from agent.trace.protocols import TraceStore
|
|
|
from agent.trace.goal_models import GoalTree
|
|
from agent.trace.goal_models import GoalTree
|
|
|
-from agent.tools.builtin.experience import _get_structured_experiences, _batch_update_experiences
|
|
|
|
|
|
|
+from agent.tools.builtin.knowledge import _get_structured_knowledge, _batch_update_knowledge, save_knowledge
|
|
|
from agent.trace.compaction import (
|
|
from agent.trace.compaction import (
|
|
|
CompressionConfig,
|
|
CompressionConfig,
|
|
|
filter_by_goal_status,
|
|
filter_by_goal_status,
|
|
@@ -79,8 +79,11 @@ class RunConfig:
|
|
|
# --- 额外 LLM 参数(传给 llm_call 的 **kwargs)---
|
|
# --- 额外 LLM 参数(传给 llm_call 的 **kwargs)---
|
|
|
extra_llm_params: Dict[str, Any] = field(default_factory=dict)
|
|
extra_llm_params: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
+ # --- 研究流程控制 ---
|
|
|
|
|
+ enable_research_flow: bool = True # 是否启用自动研究流程(知识检索→经验检索→调研→计划)
|
|
|
|
|
|
|
|
-# 内置工具列表(始终自动加载)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 内置工具列表(始终自动加载)
|
|
|
BUILTIN_TOOLS = [
|
|
BUILTIN_TOOLS = [
|
|
|
# 文件操作工具
|
|
# 文件操作工具
|
|
|
"read_file",
|
|
"read_file",
|
|
@@ -104,11 +107,18 @@ BUILTIN_TOOLS = [
|
|
|
"get_experience",
|
|
"get_experience",
|
|
|
"get_search_suggestions",
|
|
"get_search_suggestions",
|
|
|
|
|
|
|
|
|
|
+ # 知识管理工具
|
|
|
|
|
+ "search_knowledge",
|
|
|
|
|
+ "save_knowledge",
|
|
|
|
|
+ "update_knowledge",
|
|
|
|
|
+ "list_knowledge",
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# 沙箱工具
|
|
# 沙箱工具
|
|
|
- "sandbox_create_environment",
|
|
|
|
|
- "sandbox_run_shell",
|
|
|
|
|
- "sandbox_rebuild_with_ports",
|
|
|
|
|
- "sandbox_destroy_environment",
|
|
|
|
|
|
|
+ # "sandbox_create_environment",
|
|
|
|
|
+ # "sandbox_run_shell",
|
|
|
|
|
+ # "sandbox_rebuild_with_ports",
|
|
|
|
|
+ # "sandbox_destroy_environment",
|
|
|
|
|
|
|
|
# 浏览器工具
|
|
# 浏览器工具
|
|
|
"browser_navigate_to_url",
|
|
"browser_navigate_to_url",
|
|
@@ -218,13 +228,19 @@ class AgentRunner:
|
|
|
self.utility_llm_call = utility_llm_call
|
|
self.utility_llm_call = utility_llm_call
|
|
|
self.config = config or AgentConfig()
|
|
self.config = config or AgentConfig()
|
|
|
self.skills_dir = skills_dir
|
|
self.skills_dir = skills_dir
|
|
|
- # 确保 experiences_path 不为 None
|
|
|
|
|
- self.experiences_path = experiences_path
|
|
|
|
|
|
|
+ # 保留 experiences_path 参数以向后兼容,但不再使用(经验已迁移到知识系统)
|
|
|
|
|
+ self.experiences_path = experiences_path or "./.cache/experiences.md"
|
|
|
self.goal_tree = goal_tree
|
|
self.goal_tree = goal_tree
|
|
|
self.debug = debug
|
|
self.debug = debug
|
|
|
self._cancel_events: Dict[str, asyncio.Event] = {} # trace_id → cancel event
|
|
self._cancel_events: Dict[str, asyncio.Event] = {} # trace_id → cancel event
|
|
|
self.used_ex_ids: List[str] = [] # 当前运行中使用过的经验 ID
|
|
self.used_ex_ids: List[str] = [] # 当前运行中使用过的经验 ID
|
|
|
|
|
|
|
|
|
|
+ # 研究流程状态管理(每个 trace 独立)
|
|
|
|
|
+ self._research_states: Dict[str, Dict[str, Any]] = {} # trace_id → research_state
|
|
|
|
|
+
|
|
|
|
|
+ # 知识保存跟踪(每个 trace 独立)
|
|
|
|
|
+ self._saved_knowledge_ids: Dict[str, List[str]] = {} # trace_id → [knowledge_ids]
|
|
|
|
|
+
|
|
|
# ===== 核心公开方法 =====
|
|
# ===== 核心公开方法 =====
|
|
|
|
|
|
|
|
async def run(
|
|
async def run(
|
|
@@ -338,11 +354,16 @@ class AgentRunner:
|
|
|
status = "failed"
|
|
status = "failed"
|
|
|
error = error or "Agent 没有产生 assistant 文本结果"
|
|
error = error or "Agent 没有产生 assistant 文本结果"
|
|
|
|
|
|
|
|
|
|
+ # 获取保存的知识 ID
|
|
|
|
|
+ trace_id = final_trace.trace_id if final_trace else config.trace_id
|
|
|
|
|
+ saved_knowledge_ids = self._saved_knowledge_ids.get(trace_id, [])
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
"status": status,
|
|
"status": status,
|
|
|
"summary": summary,
|
|
"summary": summary,
|
|
|
- "trace_id": final_trace.trace_id if final_trace else config.trace_id,
|
|
|
|
|
|
|
+ "trace_id": trace_id,
|
|
|
"error": error,
|
|
"error": error,
|
|
|
|
|
+ "saved_knowledge_ids": saved_knowledge_ids, # 新增:返回保存的知识 ID
|
|
|
"stats": {
|
|
"stats": {
|
|
|
"total_messages": final_trace.total_messages if final_trace else 0,
|
|
"total_messages": final_trace.total_messages if final_trace else 0,
|
|
|
"total_tokens": final_trace.total_tokens if final_trace else 0,
|
|
"total_tokens": final_trace.total_tokens if final_trace else 0,
|
|
@@ -636,8 +657,235 @@ class AgentRunner:
|
|
|
if self.trace_store and head_seq is not None:
|
|
if self.trace_store and head_seq is not None:
|
|
|
await self.trace_store.update_trace(trace_id, head_sequence=head_seq)
|
|
await self.trace_store.update_trace(trace_id, head_sequence=head_seq)
|
|
|
|
|
|
|
|
|
|
+ # 6. 初始化研究流程(仅在新建 trace 且启用研究流程时)
|
|
|
|
|
+ if config.enable_research_flow and not config.trace_id:
|
|
|
|
|
+ await self._init_research_flow(trace_id, new_messages, goal_tree, config)
|
|
|
|
|
+
|
|
|
return history, sequence, created_messages, head_seq or 0
|
|
return history, sequence, created_messages, head_seq or 0
|
|
|
|
|
|
|
|
|
|
+ async def _init_research_flow(
|
|
|
|
|
+ self,
|
|
|
|
|
+ trace_id: str,
|
|
|
|
|
+ messages: List[Dict],
|
|
|
|
|
+ goal_tree: Optional[GoalTree],
|
|
|
|
|
+ config: RunConfig,
|
|
|
|
|
+ ):
|
|
|
|
|
+ """
|
|
|
|
|
+ 初始化研究流程状态
|
|
|
|
|
+
|
|
|
|
|
+ 研究流程阶段(已简化):
|
|
|
|
|
+ 1. research_decision: 决定是否需要调研(知识和经验已自动注入到 GoalTree)
|
|
|
|
|
+ 2. research: 执行调研(如果需要)
|
|
|
|
|
+ 3. planning: 制定计划
|
|
|
|
|
+ 4. execution: 正常执行
|
|
|
|
|
+ """
|
|
|
|
|
+ # 提取任务描述
|
|
|
|
|
+ task_desc = self._extract_task_description(messages)
|
|
|
|
|
+
|
|
|
|
|
+ # 初始化研究状态(直接从 research_decision 开始,因为知识已自动注入)
|
|
|
|
|
+ self._research_states[trace_id] = {
|
|
|
|
|
+ "stage": "research_decision", # 直接进入决策阶段
|
|
|
|
|
+ "task_desc": task_desc,
|
|
|
|
|
+ "knowledge_found": False,
|
|
|
|
|
+ "experience_found": False,
|
|
|
|
|
+ "need_research": False,
|
|
|
|
|
+ "research_completed": False,
|
|
|
|
|
+ "planning_completed": False,
|
|
|
|
|
+ "knowledge_results": [],
|
|
|
|
|
+ "experience_results": [],
|
|
|
|
|
+ "decision_guide_injected": False, # 防止重复注入决策引导
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"[Research Flow] 初始化研究流程(知识已自动注入): {task_desc[:50]}...")
|
|
|
|
|
+
|
|
|
|
|
+ def _extract_task_description(self, messages: List[Dict]) -> str:
|
|
|
|
|
+ """从消息中提取任务描述"""
|
|
|
|
|
+ for msg in messages:
|
|
|
|
|
+ if msg.get("role") == "user":
|
|
|
|
|
+ content = msg.get("content", "")
|
|
|
|
|
+ if isinstance(content, str):
|
|
|
|
|
+ return content
|
|
|
|
|
+ elif isinstance(content, list):
|
|
|
|
|
+ for part in content:
|
|
|
|
|
+ if isinstance(part, dict) and part.get("type") == "text":
|
|
|
|
|
+ return part.get("text", "")
|
|
|
|
|
+ return "未知任务"
|
|
|
|
|
+
|
|
|
|
|
+ def _get_research_state(self, trace_id: str) -> Optional[Dict[str, Any]]:
|
|
|
|
|
+ """获取研究流程状态"""
|
|
|
|
|
+ return self._research_states.get(trace_id)
|
|
|
|
|
+
|
|
|
|
|
+ def _update_research_stage(self, trace_id: str, stage: str, **kwargs):
|
|
|
|
|
+ """更新研究流程阶段"""
|
|
|
|
|
+ if trace_id in self._research_states:
|
|
|
|
|
+ self._research_states[trace_id]["stage"] = stage
|
|
|
|
|
+ self._research_states[trace_id].update(kwargs)
|
|
|
|
|
+ logger.info(f"[Research Flow] 阶段切换: {stage}")
|
|
|
|
|
+
|
|
|
|
|
+ def _build_research_guide(self, research_state: Dict[str, Any]) -> str:
|
|
|
|
|
+ """根据研究流程状态构建引导消息"""
|
|
|
|
|
+ stage = research_state["stage"]
|
|
|
|
|
+ task_desc = research_state["task_desc"]
|
|
|
|
|
+
|
|
|
|
|
+ if stage == "research":
|
|
|
|
|
+ # 读取 research.md 的内容
|
|
|
|
|
+ research_skill_content = ""
|
|
|
|
|
+ research_skill_path = os.path.join(
|
|
|
|
|
+ os.path.dirname(__file__),
|
|
|
|
|
+ "..", "memory", "skills", "research.md"
|
|
|
|
|
+ )
|
|
|
|
|
+ try:
|
|
|
|
|
+ with open(research_skill_path, "r", encoding="utf-8") as f:
|
|
|
|
|
+ research_skill_content = f.read()
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning(f"无法读取 research.md: {e}")
|
|
|
|
|
+ research_skill_content = "(无法加载 research.md 内容)"
|
|
|
|
|
+
|
|
|
|
|
+ return f"""
|
|
|
|
|
+## 📚 研究流程 - 执行调研
|
|
|
|
|
+
|
|
|
|
|
+现有信息不足,需要进行调研。
|
|
|
|
|
+
|
|
|
|
|
+{research_skill_content}
|
|
|
|
|
+
|
|
|
|
|
+**重要提示**:
|
|
|
|
|
+- 调研完成后,请使用 `save_knowledge` 工具保存调研结果
|
|
|
|
|
+- 系统会自动检测到 save_knowledge 调用,并进入下一阶段(计划)
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+ elif stage == "planning":
|
|
|
|
|
+ return f"""
|
|
|
|
|
+## 📋 研究流程 - 制定计划
|
|
|
|
|
+
|
|
|
|
|
+调研已完成(或无需调研),现在请制定执行计划。
|
|
|
|
|
+
|
|
|
|
|
+**请立即执行以下操作**:
|
|
|
|
|
+1. 使用 `goal` 工具创建目标树
|
|
|
|
|
+2. 将任务分解为可执行的子目标
|
|
|
|
|
+3. 为每个子目标设置合理的优先级
|
|
|
|
|
+
|
|
|
|
|
+注意:这是强制步骤,必须创建 goal tree 才能进入执行阶段。
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+ # research_decision 阶段的引导消息已移到 _build_research_decision_guide
|
|
|
|
|
+ return ""
|
|
|
|
|
+
|
|
|
|
|
+ def _build_research_decision_guide(self, research_state: Dict[str, Any]) -> str:
|
|
|
|
|
+ """构建调研决策阶段的引导消息(基于已自动注入的知识和经验)"""
|
|
|
|
|
+ experience_results = research_state.get("experience_results", [])
|
|
|
|
|
+ task_desc = research_state.get("task_desc", "")
|
|
|
|
|
+
|
|
|
|
|
+ # 构建经验摘要
|
|
|
|
|
+ experience_summary = ""
|
|
|
|
|
+ if experience_results:
|
|
|
|
|
+ experience_summary = f"✅ 已自动检索到 {len(experience_results)} 条相关经验(见上方 GoalTree 中的「📚 相关知识」)\n"
|
|
|
|
|
+ else:
|
|
|
|
|
+ experience_summary = "❌ 未找到相关经验\n"
|
|
|
|
|
+
|
|
|
|
|
+ return f"""
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 🤔 调研决策
|
|
|
|
|
+
|
|
|
|
|
+{experience_summary}
|
|
|
|
|
+
|
|
|
|
|
+### 决策指南
|
|
|
|
|
+
|
|
|
|
|
+**当前状态**:系统已自动检索知识库和经验库,相关内容已注入到上方的 GoalTree 中(查看 Current Goal 下的「📚 相关知识」部分)。
|
|
|
|
|
+
|
|
|
|
|
+**请根据已注入的知识和经验,选择下一步行动**:
|
|
|
|
|
+
|
|
|
|
|
+**选项 1: 知识充足,直接制定计划**
|
|
|
|
|
+- 如果上方显示的知识和经验已经足够完成任务
|
|
|
|
|
+- 直接使用 `goal` 工具制定执行计划
|
|
|
|
|
+
|
|
|
|
|
+**选项 2: 知识不足,需要调研** ⭐
|
|
|
|
|
+- 如果上方没有显示相关知识,或现有知识不足以完成任务
|
|
|
|
|
+- **立即调用 `agent` 工具启动调研子任务**:
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+agent(
|
|
|
|
|
+ task=\"\"\"针对任务「{task_desc[:100]}」进行深入调研:
|
|
|
|
|
+
|
|
|
|
|
+1. 使用 web_search 工具搜索相关技术文档、教程、最佳实践
|
|
|
|
|
+2. 搜索关键词建议:
|
|
|
|
|
+ - 核心技术名称 + "教程"
|
|
|
|
|
+ - 核心技术名称 + "最佳实践"
|
|
|
|
|
+ - 核心技术名称 + "示例代码"
|
|
|
|
|
+3. 使用 read_file 工具查看项目中的相关文件
|
|
|
|
|
+4. 对每条有价值的信息,使用 save_knowledge 工具保存,标签类型选择:
|
|
|
|
|
+ - tool: 工具使用方法
|
|
|
|
|
+ - definition: 概念定义
|
|
|
|
|
+ - usercase: 使用案例
|
|
|
|
|
+ - strategy: 策略经验
|
|
|
|
|
+
|
|
|
|
|
+调研完成后,系统会自动进入计划阶段。
|
|
|
|
|
+\"\"\",
|
|
|
|
|
+ skills=["research"] # 注入调研指南
|
|
|
|
|
+)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**重要提示**:
|
|
|
|
|
+- 如果 GoalTree 中没有显示「📚 相关知识」,说明知识库为空,必须先调研
|
|
|
|
|
+- 调研应该简洁高效,最多设立两个 goal
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+ async def _handle_research_flow_transition(
|
|
|
|
|
+ self,
|
|
|
|
|
+ trace_id: str,
|
|
|
|
|
+ tool_name: str,
|
|
|
|
|
+ tool_result: Any,
|
|
|
|
|
+ goal_tree: Optional[GoalTree],
|
|
|
|
|
+ ):
|
|
|
|
|
+ """处理研究流程的状态转换"""
|
|
|
|
|
+ research_state = self._get_research_state(trace_id)
|
|
|
|
|
+ if not research_state:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ stage = research_state["stage"]
|
|
|
|
|
+
|
|
|
|
|
+ # 阶段 1: 调研决策(通过 assistant 的文本回复或 agent 工具调用判断)
|
|
|
|
|
+ # 这个阶段的转换在 assistant 回复后处理,或检测到 agent 工具调用
|
|
|
|
|
+
|
|
|
|
|
+ # 阶段 2: 调研完成
|
|
|
|
|
+ # 情况 1: 检测到 save_knowledge 调用(直接调研)
|
|
|
|
|
+ # 情况 2: 检测到 agent 工具执行完成(子 agent 调研)
|
|
|
|
|
+ if stage == "research":
|
|
|
|
|
+ if tool_name == "save_knowledge":
|
|
|
|
|
+ # 直接调研:检测到 save_knowledge 调用
|
|
|
|
|
+ self._update_research_stage(
|
|
|
|
|
+ trace_id,
|
|
|
|
|
+ "planning",
|
|
|
|
|
+ research_completed=True
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.info(f"[Research Flow] 调研完成(直接调研),进入计划阶段")
|
|
|
|
|
+ elif tool_name == "agent":
|
|
|
|
|
+ # 子 agent 调研:agent 工具执行完成
|
|
|
|
|
+ self._update_research_stage(
|
|
|
|
|
+ trace_id,
|
|
|
|
|
+ "planning",
|
|
|
|
|
+ research_completed=True
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.info(f"[Research Flow] 调研完成(子 agent 调研),进入计划阶段")
|
|
|
|
|
+
|
|
|
|
|
+ # 阶段 3: 计划完成(检测到 goal 工具调用)
|
|
|
|
|
+ elif stage == "planning" and tool_name == "goal":
|
|
|
|
|
+ # 检查是否创建了 goal tree
|
|
|
|
|
+ if goal_tree and goal_tree.goals:
|
|
|
|
|
+ self._update_research_stage(
|
|
|
|
|
+ trace_id,
|
|
|
|
|
+ "execution",
|
|
|
|
|
+ planning_completed=True
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.info(f"[Research Flow] 计划完成,进入执行阶段")
|
|
|
|
|
+
|
|
|
|
|
+ # 打印 goal tree
|
|
|
|
|
+ print("\n" + "="*60)
|
|
|
|
|
+ print("📋 Goal Tree 已创建:")
|
|
|
|
|
+ print("="*60)
|
|
|
|
|
+ print(goal_tree.to_prompt())
|
|
|
|
|
+ print("="*60 + "\n")
|
|
|
|
|
+
|
|
|
# ===== Phase 3: AGENT LOOP =====
|
|
# ===== Phase 3: AGENT LOOP =====
|
|
|
|
|
|
|
|
async def _agent_loop(
|
|
async def _agent_loop(
|
|
@@ -690,14 +938,26 @@ class AgentRunner:
|
|
|
for part in msg["content"]
|
|
for part in msg["content"]
|
|
|
if isinstance(part, dict) and part.get("type") in ("image", "image_url")
|
|
if isinstance(part, dict) and part.get("type") in ("image", "image_url")
|
|
|
)
|
|
)
|
|
|
- print(f"\n[压缩评估] 消息数: {msg_count} | 图片数: {img_count} | Token: {token_count:,} / {max_tokens:,} ({progress_pct:.1f}%)")
|
|
|
|
|
|
|
|
|
|
- if token_count > max_tokens:
|
|
|
|
|
- print(f"[压缩评估] ⚠️ 超过阈值,触发压缩流程")
|
|
|
|
|
|
|
+ # 检查是否需要压缩(token 或消息数量超限)
|
|
|
|
|
+ needs_compression_by_tokens = token_count > max_tokens
|
|
|
|
|
+ needs_compression_by_count = (
|
|
|
|
|
+ compression_config.max_messages > 0 and
|
|
|
|
|
+ msg_count > compression_config.max_messages
|
|
|
|
|
+ )
|
|
|
|
|
+ needs_compression = needs_compression_by_tokens or needs_compression_by_count
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n[压缩评估] 消息数: {msg_count} / {compression_config.max_messages} | 图片数: {img_count} | Token: {token_count:,} / {max_tokens:,} ({progress_pct:.1f}%)")
|
|
|
|
|
+
|
|
|
|
|
+ if needs_compression:
|
|
|
|
|
+ if needs_compression_by_count:
|
|
|
|
|
+ print(f"[压缩评估] ⚠️ 消息数超过阈值 ({msg_count} > {compression_config.max_messages}),触发压缩流程")
|
|
|
|
|
+ if needs_compression_by_tokens:
|
|
|
|
|
+ print(f"[压缩评估] ⚠️ Token 数超过阈值,触发压缩流程")
|
|
|
else:
|
|
else:
|
|
|
print(f"[压缩评估] ✅ 未超阈值,无需压缩")
|
|
print(f"[压缩评估] ✅ 未超阈值,无需压缩")
|
|
|
|
|
|
|
|
- if token_count > max_tokens and self.trace_store and goal_tree:
|
|
|
|
|
|
|
+ if needs_compression and self.trace_store and goal_tree:
|
|
|
# 使用本地 head_seq(store 中的 head_sequence 在 loop 期间未更新,是过时的)
|
|
# 使用本地 head_seq(store 中的 head_sequence 在 loop 期间未更新,是过时的)
|
|
|
if head_seq > 0:
|
|
if head_seq > 0:
|
|
|
main_path_msgs = await self.trace_store.get_main_path_messages(
|
|
main_path_msgs = await self.trace_store.get_main_path_messages(
|
|
@@ -726,38 +986,64 @@ class AgentRunner:
|
|
|
sum(1 for g in goal_tree.goals
|
|
sum(1 for g in goal_tree.goals
|
|
|
if g.status in ("completed", "abandoned")),
|
|
if g.status in ("completed", "abandoned")),
|
|
|
)
|
|
)
|
|
|
- elif token_count > max_tokens:
|
|
|
|
|
|
|
+ elif needs_compression:
|
|
|
print("[压缩评估] ⚠️ 无法执行 Level 1 压缩(缺少 store 或 goal_tree)")
|
|
print("[压缩评估] ⚠️ 无法执行 Level 1 压缩(缺少 store 或 goal_tree)")
|
|
|
logger.warning(
|
|
logger.warning(
|
|
|
- "消息 token 数 (%d) 超过阈值 (%d),但无法执行 Level 1 压缩(缺少 store 或 goal_tree)",
|
|
|
|
|
- token_count, max_tokens,
|
|
|
|
|
|
|
+ "消息数 (%d) 或 token 数 (%d) 超过阈值 (max_messages=%d, max_tokens=%d),但无法执行 Level 1 压缩(缺少 store 或 goal_tree)",
|
|
|
|
|
+ msg_count, token_count, compression_config.max_messages, max_tokens,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Level 2 压缩:LLM 总结(Level 1 后仍超阈值时触发)
|
|
# Level 2 压缩:LLM 总结(Level 1 后仍超阈值时触发)
|
|
|
token_count_after = estimate_tokens(history)
|
|
token_count_after = estimate_tokens(history)
|
|
|
- if token_count_after > max_tokens:
|
|
|
|
|
|
|
+ msg_count_after = len(history)
|
|
|
|
|
+ needs_level2_by_tokens = token_count_after > max_tokens
|
|
|
|
|
+ needs_level2_by_count = (
|
|
|
|
|
+ compression_config.max_messages > 0 and
|
|
|
|
|
+ msg_count_after > compression_config.max_messages
|
|
|
|
|
+ )
|
|
|
|
|
+ needs_level2 = needs_level2_by_tokens or needs_level2_by_count
|
|
|
|
|
+
|
|
|
|
|
+ if needs_level2:
|
|
|
progress_pct_after = (token_count_after / max_tokens * 100) if max_tokens > 0 else 0
|
|
progress_pct_after = (token_count_after / max_tokens * 100) if max_tokens > 0 else 0
|
|
|
|
|
+ reason = []
|
|
|
|
|
+ if needs_level2_by_count:
|
|
|
|
|
+ reason.append(f"消息数 {msg_count_after} > {compression_config.max_messages}")
|
|
|
|
|
+ if needs_level2_by_tokens:
|
|
|
|
|
+ reason.append(f"Token {token_count_after:,} > {max_tokens:,} ({progress_pct_after:.1f}%)")
|
|
|
print(
|
|
print(
|
|
|
- f"[Level 2 压缩] Level 1 后仍超阈值: {token_count_after:,} / {max_tokens:,} ({progress_pct_after:.1f}%) "
|
|
|
|
|
|
|
+ f"[Level 2 压缩] Level 1 后仍超阈值: {' | '.join(reason)} "
|
|
|
f"→ 触发 LLM 总结"
|
|
f"→ 触发 LLM 总结"
|
|
|
)
|
|
)
|
|
|
logger.info(
|
|
logger.info(
|
|
|
- "Level 1 后 token 仍超阈值 (%d > %d),触发 Level 2 压缩",
|
|
|
|
|
- token_count_after, max_tokens,
|
|
|
|
|
|
|
+ "Level 1 后仍超阈值 (消息数=%d/%d, token=%d/%d),触发 Level 2 压缩",
|
|
|
|
|
+ msg_count_after, compression_config.max_messages, token_count_after, max_tokens,
|
|
|
)
|
|
)
|
|
|
history, head_seq, sequence = await self._compress_history(
|
|
history, head_seq, sequence = await self._compress_history(
|
|
|
trace_id, history, goal_tree, config, sequence, head_seq,
|
|
trace_id, history, goal_tree, config, sequence, head_seq,
|
|
|
)
|
|
)
|
|
|
final_tokens = estimate_tokens(history)
|
|
final_tokens = estimate_tokens(history)
|
|
|
print(f"[Level 2 压缩] 完成: Token {token_count_after:,} → {final_tokens:,}")
|
|
print(f"[Level 2 压缩] 完成: Token {token_count_after:,} → {final_tokens:,}")
|
|
|
- elif token_count > max_tokens:
|
|
|
|
|
|
|
+ elif needs_compression:
|
|
|
# Level 1 压缩成功,未触发 Level 2
|
|
# Level 1 压缩成功,未触发 Level 2
|
|
|
- print(f"[压缩评估] ✅ Level 1 压缩后达标: {token_count_after:,} / {max_tokens:,}")
|
|
|
|
|
|
|
+ print(f"[压缩评估] ✅ Level 1 压缩后达标: 消息数 {msg_count_after} | Token {token_count_after:,} / {max_tokens:,}")
|
|
|
print() # 空行分隔
|
|
print() # 空行分隔
|
|
|
|
|
|
|
|
# 构建 LLM messages(注入上下文)
|
|
# 构建 LLM messages(注入上下文)
|
|
|
llm_messages = list(history)
|
|
llm_messages = list(history)
|
|
|
|
|
|
|
|
|
|
+ # 收集需要持久化的消息
|
|
|
|
|
+ user_messages_to_persist = [] # 研究流程引导和经验检索改为 user 消息
|
|
|
|
|
+ system_messages_to_persist = [] # 上下文注入保持为 system 消息
|
|
|
|
|
+
|
|
|
|
|
+ # 研究流程引导(仅在启用且处于研究阶段时)- 改为 user 消息
|
|
|
|
|
+ research_state = self._get_research_state(trace_id)
|
|
|
|
|
+ if research_state and research_state["stage"] != "execution":
|
|
|
|
|
+ research_guide = self._build_research_guide(research_state)
|
|
|
|
|
+ if research_guide:
|
|
|
|
|
+ user_msg = {"role": "user", "content": research_guide}
|
|
|
|
|
+ llm_messages.append(user_msg)
|
|
|
|
|
+ user_messages_to_persist.append(("研究流程引导", user_msg))
|
|
|
|
|
+
|
|
|
# 先对历史消息应用 Prompt Caching(在注入动态内容之前)
|
|
# 先对历史消息应用 Prompt Caching(在注入动态内容之前)
|
|
|
# 这样可以确保历史消息的缓存点固定,不受动态注入影响
|
|
# 这样可以确保历史消息的缓存点固定,不受动态注入影响
|
|
|
llm_messages = self._add_cache_control(
|
|
llm_messages = self._add_cache_control(
|
|
@@ -771,7 +1057,9 @@ class AgentRunner:
|
|
|
if iteration % CONTEXT_INJECTION_INTERVAL == 0:
|
|
if iteration % CONTEXT_INJECTION_INTERVAL == 0:
|
|
|
context_injection = self._build_context_injection(trace, goal_tree)
|
|
context_injection = self._build_context_injection(trace, goal_tree)
|
|
|
if context_injection:
|
|
if context_injection:
|
|
|
- llm_messages.append({"role": "system", "content": context_injection})
|
|
|
|
|
|
|
+ system_msg = {"role": "system", "content": context_injection}
|
|
|
|
|
+ llm_messages.append(system_msg)
|
|
|
|
|
+ system_messages_to_persist.append(("上下文注入", system_msg))
|
|
|
|
|
|
|
|
# 经验检索:goal 切换时重新检索,注入为 system message
|
|
# 经验检索:goal 切换时重新检索,注入为 system message
|
|
|
current_goal_id = goal_tree.current_id if goal_tree else None
|
|
current_goal_id = goal_tree.current_id if goal_tree else None
|
|
@@ -780,12 +1068,18 @@ class AgentRunner:
|
|
|
current_goal = goal_tree.find(current_goal_id)
|
|
current_goal = goal_tree.find(current_goal_id)
|
|
|
if current_goal:
|
|
if current_goal:
|
|
|
try:
|
|
try:
|
|
|
- relevant_exps = await _get_structured_experiences(
|
|
|
|
|
|
|
+ relevant_exps = await _get_structured_knowledge(
|
|
|
query_text=current_goal.description,
|
|
query_text=current_goal.description,
|
|
|
top_k=3,
|
|
top_k=3,
|
|
|
- context={"runner": self}
|
|
|
|
|
|
|
+ context={"runner": self},
|
|
|
)
|
|
)
|
|
|
if relevant_exps:
|
|
if relevant_exps:
|
|
|
|
|
+ # 保存到 goal 对象
|
|
|
|
|
+ current_goal.knowledge = relevant_exps
|
|
|
|
|
+ logger.info(f"[Knowledge Injection] 已将 {len(relevant_exps)} 条知识注入到 goal {current_goal.id}: {current_goal.description[:40]}")
|
|
|
|
|
+ logger.debug(f"[Knowledge Injection] 注入的知识 IDs: {[exp.get('id') for exp in relevant_exps]}")
|
|
|
|
|
+ # 持久化保存 goal_tree
|
|
|
|
|
+ await self.trace_store.update_goal_tree(trace_id, goal_tree)
|
|
|
self.used_ex_ids = [exp['id'] for exp in relevant_exps]
|
|
self.used_ex_ids = [exp['id'] for exp in relevant_exps]
|
|
|
parts = [f"[{exp['id']}] {exp['content']}" for exp in relevant_exps]
|
|
parts = [f"[{exp['id']}] {exp['content']}" for exp in relevant_exps]
|
|
|
_cached_exp_text = "## 参考历史经验\n" + "\n\n".join(parts)
|
|
_cached_exp_text = "## 参考历史经验\n" + "\n\n".join(parts)
|
|
@@ -796,14 +1090,83 @@ class AgentRunner:
|
|
|
self.used_ex_ids,
|
|
self.used_ex_ids,
|
|
|
)
|
|
)
|
|
|
else:
|
|
else:
|
|
|
|
|
+ current_goal.knowledge = []
|
|
|
|
|
+ logger.info(f"[Knowledge Injection] goal {current_goal.id} 未找到相关知识")
|
|
|
|
|
+ # 持久化保存 goal_tree
|
|
|
|
|
+ await self.trace_store.update_goal_tree(trace_id, goal_tree)
|
|
|
_cached_exp_text = ""
|
|
_cached_exp_text = ""
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "经验检索: goal='%s', 未找到相关经验",
|
|
|
|
|
+ current_goal.description[:40],
|
|
|
|
|
+ )
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.warning("经验检索失败: %s", e)
|
|
logger.warning("经验检索失败: %s", e)
|
|
|
|
|
+ current_goal.knowledge = []
|
|
|
_cached_exp_text = ""
|
|
_cached_exp_text = ""
|
|
|
|
|
|
|
|
- # 经验注入:goal切换时注入相关历史经验
|
|
|
|
|
- if _cached_exp_text:
|
|
|
|
|
- llm_messages.append({"role": "system", "content": _cached_exp_text})
|
|
|
|
|
|
|
+ # 经验注入:goal切换时注入相关历史经验 - 改为 user 消息
|
|
|
|
|
+ # 或者在 research_decision 阶段注入调研决策引导
|
|
|
|
|
+ if _cached_exp_text or (research_state and research_state["stage"] == "research_decision" and not research_state.get("decision_guide_injected", False)):
|
|
|
|
|
+ exp_content = _cached_exp_text if _cached_exp_text else ""
|
|
|
|
|
+
|
|
|
|
|
+ # 如果处于 research_decision 阶段,追加引导消息
|
|
|
|
|
+ if research_state and research_state["stage"] == "research_decision" and not research_state.get("decision_guide_injected", False):
|
|
|
|
|
+ if exp_content:
|
|
|
|
|
+ exp_content += "\n\n"
|
|
|
|
|
+ exp_content += self._build_research_decision_guide(research_state)
|
|
|
|
|
+ # 标记已注入,防止重复
|
|
|
|
|
+ research_state["decision_guide_injected"] = True
|
|
|
|
|
+ logger.info("[Research Flow] 已注入调研决策引导消息")
|
|
|
|
|
+
|
|
|
|
|
+ if exp_content: # 确保有内容才注入
|
|
|
|
|
+ user_msg = {"role": "user", "content": exp_content}
|
|
|
|
|
+ llm_messages.append(user_msg)
|
|
|
|
|
+ user_messages_to_persist.append(("经验检索", user_msg))
|
|
|
|
|
+
|
|
|
|
|
+ # 持久化 user 消息到 trace 和 history
|
|
|
|
|
+ for label, usr_msg in user_messages_to_persist:
|
|
|
|
|
+ # 添加到 history(这样会被包含在后续的对话中)
|
|
|
|
|
+ history.append(usr_msg)
|
|
|
|
|
+
|
|
|
|
|
+ # 保存到 trace store
|
|
|
|
|
+ if self.trace_store:
|
|
|
|
|
+ # 在 content 前添加标签,这样会自动出现在 description 中
|
|
|
|
|
+ labeled_content = f"[{label}]\n{usr_msg['content']}"
|
|
|
|
|
+ user_message = Message.create(
|
|
|
|
|
+ trace_id=trace_id,
|
|
|
|
|
+ role="user",
|
|
|
|
|
+ sequence=sequence,
|
|
|
|
|
+ goal_id=current_goal_id,
|
|
|
|
|
+ parent_sequence=head_seq if head_seq > 0 else None,
|
|
|
|
|
+ content=labeled_content,
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.trace_store.add_message(user_message)
|
|
|
|
|
+ yield user_message
|
|
|
|
|
+ head_seq = sequence
|
|
|
|
|
+ sequence += 1
|
|
|
|
|
+
|
|
|
|
|
+ # 持久化 system 消息到 trace 和 history
|
|
|
|
|
+ for label, sys_msg in system_messages_to_persist:
|
|
|
|
|
+ # 添加到 history(这样会被包含在后续的对话中)
|
|
|
|
|
+ history.append(sys_msg)
|
|
|
|
|
+
|
|
|
|
|
+ # 保存到 trace store
|
|
|
|
|
+ if self.trace_store:
|
|
|
|
|
+ # 在 content 前添加标签,这样会自动出现在 description 中
|
|
|
|
|
+ labeled_content = f"[{label}]\n{sys_msg['content']}"
|
|
|
|
|
+ system_message = Message.create(
|
|
|
|
|
+ trace_id=trace_id,
|
|
|
|
|
+ role="system",
|
|
|
|
|
+ sequence=sequence,
|
|
|
|
|
+ goal_id=current_goal_id,
|
|
|
|
|
+ parent_sequence=head_seq if head_seq > 0 else None,
|
|
|
|
|
+ content=labeled_content,
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.trace_store.add_message(system_message)
|
|
|
|
|
+ yield system_message
|
|
|
|
|
+ head_seq = sequence
|
|
|
|
|
+ sequence += 1
|
|
|
|
|
+
|
|
|
|
|
|
|
|
# 调用 LLM
|
|
# 调用 LLM
|
|
|
result = await self.llm_call(
|
|
result = await self.llm_call(
|
|
@@ -823,6 +1186,40 @@ class AgentRunner:
|
|
|
cache_creation_tokens = result.get("cache_creation_tokens")
|
|
cache_creation_tokens = result.get("cache_creation_tokens")
|
|
|
cache_read_tokens = result.get("cache_read_tokens")
|
|
cache_read_tokens = result.get("cache_read_tokens")
|
|
|
|
|
|
|
|
|
|
+ # 研究流程:处理 research_decision 阶段的转换
|
|
|
|
|
+ research_state = self._get_research_state(trace_id)
|
|
|
|
|
+ research_decision_handled = False
|
|
|
|
|
+ if research_state and research_state["stage"] == "research_decision":
|
|
|
|
|
+ # 检查是否调用了 agent 工具进行调研
|
|
|
|
|
+ if tool_calls:
|
|
|
|
|
+ has_agent_call = any(
|
|
|
|
|
+ tc.get("function", {}).get("name") == "agent"
|
|
|
|
|
+ for tc in tool_calls
|
|
|
|
|
+ )
|
|
|
|
|
+ if has_agent_call:
|
|
|
|
|
+ # LLM 决定使用子 agent 进行调研
|
|
|
|
|
+ self._update_research_stage(trace_id, "research", need_research=True)
|
|
|
|
|
+ logger.info(f"[Research Flow] LLM 决定使用子 agent 进行调研,进入调研阶段")
|
|
|
|
|
+ research_decision_handled = True
|
|
|
|
|
+ # 继续执行 agent 工具调用
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 检查是否调用了 goal 工具(直接进入计划)
|
|
|
|
|
+ has_goal_call = any(
|
|
|
|
|
+ tc.get("function", {}).get("name") == "goal"
|
|
|
|
|
+ for tc in tool_calls
|
|
|
|
|
+ )
|
|
|
|
|
+ if has_goal_call:
|
|
|
|
|
+ self._update_research_stage(trace_id, "planning", need_research=False)
|
|
|
|
|
+ logger.info(f"[Research Flow] LLM 决定无需调研,直接进入计划阶段")
|
|
|
|
|
+ research_decision_handled = True
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 根据 assistant 的文本回复判断
|
|
|
|
|
+ response_lower = response_content.lower()
|
|
|
|
|
+ if "无需调研" in response_content or "不需要调研" in response_content or "信息充足" in response_content:
|
|
|
|
|
+ self._update_research_stage(trace_id, "planning", need_research=False)
|
|
|
|
|
+ logger.info(f"[Research Flow] LLM 决定无需调研,直接进入计划阶段")
|
|
|
|
|
+ research_decision_handled = True
|
|
|
|
|
+
|
|
|
# 按需自动创建 root goal
|
|
# 按需自动创建 root goal
|
|
|
if goal_tree and not goal_tree.goals and tool_calls:
|
|
if goal_tree and not goal_tree.goals and tool_calls:
|
|
|
has_goal_call = any(
|
|
has_goal_call = any(
|
|
@@ -936,6 +1333,16 @@ class AgentRunner:
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ # 跟踪保存的知识 ID
|
|
|
|
|
+ if tool_name == "save_knowledge" and isinstance(tool_result, dict):
|
|
|
|
|
+ metadata = tool_result.get("metadata", {})
|
|
|
|
|
+ knowledge_id = metadata.get("knowledge_id")
|
|
|
|
|
+ if knowledge_id:
|
|
|
|
|
+ if trace_id not in self._saved_knowledge_ids:
|
|
|
|
|
+ self._saved_knowledge_ids[trace_id] = []
|
|
|
|
|
+ self._saved_knowledge_ids[trace_id].append(knowledge_id)
|
|
|
|
|
+ logger.info(f"[Knowledge Tracking] 记录保存的知识 ID: {knowledge_id}")
|
|
|
|
|
+
|
|
|
# --- 支持多模态工具反馈 ---
|
|
# --- 支持多模态工具反馈 ---
|
|
|
# execute() 返回 dict{"text","images","tool_usage"} 或 str
|
|
# execute() 返回 dict{"text","images","tool_usage"} 或 str
|
|
|
# 统一为dict格式
|
|
# 统一为dict格式
|
|
@@ -1013,8 +1420,18 @@ class AgentRunner:
|
|
|
})
|
|
})
|
|
|
# ------------------------------------------
|
|
# ------------------------------------------
|
|
|
|
|
|
|
|
|
|
+ # 研究流程状态转换
|
|
|
|
|
+ await self._handle_research_flow_transition(
|
|
|
|
|
+ trace_id, tool_name, tool_result, goal_tree
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
continue # 继续循环
|
|
continue # 继续循环
|
|
|
|
|
|
|
|
|
|
+ # 研究流程决策阶段:即使没有工具调用也要继续
|
|
|
|
|
+ if research_decision_handled:
|
|
|
|
|
+ logger.info(f"[Research Flow] 研究决策完成,继续下一轮循环")
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
# 无工具调用,任务完成
|
|
# 无工具调用,任务完成
|
|
|
break
|
|
break
|
|
|
|
|
|
|
@@ -1136,10 +1553,54 @@ created_at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
|
structured_entries.append(entry)
|
|
structured_entries.append(entry)
|
|
|
|
|
|
|
|
if structured_entries:
|
|
if structured_entries:
|
|
|
- os.makedirs(os.path.dirname(self.experiences_path), exist_ok=True)
|
|
|
|
|
- with open(self.experiences_path, "a", encoding="utf-8") as f:
|
|
|
|
|
- f.write("\n\n" + "\n\n".join(structured_entries))
|
|
|
|
|
- logger.info(f"已提取并保存 {len(structured_entries)} 条结构化经验")
|
|
|
|
|
|
|
+ # 保存经验为知识(strategy 标签)
|
|
|
|
|
+ saved_count = 0
|
|
|
|
|
+ for entry in structured_entries:
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 从 entry 中提取信息
|
|
|
|
|
+ lines = entry.split("\n")
|
|
|
|
|
+ ex_id = ""
|
|
|
|
|
+ intents = []
|
|
|
|
|
+ states = []
|
|
|
|
|
+ content = ""
|
|
|
|
|
+
|
|
|
|
|
+ for line in lines:
|
|
|
|
|
+ if line.startswith("id:"):
|
|
|
|
|
+ ex_id = line.split(":", 1)[1].strip()
|
|
|
|
|
+ elif line.startswith("tags:"):
|
|
|
|
|
+ tags_match = _re2.search(r"intent:\s*\[(.*?)\].*state:\s*\[(.*?)\]", line)
|
|
|
|
|
+ if tags_match:
|
|
|
|
|
+ intents_str = tags_match.group(1).strip("'\"")
|
|
|
|
|
+ states_str = tags_match.group(2).strip("'\"")
|
|
|
|
|
+ intents = [i.strip().strip("'\"") for i in intents_str.split(",") if i.strip()]
|
|
|
|
|
+ states = [s.strip().strip("'\"") for s in states_str.split(",") if s.strip()]
|
|
|
|
|
+ elif line.startswith("- ") and not line.startswith("- 经验ID:"):
|
|
|
|
|
+ content = line[2:].strip()
|
|
|
|
|
+
|
|
|
|
|
+ # 构建 scenario(从 intent 和 state 生成)
|
|
|
|
|
+ scenario_parts = []
|
|
|
|
|
+ if intents:
|
|
|
|
|
+ scenario_parts.append(f"意图: {', '.join(intents)}")
|
|
|
|
|
+ if states:
|
|
|
|
|
+ scenario_parts.append(f"状态: {', '.join(states)}")
|
|
|
|
|
+ scenario = " | ".join(scenario_parts) if scenario_parts else "通用经验"
|
|
|
|
|
+
|
|
|
|
|
+ # 调用 save_knowledge 保存为 strategy 标签的知识
|
|
|
|
|
+ await save_knowledge(
|
|
|
|
|
+ scenario=scenario,
|
|
|
|
|
+ content=content,
|
|
|
|
|
+ tags_type=["strategy"],
|
|
|
|
|
+ urls=[],
|
|
|
|
|
+ agent_id="runner",
|
|
|
|
|
+ score=3,
|
|
|
|
|
+ trace_id=trace_id
|
|
|
|
|
+ )
|
|
|
|
|
+ saved_count += 1
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning(f"保存经验失败: {e}")
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"已提取并保存 {saved_count}/{len(structured_entries)} 条结构化经验到知识库")
|
|
|
else:
|
|
else:
|
|
|
logger.warning("未能解析出符合格式的经验条目,请检查 REFLECT_PROMPT。")
|
|
logger.warning("未能解析出符合格式的经验条目,请检查 REFLECT_PROMPT。")
|
|
|
logger.debug(f"LLM Raw Output:\n{reflection_text}")
|
|
logger.debug(f"LLM Raw Output:\n{reflection_text}")
|
|
@@ -1186,16 +1647,17 @@ created_at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
|
import re as _re
|
|
import re as _re
|
|
|
update_map = {}
|
|
update_map = {}
|
|
|
for line in eval_block.splitlines():
|
|
for line in eval_block.splitlines():
|
|
|
- m = _re.search(r"ID:\s*(ex_\S+)\s*\|\s*Result:\s*(\w+)", line)
|
|
|
|
|
|
|
+ # 匹配新的知识 ID 格式:knowledge-xxx 或 research-xxx
|
|
|
|
|
+ m = _re.search(r"ID:\s*((?:knowledge|research)-\S+)\s*\|\s*Result:\s*(\w+)", line)
|
|
|
if m:
|
|
if m:
|
|
|
- ex_id, result = m.group(1), m.group(2).lower()
|
|
|
|
|
|
|
+ knowledge_id, result = m.group(1), m.group(2).lower()
|
|
|
if result in ("helpful", "harmful"):
|
|
if result in ("helpful", "harmful"):
|
|
|
- update_map[ex_id] = {"action": result, "feedback": ""}
|
|
|
|
|
|
|
+ update_map[knowledge_id] = {"action": result, "feedback": ""}
|
|
|
elif result == "mixed":
|
|
elif result == "mixed":
|
|
|
- update_map[ex_id] = {"action": "helpful", "feedback": ""}
|
|
|
|
|
|
|
+ update_map[knowledge_id] = {"action": "helpful", "feedback": ""}
|
|
|
if update_map:
|
|
if update_map:
|
|
|
- count = await _batch_update_experiences(update_map, context={"runner": self})
|
|
|
|
|
- logger.info("经验评估完成,更新了 %s 条经验", count)
|
|
|
|
|
|
|
+ count = await _batch_update_knowledge(update_map, context={"runner": self})
|
|
|
|
|
+ logger.info("知识评估完成,更新了 %s 条知识", count)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.warning("经验评估解析失败(不影响压缩): %s", e)
|
|
logger.warning("经验评估解析失败(不影响压缩): %s", e)
|
|
|
|
|
|