Adds documentation and fixes
This commit is contained in:
@@ -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():
|
||||
@@ -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,
|
||||
|
||||
131
docs/README.md
Normal file
131
docs/README.md
Normal file
@@ -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
|
||||
273
docs/api-reference.md
Normal file
273
docs/api-reference.md
Normal file
@@ -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 |
|
||||
269
docs/frontend-app.md
Normal file
269
docs/frontend-app.md
Normal file
@@ -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: <Chave API do AWS Secrets Manager>
|
||||
```
|
||||
|
||||
### 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
|
||||
319
docs/langfuse-guide.md
Normal file
319
docs/langfuse-guide.md
Normal file
@@ -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)
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user