
This publication presents a comprehensive, production-ready conversational AI system designed for the Ready Tensor Agentic AI Developer Certification Program. The system implements advanced security measures, robust input validation, and comprehensive output filtering while leveraging LangGraph for stateful workflow orchestration, LangChain's ToolNode for tool integration, and Chainlit for a modern user interface. Key innovations include multi-layered security guardrails aligned with OWASP Top 10 for LLM Applications, enhanced JSON parsing with retry mechanisms, sandboxed code execution with timeout protection, and comprehensive audit logging. This work demonstrates practical implementation of enterprise-grade agentic AI patterns with emphasis on safety, security, and reliability.
Keywords: Agentic AI, LangGraph, LangChain, Chatbot, Security Guardrails, OWASP LLM Top 10, Input Validation, Output Filtering, Tool-Calling, State Management, Conversational AI, Production Systems
The proliferation of Large Language Models (LLMs) has revolutionized conversational AI, enabling systems that understand context, generate human-like responses, and perform complex reasoning tasks. However, deploying production-ready chatbots that safely integrate external tools, maintain conversation state, and protect against security vulnerabilities presents significant engineering challenges. This is particularly critical in educational contexts where users may experiment with various inputs, including potentially harmful code or malicious prompts.
The Ready Tensor Agentic AI Developer Certification Program provides comprehensive training in modern AI agent development, yet learners require hands-on support tools that exemplify best practices in security, reliability, and user experience. This project addresses this need by implementing a chatbot that not only serves as an educational assistant but also demonstrates production-grade safety measures and architectural patterns.
Importance in Educational Technology:
Educational chatbots must balance openness (allowing learners to experiment) with security (preventing misuse). This system achieves this balance through multi-layered guardrails, transparent error handling, and comprehensive logging that supports both learning and system integrity.
Contemporary conversational AI systems face critical challenges that impact their reliability, security, and usability:
This project pursues the following objectives:
Primary Objectives:
Secondary Objectives:
Success Criteria:
In Scope:
Out of Scope:
This work makes the following contributions to the field of conversational AI and educational technology:
This publication targets:
The system employs a layered, modular architecture that separates security, orchestration, tool execution, and user interface concerns. This design enables independent development, testing, and security auditing of each layer while maintaining clear interfaces between components.
┌─────────────────────────────────────────────────────────────────┐
│ USER INTERFACE LAYER │
│ (Chainlit Framework - Async UI) │
│ Features: Session Management, Markdown Rendering, │
│ Real-time Streaming, Error Display │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SECURITY LAYER │
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────────┐ │
│ │ Input │ │ Output │ │ Rate Limiting │ │
│ │ Validation │ │ Filtering │ │ & Throttling │ │
│ └──────────────┘ └───────────────┘ └──────────────────┘ │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ORCHESTRATION LAYER │
│ (LangGraph State Machine) │
│ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ Agent │◄──►│ Should │◄──►│ ToolNode │ │
│ │ Node │ │ Continue? │ │ Executor │ │
│ └──────────┘ └────────────┘ └──────────────┘ │
│ Features: State Management, Conditional Routing, │
│ Tool Selection, Context Management │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ TOOLS LAYER │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Web Search │ │ Document │ │ Code Executor │ │
│ │ (Tavily API) │ │ Retrieval │ │ (Sandboxed) │ │
│ │ + Retry │ │ + Cache │ │ + Timeout │ │
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
│ Common: Structured JSON, Error Handling, Validation │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Logging │ │ Metrics │ │ Error Handling │ │
│ │ (Rotating) │ │ Collection │ │ & Recovery │ │
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
│ Features: Audit Trail, Performance Monitoring, Alerting │
└─────────────────────────────────────────────────────────────────┘
The architecture adheres to the following design principles:
Technology: Chainlit v0.7+
Responsibilities:
Security Features:
Implementation Highlights:
@cl.on_chat_start async def start(): """ Initialize secure chat session - Validate environment configuration - Initialize LLM with API key from environment - Create isolated session storage - Set up conversation history """ # Environment validation if not os.getenv("GROQ_API_KEY"): await cl.Message(content="❌ API key not configured").send() return # Session initialization llm = ChatGroq(api_key=os.getenv("GROQ_API_KEY")) cl.user_session.set("llm", llm) cl.user_session.set("conversation_history", []) cl.user_session.set("session_id", generate_session_id())
Purpose: Provide comprehensive input validation, output filtering, and access control
Components:
Input Validation Module
Output Filtering Module
Rate Limiting Module
Implementation (detailed in Section 3)
Technology: LangGraph + LangChain ToolNode
State Definition:
class AgentState(TypedDict): """ Conversation state maintained throughout workflow Attributes: messages: Complete conversation history (user, AI, tool messages) query: Current user query being processed next_action: Routing decision for state machine """ messages: Annotated[List, operator.add] # Append-only for history query: str next_action: str
Graph Construction:
def create_graph(): """Build LangGraph workflow with security integration""" workflow = StateGraph(AgentState) # Core nodes workflow.add_node("agent", call_model_with_guardrails) # Enhanced with security workflow.add_node("tools", tool_node) # Routing logic workflow.add_edge(START, "agent") workflow.add_conditional_edges( "agent", should_continue, {"tools": "tools", "end": END} ) workflow.add_edge("tools", "agent") # Loop for multi-tool scenarios return workflow.compile()
Conditional Routing:
def should_continue(state: AgentState) -> Literal["tools", "end"]: """ Determine next action based on LLM decision Security: Validates tool calls before routing to prevent unauthorized tool execution """ last_message = state["messages"][-1] if not hasattr(last_message, "tool_calls") or not last_message.tool_calls: return "end" # Validate all tool calls before allowing execution for tool_call in last_message.tool_calls: validation = validate_tool_call( tool_call.get("name"), tool_call.get("args", {}) ) if not validation["valid"]: logger.warning(f"Invalid tool call blocked: {validation['message']}") return "end" return "tools"
Each tool implements the following standardized interface:
@tool def tool_function(params: ToolParams) -> dict: """ Standardized tool interface Returns: { "status": "success" | "error" | "blocked" | "not_found", "message": "Human-readable status", "data": {...}, # Tool-specific payload "metadata": {"timestamp": "...", "execution_time_ms": ...} } """
Tool Implementations (detailed in Section 4.3)
Logging System:
def setup_logging() -> logging.Logger: """ Configure structured logging with security considerations - PII redaction in log messages - Separate audit log for security events - Rotating file handlers to prevent disk exhaustion - Different log levels for file (INFO) vs console (WARNING) """ logger = logging.getLogger("ready_tensor_chatbot") # File handler with rotation (10MB, 5 backups) file_handler = RotatingFileHandler( "logs/chatbot.log", maxBytes=10*1024*1024, backupCount=5 ) # Custom formatter with PII redaction formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s' ) file_handler.setFormatter(formatter) return logger
Request Processing Flow:
Error Handling Strategy:
Error Occurs
↓
Capture with try/except at source
↓
Log with full context (stack trace, inputs, state)
↓
Transform to structured error response
↓
Apply output filtering (redact sensitive details)
↓
Return user-friendly error message
↓
Audit log security-relevant errors
↓
Update metrics/monitoring
Example:
try: result = web_search_tool.invoke({"query": user_query}) except ConnectionError as e: logger.error(f"Network error in web search: {e}", exc_info=True) return { "status": "error", "message": "Network connection issue. Please try again.", "user_message": "Unable to perform web search due to connectivity", "retry_recommended": True }
The system implements a comprehensive security framework based on industry best practices, OWASP guidelines, and LLM-specific threat models. Security is integrated at every architectural layer rather than implemented as an afterthought.
Identified Threats:
Prompt Injection (OWASP LLM01)
Code Execution Exploits (OWASP LLM07, LLM08)
Information Disclosure (OWASP LLM06)
Denial of Service (OWASP LLM04)
Data Poisoning (OWASP LLM03)
Defense-in-Depth Layers:
| Layer | Controls | Purpose |
|---|---|---|
| Perimeter | Input validation, rate limiting | Reject malicious requests before processing |
| Application | Output filtering, tool validation | Prevent harmful operations during execution |
| Data | PII redaction, sensitive data masking | Protect information in transit and at rest |
| Logging | Audit trail, security event monitoring | Detect and respond to security incidents |
| Recovery | Error boundaries, graceful degradation | Maintain availability during attacks |
All user inputs pass through a multi-stage validation pipeline before processing:
def validate_user_input(query: str) -> Dict[str, Any]: """ Comprehensive input validation pipeline Stages: 1. Basic validation (length, emptiness, encoding) 2. Prompt injection detection 3. Content safety screening 4. Character encoding validation Returns: { "valid": bool, "message": str, # Error message if invalid "sanitized_input": str, # Clean version if valid "threat_detected": str | None # Threat type if applicable } """ # Stage 1: Basic Validation if not query or not query.strip(): logger.warning("Empty input received") return { "valid": False, "message": "Query cannot be empty", "threat_detected": None } if len(query) > 5000: logger.warning(f"Oversized input: {len(query)} characters") return { "valid": False, "message": "Query too long (maximum 5000 characters)", "threat_detected": "length_violation" } # Stage 2: Prompt Injection Detection injection_patterns = [ (r'ignore\s+previous\s+instructions', 'instruction_override'), (r'ignore\s+all\s+previous', 'instruction_override'), (r'disregard\s+previous', 'instruction_override'), (r'you\s+are\s+now', 'role_manipulation'), (r'your\s+new\s+role', 'role_manipulation'), (r'system\s*:\s*ignore', 'system_injection'), (r'forget\s+everything', 'memory_manipulation'), (r'reveal\s+your\s+prompt', 'prompt_extraction'), (r'what\s+are\s+your\s+instructions', 'prompt_extraction'), ] for pattern, threat_type in injection_patterns: if re.search(pattern, query, re.IGNORECASE): logger.warning(f"Prompt injection detected: {threat_type} in '{query[:100]}'") AuditLogger.log_security_event("prompt_injection_attempt", { "severity": "high", "pattern": pattern, "threat_type": threat_type, "input_preview": query[:200] }) return { "valid": False, "message": "Input contains potentially harmful patterns and cannot be processed", "threat_detected": threat_type } # Stage 3: Content Safety Screening harmful_indicators = [ r'jailbreak', r'dan\s+mode', # "Do Anything Now" attacks r'evil\s+mode', r'developer\s+mode', ] for indicator in harmful_indicators: if re.search(indicator, query, re.IGNORECASE): logger.warning(f"Harmful content indicator: {indicator}") return { "valid": False, "message": "Query contains content that violates usage policies", "threat_detected": "content_violation" } # Stage 4: Character Encoding Validation try: query.encode('utf-8').decode('utf-8') except UnicodeError: logger.error("Invalid character encoding in input") return { "valid": False, "message": "Input contains invalid characters", "threat_detected": "encoding_error" } # Input passed all validations return { "valid": True, "sanitized_input": query.strip(), "threat_detected": None }
def call_model_with_guardrails(state: AgentState) -> AgentState: """Enhanced model call with input validation""" messages = state["messages"] last_user_message = next( (m for m in reversed(messages) if isinstance(m, HumanMessage)), None ) if last_user_message: # Apply input validation validation = validate_user_input(last_user_message.content) if not validation["valid"]: # Return error message to user error_response = AIMessage( content=f"⚠️ **Input Validation Failed**\n\n{validation['message']}" ) return {"messages": [error_response]} # Use sanitized input for processing last_user_message.content = validation["sanitized_input"] # Continue with normal processing...
def filter_llm_output(response: str) -> Dict[str, Any]: """ Screen LLM responses for sensitive information Detection Categories: - API keys and tokens - Social Security Numbers (SSN) - Credit card numbers - Email addresses (except public/author) - Phone numbers - IP addresses (private ranges) - File paths (system directories) Returns: { "safe": bool, "response": str, # Original or redacted "redactions": List[str], # Types of data redacted "severity": str # "low" | "medium" | "high" } """ redactions = [] modified_response = response severity = "low" # API Keys and Tokens api_key_patterns = [ (r'(?i)(api[_\s]?key|secret[_\s]?key|token)\s*[:=]\s*[\w-]{20,}', 'api_key'), (r'(?i)bearer\s+[\w-]{20,}', 'bearer_token'), (r'(?i)sk-[a-zA-Z0-9]{40,}', 'openai_key'), (r'(?i)gsk_[a-zA-Z0-9]{40,}', 'groq_key'), ] for pattern, key_type in api_key_patterns: if re.search(pattern, response): modified_response = re.sub(pattern, f'[REDACTED: {key_type}]', modified_response) redactions.append(key_type) severity = "high" logger.error(f"API key detected in output: {key_type}") AuditLogger.log_security_event("api_key_exposure", { "severity": "critical", "key_type": key_type }) # Social Security Numbers ssn_pattern = r'\b\d{3}-\d{2}-\d{4}\b' if re.search(ssn_pattern, response): modified_response = re.sub(ssn_pattern, '[REDACTED: SSN]', modified_response) redactions.append('ssn') severity = "high" logger.error("SSN detected in output") # Credit Card Numbers cc_pattern = r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b' if re.search(cc_pattern, response): mo *For questions, contributions, or collaboration inquiries, please contact the author at ilayetimibofa3@gmail.com or open an issue on the GitHub repository. ::youtube[Title]{KCTsVfQBgo4}