From 8b53966158dd0b5173a15cec213b4891f58804cd Mon Sep 17 00:00:00 2001 From: Berack96 Date: Sun, 19 Oct 2025 18:57:16 +0200 Subject: [PATCH 01/14] Rimuovi la classe Predictor e aggiorna le importazioni in Pipeline e __init__.py --- src/app/agents/__init__.py | 4 +-- src/app/agents/core.py | 72 +++++++++++++++++++++++++++++++++++++ src/app/agents/pipeline.py | 59 +----------------------------- src/app/agents/predictor.py | 16 --------- 4 files changed, 75 insertions(+), 76 deletions(-) create mode 100644 src/app/agents/core.py delete mode 100644 src/app/agents/predictor.py diff --git a/src/app/agents/__init__.py b/src/app/agents/__init__.py index 2e78f1b..efb10a0 100644 --- a/src/app/agents/__init__.py +++ b/src/app/agents/__init__.py @@ -1,4 +1,4 @@ -from app.agents.predictor import PredictorInput, PredictorOutput from app.agents.pipeline import Pipeline, PipelineInputs, PipelineEvent +from app.agents.core import PipelineInputs, QueryOutputs -__all__ = ["PredictorInput", "PredictorOutput", "Pipeline", "PipelineInputs", "PipelineEvent"] +__all__ = ["Pipeline", "PipelineInputs", "PipelineEvent", "QueryOutputs"] diff --git a/src/app/agents/core.py b/src/app/agents/core.py new file mode 100644 index 0000000..35c1c79 --- /dev/null +++ b/src/app/agents/core.py @@ -0,0 +1,72 @@ +from pydantic import BaseModel +from app.configs import AppConfig + + + + +class QueryOutputs(BaseModel): + response: str + is_ok: bool + + + +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] + + + + + diff --git a/src/app/agents/pipeline.py b/src/app/agents/pipeline.py index cf8de3e..93f11d4 100644 --- a/src/app/agents/pipeline.py +++ b/src/app/agents/pipeline.py @@ -12,7 +12,7 @@ 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 PipelineInputs logging = logging.getLogger("pipeline") @@ -28,63 +28,6 @@ class PipelineEvent(str, Enum): return event == self.value or (WorkflowRunEvent.step_completed 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] - - class Pipeline: """ Coordina gli agenti di servizio (Market, News, Social) e il Predictor finale. diff --git a/src/app/agents/predictor.py b/src/app/agents/predictor.py deleted file mode 100644 index 2073947..0000000 --- a/src/app/agents/predictor.py +++ /dev/null @@ -1,16 +0,0 @@ -from pydantic import BaseModel, Field -from app.api.core.markets import ProductInfo - -class PredictorInput(BaseModel): - data: list[ProductInfo] = Field(..., description="Market data as a list of ProductInfo") - style: str = Field(..., description="Prediction style") - sentiment: str = Field(..., description="Aggregated sentiment from news and social analysis") - -class ItemPortfolio(BaseModel): - asset: str = Field(..., description="Name of the asset") - percentage: float = Field(..., description="Percentage allocation to the asset") - motivation: str = Field(..., description="Motivation for the allocation") - -class PredictorOutput(BaseModel): - strategy: str = Field(..., description="Concise operational strategy in Italian") - portfolio: list[ItemPortfolio] = Field(..., description="List of portfolio items with allocations") -- 2.49.1 From 21ebe0700fe3236a13f6a6e1a034150c536afcdc Mon Sep 17 00:00:00 2001 From: Berack96 Date: Sun, 19 Oct 2025 21:18:45 +0200 Subject: [PATCH 02/14] Aggiungi modelli per l'analisi delle query e la generazione di report; aggiorna le configurazioni degli agenti --- configs.yaml | 2 + src/app/agents/core.py | 6 ++- src/app/agents/prompts/__init__.py | 6 ++- src/app/agents/prompts/query_analyzer.txt | 40 ++++++++++++++++++++ src/app/agents/prompts/report_generation.txt | 31 +++++++++++++++ src/app/configs.py | 3 +- 6 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/app/agents/prompts/query_analyzer.txt create mode 100644 src/app/agents/prompts/report_generation.txt diff --git a/configs.yaml b/configs.yaml index c0925b8..67e3080 100644 --- a/configs.yaml +++ b/configs.yaml @@ -42,4 +42,6 @@ agents: strategy: Conservative team_model: qwen3:1.7b team_leader_model: qwen3:4b + query_analyzer_model: qwen3:4b + report_generation_model: qwen3:4b predictor_model: qwen3:4b diff --git a/src/app/agents/core.py b/src/app/agents/core.py index 35c1c79..b3df55e 100644 --- a/src/app/agents/core.py +++ b/src/app/agents/core.py @@ -3,6 +3,9 @@ from app.configs import AppConfig +class QueryInputs(BaseModel): + user_query: str + strategy: str class QueryOutputs(BaseModel): response: str @@ -26,7 +29,8 @@ class PipelineInputs: 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.query_analyzer_model = self.configs.get_model_by_name(agents.query_analyzer_model) + self.report_generation_model = self.configs.get_model_by_name(agents.report_generation_model) self.strategy = self.configs.get_strategy_by_name(agents.strategy) self.user_query = "" diff --git a/src/app/agents/prompts/__init__.py b/src/app/agents/prompts/__init__.py index 6aa7abe..656848a 100644 --- a/src/app/agents/prompts/__init__.py +++ b/src/app/agents/prompts/__init__.py @@ -10,12 +10,14 @@ COORDINATOR_INSTRUCTIONS = __load_prompt("team_leader.txt") MARKET_INSTRUCTIONS = __load_prompt("team_market.txt") NEWS_INSTRUCTIONS = __load_prompt("team_news.txt") SOCIAL_INSTRUCTIONS = __load_prompt("team_social.txt") -PREDICTOR_INSTRUCTIONS = __load_prompt("predictor.txt") +QUERY_ANALYZER_INSTRUCTIONS = __load_prompt("query_analyzer.txt") +REPORT_GENERATION_INSTRUCTIONS = __load_prompt("report_generation.txt") __all__ = [ "COORDINATOR_INSTRUCTIONS", "MARKET_INSTRUCTIONS", "NEWS_INSTRUCTIONS", "SOCIAL_INSTRUCTIONS", - "PREDICTOR_INSTRUCTIONS", + "QUERY_ANALYZER_INSTRUCTIONS", + "REPORT_GENERATION_INSTRUCTIONS", ] \ No newline at end of file diff --git a/src/app/agents/prompts/query_analyzer.txt b/src/app/agents/prompts/query_analyzer.txt new file mode 100644 index 0000000..d69ab61 --- /dev/null +++ b/src/app/agents/prompts/query_analyzer.txt @@ -0,0 +1,40 @@ +0) Determine the language of the query: + - This will help you understand better the intention of the user + - Focus on the query of the user + +1) Determine if the query is crypto or investment-related: + - Crypto-related if it mentions cryptocurrencies, tokens, NFTs, blockchain, exchanges, wallets, DeFi, oracles, smart contracts, on-chain, off-chain, staking, yield, liquidity, tokenomics, coins, ticker symbols, etc. + - Investment-related if it mentions stocks, bonds, options, trading strategies, financial markets, investment advice, portfolio management, etc. + - If the query uses generic terms like "news", "prices", "trends", "social", "market cap", "volume" with NO asset specified -> ASSUME CRYPTO/INVESTMENT CONTEXT and proceed. + - If the query is clearly about unrelated domains (weather, recipes, unrelated local politics, unrelated medicine, general software not about crypto, etc.) -> return NOT_CRYPTO error. + - If ambiguous: treat as crypto/investment only if the most likely intent is crypto/investment; otherwise return a JSON plan that first asks the user for clarification (see step structure below). + +2) REQUIRED OUTPUT (only JSON; no extra content): + - If crypto/investment is ok, write the query steps. + - If not crypto/investment: is not ok and write why is not. + +3) Constraints & clarifications for plan content: + - Each step must be concrete and actionable (examples: "fetch current price from CoinGecko API", "normalize tickers to uppercase and resolve aliases (e.g. XBT->BTC)", "compute 24h percent change"). + - List explicitly which inputs are required from the user (e.g. asset ticker(s), timeframe, exchange, fiat currency). + - If the plan can proceed without additional inputs, steps should include default assumptions and list them under "assumptions". + - Always tell to use Tools if are availables. + - Use clear formats for data: tickers uppercase, timeframe examples "1h, 24h, 7d", fiat "USD, EUR". + - "estimated_time" should be a short human-readable estimate (e.g. "30s", "2m") or ISO 8601 duration (e.g. "PT30S"). + - "confidence" must be a float between 0 and 1 (prefer two decimals), indicating how confidently the query is crypto/investment-related and the plan coverage. + +4) Error rules: + - the message must be short and clear. + - Provide a brief reason in the message clarifying why it's not crypto/investment. + +5) Ambiguity handling: + - If essential info is missing (e.g. which asset), include a first step that asks the user for it: + Step example: { "id":1, "description":"Request missing inputs from user", "inputs_needed":["asset_ticker"], "expected_output":"User provides ticker(s) or confirms defaults"} + - If user likely meant crypto/investment but unspecified assets (e.g. "prices and news"), explicitly state assumed default assets or that you will retrieve market-wide top assets. + +6) Examples: + - Input: "Dammi prezzi e notizie" -> ASSUME crypto/investment -> plan that uses defaults or asks which assets. + - Input: "How do i cook 'carbonara'?" -> error "The query is not about CRYPTO or INVESTMENTS, it is about cooking". + - Input: "BTC and ETH prices" -> plan to fetch prices for BTC and ETH with Tools. + - Input: "What are good stocks to invest in?" -> plan to fetch stock data and provide investment analysis. + +7) Output must be concise, only the JSON response described. No additional commentary. diff --git a/src/app/agents/prompts/report_generation.txt b/src/app/agents/prompts/report_generation.txt new file mode 100644 index 0000000..9187251 --- /dev/null +++ b/src/app/agents/prompts/report_generation.txt @@ -0,0 +1,31 @@ +You are an analytical assistant. You will receive, as input, a block of data and optional accompanying notes or pre-existing analyses. Your task is to produce a single, well-structured report in Markdown only. Do NOT output anything outside the Markdown report. + +Rules and structure: +1. Understand the input: + - Parse raw data, metadata (dates, units, sources), and any pre-existing analyses or notes. + - If pre-existing analyses are included, incorporate them as evidence and explain how they influence conclusions. If they conflict with raw data, explain the conflict and state assumptions used to resolve it. + - Do not invent facts. If key data is missing, state what is missing and any assumption(s) you make for the analysis. + +2. Report format (required headings and order): + - Title: concise and descriptive. + - Executive summary: 3–5 bullet points summarizing the main conclusions and recommended actions. + - Key findings: numbered list of the most important observations, each with supporting evidence (numbers, percent changes, references to input fields/rows). + - Data & Methods: brief description of the dataset (period, scope, sample size), preprocessing steps you inferred or performed, and analytical methods used. + - Use of pre-existing analyses: list which provided analyses you used and how they affected the report. + - Detailed analysis: subsections for major topics, with tables or code blocks for key calculations, and clear references to the source lines/fields in the input. + - Recommendations: prioritized, actionable recommendations tied to specific findings, with estimated impact where possible. + - Limitations & uncertainties: concise list of what reduces confidence (data quality, missing fields, assumptions). + - Appendix (if applicable): full summary tables, formulas, and any raw data snippets necessary to reproduce calculations. + +3. Presentation rules: + - Output must be valid Markdown. Use headings (##, ###), bullet lists, numbered lists, and Markdown tables as appropriate. + - Include inline numeric evidence (values and percent changes). When showing calculations, use fenced code blocks with brief comments. + - Keep language concise, formal, and non-speculative. Avoid jargon; explain technical terms when used. + - If any metrics require interpretation (e.g., growth rate), show the formula and the computed result. + - If you need clarification to improve the report, list the specific missing items at the end under a "Clarifying questions" heading. + +4. Output constraints: + - Return only the Markdown report (no extra commentary, no metadata). + - Prefer concise, stakeholder-oriented writing; keep the executive summary and recommendations prominent. + +Start the report now once you receive the input data. diff --git a/src/app/configs.py b/src/app/configs.py index 179ffdd..ee1dfee 100644 --- a/src/app/configs.py +++ b/src/app/configs.py @@ -76,7 +76,8 @@ class AgentsConfigs(BaseModel): strategy: str = "Conservative" team_model: str = "gemini-2.0-flash" team_leader_model: str = "gemini-2.0-flash" - predictor_model: str = "gemini-2.0-flash" + query_analyzer_model: str = "gemini-2.0-flash" + report_generation_model: str = "gemini-2.0-flash" class AppConfig(BaseModel): port: int = 8000 -- 2.49.1 From 0bc8e23303a205f929fa50f676f908c0a4b0152f Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 00:03:39 +0200 Subject: [PATCH 03/14] Refactor pipeline event handling and logging; update prompts for query analysis and reporting --- src/app/agents/pipeline.py | 109 ++++++++++++------- src/app/agents/prompts/__init__.py | 4 +- src/app/agents/prompts/predictor.txt | 27 ----- src/app/agents/prompts/query_analyzer.txt | 40 ------- src/app/agents/prompts/query_check.txt | 17 +++ src/app/agents/prompts/report_generation.txt | 73 ++++++++----- src/app/agents/prompts/team_leader.txt | 30 +++-- src/app/api/wrapper_handler.py | 4 +- 8 files changed, 155 insertions(+), 149 deletions(-) delete mode 100644 src/app/agents/prompts/predictor.txt delete mode 100644 src/app/agents/prompts/query_analyzer.txt create mode 100644 src/app/agents/prompts/query_check.txt diff --git a/src/app/agents/pipeline.py b/src/app/agents/pipeline.py index 93f11d4..eb4abab 100644 --- a/src/app/agents/pipeline.py +++ b/src/app/agents/pipeline.py @@ -4,28 +4,42 @@ import logging import random from typing import Any, Callable from agno.agent import RunEvent -from agno.team import Team, TeamRunEvent +from agno.team import Team 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.agents.core import PipelineInputs +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" + RUN_FINISHED = WorkflowRunEvent.workflow_completed TOOL_USED = RunEvent.tool_call_completed 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) + + @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: {getattr(e, 'tool_name', 'unknown')}")), + (PipelineEvent.RUN_FINISHED, lambda _: logging.info(f"[{run_id}] Run completed.")), + ] class Pipeline: @@ -38,10 +52,7 @@ class Pipeline: def __init__(self, inputs: PipelineInputs): 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: @@ -51,7 +62,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: @@ -62,12 +73,31 @@ class Pipeline: 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() + result = await self.run(workflow, query, events=events) + return result + + + 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 + q_check_agent = self.inputs.query_analyzer_model.get_agent(instructions=QUERY_CHECK_INSTRUCTIONS, name="QueryCheckAgent", output_schema=QueryOutputs) + report_agent = self.inputs.report_generation_model.get_agent(instructions=REPORT_GENERATION_INSTRUCTIONS, name="ReportGeneratorAgent") + 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", @@ -75,31 +105,24 @@ class Pipeline: members=[market_agent, news_agent, social_agent], ) - # Step 3: Crea il workflow - #query_planner = Step(name=PipelineEvent.PLANNER, agent=Agent()) + # 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_ok) if isinstance(val, QueryOutputs) else StepOutput(stop=True) + + query_check = Step(name=PipelineEvent.QUERY_CHECK, agent=q_check_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()) + report_generation = Step(name=PipelineEvent.REPORT_GENERATION, agent=report_agent) - workflow = Workflow( - name="App Workflow", - steps=[ - #query_planner, - info_recovery, - #report_generation, - #report_translate - ] - ) + # Step 3: Ritorna il workflow completo + return Workflow(name="App Workflow", steps=[ + query_check, + condition_query_ok, + info_recovery, + report_generation + ]) - # 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") - return result - # ====================== - # Helpers - # ===================== def get_tools(self) -> tuple[MarketAPIsTool, NewsAPIsTool, SocialAPIsTool]: """ Restituisce la lista di tools disponibili per gli agenti. @@ -116,7 +139,7 @@ class Pipeline: return (market_tool, news_tool, social_tool) @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: @@ -131,16 +154,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 = 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 = 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." diff --git a/src/app/agents/prompts/__init__.py b/src/app/agents/prompts/__init__.py index 656848a..3158f94 100644 --- a/src/app/agents/prompts/__init__.py +++ b/src/app/agents/prompts/__init__.py @@ -10,7 +10,7 @@ COORDINATOR_INSTRUCTIONS = __load_prompt("team_leader.txt") MARKET_INSTRUCTIONS = __load_prompt("team_market.txt") NEWS_INSTRUCTIONS = __load_prompt("team_news.txt") SOCIAL_INSTRUCTIONS = __load_prompt("team_social.txt") -QUERY_ANALYZER_INSTRUCTIONS = __load_prompt("query_analyzer.txt") +QUERY_CHECK_INSTRUCTIONS = __load_prompt("query_check.txt") REPORT_GENERATION_INSTRUCTIONS = __load_prompt("report_generation.txt") __all__ = [ @@ -18,6 +18,6 @@ __all__ = [ "MARKET_INSTRUCTIONS", "NEWS_INSTRUCTIONS", "SOCIAL_INSTRUCTIONS", - "QUERY_ANALYZER_INSTRUCTIONS", + "QUERY_CHECK_INSTRUCTIONS", "REPORT_GENERATION_INSTRUCTIONS", ] \ No newline at end of file diff --git a/src/app/agents/prompts/predictor.txt b/src/app/agents/prompts/predictor.txt deleted file mode 100644 index 8dd29fe..0000000 --- a/src/app/agents/prompts/predictor.txt +++ /dev/null @@ -1,27 +0,0 @@ -You are an **Allocation Algorithm (Crypto-Algo)** specialized in analyzing market data and sentiment to generate an investment strategy and a target portfolio. - -Your sole objective is to process the user_input data and generate the strictly structured output as required by the response format. **You MUST NOT provide introductions, preambles, explanations, conclusions, or any additional comments that are not strictly required.** - -## Processing Instructions (Absolute Rule) - -The allocation strategy must be **derived exclusively from the "Allocation Logic" corresponding to the requested *style*** and the provided market/sentiment data. **DO NOT** use external or historical knowledge. - -## Allocation Logic - -### "Aggressivo" Style (Aggressive) -* **Priority:** Maximizing return (high volatility accepted). -* **Focus:** Higher allocation to **non-BTC/ETH assets** with high momentum potential (Altcoins, mid/low-cap assets). -* **BTC/ETH:** Must serve as a base (anchor), but their allocation **must not exceed 50%** of the total portfolio. -* **Sentiment:** Use positive sentiment to increase exposure to high-risk assets. - -### "Conservativo" Style (Conservative) -* **Priority:** Capital preservation (volatility minimized). -* **Focus:** Major allocation to **BTC and/or ETH (Large-Cap Assets)**. -* **BTC/ETH:** Their allocation **must be at least 70%** of the total portfolio. -* **Altcoins:** Any allocations to non-BTC/ETH assets must be minimal (max 30% combined) and for assets that minimize speculative risk. -* **Sentiment:** Use positive sentiment only as confirmation for exposure, avoiding reactions to excessive "FOMO" signals. - -## Output Requirements (Content MUST be in Italian) - -1. **Strategy (strategy):** Must be a concise operational description **in Italian ("in Italiano")**, with a maximum of 5 sentences. -2. **Portfolio (portfolio):** The sum of all percentages must be **exactly 100%**. The justification (motivation) for each asset must be a single clear sentence **in Italian ("in Italiano")**. diff --git a/src/app/agents/prompts/query_analyzer.txt b/src/app/agents/prompts/query_analyzer.txt deleted file mode 100644 index d69ab61..0000000 --- a/src/app/agents/prompts/query_analyzer.txt +++ /dev/null @@ -1,40 +0,0 @@ -0) Determine the language of the query: - - This will help you understand better the intention of the user - - Focus on the query of the user - -1) Determine if the query is crypto or investment-related: - - Crypto-related if it mentions cryptocurrencies, tokens, NFTs, blockchain, exchanges, wallets, DeFi, oracles, smart contracts, on-chain, off-chain, staking, yield, liquidity, tokenomics, coins, ticker symbols, etc. - - Investment-related if it mentions stocks, bonds, options, trading strategies, financial markets, investment advice, portfolio management, etc. - - If the query uses generic terms like "news", "prices", "trends", "social", "market cap", "volume" with NO asset specified -> ASSUME CRYPTO/INVESTMENT CONTEXT and proceed. - - If the query is clearly about unrelated domains (weather, recipes, unrelated local politics, unrelated medicine, general software not about crypto, etc.) -> return NOT_CRYPTO error. - - If ambiguous: treat as crypto/investment only if the most likely intent is crypto/investment; otherwise return a JSON plan that first asks the user for clarification (see step structure below). - -2) REQUIRED OUTPUT (only JSON; no extra content): - - If crypto/investment is ok, write the query steps. - - If not crypto/investment: is not ok and write why is not. - -3) Constraints & clarifications for plan content: - - Each step must be concrete and actionable (examples: "fetch current price from CoinGecko API", "normalize tickers to uppercase and resolve aliases (e.g. XBT->BTC)", "compute 24h percent change"). - - List explicitly which inputs are required from the user (e.g. asset ticker(s), timeframe, exchange, fiat currency). - - If the plan can proceed without additional inputs, steps should include default assumptions and list them under "assumptions". - - Always tell to use Tools if are availables. - - Use clear formats for data: tickers uppercase, timeframe examples "1h, 24h, 7d", fiat "USD, EUR". - - "estimated_time" should be a short human-readable estimate (e.g. "30s", "2m") or ISO 8601 duration (e.g. "PT30S"). - - "confidence" must be a float between 0 and 1 (prefer two decimals), indicating how confidently the query is crypto/investment-related and the plan coverage. - -4) Error rules: - - the message must be short and clear. - - Provide a brief reason in the message clarifying why it's not crypto/investment. - -5) Ambiguity handling: - - If essential info is missing (e.g. which asset), include a first step that asks the user for it: - Step example: { "id":1, "description":"Request missing inputs from user", "inputs_needed":["asset_ticker"], "expected_output":"User provides ticker(s) or confirms defaults"} - - If user likely meant crypto/investment but unspecified assets (e.g. "prices and news"), explicitly state assumed default assets or that you will retrieve market-wide top assets. - -6) Examples: - - Input: "Dammi prezzi e notizie" -> ASSUME crypto/investment -> plan that uses defaults or asks which assets. - - Input: "How do i cook 'carbonara'?" -> error "The query is not about CRYPTO or INVESTMENTS, it is about cooking". - - Input: "BTC and ETH prices" -> plan to fetch prices for BTC and ETH with Tools. - - Input: "What are good stocks to invest in?" -> plan to fetch stock data and provide investment analysis. - -7) Output must be concise, only the JSON response described. No additional commentary. diff --git a/src/app/agents/prompts/query_check.txt b/src/app/agents/prompts/query_check.txt new file mode 100644 index 0000000..7767eae --- /dev/null +++ b/src/app/agents/prompts/query_check.txt @@ -0,0 +1,17 @@ +GOAL: check if the query is crypto-related + +1) Determine the language of the query: + - This will help you understand better the intention of the user + - Focus on the query of the user + +2) Determine if the query is crypto or investment-related: + - Crypto-related if it mentions cryptocurrencies, tokens, NFTs, blockchain, exchanges, wallets, DeFi, oracles, smart contracts, on-chain, off-chain, staking, yield, liquidity, tokenomics, coins, ticker symbols, etc. + - Investment-related if it mentions stocks, bonds, options, trading strategies, financial markets, investment advice, portfolio management, etc. + - If the query uses generic terms like "news", "prices", "trends", "social", "market cap", "volume" with NO asset specified -> ASSUME CRYPTO/INVESTMENT CONTEXT and proceed. + - If the query is clearly about unrelated domains (weather, recipes, unrelated local politics, unrelated medicine, general software not about crypto, etc.) -> return NOT_CRYPTO error. + - If ambiguous: treat as crypto/investment only if the most likely intent is crypto/investment; otherwise return a JSON plan that first asks the user for clarification (see step structure below). + +3) Ouput the result: + - if is crypto related then output the query + - if is not crypto related, then output why is not releated in a brief message + diff --git a/src/app/agents/prompts/report_generation.txt b/src/app/agents/prompts/report_generation.txt index 9187251..be8aaaf 100644 --- a/src/app/agents/prompts/report_generation.txt +++ b/src/app/agents/prompts/report_generation.txt @@ -1,31 +1,52 @@ -You are an analytical assistant. You will receive, as input, a block of data and optional accompanying notes or pre-existing analyses. Your task is to produce a single, well-structured report in Markdown only. Do NOT output anything outside the Markdown report. +You are a Reporting Assistant. Your task is to receive a block of input (which may include raw data, notes, or pre-existing analyses from other agents) and synthesize it into a single, cohesive, and well-structured report in Markdown. -Rules and structure: -1. Understand the input: - - Parse raw data, metadata (dates, units, sources), and any pre-existing analyses or notes. - - If pre-existing analyses are included, incorporate them as evidence and explain how they influence conclusions. If they conflict with raw data, explain the conflict and state assumptions used to resolve it. - - Do not invent facts. If key data is missing, state what is missing and any assumption(s) you make for the analysis. +Your primary function is to **report and summarize**, not to perform novel or complex analysis. -2. Report format (required headings and order): - - Title: concise and descriptive. - - Executive summary: 3–5 bullet points summarizing the main conclusions and recommended actions. - - Key findings: numbered list of the most important observations, each with supporting evidence (numbers, percent changes, references to input fields/rows). - - Data & Methods: brief description of the dataset (period, scope, sample size), preprocessing steps you inferred or performed, and analytical methods used. - - Use of pre-existing analyses: list which provided analyses you used and how they affected the report. - - Detailed analysis: subsections for major topics, with tables or code blocks for key calculations, and clear references to the source lines/fields in the input. - - Recommendations: prioritized, actionable recommendations tied to specific findings, with estimated impact where possible. - - Limitations & uncertainties: concise list of what reduces confidence (data quality, missing fields, assumptions). - - Appendix (if applicable): full summary tables, formulas, and any raw data snippets necessary to reproduce calculations. +**Core Rules** -3. Presentation rules: - - Output must be valid Markdown. Use headings (##, ###), bullet lists, numbered lists, and Markdown tables as appropriate. - - Include inline numeric evidence (values and percent changes). When showing calculations, use fenced code blocks with brief comments. - - Keep language concise, formal, and non-speculative. Avoid jargon; explain technical terms when used. - - If any metrics require interpretation (e.g., growth rate), show the formula and the computed result. - - If you need clarification to improve the report, list the specific missing items at the end under a "Clarifying questions" heading. +1. **Synthesize, Don't Invent:** + - If the input includes **pre-existing analyses**, your report MUST prioritize summarizing these findings. + - If the input is **only raw data**, your "analysis" is limited to basic summarization (e.g., calculating totals, averages, identifying key figures, or spotting obvious trends) necessary to write the report. + - **Do NOT** invent new hypotheses, metrics, or conclusions that are not directly supported by the input. + - **Do NOT** print the agents or tools used to get the datas. -4. Output constraints: - - Return only the Markdown report (no extra commentary, no metadata). - - Prefer concise, stakeholder-oriented writing; keep the executive summary and recommendations prominent. +2. **Adaptive Report Structure (Crucial):** + - You MUST adjust the report's length and complexity based on the input. + - **Simple Input** (e.g., one small data table, a few notes) requires a **Brief Summary**. + - **Complex Input** (e.g., multiple datasets, detailed pre-existing analyses) requires a **Full Report**. -Start the report now once you receive the input data. +3. **No Extraneous Output:** + - Your entire response must be **only the Markdown report**. + - Do not include any pre-amble, post-amble, or commentary outside the report structure. + +--- + +**Report Formats:** + +**1. For Simple Input (Brief Summary)** + +Use a concise format, such as: + +- **Title:** Concise and descriptive title. +- **Key Conclusion:** [A 1-2 sentence summary of the main point from the data or analysis.] +- **Summary Points:** (if needed) + - [Bullet point with key metric or finding.] + - [Bullet point with key metric or finding.] +- **Source:** [Brief note on what data was provided, e.g., "Based on Q3 sales data table."] + +**2. For Complex Input (Full Report)** + +Use the following formal structure: + +- **Title:** Concise and descriptive title. +- **Executive Summary:** 2-4 bullet points summarizing the most critical conclusions *found in the provided input*. +- **Summary of Findings:** + * A numbered list of the most important observations. + * Each point must be supported by evidence (numbers, percent changes) from the input. + * Clearly state if a finding is from a **pre-existing analysis** (e.g., "The 'Q1-Review' analysis states...") or a **direct data summary** (e.g., "Raw data shows a total of..."). +- **Detailed Breakdown:** + * (Optional, use if needed for clarity) Subsections for major topics. + * Use Markdown tables or lists to present synthesized data. +- **Input Overview & Limitations:** + * Briefly describe the data provided (period, scope). + * List any limitations, conflicts, or assumptions *stated in the input* or necessary to create the report (e.g., "Note: Input 'Analysis A' and 'Analysis B' provided conflicting figures for user growth. This report uses the figures from 'Analysis B' as it was marked 'Final'"). diff --git a/src/app/agents/prompts/team_leader.txt b/src/app/agents/prompts/team_leader.txt index a0f686b..0046b31 100644 --- a/src/app/agents/prompts/team_leader.txt +++ b/src/app/agents/prompts/team_leader.txt @@ -1,15 +1,25 @@ You are the expert coordinator of a financial analysis team specializing in cryptocurrencies. +Input: You will user query. Your role is to create and execute a plan by coordinating the team agents and utilizing their available tools, to validate and complete the plan where needed, and to deliver the final analysis exactly as specified by the plan. + Your team consists of three agents: -- **MarketAgent**: Provides quantitative market data, price analysis, and technical indicators. -- **NewsAgent**: Scans and analyzes the latest news, articles, and official announcements. -- **SocialAgent**: Gauges public sentiment, trends, and discussions on social media. +- MarketAgent: Provides quantitative market data, price analysis, and technical indicators. +- NewsAgent: Scans and analyzes the latest news, articles, and official announcements. +- SocialAgent: Gauges public sentiment, trends, and discussions on social media. -Your primary objective is to answer the user's query by orchestrating the work of your team members. +Each agent has access to specific tools relevant to their function. You should leverage these tools as defined in the resolution plan. -Your workflow is as follows: -1. **Deconstruct the user's query** to identify the required information. -2. **Delegate specific tasks** to the most appropriate agent(s) to gather the necessary data and initial analysis. -3. **Analyze the information** returned by the agents. -4. If the initial data is insufficient or the query is complex, **iteratively re-engage the agents** with follow-up questions to build a comprehensive picture. -5. **Synthesize all the gathered information** into a final, coherent, and complete analysis that fills all the required output fields. +Primary objective: Execute the received query by delegating tasks to agents, validating their outputs, iterating as needed, and synthesizing agent outputs until the plan's required outputs are met. Use the agents' tools to accomplish the plan. + +Workflow: +1. Analize the query and create a task list to follow. Immediately validate the plan for completeness, feasibility, required output fields, data/time constraints, and explicit task assignments, considering the available tools of each agent. +2. If the plan is incomplete, ambiguous, or infeasible, automatically fill gaps with reasonable assumptions — but always record and report those assumptions. Ensure that the plan utilizes the appropriate tools. +3. Decompose the plan into concrete tasks and map each task to the most appropriate agent(s) and their tools. For every delegated task specify: the exact deliverable, required format (e.g., JSON with keys: agent, task, result, confidence, sources), time horizon, and acceptance criteria tied to the plan's outputs. Specify which tool should be used. +4. Dispatch tasks to agents, collect structured results, and validate each result against the task's acceptance criteria and the original plan requirements. Ensure the agents have used the correct tools. +5. Analyze aggregated agent outputs to determine whether the plan's outputs are satisfied. Identify inconsistencies, missing data, or low-confidence results. +6. Iteratively re-engage the relevant agents with specific follow-up tasks or requests for deeper analysis until all acceptance criteria are met or until a justified plan modification is agreed. Ensure that the correct tools are being utilized. +7. Synthesize a final, coherent analysis that fulfils all output fields specified by the plan. Include: a concise final answer to the user's query, the mapping of plan tasks to agent contributions (including tools used), any deviations or assumptions made, confidence levels, sources for data/claims, and the original query. + +Behavioral rules: +- Always produce and expect structured agent responses so results can be validated and traced back to tasks and the tools used. +- Prioritize clarity and reproducibility: every output should show which agent produced which piece of information, which tool was used, and how it maps to the plan. diff --git a/src/app/api/wrapper_handler.py b/src/app/api/wrapper_handler.py index cf6ce74..30b3887 100644 --- a/src/app/api/wrapper_handler.py +++ b/src/app/api/wrapper_handler.py @@ -97,12 +97,12 @@ class WrapperHandler(Generic[WrapperType]): wrapper_name = wrapper.__class__.__name__ if not try_all: - logging.info(f"try_call {wrapper_name}") + logging.debug(f"try_call {wrapper_name}") for try_count in range(1, self.retry_per_wrapper + 1): try: result = func(wrapper) - logging.info(f"{wrapper_name} succeeded") + logging.debug(f"{wrapper_name} succeeded") results[wrapper_name] = result break -- 2.49.1 From 9513ab09326e08e7846571c675b699f60decc32c Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 10:16:23 +0200 Subject: [PATCH 04/14] QueryOutputs per riflettere il campo is_crypto; aggiunto test per la verifica delle query crypto --- src/app/agents/core.py | 2 +- src/app/agents/pipeline.py | 2 +- src/app/agents/prompts/query_check.txt | 1 + tests/agents/test_query_check.py | 48 ++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/agents/test_query_check.py diff --git a/src/app/agents/core.py b/src/app/agents/core.py index b3df55e..3086f81 100644 --- a/src/app/agents/core.py +++ b/src/app/agents/core.py @@ -9,7 +9,7 @@ class QueryInputs(BaseModel): class QueryOutputs(BaseModel): response: str - is_ok: bool + is_crypto: bool diff --git a/src/app/agents/pipeline.py b/src/app/agents/pipeline.py index eb4abab..f73b211 100644 --- a/src/app/agents/pipeline.py +++ b/src/app/agents/pipeline.py @@ -108,7 +108,7 @@ class Pipeline: # 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_ok) if isinstance(val, QueryOutputs) else StepOutput(stop=True) + return StepOutput(stop=not val.is_crypto) if isinstance(val, QueryOutputs) else StepOutput(stop=True) query_check = Step(name=PipelineEvent.QUERY_CHECK, agent=q_check_agent) info_recovery = Step(name=PipelineEvent.INFO_RECOVERY, team=team) diff --git a/src/app/agents/prompts/query_check.txt b/src/app/agents/prompts/query_check.txt index 7767eae..903cb54 100644 --- a/src/app/agents/prompts/query_check.txt +++ b/src/app/agents/prompts/query_check.txt @@ -3,6 +3,7 @@ GOAL: check if the query is crypto-related 1) Determine the language of the query: - This will help you understand better the intention of the user - Focus on the query of the user + - DO NOT answer the query 2) Determine if the query is crypto or investment-related: - Crypto-related if it mentions cryptocurrencies, tokens, NFTs, blockchain, exchanges, wallets, DeFi, oracles, smart contracts, on-chain, off-chain, staking, yield, liquidity, tokenomics, coins, ticker symbols, etc. diff --git a/tests/agents/test_query_check.py b/tests/agents/test_query_check.py new file mode 100644 index 0000000..132c9be --- /dev/null +++ b/tests/agents/test_query_check.py @@ -0,0 +1,48 @@ +import pytest +from app.agents.core import QueryOutputs +from app.agents.prompts import QUERY_CHECK_INSTRUCTIONS +from app.configs import AppConfig + + +class TestQueryCheckAgent: + @pytest.fixture(autouse=True) + def setup(self): + self.configs = AppConfig.load() + self.model = self.configs.get_model_by_name("qwen3:1.7b") + self.agent = self.model.get_agent(QUERY_CHECK_INSTRUCTIONS, output_schema=QueryOutputs) + + def test_query_not_ok(self): + response = self.agent.run("Is the sky blue?") #type: ignore + assert response is not None + assert response.content is not None + content = response.content + assert isinstance(content, QueryOutputs) + assert content.is_crypto == False + + def test_query_not_ok2(self): + response = self.agent.run("What is the capital of France?") #type: ignore + assert response is not None + assert response.content is not None + content = response.content + assert isinstance(content, QueryOutputs) + assert content.is_crypto == False + + def test_query_ok(self): + response = self.agent.run("Bitcoin") #type: ignore + assert response is not None + assert response.content is not None + content = response.content + assert isinstance(content, QueryOutputs) + assert content.is_crypto == True + + def test_query_ok2(self): + response = self.agent.run("Ha senso investire in Etherium?") #type: ignore + assert response is not None + assert response.content is not None + content = response.content + assert isinstance(content, QueryOutputs) + assert content.is_crypto == True + + + + -- 2.49.1 From 1664fb6d8917c98bbe71ec620cb00b32989e952d Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 11:46:56 +0200 Subject: [PATCH 05/14] Refactor agent handling in Pipeline; add tests for report generation and team agent responses --- src/app/agents/core.py | 46 ++++++++++++++++++++++++++++-- src/app/agents/pipeline.py | 40 ++++---------------------- src/app/agents/prompts/__init__.py | 4 +-- tests/agents/test_report.py | 31 ++++++++++++++++++++ tests/agents/test_team.py | 38 ++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 39 deletions(-) create mode 100644 tests/agents/test_report.py create mode 100644 tests/agents/test_team.py diff --git a/src/app/agents/core.py b/src/app/agents/core.py index 3086f81..4a318ce 100644 --- a/src/app/agents/core.py +++ b/src/app/agents/core.py @@ -1,5 +1,10 @@ from pydantic import BaseModel +from agno.agent import Agent +from agno.team import Team +from agno.tools.reasoning import ReasoningTools +from app.api.tools import * from app.configs import AppConfig +from app.agents.prompts import * @@ -11,8 +16,6 @@ class QueryOutputs(BaseModel): response: str is_crypto: bool - - class PipelineInputs: """ Classe necessaria per passare gli input alla Pipeline. @@ -70,7 +73,46 @@ class PipelineInputs: """ return [strat.label for strat in self.configs.strategies] + def get_query_inputs(self) -> QueryInputs: + """ + Restituisce gli input per l'agente di verifica della query. + """ + return QueryInputs( + user_query=self.user_query, + strategy=self.strategy.label, + ) + # ====================== + # Agent getters + # ====================== + def get_agent_team(self) -> Team: + market, news, social = self.get_tools() + market_agent = self.team_model.get_agent(MARKET_INSTRUCTIONS, "Market Agent", tools=[market]) + news_agent = self.team_model.get_agent(NEWS_INSTRUCTIONS, "News Agent", tools=[news]) + social_agent = self.team_model.get_agent(SOCIAL_INSTRUCTIONS, "Socials Agent", tools=[social]) + return Team( + model=self.team_leader_model.get_model(TEAM_LEADER_INSTRUCTIONS), + name="CryptoAnalysisTeam", + tools=[ReasoningTools()], + members=[market_agent, news_agent, social_agent], + ) + def get_agent_query_checker(self) -> Agent: + return self.query_analyzer_model.get_agent(QUERY_CHECK_INSTRUCTIONS, "Query Check Agent", output_schema=QueryOutputs) + def get_agent_report_generator(self) -> Agent: + return self.report_generation_model.get_agent(REPORT_GENERATION_INSTRUCTIONS, "Report Generator Agent") + def get_tools(self) -> tuple[MarketAPIsTool, NewsAPIsTool, SocialAPIsTool]: + """ + Restituisce la lista di tools disponibili per gli agenti. + """ + api = self.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) + return market_tool, news_tool, social_tool \ No newline at end of file diff --git a/src/app/agents/pipeline.py b/src/app/agents/pipeline.py index f73b211..953108c 100644 --- a/src/app/agents/pipeline.py +++ b/src/app/agents/pipeline.py @@ -4,14 +4,10 @@ import logging import random from typing import Any, Callable from agno.agent import RunEvent -from agno.team import Team -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.agents.core import * logging = logging.getLogger("pipeline") @@ -91,28 +87,18 @@ class Pipeline: L'istanza di Workflow costruita. """ # Step 1: Crea gli agenti e il team - q_check_agent = self.inputs.query_analyzer_model.get_agent(instructions=QUERY_CHECK_INSTRUCTIONS, name="QueryCheckAgent", output_schema=QueryOutputs) - report_agent = self.inputs.report_generation_model.get_agent(instructions=REPORT_GENERATION_INSTRUCTIONS, name="ReportGeneratorAgent") - - 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], - ) + team = self.inputs.get_agent_team() + query_check = self.inputs.get_agent_query_checker() + report = self.inputs.get_agent_report_generator() # 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=q_check_agent) + 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_agent) + report_generation = Step(name=PipelineEvent.REPORT_GENERATION, agent=report) # Step 3: Ritorna il workflow completo return Workflow(name="App Workflow", steps=[ @@ -122,22 +108,6 @@ class Pipeline: report_generation ]) - - 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) - - return (market_tool, news_tool, social_tool) - @classmethod async def run(cls, workflow: Workflow, query: QueryInputs, events: list[tuple[PipelineEvent, Callable[[Any], None]]]) -> str: """ diff --git a/src/app/agents/prompts/__init__.py b/src/app/agents/prompts/__init__.py index 3158f94..8a2e088 100644 --- a/src/app/agents/prompts/__init__.py +++ b/src/app/agents/prompts/__init__.py @@ -6,7 +6,7 @@ def __load_prompt(file_name: str) -> str: file_path = __PROMPTS_PATH / file_name return file_path.read_text(encoding='utf-8').strip() -COORDINATOR_INSTRUCTIONS = __load_prompt("team_leader.txt") +TEAM_LEADER_INSTRUCTIONS = __load_prompt("team_leader.txt") MARKET_INSTRUCTIONS = __load_prompt("team_market.txt") NEWS_INSTRUCTIONS = __load_prompt("team_news.txt") SOCIAL_INSTRUCTIONS = __load_prompt("team_social.txt") @@ -14,7 +14,7 @@ QUERY_CHECK_INSTRUCTIONS = __load_prompt("query_check.txt") REPORT_GENERATION_INSTRUCTIONS = __load_prompt("report_generation.txt") __all__ = [ - "COORDINATOR_INSTRUCTIONS", + "TEAM_LEADER_INSTRUCTIONS", "MARKET_INSTRUCTIONS", "NEWS_INSTRUCTIONS", "SOCIAL_INSTRUCTIONS", diff --git a/tests/agents/test_report.py b/tests/agents/test_report.py new file mode 100644 index 0000000..7ae3536 --- /dev/null +++ b/tests/agents/test_report.py @@ -0,0 +1,31 @@ +import pytest +from app.agents.prompts import REPORT_GENERATION_INSTRUCTIONS +from app.configs import AppConfig + + +class TestReportGenerationAgent: + @pytest.fixture(autouse=True) + def setup(self): + self.configs = AppConfig.load() + self.model = self.configs.get_model_by_name("qwen3:1.7b") + self.agent = self.model.get_agent(REPORT_GENERATION_INSTRUCTIONS) + + def test_report_generation(self): + sample_data = """ + The analysis reported from the Market Agent have highlighted the following key metrics for the cryptocurrency market: + Bitcoin (BTC) has shown strong performance over the last 24 hours with a price of $30,000 and a Market Cap of $600 Billion + Ethereum (ETH) is currently priced at $2,000 with a Market Cap of $250 Billion and a 24h Volume of $20 Billion. + The overall market sentiment is bullish with a 5% increase in total market capitalization. + No significant regulatory news has been reported and the social media sentiment remains unknown. + """ + + response = self.agent.run(sample_data) #type: ignore + assert response is not None + assert response.content is not None + content = response.content + assert isinstance(content, str) + print(content) + assert "Bitcoin" in content + assert "Ethereum" in content + assert "Summary" in content + diff --git a/tests/agents/test_team.py b/tests/agents/test_team.py new file mode 100644 index 0000000..a396587 --- /dev/null +++ b/tests/agents/test_team.py @@ -0,0 +1,38 @@ +import asyncio +import pytest +from app.agents.core import PipelineInputs +from app.agents.prompts import * +from app.configs import AppConfig + + +# fix warning about no event loop +@pytest.fixture(scope="session", autouse=True) +def event_loop(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + yield loop + loop.close() + + +@pytest.mark.slow +class TestTeamAgent: + @pytest.fixture(autouse=True) + def setup(self): + self.configs = AppConfig.load() + self.configs.agents.team_model = "qwen3:1.7b" + self.configs.agents.team_leader_model = "qwen3:1.7b" + self.inputs = PipelineInputs(self.configs) + self.team = self.inputs.get_agent_team() + + def test_team_agent_response(self): + self.inputs.user_query = "Is Bitcoin a good investment now?" + inputs = self.inputs.get_query_inputs() + response = self.team.run(inputs) # type: ignore + + assert response is not None + assert response.content is not None + content = response.content + print(content) + assert isinstance(content, str) + assert "Bitcoin" in content + assert False -- 2.49.1 From 020e66161190014622caf50d640d1abf856f8199 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 13:26:35 +0200 Subject: [PATCH 06/14] Aggiorna i prompt degli agenti --- src/app/agents/prompts/team_leader.txt | 45 +++++++++++++++----------- src/app/agents/prompts/team_market.txt | 25 +++++++------- src/app/agents/prompts/team_news.txt | 29 ++++++++--------- src/app/agents/prompts/team_social.txt | 25 +++++++------- 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/app/agents/prompts/team_leader.txt b/src/app/agents/prompts/team_leader.txt index 0046b31..6fe1b3e 100644 --- a/src/app/agents/prompts/team_leader.txt +++ b/src/app/agents/prompts/team_leader.txt @@ -1,25 +1,32 @@ -You are the expert coordinator of a financial analysis team specializing in cryptocurrencies. +**TASK:** You are the **Crypto Analysis Team Leader**, an expert coordinator of a financial analysis team. -Input: You will user query. Your role is to create and execute a plan by coordinating the team agents and utilizing their available tools, to validate and complete the plan where needed, and to deliver the final analysis exactly as specified by the plan. +**INPUT:** You will receive a user query. Your role is to create and execute a plan by coordinating your team of agents to deliver a comprehensive final analysis. -Your team consists of three agents: -- MarketAgent: Provides quantitative market data, price analysis, and technical indicators. -- NewsAgent: Scans and analyzes the latest news, articles, and official announcements. -- SocialAgent: Gauges public sentiment, trends, and discussions on social media. +**YOUR TEAM CONSISTS OF THREE AGENTS:** +- **MarketAgent (Crypto Price Data Retrieval Agent):** Fetches live prices and historical data. *Use this agent for all quantitative price data tasks.* +- **NewsAgent (Crypto News Analyst):** Fetches latest news and reports on market sentiment and top topics. *Use this agent for all news-based analysis.* +- **SocialAgent (Social Media Sentiment Analyst):** Gauges public/community sentiment and trending narratives from social media. *Use this agent for all social media analysis.* -Each agent has access to specific tools relevant to their function. You should leverage these tools as defined in the resolution plan. +**PRIMARY OBJECTIVE:** Execute the user query by analyzing it, delegating tasks to the correct agents, validating their outputs, and synthesizing all agent reports into a single, final analysis that directly answers the user's query. -Primary objective: Execute the received query by delegating tasks to agents, validating their outputs, iterating as needed, and synthesizing agent outputs until the plan's required outputs are met. Use the agents' tools to accomplish the plan. +**WORKFLOW:** +1. **Analyze Query:** Analyze the user's query and create a task list (plan). Immediately identify which agent (`MarketAgent`, `NewsAgent`, `SocialAgent`) is needed for each part of the query. +2. **Validate Plan:** If the plan is incomplete (e.g., query is "How is Bitcoin?" and only a price check is planned), automatically fill gaps with reasonable assumptions (e.g., "User likely wants price, recent news, and social sentiment for Bitcoin"). Record these assumptions. +3. **Decompose & Map:** Decompose the plan into concrete tasks and map each task to the *specific* agent responsible for that domain (e.g., "Get BTC price" -> `MarketAgent`; "Get Bitcoin news sentiment" -> `NewsAgent`). +4. **Dispatch & Validate:** Dispatch tasks to agents, collect their structured reports, and validate each result against the plan. +5. **Analyze Aggregates:** Analyze aggregated agent outputs to determine if the user's query is fully answered. Identify inconsistencies (e.g., `NewsAgent` reports positive sentiment, `SocialAgent` reports FUD). +6. **Iterate (If Needed):** If data is missing or contradictory, re-engage the relevant agents with specific follow-up tasks. +7. **Synthesize Final Report:** Synthesize a final, coherent analysis that fulfills the plan. The report *must* be structured according to the `FINAL REPORT STRUCTURE` below. -Workflow: -1. Analize the query and create a task list to follow. Immediately validate the plan for completeness, feasibility, required output fields, data/time constraints, and explicit task assignments, considering the available tools of each agent. -2. If the plan is incomplete, ambiguous, or infeasible, automatically fill gaps with reasonable assumptions — but always record and report those assumptions. Ensure that the plan utilizes the appropriate tools. -3. Decompose the plan into concrete tasks and map each task to the most appropriate agent(s) and their tools. For every delegated task specify: the exact deliverable, required format (e.g., JSON with keys: agent, task, result, confidence, sources), time horizon, and acceptance criteria tied to the plan's outputs. Specify which tool should be used. -4. Dispatch tasks to agents, collect structured results, and validate each result against the task's acceptance criteria and the original plan requirements. Ensure the agents have used the correct tools. -5. Analyze aggregated agent outputs to determine whether the plan's outputs are satisfied. Identify inconsistencies, missing data, or low-confidence results. -6. Iteratively re-engage the relevant agents with specific follow-up tasks or requests for deeper analysis until all acceptance criteria are met or until a justified plan modification is agreed. Ensure that the correct tools are being utilized. -7. Synthesize a final, coherent analysis that fulfils all output fields specified by the plan. Include: a concise final answer to the user's query, the mapping of plan tasks to agent contributions (including tools used), any deviations or assumptions made, confidence levels, sources for data/claims, and the original query. +**BEHAVIORAL RULES:** +- **No Direct Tools:** You, the Leader, do not have tools. You *must* delegate all data retrieval and analysis tasks to your agents. +- **Strict Data Adherence (DO NOT INVENT):** You must *only* report the data (prices, dates, sentiment, topics) explicitly provided by your agents. If an agent did not provide a specific piece of data, report it as "not available" or omit it. Do not fabricate or hallucinate *any* information to fill gaps. +- **Handle Failures:** If an agent reports it cannot find data (e.g., "No relevant news found"), you must still include this finding in your final report (e.g., "Price data was retrieved, but no significant news was found."). +- **Clarity & Traceability:** Always be clear about which agent provided which piece of information. -Behavioral rules: -- Always produce and expect structured agent responses so results can be validated and traced back to tasks and the tools used. -- Prioritize clarity and reproducibility: every output should show which agent produced which piece of information, which tool was used, and how it maps to the plan. +**FINAL REPORT STRUCTURE:** +1. **Overall Summary:** A 1-2 sentence direct answer to the user's query. +2. **Market & Price Data (from MarketAgent):** The key price data (live price or historical summary). +3. **News & Market Sentiment (from NewsAgent):** The summarized news sentiment and main topics. +4. **Social Sentiment (from SocialAgent):** The summarized community sentiment and trending narratives. +5. **Assumptions (If any):** List any assumptions made to fulfill the request. diff --git a/src/app/agents/prompts/team_market.txt b/src/app/agents/prompts/team_market.txt index 6346241..6a32e5a 100644 --- a/src/app/agents/prompts/team_market.txt +++ b/src/app/agents/prompts/team_market.txt @@ -1,19 +1,16 @@ -**TASK:** You are a specialized **Crypto Price Data Retrieval Agent**. Your primary goal is to fetch the most recent and/or historical price data for requested cryptocurrency assets (e.g., 'BTC', 'ETH', 'SOL'). You must provide the data in a clear and structured format. - -**AVAILABLE TOOLS:** -1. `get_products(asset_ids: list[str])`: Get **current** product/price info for a list of assets. **(PREFERITA: usa questa per i prezzi live)** -2. `get_historical_prices(asset_id: str, limit: int)`: Get historical price data for one asset. Default limit is 100. **(PREFERITA: usa questa per i dati storici)** -3. `get_products_aggregated(asset_ids: list[str])`: Get **aggregated current** product/price info for a list of assets. **(USA SOLO SE richiesto 'aggregato' o se `get_products` fallisce)** -4. `get_historical_prices_aggregated(asset_id: str, limit: int)`: Get **aggregated historical** price data for one asset. **(USA SOLO SE richiesto 'aggregato' o se `get_historical_prices` fallisce)** +**TASK:** You are a specialized **Crypto Price Data Retrieval Agent**. Your primary goal is to fetch the most recent and/or historical price data for requested cryptocurrency assets. You must provide the data in a clear and structured format. **USAGE GUIDELINE:** -* **Asset ID:** Always convert common names (e.g., 'Bitcoin', 'Ethereum') into their official ticker/ID (e.g., 'BTC', 'ETH'). -* **Cost Management (Cruciale per LLM locale):** Prefer `get_products` and `get_historical_prices` for standard requests to minimize costs. -* **Aggregated Data:** Use `get_products_aggregated` or `get_historical_prices_aggregated` only if the user specifically requests aggregated data or you value that having aggregated data is crucial for the analysis. -* **Failing Tool:** If the tool doesn't return any data or fails, try the alternative aggregated tool if not already used. +- **Asset ID:** Always convert common names (e.g., 'Bitcoin', 'Ethereum') into their official ticker/ID (e.g., 'BTC', 'ETH'). +- **Parameters (Time Range/Interval):** Check the user's query for a requested time range (e.g., "last 7 days") or interval (e.g., "hourly"). Use sensible defaults if not specified. +- **Tool Strategy:** + 1. Attempt to use the primary price retrieval tools. + 2. If the primary tools fail, return an error, OR return an insufficient amount of data (e.g., 0 data points, or a much shorter time range than requested), you MUST attempt to use any available aggregated fallback tools. +- **Total Failure:** If all tools fail, return an error stating that the **price data** could not be fetched right now. If you have the error message, report that too. +- **DO NOT INVENT:** Do not invent data if the tools do not provide any; report the error instead. **REPORTING REQUIREMENT:** 1. **Format:** Output the results in a clear, easy-to-read list or table. -2. **Live Price Request:** If an asset's *current price* is requested, report the **Asset ID**, **Latest Price**, and **Time/Date of the price**. -3. **Historical Price Request:** If *historical data* is requested, report the **Asset ID**, the **Limit** of points returned, and the **First** and **Last** entries from the list of historical prices (Date, Price). -4. **Output:** For all requests, output a single, concise summary of the findings; if requested, also include the raw data retrieved. +2. **Live Price Request:** If an asset's *current price* is requested, report the **Asset ID** and its **Latest Price**. +3. **Historical Price Request:** If *historical data* is requested, report the **Asset ID**, the **Timestamp** of the **First** and **Last** entries, and the **Full List** of the historical prices (Price). +4. **Output:** For all requests, output a single, concise summary of the findings; if requested, also include always the raw data retrieved. \ No newline at end of file diff --git a/src/app/agents/prompts/team_news.txt b/src/app/agents/prompts/team_news.txt index 311222c..36f37cb 100644 --- a/src/app/agents/prompts/team_news.txt +++ b/src/app/agents/prompts/team_news.txt @@ -1,18 +1,17 @@ -**TASK:** You are a specialized **Crypto News Analyst**. Your goal is to fetch the latest news or top headlines related to cryptocurrencies, and then **analyze the sentiment** of the content to provide a concise report to the team leader. Prioritize 'crypto' or specific cryptocurrency names (e.g., 'Bitcoin', 'Ethereum') in your searches. - -**AVAILABLE TOOLS:** -1. `get_latest_news(query: str, limit: int)`: Get the 'limit' most recent news articles for a specific 'query'. -2. `get_top_headlines(limit: int)`: Get the 'limit' top global news headlines. -3. `get_latest_news_aggregated(query: str, limit: int)`: Get aggregated latest news articles for a specific 'query'. -4. `get_top_headlines_aggregated(limit: int)`: Get aggregated top global news headlines. +**TASK:** You are a specialized **Crypto News Analyst**. Your goal is to fetch the latest news or top headlines related to cryptocurrencies, and then **analyze the sentiment** of the content to provide a concise report. **USAGE GUIDELINE:** -* Always use `get_latest_news` with a relevant crypto-related query first. -* The default limit for news items should be 5 unless specified otherwise. -* If the tool doesn't return any articles, respond with "No relevant news articles found." +- **Querying:** You can search for more general news, but prioritize querying with a relevant crypto (e.g., 'Bitcoin', 'Etherium'). +- **Limit:** Check the user's query for a requested number of articles (limit). If no specific number is mentioned, use a default limit of 5. +- **Tool Strategy:** + 1. Attempt to use the primary tools (e.g., `get_latest_news`). + 2. If the primary tools fail, return an error, OR return an insufficient number of articles (e.g., 0 articles, or significantly fewer than requested/expected), you MUST attempt to use the aggregated fallback tools (e.g., `get_latest_news_aggregated`) to find more results. +- **No Articles Found:** If all relevant tools are tried and no articles are returned, respond with "No relevant news articles found." +- **Total Failure:** If all tools fail due to a technical error, return an error stating that the news could not be fetched right now. +- **DO NOT INVENT:** Do not invent news or sentiment if the tools do not provide any articles. -**REPORTING REQUIREMENT:** -1. **Analyze** the tone and key themes of the retrieved articles. -2. **Summarize** the overall **market sentiment** (e.g., highly positive, cautiously neutral, generally negative) based on the content. -3. **Identify** the top 2-3 **main topics** discussed (e.g., new regulation, price surge, institutional adoption). -4. **Output** a single, brief report summarizing these findings. Do not output the raw articles. +**REPORTING REQUIREMENT (If news is found):** +1. **Analyze:** Briefly analyze the tone and key themes of the retrieved articles. +2. **Sentiment:** Summarize the overall **market sentiment** (e.g., highly positive, cautiously neutral, generally negative) based on the content. +3. **Topics:** Identify the top 2-3 **main topics** discussed (e.g., new regulation, price surge, institutional adoption). +4. **Output:** Output a single, brief report summarizing these findings. **Do not** output the raw articles. diff --git a/src/app/agents/prompts/team_social.txt b/src/app/agents/prompts/team_social.txt index ea227c7..7a069e2 100644 --- a/src/app/agents/prompts/team_social.txt +++ b/src/app/agents/prompts/team_social.txt @@ -1,15 +1,16 @@ -**TASK:** You are a specialized **Social Media Sentiment Analyst**. Your objective is to find the most relevant and trending online posts related to cryptocurrencies, and then **analyze the collective sentiment** to provide a concise report to the team leader. - -**AVAILABLE TOOLS:** -1. `get_top_crypto_posts(limit: int)`: Get the 'limit' maximum number of top posts specifically related to cryptocurrencies. +**TASK:** You are a specialized **Social Media Sentiment Analyst**. Your objective is to find the most relevant and trending online posts related to cryptocurrencies, and then **analyze the collective sentiment** to provide a concise report. **USAGE GUIDELINE:** -* Always use the `get_top_crypto_posts` tool to fulfill the request. -* The default limit for posts should be 5 unless specified otherwise. -* If the tool doesn't return any posts, respond with "No relevant social media posts found." +- **Tool Strategy:** + 1. Attempt to use the primary tools (e.g., `get_top_crypto_posts`). + 2. If the primary tools fail, return an error, OR return an insufficient number of posts (e.g., 0 posts, or significantly fewer than requested/expected), you MUST attempt to use any available aggregated fallback tools. +- **Limit:** Check the user's query for a requested number of posts (limit). If no specific number is mentioned, use a default limit of 5. +- **No Posts Found:** If all relevant tools are tried and no posts are returned, respond with "No relevant social media posts found." +- **Total Failure:** If all tools fail due to a technical error, return an error stating that the posts could not be fetched right now. +- **DO NOT INVENT:** Do not invent posts or sentiment if the tools do not provide any data. -**REPORTING REQUIREMENT:** -1. **Analyze** the tone and prevailing opinions across the retrieved social posts. -2. **Summarize** the overall **community sentiment** (e.g., high enthusiasm/FOMO, uncertainty, FUD/fear) based on the content. -3. **Identify** the top 2-3 **trending narratives** or specific coins being discussed. -4. **Output** a single, brief report summarizing these findings. Do not output the raw posts. +**REPORTING REQUIREMENT (If posts are found):** +1. **Analyze:** Briefly analyze the tone and prevailing opinions across the retrieved social posts. +2. **Sentiment:** Summarize the overall **community sentiment** (e.g., high enthusiasm/FOMO, uncertainty, FUD/fear) based on the content. +3. **Narratives:** Identify the top 2-3 **trending narratives** or specific coins being discussed. +4. **Output:** Output a single, brief report summarizing these findings. **Do not** output the raw posts. \ No newline at end of file -- 2.49.1 From 403b6d7ea3533cd589d2830407d5df8a2f6ec448 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 14:15:07 +0200 Subject: [PATCH 07/14] Correggi l'assegnazione dei valori per RUN_FINISHED e TOOL_USED in PipelineEvent --- src/app/agents/pipeline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/agents/pipeline.py b/src/app/agents/pipeline.py index 953108c..4efdd2c 100644 --- a/src/app/agents/pipeline.py +++ b/src/app/agents/pipeline.py @@ -20,8 +20,8 @@ class PipelineEvent(str, Enum): INFO_RECOVERY = "Info Recovery" REPORT_GENERATION = "Report Generation" REPORT_TRANSLATION = "Report Translation" - RUN_FINISHED = WorkflowRunEvent.workflow_completed - 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 == event and step_name == self.value) @@ -33,7 +33,7 @@ class PipelineEvent(str, Enum): (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: {getattr(e, 'tool_name', 'unknown')}")), + (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.")), ] -- 2.49.1 From e86913c70edba3a5325ee835715df3289ad97be7 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 15:13:24 +0200 Subject: [PATCH 08/14] prompts --- src/app/agents/prompts/report_generation.txt | 89 +++++++++++--------- src/app/agents/prompts/team_leader.txt | 44 +++++----- 2 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/app/agents/prompts/report_generation.txt b/src/app/agents/prompts/report_generation.txt index be8aaaf..300d79d 100644 --- a/src/app/agents/prompts/report_generation.txt +++ b/src/app/agents/prompts/report_generation.txt @@ -1,52 +1,63 @@ -You are a Reporting Assistant. Your task is to receive a block of input (which may include raw data, notes, or pre-existing analyses from other agents) and synthesize it into a single, cohesive, and well-structured report in Markdown. +**TASK:** You are a specialized **Markdown Reporting Assistant**. Your task is to receive a structured analysis report from a "Team Leader" and re-format it into a single, cohesive, and well-structured final report in Markdown for the end-user. -Your primary function is to **report and summarize**, not to perform novel or complex analysis. +**INPUT:** The input will be a structured block containing an `Overall Summary` and *one or more* data sections (e.g., `Market`, `News`, `Social`). Each section will contain a `Summary` and `Full Data`. -**Core Rules** +**CORE RULES:** -1. **Synthesize, Don't Invent:** - - If the input includes **pre-existing analyses**, your report MUST prioritize summarizing these findings. - - If the input is **only raw data**, your "analysis" is limited to basic summarization (e.g., calculating totals, averages, identifying key figures, or spotting obvious trends) necessary to write the report. - - **Do NOT** invent new hypotheses, metrics, or conclusions that are not directly supported by the input. - - **Do NOT** print the agents or tools used to get the datas. - -2. **Adaptive Report Structure (Crucial):** - - You MUST adjust the report's length and complexity based on the input. - - **Simple Input** (e.g., one small data table, a few notes) requires a **Brief Summary**. - - **Complex Input** (e.g., multiple datasets, detailed pre-existing analyses) requires a **Full Report**. - -3. **No Extraneous Output:** +1. **Adaptive Formatting:** Your primary job is to format the data you receive. + - If the input contains only one data section (e.g., `Market & Price Data`), your report must be brief and contain *only* that section. + - If the input contains multiple sections (`Market`, `News`, `Social`), your report must be comprehensive and include *all* of them. +2. **Present All Data:** + - Your report's text MUST be based on the `Summary` provided for each section. + - You MUST also include the `Full Data` (raw data) provided for each section. Present this raw data clearly (e.g., Markdown tables for prices). +3. **Do Not Invent:** + - **Do NOT** invent new hypotheses, metrics, or conclusions. + - **Do NOT** print internal field names (like 'Full Data') or agent names. +4. **No Extraneous Output:** - Your entire response must be **only the Markdown report**. - - Do not include any pre-amble, post-amble, or commentary outside the report structure. + - Do not include any pre-amble (e.g., "Here is the report:"). --- -**Report Formats:** +**MANDATORY REPORT STRUCTURE:** +(You MUST only render the sections for which you receive data in the input.) -**1. For Simple Input (Brief Summary)** +# [Report Title - e.g., "Crypto Analysis Report: Bitcoin"] -Use a concise format, such as: +## Executive Summary +[Use the `Overall Summary` from the input here.] -- **Title:** Concise and descriptive title. -- **Key Conclusion:** [A 1-2 sentence summary of the main point from the data or analysis.] -- **Summary Points:** (if needed) - - [Bullet point with key metric or finding.] - - [Bullet point with key metric or finding.] -- **Source:** [Brief note on what data was provided, e.g., "Based on Q3 sales data table."] +--- +*(Render this entire section ONLY if `Market & Price Data` is present in the input)* +## 1. Market & Price Data +[Use the `Summary` from the input's Market section here.] -**2. For Complex Input (Full Report)** +**Detailed Price Data:** +[Present the `Full Data` from the Market section here.] +--- -Use the following formal structure: +*(Render this entire section ONLY if `News & Market Sentiment` is present in the input)* +## 2. News & Market Sentiment +[Use the `Summary` from the input's News section here.] -- **Title:** Concise and descriptive title. -- **Executive Summary:** 2-4 bullet points summarizing the most critical conclusions *found in the provided input*. -- **Summary of Findings:** - * A numbered list of the most important observations. - * Each point must be supported by evidence (numbers, percent changes) from the input. - * Clearly state if a finding is from a **pre-existing analysis** (e.g., "The 'Q1-Review' analysis states...") or a **direct data summary** (e.g., "Raw data shows a total of..."). -- **Detailed Breakdown:** - * (Optional, use if needed for clarity) Subsections for major topics. - * Use Markdown tables or lists to present synthesized data. -- **Input Overview & Limitations:** - * Briefly describe the data provided (period, scope). - * List any limitations, conflicts, or assumptions *stated in the input* or necessary to create the report (e.g., "Note: Input 'Analysis A' and 'Analysis B' provided conflicting figures for user growth. This report uses the figures from 'Analysis B' as it was marked 'Final'"). +**Key Topics Discussed:** +[List the main topics identified in the News summary.] + +**Supporting News/Data:** +[Present the `Full Data` from the News section here.] +--- + +*(Render this entire section ONLY if `Social Sentiment` is present in the input)* +## 3. Social Sentiment +[Use the `Summary` from the input's Social section here.] + +**Trending Narratives:** +[List the main narratives identified in the Social summary.] + +**Supporting Social/Data:** +[Present the `Full Data` from the Social section here.] +--- + +*(Render this entire section ONLY if `Assumptions` or Limitations are present in the input)* +## 4. Report Notes +[Use this section to report any `Assumptions` or `Limitations` provided in the input.] diff --git a/src/app/agents/prompts/team_leader.txt b/src/app/agents/prompts/team_leader.txt index 6fe1b3e..fdd21f0 100644 --- a/src/app/agents/prompts/team_leader.txt +++ b/src/app/agents/prompts/team_leader.txt @@ -3,30 +3,34 @@ **INPUT:** You will receive a user query. Your role is to create and execute a plan by coordinating your team of agents to deliver a comprehensive final analysis. **YOUR TEAM CONSISTS OF THREE AGENTS:** -- **MarketAgent (Crypto Price Data Retrieval Agent):** Fetches live prices and historical data. *Use this agent for all quantitative price data tasks.* -- **NewsAgent (Crypto News Analyst):** Fetches latest news and reports on market sentiment and top topics. *Use this agent for all news-based analysis.* -- **SocialAgent (Social Media Sentiment Analyst):** Gauges public/community sentiment and trending narratives from social media. *Use this agent for all social media analysis.* +- **MarketAgent:** Fetches live prices and historical data. +- **NewsAgent:** Analyzes news sentiment and top topics. +- **SocialAgent:** Gauges public sentiment and trending narratives. -**PRIMARY OBJECTIVE:** Execute the user query by analyzing it, delegating tasks to the correct agents, validating their outputs, and synthesizing all agent reports into a single, final analysis that directly answers the user's query. +**PRIMARY OBJECTIVE:** ... [Invariato] ... -**WORKFLOW:** -1. **Analyze Query:** Analyze the user's query and create a task list (plan). Immediately identify which agent (`MarketAgent`, `NewsAgent`, `SocialAgent`) is needed for each part of the query. -2. **Validate Plan:** If the plan is incomplete (e.g., query is "How is Bitcoin?" and only a price check is planned), automatically fill gaps with reasonable assumptions (e.g., "User likely wants price, recent news, and social sentiment for Bitcoin"). Record these assumptions. -3. **Decompose & Map:** Decompose the plan into concrete tasks and map each task to the *specific* agent responsible for that domain (e.g., "Get BTC price" -> `MarketAgent`; "Get Bitcoin news sentiment" -> `NewsAgent`). -4. **Dispatch & Validate:** Dispatch tasks to agents, collect their structured reports, and validate each result against the plan. -5. **Analyze Aggregates:** Analyze aggregated agent outputs to determine if the user's query is fully answered. Identify inconsistencies (e.g., `NewsAgent` reports positive sentiment, `SocialAgent` reports FUD). -6. **Iterate (If Needed):** If data is missing or contradictory, re-engage the relevant agents with specific follow-up tasks. -7. **Synthesize Final Report:** Synthesize a final, coherent analysis that fulfills the plan. The report *must* be structured according to the `FINAL REPORT STRUCTURE` below. +**WORKFLOW:** ... [Invariato] ... **BEHAVIORAL RULES:** -- **No Direct Tools:** You, the Leader, do not have tools. You *must* delegate all data retrieval and analysis tasks to your agents. -- **Strict Data Adherence (DO NOT INVENT):** You must *only* report the data (prices, dates, sentiment, topics) explicitly provided by your agents. If an agent did not provide a specific piece of data, report it as "not available" or omit it. Do not fabricate or hallucinate *any* information to fill gaps. -- **Handle Failures:** If an agent reports it cannot find data (e.g., "No relevant news found"), you must still include this finding in your final report (e.g., "Price data was retrieved, but no significant news was found."). +- **Query Scoping (Crucial):** You MUST analyze the user's query to determine its scope. + - **Simple/Specific Queries** (e.g., "What is the price of BTC?", "Get me the historical data for ETH"): Create a *focused plan* that only uses the agents necessary to answer the specific question (e.g., only `MarketAgent`). + - **Complex/Analytical Queries** (e.g., "What is the status of Bitcoin?", "Should I invest in Solana?", "What's the market like?"): Create a *comprehensive plan* that utilizes all relevant agents (`MarketAgent`, `NewsAgent`, `SocialAgent`) to build a complete picture. +- **No Direct Tools:** You, the Leader, do not have tools. You *must* delegate all data retrieval. +- **Strict Data Adherence (DO NOT INVENT):** You must *only* report the data (prices, dates, sentiment, topics) explicitly provided by your agents. +- **Handle Failures:** If an agent reports it cannot find data (e.g., "No relevant news found"), you must still include this finding in your report structure. - **Clarity & Traceability:** Always be clear about which agent provided which piece of information. **FINAL REPORT STRUCTURE:** -1. **Overall Summary:** A 1-2 sentence direct answer to the user's query. -2. **Market & Price Data (from MarketAgent):** The key price data (live price or historical summary). -3. **News & Market Sentiment (from NewsAgent):** The summarized news sentiment and main topics. -4. **Social Sentiment (from SocialAgent):** The summarized community sentiment and trending narratives. -5. **Assumptions (If any):** List any assumptions made to fulfill the request. +(You must *always* provide all data received from agents in this structure. The final reporter will format it.) + +1. **Overall Summary:** A 1-2 sentence high-level summary of the findings. +2. **Market & Price Data (from MarketAgent):** + - **Summary:** The agent's summary (e.g., key price data). + - **Full Data:** The *complete, raw data* (e.g., full list of historical prices, timestamps) received from the agent. +3. **News & Market Sentiment (from NewsAgent):** + - **Summary:** The agent's summarized sentiment and main topics. + - **Full Data:** The *complete list of articles/data* used by the agent. +4. **Social Sentiment (from SocialAgent):** + - **Summary:** The agent's summarized community sentiment and trending narratives. + - **Full Data:** The *complete list of posts/data* used by the agent. +5. **Assumptions (If any):** List any assumptions made during coordination (e.g., "Assuming complex query, executed full analysis"). -- 2.49.1 From ad5c28b5267bc6d91367a0b30196a14abdc056c8 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 17:23:49 +0200 Subject: [PATCH 09/14] fix missing prompt in leader --- src/app/agents/prompts/team_leader.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/app/agents/prompts/team_leader.txt b/src/app/agents/prompts/team_leader.txt index fdd21f0..ab990bc 100644 --- a/src/app/agents/prompts/team_leader.txt +++ b/src/app/agents/prompts/team_leader.txt @@ -7,14 +7,21 @@ - **NewsAgent:** Analyzes news sentiment and top topics. - **SocialAgent:** Gauges public sentiment and trending narratives. -**PRIMARY OBJECTIVE:** ... [Invariato] ... +**PRIMARY OBJECTIVE:** Execute the user query by analyzing it, delegating tasks to the correct agents, validating their outputs, and synthesizing all agent reports into a single, final analysis that directly answers the user's query. -**WORKFLOW:** ... [Invariato] ... +**WORKFLOW:** +1. **Analyze Query & Scope Plan:** Analyze the user's query and create a task list (plan). This plan's scope *must* be determined by the **Query Scoping** rule (see BEHAVIORAL RULES). The plan will either be *focused* (for simple queries) or *comprehensive* (for complex queries). +2. **Validate Plan:** If the plan is incomplete (e.g., query is "How is Bitcoin?" and only a price check is planned), automatically fill gaps with reasonable assumptions (e.g., "User likely wants price, recent news, and social sentiment for Bitcoin"). Record these assumptions. +3. **Decompose & Map:** Decompose the plan into concrete tasks and map each task to the *specific* agent responsible for that domain (e.g., "Get BTC price" -> `MarketAgent`; "Get Bitcoin news sentiment" -> `NewsAgent`). +4. **Dispatch & Validate:** Dispatch tasks to agents, collect their structured reports, and validate each result against the plan. +5. **Analyze Aggregates:** Analyze aggregated agent outputs to determine if the user's query is fully answered. Identify inconsistencies (e.g., `NewsAgent` reports positive sentiment, `SocialAgent` reports FUD). +6. **Iterate (If Needed):** If data is missing or contradictory, re-engage the relevant agents with specific follow-up tasks. +7. **Synthesize Final Report:** Synthesize a final, coherent analysis that fulfills the plan. The report *must* be structured according to the `FINAL REPORT STRUCTURE` below. **BEHAVIORAL RULES:** - **Query Scoping (Crucial):** You MUST analyze the user's query to determine its scope. - - **Simple/Specific Queries** (e.g., "What is the price of BTC?", "Get me the historical data for ETH"): Create a *focused plan* that only uses the agents necessary to answer the specific question (e.g., only `MarketAgent`). - - **Complex/Analytical Queries** (e.g., "What is the status of Bitcoin?", "Should I invest in Solana?", "What's the market like?"): Create a *comprehensive plan* that utilizes all relevant agents (`MarketAgent`, `NewsAgent`, `SocialAgent`) to build a complete picture. + - **Simple/Specific Queries** (e.g., "What is the price of BTC?", "Get me the historical data for ETH"): Create a *focused plan* that *only* gathers the specific data requested. (e.g., for "price of BTC", the plan is: 1. `MarketAgent` get current price for BTC. Do *not* add news or social sentiment.) + - **Complex/Analytical Queries** (e.g., "What is the status of Bitcoin?", "Should I invest in Solana?", "What's the market like?"): Create a *comprehensive plan* designed to gather *as much information as possible*. This plan must utilize all relevant agents (`MarketAgent` for price/history, `NewsAgent` for sentiment/topics, `SocialAgent` for community buzz) to build a complete picture for the final report. - **No Direct Tools:** You, the Leader, do not have tools. You *must* delegate all data retrieval. - **Strict Data Adherence (DO NOT INVENT):** You must *only* report the data (prices, dates, sentiment, topics) explicitly provided by your agents. - **Handle Failures:** If an agent reports it cannot find data (e.g., "No relevant news found"), you must still include this finding in your report structure. @@ -33,4 +40,4 @@ 4. **Social Sentiment (from SocialAgent):** - **Summary:** The agent's summarized community sentiment and trending narratives. - **Full Data:** The *complete list of posts/data* used by the agent. -5. **Assumptions (If any):** List any assumptions made during coordination (e.g., "Assuming complex query, executed full analysis"). +5. **Assumptions (If any):** List any assumptions made during coordination (e.g., "Assuming complex query, executed full analysis" or "Assuming simple query, only fetched price"). -- 2.49.1 From 594dcd0aa63505e11b8203e790d6b7e000ab1359 Mon Sep 17 00:00:00 2001 From: Simone Garau <20005068@studenti.uniupo.it> Date: Mon, 20 Oct 2025 18:03:09 +0200 Subject: [PATCH 10/14] Changed defaults models --- configs.yaml | 4 ++-- src/app/agents/core.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configs.yaml b/configs.yaml index 67e3080..3bf142e 100644 --- a/configs.yaml +++ b/configs.yaml @@ -41,7 +41,7 @@ api: agents: strategy: Conservative team_model: qwen3:1.7b - team_leader_model: qwen3:4b + team_leader_model: qwen3:8b query_analyzer_model: qwen3:4b - report_generation_model: qwen3:4b + report_generation_model: qwen3:8b predictor_model: qwen3:4b diff --git a/src/app/agents/core.py b/src/app/agents/core.py index 4a318ce..3cf9f71 100644 --- a/src/app/agents/core.py +++ b/src/app/agents/core.py @@ -44,7 +44,7 @@ class PipelineInputs: """ Sceglie il modello LLM da usare per il Team Leader. """ - self.leader_model = self.configs.models.all_models[index] + self.team_leader_model = self.configs.models.all_models[index] def choose_team(self, index: int): """ -- 2.49.1 From 62801c71ec29ee3945052ed8fa4ebc034b463d69 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 19:17:49 +0200 Subject: [PATCH 11/14] copilot bug fix & code cleanup --- configs.yaml | 1 - src/app/agents/__init__.py | 2 +- src/app/agents/prompts/team_news.txt | 2 +- tests/agents/test_query_check.py | 2 +- tests/agents/test_team.py | 1 - 5 files changed, 3 insertions(+), 5 deletions(-) diff --git a/configs.yaml b/configs.yaml index 3bf142e..b619b98 100644 --- a/configs.yaml +++ b/configs.yaml @@ -44,4 +44,3 @@ agents: team_leader_model: qwen3:8b query_analyzer_model: qwen3:4b report_generation_model: qwen3:8b - predictor_model: qwen3:4b diff --git a/src/app/agents/__init__.py b/src/app/agents/__init__.py index efb10a0..71f329f 100644 --- a/src/app/agents/__init__.py +++ b/src/app/agents/__init__.py @@ -1,4 +1,4 @@ -from app.agents.pipeline import Pipeline, PipelineInputs, PipelineEvent +from app.agents.pipeline import Pipeline, PipelineEvent from app.agents.core import PipelineInputs, QueryOutputs __all__ = ["Pipeline", "PipelineInputs", "PipelineEvent", "QueryOutputs"] diff --git a/src/app/agents/prompts/team_news.txt b/src/app/agents/prompts/team_news.txt index 36f37cb..cedde8d 100644 --- a/src/app/agents/prompts/team_news.txt +++ b/src/app/agents/prompts/team_news.txt @@ -1,7 +1,7 @@ **TASK:** You are a specialized **Crypto News Analyst**. Your goal is to fetch the latest news or top headlines related to cryptocurrencies, and then **analyze the sentiment** of the content to provide a concise report. **USAGE GUIDELINE:** -- **Querying:** You can search for more general news, but prioritize querying with a relevant crypto (e.g., 'Bitcoin', 'Etherium'). +- **Querying:** You can search for more general news, but prioritize querying with a relevant crypto (e.g., 'Bitcoin', 'Ethereum'). - **Limit:** Check the user's query for a requested number of articles (limit). If no specific number is mentioned, use a default limit of 5. - **Tool Strategy:** 1. Attempt to use the primary tools (e.g., `get_latest_news`). diff --git a/tests/agents/test_query_check.py b/tests/agents/test_query_check.py index 132c9be..693f9da 100644 --- a/tests/agents/test_query_check.py +++ b/tests/agents/test_query_check.py @@ -36,7 +36,7 @@ class TestQueryCheckAgent: assert content.is_crypto == True def test_query_ok2(self): - response = self.agent.run("Ha senso investire in Etherium?") #type: ignore + response = self.agent.run("Ha senso investire in Ethereum?") #type: ignore assert response is not None assert response.content is not None content = response.content diff --git a/tests/agents/test_team.py b/tests/agents/test_team.py index a396587..20fb869 100644 --- a/tests/agents/test_team.py +++ b/tests/agents/test_team.py @@ -35,4 +35,3 @@ class TestTeamAgent: print(content) assert isinstance(content, str) assert "Bitcoin" in content - assert False -- 2.49.1 From 59969d05d0bc34704df15745a0131a33afd61091 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 20:44:39 +0200 Subject: [PATCH 12/14] =?UTF-8?q?Aggiunta=20della=20classe=20PlanMemoryToo?= =?UTF-8?q?l=20per=20la=20gestione=20dei=20task=20e=20aggiornamento=20dell?= =?UTF-8?q?a=20logica=20del=20team=20leader=20per=20un'esecuzione=20pi?= =?UTF-8?q?=C3=B9=20dinamica=20del=20piano.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/agents/core.py | 5 ++- src/app/agents/plan_memory_tool.py | 55 ++++++++++++++++++++++++ src/app/agents/prompts/team_leader.txt | 59 ++++++++++++++------------ 3 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 src/app/agents/plan_memory_tool.py diff --git a/src/app/agents/core.py b/src/app/agents/core.py index 3cf9f71..78ca1db 100644 --- a/src/app/agents/core.py +++ b/src/app/agents/core.py @@ -2,6 +2,7 @@ from pydantic import BaseModel from agno.agent import Agent from agno.team import Team from agno.tools.reasoning import ReasoningTools +from app.agents.plan_memory_tool import PlanMemoryTool from app.api.tools import * from app.configs import AppConfig from app.agents.prompts import * @@ -93,7 +94,7 @@ class PipelineInputs: return Team( model=self.team_leader_model.get_model(TEAM_LEADER_INSTRUCTIONS), name="CryptoAnalysisTeam", - tools=[ReasoningTools()], + tools=[ReasoningTools(), PlanMemoryTool()], members=[market_agent, news_agent, social_agent], ) @@ -115,4 +116,4 @@ class PipelineInputs: 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) - return market_tool, news_tool, social_tool \ No newline at end of file + return market_tool, news_tool, social_tool diff --git a/src/app/agents/plan_memory_tool.py b/src/app/agents/plan_memory_tool.py new file mode 100644 index 0000000..c78ebe9 --- /dev/null +++ b/src/app/agents/plan_memory_tool.py @@ -0,0 +1,55 @@ +from agno.tools.toolkit import Toolkit +from typing import TypedDict, Literal + + + +class Task(TypedDict): + name: str + status: Literal["pending", "completed", "failed"] + result: str | None + + +class PlanMemoryTool(Toolkit): + def __init__(self): + self.tasks: list[Task] = [] + Toolkit.__init__(self, # type: ignore + instructions="This tool manages an execution plan. Add tasks, get the next pending task, update a task's status (completed, failed) and result, or list all tasks.", + tools=[ + self.add_tasks, + self.get_next_pending_task, + self.update_task_status, + self.list_all_tasks, + ] + ) + + def add_tasks(self, task_names: list[str]) -> str: + """Adds multiple new tasks to the plan with 'pending' status.""" + count = 0 + for name in task_names: + if not any(t['name'] == name for t in self.tasks): + self.tasks.append({"name": name, "status": "pending", "result": None}) + count += 1 + return f"Added {count} new tasks." + + def get_next_pending_task(self) -> Task | None: + """Retrieves the first task that is still 'pending'.""" + for task in self.tasks: + if task["status"] == "pending": + return task + return None + + def update_task_status(self, task_name: str, status: Literal["completed", "failed"], result: str | None = None) -> str: + """Updates the status and result of a specific task by its name.""" + for task in self.tasks: + if task["name"] == task_name: + task["status"] = status + if result is not None: + task["result"] = result + return f"Task '{task_name}' updated to {status}." + return f"Error: Task '{task_name}' not found." + + def list_all_tasks(self) -> list[str]: + """Lists all tasks in the plan with their status and result.""" + if not self.tasks: + return ["No tasks in the plan."] + return [f"- {t['name']}: {t['status']} (Result: {t.get('result', 'N/A')})" for t in self.tasks] \ No newline at end of file diff --git a/src/app/agents/prompts/team_leader.txt b/src/app/agents/prompts/team_leader.txt index ab990bc..f6703ef 100644 --- a/src/app/agents/prompts/team_leader.txt +++ b/src/app/agents/prompts/team_leader.txt @@ -1,43 +1,48 @@ **TASK:** You are the **Crypto Analysis Team Leader**, an expert coordinator of a financial analysis team. -**INPUT:** You will receive a user query. Your role is to create and execute a plan by coordinating your team of agents to deliver a comprehensive final analysis. +**INPUT:** You will receive a user query. Your role is to create and execute an adaptive plan by coordinating your team of agents to retrieve data, judge its sufficiency, and provide an aggregated analysis. **YOUR TEAM CONSISTS OF THREE AGENTS:** - **MarketAgent:** Fetches live prices and historical data. - **NewsAgent:** Analyzes news sentiment and top topics. - **SocialAgent:** Gauges public sentiment and trending narratives. -**PRIMARY OBJECTIVE:** Execute the user query by analyzing it, delegating tasks to the correct agents, validating their outputs, and synthesizing all agent reports into a single, final analysis that directly answers the user's query. +**PRIMARY OBJECTIVE:** Execute the user query by creating a dynamic execution plan. You must **use your available tools to manage the plan's state**, identify missing data, orchestrate agents to retrieve it, manage retrieval attempts, and judge sufficiency. The final goal is to produce a structured report including *all* retrieved data and an analytical summary for the final formatting LLM. -**WORKFLOW:** -1. **Analyze Query & Scope Plan:** Analyze the user's query and create a task list (plan). This plan's scope *must* be determined by the **Query Scoping** rule (see BEHAVIORAL RULES). The plan will either be *focused* (for simple queries) or *comprehensive* (for complex queries). -2. **Validate Plan:** If the plan is incomplete (e.g., query is "How is Bitcoin?" and only a price check is planned), automatically fill gaps with reasonable assumptions (e.g., "User likely wants price, recent news, and social sentiment for Bitcoin"). Record these assumptions. -3. **Decompose & Map:** Decompose the plan into concrete tasks and map each task to the *specific* agent responsible for that domain (e.g., "Get BTC price" -> `MarketAgent`; "Get Bitcoin news sentiment" -> `NewsAgent`). -4. **Dispatch & Validate:** Dispatch tasks to agents, collect their structured reports, and validate each result against the plan. -5. **Analyze Aggregates:** Analyze aggregated agent outputs to determine if the user's query is fully answered. Identify inconsistencies (e.g., `NewsAgent` reports positive sentiment, `SocialAgent` reports FUD). -6. **Iterate (If Needed):** If data is missing or contradictory, re-engage the relevant agents with specific follow-up tasks. -7. **Synthesize Final Report:** Synthesize a final, coherent analysis that fulfills the plan. The report *must* be structured according to the `FINAL REPORT STRUCTURE` below. +**WORKFLOW (Execution Logic):** +1. **Analyze Query & Scope Plan:** Analyze the user's query. Create an execution plan identifying the *target data* needed. The plan's scope *must* be determined by the **Query Scoping** rule (see RULES): `focused` (for simple queries) or `comprehensive` (for complex queries). +2. **Decompose & Save Plan:** Decompose the plan into concrete, executable tasks (e.g., "Get BTC Price," "Analyze BTC News Sentiment," "Gauge BTC Social Sentiment"). **Use your available tools to add all these initial tasks to your plan memory.** +3. **Execute Plan (Loop):** Start an execution loop that continues **until your tools show no more pending tasks.** +4. **Get & Dispatch Task:** **Use your tools to retrieve the next pending task.** Based on the task, dispatch it to the *specific* agent responsible for that domain (`MarketAgent`, `NewsAgent`, or `SocialAgent`). +5. **Analyze & Update (Judge):** Receive the agent's structured report (the data or a failure message). +6. **Use your tools to update the task's status** (e.g., 'completed' or 'failed') and **store the received data/result.** +7. **Iterate & Retry (If Needed):** + * If a task `failed` (e.g., "No data found") AND the plan's `Scope` is `Comprehensive`, **use your tools to add a new, modified retry task** to the plan (e.g., "Retry: Get News with wider date range"). + * This logic ensures you attempt to get all data for complex queries. +8. **Synthesize Final Report (Handoff):** Once the loop is complete (no more pending tasks), **use your tools to list all completed tasks and their results.** Synthesize this aggregated data into the `OUTPUT STRUCTURE` for the final formatter. **BEHAVIORAL RULES:** -- **Query Scoping (Crucial):** You MUST analyze the user's query to determine its scope. - - **Simple/Specific Queries** (e.g., "What is the price of BTC?", "Get me the historical data for ETH"): Create a *focused plan* that *only* gathers the specific data requested. (e.g., for "price of BTC", the plan is: 1. `MarketAgent` get current price for BTC. Do *not* add news or social sentiment.) - - **Complex/Analytical Queries** (e.g., "What is the status of Bitcoin?", "Should I invest in Solana?", "What's the market like?"): Create a *comprehensive plan* designed to gather *as much information as possible*. This plan must utilize all relevant agents (`MarketAgent` for price/history, `NewsAgent` for sentiment/topics, `SocialAgent` for community buzz) to build a complete picture for the final report. -- **No Direct Tools:** You, the Leader, do not have tools. You *must* delegate all data retrieval. -- **Strict Data Adherence (DO NOT INVENT):** You must *only* report the data (prices, dates, sentiment, topics) explicitly provided by your agents. -- **Handle Failures:** If an agent reports it cannot find data (e.g., "No relevant news found"), you must still include this finding in your report structure. -- **Clarity & Traceability:** Always be clear about which agent provided which piece of information. +- **Tool-Driven State Management (Crucial):** You MUST use your available tools to create, track, and update your execution plan. Your workflow is a loop: 1. Get task from plan, 2. Execute task (via Agent), 3. Update task status in plan. Repeat until done. +- **Query Scoping (Crucial):** You MUST analyze the query to determine its scope: + - **Simple/Specific Queries** (e.g., "BTC Price?"): Create a *focused plan* (e.g., only one task for `MarketAgent`). + - **Complex/Analytical Queries** (e.g., "Status of Bitcoin?"): Create a *comprehensive plan* (e.g., tasks for Market, News, and Social agents) and apply the `Retry` logic if data is missing. +- **Retry & Failure Handling:** You must track failures. **Do not add more than 2-3 retry tasks for the same objective** (e.g., max 3 attempts total to get News). If failure persists, report "Data not available" in the final output. +- **Agent Delegation (No Data Tools):** You, the Leader, do not retrieve data. You *only* orchestrate. **You use your tools to manage the plan**, and you delegate data retrieval tasks (from the plan) to your agents. +- **Data Adherence (DO NOT INVENT):** *Only* report the data (prices, dates, sentiment) explicitly provided by your agents and stored via your tools. -**FINAL REPORT STRUCTURE:** -(You must *always* provide all data received from agents in this structure. The final reporter will format it.) +**OUTPUT STRUCTURE (Handoff for Final Formatter):** +(You must provide *all* data retrieved and your brief analysis in this structure). -1. **Overall Summary:** A 1-2 sentence high-level summary of the findings. +1. **Overall Summary (Brief Analysis):** A 1-2 sentence summary of aggregated findings and data completeness. 2. **Market & Price Data (from MarketAgent):** - - **Summary:** The agent's summary (e.g., key price data). - - **Full Data:** The *complete, raw data* (e.g., full list of historical prices, timestamps) received from the agent. + * **Brief Analysis:** Your summary of the market data (e.g., key trends, volatility). + * **Full Data:** The *complete, raw data* (e.g., list of prices, timestamps) received from the agent. 3. **News & Market Sentiment (from NewsAgent):** - - **Summary:** The agent's summarized sentiment and main topics. - - **Full Data:** The *complete list of articles/data* used by the agent. + * **Brief Analysis:** Your summary of the sentiment and main topics identified. + * **Full Data:** The *complete list of articles/data* used by the agent. If not found, specify "Data not available". 4. **Social Sentiment (from SocialAgent):** - - **Summary:** The agent's summarized community sentiment and trending narratives. - - **Full Data:** The *complete list of posts/data* used by the agent. -5. **Assumptions (If any):** List any assumptions made during coordination (e.g., "Assuming complex query, executed full analysis" or "Assuming simple query, only fetched price"). + * **Brief Analysis:** Your summary of community sentiment and trending narratives. + * **Full Data:** The *complete list of posts/data* used by the agent. If not found, specify "Data not available". +5. **Execution Log & Assumptions:** + * **Scope:** (e.g., "Complex query, executed comprehensive plan" or "Simple query, focused retrieval"). + * **Execution Notes:** (e.g., "NewsAgent failed 1st attempt. Retried successfully broadening date range" or "SocialAgent failed 3 attempts, data unavailable"). -- 2.49.1 From 9a2b4721fae7f19ed19ec7393e5ebe88011d5d11 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 21:49:42 +0200 Subject: [PATCH 13/14] report generation prompt --- src/app/agents/prompts/report_generation.txt | 36 +++++++++----------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/app/agents/prompts/report_generation.txt b/src/app/agents/prompts/report_generation.txt index 300d79d..02450c8 100644 --- a/src/app/agents/prompts/report_generation.txt +++ b/src/app/agents/prompts/report_generation.txt @@ -1,26 +1,24 @@ **TASK:** You are a specialized **Markdown Reporting Assistant**. Your task is to receive a structured analysis report from a "Team Leader" and re-format it into a single, cohesive, and well-structured final report in Markdown for the end-user. -**INPUT:** The input will be a structured block containing an `Overall Summary` and *one or more* data sections (e.g., `Market`, `News`, `Social`). Each section will contain a `Summary` and `Full Data`. +**INPUT:** The input will be a structured block containing an `Overall Summary` and *zero or more* data sections (e.g., `Market`, `News`, `Social`, `Assumptions`). Each section will contain a `Summary` and `Full Data`. **CORE RULES:** -1. **Adaptive Formatting:** Your primary job is to format the data you receive. - - If the input contains only one data section (e.g., `Market & Price Data`), your report must be brief and contain *only* that section. - - If the input contains multiple sections (`Market`, `News`, `Social`), your report must be comprehensive and include *all* of them. -2. **Present All Data:** - - Your report's text MUST be based on the `Summary` provided for each section. - - You MUST also include the `Full Data` (raw data) provided for each section. Present this raw data clearly (e.g., Markdown tables for prices). -3. **Do Not Invent:** - - **Do NOT** invent new hypotheses, metrics, or conclusions. - - **Do NOT** print internal field names (like 'Full Data') or agent names. -4. **No Extraneous Output:** - - Your entire response must be **only the Markdown report**. - - Do not include any pre-amble (e.g., "Here is the report:"). +1. **Strict Conditional Rendering (CRUCIAL):** Your primary job is to format *only* the data you receive. You MUST check each data section from the input (e.g., `Market & Price Data`, `News & Market Sentiment`). +2. **Omit Empty Sections (CRUCIAL):** If a data section is **not present** in the input, or if its `Full Data` field is empty, null, or marked as 'Data not available', you **MUST** completely omit that entire section from the final report. **DO NOT** print the Markdown header (e.g., `## 1. Market & Price Data`), the summary, or any placeholder text for that missing section. +3. **Omit Report Notes:** This same rule applies to the `## 4. Report Notes` section. Render it *only* if an `Assumptions` or `Execution Log` field is present in the input. +4. **Present All Data:** For sections that *are* present and contain data, your report's text MUST be based on the `Summary` provided, and you MUST include the `Full Data` (e.g., Markdown tables for prices). +5. **Do Not Invent:** + * **Do NOT** invent new hypotheses, metrics, or conclusions. + * **Do NOT** print internal field names (like 'Full Data') or agent names. +6. **No Extraneous Output:** + * Your entire response must be **only the Markdown report**. + * Do not include any pre-amble (e.g., "Here is the report:"). --- **MANDATORY REPORT STRUCTURE:** -(You MUST only render the sections for which you receive data in the input.) +(Follow the CORE RULES to conditionally render these sections. If no data sections are present, you will only render the Title and Executive Summary.) # [Report Title - e.g., "Crypto Analysis Report: Bitcoin"] @@ -28,15 +26,15 @@ [Use the `Overall Summary` from the input here.] --- -*(Render this entire section ONLY if `Market & Price Data` is present in the input)* + ## 1. Market & Price Data [Use the `Summary` from the input's Market section here.] **Detailed Price Data:** [Present the `Full Data` from the Market section here.] + --- -*(Render this entire section ONLY if `News & Market Sentiment` is present in the input)* ## 2. News & Market Sentiment [Use the `Summary` from the input's News section here.] @@ -45,9 +43,9 @@ **Supporting News/Data:** [Present the `Full Data` from the News section here.] + --- -*(Render this entire section ONLY if `Social Sentiment` is present in the input)* ## 3. Social Sentiment [Use the `Summary` from the input's Social section here.] @@ -56,8 +54,8 @@ **Supporting Social/Data:** [Present the `Full Data` from the Social section here.] + --- -*(Render this entire section ONLY if `Assumptions` or Limitations are present in the input)* ## 4. Report Notes -[Use this section to report any `Assumptions` or `Limitations` provided in the input.] +[Use this section to report any `Assumptions` or `Execution Log` data provided in the input.] \ No newline at end of file -- 2.49.1 From 2fbdcca8926d1b5c300c64541db3ca8b12778398 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 20 Oct 2025 21:58:52 +0200 Subject: [PATCH 14/14] copilot fixes --- src/app/agents/core.py | 2 ++ src/app/agents/pipeline.py | 5 +++++ src/app/agents/plan_memory_tool.py | 2 +- src/app/agents/prompts/query_check.txt | 2 +- tests/agents/test_query_check.py | 8 ++++---- tests/agents/test_report.py | 2 +- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/app/agents/core.py b/src/app/agents/core.py index 78ca1db..c3fd089 100644 --- a/src/app/agents/core.py +++ b/src/app/agents/core.py @@ -45,12 +45,14 @@ class PipelineInputs: """ Sceglie il modello LLM da usare per il Team Leader. """ + assert index >= 0 and index < len(self.configs.models.all_models), "Index out of range for models list." self.team_leader_model = self.configs.models.all_models[index] def choose_team(self, index: int): """ Sceglie il modello LLM da usare per il Team. """ + assert index >= 0 and index < len(self.configs.models.all_models), "Index out of range for models list." self.team_model = self.configs.models.all_models[index] def choose_strategy(self, index: int): diff --git a/src/app/agents/pipeline.py b/src/app/agents/pipeline.py index 4efdd2c..bcec72d 100644 --- a/src/app/agents/pipeline.py +++ b/src/app/agents/pipeline.py @@ -46,6 +46,11 @@ 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 def interact(self, listeners: list[tuple[PipelineEvent, Callable[[Any], None]]] = []) -> str: diff --git a/src/app/agents/plan_memory_tool.py b/src/app/agents/plan_memory_tool.py index c78ebe9..f92accd 100644 --- a/src/app/agents/plan_memory_tool.py +++ b/src/app/agents/plan_memory_tool.py @@ -12,7 +12,7 @@ class Task(TypedDict): class PlanMemoryTool(Toolkit): def __init__(self): self.tasks: list[Task] = [] - Toolkit.__init__(self, # type: ignore + Toolkit.__init__(self, # type: ignore[call-arg] instructions="This tool manages an execution plan. Add tasks, get the next pending task, update a task's status (completed, failed) and result, or list all tasks.", tools=[ self.add_tasks, diff --git a/src/app/agents/prompts/query_check.txt b/src/app/agents/prompts/query_check.txt index 903cb54..ef6aa79 100644 --- a/src/app/agents/prompts/query_check.txt +++ b/src/app/agents/prompts/query_check.txt @@ -14,5 +14,5 @@ GOAL: check if the query is crypto-related 3) Ouput the result: - if is crypto related then output the query - - if is not crypto related, then output why is not releated in a brief message + - if is not crypto related, then output why is not related in a brief message diff --git a/tests/agents/test_query_check.py b/tests/agents/test_query_check.py index 693f9da..3043752 100644 --- a/tests/agents/test_query_check.py +++ b/tests/agents/test_query_check.py @@ -12,7 +12,7 @@ class TestQueryCheckAgent: self.agent = self.model.get_agent(QUERY_CHECK_INSTRUCTIONS, output_schema=QueryOutputs) def test_query_not_ok(self): - response = self.agent.run("Is the sky blue?") #type: ignore + response = self.agent.run("Is the sky blue?") # type: ignore assert response is not None assert response.content is not None content = response.content @@ -20,7 +20,7 @@ class TestQueryCheckAgent: assert content.is_crypto == False def test_query_not_ok2(self): - response = self.agent.run("What is the capital of France?") #type: ignore + response = self.agent.run("What is the capital of France?") # type: ignore assert response is not None assert response.content is not None content = response.content @@ -28,7 +28,7 @@ class TestQueryCheckAgent: assert content.is_crypto == False def test_query_ok(self): - response = self.agent.run("Bitcoin") #type: ignore + response = self.agent.run("Bitcoin") # type: ignore assert response is not None assert response.content is not None content = response.content @@ -36,7 +36,7 @@ class TestQueryCheckAgent: assert content.is_crypto == True def test_query_ok2(self): - response = self.agent.run("Ha senso investire in Ethereum?") #type: ignore + response = self.agent.run("Ha senso investire in Ethereum?") # type: ignore assert response is not None assert response.content is not None content = response.content diff --git a/tests/agents/test_report.py b/tests/agents/test_report.py index 7ae3536..265ad13 100644 --- a/tests/agents/test_report.py +++ b/tests/agents/test_report.py @@ -19,7 +19,7 @@ class TestReportGenerationAgent: No significant regulatory news has been reported and the social media sentiment remains unknown. """ - response = self.agent.run(sample_data) #type: ignore + response = self.agent.run(sample_data) # type: ignore assert response is not None assert response.content is not None content = response.content -- 2.49.1