Spaces:
Sleeping
Sleeping
Commit
·
f0aeabd
0
Parent(s):
Add application file
Browse files- .gitignore +3 -0
- README.md +12 -0
- app.py +146 -0
- llm_services/__init__.py +0 -0
- llm_services/__pycache__/__init__.cpython-312.pyc +0 -0
- llm_services/__pycache__/agentbase.cpython-312.pyc +0 -0
- llm_services/__pycache__/agenthub.cpython-312.pyc +0 -0
- llm_services/__pycache__/llm.cpython-312.pyc +0 -0
- llm_services/__pycache__/prompts.cpython-312.pyc +0 -0
- llm_services/__pycache__/tools.cpython-312.pyc +0 -0
- llm_services/agentbase.py +34 -0
- llm_services/agenthub.py +28 -0
- llm_services/llm.py +68 -0
- llm_services/prompts.py +45 -0
- llm_services/tools.py +123 -0
- modals/__init__.py +0 -0
- modals/__pycache__/__init__.cpython-312.pyc +0 -0
- modals/__pycache__/inputs.cpython-312.pyc +0 -0
- modals/inputs.py +6 -0
- requirements.txt +73 -0
.gitignore
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
venv/
|
2 |
+
venv
|
3 |
+
venv/*
|
README.md
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Talent Recommender
|
2 |
+
|
3 |
+
A Streamlit application that uses AI to recommend talent based on natural language requests.
|
4 |
+
|
5 |
+
## Features
|
6 |
+
- Natural language processing for brand requests
|
7 |
+
- Displays matched talent profiles in card and table views
|
8 |
+
- Easy to use interface
|
9 |
+
|
10 |
+
## How to use
|
11 |
+
1. Enter your talent requirements in natural language
|
12 |
+
2. Click "Find Talent" to see matching profiles
|
app.py
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
from llm_services.agenthub import recommend_talent_agent
|
4 |
+
from llm_services.tools import recommend_talent_tool
|
5 |
+
|
6 |
+
st.set_page_config(
|
7 |
+
page_title="Talent Recommender",
|
8 |
+
page_icon="🎯",
|
9 |
+
layout="wide"
|
10 |
+
)
|
11 |
+
|
12 |
+
st.markdown("""
|
13 |
+
<style>
|
14 |
+
.profile-card {
|
15 |
+
background-color: #f8f9fa;
|
16 |
+
border-radius: 10px;
|
17 |
+
padding: 20px;
|
18 |
+
margin-bottom: 20px;
|
19 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
20 |
+
}
|
21 |
+
.metrics-container {
|
22 |
+
display: flex;
|
23 |
+
justify-content: space-between;
|
24 |
+
margin-top: 15px;
|
25 |
+
}
|
26 |
+
.metric-item {
|
27 |
+
text-align: center;
|
28 |
+
padding: 10px;
|
29 |
+
border-radius: 5px;
|
30 |
+
background-color: #e9ecef;
|
31 |
+
}
|
32 |
+
.header-container {
|
33 |
+
padding: 1.5rem;
|
34 |
+
background: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
|
35 |
+
color: white;
|
36 |
+
border-radius: 10px;
|
37 |
+
margin-bottom: 2rem;
|
38 |
+
}
|
39 |
+
</style>
|
40 |
+
""", unsafe_allow_html=True)
|
41 |
+
|
42 |
+
st.markdown("""
|
43 |
+
<div class="header-container">
|
44 |
+
<h1 style="text-align: center;">Talent Recommender</h1>
|
45 |
+
<p style="text-align: center; font-size: 1.2rem;">Find the perfect influencer match for your brand</p>
|
46 |
+
</div>
|
47 |
+
""", unsafe_allow_html=True)
|
48 |
+
|
49 |
+
if 'search_history' not in st.session_state:
|
50 |
+
st.session_state.search_history = []
|
51 |
+
|
52 |
+
st.markdown("### What kind of talent are you looking for?")
|
53 |
+
brand_request = st.text_area(
|
54 |
+
"Describe your needs in natural language",
|
55 |
+
placeholder="e.g., We need financial advisors with high engagement to promote our investment app to professionals aged 30-50",
|
56 |
+
height=120
|
57 |
+
)
|
58 |
+
|
59 |
+
search_button = st.button("Find Talent", type="primary")
|
60 |
+
|
61 |
+
if search_button and brand_request:
|
62 |
+
if brand_request not in st.session_state.search_history:
|
63 |
+
st.session_state.search_history.append(brand_request)
|
64 |
+
|
65 |
+
with st.spinner("Finding the perfect talent matches..."):
|
66 |
+
try:
|
67 |
+
search_args = recommend_talent_agent(brand_request=brand_request)
|
68 |
+
|
69 |
+
with st.expander("Search Parameters", expanded=False):
|
70 |
+
st.json(search_args)
|
71 |
+
|
72 |
+
profiles = recommend_talent_tool(**search_args)
|
73 |
+
|
74 |
+
st.subheader(f"Found {len(profiles)} Matching Profiles")
|
75 |
+
|
76 |
+
tab1, tab2 = st.tabs(["Cards View", "Table View"])
|
77 |
+
|
78 |
+
with tab1:
|
79 |
+
for i, profile in enumerate(profiles):
|
80 |
+
with st.container():
|
81 |
+
st.markdown(f"""
|
82 |
+
<div class="profile-card">
|
83 |
+
<h3>{profile['name']}</h3>
|
84 |
+
<p><strong>Age:</strong> {profile['age']} | <strong>Gender:</strong> {profile['gender']}</p>
|
85 |
+
<p><strong>Verticals:</strong> {', '.join(profile['verticals'])}</p>
|
86 |
+
<p><strong>Bio:</strong> {profile['bio']}</p>
|
87 |
+
<div class="metrics-container">
|
88 |
+
<div class="metric-item">
|
89 |
+
<p style="margin:0; font-weight:bold;">{profile['follower_count']:,}</p>
|
90 |
+
<p style="margin:0; font-size:0.8rem;">Followers</p>
|
91 |
+
</div>
|
92 |
+
<div class="metric-item">
|
93 |
+
<p style="margin:0; font-weight:bold;">{profile['overall_engagement']:.1%}</p>
|
94 |
+
<p style="margin:0; font-size:0.8rem;">Engagement</p>
|
95 |
+
</div>
|
96 |
+
</div>
|
97 |
+
</div>
|
98 |
+
""", unsafe_allow_html=True)
|
99 |
+
|
100 |
+
|
101 |
+
with tab2:
|
102 |
+
table_data = []
|
103 |
+
for profile in profiles:
|
104 |
+
table_data.append({
|
105 |
+
"Name": profile['name'],
|
106 |
+
"Age": profile['age'],
|
107 |
+
"Gender": profile['gender'],
|
108 |
+
"Verticals": ", ".join(profile['verticals']),
|
109 |
+
"Followers": profile['follower_count'],
|
110 |
+
"Engagement": f"{profile['overall_engagement']:.1%}"
|
111 |
+
})
|
112 |
+
|
113 |
+
df = pd.DataFrame(table_data)
|
114 |
+
st.dataframe(
|
115 |
+
df,
|
116 |
+
use_container_width=True,
|
117 |
+
hide_index=True
|
118 |
+
)
|
119 |
+
|
120 |
+
|
121 |
+
except Exception as e:
|
122 |
+
st.error(f"An error occurred: {str(e)}")
|
123 |
+
st.info("Please try refining your request or check your connection.")
|
124 |
+
|
125 |
+
else:
|
126 |
+
if st.session_state.search_history:
|
127 |
+
st.markdown("### Recent Searches")
|
128 |
+
for idx, search in enumerate(st.session_state.search_history[-3:]):
|
129 |
+
if st.button(f"{search}", key=f"history_{idx}"):
|
130 |
+
brand_request = search
|
131 |
+
st.experimental_rerun()
|
132 |
+
|
133 |
+
st.markdown("""
|
134 |
+
### How to use this tool:
|
135 |
+
|
136 |
+
Simply describe what kind of talent you're looking for in natural language. Our AI will analyze your request and find the most suitable matches from our database.
|
137 |
+
|
138 |
+
**Example:** "We need financial advisors with high engagement rates to promote our new investment app targeting professionals aged 35-55."
|
139 |
+
""")
|
140 |
+
|
141 |
+
st.markdown("---")
|
142 |
+
st.markdown("""
|
143 |
+
<p style="text-align: center; color: #6c757d; font-size: 0.8rem;">
|
144 |
+
Talent Recommender v1.0 | Powered by AI | © 2025
|
145 |
+
</p>
|
146 |
+
""", unsafe_allow_html=True)
|
llm_services/__init__.py
ADDED
File without changes
|
llm_services/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (163 Bytes). View file
|
|
llm_services/__pycache__/agentbase.cpython-312.pyc
ADDED
Binary file (1.79 kB). View file
|
|
llm_services/__pycache__/agenthub.cpython-312.pyc
ADDED
Binary file (1.42 kB). View file
|
|
llm_services/__pycache__/llm.cpython-312.pyc
ADDED
Binary file (3.28 kB). View file
|
|
llm_services/__pycache__/prompts.cpython-312.pyc
ADDED
Binary file (2.11 kB). View file
|
|
llm_services/__pycache__/tools.cpython-312.pyc
ADDED
Binary file (4.5 kB). View file
|
|
llm_services/agentbase.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from .llm import LLM
|
3 |
+
from modals.inputs import LLMConfig
|
4 |
+
from typing import List, Dict, Any
|
5 |
+
|
6 |
+
|
7 |
+
class AgentBase(LLM):
|
8 |
+
def __init__(self, system_prompt=None, llm_config: LLMConfig = None):
|
9 |
+
super().__init__()
|
10 |
+
self.llm_config = llm_config
|
11 |
+
self.system_prompt = system_prompt
|
12 |
+
|
13 |
+
def generate_response(self, messages: List[Dict[str, str]] = None):
|
14 |
+
if self.system_prompt:
|
15 |
+
messages = [{"role": "system", "content": self.system_prompt}] + messages
|
16 |
+
output = self.step(messages=messages, llm_config=self.llm_config)
|
17 |
+
return output
|
18 |
+
|
19 |
+
|
20 |
+
if __name__ == "__main__":
|
21 |
+
llm_config = LLMConfig(
|
22 |
+
api_key="AIzaSyCsstACK4dJx61ad2_fhWugtvCcEDcTiTE",
|
23 |
+
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
|
24 |
+
model="gemini-2.0-flash",
|
25 |
+
)
|
26 |
+
|
27 |
+
messages = [
|
28 |
+
{"role": "system", "content": "You are a helpful AI assistant."},
|
29 |
+
{"role": "user", "content": "Tell me a fun fact about space."}
|
30 |
+
]
|
31 |
+
|
32 |
+
agent = AgentBase(llm_config=llm_config)
|
33 |
+
response = agent.generate_response(messages=messages)
|
34 |
+
print(response)
|
llm_services/agenthub.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .agentbase import AgentBase
|
2 |
+
from .prompts import RECOMMENDATION_AGENT_PROMPT
|
3 |
+
from modals.inputs import LLMConfig
|
4 |
+
from .tools import recommend_talent_tool
|
5 |
+
|
6 |
+
|
7 |
+
llm_config = LLMConfig(
|
8 |
+
api_key="AIzaSyCOIgSPUdsfLcrQTwIN61W5Me5lv1krOr8",
|
9 |
+
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
|
10 |
+
model="gemini-2.0-flash",
|
11 |
+
)
|
12 |
+
|
13 |
+
def recommend_talent_agent(brand_request):
|
14 |
+
messages = [{'role': 'system', 'content': RECOMMENDATION_AGENT_PROMPT}, {'role': 'user', 'content': f'Brand Request: {brand_request}'}]
|
15 |
+
agent = AgentBase(llm_config=llm_config)
|
16 |
+
parsed_response = None
|
17 |
+
if not isinstance(parsed_response, dict):
|
18 |
+
response = agent.generate_response(messages=messages)
|
19 |
+
parsed_response = agent.parse_json(response)
|
20 |
+
return parsed_response
|
21 |
+
|
22 |
+
|
23 |
+
if __name__ == "__main__":
|
24 |
+
brand_request = "Find me young male fitness bloggers with a medium-sized following"
|
25 |
+
search_tool_args = recommend_talent_agent(brand_request)
|
26 |
+
print(search_tool_args)
|
27 |
+
print(recommend_talent_tool(**search_tool_args))
|
28 |
+
|
llm_services/llm.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from openai import OpenAI
|
3 |
+
import logging
|
4 |
+
from typing import List, Dict, Any
|
5 |
+
import json
|
6 |
+
import time
|
7 |
+
from modals.inputs import LLMConfig
|
8 |
+
|
9 |
+
|
10 |
+
class LLM:
|
11 |
+
def __init__(self, max_retries: int = 3, retry_delay: float = 1.0):
|
12 |
+
self.max_retries = max_retries
|
13 |
+
self.retry_delay = retry_delay
|
14 |
+
|
15 |
+
def parse_json(self, response: str) -> Dict[str, Any]:
|
16 |
+
import re
|
17 |
+
match = re.search(r'```json\s*(\{.*?\}|\[.*?\])\s*```', response, re.DOTALL)
|
18 |
+
if match:
|
19 |
+
json_str = match.group(1)
|
20 |
+
try:
|
21 |
+
parsed_json = json.loads(json_str)
|
22 |
+
return parsed_json
|
23 |
+
except json.JSONDecodeError as e:
|
24 |
+
raise
|
25 |
+
raise
|
26 |
+
|
27 |
+
def step(self, messages: List[Dict[str, str]] = None, llm_config: LLMConfig = None) -> str:
|
28 |
+
messages = messages or []
|
29 |
+
llm = OpenAI(
|
30 |
+
api_key=llm_config.api_key,
|
31 |
+
base_url=llm_config.base_url
|
32 |
+
)
|
33 |
+
for attempt in range(self.max_retries):
|
34 |
+
try:
|
35 |
+
response = llm.chat.completions.create(
|
36 |
+
model=llm_config.model,
|
37 |
+
messages=messages,
|
38 |
+
temperature=0.2
|
39 |
+
)
|
40 |
+
return response.choices[0].message.content
|
41 |
+
except Exception as e:
|
42 |
+
logging.error(f"Error in LLM step (attempt {attempt + 1}/{self.max_retries}): {e}")
|
43 |
+
if attempt < self.max_retries - 1:
|
44 |
+
time.sleep(self.retry_delay * (2 ** attempt)) # Exponential backoff
|
45 |
+
else:
|
46 |
+
raise
|
47 |
+
|
48 |
+
if __name__ == "__main__":
|
49 |
+
# llm_config = LLMConfig(
|
50 |
+
# base_url="https://openrouter.ai/api/v1",
|
51 |
+
# api_key="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",
|
52 |
+
# model="openai/gpt-4o-mini"
|
53 |
+
# )
|
54 |
+
|
55 |
+
llm_config = LLMConfig(
|
56 |
+
api_key="AIzaSyCsstACK4dJx61ad2_fhWugtvCcEDcTiTE",
|
57 |
+
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
|
58 |
+
model="gemini-2.0-flash",
|
59 |
+
)
|
60 |
+
|
61 |
+
messages = [
|
62 |
+
{"role": "system", "content": "You are a helpful AI assistant."},
|
63 |
+
{"role": "user", "content": "Tell me a fun fact about space."}
|
64 |
+
]
|
65 |
+
|
66 |
+
llm = LLM()
|
67 |
+
response = llm.step(messages, llm_config)
|
68 |
+
print(response)
|
llm_services/prompts.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
RECOMMENDATION_AGENT_PROMPT = """
|
2 |
+
You are a helpful assistant whose role is to recommend social media profiles based on user requests. You analyze the user's query and translate it into structured search parameters.
|
3 |
+
|
4 |
+
## Guidelines for Parameter Interpretation:
|
5 |
+
|
6 |
+
### Follower Counts:
|
7 |
+
- "Micro" or "small": 1,000-10,000 followers
|
8 |
+
- "Medium": 10,000-100,000 followers
|
9 |
+
- "Macro" or "large": 100,000-1,000,000 followers
|
10 |
+
- "Mega" or "celebrity": 1,000,000+ followers
|
11 |
+
|
12 |
+
### Engagement Rates:
|
13 |
+
- "Low": 0.001-0.01 (0.1%-1%)
|
14 |
+
- "Medium": 0.01-0.03 (1%-3%)
|
15 |
+
- "High": 0.03-0.06 (3%-6%)
|
16 |
+
- "Very high": 0.06-0.1 (6%-10%)
|
17 |
+
|
18 |
+
### Age Categories:
|
19 |
+
- "Teen" or "Gen Z": 13-24
|
20 |
+
- "Young" or "Young adult": 18-30
|
21 |
+
- "Millennial": 25-40
|
22 |
+
- "Middle-aged": 35-55
|
23 |
+
- "Mature" or "Senior": 55+
|
24 |
+
|
25 |
+
### Gender:
|
26 |
+
- Only specify gender if the user explicitly requests a specific gender
|
27 |
+
- If both male and female are mentioned or no gender preference is stated, use "" (empty string)
|
28 |
+
|
29 |
+
## Response Format
|
30 |
+
Your response should be in structured JSON format:
|
31 |
+
|
32 |
+
```json
|
33 |
+
{
|
34 |
+
"search_query": "[detailed search phrase based on user request]",
|
35 |
+
"verticals": ["List of relevant verticals from the provided options"],
|
36 |
+
"min_age": "[minimum age of influencer, default to 13 if unspecified]",
|
37 |
+
"max_age": "[maximum age of influencer, default to 80 if unspecified]",
|
38 |
+
"gender": "[influencer gender: 'Female', 'Male', or '' if not specified or both]",
|
39 |
+
"min_followers": "[minimum number of followers, numeric value only]",
|
40 |
+
"max_followers": "[maximum number of followers, numeric value only]",
|
41 |
+
"min_overall_engagement": "[minimum engagement ratio, numeric value only]",
|
42 |
+
"max_overall_engagement": "[maximum engagement ratio, numeric value only]"
|
43 |
+
}
|
44 |
+
Always provide numeric values for all parameters. If the user's request is ambiguous, use reasonable defaults based on the interpretation guidelines above. For verticals, select all that closely match the user's interests.
|
45 |
+
"""
|
llm_services/tools.py
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from openai import OpenAI
|
3 |
+
import logging
|
4 |
+
from typing import List, Dict, Any
|
5 |
+
import json
|
6 |
+
import time
|
7 |
+
from pydantic import BaseModel
|
8 |
+
import time
|
9 |
+
from qdrant_client import QdrantClient, models
|
10 |
+
from qdrant_client import QdrantClient
|
11 |
+
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range, MatchAny
|
12 |
+
|
13 |
+
|
14 |
+
qdrant_client = QdrantClient(
|
15 |
+
url="https://00e40cf4-6976-43c1-aa08-be895735804b.europe-west3-0.gcp.cloud.qdrant.io:6333",
|
16 |
+
api_key="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.lfvuJEBzsmB7vez0nhv7HBbUlW77eUAT8raSazqYXHA",
|
17 |
+
)
|
18 |
+
|
19 |
+
qdrant_client.set_model("sentence-transformers/all-MiniLM-L6-v2")
|
20 |
+
qdrant_client.set_sparse_model("prithivida/Splade_PP_en_v1")
|
21 |
+
|
22 |
+
def recommend_talent_tool(search_query: str, verticals=None, min_age=None, max_age=None, gender=None,
|
23 |
+
min_followers=None, max_followers=None,
|
24 |
+
min_overall_engagement=None, max_overall_engagement=None):
|
25 |
+
conditions = []
|
26 |
+
|
27 |
+
if verticals:
|
28 |
+
if isinstance(verticals, str):
|
29 |
+
verticals = [verticals]
|
30 |
+
conditions.append(
|
31 |
+
FieldCondition(
|
32 |
+
key="verticals",
|
33 |
+
match=MatchAny(any=verticals)
|
34 |
+
)
|
35 |
+
)
|
36 |
+
|
37 |
+
if min_age is not None or max_age is not None:
|
38 |
+
range_params = {}
|
39 |
+
if min_age is not None:
|
40 |
+
range_params["gte"] = int(min_age) # Ensure it's an integer
|
41 |
+
if max_age is not None:
|
42 |
+
range_params["lte"] = int(max_age)
|
43 |
+
conditions.append(
|
44 |
+
FieldCondition(
|
45 |
+
key="age",
|
46 |
+
range=Range(**range_params)
|
47 |
+
)
|
48 |
+
)
|
49 |
+
|
50 |
+
if gender:
|
51 |
+
conditions.append(
|
52 |
+
FieldCondition(
|
53 |
+
key="gender",
|
54 |
+
match=MatchValue(value=gender)
|
55 |
+
)
|
56 |
+
)
|
57 |
+
|
58 |
+
if min_followers is not None or max_followers is not None:
|
59 |
+
range_params = {}
|
60 |
+
if min_followers is not None:
|
61 |
+
range_params["gte"] = int(min_followers) # Convert to int
|
62 |
+
if max_followers is not None:
|
63 |
+
range_params["lte"] = int(max_followers)
|
64 |
+
conditions.append(
|
65 |
+
FieldCondition(
|
66 |
+
key="follower_count",
|
67 |
+
range=Range(**range_params)
|
68 |
+
)
|
69 |
+
)
|
70 |
+
|
71 |
+
if min_overall_engagement is not None or max_overall_engagement is not None:
|
72 |
+
range_params = {}
|
73 |
+
if min_overall_engagement is not None:
|
74 |
+
range_params["gte"] = float(min_overall_engagement) # Convert to float
|
75 |
+
if max_overall_engagement is not None:
|
76 |
+
range_params["lte"] = float(max_overall_engagement)
|
77 |
+
conditions.append(
|
78 |
+
FieldCondition(
|
79 |
+
key="overall_engagement",
|
80 |
+
range=Range(**range_params)
|
81 |
+
)
|
82 |
+
)
|
83 |
+
|
84 |
+
query_filter = Filter(must=conditions) if conditions else None
|
85 |
+
|
86 |
+
search_result = qdrant_client.query(
|
87 |
+
collection_name="social_media_profiles",
|
88 |
+
query_text=search_query,
|
89 |
+
query_filter=query_filter,
|
90 |
+
limit=10
|
91 |
+
)
|
92 |
+
|
93 |
+
# Extract metadata and scores
|
94 |
+
results = []
|
95 |
+
max_followers = max((hit.metadata.get("follower_count", 1) for hit in search_result), default=1)
|
96 |
+
max_engagement = max((hit.metadata.get("overall_engagement", 1) for hit in search_result), default=1)
|
97 |
+
|
98 |
+
W1, W2, W3 = 0.5, 2.5, 0.25
|
99 |
+
|
100 |
+
for hit in search_result:
|
101 |
+
metadata = hit.metadata
|
102 |
+
vector_similarity = hit.score # Qdrant should return a similarity score
|
103 |
+
|
104 |
+
follower_count = metadata.get("follower_count", 0)
|
105 |
+
overall_engagement = metadata.get("overall_engagement", 0)
|
106 |
+
|
107 |
+
# Normalize follower count and engagement score
|
108 |
+
normalized_followers = follower_count / max_followers if max_followers > 0 else 0
|
109 |
+
normalized_engagement = overall_engagement / max_engagement if max_engagement > 0 else 0
|
110 |
+
|
111 |
+
# Calculate the matching score
|
112 |
+
matching_score = (
|
113 |
+
W1 * vector_similarity +
|
114 |
+
W2 * normalized_followers +
|
115 |
+
W3 * normalized_engagement
|
116 |
+
)
|
117 |
+
|
118 |
+
results.append({"metadata": metadata, "matching_score": matching_score})
|
119 |
+
|
120 |
+
# Sort by matching score in descending order
|
121 |
+
sorted_results = sorted(results, key=lambda x: x["matching_score"], reverse=True)
|
122 |
+
|
123 |
+
return [item["metadata"] for item in sorted_results]
|
modals/__init__.py
ADDED
File without changes
|
modals/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (157 Bytes). View file
|
|
modals/__pycache__/inputs.cpython-312.pyc
ADDED
Binary file (470 Bytes). View file
|
|
modals/inputs.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
class LLMConfig(BaseModel):
|
4 |
+
model: str
|
5 |
+
api_key: str
|
6 |
+
base_url: str
|
requirements.txt
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
altair==5.5.0
|
2 |
+
annotated-types==0.7.0
|
3 |
+
anyio==4.9.0
|
4 |
+
attrs==25.3.0
|
5 |
+
blinker==1.9.0
|
6 |
+
cachetools==5.5.2
|
7 |
+
certifi==2025.1.31
|
8 |
+
charset-normalizer==3.4.1
|
9 |
+
click==8.1.8
|
10 |
+
coloredlogs==15.0.1
|
11 |
+
distro==1.9.0
|
12 |
+
fastembed==0.5.1
|
13 |
+
filelock==3.18.0
|
14 |
+
flatbuffers==25.2.10
|
15 |
+
fsspec==2025.3.2
|
16 |
+
gitdb==4.0.12
|
17 |
+
GitPython==3.1.44
|
18 |
+
grpcio==1.71.0
|
19 |
+
grpcio-tools==1.71.0
|
20 |
+
h11==0.14.0
|
21 |
+
h2==4.2.0
|
22 |
+
hpack==4.1.0
|
23 |
+
httpcore==1.0.7
|
24 |
+
httpx==0.28.1
|
25 |
+
huggingface-hub==0.30.1
|
26 |
+
humanfriendly==10.0
|
27 |
+
hyperframe==6.1.0
|
28 |
+
idna==3.10
|
29 |
+
Jinja2==3.1.6
|
30 |
+
jiter==0.9.0
|
31 |
+
jsonschema==4.23.0
|
32 |
+
jsonschema-specifications==2024.10.1
|
33 |
+
loguru==0.7.3
|
34 |
+
MarkupSafe==3.0.2
|
35 |
+
mmh3==4.1.0
|
36 |
+
mpmath==1.3.0
|
37 |
+
narwhals==1.33.0
|
38 |
+
numpy==2.2.4
|
39 |
+
onnxruntime==1.21.0
|
40 |
+
openai==1.70.0
|
41 |
+
packaging==24.2
|
42 |
+
pandas==2.2.3
|
43 |
+
pillow==10.4.0
|
44 |
+
portalocker==2.10.1
|
45 |
+
protobuf==5.29.4
|
46 |
+
py_rust_stemmers==0.1.5
|
47 |
+
pyarrow==19.0.1
|
48 |
+
pydantic==2.11.1
|
49 |
+
pydantic_core==2.33.0
|
50 |
+
pydeck==0.9.1
|
51 |
+
python-dateutil==2.9.0.post0
|
52 |
+
pytz==2025.2
|
53 |
+
PyYAML==6.0.2
|
54 |
+
qdrant-client==1.13.3
|
55 |
+
referencing==0.36.2
|
56 |
+
requests==2.32.3
|
57 |
+
rpds-py==0.24.0
|
58 |
+
setuptools==78.1.0
|
59 |
+
six==1.17.0
|
60 |
+
smmap==5.0.2
|
61 |
+
sniffio==1.3.1
|
62 |
+
streamlit==1.44.1
|
63 |
+
sympy==1.13.3
|
64 |
+
tenacity==9.0.0
|
65 |
+
tokenizers==0.21.1
|
66 |
+
toml==0.10.2
|
67 |
+
tornado==6.4.2
|
68 |
+
tqdm==4.67.1
|
69 |
+
typing-inspection==0.4.0
|
70 |
+
typing_extensions==4.13.0
|
71 |
+
tzdata==2025.2
|
72 |
+
urllib3==2.3.0
|
73 |
+
watchdog==6.0.0
|