From d6adaa204ceb000e65da520bf7896ab5cefafc4a Mon Sep 17 00:00:00 2001 From: "william.dias" Date: Wed, 21 Jan 2026 14:54:48 -0300 Subject: [PATCH] feat: Update architecture proposal with enhanced automation details and SQL optimization insights --- docs/proposta-arquitetura-agno.md | 22 +++++- optimization_metadata.json | 8 +++ .../agents/sql_quality_agent.py | 3 +- .../infrastructure/llm/agno_adapter.py | 7 +- .../infrastructure/llm/factory.py | 10 ++- src/sql_optimizer_team/tools/sql_tools.py | 69 ++++++++++++------- 6 files changed, 88 insertions(+), 31 deletions(-) diff --git a/docs/proposta-arquitetura-agno.md b/docs/proposta-arquitetura-agno.md index bdf922c..e56c843 100644 --- a/docs/proposta-arquitetura-agno.md +++ b/docs/proposta-arquitetura-agno.md @@ -2,7 +2,7 @@ ## 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. O desenho prioriza previsibilidade de custos, rastreabilidade e integração com processos corporativos já existentes. +Esta proposta posiciona a Icatu na vanguarda da automação inteligente com uma arquitetura Agno-first para otimização de SQL Oracle e SQL Server. O resultado esperado é **ganho acelerado de performance**, **redução direta de custos** e **escala de produtividade** sem aumento proporcional de equipes. A solução traz governança, observabilidade e segurança desde o primeiro dia, com integração total aos fluxos corporativos e potencial de automação transversal em toda a empresa. ## 2) Objetivo do projeto @@ -12,6 +12,8 @@ Esta proposta apresenta uma arquitetura moderna baseada em Agno para construçã - Viabilizar RAG corporativo para respostas alinhadas ao conhecimento interno. - Preparar integração com canais corporativos (ex.: Microsoft Teams) para entrada de solicitações. +Resultado esperado: **padronização técnica corporativa**, **velocidade de entrega** e **governança executiva** para decisões de performance. + ## 3) Escopo funcional - Otimização de queries SQL para Oracle e SQL Server. @@ -20,6 +22,8 @@ Esta proposta apresenta uma arquitetura moderna baseada em Agno para construçã - Execução assistida (manual ou semi-automática) com trilha de auditoria. - Produção de relatórios executivos e técnicos por área/solicitante. +Escopo desenhado para impacto rápido: **as consultas mais críticas recebem atenção imediata**, e o conhecimento se acumula para ganhos contínuos. + ## 4) Benefícios para a diretoria - **Eficiência operacional**: redução de tempo gasto por equipes na análise manual de queries. @@ -28,7 +32,7 @@ Esta proposta apresenta uma arquitetura moderna baseada em Agno para construçã - **Governança e compliance**: rastreabilidade, métricas, custos e decisões auditáveis. - **Redução de risco**: revisão automática e análise conservadora antes de mudanças. -Detalhamento das vantagens: +Detalhamento das vantagens (foco executivo): - **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. @@ -36,6 +40,7 @@ Detalhamento das vantagens: - **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. +- **Imagem institucional**: adoção de IA aplicada com governança fortalece a percepção de inovação e maturidade tecnológica. ## 5) Novas possibilidades de automação @@ -45,6 +50,7 @@ Detalhamento das vantagens: - **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. +- **Automação orientada a metas**: backlog de otimizações priorizado por impacto financeiro. ## 5.1) Cenários de automação por setor @@ -98,6 +104,8 @@ Detalhamento das vantagens: - **Integrações corporativas**: bancos, repositórios de conhecimento, logs e custos. - **Camada de governança**: políticas de acesso, limites de uso e auditoria. +Arquitetura pensada para expansão: novos agentes, novos bancos e novos fluxos entram sem reescrita do core. + ## 6.1) Integração com Microsoft Teams - **Canal dedicado**: solicitações feitas diretamente no Teams por usuários autorizados. @@ -106,6 +114,7 @@ Detalhamento das vantagens: - **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. +- **Experiência fluida**: usuários solicitam melhorias sem sair do canal que já utilizam diariamente. ## 7) Observabilidade (obrigatório) @@ -122,6 +131,7 @@ Métricas mínimas: - Latência média e p95. - Taxa de retrabalho/recusa. - Taxa de aprovação e impacto percebido. +- Ganhos mensais estimados por área (tempo e custo). ## 8) Segurança da informação @@ -130,6 +140,7 @@ Métricas mínimas: - **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. +- **Conformidade reforçada**: trilhas claras para auditoria interna e externa. ## 9) RAG corporativo @@ -137,6 +148,7 @@ Métricas mínimas: - 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. +- **Aumento de precisão**: respostas consistentes com políticas internas e padrões técnicos. ## 10) Frameworks considerados @@ -145,6 +157,7 @@ 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. +- **Escala corporativa**: mais aderente a integrações enterprise e customizações profundas. ### B) Langflow @@ -164,6 +177,7 @@ Métricas mínimas: - **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). +- **Escala com controle**: custos e resultados visíveis para a diretoria em dashboards executivos. ## 12) Roadmap proposto @@ -171,6 +185,7 @@ Métricas mínimas: 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. +5. **Fase 5**: expansão para todo o ciclo de vida de performance (detecção, otimização, validação, monitoramento). ## 13) Riscos e mitigação @@ -179,12 +194,15 @@ Métricas mínimas: - **Vazamento de dados**: mitigado com RAG isolado e mascaramento. - **Mudanças não controladas**: mitigado com workflow de aprovação. +O modelo foi desenhado para que **os ganhos superem consistentemente qualquer esforço de implantação**, com governança e controle pleno. + ## 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). +- Plano de comunicação executiva e cronograma de entregas visíveis para a diretoria. --- diff --git a/optimization_metadata.json b/optimization_metadata.json index c1eb9ed..156adfe 100644 --- a/optimization_metadata.json +++ b/optimization_metadata.json @@ -14,5 +14,13 @@ "database_type": "oracle", "version": "1.1", "optimized_at": "2026-01-21T17:34:23.063780+00:00" + }, + "172701d41ec9ae46": { + "query_hash": "172701d41ec9ae46", + "original_query": "SELECT DISTINCT c.customer_id, c.first_name, c.last_name, c.email, (SELECT COUNT(*) FROM orders o1 WHERE o1.customer_id = c.customer_id) as total_orders, (SELECT COUNT(*) FROM orders o2 WHERE o2.customer_id = c.customer_id AND o2.status = 'completed') as completed_orders, (SELECT COUNT(*) FROM orders o3 WHERE o3.customer_id = c.customer_id AND o3.status = 'pending') as pending_orders, (SELECT AVG(o5.total_amount) FROM orders o5 WHERE o5.customer_id = c.customer_id) as avg_order_value, (SELECT MAX(o6.order_date) FROM orders o6 WHERE o6.customer_id = c.customer_id) as last_order_date, CASE WHEN (SELECT COUNT(*) FROM orders o8 WHERE o8.customer_id = c.customer_id) > 50 THEN 'VIP' WHEN (SELECT COUNT(*) FROM orders o9 WHERE o9.customer_id = c.customer_id) > 20 THEN 'Premium' WHEN (SELECT COUNT(*) FROM orders o10 WHERE o10.customer_id = c.customer_id) > 5 THEN 'Regular' ELSE 'New' END as customer_tier FROM customers c WHERE c.customer_id IN (SELECT DISTINCT o19.customer_id FROM orders o19 WHERE o19.order_date >= SYSDATE - 730) AND EXISTS (SELECT 1 FROM orders o21 WHERE o21.customer_id = c.customer_id) AND c.email LIKE '%@%' ORDER BY (SELECT COUNT(*) FROM orders o22 WHERE o22.customer_id = c.customer_id) DESC.", + "explanation": "Below is a precise, step‑by‑step natural‑language description of the SQL, including all objects, operations, and required performance flags.\n\n---\n\n## 1. Overall Purpose\nThe query retrieves a **distinct list of customers** from the `customers` table and computes several **per‑customer aggregated metrics** from the `orders` table (counts, average order value, last order date), and classifies each customer into a tier based on number of orders. It **filters** customers to those with at least one order and with an order in the last 730 days, and whose email contains an “@” character. The results are **sorted** by total number of orders in descending order.\n\n---\n\n## 2. All Involved Database Objects\n**Tables**\n- `customers` (alias: `c`)\n- `orders` (multiple correlated subqueries with aliases: `o1`, `o2`, `o3`, `o5`, `o6`, `o8`, `o9`, `o10`, `o19`, `o21`, `o22`)\n\n**Subqueries / Derived Queries**\n- Correlated scalar subqueries in the SELECT list:\n - `SELECT COUNT(*) FROM orders o1 WHERE o1.customer_id = c.customer_id` → `total_orders`\n - `SELECT COUNT(*) FROM orders o2 WHERE o2.customer_id = c.customer_id AND o2.status = 'completed'` → `completed_orders`\n - `SELECT COUNT(*) FROM orders o3 WHERE o3.customer_id = c.customer_id AND o3.status = 'pending'` → `pending_orders`\n - `SELECT AVG(o5.total_amount) FROM orders o5 WHERE o5.customer_id = c.customer_id` → `avg_order_value`\n - `SELECT MAX(o6.order_date) FROM orders o6 WHERE o6.customer_id = c.customer_id` → `last_order_date`\n - `CASE` expression uses multiple correlated counts:\n - `SELECT COUNT(*) FROM orders o8 WHERE o8.customer_id = c.customer_id`\n - `SELECT COUNT(*) FROM orders o9 WHERE o9.customer_id = c.customer_id`\n - `SELECT COUNT(*) FROM orders o10 WHERE o10.customer_id = c.customer_id`\n- Subquery in the WHERE clause:\n - `SELECT DISTINCT o19.customer_id FROM orders o19 WHERE o19.order_date >= SYSDATE - 730`\n- EXISTS subquery in WHERE:\n - `SELECT 1 FROM orders o21 WHERE o21.customer_id = c.customer_id`\n- ORDER BY subquery:\n - `SELECT COUNT(*) FROM orders o22 WHERE o22.customer_id = c.customer_id`\n\n**Functions**\n- `COUNT(*)`\n- `AVG(o5.total_amount)`\n- `MAX(o6.order_date)`\n- `SYSDATE` and arithmetic `SYSDATE - 730`\n\n**No CTEs, views, stored procedures, or temporary tables are referenced.**\n\n---\n\n## 3. Essential Operations (Exact Columns and Logic)\n\n### SELECT Clause (output columns)\nThe query returns **DISTINCT** rows with the following columns from `customers c` plus correlated aggregate values:\n1. `c.customer_id`\n2. `c.first_name`\n3. `c.last_name`\n4. `c.email`\n5. `total_orders` \n - computed as `COUNT(*)` from `orders o1` where `o1.customer_id = c.customer_id`\n6. `completed_orders` \n - computed as `COUNT(*)` from `orders o2` where `o2.customer_id = c.customer_id` and `o2.status = 'completed'`\n7. `pending_orders` \n - computed as `COUNT(*)` from `orders o3` where `o3.customer_id = c.customer_id` and `o3.status = 'pending'`\n8. `avg_order_value` \n - computed as `AVG(o5.total_amount)` from `orders o5` where `o5.customer_id = c.customer_id`\n9. `last_order_date` \n - computed as `MAX(o6.order_date)` from `orders o6` where `o6.customer_id = c.customer_id`\n10. `customer_tier` \n - computed by a `CASE` expression using repeated `COUNT(*)` correlated subqueries:\n - If `COUNT(*)` from `orders o8` where `o8.customer_id = c.customer_id` > 50 → `'VIP'`\n - Else if `COUNT(*)` from `orders o9` where `o9.customer_id = c.customer_id` > 20 → `'Premium'`\n - Else if `COUNT(*)` from `orders o10` where `o10.customer_id = c.customer_id` > 5 → `'Regular'`\n - Else → `'New'`\n\n### FROM Clause\n- Base table: `customers c`\n\n### WHERE Clause (filters)\nThe main query includes **three filters**:\n1. `c.customer_id IN (...)` \n - Subquery: `SELECT DISTINCT o19.customer_id FROM orders o19 WHERE o19.order_date >= SYSDATE - 730` \n - Filters customers to those with at least one order in the last 730 days.\n2. `EXISTS (SELECT 1 FROM orders o21 WHERE o21.customer_id = c.customer_id)` \n - Ensures that the customer has at least one order.\n3. `c.email LIKE '%@%'` \n - Filters customers whose `email` contains an “@”.\n\n### Aggregations\nAll aggregation is done through **correlated scalar subqueries** (COUNT, AVG, MAX). There is **no GROUP BY** in the outer query.\n\n### DISTINCT\n- `SELECT DISTINCT` is used on the full row (customer columns + computed aggregates).\n\n### ORDER BY\n- Results are sorted by:\n - `ORDER BY (SELECT COUNT(*) FROM orders o22 WHERE o22.customer_id = c.customer_id) DESC`\n - This is another correlated subquery that recomputes order count per customer.\n\n### Oracle‑specific features\n- `SYSDATE` is used for date arithmetic (`SYSDATE - 730`).\n\n---\n\n## 4. Performance Issues (Critical Flags)\n\n**⚠️ CRITICAL: Leading Wildcard in LIKE**\n- `c.email LIKE '%@%'` \n This begins with `%`, which prevents index usage on `email` and makes the predicate non‑sargable.\n\n**Other observations (not flagged as “critical” per instructions, but noteworthy):**\n- The query runs **many redundant correlated subqueries** (multiple `COUNT(*)` on `orders` for the same customer, including in the ORDER BY and CASE). This can lead to repeated scans of `orders` per customer and significant performance cost.\n\n---\n\n## 5. Summary for Reconstruction\nTo reconstruct the query: \n- Start with `SELECT DISTINCT` from `customers c`. \n- Return `c.customer_id`, `c.first_name`, `c.last_name`, `c.email`. \n- Add multiple correlated scalar subqueries on `orders` for counts, averages, and max dates. \n- Build a `CASE` expression that classifies customers based on three separate correlated `COUNT(*)` checks. \n- Apply WHERE conditions: `c.customer_id IN (SELECT DISTINCT o19.customer_id FROM orders o19 WHERE o19.order_date >= SYSDATE - 730)`, `EXISTS (SELECT 1 FROM orders o21 WHERE o21.customer_id = c.customer_id)`, and `c.email LIKE '%@%'`. \n- Order by a correlated count of orders descending.\n\n---\n\nIf you want optimization ideas or a rewritten version that removes redundant subqueries, I can provide that too.", + "database_type": "oracle", + "version": "1.1", + "optimized_at": "2026-01-21T17:49:44.839914+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 f3489e7..d4afa21 100644 --- a/src/sql_optimizer_team/agents/sql_quality_agent.py +++ b/src/sql_optimizer_team/agents/sql_quality_agent.py @@ -30,7 +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 (lado a lado) e depois comente cada trecho alterado.", + "- Inclua um diff lado a lado (tabela) imediatamente após a SQL otimizada.", + "- Depois do diff, 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 eff0acb..3d5c581 100644 --- a/src/sql_optimizer_team/infrastructure/llm/agno_adapter.py +++ b/src/sql_optimizer_team/infrastructure/llm/agno_adapter.py @@ -29,7 +29,7 @@ class AgnoLLMAdapter(BaseLLMAdapter): self._log_request(prompt, temperature=temperature, max_tokens=max_tokens) try: # Best-effort: set common parameters if supported by model - if hasattr(self._model, "temperature"): + if self._supports_temperature() and hasattr(self._model, "temperature"): setattr(self._model, "temperature", temperature) if hasattr(self._model, "max_tokens"): setattr(self._model, "max_tokens", max_tokens) @@ -50,6 +50,11 @@ class AgnoLLMAdapter(BaseLLMAdapter): def provider_name(self) -> str: return self._model.get_provider() + def _supports_temperature(self) -> bool: + model_id = getattr(self._model, "id", "") + normalized = str(model_id).strip().lower() + return not normalized.startswith("gpt-5") + @staticmethod def _extract_text(response: Any) -> Optional[str]: """Extract response text from Agno ModelResponse.""" diff --git a/src/sql_optimizer_team/infrastructure/llm/factory.py b/src/sql_optimizer_team/infrastructure/llm/factory.py index 294845d..badf10f 100644 --- a/src/sql_optimizer_team/infrastructure/llm/factory.py +++ b/src/sql_optimizer_team/infrastructure/llm/factory.py @@ -2,7 +2,7 @@ from agno.models.anthropic import Claude from agno.models.google import Gemini -from agno.models.openai import OpenAIChat +from agno.models.openai import OpenAIChat, OpenAIResponses from agno.models.groq import Groq from agno.models.mistral.mistral import MistralChat from agno.models.ollama import Ollama @@ -75,9 +75,15 @@ class LLMAdapterFactory: provider = settings.llm.provider api_key = settings.llm.api_key + def _openai_model_class(model_id: str): + normalized = model_id.strip().lower() + if normalized.startswith("gpt-5") or normalized.startswith("o3") or normalized.startswith("o4-mini"): + return OpenAIResponses + return OpenAIChat + providers = { "gemini": (Gemini, "GOOGLE_API_KEY"), - "openai": (OpenAIChat, "OPENAI_API_KEY"), + "openai": (_openai_model_class(model), "OPENAI_API_KEY"), "claude": (Claude, "ANTHROPIC_API_KEY"), "groq": (Groq, "GROQ_API_KEY"), "mistral": (MistralChat, "MISTRAL_API_KEY"), diff --git a/src/sql_optimizer_team/tools/sql_tools.py b/src/sql_optimizer_team/tools/sql_tools.py index 337523b..8cc6ebc 100644 --- a/src/sql_optimizer_team/tools/sql_tools.py +++ b/src/sql_optimizer_team/tools/sql_tools.py @@ -177,6 +177,40 @@ def _escape_html(text: str) -> str: ) +def _append_row(rows: list[str], left: str, right: str) -> None: + rows.append( + f"
{_escape_html(left)}
{_escape_html(right)}
" + ) + + +def _iter_pairs(left_lines: list[str], right_lines: list[str]) -> Iterable[tuple[str, str]]: + max_len = max(len(left_lines), len(right_lines)) + for idx in range(max_len): + left = left_lines[idx] if idx < len(left_lines) else "" + right = right_lines[idx] if idx < len(right_lines) else "" + yield left, right + + +def _handle_equal(rows: list[str], left: list[str], right: list[str]) -> None: + for lval, rval in zip(left, right): + _append_row(rows, lval, rval) + + +def _handle_replace(rows: list[str], left: list[str], right: list[str]) -> None: + for lval, rval in _iter_pairs(left, right): + _append_row(rows, lval, rval) + + +def _handle_delete(rows: list[str], left: list[str], _right: list[str]) -> None: + for lval in left: + _append_row(rows, lval, "") + + +def _handle_insert(rows: list[str], _left: list[str], right: list[str]) -> None: + for rval in right: + _append_row(rows, "", rval) + + def diff_sql(original_sql: str, optimized_sql: str) -> str: """Generate a side-by-side diff between original and optimized SQL. @@ -193,32 +227,17 @@ def diff_sql(original_sql: str, optimized_sql: str) -> str: matcher = difflib.SequenceMatcher(a=original_lines, b=optimized_lines) rows: list[str] = [] + handlers = { + "equal": _handle_equal, + "replace": _handle_replace, + "delete": _handle_delete, + "insert": _handle_insert, + } + 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)}
" - ) + handler = handlers.get(tag) + if handler: + handler(rows, original_lines[i1:i2], optimized_lines[j1:j2]) if not rows: return "
OriginalOtimizada
"