krinya commited on
Commit
e252f82
Β·
1 Parent(s): c7abf8d

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 CHANGED
@@ -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
+ ```
src/sales_assistant/agent_main/tools_node.py CHANGED
@@ -1,54 +1,98 @@
1
  """
2
- Tools node that are using the agent_tools folder's tools
3
  """
4
- from typing import List
 
5
  from langchain_core.tools import BaseTool
6
  from langgraph.prebuilt import ToolNode
7
- from pydantic import BaseModel, Field
 
8
 
9
- # Import all the tools from agent_tools
 
 
 
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 GPT-5-mini optimized tools using built-in LangChain patterns.
22
 
23
  Args:
24
- config: Optional tool configuration
25
 
26
  Returns:
27
- List of validated tools for GPT-5-mini
28
  """
29
  if config is None:
30
  config = ToolConfig()
31
 
32
- # Core tools optimized for GPT-5-mini
33
  core_tools = [
34
  execute_sql_query,
35
  exchange_converter,
36
  create_quote
37
  ]
38
 
39
- # Validate tools have proper schemas (built-in validation)
40
- validated_tools = []
41
- for tool in core_tools:
42
- if hasattr(tool, 'args_schema') or hasattr(tool, 'name'):
43
- validated_tools.append(tool)
44
 
45
- return validated_tools[:config.max_tools]
 
46
 
47
- # Create the tool node using LangGraph's built-in ToolNode with error handling
48
- def create_tool_node() -> ToolNode:
49
- """Create a validated tool node with built-in error handling."""
50
- tools = get_all_tools()
51
  return ToolNode(tools)
52
 
53
- # Use the factory function for better control
54
- tool_node = create_tool_node()
 
 
 
 
 
 
 
 
 
 
 
 
 
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()
src/sales_assistant/agent_tools/agent_tools_utils.py CHANGED
@@ -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
- str: Formatted string of query results or error message.
21
  """
22
- try:
23
- # Log the SQL query being executed
24
- logger.info(f"Executing SQL query: {query}")
25
-
26
- df_original = read_sql(query)
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
- if return_type == "string":
35
- if df_original.empty:
36
- df_formated = "No data found."
37
- else:
38
- df_formated = df_original.to_string(index=False)
 
 
 
 
 
 
39
 
40
- return df_original, df_formated
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(results_orignal: pd.DataFrame) -> dict:
50
  """
51
  Create metadata about the results such as count of records and columns.
52
 
53
  Args:
54
- results_orignal (pd.DataFrame): Original DataFrame of query results.
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
- 'row count': rows
 
 
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
src/sales_assistant/agent_tools/create_quote.py CHANGED
@@ -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 Any, Dict, List, Optional, Union
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 "error" not in conversion_result:
69
- converted_price = conversion_result["converted_amount"]
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: Optional[str] = None,
165
- customer_company: Optional[str] = None,
166
  target_currency: str = "EUR",
167
- notes: Optional[str] = None
168
- ) -> Dict[str, Any]:
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 can 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. Example: [1, 1, 1, 3, 4, 5] means 3x product ID 1, 1x product ID 3, 1x product ID 4, 1x product ID 5
 
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
- Dict containing quote details and file path.
196
  """
197
  try:
198
- # Validate inputs
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 {"error": "No products found for the provided IDs"}
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
- # Prepare response
300
- response = {
301
- "success": True,
302
- "quote_id": quote.quote_id,
303
- "customer_name": customer_name,
304
- "grand_total": quote.grand_total,
305
- "currency": target_currency,
306
- "item_count": len(quote_items),
307
- "unique_products": len(unique_product_ids),
308
- "total_items": sum(product_quantities.values()),
309
- "file_path": file_path,
310
- "file_format": "markdown",
311
- "created_date": quote.created_date.isoformat(),
312
- "valid_until": quote.valid_until.isoformat(),
313
- "quote_summary": {
 
 
 
 
 
 
 
 
 
 
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 {"error": f"Quote creation failed: {str(e)}"}
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():
src/sales_assistant/agent_tools/execute_sql_query.py CHANGED
@@ -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
- ) -> Dict[str, Any]:
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
- Dict[str, Any]: Results from the SQL query execution with metadata.
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
- return result_dict
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  except Exception as e:
75
- return {
76
- "error": f"SQL query execution failed: {str(e)}",
77
- "query_executed": sql_query,
78
- "query_type": query_type
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
+ )
src/sales_assistant/agent_tools/get_exchange_rates.py CHANGED
@@ -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) -> Dict[str, Any]:
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 {"error": "from_currency and to_currency are required strings"}
33
 
34
  if fc not in _EXCHANGE_RATES or tc not in _EXCHANGE_RATES:
35
- return {"error": f"Unsupported currency. Supported: {', '.join(sorted(_EXCHANGE_RATES.keys()))}"}
36
 
37
  try:
38
  amt = float(amount)
39
  except Exception:
40
- return {"error": "Amount must be a number"}
41
 
42
  if amt < 0:
43
- return {"error": "Amount must be non-negative"}
44
 
45
  if fc == tc:
46
- return {"converted_amount": round(amt, precision), "exchange_rate": 1.0, "from": fc, "to": tc}
 
 
 
 
 
 
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
- "original_amount": round(amt, precision),
54
- "from": fc,
55
- "converted_amount": round(converted, precision),
56
- "to": tc,
57
- "exchange_rate": round(rate, max(4, precision))
58
- }
59
 
60
 
61
- @tool
62
- def exchange_converter(from_currency: str = "EUR", to_currency: str = "HUF", amount: float = 1.0, precision: int = 2) -> Dict[str, Any]:
63
  """
64
- Currency converter tool. That can convert between EUR, USD, and HUF, allowing specification of precision.
 
 
 
 
 
 
 
 
 
 
 
 
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)
src/sales_assistant/agent_tools/tool_schemas.py ADDED
@@ -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")
src/sales_assistant/db/utils/db_utils.py CHANGED
@@ -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
- # return an empty DataFrame on error
34
- return pd.DataFrame()
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
 
src/sales_assistant/prompts/__init__.py CHANGED
@@ -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"]
src/sales_assistant/prompts/system_prompt.py CHANGED
@@ -1,141 +1,56 @@
1
-
2
- 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.
3
-
4
- ## Your Mission
5
- 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.
6
- - tips: start wth the manifcaturer, get the categories and subcategories and then look into the descipton column to find products
7
- 2. **Understand the Data**: Use multiple tools with creative SQL queries to understand what products are available
8
- 3. **Query Strategically**: Build sophisticated SQL queries based on your exploration findings run multiple queries to get a complete picture
9
- 4. **Answer Comprehensively**: Provide helpful, detailed answers with specific product information
10
-
11
- ## Database Information
12
- - Main table: `streamnet.products_list`
13
- - Key columns: id, manufacturer, category, sub_category, model_name, model_number_long, model_number_short, description, currency, distributor_pricing, msrp
14
- - Contains various products with pricing and detailed information from various manufacturers
15
- - 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
16
-
17
- ## Information about user's intent:
18
- - 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
19
- - 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.
20
- - the description column can contain additional information about the product like: size, resolution, etc so you can creatively use it to find products
21
-
22
- ## Available Dynamic SQL Tools
23
- All tools now accept custom SQL queries as input, giving you complete flexibility:
24
-
25
- - `describe_table`: Use custom SQL to explore table structure (DESCRIBE, SHOW COLUMNS, information_schema queries)
26
- - `get_sample_data`: Use dynamic SQL with WHERE clauses, JOINs, filtering to sample specific data subsets
27
- - `get_distinct_values`: Use GROUP BY, aggregations, complex filtering to explore data patterns and distributions
28
- - `search_products_by_criteria`: Use sophisticated WHERE clauses, LIKE patterns, ranges, sorting for product searches
29
- - `get_table_statistics`: Use aggregation functions (COUNT, AVG, MIN, MAX, STDDEV) for analytical insights
30
- - `execute_advanced_query`: Run any complex SQL including subqueries, CTEs, window functions, advanced analytics
31
-
32
- ## Avalable Non-SQL Tools
33
- - `exchange_converter`: Convert between EUR, USD, and HUF currencies
34
- - `create_quote`: Generate professional quotes with multiple products, customer info, and LLM-generated content
35
-
36
- ## Your Enhanced ReAct Process with Dynamic SQL
37
- 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
38
- 2. **Act**: Craft custom SQL queries using the appropriate tools for maximum flexibility trying out different queries to explore the database
39
- 3. **Observe**: Analyze the results from your SQL queries
40
- 4. **Iterate**: Refine your SQL queries based on results, add filters, change aggregations, explore different angles
41
- 5. **Respond**: Provide a comprehensive answer with specific product details
42
-
43
- ## Advanced SQL examples and tips
44
- - Use WHERE clauses creatively to filter data: `WHERE manufacturer = 'Samsung' AND category = 'Electronics AND sub_category'`
45
- - Use LIKE for pattern matching: `WHERE description LIKE '%50%' OR model_name LIKE '%Pro%'`
46
- - Use aggregations for insights: `SELECT manufacturer, COUNT(*), AVG(msrp) FROM ... GROUP BY manufacturer`
47
- - Use ORDER BY for meaningful sorting: `ORDER BY msrp DESC, manufacturer ASC`
48
- - Use LIMIT to control result size: `LIMIT 10` or `LIMIT 20` you an go even higher to get more results
49
- - Combine multiple conditions: `WHERE msrp BETWEEN 100 AND 500 AND manufacturer IN ('Apple', 'Samsung')`
50
- - Use subqueries for complex logic: `WHERE msrp > (SELECT AVG(msrp) FROM streamnet.products_list)`
51
-
52
- ## SQL Query Examples for Inspiration
53
- - `SELECT * FROM streamnet.products_list WHERE manufacturer = 'Samsung' AND model_name like '%Entry%'`
54
- - `SELECT DISTINCT category FROM streamnet.products_list WHERE manufacturer like 'angekis'`
55
- - `SELECT * FROM streamnet.products_list WHERE category = 'Camera'
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."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/sales_assistant/ui_dashboard/gradio_app.py CHANGED
@@ -28,7 +28,7 @@ class SalesAssistantChat:
28
  """Initialize the chat interface."""
29
  self.compiled_graph = None
30
  self.checkpointer = None
31
- self.langsmith_client = None
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.langsmith_client, self.thread_id = create_agent_runner(config)
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
- langsmith_client=self.langsmith_client
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 detailed quotes with pricing
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
- "Show me information about our products",
153
- "I need a quote for industrial equipment",
154
  "What are the current exchange rates?",
155
- "Find products with price under $1000",
156
- "Generate a quote for customer 'Tech Solutions Inc'",
157
- "Show me product statistics"
158
  ],
159
  inputs=msg_input,
160
- label="Example Queries"
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