95 lines
4.6 KiB
Python
95 lines
4.6 KiB
Python
"""圆桌各角色「单轮 run 参数」的**单一数据源**。
|
||
|
||
历史问题:前端手动路径(``multi_agent.py`` 的 ``_leader_run`` / ``_special_run``)
|
||
和后台进程内网关(``roundtable_inprocess_gateway.py``)各自硬编码了一份「每角色
|
||
工具裁剪 + thinking + reasoning」,结果**漂移**出差异(后台席位 / 报告少禁了
|
||
``web_search``,能联网搜索而前端不能)。
|
||
|
||
本模块把这套策略收敛成唯一定义,两条路径都从这里取,保证「后台挂起」与「前端
|
||
手动调用」传参**逐字一致**,且以后改一处即可,不再漂移。
|
||
|
||
角色(``role``):
|
||
- ``leader`` —— 总控派活:短决策,关 thinking + low reasoning(首字节快);
|
||
只禁 ``web_search``;并配合 ``SkillStopMiddleware`` 在 ``agent_orchestration``
|
||
调用后停图,便于网关从 AIMessage.tool_calls 解析派活列表。
|
||
- ``seat`` —— 子智能体交付:开 thinking + medium reasoning;禁派活 /
|
||
澄清 / 展示文件 / 看图 / 联网搜索(与前端 ``_special_run`` 一致)。
|
||
- ``report`` —— Step3 报告绘制:在 ``seat`` 基础上**额外禁 ``bash`` /
|
||
``str_replace``**:
|
||
* 禁 ``bash`` → 杜绝它写 Python 脚本再跑出 HTML(脚本产物落在 outputs,
|
||
但前端虚拟预览只抓 ``write_file`` 的 ``args.content``,抓不到脚本产物 →
|
||
预览停在半截 ``write_file``,正是「没写完就去生成 py 文件」的现象);
|
||
* 禁 ``str_replace`` → 强制返工用整文件 ``write_file`` 重写
|
||
(``str_replace`` 没有 ``content`` 字段,会让前端虚拟预览读到 undefined
|
||
而清空沙箱)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
|
||
# ── 每角色基础工具裁剪(唯一定义处)─────────────────────────────────────────────
|
||
#
|
||
# 注意:``seat`` / ``report`` 都包含 ``web_search`` —— 圆桌研讨里联网搜索一律禁用
|
||
# (与前端 ``_special_run`` 的 excluded_tools 对齐)。leader 也禁 ``web_search``。
|
||
_LEADER_EXCLUDED: list[str] = ["web_search"]
|
||
_SEAT_EXCLUDED: list[str] = ["web_search", "ask_clarification", "present_files", "view_image", "agent_orchestration"]
|
||
_REPORT_EXCLUDED: list[str] = [*_SEAT_EXCLUDED, "bash", "str_replace"]
|
||
|
||
# leader 内禀的 skill_stop:``agent_orchestration`` 调用后停图。调用方可在此基础上
|
||
# 再叠加自己的 skill_stop(见 ``merge_skill_stop_names``)。
|
||
_LEADER_SKILL_STOP: list[str] = ["agent_orchestration"]
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RoundtableRunPolicy:
|
||
"""一个角色单轮 run 的归一化参数。
|
||
|
||
与 ``multi_agent._run_payload`` 的入参一一对应:``excluded_tools`` /
|
||
``skill_stop_names`` / ``thinking_enabled`` / ``reasoning_effort``。
|
||
"""
|
||
|
||
excluded_tools: list[str]
|
||
skill_stop_names: list[str]
|
||
thinking_enabled: bool
|
||
reasoning_effort: str
|
||
|
||
|
||
def roundtable_run_policy(role: str) -> RoundtableRunPolicy:
|
||
"""返回某角色(``leader`` / ``seat`` / ``report``)的单轮 run 参数。
|
||
|
||
返回值里的列表是**新副本**,调用方可安全地再 append/merge 而不污染本模块常量。
|
||
未知角色抛 ``ValueError``(早失败,避免悄悄退化到错误的工具面)。
|
||
"""
|
||
if role == "leader":
|
||
return RoundtableRunPolicy(
|
||
excluded_tools=list(_LEADER_EXCLUDED),
|
||
skill_stop_names=list(_LEADER_SKILL_STOP),
|
||
thinking_enabled=False,
|
||
reasoning_effort="low",
|
||
)
|
||
if role == "seat":
|
||
return RoundtableRunPolicy(
|
||
excluded_tools=list(_SEAT_EXCLUDED),
|
||
skill_stop_names=[],
|
||
thinking_enabled=True,
|
||
reasoning_effort="medium",
|
||
)
|
||
if role == "report":
|
||
return RoundtableRunPolicy(
|
||
excluded_tools=list(_REPORT_EXCLUDED),
|
||
skill_stop_names=[],
|
||
thinking_enabled=True,
|
||
reasoning_effort="medium",
|
||
)
|
||
raise ValueError(f"unknown roundtable role: {role!r} (expected leader/seat/report)")
|
||
|
||
|
||
def merge_skill_stop_names(policy_names: list[str], caller_names: list[str] | None) -> list[str]:
|
||
"""把角色内禀 skill_stop 与调用方追加的 skill_stop 合并去重(保序)。
|
||
|
||
取代历史上 ``list({"agent_orchestration", *skill_stop_names})`` 的 set 写法
|
||
(set 顺序不确定);这里用 dict.fromkeys 保序去重,结果确定、便于断言。
|
||
"""
|
||
return list(dict.fromkeys([*policy_names, *(caller_names or [])]))
|