From 6493b5844fb89a1198f66280e5e82d74c71fe4bb Mon Sep 17 00:00:00 2001 From: Madhu Date: Fri, 13 Dec 2024 07:03:38 +0530 Subject: [PATCH 01/21] committing the project AI Design Agent Team --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a3aa51 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +llm_apps_with_memory_tutorials/openai_swarm_agent_memory \ No newline at end of file From e95972d1027b47e6710ab788ccb07ed739404833 Mon Sep 17 00:00:00 2001 From: Madhu Date: Sun, 15 Dec 2024 04:19:04 +0530 Subject: [PATCH 02/21] New proj with cohere - rag + agent --- .gitignore | 1 - rag_tutorials/rag_agent_cohere/README.md | 68 ++++ .../rag_agent_cohere/rag_agent_cohere.py | 356 ++++++++++++++++++ .../rag_agent_cohere/requirements.txt | 7 + 4 files changed, 431 insertions(+), 1 deletion(-) delete mode 100644 .gitignore create mode 100644 rag_tutorials/rag_agent_cohere/README.md create mode 100644 rag_tutorials/rag_agent_cohere/rag_agent_cohere.py create mode 100644 rag_tutorials/rag_agent_cohere/requirements.txt diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8a3aa51..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -llm_apps_with_memory_tutorials/openai_swarm_agent_memory \ No newline at end of file diff --git a/rag_tutorials/rag_agent_cohere/README.md b/rag_tutorials/rag_agent_cohere/README.md new file mode 100644 index 0000000..192dd0e --- /dev/null +++ b/rag_tutorials/rag_agent_cohere/README.md @@ -0,0 +1,68 @@ +# RAG Agent with Cohere 🤖 + +A RAG Agentic system built with Cohere's new model Command-r7b-12-2024, Qdrant for vector storage, Langchain for RAG and LangGraph for orchestration. This application allows users to upload documents, ask questions about them, and get AI-powered responses with fallback to web search when needed. + +## Demo + + + +## Features + +- **Document Processing** + - PDF document upload and processing + - Automatic text chunking and embedding + - Vector storage in Qdrant cloud + +- **Intelligent Querying** + - RAG-based document retrieval + - Similarity search with threshold filtering + - Automatic fallback to web search when no relevant documents found + - Source attribution for answers + +- **Advanced Capabilities** + - DuckDuckGo web search integration + - LangGraph agent for web research + - Context-aware response generation + - Long answer summarization + +- **Model Specific Features** + - Command-r7b-12-2024 model for Chat and RAG + - cohere embed-english-v3.0 model for embeddings + - create_react_agent function from langgraph + - DuckDuckGoSearchRun tool for web search + +## Prerequisites + +### 1. Cohere API Key +1. Go to [Cohere Platform](https://dashboard.cohere.ai/api-keys) +2. Sign up or log in to your account +3. Navigate to API Keys section +4. Create a new API key + +### 2. Qdrant Cloud Setup +1. Visit [Qdrant Cloud](https://cloud.qdrant.io/) +2. Create an account or sign in +3. Create a new cluster +4. Get your credentials: + - Qdrant API Key: Found in API Keys section + - Qdrant URL: Your cluster URL (format: `https://xxx-xxx.aws.cloud.qdrant.io`) + + +## How to Run + +1. Clone the repository: +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd rag_tutorials/rag_agent_cohere +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +```bash +streamlit run rag_agent_cohere.py +``` + + diff --git a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py new file mode 100644 index 0000000..f2aea4b --- /dev/null +++ b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py @@ -0,0 +1,356 @@ +import os +import streamlit as st +from langchain_community.document_loaders import PyPDFLoader +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_cohere import CohereEmbeddings, ChatCohere +from langchain_qdrant import QdrantVectorStore +from qdrant_client import QdrantClient +from qdrant_client.models import Distance, VectorParams +from langchain.chains.combine_documents import create_stuff_documents_chain +from langchain.chains import create_retrieval_chain +from langchain import hub +import tempfile +from langgraph.prebuilt import create_react_agent +from langchain_community.tools import DuckDuckGoSearchRun + + +def init_session_state(): + """Initialize session state variables.""" + if 'api_keys_submitted' not in st.session_state: + st.session_state.api_keys_submitted = False + if 'chat_history' not in st.session_state: + st.session_state.chat_history = [] + if 'vectorstore' not in st.session_state: + st.session_state.vectorstore = None + if 'qdrant_api_key' not in st.session_state: + st.session_state.qdrant_api_key = "" + if 'qdrant_url' not in st.session_state: + st.session_state.qdrant_url = "" + +def sidebar_api_form(): + """Render API credentials form in sidebar.""" + with st.sidebar: + st.header("API Credentials") + + # Show current status + if st.session_state.api_keys_submitted: + st.success("API credentials verified") + if st.button("Reset Credentials"): + st.session_state.clear() + st.rerun() + return True + + # Show API form + with st.form("api_credentials"): + cohere_key = st.text_input("Cohere API Key", type="password") + qdrant_key = st.text_input( + "Qdrant API Key", + type="password", + help="Enter your Qdrant API key" + ) + qdrant_url = st.text_input( + "Qdrant URL", + placeholder="https://xyz-example.eu-central.aws.cloud.qdrant.io:6333", + help="Enter your Qdrant instance URL" + ) + + if st.form_submit_button("Submit Credentials"): + try: + # First validate the credentials before saving to session state + client = QdrantClient( + url=qdrant_url, + api_key=qdrant_key, + timeout=60 + ) + # Test connection + client.get_collections() + + # Only save to session state after successful validation + st.session_state.cohere_api_key = cohere_key + st.session_state.qdrant_api_key = qdrant_key + st.session_state.qdrant_url = qdrant_url + st.session_state.api_keys_submitted = True + + st.success("Credentials verified!") + st.rerun() + except Exception as e: + st.error(f"Qdrant connection failed: {str(e)}") + return False + +def init_qdrant() -> QdrantClient: + """Initialize Qdrant vector database.""" + if not st.session_state.get("qdrant_api_key"): + raise ValueError("Qdrant API key not provided") + if not st.session_state.get("qdrant_url"): + raise ValueError("Qdrant URL not provided") + + return QdrantClient( + url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key, + timeout=60 + ) + +# Initialize session state +init_session_state() + +# Main application logic +if not sidebar_api_form(): + st.info("Please enter your API credentials in the sidebar to continue.") + st.stop() + +# Initialize services with verified credentials +embedding = CohereEmbeddings( + model="embed-english-v3.0", + cohere_api_key=st.session_state.cohere_api_key +) + +chat_model = ChatCohere( + model="command-r7b-12-2024", + temperature=0.1, + max_tokens=512, + verbose=True, + cohere_api_key=st.session_state.cohere_api_key +) + +client = init_qdrant() + +#document preprocessing + +def process_document(file): + """Process uploaded PDF document using a temporary file.""" + try: + # Create a temporary file + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + tmp_file.write(file.getvalue()) + tmp_path = tmp_file.name + + # Process the temporary file + loader = PyPDFLoader(tmp_path) + documents = loader.load() + text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) + texts = text_splitter.split_documents(documents) + + # Clean up the temporary file + os.unlink(tmp_path) + + return texts + except Exception as e: + st.error(f"Error processing document: {e}") + return [] + +COLLECTION_NAME = "cohere_rag" + +def create_vector_stores(texts): + """Create and populate vector store with documents.""" + try: + # First, create the collection explicitly + try: + client.create_collection( + collection_name=COLLECTION_NAME, + vectors_config=VectorParams( + size=1024, # Dimension for Cohere embed-english-v3.0 + distance=Distance.COSINE + ) + ) + st.success(f"Created new collection: {COLLECTION_NAME}") + except Exception as e: + if "already exists" not in str(e).lower(): + raise e + + # Then initialize the vector store + vector_store = QdrantVectorStore( + client=client, + collection_name=COLLECTION_NAME, + embedding=embedding, + ) + + with st.spinner('Storing documents in Qdrant...'): + vector_store.add_documents(texts) + st.success("Documents successfully stored in Qdrant!") + + return vector_store + + except Exception as e: + st.error(f"Error in vector store creation: {str(e)}") + return None + +def create_fallback_agent(): + """Create a LangGraph agent with DuckDuckGo search tool.""" + + def web_research(query: str) -> str: + """Search the web for information about a query.""" + search = DuckDuckGoSearchRun() + results = search.run(query) + return f"Web search results: {results}" + + tools = [web_research] + + # Create agent with Cohere model + agent = create_react_agent( + chat_model, # Using the already initialized Cohere model + tools=tools, + ) + + return agent + +def process_query(vectorstore, query) -> tuple[str, list]: + """Process a query using RAG with fallback to web search.""" + try: + # First try vector store retrieval + retriever = vectorstore.as_retriever( + search_type="similarity_score_threshold", + search_kwargs={ + "k": 10, + "score_threshold": 0.7 # Only return relevant documents + } + ) + + # Get relevant documents + with st.spinner('Searching document database...'): + relevant_docs = retriever.get_relevant_documents(query) + + if relevant_docs: + # Use RAG with document context + retrieval_qa_prompt = hub.pull("langchain-ai/retrieval-qa-chat") + + combine_docs_chain = create_stuff_documents_chain( + chat_model, + retrieval_qa_prompt + ) + + retrieval_chain = create_retrieval_chain( + retriever, + combine_docs_chain + ) + + with st.spinner('Generating response from documents...'): + response = retrieval_chain.invoke({"input": query}) + if not response or 'answer' not in response: + raise ValueError("No response generated") + + return response['answer'], relevant_docs + else: + # Fallback to web search using LangGraph agent + st.info("No relevant documents found. Searching the web...") + + fallback_agent = create_fallback_agent() + + with st.spinner('Searching web and generating response...'): + # Prepare input for the agent + agent_input = { + "messages": [ + ("user", f"Please search and answer this question: {query}") + ] + } + + # Get agent response + response = fallback_agent.invoke(agent_input) + last_message = response["messages"][-1] + + if isinstance(last_message, tuple): + answer = last_message[1] + else: + answer = last_message.content + + return f"Based on web search: {answer}", [] + + except Exception as e: + st.error(f"Error processing query: {str(e)}") + return "I encountered an error processing your query. Please try again.", [] + +#post processing - strip, summarize along with formatted sources +def post_process(answer, sources): + """Post-process the answer and format sources.""" + answer = answer.strip() + + # Summarize long answers + if len(answer) > 500: + summary_prompt = f"Summarize the following answer in 2-3 sentences: {answer}" + summary = chat_model.invoke(summary_prompt).content # Changed from predict to invoke + answer = f"{summary}\n\nFull Answer: {answer}" + + formatted_sources = [] + for i, source in enumerate(sources, 1): + formatted_source = f"{i}. {source.page_content[:200]}..." + formatted_sources.append(formatted_source) + return answer, formatted_sources + +st.title("RAG Agent with Cohere 🤖") # New heading + +uploaded_file = st.file_uploader("Choose a PDF or Image File", type=["pdf", "jpg", "jpeg"]) + +if uploaded_file is not None: + with st.spinner('Processing file... This may take a while for images.'): + texts = process_document(uploaded_file) + vectorstore = create_vector_stores(texts) + if vectorstore: + st.session_state.vectorstore = vectorstore + st.success('File uploaded and processed successfully!') + else: + st.error('Failed to process file. Please try again.') + +# Display chat history +for message in st.session_state.chat_history: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + +# Chat input +if query := st.chat_input("Ask a question about the document:"): + st.session_state.chat_history.append({"role": "user", "content": query}) + with st.chat_message("user"): + st.markdown(query) + + if st.session_state.vectorstore: + with st.chat_message("assistant"): + try: + answer, sources = process_query(st.session_state.vectorstore, query) + + if sources: # Only post-process if we have sources + processed_answer, formatted_sources = post_process(answer, sources) + else: + processed_answer, formatted_sources = answer, [] + + st.markdown(f"{processed_answer}") + + if formatted_sources: + with st.expander("Sources"): + for source in formatted_sources: + st.markdown(f"- {source}") + + st.session_state.chat_history.append({ + "role": "assistant", + "content": processed_answer + }) + except Exception as e: + st.error(f"Error: {str(e)}") + st.info("Please try asking your question again.") + else: + st.error("Please upload a document first.") + + +# Add to sidebar +with st.sidebar: + st.divider() + col1, col2 = st.columns(2) + with col1: + if st.button('Clear Chat History'): + st.session_state.chat_history = [] + st.rerun() + with col2: + if st.button('Clear All Data'): + try: + # Check if collections exist before deleting + collections = client.get_collections().collections + collection_names = [col.name for col in collections] + + if COLLECTION_NAME in collection_names: + client.delete_collection(COLLECTION_NAME) + if f"{COLLECTION_NAME}_compressed" in collection_names: + client.delete_collection(f"{COLLECTION_NAME}_compressed") + + st.session_state.vectorstore = None + st.session_state.chat_history = [] + st.success("All data cleared successfully!") + st.rerun() + except Exception as e: + st.error(f"Error clearing data: {str(e)}") diff --git a/rag_tutorials/rag_agent_cohere/requirements.txt b/rag_tutorials/rag_agent_cohere/requirements.txt new file mode 100644 index 0000000..827210d --- /dev/null +++ b/rag_tutorials/rag_agent_cohere/requirements.txt @@ -0,0 +1,7 @@ +langgraph>=0.2.53 +langchain>=0.3.11 +langchain-community>=0.0.10 +cohere==5.11.4 +qdrant-client==1.12.1 +duckduckgo-search==4.1.1 +streamlit==1.40.2 \ No newline at end of file From 7e8375831a09c947d46724a9d0ce199487ab27cd Mon Sep 17 00:00:00 2001 From: Madhu Date: Sun, 15 Dec 2024 04:32:04 +0530 Subject: [PATCH 03/21] missing libs --- rag_tutorials/rag_agent_cohere/requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rag_tutorials/rag_agent_cohere/requirements.txt b/rag_tutorials/rag_agent_cohere/requirements.txt index 827210d..f50128a 100644 --- a/rag_tutorials/rag_agent_cohere/requirements.txt +++ b/rag_tutorials/rag_agent_cohere/requirements.txt @@ -4,4 +4,6 @@ langchain-community>=0.0.10 cohere==5.11.4 qdrant-client==1.12.1 duckduckgo-search==4.1.1 -streamlit==1.40.2 \ No newline at end of file +streamlit==1.40.2 +langchain-cohere==0.3.2 +langchain-qdrant==0.2.0 \ No newline at end of file From 93331106d71daeaa4081c50eb97b1e9bb69a9393 Mon Sep 17 00:00:00 2001 From: Madhu Date: Sun, 15 Dec 2024 07:41:59 +0530 Subject: [PATCH 04/21] complete working code --- .../rag_agent_cohere/rag_agent_cohere.py | 235 ++++++++---------- 1 file changed, 99 insertions(+), 136 deletions(-) diff --git a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py index f2aea4b..cf98be2 100644 --- a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py +++ b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py @@ -12,10 +12,14 @@ from langchain import hub import tempfile from langgraph.prebuilt import create_react_agent from langchain_community.tools import DuckDuckGoSearchRun +from typing import TypedDict, List +from langchain_core.language_models import BaseLanguageModel +from langchain_core.messages import AIMessage, HumanMessage, SystemMessage +from time import sleep +from tenacity import retry, wait_exponential, stop_after_attempt def init_session_state(): - """Initialize session state variables.""" if 'api_keys_submitted' not in st.session_state: st.session_state.api_keys_submitted = False if 'chat_history' not in st.session_state: @@ -28,11 +32,9 @@ def init_session_state(): st.session_state.qdrant_url = "" def sidebar_api_form(): - """Render API credentials form in sidebar.""" with st.sidebar: st.header("API Credentials") - # Show current status if st.session_state.api_keys_submitted: st.success("API credentials verified") if st.button("Reset Credentials"): @@ -40,32 +42,18 @@ def sidebar_api_form(): st.rerun() return True - # Show API form with st.form("api_credentials"): cohere_key = st.text_input("Cohere API Key", type="password") - qdrant_key = st.text_input( - "Qdrant API Key", - type="password", - help="Enter your Qdrant API key" - ) - qdrant_url = st.text_input( - "Qdrant URL", - placeholder="https://xyz-example.eu-central.aws.cloud.qdrant.io:6333", - help="Enter your Qdrant instance URL" - ) + qdrant_key = st.text_input("Qdrant API Key", type="password", help="Enter your Qdrant API key") + qdrant_url = st.text_input("Qdrant URL", + placeholder="https://xyz-example.eu-central.aws.cloud.qdrant.io:6333", + help="Enter your Qdrant instance URL") if st.form_submit_button("Submit Credentials"): try: - # First validate the credentials before saving to session state - client = QdrantClient( - url=qdrant_url, - api_key=qdrant_key, - timeout=60 - ) - # Test connection + client = QdrantClient(url=qdrant_url, api_key=qdrant_key, timeout=60) client.get_collections() - # Only save to session state after successful validation st.session_state.cohere_api_key = cohere_key st.session_state.qdrant_api_key = qdrant_key st.session_state.qdrant_url = qdrant_url @@ -78,59 +66,43 @@ def sidebar_api_form(): return False def init_qdrant() -> QdrantClient: - """Initialize Qdrant vector database.""" if not st.session_state.get("qdrant_api_key"): raise ValueError("Qdrant API key not provided") if not st.session_state.get("qdrant_url"): raise ValueError("Qdrant URL not provided") - return QdrantClient( - url=st.session_state.qdrant_url, - api_key=st.session_state.qdrant_api_key, - timeout=60 - ) + return QdrantClient(url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key, + timeout=60) -# Initialize session state init_session_state() -# Main application logic if not sidebar_api_form(): st.info("Please enter your API credentials in the sidebar to continue.") st.stop() -# Initialize services with verified credentials -embedding = CohereEmbeddings( - model="embed-english-v3.0", - cohere_api_key=st.session_state.cohere_api_key -) +embedding = CohereEmbeddings(model="embed-english-v3.0", + cohere_api_key=st.session_state.cohere_api_key) -chat_model = ChatCohere( - model="command-r7b-12-2024", - temperature=0.1, - max_tokens=512, - verbose=True, - cohere_api_key=st.session_state.cohere_api_key -) +chat_model = ChatCohere(model="command-r7b-12-2024", + temperature=0.1, + max_tokens=512, + verbose=True, + cohere_api_key=st.session_state.cohere_api_key) client = init_qdrant() -#document preprocessing - def process_document(file): - """Process uploaded PDF document using a temporary file.""" try: - # Create a temporary file with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: tmp_file.write(file.getvalue()) tmp_path = tmp_file.name - # Process the temporary file loader = PyPDFLoader(tmp_path) documents = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) texts = text_splitter.split_documents(documents) - # Clean up the temporary file os.unlink(tmp_path) return texts @@ -143,26 +115,18 @@ COLLECTION_NAME = "cohere_rag" def create_vector_stores(texts): """Create and populate vector store with documents.""" try: - # First, create the collection explicitly try: - client.create_collection( - collection_name=COLLECTION_NAME, - vectors_config=VectorParams( - size=1024, # Dimension for Cohere embed-english-v3.0 - distance=Distance.COSINE - ) - ) + client.create_collection(collection_name=COLLECTION_NAME, + vectors_config=VectorParams(size=1024, + distance=Distance.COSINE)) st.success(f"Created new collection: {COLLECTION_NAME}") except Exception as e: if "already exists" not in str(e).lower(): raise e - # Then initialize the vector store - vector_store = QdrantVectorStore( - client=client, - collection_name=COLLECTION_NAME, - embedding=embedding, - ) + vector_store = QdrantVectorStore(client=client, + collection_name=COLLECTION_NAME, + embedding=embedding) with st.spinner('Storing documents in Qdrant...'): vector_store.add_documents(texts) @@ -174,91 +138,99 @@ def create_vector_stores(texts): st.error(f"Error in vector store creation: {str(e)}") return None -def create_fallback_agent(): - """Create a LangGraph agent with DuckDuckGo search tool.""" +# Define the state schema using TypedDict +class AgentState(TypedDict): + """State schema for the agent.""" + messages: List[HumanMessage | AIMessage | SystemMessage] + is_last_step: bool + +class RateLimitedDuckDuckGo(DuckDuckGoSearchRun): + @retry(wait=wait_exponential(multiplier=1, min=4, max=10), + stop=stop_after_attempt(3)) + def run(self, query: str) -> str: + """Run search with rate limiting.""" + try: + sleep(2) # Add delay between requests + return super().run(query) + except Exception as e: + if "Ratelimit" in str(e): + sleep(5) # Longer delay on rate limit + return super().run(query) + raise e + +def create_fallback_agent(chat_model: BaseLanguageModel): + """Create a LangGraph agent for web research.""" def web_research(query: str) -> str: - """Search the web for information about a query.""" - search = DuckDuckGoSearchRun() - results = search.run(query) - return f"Web search results: {results}" - + """Web search with result formatting.""" + try: + search = DuckDuckGoSearchRun(num_results=5) + results = search.run(query) + return results + except Exception as e: + return f"Search failed: {str(e)}. Providing answer based on general knowledge." + tools = [web_research] - # Create agent with Cohere model - agent = create_react_agent( - chat_model, # Using the already initialized Cohere model - tools=tools, - ) + agent = create_react_agent(model=chat_model, + tools=tools, + debug=False) return agent def process_query(vectorstore, query) -> tuple[str, list]: """Process a query using RAG with fallback to web search.""" try: - # First try vector store retrieval retriever = vectorstore.as_retriever( search_type="similarity_score_threshold", search_kwargs={ "k": 10, - "score_threshold": 0.7 # Only return relevant documents + "score_threshold": 0.7 } ) - # Get relevant documents - with st.spinner('Searching document database...'): - relevant_docs = retriever.get_relevant_documents(query) + relevant_docs = retriever.get_relevant_documents(query) if relevant_docs: - # Use RAG with document context retrieval_qa_prompt = hub.pull("langchain-ai/retrieval-qa-chat") + combine_docs_chain = create_stuff_documents_chain(chat_model, retrieval_qa_prompt) + retrieval_chain = create_retrieval_chain(retriever, combine_docs_chain) + response = retrieval_chain.invoke({"input": query}) + return response['answer'], relevant_docs - combine_docs_chain = create_stuff_documents_chain( - chat_model, - retrieval_qa_prompt - ) - - retrieval_chain = create_retrieval_chain( - retriever, - combine_docs_chain - ) - - with st.spinner('Generating response from documents...'): - response = retrieval_chain.invoke({"input": query}) - if not response or 'answer' not in response: - raise ValueError("No response generated") - - return response['answer'], relevant_docs else: - # Fallback to web search using LangGraph agent - st.info("No relevant documents found. Searching the web...") + st.info("No relevant documents found. Searching web...") + fallback_agent = create_fallback_agent(chat_model) - fallback_agent = create_fallback_agent() - - with st.spinner('Searching web and generating response...'): - # Prepare input for the agent + with st.spinner('Researching...'): agent_input = { "messages": [ - ("user", f"Please search and answer this question: {query}") - ] + HumanMessage(content=f"""Please thoroughly research the question: '{query}' and provide a detailed and comprehensive response. Make sure to gather the latest information from credible sources. Minimum 400 words.""") + ], + "is_last_step": False } - # Get agent response - response = fallback_agent.invoke(agent_input) - last_message = response["messages"][-1] + config = {"recursion_limit": 100} - if isinstance(last_message, tuple): - answer = last_message[1] - else: - answer = last_message.content - - return f"Based on web search: {answer}", [] + try: + response = fallback_agent.invoke(agent_input, config=config) + + if isinstance(response, dict) and "messages" in response: + last_message = response["messages"][-1] + answer = last_message.content if hasattr(last_message, 'content') else str(last_message) + + return f"""Comprehensive Research Results: +{answer} +""", [] + + except Exception as agent_error: + fallback_response = chat_model.invoke(f"Please provide a general answer to: {query}").content + return f"Web search unavailable. General response: {fallback_response}", [] except Exception as e: - st.error(f"Error processing query: {str(e)}") - return "I encountered an error processing your query. Please try again.", [] + st.error(f"Error: {str(e)}") + return "I encountered an error. Please try rephrasing your question.", [] -#post processing - strip, summarize along with formatted sources def post_process(answer, sources): """Post-process the answer and format sources.""" answer = answer.strip() @@ -266,7 +238,7 @@ def post_process(answer, sources): # Summarize long answers if len(answer) > 500: summary_prompt = f"Summarize the following answer in 2-3 sentences: {answer}" - summary = chat_model.invoke(summary_prompt).content # Changed from predict to invoke + summary = chat_model.invoke(summary_prompt).content answer = f"{summary}\n\nFull Answer: {answer}" formatted_sources = [] @@ -275,26 +247,25 @@ def post_process(answer, sources): formatted_sources.append(formatted_source) return answer, formatted_sources -st.title("RAG Agent with Cohere 🤖") # New heading +st.title("RAG Agent with Cohere 🤖") uploaded_file = st.file_uploader("Choose a PDF or Image File", type=["pdf", "jpg", "jpeg"]) -if uploaded_file is not None: +if uploaded_file is not None and 'processed_file' not in st.session_state: with st.spinner('Processing file... This may take a while for images.'): texts = process_document(uploaded_file) vectorstore = create_vector_stores(texts) if vectorstore: st.session_state.vectorstore = vectorstore + st.session_state.processed_file = True st.success('File uploaded and processed successfully!') else: st.error('Failed to process file. Please try again.') -# Display chat history for message in st.session_state.chat_history: with st.chat_message(message["role"]): st.markdown(message["content"]) -# Chat input if query := st.chat_input("Ask a question about the document:"): st.session_state.chat_history.append({"role": "user", "content": query}) with st.chat_message("user"): @@ -304,31 +275,24 @@ if query := st.chat_input("Ask a question about the document:"): with st.chat_message("assistant"): try: answer, sources = process_query(st.session_state.vectorstore, query) + st.markdown(answer) - if sources: # Only post-process if we have sources - processed_answer, formatted_sources = post_process(answer, sources) - else: - processed_answer, formatted_sources = answer, [] - - st.markdown(f"{processed_answer}") - - if formatted_sources: + if sources: with st.expander("Sources"): - for source in formatted_sources: - st.markdown(f"- {source}") + for source in sources: + st.markdown(f"- {source.page_content[:200]}...") st.session_state.chat_history.append({ - "role": "assistant", - "content": processed_answer + "role": "assistant", + "content": answer }) + except Exception as e: st.error(f"Error: {str(e)}") st.info("Please try asking your question again.") else: st.error("Please upload a document first.") - -# Add to sidebar with st.sidebar: st.divider() col1, col2 = st.columns(2) @@ -339,7 +303,6 @@ with st.sidebar: with col2: if st.button('Clear All Data'): try: - # Check if collections exist before deleting collections = client.get_collections().collections collection_names = [col.name for col in collections] From 67cd362035c2072880323b986834fa44bf865395 Mon Sep 17 00:00:00 2001 From: Madhu Date: Sun, 15 Dec 2024 07:43:58 +0530 Subject: [PATCH 05/21] library checks --- rag_tutorials/rag_agent_cohere/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rag_tutorials/rag_agent_cohere/requirements.txt b/rag_tutorials/rag_agent_cohere/requirements.txt index f50128a..834543d 100644 --- a/rag_tutorials/rag_agent_cohere/requirements.txt +++ b/rag_tutorials/rag_agent_cohere/requirements.txt @@ -1,6 +1,6 @@ -langgraph>=0.2.53 -langchain>=0.3.11 -langchain-community>=0.0.10 +langgraph==0.2.53 +langchain==0.3.11 +langchain-community==0.0.10 cohere==5.11.4 qdrant-client==1.12.1 duckduckgo-search==4.1.1 From f83854fc3dfffd218028c25c772ef7097a0fff13 Mon Sep 17 00:00:00 2001 From: Madhu Date: Sun, 15 Dec 2024 09:04:34 +0530 Subject: [PATCH 06/21] final changes --- rag_tutorials/rag_agent_cohere/README.md | 2 +- rag_tutorials/rag_agent_cohere/requirements.txt | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/rag_tutorials/rag_agent_cohere/README.md b/rag_tutorials/rag_agent_cohere/README.md index 192dd0e..0fc01bd 100644 --- a/rag_tutorials/rag_agent_cohere/README.md +++ b/rag_tutorials/rag_agent_cohere/README.md @@ -1,4 +1,4 @@ -# RAG Agent with Cohere 🤖 +# RAG Agent with Cohere A RAG Agentic system built with Cohere's new model Command-r7b-12-2024, Qdrant for vector storage, Langchain for RAG and LangGraph for orchestration. This application allows users to upload documents, ask questions about them, and get AI-powered responses with fallback to web search when needed. diff --git a/rag_tutorials/rag_agent_cohere/requirements.txt b/rag_tutorials/rag_agent_cohere/requirements.txt index 834543d..0d69870 100644 --- a/rag_tutorials/rag_agent_cohere/requirements.txt +++ b/rag_tutorials/rag_agent_cohere/requirements.txt @@ -1,9 +1,14 @@ -langgraph==0.2.53 -langchain==0.3.11 -langchain-community==0.0.10 +langchain==0.3.12 +langchain-community==0.3.12 +langchain-core==0.3.25 +langchain-cohere==0.3.2 +langchain-qdrant==0.2.0 cohere==5.11.4 qdrant-client==1.12.1 -duckduckgo-search==4.1.1 +duckduckgo-search==6.4.1 streamlit==1.40.2 -langchain-cohere==0.3.2 -langchain-qdrant==0.2.0 \ No newline at end of file +tenacity==9.0.0 +typing-extensions==4.12.2 +pydantic==2.9.2 +pydantic-core==2.23.4 +langgraph==0.2.53 \ No newline at end of file From a3dfdc0e7044283b602c73876026a5e58553251a Mon Sep 17 00:00:00 2001 From: Madhu Date: Sun, 15 Dec 2024 09:16:43 +0530 Subject: [PATCH 07/21] command R addition --- rag_tutorials/rag_agent_cohere/README.md | 2 +- rag_tutorials/rag_agent_cohere/rag_agent_cohere.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rag_tutorials/rag_agent_cohere/README.md b/rag_tutorials/rag_agent_cohere/README.md index 0fc01bd..1d30b7c 100644 --- a/rag_tutorials/rag_agent_cohere/README.md +++ b/rag_tutorials/rag_agent_cohere/README.md @@ -1,4 +1,4 @@ -# RAG Agent with Cohere +# RAG Agent with Cohere ⌘R A RAG Agentic system built with Cohere's new model Command-r7b-12-2024, Qdrant for vector storage, Langchain for RAG and LangGraph for orchestration. This application allows users to upload documents, ask questions about them, and get AI-powered responses with fallback to web search when needed. diff --git a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py index cf98be2..fb530c2 100644 --- a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py +++ b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py @@ -247,7 +247,7 @@ def post_process(answer, sources): formatted_sources.append(formatted_source) return answer, formatted_sources -st.title("RAG Agent with Cohere 🤖") +st.title("RAG Agent with Cohere ⌘R") uploaded_file = st.file_uploader("Choose a PDF or Image File", type=["pdf", "jpg", "jpeg"]) From 8a2429e1f9a13ee357f167d498140e081ffcf471 Mon Sep 17 00:00:00 2001 From: ShubhamSaboo Date: Sat, 14 Dec 2024 22:26:28 -0600 Subject: [PATCH 08/21] chore: updated README --- rag_tutorials/rag_agent_cohere/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rag_tutorials/rag_agent_cohere/README.md b/rag_tutorials/rag_agent_cohere/README.md index 1d30b7c..e781e23 100644 --- a/rag_tutorials/rag_agent_cohere/README.md +++ b/rag_tutorials/rag_agent_cohere/README.md @@ -2,10 +2,6 @@ A RAG Agentic system built with Cohere's new model Command-r7b-12-2024, Qdrant for vector storage, Langchain for RAG and LangGraph for orchestration. This application allows users to upload documents, ask questions about them, and get AI-powered responses with fallback to web search when needed. -## Demo - - - ## Features - **Document Processing** From dec968bc7b1453a13867e4c57c6955d0fe5bb9e3 Mon Sep 17 00:00:00 2001 From: Madhu Date: Sun, 15 Dec 2024 10:16:56 +0530 Subject: [PATCH 09/21] changess --- rag_tutorials/rag_agent_cohere/rag_agent_cohere.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py index fb530c2..8e26d81 100644 --- a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py +++ b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py @@ -219,7 +219,7 @@ def process_query(vectorstore, query) -> tuple[str, list]: last_message = response["messages"][-1] answer = last_message.content if hasattr(last_message, 'content') else str(last_message) - return f"""Comprehensive Research Results: + return f"""Web Search Result: {answer} """, [] From 69a3f20a8c7080c9c960fdc264d7d8654d5c74ca Mon Sep 17 00:00:00 2001 From: ShubhamSaboo Date: Sun, 15 Dec 2024 21:00:42 -0600 Subject: [PATCH 10/21] Added new demo --- .../multimodal_ai_agent.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 ai_agent_tutorials/gemini_multimodal_agent_demo/multimodal_ai_agent.py diff --git a/ai_agent_tutorials/gemini_multimodal_agent_demo/multimodal_ai_agent.py b/ai_agent_tutorials/gemini_multimodal_agent_demo/multimodal_ai_agent.py new file mode 100644 index 0000000..ce79a64 --- /dev/null +++ b/ai_agent_tutorials/gemini_multimodal_agent_demo/multimodal_ai_agent.py @@ -0,0 +1,33 @@ +from phi.agent import Agent +from phi.model.google import Gemini +from phi.tools.duckduckgo import DuckDuckGo +from google.generativeai import upload_file, get_file +import time + +# 1. Initialize the Multimodal Agent +agent = Agent(model=Gemini(id="gemini-2.0-flash-exp"), tools=[DuckDuckGo()], markdown=True) + +# 2. Image Input +image_url = "https://example.com/sample_image.jpg" + +# 3. Audio Input +audio_file = "sample_audio.mp3" + +# 4. Video Input +video_file = upload_file("sample_video.mp4") +while video_file.state.name == "PROCESSING": + time.sleep(2) + video_file = get_file(video_file.name) + +# 5. Multimodal Query +query = """ +Combine insights from the inputs: +1. **Image**: Describe the scene and its significance. +2. **Audio**: Extract key messages that relate to the visual. +3. **Video**: Look at the video input and provide insights that connect with the image and audio context. +4. **Web Search**: Find the latest updates or events linking all these topics. +Summarize the overall theme or story these inputs convey. +""" + +# 6. Multimodal Agent generates unified response +agent.print_response(query, images=[image_url], audio=audio_file, videos=[video_file], stream=True) \ No newline at end of file From 16709ff6e7eb3ba874af5fe8d8df74d341c7d540 Mon Sep 17 00:00:00 2001 From: Devendra Parihar <54232149+Devparihar5@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:10:31 +0530 Subject: [PATCH 11/21] add missing frameworks - it automatically throws the error for `duckduckgo-search` that it's not installed - but when it comes to `pypdf` it's not showing a related error: only showing an error while processing documents --- ai_agent_tutorials/ai_legal_agent_team/requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ai_agent_tutorials/ai_legal_agent_team/requirements.txt b/ai_agent_tutorials/ai_legal_agent_team/requirements.txt index c9bfc66..35e0aa7 100644 --- a/ai_agent_tutorials/ai_legal_agent_team/requirements.txt +++ b/ai_agent_tutorials/ai_legal_agent_team/requirements.txt @@ -1,5 +1,6 @@ phidata==2.5.33 streamlit==1.40.2 qdrant-client==1.12.1 -openai - \ No newline at end of file +openai +pypdf +duckduckgo-search From e36557a97aaedfc765ef226c2c74becbd8d4e165 Mon Sep 17 00:00:00 2001 From: Boris Sorochkin Date: Mon, 16 Dec 2024 20:43:27 +0100 Subject: [PATCH 12/21] Update journalist_agent to use Newspaper4k instead of v3 --- ai_agent_tutorials/ai_journalist_agent/journalist_agent.py | 2 +- ai_agent_tutorials/ai_journalist_agent/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py b/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py index 48bde3b..3bc8ab5 100644 --- a/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py +++ b/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py @@ -2,7 +2,7 @@ from textwrap import dedent from phi.assistant import Assistant from phi.tools.serpapi_tools import SerpApiTools -from phi.tools.newspaper_toolkit import NewspaperToolkit +from phi.tools.newspaper4k import Newspaper4k as NewspaperToolkit import streamlit as st from phi.llm.openai import OpenAIChat diff --git a/ai_agent_tutorials/ai_journalist_agent/requirements.txt b/ai_agent_tutorials/ai_journalist_agent/requirements.txt index 2316761..56f1049 100644 --- a/ai_agent_tutorials/ai_journalist_agent/requirements.txt +++ b/ai_agent_tutorials/ai_journalist_agent/requirements.txt @@ -2,5 +2,5 @@ streamlit phidata openai google-search-results -newspaper3k +newspaper4k lxml_html_clean \ No newline at end of file From a388bafe80c008b5fd47ea7cfd234aaf6cae5dec Mon Sep 17 00:00:00 2001 From: Madhu Date: Wed, 18 Dec 2024 20:13:15 +0530 Subject: [PATCH 13/21] project - ai medical imaging diagnosis agent --- .../ai_medical_imaging_agent/README.md | 66 ++++++++ .../ai_medical_imaging.py | 157 ++++++++++++++++++ .../ai_medical_imaging_agent/requirements.txt | 5 + 3 files changed, 228 insertions(+) create mode 100644 ai_agent_tutorials/ai_medical_imaging_agent/README.md create mode 100644 ai_agent_tutorials/ai_medical_imaging_agent/ai_medical_imaging.py create mode 100644 ai_agent_tutorials/ai_medical_imaging_agent/requirements.txt diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/README.md b/ai_agent_tutorials/ai_medical_imaging_agent/README.md new file mode 100644 index 0000000..7311c80 --- /dev/null +++ b/ai_agent_tutorials/ai_medical_imaging_agent/README.md @@ -0,0 +1,66 @@ +# Medical Imaging Diagnosis Agent + +A Medical Imaging Diagnosis Agent build on phidata powered by Gemini 2.0 Flash Experimental that provides AI-assisted analysis of medical images of various scans. The agent acts as a medical imaging diagnosis expert to analyze various types of medical images and videos, providing detailed diagnostic insights and explanations. + +## Features + +- **Comprehensive Image Analysis** + - Image Type Identification (X-ray, MRI, CT scan, ultrasound) + - Anatomical Region Detection + - Key Findings and Observations + - Potential Abnormalities Detection + - Image Quality Assessment + - Severity Assessment + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_medical_diagnosis_agent + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Configure API Keys** + - Get Google API key from [Google AI Studio](https://aistudio.google.com) + +3. **Run the Application** + ```bash + streamlit run medical_image_diagnosis.py + ``` + +## Analysis Components + +- **Image Type and Region** + - Identifies imaging modality + - Specifies anatomical region + +- **Key Findings** + - Systematic listing of observations + - Detailed appearance descriptions + - Abnormality highlighting + +- **Diagnostic Assessment** + - Potential diagnoses ranking + - Differential diagnoses + - Severity assessment + +- **Patient-Friendly Explanations** + - Simplified terminology + - Detailed first-principles explanations + - Visual reference points + +## Notes + +- Uses Gemini 2.0 Flash for analysis +- Requires stable internet connection +- API usage costs apply +- For educational and development purposes only +- Not a replacement for professional medical diagnosis + +## Disclaimer + +This tool is for educational and informational purposes only. All analyses should be reviewed by qualified healthcare professionals. Do not make medical decisions based solely on this analysis. \ No newline at end of file diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/ai_medical_imaging.py b/ai_agent_tutorials/ai_medical_imaging_agent/ai_medical_imaging.py new file mode 100644 index 0000000..c42c379 --- /dev/null +++ b/ai_agent_tutorials/ai_medical_imaging_agent/ai_medical_imaging.py @@ -0,0 +1,157 @@ +import os +from PIL import Image +from phi.agent import Agent +from phi.model.google import Gemini +import streamlit as st +from phi.tools.duckduckgo import DuckDuckGo + +if "GOOGLE_API_KEY" not in st.session_state: + st.session_state.GOOGLE_API_KEY = None + +with st.sidebar: + st.title("ℹ️ Configuration") + + if not st.session_state.GOOGLE_API_KEY: + api_key = st.text_input( + "Enter your Google API Key:", + type="password" + ) + st.caption( + "Get your API key from [Google AI Studio]" + "(https://aistudio.google.com/apikey) 🔑" + ) + if api_key: + st.session_state.GOOGLE_API_KEY = api_key + st.success("API Key saved!") + st.rerun() + else: + st.success("API Key is configured") + if st.button("🔄 Reset API Key"): + st.session_state.GOOGLE_API_KEY = None + st.rerun() + + st.info( + "This tool provides AI-powered analysis of medical imaging data using " + "advanced computer vision and radiological expertise." + ) + st.warning( + "⚠DISCLAIMER: This tool is for educational and informational purposes only. " + "All analyses should be reviewed by qualified healthcare professionals. " + "Do not make medical decisions based solely on this analysis." + ) + +medical_agent = Agent( + model=Gemini( + api_key=st.session_state.GOOGLE_API_KEY, + id="gemini-2.0-flash-exp" + ), + tools=[DuckDuckGo()], + markdown=True +) if st.session_state.GOOGLE_API_KEY else None + +if not medical_agent: + st.warning("Please configure your API key in the sidebar to continue") + +# Medical Analysis Query +query = """ +You are a highly skilled medical imaging expert with extensive knowledge in radiology and diagnostic imaging. Analyze the patient's medical image and structure your response as follows: + +### 1. Image Type & Region +- Specify imaging modality (X-ray/MRI/CT/Ultrasound/etc.) +- Identify the patient's anatomical region and positioning +- Comment on image quality and technical adequacy + +### 2. Key Findings +- List primary observations systematically +- Note any abnormalities in the patient's imaging with precise descriptions +- Include measurements and densities where relevant +- Describe location, size, shape, and characteristics +- Rate severity: Normal/Mild/Moderate/Severe + +### 3. Diagnostic Assessment +- Provide primary diagnosis with confidence level +- List differential diagnoses in order of likelihood +- Support each diagnosis with observed evidence from the patient's imaging +- Note any critical or urgent findings + +### 4. Patient-Friendly Explanation +- Explain the findings in simple, clear language that the patient can understand +- Avoid medical jargon or provide clear definitions +- Include visual analogies if helpful +- Address common patient concerns related to these findings + +### 5. Research Context +IMPORTANT: Use the DuckDuckGo search tool to: +- Find recent medical literature about similar cases +- Search for standard treatment protocols +- Provide a list of relevant medical links of them too +- Research any relevant technological advances +- Include 2-3 key references to support your analysis + +Format your response using clear markdown headers and bullet points. Be concise yet thorough. +""" + +st.title("🏥 Medical Imaging Diagnosis Agent") +st.write("Upload a medical image for professional analysis") + +# Create containers for better organization +upload_container = st.container() +image_container = st.container() +analysis_container = st.container() + +with upload_container: + uploaded_file = st.file_uploader( + "Upload Medical Image", + type=["jpg", "jpeg", "png", "dicom"], + help="Supported formats: JPG, JPEG, PNG, DICOM" + ) + +if uploaded_file is not None: + with image_container: + # Center the image using columns + col1, col2, col3 = st.columns([1, 2, 1]) + with col2: + image = Image.open(uploaded_file) + # Calculate aspect ratio for resizing + width, height = image.size + aspect_ratio = width / height + new_width = 500 + new_height = int(new_width / aspect_ratio) + resized_image = image.resize((new_width, new_height)) + + st.image( + resized_image, + caption="Uploaded Medical Image", + use_container_width=True + ) + + analyze_button = st.button( + "🔍 Analyze Image", + type="primary", + use_container_width=True + ) + + with analysis_container: + if analyze_button: + image_path = "temp_medical_image.png" + with open(image_path, "wb") as f: + f.write(uploaded_file.getbuffer()) + + with st.spinner("🔄 Analyzing image... Please wait."): + try: + response = medical_agent.run(query, images=[image_path]) + st.markdown("### 📋 Analysis Results") + st.markdown("---") + st.markdown(response.content) + st.markdown("---") + st.caption( + "Note: This analysis is generated by AI and should be reviewed by " + "a qualified healthcare professional." + ) + except Exception as e: + st.error(f"Analysis error: {e}") + finally: + if os.path.exists(image_path): + os.remove(image_path) +else: + st.info("👆 Please upload a medical image to begin analysis") diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/requirements.txt b/ai_agent_tutorials/ai_medical_imaging_agent/requirements.txt new file mode 100644 index 0000000..69bc025 --- /dev/null +++ b/ai_agent_tutorials/ai_medical_imaging_agent/requirements.txt @@ -0,0 +1,5 @@ +streamlit==1.40.2 +phidata==2.7.3 +Pillow==10.0.0 +duckduckgo-search==6.4.1 +google-generativeai==0.8.3 \ No newline at end of file From a979b2baa59f222caf282648658d882f4ea6687b Mon Sep 17 00:00:00 2001 From: Madhu Date: Wed, 18 Dec 2024 20:17:41 +0530 Subject: [PATCH 14/21] readme changes --- ai_agent_tutorials/ai_medical_imaging_agent/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/README.md b/ai_agent_tutorials/ai_medical_imaging_agent/README.md index 7311c80..7df234d 100644 --- a/ai_agent_tutorials/ai_medical_imaging_agent/README.md +++ b/ai_agent_tutorials/ai_medical_imaging_agent/README.md @@ -10,7 +10,7 @@ A Medical Imaging Diagnosis Agent build on phidata powered by Gemini 2.0 Flash E - Key Findings and Observations - Potential Abnormalities Detection - Image Quality Assessment - - Severity Assessment + - Research and Reference ## How to Run @@ -18,7 +18,7 @@ A Medical Imaging Diagnosis Agent build on phidata powered by Gemini 2.0 Flash E ```bash # Clone the repository git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git - cd ai_medical_diagnosis_agent + cd ai_agent_tutorials/ai_medical_imaging_agent # Install dependencies pip install -r requirements.txt @@ -29,7 +29,7 @@ A Medical Imaging Diagnosis Agent build on phidata powered by Gemini 2.0 Flash E 3. **Run the Application** ```bash - streamlit run medical_image_diagnosis.py + streamlit run ai_medical_imaging.py ``` ## Analysis Components From ad98dc881a23892df92a6ee0448d9f639a4f65ed Mon Sep 17 00:00:00 2001 From: ShubhamSaboo Date: Thu, 19 Dec 2024 20:18:56 -0600 Subject: [PATCH 15/21] Added new demo --- .../multimodal_reasoning_agent.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 ai_agent_tutorials/multimodal_ai_agent/multimodal_reasoning_agent.py diff --git a/ai_agent_tutorials/multimodal_ai_agent/multimodal_reasoning_agent.py b/ai_agent_tutorials/multimodal_ai_agent/multimodal_reasoning_agent.py new file mode 100644 index 0000000..cb12428 --- /dev/null +++ b/ai_agent_tutorials/multimodal_ai_agent/multimodal_reasoning_agent.py @@ -0,0 +1,62 @@ +import streamlit as st +from phi.agent import Agent +from phi.model.google import Gemini +import tempfile +import os + +def main(): + # Set up the reasoning agent + agent = Agent( + model=Gemini(id="gemini-2.0-flash-thinking-exp-1219"), + markdown=True + ) + + # Streamlit app title + st.title("Multimodal Reasoning AI Agent 🧠") + + # Instruction + st.write( + "Upload an image and provide a reasoning-based task for the AI Agent. " + "The AI Agent will analyze the image and respond based on your input." + ) + + # File uploader for image + uploaded_file = st.file_uploader("Upload Image", type=["jpg", "jpeg", "png"]) + + if uploaded_file is not None: + try: + # Save uploaded file to temporary file + with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as tmp_file: + tmp_file.write(uploaded_file.getvalue()) + temp_path = tmp_file.name + + # Display the uploaded image + st.image(uploaded_file, caption="Uploaded Image", use_container_width=True) + + # Input for dynamic task + task_input = st.text_area( + "Enter your task/question for the AI Agent:" + ) + + # Button to process the image and task + if st.button("Analyze Image") and task_input: + with st.spinner("AI is thinking... 🤖"): + try: + # Call the agent with the dynamic task and image path + response = agent.run(task_input, images=[temp_path]) + + # Display the response from the model + st.markdown("### AI Response:") + st.markdown(response.content) + except Exception as e: + st.error(f"An error occurred during analysis: {str(e)}") + finally: + # Clean up temp file + if os.path.exists(temp_path): + os.unlink(temp_path) + + except Exception as e: + st.error(f"An error occurred while processing the image: {str(e)}") + +if __name__ == "__main__": + main() \ No newline at end of file From 42cee9621a6ac5445f100894eba614e47d8860c7 Mon Sep 17 00:00:00 2001 From: Madhu Date: Fri, 20 Dec 2024 12:58:27 +0530 Subject: [PATCH 16/21] initial code --- .../ai_recruitment_agent_team/.gitignore | 4 + .../ai_recruitment_agent_team/README.md | 0 .../ai_recruitment_agent_team.py | 494 ++++++++++++++++++ .../requirements.txt | 0 4 files changed, 498 insertions(+) create mode 100644 ai_agent_tutorials/ai_recruitment_agent_team/.gitignore create mode 100644 ai_agent_tutorials/ai_recruitment_agent_team/README.md create mode 100644 ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py create mode 100644 ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/.gitignore b/ai_agent_tutorials/ai_recruitment_agent_team/.gitignore new file mode 100644 index 0000000..9b845a2 --- /dev/null +++ b/ai_agent_tutorials/ai_recruitment_agent_team/.gitignore @@ -0,0 +1,4 @@ +# Environment variables and secrets +.env +.env.* +*.env \ No newline at end of file diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/README.md b/ai_agent_tutorials/ai_recruitment_agent_team/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py new file mode 100644 index 0000000..5b3c471 --- /dev/null +++ b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py @@ -0,0 +1,494 @@ +from typing import Literal, Tuple, Dict, Optional +import os +import time +import json +import requests +import PyPDF2 +from datetime import datetime, timedelta + +import streamlit as st +from phi.agent import Agent +from phi.model.openai import OpenAIChat +from phi.tools.email import EmailTools +from phi.tools.zoom import ZoomTool +from phi.utils.log import logger +from dotenv import load_dotenv + +load_dotenv() + +# Constants +FROM_EMAIL = "ryomensukuna64@gmail.com" + +ACCOUNT_ID = os.getenv("ZOOM_ACCOUNT_ID") +CLIENT_ID = os.getenv("ZOOM_CLIENT_ID") +CLIENT_SECRET = os.getenv("ZOOM_CLIENT_SECRET") + + + + +class CustomZoomTool(ZoomTool): + def __init__( + self, + account_id: Optional[str] = None, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + name: str = "zoom_tool", + ): + super().__init__( + account_id=account_id, + client_id=client_id, + client_secret=client_secret, + name=name + ) + self.token_url = "https://zoom.us/oauth/token" + self.access_token = None + self.token_expires_at = 0 + + def get_access_token(self) -> str: + if self.access_token and time.time() < self.token_expires_at: + return str(self.access_token) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + data = {"grant_type": "account_credentials", "account_id": self.account_id} + + try: + response = requests.post( + self.token_url, + headers=headers, + data=data, + auth=(self.client_id, self.client_secret) # Use basic auth + ) + response.raise_for_status() + + token_info = response.json() + self.access_token = token_info["access_token"] + expires_in = token_info["expires_in"] + self.token_expires_at = time.time() + expires_in - 60 + + # Update this line to use the helper method + self._set_parent_token(str(self.access_token)) + return str(self.access_token) + + except requests.RequestException as e: + logger.error(f"Error fetching access token: {e}") + return "" + + def _set_parent_token(self, token: str) -> None: + """Helper method to set the token in the parent ZoomTool class""" + if token: + self._ZoomTool__access_token = token + + +# Role requirements as a constant dictionary +ROLE_REQUIREMENTS: Dict[str, str] = { + "ai_ml_engineer": """ + Required Skills: + - Python, PyTorch/TensorFlow + - Machine Learning algorithms and frameworks + - Deep Learning and Neural Networks + - Data preprocessing and analysis + - MLOps and model deployment + - RAG, LLM, Finetuning and Prompt Engineering + """, + + "frontend_engineer": """ + Required Skills: + - React/Vue.js/Angular + - HTML5, CSS3, JavaScript/TypeScript + - Responsive design + - State management + - Frontend testing + """, + + "backend_engineer": """ + Required Skills: + - Python/Java/Node.js + - REST APIs + - Database design and management + - System architecture + - Cloud services (AWS/GCP/Azure) + - Kubernetes, Docker, CI/CD + """ +} + + +def init_session_state() -> None: + """Initialize only necessary session state variables.""" + defaults = { + 'candidate_email': "", + 'openai_api_key': "", + 'resume_text': "", + 'analysis_complete': False, + 'is_selected': False + } + for key, value in defaults.items(): + if key not in st.session_state: + st.session_state[key] = value + + +def create_resume_analyzer() -> Agent: + """Creates and returns a resume analysis agent.""" + if not st.session_state.openai_api_key: + st.error("Please enter your OpenAI API key first.") + return None + + return Agent( + model=OpenAIChat( + id="gpt-4o", + api_key=st.session_state.openai_api_key + ), + description="You are an expert technical recruiter who analyzes resumes.", + instructions=[ + "Analyze the resume against the provided job requirements", + "Be lenient with AI/ML candidates who show strong potential", + "Consider project experience as valid experience", + "Value hands-on experience with key technologies", + "Return a JSON response with selection decision and feedback" + ], + markdown=True + ) + +def create_email_agent() -> Agent: + return Agent( + model=OpenAIChat( + id="gpt-4o", + api_key=st.session_state.openai_api_key + ), + tools=[EmailTools( + receiver_email=st.session_state.candidate_email, + sender_email=FROM_EMAIL, + sender_name="AI Recruitment Team", + sender_passkey=os.getenv("EMAIL_PASSKEY") + )], + description="You are a professional recruitment coordinator handling email communications.", + instructions=[ + "Draft and send professional recruitment emails", + "Act like a human writing an email and use all lowercase letters", + "Maintain a friendly yet professional tone", + "Always end emails with exactly: 'best,\nthe ai recruiting team'", + "Never include the sender's or receiver's name in the signature", + "The name of the company is 'AI Recruiting Team'" + ], + markdown=True, + show_tool_calls=True + ) + + +def create_scheduler_agent() -> Agent: + + zoom_tools = CustomZoomTool(account_id=ACCOUNT_ID, client_id=CLIENT_ID, client_secret=CLIENT_SECRET) + + return Agent( + name="Interview Scheduler", + model=OpenAIChat( + id="gpt-4o", + api_key=st.session_state.openai_api_key + ), + tools=[zoom_tools], + description="You are an interview scheduling coordinator.", + instructions=[ + "You are an expert at scheduling technical interviews using Zoom.", + "Schedule interviews during business hours (9 AM - 5 PM EST)", + "Create meetings with proper titles and descriptions", + "Ensure all meeting details are included in responses", + "Use ISO 8601 format for dates", + "Handle scheduling errors gracefully" + ], + markdown=True, + show_tool_calls=True + ) + + +def extract_text_from_pdf(pdf_file) -> str: + try: + pdf_reader = PyPDF2.PdfReader(pdf_file) + text = "" + for page in pdf_reader.pages: + text += page.extract_text() + return text + except Exception as e: + st.error(f"Error extracting PDF text: {str(e)}") + return "" + + +def analyze_resume( + resume_text: str, + role: Literal["ai_ml_engineer", "frontend_engineer", "backend_engineer"], + analyzer: Agent +) -> Tuple[bool, str]: + try: + response = analyzer.run(f""" + Please analyze this resume against the following requirements and provide your response in valid JSON format: + Role Requirements: + {ROLE_REQUIREMENTS[role]} + Resume Text: + {resume_text} + Your response must be a valid JSON object like this: + {{ + "selected": true/false, + "feedback": "Detailed feedback explaining the decision", + "matching_skills": ["skill1", "skill2"], + "missing_skills": ["skill3", "skill4"], + "experience_level": "junior/mid/senior" + }} + Evaluation criteria: + 1. Match at least 70% of required skills + 2. Consider both theoretical knowledge and practical experience + 3. Value project experience and real-world applications + 4. Consider transferable skills from similar technologies + 5. Look for evidence of continuous learning and adaptability + Important: Return ONLY the JSON object without any markdown formatting or backticks. + """) + + # Extract the assistant's message content + assistant_message = None + for message in response.messages: + if message.role == 'assistant': + assistant_message = message.content + break + + if not assistant_message: + raise ValueError("No assistant message found in response.") + result = json.loads(assistant_message.strip()) + + if not isinstance(result, dict): + raise ValueError("Response is not a JSON object") + + if "selected" not in result or "feedback" not in result: + raise ValueError("Missing required fields in response") + + return result["selected"], result["feedback"] + + except (json.JSONDecodeError, ValueError) as e: + st.error(f"Error processing response: {str(e)}") + return False, f"Error analyzing resume: {str(e)}" + + +def send_selection_email(email_agent: Agent, to_email: str, role: str) -> None: + email_agent.run( + f""" + Send an email to {to_email} regarding their selection for the {role} position. + The email should: + 1. Congratulate them on being selected + 2. Explain the next steps in the process + 3. Mention that they will receive interview details shortly + 4. The name of the company is 'AI Recruiting Team' + """ + ) + + +def send_rejection_email(email_agent: Agent, to_email: str, role: str, feedback: str) -> None: + """ + Send a rejection email with constructive feedback. + """ + email_agent.run( + f""" + Send an email to {to_email} regarding their application for the {role} position. + Use this specific style: + 1. use all lowercase letters + 2. be empathetic and human + 3. mention specific feedback from: {feedback} + 4. encourage them to upskill and try again + 5. suggest some learning resources based on missing skills + 6. end the email with exactly: + best, + the ai recruiting team + + Do not include any names in the signature. + The tone should be like a human writing a quick but thoughtful email. + """ + ) + + +def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None: + """ + Basic interview scheduler that creates a Zoom meeting and sends email details. + Schedules interviews during business hours (9 AM - 5 PM EST). + """ + try: + # Calculate tomorrow at 2 PM EST (instead of UTC) + tomorrow = datetime.now() + timedelta(days=1) + interview_time = tomorrow.replace(hour=14, minute=0, second=0, microsecond=0) + + # 1. Schedule the meeting with EST timezone specification + meeting_response = scheduler.run( + f"Schedule a 60-minute meeting titled '{role} Technical Interview' " + f"for tomorrow at 2 PM EST (Eastern Time) 2024 with attendee {candidate_email}. " + f"Ensure the meeting is between 9 AM - 5 PM EST only." + ) + + # 2. Send email notification + email_agent.run( + f"Send an email to {candidate_email} about their scheduled interview for " + f"the {role} position. Include the Zoom meeting link from: {meeting_response}. " + f"Make sure to specify that the time is in EST timezone." + ) + + st.success("Interview scheduled successfully!") + + except Exception as e: + logger.error(f"Error scheduling interview: {str(e)}") + st.error("Unable to schedule interview. Please try again.") + + +def main() -> None: + """Main function to run the Streamlit application.""" + st.title("AI Recruitment System") + + # Initialize session state + init_session_state() + + # Sidebar for API key + with st.sidebar: + st.header("Configuration") + api_key = st.text_input( + "Enter your OpenAI API key", + type="password", + value=st.session_state.openai_api_key, + help="Get your API key from platform.openai.com" + ) + if api_key: + st.session_state.openai_api_key = api_key + + # Main content + if not st.session_state.openai_api_key: + st.warning("Please enter your OpenAI API key in the sidebar to continue.") + return + + # Role selection with requirements display + role = st.selectbox( + "Select the role you're applying for:", + ["ai_ml_engineer", "frontend_engineer", "backend_engineer"] + ) + + # Display requirements for selected role + with st.expander("View Required Skills", expanded=True): + st.markdown(ROLE_REQUIREMENTS[role]) + + # Resume upload and processing + resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"]) + if resume_file and not st.session_state.resume_text: + with st.spinner("Processing your resume..."): + resume_text = extract_text_from_pdf(resume_file) + if resume_text: + st.session_state.resume_text = resume_text + st.success("Resume processed successfully!") + else: + st.error("Could not process the PDF. Please try again.") + + # Email input with session state + email = st.text_input( + "Your email address", + value=st.session_state.candidate_email, + key="email_input" + ) + st.session_state.candidate_email = email + + # Analysis and next steps + if st.session_state.resume_text and email and not st.session_state.analysis_complete: + if st.button("Analyze Resume"): + with st.spinner("Analyzing your resume..."): + resume_analyzer = create_resume_analyzer() + email_agent = create_email_agent() # Create email agent here + + if resume_analyzer and email_agent: + print("DEBUG: Starting resume analysis") + is_selected, feedback = analyze_resume( + st.session_state.resume_text, + role, + resume_analyzer + ) + print(f"DEBUG: Analysis complete - Selected: {is_selected}, Feedback: {feedback}") + + if is_selected: + st.success("Congratulations! Your skills match our requirements.") + st.session_state.analysis_complete = True + st.session_state.is_selected = True + st.rerun() + else: + st.warning("Unfortunately, your skills don't match our requirements.") + st.write(f"Feedback: {feedback}") + + # Send rejection email + with st.spinner("Sending feedback email..."): + try: + send_rejection_email( + email_agent=email_agent, + to_email=email, + role=role, + feedback=feedback + ) + st.info("We've sent you an email with detailed feedback.") + except Exception as e: + logger.error(f"Error sending rejection email: {e}") + st.error("Could not send feedback email. Please try again.") + + if st.session_state.get('analysis_complete') and st.session_state.get('is_selected', False): + st.success("Congratulations! Your skills match our requirements.") + st.info("Click 'Proceed with Application' to continue with the interview process.") + + if st.button("Proceed with Application", key="proceed_button"): + print("DEBUG: Proceed button clicked") # Debug + with st.spinner("🔄 Processing your application..."): + try: + print("DEBUG: Creating email agent") # Debug + email_agent = create_email_agent() + print(f"DEBUG: Email agent created: {email_agent}") # Debug + + print("DEBUG: Creating scheduler agent") # Debug + scheduler_agent = create_scheduler_agent() + print(f"DEBUG: Scheduler agent created: {scheduler_agent}") # Debug + + # 3. Send selection email + with st.status("📧 Sending confirmation email...", expanded=True) as status: + print(f"DEBUG: Attempting to send email to {st.session_state.candidate_email}") # Debug + send_selection_email( + email_agent, + st.session_state.candidate_email, + role + ) + print("DEBUG: Email sent successfully") # Debug + status.update(label="✅ Confirmation email sent!") + + # 4. Schedule interview + with st.status("📅 Scheduling interview...", expanded=True) as status: + print("DEBUG: Attempting to schedule interview") # Debug + schedule_interview( + scheduler_agent, + st.session_state.candidate_email, + email_agent, + role + ) + print("DEBUG: Interview scheduled successfully") # Debug + status.update(label="✅ Interview scheduled!") + + print("DEBUG: All processes completed successfully") # Debug + st.success(""" + 🎉 Application Successfully Processed! + + Please check your email for: + 1. Selection confirmation ✅ + 2. Interview details with Zoom link 🔗 + + Next steps: + 1. Review the role requirements + 2. Prepare for your technical interview + 3. Join the interview 5 minutes early + """) + + except Exception as e: + print(f"DEBUG: Error occurred: {str(e)}") # Debug + print(f"DEBUG: Error type: {type(e)}") # Debug + import traceback + print(f"DEBUG: Full traceback: {traceback.format_exc()}") # Debug + st.error(f"An error occurred: {str(e)}") + st.error("Please try again or contact support.") + + # Reset button + if st.sidebar.button("Reset Application"): + for key in st.session_state.keys(): + if key != 'openai_api_key': + del st.session_state[key] + st.rerun() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt b/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt new file mode 100644 index 0000000..e69de29 From abc0c10e55954ebcf89d28ec30e13c53f8f6c045 Mon Sep 17 00:00:00 2001 From: Madhu Date: Sat, 21 Dec 2024 11:54:53 +0530 Subject: [PATCH 17/21] final code changes --- .../ai_recruitment_agent_team.py | 260 +++++++++--------- 1 file changed, 137 insertions(+), 123 deletions(-) diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py index 5b3c471..7fbd822 100644 --- a/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py +++ b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py @@ -5,6 +5,7 @@ import json import requests import PyPDF2 from datetime import datetime, timedelta +import pytz import streamlit as st from phi.agent import Agent @@ -13,33 +14,14 @@ from phi.tools.email import EmailTools from phi.tools.zoom import ZoomTool from phi.utils.log import logger from dotenv import load_dotenv +from streamlit_pdf_viewer import pdf_viewer load_dotenv() -# Constants -FROM_EMAIL = "ryomensukuna64@gmail.com" - -ACCOUNT_ID = os.getenv("ZOOM_ACCOUNT_ID") -CLIENT_ID = os.getenv("ZOOM_CLIENT_ID") -CLIENT_SECRET = os.getenv("ZOOM_CLIENT_SECRET") - - - class CustomZoomTool(ZoomTool): - def __init__( - self, - account_id: Optional[str] = None, - client_id: Optional[str] = None, - client_secret: Optional[str] = None, - name: str = "zoom_tool", - ): - super().__init__( - account_id=account_id, - client_id=client_id, - client_secret=client_secret, - name=name - ) + def __init__(self, *, account_id: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, name: str = "zoom_tool"): + super().__init__(account_id=account_id, client_id=client_id, client_secret=client_secret, name=name) self.token_url = "https://zoom.us/oauth/token" self.access_token = None self.token_expires_at = 0 @@ -47,16 +29,12 @@ class CustomZoomTool(ZoomTool): def get_access_token(self) -> str: if self.access_token and time.time() < self.token_expires_at: return str(self.access_token) + headers = {"Content-Type": "application/x-www-form-urlencoded"} data = {"grant_type": "account_credentials", "account_id": self.account_id} try: - response = requests.post( - self.token_url, - headers=headers, - data=data, - auth=(self.client_id, self.client_secret) # Use basic auth - ) + response = requests.post(self.token_url, headers=headers, data=data, auth=(self.client_id, self.client_secret)) response.raise_for_status() token_info = response.json() @@ -64,7 +42,6 @@ class CustomZoomTool(ZoomTool): expires_in = token_info["expires_in"] self.token_expires_at = time.time() + expires_in - 60 - # Update this line to use the helper method self._set_parent_token(str(self.access_token)) return str(self.access_token) @@ -114,11 +91,9 @@ ROLE_REQUIREMENTS: Dict[str, str] = { def init_session_state() -> None: """Initialize only necessary session state variables.""" defaults = { - 'candidate_email': "", - 'openai_api_key': "", - 'resume_text': "", - 'analysis_complete': False, - 'is_selected': False + 'candidate_email': "", 'openai_api_key': "", 'resume_text': "", 'analysis_complete': False, + 'is_selected': False, 'zoom_account_id': "", 'zoom_client_id': "", 'zoom_client_secret': "", + 'email_sender': "", 'email_passkey': "", 'company_name': "" } for key, value in defaults.items(): if key not in st.session_state: @@ -155,9 +130,9 @@ def create_email_agent() -> Agent: ), tools=[EmailTools( receiver_email=st.session_state.candidate_email, - sender_email=FROM_EMAIL, - sender_name="AI Recruitment Team", - sender_passkey=os.getenv("EMAIL_PASSKEY") + sender_email=st.session_state.email_sender, + sender_name=st.session_state.company_name, + sender_passkey=st.session_state.email_passkey )], description="You are a professional recruitment coordinator handling email communications.", instructions=[ @@ -166,7 +141,7 @@ def create_email_agent() -> Agent: "Maintain a friendly yet professional tone", "Always end emails with exactly: 'best,\nthe ai recruiting team'", "Never include the sender's or receiver's name in the signature", - "The name of the company is 'AI Recruiting Team'" + f"The name of the company is '{st.session_state.company_name}'" ], markdown=True, show_tool_calls=True @@ -174,8 +149,11 @@ def create_email_agent() -> Agent: def create_scheduler_agent() -> Agent: - - zoom_tools = CustomZoomTool(account_id=ACCOUNT_ID, client_id=CLIENT_ID, client_secret=CLIENT_SECRET) + zoom_tools = CustomZoomTool( + account_id=st.session_state.zoom_account_id, + client_id=st.session_state.zoom_client_id, + client_secret=st.session_state.zoom_client_secret + ) return Agent( name="Interview Scheduler", @@ -216,45 +194,37 @@ def analyze_resume( analyzer: Agent ) -> Tuple[bool, str]: try: - response = analyzer.run(f""" - Please analyze this resume against the following requirements and provide your response in valid JSON format: - Role Requirements: - {ROLE_REQUIREMENTS[role]} - Resume Text: - {resume_text} - Your response must be a valid JSON object like this: - {{ - "selected": true/false, - "feedback": "Detailed feedback explaining the decision", - "matching_skills": ["skill1", "skill2"], - "missing_skills": ["skill3", "skill4"], - "experience_level": "junior/mid/senior" - }} - Evaluation criteria: - 1. Match at least 70% of required skills - 2. Consider both theoretical knowledge and practical experience - 3. Value project experience and real-world applications - 4. Consider transferable skills from similar technologies - 5. Look for evidence of continuous learning and adaptability - Important: Return ONLY the JSON object without any markdown formatting or backticks. - """) - - # Extract the assistant's message content - assistant_message = None - for message in response.messages: - if message.role == 'assistant': - assistant_message = message.content - break + response = analyzer.run( + f"""Please analyze this resume against the following requirements and provide your response in valid JSON format: + Role Requirements: + {ROLE_REQUIREMENTS[role]} + Resume Text: + {resume_text} + Your response must be a valid JSON object like this: + {{ + "selected": true/false, + "feedback": "Detailed feedback explaining the decision", + "matching_skills": ["skill1", "skill2"], + "missing_skills": ["skill3", "skill4"], + "experience_level": "junior/mid/senior" + }} + Evaluation criteria: + 1. Match at least 70% of required skills + 2. Consider both theoretical knowledge and practical experience + 3. Value project experience and real-world applications + 4. Consider transferable skills from similar technologies + 5. Look for evidence of continuous learning and adaptability + Important: Return ONLY the JSON object without any markdown formatting or backticks. + """ + ) + assistant_message = next((msg.content for msg in response.messages if msg.role == 'assistant'), None) if not assistant_message: raise ValueError("No assistant message found in response.") + result = json.loads(assistant_message.strip()) - - if not isinstance(result, dict): - raise ValueError("Response is not a JSON object") - - if "selected" not in result or "feedback" not in result: - raise ValueError("Missing required fields in response") + if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback"]): + raise ValueError("Invalid response format") return result["selected"], result["feedback"] @@ -301,29 +271,45 @@ def send_rejection_email(email_agent: Agent, to_email: str, role: str, feedback: def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None: """ - Basic interview scheduler that creates a Zoom meeting and sends email details. - Schedules interviews during business hours (9 AM - 5 PM EST). + Schedule interviews during business hours (9 AM - 5 PM IST). """ try: - # Calculate tomorrow at 2 PM EST (instead of UTC) - tomorrow = datetime.now() + timedelta(days=1) - interview_time = tomorrow.replace(hour=14, minute=0, second=0, microsecond=0) - - # 1. Schedule the meeting with EST timezone specification + # Get current time in IST + ist_tz = pytz.timezone('Asia/Kolkata') + current_time_ist = datetime.now(ist_tz) + + tomorrow_ist = current_time_ist + timedelta(days=1) + interview_time = tomorrow_ist.replace(hour=11, minute=0, second=0, microsecond=0) + formatted_time = interview_time.strftime('%Y-%m-%dT%H:%M:%S') + meeting_response = scheduler.run( - f"Schedule a 60-minute meeting titled '{role} Technical Interview' " - f"for tomorrow at 2 PM EST (Eastern Time) 2024 with attendee {candidate_email}. " - f"Ensure the meeting is between 9 AM - 5 PM EST only." + f"""Schedule a 60-minute technical interview with these specifications: + - Title: '{role} Technical Interview' + - Date: {formatted_time} + - Timezone: IST (India Standard Time) + - Attendee: {candidate_email} + + Important Notes: + - The meeting must be between 9 AM - 5 PM IST + - Use IST (UTC+5:30) timezone for all communications + - Include timezone information in the meeting details + """ ) - - # 2. Send email notification + email_agent.run( - f"Send an email to {candidate_email} about their scheduled interview for " - f"the {role} position. Include the Zoom meeting link from: {meeting_response}. " - f"Make sure to specify that the time is in EST timezone." + f"""Send an interview confirmation email with these details: + - Role: {role} position + - Meeting Details: {meeting_response} + + Important: + - Clearly specify that the time is in IST (India Standard Time) + - Ask the candidate to join 5 minutes early + - Include timezone conversion link if possible + - Ask him to be confident and not so nervous and prepare well for the interview + """ ) - st.success("Interview scheduled successfully!") + st.success("Interview scheduled successfully! Check your email for details.") except Exception as e: logger.error(f"Error scheduling interview: {str(e)}") @@ -331,53 +317,81 @@ def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agen def main() -> None: - """Main function to run the Streamlit application.""" st.title("AI Recruitment System") - # Initialize session state init_session_state() - - # Sidebar for API key with st.sidebar: st.header("Configuration") - api_key = st.text_input( - "Enter your OpenAI API key", - type="password", - value=st.session_state.openai_api_key, - help="Get your API key from platform.openai.com" - ) - if api_key: - st.session_state.openai_api_key = api_key + + # OpenAI Configuration + st.subheader("OpenAI Settings") + api_key = st.text_input("OpenAI API Key", type="password", value=st.session_state.openai_api_key, help="Get your API key from platform.openai.com") + if api_key: st.session_state.openai_api_key = api_key + + st.subheader("Zoom Settings") + zoom_account_id = st.text_input("Zoom Account ID", type="password", value=st.session_state.zoom_account_id) + zoom_client_id = st.text_input("Zoom Client ID", type="password", value=st.session_state.zoom_client_id) + zoom_client_secret = st.text_input("Zoom Client Secret", type="password", value=st.session_state.zoom_client_secret) + + st.subheader("Email Settings") + email_sender = st.text_input("Sender Email", value=st.session_state.email_sender, help="Email address to send from") + email_passkey = st.text_input("Email App Password", type="password", value=st.session_state.email_passkey, help="App-specific password for email") + company_name = st.text_input("Company Name", value=st.session_state.company_name, help="Name to use in email communications") + + if zoom_account_id: st.session_state.zoom_account_id = zoom_account_id + if zoom_client_id: st.session_state.zoom_client_id = zoom_client_id + if zoom_client_secret: st.session_state.zoom_client_secret = zoom_client_secret + if email_sender: st.session_state.email_sender = email_sender + if email_passkey: st.session_state.email_passkey = email_passkey + if company_name: st.session_state.company_name = company_name + + required_configs = {'OpenAI API Key': st.session_state.openai_api_key, 'Zoom Account ID': st.session_state.zoom_account_id, + 'Zoom Client ID': st.session_state.zoom_client_id, 'Zoom Client Secret': st.session_state.zoom_client_secret, + 'Email Sender': st.session_state.email_sender, 'Email Password': st.session_state.email_passkey, + 'Company Name': st.session_state.company_name} + + missing_configs = [k for k, v in required_configs.items() if not v] + if missing_configs: + st.warning(f"Please configure the following in the sidebar: {', '.join(missing_configs)}") + return - # Main content if not st.session_state.openai_api_key: st.warning("Please enter your OpenAI API key in the sidebar to continue.") return - # Role selection with requirements display - role = st.selectbox( - "Select the role you're applying for:", - ["ai_ml_engineer", "frontend_engineer", "backend_engineer"] - ) + role = st.selectbox("Select the role you're applying for:", ["ai_ml_engineer", "frontend_engineer", "backend_engineer"]) + with st.expander("View Required Skills", expanded=True): st.markdown(ROLE_REQUIREMENTS[role]) - # Display requirements for selected role - with st.expander("View Required Skills", expanded=True): - st.markdown(ROLE_REQUIREMENTS[role]) - - # Resume upload and processing resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"]) - if resume_file and not st.session_state.resume_text: - with st.spinner("Processing your resume..."): - resume_text = extract_text_from_pdf(resume_file) - if resume_text: - st.session_state.resume_text = resume_text - st.success("Resume processed successfully!") - else: - st.error("Could not process the PDF. Please try again.") + if resume_file: + st.subheader("Uploaded Resume") + col1, col2 = st.columns([4, 1]) + + with col1: + import tempfile, os + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + tmp_file.write(resume_file.read()) + tmp_file_path = tmp_file.name + resume_file.seek(0) + try: pdf_viewer(tmp_file_path) + finally: os.unlink(tmp_file_path) + + with col2: + st.download_button(label="📥 Download", data=resume_file, file_name=resume_file.name, mime="application/pdf") + if st.button("🗑️ Clear"): st.session_state.resume_text = ""; st.rerun() + # Process the resume text + if not st.session_state.resume_text: + with st.spinner("Processing your resume..."): + resume_text = extract_text_from_pdf(resume_file) + if resume_text: + st.session_state.resume_text = resume_text + st.success("Resume processed successfully!") + else: + st.error("Could not process the PDF. Please try again.") # Email input with session state email = st.text_input( - "Your email address", + "Candidate's email address", value=st.session_state.candidate_email, key="email_input" ) From 0504c5c288a19e0d788f3921c6dfbb7f15f27000 Mon Sep 17 00:00:00 2001 From: Madhu Date: Sat, 21 Dec 2024 12:27:05 +0530 Subject: [PATCH 18/21] added the readme, code, requirements.txt --- .../ai_recruitment_agent_team/.gitignore | 4 - .../ai_recruitment_agent_team/README.md | 103 ++++++++++++++++++ .../ai_recruitment_agent_team.py | 23 +++- .../requirements.txt | 12 ++ 4 files changed, 133 insertions(+), 9 deletions(-) delete mode 100644 ai_agent_tutorials/ai_recruitment_agent_team/.gitignore diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/.gitignore b/ai_agent_tutorials/ai_recruitment_agent_team/.gitignore deleted file mode 100644 index 9b845a2..0000000 --- a/ai_agent_tutorials/ai_recruitment_agent_team/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Environment variables and secrets -.env -.env.* -*.env \ No newline at end of file diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/README.md b/ai_agent_tutorials/ai_recruitment_agent_team/README.md index e69de29..0d99d09 100644 --- a/ai_agent_tutorials/ai_recruitment_agent_team/README.md +++ b/ai_agent_tutorials/ai_recruitment_agent_team/README.md @@ -0,0 +1,103 @@ +# AI Recruitment Agent Team + +An Agentic recruitment system built on phidata and Streamlitthat automates the technical hiring proces which helps the lives of recruiters easy. The agent team consists of multiple specialized agents working together to handle resume analysis, interview scheduling with zoom and candidate communications. + +## Demo + +## Features + +- **Automated Resume Analysis** + - Skills Matching based on the role requirements - [AI/ML Engineer, Frontend Engineer, Backend Engineer] + - Experience Assessment- If the resume clears 70% of the requirements, the candidate is selected for the next round + +- **Automated Communications** + - Acceptance Email and a Technical Interview Email + - Rejection Feedback + - Interview Scheduling with Zoom + +- **Intelligent Scheduling** + - Automated Zoom Meeting Setup + - Timezone Management + - Calendar Integration + - Reminder System + +## Important Things to do before running the application + +- Create/Use a new Gmail account for the recruiter +- Enable 2-Step Verification and generate an App Password for the Gmail account +- The App Password is a 16 digit code (use without spaces) that should be generated here - [Google App Password](https://support.google.com/accounts/answer/185833?hl=en) Please go through the steps to generate the password - it will of the format - 'afec wejf awoj fwrv' (remove the spaces and enter it in the streamlit app) +- Create/ Use a Zoom account and go to the Zoom App Marketplace to get the API credentials : +[Zoom Marketplace](https://marketplace.zoom.us) +- Go to Developer Dashboard and create a new app - Select Server to Server OAuth and get the credentials, You see 3 credentials - Client ID, Client Secret and Account ID +- After that, you need to add a few scopes to the app - so that the zoom link of the candidate is sent and created through the mail. +- The Scopes are meeting:write:invite_links:admin, meeting:write:meeting:admin, meeting:write:meeting:master, meeting:write:invite_links:master, meeting:write:open_app:admin, user:read:email:admin, user:read:list_users:admin, billing:read:user_entitlement:admin, dashboard:read:list_meeting_participants:admin [last 3 are optional] + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_recruitment_agent_team + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Configure API Keys** + - OpenAI API key for GPT-4o access + - Zoom API credentials (Account ID, Client ID, Client Secret) + - Email App Password of Recruiter's Email + +3. **Run the Application** + ```bash + streamlit run ai_recruitment_agent_team.py + ``` + +## System Components + +- **Resume Analyzer Agent** + - Skills matching algorithm + - Experience verification + - Technical assessment + - Selection decision making + +- **Email Communication Agent** + - Professional email drafting + - Automated notifications + - Feedback communication + - Follow-up management + +- **Interview Scheduler Agent** + - Zoom meeting coordination + - Calendar management + - Timezone handling + - Reminder system + +- **Candidate Experience** + - Simple upload interface + - Real-time feedback + - Clear communication + - Streamlined process + +## Technical Stack + +- **Framework**: Phidata +- **Model**: OpenAI GPT-4o +- **Integration**: Zoom API, EmailTools Tool from Phidata +- **PDF Processing**: PyPDF2 +- **Time Management**: pytz +- **State Management**: Streamlit Session State + + +## Disclaimer + +This tool is designed to assist in the recruitment process but should not completely replace human judgment in hiring decisions. All automated decisions should be reviewed by human recruiters for final approval. + +## Future Enhancements + +- Integration with ATS systems +- Advanced candidate scoring +- Video interview capabilities +- Skills assessment integration +- Multi-language support diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py index 7fbd822..aa63971 100644 --- a/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py +++ b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py @@ -13,10 +13,8 @@ from phi.model.openai import OpenAIChat from phi.tools.email import EmailTools from phi.tools.zoom import ZoomTool from phi.utils.log import logger -from dotenv import load_dotenv from streamlit_pdf_viewer import pdf_viewer -load_dotenv() class CustomZoomTool(ZoomTool): @@ -93,7 +91,7 @@ def init_session_state() -> None: defaults = { 'candidate_email': "", 'openai_api_key': "", 'resume_text': "", 'analysis_complete': False, 'is_selected': False, 'zoom_account_id': "", 'zoom_client_id': "", 'zoom_client_secret': "", - 'email_sender': "", 'email_passkey': "", 'company_name': "" + 'email_sender': "", 'email_passkey': "", 'company_name': "", 'current_pdf': None } for key, value in defaults.items(): if key not in st.session_state: @@ -362,7 +360,23 @@ def main() -> None: role = st.selectbox("Select the role you're applying for:", ["ai_ml_engineer", "frontend_engineer", "backend_engineer"]) with st.expander("View Required Skills", expanded=True): st.markdown(ROLE_REQUIREMENTS[role]) - resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"]) + # Add a "New Application" button before the resume upload + if st.button("📝 New Application"): + # Clear only the application-related states + keys_to_clear = ['resume_text', 'analysis_complete', 'is_selected', 'candidate_email', 'current_pdf'] + for key in keys_to_clear: + if key in st.session_state: + st.session_state[key] = None if key == 'current_pdf' else "" + st.rerun() + + resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"], key="resume_uploader") + if resume_file is not None and resume_file != st.session_state.get('current_pdf'): + st.session_state.current_pdf = resume_file + st.session_state.resume_text = "" + st.session_state.analysis_complete = False + st.session_state.is_selected = False + st.rerun() + if resume_file: st.subheader("Uploaded Resume") col1, col2 = st.columns([4, 1]) @@ -378,7 +392,6 @@ def main() -> None: with col2: st.download_button(label="📥 Download", data=resume_file, file_name=resume_file.name, mime="application/pdf") - if st.button("🗑️ Clear"): st.session_state.resume_text = ""; st.rerun() # Process the resume text if not st.session_state.resume_text: with st.spinner("Processing your resume..."): diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt b/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt index e69de29..ac1d845 100644 --- a/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt +++ b/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt @@ -0,0 +1,12 @@ +# Core dependencies +phidata==2.7.3 +streamlit==1.40.2 +PyPDF2==3.0.1 +streamlit-pdf-viewer==0.0.19 +requests==2.32.3 +pytz==2023.4 +typing-extensions>=4.9.0 + +# Optional but recommended +black>=24.1.1 # for code formatting +python-dateutil>=2.8.2 # for date parsing From f4337970c1e683a7c29266ed078277db56aa8817 Mon Sep 17 00:00:00 2001 From: Tammy-228 <137596789+Tammy-228@users.noreply.github.com> Date: Sun, 22 Dec 2024 21:49:13 +1100 Subject: [PATCH 19/21] Update README.md --- ai_agent_tutorials/multimodal_ai_agent/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ai_agent_tutorials/multimodal_ai_agent/README.md b/ai_agent_tutorials/multimodal_ai_agent/README.md index bec4ebd..1cb9b4d 100644 --- a/ai_agent_tutorials/multimodal_ai_agent/README.md +++ b/ai_agent_tutorials/multimodal_ai_agent/README.md @@ -16,7 +16,7 @@ A Streamlit application that combines video analysis and web search capabilities ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git -cd multimodal_ai_agents +cd ai_agent_tutorials/multimodal_ai_agent ``` 2. Install the required dependencies: @@ -36,4 +36,4 @@ GOOGLE_API_KEY=your_api_key_here 5. Run the Streamlit App ```bash streamlit run multimodal_agent.py -``` \ No newline at end of file +``` From ba01d1227bc76e2b891e1fcf3ee69cdd90abec58 Mon Sep 17 00:00:00 2001 From: ShubhamSaboo Date: Tue, 24 Dec 2024 20:21:05 -0600 Subject: [PATCH 20/21] chore: updated ai_recruitment_agent_team README --- .../ai_recruitment_agent_team/README.md | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/README.md b/ai_agent_tutorials/ai_recruitment_agent_team/README.md index 0d99d09..980251a 100644 --- a/ai_agent_tutorials/ai_recruitment_agent_team/README.md +++ b/ai_agent_tutorials/ai_recruitment_agent_team/README.md @@ -1,25 +1,23 @@ # AI Recruitment Agent Team -An Agentic recruitment system built on phidata and Streamlitthat automates the technical hiring proces which helps the lives of recruiters easy. The agent team consists of multiple specialized agents working together to handle resume analysis, interview scheduling with zoom and candidate communications. - -## Demo +A Streamlit application that simulates a full-service recruitment team using multiple AI agents to automate and streamline the hiring process. Each agent represents a different recruitment specialist role - from resume analysis and candidate evaluation to interview scheduling and communication - working together to provide comprehensive hiring solutions. The system combines the expertise of technical recruiters, HR coordinators, and scheduling specialists into a cohesive automated workflow. ## Features -- **Automated Resume Analysis** - - Skills Matching based on the role requirements - [AI/ML Engineer, Frontend Engineer, Backend Engineer] - - Experience Assessment- If the resume clears 70% of the requirements, the candidate is selected for the next round +#### Specialized AI Agents -- **Automated Communications** - - Acceptance Email and a Technical Interview Email - - Rejection Feedback - - Interview Scheduling with Zoom +- Technical Recruiter Agent: Analyzes resumes and evaluates technical skills +- Communication Agent: Handles professional email correspondence +- Scheduling Coordinator Agent: Manages interview scheduling and coordination +- Each agent has specific expertise and collaborates for comprehensive recruitment -- **Intelligent Scheduling** - - Automated Zoom Meeting Setup - - Timezone Management - - Calendar Integration - - Reminder System + +#### End-to-End Recruitment Process +- Automated resume screening and analysis +- Role-specific technical evaluation +- Professional candidate communication +- Automated interview scheduling +- Integrated feedback system ## Important Things to do before running the application From 7af3c3c2c3fea02a58d185cd3b9e8eafbce0719e Mon Sep 17 00:00:00 2001 From: ShubhamSaboo Date: Wed, 25 Dec 2024 11:07:49 -0600 Subject: [PATCH 21/21] chore: updated README --- README.md | 2 ++ ai_agent_tutorials/ai_medical_imaging_agent/README.md | 2 +- ai_agent_tutorials/ai_recruitment_agent_team/README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b614f5c..5031bd8 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,14 @@ A curated collection of awesome LLM apps built with RAG and AI agents. This repo - [💼 AI Customer Support Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_customer_support_agent) - [📈 AI Investment Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_investment_agent) - [👨‍⚖️ AI Legal Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_legal_agent_team) +- [💼 AI Recruitment Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_recruitment_agent_team) - [👨‍💼 AI Services Agency](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_services_agency) - [🏋️‍♂️ AI Health & Fitness Planner Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_health_fitness_agent) - [📈 AI Startup Trend Analysis Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_startup_trend_analysis_agent) - [🗞️ AI Journalist Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_journalist_agent) - [💲 AI Finance Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_finance_agent_team) - [💰 AI Personal Finance Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_personal_finance_agent) +- [🩻 AI Medical Scan Diagnosis Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_medical_imaging_agent) - [🛫 AI Travel Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_travel_agent) - [🎬 AI Movie Production Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_movie_production_agent) - [📰 Multi-Agent AI Researcher](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/multi_agent_researcher) diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/README.md b/ai_agent_tutorials/ai_medical_imaging_agent/README.md index 7df234d..3a4ca7c 100644 --- a/ai_agent_tutorials/ai_medical_imaging_agent/README.md +++ b/ai_agent_tutorials/ai_medical_imaging_agent/README.md @@ -1,4 +1,4 @@ -# Medical Imaging Diagnosis Agent +# 🩻 Medical Imaging Diagnosis Agent A Medical Imaging Diagnosis Agent build on phidata powered by Gemini 2.0 Flash Experimental that provides AI-assisted analysis of medical images of various scans. The agent acts as a medical imaging diagnosis expert to analyze various types of medical images and videos, providing detailed diagnostic insights and explanations. diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/README.md b/ai_agent_tutorials/ai_recruitment_agent_team/README.md index 980251a..684988b 100644 --- a/ai_agent_tutorials/ai_recruitment_agent_team/README.md +++ b/ai_agent_tutorials/ai_recruitment_agent_team/README.md @@ -1,4 +1,4 @@ -# AI Recruitment Agent Team +# 💼 AI Recruitment Agent Team A Streamlit application that simulates a full-service recruitment team using multiple AI agents to automate and streamline the hiring process. Each agent represents a different recruitment specialist role - from resume analysis and candidate evaluation to interview scheduling and communication - working together to provide comprehensive hiring solutions. The system combines the expertise of technical recruiters, HR coordinators, and scheduling specialists into a cohesive automated workflow.