Pacama95/chatbot_agent
0
1import gradio as gr2import sys3import uuid4from langchain_core.messages import SystemMessage, HumanMessage5 6# Add notebook utilities7sys.path.append('.')8 9def create_and_run_agent():10 """11 Create the agent graph - simplified version12 """13 from langchain_ollama import ChatOllama14 from langgraph.checkpoint.memory import MemorySaver15 from langchain_core.rate_limiters import InMemoryRateLimiter16 from langchain_openai import ChatOpenAI17 from langchain_community.tools.tavily_search import TavilySearchResults18 from langchain_community.tools import WikipediaQueryRun19 from langchain_community.utilities import WikipediaAPIWrapper20 from langgraph.graph import StateGraph, START, END21 from langgraph.prebuilt import ToolNode22 23 # Import custom tools24 from tools.image_analyzer import ImageAnalyzer25 from tools.simple_image_question_answering_tool import SimpleImageQuestionAnsweringTool26 from tools.youtube_video_transcript import YoutubeVideoTranscriptTool27 from tools.document_question_answering_tool import DocumentQuestionAnsweringTool28 from tools.visit_webpage import VisitWebpageTool29 from tools.audio_transcription import AudioTranscriptionTool30 from tools.document_reader import DocumentReader31 from tools.video_analyzer import VideoAnalyzer32 from tools.string_utils import StringUtils33 34 # Import nodes35 from nodes.assistant_state import AgentState36 37 # Initialize memory and tools38 memory = MemorySaver()39 40 try:41 tavily_web_search = TavilySearchResults(max_results=3)42 image_analyzer_tool = ImageAnalyzer()43 simple_image_question_answering_tool = SimpleImageQuestionAnsweringTool()44 youtube_video_transcript_tool = YoutubeVideoTranscriptTool()45 document_question_answering_tool = DocumentQuestionAnsweringTool()46 visit_web_page_tool = VisitWebpageTool()47 audio_transcription_tool = AudioTranscriptionTool()48 document_reader = DocumentReader()49 video_analyzer = VideoAnalyzer()50 wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())51 52 tools = [tavily_web_search,53 image_analyzer_tool,54 youtube_video_transcript_tool, 55 simple_image_question_answering_tool, 56 document_question_answering_tool,57 visit_web_page_tool,58 audio_transcription_tool,59 document_reader,60 video_analyzer,61 wikipedia]62 63 rate_limiter = InMemoryRateLimiter(requests_per_second=2, check_every_n_seconds=0.2, max_bucket_size=5)64 65 # Initialize LLM66 llm = ChatOpenAI(model="gpt-4o", rate_limiter=rate_limiter)67 llm_with_tools = llm.bind_tools(tools)68 69 # Node definitions70 def planner(state: AgentState):71 print("\n----- Running planner -----\n")72 73 planner_llm = ChatOpenAI(model="gpt-4o")74 original_input = state.get("original_user_input", "")75 76 tool_descriptions = []77 for tool in tools:78 name = tool.name79 desc = tool.description80 tool_descriptions.append(f"- {name}: {desc}")81 82 tools_list_text = "\n".join(tool_descriptions)83 84 system_prompt = (85 "You are a planning agent. Break down the following user request into a step-by-step strategy "86 "the assistant should follow. Format the plan clearly and concisely.\n\n"87 "The assistant will use this plan as a system message to guide its behavior.\n"88 "Avoid solving the problem yourself — only create the plan.\n\n"89 f"Here are the available tools the assistant can use:\n{tools_list_text}\n"90 )91 92 user_prompt = f"User request: {original_input}"93 94 plan_message = planner_llm.invoke([95 {"role": "system", "content": system_prompt},96 {"role": "user", "content": user_prompt}97 ])98 99 system_plan = SystemMessage(content=f"Execution Plan:\n{plan_message.content}")100 101 return {102 "original_user_input": original_input,103 "messages": [system_plan],104 "response_feedback": None,105 "final_answer": None,106 "retry_count": 0107 }108 109 def assistant(state: AgentState):110 print("\n\n\n ----- Running assistant... ----- \n\n\n")111 112 if state.get("response_feedback") == 'INCORRECT':113 print(" ----- Negative feedback loop... ----- \n\n\n")114 original_input = state.get("original_user_input", "")115 state['messages'].append(SystemMessage(content=f"Reminder: the user originally asked '{original_input}'"))116 117 response = llm_with_tools.invoke(state['messages'])118 119 return {120 "original_user_input": state["original_user_input"],121 "response_feedback": state.get("response_feedback"),122 "final_answer": state.get("final_answer"),123 "retry_count": state.get("retry_count", 0),124 "messages": state['messages'] + [response]125 }126 127 def judge(state: AgentState):128 print("\n\n\n ----- Running judge... ----- \n\n\n")129 130 judge_llm = ChatOpenAI(model="gpt-4o")131 132 instructions = (133 "You are an evaluator for an AI assistant. Your job is to determine whether the final answer given to the user is correct.\n\n"134 "Evaluation Rules:\n"135 "1. Focus only on the final result or answer, not intermediate steps or reasoning.\n"136 "2. The answer must directly and conclusively address the user's original request.\n"137 "3. The answer content is important, but if you cannot find the answer due to lack of tools but the answer seems reasonable, respond with: CORRECT\n"138 "4. If the answer is correct, respond with: CORRECT\n"139 "5. If the answer is incorrect or incomplete, explain why briefly and end your message with: INCORRECT"140 )141 142 messages = state.get("messages", [])143 if messages:144 last_message = messages[-1]145 actual_answer = last_message.content if hasattr(last_message, 'content') else str(last_message)146 actual_answer = StringUtils.remove_think_sections(actual_answer)147 else:148 actual_answer = ""149 150 original_user_input = state.get("original_user_input", "")151 152 print(f"\n\n Candidate answer: {actual_answer} \n\n")153 154 user_msg = (155 f"Given this query or question from the user: {original_user_input}"156 f"\n The answer is: {actual_answer}"157 )158 159 response = judge_llm.invoke(160 [161 {"role": "system", "content": instructions},162 {"role": "user", "content": user_msg}163 ]164 )165 166 print(f"\n\n Judge response: {response} \n\n")167 168 import re169 if re.search(r"\bINCORRECT\b", response.content.strip(), re.IGNORECASE):170 feedback = "INCORRECT"171 else:172 feedback = "CORRECT"173 174 if feedback == 'CORRECT':175 print("\n\n\n ----- Giving positive feedback and storing the correct answer... ----- \n\n\n")176 state['final_answer'] = actual_answer177 178 state['messages'].append(response)179 180 return {181 "original_user_input": state["original_user_input"],182 "response_feedback": feedback,183 "final_answer": state.get("final_answer"),184 "retry_count": state.get("retry_count", 0),185 "messages": state['messages']186 }187 188 def route_tools(state: AgentState):189 if isinstance(state, list):190 ai_message = state[-1]191 elif messages := state.get("messages", []):192 ai_message = messages[-1]193 else:194 raise ValueError(f"No messages found in input state to tool_edge: {state}")195 196 if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:197 return "tools"198 return "judge"199 200 MAX_RETRIES = 5201 202 def route_tools_judge(state: AgentState) -> str:203 feedback = state.get("response_feedback", "")204 if isinstance(feedback, str):205 feedback = feedback.strip().upper()206 else:207 feedback = str(feedback).strip().upper()208 209 retry_count = state.get("retry_count", 0)210 print(f"\nJudge Feedback: {feedback}, Retry Count: {retry_count}\n")211 212 if feedback == "INCORRECT":213 if retry_count >= MAX_RETRIES:214 print("⚠️ Retry limit reached. Ending.")215 return "END"216 state["retry_count"] = retry_count + 1217 print("🔁 Retrying assistant node...")218 return "assistant"219 elif feedback == "CORRECT":220 print("✅ Final answer accepted. Ending.")221 return "END"222 else:223 raise ValueError(f"Unexpected feedback: {feedback}")224 225 # Build the graph226 graph_builder = StateGraph(AgentState)227 228 tool_node = ToolNode(tools)229 graph_builder.add_node("planner", planner)230 graph_builder.add_node("assistant", assistant)231 graph_builder.add_node("judge", judge)232 graph_builder.add_node("tools", tool_node)233 234 graph_builder.add_edge(START, "planner")235 graph_builder.add_edge("planner", "assistant")236 graph_builder.add_edge("tools", "assistant")237 238 graph_builder.add_conditional_edges(239 "assistant",240 route_tools,241 {"tools": "tools", "judge": "judge"}242 )243 244 graph_builder.add_conditional_edges(245 "judge",246 route_tools_judge,247 {"assistant": "assistant", "END": END}248 )249 250 graph = graph_builder.compile(checkpointer=memory)251 return graph, StringUtils252 253 except Exception as e:254 print(f"Error creating agent: {e}")255 return None, None256 257def chat_function(message, history, debug_messages, current_thread_id):258 """259 Chat function that works with the agent and captures debug information260 """261 if not hasattr(chat_function, 'graph') or chat_function.graph is None:262 chat_function.graph, chat_function.string_utils = create_and_run_agent()263 264 if chat_function.graph is None:265 error_msg = "Error: Could not initialize agent. Please check your API keys and dependencies."266 debug_info = f"🚨 **INITIALIZATION ERROR**\n{error_msg}"267 return (history + [[message, error_msg]], 268 debug_messages + f"\n\n---\n\n{debug_info}",269 current_thread_id)270 271 # Use the current thread ID (persistent across messages unless reset)272 config = {"configurable": {"thread_id": current_thread_id}}273 274 # Capture debug information275 debug_info = f"🔄 **NEW REQUEST**: {message}\n"276 debug_info += f"📋 **Thread ID**: {current_thread_id}\n\n"277 278 try:279 # System prompt for general conversation280 system_prompt = """You are a helpful AI assistant. Answer the user's questions and help them with their tasks. 281 Use the available tools when needed to provide accurate and comprehensive responses."""282 283 debug_info += f"💬 **SYSTEM PROMPT**: {system_prompt}\n\n"284 285 # Invoke the graph with the user's message286 response = chat_function.graph.invoke(287 input={288 "messages": [289 SystemMessage(content=system_prompt), 290 HumanMessage(content=message)291 ], 292 "original_user_input": message, 293 "response_feedback": "", 294 "final_answer": None295 }, 296 config=config297 )298 299 # Capture response details300 debug_info += f"📊 **FULL RESPONSE**:\n"301 debug_info += f"- Original Input: {response.get('original_user_input', 'N/A')}\n"302 debug_info += f"- Feedback: {response.get('response_feedback', 'N/A')}\n"303 debug_info += f"- Retry Count: {response.get('retry_count', 0)}\n"304 debug_info += f"- Messages Count: {len(response.get('messages', []))}\n\n"305 306 # Show all messages in the conversation307 messages = response.get('messages', [])308 for i, msg in enumerate(messages):309 if hasattr(msg, 'content'):310 msg_type = type(msg).__name__311 content = msg.content # Show complete content without truncation312 debug_info += f"📝 **Message {i+1}** ({msg_type}):\n{content}\n\n"313 elif hasattr(msg, 'tool_calls') and msg.tool_calls:314 debug_info += f"🔧 **Tool Calls** (Message {i+1}):\n"315 for tool_call in msg.tool_calls:316 debug_info += f"- {tool_call.get('name', 'Unknown Tool')}: {tool_call.get('args', {})}\n"317 debug_info += "\n"318 319 # Get the final answer from the response320 agent_response = response.get('final_answer')321 if agent_response is None:322 agent_response = "I'm sorry, I couldn't process your request. Please try again."323 debug_info += f"⚠️ **NO FINAL ANSWER**: Using fallback response\n"324 else:325 # Clean up the response326 clean_response = chat_function.string_utils.remove_think_sections(agent_response)327 debug_info += f"✅ **FINAL ANSWER**: {agent_response}\n"328 debug_info += f"🧹 **CLEANED**: {clean_response}\n"329 agent_response = clean_response330 331 # Return updated history and debug info332 return (history + [[message, agent_response]], 333 debug_messages + f"\n\n---\n\n{debug_info}",334 current_thread_id)335 336 except Exception as e:337 error_msg = f"An error occurred: {str(e)}"338 debug_info += f"🚨 **EXCEPTION**: {error_msg}\n"339 debug_info += f"📍 **Error Type**: {type(e).__name__}\n"340 341 return (history + [[message, error_msg]], 342 debug_messages + f"\n\n---\n\n{debug_info}",343 current_thread_id)344 345# Initialize the graph346chat_function.graph = None347chat_function.string_utils = None348 349def reset_memory():350 """351 Reset the agent's memory by generating a new thread ID352 """353 new_thread_id = str(uuid.uuid4())354 reset_message = f"🔄 **MEMORY RESET**\nNew Thread ID: {new_thread_id}\nAgent memory has been cleared."355 return new_thread_id, reset_message356 357# Create Gradio interface358def create_interface():359 with gr.Blocks(title="LangGraph AI Agent") as demo:360 gr.Markdown("# 🤖 LangGraph AI Agent")361 gr.Markdown("Chat with an AI agent powered by LangGraph with access to various tools including web search, document analysis, image analysis, and more!")362 363 # Thread ID state - hidden from user364 thread_id_state = gr.State(value=str(uuid.uuid4()))365 366 chatbot = gr.Chatbot(height=600)367 msg = gr.Textbox(368 label="Type your message here...",369 placeholder="Ask me anything! I can help with research, analyze documents, images, videos, and more.",370 lines=3371 )372 373 with gr.Row():374 send_btn = gr.Button("Send", variant="primary")375 reset_memory_btn = gr.Button("Reset Memory", variant="secondary")376 clear_btn = gr.Button("Clear Chat", variant="secondary")377 378 # Debug section in an expandable accordion379 with gr.Accordion("🔍 Agent Debug Messages", open=False):380 debug_display = gr.Markdown(381 value="Debug information will appear here after sending messages...",382 label="Internal Agent Processing",383 elem_classes=["debug-messages"]384 )385 clear_debug_btn = gr.Button("Clear Debug Messages", size="sm")386 387 # Function to handle memory reset388 def handle_reset_memory(debug_messages):389 new_thread_id, reset_message = reset_memory()390 return new_thread_id, debug_messages + f"\n\n---\n\n{reset_message}"391 392 # Event handlers393 msg.submit(394 chat_function, 395 [msg, chatbot, debug_display, thread_id_state], 396 [chatbot, debug_display, thread_id_state]397 ).then(398 lambda: "", None, msg399 )400 401 send_btn.click(402 chat_function, 403 [msg, chatbot, debug_display, thread_id_state], 404 [chatbot, debug_display, thread_id_state]405 ).then(406 lambda: "", None, msg407 )408 409 reset_memory_btn.click(410 handle_reset_memory, 411 [debug_display], 412 [thread_id_state, debug_display]413 )414 415 clear_btn.click(lambda: [], None, chatbot)416 clear_debug_btn.click(417 lambda: "Debug messages cleared...", 418 None, 419 debug_display420 )421 422 gr.Markdown("""423 ### 🛠️ Available Tools:424 - 🔍 **Web Search** - Search the internet using Tavily425 - 🖼️ **Image Analysis** - Analyze and describe images426 - 🎥 **YouTube Transcripts** - Extract and analyze YouTube video content427 - 📄 **Document Q&A** - Answer questions about documents428 - 🌐 **Web Page Visitor** - Visit and analyze web pages429 - 🎵 **Audio Transcription** - Convert audio to text430 - 📖 **Document Reader** - Read and process various document types431 - 🎬 **Video Analysis** - Analyze video content432 - 📚 **Wikipedia Search** - Search Wikipedia for information433 434 ### 🔄 Memory Management:435 - **Reset Memory** - Clears the agent's conversation memory by creating a new thread ID436 - The agent maintains context across messages until memory is reset437 - Use this when you want to start a completely fresh conversation438 439 ### 🔍 Debug Feature:440 - Expand the **"Agent Debug Messages"** section below to see internal processing441 - View planner strategies, assistant reasoning, judge feedback, and tool calls442 - Monitor retry attempts and error handling in real-time443 - Thread ID tracking shows conversation continuity444 445 ### 💡 Example Questions:446 - "What's the latest news about AI?"447 - "Analyze this image for me" (upload an image)448 - "What's the transcript of this YouTube video: [URL]?"449 - "Search Wikipedia for information about quantum computing"450 """)451 452 return demo453 454if __name__ == "__main__":455 interface = create_interface()456 interface.launch(share=True, debug=True) 