mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 09:28:34 +00:00
fix(agent): render ask_user options without buttons
Made-with: Cursor
This commit is contained in:
parent
cfc76ffbbf
commit
3b1ea99ee1
@ -54,6 +54,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
UNIFIED_SESSION_KEY = "unified:default"
|
UNIFIED_SESSION_KEY = "unified:default"
|
||||||
|
BUTTON_CHANNELS = frozenset({"telegram"})
|
||||||
|
|
||||||
|
|
||||||
class _LoopHook(AgentHook):
|
class _LoopHook(AgentHook):
|
||||||
@ -459,6 +460,19 @@ class AgentLoop:
|
|||||||
return [str(option) for option in options if isinstance(option, str)]
|
return [str(option) for option in options if isinstance(option, str)]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ask_user_outbound(
|
||||||
|
content: str | None,
|
||||||
|
options: list[str],
|
||||||
|
channel: str,
|
||||||
|
) -> tuple[str | None, list[list[str]]]:
|
||||||
|
if not options:
|
||||||
|
return content, []
|
||||||
|
if channel in BUTTON_CHANNELS:
|
||||||
|
return content, [options]
|
||||||
|
option_text = "\n".join(f"{index}. {option}" for index, option in enumerate(options, 1))
|
||||||
|
return f"{content}\n\n{option_text}" if content else option_text, []
|
||||||
|
|
||||||
async def _run_agent_loop(
|
async def _run_agent_loop(
|
||||||
self,
|
self,
|
||||||
initial_messages: list[dict],
|
initial_messages: list[dict],
|
||||||
@ -861,11 +875,16 @@ class AgentLoop:
|
|||||||
self.sessions.save(session)
|
self.sessions.save(session)
|
||||||
self._schedule_background(self.consolidator.maybe_consolidate_by_tokens(session))
|
self._schedule_background(self.consolidator.maybe_consolidate_by_tokens(session))
|
||||||
options = self._ask_user_options_from_messages(all_msgs) if stop_reason == "ask_user" else []
|
options = self._ask_user_options_from_messages(all_msgs) if stop_reason == "ask_user" else []
|
||||||
|
content, buttons = self._ask_user_outbound(
|
||||||
|
final_content or "Background task completed.",
|
||||||
|
options,
|
||||||
|
channel,
|
||||||
|
)
|
||||||
return OutboundMessage(
|
return OutboundMessage(
|
||||||
channel=channel,
|
channel=channel,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
content=final_content or "Background task completed.",
|
content=content,
|
||||||
buttons=[options] if options else [],
|
buttons=buttons,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract document text from media at the processing boundary so all
|
# Extract document text from media at the processing boundary so all
|
||||||
@ -1011,11 +1030,11 @@ class AgentLoop:
|
|||||||
logger.info("Response to {}:{}: {}", msg.channel, msg.sender_id, preview)
|
logger.info("Response to {}:{}: {}", msg.channel, msg.sender_id, preview)
|
||||||
|
|
||||||
meta = dict(msg.metadata or {})
|
meta = dict(msg.metadata or {})
|
||||||
buttons: list[list[str]] = []
|
final_content, buttons = self._ask_user_outbound(
|
||||||
if stop_reason == "ask_user":
|
final_content,
|
||||||
options = self._ask_user_options_from_messages(all_msgs)
|
self._ask_user_options_from_messages(all_msgs) if stop_reason == "ask_user" else [],
|
||||||
if options:
|
msg.channel,
|
||||||
buttons = [options]
|
)
|
||||||
if on_stream is not None and stop_reason != "error":
|
if on_stream is not None and stop_reason != "error":
|
||||||
meta["_streamed"] = True
|
meta["_streamed"] = True
|
||||||
return OutboundMessage(
|
return OutboundMessage(
|
||||||
|
|||||||
@ -93,7 +93,7 @@ async def test_runner_pauses_on_ask_user_without_executing_later_tools():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_ask_user_sends_buttons_and_resumes_with_next_message(tmp_path):
|
async def test_ask_user_text_fallback_resumes_with_next_message(tmp_path):
|
||||||
seen_messages: list[list[dict]] = []
|
seen_messages: list[list[dict]] = []
|
||||||
|
|
||||||
async def chat_with_retry(**kwargs):
|
async def chat_with_retry(**kwargs):
|
||||||
@ -127,8 +127,8 @@ async def test_ask_user_sends_buttons_and_resumes_with_next_message(tmp_path):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert first is not None
|
assert first is not None
|
||||||
assert first.content == "Install the optional package?"
|
assert first.content == "Install the optional package?\n\n1. Install\n2. Skip"
|
||||||
assert first.buttons == [["Install", "Skip"]]
|
assert first.buttons == []
|
||||||
|
|
||||||
session = loop.sessions.get_or_create("cli:direct")
|
session = loop.sessions.get_or_create("cli:direct")
|
||||||
assert any(message.get("role") == "assistant" and message.get("tool_calls") for message in session.messages)
|
assert any(message.get("role") == "assistant" and message.get("tool_calls") for message in session.messages)
|
||||||
@ -156,3 +156,37 @@ async def test_ask_user_sends_buttons_and_resumes_with_next_message(tmp_path):
|
|||||||
and message.get("content") == "Skip"
|
and message.get("content") == "Skip"
|
||||||
for message in session.messages
|
for message in session.messages
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ask_user_keeps_buttons_for_telegram(tmp_path):
|
||||||
|
async def chat_with_retry(**kwargs):
|
||||||
|
return LLMResponse(
|
||||||
|
content="",
|
||||||
|
finish_reason="tool_calls",
|
||||||
|
tool_calls=[
|
||||||
|
ToolCallRequest(
|
||||||
|
id="call_ask",
|
||||||
|
name="ask_user",
|
||||||
|
arguments={
|
||||||
|
"question": "Install the optional package?",
|
||||||
|
"options": ["Install", "Skip"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
loop = AgentLoop(
|
||||||
|
bus=MessageBus(),
|
||||||
|
provider=_make_provider(chat_with_retry),
|
||||||
|
workspace=tmp_path,
|
||||||
|
model="test-model",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await loop._process_message(
|
||||||
|
InboundMessage(channel="telegram", sender_id="user", chat_id="123", content="set it up")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response is not None
|
||||||
|
assert response.content == "Install the optional package?"
|
||||||
|
assert response.buttons == [["Install", "Skip"]]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user