Anas989898 commited on
Commit
f0aeabd
·
0 Parent(s):

Add application file

Browse files
.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