diff --git a/docs/proposta-arquitetura-agno.md b/docs/proposta-arquitetura-agno.md index 4f61499..bdf922c 100644 --- a/docs/proposta-arquitetura-agno.md +++ b/docs/proposta-arquitetura-agno.md @@ -2,21 +2,23 @@ ## 1) Resumo executivo -Esta proposta apresenta uma arquitetura moderna baseada em Agno para construção de agentes especializados em performance e refatoração de queries SQL Server e Oracle, com governança, observabilidade e segurança de dados. O objetivo é acelerar a melhoria de desempenho, reduzir custos operacionais e habilitar automações confiáveis em múltiplos setores. +Esta proposta apresenta uma arquitetura moderna baseada em Agno para construção de agentes especializados em performance e refatoração de queries SQL Server e Oracle, com governança, observabilidade e segurança de dados. O objetivo é acelerar a melhoria de desempenho, reduzir custos operacionais e habilitar automações confiáveis em múltiplos setores. O desenho prioriza previsibilidade de custos, rastreabilidade e integração com processos corporativos já existentes. ## 2) Objetivo do projeto - Criar um time de agentes especializados em SQL (explicação, otimização, revisão de qualidade e análise conservadora). -- Reaproveitar e evoluir a base existente de prompts e conhecimento. -- Garantir segurança, rastreabilidade e controle de custo/latência. +- Reaproveitar e evoluir a base existente de prompts e conhecimento técnico. +- Garantir segurança, rastreabilidade e controle de custo/latência desde o MVP. - Viabilizar RAG corporativo para respostas alinhadas ao conhecimento interno. +- Preparar integração com canais corporativos (ex.: Microsoft Teams) para entrada de solicitações. ## 3) Escopo funcional - Otimização de queries SQL para Oracle e SQL Server. -- Revisão por agente de qualidade com diff e riscos. -- Análise conservadora para cenários sensíveis. +- Revisão por agente de qualidade com diff e análise de risco. +- Análise conservadora para cenários sensíveis e críticos. - Execução assistida (manual ou semi-automática) com trilha de auditoria. +- Produção de relatórios executivos e técnicos por área/solicitante. ## 4) Benefícios para a diretoria @@ -31,8 +33,9 @@ Detalhamento das vantagens: - **ROI acelerado**: melhora contínua de performance reduz custos de infraestrutura e tempo de execução de cargas críticas. - **Padronização técnica**: boas práticas de SQL e refatoração aplicadas de forma consistente em toda a empresa. - **Velocidade de entrega**: automação de análise e revisão reduz ciclos de aprovação e retrabalho. -- **Observabilidade e previsibilidade**: visibilidade clara de custos, latência e impacto das mudanças. -- **Escala por demanda**: agentes podem ser acionados sob demanda por diferentes áreas sem aumentar headcount. +- **Observabilidade e previsibilidade**: visibilidade clara de custos, latência e impacto das mudanças por área. +- **Escala por demanda**: agentes acionados sob demanda, com priorização por SLA e criticidade. +- **Qualidade contínua**: revisão e validação reduzem regressões em ambientes críticos. ## 5) Novas possibilidades de automação @@ -41,6 +44,7 @@ Detalhamento das vantagens: - **Apoio ao refactoring** em migrações e reengenharias. - **Assistente de engenharia** para padrões corporativos de SQL. - **RAG corporativo** para políticas internas, padrões de modelagem e convenções. +- **Apoio a squads** com respostas contextualizadas e justificativas técnicas. ## 5.1) Cenários de automação por setor @@ -49,36 +53,42 @@ Detalhamento das vantagens: - Otimização de relatórios de fechamento e conciliações. - Revisão automática de queries críticas de faturamento. - Alertas de performance para consultas que impactam SLA. +- Padronização de consultas de provisão e contabilização. ### Seguros - Refatoração de consultas de sinistros e risco. - Otimização de relatórios regulatórios. - Padronização de queries de precificação e provisões. +- Apoio a auditorias internas com relatórios técnicos. ### Operações - Otimização de consultas de processamento em lote. - Identificação de gargalos em pipelines operacionais. - Sugestões de índices e melhorias de execução. +- Monitoramento de consultas que degradam serviços críticos. ### BI e Analytics - Ajustes de queries para dashboards e cubos. - Redução de latência em relatórios executivos. - Padronização de joins e filtros complexos. +- Análise de custo por relatório e priorização de refatoração. ### TI e Engenharia - Revisão e refatoração de SQL em pipelines e integrações. - Aceleração de migrações entre ambientes e bancos. - Automação de documentação de queries críticas. +- Padronização de pipelines com base em boas práticas corporativas. ### Jurídico e Compliance - Auditoria automática de queries sensíveis. - Verificação de padrões de acesso e mascaramento. - Geração de relatórios para conformidade interna. +- Rastreabilidade de alterações com justificativas técnicas. ## 6) Arquitetura proposta (Agno-first) @@ -86,14 +96,16 @@ Detalhamento das vantagens: - **Core de negócio exposto como ferramentas**: casos de uso (ex: otimizar, explicar, comparar) invocados via tools. - **Módulos únicos sob sql_optimizer_team**: todos os componentes centralizados no mesmo namespace. - **Integrações corporativas**: bancos, repositórios de conhecimento, logs e custos. +- **Camada de governança**: políticas de acesso, limites de uso e auditoria. ## 6.1) Integração com Microsoft Teams -- **Canal dedicado**: solicitações feitas diretamente no Teams. +- **Canal dedicado**: solicitações feitas diretamente no Teams por usuários autorizados. - **Comandos orientados**: prompts guiados (ex: “Otimizar query X para Oracle”). -- **Workflows com aprovação**: etapa de validação antes de aplicar mudanças. +- **Workflows com aprovação**: etapa de validação antes de aplicar mudanças em produção. - **Notificações automáticas**: alertas de performance e relatórios em tempo real. - **Trilha de auditoria**: registro de solicitações e respostas por usuário/time. +- **Rotas por squad**: distribuição automática de tarefas por área responsável. ## 7) Observabilidade (obrigatório) @@ -109,6 +121,7 @@ Métricas mínimas: - Custo estimado mensal por time/serviço. - Latência média e p95. - Taxa de retrabalho/recusa. +- Taxa de aprovação e impacto percebido. ## 8) Segurança da informação @@ -116,12 +129,14 @@ Métricas mínimas: - **Bloqueio de Deep Search na web** por padrão; uso somente com autorização. - **Mascaramento de dados sensíveis** antes do envio ao LLM. - **Políticas de retenção** e segregação de dados por área. +- **Auditoria completa** de entradas/saídas para rastreabilidade. ## 9) RAG corporativo - Base de conhecimento interna versionada. -- Ingestão de documentação técnica, padrões, e decisões arquiteturais. +- Ingestão de documentação técnica, padrões e decisões arquiteturais. - Recuperação com filtros por área e confidencialidade. +- Curadoria contínua com feedback dos times para melhorar a relevância. ## 10) Frameworks considerados @@ -129,40 +144,47 @@ Métricas mínimas: - **Pontos fortes**: orquestração de agentes, memória, tools, execução local, integração rápida. - **Ideal para**: time de agentes especializados e integração customizada. +- **Governança**: fácil acoplamento com módulos de observabilidade e segurança. ### B) Langflow - **Pontos fortes**: low-code visual, facilita protótipos e POCs. - **Ideal para**: validação rápida de fluxos e PoCs de RAG. +- **Limitações**: menor flexibilidade para integrações profundas corporativas. ### C) Dify - **Pontos fortes**: plataforma pronta para apps com RAG, autenticação e gestão. - **Ideal para**: portal de uso corporativo com usuários finais. +- **Limitações**: customizações avançadas podem exigir extensões adicionais. ## 11) Recomendações de escolha - **Agno** para o core e a arquitetura principal. - **Langflow** para protótipos e desenho de fluxos. - **Dify** para distribuição ampla (se desejarem um portal corporativo pronto). +- **Observabilidade** acoplada desde o início (OpenTelemetry + Langfuse/Phoenix). ## 12) Roadmap proposto 1. **Fase 1**: validação técnica (agentes + prompts + RAG inicial). 2. **Fase 2**: observabilidade + segurança + governança. 3. **Fase 3**: escala para áreas e processos internos. +4. **Fase 4**: integração com Teams e automações cross-area. ## 13) Riscos e mitigação - **Qualidade do output**: mitigado com revisão automática + validação conservadora. -- **Custo**: mitigado com observabilidade e limites de uso. +- **Custo**: mitigado com observabilidade e limites de uso por time. - **Vazamento de dados**: mitigado com RAG isolado e mascaramento. +- **Mudanças não controladas**: mitigado com workflow de aprovação. ## 14) Próximos passos - Alinhamento técnico com @Rodrigo Bittencourt De Macedo. - Definição do escopo mínimo viável (Oracle + SQL Server). - Aprovação do framework prioritário e das ferramentas de observabilidade. +- Definição de critérios de sucesso (KPIs técnicos e executivos). --- diff --git a/optimization_metadata.json b/optimization_metadata.json new file mode 100644 index 0000000..c1eb9ed --- /dev/null +++ b/optimization_metadata.json @@ -0,0 +1,18 @@ +{ + "b6e1d99d9d5dd236": { + "query_hash": "b6e1d99d9d5dd236", + "original_query": "SELECT DISTINCT\n\nc.customer_id,\n\nc.first_name,\n\nc.last_name,\n\nc.email,\n\nCOUNT(o.order_id) as total_orders,\n\nSUM(CASE WHEN o.status = 'completed' THEN 1 ELSE 0 END) as completed_orders,\n\nSUM(CASE WHEN o.status = 'pending' THEN 1 ELSE 0 END) as pending_orders,\n\nAVG(o.total_amount) as avg_order_value,\n\nMAX(o.order_date) as last_order_date,\n\nCASE\n\nWHEN COUNT(o.order_id) > 50 THEN 'VIP'\n\nWHEN COUNT(o.order_id) > 20 THEN 'Premium'\n\nWHEN COUNT(o.order_id) > 5 THEN 'Regular'\n\nELSE 'New'\n\nEND as customer_tier\n\nFROM customers c\n\nLEFT JOIN orders o ON o.customer_id = c.customer_id\n\nWHERE c.customer_id IN (\n\nSELECT DISTINCT o19.customer_id\n\nFROM orders o19\n\nWHERE o19.order_date >= SYSDATE - 730\n\n)\n\nAND c.email LIKE '%@%'\n\nGROUP BY c.customer_id, c.first_name, c.last_name, c.email\n\nHAVING COUNT(o.order_id) > 0\n\nORDER BY COUNT(o.order_id) DESC", + "explanation": "1. **Overall Purpose**\nThe query is designed to retrieve a list of customers, their order details, and a classification of their customer tier based on the number of orders they have placed. The query is intended to provide a detailed view of customer activity, including the total number of orders, the number of completed and pending orders, the average order value, the date of the last order, and a classification of the customer based on their order volume.\n\n2. **Involved Database Objects**\nThe query involves two tables: `customers` and `orders`. It also includes a subquery on the `orders` table.\n\n3. **Essential Operations**\n- The query retrieves the following columns: `c.customer_id`, `c.first_name`, `c.last_name`, `c.email`, `total_orders`, `completed_orders`, `pending_orders`, `avg_order_value`, `last_order_date`, and `customer_tier`.\n- A LEFT JOIN is performed between the `customers` table (aliased as `c`) and the `orders` table (aliased as `o`) on the condition that `o.customer_id = c.customer_id`.\n- The WHERE clause filters the customers based on two conditions: the customer's ID must be in the list of customer IDs who have placed an order in the last two years (730 days), and the customer's email must contain an '@' symbol.\n- The COUNT function is used to calculate the total number of orders per customer (`total_orders`), and the SUM function is used with a CASE statement to calculate the number of completed and pending orders.\n- The AVG function is used to calculate the average order value (`avg_order_value`), and the MAX function is used to determine the date of the last order (`last_order_date`).\n- A CASE statement is used to classify customers into tiers based on the number of orders they have placed.\n- The GROUP BY clause groups the results by `c.customer_id`, `c.first_name`, `c.last_name`, and `c.email`.\n- The HAVING clause filters out customers who have not placed any orders.\n- The ORDER BY clause sorts the results in descending order based on the total number of orders.\n\n4. **Performance Issues**\n- The query uses a leading wildcard in the LIKE condition (`c.email LIKE '%@%'`), which can prevent the use of indexes and slow down the query.\n- The subquery in the WHERE clause could potentially be a performance issue, depending on the size of the `orders` table and the number of distinct customer IDs. It might be more efficient to use a JOIN instead.\n- The query uses multiple COUNT functions in the SELECT and ORDER BY clauses, which could be a performance issue if the number of rows is large. It might be more efficient to calculate the count once and store it in a variable or a temporary table.", + "database_type": "postgresql", + "version": "1.1", + "optimized_at": "2026-01-21T17:31:17.127080+00:00" + }, + "60243f154ec1c6c5": { + "query_hash": "60243f154ec1c6c5", + "original_query": "SELECT DISTINCT\n\nc.customer_id,\n\nc.first_name,\n\nc.last_name,\n\nc.email,\n\n(SELECT COUNT() FROM orders o1 WHERE o1.customer_id = c.customer_id) as total_orders,\n\n(SELECT COUNT() FROM orders o2 WHERE o2.customer_id = c.customer_id AND o2.status = 'completed') as completed_orders,\n\n(SELECT COUNT() FROM orders o3 WHERE o3.customer_id = c.customer_id AND o3.status = 'pending') as pending_orders,\n\n(SELECT AVG(o5.total_amount) FROM orders o5 WHERE o5.customer_id = c.customer_id) as avg_order_value,\n\n(SELECT MAX(o6.order_date) FROM orders o6 WHERE o6.customer_id = c.customer_id) as last_order_date,\n\nCASE\n\nWHEN (SELECT COUNT() FROM orders o8 WHERE o8.customer_id = c.customer_id) > 50 THEN 'VIP'\n\nWHEN (SELECT COUNT() FROM orders o9 WHERE o9.customer_id = c.customer_id) > 20 THEN 'Premium'\n\nWHEN (SELECT COUNT() FROM orders o10 WHERE o10.customer_id = c.customer_id) > 5 THEN 'Regular'\n\nELSE 'New'\n\nEND as customer_tier\n\nFROM customers c\n\nWHERE c.customer_id IN (\n\nSELECT DISTINCT o19.customer_id\n\nFROM orders o19\n\nWHERE o19.order_date >= SYSDATE - 730\n\n)\n\nAND EXISTS (\n\nSELECT 1 FROM orders o21 WHERE o21.customer_id = c.customer_id\n\n)\n\nAND c.email LIKE '%@%'\n\nORDER BY (SELECT COUNT(*) FROM orders o22 WHERE o22.customer_id = c.customer_id) DESC.", + "explanation": "1. **Overall Purpose**\nThe query retrieves a list of customers and their order details from an Oracle database. It provides a summary of each customer's order history, including the total number of orders, the number of completed and pending orders, the average order value, the date of the last order, and a customer tier based on the total number of orders. The query is designed to only include customers who have placed an order in the last two years, have at least one order in the system, and have a valid email address.\n\n2. **Involved Database Objects**\nThe query involves two tables: `customers` and `orders`. There are no views, CTEs, stored procedures, temporary tables, or schema-qualified objects involved. The query uses several subqueries and a CASE statement.\n\n3. **Essential Operations**\n- Columns retrieved: `customer_id`, `first_name`, `last_name`, `email` from the `customers` table. The query also calculates and retrieves `total_orders`, `completed_orders`, `pending_orders`, `avg_order_value`, `last_order_date`, and `customer_tier`.\n- There are no joins in this query.\n- Filters and conditions: The query filters customers based on whether they have placed an order in the last two years (`o19.order_date >= SYSDATE - 730`), whether they have at least one order in the system (`EXISTS (SELECT 1 FROM orders o21 WHERE o21.customer_id = c.customer_id)`), and whether they have a valid email address (`c.email LIKE '%@%'`).\n- Aggregations: The query uses `COUNT()` to calculate the total, completed, and pending orders for each customer, `AVG()` to calculate the average order value, and `MAX()` to find the date of the last order.\n- Sorting: The query sorts the results by the total number of orders in descending order (`ORDER BY (SELECT COUNT(*) FROM orders o22 WHERE o22.customer_id = c.customer_id) DESC`).\n- The query uses the `DISTINCT` keyword to ensure that each customer appears only once in the results.\n- Oracle-specific features: The query uses the `SYSDATE` function to get the current date and time.\n\n4. **Performance Issues**\n- The query uses a leading wildcard in the `LIKE` operator (`c.email LIKE '%@%'`). This can prevent the use of an index and slow down the query.\n- The query uses multiple subqueries in the `SELECT` clause, which can be inefficient. These could potentially be replaced with JOINs or window functions for better performance.\n- The query uses a subquery in the `ORDER BY` clause, which can be inefficient. This could potentially be replaced with a calculated column in the main query.\n- The query uses a subquery in the `WHERE` clause to filter customers based on whether they have placed an order in the last two years. This could potentially be replaced with a JOIN for better performance.", + "database_type": "oracle", + "version": "1.1", + "optimized_at": "2026-01-21T17:34:23.063780+00:00" + } +} \ No newline at end of file diff --git a/src/sql_optimizer_team/agents/sql_quality_agent.py b/src/sql_optimizer_team/agents/sql_quality_agent.py index 7214830..f3489e7 100644 --- a/src/sql_optimizer_team/agents/sql_quality_agent.py +++ b/src/sql_optimizer_team/agents/sql_quality_agent.py @@ -30,8 +30,8 @@ sql_quality_agent = Agent( "- Compare a explicação natural com a SQL otimizada e destaque riscos de divergência.", "- Use checklist: tabelas, colunas, CASE, subqueries, filtros, joins, aliases, cálculos.", "- Gere o diff linha a linha usando diff_sql(original_sql, optimized_sql).", - "- Apresente o diff em formato visual (bloco diff) e depois comente cada alteração em bullets.", - "- Para cada alteração: descreva o que mudou, por que mudou, vantagens, desvantagens e riscos.", + "- Apresente o diff em formato visual (lado a lado) e depois comente cada trecho alterado.", + "- Para cada trecho alterado: descreva o que mudou, por que mudou, vantagens, desvantagens e riscos.", "- Se houver risco, solicite correção ao SQL Optimizer com detalhes objetivos.", "- Não reescreva a SQL diretamente.", ], diff --git a/src/sql_optimizer_team/infrastructure/llm/agno_adapter.py b/src/sql_optimizer_team/infrastructure/llm/agno_adapter.py index 696bafd..eff0acb 100644 --- a/src/sql_optimizer_team/infrastructure/llm/agno_adapter.py +++ b/src/sql_optimizer_team/infrastructure/llm/agno_adapter.py @@ -20,8 +20,8 @@ class AgnoLLMAdapter(BaseLLMAdapter): """Adapter that wraps an Agno Model to satisfy the LLMPort interface.""" def __init__(self, model: Model) -> None: - super().__init__(model.id) self._model = model + super().__init__(model.id) async def generate_text( self, prompt: str, temperature: float = 0.1, max_tokens: int = 4000 diff --git a/src/sql_optimizer_team/tools/core_tools.py b/src/sql_optimizer_team/tools/core_tools.py index 0d09da2..ca5265e 100644 --- a/src/sql_optimizer_team/tools/core_tools.py +++ b/src/sql_optimizer_team/tools/core_tools.py @@ -32,6 +32,13 @@ def _work_dir(output_dir: str | None = None) -> Path: return path +def _normalize_db_input(value: str) -> str: + cleaned = value.strip().lower() + if cleaned in {"sql", "mssql", "ms-sql", "ms_sql"}: + return "sqlserver" + return cleaned + + def _build_settings( database_type: DatabaseType, provider: str | None = None, @@ -76,7 +83,7 @@ async def explain_query_core( api_key: str | None = None, ) -> dict[str, str]: """Generate a natural-language explanation using the core LLM pipeline.""" - db_type = DatabaseType.from_string(database_type) + db_type = DatabaseType.from_string(_normalize_db_input(database_type)) settings = _build_settings(db_type, provider, model, temperature, max_tokens, api_key) llm = LLMAdapterFactory.create(settings) generator = PromptGeneratorFactory.create(db_type) @@ -107,7 +114,7 @@ async def optimize_query_core( no_review: bool = False, ) -> dict[str, str | dict[str, str]]: """Run the full optimization use case and return the DTO as dict.""" - db_type = DatabaseType.from_string(database_type) + db_type = DatabaseType.from_string(_normalize_db_input(database_type)) settings = _build_settings(db_type, provider, model, temperature, max_tokens, api_key) llm = LLMAdapterFactory.create(settings) @@ -151,7 +158,7 @@ async def compare_optimizations_core( if not database_types: raise ValueError("database_types não pode ser vazio") - db_types = [DatabaseType.from_string(db) for db in database_types] + db_types = [DatabaseType.from_string(_normalize_db_input(db)) for db in database_types] settings = _build_settings(db_types[0], provider, model, temperature, max_tokens, api_key) llm = LLMAdapterFactory.create(settings) diff --git a/src/sql_optimizer_team/tools/sql_tools.py b/src/sql_optimizer_team/tools/sql_tools.py index b59b40c..337523b 100644 --- a/src/sql_optimizer_team/tools/sql_tools.py +++ b/src/sql_optimizer_team/tools/sql_tools.py @@ -169,23 +169,60 @@ def list_supported_databases(values: Iterable[str]) -> str: return ", ".join(values) +def _escape_html(text: str) -> str: + return ( + text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + ) + + def diff_sql(original_sql: str, optimized_sql: str) -> str: - """Generate a unified diff between original and optimized SQL. + """Generate a side-by-side diff between original and optimized SQL. Args: original_sql: Original SQL text optimized_sql: Optimized SQL text Returns: - Unified diff string (line-by-line) + Side-by-side diff in HTML table format """ original_lines = original_sql.strip().splitlines() optimized_lines = optimized_sql.strip().splitlines() - diff = difflib.unified_diff( - original_lines, - optimized_lines, - fromfile="original.sql", - tofile="optimized.sql", - lineterm="", - ) - return "\n".join(diff) + + matcher = difflib.SequenceMatcher(a=original_lines, b=optimized_lines) + rows: list[str] = [] + + for tag, i1, i2, j1, j2 in matcher.get_opcodes(): + if tag == "equal": + for left, right in zip(original_lines[i1:i2], optimized_lines[j1:j2]): + rows.append( + f"
{_escape_html(left)}
{_escape_html(right)}
" + ) + elif tag == "replace": + left_chunk = original_lines[i1:i2] + right_chunk = optimized_lines[j1:j2] + max_len = max(len(left_chunk), len(right_chunk)) + for idx in range(max_len): + left = left_chunk[idx] if idx < len(left_chunk) else "" + right = right_chunk[idx] if idx < len(right_chunk) else "" + rows.append( + f"
{_escape_html(left)}
{_escape_html(right)}
" + ) + elif tag == "delete": + for left in original_lines[i1:i2]: + rows.append( + f"
{_escape_html(left)}
"
+                )
+        elif tag == "insert":
+            for right in optimized_lines[j1:j2]:
+                rows.append(
+                    f"
{_escape_html(right)}
" + ) + + if not rows: + return "
OriginalOtimizada
" + + header = "OriginalOtimizada" + body = "".join(rows) + return f"{header}{body}
"