在本文中,我将带您一步步构建一个由 Qwen-Agent 驱动的浏览器扩展,可实时总结任意网页的内容。我们将介绍如何:
通过 Ollama 在本地使用阿里巴巴的 Qwen3 模型
使用 Qwen-Agent 进行结构化提示与工具调用
构建具有流式 UI 更新的 Chrome 扩展
添加 FastAPI 后端以支持实时摘要
完成后,您将获得一个由 Qwen3 提供动力、支持离线的摘要代理,只需一键即可在浏览器中读取并总结网页内容。
我们通过每周五免费的新闻简报 The Median 为读者带来最新的 AI 动态,梳理一周要闻。订阅即可用几分钟保持前沿:
什么是 Qwen-Agent?
Qwen-Agent 是阿里巴巴打造的轻量级 Python 框架,面向 Qwen3 系列模型,便于开发强大的大模型应用。其重点支持:
遵循指令的智能体
工具使用与函数调用
记忆与多轮规划
通过结构化消息实现灵活提示
Qwen-Agent 的核心是简化具备稳健推理与代码执行能力的模块化 AI 智能体开发。它既提供 LLM 封装与工具等原子组件,也提供用于编排流程的高层 Agent 类。
Qwen-Agent 既可对接云端 API(如阿里云 DashScope),也能连接本地 OpenAI 兼容运行时,如 vLLM 和 Ollama 。它让您轻松构建强大的 LLM 应用,具备:
通过 FastAPI 快速集成
内置前端 UI 流式输出
可定制的函数工具链
完全离线的隐私与性能
项目概览:实时网页摘要扩展
本节我们将构建一个由 Qwen-Agent 驱动的实时摘要扩展,包括:
一个 FastAPI 后端,接收网页文本并返回摘要
一个用于捕获可见页面内容的 Chrome 扩展弹窗
在生成摘要时进行流式 UI 展示
本地通过 Ollama 运行的 Qwen3:1.7B 作为后端
下面我们依次讲解项目的各个部分。
步骤 1:搭建 Qwen3 后端
本步骤将介绍为后端与扩展同步工作所需设置的全部代码文件。
步骤 1.1:依赖与库
先在系统上安装 ollama 。然后在 requirements.txt 中列出后端所需的全部 Python 依赖。这有助于在不同机器与容器中保持一致的环境。
fastapi
uvicorn
python-dateutil
python-dotenv
qwen-agent[code_interpreter]
matplotlib技术支持
下面是各库的用途:
qwen-agent[code_interpreter]:核心智能体框架,支持工具使用,便于代码执行与结构化推理。
fastapi 与 uvicorn:共同构建异步 API 后端并承载您的摘要逻辑。
python-dotenv 与 python-dateutil:用于处理环境变量与日期/时间(便于后续扩展)。
matplotlib:用于绘图或展示可视化输出(可选)
若要在本地运行该项目,执行下列命令安装所有依赖:
pip install -r requirements.txt技术支持
步骤 1.2:DockerFile 设置(可选)
现在,我们编写一个 Dockerfile,创建包含应用的最小 Python 环境,并开放 7864 端口,便于 Chrome 扩展向 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"]技术支持
如果您选择使用 Uvicorn 而非 Docker 在本地运行,此设置可省略。
步骤 1.3:用 Ollama 提供 Qwen 服务
接下来,您需要使用 Ollama 在本地拉取并提供 Qwen3 模型服务。该步骤会在您的机器上托管 LLM 服务,从而实现离线推理。
ollama pull qwen3:1.7b
ollama serve 技术支持
步骤 1.4:构建 FastAPI 摘要服务
app.py 定义了一个 FastAPI 服务器,通过 Qwen-Agent 调用本地运行的 Qwen3:1.7B,为网页内容提供实时流式摘要。
步骤 1.4.1:导入
首先导入 FastAPI、流式工具,以及 Qwen-Agent 的 Assistant 等组件,它封装了模型与工具能力。
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技术支持
步骤 1.4.2:FastAPI 应用初始化
接着定义用于接收内容的简单 RequestData 模式,并创建一个 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."
) 技术支持
上述代码中,bot 是 Qwen-Agent 的 Assistant 类实例,配置为通过 Ollama 与本地的 Qwen3:1.7B 交互。它使用内置 code_interpreter 工具以便在需要时进行内容推理与 Python 代码执行。自定义的 system_message 引导其生成简洁的网页摘要。
注意:您可以使用更小的模型,如 qwen3:0.6b 或 qwen3:1.7b,大幅降低响应时间与内存占用。这对无需大量推理的快速摘要任务尤其有用。
步骤 1.4.3:摘要路由
最后,一个端点读取传入内容,并在模型生成的同时实时返回摘要文本。它使用流式输出,用户无需等待完整响应生成后再查看。
@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)
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" ) 技术支持
我们的摘要器按如下方式工作:
该函数使用 RequestData 模式接收 POST 数据,期望名为 content 的字段承载网页原始文本。
定义了一个嵌套的 stream() 生成器以逐步输出消息,从而在最终摘要完成前即可向前端发送实时反馈。
消息列表在用户消息中显式加入 </nothink> 标签包裹以抑制内部推理,并由详细的 system 提示强化该行为。
通过 bot.run(messages) 调用 Qwen-Agent,其中 messages 是只包含一条用户输入的列表。
模型响应以流的形式返回并收集到 result_list,代码随后反向遍历以提取 LLM 返回的最新内容字符串。
使用正则移除 Qwen 的内部推理标记如 <think> 与 </think>,以清理最终摘要。
若未找到有效内容,则返回兜底消息;否则将清理后的摘要流式发送至前端。
步骤 2:构建 Chrome 扩展
本节将介绍扩展所需的全部文件,包括前端 UI、页面内容提取逻辑、后台通信脚本与配置元数据。
在查看各脚本前,先简单了解每个文件的作用:
manifest.json:配置文件,定义扩展的元数据、权限与脚本。
popup.html:定义 Chrome 扩展弹窗可见 UI,包含按钮与输出面板。
popup.js:处理前端逻辑,捕获当前标签页文本并将摘要流式展示到 UI。
content.js:作为内容脚本,在需要时提取网页可见内容。
background.js:协调与后端的通信,并将流式摘要转发给弹窗。
icon.png:扩展图标,显示在 Chrome 工具栏与扩展管理页。
这些文件共同让扩展具备交互性、响应性,并可与本地模型服务器通信。
步骤 1:扩展元数据
manifest.json 包含 Chrome 的元数据。它定义扩展的行为、权限、后台 service worker 与弹窗 UI。
{
"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"
}
]
} 技术支持
该文件定义了 Chrome 扩展的配置与能力。它授予必要权限,如 scripting(注入 JavaScript)、activeTab(访问当前标签页)和 storage(保存用户偏好)。
host_permissions 字段中的 "<all_urls>" 允许扩展在任意网页上运行。它通过 popup.html 与 icon.png 配置扩展 UI,并通过 service worker(background.js)注册后台行为。最后,它定义了在所有已加载页面上运行的内容脚本(content.js),以便在需要时与网页内容交互。
步骤 2:搭建可见界面
popup.html 定义扩展的可见界面。它连接到活动标签页,捕获可见文本并将其发送至您的后端服务器。
<! 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> 技术支持
我们逐步理解上述代码:
首先设置基础 HTML 结构,<head> 中包含字符编码、视口设置与 <title> 标签。
随后在 <body> 中,一个容器包含所有 UI 元素,包括:
提示用户总结当前页面的 <h2> 标题。
一个 Summarize 按钮(“#summarizeBtn”),用户点击后触发内容提取与摘要。
一个 <pre> 元素(“#output”),作为可滚动面板用于实时显示流式摘要。
使用内联 CSS 定义样式,使弹窗清晰易读。通过 pre 区块让输出可滚动且视觉上独立。
popup.js 监听按钮点击,提取页面内容,将其发送至 FastAPI 后端,并以流式文本更新输出面板。
步骤 3:构建前端 UI
popup.js 作为前端逻辑控制器,响应按钮点击,从当前标签页提取文本,发送至后端,并将摘要实时流式回显在输出区。
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;
}
}
) ;
} ) ;
} ) ; 技术支持
该 JavaScript 文件为 Chrome 扩展的前端 UI 逻辑提供支持,让用户一键提取并总结网页内容。
其工作原理如下:
当弹窗加载完成(DOMContentLoaded)时,为 “Summarize” 按钮绑定事件监听。
点击按钮后,扩展通过 chrome.tabs.query 查询当前活动标签页。
随后用 chrome.scripting.executeScript 在该标签页中执行脚本,尝试提取页面主体的 innerText。
若脚本失败或内容为空,在 UI 中显示相应的兜底信息。
若成功提取文本,则以 POST 请求的形式发送至本地 FastAPI 后端 http://127.0.0.1:7864/summarize_stream_status。
使用 res.body.getReader() 以流式读取响应,让摘要的部分内容实时显示在输出面板中。
通过 TextDecoder 解码每一段流式文本,并即时追加到显示区域。
任何网络或后端错误都会被捕获并显示在弹窗界面,便于调试或用户反馈。
步骤 4:提取网页内容
在将网页内容发送至后端前,需要先从当前标签页提取内容。content.js 监听来自后台或弹窗脚本的消息,并返回网页的可见文本内容。
chrome. runtime. onMessage. addListener ( ( request, sender, sendResponse ) => {
if ( request. type === "GET_PAGE_TEXT" ) {
const bodyText = document. body. innerText. trim ( ) ;
sendResponse ( { text : bodyText || null } ) ;
}
} ) ; 技术支持
该脚本监听类型为 "GET_PAGE_TEXT" 的消息,并通过提取可见主体文本(document.body.innerText)进行响应。这样,后台 worker 无需每次都注入代码即可便捷地请求网页内容,通信更简洁、异步。
步骤 5:后端协调
后台脚本(background.js)连接弹窗 UI 与内容脚本,确保消息传递与摘要协调在后台正确进行。
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 ;
}
} ) ;
技术支持
上述代码确保内容提取、摘要与实时更新的顺畅流程。工作原理如下:
监听由弹窗按钮点击触发的 "SUMMARIZE_PAGE" 消息。
触发后通过 sender.tab.id 定位活动标签页,并用 chrome.scripting.executeScript 注入脚本以提取网页可见文本。
若无法访问或内容为空,则返回兜底信息。
否则,从 Chrome 本地存储中读取 database_host(通常为 127.0.0.1),并据此向 FastAPI 后端发送包含提取内容的 POST 请求。
随后打开流式连接,使用 ReadableStreamDefaultReader 按块读取响应。
每解码一块数据,便通过 chrome.runtime.sendMessage(类型为 'SUMMARY_PROGRESS')向弹窗发送阶段性更新。
流结束后发送包含完整摘要的最终消息;若过程中出错,则发送兜底消息。
该后台脚本是实现实时流式摘要而不阻塞 UI 或刷新页面的关键。
步骤 6:添加扩展图标
选择或绘制一个扩展图标,并在扩展文件夹中保存为 icon.png。我们将使用如下图标:
图像由 FireFly 生成
整体文件夹结构应如下所示:
QWEN-WEB-SUMMARIZER/
├── backend/
│ ├── app.py
│ ├── Dockerfile
│ └── requirements.txt
├── extension/
│ ├── background.js
│ ├── content.js
│ ├── icon.png
│ ├── manifest.json
│ ├── popup.html
│ └── popup.js技术支持
步骤 3:运行应用
要在本地运行全部组件,在终端执行:
uvicorn backend.app:app --host 0.0 .0.0 --port 7864 技术支持
这非常适合在无 Docker 的本地开发或调试。若要通过 Docker 运行,请依次执行:
docker build -t qwen-agent-backend .
docker run -p 7864 :7864 qwen-agent-backend技术支持
Chrome 扩展与 7864 端口通信。现在按以下步骤在 Chrome 中运行扩展:
打开 Chrome,点击“扩展程序” (位于搜索栏右侧)。
开启“开发者模式”,点击左上角“加载已解压的扩展程序”。
然后加载包含所有文件的扩展文件夹,等待确认。
弹窗确认扩展设置完成后,在任意网页点击扩展图标,即可使用本地 Qwen3 模型获得流式摘要。
来看一下效果:
结语
在本项目中,我们使用 Qwen-Agent 框架与本地通过 Ollama 运行的 Qwen3:1.7B 构建了一个实时网页摘要助手。我们开发了用于处理 LLM 推理的 FastAPI 后端,并将其与 Chrome 扩展集成,以捕获可见页面内容并展示流式摘要。
该演示展示了 Qwen-Agent 如何在浏览器中实现完全离线、工具增强的推理工作流,并可作为构建更高级本地智能体的基础,例如浏览器自动化工具、研究助理或基于文档的聊天机器人。
想进一步了解 AI 智能体,请查看以下博文:
在本文中,我将带您一步步构建一个由Qwen-Agent 驱动的浏览器扩展,可实时总结任意网页的内容。我们将介绍如何:
完成后,您将获得一个由 Qwen3 提供动力、支持离线的摘要代理,只需一键即可在浏览器中读取并总结网页内容。
我们通过每周五免费的新闻简报 The Median 为读者带来最新的 AI 动态,梳理一周要闻。订阅即可用几分钟保持前沿:
什么是 Qwen-Agent?
Qwen-Agent 是阿里巴巴打造的轻量级 Python 框架,面向 Qwen3 系列模型,便于开发强大的大模型应用。其重点支持:
Qwen-Agent 的核心是简化具备稳健推理与代码执行能力的模块化 AI 智能体开发。它既提供 LLM 封装与工具等原子组件,也提供用于编排流程的高层
Agent类。Qwen-Agent 既可对接云端 API(如阿里云 DashScope),也能连接本地 OpenAI 兼容运行时,如 vLLM 和 Ollama。它让您轻松构建强大的 LLM 应用,具备:
项目概览:实时网页摘要扩展
本节我们将构建一个由 Qwen-Agent 驱动的实时摘要扩展,包括:
下面我们依次讲解项目的各个部分。
步骤 1:搭建 Qwen3 后端
本步骤将介绍为后端与扩展同步工作所需设置的全部代码文件。
步骤 1.1:依赖与库
先在系统上安装 ollama。然后在
requirements.txt中列出后端所需的全部 Python 依赖。这有助于在不同机器与容器中保持一致的环境。下面是各库的用途:
qwen-agent[code_interpreter]:核心智能体框架,支持工具使用,便于代码执行与结构化推理。fastapi与uvicorn:共同构建异步 API 后端并承载您的摘要逻辑。python-dotenv与python-dateutil:用于处理环境变量与日期/时间(便于后续扩展)。matplotlib:用于绘图或展示可视化输出(可选)若要在本地运行该项目,执行下列命令安装所有依赖:
步骤 1.2:DockerFile 设置(可选)
现在,我们编写一个 Dockerfile,创建包含应用的最小 Python 环境,并开放 7864 端口,便于 Chrome 扩展向 FastAPI 后端发送请求。
如果您选择使用 Uvicorn 而非 Docker 在本地运行,此设置可省略。
步骤 1.3:用 Ollama 提供 Qwen 服务
接下来,您需要使用 Ollama 在本地拉取并提供 Qwen3 模型服务。该步骤会在您的机器上托管 LLM 服务,从而实现离线推理。
步骤 1.4:构建 FastAPI 摘要服务
app.py定义了一个 FastAPI 服务器,通过 Qwen-Agent 调用本地运行的 Qwen3:1.7B,为网页内容提供实时流式摘要。步骤 1.4.1:导入
首先导入 FastAPI、流式工具,以及 Qwen-Agent 的
Assistant等组件,它封装了模型与工具能力。步骤 1.4.2:FastAPI 应用初始化
接着定义用于接收内容的简单
RequestData模式,并创建一个Assistant的机器人实例。上述代码中,
bot是 Qwen-Agent 的Assistant类实例,配置为通过 Ollama 与本地的 Qwen3:1.7B 交互。它使用内置code_interpreter工具以便在需要时进行内容推理与 Python 代码执行。自定义的system_message引导其生成简洁的网页摘要。注意:您可以使用更小的模型,如
qwen3:0.6b或qwen3:1.7b,大幅降低响应时间与内存占用。这对无需大量推理的快速摘要任务尤其有用。步骤 1.4.3:摘要路由
最后,一个端点读取传入内容,并在模型生成的同时实时返回摘要文本。它使用流式输出,用户无需等待完整响应生成后再查看。
我们的摘要器按如下方式工作:
RequestData模式接收POST数据,期望名为content的字段承载网页原始文本。stream()生成器以逐步输出消息,从而在最终摘要完成前即可向前端发送实时反馈。</nothink>标签包裹以抑制内部推理,并由详细的 system 提示强化该行为。bot.run(messages)调用 Qwen-Agent,其中messages是只包含一条用户输入的列表。result_list,代码随后反向遍历以提取 LLM 返回的最新内容字符串。<think>与</think>,以清理最终摘要。步骤 2:构建 Chrome 扩展
本节将介绍扩展所需的全部文件,包括前端 UI、页面内容提取逻辑、后台通信脚本与配置元数据。
在查看各脚本前,先简单了解每个文件的作用:
manifest.json:配置文件,定义扩展的元数据、权限与脚本。popup.html:定义 Chrome 扩展弹窗可见 UI,包含按钮与输出面板。popup.js:处理前端逻辑,捕获当前标签页文本并将摘要流式展示到 UI。content.js:作为内容脚本,在需要时提取网页可见内容。background.js:协调与后端的通信,并将流式摘要转发给弹窗。icon.png:扩展图标,显示在 Chrome 工具栏与扩展管理页。这些文件共同让扩展具备交互性、响应性,并可与本地模型服务器通信。
步骤 1:扩展元数据
manifest.json包含 Chrome 的元数据。它定义扩展的行为、权限、后台 service worker 与弹窗 UI。该文件定义了 Chrome 扩展的配置与能力。它授予必要权限,如
scripting(注入 JavaScript)、activeTab(访问当前标签页)和storage(保存用户偏好)。host_permissions字段中的 "<all_urls>" 允许扩展在任意网页上运行。它通过popup.html与icon.png配置扩展 UI,并通过 service worker(background.js)注册后台行为。最后,它定义了在所有已加载页面上运行的内容脚本(content.js),以便在需要时与网页内容交互。步骤 2:搭建可见界面
popup.html定义扩展的可见界面。它连接到活动标签页,捕获可见文本并将其发送至您的后端服务器。我们逐步理解上述代码:
<head>中包含字符编码、视口设置与<title>标签。<body>中,一个容器包含所有 UI 元素,包括:<h2>标题。Summarize按钮(“#summarizeBtn”),用户点击后触发内容提取与摘要。<pre>元素(“#output”),作为可滚动面板用于实时显示流式摘要。pre区块让输出可滚动且视觉上独立。popup.js监听按钮点击,提取页面内容,将其发送至 FastAPI 后端,并以流式文本更新输出面板。步骤 3:构建前端 UI
popup.js作为前端逻辑控制器,响应按钮点击,从当前标签页提取文本,发送至后端,并将摘要实时流式回显在输出区。该 JavaScript 文件为 Chrome 扩展的前端 UI 逻辑提供支持,让用户一键提取并总结网页内容。
其工作原理如下:
DOMContentLoaded)时,为“Summarize”按钮绑定事件监听。chrome.tabs.query查询当前活动标签页。chrome.scripting.executeScript在该标签页中执行脚本,尝试提取页面主体的innerText。http://127.0.0.1:7864/summarize_stream_status。res.body.getReader()以流式读取响应,让摘要的部分内容实时显示在输出面板中。TextDecoder解码每一段流式文本,并即时追加到显示区域。步骤 4:提取网页内容
在将网页内容发送至后端前,需要先从当前标签页提取内容。
content.js监听来自后台或弹窗脚本的消息,并返回网页的可见文本内容。该脚本监听类型为 "GET_PAGE_TEXT" 的消息,并通过提取可见主体文本(
document.body.innerText)进行响应。这样,后台 worker 无需每次都注入代码即可便捷地请求网页内容,通信更简洁、异步。步骤 5:后端协调
后台脚本(
background.js)连接弹窗 UI 与内容脚本,确保消息传递与摘要协调在后台正确进行。上述代码确保内容提取、摘要与实时更新的顺畅流程。工作原理如下:
sender.tab.id定位活动标签页,并用chrome.scripting.executeScript注入脚本以提取网页可见文本。database_host(通常为127.0.0.1),并据此向 FastAPI 后端发送包含提取内容的POST请求。ReadableStreamDefaultReader按块读取响应。chrome.runtime.sendMessage(类型为 'SUMMARY_PROGRESS')向弹窗发送阶段性更新。该后台脚本是实现实时流式摘要而不阻塞 UI 或刷新页面的关键。
步骤 6:添加扩展图标
选择或绘制一个扩展图标,并在扩展文件夹中保存为
icon.png。我们将使用如下图标:图像由 FireFly 生成
整体文件夹结构应如下所示:
步骤 3:运行应用
要在本地运行全部组件,在终端执行:
这非常适合在无 Docker 的本地开发或调试。若要通过 Docker 运行,请依次执行:
Chrome 扩展与 7864 端口通信。现在按以下步骤在 Chrome 中运行扩展:
来看一下效果:
结语
在本项目中,我们使用 Qwen-Agent 框架与本地通过 Ollama 运行的 Qwen3:1.7B 构建了一个实时网页摘要助手。我们开发了用于处理 LLM 推理的 FastAPI 后端,并将其与 Chrome 扩展集成,以捕获可见页面内容并展示流式摘要。
该演示展示了 Qwen-Agent 如何在浏览器中实现完全离线、工具增强的推理工作流,并可作为构建更高级本地智能体的基础,例如浏览器自动化工具、研究助理或基于文档的聊天机器人。
想进一步了解 AI 智能体,请查看以下博文: