import io import asyncio import os import traceback from langchain.agents import create_agent from langchain_openai import ChatOpenAI from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver from pypdf import PdfReader from telegram import ( InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, Update, ) from telegram.ext import ( ApplicationBuilder, CommandHandler, ContextTypes, MessageHandler, filters, ) from vacancies.conf.settings import DB_URI from vacancies.main.models import Customer, CustomerCV from vacancies.main.vector_store import ( add_vectors, batch_extract_features, get_next_vacancy, embed_features, ) SYSTEM_PROMPT = """ Ты — карьерный копилот для ИТ. Ты можешь отвечать на любые вопросы по тематике карьеры. У тебя есть доступ к резюме пользователя при необходимости. Пиши кратко (до 5–6 строк, буллеты приветствуются). После полезного ответа предложи что-нибудь, чем ты можешь помочь еще. Отвечай простым текстом, не используй форматирование markdown. """ async def get_user_resume(user_id: int): """Получает резюме пользователя для подбора вакансий.""" customer_cv = await CustomerCV.objects.filter(customer__telegram_id=user_id).afirst() return customer_cv.content if customer_cv else "" async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await Customer.objects.aget_or_create( telegram_id=update.effective_user.id, defaults=dict( username=update.effective_user.username, chat_id=update.effective_chat.id, ), ) keyboard = [[KeyboardButton("Получить следующую вакансию")]] reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True, one_time_keyboard=False) text = "Привет! Я карьерный копилот: помогу с работой, интервью и расскажу новости по рынку, специально для тебя. С чего начнем?" await context.bot.send_message(chat_id=update.effective_chat.id, text=text, reply_markup=reply_markup) async def next_vacancy(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(update.effective_chat.id, "📝 Обрабатываю твой запрос. Пожалуйста, подождите...") customer_cv = await CustomerCV.objects.filter(customer__telegram_id=update.effective_user.id).afirst() if not customer_cv: message = "Пришлите мне свое резюме, чтобы я мог подобрать вам вакансии!" await context.bot.send_message(chat_id=update.effective_chat.id, text=message) return result = get_next_vacancy(customer_cv) if not result: message = "Вакансии закончились, возвращайтесь позже!" await context.bot.send_message(chat_id=update.effective_chat.id, text=message) return recommendation, vacancy_content, link = result await context.bot.send_message( chat_id=update.effective_chat.id, text=vacancy_content, reply_markup=InlineKeyboardMarkup([[ InlineKeyboardButton("Откликнуться", url=link), ]]), ) async def prompt(update: Update, context: ContextTypes.DEFAULT_TYPE): async with AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer: agent = create_agent( model=ChatOpenAI(model_name="gpt-5-mini", reasoning_effort="minimal"), tools=[get_user_resume], system_prompt=SYSTEM_PROMPT, checkpointer=checkpointer, ) message = await context.bot.send_message(update.effective_chat.id, "📝 Обрабатываю твой запрос. Пожалуйста, подождите...") response = await agent.ainvoke( input={"messages": [{"role": "user", "content": f'user_id = {update.effective_user.id}\n{update.message.text}'}]}, config={"configurable": {"thread_id": update.effective_user.id}}, ) await context.bot.editMessageText(response['messages'][-1].content, update.effective_chat.id, message.id) async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: traceback.print_exception(context.error) await context.bot.send_message(chat_id=update.effective_chat.id, text="Произошла ошибка. Повтоите попытку позже.") async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE): message = await context.bot.send_message(update.effective_chat.id, "📝 Обрабатываю твой запрос. Пожалуйста, подождите...") if not update.message.document: await context.bot.send_message(chat_id=update.effective_chat.id, text="Не удалось прочитать информацию из файла! Попробуйте другой формат.") return buffer = io.BytesIO() file = await update.message.document.get_file() await file.download_to_memory(buffer) reader = PdfReader(buffer) resume = "\n".join(page.extract_text() for page in reader.pages) customer = await Customer.objects.aget(telegram_id=update.effective_user.id) customer_cv, _ = await CustomerCV.objects.aupdate_or_create(customer=customer, defaults=dict( content=resume, )) def upload_vectors(): features = batch_extract_features([customer_cv.content])[0] add_vectors( "cvs", customer_cv.id, features.model_dump(), {'content': customer_cv.content, 'features_json': features.model_dump()}, embed_features(features.model_dump()), ) await asyncio.to_thread(upload_vectors) await context.bot.editMessageText("Отлично! Запомнил Ваше резюме.", update.effective_chat.id, message.id) application = ApplicationBuilder().token(os.environ["BOT_TOKEN"]).concurrent_updates(True).build() application.add_handler(CommandHandler('start', start, block=False)) application.add_handler(MessageHandler(filters.Text("Получить следующую вакансию"), next_vacancy, block=False)) application.add_handler(MessageHandler(filters.TEXT & (~filters.COMMAND), prompt, block=False)) application.add_handler(MessageHandler((filters.Document.ALL | filters.PHOTO) & (~filters.COMMAND), handle_document, block=False)) application.add_error_handler(error_handler)