File size: 26,131 Bytes
7e48ec4
aae4d3d
b8beb50
55f46c1
 
f9a1a18
 
 
 
 
 
aae4d3d
f9a1a18
 
 
7e48ec4
edd5b40
7e48ec4
eebeb78
f9a1a18
a1f037d
 
 
 
12d3e1a
 
 
 
 
 
dc376b6
12d3e1a
1286e81
588b95c
f9a1a18
 
1286e81
 
dc376b6
eebeb78
b8beb50
d514965
7e48ec4
aae4d3d
63cd221
7e48ec4
1286e81
a1f037d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e48ec4
 
63cd221
 
 
 
7e48ec4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d514965
 
 
 
 
 
 
 
 
 
 
 
 
 
63cd221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5550d4
 
 
63cd221
e5550d4
 
 
63cd221
e5550d4
 
 
63cd221
 
7e48ec4
dc376b6
f9a1a18
 
 
 
 
 
 
63cd221
f9a1a18
 
 
 
 
 
 
aae4d3d
 
 
 
12d3e1a
1286e81
 
a1f037d
 
 
f9a1a18
7e48ec4
1286e81
4b27032
e5550d4
d514965
4b27032
1286e81
3d2062e
a1f037d
e5550d4
a1f037d
f9a1a18
 
 
 
 
dc376b6
d514965
a1f037d
 
 
 
7e48ec4
 
1286e81
a1f037d
7e48ec4
f9a1a18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aae4d3d
 
 
 
f9a1a18
 
 
 
 
 
 
 
 
 
e5550d4
 
 
f9a1a18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1286e81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1f037d
1286e81
a1f037d
1286e81
 
 
 
 
dc376b6
1286e81
 
 
 
 
dc376b6
 
 
 
 
 
 
 
 
 
 
 
 
 
1286e81
 
dc376b6
 
 
1286e81
dc376b6
 
 
 
 
 
 
 
 
1286e81
 
dc376b6
1286e81
f9a1a18
dc376b6
 
 
f9a1a18
 
 
dc376b6
f9a1a18
 
 
 
dc376b6
1286e81
cb23311
a1f037d
e1d2a79
1286e81
 
f9a1a18
7e48ec4
75f900c
 
 
 
b8beb50
63cd221
e5550d4
63cd221
85ee925
 
1286e81
9acef67
75f900c
9acef67
 
 
 
75f900c
7e48ec4
 
 
 
75f900c
1286e81
9ee4c9e
63cd221
9ee4c9e
 
75f900c
9644984
 
 
7e48ec4
 
 
 
 
 
75f900c
 
 
7e48ec4
9ee4c9e
63cd221
9ee4c9e
 
9acef67
7e48ec4
 
 
 
9acef67
7e48ec4
1286e81
7e48ec4
 
 
f9a1a18
 
 
1286e81
 
 
 
 
85ee925
f9a1a18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5550d4
 
 
f9a1a18
 
 
 
 
 
 
5acd42c
 
 
f9a1a18
 
 
 
 
 
 
0872b76
 
 
 
 
 
 
5acd42c
0872b76
 
63cd221
0872b76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aae4d3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5550d4
 
 
aae4d3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5550d4
 
 
aae4d3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
from dataclasses import dataclass
from langchain_core.messages import HumanMessage
from typing import Any, List, Dict, Literal, Tuple, Optional, Union, cast

from pydantic import SecretStr
from _utils.Utils_Class import UtilsClass
from _utils.axiom_logs import AxiomLogs
from _utils.bubble_integrations.enviar_resposta_final import enviar_resposta_final
from _utils.gerar_documento_utils.contextual_retriever import ContextualRetriever
from _utils.gerar_documento_utils.llm_calls import agemini_answer
from _utils.gerar_documento_utils.prompts import (
    create_prompt_auxiliar_do_contextual_prompt,
    prompt_gerar_query_dinamicamente,
    prompt_para_gerar_titulo,
)
from _utils.langchain_utils.Chain_class import Chain
from _utils.langchain_utils.LLM_class import LLM, Google_llms
from _utils.langchain_utils.Prompt_class import Prompt
from _utils.langchain_utils.Vector_store_class import VectorStore
from _utils.utils import convert_markdown_to_HTML
from gerar_documento.serializer import (
    GerarDocumentoComPDFProprioSerializerData,
    GerarDocumentoSerializerData,
)
from setup.easy_imports import (
    Chroma,
    ChatOpenAI,
    PromptTemplate,
    BM25Okapi,
    Response,
    HuggingFaceEmbeddings,
)
import logging
from _utils.models.gerar_documento import (
    ContextualizedChunk,
    DocumentChunk,
    RetrievalConfig,
)
from cohere import Client
from _utils.langchain_utils.Splitter_class import Splitter
import time
from setup.tokens import openai_api_key, cohere_api_key
from setup.logging import Axiom
import tiktoken
from setup.environment import default_model


def reciprocal_rank_fusion(result_lists, weights=None):
    """Combine multiple ranked lists using reciprocal rank fusion"""
    fused_scores = {}
    num_lists = len(result_lists)
    if weights is None:
        weights = [1.0] * num_lists

    for i in range(num_lists):
        for doc_id, score in result_lists[i]:
            if doc_id not in fused_scores:
                fused_scores[doc_id] = 0
            fused_scores[doc_id] += weights[i] * score

    # Sort by score in descending order
    sorted_results = sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)

    return sorted_results


@dataclass
class GerarDocumentoUtils:
    axiom_instance: Axiom
    temperature = 0.0
    model = default_model

    def criar_output_estruturado(self, summaries: List[str | Any], sources: Any):
        structured_output = []
        for idx, summary in enumerate(summaries):
            source_idx = min(idx, len(sources) - 1)
            structured_output.append(
                {
                    "content": summary,
                    "source": {
                        "page": sources[source_idx]["page"],
                        "text": sources[source_idx]["content"][:200] + "...",
                        "context": sources[source_idx]["context"],
                        "relevance_score": sources[source_idx]["relevance_score"],
                        "chunk_id": sources[source_idx]["chunk_id"],
                    },
                }
            )

        return structured_output

    def ultima_tentativa_requisicao(self, prompt_gerar_documento_formatado):
        llm = LLM()
        resposta = llm.open_ai().invoke(prompt_gerar_documento_formatado)
        documento_gerado = resposta.content.strip()  # type: ignore
        if not documento_gerado:
            raise Exception(
                "Falha ao tentar gerar o documento final por 5 tentativas e também ao tentar na última tentativa com o chat-gpt 4o mini."
            )
        else:
            return documento_gerado

    def create_retrieval_config(
        self,
        serializer: Union[
            GerarDocumentoSerializerData, GerarDocumentoComPDFProprioSerializerData, Any
        ],
    ):
        return RetrievalConfig(
            num_chunks=serializer.num_chunks_retrieval,
            embedding_weight=serializer.embedding_weight,
            bm25_weight=serializer.bm25_weight,
            context_window=serializer.context_window,
            chunk_overlap=serializer.chunk_overlap,
        )

    async def checar_se_resposta_vazia_do_documento_final(
        self, llm_ultimas_requests: str, prompt: str
    ):
        llm = self.select_model_for_last_requests(llm_ultimas_requests)  # type: ignore
        documento_gerado = ""
        tentativas = 0

        while tentativas < 5 and not documento_gerado:
            tentativas += 1
            try:
                resposta = llm.invoke(prompt)
                if hasattr(resposta, "content") and resposta.content.strip():  # type: ignore
                    if isinstance(resposta.content, list):
                        resposta.content = "\n".join(resposta.content)  # type: ignore

                    documento_gerado = resposta.content.strip()  # type: ignore
                else:
                    print(f"Tentativa {tentativas}: resposta vazia ou inexistente.")
            except Exception as e:
                llm = self.select_model_for_last_requests("gemini-2.0-flash")
                print(f"Tentativa {tentativas}: erro ao invocar o modelo: {e}")
            time.sleep(5)

        if not documento_gerado:
            try:
                self.axiom_instance.send_axiom(
                    "TENTANDO GERAR DOCUMENTO FINAL COM GPT 4o-mini COMO ÚLTIMA TENTATIVA"
                )
                documento_gerado = self.ultima_tentativa_requisicao(prompt)
            except Exception as e:
                raise Exception(
                    "Falha ao gerar o documento final na última tentativa."
                ) from e

        return documento_gerado

    def select_model_for_last_requests(
        self,
        llm_ultimas_requests: Literal[
            "gpt-4o-mini", "deepseek-chat", "gemini-2.0-flash", "gemini-2.5-pro"
        ],
    ):
        llm_instance = LLM()
        if llm_ultimas_requests == "gpt-4o-mini":
            llm = ChatOpenAI(
                temperature=self.temperature,
                model=self.model,
                api_key=SecretStr(openai_api_key),
            )
        elif llm_ultimas_requests == "deepseek-chat":
            llm = llm_instance.deepseek()
        elif llm_ultimas_requests == "gemini-2.0-flash":
            llm = llm_instance.google_gemini(
                "gemini-2.0-flash", temperature=self.temperature
            )
        elif llm_ultimas_requests == "gemini-2.5-pro":
            llm = llm_instance.google_gemini(
                "gemini-2.5-pro-preview-05-06", temperature=self.temperature
            )
        elif llm_ultimas_requests == "gemini-2.5-flash":
            llm = llm_instance.google_gemini(
                "gemini-2.5-flash-preview-04-17", temperature=self.temperature
            )
        return llm


class GerarDocumento:
    lista_pdfs: List[str]
    should_use_llama_parse: bool
    all_PDFs_chunks: List[DocumentChunk]
    full_text_as_array: List[str]
    isBubble: bool
    chunks_processados: List[ContextualizedChunk] | List[DocumentChunk]
    resumo_auxiliar: str
    gerar_documento_utils: GerarDocumentoUtils
    utils = UtilsClass()
    llm = LLM()
    enhanced_vector_store: tuple[Chroma, BM25Okapi, List[str]]
    query_gerado_dinamicamente_para_o_vector_store: str
    structured_output: List[Any]
    texto_completo_como_html: str
    titulo_do_documento: str
    encoding_tiktoken = tiktoken.get_encoding("cl100k_base")
    serializer: Union[
        GerarDocumentoSerializerData, GerarDocumentoComPDFProprioSerializerData, Any
    ]

    def __init__(
        self,
        serializer: Union[
            GerarDocumentoSerializerData, GerarDocumentoComPDFProprioSerializerData, Any
        ],
        isBubble: bool,
        axiom_instance: Axiom,
    ):
        self.gerar_documento_utils = GerarDocumentoUtils(axiom_instance)
        self.gerar_documento_utils.temperature = serializer.gpt_temperature
        self.config = self.gerar_documento_utils.create_retrieval_config(serializer)
        self.serializer = serializer
        self.logger = logging.getLogger(__name__)
        # self.prompt_auxiliar = prompt_auxiliar
        self.gpt_model = serializer.model
        self.llm_temperature = serializer.gpt_temperature
        self.prompt_gerar_documento = serializer.prompt_gerar_documento
        self.should_use_llama_parse = serializer.should_use_llama_parse
        self.isBubble = isBubble
        self.is_contextualized_chunk = serializer.should_have_contextual_chunks
        self.contextual_retriever = ContextualRetriever(serializer)
        self.llm_ultimas_requests = serializer.llm_ultimas_requests

        self.cohere_client = Client(cohere_api_key)
        self.embeddings = HuggingFaceEmbeddings(model_name=serializer.hf_embedding)
        self.num_k_rerank = serializer.num_k_rerank
        self.model_cohere_rerank = serializer.model_cohere_rerank
        self.splitter = Splitter(serializer.chunk_size, serializer.chunk_overlap)
        self.prompt_gerar_documento_etapa_2 = serializer.prompt_gerar_documento_etapa_2
        self.prompt_gerar_documento_etapa_3 = serializer.prompt_gerar_documento_etapa_3

        self.vector_store = VectorStore(serializer.hf_embedding)
        self.axiom_instance: Axiom = axiom_instance
        self.ax = AxiomLogs(axiom_instance)

    async def get_text_and_pdf_chunks(self):
        all_PDFs_chunks, full_text_as_array = (
            await self.utils.handle_files.get_full_text_and_all_PDFs_chunks(
                self.lista_pdfs,
                self.splitter,
                self.should_use_llama_parse,
                self.isBubble,
            )
        )
        self.ax.texto_completo_pdf(full_text_as_array)

        self.all_PDFs_chunks = all_PDFs_chunks
        self.full_text_as_array = full_text_as_array
        return all_PDFs_chunks, full_text_as_array

    async def generate_chunks_processados(self):
        if self.is_contextualized_chunk:
            self.ax.inicio_requisicao_contextual()
            contextualized_chunks = (
                await self.contextual_retriever.contextualize_all_chunks(
                    self.all_PDFs_chunks, self.resumo_auxiliar, self.axiom_instance
                )
            )
            self.ax.fim_requisicao_contextual()

        chunks_processados = (
            contextualized_chunks
            if self.is_contextualized_chunk
            else self.all_PDFs_chunks
        )
        self.chunks_processados = chunks_processados
        if len(self.chunks_processados) == 0:
            self.chunks_processados = self.all_PDFs_chunks
        self.ax.chunks_inicialmente(self.chunks_processados)
        return self.chunks_processados

    async def generate_query_for_vector_store(self):
        prompt_para_gerar_query_dinamico = prompt_gerar_query_dinamicamente(
            cast(str, self.resumo_auxiliar)
        )

        self.axiom_instance.send_axiom(
            "COMEÇANDO REQUISIÇÃO PARA GERAR O QUERY DINAMICAMENTE DO VECTOR STORE"
        )
        response = await self.llm.google_gemini_ainvoke(
            prompt_para_gerar_query_dinamico,
            "gemini-2.0-flash",
            temperature=self.llm_temperature,
        )

        self.query_gerado_dinamicamente_para_o_vector_store = cast(
            str, response.content
        )

        self.axiom_instance.send_axiom(
            f"query_gerado_dinamicamente_para_o_vector_store: {self.query_gerado_dinamicamente_para_o_vector_store}",
        )

        return self.query_gerado_dinamicamente_para_o_vector_store

    async def create_enhanced_vector_store(self):
        vector_store, bm25, chunk_ids = self.vector_store.create_enhanced_vector_store(
            self.chunks_processados, self.is_contextualized_chunk, self.axiom_instance  # type: ignore
        )

        self.enhanced_vector_store = vector_store, bm25, chunk_ids
        return vector_store, bm25, chunk_ids

    def retrieve_with_rank_fusion(
        self, vector_store: Chroma, bm25: BM25Okapi, chunk_ids: List[str], query: str
    ) -> List[Dict]:
        """Combine embedding and BM25 retrieval results"""
        try:
            # Get embedding results
            embedding_results = vector_store.similarity_search_with_score(
                query, k=self.config.num_chunks
            )

            # Convert embedding results to list of (chunk_id, score)
            embedding_list = [
                (doc.metadata["chunk_id"], 1 / (1 + score))
                for doc, score in embedding_results
            ]

            # Get BM25 results
            tokenized_query = query.split()
            bm25_scores = bm25.get_scores(tokenized_query)

            # Convert BM25 scores to list of (chunk_id, score)
            bm25_list = [
                (chunk_ids[i], float(score)) for i, score in enumerate(bm25_scores)
            ]

            # Sort bm25_list by score in descending order and limit to top N results
            bm25_list = sorted(bm25_list, key=lambda x: x[1], reverse=True)[
                : self.config.num_chunks
            ]

            # Normalize BM25 scores
            calculo_max = max(
                [score for _, score in bm25_list]
            )  # Criei este max() pois em alguns momentos estava vindo valores 0, e reclamava que não podia dividir por 0
            max_bm25 = calculo_max if bm25_list and calculo_max else 1
            bm25_list = [(doc_id, score / max_bm25) for doc_id, score in bm25_list]

            # Pass the lists to rank fusion
            result_lists = [embedding_list, bm25_list]
            weights = [self.config.embedding_weight, self.config.bm25_weight]

            combined_results = reciprocal_rank_fusion(result_lists, weights=weights)

            return combined_results  # type: ignore

        except Exception as e:
            self.logger.error(f"Error in rank fusion retrieval: {str(e)}")
            raise

    def rank_fusion_get_top_results(
        self,
        vector_store: Chroma,
        bm25: BM25Okapi,
        chunk_ids: List[str],
        query: str = "Summarize the main points of this document",
    ):
        # Get combined results using rank fusion
        ranked_results = self.retrieve_with_rank_fusion(
            vector_store, bm25, chunk_ids, query
        )

        # Prepare context and track sources
        contexts = []
        sources = []

        # Get full documents for top results
        for chunk_id, score in ranked_results[: self.config.num_chunks]:
            results = vector_store.get(
                where={"chunk_id": chunk_id}, include=["documents", "metadatas"]
            )

            if results["documents"]:
                context = results["documents"][0]
                metadata = results["metadatas"][0]

                contexts.append(context)
                sources.append(
                    {
                        "content": context,
                        "page": metadata["page"],
                        "chunk_id": chunk_id,
                        "relevance_score": score,
                        "context": metadata.get("context", ""),
                    }
                )

        return sources, contexts

    async def do_last_requests(
        self,
    ) -> List[Dict]:
        try:
            self.axiom_instance.send_axiom("COMEÇANDO A FAZER ÚLTIMA REQUISIÇÃO")
            vector_store, bm25, chunk_ids = self.enhanced_vector_store

            sources, contexts = self.rank_fusion_get_top_results(
                vector_store,
                bm25,
                chunk_ids,
                self.query_gerado_dinamicamente_para_o_vector_store,
            )

            prompt_gerar_documento = PromptTemplate(
                template=cast(str, self.prompt_gerar_documento),
                input_variables=["context"],
            )

            llm_ultimas_requests = self.llm_ultimas_requests
            prompt_instance = Prompt()
            context_do_prompt_primeira_etapa = "\n\n".join(contexts)
            prompt_primeira_etapa = prompt_gerar_documento.format(
                context=context_do_prompt_primeira_etapa,
            )

            self.gerar_documento_utils.model = self.gpt_model
            self.gerar_documento_utils.temperature = self.llm_temperature
            documento_gerado = await self.gerar_documento_utils.checar_se_resposta_vazia_do_documento_final(
                llm_ultimas_requests, prompt_primeira_etapa
            )

            texto_final_juntando_as_etapas = ""
            resposta_primeira_etapa = documento_gerado
            texto_final_juntando_as_etapas += resposta_primeira_etapa
            self.axiom_instance.send_axiom(
                f"RESULTADO ETAPA 1: {resposta_primeira_etapa}"
            )

            if self.prompt_gerar_documento_etapa_2:
                self.axiom_instance.send_axiom("GERANDO DOCUMENTO - COMEÇANDO ETAPA 2")
                prompt_etapa_2 = prompt_instance.create_and_invoke_prompt(
                    self.prompt_gerar_documento_etapa_2,
                    dynamic_dict={"context": context_do_prompt_primeira_etapa},
                )
                # documento_gerado = llm.invoke(prompt_etapa_2).content
                documento_gerado = self.gerar_documento_utils.checar_se_resposta_vazia_do_documento_final(
                    llm_ultimas_requests, prompt_etapa_2.to_string()
                )
                resposta_segunda_etapa = documento_gerado
                texto_final_juntando_as_etapas += (
                    f"\n\nresposta_segunda_etapa:{resposta_segunda_etapa}"
                )
                self.axiom_instance.send_axiom(f"RESULTADO ETAPA 2: {documento_gerado}")

            if self.prompt_gerar_documento_etapa_3:
                self.axiom_instance.send_axiom("GERANDO DOCUMENTO - COMEÇANDO ETAPA 3")
                prompt_etapa_3 = prompt_instance.create_and_invoke_prompt(
                    self.prompt_gerar_documento_etapa_3,
                    dynamic_dict={
                        "context": f"{resposta_primeira_etapa}\n\n{resposta_segunda_etapa}"
                    },
                )
                # documento_gerado = llm.invoke(prompt_etapa_3).content
                documento_gerado = self.gerar_documento_utils.checar_se_resposta_vazia_do_documento_final(
                    llm_ultimas_requests, prompt_etapa_3.to_string()
                )
                texto_final_juntando_as_etapas += f"\n\n{documento_gerado}"
                self.axiom_instance.send_axiom(f"RESULTADO ETAPA 3: {documento_gerado}")

            # Split the response into paragraphs
            summaries = [
                p.strip() for p in texto_final_juntando_as_etapas.split("\n\n") if p.strip()  # type: ignore
            ]

            structured_output = self.gerar_documento_utils.criar_output_estruturado(
                summaries, sources
            )

            self.axiom_instance.send_axiom("TERMINOU DE FAZER A ÚLTIMA REQUISIÇÃO")
            self.structured_output = structured_output
            return structured_output

        except Exception as e:
            self.logger.error(f"Error generating enhanced summary: {str(e)}")
            raise

    async def generate_complete_text(self):
        texto_completo = "\n\n"

        for x in self.structured_output:
            texto_completo = texto_completo + x["content"] + "\n"
            x["source"]["text"] = x["source"]["text"][0:200]
            x["source"]["context"] = x["source"]["context"][0:200]

        self.texto_completo_como_html = convert_markdown_to_HTML(
            texto_completo
        ).replace("resposta_segunda_etapa:", "<br><br>")

        self.axiom_instance.send_axiom(
            f"texto_completo_como_html: {self.texto_completo_como_html}"
        )

    async def get_document_title(self):
        if self.is_contextualized_chunk:
            resumo_para_gerar_titulo = self.resumo_auxiliar
        else:
            resumo_para_gerar_titulo = self.texto_completo_como_html

        prompt = prompt_para_gerar_titulo(resumo_para_gerar_titulo)
        response = await agemini_answer(
            prompt, "gemini-2.0-flash-lite", temperature=self.llm_temperature
        )
        self.titulo_do_documento = response
        return self.titulo_do_documento

    async def send_to_bubble(self):
        self.axiom_instance.send_axiom("COMEÇANDO A REQUISIÇÃO FINAL PARA O BUBBLE")

        enviar_resposta_final(
            self.serializer.doc_id,  # type: ignore
            self.serializer.form_response_id,  # type: ignore
            self.serializer.version,  # type: ignore
            self.texto_completo_como_html,
            False,
            cast(str, self.titulo_do_documento),
        )

        self.axiom_instance.send_axiom("TERMINOU A REQUISIÇÃO FINAL PARA O BUBBLE")

    async def gerar_ementa_final(
        self,
        llm_ultimas_requests: str,
        prompt_primeira_etapa: str,
        context_primeiro_prompt: str,
    ):

        llm = self.gerar_documento_utils.select_model_for_last_requests(llm_ultimas_requests)  # type: ignore
        prompt_instance = Prompt()

        documento_gerado = await self.gerar_documento_utils.checar_se_resposta_vazia_do_documento_final(
            llm_ultimas_requests, prompt_primeira_etapa
        )

        texto_final_juntando_as_etapas = ""
        resposta_primeira_etapa = documento_gerado
        texto_final_juntando_as_etapas += resposta_primeira_etapa
        self.axiom_instance.send_axiom(f"RESULTADO ETAPA 1: {resposta_primeira_etapa}")

        if self.prompt_gerar_documento_etapa_2:
            self.axiom_instance.send_axiom("GERANDO DOCUMENTO - COMEÇANDO ETAPA 2")
            prompt_etapa_2 = prompt_instance.create_and_invoke_prompt(
                self.prompt_gerar_documento_etapa_2,
                dynamic_dict={"context": context_primeiro_prompt},
            )
            documento_gerado = llm.invoke(prompt_etapa_2).content
            resposta_segunda_etapa = documento_gerado
            texto_final_juntando_as_etapas += (
                f"\n\nresposta_segunda_etapa:{resposta_segunda_etapa}"
            )
            self.axiom_instance.send_axiom(f"RESULTADO ETAPA 2: {documento_gerado}")

        if self.prompt_gerar_documento_etapa_3:
            self.axiom_instance.send_axiom("GERANDO DOCUMENTO - COMEÇANDO ETAPA 3")
            prompt_etapa_3 = prompt_instance.create_and_invoke_prompt(
                self.prompt_gerar_documento_etapa_3,
                dynamic_dict={
                    "context": f"{resposta_primeira_etapa}\n\n{resposta_segunda_etapa}"
                },
            )
            documento_gerado = llm.invoke(prompt_etapa_3).content
            texto_final_juntando_as_etapas += f"\n\n{documento_gerado}"
            self.axiom_instance.send_axiom(f"RESULTADO ETAPA 3: {documento_gerado}")

        return texto_final_juntando_as_etapas

    # Esta função gera a resposta que será usada em cada um das requisições de cada chunk
    async def get_response_from_auxiliar_contextual_prompt(self):
        llms = LLM()
        responses = []

        current_chunk = []
        current_token_count = 0
        chunk_counter = 1

        for part in self.full_text_as_array:
            part_tokens = len(self.encoding_tiktoken.encode(part))

            # Check if adding this part would EXCEED the limit
            if current_token_count + part_tokens > 600000:
                # Process the accumulated chunk before it exceeds the limit
                chunk_text = "".join(current_chunk)
                print(
                    f"\nProcessing chunk {chunk_counter} with {current_token_count} tokens"
                )

                prompt = create_prompt_auxiliar_do_contextual_prompt(chunk_text)
                response = await llms.google_gemini(
                    temperature=self.llm_temperature
                ).ainvoke([HumanMessage(content=prompt)])
                responses.append(response.content)

                # Start new chunk with current part
                current_chunk = [part]
                current_token_count = part_tokens
                chunk_counter += 1
            else:
                # Safe to add to current chunk
                current_chunk.append(part)
                current_token_count += part_tokens

        # Process the final remaining chunk
        if current_chunk:
            chunk_text = "".join(current_chunk)
            print(
                f"\nProcessing final chunk {chunk_counter} with {current_token_count} tokens"
            )
            prompt = create_prompt_auxiliar_do_contextual_prompt(chunk_text)
            response = await llms.google_gemini(
                temperature=self.llm_temperature
            ).ainvoke([HumanMessage(content=prompt)])
            responses.append(response.content)

        self.resumo_auxiliar = "".join(responses)
        self.ax.resumo_inicial_processo(self.resumo_auxiliar)

        return self.resumo_auxiliar

    def gerar_resposta_compilada(self):
        serializer = self.serializer
        return {
            "num_chunks_retrieval": serializer.num_chunks_retrieval,
            "embedding_weight": serializer.embedding_weight,
            "bm25_weight": serializer.bm25_weight,
            "context_window": serializer.context_window,
            "chunk_overlap": serializer.chunk_overlap,
            "num_k_rerank": serializer.num_k_rerank,
            "model_cohere_rerank": serializer.model_cohere_rerank,
            "more_initial_chunks_for_reranking": serializer.more_initial_chunks_for_reranking,
            "claude_context_model": serializer.claude_context_model,
            "gpt_temperature": serializer.gpt_temperature,
            "user_message": serializer.user_message,
            "model": serializer.model,
            "hf_embedding": serializer.hf_embedding,
            "chunk_size": serializer.chunk_size,
            "chunk_overlap": serializer.chunk_overlap,
            # "prompt_auxiliar": serializer.prompt_auxiliar,
            "prompt_gerar_documento": serializer.prompt_gerar_documento[0:200],
        }