import os import io import traceback from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ReplyKeyboardMarkup, KeyboardButton from telegram.ext import filters, ApplicationBuilder, MessageHandler, CommandHandler, ContextTypes from pypdf import PdfReader from vacancies.main.models import Customer, CustomerCV from langchain.agents import create_agent from langchain_openai import ChatOpenAI from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver from vacancies.main.vector_store import add_vectors, extract_features, get_next_vacancy from vacancies.conf.settings import DB_URI 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, )) features = extract_features(customer_cv.content) add_vectors( "cvs", customer_cv.id, features.model_dump(), {'content': customer_cv.content, 'features_json': features.model_dump()}, ) 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)