基于LangChain开发浏览器Agent插件:全流程指南





一、需求分析与核心重难点
1. 核心需求定义
| 需求类型 | 具体描述 |
|---|---|
| 核心功能 | 1. 浏览器内自然语言交互,解析用户指令(如"打开知乎并搜索LangChain实战""提取当前页面的表格数据并导出为Excel") 2. 自主完成浏览器操作(页面导航、点击、输入、数据提取) 3. 记忆上下文(多轮对话中理解用户历史指令) 4. 结果反馈(操作结果总结、数据导出、可视化) |
| 非功能需求 | 1. 兼容性:支持Chrome/Firefox主流浏览器 2. 性能:操作响应时间<3s,页面解析无明显卡顿 3. 安全性:禁止访问敏感页面(如银行、支付),操作需用户确认 4. 可扩展性:支持自定义工具(Tool)扩展功能 |
2. 核心重难点
| 重难点 | 技术挑战 | 解决方案思路 |
|---|---|---|
| 指令解析与意图识别 | 自然语言转浏览器操作指令,处理模糊指令 | 基于LangChain的PromptTemplate+LLM(如GPT-3.5/4、通义千问)做意图解析,定义操作指令Schema |
| 浏览器操作控制 | 插件与浏览器内核交互,模拟用户操作 | 使用Chrome Extension API(chrome.tabs/chrome.runtime)+ Puppeteer(可选,后台操作) |
| 上下文记忆 | 多轮对话中关联历史指令和操作状态 | LangChain的ConversationBufferMemory/VectorStore记忆模块,持久化存储会话状态 |
| 页面数据提取 | 结构化/非结构化数据解析(文本、表格、图片) | 基于Cheerio/DOMParser解析DOM,LangChain的DocumentLoaders处理页面内容 |
| 安全性与权限控制 | 防止恶意操作、越权访问 | 操作白名单、用户确认机制、权限沙箱隔离 |
二、需求文档(PRD)
1. 产品概述
本插件是基于LangChain构建的智能浏览器Agent,通过自然语言交互实现浏览器操作自动化,降低用户操作成本,支持数据提取、页面导航、任务自动化等核心能力。
2. 功能模块
2.1 交互模块
- 触发方式:浏览器侧边栏弹窗/快捷键(Ctrl+Shift+L)唤起对话窗口
- 输入:支持文本输入、语音输入(可选)
- 输出:自然语言结果、操作步骤反馈、数据导出链接
2.2 指令解析模块
- 支持指令类型:
- 导航类:"打开XX网站""跳转到XX页面"
- 操作类:"点击搜索框输入XX""点击登录按钮"
- 提取类:"提取当前页面所有链接""导出表格数据"
- 分析类:"总结当前页面内容""对比两个页面的产品价格"
- 容错处理:指令模糊时主动追问(如"你想打开哪个平台的LangChain教程?")
2.3 浏览器操作模块
- 核心能力:标签页管理(新建/关闭/切换)、DOM操作(点击/输入/选择)、页面数据抓取
- 约束:仅允许操作用户当前激活的标签页(除非用户明确指定),敏感操作(如表单提交)需二次确认
2.4 记忆模块
- 会话记忆:保留当前会话的所有指令和操作记录
- 长期记忆:支持保存常用指令(如"每周一打开知乎查看LangChain话题")
2.5 扩展模块
- 自定义工具:允许用户添加自定义Tool(如"调用API分析页面数据")
- 插件配置:设置默认LLM、操作超时时间、安全白名单
3. 交互流程
- 用户唤起插件对话窗口,输入自然语言指令;
- 插件将指令+上下文发送给LLM(通过LangChain封装),解析为标准化操作指令;
- 操作执行引擎调用浏览器API执行指令;
- 执行结果反馈给LLM,生成自然语言总结并展示给用户;
- 会话状态存入记忆模块,供后续对话使用。
三、技术架构与核心实现
1. 技术栈
| 层级 | 技术选型 |
|---|---|
| 前端界面 | HTML+CSS+JavaScript(Vanilla)/React(侧边栏弹窗) |
| 浏览器交互 | Chrome Extension Manifest V3、chrome.tabs/chrome.runtime API |
| LLM交互 | LangChain.js、OpenAI API/通义千问API |
| 数据处理 | Cheerio(DOM解析)、js-xlsx(数据导出)、localStorage(本地记忆) |
| 构建工具 | Webpack(插件打包)、npm(依赖管理) |
2. 核心代码实现
2.1 项目结构
langchain-browser-agent/
├── src/
│ ├── background/ # 后台服务(处理LLM调用、浏览器操作)
│ │ ├── background.js # 后台入口
│ │ ├── llmService.js # LangChain封装
│ │ └── browserService.js # 浏览器操作封装
│ ├── popup/ # 侧边栏弹窗
│ │ ├── index.html
│ │ ├── popup.js
│ │ └── style.css
│ ├── content/ # 页面注入脚本(DOM操作)
│ │ └── contentScript.js
│ └── utils/ # 工具函数
│ ├── memory.js # 记忆模块
│ └── parser.js # 指令解析
├── manifest.json # 插件配置
├── package.json
└── webpack.config.js
2.2 插件配置(manifest.json)
json
{
"manifest_version": 3,
"name": "LangChain Browser Agent",
"version": "1.0.0",
"description": "智能浏览器Agent,基于LangChain实现自然语言操作浏览器",
"permissions": ["tabs", "activeTab", "storage", "scripting"],
"host_permissions": ["<all_urls>"],
"background": {
"service_worker": "dist/background/background.js"
},
"action": {
"default_popup": "dist/popup/index.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
2.3 LangChain LLM服务封装(llmService.js)
javascript
import { OpenAI } from "langchain/llms/openai";
import { ConversationChain } from "langchain/chains";
import { BufferMemory } from "langchain/memory";
import { PromptTemplate } from "langchain/prompts";
// 初始化LLM
const initLLM = () => {
return new OpenAI({
openAIApiKey: localStorage.getItem("openai_api_key"), // 从本地存储获取API Key
temperature: 0.1, // 低随机性,保证指令解析准确
modelName: "gpt-3.5-turbo"
});
};
// 定义操作指令解析Prompt
const OPERATION_PROMPT = PromptTemplate.fromTemplate(`
你是一个浏览器操作Agent,需要将用户的自然语言指令解析为标准化的浏览器操作指令。
操作指令格式为JSON:
{
"action": "navigate|click|input|extract|summary", // 操作类型
"params": {
"url": "xxx", // 仅navigate需要
"selector": "xxx", // 仅click/input/extract需要
"value": "xxx", // 仅input需要
"type": "text|table|link" // 仅extract需要
},
"description": "操作描述(自然语言)"
}
上下文历史:{history}
用户指令:{input}
注意:
1. 若指令模糊,返回{"action": "ask", "params": {"question": "追问的问题"}}
2. 仅返回JSON,不要添加其他内容
`);
// 初始化对话链(带记忆)
export const initConversationChain = () => {
const llm = initLLM();
const memory = new BufferMemory({ memoryKey: "history" });
return new ConversationChain({
llm,
memory,
prompt: OPERATION_PROMPT
});
};
// 解析用户指令
export const parseUserCommand = async (input, chain) => {
const response = await chain.call({ input });
try {
return JSON.parse(response.response);
} catch (e) {
return {
action: "error",
params: { message: "指令解析失败,请重新输入" }
};
}
};
2.4 浏览器操作封装(browserService.js)
javascript
// 导航操作
export const navigate = async (url) => {
return new Promise((resolve) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.update(tabs[0].id, { url }, () => {
resolve({ success: true, message: `已导航到${url}` });
});
});
});
};
// 点击操作
export const clickElement = async (selector) => {
return new Promise((resolve) => {
chrome.scripting.executeScript({
target: { tabId: (await chrome.tabs.query({ active: true }))[0].id },
func: (selector) => {
const element = document.querySelector(selector);
if (element) {
element.click();
return { success: true, message: `已点击${selector}` };
} else {
return { success: false, message: `未找到元素${selector}` };
}
},
args: [selector]
}, (results) => {
resolve(results[0].result);
});
});
};
// 输入操作
export const inputText = async (selector, value) => {
return new Promise((resolve) => {
chrome.scripting.executeScript({
target: { tabId: (await chrome.tabs.query({ active: true }))[0].id },
func: (selector, value) => {
const element = document.querySelector(selector);
if (element) {
element.value = value;
return { success: true, message: `已在${selector}输入${value}` };
} else {
return { success: false, message: `未找到元素${selector}` };
}
},
args: [selector, value]
}, (results) => {
resolve(results[0].result);
});
});
};
// 数据提取操作
export const extractData = async (selector, type) => {
return new Promise((resolve) => {
chrome.scripting.executeScript({
target: { tabId: (await chrome.tabs.query({ active: true }))[0].id },
func: (selector, type) => {
const element = document.querySelector(selector) || document;
let data = "";
if (type === "text") {
data = element.textContent.trim();
} else if (type === "table") {
const rows = Array.from(element.querySelectorAll("tr"));
data = rows.map(row =>
Array.from(row.querySelectorAll("td, th")).map(cell => cell.textContent.trim())
);
} else if (type === "link") {
data = Array.from(element.querySelectorAll("a")).map(a => ({
text: a.textContent.trim(),
href: a.href
}));
}
return { success: true, data, message: `已提取${type}类型数据` };
},
args: [selector, type]
}, (results) => {
resolve(results[0].result);
});
});
};
// 执行操作入口
export const executeOperation = async (operation) => {
const { action, params } = operation;
switch (action) {
case "navigate":
return await navigate(params.url);
case "click":
return await clickElement(params.selector);
case "input":
return await inputText(params.selector, params.value);
case "extract":
return await extractData(params.selector, params.type);
case "ask":
return { success: true, needAsk: true, question: params.question };
default:
return { success: false, message: `不支持的操作类型:${action}` };
}
};
2.5 弹窗交互逻辑(popup.js)
javascript
import { initConversationChain, parseUserCommand } from "../background/llmService";
import { executeOperation } from "../background/browserService";
// 初始化对话链
let conversationChain = initConversationChain();
// DOM元素
const inputEl = document.getElementById("command-input");
const sendBtn = document.getElementById("send-btn");
const resultEl = document.getElementById("result");
const historyEl = document.getElementById("history");
// 发送指令
sendBtn.addEventListener("click", async () => {
const input = inputEl.value.trim();
if (!input) return;
// 添加到历史记录
addToHistory("user", input);
inputEl.value = "";
// 解析指令
resultEl.textContent = "正在解析指令...";
const operation = await parseUserCommand(input, conversationChain);
// 执行操作
if (operation.action === "error") {
resultEl.textContent = operation.params.message;
addToHistory("agent", operation.params.message);
return;
}
if (operation.action === "ask") {
resultEl.textContent = operation.params.question;
addToHistory("agent", operation.params.question);
return;
}
resultEl.textContent = "正在执行操作...";
const result = await executeOperation(operation);
// 反馈结果
resultEl.textContent = result.message;
addToHistory("agent", `${operation.description}:${result.message}`);
// 提取数据时导出
if (action === "extract" && result.data) {
if (params.type === "table") {
exportExcel(result.data, "extracted-data.xlsx");
}
}
});
// 添加历史记录
const addToHistory = (role, content) => {
const div = document.createElement("div");
div.className = `history-item ${role}`;
div.textContent = `${role === "user" ? "你" : "Agent"}:${content}`;
historyEl.appendChild(div);
historyEl.scrollTop = historyEl.scrollHeight;
};
// Excel导出
const exportExcel = (data, filename) => {
import("xlsx").then(XLSX => {
const ws = XLSX.utils.aoa_to_sheet(data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
XLSX.writeFile(wb, filename);
});
};
2.6 后台入口(background.js)
javascript
import { initConversationChain } from "./llmService";
import { executeOperation } from "./browserService";
// 初始化全局对话链
let globalChain = initConversationChain();
// 监听弹窗消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "parseCommand") {
parseUserCommand(message.input, globalChain)
.then(operation => executeOperation(operation))
.then(result => sendResponse(result))
.catch(err => sendResponse({ success: false, message: err.message }));
return true; // 保持消息通道开放
}
if (message.type === "resetMemory") {
globalChain = initConversationChain();
sendResponse({ success: true });
return true;
}
});
四、项目实现步骤
1. 环境准备
- 初始化项目:
bash
mkdir langchain-browser-agent && cd langchain-browser-agent
npm init -y
npm install langchain openai cheerio js-xlsx webpack webpack-cli copy-webpack-plugin --save
npm install webpack-dev-server --save-dev
- 配置Webpack(webpack.config.js):
javascript
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
entry: {
"background/background": "./src/background/background.js",
"popup/popup": "./src/popup/popup.js",
"content/contentScript": "./src/content/contentScript.js"
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js"
},
plugins: [
new CopyPlugin({
patterns: [
{ from: "./src/popup/index.html", to: "popup/index.html" },
{ from: "./src/popup/style.css", to: "popup/style.css" },
{ from: "./manifest.json", to: "manifest.json" },
{ from: "./icons", to: "icons" }
]
})
],
mode: "production"
};
2. 开发核心功能
- 实现LLM服务封装(llmService.js):配置API Key、Prompt模板、对话链;
- 实现浏览器操作(browserService.js):封装导航、点击、输入、提取等操作;
- 开发弹窗界面(popup/index.html + popup.js):实现输入、历史记录、结果展示;
- 配置后台服务(background.js):监听消息、处理异步操作。
3. 测试与调试
- 打包项目:
npx webpack; - 浏览器加载插件:Chrome → 扩展程序 → 开发者模式 → 加载已解压的扩展程序 → 选择dist目录;
- 配置OpenAI API Key:在插件设置页输入API Key(需提前在localStorage存储);
- 测试指令:
- 基础导航:"打开https://www.zhihu.com"
- 页面操作:"点击搜索框输入LangChain"
- 数据提取:"提取当前页面的表格数据"
4. 优化与扩展
- 记忆优化:使用VectorStore(如Pinecone)替代本地BufferMemory,支持长期记忆;
- 多LLM支持:扩展llmService.js,添加通义千问、文心一言等LLM适配;
- 安全加固:添加操作白名单、敏感操作确认弹窗;
- 性能优化:缓存DOM解析结果、批量处理操作指令;
- 扩展功能:添加语音输入、定时任务、自定义Tool市场。
五、注意事项
- API Key安全:不要硬编码API Key,建议通过弹窗让用户输入并存储在localStorage,或使用后台服务代理LLM请求;
- 浏览器权限:Manifest V3对权限管控更严格,需合理申请permissions/host_permissions;
- LLM调用成本:添加指令缓存,避免重复调用LLM;
- 兼容性:针对Firefox适配不同的扩展API(如browser.tabs替代chrome.tabs);
- 隐私保护:禁止抓取敏感页面数据,不存储用户的隐私信息。
六、交付物
- 插件源码(dist目录,可直接加载到浏览器);
- 需求文档(PRD);
- 开发文档(API说明、扩展指南);
- 测试用例(核心指令测试清单)。
通过以上流程,可实现一个基于LangChain的智能浏览器Agent插件,核心解决"自然语言解析→浏览器操作→结果反馈"的全流程自动化,同时兼顾安全性、可扩展性和用户体验。