Spaces:
Runtime error
Runtime error
File size: 6,445 Bytes
927c050 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | """Invoice information tools for the multi-agent system."""
import logging
from langchain_core.tools import tool
from src.db.database import run_query_safe
logger = logging.getLogger(__name__)
def _safe_int(value: str, label: str = "value") -> int:
try:
return int(value)
except (ValueError, TypeError):
raise ValueError(f"Invalid {label}: '{value}'. Please provide a numeric value.")
@tool
def get_invoices_by_customer_sorted_by_date(customer_id: str) -> str:
"""
Look up all invoices for a customer using their ID.
Returns invoices sorted by date (most recent first).
"""
logger.info(f"TOOL_CALL: get_invoices_by_customer_sorted_by_date | customer_id={customer_id}")
try:
result = run_query_safe(
"""
SELECT InvoiceId, CustomerId, InvoiceDate, BillingAddress, BillingCity,
BillingState, BillingCountry, BillingPostalCode, Total
FROM Invoice
WHERE CustomerId = :customer_id
ORDER BY InvoiceDate DESC;
""",
{"customer_id": _safe_int(customer_id, "customer ID")},
)
logger.info(f"TOOL_RESULT: get_invoices_by_customer_sorted_by_date | result_length={len(result)}")
if result == "[]":
return f"No invoices found for customer {customer_id}."
return result
except Exception as e:
logger.error(f"Error in get_invoices_by_customer_sorted_by_date: {e}")
return f"Error retrieving invoices for customer {customer_id}. Please try again."
@tool
def get_invoice_line_items_sorted_by_price(customer_id: str) -> str:
"""
Look up all purchased line items for a customer, sorted by unit price (highest first).
Each row is a single purchased track (NOT a full invoice). An invoice with 5 tracks
will appear as 5 separate rows, each showing the track name, unit price, and quantity.
"""
logger.info(f"TOOL_CALL: get_invoice_line_items_sorted_by_price | customer_id={customer_id}")
try:
result = run_query_safe(
"""
SELECT Invoice.InvoiceId, Invoice.InvoiceDate, Invoice.Total AS InvoiceTotal,
Track.Name AS TrackName, InvoiceLine.UnitPrice, InvoiceLine.Quantity
FROM Invoice
JOIN InvoiceLine ON Invoice.InvoiceId = InvoiceLine.InvoiceId
JOIN Track ON InvoiceLine.TrackId = Track.TrackId
WHERE Invoice.CustomerId = :customer_id
ORDER BY InvoiceLine.UnitPrice DESC;
""",
{"customer_id": _safe_int(customer_id, "customer ID")},
)
logger.info(f"TOOL_RESULT: get_invoice_line_items_sorted_by_price | result_length={len(result)}")
if result == "[]":
return f"No purchase records found for customer {customer_id}."
return result
except Exception as e:
logger.error(f"Error in get_invoice_line_items_sorted_by_price: {e}")
return f"Error retrieving purchase records for customer {customer_id}. Please try again."
@tool
def get_employee_by_invoice_and_customer(invoice_id: str, customer_id: str) -> str:
"""
Find the employee (support rep) associated with a specific invoice and customer.
Returns employee full name, title, and email.
"""
logger.info(f"TOOL_CALL: get_employee_by_invoice_and_customer | invoice_id={invoice_id}, customer_id={customer_id}")
try:
result = run_query_safe(
"""
SELECT Employee.FirstName, Employee.LastName, Employee.Title, Employee.Email
FROM Employee
JOIN Customer ON Customer.SupportRepId = Employee.EmployeeId
JOIN Invoice ON Invoice.CustomerId = Customer.CustomerId
WHERE Invoice.InvoiceId = :invoice_id AND Invoice.CustomerId = :customer_id;
""",
{"invoice_id": _safe_int(invoice_id, "invoice ID"), "customer_id": _safe_int(customer_id, "customer ID")},
)
logger.info(f"TOOL_RESULT: get_employee_by_invoice_and_customer | result_length={len(result)}")
if result == "[]":
return f"No employee found for invoice ID {invoice_id} and customer ID {customer_id}."
return result
except Exception as e:
logger.error(f"Error in get_employee_by_invoice_and_customer: {e}")
return f"Error finding employee for invoice {invoice_id}. Please try again."
@tool
def get_invoice_line_items(invoice_id: str, customer_id: str) -> str:
"""
Get the detailed line items (tracks purchased) for a specific invoice.
Returns full track details for each purchased item.
"""
logger.info(f"TOOL_CALL: get_invoice_line_items | invoice_id={invoice_id}, customer_id={customer_id}")
try:
result = run_query_safe(
"""
SELECT Track.TrackId,
Track.Name AS TrackName,
Artist.Name AS ArtistName,
Album.Title AS AlbumTitle,
Genre.Name AS GenreName,
Track.Composer,
Track.Milliseconds,
ROUND(Track.Milliseconds / 60000.0, 1) AS DurationMinutes,
InvoiceLine.UnitPrice,
InvoiceLine.Quantity
FROM InvoiceLine
JOIN Invoice ON InvoiceLine.InvoiceId = Invoice.InvoiceId
JOIN Track ON InvoiceLine.TrackId = Track.TrackId
LEFT JOIN Album ON Track.AlbumId = Album.AlbumId
LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId
LEFT JOIN Genre ON Track.GenreId = Genre.GenreId
WHERE Invoice.InvoiceId = :invoice_id AND Invoice.CustomerId = :customer_id
ORDER BY Track.Name;
""",
{"invoice_id": _safe_int(invoice_id, "invoice ID"), "customer_id": _safe_int(customer_id, "customer ID")},
)
logger.info(f"TOOL_RESULT: get_invoice_line_items | result_length={len(result)}")
if result == "[]":
return f"No line items found for invoice {invoice_id} (customer {customer_id})."
return result
except Exception as e:
logger.error(f"Error in get_invoice_line_items: {e}")
return f"Error retrieving line items for invoice {invoice_id}. Please try again."
invoice_tools = [
get_invoices_by_customer_sorted_by_date,
get_invoice_line_items_sorted_by_price,
get_employee_by_invoice_and_customer,
get_invoice_line_items,
]
|