Compare commits
9 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
52f4e8579e | |
|
|
38c7ab8956 | |
|
|
f83da185f8 | |
|
|
3dbba3f2d6 | |
|
|
ad08ce74ce | |
|
|
10f40c54ee | |
|
|
481cae2a61 | |
|
|
c617cb3f95 | |
|
|
b68ffa3112 |
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Użyj oficjalnego obrazu Python jako bazowego
|
||||||
|
FROM --platform=linux/amd64 python:3.9-slim
|
||||||
|
|
||||||
|
# Ustaw katalog roboczy w kontenerze
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Zainstaluj git
|
||||||
|
RUN apt-get update && apt-get install -y git nano wget curl iputils-ping
|
||||||
|
|
||||||
|
# Skopiuj pliki wymagań (jeśli istnieją) i zainstaluj zależności
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Skopiuj plik requirements.txt do kontenera
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Zainstaluj zależności z pliku requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Zainstaluj Tesseract OCR
|
||||||
|
RUN apt-get install -y tesseract-ocr
|
||||||
|
|
||||||
|
# Skopiuj kod źródłowy do kontenera
|
||||||
|
COPY . .
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
# Uruchom aplikację
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
|
||||||
|
|
||||||
model = AutoModelForSeq2SeqLM.from_pretrained("allegro/multislav-5lang")
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained("allegro/multislav-5lang")
|
|
||||||
|
|
||||||
model.save_pretrained("./models/ably")
|
|
||||||
tokenizer.save_pretrained("./models/ably")
|
|
||||||
|
|
||||||
print("✅ Model został wytrenowany i zapisany!")
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"kodekspracy": "Kodeks Pracy",
|
|
||||||
"urlopproporcjonalny": "Rozporządzenie BHP",
|
|
||||||
"ustawaopanstwowejinspekcjipracy": "Ustawa o Państwowej inspekcji pracy"
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3859
docs/kodekspracy.txt
3859
docs/kodekspracy.txt
File diff suppressed because it is too large
Load Diff
|
|
@ -1,11 +0,0 @@
|
||||||
Podstawowe zasady naliczania urlopu proporcjonalnego
|
|
||||||
|
|
||||||
Kalendarzowy miesiąc pracy odpowiada 1/12 wymiaru urlopu wypoczynkowego, który przysługuje pracownikowi na podstawie art. 154 § 1 i 2 k.p. To oznacza, że 1 kalendarzowy miesiąc pracy to 1/12 z 20 dni (1,66) lub 26 dni (2,16) urlopu wypoczynkowego dla pracownika na pełnym etacie. Niektórzy zaokrąglają wyniki do 1,67 dnia urlopu i 2,17 dnia urlopu.
|
|
||||||
|
|
||||||
Niepełny kalendarzowy miesiąc pracy zaokrągla się w górę do pełnego miesiąca. Jeżeli pracownik przepracuje tylko 1 dzień w miesiącu, zyska prawo do urlopu za cały miesiąc.
|
|
||||||
|
|
||||||
Niepełny dzień urlopu zaokrągla się w górę do pełnego dnia. Uwaga – nie musisz tak postąpić w przypadku urlopu liczonego proporcjonalnie dla osoby, która podjęła pierwszą pracę w życiu.
|
|
||||||
|
|
||||||
Zaokrąglając niepełne dni urlopu, pamiętaj, że wymiar urlopu wypoczynkowego należny pracownikowi pełnoetatowemu w danym roku kalendarzowym nie może przekroczyć 20 lub 26 dni (w zależności od stażu pracy).
|
|
||||||
|
|
||||||
Jeśli pracownik rozwiązuje umowę o pracę z dotychczasowym pracodawcą i zawiera nową umowę o pracę z kolejnym pracodawcą w tym samym miesiącu kalendarzowym, to tylko wcześniejszy pracodawca zaokrągla ten niepełny miesiąc pracy w górę.
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
python /app/ollama_service.py
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
import os
|
|
||||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
||||||
|
|
||||||
import faiss
|
|
||||||
import numpy as np
|
|
||||||
import ollama
|
|
||||||
import gradio as gr
|
|
||||||
import os
|
|
||||||
import argparse
|
|
||||||
from sentence_transformers import SentenceTransformer
|
|
||||||
|
|
||||||
# === KONFIGURACJA ===
|
|
||||||
model_name = "hse.ably.do:latest" # Nazwa modelu Ollama
|
|
||||||
faiss_index_path = "faiss_index.idx" # Plik indeksu FAISS
|
|
||||||
kodeks_file = "/home/ably.do/docs/kodekspracy.txt" # Plik z treścią kodeksu pracy
|
|
||||||
embedding_model = SentenceTransformer("all-MiniLM-L6-v2") # Model do embedowania tekstu
|
|
||||||
|
|
||||||
# === KROK 1: WCZYTYWANIE KODEKSU PRACY ===
|
|
||||||
def load_kodeks(filepath):
|
|
||||||
with open(filepath, "r", encoding="utf-8") as file:
|
|
||||||
content = file.read()
|
|
||||||
articles = content.split("\n\n") # Dzielimy na sekcje
|
|
||||||
return [article.strip() for article in articles if article.strip().startswith("Art.")]
|
|
||||||
|
|
||||||
# === KROK 2: TWORZENIE INDEKSU FAISS ===
|
|
||||||
def create_faiss_index(sections):
|
|
||||||
embeddings = embedding_model.encode(sections, convert_to_numpy=True) # Tworzenie wektorów
|
|
||||||
index = faiss.IndexFlatL2(embeddings.shape[1]) # Indeks FAISS
|
|
||||||
index.add(embeddings) # Dodanie wektorów do FAISS
|
|
||||||
faiss.write_index(index, faiss_index_path) # Zapis indeksu
|
|
||||||
return index, sections
|
|
||||||
|
|
||||||
# === KROK 3: WYSZUKIWANIE NAJBLIŻSZEGO FRAGMENTU ===
|
|
||||||
def search_faiss(query, index, sections, top_k=3):
|
|
||||||
query_vector = embedding_model.encode([query], convert_to_numpy=True)
|
|
||||||
_, idx = index.search(query_vector, top_k) # Szukamy więcej wyników
|
|
||||||
|
|
||||||
results = [sections[i] for i in idx[0] if i < len(sections)]
|
|
||||||
return "\n\n".join(results) # Połącz kilka najlepszych fragmentów
|
|
||||||
|
|
||||||
# === KROK 4: GENEROWANIE ODPOWIEDZI Z OLLAMA ===
|
|
||||||
def generate_response(user_query):
|
|
||||||
if not os.path.exists(faiss_index_path):
|
|
||||||
return "Błąd: Indeks FAISS nie istnieje. Uruchom aplikację z opcją --rebuild-index."
|
|
||||||
|
|
||||||
try:
|
|
||||||
index = faiss.read_index(faiss_index_path)
|
|
||||||
except Exception as e:
|
|
||||||
return f"Błąd ładowania FAISS: {str(e)}"
|
|
||||||
|
|
||||||
sections = load_kodeks(kodeks_file)
|
|
||||||
best_match = search_faiss(user_query, index, sections)
|
|
||||||
|
|
||||||
# 👀 DEBUG: Sprawdź, co zwraca FAISS
|
|
||||||
print(f"🔍 Najlepsze dopasowanie FAISS dla '{user_query}':\n{best_match}")
|
|
||||||
|
|
||||||
prompt = f"""
|
|
||||||
Odpowiedz na pytanie na podstawie następującego tekstu:
|
|
||||||
|
|
||||||
{best_match}
|
|
||||||
|
|
||||||
Pytanie: {user_query}
|
|
||||||
Podaj dokładny tekst artykułu, jeśli go znajdziesz w treści powyżej.
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = ollama.chat(model=model_name, messages=[{"role": "user", "content": prompt}])
|
|
||||||
|
|
||||||
print(f"📝 Odpowiedź modelu:\n{response}") # 👀 DEBUG: Sprawdź odpowiedź Ollama
|
|
||||||
|
|
||||||
return response.get("message", response.get("content", "Błąd: Nie udało się wygenerować odpowiedzi."))
|
|
||||||
|
|
||||||
# === KROK 5: INTERFEJS WEBOWY ===
|
|
||||||
iface = gr.Interface(
|
|
||||||
fn=generate_response,
|
|
||||||
inputs=gr.Textbox(label="Zadaj pytanie o kodeks pracy"),
|
|
||||||
outputs=gr.Textbox(label="Odpowiedź"),
|
|
||||||
title="Asystent Kodeksu Pracy",
|
|
||||||
description="Wpisz pytanie, a system zwróci odpowiedni fragment kodeksu pracy."
|
|
||||||
)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("--rebuild-index", action="store_true", help="Odbudowanie indeksu FAISS")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.rebuild_index or not os.path.exists(faiss_index_path):
|
|
||||||
print("Tworzenie nowego indeksu FAISS...")
|
|
||||||
sections = load_kodeks(kodeks_file)
|
|
||||||
create_faiss_index(sections)
|
|
||||||
else:
|
|
||||||
print("Indeks FAISS już istnieje.")
|
|
||||||
|
|
||||||
iface.launch(share=True)
|
|
||||||
119
gemma.py
119
gemma.py
|
|
@ -1,119 +0,0 @@
|
||||||
import os
|
|
||||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
||||||
|
|
||||||
import torch
|
|
||||||
import faiss
|
|
||||||
import numpy as np
|
|
||||||
from sentence_transformers import SentenceTransformer
|
|
||||||
from datasets import Dataset
|
|
||||||
from peft import LoraConfig, get_peft_model
|
|
||||||
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForLanguageModeling
|
|
||||||
|
|
||||||
# 1️⃣ Inicjalizacja modelu do embeddingów
|
|
||||||
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
|
|
||||||
|
|
||||||
# 2️⃣ Dodanie dokumentów i embeddingów
|
|
||||||
def read_documents_from_file(file_path):
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
|
||||||
content = file.read()
|
|
||||||
articles = content.split('\n\n')
|
|
||||||
documents = []
|
|
||||||
for article in articles:
|
|
||||||
if article.strip().startswith('Art.'):
|
|
||||||
documents.append(article.strip())
|
|
||||||
return documents
|
|
||||||
#documents = [
|
|
||||||
# "Jak założyć firmę w Polsce?",
|
|
||||||
# "Jak rozliczyć podatek VAT?",
|
|
||||||
# "Procedura składania reklamacji w e-sklepie.",
|
|
||||||
# "Jakie dokumenty są potrzebne do rejestracji działalności?"
|
|
||||||
#]
|
|
||||||
file_path = './docs/kodekspracy.txt' # Zmień na właściwą ścieżkę
|
|
||||||
documents = read_documents_from_file(file_path)
|
|
||||||
embeddings = embed_model.encode(documents)
|
|
||||||
|
|
||||||
# 3️⃣ Inicjalizacja FAISS i dodanie wektorów
|
|
||||||
dim = embeddings.shape[1]
|
|
||||||
index = faiss.IndexFlatL2(dim)
|
|
||||||
index.add(np.array(embeddings, dtype=np.float32))
|
|
||||||
|
|
||||||
# 4️⃣ Przygotowanie danych treningowych
|
|
||||||
def create_training_data():
|
|
||||||
data = {
|
|
||||||
"text": documents,
|
|
||||||
"embedding": embeddings.tolist()
|
|
||||||
}
|
|
||||||
return Dataset.from_dict(data)
|
|
||||||
|
|
||||||
dataset = create_training_data()
|
|
||||||
|
|
||||||
# Podział danych na treningowe i ewaluacyjne
|
|
||||||
split_dataset = dataset.train_test_split(test_size=0.25)
|
|
||||||
train_dataset = split_dataset["train"]
|
|
||||||
eval_dataset = split_dataset["test"]
|
|
||||||
|
|
||||||
# 5️⃣ Ładowanie modelu Gemma 2B
|
|
||||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
||||||
model_name = "google/gemma-2-2b"
|
|
||||||
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16).to(device)
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
||||||
|
|
||||||
# 6️⃣ Konfiguracja LoRA
|
|
||||||
lora_config = LoraConfig(
|
|
||||||
r=8, lora_alpha=32, lora_dropout=0.1, bias="none", task_type="CAUSAL_LM"
|
|
||||||
)
|
|
||||||
model = get_peft_model(model, lora_config)
|
|
||||||
|
|
||||||
# 7️⃣ Tokenizacja danych
|
|
||||||
max_length = 384
|
|
||||||
|
|
||||||
def tokenize_function(examples):
|
|
||||||
return tokenizer(
|
|
||||||
examples["text"],
|
|
||||||
padding="max_length",
|
|
||||||
truncation=True,
|
|
||||||
max_length=max_length
|
|
||||||
)
|
|
||||||
|
|
||||||
tokenized_train = train_dataset.map(tokenize_function, batched=True)
|
|
||||||
tokenized_eval = eval_dataset.map(tokenize_function, batched=True)
|
|
||||||
|
|
||||||
# 8️⃣ Parametry treningu
|
|
||||||
training_args = TrainingArguments(
|
|
||||||
output_dir="./results",
|
|
||||||
eval_strategy="steps", # Ewaluacja co określoną liczbę kroków
|
|
||||||
eval_steps=500, # Ewaluacja co 500 kroków
|
|
||||||
save_strategy="steps", # Zapis modelu co określoną liczbę kroków
|
|
||||||
save_steps=500, # Zapis modelu co 500 kroków
|
|
||||||
learning_rate=1e-5,
|
|
||||||
per_device_train_batch_size=2,
|
|
||||||
per_device_eval_batch_size=2,
|
|
||||||
num_train_epochs=16,
|
|
||||||
weight_decay=0.01,
|
|
||||||
load_best_model_at_end=True, # Wczytaj najlepszy model na końcu
|
|
||||||
metric_for_best_model="loss", # Kryterium wyboru najlepszego modelu
|
|
||||||
greater_is_better=False, # Niższy loss = lepszy model
|
|
||||||
)
|
|
||||||
|
|
||||||
# 9️⃣ Data Collator
|
|
||||||
data_collator = DataCollatorForLanguageModeling(
|
|
||||||
tokenizer=tokenizer,
|
|
||||||
mlm=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# 🔟 Trening modelu
|
|
||||||
trainer = Trainer(
|
|
||||||
model=model,
|
|
||||||
args=training_args,
|
|
||||||
train_dataset=tokenized_train,
|
|
||||||
eval_dataset=tokenized_eval, # Dodany zestaw ewaluacyjny
|
|
||||||
data_collator=data_collator,
|
|
||||||
)
|
|
||||||
|
|
||||||
trainer.train()
|
|
||||||
|
|
||||||
# 1️⃣1️⃣ Zapis modelu
|
|
||||||
model.save_pretrained("./trained_model/gemma")
|
|
||||||
tokenizer.save_pretrained("./trained_model/gemma")
|
|
||||||
|
|
||||||
print("✅ Model został wytrenowany i zapisany!")
|
|
||||||
118
gpt.py
118
gpt.py
|
|
@ -1,118 +0,0 @@
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import torch
|
|
||||||
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForLanguageModeling
|
|
||||||
from datasets import Dataset
|
|
||||||
|
|
||||||
# Konfiguracja
|
|
||||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
||||||
MODEL_NAME = "gpt2-medium"
|
|
||||||
SPECIAL_TOKENS = ["[CITATION_START]", "[CITATION_END]"]
|
|
||||||
TEXT_FILE_PATH = "./docs/kodekspracy.txt" # Zmień na właściwą ścieżkę
|
|
||||||
|
|
||||||
def prepare_dataset_from_file(file_path):
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
|
||||||
text = f.read()
|
|
||||||
|
|
||||||
# Wydziel artykuły za pomocą wyrażenia regularnego
|
|
||||||
articles = re.findall(r'Art\.\s*\d+[a-z]*\..*?(?=\s*Art\.\s*\d+[a-z]*\.|\Z)', text, flags=re.DOTALL)
|
|
||||||
|
|
||||||
formatted_articles = []
|
|
||||||
for article in articles:
|
|
||||||
# Usuń zbędne białe znaki
|
|
||||||
article = ' '.join(article.strip().split())
|
|
||||||
|
|
||||||
# Wydziel numer artykułu i treść
|
|
||||||
art_match = re.match(r'Art\.\s*(\d+[a-z]*)\.?\s*(.*)', article, re.DOTALL)
|
|
||||||
if art_match:
|
|
||||||
art_number = art_match.group(1)
|
|
||||||
art_text = art_match.group(2)
|
|
||||||
|
|
||||||
# Podziel na paragrafy, jeśli istnieją
|
|
||||||
paragraphs = re.split(r'(§\s*\d+\.)', art_text)
|
|
||||||
if len(paragraphs) > 1:
|
|
||||||
formatted_paragraphs = []
|
|
||||||
for i in range(1, len(paragraphs), 2):
|
|
||||||
para_num = paragraphs[i].strip()
|
|
||||||
para_text = paragraphs[i+1].strip()
|
|
||||||
formatted_paragraphs.append(f"{para_num} {para_text}")
|
|
||||||
formatted = f"[CITATION_START] Kodeks Pracy, Art. {art_number} [CITATION_END]\n" + "\n".join(formatted_paragraphs)
|
|
||||||
else:
|
|
||||||
formatted = f"[CITATION_START] Kodeks Pracy, Art. {art_number} [CITATION_END] {art_text}"
|
|
||||||
|
|
||||||
formatted_articles.append({"text": formatted})
|
|
||||||
|
|
||||||
# Dodaj przykłady pytań i odpowiedzi
|
|
||||||
questions = [
|
|
||||||
f"Zacytuj artykuł {art_number} Kodeksu pracy.",
|
|
||||||
f"Co mówi artykuł {art_number} Kodeksu pracy?",
|
|
||||||
f"Podaj treść artykułu {art_number} Kodeksu pracy."
|
|
||||||
]
|
|
||||||
for question in questions:
|
|
||||||
formatted_articles.append({"text": f"{question}\n{formatted}"})
|
|
||||||
|
|
||||||
return formatted_articles
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Inicjalizacja tokenizera
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
|
||||||
tokenizer.add_special_tokens({"additional_special_tokens": SPECIAL_TOKENS})
|
|
||||||
tokenizer.pad_token = tokenizer.eos_token
|
|
||||||
|
|
||||||
# Przygotowanie danych
|
|
||||||
data = prepare_dataset_from_file(TEXT_FILE_PATH)
|
|
||||||
dataset = Dataset.from_dict({"text": [d["text"] for d in data]})
|
|
||||||
|
|
||||||
# Tokenizacja
|
|
||||||
def tokenize_function(examples):
|
|
||||||
tokenized = tokenizer(
|
|
||||||
examples["text"],
|
|
||||||
truncation=True,
|
|
||||||
padding="max_length",
|
|
||||||
max_length=1024, # Zwiększono dla dłuższych artykułów
|
|
||||||
return_tensors="pt"
|
|
||||||
)
|
|
||||||
tokenized["labels"] = tokenized["input_ids"].clone()
|
|
||||||
return tokenized
|
|
||||||
|
|
||||||
tokenized_dataset = dataset.map(tokenize_function, batched=True)
|
|
||||||
|
|
||||||
# Model i data collator
|
|
||||||
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME)
|
|
||||||
model.resize_token_embeddings(len(tokenizer), mean_resizing=False)
|
|
||||||
|
|
||||||
data_collator = DataCollatorForLanguageModeling(
|
|
||||||
tokenizer=tokenizer,
|
|
||||||
mlm=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Konfiguracja treningu
|
|
||||||
training_args = TrainingArguments(
|
|
||||||
output_dir="./results",
|
|
||||||
num_train_epochs=32, # Zwiększono liczbę epok
|
|
||||||
per_device_train_batch_size=2,
|
|
||||||
learning_rate=1e-5, #precyzja uczenia
|
|
||||||
logging_steps=10,
|
|
||||||
weight_decay=0.01,
|
|
||||||
report_to="none",
|
|
||||||
save_strategy="no",
|
|
||||||
load_best_model_at_end=True, # Ładowanie najlepszego modelu na końcu
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Trainer
|
|
||||||
trainer = Trainer(
|
|
||||||
model=model,
|
|
||||||
args=training_args,
|
|
||||||
train_dataset=tokenized_dataset,
|
|
||||||
data_collator=data_collator
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Rozpoczęcie treningu...")
|
|
||||||
trainer.train()
|
|
||||||
trainer.save_model("./trained_model/gpt")
|
|
||||||
tokenizer.save_pretrained("./trained_model/gpt")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
119
herbert.py
119
herbert.py
|
|
@ -1,119 +0,0 @@
|
||||||
import os
|
|
||||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
||||||
|
|
||||||
import torch
|
|
||||||
import faiss
|
|
||||||
import numpy as np
|
|
||||||
from sentence_transformers import SentenceTransformer
|
|
||||||
from datasets import Dataset
|
|
||||||
from peft import LoraConfig, get_peft_model
|
|
||||||
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForLanguageModeling
|
|
||||||
|
|
||||||
# 1️⃣ Inicjalizacja modelu do embeddingów
|
|
||||||
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
|
|
||||||
|
|
||||||
# 2️⃣ Dodanie dokumentów i embeddingów
|
|
||||||
def read_documents_from_file(file_path):
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
|
||||||
content = file.read()
|
|
||||||
articles = content.split('\n\n')
|
|
||||||
documents = []
|
|
||||||
for article in articles:
|
|
||||||
if article.strip().startswith('Art.'):
|
|
||||||
documents.append(article.strip())
|
|
||||||
return documents
|
|
||||||
#documents = [
|
|
||||||
# "Jak założyć firmę w Polsce?",
|
|
||||||
# "Jak rozliczyć podatek VAT?",
|
|
||||||
# "Procedura składania reklamacji w e-sklepie.",
|
|
||||||
# "Jakie dokumenty są potrzebne do rejestracji działalności?"
|
|
||||||
#]
|
|
||||||
file_path = './docs/kodekspracy.txt' # Zmień na właściwą ścieżkę
|
|
||||||
documents = read_documents_from_file(file_path)
|
|
||||||
embeddings = embed_model.encode(documents)
|
|
||||||
|
|
||||||
# 3️⃣ Inicjalizacja FAISS i dodanie wektorów
|
|
||||||
dim = embeddings.shape[1]
|
|
||||||
index = faiss.IndexFlatL2(dim)
|
|
||||||
index.add(np.array(embeddings, dtype=np.float32))
|
|
||||||
|
|
||||||
# 4️⃣ Przygotowanie danych treningowych
|
|
||||||
def create_training_data():
|
|
||||||
data = {
|
|
||||||
"text": documents,
|
|
||||||
"embedding": embeddings.tolist()
|
|
||||||
}
|
|
||||||
return Dataset.from_dict(data)
|
|
||||||
|
|
||||||
dataset = create_training_data()
|
|
||||||
|
|
||||||
# Podział danych na treningowe i ewaluacyjne
|
|
||||||
split_dataset = dataset.train_test_split(test_size=0.25)
|
|
||||||
train_dataset = split_dataset["train"]
|
|
||||||
eval_dataset = split_dataset["test"]
|
|
||||||
|
|
||||||
# 5️⃣ Ładowanie modelu Gemma 2B
|
|
||||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
||||||
model_name = "Lajonbot/vicuna-7b-v1.5-PL-lora_unload"
|
|
||||||
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16).to(device)
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
||||||
|
|
||||||
# 6️⃣ Konfiguracja LoRA
|
|
||||||
lora_config = LoraConfig(
|
|
||||||
r=8, lora_alpha=32, lora_dropout=0.1, bias="none", task_type="CAUSAL_LM"
|
|
||||||
)
|
|
||||||
model = get_peft_model(model, lora_config)
|
|
||||||
|
|
||||||
# 7️⃣ Tokenizacja danych
|
|
||||||
max_length = 384
|
|
||||||
|
|
||||||
def tokenize_function(examples):
|
|
||||||
return tokenizer(
|
|
||||||
examples["text"],
|
|
||||||
padding="max_length",
|
|
||||||
truncation=True,
|
|
||||||
max_length=max_length
|
|
||||||
)
|
|
||||||
|
|
||||||
tokenized_train = train_dataset.map(tokenize_function, batched=True)
|
|
||||||
tokenized_eval = eval_dataset.map(tokenize_function, batched=True)
|
|
||||||
|
|
||||||
# 8️⃣ Parametry treningu
|
|
||||||
training_args = TrainingArguments(
|
|
||||||
output_dir="./results",
|
|
||||||
eval_strategy="steps", # Ewaluacja co określoną liczbę kroków
|
|
||||||
eval_steps=500, # Ewaluacja co 500 kroków
|
|
||||||
save_strategy="steps", # Zapis modelu co określoną liczbę kroków
|
|
||||||
save_steps=500, # Zapis modelu co 500 kroków
|
|
||||||
learning_rate=1e-5,
|
|
||||||
per_device_train_batch_size=2,
|
|
||||||
per_device_eval_batch_size=2,
|
|
||||||
num_train_epochs=16,
|
|
||||||
weight_decay=0.01,
|
|
||||||
load_best_model_at_end=True, # Wczytaj najlepszy model na końcu
|
|
||||||
metric_for_best_model="loss", # Kryterium wyboru najlepszego modelu
|
|
||||||
greater_is_better=False, # Niższy loss = lepszy model
|
|
||||||
)
|
|
||||||
|
|
||||||
# 9️⃣ Data Collator
|
|
||||||
data_collator = DataCollatorForLanguageModeling(
|
|
||||||
tokenizer=tokenizer,
|
|
||||||
mlm=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# 🔟 Trening modelu
|
|
||||||
trainer = Trainer(
|
|
||||||
model=model,
|
|
||||||
args=training_args,
|
|
||||||
train_dataset=tokenized_train,
|
|
||||||
eval_dataset=tokenized_eval, # Dodany zestaw ewaluacyjny
|
|
||||||
data_collator=data_collator,
|
|
||||||
)
|
|
||||||
|
|
||||||
trainer.train()
|
|
||||||
|
|
||||||
# 1️⃣1️⃣ Zapis modelu
|
|
||||||
model.save_pretrained("./models/herbert")
|
|
||||||
tokenizer.save_pretrained("./models/herbert")
|
|
||||||
|
|
||||||
print("✅ Model został wytrenowany i zapisany!")
|
|
||||||
261
hft.py
261
hft.py
|
|
@ -1,261 +0,0 @@
|
||||||
import os
|
|
||||||
import torch
|
|
||||||
import torch.nn as nn
|
|
||||||
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForLanguageModeling
|
|
||||||
from datasets import Dataset
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import PyPDF2
|
|
||||||
import docx2txt
|
|
||||||
import pytesseract
|
|
||||||
from PIL import Image
|
|
||||||
from collections import defaultdict
|
|
||||||
from huggingface_hub import login
|
|
||||||
|
|
||||||
# Konfiguracja
|
|
||||||
os.environ['TORCH_USE_CUDA_DSA'] = '1'
|
|
||||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
||||||
login(token="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")
|
|
||||||
|
|
||||||
class SourceMapper:
|
|
||||||
def __init__(self):
|
|
||||||
self.source_to_idx = defaultdict(lambda: len(self.source_to_idx))
|
|
||||||
self.idx_to_source = {}
|
|
||||||
|
|
||||||
def add_source(self, source):
|
|
||||||
if source and source not in self.source_to_idx:
|
|
||||||
idx = self.source_to_idx[source]
|
|
||||||
self.idx_to_source[idx] = source
|
|
||||||
|
|
||||||
def get_idx(self, source):
|
|
||||||
return self.source_to_idx[source] if source else -1
|
|
||||||
|
|
||||||
def get_source(self, idx):
|
|
||||||
return self.idx_to_source.get(idx, "Unknown")
|
|
||||||
|
|
||||||
def load_file_catalog(catalog_path):
|
|
||||||
try:
|
|
||||||
with open(catalog_path, 'r', encoding='utf-8') as file:
|
|
||||||
return json.load(file)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Błąd wczytywania katalogu plików: {str(e)}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def identify_legal_document(filename, file_catalog):
|
|
||||||
base_name = os.path.splitext(filename)[0].lower()
|
|
||||||
return file_catalog.get(base_name, "Opracowanie własne")
|
|
||||||
|
|
||||||
def extract_text_from_file(file_path):
|
|
||||||
try:
|
|
||||||
_, ext = os.path.splitext(file_path)
|
|
||||||
ext = ext.lower()
|
|
||||||
|
|
||||||
if ext in ['.txt', '.md']:
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
|
||||||
return file.read()
|
|
||||||
elif ext == '.pdf':
|
|
||||||
text = ""
|
|
||||||
try:
|
|
||||||
with open(file_path, 'rb') as file:
|
|
||||||
reader = PyPDF2.PdfReader(file)
|
|
||||||
for page in reader.pages:
|
|
||||||
text += page.extract_text() or ""
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Błąd PDF: {str(e)}")
|
|
||||||
return text
|
|
||||||
elif ext in ['.doc', '.docx']:
|
|
||||||
return docx2txt.process(file_path)
|
|
||||||
elif ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff']:
|
|
||||||
return pytesseract.image_to_string(Image.open(file_path))
|
|
||||||
else:
|
|
||||||
print(f"Nieobsługiwany format pliku: {ext}")
|
|
||||||
return ""
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Błąd ekstrakcji tekstu: {str(e)}")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def prepare_dataset(directory, catalog_path, source_mapper):
|
|
||||||
file_catalog = load_file_catalog(catalog_path)
|
|
||||||
data = []
|
|
||||||
|
|
||||||
print(f"\n{'='*50}\nDIAGNOSTYKA DANYCH\n{'='*50}")
|
|
||||||
|
|
||||||
for root, _, files in os.walk(directory):
|
|
||||||
for file in files:
|
|
||||||
file_path = os.path.join(root, file)
|
|
||||||
print(f"\nPrzetwarzanie pliku: {file_path}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
text = extract_text_from_file(file_path)
|
|
||||||
if not text.strip():
|
|
||||||
print("Pominięto - brak tekstu")
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"Długość tekstu: {len(text)} znaków")
|
|
||||||
|
|
||||||
doc_type = identify_legal_document(file, file_catalog)
|
|
||||||
print(f"Rozpoznany typ dokumentu: {doc_type}")
|
|
||||||
|
|
||||||
if doc_type != "Opracowanie własne":
|
|
||||||
articles = re.split(r'(?i)(Art[\.\s]+\d+[\.\s]?)', text)
|
|
||||||
articles = [a.strip() for a in articles if a.strip()]
|
|
||||||
|
|
||||||
print(f"Znaleziono {len(articles)} fragmentów")
|
|
||||||
|
|
||||||
for i in range(0, len(articles)-1, 2):
|
|
||||||
article_number = articles[i]
|
|
||||||
article_content = articles[i+1]
|
|
||||||
|
|
||||||
if len(article_content) < 50:
|
|
||||||
continue
|
|
||||||
|
|
||||||
source = f"{doc_type}, {article_number}"
|
|
||||||
source_mapper.add_source(source)
|
|
||||||
data.append({
|
|
||||||
"text": f"{article_number} {article_content}",
|
|
||||||
"source_idx": source_mapper.get_idx(source)
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
clean_text = re.sub(r'\s+', ' ', text).strip()
|
|
||||||
chunks = [clean_text[i:i+512] for i in range(0, len(clean_text), 512)]
|
|
||||||
chunks = [c for c in chunks if c.strip()]
|
|
||||||
|
|
||||||
for chunk in chunks:
|
|
||||||
data.append({
|
|
||||||
"text": chunk,
|
|
||||||
"source_idx": -1
|
|
||||||
})
|
|
||||||
print(f"Dodano {len(chunks)} chunków")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Błąd podczas przetwarzania pliku: {str(e)}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"\nPodsumowanie przygotowania danych:")
|
|
||||||
print(f"Łączna liczba przykładów: {len(data)}")
|
|
||||||
if data:
|
|
||||||
print("Przykładowy wpis:")
|
|
||||||
print(json.dumps(data[0], indent=2, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
print("BRAK DANYCH - sprawdź diagnostykę powyżej")
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
class CustomModel(nn.Module):
|
|
||||||
def __init__(self, model_name, config):
|
|
||||||
super().__init__()
|
|
||||||
self.base_model = AutoModelForCausalLM.from_pretrained(model_name, config=config)
|
|
||||||
self.source_embedding = nn.Embedding(10000, config.hidden_size, padding_idx=-1)
|
|
||||||
|
|
||||||
for param in self.base_model.parameters():
|
|
||||||
param.requires_grad = False
|
|
||||||
for param in self.base_model.get_output_embeddings().parameters():
|
|
||||||
param.requires_grad = True
|
|
||||||
|
|
||||||
def forward(self, input_ids=None, attention_mask=None, labels=None, source_idx=None, **kwargs):
|
|
||||||
if source_idx is not None:
|
|
||||||
valid_indices = torch.clamp(source_idx, 0, self.source_embedding.num_embeddings-1)
|
|
||||||
source_embeds = self.source_embedding(valid_indices).unsqueeze(1)
|
|
||||||
inputs_embeds = self.base_model.get_input_embeddings()(input_ids) + source_embeds
|
|
||||||
return self.base_model(
|
|
||||||
inputs_embeds=inputs_embeds,
|
|
||||||
attention_mask=attention_mask,
|
|
||||||
labels=labels,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
return self.base_model(
|
|
||||||
input_ids=input_ids,
|
|
||||||
attention_mask=attention_mask,
|
|
||||||
labels=labels,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate(self, *args, **kwargs):
|
|
||||||
return self.base_model.generate(*args, **kwargs)
|
|
||||||
|
|
||||||
class CustomDataCollator(DataCollatorForLanguageModeling):
|
|
||||||
def torch_call(self, examples):
|
|
||||||
# Przetwórz podstawowe pola
|
|
||||||
input_ids = torch.stack([torch.tensor(ex["input_ids"]) for ex in examples])
|
|
||||||
attention_mask = torch.stack([torch.tensor(ex["attention_mask"]) for ex in examples])
|
|
||||||
labels = torch.stack([torch.tensor(ex["labels"]) for ex in examples])
|
|
||||||
|
|
||||||
batch = {
|
|
||||||
"input_ids": input_ids,
|
|
||||||
"attention_mask": attention_mask,
|
|
||||||
"labels": labels
|
|
||||||
}
|
|
||||||
|
|
||||||
# Dodaj source_idx jeśli istnieje
|
|
||||||
if "source_idx" in examples[0]:
|
|
||||||
source_idx = torch.stack([torch.tensor(ex["source_idx"]) for ex in examples])
|
|
||||||
batch["source_idx"] = source_idx
|
|
||||||
|
|
||||||
return batch
|
|
||||||
|
|
||||||
def main():
|
|
||||||
source_mapper = SourceMapper()
|
|
||||||
model_name = "crumb/nano-mistral"
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
||||||
tokenizer.pad_token = tokenizer.eos_token
|
|
||||||
|
|
||||||
# Przygotowanie danych
|
|
||||||
catalog_path = "catalog.json"
|
|
||||||
data = prepare_dataset("docs", catalog_path, source_mapper)
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
print("\nBrak danych do treningu!")
|
|
||||||
return
|
|
||||||
|
|
||||||
#dataset = Dataset.from_list(data)
|
|
||||||
dataset = Dataset.from_dict({k: [d[k] for d in data] for k in data[0]})
|
|
||||||
|
|
||||||
|
|
||||||
def tokenize_function(examples):
|
|
||||||
tokenized = tokenizer(
|
|
||||||
examples["text"],
|
|
||||||
truncation=True,
|
|
||||||
padding="max_length",
|
|
||||||
max_length=512,
|
|
||||||
return_tensors="pt"
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"input_ids": tokenized["input_ids"].squeeze(),
|
|
||||||
"attention_mask": tokenized["attention_mask"].squeeze(),
|
|
||||||
"labels": tokenized["input_ids"].squeeze().clone(),
|
|
||||||
"source_idx": examples["source_idx"] # Dodano bez konwersji do tensora
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenized_dataset = dataset.map(tokenize_function, batched=True, batch_size=16)
|
|
||||||
|
|
||||||
model = CustomModel(model_name, AutoModelForCausalLM.from_pretrained(model_name).config)
|
|
||||||
model.source_mapper = source_mapper
|
|
||||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
||||||
model.to(device)
|
|
||||||
|
|
||||||
training_args = TrainingArguments(
|
|
||||||
output_dir="./results",
|
|
||||||
num_train_epochs=3,
|
|
||||||
per_device_train_batch_size=2,
|
|
||||||
gradient_accumulation_steps=4,
|
|
||||||
learning_rate=2e-5,
|
|
||||||
fp16=torch.cuda.is_available(),
|
|
||||||
logging_steps=10,
|
|
||||||
save_strategy="steps",
|
|
||||||
save_steps=1000,
|
|
||||||
report_to="none",
|
|
||||||
remove_unused_columns=False
|
|
||||||
)
|
|
||||||
|
|
||||||
trainer = Trainer(
|
|
||||||
model=model,
|
|
||||||
args=training_args,
|
|
||||||
train_dataset=tokenized_dataset,
|
|
||||||
data_collator=CustomDataCollator(tokenizer=tokenizer, mlm=False)
|
|
||||||
)
|
|
||||||
|
|
||||||
print("\nRozpoczęcie treningu...")
|
|
||||||
trainer.train()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -0,0 +1,385 @@
|
||||||
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import ollama
|
||||||
|
import weaviate
|
||||||
|
from weaviate.connect import ConnectionParams
|
||||||
|
from weaviate.collections.classes.filters import Filter
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import uvicorn
|
||||||
|
import httpx
|
||||||
|
from typing import List, Optional
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
from datetime import datetime
|
||||||
|
####
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import urllib.parse
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
OLLAMA_BASE_URL = "http://ollama:11434"
|
||||||
|
ES_BASE_URL = "http://elastic:9200"
|
||||||
|
WEAVIATE_URL = "http://weaviate:8080"
|
||||||
|
PROMPT_DIR_PATCH = "./prompts"
|
||||||
|
SEARXNG_BASE_URL = "http://searxng:8080"
|
||||||
|
|
||||||
|
# Inicjalizacja klientów
|
||||||
|
ollama_client = ollama.Client(host=OLLAMA_BASE_URL)
|
||||||
|
es = Elasticsearch(ES_BASE_URL)
|
||||||
|
weaviate_client = weaviate.WeaviateClient(
|
||||||
|
connection_params=ConnectionParams.from_params(
|
||||||
|
http_host="weaviate",
|
||||||
|
http_port=8080,
|
||||||
|
http_secure=False,
|
||||||
|
grpc_host="weaviate",
|
||||||
|
grpc_port=50051,
|
||||||
|
grpc_secure=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
weaviate_client.connect()
|
||||||
|
collection = weaviate_client.collections.get("Document")
|
||||||
|
files_content = None
|
||||||
|
|
||||||
|
class Message(BaseModel):
|
||||||
|
role: str
|
||||||
|
content: str
|
||||||
|
|
||||||
|
class ChatRequest(BaseModel):
|
||||||
|
model: str
|
||||||
|
messages: List[Message]
|
||||||
|
stream: Optional[bool] = False
|
||||||
|
options: Optional[dict] = None
|
||||||
|
|
||||||
|
class ChatResponse(BaseModel):
|
||||||
|
model: str
|
||||||
|
created_at: str
|
||||||
|
message: Message
|
||||||
|
done: bool
|
||||||
|
total_duration: int
|
||||||
|
load_duration: int
|
||||||
|
prompt_eval_count: int
|
||||||
|
prompt_eval_duration: int
|
||||||
|
eval_count: int
|
||||||
|
eval_duration: int
|
||||||
|
|
||||||
|
def read_text_files_from_directory(directory_path):
|
||||||
|
files_dict = {}
|
||||||
|
|
||||||
|
# Iterowanie przez wszystkie pliki w katalogu
|
||||||
|
for filename in os.listdir(directory_path):
|
||||||
|
# Sprawdzanie, czy plik ma rozszerzenie .txt
|
||||||
|
if filename.endswith('.txt'):
|
||||||
|
file_path = os.path.join(directory_path, filename)
|
||||||
|
try:
|
||||||
|
# Odczytywanie zawartości pliku
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
content = file.read()
|
||||||
|
# Dodawanie do słownika z nazwą pliku bez rozszerzenia jako kluczem
|
||||||
|
file_name_without_extension = os.path.splitext(filename)[0]
|
||||||
|
files_dict[file_name_without_extension] = content
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Błąd przy odczycie pliku {filename}: {e}")
|
||||||
|
|
||||||
|
return files_dict
|
||||||
|
|
||||||
|
def analyze_query(content):
|
||||||
|
analysis = ollama_client.chat(
|
||||||
|
model="gemma2:2b",
|
||||||
|
messages=[{"role": "user", "content": content}]
|
||||||
|
)
|
||||||
|
keywords = [word.strip() for word in analysis['message']['content'].split(',') if word.strip()]
|
||||||
|
print("Słowa kluczowe:", keywords)
|
||||||
|
return keywords
|
||||||
|
|
||||||
|
def extract_full_article(content, article_number):
|
||||||
|
pattern = rf"Art\.\s*{article_number}\..*?(?=Art\.\s*\d+\.|\Z)"
|
||||||
|
match = re.search(pattern, content, re.DOTALL)
|
||||||
|
if match:
|
||||||
|
return match.group(0).strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_relevant_fragment(content, query, context_size=100):
|
||||||
|
article_match = re.match(r"Art\.\s*(\d+)", query)
|
||||||
|
if article_match:
|
||||||
|
article_number = article_match.group(1)
|
||||||
|
full_article = extract_full_article(content, article_number)
|
||||||
|
if full_article:
|
||||||
|
return full_article
|
||||||
|
|
||||||
|
index = content.lower().find(query.lower())
|
||||||
|
if index != -1:
|
||||||
|
start = max(0, index - context_size)
|
||||||
|
end = min(len(content), index + len(query) + context_size)
|
||||||
|
return f"...{content[start:end]}..."
|
||||||
|
return content[:1000] + "..."
|
||||||
|
|
||||||
|
def hybrid_search(keywords, limit=100, alpha=0.5):
|
||||||
|
if isinstance(keywords, str):
|
||||||
|
keywords = [keywords]
|
||||||
|
|
||||||
|
query = " ".join(keywords)
|
||||||
|
|
||||||
|
#print(f"\nWyszukiwanie hybrydowe dla słowa kluczowego: '{query}'")
|
||||||
|
response = collection.query.hybrid(
|
||||||
|
query=query,
|
||||||
|
alpha=alpha,
|
||||||
|
limit=limit * 2
|
||||||
|
)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for obj in response.objects:
|
||||||
|
#print(f"UUID: {obj.uuid}")
|
||||||
|
relevant_fragment = extract_relevant_fragment(obj.properties['content'], query)
|
||||||
|
#print(f"Relewantny fragment:\n{relevant_fragment}")
|
||||||
|
print(f"Nazwa pliku: {obj.properties['fileName']}")
|
||||||
|
#print("---")
|
||||||
|
# Zmieniamy warunek na 'any' zamiast 'all'
|
||||||
|
#if any(term.lower() in relevant_fragment.lower() for term in keywords):
|
||||||
|
results.append({
|
||||||
|
"uuid": obj.uuid,
|
||||||
|
"relevant_fragment": relevant_fragment,
|
||||||
|
"file_name": obj.properties['fileName'],
|
||||||
|
"content_type": obj.properties['contentType'],
|
||||||
|
"keyword": query
|
||||||
|
})
|
||||||
|
#print(f"Dodano do wyników: {obj.uuid}")
|
||||||
|
|
||||||
|
if len(results) >= limit:
|
||||||
|
break
|
||||||
|
return results[:limit]
|
||||||
|
|
||||||
|
async def fetch_json(session, url):
|
||||||
|
async with session.get(url) as response:
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def fetch_text(session, url):
|
||||||
|
async with session.get(url) as response:
|
||||||
|
html = await response.text()
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
return soup.get_text()
|
||||||
|
|
||||||
|
async def process_search_results(query):
|
||||||
|
search_url = f"{SEARXNG_BASE_URL}/search?q={urllib.parse.quote(query)}&categories=general&format=json"
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
data = await fetch_json(session, search_url)
|
||||||
|
|
||||||
|
results = data.get("results", [])
|
||||||
|
results_sorted = sorted(results, key=lambda x: x.get("score", float('inf')))[:10]
|
||||||
|
|
||||||
|
tasks = [fetch_text(session, result["url"]) for result in results_sorted]
|
||||||
|
texts = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
save_to_weaviate([{
|
||||||
|
"fileName": result["url"],
|
||||||
|
"content": json.dumps({
|
||||||
|
"prompt": query,
|
||||||
|
"completion": text
|
||||||
|
}),
|
||||||
|
"contentHash": generate_content_hash(text)
|
||||||
|
} for result, text in zip(results_sorted, texts)])
|
||||||
|
|
||||||
|
def generate_content_hash(content):
|
||||||
|
return hashlib.sha256(content.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def save_to_weaviate(data):
|
||||||
|
try:
|
||||||
|
collection = weaviate_client.collections.get("Document")
|
||||||
|
for item in data:
|
||||||
|
filters = Filter.by_property("fileName").equal(item["fileName"])
|
||||||
|
existing_docs = collection.query.fetch_objects(filters=filters)
|
||||||
|
if existing_docs.objects:
|
||||||
|
return
|
||||||
|
|
||||||
|
collection.data.insert({
|
||||||
|
"fileName": item["fileName"],
|
||||||
|
"content": item["content"],
|
||||||
|
"contentHash": item["contentHash"],
|
||||||
|
"contentType": "website"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Błąd podczas dodawania informacji do bazy. Error: {e}")
|
||||||
|
|
||||||
|
@app.get("/api/tags")
|
||||||
|
async def tags_proxy():
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{OLLAMA_BASE_URL}/api/tags")
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
@app.get("/api/version")
|
||||||
|
async def tags_proxy():
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{OLLAMA_BASE_URL}/api/version")
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
@app.post("/api/generate")
|
||||||
|
async def generate_proxy(request: Request):
|
||||||
|
data = await request.json()
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(f"{OLLAMA_BASE_URL}/api/generate", json=data)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
@app.get("/api/models")
|
||||||
|
async def list_models():
|
||||||
|
try:
|
||||||
|
models = ollama_client.list()
|
||||||
|
return {"models": [model['name'] for model in models['models']]}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
async def stream_chat(model, messages, options):
|
||||||
|
try:
|
||||||
|
# Użycie httpx do asynchronicznego pobrania danych od Ollamy
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
async with client.stream(
|
||||||
|
"POST",
|
||||||
|
f"{OLLAMA_BASE_URL}/api/chat",
|
||||||
|
json={"model": model, "messages": messages, "stream": True, "options": options},
|
||||||
|
) as response:
|
||||||
|
async for line in response.aiter_lines():
|
||||||
|
yield line + "\n"
|
||||||
|
except Exception as e:
|
||||||
|
yield json.dumps({"error": str(e)}) + "\n"
|
||||||
|
|
||||||
|
def save_to_elasticsearch(index, request_data, response_data, search_data=None):
|
||||||
|
try:
|
||||||
|
def message_to_dict(message):
|
||||||
|
if isinstance(message, dict):
|
||||||
|
return {
|
||||||
|
"role": message.get("role"),
|
||||||
|
"content": message.get("content")
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"role": getattr(message, "role", None),
|
||||||
|
"content": getattr(message, "content", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isinstance(request_data.get("messages"), list):
|
||||||
|
request_data["messages"] = [message_to_dict(msg) for msg in request_data["messages"]]
|
||||||
|
|
||||||
|
if "message" in response_data:
|
||||||
|
response_data["message"] = message_to_dict(response_data["message"])
|
||||||
|
|
||||||
|
if "timestamp" in response_data:
|
||||||
|
response_data["timestamp"] = response_data["timestamp"].isoformat()
|
||||||
|
|
||||||
|
document = {
|
||||||
|
"request": request_data,
|
||||||
|
"response": response_data,
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if search_data:
|
||||||
|
document["vector_search"] = {
|
||||||
|
"keywords": search_data.get("keywords", []),
|
||||||
|
"results": search_data.get("results", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
json_document = json.dumps(document, default=str)
|
||||||
|
|
||||||
|
index_name = index
|
||||||
|
response = es.index(index=index_name, body=json_document)
|
||||||
|
|
||||||
|
#print(response)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error saving to Elasticsearch: {e}")
|
||||||
|
|
||||||
|
@app.post("/api/chat")
|
||||||
|
async def chat_endpoint(request: ChatRequest):
|
||||||
|
try:
|
||||||
|
files_content = read_text_files_from_directory(PROMPT_DIR_PATCH)
|
||||||
|
if files_content is None:
|
||||||
|
raise KeyError(f"Nie wczytano promptów!!!")
|
||||||
|
|
||||||
|
prompt_seach = files_content.get("prompt_seach")
|
||||||
|
if prompt_seach is None:
|
||||||
|
raise KeyError(f"Nie znaleziono promptu o nazwie '{prompt_seach}'.")
|
||||||
|
|
||||||
|
prompt_system = files_content.get("prompt_system")
|
||||||
|
if prompt_system is None:
|
||||||
|
raise KeyError(f"Nie znaleziono promptu o nazwie '{prompt_system}'.")
|
||||||
|
|
||||||
|
prompt_answer = files_content.get("prompt_answer")
|
||||||
|
if prompt_answer is None:
|
||||||
|
raise KeyError(f"Nie znaleziono promptu o nazwie '{prompt_answer}'.")
|
||||||
|
|
||||||
|
prompt_data = files_content.get("prompt_data")
|
||||||
|
if prompt_data is None:
|
||||||
|
raise KeyError(f"Nie znaleziono promptu o nazwie '{prompt_data}'.")
|
||||||
|
|
||||||
|
query = request.messages[-1].content if request.messages else ""
|
||||||
|
asyncio.run(process_search_results(query))
|
||||||
|
keywords = analyze_query(prompt_seach.format(query=query))
|
||||||
|
weaviate_results = hybrid_search(keywords)
|
||||||
|
prompt_data += "\n".join([f"Źródło: {doc['file_name']}\n{doc['relevant_fragment']}\n\n" for doc in weaviate_results])
|
||||||
|
|
||||||
|
#messages_with_context =[
|
||||||
|
# {"role": "system", "content": prompt_system},
|
||||||
|
# {"role": "system", "content": prompt_data},
|
||||||
|
# {"role": "user", "content": prompt_answer.format(query=query)}
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Zmieniamy, aby przekazać pełną historię wiadomości
|
||||||
|
messages_with_context =[
|
||||||
|
{"role": "system", "content": prompt_system},
|
||||||
|
{"role": "system", "content": prompt_data},
|
||||||
|
# Dodajemy wszystkie wiadomości z historii
|
||||||
|
*[{"role": message.role, "content": message.content} for message in request.messages],
|
||||||
|
{"role": "user", "content": prompt_answer.format(query=query)}
|
||||||
|
]
|
||||||
|
|
||||||
|
if request.stream:
|
||||||
|
return StreamingResponse(stream_chat(request.model, messages_with_context, request.options), media_type="application/json")
|
||||||
|
|
||||||
|
ollama_response = ollama_client.chat(
|
||||||
|
model=request.model,
|
||||||
|
messages=messages_with_context,
|
||||||
|
stream=False,
|
||||||
|
options=request.options
|
||||||
|
)
|
||||||
|
|
||||||
|
request_data = {
|
||||||
|
"query": query,
|
||||||
|
"messages": request.messages,
|
||||||
|
"options": request.options
|
||||||
|
}
|
||||||
|
response_data = {
|
||||||
|
"model": request.model,
|
||||||
|
"created_at": ollama_response.get('created_at', ''),
|
||||||
|
"message": ollama_response['message'],
|
||||||
|
"done": ollama_response.get('done', True),
|
||||||
|
"total_duration": ollama_response.get('total_duration', 0),
|
||||||
|
"load_duration": ollama_response.get('load_duration', 0),
|
||||||
|
"prompt_eval_count": ollama_response.get('prompt_eval_count', 0),
|
||||||
|
"prompt_eval_duration": ollama_response.get('prompt_eval_duration', 0),
|
||||||
|
"eval_count": ollama_response.get('eval_count', 0),
|
||||||
|
"eval_duration": ollama_response.get('eval_duration', 0)
|
||||||
|
}
|
||||||
|
save_to_elasticsearch("ably.do", request_data, response_data, {"keywords": keywords, "results": weaviate_results })
|
||||||
|
|
||||||
|
return ChatResponse(
|
||||||
|
model=request.model,
|
||||||
|
created_at=ollama_response.get('created_at', ''),
|
||||||
|
message=Message(
|
||||||
|
role=ollama_response['message']['role'],
|
||||||
|
content=ollama_response['message']['content']
|
||||||
|
),
|
||||||
|
done=ollama_response.get('done', True),
|
||||||
|
total_duration=ollama_response.get('total_duration', 0),
|
||||||
|
load_duration=ollama_response.get('load_duration', 0),
|
||||||
|
prompt_eval_count=ollama_response.get('prompt_eval_count', 0),
|
||||||
|
prompt_eval_duration=ollama_response.get('prompt_eval_duration', 0),
|
||||||
|
eval_count=ollama_response.get('eval_count', 0),
|
||||||
|
eval_duration=ollama_response.get('eval_duration', 0)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Na podstawie powyższych informacji odpowiedz na pytanie: {query}.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Oto fragmenty aktów prawnych i dokumentów powiązanych z pytaniem użytkownika: \n\n
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
Jesteś precyzyjnym narzędziem do generowania słów kluczowych z zakresu BHP i prawa pracy. Twoje zadanie to podanie WYŁĄCZNIE najistotniejszych terminów do wyszukiwania w bazie dokumentów prawnych.
|
||||||
|
|
||||||
|
Ścisłe zasady działania:
|
||||||
|
1. Jeśli zapytanie dotyczy konkretnego artykułu:
|
||||||
|
- Podaj TYLKO numer artykułu i nazwę kodeksu (np. "Art. 154, Kodeks pracy").
|
||||||
|
- NIE dodawaj żadnych dodatkowych słów.
|
||||||
|
|
||||||
|
2. Jeśli zapytanie nie odnosi się do konkretnego artykułu:
|
||||||
|
- Wybierz maksymalnie 3 najbardziej precyzyjne i specyficzne terminy związane z treścią zapytania.
|
||||||
|
- Unikaj ogólników takich jak „praca”, „pracownik”, „pracodawca” – chyba że są częścią specjalistycznego terminu prawnego.
|
||||||
|
|
||||||
|
3. Używaj wyłącznie terminów, które realnie występują w dokumentach prawnych lub specjalistycznych opracowaniach.
|
||||||
|
|
||||||
|
4. Nie dodawaj żadnych interpretacji, rozszerzeń ani dodatkowych komentarzy.
|
||||||
|
|
||||||
|
Format odpowiedzi:
|
||||||
|
- Odpowiedz wyłącznie listą słów kluczowych oddzielonych przecinkami, bez dodatkowego tekstu, wyjaśnień czy formatowania JSON.
|
||||||
|
|
||||||
|
Przykład:
|
||||||
|
- Zapytanie: Jak brzmi art. 3 Kodeksu pracy?
|
||||||
|
- Odpowiedź: Art. 3, Kodeks pracy
|
||||||
|
|
||||||
|
- Zapytanie: Obowiązki pracodawcy w zakresie BHP
|
||||||
|
- Odpowiedź: obowiązki pracodawcy, BHP, przepisy bezpieczeństwa
|
||||||
|
|
||||||
|
Zapytanie: {query}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
Jesteś asystentem – ekspertem ds. BHP. Twoim zadaniem jest udzielanie rzeczowych, precyzyjnych i merytorycznych odpowiedzi na pytania użytkownika na podstawie dostępnych aktów prawnych (np. kodeksów, rozporządzeń) oraz innych dokumentów, takich jak instrukcje stanowiskowe czy publikacje ekspertów.
|
||||||
|
|
||||||
|
### 📜 Zasady udzielania odpowiedzi:
|
||||||
|
1. **Korzystaj z dostępnych źródeł** – Twoje odpowiedzi powinny być oparte na dokumentach dostarczonych w kontekście.
|
||||||
|
2. **Cytuj akty prawne, jeśli to możliwe** – Jeśli użytkownik pyta o konkretny artykuł, przytocz jego treść w formie cytatu, np. "Art. 3 Kodeksu Pracy stanowi: 'Pracodawcą jest jednostka organizacyjna, choćby nie posiadała osobowości prawnej, a także osoba fizyczna, jeżeli zatrudniają one pracowników.'".
|
||||||
|
3. **Nie podawaj nazw plików źródłowych** – Możesz powoływać się na źródła (np. "Zgodnie z Kodeksem Pracy, art. 3..."), ale nie możesz ujawniać nazw plików zawierających te informacje.
|
||||||
|
4. **Wskazuj źródła przepisów** – Jeśli odpowiedź wynika z aktu prawnego, zaznacz to, np. "Zgodnie z art. 237 Kodeksu Pracy...".
|
||||||
|
5. **Jeżeli pytanie dotyczy zagadnienia, a nie konkretnego artykułu**, odpowiedź może być swobodniejsza, ale nadal musi być oparta na bazie wiedzy. Jeśli to możliwe, wskaż podstawę prawną lub dokument, który odnosi się do danej kwestii.
|
||||||
|
6. **Nie wymyślaj informacji spoza kontekstu** – Jeśli brakuje Ci danych w dostępnych dokumentach, poinformuj użytkownika, że potrzebne jest inne źródło.
|
||||||
|
|
||||||
|
### 📌 Przykłady odpowiedzi:
|
||||||
|
|
||||||
|
**🟢 Pytanie o konkretny artykuł:**
|
||||||
|
- **Pytanie:** Jak brzmi art. 3 Kodeksu Pracy?
|
||||||
|
- **Odpowiedź:** Art. 3 Kodeksu Pracy stanowi: *"Pracodawcą jest jednostka organizacyjna, choćby nie posiadała osobowości prawnej, a także osoba fizyczna, jeżeli zatrudniają one pracowników."*
|
||||||
|
|
||||||
|
**🟢 Pytanie o zagadnienie:**
|
||||||
|
- **Pytanie:** Kiedy pracownik może iść na urlop?
|
||||||
|
- **Odpowiedź:** Pracownik nabywa prawo do urlopu proporcjonalnie do przepracowanego okresu. Zgodnie z art. 154 Kodeksu Pracy, wymiar urlopu wynosi 20 dni dla pracownika zatrudnionego krócej niż 10 lat oraz 26 dni dla pracownika z co najmniej 10-letnim stażem. Urlop powinien być udzielany zgodnie z planem urlopowym lub na wniosek pracownika, jeśli pracodawca wyrazi na to zgodę.
|
||||||
|
|
||||||
|
**🟢 Pytanie bez jasnej podstawy prawnej:**
|
||||||
|
- **Pytanie:** Czy pracodawca musi zapewnić wodę pitną w miejscu pracy?
|
||||||
|
- **Odpowiedź:** Tak, zgodnie z przepisami BHP pracodawca jest zobowiązany do zapewnienia pracownikom dostępu do wody pitnej. Obowiązek ten wynika z przepisów rozporządzenia dotyczącego ogólnych przepisów BHP, które określają warunki higieniczne w miejscu pracy.
|
||||||
|
|
||||||
|
Dostosuj odpowiedzi do poziomu wiedzy użytkownika i nie podawaj nazw plików, w których znajdują się dokumenty źródłowe.
|
||||||
35
readme.md
35
readme.md
|
|
@ -1,35 +0,0 @@
|
||||||
|
|
||||||
# Przeczytaj uważnie przed uruchomieniem tego repo 📝
|
|
||||||
To jest biblia szkolenia modeli obsługiwanych przez Ably.do
|
|
||||||
|
|
||||||
## Konfiguracja Git 🔥
|
|
||||||
**git config --global credential.helper store** \
|
|
||||||
Przejdź do folderu, w którym będziesz przechowywał lokalne repo. (np. **cd /home**) \
|
|
||||||
Pobierz repo: \
|
|
||||||
**git clone https://repo.pokash.pl/POKASH.PL/ably.do.git** \
|
|
||||||
pierwszym razem zostaniesz poproszony o zalogowanie się.
|
|
||||||
|
|
||||||
## Konfiguracja Hugging Face Transpormers 🚀
|
|
||||||
**huggingface-cli login** \
|
|
||||||
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
|
|
||||||
|
|
||||||
**W przypadku niektórych obrazów modeli musisz przejść przez akceptację licencji**
|
|
||||||
|
|
||||||
## Trenowanie modelu 🔥
|
|
||||||
Przejdź do folderu, w którym będziesz pobierał wiedzę z repo. (np. /home). \
|
|
||||||
Pobierz najnowsze zmiany (**git pull**) \
|
|
||||||
Uruchom skrypt Python, który rozpocznie trenowanie modelu: \
|
|
||||||
**python3 hft.py**
|
|
||||||
|
|
||||||
## Wdrażanie modelu ✨
|
|
||||||
Po wytrenowaniu modelu,
|
|
||||||
musisz przekonwertować go do formatu GGUF, który obsługuje Ollama. \
|
|
||||||
Konwersja do GGUF
|
|
||||||
1. Skorzystaj z narzędzia dostarczonego przez Ollama do konwersji: \
|
|
||||||
**ollama convert your_model.bin --output your_model.gguf** \
|
|
||||||
2. Umieść przekonwertowany model w katalogu Ollama: \
|
|
||||||
**mv your_model.gguf ~/.ollama/models/**
|
|
||||||
|
|
||||||
Uruchomienie dostrojonego modelu \
|
|
||||||
Użyj nazwy swojego modelu w poleceniu: \
|
|
||||||
**ollama run your_model** *"Jakie są wymagania dotyczące ochrony słuchu?"*
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
torch>=2.0.1
|
fastapi
|
||||||
transformers>=4.30.2
|
uvicorn
|
||||||
datasets>=2.13.1
|
ollama
|
||||||
Pillow>=9.4.0
|
weaviate-client
|
||||||
pytesseract>=0.3.10
|
unidecode
|
||||||
python-docx>=0.8.11
|
elasticsearch
|
||||||
PyPDF2>=3.0.1
|
aiohttp
|
||||||
huggingface-hub>=0.16.4
|
beautifulsoup4
|
||||||
22
test.py
22
test.py
|
|
@ -1,22 +0,0 @@
|
||||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
||||||
|
|
||||||
model_path = "./trained_model/gpt"
|
|
||||||
model = AutoModelForCausalLM.from_pretrained(model_path)
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
|
||||||
tokenizer.pad_token = tokenizer.eos_token
|
|
||||||
model.config.pad_token_id = tokenizer.eos_token_id
|
|
||||||
|
|
||||||
def generate_response(prompt, max_length=1000):
|
|
||||||
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True)
|
|
||||||
outputs = model.generate(
|
|
||||||
inputs.input_ids,
|
|
||||||
attention_mask=inputs.attention_mask,
|
|
||||||
pad_token_id=tokenizer.pad_token_id,
|
|
||||||
max_length=100
|
|
||||||
)
|
|
||||||
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
||||||
return response
|
|
||||||
|
|
||||||
prompt = "Zacytuj paragraf pierwszy artykułu 154 Kodeksu pracy."
|
|
||||||
response = generate_response(prompt)
|
|
||||||
print(response)
|
|
||||||
Loading…
Reference in New Issue