Feat: Adds parallel and async process to calls and ocr
This commit is contained in:
208
code/app.py
208
code/app.py
@@ -3,21 +3,19 @@ from fastapi.security import APIKeyHeader
|
||||
from pydantic import BaseModel
|
||||
import uvicorn
|
||||
import boto3
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import io
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
from PyPDF2 import PdfReader
|
||||
|
||||
from PyPDF2 import PdfReader,PdfWriter
|
||||
from datetime import datetime
|
||||
from utils.langgraph_agent import RULES, run_agent
|
||||
from utils.secrets_manager import SECRETS
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
AWS_REGION = "us-east-2"
|
||||
INPUT_BUCKET="automated-pre-authorization"
|
||||
OUTPUT_BUCKET = "upflux-doc-analyzer"
|
||||
VERSION = "v1"
|
||||
|
||||
@@ -27,7 +25,9 @@ API_KEY = SECRETS["API-KEY"]
|
||||
|
||||
AWS_ACCESS_KEY =SECRETS["AWS_ACCESS_KEY"]
|
||||
AWS_SECRET_KEY = SECRETS["AWS_SECRET_KEY"]
|
||||
|
||||
_s3_input = boto3.client("s3",aws_access_key_id=AWS_ACCESS_KEY,aws_secret_access_key=AWS_SECRET_KEY,region_name=AWS_REGION)
|
||||
_s3_output = boto3.client("s3", region_name=AWS_REGION)
|
||||
_textract = boto3.client("textract", region_name=AWS_REGION)
|
||||
def verify_api_key(api_key: str = Security(_api_key_header)):
|
||||
if api_key != API_KEY:
|
||||
raise HTTPException(status_code=403, detail="Invalid API key")
|
||||
@@ -47,27 +47,6 @@ def parse_s3_uri(s3_uri: str) -> tuple[str, str]:
|
||||
return bucket, key
|
||||
|
||||
|
||||
def get_s3_input_client():
|
||||
"""S3 client with cross-account credentials for INPUT_BUCKET."""
|
||||
return boto3.client("s3",aws_access_key_id=AWS_ACCESS_KEY,aws_secret_access_key=AWS_SECRET_KEY,region_name=AWS_REGION)
|
||||
|
||||
|
||||
def get_s3_output_client():
|
||||
"""S3 client using ECS task role for OUTPUT_BUCKET."""
|
||||
return boto3.client("s3", region_name=AWS_REGION)
|
||||
|
||||
|
||||
def get_textract_client():
|
||||
return boto3.client("textract", region_name=AWS_REGION)
|
||||
|
||||
|
||||
def get_pdf_page_count(pdf_bytes: bytes) -> int:
|
||||
try:
|
||||
return len(PdfReader(io.BytesIO(pdf_bytes)).pages)
|
||||
except Exception:
|
||||
return 1
|
||||
|
||||
|
||||
def extract_text_from_textract_response(response: dict) -> str:
|
||||
if not response:
|
||||
return ""
|
||||
@@ -77,115 +56,87 @@ def extract_text_from_textract_response(response: dict) -> str:
|
||||
)
|
||||
|
||||
|
||||
def extract_text_from_s3_document(bucket: str, key: str) -> tuple[str, int]:
|
||||
"""Returns (extracted_text, page_count)."""
|
||||
s3_input = get_s3_input_client()
|
||||
s3_output = get_s3_output_client()
|
||||
textract = get_textract_client()
|
||||
def _split_pdf_pages(pdf_bytes: bytes) -> list[bytes]:
|
||||
reader = PdfReader(io.BytesIO(pdf_bytes))
|
||||
pages = []
|
||||
for page in reader.pages:
|
||||
writer = PdfWriter()
|
||||
writer.add_page(page)
|
||||
buf = io.BytesIO()
|
||||
writer.write(buf)
|
||||
pages.append(buf.getvalue())
|
||||
return pages
|
||||
|
||||
def _textract_detect_bytes(file_bytes: bytes) -> str:
|
||||
response = _textract.detect_document_text(Document={"Bytes": file_bytes})
|
||||
return extract_text_from_textract_response(response)
|
||||
|
||||
async def extract_text_from_s3_document(bucket: str, key: str) -> tuple[str, int]:
|
||||
file_bytes = await asyncio.to_thread(
|
||||
lambda: _s3_input.get_object(Bucket=bucket, Key=key)["Body"].read()
|
||||
)
|
||||
file_ext = Path(key).suffix.lower()
|
||||
|
||||
# Download file bytes using cross-account S3 credentials
|
||||
obj = s3_input.get_object(Bucket=bucket, Key=key)
|
||||
file_bytes = obj["Body"].read()
|
||||
|
||||
if file_ext in [".png", ".jpg", ".jpeg"]:
|
||||
# Pass bytes directly to Textract (avoids Textract needing cross-account S3 access)
|
||||
response = textract.detect_document_text(
|
||||
Document={"Bytes": file_bytes}
|
||||
)
|
||||
return extract_text_from_textract_response(response), 1
|
||||
text = await asyncio.to_thread(_textract_detect_bytes, file_bytes)
|
||||
return text, 1
|
||||
|
||||
if file_ext == ".pdf":
|
||||
page_count = get_pdf_page_count(file_bytes)
|
||||
|
||||
if page_count > 1:
|
||||
# Async API requires S3Object — copy to local bucket Textract can access
|
||||
temp_key = f"temp_textract/{Path(key).name}"
|
||||
s3_output.put_object(Bucket=OUTPUT_BUCKET, Key=temp_key, Body=file_bytes)
|
||||
|
||||
response = textract.start_document_text_detection(
|
||||
DocumentLocation={"S3Object": {"Bucket": OUTPUT_BUCKET, "Name": temp_key}}
|
||||
)
|
||||
job_id = response["JobId"]
|
||||
try:
|
||||
# Wait for job to complete
|
||||
while True:
|
||||
result = textract.get_document_text_detection(JobId=job_id)
|
||||
status = result["JobStatus"]
|
||||
if status == "SUCCEEDED":
|
||||
break
|
||||
elif status == "FAILED":
|
||||
return "", page_count
|
||||
time.sleep(2)
|
||||
|
||||
# Collect all blocks across paginated results
|
||||
all_blocks = result.get("Blocks", [])
|
||||
while "NextToken" in result:
|
||||
result = textract.get_document_text_detection(
|
||||
JobId=job_id, NextToken=result["NextToken"]
|
||||
)
|
||||
all_blocks.extend(result.get("Blocks", []))
|
||||
|
||||
return extract_text_from_textract_response({"Blocks": all_blocks}), page_count
|
||||
finally:
|
||||
s3_output.delete_object(Bucket=OUTPUT_BUCKET, Key=temp_key)
|
||||
else:
|
||||
# Single-page PDF — pass bytes directly to sync API
|
||||
response = textract.detect_document_text(
|
||||
Document={"Bytes": file_bytes}
|
||||
)
|
||||
return extract_text_from_textract_response(response), page_count
|
||||
page_bytes_list = await asyncio.to_thread(_split_pdf_pages, file_bytes)
|
||||
texts = await asyncio.gather(*[
|
||||
asyncio.to_thread(_textract_detect_bytes, p) for p in page_bytes_list
|
||||
])
|
||||
return "\n".join(texts), len(page_bytes_list)
|
||||
|
||||
return "", 0
|
||||
|
||||
|
||||
# --- Guia processing ---
|
||||
|
||||
def process_guia(guia: dict) -> dict:
|
||||
async def process_guia(guia: dict) -> dict:
|
||||
guia_code = guia.get("guia", {}).get("codigoGuiaLocal", "unknown")
|
||||
t_start = time.time()
|
||||
|
||||
# Step 1: Extract text from all anexos
|
||||
anexos = guia.get("anexos", [])
|
||||
all_extracted_texts = []
|
||||
|
||||
for anexo_idx, anexo in enumerate(anexos):
|
||||
async def _extract_anexo(anexo_idx: int, anexo: dict):
|
||||
s3_uri = anexo.get("urlAnexo") or anexo.get("URLAnexo", "")
|
||||
nome_arquivo = anexo.get("nomeArquivo", f"attachment_{anexo_idx}")
|
||||
|
||||
if not s3_uri or not s3_uri.startswith("s3://"):
|
||||
anexo["textoExtraido"] = ""
|
||||
continue
|
||||
|
||||
return None
|
||||
t0 = time.time()
|
||||
try:
|
||||
bucket, key = parse_s3_uri(s3_uri)
|
||||
extracted_text, page_count = extract_text_from_s3_document(bucket, key)
|
||||
extracted_text, page_count = await extract_text_from_s3_document(bucket, key)
|
||||
except Exception as e:
|
||||
extracted_text = ""
|
||||
page_count = 0
|
||||
anexo["error"] = str(e)
|
||||
|
||||
anexo["textoExtraido"] = extracted_text
|
||||
anexo["pageCount"] = page_count
|
||||
all_extracted_texts.append(f"--- {nome_arquivo} ---\n{extracted_text}")
|
||||
anexo["tempoExtracaoSegundos"] = round(time.time() - t0, 2)
|
||||
return f"--- {nome_arquivo} ---\n{extracted_text}"
|
||||
|
||||
file_content = "\n\n".join(all_extracted_texts)
|
||||
t_extracao_start = time.time()
|
||||
parts = await asyncio.gather(*[_extract_anexo(i, a) for i, a in enumerate(anexos)])
|
||||
file_content = "\n\n".join(p for p in parts if p)
|
||||
t_extracao = round(time.time() - t_extracao_start, 2)
|
||||
|
||||
# Step 2: For each servico, run the agent
|
||||
servicos = guia.get("servicos", [])
|
||||
avaliacao_resultados = []
|
||||
|
||||
for servico in servicos:
|
||||
async def _run_servico(servico: dict) -> dict:
|
||||
codigo_servico_raw = str(servico.get("codigoServico", ""))
|
||||
code = "".join(c for c in codigo_servico_raw if c.isdigit())
|
||||
|
||||
if code not in RULES:
|
||||
avaliacao_resultados.append({
|
||||
return {
|
||||
"codigoServico": codigo_servico_raw,
|
||||
"resultado": "SKIPPED",
|
||||
"motivo": f"Codigo '{code}' nao encontrado nas regras",
|
||||
"agentOutput": ""
|
||||
})
|
||||
continue
|
||||
"agentOutput": "",
|
||||
"tempoAgentSegundos": 0,
|
||||
}
|
||||
|
||||
query_data = {
|
||||
"atendimento": guia.get("atendimento", {}),
|
||||
@@ -195,8 +146,9 @@ def process_guia(guia: dict) -> dict:
|
||||
}
|
||||
query = json.dumps(query_data, indent=2, ensure_ascii=False)
|
||||
|
||||
t0 = time.time()
|
||||
try:
|
||||
result = run_agent(query, code, file_content)
|
||||
result = await run_agent(query, code, file_content)
|
||||
agent_output = result["response"]
|
||||
input_tokens = result["input_tokens"]
|
||||
output_tokens = result["output_tokens"]
|
||||
@@ -206,15 +158,25 @@ def process_guia(guia: dict) -> dict:
|
||||
input_tokens = 0
|
||||
output_tokens = 0
|
||||
|
||||
avaliacao_resultados.append({
|
||||
return {
|
||||
"codigoServico": codigo_servico_raw,
|
||||
"resultado": "Aprovado" if "aprov" in "".join(c for c in agent_output.lower() if c.isalnum() or c == ' ') else "Reprovado",
|
||||
"resultado": "Aprovado" if "".join(c for c in agent_output.lower() if c.isalpha()).startswith("aprov") else "Reprovado",
|
||||
"agentOutput": agent_output,
|
||||
"input_tokens": input_tokens,
|
||||
"output_tokens": output_tokens,
|
||||
})
|
||||
"tempoAgentSegundos": round(time.time() - t0, 2),
|
||||
}
|
||||
|
||||
t_agent_start = time.time()
|
||||
guia["avaliacaoAgente"] = list(await asyncio.gather(*[_run_servico(s) for s in servicos]))
|
||||
t_agent = round(time.time() - t_agent_start, 2)
|
||||
|
||||
guia["tempoProcessamento"] = {
|
||||
"extracaoSegundos": t_extracao,
|
||||
"agentSegundos": t_agent,
|
||||
"totalSegundos": round(time.time() - t_start, 2),
|
||||
}
|
||||
|
||||
guia["avaliacaoAgente"] = avaliacao_resultados
|
||||
return guia
|
||||
|
||||
|
||||
@@ -229,16 +191,15 @@ class ProcessRequest(BaseModel):
|
||||
|
||||
@app.post("/process", dependencies=[Security(verify_api_key)])
|
||||
async def process(request: ProcessRequest):
|
||||
results = []
|
||||
for idx, guia in enumerate(request.guias):
|
||||
try:
|
||||
enriched = process_guia(guia)
|
||||
results.append(enriched)
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"error": str(e),
|
||||
"guia": guia.get("guia", {}).get("codigoGuiaLocal", f"index_{idx}")
|
||||
})
|
||||
raw_results = await asyncio.gather(
|
||||
*[process_guia(guia) for guia in request.guias],
|
||||
return_exceptions=True
|
||||
)
|
||||
results = [
|
||||
{"error": str(r), "guia": request.guias[i].get("guia", {}).get("codigoGuiaLocal", f"index_{i}")}
|
||||
if isinstance(r, Exception) else r
|
||||
for i, r in enumerate(raw_results)
|
||||
]
|
||||
|
||||
response_body = {
|
||||
"status": "success",
|
||||
@@ -247,18 +208,21 @@ async def process(request: ProcessRequest):
|
||||
}
|
||||
|
||||
# Save result to S3
|
||||
# Save result to S3
|
||||
async def _save_guia(guia_result: dict):
|
||||
numero_guia = guia_result.get("guia", {}).get("codigoGuiaLocal", "unknown")
|
||||
key = f"{VERSION}/{numero_guia}_{timestamp}.json"
|
||||
await asyncio.to_thread(
|
||||
_s3_output.put_object,
|
||||
Bucket=OUTPUT_BUCKET,
|
||||
Key=key,
|
||||
Body=json.dumps(guia_result, ensure_ascii=False),
|
||||
ContentType="application/json",
|
||||
)
|
||||
|
||||
try:
|
||||
s3 = get_s3_output_client()
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
for guia_result in results:
|
||||
numero_guia = guia_result.get("guia", {}).get("codigoGuiaLocal", "unknown")
|
||||
key = f"{VERSION}/{numero_guia}_{timestamp}.json"
|
||||
s3.put_object(
|
||||
Bucket=OUTPUT_BUCKET,
|
||||
Key=key,
|
||||
Body=json.dumps(guia_result, ensure_ascii=False),
|
||||
ContentType="application/json",
|
||||
)
|
||||
await asyncio.gather(*[_save_guia(g) for g in results])
|
||||
except Exception as e:
|
||||
print(f"Error saving to S3: {e}")
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ This agent demonstrates a basic ReAct-style agent with tool calling capabilities
|
||||
import boto3
|
||||
import csv
|
||||
import json
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Annotated, TypedDict, Literal
|
||||
from langgraph.graph import StateGraph, END
|
||||
@@ -202,7 +203,7 @@ Para beneficiário de outras Unimed’s (importado)
|
||||
Ver item ATENDIMENTO INTERCÂMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
Todas as solicitações devem ser direcionadas para análise da Auditoria Médica,
|
||||
com a documentação mínima.""",
|
||||
com a Documentação.""",
|
||||
"4034906":"""Para beneficiário de outras Unimed’s (importado):
|
||||
Ver item ATENDIMENTO INTERCÃMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
@@ -230,7 +231,7 @@ Para beneficiário de outras Unimed’s (importado)
|
||||
Ver item ATENDIMENTO INTERCÂMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
Todas as solicitações devem ser direcionadas para análise da Auditoria Médica,
|
||||
com a documentação mínima.""",
|
||||
com a Documentação.""",
|
||||
"20103190":"""Autorizar sem o parecer da Auditoria Médica desde que a justificativa indique pelo
|
||||
menos uma das patologias abaixo ou CID’s relacionados:
|
||||
·Incontinência urinária (CID R32);
|
||||
@@ -272,7 +273,7 @@ Para beneficiário Unimed 0032 (atendimento local):
|
||||
Esse código não requer análise da Auditoria Médica, analisar baseado na cobertura e
|
||||
limite contratual do beneficiário.
|
||||
Caso seja necessário análise da auditoria médica devido alguma mensagem de
|
||||
negação, consultar o item "Documentação Mínima".
|
||||
negação, consultar o item "Documentação".
|
||||
Para beneficiário Unimed 0032 (atendimento em outra singular/Exportado):
|
||||
A liberação do procedimento não está condicionada a solicitação dos materiais, pois
|
||||
o mesmo poderá ser realizado sem a utilização da "Alça de Polipectomia" e da
|
||||
@@ -286,7 +287,7 @@ Para beneficiário Unimed 0032 (atendimento local):
|
||||
Esse código não requer análise da Auditoria Médica, analisar baseado na cobertura e
|
||||
limite contratual do beneficiário.
|
||||
Caso seja necessário análise da auditoria médica devido alguma mensagem de
|
||||
negação, consultar o item "Documentação Mínima".
|
||||
negação, consultar o item "Documentação".
|
||||
Para beneficiário Unimed 0032 (atendimento em outra singular/Exportado):
|
||||
A liberação do procedimento não está condicionada a solicitação dos materiais, pois
|
||||
o mesmo poderá ser realizado sem a utilização da "Alça de Polipectomia" e da
|
||||
@@ -311,53 +312,53 @@ Encaminhar para deliberação da Unimed origem.""",
|
||||
|
||||
}
|
||||
MIN_DOC={
|
||||
"20103190":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"20103190":"""Documentação
|
||||
Justificativa Médica e/ou indicação clínica.""",
|
||||
"20203020":"""DOCUMENTAÇÃO MÍNIMA:
|
||||
"20203020":"""Documentação:
|
||||
·
|
||||
Justificativa Médica e/ou indicação clínica.""",
|
||||
"31303293":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"31303293":"""Documentação
|
||||
Beneficiário 0032 (atendidos em Curitiba e em Outras cidades):
|
||||
·Justificativa médica e/ou indicação clínica;
|
||||
·Formulário de Solicitação - DIU Hormonal (ver anexo do script).
|
||||
Beneficiário Intercâmbio/Outras Unimeds:
|
||||
·
|
||||
Relatório Médico Detalhado (conforme racionalização).""",
|
||||
"40202542":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40202542":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado):
|
||||
Ver item ATENDIMENTO INTERCÃMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
Justificativa Médica e/ou indicação clínica.""",
|
||||
"40202550":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40202550":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado):
|
||||
Ver item ATENDIMENTO INTERCÃMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
Justificativa Médica e/ou indicação clínica.""",
|
||||
"40304906":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40304906":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado):
|
||||
Ver item ATENDIMENTO INTERCÃMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
Justificativa e/ou indicação clínica.""",
|
||||
"4034906":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"4034906":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado):
|
||||
Ver item ATENDIMENTO INTERCÃMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
Justificativa e/ou indicação clínica.""",
|
||||
"40314626":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40314626":"""Documentação
|
||||
Não há;""",
|
||||
"40314618":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40314618":"""Documentação
|
||||
Não há;""",
|
||||
"40323676":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40323676":"""Documentação
|
||||
Não há;""",
|
||||
"40901254":"""Justificativa Médica e/ou indicação clínica informando a idade gestacional e Laudo
|
||||
do 1º exame sonográfico gestacional realizado.""",
|
||||
"40901262":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40901262":"""Documentação
|
||||
Relatório médico informando a idade gestacional + laudo do 1o exame sonográfico
|
||||
gestacional realizado.""",
|
||||
"4091262":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"4091262":"""Documentação
|
||||
Relatório médico informando a idade gestacional + laudo do 1o exame sonográfico
|
||||
gestacional realizado.""",
|
||||
"4101230":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"4101230":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado)
|
||||
Ver item ATENDIMENTO INTERCÂMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
@@ -370,7 +371,7 @@ esforço físico;
|
||||
e/ ou TIMI risk;
|
||||
·
|
||||
Laudos de exames cardiológicos recentes.""",
|
||||
"4101230":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"4101230":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado)
|
||||
Ver item ATENDIMENTO INTERCÂMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
@@ -383,7 +384,7 @@ esforço físico;
|
||||
e/ ou TIMI risk;
|
||||
·
|
||||
Laudos de exames cardiológicos recentes.""",
|
||||
"41501144":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"41501144":"""Documentação
|
||||
PARA BENEFICIÁRIOS 0032 SENDO ATENDIDO EM CURITIBA, FORA (EXPORTADO),
|
||||
E PAC:
|
||||
·
|
||||
@@ -407,18 +408,18 @@ Esclarecemos que o envio de retinografia, poderá eventualmente ser solicitado p
|
||||
auditoria para fins de elucidação diagnóstica.
|
||||
PARA BENEFICIÁRIOS INTERCÂMBIO NACIONAL:
|
||||
SEGUIR DOCUMENTAÇÃO CONFORME RACIONALIZAÇÃO.""",
|
||||
"31005101":"""DOCUMENTAÇÃO MÍNIMA:
|
||||
"31005101":"""Documentação:
|
||||
·Relatório médico detalhado;
|
||||
·Laudo RX e/ou tomografia e/ou ressonância e/ou ultrassonografia.""",
|
||||
"31005470":"""DOCUMENTAÇÃO MÍNIMA:
|
||||
"31005470":"""Documentação:
|
||||
·Relatório médico detalhado;
|
||||
·Laudo RX e/ou tomografia e/ou ressonância e/ou ultrassonografia.""",
|
||||
"40808122":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40808122":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado):
|
||||
Ver item ATENDIMENTO INTERCÂMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
Justificativa e/ou indicação clínica.""",
|
||||
"40808130":"""DOCUMENTAÇÃO MÍNIMA
|
||||
"40808130":"""Documentação
|
||||
Para beneficiário de outras Unimed’s (importado):
|
||||
Ver item ATENDIMENTO INTERCÂMBIO.
|
||||
Para beneficiário Unimed 0032 (atendimento local):
|
||||
@@ -531,7 +532,7 @@ def create_agent(file_content: str = ""):
|
||||
return workflow.compile()
|
||||
|
||||
|
||||
def run_agent(query: str, code: str, file_content: str = "") -> str:
|
||||
async def run_agent(query: str, code: str, file_content: str = "") -> str:
|
||||
"""
|
||||
Run the agent with a given query.
|
||||
|
||||
@@ -544,14 +545,17 @@ def run_agent(query: str, code: str, file_content: str = "") -> str:
|
||||
The agent's final response
|
||||
"""
|
||||
agent = create_agent(file_content)
|
||||
SYSTEM_PROMPT = """You are a AI assistant responsible to check if a person is Allowed or Denied acces to medical procedure based on the following rules:
|
||||
<rules>
|
||||
SYSTEM_PROMPT = """You are a AI assistant responsible to check if a person is Allowed or Denied acces to medical procedure based on the inpout data. There are a few always accepted criteira in which, any of them been met, even a single one, will be accepted, these criteria been:
|
||||
<auto-accept-criteria>
|
||||
"""+RULES[code]+""""
|
||||
<\rules>
|
||||
Also this is the required documentation or infomation for aproval, equivalent information to the required one in another is allowed, do try to indetify every document as one of these if it fits, or contains the required information, or not identified:
|
||||
"""+MIN_DOC[code]+""""
|
||||
<\auto-accept-criteria>
|
||||
If those criteria aren´t met, you can check the documents to see if the following information are present, if so, aprove the procedure:
|
||||
<additional-information>"""+MIN_DOC[code]+"""
|
||||
<\additional-information>
|
||||
If the additional information is not present, but any of the auto-accept-criteira are met, allow the procedure.
|
||||
If there aren´t any auto-accept criteria present, check the documents for the additional information, and if they are all present, even if not in the exact type of document especified in them, allow the procedure.
|
||||
Your capabilities:
|
||||
- You can check the OCR of anexed documents if the json input is not enough to determinate if it should be aproved, using the check tool.
|
||||
- You can check the OCR of all the documents anexed, at the same time, if the json input is not enough to determinate if it should be aproved, using the check tool.
|
||||
For every document, check if the name of the person in json is present, and at output list every document and if it belongs to the person in the request.
|
||||
|
||||
Start your answer with either:
|
||||
@@ -559,8 +563,38 @@ Start your answer with either:
|
||||
Reprovado: If there aren't any rules met.
|
||||
|
||||
And list the document classification and the met criteira, in case of aprovation. Be really precise and succint.
|
||||
Start the response with either Aprovado or Reprovado, do not add any characters before either of them, even "*" """
|
||||
Start the response with either Aprovado or Reprovado, do not add any characters before either of them, even "*".
|
||||
You must start the message with the result, either Aprovado or Reprovado. It must be the first word at the output.
|
||||
<examples>
|
||||
Exemplos de saída:
|
||||
Aprovado
|
||||
Critério:
|
||||
Idade superior a 25 anos
|
||||
Documentos anexados:
|
||||
Nome Documento 1 - Pertence a pessoa FUlana
|
||||
Nome documento 2- Pertence a pessoa FUlana
|
||||
|
||||
Reprovado
|
||||
Critério:
|
||||
Nenhum crtiério preenchido e informações para aprovação faltando no documento
|
||||
|
||||
|
||||
Aprovado
|
||||
Critério:
|
||||
Fornecidos documentos nescessários contendo analise médica e pedido de exame
|
||||
Documentos anexados:
|
||||
Guia - Pertence a pessoa Fulana, contém pedido de exame, mesmo não sendo pedido de exame
|
||||
Laudo- Pertence a pessoa Fula, contém análise médica
|
||||
<\examples>
|
||||
<answer_format>
|
||||
(Aprovado ou Reprovado)
|
||||
Critério:
|
||||
######
|
||||
Documentos anexados:
|
||||
######
|
||||
######
|
||||
<\answer_format>
|
||||
Follow the answer format strictly, do not list the person data nor start with phrases like (Vou analisar a solicitação de ####### par a NOME DA PESSOA) or anything like it. Just stick with the format, dont add anything else """
|
||||
user_message = query
|
||||
if file_content:
|
||||
user_message += "\n\n<documentos_anexados>\n" + file_content + "\n</documentos_anexados>"
|
||||
@@ -572,13 +606,10 @@ Start your answer with either:
|
||||
]
|
||||
}
|
||||
|
||||
print(f"\nUser: {query}")
|
||||
print("-" * 50)
|
||||
|
||||
# Run the agent
|
||||
langfuse_handler = CallbackHandler()
|
||||
config = {"callbacks": [langfuse_handler]}
|
||||
final_state = agent.invoke(initial_state, config=config)
|
||||
final_state = await agent.ainvoke(initial_state, config=config)
|
||||
|
||||
# Get the final response
|
||||
final_message = final_state["messages"][-1]
|
||||
@@ -593,9 +624,7 @@ Start your answer with either:
|
||||
input_tokens += usage.get("input_tokens", 0)
|
||||
output_tokens += usage.get("output_tokens", 0)
|
||||
|
||||
langfuse.flush()
|
||||
print(f"Agent: {response}")
|
||||
print(f"Tokens - input: {input_tokens}, output: {output_tokens}")
|
||||
await asyncio.to_thread(langfuse.flush)
|
||||
return {"response": response, "input_tokens": input_tokens, "output_tokens": output_tokens}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user