From 5d48cd4130934daca43d9bb330a8794ec2830ee7 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Thu, 6 Nov 2025 02:21:42 +0100 Subject: [PATCH] mejorado bot de telegram --- ejecucion_telegram.py | 102 +++++++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 26 deletions(-) diff --git a/ejecucion_telegram.py b/ejecucion_telegram.py index 13a2cd3..8f5e56d 100644 --- a/ejecucion_telegram.py +++ b/ejecucion_telegram.py @@ -2,12 +2,15 @@ from __future__ import annotations import asyncio +import html import os import json from contextlib import suppress -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Set import requests +from agno.agent.agent import RunOutput +from agno.run.agent import ToolCallStartedEvent from dotenv import load_dotenv from agentes import DEFAULT_AGENT_NAME, create_agent @@ -51,11 +54,16 @@ class TelegramKanBot: self.agent = None self.context: Dict[str, Any] = {} + self.mcp_tool_names: Set[str] = set() async def setup_agent(self) -> None: agent, context = await create_agent(self.logger, agent=self.agent_key) self.agent = agent self.context = context + server_tool_map = context.get("server_tool_map") or {} + self.mcp_tool_names = { + tool_name for tools in server_tool_map.values() for tool_name in (tools or []) + } self.logger.info( "🤖 Agente Kan para Telegram listo", add_fields={ @@ -186,8 +194,49 @@ class TelegramKanBot: }, ) typing_task = asyncio.create_task(self._typing_indicator(chat_id)) + notified_tool_calls: Set[str] = set() + response: Optional[RunOutput] = None try: - response = await self.agent.arun(text) + run_result = await self.agent.arun( + text, + stream=True, + stream_events=True, + yield_run_response=True, + ) + + if isinstance(run_result, RunOutput): + response = run_result + else: + async for event in run_result: + if isinstance(event, RunOutput): + response = event + elif isinstance(event, ToolCallStartedEvent): + await self._maybe_notify_tool_execution(chat_id, event, notified_tool_calls) + if response is None: + raise RuntimeError("El agente no devolvió respuesta.") + + content = (response.content or "(respuesta vacía)").strip() + if not content: + content = "(respuesta vacía)" + + await self._send_message(chat_id, content) + + if response.metrics: + self.logger.info( + "📊 Métricas de ejecución", + add_fields={ + "agent_call": {"action": "agent_run_metrics"}, + "agent_response": { + "status": "completed", + "metrics": { + "input_tokens": response.metrics.input_tokens, + "output_tokens": response.metrics.output_tokens, + "total_tokens": response.metrics.total_tokens, + "duration_seconds": response.metrics.duration, + }, + }, + }, + ) except Exception as error: # pragma: no cover - manejo defensivo self.logger.exception( error, @@ -203,32 +252,11 @@ class TelegramKanBot: with suppress(asyncio.CancelledError): await typing_task - content = (response.content or "(respuesta vacía)").strip() - if not content: - content = "(respuesta vacía)" - - await self._send_message(chat_id, content) - - if response.metrics: - self.logger.info( - "📊 Métricas de ejecución", - add_fields={ - "agent_call": {"action": "agent_run_metrics"}, - "agent_response": { - "status": "completed", - "metrics": { - "input_tokens": response.metrics.input_tokens, - "output_tokens": response.metrics.output_tokens, - "total_tokens": response.metrics.total_tokens, - "duration_seconds": response.metrics.duration, - }, - }, - }, - ) - - async def _send_message(self, chat_id: int, text: str) -> None: + async def _send_message(self, chat_id: int, text: str, *, parse_mode: Optional[str] = None) -> None: for chunk in self._chunk_text(text): payload = {"chat_id": chat_id, "text": chunk} + if parse_mode: + payload["parse_mode"] = parse_mode try: response = await asyncio.to_thread( self.session.post, @@ -255,6 +283,28 @@ class TelegramKanBot: }, ) + async def _maybe_notify_tool_execution( + self, + chat_id: int, + event: ToolCallStartedEvent, + notified_tool_calls: Set[str], + ) -> None: + if not self.mcp_tool_names: + return + tool = getattr(event, "tool", None) + if not tool or not tool.tool_name: + return + tool_identifier = tool.tool_call_id or f"{tool.tool_name}:{len(notified_tool_calls)}" + if tool_identifier in notified_tool_calls: + return + if tool.tool_name not in self.mcp_tool_names: + return + + notified_tool_calls.add(tool_identifier) + + escaped_name = html.escape(tool.tool_name) + await self._send_message(chat_id, f"Ejecutado {escaped_name}", parse_mode="HTML") + async def _typing_indicator(self, chat_id: int) -> None: try: while True: