CoolFace
Apppublic

Pacama95/chatbot_agent

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py456 linesDownload Raw Back to root
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)