Spaces:
Sleeping
Sleeping
File size: 13,966 Bytes
6bd3e57 e252f82 6bd3e57 cb6cec5 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 ec870c5 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 cb6cec5 6bd3e57 cb6cec5 6bd3e57 cb6cec5 6bd3e57 cb6cec5 6bd3e57 cb6cec5 6bd3e57 cb6cec5 6bd3e57 96c920d cb6cec5 96c920d cb6cec5 96c920d 6bd3e57 96c920d 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 e252f82 6bd3e57 | 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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 | """
Quote creation tool that generates professional quotes in Markdown format using product IDs from database.
This tool queries products by ID and creates structured quotes with proper Markdown tables.
"""
import os
from datetime import datetime
from typing import Union, List, Dict, Any
from collections import Counter
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from dotenv import load_dotenv
from .quote_template import (
Quote, QuoteItem, CustomerInfo,
generate_markdown_quote,
GREETING_PROMPT, INTRO_PROMPT,
generate_product_description,
generate_product_name,
generate_product_id
)
from .get_exchange_rates import convert_amount
from .agent_tools_utils import get_data_for_agent
from .tool_schemas import QuoteInput, QuoteOutput, QuoteItemSummary, ErrorOutput
# Load environment variables
load_dotenv()
def fetch_products_by_ids(product_ids: List[int], target_currency: str = "EUR") -> List[Dict[str, Any]]:
"""
Fetch product details from database by product IDs and convert prices to target currency.
Args:
product_ids: List of product IDs to fetch
target_currency: Currency to convert prices to
Returns:
List of product dictionaries with converted prices
"""
try:
# Query to get product details including model numbers
product_ids_str = ",".join(map(str, product_ids))
query = f"""
SELECT id, manufacturer, model_name, model_number_long, model_number_short,
description, msrp, currency, category, sub_category
FROM streamnet.products_list
WHERE id IN ({product_ids_str})
"""
# Execute query
df_original, df_formatted = get_data_for_agent(query, return_type="dict_records")
if not df_formatted or len(df_formatted) == 0:
return []
# Process and convert currencies
products = []
for product in df_formatted:
original_currency = product.get('currency', 'EUR')
msrp_price = float(product.get('msrp', 0))
# Convert currency if needed
if original_currency != target_currency and msrp_price > 0:
conversion_result = convert_amount(
original_currency,
target_currency,
msrp_price
)
if hasattr(conversion_result, 'converted_amount'):
converted_price = conversion_result.converted_amount
else:
converted_price = msrp_price # Use original price
else:
converted_price = msrp_price
# Build product name
product_name = product.get('model_name', '') or product.get('model_number_long', '') or f"Product {product.get('id')}"
products.append({
'id': product.get('id'),
'product_name': product_name,
'model_number_short': product.get('model_number_short'),
'model_number_long': product.get('model_number_long'),
'unit_price': converted_price,
'currency': target_currency,
'original_currency': original_currency,
'manufacturer': product.get('manufacturer', ''),
'category': product.get('category', ''),
'sub_category': product.get('sub_category', ''),
'original_description': product.get('description', '')
})
return products
except Exception as e:
print(f"Error fetching products: {e}")
return []
def generate_dynamic_content(prompt: str, max_retries: int = 2) -> str:
"""
Generate dynamic content using LLM.
Args:
prompt: The prompt for content generation
max_retries: Number of retry attempts if generation fails
Returns:
Generated content string
"""
try:
# Initialize the LLM
model = ChatOpenAI(
model=os.getenv("MODEL_NAME", "gpt-4o-mini"),
api_key=os.getenv("OPENAI_API_KEY"),
temperature=0.3 # Slightly creative but controlled
)
# Generate content
response = model.invoke([HumanMessage(content=prompt)])
return response.content.strip()
except Exception as e:
print(f"Warning: LLM content generation failed: {e}")
# Fallback to generic content
if "greeting" in prompt.lower():
return "Dear Valued Customer, thank you for your interest in our products."
else:
return "We are pleased to provide you with this detailed quotation for your consideration."
def create_quote_file(quote: Quote, content: str) -> str:
"""
Save the quote to a Markdown file in the created_quotes directory.
Args:
quote: Quote object with metadata
content: The formatted quote content
Returns:
Path to the saved file
"""
# Get the directory of the current file and navigate to created_quotes
current_dir = os.path.dirname(os.path.abspath(__file__))
quotes_dir = os.path.join(current_dir, "..", "created_quotes")
quotes_dir = os.path.abspath(quotes_dir) # Resolve to absolute path
os.makedirs(quotes_dir, exist_ok=True)
# Generate unique filename
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
customer_name = quote.customer.name.replace(" ", "_").replace("/", "_")
filename = f"quote_{quote.quote_id}_{customer_name}_{timestamp}.md"
filepath = os.path.join(quotes_dir, filename)
# Write the quote to file
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return filepath
@tool(args_schema=QuoteInput)
def create_quote(
product_ids: List[int],
customer_name: str,
customer_email: str = None,
customer_company: str = None,
target_currency: str = "EUR",
notes: str = None
) -> Union[QuoteOutput, ErrorOutput]:
"""
A quote creation tool that generates professional quotes using product IDs from the database.
This tool takes a list of product IDs (can include duplicates for multiple quantities),
fetches product details from the database, and generates a professional quote with
proper Markdown tables and formatting. This tool only works if the products have prices
set in the database.
Args:
product_ids (List[int]): List of product IDs from database. Duplicates indicate multiple quantities.
Example: [1, 1, 1, 3, 4, 5] means 3x product ID 1, 1x product ID 3, 1x product ID 4, 1x product ID 5
customer_name (str): Customer's full name (required)
customer_email (Optional[str]): Customer's email address
customer_company (Optional[str]): Customer's company name
target_currency (str): Currency for the final quote (EUR, USD, HUF)
notes (Optional[str]): Additional notes for the quote
Example usage:
create_quote(
product_ids=[123, 123, 456, 789], # 2x product 123, 1x product 456, 1x product 789
customer_name="John Smith",
customer_email="john@company.com",
customer_company="Tech Solutions Inc",
target_currency="EUR"
)
Returns:
QuoteOutput: Quote details with file path and summary information.
"""
try:
# Pydantic validation handled by LangGraph automatically
# Count quantities for each product ID
product_quantities = Counter(product_ids)
unique_product_ids = list(product_quantities.keys())
# Fetch product details from database
products_data = fetch_products_by_ids(unique_product_ids, target_currency)
if not products_data:
return ErrorOutput(error="No products found for the provided IDs")
# Create customer info
customer = CustomerInfo(
name=customer_name,
email=customer_email,
company=customer_company
)
# Initialize LLM for description generation
llm = ChatOpenAI(
model=os.getenv("MODEL_NAME", "gpt-4o-mini"),
api_key=os.getenv("OPENAI_API_KEY"),
temperature=0.3
)
# Create quote items with quantities and LLM-generated descriptions, names, and IDs
quote_items = []
for product_data in products_data:
product_id = product_data['id']
quantity = product_quantities[product_id]
# Prepare product info for AI generation
product_info = {
'id': product_data['id'],
'manufacturer': product_data['manufacturer'],
'model_name': product_data['product_name'],
'model_number_short': product_data['model_number_short'],
'model_number_long': product_data['model_number_long'],
'category': product_data['category'],
'sub_category': product_data['sub_category'],
'description': product_data['original_description']
}
# Generate AI-enhanced product name
ai_product_name = generate_product_name(product_info, llm)
# Generate AI-selected product ID
ai_product_id = generate_product_id(product_info, llm)
# Generate concise description using LLM
llm_description = generate_product_description(product_info, llm)
quote_item = QuoteItem(
product_id=product_id, # Keep database ID for internal reference
product_name=ai_product_name, # AI-generated name
model_number_short=ai_product_id, # AI-selected ID for display
model_number_long=product_data['model_number_long'],
description=llm_description,
quantity=quantity,
unit_price=product_data['unit_price'],
currency=target_currency
)
quote_items.append(quote_item)
# Create the quote object
quote = Quote(
customer=customer,
items=quote_items,
currency=target_currency,
notes=notes
)
# Generate LLM content
company_info = f" from {customer_company}" if customer_company else ""
# Generate greeting
greeting_prompt = GREETING_PROMPT.format(
customer_name=customer_name,
company_info=company_info,
company=customer_company or "N/A"
)
greeting = generate_dynamic_content(greeting_prompt)
# Generate introduction
detailed_product_list = []
for item in quote_items:
if item.quantity > 1:
detailed_product_list.append(f"{item.quantity}x {item.product_name}") # Using AI-generated name
else:
detailed_product_list.append(item.product_name) # Using AI-generated name
product_list_str = ", ".join(detailed_product_list)
intro_prompt = INTRO_PROMPT.format(
customer_name=customer_name,
company=customer_company or "your organization",
detailed_product_list=product_list_str,
unique_product_count=len(unique_product_ids),
total_item_count=sum(product_quantities.values())
)
introduction = generate_dynamic_content(intro_prompt)
# Generate the complete Markdown quote
quote_template = generate_markdown_quote(quote)
final_quote = quote_template.format(
greeting=greeting,
introduction=introduction
)
# Save to Markdown file
file_path = create_quote_file(quote, final_quote)
# Create quote summary with Pydantic models
quote_summary_items = [
QuoteItemSummary(
product_id=item.product_id,
name=item.product_name,
quantity=item.quantity,
unit_price=item.unit_price,
total=item.total_price
).dict() for item in quote_items
]
return QuoteOutput(
success=True,
quote_id=quote.quote_id,
customer_name=customer_name,
grand_total=quote.grand_total,
currency=target_currency,
item_count=len(quote_items),
unique_products=len(unique_product_ids),
total_items=sum(product_quantities.values()),
file_path=file_path,
file_format="markdown",
created_date=quote.created_date.isoformat(),
valid_until=quote.valid_until.isoformat(),
quote_summary={
"grand_total": quote.grand_total,
"items": quote_summary_items
}
)
except Exception as e:
return ErrorOutput(error=f"Quote creation failed: {str(e)}")
def create_sample_quote_from_ids():
"""Create a sample quote using product IDs for testing purposes."""
# Sample with some repeated IDs to test quantity handling
sample_product_ids = [760, 760, 763, 765, 765, 768] # 2x product 760, 1x product 763, 2x product 765, 1x product 768
result = create_quote.invoke({
"product_ids": sample_product_ids,
"customer_name": "Jane Doe",
"customer_email": "jane.doe@example.com",
"customer_company": "Example Corp",
"target_currency": "EUR",
"notes": "Sample quote generated from product IDs"
})
return result |