- ВКонтакте
- РћРТвЂВВВВВВВВнокласснРСвЂВВВВВВВВРєРСвЂВВВВВВВВ
- РњРѕР№ Р В Р’В Р РЋРЎв„ўР В Р’В Р РЋРІР‚ВВВВВВВВРЎР‚
- Viber
- Skype
- Telegram
Помогите мне - новичку выстроить работу ИИ
Я только начинаю свой путь в работе с нейросетями. Но у меня возникают очевидные сложности, которые мне сложно решить самому, и я нуждаюсь в совете более опытных коллег :)
Сейчас у меня такая задача
У меня есть PDF файл (в перспективе их будет несколько сотен), я хочу развернуть локально какую-то модель, чтобы она отвечала мне строго по файлам
что я сделал:
я выбрал RAG
подход, распарсил PDF файл (код приведу ниже), после чего я сформировал векорную БД
далее логика такая:
пользователь делает запрос -> преобразуем его запрос в векторы -> получаем данные из векторной БД -> передаем LLM полученный контекст -> LLM дает релевантный ответ
Но я не могу определиться с тем, какая модель мне нужна и правильно ли я пользуюсь векторной БД, возможно чанки надо сделать больше или перекрытие. Важно, что вся информация в ПДФ файлах на русском (как я понял, что не каждая модель для этого подойдет), запросы пользователя, как и финальный промпт тоже на русском языке, ответ модели тоже на русском языке
сейчас я использовал llama3:8b , в целом мне нравится, как она отвечает, но проблема, как будто конеткст у нее неполный попадает, то сами ответы нерелевантные получаются, я бы сказал, что они слишком далекие от правды, возможно стоит использовать модель пожирнее или вообще что-то кроме llama3
буду безмерно благодарен за советы или ссылки на чтиво разного рода, которое могло бы помочь (английский или русский)
плюсом было бы то, что я мог бы запускать модель через ollama, так как наличие rest api для меня довольно таки важно
данную задумку я хочу реализовать на основе данных ФСНБ-2022, так как у них много данных как раз, эту идею мне подкинул ChatGPT :)
Сейчас я пытаюсь разобраться на основе одного файла прежде чем грузить все остальные
вот ссылка на скачивание этого файла с сайта Минстрой РФ
https://minstroyrf.gov.ru/trades/tree_download.php?folder=fsnb2022&ID=0
кто не доверяет ссылкам, то может сам найти файл
"Сборник ГЭСН 01 Земляные работы.pdf", данный файл находится в разделе ГЭСН
вот код парсера PDF pdf_parser.py
import pdfplumber
import json
from dataclasses import dataclass, asdict
from typing import List, Optional
from langchain.docstore.document import Document
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from tqdm import tqdm
import re
@dataclass
class Table:
header: List[str]
rows: List[List[str]]
raw_text: str
@dataclass
class Section:
title: str
content: str
tables: List[Table]
subsections: List['Section']
page_number: int
def clean_text(text: str) -> str:
text = re.sub(r'-\n\s*', '', text)
text = re.sub(r'\s+', ' ', text)
return text
def process_table(table) -> Optional[Table]:
if not table or not table[0]:
return None
header = [clean_text(cell) if cell else "" for cell in table[0]]
rows = [[clean_text(cell) if cell else "" for cell in row] for row in table[1:]]
raw_text = "\n".join([" | ".join(header)] + [" | ".join(row) for row in rows])
return Table(header=header, rows=rows, raw_text=raw_text)
def identify_section(text: str) -> bool:
section_patterns = [
r'Таблица ГЭСН \d{2}-\d{2}-\d{3}',
r'Состав работ:',
r'Измеритель:'
]
return any(re.search(pattern, text) for pattern in section_patterns)
def extract_sections(pdf_path) -> List[Section]:
sections = []
current_section = None
with pdfplumber.open(pdf_path) as pdf:
for page in tqdm(pdf.pages, desc="Processing pages"):
text_lines = page.extract_text_lines()
tables = page.extract_tables()
# Process text lines
current_text = ""
for line in text_lines:
text = clean_text(line['text'])
if identify_section(text):
if current_section:
if current_text:
current_section.content += f"\n{current_text}"
sections.append(current_section)
current_section = Section(
title=text,
content=text,
tables=[],
subsections=[],
page_number=page.page_number
)
current_text = ""
else:
current_text += f"\n{text}"
if current_section and current_text:
current_section.content += f"\n{current_text}"
# Process tables for current section
if current_section and tables:
for table in tables:
processed_table = process_table(table)
if processed_table:
current_section.tables.append(processed_table)
if current_section:
sections.append(current_section)
return sections
def section_to_document(section: Section) -> Document:
content = f"""
Title: {section.title}
Content: {section.content}
Tables:
{chr(10).join(table.raw_text for table in section.tables)}
"""
return Document(
page_content=content,
metadata={
"page_number": section.page_number,
"title": section.title,
"table_count": len(section.tables)
}
)
def build_vector_db(sections: List[Section], index_path="smeta_faiss_index"):
documents = [section_to_document(section) for section in sections]
text_splitter = RecursiveCharacterTextSplitter(chunk_size=3000, chunk_overlap=750)
docs_split = text_splitter.split_documents(documents)
# embeddings = HuggingFaceEmbeddings(model_name="ai-forever/sbert_large_mt_nlu_ru")
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
# embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-base")
vector_db = FAISS.from_documents(docs_split, embeddings)
vector_db.save_local(index_path)
return vector_db
def main():
pdf_path = "Сборник ГЭСН 01 Земляные работы.pdf"
sections = extract_sections(pdf_path)
with open("extracted_sections.json", "w", encoding="utf8") as f:
json.dump([asdict(section) for section in sections], f, indent=2, ensure_ascii=False)
vector_db = build_vector_db(sections, index_path="faiss_index_sectioned_all-MiniLM-L6-v2")
if __name__ == "__main__":
main()
а вот код бизнес логики, где уже пользователь делает запрос, его обработка и тд
import json
from typing import List, Dict, Any
import requests
from dataclasses import dataclass
from langchain_community.vectorstores import FAISS
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
@dataclass
class GesnFormat:
"""Формат ответа ГЭСН"""
price_number: str = ""
price_name: str = ""
works: List[str] = None
resources: List[Dict[str, str]] = None
def __post_init__(self):
if self.works is None:
self.works = []
if self.resources is None:
self.resources = []
def to_dict(self) -> dict:
return {
"price_number": self.price_number,
"price_name": self.price_name,
"works": self.works,
"resources": [
{
"resource_name": res.get("resource_name", ""),
"resource_number": res.get("resource_number", "")
}
for res in self.resources
]
}
class SmetaService:
def __init__(self, vector_db_path: str, llm_url: str = "http://localhost:11434/api/generate"):
"""
Инициализация сервиса
Args:
vector_db_path: путь к векторной БД
llm_url: URL для LLM API
"""
self.embeddings = HuggingFaceEmbeddings(
# model_name="ai-forever/sbert_large_mt_nlu_ru"
model_name="all-MiniLM-L6-v2"
)
self.vector_db = FAISS.load_local(
vector_db_path,
self.embeddings,
allow_dangerous_deserialization=True
)
self.llm_url = llm_url
def search_vector_db(self, query: str, k: int = 5) -> List[str]:
"""
Поиск в векторной базе
Args:
query: запрос для поиска
k: количество результатов
Returns:
List[str]: список найденных документов
"""
try:
results = self.vector_db.similarity_search(query, k=k)
return [doc.page_content for doc in results if hasattr(doc, 'page_content')]
except Exception as e:
print(f"Ошибка поиска в векторной БД: {e}")
return []
def query_llm(self, prompt: str, temperature: float = 0.3) -> str:
"""
Запрос к LLM модели
Args:
prompt: промпт для модели
temperature: температура генерации
Returns:
str: ответ модели
"""
payload = {
"model": "llama3:8b",
"prompt": prompt,
"temperature": temperature,
}
headers = {"Content-Type": "application/json"}
try:
response = requests.post(
self.llm_url,
json=payload,
headers=headers,
stream=True
)
response.raise_for_status()
full_response = ""
print("Генерация ответа:")
for line in response.iter_lines():
if line:
try:
data = json.loads(line.decode("utf-8"))
fragment = data.get("response", "")
full_response += fragment
print(fragment, end='', flush=True)
if data.get("done", False):
break
except json.JSONDecodeError as e:
print(f"\nОшибка декодирования ответа: {e}")
continue
print()
return full_response
except requests.exceptions.RequestException as e:
print(f"Ошибка запроса к LLM: {e}")
return ""
def prepare_prompt(self, context: List[str], query: str, output_format: str = "json") -> str:
if output_format == "json":
format_example = {
"price_number": "<номер расценки ГЭСН>",
"price_name": "<название расценки ГЭСН>",
"works": ["<название работы ГЭСН>"],
"resources": [
{
"resource_name": "<название ресурса>",
"resource_number": "<код ресурса>"
}
]
}
return f"""Контекст:
{chr(10).join(context)}
Запрос: {query}
Ответь только в формате JSON без каких-либо дополнительных пояснений или текста:
{json.dumps([format_example], ensure_ascii=False, indent=2)}
Важно:
1. Весь ответ должен быть только валидным JSON
2. Не добавляй никакого текста до или после JSON
3. Не добавляй пояснений или примечаний
4. Если нужно указать несколько расценок, добавь их в массив
"""
else:
return f"""Контекст:
{chr(10).join(context)}
Запрос: {query}
Предоставь информацию в следующем формате:
Расценка ГЭСН: (номер)
Название: (название расценки)
Состав работ:
- (работа 1)
- (работа 2)
...
Ресурсы:
- [код ресурса] название ресурса
"""
def generate_response(self, user_query: str, k: int = 5, output_format: str = "json") -> Any:
full_query = f"выведи расценки ГЭСН, состав работ, ресурсы для: {user_query}"
context = self.search_vector_db(full_query, k)
if not context:
return [GesnFormat().to_dict()] if output_format == "json" else "Не найдено подходящих расценок"
prompt = self.prepare_prompt(context, user_query, output_format)
response = self.query_llm(prompt)
if output_format == "json":
try:
result = json.loads(response)
if isinstance(result, list):
return result
return [result]
except json.JSONDecodeError as e:
print(f"Ошибка парсинга JSON: {e}")
return [GesnFormat().to_dict()]
else:
return response
def main():
service = SmetaService("faiss_index_sectioned_all-MiniLM-L6-v2")
while True:
try:
user_query = input(">>> Запрос (или 'exit' для выхода): ")
if user_query.lower() == 'exit':
break
result = service.generate_response(user_query, k=10)
# print(json.dumps(result, ensure_ascii=False, indent=2))
except KeyboardInterrupt:
break
except Exception as e:
print(f"Произошла ошибка: {e}")
if __name__ == "__main__":
main()