ynab update

This commit is contained in:
2026-01-31 22:47:43 -05:00
parent f0f72cce36
commit 6ae36b51a0
15 changed files with 645 additions and 8 deletions

View File

@@ -1,6 +1,7 @@
import os
from typing import cast
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.chat_models import BaseChatModel
from langchain.tools import tool
@@ -8,6 +9,10 @@ from langchain_openai import ChatOpenAI
from tavily import AsyncTavilyClient
from blueprints.rag.logic import query_vector_store
from utils.ynab_service import YNABService
# Load environment variables
load_dotenv()
# Configure LLM with llama-server or OpenAI fallback
llama_url = os.getenv("LLAMA_SERVER_URL")
@@ -25,7 +30,15 @@ model_with_fallback = cast(
BaseChatModel,
llama_chat.with_fallbacks([openai_fallback]) if llama_chat else openai_fallback,
)
client = AsyncTavilyClient(os.getenv("TAVILY_KEY"), "")
client = AsyncTavilyClient(api_key=os.getenv("TAVILY_API_KEY", ""))
# Initialize YNAB service (will only work if YNAB_ACCESS_TOKEN is set)
try:
ynab_service = YNABService()
ynab_enabled = True
except (ValueError, Exception) as e:
print(f"YNAB service not initialized: {e}")
ynab_enabled = False
@tool
@@ -85,4 +98,198 @@ async def simba_search(query: str):
return serialized, docs
main_agent = create_agent(model=model_with_fallback, tools=[simba_search, web_search])
@tool
def ynab_budget_summary() -> str:
"""Get overall budget summary and health status from YNAB.
Use this tool when the user asks about:
- Overall budget health or status
- How much money is to be budgeted
- Total budget amounts or spending
- General budget overview questions
Returns:
Summary of budget health, to-be-budgeted amount, total budgeted,
total activity, and available amounts.
"""
if not ynab_enabled:
return "YNAB integration is not configured. Please set YNAB_ACCESS_TOKEN environment variable."
try:
summary = ynab_service.get_budget_summary()
return summary["summary"]
except Exception as e:
return f"Error fetching budget summary: {str(e)}"
@tool
def ynab_search_transactions(
start_date: str = "",
end_date: str = "",
category_name: str = "",
payee_name: str = "",
) -> str:
"""Search YNAB transactions by date range, category, or payee.
Use this tool when the user asks about:
- Specific transactions or purchases
- Spending at a particular store or payee
- Transactions in a specific category
- What was spent during a time period
Args:
start_date: Start date in YYYY-MM-DD format (optional, defaults to 30 days ago)
end_date: End date in YYYY-MM-DD format (optional, defaults to today)
category_name: Filter by category name (optional, partial match)
payee_name: Filter by payee/store name (optional, partial match)
Returns:
List of matching transactions with dates, amounts, categories, and payees.
"""
if not ynab_enabled:
return "YNAB integration is not configured. Please set YNAB_ACCESS_TOKEN environment variable."
try:
result = ynab_service.get_transactions(
start_date=start_date or None,
end_date=end_date or None,
category_name=category_name or None,
payee_name=payee_name or None,
)
if result["count"] == 0:
return "No transactions found matching the specified criteria."
# Format transactions for readability
txn_list = []
for txn in result["transactions"][:10]: # Limit to 10 for readability
txn_list.append(
f"- {txn['date']}: {txn['payee']} - ${abs(txn['amount']):.2f} ({txn['category'] or 'Uncategorized'})"
)
return (
f"Found {result['count']} transactions from {result['start_date']} to {result['end_date']}. "
f"Total: ${abs(result['total_amount']):.2f}\n\n"
+ "\n".join(txn_list)
+ (
f"\n\n(Showing first 10 of {result['count']} transactions)"
if result["count"] > 10
else ""
)
)
except Exception as e:
return f"Error searching transactions: {str(e)}"
@tool
def ynab_category_spending(month: str = "") -> str:
"""Get spending breakdown by category for a specific month.
Use this tool when the user asks about:
- Spending by category
- What categories were overspent
- Monthly spending breakdown
- Budget vs actual spending for a month
Args:
month: Month in YYYY-MM format (optional, defaults to current month)
Returns:
Spending breakdown by category with budgeted, spent, and available amounts.
"""
if not ynab_enabled:
return "YNAB integration is not configured. Please set YNAB_ACCESS_TOKEN environment variable."
try:
result = ynab_service.get_category_spending(month=month or None)
summary = (
f"Budget spending for {result['month']}:\n"
f"Total budgeted: ${result['total_budgeted']:.2f}\n"
f"Total spent: ${result['total_spent']:.2f}\n"
f"Total available: ${result['total_available']:.2f}\n"
)
if result["overspent_categories"]:
summary += (
f"\nOverspent categories ({len(result['overspent_categories'])}):\n"
)
for cat in result["overspent_categories"][:5]:
summary += f"- {cat['name']}: Budgeted ${cat['budgeted']:.2f}, Spent ${cat['spent']:.2f}, Over by ${cat['overspent_by']:.2f}\n"
# Add top spending categories
summary += "\nTop spending categories:\n"
for cat in result["categories"][:10]:
if cat["activity"] < 0: # Only show spending (negative activity)
summary += f"- {cat['category']}: ${abs(cat['activity']):.2f} (budgeted: ${cat['budgeted']:.2f}, available: ${cat['available']:.2f})\n"
return summary
except Exception as e:
return f"Error fetching category spending: {str(e)}"
@tool
def ynab_insights(months_back: int = 3) -> str:
"""Generate insights about spending patterns and budget health over time.
Use this tool when the user asks about:
- Spending trends or patterns
- Budget recommendations
- Which categories are frequently overspent
- How current spending compares to past months
- Overall budget health analysis
Args:
months_back: Number of months to analyze (default 3, max 6)
Returns:
Insights about spending trends, frequently overspent categories,
and personalized recommendations.
"""
if not ynab_enabled:
return "YNAB integration is not configured. Please set YNAB_ACCESS_TOKEN environment variable."
try:
# Limit to reasonable range
months_back = min(max(1, months_back), 6)
result = ynab_service.get_spending_insights(months_back=months_back)
if "error" in result:
return result["error"]
summary = (
f"Spending insights for the last {months_back} months:\n\n"
f"Average monthly spending: ${result['average_monthly_spending']:.2f}\n"
f"Current month spending: ${result['current_month_spending']:.2f}\n"
f"Spending trend: {result['spending_trend']}\n"
)
if result["frequently_overspent_categories"]:
summary += "\nFrequently overspent categories:\n"
for cat in result["frequently_overspent_categories"][:5]:
summary += f"- {cat['category']}: overspent in {cat['months_overspent']} of {months_back} months\n"
if result["recommendations"]:
summary += "\nRecommendations:\n"
for rec in result["recommendations"]:
summary += f"- {rec}\n"
return summary
except Exception as e:
return f"Error generating insights: {str(e)}"
# Create tools list based on what's available
tools = [simba_search, web_search]
if ynab_enabled:
tools.extend(
[
ynab_budget_summary,
ynab_search_transactions,
ynab_category_spending,
ynab_insights,
]
)
# Llama 3.1 supports native function calling via OpenAI-compatible API
main_agent = create_agent(model=model_with_fallback, tools=tools)