zcbot/tools/ask_user.py

75 lines
3.1 KiB
Python

"""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=(",", ":"),
)