Pular para o conteúdo principal

Qwen-Agent: Um guia com projeto de demonstração

Saiba como usar o Qwen-Agent e o Qwen3 para criar uma extensão de resumidor de páginas da Web em tempo real.
Actualizado 7 de mai. de 2025  · 12 min de leitura

Nesta publicação do blog, mostrarei a você como criar uma extensão de navegador com o Qwen-Agent que resume o conteúdo de qualquer página da Web em tempo real. Abordaremos como você pode:

  • Use o modelo Qwen3 da Alibaba localmente via Ollama
  • Usar o Qwen-Agent para estímulos estruturados e uso de ferramentas
  • Crie uma extensão do Chrome com atualizações de interface do usuário em fluxo contínuo
  • Adicionar backend FastAPI para oferecer suporte à compactação em tempo real

Ao final, você terá um agente resumidor habilitado para off-line, com a tecnologia Qwen3, que lê e resume páginas da Web em seu navegador com um único clique.

Mantemos nossos leitores atualizados sobre as últimas novidades em IA enviando o The Median, nosso boletim informativo gratuito de sexta-feira que detalha as principais histórias da semana. Inscreva-se e fique atento em apenas alguns minutos por semana:

O que é o Qwen-Agent?

Qwen-Agent é uma estrutura Python leve criada pela Alibaba para desenvolver aplicativos LLM poderosos usando o Qwen3 usando a família de modelos Qwen3. Ele se concentra no suporte:

  • Agentes de acompanhamento de instruções
  • Uso de ferramentas e chamadas de funções
  • Memória e planejamento de várias voltas
  • Solicitação flexível por meio de mensagens estruturadas

Em sua essência, o Qwen-Agent simplifica o desenvolvimento de agentes de IA modulares com raciocínio robusto e recursos de execução de código. Ele inclui componentes atômicos, como ferramentas e wrappers LLM, bem como classes Agent de alto nível para orquestrar fluxos de trabalho.

O Qwen-Agent funciona com APIs baseadas em nuvem (como o Alibaba DashScope) e tempos de execução locais compatíveis com OpenAI, como vLLM e Ollama. Com ele, você pode criar facilmente aplicativos LLM avançados:

  • Integração rápida via FastAPI
  • Fluxo integrado para UIs de front-end
  • Cadeias de ferramentas de funções personalizáveis
  • Privacidade e desempenho off-line completos

Visão geral do projeto: Extensão do Web Summarizer em tempo real

Nesta seção, criaremos uma extensão de resumidor em tempo real alimentada pelo Qwen-Agent. Isso inclui:

  • Um backend FastAPI que aceita texto de página da Web e retorna um resumo
  • Um pop-up de extensão do Chrome para capturar o conteúdo visível da página
  • Uma interface de usuário de fluxo contínuo como um resumo é gerada
  • Backend com tecnologia Qwen3:1.7B em execução local via Ollama

Vamos examinar cada parte desse projeto.

Etapa 1: Configurando o back-end do Qwen3

Nesta etapa, abordaremos todos os arquivos de código que precisamos configurar para que nosso backend funcione de forma síncrona com nossa extensão.

Etapa 1.1: Dependências e bibliotecas 

Comece instalando o ollama em seu sistema. Em seguida, liste todas as dependências do Python necessárias para o backend em requirements.txt.. Isso ajuda a garantir uma configuração consistente do ambiente entre máquinas e contêineres.

fastapi
uvicorn
python-dateutil
python-dotenv
qwen-agent[code_interpreter]
matplotlib

Aqui está um detalhamento da finalidade de cada biblioteca:

  • qwen-agent[code_interpreter]: Trata-se de uma estrutura de agente central com suporte ao uso de ferramentas, permitindo a execução de código e o raciocínio estruturado.
  • fastapi e uvicorn: Juntas, essas bibliotecas alimentam o backend da API assíncrona e atendem à lógica do resumidor.
  • python-dotenv e python-dateutil: Essas bibliotecas de utilitários lidam com variáveis de ambiente e processamento de data/hora (útil para escalonamento futuro).
  • matplotlib: É usado para plotar ou mostrar resultados visuais (opcional) 

Se você quiser executar esse projeto localmente, execute o seguinte comando para instalar todas as dependências:

pip install -r requirements.txt

Etapa 1.2: Configuração do DockerFile (opcional)

Agora, vamos configurar um Dockerfile que cria um ambiente Python mínimo com nosso aplicativo e expõe a porta 7864, permitindo que a extensão do Chrome envie solicitações ao backend FastAPI.

FROM python:3.10

WORKDIR /app
COPY . .

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 7864

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7864"]

Essa configuração é opcional se você optar por executar o projeto localmente usando o Uvicorn em vez do Docker.

Etapa 1.3: Servindo Qwen com Ollama

Em seguida, você precisará extrair e servir o modelo Qwen3 localmente usando o Ollama. Esta etapa permite a inferência off-line ao hospedar o serviço LLM em seu computador.

ollama pull qwen3:1.7b
ollama serve 

Etapa 1.4: Criando o servidor de compactação FastAPI

O arquivo app.py define um servidor FastAPI que transmite resumos em tempo real do conteúdo de páginas da Web usando um modelo Qwen3:1.7B em execução local por meio do Qwen-Agent.

Etapa 1.4.1: Importações

Comece importando FastAPI, utilitários de streaming e componentes do Qwen-Agent, como Assistant, que envolve o modelo e suas habilidades de ferramenta.

import re
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import List, Dict, Any
from qwen_agent.agents import Assistant
import logging

Etapa 1.4.2: Inicialização do aplicativo FastAPI

Em seguida, definimos um esquema simples de RequestData para receber conteúdo e criamos uma instância de bot de Assistant

app = FastAPI()
class RequestData(BaseModel):
    content: str
bot = Assistant(
    llm={
        "model": "qwen3:1.7b",
        "model_server": "http://localhost:11434/v1",
        "api_key": "EMPTY"
    },
    function_list=["code_interpreter"],
    system_message="You are a summarization assistant. Directly output the cleaned summary of the given text without any reasoning, self-talk, thoughts, or internal planning steps. Do not include phrases like 'I think', 'maybe', 'let's', 'the user wants', or anything not part of the final summary. Your output must look like it was written by an editor, not a model."
)

No código acima, o bot é uma instância da classe Assistant do Qwen-Agent, configurada para interagir com o modelo Qwen3:1.7B servido localmente por meio do Ollama. Ele usa a ferramenta integrada code_interpreter para analisar o conteúdo e executar o código Python, se necessário. Um system_message personalizado orienta seu comportamento para gerar resumos concisos de páginas da Web.

Observação: Você pode usar modelos menores, como qwen3:0.6b ou qwen3:1.7b, para reduzir significativamente o tempo de resposta e o uso da memória. Isso é especialmente útil para tarefas de resumo mais rápidas sem muita sobrecarga de raciocínio.

Etapa 1.4.3: Rota do resumidor

Por fim, um endpoint lê o conteúdo de entrada e produz um texto de resumo em tempo real à medida que é gerado pelo modelo. Ele usa saída de streaming para que o usuário não precise esperar que a resposta completa seja gerada.

@app.post("/summarize_stream_status")
async def summarize_stream_status(data: RequestData):
    user_input = data.content
    def stream():
        try:
            yield "🔍 Reading content on website...\n"
            print(" Received text:", user_input[:200])
            messages = [
            {"role": "system", "content": "You are a summarization assistant. Directly output the cleaned summary of the given text without any reasoning, self-talk, thoughts, or internal planning steps. Do not include phrases like 'I think', 'maybe', 'let's', 'the user wants', or anything not part of the final summary. Your output must look like it was written by an editor, not a model."},
            {"role": "user", "content": "<nothink>\nSummarize the following text clearly and concisely. Do not include any internal thoughts, planning, or reasoning. Just return the final summary:\n\n" + user_input + "\n</nothink>"}
            ]
            yield "🧠 Generating summary...\n"
            result = bot.run(messages)
            result_list = list(result)
            print(" Raw result:", result_list)
            # Extract summary
            last_content = None
            for item in reversed(result_list):
                if isinstance(item, list):
                    for subitem in reversed(item):
                        if isinstance(subitem, dict) and "content" in subitem:
                            last_content = subitem["content"]
                            break
                if last_content:
                    break
            if not last_content:
                yield " No valid summary found.\n"
                return
            summary = re.sub(r"</?think>", "", last_content)
            summary = re.sub(
                r"(?s)^.*?(Summary:|Here's a summary|The key points are|Your tutorial|This tutorial|To summarize|Final summary:)", 
                "", 
                summary, 
                flags=re.IGNORECASE
            )
            summary = re.sub(r"\n{3,}", "\n\n", summary)
            summary = summary.strip()
            yield "\n📄 Summary:\n" + summary + "\n"
        except Exception as e:
            print(" Error:", e)
            yield f"\n Error: {str(e)}\n"
    return StreamingResponse(stream(), media_type="text/plain")

Veja como nosso resumidor funciona:

  • A função recebe a instrução de dados POST usando o esquema RequestData, esperando um campo chamado content que contém o texto bruto da página da Web.
  • Uma função geradora stream() aninhada é definida para produzir mensagens progressivamente. Isso permite que o feedback em tempo real seja enviado de volta ao front-end antes que o resumo final seja concluído.
  • A lista de mensagens inclui explicitamente um wrapper de tag na mensagem do usuário para suprimir o raciocínio interno, e um prompt detalhado do sistema reforça esse comportamento.
  • O bot Qwen-Agent é chamado usando bot.run(messages), onde messages é uma lista que contém uma única mensagem de entrada do usuário. 
  • As respostas do modelo são transmitidas e coletadas em result_list. Em seguida, o código percorre a lista de forma inversa para extrair a string de conteúdo mais recente retornada pelo LLM.
  • Os marcadores de raciocínio interno do Qwen, como e , são removidos da saída usando um regex para limpar o resumo final.
  • Se nenhum conteúdo válido for encontrado, uma mensagem de fallback será enviada. Caso contrário, o resumo limpo é transmitido para o frontend.

Etapa 2: Criando a extensão do Chrome

Esta seção apresenta todos os arquivos necessários para configurar a extensão, inclusive a interface do usuário de front-end, a lógica para extrair o conteúdo da página, os scripts de comunicação em segundo plano e os metadados de configuração.

Antes de examinarmos os scripts individuais, aqui está um detalhamento simples do que cada arquivo faz:

  • manifest.json: Esse é um arquivo de configuração que define metadados, permissões e scripts de extensão.
  • popup.html: Esse script define a interface do usuário visível para o pop-up da extensão do Chrome com um botão e um painel de saída.
  • popup.js: Isso lida com a lógica de front-end e captura o texto da guia atual e transmite a resposta resumida para a interface do usuário.
  • content.js: Ele extrai o conteúdo visível da página da Web quando solicitado, atuando como o script de conteúdo.
  • background.js: Esse script coordena a comunicação de back-end e retransmite os resumos transmitidos para o pop-up.
  • icon.png: Esse é um ícone de extensão exibido na barra de ferramentas e no gerenciador de extensões do Chrome.

Juntos, esses arquivos tornam a extensão interativa, reativa e capaz de se comunicar com um servidor de modelo local.

Etapa 1: Metadados de extensão

O script manifest.json contém metadados para o Chrome. Ele define o comportamento da extensão, as permissões, o trabalhador do serviço em segundo plano e a interface do usuário pop-up.

{
    "manifest_version": 3,
    "name": "Web Summarizer",
    "version": "1.0",
    "description": "Summarize the content of the current page using a local LLM agent.",
    "permissions": [
      "scripting",
      "activeTab",
      "storage"
    ],
    "host_permissions": [
      "<all_urls>"
    ],
    "action": {
      "default_popup": "popup.html",
      "default_icon": "icon.png"
    },
    "background": {
      "service_worker": "background.js"
    },
    "content_scripts": [
      {
        "matches": ["<all_urls>"],
        "js": ["content.js"],
        "run_at": "document_idle"
      }
    ]
}  

Esse arquivo define a configuração e os recursos de sua extensão do Chrome. Ele concede permissões essenciais, como scripting (para injetar JavaScript), activeTab (para acessar a guia atual) e storage (para salvar as preferências do usuário).

O campo host_permissions com "<all_urls>" permite que a extensão seja executada em qualquer página da Web. Ele também configura a interface do usuário da extensão por meio de popup.html e icon.png e registra o comportamento em segundo plano usando um service worker (background.js). Por fim, ele define um script de conteúdo (content.js) a ser executado em todas as páginas carregadas, permitindo a interação com o conteúdo da página da Web quando necessário.

Etapa 2: Criando uma interface visível 

O site popup.html define a interface visível da extensão. Ele se conecta à guia ativa que captura o texto visível e o envia ao seu servidor de back-end. 

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web Summarizer Assistant</title>
  <style>
    body {
      width: 500px;
      height: 600px;
      background-color: aliceblue;
      
      
      
    }
    .container {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 15px;
    }
    button {
      border: none;
      border-radius: 5px;
      background-
      color: white;
      
      
      cursor: pointer;
    }
    button:hover {
      background-
    }
    pre {
      background-
      border: 1px solid #ccc;
      border-radius: 5px;
      
      width: 90%;
      height: 350px;
      overflow: auto;
    }
    input[type="text"] {
      
      width: 90%;
      border-radius: 5px;
      border: 1px solid #ccc;
    }
  </style>
</head>
<body>
  <div class="container">
    <h2>Summarize Current Page</h2>
    <button id="summarizeBtn">Summarize</button>
    <pre id="output">Summary will appear here...</pre>
  </div>
  <script src="popup.js"></script>
</body>
</html>

Vamos entender o código acima passo a passo:

  1. Primeiro, configuramos uma estrutura HTML básica com uma tag que inclui metatags para codificação de caracteres, configurações de viewport e uma tag </code>.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Em seguida, dentro da tag <code><body></code>, um contêiner contém todos os elementos da interface do usuário, inclusive:</span></li> </ol> <ul style="padding-inline-start: 48px;"> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Um título <code><h2></code> que solicita que o usuário resuma a página atual.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Um botão <code>Summarize</code> ("#summarizeBtn") no qual o usuário clica para acionar a extração e o resumo do conteúdo.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Um elemento <code><pre></code> ("#output") que funciona como um painel rolável para exibir o resumo à medida que ele é transmitido.</span></li> </ul> <ol style="padding-inline-start: 48px;" start="3"> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">O estilo é definido usando CSS incorporado para tornar o pop-up visualmente limpo e legível. O bloco <code>pre</code> é usado para tornar a saída rolável e visualmente distinta.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">O script <code>popup.js</code> escuta os cliques nos botões, extrai o conteúdo da página, envia-o para o back-end da FastAPI e atualiza o painel de saída com o texto de resumo do fluxo.</span></li> </ol> <h3 dir="ltr">Etapa 3: Criação de uma interface de usuário de front-end </h3> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">O site <code>popup.js</code> atua como um controlador lógico de front-end que responde a cliques em botões, extrai texto da guia atual, envia-o para o back-end e transmite o resumo de volta para a área de saída em tempo real.</span></p> <pre class="language-javascript"><code>document.addEventListener('DOMContentLoaded', function () { let serverAddress = '127.0.0.1'; document.getElementById('summarizeBtn').addEventListener('click', async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); chrome.scripting.executeScript( { target: { tabId: tab.id }, func: () => { try { return document.body?.innerText || 'EMPTY'; } catch (e) { return 'SCRIPT_ERROR'; } } }, async (results) => { const pageText = results?.[0]?.result || ''; console.log(' Extracted text:', pageText.slice(0, 500)); if (pageText === 'SCRIPT_ERROR') { document.getElementById('output').innerText = 'Could not access page content (script error).'; return; } if (!pageText || pageText.trim() === 'EMPTY') { document.getElementById('output').innerText = ' No page text found.'; return; } try { console.log( Sending streaming POST to http://${serverAddress}:7864/summarize_stream_status); const res = await fetch(http://${serverAddress}:7864/summarize_stream_status, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: pageText }), }); const reader = res.body.getReader(); const decoder = new TextDecoder(); let resultText = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); resultText += chunk; document.getElementById('output').innerText = resultText; } } catch (err) { console.error(' Fetch error:', err); document.getElementById('output').innerText = ' Failed to get summary.\n' + err.message; } } ); }); });</code></pre> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Esse arquivo JavaScript alimenta a lógica da interface do usuário de front-end da extensão do Chrome, permitindo que os usuários extraiam e resumam o conteúdo da página da Web com um clique.</span></p> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Veja como isso funciona:</span></p> <ul style="padding-inline-start: 48px;"> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Quando o pop-up é carregado (<code>DOMContentLoaded</code>), um ouvinte de evento é anexado ao botão </span><span style="background-color: transparent; font-weight: bold; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">"Summarize" (Resumir)</span><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> botão.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Ao clicar no botão, a extensão consulta a guia ativa no momento usando <code>chrome.tabs.query</code>.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Em seguida, ele executa um script de conteúdo dentro dessa guia usando <code>chrome.scripting.executeScript</code>, que tenta extrair o <code>innerText</code> do corpo da página.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Se o script falhar ou retornar conteúdo vazio, as mensagens de fallback apropriadas serão mostradas na interface do usuário.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Se o texto for extraído com êxito, ele será enviado como uma solicitação POST para o backend local da FastAPI em <code>http://127.0.0.1:7864/summarize_stream_status</code>.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A resposta é transmitida usando <code>res.body.getReader()</code>, o que permite que partes parciais do resumo sejam exibidas em tempo real no painel de saída.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">O site <code>TextDecoder</code> decodifica cada trecho de texto transmitido, anexando-o ao vivo à tela.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Todos os erros de rede ou de back-end são detectados e exibidos na interface pop-up para depuração ou feedback do usuário.</span></li> </ul> <h3 dir="ltr">Etapa 4: Extração do conteúdo da página da Web</h3> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Antes de enviarmos o conteúdo da página da Web para o backend, precisamos de uma maneira de extraí-lo da guia atual. O script <code>content.js</code> escuta as mensagens dos scripts de fundo ou pop-up e retorna o conteúdo de texto visível da página da Web. </span></p> <pre class="language-javascript"><code>chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === "GET_PAGE_TEXT") { const bodyText = document.body.innerText.trim(); sendResponse({ text: bodyText || null }); } });</code></pre> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Esse script escuta mensagens do tipo "GET_PAGE_TEXT" e responde extraindo e retornando o texto do corpo visível (<code>document.body.innerText</code>). Isso permite que o trabalhador em segundo plano solicite facilmente o conteúdo da página da Web sem injetar código a cada vez, mantendo a comunicação limpa e assíncrona.</span></p> <h3 dir="ltr">Etapa 5: Coordenação de back-end</h3> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">O script de plano de fundo (<code>background.js</code>) faz a ponte entre a IU do popup e os scripts de conteúdo. Ele garante que a transmissão de mensagens e a coordenação de compactação ocorram corretamente nos bastidores.</span></p> <pre class="language-javascript"><code>chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.type === "SUMMARIZE_PAGE") { const tabId = sender.tab.id; chrome.scripting.executeScript( { target: { tabId }, func: () => { try { return document.body?.innerText || 'EMPTY'; } catch (e) { return 'SCRIPT_ERROR'; } } }, async (results) => { const pageText = results?.[0]?.result || ''; if (!pageText || pageText === 'SCRIPT_ERROR') { chrome.runtime.sendMessage({ type: 'SUMMARY_RESULT', summary: ' Failed to read page text.', }); return; } chrome.storage.local.get(['database_host'], async function (result) { const host = result.database_host || '127.0.0.1'; try { const response = await fetch(http://${host}:7864/summarize_stream_status, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: pageText }), }); const reader = response.body.getReader(); const decoder = new TextDecoder(); let fullSummary = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); fullSummary += chunk; chrome.runtime.sendMessage({ type: 'SUMMARY_PROGRESS', chunk }); } chrome.runtime.sendMessage({ type: 'SUMMARY_DONE', summary: fullSummary }); } catch (err) { chrome.runtime.sendMessage({ type: 'SUMMARY_RESULT', summary: ' Error during summarization: ' + err.message }); } }); } ); return true; } }); </code></pre> <p dir="ltr"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">O código acima garante um fluxo suave de extração de conteúdo, resumo e atualizações em tempo real. Aqui está um detalhamento passo a passo de como isso funciona:</span></p> <ul style="padding-inline-start: 48px;"> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Ele fica atento a uma mensagem do tipo "SUMMARIZE_PAGE" acionada por um clique de botão no pop-up.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Uma vez acionado, ele identifica a guia ativa usando <code>sender.tab.id</code> e executa um script injetado via <code>chrome.scripting.executeScript</code> para extrair o texto visível da página da Web.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Se o conteúdo não puder ser acessado ou estiver vazio, ele enviará de volta uma mensagem de fallback.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Caso contrário, ele obtém o <code>database_host</code> (geralmente <code>127.0.0.1</code>) do armazenamento local do Chrome e o utiliza para enviar uma solicitação <code>POST</code> para o backend da FastAPI com o conteúdo extraído.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Em seguida, ele abre uma conexão de streaming e lê a resposta trecho por trecho usando um <code>ReadableStreamDefaultReader</code>.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">À medida que cada bloco é decodificado, ele envia atualizações provisórias para o pop-up usando <code>chrome.runtime.sendMessage</code> com o tipo 'SUMMARY_PROGRESS'.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Quando o fluxo termina, ele envia uma mensagem final com o resumo completo. Se ocorrer um erro durante esse processo, uma mensagem de fallback será enviada em seu lugar.</span></li> </ul> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Esse script em segundo plano é fundamental para permitir resumos de streaming em tempo real na extensão do Chrome sem bloquear a interface do usuário ou recarregar a página.</span></p> <h3 dir="ltr">Etapa 6: Adicionar um ícone de extensão</h3> <p dir="ltr"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Escolha ou desenhe uma imagem de ícone para a extensão e salve-a como <code>icon.png</code> na pasta da extensão. É isso que usaremos:</span></p> <p dir="ltr"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://media.datacamp.com/cms/ad_4nxenz-ec-m6n1ovwrqkq_ya8gybch-wok3r1ildciiyiunz9kusu_-uucfrkzqwfl1w-jjgrvcoz_erdhjcufxweuwpchh-0kqthmmw1xlxhozmlr7hor5hkf9rnpykvvyzp2p7t4g.png" alt="Ícone de extensão" width="400" height="264" /></p> <p dir="ltr" style="text-align: center;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Imagem gerada com o FireFly</span></p> <p dir="ltr"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Sua estrutura geral de pastas deve ser semelhante a esta:</span></p> <pre class="language-markdown"><code>QWEN-WEB-SUMMARIZER/ ├── backend/ │ ├── app.py │ ├── Dockerfile │ └── requirements.txt ├── extension/ │ ├── background.js │ ├── content.js │ ├── icon.png │ ├── manifest.json │ ├── popup.html │ └── popup.js</code></pre> <h2 dir="ltr">Etapa 3: Executando o aplicativo</h2> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Para executar tudo localmente, execute o seguinte comando no terminal:</span></p> <pre class="language-bash"><code>uvicorn backend.app:app --host 0.0.0.0 --port 7864</code></pre> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Isso é ideal para desenvolvimento local ou depuração sem o Docker. Para executar no Docker, execute os seguintes comandos, um de cada vez, em seu terminal:</span></p> <pre class="language-bash"><code>docker build -t qwen-agent-backend . docker run -p 7864:7864 qwen-agent-backend</code></pre> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A extensão do Chrome se comunica com a porta 7864.  Agora, siga as etapas a seguir para que sua extensão seja executada no Chrome:</span></p> <ul style="padding-inline-start: 48px;"> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Abra o Chrome e clique em "Extensões"</span><span style="background-color: transparent; font-weight: bold; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">(no lado direito da barra de pesquisa).</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Ative o "Modo de desenvolvedor" e clique em "Carregar descompactado" (canto superior esquerdo). </span></li> </ul> <p dir="ltr"><img loading="lazy" style="display: block; margin-left: auto; margin-right: auto;" src="https://media.datacamp.com/cms/ad_4nxeqediol3e_2camavptkettnqaz0dwk2biqs72uwhqpjjhnqgnzqbuhlyrx7ve4ropcgvz4czhzjc0lc496kwq7pie_epl-gqo04bdagklllw_5asyyo-_k0ny9ggqapx4rs_5v6q.png" alt="Guia Extensão do Chrome para ativar uma extensão com o Qwen-Agent" width="800" height="222" /></p> <ul style="padding-inline-start: 48px;"> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Em seguida, carregue nossa pasta de extensões que contém todos os arquivos e aguarde a confirmação.</span></li> <li><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Depois que a janela pop-up confirmar a configuração da extensão, clique no ícone da extensão em qualquer página da Web e obtenha um resumo da transmissão usando o modelo local do Qwen3.</span></li> </ul> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Vamos ver os resultados:</span></p> <p dir="ltr"><img loading="lazy" style="display: block; margin-left: auto; margin-right: auto;" src="https://media.datacamp.com/cms/ad_4nxen-zef7jtzs6nuqx9tscg2ggzh79ftzn4rwghacrjz4ztiz9qy-bvbr1879yiyy6y9woe_dzsak4cap42tu2qr5oc-wquz8x9k7wvvbkeprq5l8tsuntnnckm8my0pvnsqw0heza.png" alt="Exemplo de extensão do Qwen-Agent" width="800" height="512" /></p> <h2 dir="ltr">Conclusão</h2> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Neste projeto, criamos um assistente de resumo de páginas da Web em tempo real usando a estrutura Qwen-Agent e o modelo Qwen3:1.7B executado localmente via Ollama. Desenvolvemos um backend FastAPI para lidar com a inferência LLM e o integramos a uma extensão do Chrome que captura o conteúdo visível da página e exibe um resumo transmitido ao vivo.</span></p> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Esta demonstração mostra como o Qwen-Agent pode habilitar fluxos de trabalho de raciocínio totalmente off-line e com ferramentas no navegador. Ele estabelece a base para a criação de agentes locais mais avançados, como ferramentas de automação de navegador, copilotos de pesquisa ou chatbots baseados em documentos.</span></p> <p dir="ltr" style="text-align: justify;"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Para saber mais sobre agentes de IA, confira estes blogs:</span></p> <ul> <li dir="ltr" style="text-align: justify;"><a href="https://www.datacamp.com/pt/tutorial/n8n-ai"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">n8n: Um guia com exemplos</span></a></li> <li dir="ltr" style="text-align: justify;"><a href="https://www.datacamp.com/pt/blog/types-of-ai-agents"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Tipos de agentes de IA</span></a></li> <li dir="ltr" style="text-align: justify;"><a href="https://www.datacamp.com/pt/blog/agentic-ai"><span style="background-color: transparent; font-weight: 400; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">O que é IA agêntica?</span></a></li> </ul>

Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

Sou Google Developers Expert em ML (Gen AI), Kaggle 3x Expert e Women Techmakers Ambassador com mais de 3 anos de experiência em tecnologia. Fui cofundador de uma startup de tecnologia de saúde em 2020 e estou fazendo mestrado em ciência da computação na Georgia Tech, com especialização em machine learning.

Temas
Relacionado

Tutorial

Um guia para iniciantes na engenharia de prompts do ChatGPT

Descubra como fazer com que o ChatGPT forneça os resultados que você deseja, fornecendo a ele as entradas necessárias.
Matt Crabtree's photo

Matt Crabtree

6 min

Tutorial

Guia para iniciantes no uso da API do ChatGPT

Este guia o orienta sobre os conceitos básicos da API ChatGPT, demonstrando seu potencial no processamento de linguagem natural e na comunicação orientada por IA.
Moez Ali's photo

Moez Ali

11 min

Tutorial

Guia de Introdução ao Ajuste Fino de LLMs

O ajuste fino dos grandes modelos de linguagem (LLMs, Large Language Models) revolucionou o processamento de linguagem natural (PLN), oferecendo recursos sem precedentes em tarefas como tradução de idiomas, análise de sentimentos e geração de textos. Essa abordagem transformadora aproveita modelos pré-treinados como o GPT-2, aprimorando seu desempenho em domínios específicos pelo processo de ajuste fino.
Josep Ferrer's photo

Josep Ferrer

11 min

Tutorial

Tutorial da API de assistentes da OpenAI

Uma visão geral abrangente da API Assistants com nosso artigo, que oferece uma análise aprofundada de seus recursos, usos no setor, orientação de configuração e práticas recomendadas para maximizar seu potencial em vários aplicativos de negócios.
Zoumana Keita 's photo

Zoumana Keita

14 min

Tutorial

Visão GPT-4: Um guia abrangente para iniciantes

Este tutorial apresentará tudo o que você precisa saber sobre o GPT-4 Vision, desde o acesso a ele, passando por exemplos práticos do mundo real, até suas limitações.
Arunn Thevapalan's photo

Arunn Thevapalan

12 min

Tutorial

Como criar aplicativos LLM com o tutorial LangChain

Explore o potencial inexplorado dos modelos de linguagem grandes com o LangChain, uma estrutura Python de código aberto para criar aplicativos avançados de IA.
Moez Ali's photo

Moez Ali

12 min

Ver maisVer mais