- moved action_registry fnction to action_registry file
- removed unnecesasry functions in pupeline and fixed events
This commit is contained in:
2025-10-30 22:06:29 +01:00
parent 765fc0ac72
commit 2803263bed
5 changed files with 83 additions and 113 deletions

View File

@@ -6,4 +6,12 @@ def register_friendly_actions(actions: dict[str, str]):
Aggiunge le descrizioni di un Toolkit al registro globale.
"""
global ACTION_DESCRIPTIONS
ACTION_DESCRIPTIONS.update(actions)
ACTION_DESCRIPTIONS.update(actions)
def get_user_friendly_action(tool_name: str) -> str:
"""
Restituisce un messaggio leggibile e descrittivo per l'utente
leggendo dal registro globale.
"""
# Usa il dizionario ACTION_DESCRIPTIONS importato
return ACTION_DESCRIPTIONS.get(tool_name, f"⚙️ Eseguo l'operazione: {tool_name}...")

View File

@@ -155,12 +155,20 @@ class RunMessage:
self.base_message = f"Running configurations: \n{prefix}{inputs}{suffix}\n\n"
self.emojis = ['🔳', '➡️', '']
self.placeholder = '<<<>>>'
self.set_steps(["Query Check", "Info Recovery", "Report Generation"])
def set_steps(self, steps: list[str]) -> 'RunMessage':
"""
Inizializza gli step di esecuzione con lo stato iniziale.
Args:
steps (list[str]): Lista degli step da includere nel messaggio.
Returns:
RunMessage: L'istanza aggiornata di RunMessage.
"""
self.steps_total = [(f"{self.placeholder} {step}", 0) for step in steps]
self.steps_total[0] = (self.steps_total[0][0], 1) # Primo step in esecuzione
self.current = 0
self.steps_total = [
(f"{self.placeholder} Query Check", 1),
(f"{self.placeholder} Info Recovery", 0),
(f"{self.placeholder} Report Generation", 0),
]
return self
def update(self) -> 'RunMessage':
"""
@@ -177,15 +185,15 @@ class RunMessage:
self.steps_total[self.current] = (text_curr, state_curr + 1)
return self
def update_step(self, text_extra: str = "") -> 'RunMessage':
def update_step_with_tool(self, tool_used: str = "") -> 'RunMessage':
"""
Aggiorna il messaggio per lo step corrente.
Args:
text_extra (str, optional): Testo aggiuntivo da includere nello step. Defaults to "".
tool_used (str, optional): Testo aggiuntivo da includere nello step. Defaults to "".
"""
text_curr, state_curr = self.steps_total[self.current]
if text_extra:
text_curr = f"{text_curr.replace('', '')}\n╚═ {text_extra}"
if tool_used:
text_curr = f"{text_curr.replace('', '')}\n╚═ {tool_used}"
self.steps_total[self.current] = (text_curr, state_curr)
return self
@@ -197,3 +205,4 @@ class RunMessage:
"""
steps = [msg.replace(self.placeholder, self.emojis[state]) for msg, state in self.steps_total]
return self.base_message + "\n".join(steps)

View File

@@ -1,50 +1,45 @@
import asyncio
from enum import Enum
import logging
import random
from typing import Any, Callable
from typing import Any, AsyncGenerator, Callable
from agno.agent import RunEvent
from agno.run.workflow import WorkflowRunEvent
from agno.workflow.types import StepInput, StepOutput
from agno.workflow.step import Step
from agno.workflow.workflow import Workflow
from app.agents.action_registry import ACTION_DESCRIPTIONS
from app.agents.core import *
logging = logging.getLogger("pipeline")
def _get_user_friendly_action(tool_name: str) -> str:
"""
Restituisce un messaggio leggibile e descrittivo per l'utente
leggendo dal registro globale.
"""
# Usa il dizionario ACTION_DESCRIPTIONS importato
return ACTION_DESCRIPTIONS.get(tool_name, f"⚙️ Eseguo l'operazione: {tool_name}...")
class PipelineEvent(str, Enum):
QUERY_CHECK = "Query Check"
QUERY_ANALYZER = "Query Analyzer"
QUERY_CHECK_END = "Query Check End"
INFO_RECOVERY = "Info Recovery"
INFO_RECOVERY_END = "Info Recovery End"
REPORT_GENERATION = "Report Generation"
REPORT_TRANSLATION = "Report Translation"
RUN_FINISHED = WorkflowRunEvent.workflow_completed.value
TOOL_USED = RunEvent.tool_call_completed.value
REPORT_GENERATION_END = "Report Generation End"
TOOL_USED = RunEvent.tool_call_started.value
TOOL_USED_END = RunEvent.tool_call_completed.value
RUN_END = WorkflowRunEvent.workflow_completed.value
def check_event(self, event: str, step_name: str) -> bool:
return event == self.value or (WorkflowRunEvent.step_completed == event and step_name == self.value)
if event == self.value:
return True
index = self.value.rfind(" End")
value = self.value[:index] if index > -1 else self.value
step_state = WorkflowRunEvent.step_completed if index > -1 else WorkflowRunEvent.step_started
return step_name == value and step_state == event
@classmethod
def get_log_events(cls, run_id: int) -> list[tuple['PipelineEvent', Callable[[Any], None]]]:
def get_log_events(cls, run_id: int) -> list[tuple['PipelineEvent', Callable[[Any], str | None]]]:
return [
(PipelineEvent.QUERY_CHECK, lambda _: logging.info(f"[{run_id}] Query Check completed.")),
(PipelineEvent.QUERY_ANALYZER, lambda _: logging.info(f"[{run_id}] Query Analyzer completed.")),
(PipelineEvent.INFO_RECOVERY, lambda _: logging.info(f"[{run_id}] Info Recovery completed.")),
(PipelineEvent.REPORT_GENERATION, lambda _: logging.info(f"[{run_id}] Report Generation completed.")),
(PipelineEvent.TOOL_USED, lambda e: logging.info(f"[{run_id}] Tool used [{e.tool.tool_name} {e.tool.tool_args}] by {e.agent_name}.")),
(PipelineEvent.RUN_FINISHED, lambda _: logging.info(f"[{run_id}] Run completed.")),
(PipelineEvent.QUERY_CHECK_END, lambda _: logging.info(f"[{run_id}] Query Check completed.")),
(PipelineEvent.INFO_RECOVERY_END, lambda _: logging.info(f"[{run_id}] Info Recovery completed.")),
(PipelineEvent.REPORT_GENERATION_END, lambda _: logging.info(f"[{run_id}] Report Generation completed.")),
(PipelineEvent.TOOL_USED_END, lambda e: logging.info(f"[{run_id}] Tool used [{e.tool.tool_name} {e.tool.tool_args}] by {e.agent_name}.")),
(PipelineEvent.RUN_END, lambda _: logging.info(f"[{run_id}] Run completed.")),
]
@@ -63,7 +58,7 @@ class Pipeline:
"""
self.inputs = inputs
def interact(self, listeners: list[tuple[PipelineEvent, Callable[[Any], None]]] = []) -> str:
async def interact(self, listeners: list[tuple[PipelineEvent, Callable[[Any], str | None]]] = []) -> str:
"""
Esegue la pipeline di agenti per rispondere alla query dell'utente.
Args:
@@ -71,9 +66,12 @@ class Pipeline:
Returns:
La risposta generata dalla pipeline.
"""
return asyncio.run(self.interact_async(listeners))
response = ""
async for chunk in self.interact_stream(listeners):
response = chunk
return response
async def interact_async(self, listeners: list[tuple[PipelineEvent, Callable[[Any], None]]] = []) -> str:
async def interact_stream(self, listeners: list[tuple[PipelineEvent, Callable[[Any], str | None]]] = []) -> AsyncGenerator[str, None]:
"""
Versione asincrona che esegue la pipeline di agenti per rispondere alla query dell'utente.
Args:
@@ -91,26 +89,6 @@ class Pipeline:
)
workflow = self.build_workflow()
result = await self.run(workflow, query, events=events)
return result
async def interact_stream(self, listeners: list[tuple[PipelineEvent, Callable[[Any], None]]] = []):
"""
Versione asincrona in streaming che ESEGUE (yield) la pipeline,
restituendo gli aggiornamenti di stato e il risultato finale.
"""
run_id = random.randint(1000, 9999) # Per tracciare i log
logging.info(f"[{run_id}] Pipeline query: {self.inputs.user_query}")
events = [*PipelineEvent.get_log_events(run_id), *listeners]
query = QueryInputs(
user_query=self.inputs.user_query,
strategy=self.inputs.strategy.description
)
workflow = self.build_workflow()
# Delega al classmethod 'run_stream' per lo streaming
async for item in self.run_stream(workflow, query, events=events):
yield item
@@ -143,69 +121,37 @@ class Pipeline:
])
@classmethod
async def run(cls, workflow: Workflow, query: QueryInputs,
events: list[tuple[PipelineEvent, Callable[[Any], None]]]) -> str:
"""
Esegue il workflow e gestisce gli eventi, restituendo solo il risultato finale.
Consuma il generatore 'run_stream'.
"""
final_result = "Errore durante l'esecuzione del workflow."
# Consuma il generatore e salva solo l'ultimo item
async for item in cls.run_stream(workflow, query, events):
final_result = item
return final_result
@classmethod
async def run_stream(cls, workflow: Workflow, query: QueryInputs,
events: list[tuple[PipelineEvent, Callable[[Any], None]]]):
async def run_stream(cls, workflow: Workflow, query: QueryInputs, events: list[tuple[PipelineEvent, Callable[[Any], str | None]]]) -> AsyncGenerator[str, None]:
"""
Esegue il workflow e restituisce gli eventi di stato e il risultato finale.
Args:
workflow: L'istanza di Workflow da eseguire.
query: Gli input della query.
events: La lista di eventi e callback da gestire durante l'esecuzione.
Yields:
Aggiornamenti di stato e la risposta finale generata dal workflow.
"""
iterator = await workflow.arun(query, stream=True, stream_intermediate_steps=True)
content = None
current_active_step = None
async for event in iterator:
step_name = getattr(event, 'step_name', '')
# 1. Chiama i listeners (per i log)
# Chiama i listeners (se presenti) per ogni evento
for app_event, listener in events:
if app_event.check_event(event.event, step_name):
listener(event)
update = listener(event)
if update: yield update
# 2. Restituisce gli aggiornamenti di stato per Gradio
if event.event == WorkflowRunEvent.step_started.value:
current_active_step = step_name
if step_name == PipelineEvent.QUERY_CHECK.value:
yield "🔍 Sto controllando la tua richiesta..."
elif step_name == PipelineEvent.INFO_RECOVERY.value:
yield "📊 Sto recuperando i dati (mercato, news, social)..."
elif step_name == PipelineEvent.REPORT_GENERATION.value:
yield "✍️ Sto scrivendo il report finale..."
# Gestisce gli eventi di tool promossi dal Team
elif event.event == PipelineEvent.TOOL_USED.value:
if current_active_step == PipelineEvent.INFO_RECOVERY.value:
tool_object = getattr(event, 'tool', None)
if tool_object:
tool_name = getattr(tool_object, 'tool_name', 'uno strumento sconosciuto')
user_message = _get_user_friendly_action(tool_name)
yield f"{user_message}"
else:
yield f"Sto usando uno strumento sconosciuto..."
# 3. Salva il contenuto finale quando uno step è completato
elif event.event == WorkflowRunEvent.step_completed.value:
current_active_step = None
# Salva il contenuto finale quando uno step è completato
if event.event == WorkflowRunEvent.step_completed.value:
content = getattr(event, 'content', '')
# 4. Restituisce la risposta finale
# Restituisce la risposta finale
if content and isinstance(content, str):
think_str = "</think>"
think = content.rfind(think_str)
final_answer = content[(think + len(think_str)):] if think != -1 else content
yield final_answer
yield content[(think + len(think_str)):] if think != -1 else content
elif content and isinstance(content, QueryOutputs):
yield content.response
else:

View File

@@ -1,7 +1,9 @@
import os
import json
from typing import Any, Callable
import gradio as gr
from app.agents.pipeline import Pipeline, PipelineInputs
from app.agents.action_registry import get_user_friendly_action
from app.agents.pipeline import Pipeline, PipelineEvent, PipelineInputs
class ChatManager:
@@ -56,10 +58,15 @@ class ChatManager:
"""
self.inputs.user_query = message
pipeline = Pipeline(self.inputs)
listeners: list[tuple[PipelineEvent, Callable[[Any], str | None]]] = [
(PipelineEvent.QUERY_CHECK, lambda _: "🔍 Sto controllando la tua richiesta..."),
(PipelineEvent.INFO_RECOVERY, lambda _: "📊 Sto recuperando i dati (mercato, news, social)..."),
(PipelineEvent.REPORT_GENERATION, lambda _: "✍️ Sto scrivendo il report finale..."),
(PipelineEvent.TOOL_USED, lambda e: get_user_friendly_action(e.tool.tool_name))
]
response = None
# Itera sul nuovo generatore asincrono
async for chunk in pipeline.interact_stream():
async for chunk in pipeline.interact_stream(listeners=listeners):
response = chunk # Salva l'ultimo chunk (che sarà la risposta finale)
yield response # Restituisce l'aggiornamento (o la risposta finale) a Gradio

View File

@@ -271,7 +271,7 @@ class TelegramApp:
await bot.delete_message(chat_id=chat_id, message_id=update.message.id)
def update_user(update_step: str = "") -> None:
if update_step: run_message.update_step(update_step)
if update_step: run_message.update_step_with_tool(update_step)
else: run_message.update()
message = run_message.get_latest()
@@ -280,11 +280,11 @@ class TelegramApp:
await bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
pipeline = Pipeline(inputs)
report_content = await pipeline.interact_async(listeners=[
(PipelineEvent.QUERY_CHECK, lambda _: update_user()),
(PipelineEvent.TOOL_USED, lambda e: update_user(e.tool.tool_name.replace('get_', '').replace("_", "\\_"))),
(PipelineEvent.INFO_RECOVERY, lambda _: update_user()),
(PipelineEvent.REPORT_GENERATION, lambda _: update_user()),
report_content = await pipeline.interact(listeners=[
(PipelineEvent.QUERY_CHECK_END, lambda _: update_user()),
(PipelineEvent.TOOL_USED_END, lambda e: update_user(e.tool.tool_name.replace('get_', '').replace("_", "\\_"))),
(PipelineEvent.INFO_RECOVERY_END, lambda _: update_user()),
(PipelineEvent.REPORT_GENERATION_END, lambda _: update_user()),
])
# attach report file to the message