From 4e9e18faea9bade7b1285b58196067dcf7d30eec Mon Sep 17 00:00:00 2001 From: DNXBrasil Date: Fri, 23 Jan 2026 14:49:44 -0300 Subject: [PATCH] Adds documentation and fixes --- assistente/agent.py | 13 +- docs/README.md | 131 ++++++++++++++++ docs/api-reference.md | 273 ++++++++++++++++++++++++++++++++ docs/frontend-app.md | 269 ++++++++++++++++++++++++++++++++ docs/langfuse-guide.md | 319 ++++++++++++++++++++++++++++++++++++++ infra/ecs_alb/__main__.py | 60 +++++++ 6 files changed, 1058 insertions(+), 7 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/api-reference.md create mode 100644 docs/frontend-app.md create mode 100644 docs/langfuse-guide.md diff --git a/assistente/agent.py b/assistente/agent.py index 069dd7e..6024f08 100644 --- a/assistente/agent.py +++ b/assistente/agent.py @@ -12,10 +12,12 @@ import uuid import difflib import re from collections import Counter +import os +secrets=json.loads(secrets.get_secret()) langfuse = Langfuse( - public_key=json.loads(secrets.get_secret())['api-langfuse-public'], - secret_key=json.loads(secrets.get_secret())['api-langfuse-secret'], - host="http://3.218.244.68:3000" + public_key=secrets['api-langfuse-public'], + secret_key=secrets['api-langfuse-secret'], + host=os.environ["LANGFUSE_HOST"] ) @tool def out_of_scope_and_dont_know_answer(): @@ -277,7 +279,7 @@ Na prática, isso significa que o Comm.Pix monetiza o fluxo de pagamento, mas se O Comm.Pix ganha nas taxas já embutidas no valor pago pelo cliente, e não do parceiro. Assim, o vendor opera sem custos diretos e com total transparência sobre o valor que vai receber. <\general_info> -Answer the following questions as best you can, and use the """+language+"""language. You have access to the following tools: +Answer the following questions as best you can,and use the """+language+"""language. You have access to the following tools: {tools} @@ -289,12 +291,9 @@ Chat History:"""+str(history) #input_message=[{"role":"user","content":"aluno superior, nunca recebi auxilio, campus são paulo, Meu pai não é registrado, como faço para ganhar auxilio?"}] response="" started=False - finished=False - firstspan=[] for step in agent_executor.stream({"messages": input_message}, config, stream_mode="values"): if not started: with langfuse.start_as_current_span(name="my-operation") as span: - firstspan=span span.score_trace( name="Origem", value=origem, diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..531450f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,131 @@ +# Assistente Projeto Produto - Documentacao + +Esta documentacao cobre o sistema de assistente de IA construido com LangChain/LangGraph para suporte ao Comm.Pix. + +## Indice + +- [Visao Geral](#visao-geral) +- [Arquitetura](#arquitetura) +- [Componentes](#componentes) +- [Configuracao](#configuracao) +- [Uso](#uso) +- [Documentacao Adicional](#documentacao-adicional) + +## Visao Geral + +O Assistente e um agente RAG (Retrieval-Augmented Generation) que fornece suporte tecnico para usuarios da plataforma Comm.Pix. Ele utiliza: + +- **Claude Sonnet 4** via Amazon Bedrock para capacidades de LLM +- **Amazon Knowledge Bases** para recuperacao de documentos +- **DynamoDB** para persistencia de memoria de conversas +- **Langfuse** para observabilidade e scoring +- **LangGraph** para orquestracao do agente + +## Arquitetura + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Entrada User │────▶│ agent_call() │────▶│ Claude Sonnet │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ │ + │ ▼ + ┌──────┴──────┐ ┌─────────────────┐ + │ │ │ Knowledge Base │ + ▼ ▼ │ Retriever │ + ┌──────────┐ ┌──────────┐ └─────────────────┘ + │ DynamoDB │ │ Langfuse │ + │ Memoria │ │ Tracking │ + └──────────┘ └──────────┘ +``` + +## Componentes + +### agent.py + +Modulo principal de orquestracao do agente contendo: + +- `agent_call(event, context)` - Ponto de entrada principal para a funcao Lambda +- `out_of_scope_and_dont_know_answer()` - Ferramenta para lidar com perguntas desconhecidas + +**Estrutura do Evento de Entrada:** +```json +{ + "username": "usuario123", + "email": "usuario@exemplo.com", + "language": "Portuguese", + "message": [{"role": "user", "content": "Pergunta aqui"}], + "chat_history": [], + "origem": "web" +} +``` + +**Estrutura da Resposta:** +```json +{ + "json": "Texto da resposta do assistente", + "dynamo_response": {...}, + "chat_history": [...] +} +``` + +### tools/dynamo.py + +Operacoes DynamoDB para memoria de conversas: + +- `write_memory(user, timestamp, role, content)` - Armazena mensagens da conversa +- `read_memory(userid)` - Recupera as ultimas 30 mensagens de um usuario + +### tools/secrets.py + +Integracao com AWS Secrets Manager: + +- `get_secret()` - Recupera chaves de API do Langfuse do AWS Secrets Manager + +## Configuracao + +### Recursos AWS Necessarios + +1. **Tabela DynamoDB**: `assistente-produtos-servicos-memoria` + - Partition Key: `UserId` (String) + - Sort Key: `Timestamp` (Number) + +2. **Secret no AWS Secrets Manager**: `assistente-produtos-servicos` + - Chaves: `api-langfuse-public`, `api-langfuse-secret` + +3. **Amazon Knowledge Base**: ID `PETAZDUOFZ` + +### Requisitos de Ambiente + +- Credenciais AWS configuradas (via IAM role ou variaveis de ambiente) +- Python 3.9+ +- Pacotes necessarios no `requirements.txt` + +## Uso + +### Uso Basico + +```python +from agent import agent_call + +event = { + "username": "usuario123", + "email": "usuario@exemplo.com", + "language": "Portuguese", + "message": [{"role": "user", "content": "Como funciona o Pix?"}], + "chat_history": [], + "origem": "web" +} + +response = agent_call(event, None) +print(response["json"]) +``` + +### Como AWS Lambda + +A funcao `agent_call` foi projetada para ser usada como handler de AWS Lambda. + +## Documentacao Adicional + +- [Guia de Integracao Langfuse](./langfuse-guide.md) - Como usar Langfuse para scoring e observabilidade +- [Referencia da API](./api-reference.md) - Documentacao detalhada da API +- [Melhorias de Codigo](./code-improvements.md) - Sugestoes de melhorias para o codigo diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..bad2df3 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,273 @@ +# Referencia da API + +Documentacao detalhada de todas as funcoes e modulos do assistente. + +## Indice + +- [agent.py](#agentpy) +- [tools/dynamo.py](#toolsdynamopy) +- [tools/secrets.py](#toolssecretspy) + +--- + +## agent.py + +Modulo principal que contem a logica do agente de IA. + +### agent_call(event, context) + +Funcao principal que processa perguntas do usuario e retorna respostas do assistente. + +**Parametros:** + +| Parametro | Tipo | Obrigatorio | Descricao | +|-----------|------|-------------|-----------| +| `event` | dict | Sim | Evento contendo dados da requisicao | +| `context` | object | Nao | Contexto do Lambda (pode ser None) | + +**Estrutura do `event`:** + +```python +{ + "username": str, # Identificador do usuario + "email": str, # Email do usuario + "language": str, # Idioma desejado (ex: "Portuguese", "English") + "message": list, # Lista de mensagens no formato LangChain + "chat_history": list, # Historico de chat ([] para nova conversa) + "origem": str # (Opcional) Canal de origem da pergunta +} +``` + +**Formato das mensagens:** + +```python +# Mensagem do usuario +{"role": "user", "content": "Texto da pergunta"} + +# Mensagem do assistente +{"role": "assistant", "content": "Texto da resposta"} +``` + +**Retorno:** + +```python +{ + "json": str, # Resposta do assistente + "dynamo_response": dict, # Resposta da operacao DynamoDB + "chat_history": list # Historico atualizado +} +``` + +**Exemplo de Uso:** + +```python +from agent import agent_call + +# Nova conversa +event = { + "username": "joao.silva", + "email": "joao@empresa.com", + "language": "Portuguese", + "message": [{"role": "user", "content": "O que e o Comm.Pix?"}], + "chat_history": [], + "origem": "web" +} + +response = agent_call(event, None) +print(response["json"]) # Resposta do assistente +``` + +**Codigos de Erro:** + +| Erro | Causa | Solucao | +|------|-------|---------| +| KeyError | Campo obrigatorio ausente no event | Verificar estrutura do event | +| BedrockException | Erro na API do Bedrock | Verificar credenciais AWS | +| DynamoDBException | Erro ao acessar DynamoDB | Verificar permissoes IAM | + +--- + +### out_of_scope_and_dont_know_answer() + +Tool do LangChain para respostas padrao quando o agente nao encontra a resposta. + +**Parametros:** Nenhum + +**Retorno:** `str` - Mensagem padrao orientando o usuario a abrir ticket de suporte + +**Comportamento:** +- Registra um score "Miss" no Langfuse +- Retorna mensagem com link para portal de suporte + +**Quando e Chamada:** +- Perguntas fora do escopo do Comm.Pix +- Perguntas dentro do escopo mas sem resposta nos documentos + +--- + +## tools/dynamo.py + +Modulo para operacoes de memoria no DynamoDB. + +### write_memory(user, timestamp, role, content) + +Armazena uma mensagem da conversa no DynamoDB. + +**Parametros:** + +| Parametro | Tipo | Obrigatorio | Descricao | +|-----------|------|-------------|-----------| +| `user` | str | Sim | Identificador do usuario (UserId) | +| `timestamp` | int | Sim | Timestamp Unix da mensagem | +| `role` | str | Sim | Papel da mensagem ("user" ou "assistant") | +| `content` | str | Sim | Conteudo da mensagem | + +**Retorno:** `None` + +**Exemplo:** + +```python +from tools import dynamo +import time + +dynamo.write_memory( + user="joao.silva", + timestamp=int(time.time()), + role="user", + content="Como funciona o Finance Batch?" +) +``` + +**Estrutura no DynamoDB:** + +```json +{ + "UserId": "joao.silva", + "Timestamp": 1704067200, + "role": "user", + "content": "Como funciona o Finance Batch?" +} +``` + +--- + +### read_memory(userid) + +Recupera as ultimas 30 mensagens de um usuario. + +**Parametros:** + +| Parametro | Tipo | Obrigatorio | Descricao | +|-----------|------|-------------|-----------| +| `userid` | str | Sim | Identificador do usuario | + +**Retorno:** `list` - Lista de mensagens ordenadas por timestamp (mais recentes primeiro) + +**Exemplo:** + +```python +from tools import dynamo + +history = dynamo.read_memory("joao.silva") +for msg in history: + print(f"{msg['role']}: {msg['content']}") +``` + +**Retorno Exemplo:** + +```python +[ + {"UserId": "joao.silva", "Timestamp": 1704067300, "role": "assistant", "content": "..."}, + {"UserId": "joao.silva", "Timestamp": 1704067200, "role": "user", "content": "..."}, + # ... ate 30 mensagens +] +``` + +--- + +## tools/secrets.py + +Modulo para recuperacao de segredos do AWS Secrets Manager. + +### get_secret() + +Recupera as credenciais armazenadas no Secrets Manager. + +**Parametros:** Nenhum + +**Retorno:** `str` - String JSON contendo os segredos + +**Exemplo:** + +```python +from tools import secrets +import json + +secret_string = secrets.get_secret() +secrets_data = json.loads(secret_string) + +langfuse_public = secrets_data['api-langfuse-public'] +langfuse_secret = secrets_data['api-langfuse-secret'] +``` + +**Estrutura do Segredo:** + +```json +{ + "api-langfuse-public": "pk-lf-xxxxxxxx", + "api-langfuse-secret": "sk-lf-xxxxxxxx" +} +``` + +**Erros Possiveis:** + +| Erro | Causa | Solucao | +|------|-------|---------| +| `ResourceNotFoundException` | Segredo nao encontrado | Verificar nome do segredo | +| `AccessDeniedException` | Sem permissao | Verificar politica IAM | +| `DecryptionFailure` | Erro de descriptografia | Verificar chave KMS | + +--- + +## Fluxo Completo de Execucao + +``` +1. agent_call() recebe o evento + │ +2. Inicializa Langfuse handler + │ +3. Configura ChatBedrock (Claude Sonnet 4) + │ +4. Configura Knowledge Base Retriever + │ +5. Carrega historico + │ ├── Se chat_history vazio: dynamo.read_memory('frente') + │ └── Se nao: usa chat_history do evento + │ +6. Cria agente ReAct com tools: + │ ├── retriever.as_tool() - Busca documentos + │ └── out_of_scope_and_dont_know_answer - Resposta padrao + │ +7. Executa agente com streaming + │ ├── Registra scores no Langfuse (Origem, Email) + │ └── Processa cada step do agente + │ +8. Salva mensagem no DynamoDB + │ +9. Retorna resposta +``` + +--- + +## Variaveis de Configuracao + +| Variavel | Local | Valor Padrao | Descricao | +|----------|-------|--------------|-----------| +| `model_id` | agent.py | `us.anthropic.claude-sonnet-4-20250514-v1:0` | Modelo Claude | +| `region_name` | agent.py | `us-east-1` | Regiao AWS | +| `knowledge_base_id` | agent.py | `PETAZDUOFZ` | ID da Knowledge Base | +| `temperature` | agent.py | `0.1` | Temperatura do modelo | +| `max_tokens` | agent.py | `2000` | Maximo de tokens | +| `numberOfResults` | agent.py | `4` | Documentos retornados | +| `table_name` | dynamo.py | `assistente-produtos-servicos-memoria` | Tabela DynamoDB | +| `secret_name` | secrets.py | `assistente-produtos-servicos` | Nome do segredo | diff --git a/docs/frontend-app.md b/docs/frontend-app.md new file mode 100644 index 0000000..71ce587 --- /dev/null +++ b/docs/frontend-app.md @@ -0,0 +1,269 @@ +# Documentação da Aplicação Frontend + +Este documento descreve a aplicação web Streamlit localizada na pasta `front/app`, que fornece uma interface de chat conversacional para o assistente de produtos e serviços baseado em IA. + +## Estrutura de Diretórios + +``` +front/app/ +├── front.py # Aplicação principal Streamlit (74 linhas) +├── st_auth.py # Módulo de autenticação AWS Secrets Manager (28 linhas) +└── __pycache__/ # Bytecode Python compilado +``` + +## Visão Geral da Arquitetura + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Interface Web Streamlit │ +│ (Executa na porta 8501 via Docker) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ [Autenticação de Usuário via AWS Cognito OIDC] │ +│ (Token JWT no header x-amzn-oidc-data) │ +│ ↓ │ +│ [Seletor de Idioma] [Campo de Entrada do Chat] │ +│ ↓ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ Gerenciamento de Estado da Sessão │ │ +│ │ - user_prompt_history │ │ +│ │ - chat_answer_history │ │ +│ │ - chat_history (conversa completa) │ │ +│ └──────────────────────────────────────────┘ │ +│ ↓ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ Manipulador de Requisições API │ │ +│ │ - Busca chave API do AWS Secrets Mgr │ │ +│ │ - POST para AWS API Gateway │ │ +│ │ - Inclui contexto do usuário e histórico│ │ +│ └──────────────────────────────────────────┘ │ +│ ↓ │ +├─────────────────────────────────────────────────────────────┤ +│ Backend AWS (Serviço Externo) │ +│ API Gateway → Lambda → Assistente IA │ +│ (Retorna JSON com chat_history e mensagem) │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Descrição dos Arquivos + +### st_auth.py - Integração com AWS Secrets Manager + +**Propósito:** Recupera chaves de API e segredos sensíveis do AWS Secrets Manager. + +#### Função: `get_secret()` + +Conecta ao AWS Secrets Manager e recupera os segredos da aplicação. + +```python +def get_secret() -> str: + """ + Recupera o segredo 'assistente-produtos-servicos' do AWS Secrets Manager. + + Retorna: + str: A string do segredo (formato JSON) contendo chaves de API e credenciais. + + Exceções: + ClientError: Se o segredo não puder ser recuperado da AWS. + """ +``` + +**Detalhes Importantes:** +- Conecta ao AWS Secrets Manager na região `us-east-1` +- Recupera o segredo chamado `"assistente-produtos-servicos"` +- Retorna o segredo como string (formato JSON esperado) +- Usa sessão boto3 para operações do SDK AWS +- Inclui tratamento de erros para exceções `ClientError` + +**Dependências:** +- `boto3` - SDK AWS para Python +- `botocore.exceptions` - Tratamento de erros para operações AWS + +--- + +### front.py - Aplicação Principal Streamlit + +**Propósito:** Interface de chatbot interativa para assistência de produtos/serviços com autenticação AWS Cognito e respostas baseadas em IA. + +#### Componentes + +##### 1. Autenticação e Contexto do Usuário (Linhas 1-27) + +Extrai informações do usuário da autenticação AWS ALB OIDC: + +- Extrai token JWT do header `x-amzn-oidc-data` +- Decodifica token JWT sem verificação de assinatura +- Extrai identificação do usuário de múltiplos campos JWT: + - `sub` (Subject - campo JWT padrão) + - `cognito:username` (nome de usuário Cognito) + - `username` (campo alternativo de nome de usuário) + - `user_id` (campo customizado) +- Recupera email do usuário do token decodificado + +##### 2. Elementos da Interface + +| Elemento | Descrição | +|----------|-----------| +| Seletor de Idioma | Dropdown com opções English, Portuguese, Spanish | +| Cabeçalho Principal | "Assistente Produtos Servicos" | +| Entrada do Chat | Campo de texto com placeholder em português | + +##### 3. Gerenciamento de Estado da Sessão + +A aplicação mantém três variáveis de estado de sessão: + +| Variável | Tipo | Descrição | +|----------|------|-----------| +| `user_prompt_history` | `List[str]` | Lista de perguntas do usuário | +| `chat_answer_history` | `List[str]` | Lista de respostas da IA | +| `chat_history` | `List[dict]` | Histórico completo da conversa com papéis | + +##### 4. Função Utilitária: `create_sources_string()` + +```python +def create_sources_string(source_urls: Set[str]) -> str: + """ + Formata fontes de citação para exibição. + + Args: + source_urls: Conjunto de URLs de fontes únicas para formatar. + + Retorna: + str: Lista numerada de fontes para exibição. + """ +``` + +*Nota: Atualmente definida mas não utilizada ativamente no código.* + +##### 5. Fluxo Principal do Chat + +Quando um usuário envia uma mensagem, a seguinte sequência ocorre: + +1. **Exibe mensagem do usuário** - Mostra a pergunta na interface imediatamente +2. **Faz requisição à API:** + - Constrói payload com mensagem, histórico do chat, nome de usuário, email, origem, idioma + - Envia requisição POST para endpoint AWS API Gateway + - Inclui chave API nos headers obtida do AWS Secrets Manager +3. **Processa resposta:** + - Analisa resposta JSON + - Atualiza histórico do chat da resposta se fornecido + - Extrai conteúdo da mensagem da estrutura de resposta + - Trata erros de timeout com mensagem amigável ao usuário +4. **Atualiza estado:** + - Adiciona pergunta do usuário ao histórico + - Adiciona resposta da IA ao histórico + - Atualiza histórico do chat com registros de conversa baseados em papéis + +## Fluxo de Dados + +``` +1. Acesso do Usuário → AWS ALB injeta token JWT nos headers + ↓ +2. Autenticação → Extrai user_id e email do JWT + ↓ +3. Entrada do Usuário → Mensagem de chat com preferência de idioma + ↓ +4. Chamada API → POST para AWS Lambda via API Gateway com: + - Conteúdo da mensagem + - Histórico do chat + - Metadados do usuário (username, email, origin, language) + ↓ +5. Processamento Backend → Assistente IA gera resposta + ↓ +6. Resposta → JSON com chat_history e conteúdo da mensagem + ↓ +7. Exibição → Atualiza interface e mantém estado da sessão +``` + +## Comunicação com API + +### Endpoint + +``` +POST https://xexm2wsz07-vpce-05915540d0592b921.execute-api.us-east-1.amazonaws.com/dev +``` + +### Payload da Requisição + +```json +{ + "message": "Pergunta ou mensagem do usuário", + "chat_history": [ + {"role": "user", "content": "Mensagem anterior do usuário"}, + {"role": "assistant", "content": "Resposta anterior do assistente"} + ], + "username": "user_id_do_jwt", + "email": "usuario@exemplo.com", + "origin": "streamlit", + "language": "Portuguese" +} +``` + +### Headers da Requisição + +``` +x-api-key: +``` + +### Formato da Resposta + +```json +{ + "chat_history": [...], + "message": { + "content": "Texto de resposta do assistente" + } +} +``` + +## Dependências + +| Pacote | Versão | Propósito | +|--------|--------|-----------| +| `streamlit` | ^1.49.1 | Framework de interface web | +| `boto3` | ^1.40.37 | SDK AWS | +| `requests` | - | Cliente HTTP para chamadas API | +| `PyJWT` | - | Decodificação de tokens JWT | +| `pyyaml` | - | Parsing de YAML | + +## Configuração + +### Ambiente + +- **Porta:** 8501 (porta padrão do Streamlit) +- **Região AWS:** us-east-1 +- **Nome do Segredo:** `assistente-produtos-servicos` + +### Idiomas Suportados + +- English (Inglês) +- Portuguese (Português) +- Spanish (Espanhol) + +## Tratamento de Erros + +| Cenário | Comportamento | +|---------|---------------| +| Timeout da API | Exibe: "Desculpe, a busca pode ter demorado mais que o esperado. Por favor tente novamente." | +| Erros gerais da API | Resposta pode não ser exibida corretamente | + +## Comportamento da Sessão + +- Histórico do chat persiste dentro de uma única sessão do navegador +- Histórico é limpo ao atualizar a página ou fechar a aba +- Não há armazenamento persistente de conversas + +## Notas de Implantação + +A aplicação foi projetada para executar atrás de um AWS Application Load Balancer (ALB) com autenticação Cognito configurada. O ALB injeta o header `x-amzn-oidc-data` contendo o token JWT para usuários autenticados. + +### Docker + +A aplicação executa em um container Docker (veja `front/Dockerfile`) e expõe a porta 8501. + +## Considerações Conhecidas + +1. **Endpoint de API Hardcoded:** A URL do AWS API Gateway está hardcoded no código fonte +2. **Funcionalidades Não Utilizadas:** Dependências `streamlit-authenticator` e `sseclient` são importadas mas não utilizadas +3. **Sem Validação de Entrada:** Perguntas dos usuários e respostas da API não são validadas antes do uso +4. **Persistência Apenas na Sessão:** Conversas não persistem entre sessões diff --git a/docs/langfuse-guide.md b/docs/langfuse-guide.md new file mode 100644 index 0000000..d008c44 --- /dev/null +++ b/docs/langfuse-guide.md @@ -0,0 +1,319 @@ +# Guia de Integracao Langfuse + +Este guia explica como o Langfuse esta integrado ao assistente e como utilizar suas funcionalidades de scoring e observabilidade. + +## Indice + +- [O que e Langfuse](#o-que-e-langfuse) +- [Configuracao Inicial](#configuracao-inicial) +- [Sistema de Scores](#sistema-de-scores) +- [Adicionando Novos Scores](#adicionando-novos-scores) +- [Visualizando Metricas](#visualizando-metricas) +- [Boas Praticas](#boas-praticas) + +## O que e Langfuse + +Langfuse e uma plataforma de observabilidade para aplicacoes LLM que permite: + +- Rastrear todas as chamadas ao modelo +- Adicionar scores e metricas customizadas +- Analisar performance e custos +- Debugar conversas problematicas + +## Configuracao Inicial + +### 1. Credenciais + +As credenciais do Langfuse sao armazenadas no AWS Secrets Manager: + +```python +# Recuperando credenciais +from tools import secrets +import json + +secrets_data = json.loads(secrets.get_secret()) +public_key = secrets_data['api-langfuse-public'] +secret_key = secrets_data['api-langfuse-secret'] +``` + +### 2. Inicializacao do Cliente + +```python +from langfuse import Langfuse +from langfuse.langchain import CallbackHandler + +# Cliente principal para spans e scores +langfuse = Langfuse( + public_key=public_key, + secret_key=secret_key, + host="http://SEU_HOST_LANGFUSE:3000" +) + +# Callback handler para integracao automatica com LangChain +langfuse_handler = CallbackHandler() +``` + +### 3. Integracao com LangGraph + +```python +# Adicionar o handler na configuracao do agente +config = { + "configurable": {"thread_id": "abc123"}, + "callbacks": [langfuse_handler] +} + +# Executar o agente +for step in agent_executor.stream({"messages": input_message}, config, stream_mode="values"): + # processamento... + pass + +# IMPORTANTE: Flush para garantir envio dos dados +langfuse.flush() +``` + +## Sistema de Scores + +### Scores Atuais + +O sistema atual utiliza os seguintes scores: + +| Score | Tipo | Descricao | +|-------|------|-----------| +| `Miss` | CATEGORICAL | Indica quando o agente nao encontrou a resposta | +| `Origem` | CATEGORICAL | Canal de origem da pergunta (web, app, etc.) | +| `Email` | CATEGORICAL | Email do usuario para rastreabilidade | + +### Como os Scores sao Registrados + +```python +# Score de "Miss" - quando nao encontra resposta +@tool +def out_of_scope_and_dont_know_answer(): + with langfuse.start_as_current_span(name="my-operation") as span: + span.score_trace( + name="Miss", + value="True", + data_type="CATEGORICAL" + ) + return "Mensagem de resposta padrao..." + +# Scores de metadados - origem e email +with langfuse.start_as_current_span(name="my-operation") as span: + span.score_trace( + name="Origem", + value=origem, + data_type="CATEGORICAL" + ) + span.score_trace( + name="Email", + value=email, + data_type="CATEGORICAL" + ) +``` + +## Adicionando Novos Scores + +### Tipos de Score Disponiveis + +1. **CATEGORICAL** - Para valores de categorias (string) +2. **NUMERIC** - Para valores numericos (int/float) +3. **BOOLEAN** - Para valores verdadeiro/falso + +### Exemplos de Novos Scores + +#### Score de Satisfacao do Usuario + +```python +def add_user_satisfaction_score(span, satisfaction_rating): + """ + Adiciona score de satisfacao do usuario (1-5) + """ + span.score_trace( + name="user_satisfaction", + value=satisfaction_rating, + data_type="NUMERIC" + ) +``` + +#### Score de Tempo de Resposta + +```python +import time + +def add_response_time_score(span, start_time): + """ + Adiciona score de tempo de resposta em segundos + """ + response_time = time.time() - start_time + span.score_trace( + name="response_time_seconds", + value=response_time, + data_type="NUMERIC" + ) +``` + +#### Score de Documentos Consultados + +```python +def add_documents_used_score(span, num_documents): + """ + Registra quantos documentos foram consultados + """ + span.score_trace( + name="documents_consulted", + value=num_documents, + data_type="NUMERIC" + ) +``` + +#### Score de Qualidade da Resposta + +```python +def add_response_quality_score(span, quality_level): + """ + Classifica qualidade da resposta + Valores: "excellent", "good", "fair", "poor" + """ + span.score_trace( + name="response_quality", + value=quality_level, + data_type="CATEGORICAL" + ) +``` + +#### Score de Feedback Explicito + +```python +def add_feedback_score(span, was_helpful): + """ + Registra se o usuario achou a resposta util + """ + span.score_trace( + name="user_found_helpful", + value="True" if was_helpful else "False", + data_type="CATEGORICAL" + ) +``` + +### Implementacao Completa de Scoring + +```python +class LangfuseScorer: + """ + Classe utilitaria para gerenciar scores do Langfuse + """ + + def __init__(self, langfuse_client): + self.langfuse = langfuse_client + + def score_interaction(self, span, metrics: dict): + """ + Adiciona multiplos scores de uma vez + + Args: + span: Span atual do Langfuse + metrics: Dicionario com metricas + { + "origem": "web", + "email": "user@example.com", + "response_time": 2.5, + "documents_used": 3, + "found_answer": True + } + """ + # Scores categoricos + if "origem" in metrics: + span.score_trace(name="Origem", value=metrics["origem"], data_type="CATEGORICAL") + + if "email" in metrics: + span.score_trace(name="Email", value=metrics["email"], data_type="CATEGORICAL") + + # Scores numericos + if "response_time" in metrics: + span.score_trace(name="response_time", value=metrics["response_time"], data_type="NUMERIC") + + if "documents_used" in metrics: + span.score_trace(name="documents_used", value=metrics["documents_used"], data_type="NUMERIC") + + # Scores booleanos (como categoricos) + if "found_answer" in metrics: + span.score_trace( + name="found_answer", + value="True" if metrics["found_answer"] else "False", + data_type="CATEGORICAL" + ) +``` + +## Visualizando Metricas + +### Acessando o Dashboard + +1. Acesse a interface web do Langfuse: `http://SEU_HOST:3000` +2. Navegue ate "Traces" para ver todas as execucoes +3. Use filtros por score para analises especificas + +### Queries Uteis + +#### Encontrar conversas sem resposta +- Filtrar por: `score.Miss = "True"` + +#### Analise por canal de origem +- Agrupar por: `score.Origem` + +#### Metricas de performance +- Ordenar por: `score.response_time` + +## Boas Praticas + +### 1. Sempre fazer Flush + +```python +# No final de cada execucao +langfuse.flush() +``` + +### 2. Usar Nomes Consistentes + +```python +# BOM - nomes consistentes e descritivos +span.score_trace(name="user_satisfaction", value=5, data_type="NUMERIC") +span.score_trace(name="response_quality", value="good", data_type="CATEGORICAL") + +# EVITAR - nomes inconsistentes +span.score_trace(name="sat", value=5, data_type="NUMERIC") +span.score_trace(name="Quality", value="good", data_type="CATEGORICAL") +``` + +### 3. Documentar Valores Possiveis + +Para scores CATEGORICAL, documente os valores possiveis: + +```python +# response_quality: "excellent" | "good" | "fair" | "poor" +# user_type: "vendor" | "subvendor" | "supervendor" | "support" +# channel: "web" | "api" | "whatsapp" | "slack" +``` + +### 4. Tratar Erros + +```python +try: + with langfuse.start_as_current_span(name="operation") as span: + span.score_trace(name="metric", value=value, data_type="CATEGORICAL") +except Exception as e: + print(f"Erro ao registrar score: {e}") + # Continua execucao sem falhar +``` + +### 5. Nao Bloquear por Metricas + +```python +# Scores nao devem impactar a resposta ao usuario +# Se o Langfuse falhar, a conversa deve continuar +``` + +## Referencias + +- [Documentacao Oficial Langfuse](https://langfuse.com/docs) +- [Langfuse Python SDK](https://langfuse.com/docs/sdk/python) +- [Integracao LangChain](https://langfuse.com/docs/integrations/langchain) diff --git a/infra/ecs_alb/__main__.py b/infra/ecs_alb/__main__.py index d844815..cbea89a 100644 --- a/infra/ecs_alb/__main__.py +++ b/infra/ecs_alb/__main__.py @@ -4,6 +4,66 @@ import conf as config import iam import ecs +import pulumi +import pulumi_aws as aws +import conf as config +import iam +import ecs + + +# ECS Cluster Setup +app_ecs_cluster = aws.ecs.Cluster(f"{config.project_name}-ecs-cluster", + configuration=aws.ecs.ClusterConfigurationArgs( + execute_command_configuration=aws.ecs.ClusterConfigurationExecuteCommandConfigurationArgs( + logging="DEFAULT", + ), + ), + settings=[aws.ecs.ClusterSettingArgs( + name="containerInsights", + value="disabled", + )], + tags={"Name": f"{config.project_name}-{config.stack_name}"}, +) + +ecs_cluster_capacity_providers = aws.ecs.ClusterCapacityProviders(f"{config.project_name}-cluster-capacity-providers", + cluster_name=app_ecs_cluster.name, + capacity_providers=["FARGATE", "FARGATE_SPOT"], +) + +# Security Group Setup +alb_security_group = aws.ec2.SecurityGroup(f"{config.project_name}-security-group", + vpc_id=config.network["vpc_id"], + ingress=[aws.ec2.SecurityGroupIngressArgs( + protocol="-1", + from_port=0, + to_port=0, + cidr_blocks=config.network["alb_allow_ingress_cidr"], + ), + ], + egress=[aws.ec2.SecurityGroupEgressArgs( + protocol="-1", + from_port=0, + to_port=0, + cidr_blocks=["0.0.0.0/0"], + )], +) + +# Load Balancer Setup +app_load_balancer = aws.lb.LoadBalancer( + f"alb-{config.project_name}", + load_balancer_type="application", + security_groups=[alb_security_group.id], + subnets=config.network["alb_subnet_ids"], + idle_timeout=(1200), + internal=config.network['alb_internal'], +) + +for ecs_app in config.ecs: + ecs.deploy_app(ecs_app, app_ecs_cluster, alb_security_group, app_load_balancer.arn) + +# Export the ALB DNS Name +pulumi.export("url", app_load_balancer.dns_name.apply(lambda dns_name: f"http://{dns_name}")) + # ECS Cluster Setup app_ecs_cluster = aws.ecs.Cluster(f"{config.project_name}-ecs-cluster",