Commit
Β·
4a5b92f
1
Parent(s):
305420b
π§ Update main.py to change MCP server flag, add Hugging Face dependencies in pyproject.toml, and enhance LLM service with Hugging Face integration. Add new job listings and user profiles in JSON data files.
Browse files- data/embeddings_metadata.json +0 -0
- data/jobs_cache.json +0 -0
- data/profiles.json +54 -0
- main.py +3 -3
- pyproject.toml +1 -0
- src/config/settings.py +23 -3
- src/services/llm_service.py +94 -6
- uv.lock +2 -0
data/embeddings_metadata.json
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
data/jobs_cache.json
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
data/profiles.json
CHANGED
@@ -17,5 +17,59 @@
|
|
17 |
"certifications": null,
|
18 |
"created_at": "2025-06-07 15:09:40.290202",
|
19 |
"updated_at": "2025-06-07 15:09:40.290541"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
}
|
21 |
}
|
|
|
17 |
"certifications": null,
|
18 |
"created_at": "2025-06-07 15:09:40.290202",
|
19 |
"updated_at": "2025-06-07 15:09:40.290541"
|
20 |
+
},
|
21 |
+
"demo_user1": {
|
22 |
+
"user_id": "demo_user1",
|
23 |
+
"resume": "AI engineer with experience in NLP, computer vision, and legal AI systems. Skilled in building RAG-based assistants, deploying FastAPI services, and optimizing ML models.",
|
24 |
+
"skills": [
|
25 |
+
"Python",
|
26 |
+
"FastAPI",
|
27 |
+
"Hugging Face Transformers",
|
28 |
+
"OpenPose",
|
29 |
+
"Deep SORT",
|
30 |
+
"Docker",
|
31 |
+
"PostgreSQL",
|
32 |
+
"Bash",
|
33 |
+
"Claude",
|
34 |
+
"Gemini",
|
35 |
+
"YOLO",
|
36 |
+
"Pandas",
|
37 |
+
"NumPy"
|
38 |
+
],
|
39 |
+
"salary_wish": "$30,000 - $50,000 annually",
|
40 |
+
"career_goals": "Grow as an AI engineer by working on real-world AI applications, especially legal and vision-based systems. Open to research or applied ML roles that help improve model reliability and deployment pipelines.",
|
41 |
+
"experience_level": "Entry-level",
|
42 |
+
"location": "Yerevan / Remote",
|
43 |
+
"education": "BS in Artificial Intelligence (in progress, 2023\u20132027), National Polytechnic University of Armenia",
|
44 |
+
"certifications": null,
|
45 |
+
"created_at": "2025-06-08 00:48:18.790480",
|
46 |
+
"updated_at": "2025-06-08 00:48:18.790482"
|
47 |
+
},
|
48 |
+
"Tatul's_profile": {
|
49 |
+
"user_id": "Tatul's_profile",
|
50 |
+
"resume": "AI engineer with experience in NLP, computer vision, and legal AI systems. Skilled in building RAG-based assistants, deploying FastAPI services, and optimizing ML models.",
|
51 |
+
"skills": [
|
52 |
+
"Python",
|
53 |
+
"FastAPI",
|
54 |
+
"Hugging Face Transformers",
|
55 |
+
"OpenPose",
|
56 |
+
"Deep SORT",
|
57 |
+
"Docker",
|
58 |
+
"PostgreSQL",
|
59 |
+
"Bash",
|
60 |
+
"Claude",
|
61 |
+
"Gemini",
|
62 |
+
"YOLO",
|
63 |
+
"Pandas",
|
64 |
+
"NumPy"
|
65 |
+
],
|
66 |
+
"salary_wish": "$30,000 - $50,000 annually",
|
67 |
+
"career_goals": "Grow as an AI engineer by working on real-world AI applications, especially legal and vision-based systems. Open to research or applied ML roles that help improve model reliability and deployment pipelines.",
|
68 |
+
"experience_level": "Entry-level",
|
69 |
+
"location": "Yerevan / Remote",
|
70 |
+
"education": "BS in Artificial Intelligence (in progress, 2023\u20132027), National Polytechnic University of Armenia",
|
71 |
+
"certifications": null,
|
72 |
+
"created_at": "2025-06-08 00:48:36.150851",
|
73 |
+
"updated_at": "2025-06-08 00:48:36.150854"
|
74 |
}
|
75 |
}
|
main.py
CHANGED
@@ -420,10 +420,10 @@ def main():
|
|
420 |
demo.launch(
|
421 |
server_name=mcp_server.settings.host,
|
422 |
server_port=mcp_server.settings.port,
|
423 |
-
|
424 |
share=False,
|
425 |
-
show_error=True
|
426 |
-
|
427 |
|
428 |
|
429 |
if __name__ == "__main__":
|
|
|
420 |
demo.launch(
|
421 |
server_name=mcp_server.settings.host,
|
422 |
server_port=mcp_server.settings.port,
|
423 |
+
mcp_server=True,
|
424 |
share=False,
|
425 |
+
show_error=True
|
426 |
+
)
|
427 |
|
428 |
|
429 |
if __name__ == "__main__":
|
pyproject.toml
CHANGED
@@ -26,6 +26,7 @@ dependencies = [
|
|
26 |
"aiohttp>=3.8.0",
|
27 |
"typing-extensions>=4.5.0",
|
28 |
"pydantic-settings>=2.9.1",
|
|
|
29 |
]
|
30 |
|
31 |
[project.optional-dependencies]
|
|
|
26 |
"aiohttp>=3.8.0",
|
27 |
"typing-extensions>=4.5.0",
|
28 |
"pydantic-settings>=2.9.1",
|
29 |
+
"huggingface-hub>=0.32.4",
|
30 |
]
|
31 |
|
32 |
[project.optional-dependencies]
|
src/config/settings.py
CHANGED
@@ -3,10 +3,14 @@
|
|
3 |
import os
|
4 |
from functools import lru_cache
|
5 |
from typing import Optional
|
|
|
6 |
|
7 |
from pydantic import Field
|
8 |
from pydantic_settings import BaseSettings
|
9 |
|
|
|
|
|
|
|
10 |
|
11 |
class Settings(BaseSettings):
|
12 |
"""Application settings and configuration."""
|
@@ -14,6 +18,7 @@ class Settings(BaseSettings):
|
|
14 |
# API Keys
|
15 |
openai_api_key: Optional[str] = Field(default=None, env="OPENAI_API_KEY")
|
16 |
anthropic_api_key: Optional[str] = Field(default=None, env="ANTHROPIC_API_KEY")
|
|
|
17 |
|
18 |
# Job Search APIs
|
19 |
linkedin_api_key: Optional[str] = Field(default=None, env="LINKEDIN_API_KEY")
|
@@ -30,8 +35,13 @@ class Settings(BaseSettings):
|
|
30 |
embedding_dimension: int = Field(default=384, env="EMBEDDING_DIMENSION")
|
31 |
|
32 |
# LLM Settings
|
33 |
-
llm_provider: str = Field(
|
34 |
-
|
|
|
|
|
|
|
|
|
|
|
35 |
max_tokens: int = Field(default=300, env="MAX_TOKENS")
|
36 |
temperature: float = Field(default=0.7, env="TEMPERATURE")
|
37 |
|
@@ -66,4 +76,14 @@ class Settings(BaseSettings):
|
|
66 |
@lru_cache()
|
67 |
def get_settings() -> Settings:
|
68 |
"""Get cached settings instance."""
|
69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
import os
|
4 |
from functools import lru_cache
|
5 |
from typing import Optional
|
6 |
+
from dotenv import load_dotenv
|
7 |
|
8 |
from pydantic import Field
|
9 |
from pydantic_settings import BaseSettings
|
10 |
|
11 |
+
# Load environment variables from .env file
|
12 |
+
load_dotenv()
|
13 |
+
|
14 |
|
15 |
class Settings(BaseSettings):
|
16 |
"""Application settings and configuration."""
|
|
|
18 |
# API Keys
|
19 |
openai_api_key: Optional[str] = Field(default=None, env="OPENAI_API_KEY")
|
20 |
anthropic_api_key: Optional[str] = Field(default=None, env="ANTHROPIC_API_KEY")
|
21 |
+
hf_access_token: Optional[str] = Field(default=None, env="HF_ACCESS_TOKEN")
|
22 |
|
23 |
# Job Search APIs
|
24 |
linkedin_api_key: Optional[str] = Field(default=None, env="LINKEDIN_API_KEY")
|
|
|
35 |
embedding_dimension: int = Field(default=384, env="EMBEDDING_DIMENSION")
|
36 |
|
37 |
# LLM Settings
|
38 |
+
llm_provider: str = Field(
|
39 |
+
default="huggingface", env="LLM_PROVIDER"
|
40 |
+
) # openai, anthropic, huggingface
|
41 |
+
llm_model: str = Field(default="deepseek/deepseek-v3-turbo", env="LLM_MODEL")
|
42 |
+
hf_inference_provider: str = Field(
|
43 |
+
default="novita", env="HF_INFERENCE_PROVIDER"
|
44 |
+
) # novita, together, fireworks, etc.
|
45 |
max_tokens: int = Field(default=300, env="MAX_TOKENS")
|
46 |
temperature: float = Field(default=0.7, env="TEMPERATURE")
|
47 |
|
|
|
76 |
@lru_cache()
|
77 |
def get_settings() -> Settings:
|
78 |
"""Get cached settings instance."""
|
79 |
+
settings = Settings()
|
80 |
+
|
81 |
+
# Debug print the HF token
|
82 |
+
if settings.hf_access_token:
|
83 |
+
print(
|
84 |
+
f"π HF Access Token loaded: {settings.hf_access_token[:20]}...{settings.hf_access_token[-10:]}"
|
85 |
+
)
|
86 |
+
else:
|
87 |
+
print("β No HF Access Token found in environment variables")
|
88 |
+
|
89 |
+
return settings
|
src/services/llm_service.py
CHANGED
@@ -3,6 +3,8 @@
|
|
3 |
from typing import Dict, Any, Optional
|
4 |
import openai
|
5 |
from anthropic import Anthropic
|
|
|
|
|
6 |
|
7 |
from ..config import get_settings
|
8 |
from .profile_service import UserProfile
|
@@ -15,6 +17,7 @@ class LLMService:
|
|
15 |
self.settings = get_settings()
|
16 |
self.openai_client = None
|
17 |
self.anthropic_client = None
|
|
|
18 |
self._initialize_clients()
|
19 |
|
20 |
def _initialize_clients(self):
|
@@ -26,12 +29,29 @@ class LLMService:
|
|
26 |
if self.settings.anthropic_api_key:
|
27 |
self.anthropic_client = Anthropic(api_key=self.settings.anthropic_api_key)
|
28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
def _get_client(self):
|
30 |
"""Get the appropriate LLM client based on provider setting."""
|
31 |
-
if self.settings.llm_provider == "
|
|
|
|
|
32 |
return self.openai_client, "openai"
|
33 |
elif self.settings.llm_provider == "anthropic" and self.anthropic_client:
|
34 |
return self.anthropic_client, "anthropic"
|
|
|
|
|
35 |
elif self.openai_client:
|
36 |
return self.openai_client, "openai"
|
37 |
elif self.anthropic_client:
|
@@ -81,13 +101,81 @@ class LLMService:
|
|
81 |
print(f"Anthropic API error: {e}")
|
82 |
return None
|
83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
def generate_text(self, messages: list, max_tokens: int = None) -> Optional[str]:
|
85 |
"""Generate text using the configured LLM."""
|
86 |
client, provider = self._get_client()
|
87 |
if not client:
|
|
|
88 |
return None
|
89 |
|
90 |
-
|
|
|
|
|
|
|
|
|
91 |
return self._call_openai(messages, max_tokens)
|
92 |
elif provider == "anthropic":
|
93 |
return self._call_anthropic(messages, max_tokens)
|
@@ -130,10 +218,10 @@ JOB DESCRIPTION:
|
|
130 |
|
131 |
CANDIDATE PROFILE:
|
132 |
- Skills: {skills_text}
|
133 |
-
- Experience: {profile.experience_level or
|
134 |
- Career Goals: {profile.career_goals}
|
135 |
-
- Location: {profile.location or
|
136 |
-
- Education: {profile.education or
|
137 |
|
138 |
RESUME SUMMARY:
|
139 |
{profile.resume[:1000]} # Limit resume text
|
@@ -202,7 +290,7 @@ CONTEXT: {context}
|
|
202 |
|
203 |
CANDIDATE BACKGROUND:
|
204 |
- Skills: {", ".join(profile.skills[:8])}
|
205 |
-
- Experience Level: {profile.experience_level or
|
206 |
- Career Goals: {profile.career_goals}
|
207 |
- Key Background: {profile.resume[:800]}
|
208 |
|
|
|
3 |
from typing import Dict, Any, Optional
|
4 |
import openai
|
5 |
from anthropic import Anthropic
|
6 |
+
import requests
|
7 |
+
import json
|
8 |
|
9 |
from ..config import get_settings
|
10 |
from .profile_service import UserProfile
|
|
|
17 |
self.settings = get_settings()
|
18 |
self.openai_client = None
|
19 |
self.anthropic_client = None
|
20 |
+
self.hf_client = None
|
21 |
self._initialize_clients()
|
22 |
|
23 |
def _initialize_clients(self):
|
|
|
29 |
if self.settings.anthropic_api_key:
|
30 |
self.anthropic_client = Anthropic(api_key=self.settings.anthropic_api_key)
|
31 |
|
32 |
+
if self.settings.hf_access_token:
|
33 |
+
# Use the new Inference Providers API
|
34 |
+
self.hf_client = {
|
35 |
+
"api_url": f"https://router.huggingface.co/{self.settings.hf_inference_provider}/v3/openai/chat/completions",
|
36 |
+
"headers": {
|
37 |
+
"Authorization": f"Bearer {self.settings.hf_access_token}",
|
38 |
+
"Content-Type": "application/json",
|
39 |
+
},
|
40 |
+
}
|
41 |
+
print(
|
42 |
+
f"π HuggingFace Inference Providers initialized with {self.settings.hf_inference_provider} provider"
|
43 |
+
)
|
44 |
+
|
45 |
def _get_client(self):
|
46 |
"""Get the appropriate LLM client based on provider setting."""
|
47 |
+
if self.settings.llm_provider == "huggingface" and self.hf_client:
|
48 |
+
return self.hf_client, "huggingface"
|
49 |
+
elif self.settings.llm_provider == "openai" and self.openai_client:
|
50 |
return self.openai_client, "openai"
|
51 |
elif self.settings.llm_provider == "anthropic" and self.anthropic_client:
|
52 |
return self.anthropic_client, "anthropic"
|
53 |
+
elif self.hf_client:
|
54 |
+
return self.hf_client, "huggingface"
|
55 |
elif self.openai_client:
|
56 |
return self.openai_client, "openai"
|
57 |
elif self.anthropic_client:
|
|
|
101 |
print(f"Anthropic API error: {e}")
|
102 |
return None
|
103 |
|
104 |
+
def _call_huggingface(
|
105 |
+
self, messages: list, max_tokens: int = None
|
106 |
+
) -> Optional[str]:
|
107 |
+
"""Call HuggingFace Inference Providers API."""
|
108 |
+
try:
|
109 |
+
# Use OpenAI-compatible format for Inference Providers
|
110 |
+
payload = {
|
111 |
+
"model": self.settings.llm_model,
|
112 |
+
"messages": messages,
|
113 |
+
"max_tokens": max_tokens or self.settings.max_tokens,
|
114 |
+
"temperature": self.settings.temperature,
|
115 |
+
"stream": False,
|
116 |
+
}
|
117 |
+
|
118 |
+
print(
|
119 |
+
f"π Calling HF Inference Providers API with {self.settings.hf_inference_provider} provider..."
|
120 |
+
)
|
121 |
+
print(f"π‘ URL: {self.hf_client['api_url']}")
|
122 |
+
print(f"π€ Model: {self.settings.llm_model}")
|
123 |
+
|
124 |
+
response = requests.post(
|
125 |
+
self.hf_client["api_url"],
|
126 |
+
headers=self.hf_client["headers"],
|
127 |
+
json=payload,
|
128 |
+
timeout=60,
|
129 |
+
)
|
130 |
+
|
131 |
+
print(f"π Response status: {response.status_code}")
|
132 |
+
|
133 |
+
if response.status_code == 200:
|
134 |
+
result = response.json()
|
135 |
+
print(f"β
Success: {result}")
|
136 |
+
|
137 |
+
# Handle OpenAI-compatible response format
|
138 |
+
if "choices" in result and len(result["choices"]) > 0:
|
139 |
+
content = result["choices"][0]["message"]["content"]
|
140 |
+
return content.strip()
|
141 |
+
else:
|
142 |
+
print(f"β Unexpected response format: {result}")
|
143 |
+
return None
|
144 |
+
else:
|
145 |
+
error_detail = response.text
|
146 |
+
print(
|
147 |
+
f"β HuggingFace Inference Providers API error: {response.status_code} - {error_detail}"
|
148 |
+
)
|
149 |
+
|
150 |
+
# Try to parse error details
|
151 |
+
try:
|
152 |
+
error_json = response.json()
|
153 |
+
if "error" in error_json:
|
154 |
+
print(f"π Error details: {error_json['error']}")
|
155 |
+
except:
|
156 |
+
pass
|
157 |
+
|
158 |
+
return None
|
159 |
+
|
160 |
+
except requests.exceptions.Timeout:
|
161 |
+
print(f"β±οΈ HuggingFace API timeout error")
|
162 |
+
return None
|
163 |
+
except Exception as e:
|
164 |
+
print(f"β HuggingFace API error: {e}")
|
165 |
+
return None
|
166 |
+
|
167 |
def generate_text(self, messages: list, max_tokens: int = None) -> Optional[str]:
|
168 |
"""Generate text using the configured LLM."""
|
169 |
client, provider = self._get_client()
|
170 |
if not client:
|
171 |
+
print("β No LLM client available")
|
172 |
return None
|
173 |
|
174 |
+
print(f"π― Using {provider} provider for text generation")
|
175 |
+
|
176 |
+
if provider == "huggingface":
|
177 |
+
return self._call_huggingface(messages, max_tokens)
|
178 |
+
elif provider == "openai":
|
179 |
return self._call_openai(messages, max_tokens)
|
180 |
elif provider == "anthropic":
|
181 |
return self._call_anthropic(messages, max_tokens)
|
|
|
218 |
|
219 |
CANDIDATE PROFILE:
|
220 |
- Skills: {skills_text}
|
221 |
+
- Experience: {profile.experience_level or "Not specified"}
|
222 |
- Career Goals: {profile.career_goals}
|
223 |
+
- Location: {profile.location or "Not specified"}
|
224 |
+
- Education: {profile.education or "Not specified"}
|
225 |
|
226 |
RESUME SUMMARY:
|
227 |
{profile.resume[:1000]} # Limit resume text
|
|
|
290 |
|
291 |
CANDIDATE BACKGROUND:
|
292 |
- Skills: {", ".join(profile.skills[:8])}
|
293 |
+
- Experience Level: {profile.experience_level or "Not specified"}
|
294 |
- Career Goals: {profile.career_goals}
|
295 |
- Key Background: {profile.resume[:800]}
|
296 |
|
uv.lock
CHANGED
@@ -886,6 +886,7 @@ dependencies = [
|
|
886 |
{ name = "faiss-cpu" },
|
887 |
{ name = "gradio", extra = ["mcp"] },
|
888 |
{ name = "httpx" },
|
|
|
889 |
{ name = "lxml" },
|
890 |
{ name = "numpy" },
|
891 |
{ name = "openai" },
|
@@ -922,6 +923,7 @@ requires-dist = [
|
|
922 |
{ name = "flake8", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
923 |
{ name = "gradio", extras = ["mcp"], specifier = ">=5.0.0" },
|
924 |
{ name = "httpx", specifier = ">=0.24.0" },
|
|
|
925 |
{ name = "isort", marker = "extra == 'dev'", specifier = ">=5.12.0" },
|
926 |
{ name = "lxml", specifier = ">=4.9.0" },
|
927 |
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" },
|
|
|
886 |
{ name = "faiss-cpu" },
|
887 |
{ name = "gradio", extra = ["mcp"] },
|
888 |
{ name = "httpx" },
|
889 |
+
{ name = "huggingface-hub" },
|
890 |
{ name = "lxml" },
|
891 |
{ name = "numpy" },
|
892 |
{ name = "openai" },
|
|
|
923 |
{ name = "flake8", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
924 |
{ name = "gradio", extras = ["mcp"], specifier = ">=5.0.0" },
|
925 |
{ name = "httpx", specifier = ">=0.24.0" },
|
926 |
+
{ name = "huggingface-hub", specifier = ">=0.32.4" },
|
927 |
{ name = "isort", marker = "extra == 'dev'", specifier = ">=5.12.0" },
|
928 |
{ name = "lxml", specifier = ">=4.9.0" },
|
929 |
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" },
|