|
@@ -4,7 +4,7 @@ Agent Runner - Agent 执行引擎
|
|
|
核心职责:
|
|
核心职责:
|
|
|
1. 执行 Agent 任务(循环调用 LLM + 工具)
|
|
1. 执行 Agent 任务(循环调用 LLM + 工具)
|
|
|
2. 记录执行轨迹(Trace + Messages + GoalTree)
|
|
2. 记录执行轨迹(Trace + Messages + GoalTree)
|
|
|
-3. 检索和注入记忆(Experience + Skill)
|
|
|
|
|
|
|
+3. 加载和注入技能(Skill)
|
|
|
4. 管理执行计划(GoalTree)
|
|
4. 管理执行计划(GoalTree)
|
|
|
5. 支持续跑(continue)和回溯重跑(rewind)
|
|
5. 支持续跑(continue)和回溯重跑(rewind)
|
|
|
|
|
|
|
@@ -36,27 +36,18 @@ from agent.trace.compaction import (
|
|
|
build_reflect_prompt,
|
|
build_reflect_prompt,
|
|
|
)
|
|
)
|
|
|
from agent.memory.models import Skill
|
|
from agent.memory.models import Skill
|
|
|
-from agent.memory.protocols import MemoryStore, StateStore
|
|
|
|
|
from agent.memory.skill_loader import load_skills_from_dir
|
|
from agent.memory.skill_loader import load_skills_from_dir
|
|
|
from agent.tools import ToolRegistry, get_tool_registry
|
|
from agent.tools import ToolRegistry, get_tool_registry
|
|
|
from agent.core.prompts import (
|
|
from agent.core.prompts import (
|
|
|
DEFAULT_SYSTEM_PREFIX,
|
|
DEFAULT_SYSTEM_PREFIX,
|
|
|
- RESEARCH_STAGE_PROMPT_TEMPLATE,
|
|
|
|
|
- PLANNING_STAGE_PROMPT,
|
|
|
|
|
- RESEARCH_DECISION_GUIDE_TEMPLATE,
|
|
|
|
|
TRUNCATION_HINT,
|
|
TRUNCATION_HINT,
|
|
|
TOOL_INTERRUPTED_MESSAGE,
|
|
TOOL_INTERRUPTED_MESSAGE,
|
|
|
AGENT_INTERRUPTED_SUMMARY,
|
|
AGENT_INTERRUPTED_SUMMARY,
|
|
|
AGENT_CONTINUE_HINT_TEMPLATE,
|
|
AGENT_CONTINUE_HINT_TEMPLATE,
|
|
|
TASK_NAME_GENERATION_SYSTEM_PROMPT,
|
|
TASK_NAME_GENERATION_SYSTEM_PROMPT,
|
|
|
TASK_NAME_FALLBACK,
|
|
TASK_NAME_FALLBACK,
|
|
|
- EXPERIENCE_ENTRY_TEMPLATE,
|
|
|
|
|
- EXPERIENCE_SUMMARY_WITH_RESULTS,
|
|
|
|
|
- EXPERIENCE_SUMMARY_NO_RESULTS,
|
|
|
|
|
EXPERIENCE_PARSE_WARNING,
|
|
EXPERIENCE_PARSE_WARNING,
|
|
|
SUMMARY_HEADER_TEMPLATE,
|
|
SUMMARY_HEADER_TEMPLATE,
|
|
|
- build_research_stage_prompt,
|
|
|
|
|
- build_research_decision_guide,
|
|
|
|
|
build_summary_header,
|
|
build_summary_header,
|
|
|
build_tool_interrupted_message,
|
|
build_tool_interrupted_message,
|
|
|
build_agent_continue_hint,
|
|
build_agent_continue_hint,
|
|
@@ -103,7 +94,7 @@ class RunConfig:
|
|
|
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 # 是否启用自动研究流程(知识检索→经验检索→调研→计划)
|
|
|
|
|
|
|
+ enable_research_flow: bool = False # 已废弃,保留字段避免调用方传参报错
|
|
|
|
|
|
|
|
|
|
|
|
|
# 内置工具列表(始终自动加载)
|
|
# 内置工具列表(始终自动加载)
|
|
@@ -174,17 +165,6 @@ BUILTIN_TOOLS = [
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
-# ===== 向后兼容 =====
|
|
|
|
|
-
|
|
|
|
|
-@dataclass
|
|
|
|
|
-class AgentConfig:
|
|
|
|
|
- """[向后兼容] Agent 配置,新代码请使用 RunConfig"""
|
|
|
|
|
- agent_type: str = "default"
|
|
|
|
|
- max_iterations: int = 200
|
|
|
|
|
- enable_memory: bool = True
|
|
|
|
|
- auto_execute_tools: bool = True
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
@dataclass
|
|
@dataclass
|
|
|
class CallResult:
|
|
class CallResult:
|
|
|
"""单次调用结果"""
|
|
"""单次调用结果"""
|
|
@@ -214,13 +194,9 @@ class AgentRunner:
|
|
|
def __init__(
|
|
def __init__(
|
|
|
self,
|
|
self,
|
|
|
trace_store: Optional[TraceStore] = None,
|
|
trace_store: Optional[TraceStore] = None,
|
|
|
- memory_store: Optional[MemoryStore] = None,
|
|
|
|
|
- state_store: Optional[StateStore] = None,
|
|
|
|
|
tool_registry: Optional[ToolRegistry] = None,
|
|
tool_registry: Optional[ToolRegistry] = None,
|
|
|
llm_call: Optional[Callable] = None,
|
|
llm_call: Optional[Callable] = None,
|
|
|
utility_llm_call: Optional[Callable] = None,
|
|
utility_llm_call: Optional[Callable] = None,
|
|
|
- embedding_call: Optional[Callable] = None,
|
|
|
|
|
- config: Optional[AgentConfig] = None,
|
|
|
|
|
skills_dir: Optional[str] = None,
|
|
skills_dir: Optional[str] = None,
|
|
|
goal_tree: Optional[GoalTree] = None,
|
|
goal_tree: Optional[GoalTree] = None,
|
|
|
debug: bool = False,
|
|
debug: bool = False,
|
|
@@ -230,33 +206,21 @@ class AgentRunner:
|
|
|
|
|
|
|
|
Args:
|
|
Args:
|
|
|
trace_store: Trace 存储
|
|
trace_store: Trace 存储
|
|
|
- memory_store: Memory 存储(可选)
|
|
|
|
|
- state_store: State 存储(可选)
|
|
|
|
|
tool_registry: 工具注册表(默认使用全局注册表)
|
|
tool_registry: 工具注册表(默认使用全局注册表)
|
|
|
llm_call: 主 LLM 调用函数
|
|
llm_call: 主 LLM 调用函数
|
|
|
- embedding_call: 语义嵌入向量LLM
|
|
|
|
|
utility_llm_call: 轻量 LLM(用于生成任务标题等),可选
|
|
utility_llm_call: 轻量 LLM(用于生成任务标题等),可选
|
|
|
- config: [向后兼容] AgentConfig
|
|
|
|
|
skills_dir: Skills 目录路径
|
|
skills_dir: Skills 目录路径
|
|
|
goal_tree: 初始 GoalTree(可选)
|
|
goal_tree: 初始 GoalTree(可选)
|
|
|
debug: 保留参数(已废弃)
|
|
debug: 保留参数(已废弃)
|
|
|
"""
|
|
"""
|
|
|
self.trace_store = trace_store
|
|
self.trace_store = trace_store
|
|
|
- self.memory_store = memory_store
|
|
|
|
|
- self.state_store = state_store
|
|
|
|
|
self.tools = tool_registry or get_tool_registry()
|
|
self.tools = tool_registry or get_tool_registry()
|
|
|
self.llm_call = llm_call
|
|
self.llm_call = llm_call
|
|
|
- self.embedding_call = embedding_call
|
|
|
|
|
self.utility_llm_call = utility_llm_call
|
|
self.utility_llm_call = utility_llm_call
|
|
|
- self.config = config or AgentConfig()
|
|
|
|
|
self.skills_dir = skills_dir
|
|
self.skills_dir = skills_dir
|
|
|
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
|
|
|
|
|
-
|
|
|
|
|
- # 研究流程状态管理(每个 trace 独立)
|
|
|
|
|
- self._research_states: Dict[str, Dict[str, Any]] = {} # trace_id → research_state
|
|
|
|
|
|
|
|
|
|
# 知识保存跟踪(每个 trace 独立)
|
|
# 知识保存跟踪(每个 trace 独立)
|
|
|
self._saved_knowledge_ids: Dict[str, List[str]] = {} # trace_id → [knowledge_ids]
|
|
self._saved_knowledge_ids: Dict[str, List[str]] = {} # trace_id → [knowledge_ids]
|
|
@@ -558,32 +522,7 @@ class AgentRunner:
|
|
|
return trace_obj, goal_tree, sequence
|
|
return trace_obj, goal_tree, sequence
|
|
|
|
|
|
|
|
# ===== Phase 2: BUILD HISTORY =====
|
|
# ===== Phase 2: BUILD HISTORY =====
|
|
|
- async def _get_embedding(self, text: str) -> List[float]:
|
|
|
|
|
- """
|
|
|
|
|
- 获取文本的嵌入向量(Embedding)
|
|
|
|
|
-
|
|
|
|
|
- Args:
|
|
|
|
|
- text: 需要向量化的文本
|
|
|
|
|
-
|
|
|
|
|
- Returns:
|
|
|
|
|
- List[float]: 嵌入向量
|
|
|
|
|
- """
|
|
|
|
|
- if not text or not text.strip():
|
|
|
|
|
- return []
|
|
|
|
|
-
|
|
|
|
|
- # 优先使用注入的 embedding_call
|
|
|
|
|
- if self.embedding_call:
|
|
|
|
|
- try:
|
|
|
|
|
- return await self.embedding_call(text)
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- logger.error(f"Error in embedding_call: {e}")
|
|
|
|
|
- raise
|
|
|
|
|
-
|
|
|
|
|
- # 兜底方案:如果没有注入 embedding_call,但有 llm_call,
|
|
|
|
|
- # 某些 SDK 封装可能支持通过 llm_call 的客户端直接获取
|
|
|
|
|
- # 这里建议强制要求基础设施层提供该函数以保证分层清晰
|
|
|
|
|
- raise ValueError("embedding_call function not provided to AgentRunner")
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
async def _build_history(
|
|
async def _build_history(
|
|
|
self,
|
|
self,
|
|
|
trace_id: str,
|
|
trace_id: str,
|
|
@@ -677,224 +616,8 @@ 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. 初始化研究流程(已废弃,知识注入现在在 goal_tool.py 中实现)
|
|
|
|
|
- # 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}
|
|
|
|
|
-
|
|
|
|
|
-**重要提示**:
|
|
|
|
|
-- 调研完成后,请使用 `knowledge_save` 工具保存调研结果
|
|
|
|
|
-- 系统会自动检测到 knowledge_save 调用,并进入下一阶段(计划)
|
|
|
|
|
-"""
|
|
|
|
|
-
|
|
|
|
|
- elif stage == "planning":
|
|
|
|
|
- return PLANNING_STAGE_PROMPT
|
|
|
|
|
-
|
|
|
|
|
- # 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. 对每条有价值的信息,使用 knowledge_save 工具保存,标签类型选择:
|
|
|
|
|
- - 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: 检测到 knowledge_save 调用(直接调研)
|
|
|
|
|
- # 情况 2: 检测到 agent 工具执行完成(子 agent 调研)
|
|
|
|
|
- if stage == "research":
|
|
|
|
|
- if tool_name == "knowledge_save":
|
|
|
|
|
- # 直接调研:检测到 knowledge_save 调用
|
|
|
|
|
- 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(
|
|
@@ -912,10 +635,6 @@ agent(
|
|
|
# 当前主路径头节点的 sequence(用于设置 parent_sequence)
|
|
# 当前主路径头节点的 sequence(用于设置 parent_sequence)
|
|
|
head_seq = trace.head_sequence
|
|
head_seq = trace.head_sequence
|
|
|
|
|
|
|
|
- # 经验检索缓存:只在 goal 切换时重新检索
|
|
|
|
|
- _last_goal_id = None
|
|
|
|
|
- _cached_exp_text = ""
|
|
|
|
|
-
|
|
|
|
|
for iteration in range(config.max_iterations):
|
|
for iteration in range(config.max_iterations):
|
|
|
# 检查取消信号
|
|
# 检查取消信号
|
|
|
cancel_event = self._cancel_events.get(trace_id)
|
|
cancel_event = self._cancel_events.get(trace_id)
|
|
@@ -1040,98 +759,35 @@ agent(
|
|
|
# 构建 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(
|
|
|
llm_messages,
|
|
llm_messages,
|
|
|
config.model,
|
|
config.model,
|
|
|
config.enable_prompt_caching
|
|
config.enable_prompt_caching
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # 然后追加动态注入的内容(不影响已缓存的历史消息)
|
|
|
|
|
- # 周期性注入 GoalTree + Collaborators
|
|
|
|
|
|
|
+ # 周期性注入 GoalTree + Collaborators(动态内容追加在缓存点之后)
|
|
|
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:
|
|
|
system_msg = {"role": "system", "content": context_injection}
|
|
system_msg = {"role": "system", "content": context_injection}
|
|
|
llm_messages.append(system_msg)
|
|
llm_messages.append(system_msg)
|
|
|
- system_messages_to_persist.append(("上下文注入", system_msg))
|
|
|
|
|
-
|
|
|
|
|
- # 经验检索:已废弃,知识注入现在在 goal_tool.py 的 focus 操作中自动执行
|
|
|
|
|
- # current_goal_id = goal_tree.current_id if goal_tree else None
|
|
|
|
|
- # if current_goal_id and current_goal_id != _last_goal_id:
|
|
|
|
|
- # ... (已移除)
|
|
|
|
|
- # # 经验注入: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
|
|
|
|
|
|
|
+ # 持久化上下文注入消息
|
|
|
|
|
+ if self.trace_store:
|
|
|
|
|
+ current_goal_id = goal_tree.current_id if (goal_tree and goal_tree.current_id) else None
|
|
|
|
|
+ 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=f"[上下文注入]\n{context_injection}",
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.trace_store.add_message(system_message)
|
|
|
|
|
+ history.append(system_msg)
|
|
|
|
|
+ head_seq = sequence
|
|
|
|
|
+ sequence += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
# 调用 LLM
|
|
# 调用 LLM
|
|
@@ -1152,40 +808,6 @@ agent(
|
|
|
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(
|
|
@@ -1379,22 +1001,11 @@ agent(
|
|
|
"role": "tool",
|
|
"role": "tool",
|
|
|
"tool_call_id": tc["id"],
|
|
"tool_call_id": tc["id"],
|
|
|
"name": tool_name,
|
|
"name": tool_name,
|
|
|
- "content": tool_content_for_llm, # 这里传入 list 即可触发模型的视觉能力
|
|
|
|
|
|
|
+ "content": tool_content_for_llm,
|
|
|
})
|
|
})
|
|
|
- # ------------------------------------------
|
|
|
|
|
-
|
|
|
|
|
- # 研究流程状态转换
|
|
|
|
|
- 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
|
|
|
|
|
|
|
@@ -1539,21 +1150,31 @@ agent(
|
|
|
elif line.startswith("- ") and not line.startswith("- 经验ID:"):
|
|
elif line.startswith("- ") and not line.startswith("- 经验ID:"):
|
|
|
content = line[2:].strip()
|
|
content = line[2:].strip()
|
|
|
|
|
|
|
|
- # 构建 scenario(从 intent 和 state 生成)
|
|
|
|
|
- scenario_parts = []
|
|
|
|
|
|
|
+ # 构建 task(从 intent 和 state 生成)
|
|
|
|
|
+ task_parts = []
|
|
|
|
|
+ if intents:
|
|
|
|
|
+ task_parts.append(f"意图: {', '.join(intents)}")
|
|
|
|
|
+ if states:
|
|
|
|
|
+ task_parts.append(f"状态: {', '.join(states)}")
|
|
|
|
|
+ task = " | ".join(task_parts) if task_parts else "通用经验"
|
|
|
|
|
+
|
|
|
|
|
+ # 构建 tags(将 intents 和 states 作为业务标签)
|
|
|
|
|
+ tags = {}
|
|
|
if intents:
|
|
if intents:
|
|
|
- scenario_parts.append(f"意图: {', '.join(intents)}")
|
|
|
|
|
|
|
+ tags["intent"] = ", ".join(intents)
|
|
|
if states:
|
|
if states:
|
|
|
- scenario_parts.append(f"状态: {', '.join(states)}")
|
|
|
|
|
- scenario = " | ".join(scenario_parts) if scenario_parts else "通用经验"
|
|
|
|
|
|
|
+ tags["state"] = ", ".join(states)
|
|
|
|
|
|
|
|
- # 调用 knowledge_save 保存为 strategy 标签的知识
|
|
|
|
|
|
|
+ # 调用 knowledge_save 保存为 strategy 类型的知识
|
|
|
result = await knowledge_save(
|
|
result = await knowledge_save(
|
|
|
- scenario=scenario,
|
|
|
|
|
|
|
+ task=task,
|
|
|
content=content,
|
|
content=content,
|
|
|
- tags_type=["strategy"],
|
|
|
|
|
|
|
+ types=["strategy"],
|
|
|
|
|
+ tags=tags,
|
|
|
urls=[],
|
|
urls=[],
|
|
|
agent_id="runner",
|
|
agent_id="runner",
|
|
|
|
|
+ source_name="compression_reflection",
|
|
|
|
|
+ source_category="exp",
|
|
|
score=3,
|
|
score=3,
|
|
|
message_id=trace_id # 使用 trace_id 作为 message_id
|
|
message_id=trace_id # 使用 trace_id 作为 message_id
|
|
|
)
|
|
)
|
|
@@ -1572,8 +1193,8 @@ agent(
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.error(f"Level 2 经验提取失败: {e}")
|
|
logger.error(f"Level 2 经验提取失败: {e}")
|
|
|
|
|
|
|
|
- # --- Step 2: 压缩总结 + 经验评估 ---
|
|
|
|
|
- compress_prompt = build_compression_prompt(goal_tree, used_ex_ids=self.used_ex_ids)
|
|
|
|
|
|
|
+ # --- Step 2: 压缩总结 ---
|
|
|
|
|
+ compress_prompt = build_compression_prompt(goal_tree)
|
|
|
compress_messages = list(history) + [{"role": "user", "content": compress_prompt}]
|
|
compress_messages = list(history) + [{"role": "user", "content": compress_prompt}]
|
|
|
|
|
|
|
|
# 应用 Prompt Caching
|
|
# 应用 Prompt Caching
|
|
@@ -1596,48 +1217,11 @@ agent(
|
|
|
logger.warning("Level 2 压缩跳过:LLM 未返回内容")
|
|
logger.warning("Level 2 压缩跳过:LLM 未返回内容")
|
|
|
return history, head_seq, sequence
|
|
return history, head_seq, sequence
|
|
|
|
|
|
|
|
- # 解析 [[EVALUATION]] 块并更新经验
|
|
|
|
|
- if self.used_ex_ids:
|
|
|
|
|
- try:
|
|
|
|
|
- eval_block = ""
|
|
|
|
|
- if "[[EVALUATION]]" in raw_output:
|
|
|
|
|
- eval_start = raw_output.index("[[EVALUATION]]") + len("[[EVALUATION]]")
|
|
|
|
|
- eval_end = raw_output.index("[[SUMMARY]]") if "[[SUMMARY]]" in raw_output else len(raw_output)
|
|
|
|
|
- eval_block = raw_output[eval_start:eval_end].strip()
|
|
|
|
|
-
|
|
|
|
|
- if eval_block:
|
|
|
|
|
- import re as _re
|
|
|
|
|
- update_map = {}
|
|
|
|
|
- for line in eval_block.splitlines():
|
|
|
|
|
- # 匹配新的知识 ID 格式:knowledge-xxx 或 research-xxx
|
|
|
|
|
- m = _re.search(r"ID:\s*((?:knowledge|research)-\S+)\s*\|\s*Result:\s*(\w+)", line)
|
|
|
|
|
- if m:
|
|
|
|
|
- knowledge_id, result = m.group(1), m.group(2).lower()
|
|
|
|
|
- if result in ("helpful", "harmful"):
|
|
|
|
|
- update_map[knowledge_id] = {"action": result, "feedback": ""}
|
|
|
|
|
- elif result == "mixed":
|
|
|
|
|
- update_map[knowledge_id] = {"action": "helpful", "feedback": ""}
|
|
|
|
|
- if update_map:
|
|
|
|
|
- # 转换为 knowledge_batch_update 的格式
|
|
|
|
|
- feedback_list = []
|
|
|
|
|
- for kid, action_data in update_map.items():
|
|
|
|
|
- feedback_list.append({
|
|
|
|
|
- "knowledge_id": kid,
|
|
|
|
|
- "is_effective": action_data["action"] == "helpful",
|
|
|
|
|
- "feedback": action_data.get("feedback", "")
|
|
|
|
|
- })
|
|
|
|
|
- result = await knowledge_batch_update(feedback_list=feedback_list)
|
|
|
|
|
- logger.info("知识评估完成,更新了知识")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- logger.warning("经验评估解析失败(不影响压缩): %s", e)
|
|
|
|
|
-
|
|
|
|
|
# 提取 [[SUMMARY]] 块
|
|
# 提取 [[SUMMARY]] 块
|
|
|
summary_text = raw_output
|
|
summary_text = raw_output
|
|
|
if "[[SUMMARY]]" in raw_output:
|
|
if "[[SUMMARY]]" in raw_output:
|
|
|
summary_text = raw_output[raw_output.index("[[SUMMARY]]") + len("[[SUMMARY]]"):].strip()
|
|
summary_text = raw_output[raw_output.index("[[SUMMARY]]") + len("[[SUMMARY]]"):].strip()
|
|
|
|
|
|
|
|
- # 压缩完成后清空 used_ex_ids
|
|
|
|
|
- self.used_ex_ids = []
|
|
|
|
|
if not summary_text:
|
|
if not summary_text:
|
|
|
logger.warning("Level 2 压缩跳过:LLM 未返回 summary")
|
|
logger.warning("Level 2 压缩跳过:LLM 未返回 summary")
|
|
|
return history, head_seq, sequence
|
|
return history, head_seq, sequence
|