Spaces:
Sleeping
Sleeping
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
|
| 3 |
-
import
|
| 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
|
| 9 |
-
from
|
| 10 |
-
from
|
| 11 |
from dotenv import load_dotenv
|
| 12 |
from .agent_node import agent_node
|
| 13 |
from .tools_node import tool_node
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
"""
|
| 17 |
-
Create the ReAct
|
| 18 |
|
| 19 |
Args:
|
| 20 |
checkpointer: Optional checkpointer for state persistence
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
"""
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 33 |
workflow.add_conditional_edges(
|
| 34 |
"agent",
|
| 35 |
-
tools_condition, # Built-in condition
|
| 36 |
{
|
| 37 |
"tools": "tools",
|
| 38 |
-
END: END,
|
| 39 |
}
|
| 40 |
)
|
| 41 |
|
| 42 |
-
# Tools
|
| 43 |
workflow.add_edge("tools", "agent")
|
| 44 |
|
| 45 |
-
# Compile
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
| 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
|
| 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
|
| 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 |
-
#
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 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
|
| 59 |
messages = state["messages"]
|
| 60 |
|
| 61 |
-
# Add system prompt if
|
| 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
|
|
|
|
| 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 |
-
"""
|
| 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
|
| 27 |
|
| 28 |
Args:
|
| 29 |
config: Configuration for the conversation
|
| 30 |
|
| 31 |
Returns:
|
| 32 |
-
|
| 33 |
"""
|
| 34 |
-
if not config.enable_langsmith:
|
| 35 |
return None
|
| 36 |
|
| 37 |
try:
|
| 38 |
-
#
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 56 |
|
| 57 |
Args:
|
| 58 |
-
config: Optional
|
| 59 |
|
| 60 |
Returns:
|
| 61 |
-
Tuple of (compiled_graph, checkpointer,
|
| 62 |
"""
|
| 63 |
if config is None:
|
| 64 |
config = ConversationConfig()
|
| 65 |
|
| 66 |
-
# Setup LangSmith tracing
|
| 67 |
-
|
| 68 |
|
| 69 |
-
#
|
| 70 |
checkpointer = MemorySaver()
|
| 71 |
|
| 72 |
-
# Create
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
# Use session_id as thread_id for LangGraph's memory
|
| 76 |
thread_id = config.session_id
|
| 77 |
|
| 78 |
-
return compiled_graph, checkpointer,
|
| 79 |
-
|
| 80 |
|
| 81 |
def run_conversation_turn(
|
| 82 |
compiled_graph,
|
| 83 |
thread_id: str,
|
| 84 |
user_input: str,
|
| 85 |
-
|
| 86 |
) -> str:
|
| 87 |
"""
|
| 88 |
-
Run
|
| 89 |
|
| 90 |
Args:
|
| 91 |
-
compiled_graph: The compiled LangGraph
|
| 92 |
-
thread_id: Thread ID for conversation persistence
|
| 93 |
user_input: User's input message
|
| 94 |
-
|
| 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 |
-
#
|
| 104 |
-
config = {
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
-
# Invoke
|
| 107 |
-
# LangGraph will automatically handle state persistence
|
| 108 |
result = compiled_graph.invoke(
|
| 109 |
{"messages": [user_message]},
|
| 110 |
config=config
|
| 111 |
)
|
| 112 |
|
| 113 |
-
#
|
| 114 |
-
|
| 115 |
-
|
|
|
|
| 116 |
|
| 117 |
-
return
|
| 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
|
| 128 |
|
| 129 |
Args:
|
| 130 |
-
config: Optional
|
| 131 |
"""
|
| 132 |
if config is None:
|
| 133 |
config = ConversationConfig()
|
| 134 |
|
| 135 |
-
# Create agent runner with
|
| 136 |
-
compiled_graph, checkpointer,
|
| 137 |
|
| 138 |
-
print("๐ค Sales Assistant initialized!")
|
| 139 |
-
print(f"Using
|
| 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
|
| 144 |
print(f"๐ LangSmith tracking enabled - Project: {config.langsmith_project}")
|
| 145 |
print("-" * 50)
|
| 146 |
|
| 147 |
-
|
|
|
|
| 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 |
-
|
| 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
|
| 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
|
| 185 |
"""
|
| 186 |
try:
|
| 187 |
config = {"configurable": {"thread_id": thread_id}}
|
| 188 |
-
#
|
| 189 |
state = compiled_graph.get_state(config)
|
| 190 |
messages = state.values.get("messages", [])
|
| 191 |
|
| 192 |
-
# Convert
|
| 193 |
history = []
|
| 194 |
for msg in messages:
|
| 195 |
-
if hasattr(msg, "type"):
|
| 196 |
history.append({
|
| 197 |
"type": msg.type,
|
| 198 |
"content": msg.content,
|
| 199 |
-
"
|
| 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
|
| 262 |
-
|
|
|
|
|
|
| 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
|
| 23 |
|
|
|
|
|
|
|
|
|
|
| 24 |
Returns:
|
| 25 |
-
List of
|
| 26 |
"""
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
get_table_statistics,
|
| 35 |
-
exchange_converter,
|
| 36 |
create_quote
|
| 37 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
#
|
| 43 |
-
tool_node =
|
|
|
|
| 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
|
| 111 |
-
print("
|
| 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,
|
| 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,
|
| 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,
|
| 150 |
|
| 151 |
response = run_conversation_turn(
|
| 152 |
compiled_graph,
|
| 153 |
thread_id,
|
| 154 |
query,
|
| 155 |
-
|
| 156 |
)
|
| 157 |
|
| 158 |
return response
|
|
@@ -172,7 +187,7 @@ def main():
|
|
| 172 |
)
|
| 173 |
parser.add_argument(
|
| 174 |
"--mode",
|
| 175 |
-
choices=["interactive", "
|
| 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,
|
| 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__":
|