Talegorithm 1 неделя назад
Родитель
Сommit
f922099ec2
2 измененных файлов с 72 добавлено и 26 удалено
  1. 34 17
      agent/core/runner.py
  2. 38 9
      docs/README.md

+ 34 - 17
agent/core/runner.py

@@ -549,25 +549,38 @@ class AgentRunner:
                 if main_path:
                     head_seq = main_path[-1].sequence
 
-        # 2. 构建 system prompt(如果历史中没有 system message)
+        # 2. 构建/注入 skills 到 system prompt
         has_system = any(m.get("role") == "system" for m in history)
         has_system_in_new = any(m.get("role") == "system" for m in new_messages)
 
-        if not has_system and not has_system_in_new:
-            system_prompt = await self._build_system_prompt(config)
-            if system_prompt:
-                history = [{"role": "system", "content": system_prompt}] + history
+        if not has_system:
+            if has_system_in_new:
+                # 入参消息已含 system,将 skills 注入其中(在 step 4 持久化之前)
+                augmented = []
+                for msg in new_messages:
+                    if msg.get("role") == "system":
+                        base = msg.get("content") or ""
+                        enriched = await self._build_system_prompt(config, base_prompt=base)
+                        augmented.append({**msg, "content": enriched or base})
+                    else:
+                        augmented.append(msg)
+                new_messages = augmented
+            else:
+                # 没有 system,自动构建并插入历史
+                system_prompt = await self._build_system_prompt(config)
+                if system_prompt:
+                    history = [{"role": "system", "content": system_prompt}] + history
 
-                if self.trace_store:
-                    system_msg = Message.create(
-                        trace_id=trace_id, role="system", sequence=sequence,
-                        goal_id=None, content=system_prompt,
-                        parent_sequence=None,  # system message 是 root
-                    )
-                    await self.trace_store.add_message(system_msg)
-                    created_messages.append(system_msg)
-                    head_seq = sequence
-                    sequence += 1
+                    if self.trace_store:
+                        system_msg = Message.create(
+                            trace_id=trace_id, role="system", sequence=sequence,
+                            goal_id=None, content=system_prompt,
+                            parent_sequence=None,  # system message 是 root
+                        )
+                        await self.trace_store.add_message(system_msg)
+                        created_messages.append(system_msg)
+                        head_seq = sequence
+                        sequence += 1
 
         # 3. 新建时:在第一条 user message 末尾注入当前经验
         if not config.trace_id:  # 新建模式
@@ -1433,17 +1446,21 @@ class AgentRunner:
     # 默认 system prompt 前缀(当 config.system_prompt 和前端都未提供 system message 时使用)
     DEFAULT_SYSTEM_PREFIX = "你是最顶尖的AI助手,可以拆分并调用工具逐步解决复杂问题。"
 
-    async def _build_system_prompt(self, config: RunConfig) -> Optional[str]:
+    async def _build_system_prompt(self, config: RunConfig, base_prompt: Optional[str] = None) -> Optional[str]:
         """构建 system prompt(注入 skills)
 
         优先级:
         1. config.skills 显式指定 → 按名称过滤
         2. config.skills 为 None → 查 preset 的默认 skills 列表
         3. preset 也无 skills(None)→ 加载全部(向后兼容)
+
+        Args:
+            base_prompt: 已有 system 内容(来自消息或 config.system_prompt),
+                         None 时使用 config.system_prompt
         """
         from agent.core.presets import AGENT_PRESETS
 
-        system_prompt = config.system_prompt
+        system_prompt = base_prompt if base_prompt is not None else config.system_prompt
 
         # 确定要加载哪些 skills
         skills_filter: Optional[List[str]] = config.skills

+ 38 - 9
docs/README.md

@@ -60,8 +60,10 @@ agent/
 │   ├── protocols.py       # MemoryStore 接口
 │   ├── stores.py          # 存储实现
 │   ├── skill_loader.py    # Skill 加载器
-│   └── skills/            # 内置 Skills
-│       └── core.md        # Core Skill(自动加载)
+│   └── skills/            # 内置 Skills(自动注入 system prompt)
+│       ├── planning.md    # 计划与 Goal 工具使用
+│       ├── research.md    # 搜索与内容研究
+│       └── browser.md     # 浏览器自动化
 ├── llm/                   # LLM 集成
 │   ├── gemini.py          # Gemini Provider
@@ -167,6 +169,7 @@ class RunConfig:
     agent_type: str = "default"
     uid: Optional[str] = None
     system_prompt: Optional[str] = None        # None = 从 skills 自动构建
+    skills: Optional[List[str]] = None         # 注入 system prompt 的 skill 名称列表;None = 按 preset 决定
     enable_memory: bool = True
     auto_execute_tools: bool = True
     name: Optional[str] = None                 # 显示名称(空则由 utility_llm 自动生成)
@@ -304,7 +307,7 @@ agent 工具的合成结果对齐正常返回值格式(含 `sub_trace_id` 字
 **实现**:`agent/core/runner.py:AgentRunner._heal_orphaned_tool_calls`
 
 - `run(messages, config)`:**核心方法**,流式返回 `AsyncIterator[Union[Trace, Message]]`
-- `run_result(messages, config)`:便利方法,内部消费 `run()`,返回结构化结果。主要用于 `agent`/`evaluate` 工具内部
+- `run_result(messages, config, on_event=None)`:便利方法,内部消费 `run()`,返回结构化结果。`on_event` 回调可实时接收每个 Trace/Message 事件(用于调试时输出子 Agent 执行过程)。主要用于 `agent`/`evaluate` 工具内部
 
 ### REST API
 
@@ -544,19 +547,24 @@ class AgentPreset:
     denied_tools: Optional[List[str]] = None   # 黑名单
     max_iterations: int = 30
     temperature: Optional[float] = None
+    skills: Optional[List[str]] = None         # 注入 system prompt 的 skill 名称列表;None = 加载全部
     description: Optional[str] = None
 
 
+_DEFAULT_SKILLS = ["planning", "research", "browser"]
+
 AGENT_PRESETS = {
     "default": AgentPreset(
         allowed_tools=None,
         max_iterations=30,
+        skills=_DEFAULT_SKILLS,
         description="默认 Agent,拥有全部工具权限",
     ),
     "explore": AgentPreset(
         allowed_tools=["read", "glob", "grep", "list_files"],
         denied_tools=["write", "edit", "bash", "task"],
         max_iterations=15,
+        skills=["planning"],
         description="探索型 Agent,只读权限,用于代码分析",
     ),
     "analyst": AgentPreset(
@@ -564,6 +572,7 @@ AGENT_PRESETS = {
         denied_tools=["write", "edit", "bash", "task"],
         temperature=0.3,
         max_iterations=25,
+        skills=["planning", "research"],
         description="分析型 Agent,用于深度分析和研究",
     ),
 }
@@ -571,7 +580,7 @@ AGENT_PRESETS = {
 
 **实现**:`agent/core/presets.py`
 
-**用户自定义**:项目级配置 `.agent/presets.json` 可覆盖或添加预设。
+**用户自定义**:项目级配置文件(如 `examples/how/presets.json`)可通过 `register_preset()` 注册额外预设。项目专用的 Agent 类型建议放在项目目录下,而非内置预设。
 
 ---
 
@@ -589,10 +598,15 @@ async def agent(
     task: Union[str, List[str]],
     messages: Optional[Union[Messages, List[Messages]]] = None,
     continue_from: Optional[str] = None,
+    agent_type: Optional[str] = None,
+    skills: Optional[List[str]] = None,
     context: Optional[dict] = None,
 ) -> Dict[str, Any]:
 ```
 
+- `agent_type`: 子 Agent 类型,决定工具权限和默认 skills(对应 `AgentPreset` 名称,如 `"deconstruct"`)
+- `skills`: 覆盖 preset 默认值,显式指定注入 system prompt 的 skill 列表
+
 **单任务(delegate)**:`task: str`
 - 创建单个 Sub-Trace
 - 完整工具权限(除 agent/evaluate 外,防止递归)
@@ -748,17 +762,32 @@ ToolResult(
 
 | 类型 | 加载位置 | 加载时机 |
 |------|---------|---------|
-| **Core Skill** | System Prompt | Agent 启动时自动加载 |
+| **内置 Skill** | System Prompt | Agent 启动时自动注入 |
+| **项目 Skill** | System Prompt | Agent 启动时按 preset/call-site 过滤后注入 |
 | **普通 Skill** | 对话消息 | 模型调用 `skill` 工具时 |
 
 ### 目录结构
 
 ```
-agent/memory/skills/
-├── core.md              # Core Skill(自动加载到 System Prompt)
-└── browser_use/         # 普通 Skill(按需加载)
+agent/memory/skills/         # 内置 Skills(始终加载)
+├── planning.md              # 计划与 Goal 工具使用
+├── research.md              # 搜索与内容研究
+└── browser.md               # 浏览器自动化
+
+./skills/                    # 项目自定义 Skills
+```
 
-./skills/                # 项目自定义 Skills(按需加载)
+### Skills 过滤(call-site 选择)
+
+不同 Agent 类型所需的 skills 不同。过滤优先级:
+
+1. `agent()` 工具的 `skills` 参数(显式指定,最高优先级)
+2. `AgentPreset.skills`(preset 默认值)
+3. `None`(加载全部,向后兼容)
+
+示例:调用子 Agent 时只注入解构相关 skill:
+```python
+agent(task="...", agent_type="deconstruct", skills=["planning", "deconstruct"])
 ```
 
 **实现**:`agent/memory/skill_loader.py`