Spaces:
Sleeping
Sleeping
File size: 14,094 Bytes
89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 127bfda 89b6a21 | 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 | import re
from bot.byte_semantics_base import (
is_current_events_prompt,
is_follow_up_prompt,
is_high_risk_current_events_prompt,
is_serious_technical_prompt,
)
from bot.byte_semantics_constants import (
ANALYTICAL_STYLE_TERMS,
BRIEF_STYLE_TERMS,
EXISTENCE_QUESTION_TERMS,
GENERIC_RESPONSE_HINT_TERMS,
OVERCONFIDENT_HINT_TERMS,
PLAYFUL_STYLE_TERMS,
QUALITY_SAFE_FALLBACK,
QUALITY_STOPWORDS,
QUESTION_OPENING_TERMS,
RECOMMENDATION_HINT_TERMS,
REWRITE_REASON_GUIDANCE,
)
from bot.byte_semantics_current_events import (
build_server_time_anchor_instruction,
build_verifiable_prompt,
has_current_events_confidence_label,
has_current_events_source_anchor,
has_current_events_temporal_anchor,
has_current_events_uncertainty,
is_canonical_high_risk_fallback,
)
def build_research_priority_instruction(prompt: str) -> str:
if not is_serious_technical_prompt(prompt):
return ""
return (
"Instrucoes de pesquisa aprofundada: priorize evidencia recente (2025-2026) e fontes confiaveis "
"(universidades, orgaos de saude, journals). "
"Quando existir contexto em alta relacionado ao tema, incorpore esse contexto explicitamente. "
"Se houver contribuicao relevante do Brasil no tema, mencione de forma objetiva."
)
def build_anti_generic_contract_instruction(prompt: str) -> str:
clean_prompt = (prompt or "").strip()
if not clean_prompt:
return ""
return (
"Contrato anti-generico: responda o caso especifico da pergunta, sem texto enciclopedico amplo. "
"Nao use frases vagas como 'depende', 'em geral' ou 'cada caso' sem antes entregar resposta objetiva."
)
def build_direct_answer_instruction(prompt: str) -> str:
normalized = " ".join((prompt or "").lower().split()).strip(" ?!.,:")
if not normalized:
return ""
is_question = "?" in (prompt or "")
starts_like_question = normalized.startswith(QUESTION_OPENING_TERMS)
if not is_question and not starts_like_question:
return ""
instructions = [
"Formato de resposta obrigatorio: responda a pergunta principal na primeira linha, sem introducao."
]
if normalized.startswith(EXISTENCE_QUESTION_TERMS):
instructions.append("Se for pergunta de existencia, comece com 'Sim,' ou 'Nao,'.")
if any(term in normalized for term in RECOMMENDATION_HINT_TERMS):
instructions.append("Se houver opcoes concretas, cite 1 a 3 nomes diretamente relevantes.")
instructions.append("Evite texto generico e nao desvie do foco da pergunta.")
return " ".join(instructions)
def build_adaptive_ai_instruction(prompt: str) -> str:
lowered = (prompt or "").lower()
if any(term in lowered for term in BRIEF_STYLE_TERMS):
return "Estilo de resposta: ultra objetivo, no maximo 3 linhas."
if any(term in lowered for term in ANALYTICAL_STYLE_TERMS):
return "Estilo de resposta: analise pratica em ate 4 linhas, com 2 ou 3 pontos claros."
if any(term in lowered for term in PLAYFUL_STYLE_TERMS):
return "Estilo de resposta: humor leve e inteligente, sem perder precisao factual."
return ""
def _quality_tokenize(text: str) -> list[str]:
tokens = re.findall(r"[a-z0-9]{3,}", (text or "").lower())
normalized: list[str] = []
for token in tokens:
if len(token) > 4 and token.endswith("s"):
token = token[:-1]
normalized.append(token)
return normalized
def _extract_focus_terms(prompt: str, max_terms: int = 10) -> list[str]:
terms: list[str] = []
for token in _quality_tokenize(prompt):
if token in QUALITY_STOPWORDS:
continue
if token.isdigit() and len(token) < 4:
continue
if token in terms:
continue
terms.append(token)
if len(terms) >= max_terms:
break
return terms
def _count_focus_overlap(focus_terms: list[str], answer_tokens: set[str]) -> int:
if not focus_terms or not answer_tokens:
return 0
overlap = 0
for focus_term in focus_terms:
if focus_term in answer_tokens:
overlap += 1
continue
focus_prefix = focus_term[:5]
if len(focus_prefix) < 4:
continue
if any(
token.startswith(focus_prefix) or focus_term.startswith(token[:5])
for token in answer_tokens
if len(token) >= 4
):
overlap += 1
return overlap
def build_quality_prompt_script(prompt: str, server_time_instruction: str | None = None) -> str:
clean_prompt = (prompt or "").strip()
is_current = is_current_events_prompt(clean_prompt)
is_high_risk_current = is_high_risk_current_events_prompt(clean_prompt)
active_server_time_instruction = (
server_time_instruction or build_server_time_anchor_instruction()
)
lines = [
"Script de qualidade obrigatorio:\n"
"1) Responda a pergunta principal na primeira linha, sem introducao.\n"
"2) Inclua 1 ou 2 detalhes concretos ligados ao pedido (nome, data, local, obra ou numero).\n"
"3) Maximize densidade informacional: cada linha precisa trazer dado novo.\n"
"4) Corte frases vagas/repetitivas que nao agregam informacao.\n"
"5) Se houver baixa confianca, admita incerteza de forma explicita e peca 1 fonte."
]
if is_current:
lines.append(f"6) {active_server_time_instruction}")
lines.append(
"7) Para tema atual, separe explicitamente o que esta confirmado agora e o que ainda e rumor."
)
if is_high_risk_current:
lines.append(
"8) Para noticia/anuncio atual, inclua as linhas finais: 'Confianca: alta|media|baixa' e 'Fonte: ...'."
)
lines.append(
f"9) Se faltar confirmacao robusta, use exatamente: '{QUALITY_SAFE_FALLBACK}'"
)
else:
lines.append(
f"8) Se faltar confirmacao robusta, use exatamente: '{QUALITY_SAFE_FALLBACK}'"
)
else:
lines.append("6) Mantenha foco total no objeto perguntado.")
lines.append("7) Evite qualquer afirmacao absoluta sem base verificavel.")
lines.append(
"10) Nao termine com pergunta aberta, exceto se faltar dado essencial para responder."
)
return "\n".join(lines)
def is_low_quality_answer(prompt: str, answer: str) -> tuple[bool, str]:
clean_prompt = " ".join((prompt or "").split()).strip()
answer_lines = [
line.strip()
for line in (answer or "").replace("\r\n", "\n").replace("\r", "\n").split("\n")
if line.strip()
]
if not answer_lines:
return True, "resposta_vazia"
clean_answer = "\n".join(answer_lines)
compact_answer = " ".join(clean_answer.split()).strip()
if not compact_answer:
return True, "resposta_vazia"
normalized_prompt = " ".join(clean_prompt.lower().split()).strip(" ?!.,:")
lowered_answer = clean_answer.lower()
lowered_compact_answer = compact_answer.lower()
generic_hits = sum(1 for term in GENERIC_RESPONSE_HINT_TERMS if term in lowered_answer)
overconfident_hits = sum(1 for term in OVERCONFIDENT_HINT_TERMS if term in lowered_answer)
has_uncertainty = has_current_events_uncertainty(clean_answer)
has_temporal_anchor = has_current_events_temporal_anchor(clean_answer)
has_source_anchor = has_current_events_source_anchor(clean_answer)
has_confidence_label = has_current_events_confidence_label(clean_answer)
focus_terms = _extract_focus_terms(clean_prompt)
answer_tokens = set(_quality_tokenize(compact_answer))
overlap_count = _count_focus_overlap(focus_terms, answer_tokens)
first_line = answer_lines[0].strip().lower()
starts_too_generic = lowered_answer.startswith(
("depende", "em geral", "de forma geral", "geralmente", "na maioria dos casos")
)
prompt_is_complex = len(clean_prompt) >= 40
answer_is_short = len(clean_answer) < 70
follow_up_prompt = is_follow_up_prompt(clean_prompt)
current_events_prompt = is_current_events_prompt(clean_prompt)
current_events_high_risk = is_high_risk_current_events_prompt(clean_prompt)
existence_question = normalized_prompt.startswith(EXISTENCE_QUESTION_TERMS)
if (
"conexao com o modelo instavel" in lowered_answer
or "conexão com o modelo instável" in lowered_answer
):
return True, "modelo_indisponivel"
if generic_hits >= 2 and overlap_count <= 1:
return True, "resposta_generica"
if starts_too_generic and overlap_count <= 1:
return True, "abertura_generica"
if existence_question and not first_line.startswith(
("sim", "nao", "não", "existe", "tem", "ha ", "há ")
):
return True, "resposta_existencia_sem_posicao"
if prompt_is_complex and answer_is_short and overlap_count <= 1:
return True, "resposta_curta_sem_substancia"
if len(focus_terms) >= 3 and overlap_count == 0 and (generic_hits > 0 or answer_is_short):
return True, "off_topic"
if (
current_events_prompt
and not follow_up_prompt
and len(clean_prompt) >= 18
and not has_temporal_anchor
and not has_uncertainty
):
return True, "tema_atual_sem_ancora_temporal"
if (
current_events_high_risk
and has_uncertainty
and not is_canonical_high_risk_fallback(clean_answer)
):
return True, "incerteza_fora_fallback_canonico"
if current_events_high_risk and not follow_up_prompt and not has_confidence_label:
return True, "tema_atual_sem_confianca_explicita"
if (
current_events_high_risk
and not follow_up_prompt
and not has_uncertainty
and not has_source_anchor
and len(clean_answer) >= 120
):
return True, "tema_atual_sem_base_verificavel"
if (
current_events_prompt
and has_uncertainty
and "link/fonte" not in lowered_answer
and "fonte" not in lowered_answer
):
return True, "incerteza_sem_pedido_de_fonte"
if lowered_compact_answer.endswith("?") and not has_uncertainty and not follow_up_prompt:
return True, "termina_com_pergunta_aberta"
if overconfident_hits > 0 and not has_temporal_anchor and not has_uncertainty:
return True, "confianca_absoluta_sem_base"
return False, ""
def build_quality_rewrite_prompt(
prompt: str, draft_answer: str, reason: str, server_time_instruction: str | None = None
) -> str:
clean_prompt = (prompt or "").strip()
clean_draft = (draft_answer or "").strip()
safe_reason = (reason or "qualidade_insuficiente").strip()
active_server_time_instruction = (
server_time_instruction or build_server_time_anchor_instruction()
)
reason_fix = REWRITE_REASON_GUIDANCE.get(
safe_reason, "Ajuste a resposta para ficar objetiva, verificavel e aderente a pergunta."
)
return (
f"Pergunta original: {clean_prompt}\n"
f"Rascunho anterior reprovado ({safe_reason}): {clean_draft}\n"
"Reescreva para chat Twitch com as regras:\n"
f"- {active_server_time_instruction}\n"
f"- Correcao alvo: {reason_fix}\n"
"- Primeira linha responde direto.\n"
"- No maximo 4 linhas.\n"
"- Alta densidade: cada linha deve adicionar informacao nova.\n"
"- Inclua 1 ou 2 fatos concretos e relevantes.\n"
"- Para tema atual, diferencie confirmado agora vs rumor.\n"
"- Nao termine com pergunta aberta.\n"
"- Nao use frase vaga/generica.\n"
"- Se nao der para confirmar, use exatamente: "
f"'{QUALITY_SAFE_FALLBACK}'"
)
def build_llm_enhanced_prompt(prompt: str, server_time_instruction: str | None = None) -> str:
clean_prompt = (prompt or "").strip()
if not clean_prompt:
return ""
serious_mode = is_serious_technical_prompt(clean_prompt)
active_server_time_instruction = (
server_time_instruction or build_server_time_anchor_instruction()
)
enriched_prompt = build_verifiable_prompt(
clean_prompt,
concise_mode=not serious_mode,
server_time_instruction=active_server_time_instruction,
)
extra_instructions: list[str] = []
if active_server_time_instruction not in enriched_prompt:
extra_instructions.append(active_server_time_instruction)
anti_generic_instruction = build_anti_generic_contract_instruction(clean_prompt)
if anti_generic_instruction:
extra_instructions.append(anti_generic_instruction)
direct_answer_instruction = build_direct_answer_instruction(clean_prompt)
if direct_answer_instruction:
extra_instructions.append(direct_answer_instruction)
if is_follow_up_prompt(clean_prompt):
extra_instructions.append(
"Instrucoes de continuidade: trate esta pergunta como follow-up e resolva referencias vagas "
"(ex.: ele, ela, isso) usando o historico recente do chat."
)
research_instruction = build_research_priority_instruction(clean_prompt)
if research_instruction:
extra_instructions.append(research_instruction)
if serious_mode:
extra_instructions.append(
"Para tema tecnico serio e relevante, responda em uma unica mensagem. "
"Contrato obrigatorio: no maximo 4 linhas, alta densidade, sem separador de multiparte."
)
if not is_current_events_prompt(clean_prompt):
adaptive_instruction = build_adaptive_ai_instruction(clean_prompt)
if adaptive_instruction:
extra_instructions.append(adaptive_instruction)
quality_script = build_quality_prompt_script(
clean_prompt, server_time_instruction=active_server_time_instruction
)
if quality_script:
extra_instructions.append(quality_script)
if not extra_instructions:
return enriched_prompt
return f"{enriched_prompt}\n" + "\n".join(extra_instructions)
|