Spaces:
Sleeping
Enhance Sales Assistant with Pydantic Validation and Gradio Dashboard
Browse files- Updated README.md to include instructions for running the Gradio dashboard.
- Refactored tools_node.py to implement Pydantic validation for tool configurations and schemas.
- Improved agent_tools_utils.py to streamline data retrieval and error handling.
- Enhanced create_quote.py to utilize Pydantic models for input/output validation and error handling.
- Updated execute_sql_query.py to return structured output using Pydantic schemas.
- Refined get_exchange_rates.py to implement Pydantic validation for currency conversion.
- Modified db_utils.py to raise exceptions for better error handling.
- Improved prompts module with a new system prompt for enhanced user interaction.
- Updated Gradio app to improve UI and user experience with example queries.
- Introduced tool_schemas.py to define Pydantic schemas for various tool inputs and outputs.
- README.md +6 -0
- src/sales_assistant/agent_main/tools_node.py +66 -22
- src/sales_assistant/agent_tools/agent_tools_utils.py +26 -29
- src/sales_assistant/agent_tools/create_quote.py +44 -48
- src/sales_assistant/agent_tools/execute_sql_query.py +52 -17
- src/sales_assistant/agent_tools/get_exchange_rates.py +37 -18
- src/sales_assistant/agent_tools/tool_schemas.py +107 -0
- src/sales_assistant/db/utils/db_utils.py +5 -2
- src/sales_assistant/prompts/__init__.py +8 -0
- src/sales_assistant/prompts/system_prompt.py +56 -141
- src/sales_assistant/ui_dashboard/gradio_app.py +21 -11
|
@@ -19,3 +19,9 @@ uv pip install -e .
|
|
| 19 |
```bash
|
| 20 |
uv run python src/sales_assistant/main.py
|
| 21 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
```bash
|
| 20 |
uv run python src/sales_assistant/main.py
|
| 21 |
```
|
| 22 |
+
|
| 23 |
+
## TO run the gradio dashboard, run:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
uv run python src/sales_assistant/ui_dashboard/gradio_app.py
|
| 27 |
+
```
|
|
@@ -1,54 +1,98 @@
|
|
| 1 |
"""
|
| 2 |
-
Tools node
|
| 3 |
"""
|
| 4 |
-
|
|
|
|
| 5 |
from langchain_core.tools import BaseTool
|
| 6 |
from langgraph.prebuilt import ToolNode
|
| 7 |
-
from pydantic import BaseModel, Field
|
|
|
|
| 8 |
|
| 9 |
-
#
|
|
|
|
|
|
|
|
|
|
| 10 |
from ..agent_tools.execute_sql_query import execute_sql_query
|
| 11 |
from ..agent_tools.get_exchange_rates import exchange_converter
|
| 12 |
from ..agent_tools.create_quote import create_quote
|
| 13 |
|
|
|
|
| 14 |
class ToolConfig(BaseModel):
|
| 15 |
"""Configuration for tools with validation."""
|
| 16 |
enable_advanced_tools: bool = Field(default=False, description="Enable advanced database tools")
|
| 17 |
-
max_tools: int = Field(default=10, description="Maximum number of tools to load")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
def get_all_tools(config: ToolConfig = None) -> List[BaseTool]:
|
| 20 |
"""
|
| 21 |
-
Get
|
| 22 |
|
| 23 |
Args:
|
| 24 |
-
config: Optional tool configuration
|
| 25 |
|
| 26 |
Returns:
|
| 27 |
-
List of validated tools
|
| 28 |
"""
|
| 29 |
if config is None:
|
| 30 |
config = ToolConfig()
|
| 31 |
|
| 32 |
-
# Core tools
|
| 33 |
core_tools = [
|
| 34 |
execute_sql_query,
|
| 35 |
exchange_converter,
|
| 36 |
create_quote
|
| 37 |
]
|
| 38 |
|
| 39 |
-
#
|
| 40 |
-
|
| 41 |
-
for tool in core_tools:
|
| 42 |
-
if hasattr(tool, 'args_schema') or hasattr(tool, 'name'):
|
| 43 |
-
validated_tools.append(tool)
|
| 44 |
|
| 45 |
-
return
|
|
|
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
return ToolNode(tools)
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
"""
|
| 2 |
+
Tools node using state-of-the-art patterns with Pydantic validation.
|
| 3 |
"""
|
| 4 |
+
import os
|
| 5 |
+
from typing import List, Optional
|
| 6 |
from langchain_core.tools import BaseTool
|
| 7 |
from langgraph.prebuilt import ToolNode
|
| 8 |
+
from pydantic import BaseModel, Field, validator
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
|
| 11 |
+
# Load environment variables
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
# Import tools with proper schemas
|
| 15 |
from ..agent_tools.execute_sql_query import execute_sql_query
|
| 16 |
from ..agent_tools.get_exchange_rates import exchange_converter
|
| 17 |
from ..agent_tools.create_quote import create_quote
|
| 18 |
|
| 19 |
+
|
| 20 |
class ToolConfig(BaseModel):
|
| 21 |
"""Configuration for tools with validation."""
|
| 22 |
enable_advanced_tools: bool = Field(default=False, description="Enable advanced database tools")
|
| 23 |
+
max_tools: int = Field(default=10, ge=1, le=50, description="Maximum number of tools to load")
|
| 24 |
+
model_name: str = Field(default_factory=lambda: os.getenv("MODEL_NAME", "gpt-4o-mini"), description="Model optimized for these tools")
|
| 25 |
+
|
| 26 |
+
@validator('model_name')
|
| 27 |
+
def validate_model_name(cls, v):
|
| 28 |
+
allowed_models = ["gpt-5-mini", "gpt-4o-mini", "gpt-4", "gpt-3.5-turbo"]
|
| 29 |
+
if v not in allowed_models:
|
| 30 |
+
raise ValueError(f"Model must be one of {allowed_models}")
|
| 31 |
+
return v
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class ToolRegistry(BaseModel):
|
| 35 |
+
"""Registry for validated tools."""
|
| 36 |
+
tools: List[BaseTool] = Field(description="List of validated tools")
|
| 37 |
+
config: ToolConfig = Field(description="Tool configuration")
|
| 38 |
+
|
| 39 |
+
class Config:
|
| 40 |
+
arbitrary_types_allowed = True
|
| 41 |
+
|
| 42 |
+
@validator('tools')
|
| 43 |
+
def validate_tools(cls, v):
|
| 44 |
+
"""Validate that all tools have proper schemas."""
|
| 45 |
+
for tool in v:
|
| 46 |
+
if not hasattr(tool, 'args_schema'):
|
| 47 |
+
raise ValueError(f"Tool {tool.name} missing args_schema for validation")
|
| 48 |
+
if not hasattr(tool, 'name') or not tool.name:
|
| 49 |
+
raise ValueError("Tool missing required name attribute")
|
| 50 |
+
return v
|
| 51 |
+
|
| 52 |
|
| 53 |
+
def get_all_tools(config: Optional[ToolConfig] = None) -> List[BaseTool]:
|
| 54 |
"""
|
| 55 |
+
Get gpt-5-mini optimized tools with proper validation.
|
| 56 |
|
| 57 |
Args:
|
| 58 |
+
config: Optional tool configuration with validation
|
| 59 |
|
| 60 |
Returns:
|
| 61 |
+
List of validated tools
|
| 62 |
"""
|
| 63 |
if config is None:
|
| 64 |
config = ToolConfig()
|
| 65 |
|
| 66 |
+
# Core tools with Pydantic schemas for LangGraph
|
| 67 |
core_tools = [
|
| 68 |
execute_sql_query,
|
| 69 |
exchange_converter,
|
| 70 |
create_quote
|
| 71 |
]
|
| 72 |
|
| 73 |
+
# Create registry with validation
|
| 74 |
+
registry = ToolRegistry(tools=core_tools, config=config)
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
+
return registry.tools[:config.max_tools]
|
| 77 |
+
|
| 78 |
|
| 79 |
+
def create_tool_node(config: Optional[ToolConfig] = None) -> ToolNode:
|
| 80 |
+
"""Create a validated tool node with LangGraph built-in error handling."""
|
| 81 |
+
tools = get_all_tools(config)
|
| 82 |
+
# LangGraph ToolNode handles validation, execution, and error handling automatically
|
| 83 |
return ToolNode(tools)
|
| 84 |
|
| 85 |
+
|
| 86 |
+
# Factory function with LangGraph best practices
|
| 87 |
+
def create_optimized_tool_node() -> ToolNode:
|
| 88 |
+
"""Create tool node optimized for the configured model from environment."""
|
| 89 |
+
config = ToolConfig(
|
| 90 |
+
enable_advanced_tools=True,
|
| 91 |
+
max_tools=20,
|
| 92 |
+
model_name=os.getenv("MODEL_NAME", "gpt-5-mini")
|
| 93 |
+
)
|
| 94 |
+
return create_tool_node(config)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# Use the optimized factory for production
|
| 98 |
+
tool_node = create_optimized_tool_node()
|
|
@@ -15,54 +15,51 @@ def get_data_for_agent(query: str, return_type: str = "dict_records") -> str:
|
|
| 15 |
|
| 16 |
Args:
|
| 17 |
query (str): SQL query string to execute.
|
|
|
|
| 18 |
|
| 19 |
Returns:
|
| 20 |
-
|
| 21 |
"""
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
if return_type == "dict_records":
|
| 29 |
-
df_formated = df_original.to_dict(orient="records")
|
| 30 |
-
|
| 31 |
-
if return_type == "dict":
|
| 32 |
-
df_formated = df_original.to_dict()
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
except Exception as e:
|
| 43 |
-
data_value = f"Error retrieving data: {str(e)}"
|
| 44 |
-
df_original = pd.DataFrame()
|
| 45 |
-
df_formated = pd.DataFrame()
|
| 46 |
-
return df_original, df_formated
|
| 47 |
|
| 48 |
|
| 49 |
-
def create_results_metadata(
|
| 50 |
"""
|
| 51 |
Create metadata about the results such as count of records and columns.
|
| 52 |
|
| 53 |
Args:
|
| 54 |
-
|
| 55 |
|
| 56 |
Returns:
|
| 57 |
dict: Metadata including counts of records and columns.
|
| 58 |
"""
|
| 59 |
-
|
| 60 |
-
shape = results_orignal.shape
|
| 61 |
rows = shape[0]
|
| 62 |
cols = shape[1]
|
|
|
|
| 63 |
|
| 64 |
metadata = {
|
| 65 |
-
'
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
return metadata
|
|
|
|
| 15 |
|
| 16 |
Args:
|
| 17 |
query (str): SQL query string to execute.
|
| 18 |
+
return_type (str): Format for returned data ("dict_records", "dict", "string")
|
| 19 |
|
| 20 |
Returns:
|
| 21 |
+
tuple: (original_df, formatted_data) - Raises exception if query fails
|
| 22 |
"""
|
| 23 |
+
# Log the SQL query being executed
|
| 24 |
+
logger.info(f"Executing SQL query: {query}")
|
| 25 |
+
|
| 26 |
+
# Let exceptions propagate up to the calling function
|
| 27 |
+
df_original = read_sql(query)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
if return_type == "dict_records":
|
| 30 |
+
df_formated = df_original.to_dict(orient="records")
|
| 31 |
+
elif return_type == "dict":
|
| 32 |
+
df_formated = df_original.to_dict()
|
| 33 |
+
elif return_type == "string":
|
| 34 |
+
if df_original.empty:
|
| 35 |
+
df_formated = "No data found."
|
| 36 |
+
else:
|
| 37 |
+
df_formated = df_original.to_string(index=False)
|
| 38 |
+
else:
|
| 39 |
+
df_formated = df_original.to_dict(orient="records") # default
|
| 40 |
|
| 41 |
+
return df_original, df_formated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
+
def create_results_metadata(results_original: pd.DataFrame) -> dict:
|
| 45 |
"""
|
| 46 |
Create metadata about the results such as count of records and columns.
|
| 47 |
|
| 48 |
Args:
|
| 49 |
+
results_original (pd.DataFrame): Original DataFrame of query results.
|
| 50 |
|
| 51 |
Returns:
|
| 52 |
dict: Metadata including counts of records and columns.
|
| 53 |
"""
|
| 54 |
+
shape = results_original.shape
|
|
|
|
| 55 |
rows = shape[0]
|
| 56 |
cols = shape[1]
|
| 57 |
+
columns = results_original.columns.tolist()
|
| 58 |
|
| 59 |
metadata = {
|
| 60 |
+
'row_count': rows,
|
| 61 |
+
'column_count': cols,
|
| 62 |
+
'columns': columns
|
| 63 |
}
|
| 64 |
|
| 65 |
return metadata
|
|
@@ -5,7 +5,7 @@ This tool queries products by ID and creates structured quotes with proper Markd
|
|
| 5 |
|
| 6 |
import os
|
| 7 |
from datetime import datetime
|
| 8 |
-
from typing import
|
| 9 |
from collections import Counter
|
| 10 |
from langchain_core.tools import tool
|
| 11 |
from langchain_openai import ChatOpenAI
|
|
@@ -20,6 +20,7 @@ from .quote_template import (
|
|
| 20 |
)
|
| 21 |
from .get_exchange_rates import convert_amount
|
| 22 |
from .agent_tools_utils import get_data_for_agent
|
|
|
|
| 23 |
|
| 24 |
# Load environment variables
|
| 25 |
load_dotenv()
|
|
@@ -65,10 +66,9 @@ def fetch_products_by_ids(product_ids: List[int], target_currency: str = "EUR")
|
|
| 65 |
target_currency,
|
| 66 |
msrp_price
|
| 67 |
)
|
| 68 |
-
if
|
| 69 |
-
converted_price = conversion_result
|
| 70 |
else:
|
| 71 |
-
print(f"Warning: Currency conversion failed for product {product.get('id')}: {conversion_result.get('error')}")
|
| 72 |
converted_price = msrp_price # Use original price
|
| 73 |
else:
|
| 74 |
converted_price = msrp_price
|
|
@@ -157,25 +157,26 @@ def create_quote_file(quote: Quote, content: str) -> str:
|
|
| 157 |
return filepath
|
| 158 |
|
| 159 |
|
| 160 |
-
@tool
|
| 161 |
def create_quote(
|
| 162 |
product_ids: List[int],
|
| 163 |
customer_name: str,
|
| 164 |
-
customer_email:
|
| 165 |
-
customer_company:
|
| 166 |
target_currency: str = "EUR",
|
| 167 |
-
notes:
|
| 168 |
-
) ->
|
| 169 |
"""
|
| 170 |
-
A quote creation tool that generates quotes using product IDs from the database.
|
| 171 |
|
| 172 |
This tool takes a list of product IDs (can include duplicates for multiple quantities),
|
| 173 |
fetches product details from the database, and generates a professional quote with
|
| 174 |
-
proper Markdown tables and formatting. This
|
| 175 |
set in the database.
|
| 176 |
|
| 177 |
Args:
|
| 178 |
-
product_ids (List[int]): List of product IDs from database. Duplicates indicate multiple quantities.
|
|
|
|
| 179 |
customer_name (str): Customer's full name (required)
|
| 180 |
customer_email (Optional[str]): Customer's email address
|
| 181 |
customer_company (Optional[str]): Customer's company name
|
|
@@ -192,15 +193,10 @@ def create_quote(
|
|
| 192 |
)
|
| 193 |
|
| 194 |
Returns:
|
| 195 |
-
|
| 196 |
"""
|
| 197 |
try:
|
| 198 |
-
#
|
| 199 |
-
if not customer_name:
|
| 200 |
-
return {"error": "Customer name is required"}
|
| 201 |
-
|
| 202 |
-
if not product_ids or len(product_ids) == 0:
|
| 203 |
-
return {"error": "At least one product ID is required"}
|
| 204 |
|
| 205 |
# Count quantities for each product ID
|
| 206 |
product_quantities = Counter(product_ids)
|
|
@@ -210,7 +206,7 @@ def create_quote(
|
|
| 210 |
products_data = fetch_products_by_ids(unique_product_ids, target_currency)
|
| 211 |
|
| 212 |
if not products_data:
|
| 213 |
-
return
|
| 214 |
|
| 215 |
# Create customer info
|
| 216 |
customer = CustomerInfo(
|
|
@@ -296,38 +292,38 @@ def create_quote(
|
|
| 296 |
# Save to Markdown file
|
| 297 |
file_path = create_quote_file(quote, final_quote)
|
| 298 |
|
| 299 |
-
#
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
"grand_total": quote.grand_total,
|
| 315 |
-
"items":
|
| 316 |
-
{
|
| 317 |
-
"product_id": item.product_id,
|
| 318 |
-
"name": item.product_name,
|
| 319 |
-
"quantity": item.quantity,
|
| 320 |
-
"unit_price": item.unit_price,
|
| 321 |
-
"total": item.total_price
|
| 322 |
-
} for item in quote_items
|
| 323 |
-
]
|
| 324 |
}
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
return response
|
| 328 |
|
| 329 |
except Exception as e:
|
| 330 |
-
return
|
| 331 |
|
| 332 |
|
| 333 |
def create_sample_quote_from_ids():
|
|
|
|
| 5 |
|
| 6 |
import os
|
| 7 |
from datetime import datetime
|
| 8 |
+
from typing import Union, List, Dict, Any
|
| 9 |
from collections import Counter
|
| 10 |
from langchain_core.tools import tool
|
| 11 |
from langchain_openai import ChatOpenAI
|
|
|
|
| 20 |
)
|
| 21 |
from .get_exchange_rates import convert_amount
|
| 22 |
from .agent_tools_utils import get_data_for_agent
|
| 23 |
+
from .tool_schemas import QuoteInput, QuoteOutput, QuoteItemSummary, ErrorOutput
|
| 24 |
|
| 25 |
# Load environment variables
|
| 26 |
load_dotenv()
|
|
|
|
| 66 |
target_currency,
|
| 67 |
msrp_price
|
| 68 |
)
|
| 69 |
+
if hasattr(conversion_result, 'converted_amount'):
|
| 70 |
+
converted_price = conversion_result.converted_amount
|
| 71 |
else:
|
|
|
|
| 72 |
converted_price = msrp_price # Use original price
|
| 73 |
else:
|
| 74 |
converted_price = msrp_price
|
|
|
|
| 157 |
return filepath
|
| 158 |
|
| 159 |
|
| 160 |
+
@tool(args_schema=QuoteInput)
|
| 161 |
def create_quote(
|
| 162 |
product_ids: List[int],
|
| 163 |
customer_name: str,
|
| 164 |
+
customer_email: str = None,
|
| 165 |
+
customer_company: str = None,
|
| 166 |
target_currency: str = "EUR",
|
| 167 |
+
notes: str = None
|
| 168 |
+
) -> Union[QuoteOutput, ErrorOutput]:
|
| 169 |
"""
|
| 170 |
+
A quote creation tool that generates professional quotes using product IDs from the database.
|
| 171 |
|
| 172 |
This tool takes a list of product IDs (can include duplicates for multiple quantities),
|
| 173 |
fetches product details from the database, and generates a professional quote with
|
| 174 |
+
proper Markdown tables and formatting. This tool only works if the products have prices
|
| 175 |
set in the database.
|
| 176 |
|
| 177 |
Args:
|
| 178 |
+
product_ids (List[int]): List of product IDs from database. Duplicates indicate multiple quantities.
|
| 179 |
+
Example: [1, 1, 1, 3, 4, 5] means 3x product ID 1, 1x product ID 3, 1x product ID 4, 1x product ID 5
|
| 180 |
customer_name (str): Customer's full name (required)
|
| 181 |
customer_email (Optional[str]): Customer's email address
|
| 182 |
customer_company (Optional[str]): Customer's company name
|
|
|
|
| 193 |
)
|
| 194 |
|
| 195 |
Returns:
|
| 196 |
+
QuoteOutput: Quote details with file path and summary information.
|
| 197 |
"""
|
| 198 |
try:
|
| 199 |
+
# Pydantic validation handled by LangGraph automatically
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
# Count quantities for each product ID
|
| 202 |
product_quantities = Counter(product_ids)
|
|
|
|
| 206 |
products_data = fetch_products_by_ids(unique_product_ids, target_currency)
|
| 207 |
|
| 208 |
if not products_data:
|
| 209 |
+
return ErrorOutput(error="No products found for the provided IDs")
|
| 210 |
|
| 211 |
# Create customer info
|
| 212 |
customer = CustomerInfo(
|
|
|
|
| 292 |
# Save to Markdown file
|
| 293 |
file_path = create_quote_file(quote, final_quote)
|
| 294 |
|
| 295 |
+
# Create quote summary with Pydantic models
|
| 296 |
+
quote_summary_items = [
|
| 297 |
+
QuoteItemSummary(
|
| 298 |
+
product_id=item.product_id,
|
| 299 |
+
name=item.product_name,
|
| 300 |
+
quantity=item.quantity,
|
| 301 |
+
unit_price=item.unit_price,
|
| 302 |
+
total=item.total_price
|
| 303 |
+
).dict() for item in quote_items
|
| 304 |
+
]
|
| 305 |
+
|
| 306 |
+
return QuoteOutput(
|
| 307 |
+
success=True,
|
| 308 |
+
quote_id=quote.quote_id,
|
| 309 |
+
customer_name=customer_name,
|
| 310 |
+
grand_total=quote.grand_total,
|
| 311 |
+
currency=target_currency,
|
| 312 |
+
item_count=len(quote_items),
|
| 313 |
+
unique_products=len(unique_product_ids),
|
| 314 |
+
total_items=sum(product_quantities.values()),
|
| 315 |
+
file_path=file_path,
|
| 316 |
+
file_format="markdown",
|
| 317 |
+
created_date=quote.created_date.isoformat(),
|
| 318 |
+
valid_until=quote.valid_until.isoformat(),
|
| 319 |
+
quote_summary={
|
| 320 |
"grand_total": quote.grand_total,
|
| 321 |
+
"items": quote_summary_items
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
}
|
| 323 |
+
)
|
|
|
|
|
|
|
| 324 |
|
| 325 |
except Exception as e:
|
| 326 |
+
return ErrorOutput(error=f"Quote creation failed: {str(e)}")
|
| 327 |
|
| 328 |
|
| 329 |
def create_sample_quote_from_ids():
|
|
@@ -1,16 +1,20 @@
|
|
| 1 |
from typing import Any, Dict, List, Optional, Literal
|
| 2 |
from langchain_core.tools import tool
|
| 3 |
-
from .agent_tools_utils import
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
-
@tool
|
| 7 |
def execute_sql_query(
|
| 8 |
sql_query: str,
|
| 9 |
query_type: Optional[Literal["search", "statistics", "distinct_values", "sample", "advanced"]] = None
|
| 10 |
-
) ->
|
| 11 |
"""
|
| 12 |
Execute any SQL query against the database with complete flexibility for all types of operations.
|
| 13 |
This unified tool handles product searches, statistical analysis, data exploration, sampling, and advanced queries.
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
Args:
|
| 16 |
sql_query (str): Any valid SQL query to execute against the streamnet.products_list table.
|
|
@@ -22,6 +26,10 @@ def execute_sql_query(
|
|
| 22 |
- "advanced": Complex queries with joins, subqueries, analytics
|
| 23 |
|
| 24 |
Query Examples by Type:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
SEARCH QUERIES:
|
| 27 |
- "SELECT * FROM streamnet.products_list WHERE manufacturer = 'Samsung' AND category = 'Electronics' LIMIT 15;"
|
|
@@ -56,24 +64,51 @@ def execute_sql_query(
|
|
| 56 |
- "SELECT YEAR(NOW()) as current_year, manufacturer, model_name, msrp FROM streamnet.products_list WHERE description LIKE '%2024%' OR description LIKE '%new%';"
|
| 57 |
|
| 58 |
Returns:
|
| 59 |
-
|
| 60 |
"""
|
| 61 |
try:
|
| 62 |
results_original, results_formatted = get_data_for_agent(sql_query, return_type="dict_records")
|
| 63 |
metadata = create_results_metadata(results_original)
|
| 64 |
-
|
| 65 |
-
result_dict = {
|
| 66 |
-
"query_executed": sql_query,
|
| 67 |
-
"data_from_db": results_formatted,
|
| 68 |
-
"query_type": query_type,
|
| 69 |
-
"metadata": metadata
|
| 70 |
-
}
|
| 71 |
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
except Exception as e:
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from typing import Any, Dict, List, Optional, Literal
|
| 2 |
from langchain_core.tools import tool
|
| 3 |
+
from .agent_tools_utils import get_data_for_agent, create_results_metadata
|
| 4 |
+
from .tool_schemas import SQLQueryInput, SQLQueryOutput, QueryMetadata
|
| 5 |
|
| 6 |
|
| 7 |
+
@tool(args_schema=SQLQueryInput)
|
| 8 |
def execute_sql_query(
|
| 9 |
sql_query: str,
|
| 10 |
query_type: Optional[Literal["search", "statistics", "distinct_values", "sample", "advanced"]] = None
|
| 11 |
+
) -> SQLQueryOutput:
|
| 12 |
"""
|
| 13 |
Execute any SQL query against the database with complete flexibility for all types of operations.
|
| 14 |
This unified tool handles product searches, statistical analysis, data exploration, sampling, and advanced queries.
|
| 15 |
+
|
| 16 |
+
We use mySQL database with the following main table:
|
| 17 |
+
- Main table: `streamnet.products_list`
|
| 18 |
|
| 19 |
Args:
|
| 20 |
sql_query (str): Any valid SQL query to execute against the streamnet.products_list table.
|
|
|
|
| 26 |
- "advanced": Complex queries with joins, subqueries, analytics
|
| 27 |
|
| 28 |
Query Examples by Type:
|
| 29 |
+
|
| 30 |
+
DESCRIBE QUERIES:
|
| 31 |
+
- "DESCRIBE streamnet.products_list;"
|
| 32 |
+
- "SHOW COLUMNS FROM streamnet.products_list;"
|
| 33 |
|
| 34 |
SEARCH QUERIES:
|
| 35 |
- "SELECT * FROM streamnet.products_list WHERE manufacturer = 'Samsung' AND category = 'Electronics' LIMIT 15;"
|
|
|
|
| 64 |
- "SELECT YEAR(NOW()) as current_year, manufacturer, model_name, msrp FROM streamnet.products_list WHERE description LIKE '%2024%' OR description LIKE '%new%';"
|
| 65 |
|
| 66 |
Returns:
|
| 67 |
+
SQLQueryOutput: Results from the SQL query execution with metadata, including error details if query fails.
|
| 68 |
"""
|
| 69 |
try:
|
| 70 |
results_original, results_formatted = get_data_for_agent(sql_query, return_type="dict_records")
|
| 71 |
metadata = create_results_metadata(results_original)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
# Convert to Pydantic metadata model with success info
|
| 74 |
+
pydantic_metadata = QueryMetadata(
|
| 75 |
+
row_count=metadata.get("row_count", 0),
|
| 76 |
+
column_count=metadata.get("column_count", 0),
|
| 77 |
+
columns=metadata.get("columns", []),
|
| 78 |
+
execution_time=metadata.get("execution_time"),
|
| 79 |
+
query_successful=True,
|
| 80 |
+
error_message=None,
|
| 81 |
+
error_type=None
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
return SQLQueryOutput(
|
| 85 |
+
query_executed=sql_query,
|
| 86 |
+
data_from_db=results_formatted,
|
| 87 |
+
query_type=query_type,
|
| 88 |
+
metadata=pydantic_metadata,
|
| 89 |
+
success=True
|
| 90 |
+
)
|
| 91 |
|
| 92 |
except Exception as e:
|
| 93 |
+
# Capture detailed error information
|
| 94 |
+
error_type = type(e).__name__
|
| 95 |
+
error_message = str(e)
|
| 96 |
+
|
| 97 |
+
# Create metadata with error details
|
| 98 |
+
error_metadata = QueryMetadata(
|
| 99 |
+
row_count=0,
|
| 100 |
+
column_count=0,
|
| 101 |
+
columns=[],
|
| 102 |
+
execution_time=None,
|
| 103 |
+
query_successful=False,
|
| 104 |
+
error_message=error_message,
|
| 105 |
+
error_type=error_type
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
return SQLQueryOutput(
|
| 109 |
+
query_executed=sql_query,
|
| 110 |
+
data_from_db=[],
|
| 111 |
+
query_type=query_type,
|
| 112 |
+
metadata=error_metadata,
|
| 113 |
+
success=False
|
| 114 |
+
)
|
|
@@ -1,5 +1,6 @@
|
|
| 1 |
-
from typing import Any, Dict
|
| 2 |
from langchain_core.tools import tool
|
|
|
|
| 3 |
|
| 4 |
# Fixed exchange rates relative to HUF (kept simple and deterministic)
|
| 5 |
_EXCHANGE_RATES = {
|
|
@@ -13,8 +14,8 @@ def _normalize_currency(code: str) -> str:
|
|
| 13 |
return code.strip().upper() if isinstance(code, str) else ""
|
| 14 |
|
| 15 |
|
| 16 |
-
def convert_amount(from_currency: str, to_currency: str, amount: float, precision: int = 2) ->
|
| 17 |
-
"""Simple, deterministic currency converter.
|
| 18 |
|
| 19 |
Returns a small dict on success: {
|
| 20 |
"converted_amount": float,
|
|
@@ -29,38 +30,56 @@ def convert_amount(from_currency: str, to_currency: str, amount: float, precisio
|
|
| 29 |
tc = _normalize_currency(to_currency)
|
| 30 |
|
| 31 |
if not fc or not tc:
|
| 32 |
-
return
|
| 33 |
|
| 34 |
if fc not in _EXCHANGE_RATES or tc not in _EXCHANGE_RATES:
|
| 35 |
-
return
|
| 36 |
|
| 37 |
try:
|
| 38 |
amt = float(amount)
|
| 39 |
except Exception:
|
| 40 |
-
return
|
| 41 |
|
| 42 |
if amt < 0:
|
| 43 |
-
return
|
| 44 |
|
| 45 |
if fc == tc:
|
| 46 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
# Convert via HUF as base: source -> HUF -> target
|
| 49 |
converted = (amt * _EXCHANGE_RATES[fc]) / _EXCHANGE_RATES[tc]
|
| 50 |
rate = converted / amt if amt != 0 else 0.0
|
| 51 |
|
| 52 |
-
return
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
|
| 60 |
|
| 61 |
-
@tool
|
| 62 |
-
def exchange_converter(from_currency: str = "EUR", to_currency: str = "HUF", amount: float = 1.0, precision: int = 2) ->
|
| 63 |
"""
|
| 64 |
-
Currency converter tool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
"""
|
| 66 |
return convert_amount(from_currency, to_currency, amount, precision)
|
|
|
|
| 1 |
+
from typing import Any, Dict, Union
|
| 2 |
from langchain_core.tools import tool
|
| 3 |
+
from .tool_schemas import CurrencyConversionInput, CurrencyConversionOutput, ErrorOutput
|
| 4 |
|
| 5 |
# Fixed exchange rates relative to HUF (kept simple and deterministic)
|
| 6 |
_EXCHANGE_RATES = {
|
|
|
|
| 14 |
return code.strip().upper() if isinstance(code, str) else ""
|
| 15 |
|
| 16 |
|
| 17 |
+
def convert_amount(from_currency: str, to_currency: str, amount: float, precision: int = 2) -> Union[CurrencyConversionOutput, ErrorOutput]:
|
| 18 |
+
"""Simple, deterministic currency converter with Pydantic validation.
|
| 19 |
|
| 20 |
Returns a small dict on success: {
|
| 21 |
"converted_amount": float,
|
|
|
|
| 30 |
tc = _normalize_currency(to_currency)
|
| 31 |
|
| 32 |
if not fc or not tc:
|
| 33 |
+
return ErrorOutput(error="from_currency and to_currency are required strings")
|
| 34 |
|
| 35 |
if fc not in _EXCHANGE_RATES or tc not in _EXCHANGE_RATES:
|
| 36 |
+
return ErrorOutput(error=f"Unsupported currency. Supported: {', '.join(sorted(_EXCHANGE_RATES.keys()))}")
|
| 37 |
|
| 38 |
try:
|
| 39 |
amt = float(amount)
|
| 40 |
except Exception:
|
| 41 |
+
return ErrorOutput(error="Amount must be a number")
|
| 42 |
|
| 43 |
if amt < 0:
|
| 44 |
+
return ErrorOutput(error="Amount must be non-negative")
|
| 45 |
|
| 46 |
if fc == tc:
|
| 47 |
+
return CurrencyConversionOutput(
|
| 48 |
+
original_amount=round(amt, precision),
|
| 49 |
+
from_currency=fc,
|
| 50 |
+
converted_amount=round(amt, precision),
|
| 51 |
+
to_currency=tc,
|
| 52 |
+
exchange_rate=1.0
|
| 53 |
+
)
|
| 54 |
|
| 55 |
# Convert via HUF as base: source -> HUF -> target
|
| 56 |
converted = (amt * _EXCHANGE_RATES[fc]) / _EXCHANGE_RATES[tc]
|
| 57 |
rate = converted / amt if amt != 0 else 0.0
|
| 58 |
|
| 59 |
+
return CurrencyConversionOutput(
|
| 60 |
+
original_amount=round(amt, precision),
|
| 61 |
+
from_currency=fc,
|
| 62 |
+
converted_amount=round(converted, precision),
|
| 63 |
+
to_currency=tc,
|
| 64 |
+
exchange_rate=round(rate, max(4, precision))
|
| 65 |
+
)
|
| 66 |
|
| 67 |
|
| 68 |
+
@tool(args_schema=CurrencyConversionInput)
|
| 69 |
+
def exchange_converter(from_currency: str = "EUR", to_currency: str = "HUF", amount: float = 1.0, precision: int = 2) -> Union[CurrencyConversionOutput, ErrorOutput]:
|
| 70 |
"""
|
| 71 |
+
Currency converter tool that can convert between EUR, USD, and HUF currencies with configurable precision.
|
| 72 |
+
|
| 73 |
+
This tool provides deterministic currency conversion using fixed exchange rates relative to HUF.
|
| 74 |
+
Supports EUR, USD, and HUF currencies with customizable decimal precision for results.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
from_currency (str): Source currency code (EUR, USD, HUF). Default: "EUR"
|
| 78 |
+
to_currency (str): Target currency code (EUR, USD, HUF). Default: "HUF"
|
| 79 |
+
amount (float): Amount to convert (must be non-negative). Default: 1.0
|
| 80 |
+
precision (int): Decimal precision for result (0-10). Default: 2
|
| 81 |
+
|
| 82 |
+
Returns:
|
| 83 |
+
CurrencyConversionOutput: Conversion result with original amount, converted amount, and exchange rate.
|
| 84 |
"""
|
| 85 |
return convert_amount(from_currency, to_currency, amount, precision)
|
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pydantic schemas for tool inputs and outputs following LangGraph standards.
|
| 3 |
+
"""
|
| 4 |
+
from typing import Any, Dict, List, Optional, Literal, Union
|
| 5 |
+
from pydantic import BaseModel, Field, validator
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class SQLQueryInput(BaseModel):
|
| 10 |
+
"""Input schema for SQL query execution."""
|
| 11 |
+
sql_query: str = Field(description="Valid SQL query to execute")
|
| 12 |
+
query_type: Optional[Literal["search", "statistics", "distinct_values", "sample", "advanced"]] = Field(
|
| 13 |
+
default=None, description="Type hint for query classification"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class QueryMetadata(BaseModel):
|
| 18 |
+
"""Metadata for query results."""
|
| 19 |
+
row_count: int = Field(description="Number of rows returned")
|
| 20 |
+
column_count: int = Field(description="Number of columns in result")
|
| 21 |
+
columns: List[str] = Field(description="Column names")
|
| 22 |
+
execution_time: Optional[float] = Field(default=None, description="Query execution time in seconds")
|
| 23 |
+
query_successful: bool = Field(default=True, description="Whether the query executed successfully")
|
| 24 |
+
error_message: Optional[str] = Field(default=None, description="Error message if query failed")
|
| 25 |
+
error_type: Optional[str] = Field(default=None, description="Type of error that occurred")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class SQLQueryOutput(BaseModel):
|
| 29 |
+
"""Output schema for SQL query results."""
|
| 30 |
+
query_executed: str = Field(description="The SQL query that was executed")
|
| 31 |
+
data_from_db: List[Dict[str, Any]] = Field(description="Query results as list of dictionaries")
|
| 32 |
+
query_type: Optional[str] = Field(description="Type of query executed")
|
| 33 |
+
metadata: QueryMetadata = Field(description="Query execution metadata")
|
| 34 |
+
success: bool = Field(default=True, description="Whether query executed successfully")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class CurrencyConversionInput(BaseModel):
|
| 38 |
+
"""Input schema for currency conversion."""
|
| 39 |
+
from_currency: str = Field(default="EUR", description="Source currency code")
|
| 40 |
+
to_currency: str = Field(default="HUF", description="Target currency code")
|
| 41 |
+
amount: float = Field(default=1.0, ge=0, description="Amount to convert (must be non-negative)")
|
| 42 |
+
precision: int = Field(default=2, ge=0, le=10, description="Decimal precision for result")
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class CurrencyConversionOutput(BaseModel):
|
| 46 |
+
"""Output schema for currency conversion results."""
|
| 47 |
+
original_amount: float = Field(description="Original amount")
|
| 48 |
+
from_currency: str = Field(description="Source currency")
|
| 49 |
+
converted_amount: float = Field(description="Converted amount")
|
| 50 |
+
to_currency: str = Field(description="Target currency")
|
| 51 |
+
exchange_rate: float = Field(description="Exchange rate used")
|
| 52 |
+
success: bool = Field(default=True, description="Whether conversion was successful")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class QuoteItemSummary(BaseModel):
|
| 56 |
+
"""Summary of a quote item."""
|
| 57 |
+
product_id: int = Field(description="Product ID from database")
|
| 58 |
+
name: str = Field(description="Product name")
|
| 59 |
+
quantity: int = Field(description="Quantity ordered")
|
| 60 |
+
unit_price: float = Field(description="Unit price")
|
| 61 |
+
total: float = Field(description="Total price for this item")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class QuoteInput(BaseModel):
|
| 65 |
+
"""Input schema for quote creation."""
|
| 66 |
+
product_ids: List[int] = Field(description="List of product IDs (duplicates indicate multiple quantities)")
|
| 67 |
+
customer_name: str = Field(description="Customer full name")
|
| 68 |
+
customer_email: Optional[str] = Field(default=None, description="Customer email address")
|
| 69 |
+
customer_company: Optional[str] = Field(default=None, description="Customer company name")
|
| 70 |
+
target_currency: str = Field(default="EUR", description="Currency for quote")
|
| 71 |
+
notes: Optional[str] = Field(default=None, description="Additional notes")
|
| 72 |
+
|
| 73 |
+
@validator('product_ids')
|
| 74 |
+
def validate_product_ids(cls, v):
|
| 75 |
+
if not v or len(v) == 0:
|
| 76 |
+
raise ValueError("At least one product ID is required")
|
| 77 |
+
return v
|
| 78 |
+
|
| 79 |
+
@validator('customer_name')
|
| 80 |
+
def validate_customer_name(cls, v):
|
| 81 |
+
if not v or not v.strip():
|
| 82 |
+
raise ValueError("Customer name is required")
|
| 83 |
+
return v.strip()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class QuoteOutput(BaseModel):
|
| 87 |
+
"""Output schema for quote creation results."""
|
| 88 |
+
success: bool = Field(description="Whether quote was created successfully")
|
| 89 |
+
quote_id: str = Field(description="Unique quote identifier")
|
| 90 |
+
customer_name: str = Field(description="Customer name")
|
| 91 |
+
grand_total: float = Field(description="Total quote amount")
|
| 92 |
+
currency: str = Field(description="Quote currency")
|
| 93 |
+
item_count: int = Field(description="Number of unique items")
|
| 94 |
+
unique_products: int = Field(description="Number of unique products")
|
| 95 |
+
total_items: int = Field(description="Total quantity of all items")
|
| 96 |
+
file_path: str = Field(description="Path to generated quote file")
|
| 97 |
+
file_format: str = Field(description="Format of generated file")
|
| 98 |
+
created_date: str = Field(description="Quote creation date")
|
| 99 |
+
valid_until: str = Field(description="Quote expiration date")
|
| 100 |
+
quote_summary: Dict[str, Any] = Field(description="Summary of quote contents")
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class ErrorOutput(BaseModel):
|
| 104 |
+
"""Standard error output schema."""
|
| 105 |
+
error: str = Field(description="Error message")
|
| 106 |
+
success: bool = Field(default=False, description="Always False for errors")
|
| 107 |
+
timestamp: str = Field(default_factory=lambda: datetime.now().isoformat(), description="Error timestamp")
|
|
@@ -18,6 +18,9 @@ def read_sql(query_str: str) -> pd.DataFrame:
|
|
| 18 |
|
| 19 |
Returns:
|
| 20 |
pandas DataFrame with query results
|
|
|
|
|
|
|
|
|
|
| 21 |
"""
|
| 22 |
tunnel = None
|
| 23 |
engine = None
|
|
@@ -30,8 +33,8 @@ def read_sql(query_str: str) -> pd.DataFrame:
|
|
| 30 |
return df
|
| 31 |
except Exception as e:
|
| 32 |
print(f"Error executing read query: {e}")
|
| 33 |
-
#
|
| 34 |
-
|
| 35 |
finally:
|
| 36 |
disconnect(engine, tunnel)
|
| 37 |
|
|
|
|
| 18 |
|
| 19 |
Returns:
|
| 20 |
pandas DataFrame with query results
|
| 21 |
+
|
| 22 |
+
Raises:
|
| 23 |
+
Exception: Re-raises any database or connection errors for proper error handling
|
| 24 |
"""
|
| 25 |
tunnel = None
|
| 26 |
engine = None
|
|
|
|
| 33 |
return df
|
| 34 |
except Exception as e:
|
| 35 |
print(f"Error executing read query: {e}")
|
| 36 |
+
# Re-raise the exception so it can be handled by the calling function
|
| 37 |
+
raise e
|
| 38 |
finally:
|
| 39 |
disconnect(engine, tunnel)
|
| 40 |
|
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Prompts module for the sales assistant agent.
|
| 3 |
+
Optimized for GPT-5-mini reasoning capabilities.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from .system_prompt import SYSTEM_PROMPT
|
| 7 |
+
|
| 8 |
+
__all__ = ["SYSTEM_PROMPT"]
|
|
@@ -1,141 +1,56 @@
|
|
| 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 |
-
## Price list description
|
| 58 |
-
- For sony products the category and sub_category is not desciptive e.g. Professional BRAVIA Full HD & 4K are tvs, and Video Wall are led displays
|
| 59 |
-
- For sony you need to look at the description column to find the products and their categories too
|
| 60 |
-
|
| 61 |
-
## Guidelines
|
| 62 |
-
- If the user intent is straightforward, use the most relevant tool with a custom SQL query
|
| 63 |
-
- If not always start with database exploration using custom SQL queries in this way you can find the products more easily
|
| 64 |
-
- Be creative with your SQL - use complex WHERE clauses, JOINs, subqueries as needed
|
| 65 |
-
- Use multiple tools with different SQL queries to get a complete picture if needed
|
| 66 |
-
- Provide specific product details including pricing when a user ask for it
|
| 67 |
-
- the `exchange_converter` tool can be used to convert prices if the user asks for it or if you want to normalize prices to a common currency
|
| 68 |
-
- **Quote Generation**: When users want quotes, use the `create_quote` tool with:
|
| 69 |
-
- Customer information (name, email, company)
|
| 70 |
-
- Product id-s of the products they want quotes for (use multiple if needed)
|
| 71 |
-
- Appropriate currency
|
| 72 |
-
- The tool generates professional quotes with LLM-enhanced greetings and introductions
|
| 73 |
-
|
| 74 |
-
## Other Guidelines
|
| 75 |
-
- Ask follow-up questions if the user's request is unclear
|
| 76 |
-
- Build upon previous SQL query results to refine your search
|
| 77 |
-
- Remember context from previous explorations in the conversation
|
| 78 |
-
|
| 79 |
-
If you want you can start by exploring the database structure with a custom SQL query to understand what you're working with!"""
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
SYSTEM_PROMPT = """You are an intelligent database exploration and sales assistant agent. You help users explore a MySQL database and answer questions about products using dynamic SQL queries.
|
| 83 |
-
|
| 84 |
-
## Your Mission
|
| 85 |
-
1. **Explore First**: When a user asks a question, start by exploring the database structure and content using custom SQL queries. This should be broad.
|
| 86 |
-
- tips: start wth the manufcaturer, get the categories and subcategories, get some samples and then look into the descipton column to find products
|
| 87 |
-
2. **Understand the Data**: Use multiple tools with creative SQL queries to understand what products are available if needed
|
| 88 |
-
3. **Query Strategically**: Build sophisticated SQL queries based on your exploration findings run multiple queries to get a complete picture
|
| 89 |
-
4. **Answer Comprehensively**: Provide helpful, detailed answers with specific product information
|
| 90 |
-
|
| 91 |
-
## Database Information
|
| 92 |
-
- Main table: `streamnet.products_list`
|
| 93 |
-
- Key columns: id, manufacturer, category, sub_category, model_name, model_number_long, model_number_short, description, currency, distributor_pricing, msrp
|
| 94 |
-
- Contains various products with pricing and detailed information from various manufacturers
|
| 95 |
-
- different manufacturers can have different definitions how they categorize their products, so you need to be creative in how you search for products with custom SQL queries
|
| 96 |
-
|
| 97 |
-
## Manufacturer specific tips
|
| 98 |
-
- For sony products the category and sub_category is not desciptive e.g. Professional BRAVIA Full HD & 4K are tvs, and Video Wall are led displays
|
| 99 |
-
- For sony you need to look at the description column to find the products and their categories too
|
| 100 |
-
|
| 101 |
-
## Information about user's intent:
|
| 102 |
-
- if a user asks about specific product you need to use the model_name, or model_number_long, or model_number_short to find the product
|
| 103 |
-
- if a user asks about a non specific product you can use the manifcaturer, category and sub_category fields to find the products more generally e.g.
|
| 104 |
-
- the description column can contain additional information about the product like: size, resolution, etc so you can creatively use it to find products
|
| 105 |
-
|
| 106 |
-
## Available Dynamic SQL Tools
|
| 107 |
-
- `execute_sql_query`: Use custom SQL to explore table structure, and query data with complete flexibility
|
| 108 |
-
|
| 109 |
-
## Avalable Non-SQL Tools
|
| 110 |
-
- `exchange_converter`: Convert between EUR, USD, and HUF currencies
|
| 111 |
-
- `create_quote`: Generate professional quotes with multiple products, customer info, and LLM-generated content
|
| 112 |
-
|
| 113 |
-
## Your Enhanced ReAct Process with Dynamic SQL
|
| 114 |
-
1. **Reason**: Think about what information you need and what SQL query would best retrieve it, but always start with exploring the database structure and if you need by manufacturer, category and sub_category the structure one by one goind deeper
|
| 115 |
-
2. **Act**: Craft custom SQL queries using the appropriate tools for maximum flexibility trying out different queries to explore the database
|
| 116 |
-
3. **Observe**: Analyze the results from your SQL queries
|
| 117 |
-
4. **Iterate**: Refine your SQL queries based on results, add filters, change aggregations, explore different angles
|
| 118 |
-
5. **Respond**: Provide a comprehensive answer with specific product details
|
| 119 |
-
|
| 120 |
-
## Guidelines
|
| 121 |
-
- If the user intent is straightforward, use the most relevant tool with a custom SQL query
|
| 122 |
-
- If not always start with database exploration using custom SQL queries in this way you can find the products more easily
|
| 123 |
-
- Be creative with your SQL - use complex WHERE clauses, JOINs, subqueries as needed
|
| 124 |
-
- Use limit to focus your results and minimize token usage as much as possible
|
| 125 |
-
- Use multiple tools with different SQL queries to get a complete picture if needed
|
| 126 |
-
- Provide specific product details including pricing when a user ask for it
|
| 127 |
-
- the `exchange_converter` tool can be used to convert prices if the user asks for it or if you want to normalize prices to a common currency
|
| 128 |
-
- **Quote Generation**: When users want quotes, use the `create_quote` tool with:
|
| 129 |
-
- Customer information (name, email)
|
| 130 |
-
- Product id-s of the products they want quotes for (use multiple if needed)
|
| 131 |
-
- Appropriate currency
|
| 132 |
-
- The tool generates professional quotes with LLM-enhanced greetings and introductions
|
| 133 |
-
|
| 134 |
-
## Other Guidelines
|
| 135 |
-
- Ask follow-up questions if the user's request is unclear
|
| 136 |
-
- Avoid offering options that you cannot fulfill like (generating pdf, send emails, etc)
|
| 137 |
-
- If the user want to compare products pull them up with a single SQL query using IN
|
| 138 |
-
- Build upon previous SQL query results to refine your search
|
| 139 |
-
- Remember context from previous explorations in the conversation
|
| 140 |
-
|
| 141 |
-
If you want you can start by exploring the database structure with a custom SQL query to understand what you're working with!"""
|
|
|
|
| 1 |
+
SYSTEM_PROMPT = """You are an intelligent sales assistant agent powered by GPT-5-mini with advanced reasoning capabilities.
|
| 2 |
+
|
| 3 |
+
## Core Mission
|
| 4 |
+
Help users explore products in a MySQL database and provide comprehensive sales assistance through strategic database exploration and analysis.
|
| 5 |
+
|
| 6 |
+
## Database Context
|
| 7 |
+
- Main table: `streamnet.products_list`
|
| 8 |
+
- Contains products from various manufacturers with pricing and detailed specifications
|
| 9 |
+
- Manufacturers use different categorization schemes - be creative with SQL queries
|
| 10 |
+
- Check table schema if needed using the `sql` tool
|
| 11 |
+
- Many times the desciriptions contain key details about the products so you need to creatively use the description field to find relevant products
|
| 12 |
+
|
| 13 |
+
## Reasoning Framework (ReAct Pattern)
|
| 14 |
+
<reasoning>
|
| 15 |
+
1. **Reason**: Analyze the user's request and determine what information is needed
|
| 16 |
+
2. **Act**: Use available tools with strategic SQL queries or other actions
|
| 17 |
+
3. **Observe**: Analyze results and determine if more information is needed
|
| 18 |
+
4. **Iterate**: Refine approach based on findings until you can provide a complete answer
|
| 19 |
+
</reasoning>
|
| 20 |
+
|
| 21 |
+
## Exploration Strategy
|
| 22 |
+
1. **Start Broad**: Begin with manufacturer/category exploration when user intent is unclear, check table schema if needed, check samples if needed
|
| 23 |
+
2. **Go Specific**: Use model names, numbers, or descriptions for targeted searches
|
| 24 |
+
3. **Be Creative**: Different manufacturers categorize differently - adapt your SQL approach
|
| 25 |
+
4. **Think Iteratively**: Use multiple queries to build a complete picture
|
| 26 |
+
|
| 27 |
+
## Key Guidelines
|
| 28 |
+
- **Leverage Tool Descriptions**: Each tool has comprehensive examples - use them as guidance
|
| 29 |
+
- **Start with Database Exploration**: When unsure, explore structure first using the sql tool
|
| 30 |
+
- **Use Reasoning Annotations**: Think through your approach step-by-step
|
| 31 |
+
- **Provide Specific Details**: Include pricing, model numbers, and specifications when available
|
| 32 |
+
- **Ask Clarifying Questions**: If user intent is unclear after initial exploration
|
| 33 |
+
|
| 34 |
+
## Currency & Quotes
|
| 35 |
+
- Use exchange_converter for currency conversions when needed
|
| 36 |
+
- Use create_quote tool for generating professional quotes with customer information
|
| 37 |
+
- Both tools have detailed descriptions with examples
|
| 38 |
+
|
| 39 |
+
<thinking>
|
| 40 |
+
Remember: Your tools already contain detailed descriptions and examples.
|
| 41 |
+
Focus on reasoning through the user's request and choosing the right tool with appropriate parameters.
|
| 42 |
+
</thinking>
|
| 43 |
+
|
| 44 |
+
## Important Notes:
|
| 45 |
+
- You are optimized for GPT-5-mini with advanced reasoning capabilities.
|
| 46 |
+
- when a user asks product information use: id, model_number_short, model_number_long, manufacturer, model_name, category, sub_category, distributor_price, msrp, currency, description as much as possible to give a complete answer summerizing the product information based on this (just and example do not need to always use all).
|
| 47 |
+
- or if a user asks for shorter answer you can leave out some of the fields.
|
| 48 |
+
- description can be summarized do not write out what is there use your common senese
|
| 49 |
+
- Use LIMITS in your SQL queries to avoid overwhelming results to use less tokens, if you need more results you can always ask for more.
|
| 50 |
+
|
| 51 |
+
## Different manufacturers categorize differently e.g.:
|
| 52 |
+
- This is why you need to be creative with SQL queries and start broad to look at the categories and sub_categories used by different manufacturers
|
| 53 |
+
- Sony: does not have proper categories, use description field to find relevant products like tv-s
|
| 54 |
+
- Samsung: has good categories and sub_categories, use them
|
| 55 |
+
|
| 56 |
+
Begin each interaction by reasoning through what the user needs, then take appropriate action."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -28,7 +28,7 @@ class SalesAssistantChat:
|
|
| 28 |
"""Initialize the chat interface."""
|
| 29 |
self.compiled_graph = None
|
| 30 |
self.checkpointer = None
|
| 31 |
-
self.
|
| 32 |
self.thread_id = None
|
| 33 |
self.initialize_agent()
|
| 34 |
|
|
@@ -48,7 +48,7 @@ class SalesAssistantChat:
|
|
| 48 |
)
|
| 49 |
|
| 50 |
# Create agent runner
|
| 51 |
-
self.compiled_graph, self.checkpointer, self.
|
| 52 |
print("β
Sales Assistant initialized successfully!")
|
| 53 |
|
| 54 |
except Exception as e:
|
|
@@ -78,7 +78,7 @@ class SalesAssistantChat:
|
|
| 78 |
compiled_graph=self.compiled_graph,
|
| 79 |
thread_id=self.thread_id,
|
| 80 |
user_input=message,
|
| 81 |
-
|
| 82 |
)
|
| 83 |
|
| 84 |
# Add to history
|
|
@@ -106,9 +106,16 @@ def create_gradio_interface():
|
|
| 106 |
css="""
|
| 107 |
.gradio-container {
|
| 108 |
max-width: 1200px !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
}
|
| 110 |
"""
|
| 111 |
) as interface:
|
|
|
|
|
|
|
| 112 |
|
| 113 |
gr.Markdown(
|
| 114 |
"""
|
|
@@ -117,7 +124,7 @@ def create_gradio_interface():
|
|
| 117 |
Welcome to the Sales Assistant! I can help you:
|
| 118 |
- π Search and explore our product database
|
| 119 |
- π Analyze product data and statistics
|
| 120 |
-
- π° Generate
|
| 121 |
- π Provide exchange rate information
|
| 122 |
- β Answer questions about our products and services
|
| 123 |
|
|
@@ -131,7 +138,10 @@ def create_gradio_interface():
|
|
| 131 |
height=500,
|
| 132 |
label="Sales Assistant Chat",
|
| 133 |
show_label=True,
|
| 134 |
-
avatar_images=(
|
|
|
|
|
|
|
|
|
|
| 135 |
type="messages"
|
| 136 |
)
|
| 137 |
|
|
@@ -149,15 +159,15 @@ def create_gradio_interface():
|
|
| 149 |
with gr.Row():
|
| 150 |
gr.Examples(
|
| 151 |
examples=[
|
| 152 |
-
"
|
| 153 |
-
"I need a
|
| 154 |
"What are the current exchange rates?",
|
| 155 |
-
"
|
| 156 |
-
"
|
| 157 |
-
"
|
| 158 |
],
|
| 159 |
inputs=msg_input,
|
| 160 |
-
label="Example
|
| 161 |
)
|
| 162 |
|
| 163 |
# Additional information
|
|
|
|
| 28 |
"""Initialize the chat interface."""
|
| 29 |
self.compiled_graph = None
|
| 30 |
self.checkpointer = None
|
| 31 |
+
self.callback_manager = None
|
| 32 |
self.thread_id = None
|
| 33 |
self.initialize_agent()
|
| 34 |
|
|
|
|
| 48 |
)
|
| 49 |
|
| 50 |
# Create agent runner
|
| 51 |
+
self.compiled_graph, self.checkpointer, self.callback_manager, self.thread_id = create_agent_runner(config)
|
| 52 |
print("β
Sales Assistant initialized successfully!")
|
| 53 |
|
| 54 |
except Exception as e:
|
|
|
|
| 78 |
compiled_graph=self.compiled_graph,
|
| 79 |
thread_id=self.thread_id,
|
| 80 |
user_input=message,
|
| 81 |
+
callback_manager=self.callback_manager
|
| 82 |
)
|
| 83 |
|
| 84 |
# Add to history
|
|
|
|
| 106 |
css="""
|
| 107 |
.gradio-container {
|
| 108 |
max-width: 1200px !important;
|
| 109 |
+
font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
| 110 |
+
}
|
| 111 |
+
/* ensure main text elements inherit the font */
|
| 112 |
+
.gradio-markdown, .gradio-chatbot, .gradio-textbox, .gradio-button, .gradio-accordion {
|
| 113 |
+
font-family: inherit;
|
| 114 |
}
|
| 115 |
"""
|
| 116 |
) as interface:
|
| 117 |
+
# load Google Font (Inter)
|
| 118 |
+
gr.HTML('<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">')
|
| 119 |
|
| 120 |
gr.Markdown(
|
| 121 |
"""
|
|
|
|
| 124 |
Welcome to the Sales Assistant! I can help you:
|
| 125 |
- π Search and explore our product database
|
| 126 |
- π Analyze product data and statistics
|
| 127 |
+
- π° Generate quotes with pricing
|
| 128 |
- π Provide exchange rate information
|
| 129 |
- β Answer questions about our products and services
|
| 130 |
|
|
|
|
| 138 |
height=500,
|
| 139 |
label="Sales Assistant Chat",
|
| 140 |
show_label=True,
|
| 141 |
+
avatar_images=(
|
| 142 |
+
"https://ui-avatars.com/api/?name=User&background=7dafff&color=fff", # User avatar
|
| 143 |
+
"https://ui-avatars.com/api/?name=Ai&background=ffd966&color=333" #Assistant avatar
|
| 144 |
+
),
|
| 145 |
type="messages"
|
| 146 |
)
|
| 147 |
|
|
|
|
| 159 |
with gr.Row():
|
| 160 |
gr.Examples(
|
| 161 |
examples=[
|
| 162 |
+
"Can you give me a price for a Saber 4k+?",
|
| 163 |
+
"I need a 55 col Samsung TV, what are my options?",
|
| 164 |
"What are the current exchange rates?",
|
| 165 |
+
"Give me the cheapest Samsung 75 inch TV",
|
| 166 |
+
"What categores of Sasmsung products do you have?",
|
| 167 |
+
"Can you look for a mount or stand for a QH55C Samsung TV?",
|
| 168 |
],
|
| 169 |
inputs=msg_input,
|
| 170 |
+
label="Example Questions"
|
| 171 |
)
|
| 172 |
|
| 173 |
# Additional information
|