agent: use ContextVar for tool routing context

This commit is contained in:
jr_blue_551
2026-04-21 13:25:30 +08:00
committed by Xubin Ren
parent 82aa9efc02
commit ff8c28d5a8
4 changed files with 150 additions and 29 deletions
+28 -13
View File
@@ -1,5 +1,6 @@
"""Message tool for sending messages to users."""
from contextvars import ContextVar
from typing import Any, Awaitable, Callable
from nanobot.agent.tools.base import Tool, tool_parameters
@@ -30,16 +31,19 @@ class MessageTool(Tool):
default_message_id: str | None = None,
):
self._send_callback = send_callback
self._default_channel = default_channel
self._default_chat_id = default_chat_id
self._default_message_id = default_message_id
self._sent_in_turn: bool = False
self._default_channel: ContextVar[str] = ContextVar("message_default_channel", default=default_channel)
self._default_chat_id: ContextVar[str] = ContextVar("message_default_chat_id", default=default_chat_id)
self._default_message_id: ContextVar[str | None] = ContextVar(
"message_default_message_id",
default=default_message_id,
)
self._sent_in_turn_var: ContextVar[bool] = ContextVar("message_sent_in_turn", default=False)
def set_context(self, channel: str, chat_id: str, message_id: str | None = None) -> None:
"""Set the current message context."""
self._default_channel = channel
self._default_chat_id = chat_id
self._default_message_id = message_id
self._default_channel.set(channel)
self._default_chat_id.set(chat_id)
self._default_message_id.set(message_id)
def set_send_callback(self, callback: Callable[[OutboundMessage], Awaitable[None]]) -> None:
"""Set the callback for sending messages."""
@@ -49,6 +53,14 @@ class MessageTool(Tool):
"""Reset per-turn send tracking."""
self._sent_in_turn = False
@property
def _sent_in_turn(self) -> bool:
return self._sent_in_turn_var.get()
@_sent_in_turn.setter
def _sent_in_turn(self, value: bool) -> None:
self._sent_in_turn_var.set(value)
@property
def name(self) -> str:
return "message"
@@ -73,16 +85,19 @@ class MessageTool(Tool):
) -> str:
from nanobot.utils.helpers import strip_think
content = strip_think(content)
channel = channel or self._default_channel
chat_id = chat_id or self._default_chat_id
default_channel = self._default_channel.get()
default_chat_id = self._default_chat_id.get()
channel = channel or default_channel
chat_id = chat_id or default_chat_id
# Only inherit default message_id when targeting the same channel+chat.
# Cross-chat sends must not carry the original message_id, because
# some channels (e.g. Feishu) use it to determine the target
# conversation via their Reply API, which would route the message
# to the wrong chat entirely.
if channel == self._default_channel and chat_id == self._default_chat_id:
message_id = message_id or self._default_message_id
if channel == default_channel and chat_id == default_chat_id:
message_id = message_id or self._default_message_id.get()
else:
message_id = None
@@ -104,7 +119,7 @@ class MessageTool(Tool):
try:
await self._send_callback(msg)
if channel == self._default_channel and chat_id == self._default_chat_id:
if channel == default_channel and chat_id == default_chat_id:
self._sent_in_turn = True
media_info = f" with {len(media)} attachments" if media else ""
return f"Message sent to {channel}:{chat_id}{media_info}"