Merge branch 'main' into update-docs
This commit is contained in:
commit
d0785ccfc7
16 changed files with 1363 additions and 6 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ streamlit
|
|||
phidata
|
||||
openai
|
||||
google-search-results
|
||||
newspaper3k
|
||||
newspaper4k
|
||||
lxml_html_clean
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
phidata==2.5.33
|
||||
streamlit==1.40.2
|
||||
qdrant-client==1.12.1
|
||||
openai
|
||||
|
||||
openai
|
||||
pypdf
|
||||
duckduckgo-search
|
||||
|
|
|
|||
66
ai_agent_tutorials/ai_medical_imaging_agent/README.md
Normal file
66
ai_agent_tutorials/ai_medical_imaging_agent/README.md
Normal file
|
|
@ -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
|
||||
- Research and Reference
|
||||
|
||||
## 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_medical_imaging_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 ai_medical_imaging.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.
|
||||
|
|
@ -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")
|
||||
|
|
@ -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
|
||||
101
ai_agent_tutorials/ai_recruitment_agent_team/README.md
Normal file
101
ai_agent_tutorials/ai_recruitment_agent_team/README.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# 💼 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.
|
||||
|
||||
## Features
|
||||
|
||||
#### Specialized AI Agents
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
#### 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
|
||||
|
||||
- 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
|
||||
|
|
@ -0,0 +1,521 @@
|
|||
from typing import Literal, Tuple, Dict, Optional
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import requests
|
||||
import PyPDF2
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
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 streamlit_pdf_viewer import pdf_viewer
|
||||
|
||||
|
||||
|
||||
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))
|
||||
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
|
||||
|
||||
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, 'zoom_account_id': "", 'zoom_client_id': "", 'zoom_client_secret': "",
|
||||
'email_sender': "", 'email_passkey': "", 'company_name': "", 'current_pdf': None
|
||||
}
|
||||
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=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=[
|
||||
"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",
|
||||
f"The name of the company is '{st.session_state.company_name}'"
|
||||
],
|
||||
markdown=True,
|
||||
show_tool_calls=True
|
||||
)
|
||||
|
||||
|
||||
def create_scheduler_agent() -> Agent:
|
||||
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",
|
||||
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.
|
||||
"""
|
||||
)
|
||||
|
||||
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) or not all(k in result for k in ["selected", "feedback"]):
|
||||
raise ValueError("Invalid response format")
|
||||
|
||||
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:
|
||||
"""
|
||||
Schedule interviews during business hours (9 AM - 5 PM IST).
|
||||
"""
|
||||
try:
|
||||
# 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 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
|
||||
"""
|
||||
)
|
||||
|
||||
email_agent.run(
|
||||
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! Check your email for details.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error scheduling interview: {str(e)}")
|
||||
st.error("Unable to schedule interview. Please try again.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
st.title("AI Recruitment System")
|
||||
|
||||
init_session_state()
|
||||
with st.sidebar:
|
||||
st.header("Configuration")
|
||||
|
||||
# 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
|
||||
|
||||
if not st.session_state.openai_api_key:
|
||||
st.warning("Please enter your OpenAI API key in the sidebar to continue.")
|
||||
return
|
||||
|
||||
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])
|
||||
|
||||
# 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])
|
||||
|
||||
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")
|
||||
# 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(
|
||||
"Candidate's 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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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 awesome-llm-apps/ai_agent_tutorials/multimodal_ai_agent
|
||||
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
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
64
rag_tutorials/rag_agent_cohere/README.md
Normal file
64
rag_tutorials/rag_agent_cohere/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# 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.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
|
||||
319
rag_tutorials/rag_agent_cohere/rag_agent_cohere.py
Normal file
319
rag_tutorials/rag_agent_cohere/rag_agent_cohere.py
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
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
|
||||
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():
|
||||
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():
|
||||
with st.sidebar:
|
||||
st.header("API Credentials")
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
client = QdrantClient(url=qdrant_url, api_key=qdrant_key, timeout=60)
|
||||
client.get_collections()
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
init_session_state()
|
||||
|
||||
if not sidebar_api_form():
|
||||
st.info("Please enter your API credentials in the sidebar to continue.")
|
||||
st.stop()
|
||||
|
||||
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()
|
||||
|
||||
def process_document(file):
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
|
||||
tmp_file.write(file.getvalue())
|
||||
tmp_path = tmp_file.name
|
||||
|
||||
loader = PyPDFLoader(tmp_path)
|
||||
documents = loader.load()
|
||||
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
|
||||
texts = text_splitter.split_documents(documents)
|
||||
|
||||
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:
|
||||
try:
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 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:
|
||||
"""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]
|
||||
|
||||
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:
|
||||
retriever = vectorstore.as_retriever(
|
||||
search_type="similarity_score_threshold",
|
||||
search_kwargs={
|
||||
"k": 10,
|
||||
"score_threshold": 0.7
|
||||
}
|
||||
)
|
||||
|
||||
relevant_docs = retriever.get_relevant_documents(query)
|
||||
|
||||
if relevant_docs:
|
||||
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
|
||||
|
||||
else:
|
||||
st.info("No relevant documents found. Searching web...")
|
||||
fallback_agent = create_fallback_agent(chat_model)
|
||||
|
||||
with st.spinner('Researching...'):
|
||||
agent_input = {
|
||||
"messages": [
|
||||
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
|
||||
}
|
||||
|
||||
config = {"recursion_limit": 100}
|
||||
|
||||
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"""Web Search Result:
|
||||
{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: {str(e)}")
|
||||
return "I encountered an error. Please try rephrasing your question.", []
|
||||
|
||||
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
|
||||
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 ⌘R")
|
||||
|
||||
uploaded_file = st.file_uploader("Choose a PDF or Image File", type=["pdf", "jpg", "jpeg"])
|
||||
|
||||
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.')
|
||||
|
||||
for message in st.session_state.chat_history:
|
||||
with st.chat_message(message["role"]):
|
||||
st.markdown(message["content"])
|
||||
|
||||
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)
|
||||
st.markdown(answer)
|
||||
|
||||
if sources:
|
||||
with st.expander("Sources"):
|
||||
for source in sources:
|
||||
st.markdown(f"- {source.page_content[:200]}...")
|
||||
|
||||
st.session_state.chat_history.append({
|
||||
"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.")
|
||||
|
||||
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:
|
||||
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)}")
|
||||
14
rag_tutorials/rag_agent_cohere/requirements.txt
Normal file
14
rag_tutorials/rag_agent_cohere/requirements.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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==6.4.1
|
||||
streamlit==1.40.2
|
||||
tenacity==9.0.0
|
||||
typing-extensions==4.12.2
|
||||
pydantic==2.9.2
|
||||
pydantic-core==2.23.4
|
||||
langgraph==0.2.53
|
||||
Loading…
Reference in a new issue