File size: 4,751 Bytes
600d58a
 
 
 
 
1368f74
 
57a0f1d
1368f74
600d58a
ba52088
 
 
abfdf7a
83f207f
ba52088
1368f74
 
edc2f6f
1368f74
 
ba52088
5e35433
8befcd1
abfdf7a
1368f74
 
 
 
edc2f6f
1368f74
ba52088
5e35433
5048890
ba52088
abfdf7a
7b3fe08
ba52088
5e35433
ba52088
1368f74
7b3fe08
ba52088
5e35433
abfdf7a
ba52088
5e35433
ba52088
 
abfdf7a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from llama_index.core import VectorStoreIndex, Settings
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
from llama_index.core.prompts import PromptTemplate
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core.retrievers import QueryFusionRetriever
from my_logging import log_message
from config import CUSTOM_PROMPT, PROMPT_SIMPLE_POISK

def create_vector_index(documents):
    log_message("Строю векторный индекс")
    return VectorStoreIndex.from_documents(documents)

def create_query_engine(vector_index):
    try:
        bm25_retriever = BM25Retriever.from_defaults(
            docstore=vector_index.docstore, 
            similarity_top_k=50  
        )
        
        vector_retriever = VectorIndexRetriever(
            index=vector_index, 
            similarity_top_k=50,  
            similarity_cutoff=0.7
        )
        
        hybrid_retriever = QueryFusionRetriever(
            [vector_retriever, bm25_retriever],
            similarity_top_k=50,  
            num_queries=1
        )
        
        custom_prompt_template = PromptTemplate(PROMPT_SIMPLE_POISK)
        response_synthesizer = get_response_synthesizer(
            response_mode=ResponseMode.TREE_SUMMARIZE,  
            text_qa_template=custom_prompt_template
        )
        
        query_engine = RetrieverQueryEngine(
            retriever=hybrid_retriever,
            response_synthesizer=response_synthesizer
        )
        
        log_message("Query engine успешно создан")
        return query_engine
        
    except Exception as e:
        log_message(f"Ошибка создания query engine: {str(e)}")
        raise

def rerank_nodes(query, nodes, reranker, top_k=25, min_score_threshold=0.5, diversity_penalty=0.3):
    if not nodes or not reranker:
        return nodes[:top_k]
    
    try:
        log_message(f"Переранжирую {len(nodes)} узлов")
        
        pairs = [[query, node.text] for node in nodes]
        scores = reranker.predict(pairs)
        scored_nodes = list(zip(nodes, scores))
        
        scored_nodes.sort(key=lambda x: x[1], reverse=True)
        
        if min_score_threshold is not None:
            scored_nodes = [(node, score) for node, score in scored_nodes 
                          if score >= min_score_threshold]
            log_message(f"После фильтрации по порогу {min_score_threshold}: {len(scored_nodes)} узлов")
        
        if not scored_nodes:
            log_message("Нет узлов после фильтрации, снижаю порог")
            scored_nodes = list(zip(nodes, scores))
            scored_nodes.sort(key=lambda x: x[1], reverse=True)
            min_score_threshold = scored_nodes[0][1] * 0.6  
            scored_nodes = [(node, score) for node, score in scored_nodes 
                          if score >= min_score_threshold]
        
        selected_nodes = []
        selected_docs = set()
        selected_sections = set()
        
        for node, score in scored_nodes:
            if len(selected_nodes) >= top_k:
                break
            
            metadata = node.metadata if hasattr(node, 'metadata') else {}
            doc_id = metadata.get('document_id', 'unknown')
            section_key = f"{doc_id}_{metadata.get('section_path', metadata.get('section_id', ''))}"
            
            # Apply diversity penalty
            penalty = 0
            if doc_id in selected_docs:
                penalty += diversity_penalty * 0.5
            if section_key in selected_sections:
                penalty += diversity_penalty
            
            adjusted_score = score * (1 - penalty)
            
            # Add if still competitive
            if not selected_nodes or adjusted_score >= selected_nodes[0][1] * 0.6:
                selected_nodes.append((node, score))
                selected_docs.add(doc_id)
                selected_sections.add(section_key)
        
        log_message(f"Выбрано {len(selected_nodes)} узлов с разнообразием")
        log_message(f"Уникальных документов: {len(selected_docs)}, секций: {len(selected_sections)}")
        
        if selected_nodes:
            log_message(f"Score range: {selected_nodes[0][1]:.3f} to {selected_nodes[-1][1]:.3f}")
        
        return [node for node, score in selected_nodes]
        
    except Exception as e:
        log_message(f"Ошибка переранжировки: {str(e)}")
        return nodes[:top_k]