Files
qwork/ai.py

113 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import aiohttp
import json
import re
from config import OPENROUTER
SYSTEM_PROMPT_TEMPLATE = """
Ты — экспертный технический рекрутер. Твоя задача: отфильтровать IT-вакансии.
Предпочтения пользователя:
{preferences}
Верни ТОЛЬКО валидный JSON массив объектов, которые подходят под стек и грейд.
Если ничего не подходит, верни [].
Никаких пояснений и markdown-разметки.
"""
def get_forced_vacancies(vacancies: list, user_prefs: str) -> list:
stack_match = re.search(r'(?i)стек:(.*?)(?=доп:|$)', user_prefs, re.DOTALL)
if not stack_match:
return []
stack_content = stack_match.group(1).lower()
all_keywords = re.findall(r'[a-zA-Z0-9.+#]{2,}', stack_content)
common_words = {'html', 'css', 'git', 'sql', 'api', 'rest', 'remote', 'work'}
strong_keys = list(set(word for word in all_keywords if word not in common_words))
if not strong_keys:
return []
print(f"🔍 Ключи для авто-добавления: {strong_keys}")
forced = []
for vac in vacancies:
v_id = str(vac.get('id', ''))
vac_str = json.dumps(vac, ensure_ascii=False).lower()
vac_clean = re.sub(r'https?://\S+', '', vac_str)
if any(key in vac_clean for key in strong_keys):
forced.append(vac)
return forced
async def filter_vacancies_with_ai(vacancies: list, user_prefs: str) -> list:
if not vacancies:
return []
forced_vacancies = get_forced_vacancies(vacancies, user_prefs)
forced_ids = {str(v.get('id')) for v in forced_vacancies if v.get('id')}
remaining_vacancies = [v for v in vacancies if str(v.get('id')) not in forced_ids]
if not remaining_vacancies:
print(f"Все {len(forced_vacancies)} вакансий одобрены автоматом.")
return forced_vacancies
print(f"✅ Авто-одобрено: {len(forced_vacancies)}")
print(f"🤖 Отправка на AI: {len(remaining_vacancies)} (было {len(vacancies)})")
api_url = "https://openrouter.ai/api/v1/chat/completions"
system_prompt = SYSTEM_PROMPT_TEMPLATE.format(preferences=user_prefs)
async with aiohttp.ClientSession() as session:
try:
clean_token = str(OPENROUTER).strip()
async with session.post(
url=api_url,
headers={
"Authorization": f"Bearer {clean_token}",
"Content-Type": "application/json",
},
json={
"model": "openrouter/free",
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": json.dumps(remaining_vacancies, ensure_ascii=False)}
]
},
timeout=60
) as response:
if response.status != 200:
raw_err = await response.text()
print(f"❌ Ошибка API ({response.status}): {raw_err[:100]}")
return forced_vacancies
result_raw = await response.json()
if 'choices' not in result_raw:
return forced_vacancies
content = result_raw['choices'][0]['message']['content'].strip()
match = re.search(r'\[\s*\{.*\}\s*\]', content, re.DOTALL)
clean_json = match.group(0) if match else content.replace('```json', '').replace('```', '').strip()
try:
ai_vacs = json.loads(clean_json)
final = forced_vacancies.copy()
for v in ai_vacs:
if str(v.get('id')) not in forced_ids:
final.append(v)
print(f"🏁 Итог: {len(final)} релевантных вакансий")
return final
except:
return forced_vacancies
except Exception as e:
print(f"❌ Ошибка в блоке запроса: {e}")
return forced_vacancies