krinya commited on
Commit
c7abf8d
ยท
1 Parent(s): 24d4056

Enhance agent configuration and tool management with validation and improved error handling

Browse files
.github/copilot-instructions.md CHANGED
@@ -9,4 +9,6 @@ https://langchain-ai.github.io/langgraph/concepts/low_level/
9
  https://langchain-ai.github.io/langgraph/how-tos/graph-api/
10
 
11
  There is a mysql database (called: streamnet_prodcut_try) with the following tables and columns.
12
- You will use this in the chatbot retrieve data.
 
 
 
9
  https://langchain-ai.github.io/langgraph/how-tos/graph-api/
10
 
11
  There is a mysql database (called: streamnet_prodcut_try) with the following tables and columns.
12
+ You will use this in the chatbot retrieve data.
13
+
14
+ Model to use: gpt-5-mini is a new model and a vaid models to use.
src/sales_assistant/agent_main/agent_graph.py CHANGED
@@ -1,49 +1,66 @@
1
  import os
2
- import sys
3
- import uuid
4
- from typing import List, Dict, Any, Literal
5
- from langchain_core.tools import tool
6
- from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
7
  from langgraph.graph import StateGraph, MessagesState, START, END
8
- from langgraph.prebuilt import ToolNode, tools_condition
9
- from langchain.chat_models import init_chat_model
10
- from langsmith import Client
11
  from dotenv import load_dotenv
12
  from .agent_node import agent_node
13
  from .tools_node import tool_node
14
 
15
- def create_agent_graph(checkpointer=None):
 
 
 
 
 
 
 
 
 
 
 
 
16
  """
17
- Create the ReAct database exploration agent using LangGraph's built-in components.
18
 
19
  Args:
20
  checkpointer: Optional checkpointer for state persistence
 
 
 
 
21
  """
22
- # Initialize StateGraph with built-in MessagesState
 
 
 
23
  workflow = StateGraph(MessagesState)
24
 
25
- # Add nodes using built-in components
26
  workflow.add_node("agent", agent_node)
27
- workflow.add_node("tools", tool_node)
28
 
29
- # Add edges using built-in routing
30
  workflow.add_edge(START, "agent")
31
 
32
- # Use built-in tools_condition for conditional routing
33
  workflow.add_conditional_edges(
34
  "agent",
35
- tools_condition, # Built-in condition that checks for tool calls
36
  {
37
  "tools": "tools",
38
- END: END,
39
  }
40
  )
41
 
42
- # Tools always return to agent for continued reasoning
43
  workflow.add_edge("tools", "agent")
44
 
45
- # Compile the graph with optional checkpointer
46
- if checkpointer:
47
- return workflow.compile(checkpointer=checkpointer)
48
- else:
49
- return workflow.compile()
 
 
1
  import os
2
+ from typing import Literal, Optional
3
+ from langchain_core.messages import BaseMessage
 
 
 
4
  from langgraph.graph import StateGraph, MessagesState, START, END
5
+ from langgraph.prebuilt import tools_condition
6
+ from langgraph.checkpoint.base import BaseCheckpointSaver
7
+ from pydantic import BaseModel, Field
8
  from dotenv import load_dotenv
9
  from .agent_node import agent_node
10
  from .tools_node import tool_node
11
 
12
+ load_dotenv()
13
+
14
+ class AgentConfig(BaseModel):
15
+ """Configuration for the agent graph with validation."""
16
+ model_name: str = Field(default_factory=lambda: os.getenv("MODEL_NAME", "gpt-5-mini"), description="LLM model to use")
17
+ model_provider: str = Field(default_factory=lambda: os.getenv("MODEL_PROVIDER", "openai"), description="Model provider")
18
+ enable_checkpointing: bool = Field(default=True, description="Enable state persistence")
19
+ max_iterations: int = Field(default=10, description="Maximum agent iterations")
20
+
21
+ def create_agent_graph(
22
+ checkpointer: Optional[BaseCheckpointSaver] = None,
23
+ config: Optional[AgentConfig] = None
24
+ ):
25
  """
26
+ Create the ReAct agent using LangGraph's built-in components with typed state.
27
 
28
  Args:
29
  checkpointer: Optional checkpointer for state persistence
30
+ config: Optional agent configuration
31
+
32
+ Returns:
33
+ Compiled LangGraph with built-in state management
34
  """
35
+ if config is None:
36
+ config = AgentConfig()
37
+
38
+ # Initialize StateGraph with built-in MessagesState (typed)
39
  workflow = StateGraph(MessagesState)
40
 
41
+ # Add nodes using built-in components with validation
42
  workflow.add_node("agent", agent_node)
43
+ workflow.add_node("tools", tool_node)
44
 
45
+ # Add edges using built-in routing patterns
46
  workflow.add_edge(START, "agent")
47
 
48
+ # Use built-in tools_condition with proper typing
49
  workflow.add_conditional_edges(
50
  "agent",
51
+ tools_condition, # Built-in condition with proper message type checking
52
  {
53
  "tools": "tools",
54
+ END: END,
55
  }
56
  )
57
 
58
+ # Tools return to agent for continued reasoning (ReAct pattern)
59
  workflow.add_edge("tools", "agent")
60
 
61
+ # Compile with built-in error handling and validation
62
+ compile_config = {}
63
+ if checkpointer and config.enable_checkpointing:
64
+ compile_config["checkpointer"] = checkpointer
65
+
66
+ return workflow.compile(**compile_config)
src/sales_assistant/agent_main/agent_node.py CHANGED
@@ -6,7 +6,7 @@ import os
6
  from typing import Dict, Any
7
  from langchain_core.messages import SystemMessage
8
  from langgraph.graph import MessagesState
9
- from langchain_openai import ChatOpenAI
10
  from dotenv import load_dotenv
11
  from ..prompts.system_prompt import SYSTEM_PROMPT
12
  from .tools_node import get_all_tools
@@ -16,7 +16,7 @@ load_dotenv()
16
 
17
  def agent_node(state: MessagesState) -> Dict[str, Any]:
18
  """
19
- Agent node that processes messages and generates responses using the LLM.
20
 
21
  Args:
22
  state: MessagesState containing the conversation history
@@ -24,47 +24,30 @@ def agent_node(state: MessagesState) -> Dict[str, Any]:
24
  Returns:
25
  Dict containing the updated messages
26
  """
27
- # Get model configuration from environment
28
- model_provider = os.getenv("MODEL_PROVIDER", "openai").lower()
29
  model_name = os.getenv("MODEL_NAME", "gpt-5-mini")
 
30
 
31
- # Initialize the chat model based on provider
32
- if model_provider == "openai":
33
- # Check if model supports temperature
34
- if "gpt-5" in model_name:
35
- # GPT-5 models don't have temperature parameter
36
- model = ChatOpenAI(
37
- model=model_name,
38
- api_key=os.getenv("OPENAI_API_KEY"),
39
- )
40
- else:
41
- # Other models with temperature support
42
- temperature = float(os.getenv("MODEL_TEMPERATURE", "0.1"))
43
- model = ChatOpenAI(
44
- model=model_name,
45
- api_key=os.getenv("OPENAI_API_KEY"),
46
- temperature=temperature,
47
- )
48
- else:
49
- # Fallback to OpenAI if other providers not implemented
50
- model = ChatOpenAI(
51
- model=model_name,
52
- api_key=os.getenv("OPENAI_API_KEY"),
53
- )
54
 
 
55
  tools = get_all_tools()
56
  model_with_tools = model.bind_tools(tools)
57
 
58
- # Get the current messages
59
  messages = state["messages"]
60
 
61
- # Add system prompt if it's the first message or not present
62
  if not messages or not isinstance(messages[0], SystemMessage):
63
  system_message = SystemMessage(content=SYSTEM_PROMPT)
64
  messages = [system_message] + messages
65
 
66
- # Generate response
67
  response = model_with_tools.invoke(messages)
68
 
69
- # Return the updated state
70
  return {"messages": [response]}
 
6
  from typing import Dict, Any
7
  from langchain_core.messages import SystemMessage
8
  from langgraph.graph import MessagesState
9
+ from langchain.chat_models import init_chat_model
10
  from dotenv import load_dotenv
11
  from ..prompts.system_prompt import SYSTEM_PROMPT
12
  from .tools_node import get_all_tools
 
16
 
17
  def agent_node(state: MessagesState) -> Dict[str, Any]:
18
  """
19
+ Agent node using LangChain's built-in init_chat_model with env configuration.
20
 
21
  Args:
22
  state: MessagesState containing the conversation history
 
24
  Returns:
25
  Dict containing the updated messages
26
  """
27
+ # Get model configuration from environment variables
 
28
  model_name = os.getenv("MODEL_NAME", "gpt-5-mini")
29
+ model_provider = os.getenv("MODEL_PROVIDER", "openai")
30
 
31
+ # Use LangChain's built-in init_chat_model with env config
32
+ model = init_chat_model(
33
+ model=model_name,
34
+ model_provider=model_provider,
35
+ api_key=os.getenv("OPENAI_API_KEY")
36
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ # Get tools and bind them using built-in method
39
  tools = get_all_tools()
40
  model_with_tools = model.bind_tools(tools)
41
 
42
+ # Get current messages
43
  messages = state["messages"]
44
 
45
+ # Add system prompt if not present using built-in message handling
46
  if not messages or not isinstance(messages[0], SystemMessage):
47
  system_message = SystemMessage(content=SYSTEM_PROMPT)
48
  messages = [system_message] + messages
49
 
50
+ # Generate response using built-in invoke
51
  response = model_with_tools.invoke(messages)
52
 
 
53
  return {"messages": [response]}
src/sales_assistant/agent_main/agent_runner.py CHANGED
@@ -4,147 +4,168 @@ from typing import Dict, Any, Optional, List
4
  from langchain_core.messages import HumanMessage, AIMessage
5
  from langgraph.graph import MessagesState
6
  from langgraph.checkpoint.memory import MemorySaver
7
- from langsmith import Client
 
8
  from dotenv import load_dotenv
9
- from pydantic import BaseModel, Field
10
- from .agent_graph import create_agent_graph
11
 
12
- # Load environment variables
13
  load_dotenv()
14
 
15
-
16
  class ConversationConfig(BaseModel):
17
- """Configuration for the conversation."""
18
  session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
19
  user_id: str = Field(default="default_user")
20
- langsmith_project: str = Field(default_factory=lambda: os.getenv("LANGSMITH_PROJECT", "sales-assistant"))
21
  enable_langsmith: bool = Field(default=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
-
24
- def setup_langsmith_tracing(config: ConversationConfig) -> Optional[Client]:
25
  """
26
- Setup LangSmith tracing for the conversation.
27
 
28
  Args:
29
  config: Configuration for the conversation
30
 
31
  Returns:
32
- LangSmith client if enabled, None otherwise
33
  """
34
- if not config.enable_langsmith:
35
  return None
36
 
37
  try:
38
- # Set environment variables for LangSmith using the correct env var names
39
- if os.getenv("LANGSMITH_API_KEY"):
40
- os.environ["LANGCHAIN_TRACING_V2"] = os.getenv("LANGSMITH_TRACING_V2", "true")
41
- os.environ["LANGCHAIN_PROJECT"] = config.langsmith_project
42
- os.environ["LANGCHAIN_ENDPOINT"] = os.getenv("LANGSMITH_ENDPOINT", "https://api.smith.langchain.com")
43
- os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGSMITH_API_KEY")
44
-
45
- client = Client()
46
- return client
 
 
 
47
  except Exception as e:
48
  print(f"Warning: Could not setup LangSmith tracing: {e}")
49
-
50
- return None
51
-
52
 
53
  def create_agent_runner(config: ConversationConfig = None) -> tuple:
54
  """
55
- Create an agent runner with the compiled graph and LangGraph's built-in memory.
56
 
57
  Args:
58
- config: Optional configuration for the conversation
59
 
60
  Returns:
61
- Tuple of (compiled_graph, checkpointer, langsmith_client, thread_id)
62
  """
63
  if config is None:
64
  config = ConversationConfig()
65
 
66
- # Setup LangSmith tracing
67
- langsmith_client = setup_langsmith_tracing(config)
68
 
69
- # Create LangGraph's built-in memory checkpointer
70
  checkpointer = MemorySaver()
71
 
72
- # Create the agent graph with checkpointing
73
- compiled_graph = create_agent_graph(checkpointer)
 
 
 
 
 
 
74
 
75
- # Use session_id as thread_id for LangGraph's memory
76
  thread_id = config.session_id
77
 
78
- return compiled_graph, checkpointer, langsmith_client, thread_id
79
-
80
 
81
  def run_conversation_turn(
82
  compiled_graph,
83
  thread_id: str,
84
  user_input: str,
85
- langsmith_client: Optional[Client] = None
86
  ) -> str:
87
  """
88
- Run a single conversation turn with the agent using LangGraph's built-in state management.
89
 
90
  Args:
91
- compiled_graph: The compiled LangGraph agent with checkpointer
92
- thread_id: Thread ID for conversation persistence
93
  user_input: User's input message
94
- langsmith_client: Optional LangSmith client for tracing
95
 
96
  Returns:
97
  Agent's response as a string
98
  """
99
  try:
100
- # Create user message
101
  user_message = HumanMessage(content=user_input)
102
 
103
- # Create the config for this conversation thread
104
- config = {"configurable": {"thread_id": thread_id}}
 
 
 
105
 
106
- # Invoke the graph with the user message
107
- # LangGraph will automatically handle state persistence
108
  result = compiled_graph.invoke(
109
  {"messages": [user_message]},
110
  config=config
111
  )
112
 
113
- # Get the assistant's response
114
- assistant_message = result["messages"][-1]
115
- assistant_response = assistant_message.content
 
116
 
117
- return assistant_response
118
 
119
  except Exception as e:
120
  error_message = f"Error processing message: {str(e)}"
121
  print(error_message)
122
  return error_message
123
 
124
-
125
  def run_interactive_loop(config: ConversationConfig = None) -> None:
126
  """
127
- Run an interactive conversation loop with the agent using LangGraph's built-in state management.
128
 
129
  Args:
130
- config: Optional configuration for the conversation
131
  """
132
  if config is None:
133
  config = ConversationConfig()
134
 
135
- # Create agent runner with LangGraph's built-in memory
136
- compiled_graph, checkpointer, langsmith_client, thread_id = create_agent_runner(config)
137
 
138
- print("๐Ÿค– Sales Assistant initialized!")
139
- print(f"Using the following llm model: {os.getenv('MODEL_NAME', 'not set in .env')}")
140
  print("๐Ÿ’ฌ Type your questions about products or 'quit' to exit")
141
  print(f"๐Ÿ“Š Session ID: {config.session_id}")
142
  print(f"๐Ÿงต Thread ID: {thread_id}")
143
- if langsmith_client:
144
  print(f"๐Ÿ“ˆ LangSmith tracking enabled - Project: {config.langsmith_project}")
145
  print("-" * 50)
146
 
147
- while True:
 
148
  try:
149
  user_input = input("\n๐Ÿ‘ค You: ").strip()
150
 
@@ -160,10 +181,11 @@ def run_interactive_loop(config: ConversationConfig = None) -> None:
160
  compiled_graph,
161
  thread_id,
162
  user_input,
163
- langsmith_client
164
  )
165
 
166
  print(f"\n๐Ÿค– Assistant: {response}")
 
167
 
168
  except KeyboardInterrupt:
169
  print("\n๐Ÿ‘‹ Goodbye!")
@@ -171,39 +193,37 @@ def run_interactive_loop(config: ConversationConfig = None) -> None:
171
  except Exception as e:
172
  print(f"\nโŒ Error: {str(e)}")
173
 
174
-
175
  def get_conversation_history(compiled_graph, thread_id: str) -> List[Dict[str, Any]]:
176
  """
177
- Get the conversation history from LangGraph's checkpointer.
178
 
179
  Args:
180
  compiled_graph: The compiled graph with checkpointer
181
  thread_id: Thread ID for conversation
182
 
183
  Returns:
184
- List of messages in the conversation
185
  """
186
  try:
187
  config = {"configurable": {"thread_id": thread_id}}
188
- # Get the current state from LangGraph's checkpointer
189
  state = compiled_graph.get_state(config)
190
  messages = state.values.get("messages", [])
191
 
192
- # Convert messages to dict format
193
  history = []
194
  for msg in messages:
195
- if hasattr(msg, "type"):
196
  history.append({
197
  "type": msg.type,
198
  "content": msg.content,
199
- "timestamp": getattr(msg, "id", str(uuid.uuid4()))
200
  })
201
  return history
202
  except Exception as e:
203
  print(f"Error getting conversation history: {e}")
204
  return []
205
 
206
-
207
  def clear_conversation_history(compiled_graph, thread_id: str) -> None:
208
  """
209
  Clear the conversation history in LangGraph's checkpointer.
@@ -258,5 +278,6 @@ def demo_agent_runner():
258
 
259
 
260
  if __name__ == "__main__":
261
- # Run interactive loop
262
- run_interactive_loop()
 
 
4
  from langchain_core.messages import HumanMessage, AIMessage
5
  from langgraph.graph import MessagesState
6
  from langgraph.checkpoint.memory import MemorySaver
7
+ from langchain_core.tracers.langchain import LangChainTracer
8
+ from langchain_core.callbacks import CallbackManager
9
  from dotenv import load_dotenv
10
+ from pydantic import BaseModel, Field, validator
11
+ from .agent_graph import create_agent_graph, AgentConfig
12
 
 
13
  load_dotenv()
14
 
 
15
  class ConversationConfig(BaseModel):
16
+ """Enhanced configuration with validation for GPT-5-mini conversations."""
17
  session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
18
  user_id: str = Field(default="default_user")
19
+ langsmith_project: str = Field(default_factory=lambda: os.getenv("LANGSMITH_PROJECT", "sales-assistant-gpt5"))
20
  enable_langsmith: bool = Field(default=True)
21
+ model_name: str = Field(default_factory=lambda: os.getenv("MODEL_NAME", "gpt-5-mini"))
22
+ model_provider: str = Field(default_factory=lambda: os.getenv("MODEL_PROVIDER", "openai"))
23
+ max_turns: int = Field(default=50, description="Maximum conversation turns")
24
+
25
+ @validator('model_name')
26
+ def validate_model(cls, v):
27
+ if v != "gpt-5-mini":
28
+ raise ValueError("Only gpt-5-mini is supported")
29
+ return v
30
+
31
+ @validator('model_provider')
32
+ def validate_provider(cls, v):
33
+ if v != "openai":
34
+ raise ValueError("Only openai provider is supported")
35
+ return v
36
 
37
+ def setup_langsmith_tracing(config: ConversationConfig) -> Optional[CallbackManager]:
 
38
  """
39
+ Setup LangSmith tracing using built-in LangChain tracers.
40
 
41
  Args:
42
  config: Configuration for the conversation
43
 
44
  Returns:
45
+ CallbackManager with LangChain tracer if enabled
46
  """
47
+ if not config.enable_langsmith or not os.getenv("LANGSMITH_API_KEY"):
48
  return None
49
 
50
  try:
51
+ # Use built-in environment variable setup
52
+ os.environ["LANGCHAIN_TRACING_V2"] = "true"
53
+ os.environ["LANGCHAIN_PROJECT"] = config.langsmith_project
54
+ os.environ["LANGCHAIN_ENDPOINT"] = os.getenv("LANGSMITH_ENDPOINT", "https://api.smith.langchain.com")
55
+ os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGSMITH_API_KEY")
56
+
57
+ # Use built-in LangChain tracer
58
+ tracer = LangChainTracer(project_name=config.langsmith_project)
59
+ callback_manager = CallbackManager([tracer])
60
+
61
+ return callback_manager
62
+
63
  except Exception as e:
64
  print(f"Warning: Could not setup LangSmith tracing: {e}")
65
+ return None
 
 
66
 
67
  def create_agent_runner(config: ConversationConfig = None) -> tuple:
68
  """
69
+ Create agent runner with built-in LangGraph memory and state management.
70
 
71
  Args:
72
+ config: Optional conversation configuration
73
 
74
  Returns:
75
+ Tuple of (compiled_graph, checkpointer, callback_manager, thread_id)
76
  """
77
  if config is None:
78
  config = ConversationConfig()
79
 
80
+ # Setup built-in LangSmith tracing
81
+ callback_manager = setup_langsmith_tracing(config)
82
 
83
+ # Use built-in MemorySaver with validation
84
  checkpointer = MemorySaver()
85
 
86
+ # Create agent config
87
+ agent_config = AgentConfig(
88
+ model_name=config.model_name,
89
+ enable_checkpointing=True
90
+ )
91
+
92
+ # Create the agent graph with built-in components
93
+ compiled_graph = create_agent_graph(checkpointer, agent_config)
94
 
 
95
  thread_id = config.session_id
96
 
97
+ return compiled_graph, checkpointer, callback_manager, thread_id
 
98
 
99
  def run_conversation_turn(
100
  compiled_graph,
101
  thread_id: str,
102
  user_input: str,
103
+ callback_manager: Optional[CallbackManager] = None
104
  ) -> str:
105
  """
106
+ Run conversation turn with built-in state management and error handling.
107
 
108
  Args:
109
+ compiled_graph: The compiled LangGraph with built-in state
110
+ thread_id: Thread ID for conversation persistence
111
  user_input: User's input message
112
+ callback_manager: Optional callback manager for tracing
113
 
114
  Returns:
115
  Agent's response as a string
116
  """
117
  try:
118
+ # Create user message with built-in message types
119
  user_message = HumanMessage(content=user_input)
120
 
121
+ # Use built-in thread configuration
122
+ config = {
123
+ "configurable": {"thread_id": thread_id},
124
+ "callbacks": callback_manager.handlers if callback_manager else None
125
+ }
126
 
127
+ # Invoke with built-in state management and error handling
 
128
  result = compiled_graph.invoke(
129
  {"messages": [user_message]},
130
  config=config
131
  )
132
 
133
+ # Extract response using built-in message handling
134
+ if result and "messages" in result and result["messages"]:
135
+ assistant_message = result["messages"][-1]
136
+ return assistant_message.content if hasattr(assistant_message, 'content') else str(assistant_message)
137
 
138
+ return "I apologize, but I couldn't generate a response. Please try again."
139
 
140
  except Exception as e:
141
  error_message = f"Error processing message: {str(e)}"
142
  print(error_message)
143
  return error_message
144
 
 
145
  def run_interactive_loop(config: ConversationConfig = None) -> None:
146
  """
147
+ Run interactive loop with built-in state persistence and validation.
148
 
149
  Args:
150
+ config: Optional conversation configuration
151
  """
152
  if config is None:
153
  config = ConversationConfig()
154
 
155
+ # Create agent runner with built-in components
156
+ compiled_graph, checkpointer, callback_manager, thread_id = create_agent_runner(config)
157
 
158
+ print("\n๐Ÿค– Sales Assistant initialized!")
159
+ print(f"Using model: {config.model_name} (Provider: {config.model_provider})")
160
  print("๐Ÿ’ฌ Type your questions about products or 'quit' to exit")
161
  print(f"๐Ÿ“Š Session ID: {config.session_id}")
162
  print(f"๐Ÿงต Thread ID: {thread_id}")
163
+ if callback_manager:
164
  print(f"๐Ÿ“ˆ LangSmith tracking enabled - Project: {config.langsmith_project}")
165
  print("-" * 50)
166
 
167
+ turn_count = 0
168
+ while turn_count < config.max_turns:
169
  try:
170
  user_input = input("\n๐Ÿ‘ค You: ").strip()
171
 
 
181
  compiled_graph,
182
  thread_id,
183
  user_input,
184
+ callback_manager
185
  )
186
 
187
  print(f"\n๐Ÿค– Assistant: {response}")
188
+ turn_count += 1
189
 
190
  except KeyboardInterrupt:
191
  print("\n๐Ÿ‘‹ Goodbye!")
 
193
  except Exception as e:
194
  print(f"\nโŒ Error: {str(e)}")
195
 
 
196
  def get_conversation_history(compiled_graph, thread_id: str) -> List[Dict[str, Any]]:
197
  """
198
+ Get conversation history using built-in state management.
199
 
200
  Args:
201
  compiled_graph: The compiled graph with checkpointer
202
  thread_id: Thread ID for conversation
203
 
204
  Returns:
205
+ List of messages with built-in validation
206
  """
207
  try:
208
  config = {"configurable": {"thread_id": thread_id}}
209
+ # Use built-in state retrieval
210
  state = compiled_graph.get_state(config)
211
  messages = state.values.get("messages", [])
212
 
213
+ # Convert to structured format with built-in message handling
214
  history = []
215
  for msg in messages:
216
+ if hasattr(msg, "type") and hasattr(msg, "content"):
217
  history.append({
218
  "type": msg.type,
219
  "content": msg.content,
220
+ "id": getattr(msg, "id", str(uuid.uuid4()))
221
  })
222
  return history
223
  except Exception as e:
224
  print(f"Error getting conversation history: {e}")
225
  return []
226
 
 
227
  def clear_conversation_history(compiled_graph, thread_id: str) -> None:
228
  """
229
  Clear the conversation history in LangGraph's checkpointer.
 
278
 
279
 
280
  if __name__ == "__main__":
281
+ # Run with configuration from environment variables
282
+ config = ConversationConfig()
283
+ run_interactive_loop(config)
src/sales_assistant/agent_main/tools_node.py CHANGED
@@ -4,40 +4,51 @@ Tools node that are using the agent_tools folder's tools
4
  from typing import List
5
  from langchain_core.tools import BaseTool
6
  from langgraph.prebuilt import ToolNode
 
7
 
8
  # Import all the tools from agent_tools
9
- from ..agent_tools.describe_table import describe_table
10
- from ..agent_tools.execute_advanced_query import execute_advanced_query
11
- from ..agent_tools.get_distinct_values import get_distinct_values
12
- from ..agent_tools.get_product_by_criteria import search_products_by_criteria
13
- from ..agent_tools.get_samples_data import get_sample_data
14
- from ..agent_tools.get_table_statistics import get_table_statistics
15
- from ..agent_tools.get_exchange_rates import exchange_converter
16
  from ..agent_tools.execute_sql_query import execute_sql_query
 
17
  from ..agent_tools.create_quote import create_quote
18
 
 
 
 
 
19
 
20
- def get_all_tools() -> List[BaseTool]:
21
  """
22
- Get all available tools for the agent.
23
 
 
 
 
24
  Returns:
25
- List of all available tools
26
  """
27
- simple_tool_list = [execute_sql_query, exchange_converter, create_quote]
28
- advanced_tool_list = [
29
- describe_table,
30
- execute_advanced_query,
31
- get_distinct_values,
32
- search_products_by_criteria,
33
- get_sample_data,
34
- get_table_statistics,
35
- exchange_converter,
36
  create_quote
37
  ]
 
 
 
 
 
 
 
 
38
 
39
- return simple_tool_list
40
-
 
 
 
41
 
42
- # Create the tool node using LangGraph's built-in ToolNode
43
- tool_node = ToolNode(get_all_tools())
 
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()
src/sales_assistant/main.py CHANGED
@@ -16,10 +16,10 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
16
  from sales_assistant.agent_main.agent_runner import (
17
  ConversationConfig,
18
  run_interactive_loop,
19
- demo_agent_runner,
20
  create_agent_runner,
21
  run_conversation_turn
22
  )
 
23
 
24
  # Load environment variables
25
  load_dotenv()
@@ -34,18 +34,15 @@ def check_environment_setup() -> bool:
34
  """
35
  required_vars = [
36
  "OPENAI_API_KEY",
37
- "MODEL_PROVIDER",
38
  "MODEL_NAME",
39
  "MYSQL_HOST",
40
- "MYSQL_USER",
41
- "MYSQL_PASSWORD",
42
  "MYSQL_DB"
43
  ]
44
 
45
- missing_vars = []
46
- for var in required_vars:
47
- if not os.getenv(var):
48
- missing_vars.append(var)
49
 
50
  if missing_vars:
51
  print("โŒ Missing required environment variables:")
@@ -58,6 +55,26 @@ def check_environment_setup() -> bool:
58
  return True
59
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  def create_custom_config(
62
  session_id: Optional[str] = None,
63
  user_id: Optional[str] = None,
@@ -89,7 +106,6 @@ def create_custom_config(
89
 
90
  def run_sales_assistant(
91
  interactive: bool = True,
92
- demo_mode: bool = False,
93
  custom_config: Optional[ConversationConfig] = None
94
  ) -> None:
95
  """
@@ -97,7 +113,6 @@ def run_sales_assistant(
97
 
98
  Args:
99
  interactive: Whether to run in interactive mode
100
- demo_mode: Whether to run demo conversations
101
  custom_config: Optional custom configuration
102
  """
103
  print("๐Ÿš€ Starting Sales Assistant...")
@@ -106,22 +121,22 @@ def run_sales_assistant(
106
  if not check_environment_setup():
107
  return
108
 
 
 
 
109
  try:
110
- if demo_mode:
111
- print("๐ŸŽฎ Running in demo mode...")
112
- demo_agent_runner()
113
- elif interactive:
114
- print("๐Ÿ’ฌ Starting interactive mode...")
115
  config = custom_config or ConversationConfig()
116
  run_interactive_loop(config)
117
  else:
118
- print("โš™๏ธ Agent initialized and ready for programmatic use")
119
  config = custom_config or ConversationConfig()
120
- compiled_graph, checkpointer, langsmith_client, thread_id = create_agent_runner(config)
121
  print(f"๐Ÿ“Š Session ID: {config.session_id}")
122
  print(f"๐Ÿงต Thread ID: {thread_id}")
123
  print("๐Ÿ’ก Use the returned objects to run conversations programmatically")
124
- return compiled_graph, checkpointer, langsmith_client, thread_id
125
 
126
  except KeyboardInterrupt:
127
  print("\n๐Ÿ‘‹ Sales Assistant stopped by user")
@@ -146,13 +161,13 @@ def single_query(query: str, config: Optional[ConversationConfig] = None) -> str
146
 
147
  try:
148
  config = config or ConversationConfig()
149
- compiled_graph, checkpointer, langsmith_client, thread_id = create_agent_runner(config)
150
 
151
  response = run_conversation_turn(
152
  compiled_graph,
153
  thread_id,
154
  query,
155
- langsmith_client
156
  )
157
 
158
  return response
@@ -172,7 +187,7 @@ def main():
172
  )
173
  parser.add_argument(
174
  "--mode",
175
- choices=["interactive", "demo", "single"],
176
  default="interactive",
177
  help="Mode to run the assistant in"
178
  )
@@ -215,11 +230,8 @@ def main():
215
  response = single_query(args.query, config)
216
  print(f"๐Ÿค– Response: {response}")
217
 
218
- elif args.mode == "demo":
219
- run_sales_assistant(interactive=False, demo_mode=True, custom_config=config)
220
-
221
  else: # interactive mode
222
- run_sales_assistant(interactive=True, demo_mode=False, custom_config=config)
223
 
224
 
225
  if __name__ == "__main__":
 
16
  from sales_assistant.agent_main.agent_runner import (
17
  ConversationConfig,
18
  run_interactive_loop,
 
19
  create_agent_runner,
20
  run_conversation_turn
21
  )
22
+ from sales_assistant.agent_main.tools_node import get_all_tools
23
 
24
  # Load environment variables
25
  load_dotenv()
 
34
  """
35
  required_vars = [
36
  "OPENAI_API_KEY",
37
+ "MODEL_PROVIDER",
38
  "MODEL_NAME",
39
  "MYSQL_HOST",
40
+ "MYSQL_USER",
41
+ "MYSQL_PASSWORD",
42
  "MYSQL_DB"
43
  ]
44
 
45
+ missing_vars = [var for var in required_vars if not os.getenv(var)]
 
 
 
46
 
47
  if missing_vars:
48
  print("โŒ Missing required environment variables:")
 
55
  return True
56
 
57
 
58
+ def display_available_tools() -> None:
59
+ """Display available tools information."""
60
+ try:
61
+ tools = get_all_tools()
62
+ print("\n๐Ÿ› ๏ธ Available Tools:")
63
+ print("-" * 30)
64
+
65
+ for i, tool in enumerate(tools, 1):
66
+ tool_name = getattr(tool, 'name', 'Unknown Tool')
67
+ tool_description = getattr(tool, 'description', 'No description available')
68
+ print(f"{i}. {tool_name}")
69
+ print(f" ๐Ÿ“ {tool_description}")
70
+
71
+ print(f"\n๐Ÿ“Š Total tools available: {len(tools)}")
72
+ print("-" * 30)
73
+
74
+ except Exception as e:
75
+ print(f"โš ๏ธ Could not load tools information: {e}")
76
+
77
+
78
  def create_custom_config(
79
  session_id: Optional[str] = None,
80
  user_id: Optional[str] = None,
 
106
 
107
  def run_sales_assistant(
108
  interactive: bool = True,
 
109
  custom_config: Optional[ConversationConfig] = None
110
  ) -> None:
111
  """
 
113
 
114
  Args:
115
  interactive: Whether to run in interactive mode
 
116
  custom_config: Optional custom configuration
117
  """
118
  print("๐Ÿš€ Starting Sales Assistant...")
 
121
  if not check_environment_setup():
122
  return
123
 
124
+ # Display available tools
125
+ display_available_tools()
126
+
127
  try:
128
+ if interactive:
129
+ print("\n๐Ÿ’ฌ Starting interactive mode...")
 
 
 
130
  config = custom_config or ConversationConfig()
131
  run_interactive_loop(config)
132
  else:
133
+ print("\nโš™๏ธ Agent initialized and ready for programmatic use")
134
  config = custom_config or ConversationConfig()
135
+ compiled_graph, checkpointer, callback_manager, thread_id = create_agent_runner(config)
136
  print(f"๐Ÿ“Š Session ID: {config.session_id}")
137
  print(f"๐Ÿงต Thread ID: {thread_id}")
138
  print("๐Ÿ’ก Use the returned objects to run conversations programmatically")
139
+ return compiled_graph, checkpointer, callback_manager, thread_id
140
 
141
  except KeyboardInterrupt:
142
  print("\n๐Ÿ‘‹ Sales Assistant stopped by user")
 
161
 
162
  try:
163
  config = config or ConversationConfig()
164
+ compiled_graph, checkpointer, callback_manager, thread_id = create_agent_runner(config)
165
 
166
  response = run_conversation_turn(
167
  compiled_graph,
168
  thread_id,
169
  query,
170
+ callback_manager
171
  )
172
 
173
  return response
 
187
  )
188
  parser.add_argument(
189
  "--mode",
190
+ choices=["interactive", "single"],
191
  default="interactive",
192
  help="Mode to run the assistant in"
193
  )
 
230
  response = single_query(args.query, config)
231
  print(f"๐Ÿค– Response: {response}")
232
 
 
 
 
233
  else: # interactive mode
234
+ run_sales_assistant(interactive=True, custom_config=config)
235
 
236
 
237
  if __name__ == "__main__":