mirror of
https://github.com/hoshikawa2/first_contas.git
synced 2026-07-09 10:14:20 +00:00
219 lines
6.9 KiB
Python
219 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from contextvars import copy_context
|
|
from typing import Any
|
|
from agente_contas_tim.workflows.actions.registry import (
|
|
WorkflowRuntimeContext,
|
|
workflow_action,
|
|
)
|
|
from agente_contas_tim.workflows.runtime_types import ActionResult
|
|
from agente_contas_tim.workflows.actions.common.helpers import (
|
|
_result_failed_or_missing_data,
|
|
_runtime_llm_callbacks,
|
|
_runtime_llm_metadata,
|
|
_to_dict,
|
|
)
|
|
from agente_contas_tim.workflows.actions.rag.helpers import (
|
|
_build_rag_answer_item,
|
|
_extract_rag_documents,
|
|
_is_selected_rag_document,
|
|
_normalize_rag_queries,
|
|
_rag_document_text,
|
|
_rag_document_title,
|
|
)
|
|
|
|
_RAG_FALLBACK_MESSAGE = (
|
|
"Não foi possível buscar essa informação no momento. "
|
|
"Por favor, aguarde na linha para que possamos te ajudar de outra forma."
|
|
)
|
|
|
|
logger = logging.getLogger("agente_contas_tim.workflows.actions.tim_actions")
|
|
|
|
|
|
@workflow_action("buscar_informacao_rag")
|
|
def buscar_informacao_rag(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
queries = _normalize_rag_queries(params)
|
|
if not queries:
|
|
return ActionResult.fail("Informe uma pergunta para buscar na base.")
|
|
|
|
raw_top_k = params.get("top_k")
|
|
resolved_top_k: int | None = None
|
|
if raw_top_k is not None and str(raw_top_k).strip():
|
|
try:
|
|
resolved_top_k = int(raw_top_k)
|
|
except (TypeError, ValueError):
|
|
return ActionResult.fail("top_k invalido: informe um numero inteiro")
|
|
|
|
segment = str(params.get("segment", "")).strip()
|
|
results: list[dict[str, Any]] = []
|
|
documents: list[dict[str, Any]] = []
|
|
answer_parts: list[str] = []
|
|
retrieved_titles: list[str] = []
|
|
selected_titles: list[str] = []
|
|
metadata: dict[str, Any] = {}
|
|
|
|
def _execute(query: str) -> Any:
|
|
return runtime.factory.create_rag_search(
|
|
query=query,
|
|
top_k=resolved_top_k,
|
|
segment=segment,
|
|
).execute()
|
|
|
|
if len(queries) == 1:
|
|
raw_results = [_execute(queries[0])]
|
|
else:
|
|
with ThreadPoolExecutor(
|
|
max_workers=min(len(queries), 6),
|
|
thread_name_prefix="rag-search",
|
|
) as executor:
|
|
futures = [
|
|
executor.submit(copy_context().run, _execute, query)
|
|
for query in queries
|
|
]
|
|
raw_results = [future.result() for future in futures]
|
|
|
|
for query, result in zip(queries, raw_results):
|
|
if _result_failed_or_missing_data(result, state=state):
|
|
return ActionResult.fail(
|
|
result.error or "Falha na busca RAG",
|
|
**result.metadata,
|
|
)
|
|
|
|
metadata.update(result.metadata)
|
|
payload = _to_dict(result.data)
|
|
query_documents = _extract_rag_documents(payload)
|
|
query_total = len(query_documents)
|
|
query_message = (
|
|
f"Encontrei {query_total} trecho(s) relevante(s) na base."
|
|
if query_total
|
|
else "Nao encontrei trechos relevantes para essa busca."
|
|
)
|
|
query_answer = _build_rag_answer_item(query, query_documents)
|
|
results.append(
|
|
{
|
|
"query": query,
|
|
"total": query_total,
|
|
"documents": query_documents,
|
|
"message": query_message,
|
|
"answer": query_answer,
|
|
}
|
|
)
|
|
documents.extend(query_documents)
|
|
answer_parts.append(query_answer)
|
|
|
|
for doc in query_documents:
|
|
title = _rag_document_title(doc)
|
|
if not title:
|
|
continue
|
|
retrieved_titles.append(title)
|
|
if _is_selected_rag_document(doc):
|
|
selected_titles.append(title)
|
|
|
|
total = len(documents)
|
|
message = (
|
|
f"Encontrei {total} trecho(s) relevante(s) na base."
|
|
if total
|
|
else "Nao encontrei trechos relevantes para essa busca."
|
|
)
|
|
|
|
return ActionResult.ok(
|
|
{
|
|
"success": True,
|
|
"query": queries[0],
|
|
"queries": queries,
|
|
"total": total,
|
|
"documents": documents,
|
|
"results": results,
|
|
"answer": "\n\n".join(answer_parts),
|
|
"message": message,
|
|
"ragRetrievedDocuments": "|".join(retrieved_titles),
|
|
"ragSelectedDocuments": "|".join(selected_titles),
|
|
"noMatchRag": total == 0,
|
|
},
|
|
**metadata,
|
|
)
|
|
|
|
|
|
@workflow_action("reescrever_resposta_buscar_informacao")
|
|
def reescrever_resposta_buscar_informacao(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
raw_queries = params.get("queries")
|
|
queries: list[str] = []
|
|
if isinstance(raw_queries, (list, tuple)):
|
|
for item in raw_queries:
|
|
text = str(item or "").strip()
|
|
if text:
|
|
queries.append(text)
|
|
elif isinstance(raw_queries, str) and raw_queries.strip():
|
|
queries.append(raw_queries.strip())
|
|
|
|
raw_documents = params.get("documents")
|
|
documents: list[dict[str, Any]] = []
|
|
if isinstance(raw_documents, (list, tuple)):
|
|
for item in raw_documents:
|
|
if isinstance(item, dict):
|
|
documents.append(item)
|
|
|
|
rag_context_parts: list[str] = []
|
|
for doc in documents:
|
|
text = _rag_document_text(doc)
|
|
if text:
|
|
rag_context_parts.append(text)
|
|
rag_context = "\n\n".join(rag_context_parts).strip()
|
|
if not rag_context:
|
|
rag_context = str(params.get("answer", "") or "").strip()
|
|
|
|
no_match = bool(params.get("noMatchRag", False))
|
|
|
|
def _payload(answer: str, *, success: bool = True) -> dict[str, Any]:
|
|
return {
|
|
"success": success,
|
|
"answer": answer,
|
|
"noMatchRag": no_match,
|
|
}
|
|
|
|
if runtime.llm_gateway is None:
|
|
return ActionResult.ok(_payload(_RAG_FALLBACK_MESSAGE))
|
|
|
|
queries_text = "; ".join(queries) if queries else ""
|
|
try:
|
|
llm_result = runtime.llm_gateway.execute(
|
|
capability_id="fluxo_buscar_informacao_reescrita",
|
|
variables={
|
|
"queries": queries_text,
|
|
"rag_context": rag_context,
|
|
},
|
|
user_text=queries_text,
|
|
callbacks=_runtime_llm_callbacks(runtime),
|
|
tags=["workflow_action"],
|
|
metadata=_runtime_llm_metadata(runtime),
|
|
)
|
|
answer = str(getattr(llm_result, "content", "") or "").strip()
|
|
except Exception:
|
|
logger.exception(
|
|
"reescrever_resposta_buscar_informacao: falha ao invocar "
|
|
"capability fluxo_buscar_informacao_reescrita"
|
|
)
|
|
return ActionResult.ok(_payload(_RAG_FALLBACK_MESSAGE))
|
|
|
|
if not answer:
|
|
return ActionResult.ok(_payload(_RAG_FALLBACK_MESSAGE))
|
|
|
|
return ActionResult.ok(_payload(answer))
|
|
|
|
|
|
__all__ = [
|
|
'buscar_informacao_rag',
|
|
'reescrever_resposta_buscar_informacao',
|
|
]
|