refactor: consolidate bot event routing

This commit is contained in:
Junyan Qin
2026-07-01 21:04:21 +08:00
parent dff260bbd5
commit b33629b571
26 changed files with 427 additions and 370 deletions
@@ -3,7 +3,7 @@
All user-facing URLs are keyed by **bot_uuid** (not pipeline_uuid) so that
internal pipeline identifiers are never exposed to end-users. Each handler
resolves the bot_uuid to the owning ``web_page_bot`` RuntimeBot and extracts
the bound pipeline_uuid for internal routing.
the message event route's Pipeline target for internal routing.
"""
import asyncio
@@ -62,16 +62,17 @@ class EmbedRouterGroup(group.RouterGroup):
"""Resolve *bot_uuid* to ``(runtime_bot, pipeline_uuid)``.
Returns ``(None, None)`` when the bot does not exist, is not a
``web_page_bot``, is disabled, or has no pipeline bound.
``web_page_bot``, is disabled, or has no Pipeline target for messages.
"""
for bot in self.ap.platform_mgr.bots:
pipeline_uuid = bot.get_pipeline_target_for_event_type('message.received')
if (
bot.bot_entity.uuid == bot_uuid
and bot.bot_entity.adapter == 'web_page_bot'
and bot.bot_entity.enable
and bot.bot_entity.use_pipeline_uuid
and pipeline_uuid
):
return bot, bot.bot_entity.use_pipeline_uuid
return bot, pipeline_uuid
return None, None
def _get_bot_config(self, bot_uuid: str) -> dict:
+1 -1
View File
@@ -17,7 +17,7 @@ AGENT_DEFAULT_EVENT_PATTERNS = ['*']
class AgentService:
"""Unified product surface for Agent orchestration instances and Pipelines."""
"""Unified product surface for Agent processors and Pipelines."""
ap: app.Application
+23 -12
View File
@@ -15,6 +15,15 @@ class BotService:
"""Bot service"""
ap: app.Application
BOT_FIELDS = {
'uuid',
'name',
'description',
'adapter',
'adapter_config',
'enable',
'event_bindings',
}
def __init__(self, ap: app.Application) -> None:
self.ap = ap
@@ -115,6 +124,17 @@ class BotService:
return normalized
async def _prepare_bot_data(self, bot_data: dict, *, include_uuid: bool) -> dict:
"""Normalize Bot write payloads to the current event-routing model."""
update_data = bot_data.copy()
if not include_uuid:
update_data.pop('uuid', None)
update_data = {key: value for key, value in update_data.items() if key in self.BOT_FIELDS}
if 'event_bindings' in update_data:
update_data['event_bindings'] = await self._normalize_event_bindings(update_data.get('event_bindings'))
return update_data
async def get_bots(self, include_secret: bool = True) -> list[dict]:
"""获取所有机器人"""
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_bot.Bot))
@@ -188,7 +208,9 @@ class BotService:
raise ValueError(f'Maximum number of bots ({max_bots}) reached')
# TODO: 检查配置信息格式
bot_data = await self._prepare_bot_data(bot_data, include_uuid=True)
bot_data['uuid'] = str(uuid.uuid4())
bot_data.setdefault('event_bindings', [])
await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_bot.Bot).values(bot_data))
@@ -200,18 +222,7 @@ class BotService:
async def update_bot(self, bot_uuid: str, bot_data: dict) -> None:
"""Update bot"""
update_data = bot_data.copy()
if 'uuid' in update_data:
del update_data['uuid']
if 'event_bindings' in update_data:
update_data['event_bindings'] = await self._normalize_event_bindings(update_data.get('event_bindings'))
# clear legacy routing fields — routing is now fully managed via event_bindings
update_data.pop('use_pipeline_uuid', None)
update_data.pop('use_pipeline_name', None)
update_data.pop('pipeline_routing_rules', None)
update_data = await self._prepare_bot_data(bot_data, include_uuid=False)
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(persistence_bot.Bot).values(update_data).where(persistence_bot.Bot.uuid == bot_uuid)
@@ -218,19 +218,6 @@ class PipelineService:
pipeline = await self.get_pipeline(pipeline_uuid)
if 'name' in pipeline_data:
from ....entity.persistence import bot as persistence_bot
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_bot.Bot).where(persistence_bot.Bot.use_pipeline_uuid == pipeline_uuid)
)
bots = result.all()
for bot in bots:
bot_data = {'use_pipeline_name': pipeline_data['name']}
await self.ap.bot_service.update_bot(bot.uuid, bot_data)
await self.ap.pipeline_mgr.remove_pipeline(pipeline_uuid)
await self.ap.pipeline_mgr.load_pipeline(pipeline)
+4 -4
View File
@@ -142,7 +142,7 @@ class LangBotMCPServer:
return _dump({'ok': True})
# ----- Agents -------------------------------------------------- #
@mcp.tool(description='List product-level Agents, including Agent orchestrations and Pipelines.')
@mcp.tool(description='List product-level processors, including Agents and Pipelines.')
async def list_agents() -> str:
return _dump(await ap.agent_service.get_agents())
@@ -152,19 +152,19 @@ class LangBotMCPServer:
@mcp.tool(
description=(
'Create an Agent orchestration or Pipeline. `agent_data` matches '
'Create an Agent processor or Pipeline. `agent_data` matches '
'POST /api/v1/agents; set kind to `agent` or `pipeline`. Returns the new UUID and kind.'
)
)
async def create_agent(agent_data: dict) -> str:
return _dump(await ap.agent_service.create_agent(agent_data))
@mcp.tool(description='Update an Agent orchestration or Pipeline by UUID.')
@mcp.tool(description='Update an Agent processor or Pipeline by UUID.')
async def update_agent(agent_uuid: str, agent_data: dict) -> str:
await ap.agent_service.update_agent(agent_uuid, agent_data)
return _dump({'ok': True})
@mcp.tool(description='Delete an Agent orchestration or Pipeline by UUID.')
@mcp.tool(description='Delete an Agent processor or Pipeline by UUID.')
async def delete_agent(agent_uuid: str) -> str:
await ap.agent_service.delete_agent(agent_uuid)
return _dump({'ok': True})