File size: 4,529 Bytes
f5eb34f
 
 
 
1b447de
 
 
f5eb34f
 
 
1b447de
f5eb34f
1b447de
 
 
f5eb34f
1b447de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f5eb34f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1b447de
 
 
 
 
f5eb34f
 
1b447de
 
f5eb34f
1b447de
f5eb34f
 
 
 
 
 
 
 
 
 
 
 
1b447de
f5eb34f
 
 
 
 
 
 
 
 
1b447de
 
 
 
f5eb34f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Geração de respostas usando LLMs
"""
from typing import Optional, List, Dict, Any, Iterator
from .config import LLM_PROVIDER, DEFAULT_TEMPERATURE, DEFAULT_MAX_TOKENS
from .llms.factory import create_llm
from .llms.base import BaseLLM


class GenerationManager:
    """Gerenciador de geração de texto com suporte a múltiplos providers"""

    def __init__(self, provider: Optional[str] = None, model_id: Optional[str] = None):
        """
        Inicializa gerenciador de geração

        Args:
            provider: Nome do provider (huggingface, openai, anthropic, ollama)
                     Se None, usa LLM_PROVIDER do .env
            model_id: ID do modelo. Se None, usa default do provider
        """
        self.provider_name = provider or LLM_PROVIDER
        self.model_id = model_id
        self.llm: Optional[BaseLLM] = None

    def get_client(self) -> Optional[BaseLLM]:
        """Obtém cliente LLM (lazy loading com fallback)"""
        if self.llm is None:
            self.llm = create_llm(
                provider=self.provider_name,
                model_id=self.model_id,
                fallback=True
            )
        return self.llm

    def build_rag_prompt(
        self,
        question: str,
        contexts: List[Dict[str, Any]],
        system_prompt: Optional[str] = None
    ) -> str:
        """
        Constrói prompt para RAG

        Args:
            question: Pergunta do usuário
            contexts: Lista de contextos recuperados
            system_prompt: Prompt de sistema customizado

        Returns:
            Prompt formatado
        """
        if system_prompt is None:
            system_prompt = "Use os trechos fornecidos para responder à pergunta de forma precisa e concisa."

        context_text = "\n\n".join([
            f"Trecho {i+1} (fonte: {ctx['title']}):\n{ctx['content']}"
            for i, ctx in enumerate(contexts)
        ])

        prompt = f"""{system_prompt}

Contexto:
{context_text}

Pergunta: {question}

Resposta:"""

        return prompt

    def generate(
        self,
        prompt: str,
        temperature: float = DEFAULT_TEMPERATURE,
        max_tokens: int = DEFAULT_MAX_TOKENS
    ) -> str:
        """
        Gera resposta usando LLM

        Args:
            prompt: Prompt para o modelo
            temperature: Temperatura de geração
            max_tokens: Máximo de tokens a gerar

        Returns:
            Texto gerado
        """
        client = self.get_client()

        if client is None:
            return "Erro: Nenhum provider LLM disponível. Verifique as configurações no .env"

        if not client.is_available():
            error_info = client.get_model_info()
            return f"Erro: Provider {error_info.get('provider')} indisponível. {client.last_error}"

        try:
            response = client.generate(
                prompt=prompt,
                temperature=temperature,
                max_tokens=max_tokens
            )
            return response
        except Exception as e:
            return f"Erro na geração: {str(e)}"

    def generate_stream(
        self,
        prompt: str,
        temperature: float = DEFAULT_TEMPERATURE,
        max_tokens: int = DEFAULT_MAX_TOKENS
    ) -> Iterator[str]:
        """
        Gera resposta em streaming (se suportado pelo provider)

        Args:
            prompt: Prompt para o modelo
            temperature: Temperatura de geração
            max_tokens: Máximo de tokens a gerar

        Yields:
            Tokens gerados progressivamente
        """
        # Nota: Streaming ainda não implementado para todos os providers
        # Por enquanto, retorna resposta completa
        response = self.generate(prompt, temperature, max_tokens)
        yield response

    def format_sources(self, contexts: List[Dict[str, Any]]) -> str:
        """
        Formata fontes para exibição

        Args:
            contexts: Lista de contextos

        Returns:
            String formatada com fontes
        """
        if not contexts:
            return "\n\n**Fontes:** Nenhuma fonte encontrada"

        sources = []
        for i, ctx in enumerate(contexts, 1):
            preview = ctx['content'][:150] + "..." if len(ctx['content']) > 150 else ctx['content']
            score = ctx.get('score', 0)
            sources.append(
                f"{i}. **{ctx['title']}** (relevância: {score:.2%})\n   _{preview}_"
            )

        return "\n\n**Fontes:**\n" + "\n\n".join(sources)