Team Workflow aggiornato (#37)
* Rimuovi la classe Predictor e aggiorna le importazioni in Pipeline e __init__.py * Aggiungi modelli per l'analisi delle query e la generazione di report; aggiorna le configurazioni degli agenti * Tests for report generation and team agent responses * Aggiorna i prompt degli agenti * Changed defaults models * Aggiunta della classe PlanMemoryTool per la gestione dei task e aggiornamento della logica del team leader per un'esecuzione più dinamica del piano.
This commit was merged in pull request #37.
This commit is contained in:
committed by
GitHub
parent
885a70d748
commit
12339ccbff
@@ -4,85 +4,38 @@ import logging
|
||||
import random
|
||||
from typing import Any, Callable
|
||||
from agno.agent import RunEvent
|
||||
from agno.team import Team, TeamRunEvent
|
||||
from agno.tools.reasoning import ReasoningTools
|
||||
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.api.tools import *
|
||||
from app.agents.prompts import *
|
||||
from app.configs import AppConfig
|
||||
from app.agents.core import *
|
||||
|
||||
logging = logging.getLogger("pipeline")
|
||||
|
||||
|
||||
|
||||
class PipelineEvent(str, Enum):
|
||||
PLANNER = "Planner"
|
||||
QUERY_CHECK = "Query Check"
|
||||
QUERY_ANALYZER = "Query Analyzer"
|
||||
INFO_RECOVERY = "Info Recovery"
|
||||
REPORT_GENERATION = "Report Generation"
|
||||
REPORT_TRANSLATION = "Report Translation"
|
||||
TOOL_USED = RunEvent.tool_call_completed
|
||||
RUN_FINISHED = WorkflowRunEvent.workflow_completed.value
|
||||
TOOL_USED = RunEvent.tool_call_completed.value
|
||||
|
||||
def check_event(self, event: str, step_name: str) -> bool:
|
||||
return event == self.value or (WorkflowRunEvent.step_completed and step_name == self.value)
|
||||
return event == self.value or (WorkflowRunEvent.step_completed == event and step_name == self.value)
|
||||
|
||||
|
||||
class PipelineInputs:
|
||||
"""
|
||||
Classe necessaria per passare gli input alla Pipeline.
|
||||
Serve per raggruppare i parametri e semplificare l'inizializzazione.
|
||||
"""
|
||||
|
||||
def __init__(self, configs: AppConfig | None = None) -> None:
|
||||
"""
|
||||
Inputs per la Pipeline di agenti.
|
||||
Setta i valori di default se non specificati.
|
||||
"""
|
||||
self.configs = configs if configs else AppConfig()
|
||||
|
||||
agents = self.configs.agents
|
||||
self.team_model = self.configs.get_model_by_name(agents.team_model)
|
||||
self.team_leader_model = self.configs.get_model_by_name(agents.team_leader_model)
|
||||
self.predictor_model = self.configs.get_model_by_name(agents.predictor_model)
|
||||
self.strategy = self.configs.get_strategy_by_name(agents.strategy)
|
||||
self.user_query = ""
|
||||
|
||||
# ======================
|
||||
# Dropdown handlers
|
||||
# ======================
|
||||
def choose_team_leader(self, index: int):
|
||||
"""
|
||||
Sceglie il modello LLM da usare per il Team Leader.
|
||||
"""
|
||||
self.leader_model = self.configs.models.all_models[index]
|
||||
|
||||
def choose_team(self, index: int):
|
||||
"""
|
||||
Sceglie il modello LLM da usare per il Team.
|
||||
"""
|
||||
self.team_model = self.configs.models.all_models[index]
|
||||
|
||||
def choose_strategy(self, index: int):
|
||||
"""
|
||||
Sceglie la strategia da usare per il Team.
|
||||
"""
|
||||
self.strategy = self.configs.strategies[index]
|
||||
|
||||
# ======================
|
||||
# Helpers
|
||||
# ======================
|
||||
def list_models_names(self) -> list[str]:
|
||||
"""
|
||||
Restituisce la lista dei nomi dei modelli disponibili.
|
||||
"""
|
||||
return [model.label for model in self.configs.models.all_models]
|
||||
|
||||
def list_strategies_names(self) -> list[str]:
|
||||
"""
|
||||
Restituisce la lista delle strategie disponibili.
|
||||
"""
|
||||
return [strat.label for strat in self.configs.strategies]
|
||||
@classmethod
|
||||
def get_log_events(cls, run_id: int) -> list[tuple['PipelineEvent', Callable[[Any], 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}] by {e.agent_name}.")),
|
||||
(PipelineEvent.RUN_FINISHED, lambda _: logging.info(f"[{run_id}] Run completed.")),
|
||||
]
|
||||
|
||||
|
||||
class Pipeline:
|
||||
@@ -93,12 +46,14 @@ class Pipeline:
|
||||
"""
|
||||
|
||||
def __init__(self, inputs: PipelineInputs):
|
||||
"""
|
||||
Inizializza la pipeline con gli input forniti.
|
||||
Args:
|
||||
inputs: istanza di PipelineInputs contenente le configurazioni e i parametri della pipeline.
|
||||
"""
|
||||
self.inputs = inputs
|
||||
|
||||
# ======================
|
||||
# Core interaction
|
||||
# ======================
|
||||
def interact(self, listeners: dict[RunEvent | TeamRunEvent, Callable[[PipelineEvent], None]] = {}) -> str:
|
||||
def interact(self, listeners: list[tuple[PipelineEvent, Callable[[Any], None]]] = []) -> str:
|
||||
"""
|
||||
Esegue la pipeline di agenti per rispondere alla query dell'utente.
|
||||
Args:
|
||||
@@ -108,7 +63,7 @@ class Pipeline:
|
||||
"""
|
||||
return asyncio.run(self.interact_async(listeners))
|
||||
|
||||
async def interact_async(self, listeners: dict[RunEvent | TeamRunEvent, Callable[[PipelineEvent], None]] = {}) -> str:
|
||||
async def interact_async(self, listeners: list[tuple[PipelineEvent, Callable[[Any], None]]] = []) -> str:
|
||||
"""
|
||||
Versione asincrona che esegue la pipeline di agenti per rispondere alla query dell'utente.
|
||||
Args:
|
||||
@@ -119,61 +74,47 @@ class Pipeline:
|
||||
run_id = random.randint(1000, 9999) # Per tracciare i log
|
||||
logging.info(f"[{run_id}] Pipeline query: {self.inputs.user_query}")
|
||||
|
||||
# Step 1: Crea gli agenti e il team
|
||||
market_tool, news_tool, social_tool = self.get_tools()
|
||||
market_agent = self.inputs.team_model.get_agent(instructions=MARKET_INSTRUCTIONS, name="MarketAgent", tools=[market_tool])
|
||||
news_agent = self.inputs.team_model.get_agent(instructions=NEWS_INSTRUCTIONS, name="NewsAgent", tools=[news_tool])
|
||||
social_agent = self.inputs.team_model.get_agent(instructions=SOCIAL_INSTRUCTIONS, name="SocialAgent", tools=[social_tool])
|
||||
|
||||
team = Team(
|
||||
model=self.inputs.team_leader_model.get_model(COORDINATOR_INSTRUCTIONS),
|
||||
name="CryptoAnalysisTeam",
|
||||
tools=[ReasoningTools()],
|
||||
members=[market_agent, news_agent, social_agent],
|
||||
events = [*PipelineEvent.get_log_events(run_id), *listeners]
|
||||
query = QueryInputs(
|
||||
user_query=self.inputs.user_query,
|
||||
strategy=self.inputs.strategy.description
|
||||
)
|
||||
|
||||
# Step 3: Crea il workflow
|
||||
#query_planner = Step(name=PipelineEvent.PLANNER, agent=Agent())
|
||||
info_recovery = Step(name=PipelineEvent.INFO_RECOVERY, team=team)
|
||||
#report_generation = Step(name=PipelineEvent.REPORT_GENERATION, agent=Agent())
|
||||
#report_translate = Step(name=AppEvent.REPORT_TRANSLATION, agent=Agent())
|
||||
|
||||
workflow = Workflow(
|
||||
name="App Workflow",
|
||||
steps=[
|
||||
#query_planner,
|
||||
info_recovery,
|
||||
#report_generation,
|
||||
#report_translate
|
||||
]
|
||||
)
|
||||
|
||||
# Step 4: Fai partire il workflow e prendi l'output
|
||||
query = f"The user query is: {self.inputs.user_query}\n\n They requested a {self.inputs.strategy.label} investment strategy."
|
||||
result = await self.run(workflow, query, events={})
|
||||
logging.info(f"[{run_id}] Run finished")
|
||||
workflow = self.build_workflow()
|
||||
result = await self.run(workflow, query, events=events)
|
||||
return result
|
||||
|
||||
# ======================
|
||||
# Helpers
|
||||
# =====================
|
||||
def get_tools(self) -> tuple[MarketAPIsTool, NewsAPIsTool, SocialAPIsTool]:
|
||||
"""
|
||||
Restituisce la lista di tools disponibili per gli agenti.
|
||||
"""
|
||||
api = self.inputs.configs.api
|
||||
|
||||
market_tool = MarketAPIsTool(currency=api.currency)
|
||||
market_tool.handler.set_retries(api.retry_attempts, api.retry_delay_seconds)
|
||||
news_tool = NewsAPIsTool()
|
||||
news_tool.handler.set_retries(api.retry_attempts, api.retry_delay_seconds)
|
||||
social_tool = SocialAPIsTool()
|
||||
social_tool.handler.set_retries(api.retry_attempts, api.retry_delay_seconds)
|
||||
def build_workflow(self) -> Workflow:
|
||||
"""
|
||||
Costruisce il workflow della pipeline di agenti.
|
||||
Returns:
|
||||
L'istanza di Workflow costruita.
|
||||
"""
|
||||
# Step 1: Crea gli agenti e il team
|
||||
team = self.inputs.get_agent_team()
|
||||
query_check = self.inputs.get_agent_query_checker()
|
||||
report = self.inputs.get_agent_report_generator()
|
||||
|
||||
return (market_tool, news_tool, social_tool)
|
||||
# Step 2: Crea gli steps
|
||||
def condition_query_ok(step_input: StepInput) -> StepOutput:
|
||||
val = step_input.previous_step_content
|
||||
return StepOutput(stop=not val.is_crypto) if isinstance(val, QueryOutputs) else StepOutput(stop=True)
|
||||
|
||||
query_check = Step(name=PipelineEvent.QUERY_CHECK, agent=query_check)
|
||||
info_recovery = Step(name=PipelineEvent.INFO_RECOVERY, team=team)
|
||||
report_generation = Step(name=PipelineEvent.REPORT_GENERATION, agent=report)
|
||||
|
||||
# Step 3: Ritorna il workflow completo
|
||||
return Workflow(name="App Workflow", steps=[
|
||||
query_check,
|
||||
condition_query_ok,
|
||||
info_recovery,
|
||||
report_generation
|
||||
])
|
||||
|
||||
@classmethod
|
||||
async def run(cls, workflow: Workflow, query: str, events: dict[PipelineEvent, Callable[[Any], None]]) -> str:
|
||||
async def run(cls, workflow: Workflow, query: QueryInputs, events: list[tuple[PipelineEvent, Callable[[Any], None]]]) -> str:
|
||||
"""
|
||||
Esegue il workflow e gestisce gli eventi tramite le callback fornite.
|
||||
Args:
|
||||
@@ -188,16 +129,18 @@ class Pipeline:
|
||||
content = None
|
||||
async for event in iterator:
|
||||
step_name = getattr(event, 'step_name', '')
|
||||
|
||||
for app_event, listener in events.items():
|
||||
for app_event, listener in events:
|
||||
if app_event.check_event(event.event, step_name):
|
||||
listener(event)
|
||||
|
||||
if event.event == WorkflowRunEvent.workflow_completed:
|
||||
if event.event == WorkflowRunEvent.step_completed:
|
||||
content = getattr(event, 'content', '')
|
||||
if isinstance(content, str):
|
||||
think_str = "</think>"
|
||||
think = content.rfind(think_str)
|
||||
content = content[(think + len(think_str)):] if think != -1 else content
|
||||
|
||||
return content if content else "No output from workflow, something went wrong."
|
||||
if content and isinstance(content, str):
|
||||
think_str = "</think>"
|
||||
think = content.rfind(think_str)
|
||||
return content[(think + len(think_str)):] if think != -1 else content
|
||||
if content and isinstance(content, QueryOutputs):
|
||||
return content.response
|
||||
|
||||
logging.error(f"No output from workflow: {content}")
|
||||
return "No output from workflow, something went wrong."
|
||||
|
||||
Reference in New Issue
Block a user