voltar ao blog

IA nativa no WordPress 7.0: briefing automático de cada lead que chega

No post anterior eu modernizei o tema deste site com seis recursos recentes do WordPress e do PHP. Este é o round 2 — e desta vez o alvo é o recurso mais ambicioso que o WordPress 7.0 colocou no core: o cliente de IA nativo. Implementei com ele algo que mexe diretamente no meu caixa: cada lead que deixa o WhatsApp aqui no site agora ganha, em segundos, um briefing gerado por IA — quem parece ser, o que estava pesquisando, temperatura comercial e uma sugestão de primeira mensagem. Código real, decisões de arquitetura reais, e as armadilhas que quase ninguém comenta.

O problema de negócio (antes da tecnologia)

Meu funil funciona assim: o conteúdo traz a visita, a visita deixa o número de WhatsApp, e eu inicio a conversa. O momento mais frágil é exatamente a primeira mensagem. Abordar um lead “no escuro” — sem saber que artigo o trouxe, o que ele escreveu no formulário, se veio de busca ou de indicação — queima a melhor chance de resposta. E esse contexto todo já está no meu banco; só estava caro de juntar na hora, para cada lead, no celular, entre uma tarefa e outra.

É o caso de uso perfeito para IA no servidor: transformar dado que já existe em decisão pronta. Não é chatbot na home, não é conteúdo gerado — é IA como camada invisível de operação.

O AI Client do WordPress 7.0

O WordPress 7.0 embutiu no core um cliente de IA provider-agnostic (o pacote php-ai-client), com uma API fluente 100% PHP. A chave do provedor — Anthropic, OpenAI ou Google — vive no Connectors API, também do core: eu troco de provedor no admin e o código não muda uma linha. A chamada inteira do briefing:

$raw = wp_ai_client_prompt( $prompt )
    ->using_system_instruction( 'Você é o assistente comercial da Logup…' )
    ->using_temperature( 0.4 )
    ->using_max_tokens( 500 )
    ->as_json_response( array(
        'type'       => 'object',
        'properties' => array(
            'resumo'      => array( 'type' => 'string' ),
            'mensagem'    => array( 'type' => 'string' ),
            'temperatura' => array(
                'type' => 'string',
                'enum' => array( 'quente', 'morno', 'frio' ),
            ),
        ),
        'required'   => array( 'resumo', 'mensagem', 'temperatura' ),
    ) )
    ->generate_text();

Repare no as_json_response() com JSON Schema: a resposta volta estruturada e validável, não prosa solta para eu “parsear com fé”. Erros viram WP_Error — padrão WordPress, sem exception vazando para a página. É o mesmo desenho de contrato da Abilities API que mostrei no post anterior, e isso não é coincidência: o core inteiro está convergindo para APIs que máquinas conseguem consumir com segurança.

A decisão que separa sênior de tutorial: onde essa chamada roda

Uma chamada de IA leva de dois a dez segundos. O formulário de captura responde em milissegundos. Colocar a IA dentro do request do visitante significaria: pessoa esperando tela travada para me dar o número dela. Impensável — o lead é a métrica que paga as contas.

Então o fluxo é assíncrono desde o desenho:

add_action( 'logup_lead_created', static function ( $lead_id ) {
    $args = array( (int) $lead_id );
    if ( ! wp_next_scheduled( 'logup_ai_brief_lead', $args ) ) {
        wp_schedule_single_event( time() + 10, 'logup_ai_brief_lead', $args );
    }
} );

O formulário responde no mesmo tempo de sempre; dez segundos depois, um evento de cron gera o briefing e grava nas notas do lead, que aparecem direto no meu painel. E a degradação é em camadas, cada uma silenciosa: WordPress sem 7.0? Nada acontece. wp_supports_ai() desligado? Nada acontece. Conector sem chave? is_supported_for_text_generation() devolve false e nada acontece. API do provedor fora do ar? WP_Error, nada acontece. Em todos os cenários o funil continua capturando — IA é acelerador, nunca dependência.

E se o cron não rodar? Ability sob demanda

Cron de WordPress depende de tráfego. Site pequeno de madrugada pode atrasar o evento. Por isso o mesmo módulo registra uma ability — a API de “o que o site sabe fazer” que apresentei no post anterior — chamada logup/lead-briefing: recebe o ID do lead e devolve o briefing, gerando na hora se ainda não existir. Autenticada, com entrada e saída declaradas em JSON Schema.

O detalhe estratégico: meu bot de outreach (o que dispara a primeira mensagem no WhatsApp) pode consumir essa ability antes de enviar. O ciclo fecha sozinho — conteúdo → lead → briefing → primeira mensagem informada — e cada peça é substituível porque as fronteiras são contratos, não acoplamento.

PHP 8.1 até o limite (de novo, sem tocar no servidor)

O módulo novo nasceu 100% na sintaxe que o meu PHP 8.1 de produção já suporta — value object imutável com constructor property promotion e readonly properties, mais um enum para a temperatura comercial:

final class LeadBriefing {
    public function __construct(
        public readonly string $resumo,
        public readonly string $mensagem,
        public readonly LeadTemperature $temperatura,
    ) {}
}

enum LeadTemperature: string {
    case Quente = 'quente';
    case Morno  = 'morno';
    case Frio   = 'frio';

    public function label(): string {
        return match ( $this ) {
            self::Quente => 'Quente — responder primeiro',
            self::Morno  => 'Morno — abordar hoje',
            self::Frio   => 'Frio — sem urgência',
        };
    }
}

O JSON da IA passa por LeadBriefing::fromArray(), que valida e devolve null se vier incompleto — modelo alucinou o formato, briefing não entra torto no banco. No PHP 8.2 a classe inteira viraria readonly class numa palavra; no 8.4, array_find() limparia uns loops. Está no roadmap do servidor — mas de novo ficou provado que dá para escrever código de 2026 no runtime que já está pago.

Bônus: índice automático com o parser do core como token walker

Aproveitei o round para resolver um problema clássico de blog: post longo sem navegação interna. Todo artigo daqui agora ganha um índice com âncoras — gerado em tempo de render, sem plugin, sem tocar no banco. E a parte tecnicamente saborosa: o WP_HTML_Tag_Processor não lê texto quando você itera por tags… mas lê quando você itera por tokens:

while ( $processor->next_token() ) {
    if ( 'H2' === $processor->get_token_name() && '#tag' === $processor->get_token_type() ) {
        $inside = ! $processor->is_tag_closer();
        // abertura zera o rótulo; fechamento arquiva o título coletado
    } elseif ( $inside && '#text' === $processor->get_token_type() ) {
        $label .= $processor->get_modifiable_text();
    }
}

Primeiro passe coleta os títulos das seções pela stream de tokens; segundo passe grava os id de âncora (preservando qualquer id que já exista). Entidades HTML, negrito dentro do título, acento virando slug limpo — o parser resolve tudo que faria uma regex chorar. As âncoras estáveis ainda rendem jump links na busca do Google. Você está vendo o resultado agora: o índice no topo deste artigo saiu exatamente desse código.

O que fica de lição

Três princípios guiaram tudo e valem para qualquer projeto: IA nunca no caminho crítico (o visitante não espera; cron e ability esperam por mim); degradação silenciosa em cada camada (a feature nova falha sem derrubar a antiga); e contrato antes de código (JSON Schema na entrada e na saída, tanto na IA quanto na ability). O WordPress de 2026 entrega a infraestrutura para os três — a diferença está em quem monta as peças com critério.

Atualização: o índice que apresentei aqui acabou denunciando um bug que estava invisível desde o lançamento do site — contei a caçada completa em âncoras que não rolavam: o bug de 15 dias no ar.

Se o site do seu negócio captura contato de alguma forma, pergunta honesta: quanto contexto sobre cada pessoa você já tem gravado e não usa na hora de responder? Se quiser, me deixa teu WhatsApp aqui embaixo — te mostro, no teu caso, o que daria para automatizar sem colocar nada disso na frente do cliente.