196 lines
6.6 KiB
Python
196 lines
6.6 KiB
Python
|
|
"""Agent-to-Agent communication router."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import logging
|
||
|
|
import uuid
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from sqlalchemy import select
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
async def send_agent_message(
|
||
|
|
from_agent_id: str,
|
||
|
|
to_agent_name: str,
|
||
|
|
message: str,
|
||
|
|
db: AsyncSession,
|
||
|
|
tenant_id: uuid.UUID,
|
||
|
|
) -> dict:
|
||
|
|
"""Send a message from one agent to another.
|
||
|
|
|
||
|
|
1. Find target agent by name
|
||
|
|
2. Create a kommunikation message in a dedicated agent room
|
||
|
|
3. Enqueue run_agent for the target agent with the message as trigger_data
|
||
|
|
4. Return delivery status
|
||
|
|
"""
|
||
|
|
from app.plugins.builtins.automation.models import AgentDefinition
|
||
|
|
from app.plugins.builtins.automation.agent_runner import run_agent
|
||
|
|
|
||
|
|
# 1. Find target agent by name
|
||
|
|
result = await db.execute(
|
||
|
|
select(AgentDefinition)
|
||
|
|
.where(AgentDefinition.name == to_agent_name)
|
||
|
|
.where(AgentDefinition.tenant_id == tenant_id)
|
||
|
|
.limit(1)
|
||
|
|
)
|
||
|
|
target_agent = result.scalar_one_or_none()
|
||
|
|
|
||
|
|
if target_agent is None:
|
||
|
|
logger.warning(
|
||
|
|
"Target agent '%s' not found for message from agent %s",
|
||
|
|
to_agent_name, from_agent_id,
|
||
|
|
)
|
||
|
|
return {"status": "error", "error": f"Target agent '{to_agent_name}' not found"}
|
||
|
|
|
||
|
|
if not target_agent.is_active:
|
||
|
|
logger.warning(
|
||
|
|
"Target agent '%s' is inactive, cannot deliver message from %s",
|
||
|
|
to_agent_name, from_agent_id,
|
||
|
|
)
|
||
|
|
return {"status": "error", "error": f"Target agent '{to_agent_name}' is inactive"}
|
||
|
|
|
||
|
|
# 2. Create a kommunikation message in a dedicated agent room
|
||
|
|
try:
|
||
|
|
from app.plugins.builtins.kommunikation.models import Message, Room
|
||
|
|
from app.plugins.builtins.kommunikation.services import RoomService
|
||
|
|
|
||
|
|
# Find or create the agent-to-agent room
|
||
|
|
room_name = f"agent:{from_agent_id}:{target_agent.id}"
|
||
|
|
room_result = await db.execute(
|
||
|
|
select(Room).where(Room.name == room_name).limit(1)
|
||
|
|
)
|
||
|
|
room = room_result.scalar_one_or_none()
|
||
|
|
|
||
|
|
if room is None:
|
||
|
|
# Create a new room for agent communication
|
||
|
|
room = Room(
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
name=room_name,
|
||
|
|
display_name=f"Agent Chat: {from_agent_id} -> {to_agent_name}",
|
||
|
|
room_type="agent_comm",
|
||
|
|
is_direct=True,
|
||
|
|
)
|
||
|
|
db.add(room)
|
||
|
|
await db.flush()
|
||
|
|
|
||
|
|
# Create the message
|
||
|
|
msg = Message(
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
room_id=room.id,
|
||
|
|
sender_id=from_agent_id,
|
||
|
|
sender_type="agent",
|
||
|
|
content=message,
|
||
|
|
message_type="agent_comm",
|
||
|
|
)
|
||
|
|
db.add(msg)
|
||
|
|
await db.flush()
|
||
|
|
|
||
|
|
logger.info(
|
||
|
|
"Agent message created: %s -> %s in room %s",
|
||
|
|
from_agent_id, to_agent_name, room_name,
|
||
|
|
)
|
||
|
|
except ImportError:
|
||
|
|
logger.warning("Kommunikation plugin not available, skipping message storage")
|
||
|
|
except Exception as e:
|
||
|
|
logger.exception("Failed to create kommunikation message: %s", e)
|
||
|
|
|
||
|
|
# 3. Enqueue run_agent for the target agent with the message as trigger_data
|
||
|
|
try:
|
||
|
|
trigger_data = {
|
||
|
|
"from_agent_id": from_agent_id,
|
||
|
|
"message": message,
|
||
|
|
"type": "agent_comm",
|
||
|
|
}
|
||
|
|
# Run the target agent with the message as trigger data
|
||
|
|
result_data = await run_agent(
|
||
|
|
ctx={},
|
||
|
|
agent_id=str(target_agent.id),
|
||
|
|
trigger_type="agent_comm",
|
||
|
|
trigger_data=trigger_data,
|
||
|
|
)
|
||
|
|
logger.info(
|
||
|
|
"Target agent %s executed with message from %s: status=%s",
|
||
|
|
to_agent_name, from_agent_id, result_data.get("status"),
|
||
|
|
)
|
||
|
|
except Exception as e:
|
||
|
|
logger.exception("Failed to run target agent %s: %s", to_agent_name, e)
|
||
|
|
return {"status": "error", "error": f"Failed to activate target agent: {e}"}
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": "sent",
|
||
|
|
"target_agent": to_agent_name,
|
||
|
|
"target_agent_id": str(target_agent.id),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def register_agent_comm_tool():
|
||
|
|
"""Register the send_agent_message tool in the global tool registry."""
|
||
|
|
from app.plugins.builtins.ai_assistant.tool_registry import get_tool_registry
|
||
|
|
|
||
|
|
registry = get_tool_registry()
|
||
|
|
|
||
|
|
async def handler(arguments: dict[str, Any], context: dict[str, Any]) -> str:
|
||
|
|
"""Handle send_agent_message tool call from an AI agent."""
|
||
|
|
from app.core.db import get_session_factory
|
||
|
|
|
||
|
|
to_agent_name = arguments.get("to_agent_name", "")
|
||
|
|
message = arguments.get("message", "")
|
||
|
|
from_agent_id = context.get("agent_id", "unknown")
|
||
|
|
tenant_id_str = context.get("tenant_id", "")
|
||
|
|
|
||
|
|
if not to_agent_name or not message:
|
||
|
|
return json.dumps({"status": "error", "error": "Missing to_agent_name or message"})
|
||
|
|
|
||
|
|
try:
|
||
|
|
tenant_id = uuid.UUID(tenant_id_str) if tenant_id_str else uuid.uuid4()
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
return json.dumps({"status": "error", "error": "Invalid tenant_id"})
|
||
|
|
|
||
|
|
factory = get_session_factory()
|
||
|
|
async with factory() as db:
|
||
|
|
result = await send_agent_message(
|
||
|
|
from_agent_id=from_agent_id,
|
||
|
|
to_agent_name=to_agent_name,
|
||
|
|
message=message,
|
||
|
|
db=db,
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
)
|
||
|
|
return json.dumps(result)
|
||
|
|
|
||
|
|
registry.register(
|
||
|
|
name="send_agent_message",
|
||
|
|
description="Send a message to another agent. The target agent will be activated with your message.",
|
||
|
|
parameters={
|
||
|
|
"type": "object",
|
||
|
|
"properties": {
|
||
|
|
"to_agent_name": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Name of the target agent",
|
||
|
|
},
|
||
|
|
"message": {
|
||
|
|
"type": "string",
|
||
|
|
"description": "Message to send",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"required": ["to_agent_name", "message"],
|
||
|
|
},
|
||
|
|
handler=handler,
|
||
|
|
plugin_name="automation",
|
||
|
|
required_permission="agents:execute",
|
||
|
|
category="communication",
|
||
|
|
)
|
||
|
|
logger.info("Agent communication tool 'send_agent_message' registered")
|
||
|
|
|
||
|
|
|
||
|
|
def unregister_agent_comm_tool():
|
||
|
|
"""Unregister the send_agent_message tool."""
|
||
|
|
from app.plugins.builtins.ai_assistant.tool_registry import get_tool_registry
|
||
|
|
|
||
|
|
registry = get_tool_registry()
|
||
|
|
registry.unregister("send_agent_message")
|
||
|
|
logger.info("Agent communication tool 'send_agent_message' unregistered")
|