doesn work that great. right now the knowlage base agent can also use other tools. also make better prompting for knowlage base tool and so. and make a example for the knowlage base agent and the web search agent that it knows how to use its tools. and please make the agents also return to the boss if they cant find any good information. and after they returned the boss should think if thats enough information or if he should continue to call a other agent:
import os
import shutil
import gradio as gr
from langchain.embeddings import SentenceTransformerEmbeddings
from langchain.document_loaders import DirectoryLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.tools import Tool
from langchain.utilities import WikipediaAPIWrapper, DuckDuckGoSearchAPIWrapper
from langchain.llms import Ollama
from langchain.chains import LLMChain
from langchain.agents import AgentExecutor, ZeroShotAgent
from langchain.prompts import PromptTemplate
from langsmith import traceable

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "<TRUFFLEHOG_REDACTED_LANGSMITH>"
os.environ["LANGCHAIN_PROJECT"] = "rag test"  # Optional: "default" is used if not set

# Initialize embeddings and vector store
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
persist_directory = "./chroma_db"
if not os.path.exists(persist_directory):
    os.makedirs(persist_directory)
vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)

# Initialize tools
duckduckgo_search = DuckDuckGoSearchAPIWrapper()
wikipedia = WikipediaAPIWrapper()

# Create Knowledge Base Tool
def create_knowledge_base_tool(vectorstore):
    def run_knowledge_base_tool(query: str) -> str:
        results = vectorstore.similarity_search(query, k=3)
        if results:
            return "\n\n".join([doc.page_content for doc in results])
        else:
            return "No relevant information found in the knowledge base."
    return Tool(
        name="Knowledge Base Tool",
        func=run_knowledge_base_tool,
        description="Useful for searching through the knowledge base using similarity search."
    )

knowledge_base_tool = create_knowledge_base_tool(vectorstore)

# Declare global variables
documents_list = []
knowledge_content = ""

# Load knowledge documents to include in the task description
def load_knowledge_documents():
    global documents_list
    contents = []
    for doc in documents_list:
        contents.append(doc.page_content)
    return "\n\n".join(contents) if contents else "No knowledge documents available."

# Function to get the list of filenames in the knowledge base
def get_knowledge_base_files():
    files = os.listdir("uploads") if os.path.exists("uploads") else []
    return files

# Define Agents using LangChain
# Boss Agent - Uses LLM directly to decide which agent to delegate to
def create_boss_agent():
    boss_llm = Ollama(model="llama3.1")
    return boss_llm

# Internet Research Agent
def create_internet_research_agent():
    # Create the tools
    tools = [
        Tool(
            name="DuckDuckGo Search",
            func=duckduckgo_search.run,
            description="Useful for answering questions about current events or the web."
        ),
        Tool(
            name="Wikipedia",
            func=wikipedia.run,
            description="Useful for accessing information from Wikipedia."
        ),
     ]

    # Create the prompt
    prefix = "Answer the following question using the provided tools."
    suffix = "\nQuestion: {input}\n\n{agent_scratchpad}"
    prompt = ZeroShotAgent.create_prompt(
        tools=tools,
        prefix=prefix,
        suffix=suffix,
        input_variables=["input", "agent_scratchpad"]
    )

    # Create the LLM chain
    llm = Ollama(model="llama3.1")
    llm_chain = LLMChain(llm=llm, prompt=prompt)

    tool_names = [tool.name for tool in tools]

    agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)

    agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)

    return agent_executor

# Knowledge Base Agent
def create_knowledge_base_agent():
    # Create the tools
    tools = [knowledge_base_tool]

    # Create the prompt
    prefix = "Answer the following question using the knowledge base tool."
    suffix = "\nQuestion: {input}\n\n{agent_scratchpad}"
    prompt = ZeroShotAgent.create_prompt(
        tools=tools,
        prefix=prefix,
        suffix=suffix,
        input_variables=["input", "agent_scratchpad"]
    )

    # Create the LLM chain
    llm = Ollama(model="llama3.1")
    llm_chain = LLMChain(llm=llm, prompt=prompt)

    tool_names = [tool.name for tool in tools]

    agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)

    agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)

    return agent_executor

# Response Writer Agent - Uses LLM to generate the final response
def create_response_writer_agent():
    response_llm = Ollama(model="llama3.1")
    return response_llm

# Functions for processing documents and clearing the database
def process_documents(files):
    global vectorstore
    global knowledge_content
    global documents_list

    if not files:
        return "No new documents to process.", get_processed_files()

    processed_files = []
    skipped_files = []

    for file in files:
        file_path = file.name
        destination_path = os.path.join("uploads", os.path.basename(file_path))

        if os.path.exists(destination_path):
            skipped_files.append(os.path.basename(file_path))
        else:
            try:
                shutil.move(file_path, destination_path)
                processed_files.append(os.path.basename(file_path))
            except Exception as e:
                return f"Error moving file: {str(e)}", get_processed_files()

    if processed_files:
        loader = DirectoryLoader("uploads", glob="**/*")
        documents = loader.load()
        documents_list.extend(documents)  # Store documents globally
        text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=200, length_function=len)
        docs = text_splitter.split_documents(documents)

        vectorstore.add_documents(docs)

    # Update the knowledge content after processing new documents
    knowledge_content = load_knowledge_documents()

    result = f"Processed {len(processed_files)} new document(s)."
    if skipped_files:
        result += f"\nSkipped {len(skipped_files)} existing file(s): {', '.join(skipped_files)}"
        gr.Warning(f"The following files already exist and were not processed: {', '.join(skipped_files)}")

    return result, get_processed_files()

def clear_database():
    global vectorstore
    global knowledge_content
    global documents_list

    try:
        vectorstore = None
        shutil.rmtree("uploads")
        os.makedirs("uploads", exist_ok=True)
        shutil.rmtree(persist_directory)
        os.makedirs(persist_directory, exist_ok=True)
    except Exception as e:
        print(f"Error clearing database: {e}")

    # Reinitialize the vectorstore and clear knowledge content
    vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)
    knowledge_content = ""
    documents_list = []

    return "Database and uploaded files cleared.", get_processed_files()

def get_processed_files():
    files = os.listdir("uploads") if os.path.exists("uploads") else []
    return "Processed files:\n" + "\n".join(files) if files else "No files processed yet."

# Function to handle user input and process with the agents
@traceable
def handle_user_input(user_message, history):
    global knowledge_content

    # Initialize the boss LLM
    boss_llm = create_boss_agent()

    boss_prompt = (
        f"As the Boss Agent, decide which agent is best suited to answer the following user question:\n\n"
        f"User question: {user_message}\n\n"
        f"Available agents:\n"
        f"1. Internet Research Agent: Has access to internet tools.\n"
        f"2. Knowledge Base Agent: Has access to the knowledge base containing files: {', '.join(get_knowledge_base_files())}\n"
        f"3. Neither agent can answer the question.\n\n"
        f"Respond with the agent's number (1, 2, or 3) that should handle this task."
    )
    boss_decision = boss_llm.predict(boss_prompt).strip()

    # Determine which agent was selected
    if '1' in boss_decision:
        selected_agent = create_internet_research_agent()
        selected_agent_name = "Internet Research Agent"
    elif '2' in boss_decision:
        selected_agent = create_knowledge_base_agent()
        selected_agent_name = "Knowledge Base Agent"
    else:
        selected_agent = None
        selected_agent_name = "None"

    if selected_agent is not None:
        gathered_info = selected_agent.run(user_message)
    else:
        gathered_info = "I'm sorry, neither agent can answer your question."

    # Response Writer Agent crafts the final answer
    response_llm = create_response_writer_agent()
    response_prompt = (
        f"As the Response Writer Agent, using the following information, write a detailed and informative answer to the user's question.\n\n"
        f"User question: {user_message}\n\n"
        f"Information from {selected_agent_name}:\n{gathered_info}\n\n"
        f"Ensure that the answer is helpful and accurate."
    )
    final_response = response_llm.predict(response_prompt).strip()

    history.append([user_message, final_response])
    return "", history

# Gradio UI setup
with gr.Blocks() as demo:
    gr.Markdown("# Collaborative Agents Chatbot with Boss Agent and Response Writer Agent")

    with gr.Tabs():
        with gr.TabItem("Chat"):
            chatbot = gr.Chatbot()
            msg = gr.Textbox(placeholder="Enter your message...", label="Your Message")
            clear_chat = gr.Button("Clear Chat")

        with gr.TabItem("Knowledge Base"):
            with gr.Row():
                file_output = gr.File(file_count="multiple", label="Upload Documents")
                process_button = gr.Button("Process Documents")
                clear_button = gr.Button("Clear Database")

            process_output = gr.Textbox(label="Processing Output")
            processed_files = gr.Textbox(label="Processed Files", value=get_processed_files())

    # Define interactions
    msg.submit(handle_user_input, [msg, chatbot], [msg, chatbot], queue=False)
    process_button.click(process_documents, inputs=[file_output], outputs=[process_output, processed_files])
    clear_button.click(clear_database, outputs=[process_output, processed_files])
    clear_chat.click(lambda: [], outputs=chatbot, queue=False)

# Launch the Gradio interface
if __name__ == "__main__":
    if not os.path.exists("uploads"):
        os.makedirs("uploads")
    demo.queue().launch()