a8c9d25857
- L0 wiki-rule: 扩充检索路径(practices/concepts/docs/design/)+ 检索方式(index→summary→grep→full) - L1 SOUL.md: 同步测试 + PR 审查(代码改动检查设计文档+测试脚本,PR/CI/CD 三重把关) - L1 AGENTS.md: 新增测试规范段(生产隔离/残留清理/测试开发分离) - L2 prompt_composer: 新增 DeliveryChecklistSection(executor/mail/toolchain handler 注册) - 456 passed, 0 failed
201 lines
7.0 KiB
Python
201 lines
7.0 KiB
Python
"""
|
||
prompt_composer.py — PromptSection Protocol + PromptContext + PromptComposer
|
||
|
||
拼装器:有序管理 prompt 段落,按优先级排序后合并为最终 prompt。
|
||
"""
|
||
|
||
import logging
|
||
from dataclasses import dataclass, field
|
||
from typing import Dict, List, Optional, Protocol, runtime_checkable
|
||
|
||
logger = logging.getLogger("moziplus-v2.prompt_composer")
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Section 优先级范围约定
|
||
# ---------------------------------------------------------------------------
|
||
PRIORITY_CONTEXT = 10 # 任务上下文
|
||
PRIORITY_PRIOR = 20 # 前序信息
|
||
PRIORITY_ROLE = 30 # 角色规范
|
||
PRIORITY_API = 40 # API 操作指令
|
||
PRIORITY_CONSTRAINTS = 50 # 硬约束
|
||
PRIORITY_EXTENSION = 60 # 扩展段
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# PromptSection Protocol
|
||
# ---------------------------------------------------------------------------
|
||
@runtime_checkable
|
||
class PromptSection(Protocol):
|
||
"""一个 prompt 段"""
|
||
|
||
name: str # 段名(去重用,同名覆盖)
|
||
priority: int # 排序优先级(小数字=靠前)
|
||
|
||
def render(self, context: "PromptContext") -> str:
|
||
"""渲染此段的文本内容。返回空字符串表示不注入。"""
|
||
...
|
||
|
||
def should_include(self, context: "PromptContext") -> bool:
|
||
"""是否注入此段(默认 True,条件段可覆盖)。"""
|
||
...
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# PromptContext 数据对象
|
||
# ---------------------------------------------------------------------------
|
||
@dataclass
|
||
class PromptContext:
|
||
"""Prompt 渲染的统一上下文"""
|
||
|
||
task_id: str
|
||
title: str
|
||
description: str
|
||
must_haves: str
|
||
project_id: str
|
||
agent_id: str
|
||
|
||
task: Optional[Dict] = None
|
||
role: str = "executor"
|
||
spawn_type: str = "executor"
|
||
|
||
# mail 专用
|
||
from_agent: str = ""
|
||
mail_type: str = "" # inform / request
|
||
|
||
# toolchain 专用
|
||
event_type: str = "" # ci_failure / review_request / ...
|
||
event_data: Dict = field(default_factory=dict)
|
||
action_type: str = "" # 动作分类(review_result / ci_failure / ...)
|
||
action_steps: list = field(default_factory=list) # 结构化编号步骤列表
|
||
|
||
# 前序产出
|
||
depends_on_outputs: Optional[List] = None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# PromptComposer 拼装器
|
||
# ---------------------------------------------------------------------------
|
||
class PromptComposer:
|
||
"""有序拼装 prompt sections"""
|
||
|
||
SEPARATOR = "\n\n---\n\n"
|
||
TOKEN_BUDGET_WARN = 800 # token 预算警告阈值
|
||
CHARS_PER_TOKEN = 3.5 # 估算比率
|
||
|
||
def __init__(self) -> None:
|
||
self._sections: List[PromptSection] = []
|
||
|
||
def add(self, section: PromptSection) -> None:
|
||
"""添加一个 section(同名覆盖)"""
|
||
self._sections = [s for s in self._sections if s.name != section.name]
|
||
self._sections.append(section)
|
||
|
||
def add_many(self, sections: List[PromptSection]) -> None:
|
||
"""批量添加"""
|
||
for s in sections:
|
||
self.add(s)
|
||
|
||
def compose(self, context: PromptContext) -> str:
|
||
"""拼装最终 prompt
|
||
|
||
1. 过滤 should_include=False 的段
|
||
2. 按 priority 排序
|
||
3. 逐段 render
|
||
4. 过滤空段
|
||
5. 用分隔符连接
|
||
6. Token 预算警告(不截断)
|
||
"""
|
||
active = [s for s in self._sections if s.should_include(context)]
|
||
active.sort(key=lambda s: s.priority)
|
||
|
||
parts = [s.render(context) for s in active]
|
||
parts = [p for p in parts if p.strip()]
|
||
|
||
result = self.SEPARATOR.join(parts)
|
||
|
||
# Token 估算
|
||
tokens = max(1, int(len(result) / self.CHARS_PER_TOKEN))
|
||
logger.debug(
|
||
"Composed prompt from %d sections, %d tokens",
|
||
len(parts), tokens,
|
||
)
|
||
|
||
if tokens > self.TOKEN_BUDGET_WARN:
|
||
logger.warning(
|
||
"Prompt exceeds %d token budget: %d tokens (task_id=%s)",
|
||
self.TOKEN_BUDGET_WARN, tokens, context.task_id,
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
class GiteaConventionSection:
|
||
"""Gitea 标题规范引导段 — 提醒 Agent 创建 Issue/PR 时遵循标题格式。"""
|
||
|
||
name: str = "gitea_convention"
|
||
priority: int = 55 # CONSTRAINTS(50) 和 EXTENSION(60) 之间
|
||
|
||
CONVENTION_TEXT = (
|
||
"## Gitea 标题规范\n"
|
||
"创建 Issue/PR 时,标题**必须**包含项目代号前缀:\n"
|
||
"- Issue: `[代号] type: 简述`,如 `[moz] bug: Mail API 500`\n"
|
||
"- PR: `[代号] type(scope): 简述`,如 `[moz] impl(daemon): WikiGuideSection 注入`\n"
|
||
"代号:moz=moziplus_v2, quant=quant_live, vnpy=vnpy\n"
|
||
"type: bug/feat/impl/fix/docs/test/ci/refactor/chore"
|
||
)
|
||
|
||
def render(self, context: "PromptContext") -> str:
|
||
return self.CONVENTION_TEXT
|
||
|
||
def should_include(self, context: "PromptContext") -> bool:
|
||
return True
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# WikiGuideSection — 知识查询引导段
|
||
# ---------------------------------------------------------------------------
|
||
class WikiGuideSection:
|
||
"""知识查询引导段 — 引导 Agent 在关键决策点查 wiki-vault。"""
|
||
|
||
name: str = "wiki_guide"
|
||
priority: int = 60 # PRIORITY_EXTENSION
|
||
|
||
WIKI_GUIDE = (
|
||
"## 知识查询引导\n"
|
||
"涉及方案设计、编码实现、故障排查时,先查 wiki-vault 相关实践:\n"
|
||
"- 路径:/Volumes/KnowledgeBase/wiki-vault/\n"
|
||
"- 速查:index.md → grep 关键词 → summary 字段 → 按需读全文\n"
|
||
"- 查不到:在 _meta/knowledge-gaps.md 记录"
|
||
)
|
||
|
||
def render(self, context: "PromptContext") -> str:
|
||
return self.WIKI_GUIDE
|
||
|
||
def should_include(self, context: "PromptContext") -> bool:
|
||
return True
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# DeliveryChecklistSection — 交付检查清单
|
||
# ---------------------------------------------------------------------------
|
||
class DeliveryChecklistSection:
|
||
"""交付检查清单 — 提醒 Agent 完成前同步关联成果物。"""
|
||
|
||
name: str = "delivery_checklist"
|
||
priority: int = 55 # CONSTRAINTS(50) 和 EXTENSION(60) 之间
|
||
|
||
CHECKLIST_TEXT = (
|
||
"## 交付检查\n"
|
||
"完成代码改动前确认:\n"
|
||
"- 改了实现 → docs/design/ 对应设计文档是否需要更新\n"
|
||
"- 改了实现 → tests/ 是否有对应测试脚本需要更新\n"
|
||
"- 所有成果物变更通过 PR 流程:PR review 把关设计合理性,CI 把关代码质量,CD 把关部署正确性\n"
|
||
)
|
||
|
||
def render(self, context: "PromptContext") -> str:
|
||
return self.CHECKLIST_TEXT
|
||
|
||
def should_include(self, context: "PromptContext") -> bool:
|
||
return True
|