import gradio as gr import glob import requests import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity from docx import Document from ddgs import DDGS import trafilatura from transformers import AutoTokenizer, AutoModelForSeq2SeqLM # ---------- 1. Загрузка документа ---------- def find_docx(): for pattern in ["*.docx", "*.DOCX", "knowledge.docx"]: files = glob.glob(pattern) if files: return files[0] files = glob.glob(f"**/{pattern}", recursive=True) if files: return files[0] return None def load_local_doc(): path = find_docx() if not path: raise FileNotFoundError("Документ не найден.") doc = Document(path) full = "\n".join([p.text for p in doc.paragraphs if p.text.strip()]) chunks, cur = [], "" for para in full.split("\n"): if len(cur) + len(para) < 1500: cur += para + "\n" else: if cur: chunks.append(cur.strip()) cur = para + "\n" if cur: chunks.append(cur.strip()) vec = TfidfVectorizer(stop_words=None, ngram_range=(1,2), max_features=5000) tfidf = vec.fit_transform(chunks) return chunks, vec, tfidf print("Загрузка документа...") local_chunks, vectorizer, tfidf_matrix = load_local_doc() print(f"Документ загружен, {len(local_chunks)} фрагментов.") def local_search(query, k=4): if not query.strip(): return [] qv = vectorizer.transform([query]) sim = cosine_similarity(qv, tfidf_matrix).flatten() top = sim.argsort()[-k:][::-1] return [local_chunks[i] for i in top if sim[i] > 0.05] # ---------- 2. Веб‑поиск (DuckDuckGo) ---------- def web_search(query, max_results=3): snippets = [] try: with DDGS() as ddgs: results = ddgs.text(query, max_results=max_results) for r in results: url = r.get('href') title = r.get('title') if not url: continue try: resp = requests.get(url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'}) if resp.status_code == 200: text = trafilatura.extract(resp.text, include_comments=False, include_tables=False) if text and len(text) > 200: snippets.append(f"{title}\n{url}\n{text[:1500]}") except Exception: continue except Exception as e: print(f"Веб-поиск: {e}") return snippets # ---------- 3. Локальная генерация (flan-t5-small) ---------- print("Загрузка модели генерации (первый раз может занять минуту)...") tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-small") model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-small") print("Модель готова.") def generate_answer(question, context): context = context[:3000] prompt = f"""Ответь на вопрос, используя только информацию из Контекста. Если ответа нет, напиши "Не найдено". Контекст: {context} Вопрос: {question} Ответ:""" inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=1024) outputs = model.generate(**inputs, max_new_tokens=200, temperature=0.3, do_sample=False) return tokenizer.decode(outputs[0], skip_special_tokens=True) # ---------- 4. Основная функция ---------- def answer(query): if not query.strip(): return "Введите вопрос." local = local_search(query) web = web_search(query) all_context = local + web if not all_context: return "Ничего не найдено." return generate_answer(query, "\n\n---\n\n".join(all_context)) # ---------- 5. Интерфейс ---------- with gr.Blocks() as demo: gr.Markdown("# Поиск") with gr.Row(): q = gr.Textbox(label="Вопрос", lines=2, placeholder="Введите запрос...") btn = gr.Button("Найти", variant="primary") out = gr.Markdown(label="Результат") btn.click(fn=answer, inputs=q, outputs=out) demo.launch()