初始化

This commit is contained in:
gozeros committed 2026-09-07 18:24:55 +08:00
1 parent 34474bd5fc
commit b638b541d0
3008 files changed
+837699

No files matched your search

@@ -0,0 +1,137 @@
"""会商第二步席位「技能识别」强化(弱模型友好)。
痛点:席位是一次自定义 agent run,其配置技能(agent ``config.yaml → skills``)只通过
**系统提示**里的 ``<skill_system>`` 块注入(列出 name/description + SKILL.md 路径,靠模型
自己 ``read_file`` 并遵循)。强模型会照做;**弱模型(如 deepseek-chat)往往无视系统提示里
的技能块,从不 read_file SKILL.md**,于是配置的技能形同虚设——只凭模型自身知识作答。
解法:在**派给席位的本轮任务消息(human turn)**里,显式把该席位配置的每个技能(名称 +
描述 + SKILL.md 容器路径)列出来,并下达硬指令「先 read_file 对应 SKILL.md、按其说明用工具
去取信息 / 走流程,再产出交付」。把指令放进 user turn(而非仅系统提示)对弱模型的牵引力
强得多——这与本仓已有的弱模型补偿手法(``_SEAT_NO_ASK``、总控派活强化、AI 写作 SKILL.md
注入,见 ``project-ai-writing-skill-agent``)一脉相承。
**默认关闭、按需开启(2026-06-20 改造)**:不再全域生效。是否对某席位注入由两个开关
取 **OR** 决定——
1. **智能体级**:该席位 agent 的 ``config.yaml → seat_skill_directive``(智能体编辑页
可开关);
2. **业务链条级**:本次会商所选业务链条的同名开关,由调用方读出后以 ``chain_enabled``
传入(前端手动与后台挂起两条路径都按席位透传)。
任一为真即注入;两者都假 → 返回空串、原样使用任务文本(零副作用)。
两条席位执行路径共用本 helper:
- 前端手动:``multi_agent._special_run``(seat / seat_parallel 角色);
- 后台挂起:``roundtable_inprocess_gateway.InProcessRoundtableGateway.run_seat``。
``ROUNDTABLE_SEAT_SKILL_DIRECTIVE`` 仅作**总开关(kill-switch)**:默认放行,显式设为
0/false 时彻底关闭本功能(不论两个开关如何),用于线上一键熔断。它**不再** force-on。
"""
from __future__ import annotations
import logging
import os
logger = logging.getLogger(__name__)
_TRUTHY = frozenset({"1", "true", "yes", "on", "y"})
_FALSY = frozenset({"0", "false", "no", "off", "n"})
# 编排能力是圆桌总控的专属能力。两种拼写分别对应内置 tool 名和历史 custom skill
# 目录名;席位侧无论 agent 配置或业务链条开关如何,都不得把它们暴露进技能强化提示。
COORDINATOR_ONLY_SKILL_NAMES = frozenset({"agent_orchestration", "agent-orchestration"})
_SEAT_ROLE_BOUNDARY = (
"【圆桌席位角色边界(最高优先级)】你是负责完成具体任务的执行席位,不是总控智能体。"
"编排、派活和调度其它智能体的能力只允许总控智能体使用。"
"你不得加载、读取或调用 agent_orchestration / agent-orchestration,"
"不得向其它智能体派活,也不要声称准备使用编排技能;请直接完成上方交给你的具体任务并提交结果。"
)
def append_seat_role_boundary(task: str) -> str:
"""在席位本轮 human 消息末尾追加不可越权的角色边界。"""
return f"{task}\n\n---\n\n{_SEAT_ROLE_BOUNDARY}"
def _master_enabled() -> bool:
"""``ROUNDTABLE_SEAT_SKILL_DIRECTIVE`` 总开关:默认放行(kill-switch)。
空 / 未设 / 非法 = 放行;仅显式 0/false/no/off/n 时整体关闭。注意它**只熔断不强开**,
具体是否注入仍由 agent / chain 两个开关取 OR 决定。
"""
return (os.getenv("ROUNDTABLE_SEAT_SKILL_DIRECTIVE") or "").strip().lower() not in _FALSY
def build_seat_skill_directive(agent_id: str | None, *, chain_enabled: bool = False) -> str:
"""为某席位 agent 构建「必须先读 SKILL.md 再用技能」的本轮任务追加指令。
是否注入由 **agent 开关(config.yaml)OR chain 开关(``chain_enabled``)** 决定,
默认两者皆关 → 不注入。返回 ``""`` 表示无需追加(总开关熔断 / 两开关都关 / 无 agent_id /
该席位没有配置技能 / 解析失败),调用方原样使用任务文本,零副作用。
"""
if not _master_enabled() or not agent_id:
return ""
try:
from deerflow.config.agents_config import load_agent_config
cfg = load_agent_config(agent_id)
# agent 级开关 OR chain 级开关(默认关)。任一打开才继续,否则零副作用返回。
agent_enabled = bool(cfg and cfg.seat_skill_directive)
if not (agent_enabled or chain_enabled):
return ""
configured = [
name
for name in (cfg.skills or [])
if name and name.strip().lower() not in COORDINATOR_ONLY_SKILL_NAMES
] if cfg else []
if not configured:
return ""
from deerflow.skills.storage import get_or_new_skill_storage
try:
from deerflow.config import get_app_config
container_base_path = get_app_config().skills.container_path
except Exception: # noqa: BLE001 — 配置读不到时退回默认挂载路径
container_base_path = "/mnt/skills"
by_name = {s.name: s for s in get_or_new_skill_storage().load_skills(enabled_only=True)}
# 保留席位 config 里的配置顺序,剔除已删除 / 未启用的技能名。
wanted = [by_name[name] for name in configured if name in by_name]
if not wanted:
return ""
lines: list[str] = [
"【你已配置以下专属技能,必须优先使用——这是硬性要求,不要只凭自身知识作答】",
"完成本轮交付前,请先判断哪些技能与任务相关,**用 read_file 工具读取对应技能的 SKILL.md 全文**,"
"严格按其中的说明(检索方式 / 接口 / 流程 / 输出格式)去**获取真实信息或执行处理流程**"
"(其中可能要求你调用 bash / write_file 等工具),再据此产出交付:",
"",
]
for i, s in enumerate(wanted, 1):
desc = (s.description or "").strip().replace("\n", " ")
lines.append(f"{i}. 「{s.name}」{desc}".rstrip())
lines.append(f" - 技能说明文件(先 read_file 它):{s.get_container_file_path(container_base_path)}")
lines.append("")
lines.append(
"注意:只要某个技能与本轮任务相关,**务必先 read_file 它的 SKILL.md 并按其指引调用工具**,"
"不要跳过技能直接凭记忆回答;技能里写明的接口 / 命令要真正执行,不要假装调用。"
)
return "\n".join(lines)
except Exception: # noqa: BLE001 — 强化失败绝不能拖垮席位本身的执行
logger.debug("build seat skill directive failed for agent %s", agent_id, exc_info=True)
return ""
def append_seat_skill_directive(agent_id: str | None, task: str, *, chain_enabled: bool = False) -> str:
"""把技能识别指令追加到席位本轮任务文本后(无指令时原样返回)。
``chain_enabled``:本次会商所选业务链条是否打开「技能识别强化」。与 agent 级开关取 OR。
"""
directive = build_seat_skill_directive(agent_id, chain_enabled=chain_enabled)
if not directive:
return task
return f"{task}\n\n---\n\n{directive}"