"""UI-only "ask the user to pick a branch" tool. 收窄定位:**方案/分支确认**,不是通用提问器。模型在「需要用户在 2-4 个互斥方向 间拍板、且该选择会实质改变后续动作」时调用,前端把 question + options 渲成一组可点击 选项卡。用户点某项即作为其回复继续,也可不点直接用文字讨论。 与 task_progress 同属「虚拟工具」:结果体极小(只是占位),真正给前端用的内容全在 assistant tool_call 的 arguments 里(question/options),供 Web 渲染。loop 检测到本步 调用了本工具就提前结束本轮,等用户响应(见 core/loop.py)。 """ from __future__ import annotations import json from typing import Any from .base import Tool class AskUserTool(Tool): name = "ask_user" description = ( "在需要用户在 2-4 个互斥方向间拍板、且该选择会实质改变你接下来动作时,展示一组可点击" "选项让用户选。典型:确认实施方案、在多条候选路线里选一条、在明确的取舍间二选一。" "用户点某个选项即作为其回复继续,也可不点直接用文字与你讨论。" "不要用于:信息缺失的开放性提问(直接用文字问)、你能自己合理默认就推进的决定、" "单纯的是/否确认、给用户播报进度。每轮最多用一次,且只在真正的分叉点用;" "调用本工具后你的发言即结束、等待用户,不要再继续动作。" ) parameters = { "type": "object", "additionalProperties": False, "properties": { "question": { "type": "string", "description": "要用户拍板的问题,一句话讲清在选什么。", }, "options": { "type": "array", "description": "2-4 个互斥选项。", "minItems": 2, "maxItems": 4, "items": { "type": "object", "additionalProperties": False, "properties": { "label": { "type": "string", "description": "选项短标题,用户点击后即作为其回复原文发出,写成可直接当回复的一句话。", }, "description": { "type": "string", "description": "可选,一句话说明该选项的取舍 / 后果。", }, }, "required": ["label"], }, }, }, "required": ["question", "options"], } def execute(self, **kwargs: Any) -> str: options = kwargs.get("options") n = len(options) if isinstance(options, list) else 0 return json.dumps( { "ok": True, "shown": True, "n_options": n, "note": "已向用户展示选项,等待其点选或继续讨论。", }, ensure_ascii=False, separators=(",", ":"), )