213 lines
7.3 KiB
Python
213 lines
7.3 KiB
Python
"""Bootstrap 拼装 — L0-L3 四层上下文构建
|
||
|
||
L0 铁律层(~500 tokens) → Hook 注入,不占 bootstrap
|
||
L1 角色层(~2000 tokens) → SOUL.md / IDENTITY.md(Agent 自带)
|
||
L2 引擎注入层(~1500 tokens) → prompt_templates 按 role 拼装
|
||
L3 被动参考层(按需加载) → Skill description 四要素
|
||
|
||
Bootstrap 按 role 精确注入:执行者注入 Guardrail + 审查协议,
|
||
审查者注入审查协议,庞统注入审查协议。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import logging
|
||
import re
|
||
from pathlib import Path
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
logger = logging.getLogger("moziplus-v2.bootstrap")
|
||
|
||
# 粗略 token 估算:英文 ~4 chars/token,中文 ~2 chars/token
|
||
CHARS_PER_TOKEN = 3.0
|
||
|
||
|
||
def estimate_tokens(text: str) -> int:
|
||
"""估算文本 token 数"""
|
||
return max(1, int(len(text) / CHARS_PER_TOKEN))
|
||
|
||
|
||
class BootstrapBuilder:
|
||
"""Bootstrap 四层上下文构建器"""
|
||
|
||
def __init__(
|
||
self,
|
||
template_dir: Optional[Path] = None,
|
||
max_tokens: int = 4096,
|
||
):
|
||
"""
|
||
Args:
|
||
template_dir: prompt_templates 目录路径
|
||
max_tokens: bootstrap 最大 token 数
|
||
"""
|
||
self.template_dir = template_dir
|
||
self.max_tokens = max_tokens
|
||
|
||
def build(
|
||
self,
|
||
role: str,
|
||
task_context: Optional[Dict[str, Any]] = None,
|
||
project_context: Optional[Dict[str, Any]] = None,
|
||
guardrail_rules: Optional[str] = None,
|
||
review_protocols: Optional[str] = None,
|
||
experiences: Optional[List[Dict[str, Any]]] = None,
|
||
skill_descriptions: Optional[List[Dict[str, Any]]] = None,
|
||
) -> str:
|
||
"""构建 bootstrap 文本
|
||
|
||
Args:
|
||
role: 角色(executor / reviewer / planner / pangtong)
|
||
task_context: 任务上下文(黑板数据)
|
||
project_context: 项目背景
|
||
guardrail_rules: Guardrail 规则(仅执行者)
|
||
review_protocols: 审查协议(执行者+审查者+庞统)
|
||
experiences: 相关经验列表
|
||
skill_descriptions: Skill 描述列表
|
||
|
||
Returns:
|
||
拼装好的 bootstrap 文本
|
||
"""
|
||
layers: List[str] = []
|
||
|
||
# L2a: 操作规范
|
||
role_template = self._load_template(f"{role}.md")
|
||
if role_template:
|
||
layers.append(role_template)
|
||
|
||
# L2b: 项目背景
|
||
if project_context:
|
||
layers.append(self._format_project_context(project_context))
|
||
|
||
# L2c: 任务上下文
|
||
if task_context:
|
||
layers.append(self._format_task_context(task_context))
|
||
|
||
# L2d: 前序信息(depends_on 产出摘要)
|
||
if task_context and task_context.get("depends_on_outputs"):
|
||
layers.append(self._format_depends_on(task_context["depends_on_outputs"]))
|
||
|
||
# L2e: Guardrail 规则(仅执行者)
|
||
if role == "executor" and guardrail_rules:
|
||
layers.append(guardrail_rules)
|
||
|
||
# L2f: 审查协议(执行者+审查者+庞统)
|
||
if role in ("executor", "reviewer", "pangtong") and review_protocols:
|
||
layers.append(review_protocols)
|
||
|
||
# L2g: 经验注入
|
||
if experiences:
|
||
layers.append(self._format_experiences(experiences))
|
||
|
||
# L3: Skill descriptions
|
||
if skill_descriptions:
|
||
layers.append(self._format_skills(skill_descriptions))
|
||
|
||
bootstrap = "\n\n---\n\n".join(layers)
|
||
|
||
# 检查 token 限制
|
||
tokens = estimate_tokens(bootstrap)
|
||
if tokens > self.max_tokens:
|
||
logger.warning("Bootstrap exceeds token limit: %d > %d, truncating",
|
||
tokens, self.max_tokens)
|
||
bootstrap = self._truncate_to_tokens(bootstrap, self.max_tokens)
|
||
|
||
return bootstrap
|
||
|
||
def _load_template(self, filename: str) -> Optional[str]:
|
||
"""加载 prompt template"""
|
||
if not self.template_dir:
|
||
return None
|
||
path = self.template_dir / filename
|
||
if path.exists():
|
||
return path.read_text(encoding="utf-8")
|
||
return None
|
||
|
||
def _format_project_context(self, ctx: Dict[str, Any]) -> str:
|
||
"""格式化项目背景"""
|
||
parts = ["## 项目背景"]
|
||
if ctx.get("name"):
|
||
parts.append(f"项目: {ctx['name']}")
|
||
if ctx.get("description"):
|
||
parts.append(f"描述: {ctx['description']}")
|
||
if ctx.get("agents"):
|
||
parts.append(f"团队: {', '.join(ctx['agents'])}")
|
||
return "\n".join(parts)
|
||
|
||
def _format_task_context(self, ctx: Dict[str, Any]) -> str:
|
||
"""格式化任务上下文"""
|
||
parts = ["## 任务上下文"]
|
||
if ctx.get("task_id"):
|
||
parts.append(f"任务ID: {ctx['task_id']}")
|
||
if ctx.get("title"):
|
||
parts.append(f"标题: {ctx['title']}")
|
||
if ctx.get("description"):
|
||
parts.append(f"描述: {ctx['description']}")
|
||
if ctx.get("task_type"):
|
||
parts.append(f"类型: {ctx['task_type']}")
|
||
if ctx.get("must_haves"):
|
||
parts.append(f"必须完成: {ctx['must_haves']}")
|
||
if ctx.get("risk_level"):
|
||
parts.append(f"风险级别: {ctx['risk_level']}")
|
||
return "\n".join(parts)
|
||
|
||
def _format_depends_on(self, outputs: List[Dict[str, Any]]) -> str:
|
||
"""格式化前序产出摘要"""
|
||
parts = ["## 前序产出"]
|
||
for out in outputs:
|
||
parts.append(f"- [{out.get('task_id', '?')}] {out.get('summary', '无摘要')}")
|
||
return "\n".join(parts)
|
||
|
||
def _format_experiences(self, experiences: List[Dict[str, Any]]) -> str:
|
||
"""格式化经验注入"""
|
||
parts = ["## 相关经验"]
|
||
for exp in experiences[:5]: # 最多 5 条
|
||
parts.append(f"- [{exp.get('category', '?')}] {exp.get('summary', '')}")
|
||
return "\n".join(parts)
|
||
|
||
def _format_skills(self, skills: List[Dict[str, Any]]) -> str:
|
||
"""格式化 Skill 描述"""
|
||
parts = ["## 可用技能"]
|
||
for skill in skills[:10]:
|
||
parts.append(f"- **{skill.get('name', '?')}**: {skill.get('description', '')}")
|
||
return "\n".join(parts)
|
||
|
||
def _truncate_to_tokens(self, text: str, max_tokens: int) -> str:
|
||
"""截断到指定 token 数"""
|
||
max_chars = int(max_tokens * CHARS_PER_TOKEN)
|
||
if len(text) <= max_chars:
|
||
return text
|
||
return text[:max_chars] + "\n\n[... bootstrap truncated]"
|
||
|
||
def build_for_task(
|
||
self,
|
||
task: Any,
|
||
role: str,
|
||
project_config: Optional[Dict] = None,
|
||
experiences: Optional[List[Dict]] = None,
|
||
) -> str:
|
||
"""从 Task 对象构建 bootstrap(便捷方法)"""
|
||
task_context = {
|
||
"task_id": task.id,
|
||
"title": task.title,
|
||
"description": task.description,
|
||
"task_type": task.task_type,
|
||
"must_haves": task.must_haves,
|
||
"risk_level": task.risk_level,
|
||
}
|
||
|
||
project_context = None
|
||
if project_config:
|
||
project_context = {
|
||
"name": project_config.get("name"),
|
||
"description": project_config.get("description"),
|
||
"agents": project_config.get("agents", []),
|
||
}
|
||
|
||
return self.build(
|
||
role=role,
|
||
task_context=task_context,
|
||
project_context=project_context,
|
||
experiences=experiences,
|
||
)
|