agent-Specialization/modules/multi_agent/sub_agent_context.py
JOJO 4a8c38d24a feat(memory): 新增项目记忆检索工具 search_project_memory
- 后端:关键词全文检索 .astrion/memory/*.md(名称+10/描述+5/正文加权),
  返回 top-N 与带行号片段;注册为主 agent 只读工具,子智能体工具集同步;
  memory_system 模板与多智能体 prompt 增加检索指引(第二触点)
- 工具 description 含硬性触发条件、query 撰写指引、负例与防重试契约
- 前端:新增 notebook-search 组合图标(notebook 轮廓开口 + 右下角放大镜);
  工具详情渲染命中记忆的名称/描述/行号片段;活动汇总归入 memory_read
- 重构:ToolAction.vue 删除约 1600 行重复渲染函数,统一委托
  toolRenderers.ts(与 StackedBlocks/MinimalBlocks 同一实现)
2026-07-29 02:00:01 +08:00

261 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""多智能体子智能体动态上下文构建。
在子智能体创建时,为其系统提示词注入工作区相关信息:
- 执行环境(宿主机/docker + 沙箱/直接)
- AGENTS.md 项目规范
- 工作区信息(项目名称、运行环境、当前时间,不含 file_tree / recent_uploads
- .astrion/ 目录结构说明skills / memory / review / user_upload
- 可用 skill 列表
- 项目记忆索引
所有信息在子智能体创建那一刻快照写入 system_prompt.txt后续冻结不更新。
"""
from __future__ import annotations
import platform
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
try:
from config import (
PROMPTS_DIR,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
WORKSPACE_REVIEW_DIRNAME,
TERMINAL_SANDBOX_MODE,
)
from config.paths import IS_HOST_MODE
except ImportError:
import sys
project_root = Path(__file__).resolve().parents[2]
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from config import (
PROMPTS_DIR,
WORKSPACE_SKILLS_DIRNAME,
WORKSPACE_MEMORY_DIRNAME,
WORKSPACE_REVIEW_DIRNAME,
TERMINAL_SANDBOX_MODE,
)
from config.paths import IS_HOST_MODE
def _load_agents_md(workspace_path: str) -> Optional[str]:
"""加载工作区根目录的 AGENTS.md 文件内容。"""
try:
project_path = Path(workspace_path)
if not project_path.exists():
return None
agents_md_files = list(project_path.rglob("AGENTS.md"))
if not agents_md_files:
return None
latest_file = max(agents_md_files, key=lambda p: p.stat().st_mtime)
content = latest_file.read_text(encoding="utf-8")
return content.strip() if content else None
except Exception:
return None
def _build_execution_env_section(workspace_path: str, *, sandbox_mode: str = "") -> str:
"""构建执行环境信息段(精简版,只含宿主机/docker + 沙箱/直接)。"""
mode = (sandbox_mode or TERMINAL_SANDBOX_MODE or "").lower()
if IS_HOST_MODE:
runtime_label = "宿主机模式"
# host 模式下区分 sandbox / direct
if mode == "direct":
exec_label = "直接执行(完全访问权限)"
rules = (
"- 当前为宿主机直接执行模式\n"
"- 仅在必须时执行高权限操作,保持最小化命令范围\n"
"- 涉及删除/覆盖/系统级变更前,先说明风险再执行"
)
else:
exec_label = "沙箱执行"
rules = (
"- 所有命令默认在系统 OS 沙箱中执行\n"
"- 若操作受系统权限限制:先提供安全替代方案;若仍无法满足,说明需要更高权限\n"
"- 不要通过复杂绕过手段规避沙箱限制"
)
else:
runtime_label = "Docker 容器模式"
exec_label = "容器内执行"
rules = (
"- 所有命令在 Docker 容器内执行\n"
"- 工作区挂载在容器内 /workspace 路径下\n"
"- 网络可能受限,仅允许 localhost"
)
return (
f"## 执行环境\n\n"
f"- 运行环境:{runtime_label}\n"
f"- 执行方式:{exec_label}\n\n"
f"### 当前规则\n{rules}\n"
)
def _build_workspace_section(workspace_path: str) -> str:
"""构建工作区信息段(精简版,不含 file_tree 和 recent_uploads"""
project_path = Path(workspace_path)
project_name = project_path.name or "未命名项目"
system_info = f"{platform.system()} {platform.release()}"
current_time = datetime.now().strftime("%Y年%m月%d%H点24小时制")
if IS_HOST_MODE:
resource_limit = "宿主机模式无限制"
else:
resource_limit = "容器模式,受 Docker 资源限制"
return (
f"## 工作区信息\n\n"
f"- 项目名称:{project_name}\n"
f"- 运行环境:{system_info}\n"
f"- 资源限制:{resource_limit}\n"
f"- 当前时间:{current_time}\n"
)
def _build_astrion_dir_section(workspace_path: str) -> str:
"""构建 .astrion/ 目录结构说明段。"""
return (
f"## 工作区数据目录(.astrion/\n\n"
f"工作区内的 `.astrion/` 目录存储以下内容:\n"
f"- `{WORKSPACE_SKILLS_DIRNAME}/`可用技能skill目录每个 skill 含 SKILL.md 说明\n"
f"- `{WORKSPACE_MEMORY_DIRNAME}/`:项目记忆,以 Markdown 文件形式存放项目约定/决策/调试坑\n"
f"- `{WORKSPACE_REVIEW_DIRNAME}/`:对话回顾文件\n"
f"- `user_upload/`:用户上传的文件\n\n"
f"用户上传的文件位于 `.astrion/user_upload/` 目录下。"
f"项目记忆以文件形式存放在 `{WORKSPACE_MEMORY_DIRNAME}/` 下。"
f"技能skill存放在 `{WORKSPACE_SKILLS_DIRNAME}/` 下。\n"
)
def _build_agents_md_section(workspace_path: str) -> str:
"""构建 AGENTS.md 项目规范段。"""
content = _load_agents_md(workspace_path)
if not content:
return ""
return (
f"## AGENTS.md 项目规范\n\n"
f"> 以下是工作区根目录的 AGENTS.md 文件内容,请在回答时参考这些项目规范:\n\n"
f"{content}\n\n"
f"---\n"
f"注意:以上规范来自工作区根目录的 AGENTS.md 文件,若与未来代码冲突,以实际代码为准。\n"
)
def _build_skills_section(workspace_path: str, data_dir: str = "") -> str:
"""构建可用 skill 列表段。"""
try:
from modules.skills_manager import (
get_skills_catalog,
build_skills_list,
infer_private_skills_dir,
)
private_dir = infer_private_skills_dir(data_dir) if data_dir else None
catalog = get_skills_catalog(private_dir=private_dir)
# 子智能体不区分 enabled/disabled列出全部可用 skill
skills_list = build_skills_list(catalog, enabled=None)
if not skills_list:
return ""
return (
f"## 可用 AgentSkill\n\n"
f"agent skills 系统已启用,以下是可用的 skills含简要说明\n\n"
f"{skills_list}\n\n"
f"使用技能时,优先用 read_skill 通过技能名读取 SKILL.md"
f"需要阅读 skill 目录中的其他文件时再使用 read_file\n"
)
except Exception:
return ""
def _build_project_memory_section(workspace_path: str) -> str:
"""构建项目记忆索引段(只列索引,不展开内容)。"""
try:
memory_dir = Path(workspace_path) / WORKSPACE_MEMORY_DIRNAME
if not memory_dir.exists() or not memory_dir.is_dir():
return ""
entries: List[str] = []
for md_file in sorted(memory_dir.glob("*.md")):
try:
text = md_file.read_text(encoding="utf-8")
name = None
description = None
if text.startswith("---"):
end = text.find("---", 3)
if end > 0:
frontmatter = text[3:end]
for line in frontmatter.strip().split("\n"):
ls = line.strip()
if ls.startswith("name:"):
name = ls.split(":", 1)[1].strip()
elif ls.startswith("description:"):
description = ls.split(":", 1)[1].strip()
file_name = md_file.name
display_name = name or md_file.stem
desc = description or ""
if desc:
entries.append(f".astrion/memory/{file_name}{desc}")
else:
entries.append(f".astrion/memory/{file_name}:当需要{display_name}时,应该索引本记忆")
except Exception:
pass
if not entries:
return ""
return (
f"## 项目记忆\n\n"
f"项目记忆以文件形式存放在 `.astrion/memory/` 下。\n"
f"开始涉及项目约定、历史决策或已知问题的任务前,先检查项目记忆索引,"
f"不确定时先用 search_project_memory 检索正文,定位后用 recall_project_memory 读取。\n\n"
f"### 项目记忆索引\n\n"
+ "\n".join(entries)
+ "\n"
)
except Exception:
return ""
def build_sub_agent_dynamic_context(
workspace_path: str,
*,
data_dir: str = "",
sandbox_mode: str = "",
) -> str:
"""构建子智能体的动态上下文信息(拼接到 system prompt 末尾)。
所有信息在子智能体创建那一刻快照,后续冻结不更新。
Args:
workspace_path: 工作区路径
data_dir: 数据目录路径(用于推断 private skills 目录)
sandbox_mode: 沙箱模式sandbox/direct/host/docker
"""
sections: List[str] = []
# 1. 执行环境
sections.append(_build_execution_env_section(workspace_path, sandbox_mode=sandbox_mode))
# 2. 工作区信息(精简版)
sections.append(_build_workspace_section(workspace_path))
# 3. .astrion/ 目录说明
sections.append(_build_astrion_dir_section(workspace_path))
# 4. AGENTS.md
agents_md = _build_agents_md_section(workspace_path)
if agents_md:
sections.append(agents_md)
# 5. 可用 skill
skills = _build_skills_section(workspace_path, data_dir=data_dir)
if skills:
sections.append(skills)
# 6. 项目记忆索引
memory = _build_project_memory_section(workspace_path)
if memory:
sections.append(memory)
return "\n".join(sections)