def submit(): user_message = st.session_state['user_input'] if user_message: if 'active_profile' not in st.session_state or st.session_state.active_profile != username: st.session_state.active_profile = username # load saved chat for this username and populate session history if username: CHROMA_DIR = f".chroma_db_{username}" client, collection = init_chromadb(CHROMA_DIR) else: CHROMA_DIR = ".chroma_db" client, collection = init_chromadb(CHROMA_DIR) st.session_state.history.append(("user", user_message)) save_chat_message(client, collection, role="user", text=user_message, user_id=username) # save user's message memory_context = get_memory_context(collection, user_message, user_id=username, history_limit=30) result = run_internet_agent(user_message,memory_context) st.session_state.history.append(("agent", result)) save_chat_message(client, collection, role="agent", text=result, user_id=username) #save agent message if 'empty_key' not in st.session_state: st.session_state.empty_key='' st.session_state.empty_key=st.session_state.user_input # if you set user_input to a blank valueto clear the prompt, streamlit throws an error st.session_state.user_input = '' def run_internet_agent(user_message,memory_context): SYSTEM_INSTRUCTION = f""" You are a highly flexible and helpful AI research assistant. Your primary goal is to answer the user's question, using the available tools only when necessary. **MEMORY CONTEXT:** {memory_context} **FLEXIBLE FORMATTING RULES:** 1. **PRIORITY:** Adhere strictly to any formatting requested by the user (e.g., "return as bullet points," "write a short paragraph," or "output as JSON"). 2. **LINK MANDATE (Hard Rule):** For every piece of factual information, movie, book, or specific data point you mention that came from the web search tool, you MUST embed the source URL from the search results directly into the text using **Markdown link syntax: [Relevant Text](Source URL)**. """ search_tool = DuckDuckGoSearchRun() tools = [search_tool] llm = ChatOllama( model="llama3.2", temperature=0.1, # Keep it low for factual/tool-based tasks ) # 3. Create the Agent Executor # This bundles the LLM and the tools into a decision-making loop agent_executor = create_agent(llm, tools,system_prompt=SYSTEM_INSTRUCTION) # Use streaming to show the agent's step-by-step process # The agent_executor.stream() output is what we need to safely parse for chunk in agent_executor.stream( {"messages": [HumanMessage(content=user_message)]}, stream_mode="values" ): latest_message = chunk["messages"][-1] # 1. Check for Tool Call (AI's decision to search) # Use hasattr() to safely check for the 'tool_calls' attribute if hasattr(latest_message, 'tool_calls') and latest_message.tool_calls: tool_call_args = [tc['args'] for tc in latest_message.tool_calls] print(f"--- 🧠 Thinking: I need to search for: {tool_call_args}") # 2. Check if the AI has received tool results elif latest_message.type == "tool": print(f"--- 🔍 Found: {latest_message.content[:100]}... (truncated results)") # 3. Check for the final answer # Check if it's an AI message AND it has no tool calls (or the attribute is missing/empty) elif latest_message.type == "ai" and (not hasattr(latest_message, 'tool_calls') or not latest_message.tool_calls): return f"{latest_message.content}" def get_memory_context(collection, user_message, user_id=None, history_limit=30): """ Combines the last N chat messages and top M semantically similar book recommendations from memory into a single string for prompt context. """ history = load_user_chat(collection, user_id) # Get the last N conversation turns (user/agent pairs) recent_chat = history[-history_limit:] # Format the recent chat history chat_context = "\n".join( [f"{role.capitalize()}: {text}" for role, text in recent_chat] ) # Retrieve relevant book recommendations based on the current user query # The retrieve_similar function already exists and searches the 'book_memory' collection book_recs = retrieve_similar( collection, user_message, n=3, user_id=user_id ) # Format the retrieved book recommendations recs_context = [] if book_recs: for rec in book_recs: recs_context.append(f"Title: {rec['title']}, Author: {rec['author']}, Summary: {rec['doc']}") formatted_recs = "\n".join(recs_context) # Combine everything into a single context string full_context = f""" --- MEMORY CONTEXT --- RECENT CHAT HISTORY: {chat_context or "None"} SIMILAR PAST BOOK, MOVIE RECOMMENDATIONS: {formatted_recs or "None"} --- END CONTEXT --- """ return full_context