Merge branch 'main' into 47-update-chat-interface
# Conflicts: # src/app/api/tools/market_tool.py # src/app/api/tools/news_tool.py # src/app/api/tools/social_tool.py
This commit is contained in:
21
src/app/api/tools/instructions/__init__.py
Normal file
21
src/app/api/tools/instructions/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from pathlib import Path
|
||||
|
||||
__INSTRUCTIONS_PATH = Path(__file__).parent
|
||||
|
||||
def __load_tool_instruction(file_name: str) -> str:
|
||||
file_path = __INSTRUCTIONS_PATH / file_name
|
||||
return file_path.read_text(encoding='utf-8').strip()
|
||||
|
||||
MARKET_TOOL_INSTRUCTIONS = __load_tool_instruction("market_instructions.md")
|
||||
NEWS_TOOL_INSTRUCTIONS = __load_tool_instruction("news_instructions.md")
|
||||
SOCIAL_TOOL_INSTRUCTIONS = __load_tool_instruction("social_instructions.md")
|
||||
PLAN_MEMORY_TOOL_INSTRUCTIONS = __load_tool_instruction("plan_memory_instructions.md")
|
||||
SYMBOLS_TOOL_INSTRUCTIONS = __load_tool_instruction("symbols_instructions.md")
|
||||
|
||||
__all__ = [
|
||||
"MARKET_TOOL_INSTRUCTIONS",
|
||||
"NEWS_TOOL_INSTRUCTIONS",
|
||||
"SOCIAL_TOOL_INSTRUCTIONS",
|
||||
"PLAN_MEMORY_TOOL_INSTRUCTIONS",
|
||||
"SYMBOLS_TOOL_INSTRUCTIONS",
|
||||
]
|
||||
26
src/app/api/tools/instructions/market_instructions.md
Normal file
26
src/app/api/tools/instructions/market_instructions.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Market APIs - Instructions
|
||||
|
||||
## Tools (6)
|
||||
**Single-source (fast):** First available provider
|
||||
1. `get_product(asset_id)` - Current price, 1 asset
|
||||
2. `get_products(asset_ids)` - Current prices, multiple assets
|
||||
3. `get_historical_prices(asset_id, limit=100)` - Price history, 1 asset
|
||||
|
||||
**Aggregated (accurate):** All providers, VWAP calculation
|
||||
4. `get_product_aggregated(asset_id)` - Accurate price, 1 asset (4x API calls)
|
||||
5. `get_products_aggregated(asset_ids)` - Accurate prices, multiple (4x per asset)
|
||||
6. `get_historical_prices_aggregated(asset_id, limit=100)` - Historical, all sources (4x calls)
|
||||
|
||||
## Selection Strategy
|
||||
- Quick check → single-source (tools 1-3)
|
||||
- Keywords "accurate", "reliable", "comprehensive" → aggregated (tools 4-6)
|
||||
|
||||
## Key Mappings
|
||||
**Assets:** Bitcoin→BTC, Ethereum→ETH, Solana→SOL, Cardano→ADA, Ripple→XRP, Polkadot→DOT, Dogecoin→DOGE
|
||||
**Time:** "7 days"→limit=7, "30 days"→limit=30, "24h"→limit=24, "3 months"→limit=90
|
||||
|
||||
## Critical Rules
|
||||
- Never fabricate data - only report actual tool outputs
|
||||
- Include: ticker, price+currency, timestamp, provider source
|
||||
- Failure handling: Report explicit error, no placeholder data
|
||||
- Be concise to save tokens
|
||||
32
src/app/api/tools/instructions/news_instructions.md
Normal file
32
src/app/api/tools/instructions/news_instructions.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# News APIs - Instructions
|
||||
|
||||
## Tools (4)
|
||||
**Single-source (fast):** First available provider
|
||||
1. `get_top_headlines(limit=100)` - Top crypto headlines
|
||||
2. `get_latest_news(query, limit=100)` - Search specific topic
|
||||
|
||||
**Aggregated (comprehensive):** All providers (4x API calls)
|
||||
3. `get_top_headlines_aggregated(limit=100)` - Headlines from all sources
|
||||
4. `get_latest_news_aggregated(query, limit=100)` - Topic search, all sources
|
||||
|
||||
## Selection Strategy
|
||||
- Quick overview → single-source (tools 1-2)
|
||||
- Keywords "comprehensive", "all sources", "complete" → aggregated (tools 3-4)
|
||||
|
||||
## Query Formulation
|
||||
- "Bitcoin regulation" → query="Bitcoin regulation"
|
||||
- "ETH price surge" → query="Ethereum price increase"
|
||||
- Use full crypto names (Bitcoin not BTC), specific keywords for focus
|
||||
|
||||
## Article Structure
|
||||
Contains: title, source, url, published_at, description (optional), author (optional)
|
||||
|
||||
## Limits
|
||||
- Quick: 5-10 | Standard: 20-30 | Deep: 50-100
|
||||
|
||||
## Critical Rules
|
||||
- Never fabricate articles - only report actual tool outputs
|
||||
- Always include: title, source, URL, publication date
|
||||
- Failure handling: Report explicit error, suggest broader terms
|
||||
- Deduplicate same stories across sources
|
||||
- Be concise to save tokens
|
||||
64
src/app/api/tools/instructions/plan_memory_instructions.md
Normal file
64
src/app/api/tools/instructions/plan_memory_instructions.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Plan Memory Tool - Instructions
|
||||
|
||||
## Purpose
|
||||
Stateful task management for Team Leader: create, track, and record execution plans with persistent state.
|
||||
|
||||
## Tools (4)
|
||||
|
||||
### 1. `add_tasks(task_names: list[str])` → str
|
||||
Adds tasks with 'pending' status. Prevents duplicates. Returns: "Added N new tasks."
|
||||
|
||||
**Best Practices:**
|
||||
- Clear, descriptive names (e.g., "Fetch BTC price for last 7 days" not "Get data")
|
||||
- Order logically (dependencies first)
|
||||
- Include specific details in names
|
||||
|
||||
### 2. `get_next_pending_task()` → Task | None
|
||||
Returns FIRST pending task (FIFO order) or None if no pending tasks.
|
||||
|
||||
**Task Object:** `{name: str, status: "pending", result: None}`
|
||||
|
||||
### 3. `update_task_status(task_name, status, result)` → str
|
||||
Updates task after execution. Status: "completed" or "failed". Result: optional outcome/error.
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
update_task_status("Fetch BTC price", "completed", "BTC=$67,543 at 14:23:00")
|
||||
update_task_status("Analyze sentiment", "failed", "API rate limit exceeded")
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Update immediately after execution
|
||||
- Include key data in result (prices, counts, timestamps)
|
||||
- For failures, include error details
|
||||
|
||||
### 4. `list_all_tasks()` → list[str]
|
||||
Lists all tasks with status and results. Format: "- {name}: {status} (Result: {result})"
|
||||
|
||||
## Workflow Pattern
|
||||
```python
|
||||
add_tasks(["Task A", "Task B", "Task C"])
|
||||
while task := get_next_pending_task():
|
||||
result = execute(task['name'])
|
||||
update_task_status(task['name'], "completed", result)
|
||||
all_tasks = list_all_tasks()
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
1. Task names must be unique (exact match for updates)
|
||||
2. Always update status after execution
|
||||
3. Execute sequentially using get_next_pending_task()
|
||||
4. Store meaningful results, not just "Done"
|
||||
5. Handle failures: update status="failed" and continue
|
||||
6. Review with list_all_tasks() before finishing
|
||||
|
||||
## Good vs Poor Examples
|
||||
**Good Task Names:** "Fetch BTC price from Binance for 7 days" | "Analyze Ethereum news sentiment"
|
||||
**Poor Task Names:** "Get data" | "Step 1" | "Do analysis"
|
||||
|
||||
**Good Results:** "BTC: $67,543 (Binance, 2025-10-30 14:23)" | "15 articles, Bullish sentiment"
|
||||
**Poor Results:** "Done" | "Success" | "OK"
|
||||
|
||||
## State Persistence
|
||||
- Persists within single session only (not across restarts)
|
||||
- Call list_all_tasks() periodically to preserve context
|
||||
46
src/app/api/tools/instructions/social_instructions.md
Normal file
46
src/app/api/tools/instructions/social_instructions.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Social Media APIs - Instructions
|
||||
|
||||
## Tools (2)
|
||||
**Single-source (fast):** First available platform
|
||||
1. `get_top_crypto_posts(limit=5)` - Top crypto posts, first platform
|
||||
|
||||
**Aggregated (comprehensive):** All platforms (3x API calls: Reddit, X, 4chan)
|
||||
2. `get_top_crypto_posts_aggregated(limit_per_wrapper=5)` - Posts from all platforms
|
||||
|
||||
## Selection Strategy
|
||||
- Quick snapshot → single-source (tool 1)
|
||||
- Keywords "all platforms", "comprehensive", "compare" → aggregated (tool 2)
|
||||
|
||||
## Post Structure
|
||||
Contains: content, author, platform, url, created_at, score/upvotes, comments_count, subreddit/board
|
||||
|
||||
## Limits (posts are verbose)
|
||||
- Quick: 5 (default) | Standard: 10-15 | Deep: 20-30 | Max: 50
|
||||
|
||||
## Platform Notes
|
||||
- **Reddit:** r/cryptocurrency, r/bitcoin, r/ethereum (upvotes metric)
|
||||
- **X (Twitter):** High engagement crypto tweets (likes metric)
|
||||
- **4chan:** /biz/ board (replies metric, may contain inappropriate language)
|
||||
|
||||
## Critical Rules
|
||||
- Never fabricate posts - only report actual tool outputs
|
||||
- Include: platform, author, URL, engagement metrics, timestamp
|
||||
- Truncate content to max 280 chars
|
||||
- Summarize sentiment trends, don't list all posts verbatim
|
||||
- Frame as opinions, not facts - add disclaimers for unverified info
|
||||
- Be VERY concise to save tokens
|
||||
|
||||
## Sentiment Analysis
|
||||
- Identify recurring topics, positive/negative patterns, trending coins
|
||||
- Compare sentiment across platforms, highlight high engagement
|
||||
- Flag potential FUD or shilling
|
||||
- Do not treat social media posts as factual evidence
|
||||
- Encourage users to verify information from official sources
|
||||
|
||||
BEST PRACTICES:
|
||||
- Use aggregated tool for sentiment comparison across platforms
|
||||
- Combine with news data for context
|
||||
- Focus on high-engagement posts for quality
|
||||
- Summarize trends rather than listing every post
|
||||
- Be selective - quality over quantity
|
||||
- Respect character limits to avoid token overflow
|
||||
97
src/app/api/tools/instructions/symbols_instructions.md
Normal file
97
src/app/api/tools/instructions/symbols_instructions.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Crypto Symbols Tool - Instructions
|
||||
|
||||
## Purpose
|
||||
Cryptocurrency symbol lookup and name-based search using cached Yahoo Finance database.
|
||||
|
||||
## Tools (2)
|
||||
|
||||
### 1. `get_all_symbols()` → list[str]
|
||||
Returns all available cryptocurrency symbols from cache. No API calls, instant response.
|
||||
|
||||
**Returns:** List like `["BTC-USD", "ETH-USD", "SOL-USD", ...]` (~1,500+ symbols)
|
||||
|
||||
**Use Cases:**
|
||||
- Verify symbol availability before API call
|
||||
- List all supported cryptocurrencies
|
||||
- Validate user input against known symbols
|
||||
|
||||
### 2. `get_symbols_by_name(query: str)` → list[tuple[str, str]]
|
||||
Searches cryptocurrency names (case-insensitive, substring match). Returns list of (symbol, name) tuples.
|
||||
|
||||
**Examples:**
|
||||
```python
|
||||
get_symbols_by_name("bitcoin") # [("BTC-USD", "Bitcoin USD"), ("BCH-USD", "Bitcoin Cash USD"), ...]
|
||||
get_symbols_by_name("ethereum") # [("ETH-USD", "Ethereum USD"), ("ETC-USD", "Ethereum Classic USD")]
|
||||
get_symbols_by_name("doge") # [("DOGE-USD", "Dogecoin USD")]
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- Convert user-friendly names to symbols
|
||||
- Handle ambiguous input (multiple matches)
|
||||
- Discover cryptocurrencies by partial name
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Pattern 1: Symbol Validation
|
||||
```python
|
||||
matches = get_symbols_by_name(user_query)
|
||||
if not matches:
|
||||
return "Cryptocurrency not found"
|
||||
elif len(matches) == 1:
|
||||
symbol, name = matches[0]
|
||||
# Use with market API
|
||||
else:
|
||||
# Multiple matches - ask user to clarify
|
||||
return f"Multiple matches: {[name for _, name in matches]}"
|
||||
```
|
||||
|
||||
### Pattern 2: Batch Resolution
|
||||
```python
|
||||
names = ["Bitcoin", "Ethereum", "UnknownCoin"]
|
||||
resolved = []
|
||||
failed = []
|
||||
for name in names:
|
||||
matches = get_symbols_by_name(name.lower())
|
||||
if matches:
|
||||
resolved.append(matches[0][0])
|
||||
else:
|
||||
failed.append(name)
|
||||
# Use resolved with market_tool.get_products(resolved)
|
||||
```
|
||||
|
||||
## Integration with Market Tools
|
||||
1. User provides name (e.g., "Bitcoin")
|
||||
2. Search: `get_symbols_by_name("bitcoin")` → `("BTC-USD", "Bitcoin USD")`
|
||||
3. Fetch price: `market_tool.get_product("BTC-USD")`
|
||||
4. Return result
|
||||
|
||||
## Symbol Format
|
||||
- Yahoo Finance format: `BTC-USD`, `ETH-USD` (includes `-USD` suffix)
|
||||
- Some APIs need base only: strip suffix with `symbol.split('-')[0]` → `"BTC"`
|
||||
|
||||
## Common Mappings
|
||||
Bitcoin→BTC-USD | Ethereum→ETH-USD | Solana→SOL-USD | Cardano→ADA-USD | Dogecoin→DOGE-USD
|
||||
|
||||
## Critical Rules
|
||||
1. Always search before using names - never assume direct conversion
|
||||
2. Handle multiple matches (e.g., "Bitcoin" matches BTC and BCH)
|
||||
3. Case-insensitive: always use `.lower()` for queries
|
||||
4. Check empty results before accessing
|
||||
5. Remember `-USD` suffix in Yahoo symbols
|
||||
|
||||
## Search Best Practices
|
||||
- ✅ Full names: "ethereum", "bitcoin", "solana"
|
||||
- ✅ Partial OK: "doge" finds "Dogecoin"
|
||||
- ❌ Avoid: ticker symbols ("BTC"), too generic ("coin")
|
||||
|
||||
## Cache Notes
|
||||
- Cache file: `resources/cryptos.csv` (~1,500+ symbols)
|
||||
- No API calls during queries (instant response)
|
||||
- Loaded automatically on initialization
|
||||
- Static snapshot, not real-time
|
||||
|
||||
## Error Handling
|
||||
- Empty cache → Ensure `resources/cryptos.csv` exists
|
||||
- No results → Try broader terms, check spelling
|
||||
- Multiple matches → Show all, ask user to clarify
|
||||
- Symbol format mismatch → Strip `-USD` suffix if needed
|
||||
@@ -1,6 +1,7 @@
|
||||
from agno.tools import Toolkit
|
||||
|
||||
from app.agents.action_registry import friendly_action
|
||||
from app.api.tools.instructions import MARKET_TOOL_INSTRUCTIONS
|
||||
from app.api.wrapper_handler import WrapperHandler
|
||||
from app.api.core.markets import MarketWrapper, Price, ProductInfo
|
||||
from app.api.markets import BinanceWrapper, CoinBaseWrapper, CryptoCompareWrapper, YFinanceWrapper
|
||||
@@ -31,6 +32,7 @@ class MarketAPIsTool(MarketWrapper, Toolkit):
|
||||
Toolkit.__init__( # type: ignore
|
||||
self,
|
||||
name="Market APIs Toolkit",
|
||||
instructions=MARKET_TOOL_INSTRUCTIONS,
|
||||
tools=[
|
||||
self.get_product,
|
||||
self.get_products,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from agno.tools import Toolkit
|
||||
|
||||
from app.agents.action_registry import friendly_action
|
||||
from app.api.tools.instructions import NEWS_TOOL_INSTRUCTIONS
|
||||
from app.api.wrapper_handler import WrapperHandler
|
||||
from app.api.core.news import NewsWrapper, Article
|
||||
from app.api.news import NewsApiWrapper, GoogleNewsWrapper, CryptoPanicWrapper, DuckDuckGoWrapper
|
||||
@@ -34,6 +35,7 @@ class NewsAPIsTool(NewsWrapper, Toolkit):
|
||||
Toolkit.__init__( # type: ignore
|
||||
self,
|
||||
name="News APIs Toolkit",
|
||||
instructions=NEWS_TOOL_INSTRUCTIONS,
|
||||
tools=[
|
||||
self.get_top_headlines,
|
||||
self.get_latest_news,
|
||||
|
||||
95
src/app/api/tools/plan_memory_tool.py
Normal file
95
src/app/api/tools/plan_memory_tool.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from agno.tools.toolkit import Toolkit
|
||||
from typing import TypedDict, Literal
|
||||
from app.api.tools.instructions import PLAN_MEMORY_TOOL_INSTRUCTIONS
|
||||
|
||||
|
||||
|
||||
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[call-arg]
|
||||
name="Plan Memory Toolkit",
|
||||
instructions=PLAN_MEMORY_TOOL_INSTRUCTIONS,
|
||||
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 one or more new tasks to the execution plan with a 'pending' status.
|
||||
If a task with the same name already exists, it will not be added again.
|
||||
|
||||
Args:
|
||||
task_names (list[str]): A list of descriptive names for the tasks to be added.
|
||||
|
||||
Returns:
|
||||
str: A confirmation message, e.g., "Added 3 new tasks."
|
||||
"""
|
||||
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 from the plan that is currently in 'pending' status.
|
||||
This is used to fetch the next step in the execution plan.
|
||||
|
||||
Returns:
|
||||
Task | None: A Task object (dict) with 'name', 'status', and 'result' keys,
|
||||
or None if no tasks are 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, identified by its unique name.
|
||||
This is crucial for tracking the plan's progress after a step is executed.
|
||||
|
||||
Args:
|
||||
task_name (str): The exact name of the task to update (must match one from add_tasks).
|
||||
status (Literal["completed", "failed"]): The new status for the task.
|
||||
result (str | None, optional): An optional string describing the outcome or result
|
||||
of the task (e.g., a summary, an error message).
|
||||
|
||||
Returns:
|
||||
str: A confirmation message (e.g., "Task 'Task Name' updated to completed.")
|
||||
or an error message if the task is not found.
|
||||
"""
|
||||
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 currently in the execution plan, along with their status and result.
|
||||
Useful for reviewing the overall plan and progress.
|
||||
|
||||
Returns:
|
||||
list[str]: A list of formatted strings, where each string describes a task
|
||||
(e.g., "- TaskName: completed (Result: Done.)").
|
||||
"""
|
||||
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]
|
||||
@@ -1,6 +1,7 @@
|
||||
from agno.tools import Toolkit
|
||||
|
||||
from app.agents.action_registry import friendly_action
|
||||
from app.api.tools.instructions import SOCIAL_TOOL_INSTRUCTIONS
|
||||
from app.api.wrapper_handler import WrapperHandler
|
||||
from app.api.core.social import SocialPost, SocialWrapper
|
||||
from app.api.social import *
|
||||
@@ -34,7 +35,8 @@ class SocialAPIsTool(SocialWrapper, Toolkit):
|
||||
|
||||
Toolkit.__init__( # type: ignore
|
||||
self,
|
||||
name="Socials Toolkit",
|
||||
name="Socials APIs Toolkit",
|
||||
instructions=SOCIAL_TOOL_INSTRUCTIONS,
|
||||
tools=[
|
||||
self.get_top_crypto_posts,
|
||||
self.get_top_crypto_posts_aggregated,
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
import pandas as pd
|
||||
from io import StringIO
|
||||
from agno.tools.toolkit import Toolkit
|
||||
from app.api.tools.instructions import SYMBOLS_TOOL_INSTRUCTIONS
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging = logging.getLogger("crypto_symbols")
|
||||
@@ -24,7 +25,7 @@ class CryptoSymbolsTools(Toolkit):
|
||||
|
||||
Toolkit.__init__(self, # type: ignore
|
||||
name="Crypto Symbols Tool",
|
||||
instructions="Tool to get cryptocurrency symbols and search them by name.",
|
||||
instructions=SYMBOLS_TOOL_INSTRUCTIONS,
|
||||
tools=[
|
||||
self.get_all_symbols,
|
||||
self.get_symbols_by_name,
|
||||
|
||||
Reference in New Issue
Block a user