LangChain浏览器Agent开发全攻略

基于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. 交互流程

  1. 用户唤起插件对话窗口,输入自然语言指令;
  2. 插件将指令+上下文发送给LLM(通过LangChain封装),解析为标准化操作指令;
  3. 操作执行引擎调用浏览器API执行指令;
  4. 执行结果反馈给LLM,生成自然语言总结并展示给用户;
  5. 会话状态存入记忆模块,供后续对话使用。

三、技术架构与核心实现

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. 环境准备

  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
  1. 配置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. 开发核心功能

  1. 实现LLM服务封装(llmService.js):配置API Key、Prompt模板、对话链;
  2. 实现浏览器操作(browserService.js):封装导航、点击、输入、提取等操作;
  3. 开发弹窗界面(popup/index.html + popup.js):实现输入、历史记录、结果展示;
  4. 配置后台服务(background.js):监听消息、处理异步操作。

3. 测试与调试

  1. 打包项目:npx webpack
  2. 浏览器加载插件:Chrome → 扩展程序 → 开发者模式 → 加载已解压的扩展程序 → 选择dist目录;
  3. 配置OpenAI API Key:在插件设置页输入API Key(需提前在localStorage存储);
  4. 测试指令:
    • 基础导航:"打开https://www.zhihu.com"
    • 页面操作:"点击搜索框输入LangChain"
    • 数据提取:"提取当前页面的表格数据"

4. 优化与扩展

  1. 记忆优化:使用VectorStore(如Pinecone)替代本地BufferMemory,支持长期记忆;
  2. 多LLM支持:扩展llmService.js,添加通义千问、文心一言等LLM适配;
  3. 安全加固:添加操作白名单、敏感操作确认弹窗;
  4. 性能优化:缓存DOM解析结果、批量处理操作指令;
  5. 扩展功能:添加语音输入、定时任务、自定义Tool市场。

五、注意事项

  1. API Key安全:不要硬编码API Key,建议通过弹窗让用户输入并存储在localStorage,或使用后台服务代理LLM请求;
  2. 浏览器权限:Manifest V3对权限管控更严格,需合理申请permissions/host_permissions;
  3. LLM调用成本:添加指令缓存,避免重复调用LLM;
  4. 兼容性:针对Firefox适配不同的扩展API(如browser.tabs替代chrome.tabs);
  5. 隐私保护:禁止抓取敏感页面数据,不存储用户的隐私信息。

六、交付物

  1. 插件源码(dist目录,可直接加载到浏览器);
  2. 需求文档(PRD);
  3. 开发文档(API说明、扩展指南);
  4. 测试用例(核心指令测试清单)。

通过以上流程,可实现一个基于LangChain的智能浏览器Agent插件,核心解决"自然语言解析→浏览器操作→结果反馈"的全流程自动化,同时兼顾安全性、可扩展性和用户体验。

相关推荐
叫我黎大侠1 小时前
.NET 实战:调用千问视觉模型实现 OCR(车票识别完整教程)
阿里云·ai·c#·ocr·asp.net·.net·.netcore
笨蛋©2 小时前
工程图纸数字化用什么工具?详解图片格式图纸识别与FAI自动化实战
ai·cad·质量管理·制造业·图纸识别
小李子呢02112 小时前
前端八股---脚手架工具Vue CLI(Webpack) vs Vite
前端·vue.js·webpack
2401_885885042 小时前
群发彩信接口怎么开发?企业级彩信发送说明
前端·python
PILIPALAPENG2 小时前
第2周 Day 5:前端转型AI开发,朋友问我,你到底在折腾啥?
前端·人工智能·python
Mintopia2 小时前
前端卡顿的真相:不是你代码慢,是你阻塞了
前端
kyriewen2 小时前
可选链 `?.`——再也不用写一长串 `&&` 了!
前端·javascript·ecmascript 6
Mintopia2 小时前
别再乱加缓存:一套判断"该不该缓存"的方法
前端
Leisureconfused2 小时前
【记录】Node版本兼容性问题及解决
前端·vue.js·npm·node.js