AI Agent 实战:让大模型自己浏览网页、写代码、发邮件(附完整代码)

3 阅读10分钟

2026年被称为「AI Agent 元年」。从年初 Manus 的大火到开源方案遍地开花,AI 已经不满足于只陪你聊天了——它能帮你干活。今天从零构建一个能浏览网页、写代码、发邮件的 AI Agent。

什么是 AI Agent?

先厘清一个概念:

概念ChatBot(聊天机器人)AI Agent(智能体)
能力边界只能对话能执行任务
交互方式你问它答自主规划和执行
工具使用调用浏览器、API、终端
决策能力多步推理,自主选择
典型代表ChatGPT、文心一言Manus、OpenClaw、AutoGPT

一句话总结:ChatBot 是嘴,Agent 是手和脑。 ChatBot 只能告诉你怎么做,Agent 能直接帮你做完。

AI Agent 的核心技术栈

┌─────────────────────────────────────────┐
│              用户指令                     │
│         「帮我查一下今天的新闻」           │
└─────────────────┬───────────────────────┘
                  ▼
┌─────────────────────────────────────────┐
│            大模型(大脑)                  │
│   DeepSeek V4 / GPT-5.4 / Claude        │
│   理解意图 → 规划步骤 → 调用工具          │
└─────────────────┬───────────────────────┘
                  ▼
┌─────────────────────────────────────────┐
│            工具层(双手)                  │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│  │ 浏览器    │ │ 代码执行  │ │  邮件    │ │
│  │ 网页抓取  │ │ Python   │ │ SMTP    │ │
│  │ 表单填写  │ │ Shell    │ │ API     │ │
│  └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘

核心原理其实不复杂:Function Calling + 循环执行。大模型决定调用哪个工具,执行后把结果返回给大模型,大模型再决定下一步做什么——如此循环直到任务完成。

方案一:从零用 Python 构建 Agent(最灵活)

先实现一个最小可用的 Agent 框架,理解核心原理后再上复杂方案。

1. 定义工具

# agent/tools.py
import requests
from bs4 import BeautifulSoup
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import subprocess
import json

class ToolRegistry:
    """工具注册器:管理所有 Agent 可用的工具"""
    def __init__(self):
        self.tools = {}
    
    def register(self, name, description, parameters, handler):
        """注册一个工具"""
        self.tools[name] = {
            "description": description,
            "parameters": parameters,
            "handler": handler
        }
    
    def get_openai_tools(self):
        """转换为 OpenAI Function Calling 格式"""
        return [
            {
                "type": "function",
                "function": {
                    "name": name,
                    "description": tool["description"],
                    "parameters": tool["parameters"]
                }
            }
            for name, tool in self.tools.items()
        ]
    
    def call(self, name, arguments):
        """调用指定工具"""
        if name not in self.tools:
            return f"错误:未知工具 {name}"
        return self.tools[name]["handler"](**arguments)

# ========== 创建工具实例 ==========
registry = ToolRegistry()

# 工具1:浏览网页
def browse_web(url: str, extract_type: str = "text") -> str:
    """抓取网页内容"""
    try:
        headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
        resp = requests.get(url, headers=headers, timeout=10)
        resp.raise_for_status()
        resp.encoding = resp.apparent_encoding
        
        soup = BeautifulSoup(resp.text, "html.parser")
        
        if extract_type == "text":
            # 去除脚本和样式,提取纯文本
            for tag in soup(["script", "style", "nav", "footer"]):
                tag.decompose()
            text = soup.get_text(separator="\n", strip=True)
            return text[:3000]  # 限制长度,避免超过上下文窗口
        elif extract_type == "links":
            links = [f"{a.get_text(strip=True)}: {a['href']}" 
                     for a in soup.find_all("a", href=True)[:20]]
            return "\n".join(links)
        elif extract_type == "title":
            return soup.title.string if soup.title else "无标题"
    except Exception as e:
        return f"抓取失败:{str(e)}"

registry.register(
    name="browse_web",
    description="浏览指定URL的网页,提取文本内容、链接或标题",
    parameters={
        "type": "object",
        "properties": {
            "url": {"type": "string", "description": "要访问的网址"},
            "extract_type": {
                "type": "string", 
                "enum": ["text", "links", "title"],
                "description": "提取类型:text=正文, links=链接列表, title=标题"
            }
        },
        "required": ["url"]
    },
    handler=browse_web
)

# 工具2:搜索网页
def search_web(query: str) -> str:
    """通过DuckDuckGo搜索"""
    try:
        resp = requests.get(
            "https://html.duckduckgo.com/html/",
            params={"q": query},
            headers={"User-Agent": "Mozilla/5.0"},
            timeout=10
        )
        soup = BeautifulSoup(resp.text, "html.parser")
        results = []
        for item in soup.select(".result")[:5]:
            title = item.select_one(".result__title")
            snippet = item.select_one(".result__snippet")
            if title and snippet:
                results.append(f"标题:{title.get_text(strip=True)}\n摘要:{snippet.get_text(strip=True)}")
        return "\n\n".join(results) if results else "未找到相关结果"
    except Exception as e:
        return f"搜索失败:{str(e)}"

registry.register(
    name="search_web",
    description="使用搜索引擎搜索信息",
    parameters={
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "搜索关键词"}
        },
        "required": ["query"]
    },
    handler=search_web
)

# 工具3:发送邮件
def send_email(
    to: str, 
    subject: str, 
    body: str,
    smtp_host: str = "smtp.qq.com",
    smtp_port: int = 465,
    smtp_user: str = None,
    smtp_pass: str = None
) -> str:
    """发送邮件(需要配置SMTP信息)"""
    try:
        msg = MIMEMultipart()
        msg["From"] = smtp_user
        msg["To"] = to
        msg["Subject"] = subject
        msg.attach(MIMEText(body, "plain", "utf-8"))
        
        with smtplib.SMTP_SSL(smtp_host, smtp_port) as server:
            server.login(smtp_user, smtp_pass)
            server.send_message(msg)
        return f"邮件已成功发送至 {to}"
    except Exception as e:
        return f"发送失败:{str(e)}"

registry.register(
    name="send_email",
    description="发送电子邮件",
    parameters={
        "type": "object",
        "properties": {
            "to": {"type": "string", "description": "收件人邮箱"},
            "subject": {"type": "string", "description": "邮件主题"},
            "body": {"type": "string", "description": "邮件正文"}
        },
        "required": ["to", "subject", "body"]
    },
    handler=send_email
)

# 工具4:执行代码
def run_python_code(code: str) -> str:
    """在安全沙箱中执行Python代码"""
    try:
        # 限制可用的内置函数
        safe_globals = {"__builtins__": {
            "print": print, "len": len, "range": range,
            "int": int, "float": float, "str": str, "list": list,
            "dict": dict, "sum": sum, "max": max, "min": min,
            "sorted": sorted, "enumerate": enumerate, "zip": zip
        }}
        local_vars = {}
        exec(code, safe_globals, local_vars)
        return "执行成功"
    except Exception as e:
        return f"代码执行错误:{str(e)}"

registry.register(
    name="run_python_code",
    description="执行Python代码并返回结果,可用于数据分析、计算等任务",
    parameters={
        "type": "object",
        "properties": {
            "code": {"type": "string", "description": "要执行的Python代码"}
        },
        "required": ["code"]
    },
    handler=run_python_code
)

2. 实现 Agent 核心循环

# agent/core.py
from openai import OpenAI
from agent.tools import registry
import json

class Agent:
    """AI Agent 核心类"""
    
    def __init__(self, model="deepseek-v4", base_url=None, api_key=None):
        self.client = OpenAI(
            base_url=base_url or "https://api.deepseek.com/v1",
            api_key=api_key or "your-api-key"
        )
        self.model = model
        self.messages = [
            {
                "role": "system",
                "content": """你是一个AI Agent,可以自主使用工具完成任务。
                你需要:
                1. 分析用户需求
                2. 规划执行步骤
                3. 调用合适的工具
                4. 根据工具返回结果决定下一步
                5. 最终给出完整答案
                
                记住:一次只调用一个工具,不要同时调用多个。"""
            }
        ]
        self.max_steps = 10  # 防止无限循环
    
    def run(self, user_input: str) -> str:
        """执行用户指令"""
        self.messages.append({"role": "user", "content": user_input})
        
        for step in range(self.max_steps):
            print(f"\n--- 步骤 {step + 1} ---")
            
            # 调用大模型
            response = self.client.chat.completions.create(
                model=self.model,
                messages=self.messages,
                tools=registry.get_openai_tools(),
                tool_choice="auto"
            )
            
            msg = response.choices[0].message
            
            # 如果没有调用工具,说明任务完成
            if not msg.tool_calls:
                print("任务完成!")
                self.messages.append(msg)
                return msg.content
            
            # 处理工具调用
            self.messages.append(msg)
            
            for tool_call in msg.tool_calls:
                func_name = tool_call.function.name
                func_args = json.loads(tool_call.function.arguments)
                
                print(f"调用工具:{func_name}")
                print(f"参数:{json.dumps(func_args, ensure_ascii=False, indent=2)}")
                
                # 执行工具
                result = registry.call(func_name, func_args)
                print(f"结果:{result[:200]}...")
                
                # 将工具结果返回给大模型
                self.messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": str(result)
                })
        
        return "达到最大步骤限制,任务未完成。"

# ========== 使用示例 ==========
if __name__ == "__main__":
    agent = Agent(model="deepseek-v4")
    
    # 示例1:自动浏览网页并总结
    result = agent.run("帮我打开 https://news.ycombinator.com,总结今天排名前5的热门话题")
    print(result)
    
    # 示例2:搜索+分析+发邮件
    result = agent.run("""
        1. 搜索「2026年 Python 最新版本」
        2. 总结主要更新内容
        3. 写一封邮件把总结发给 team@example.com,主题是「Python最新版本更新摘要」
    """)
    print(result)

这个最小 Agent 框架的核心就是 大模型 + 工具 + 循环,总共不到 100 行核心代码。但它已经能完成相当复杂的任务。

方案二:OpenClaw —— 开箱即用的全能 Agent

如果你不想自己写代码,OpenClaw 是目前最火的开源 AI Agent 框架,GitHub 27 天获得 43,000 Star。

安装(3分钟)

# 方式一:一键安装(推荐)
curl -fsSL https://openclaw.ai/install.sh | sh

# 方式二:pip 安装
pip install openclaw

# 方式三:Docker 部署
docker run -d -v ~/.openclaw:/root/.openclaw -p 3000:3000 openclaw/openclaw

配置大模型

# ~/.openclaw/config.yaml
model:
  provider: deepseek    # 支持 openai / claude / deepseek / qwen
  api_key: sk-xxx       # 你的 API Key
  model: deepseek-v4    # 模型名称

# 或者用本地 Ollama
model:
  provider: ollama
  base_url: http://localhost:11434
  model: deepseek-r1:14b

内置能力

OpenClaw 开箱即用的能力包括:

能力说明典型场景
🔍 网页浏览自动打开浏览器、抓取内容信息收集、竞品调研
📧 邮件管理读写、发送邮件自动回复、日报生成
📁 文件管理读写本地文件整理文档、批量处理
💻 代码执行运行 Python/Shell数据分析、自动化脚本
🗓️ 日历管理创建/查询日程会议安排、提醒
🔗 API调用调用任意HTTP API集成第三方服务

实战:自动收集行业资讯并发送日报

# ~/.openclaw/workflows/daily-news.yaml
name: "每日行业资讯汇总"
description: "自动抓取AI行业新闻,生成摘要,发送邮件"

triggers:
  - schedule: "0 9 * * 1-5"  # 工作日早9点

steps:
  - name: "搜索AI行业新闻"
    tool: browse_web
    params:
      url: "https://news.ycombinator.com"
      extract_type: "text"
  
  - name: "搜索36氪AI频道"
    tool: browse_web
    params:
      url: "https://36kr.com/information/AI/"
      extract_type: "text"
  
  - name: "生成资讯摘要"
    tool: llm
    params:
      prompt: |
        根据以上抓取的内容,整理出今日5条最重要的AI行业资讯。
        格式:序号. 标题 —— 一句话摘要(来源)
    
  - name: "发送邮件"
    tool: send_email
    params:
      to: "team@company.com"
      subject: "📰 AI行业日报 - {{date}}"
      body: "{{steps.生成资讯摘要.result}}"
# 手动执行一次
openclaw run daily-news

# 查看执行日志
openclaw logs daily-news

方案三:Page Agent —— 给任何网页加 AI 能力

阿里开源的 Page Agent 项目,只需要一行 JS 代码,就能把任何网页变成 AI Agent:

<!-- 在任意网页的控制台执行,或作为Chrome扩展注入 -->
<script src="https://cdn.jsdelivr.net/npm/page-agent/dist/page-agent.min.js"></script>
<script>
  PageAgent.init({
    model: "deepseek-v4",
    apiKey: "sk-xxx",
    // 可以让AI操作当前网页
    capabilities: ["click", "input", "scroll", "extract", "navigate"]
  });
</script>

执行后,页面右下角会出现一个 AI 助手浮窗,你可以用自然语言让 AI 操作当前网页:

你:「帮我把这个表单填写一下,姓名张三,邮箱 zhangsan@example.com」
AI:[自动找到表单字段并填入]

你:「把这个页面上所有价格提取出来,做个对比表格」
AI:[自动抓取页面数据并生成表格]

适用场景

  • 🛒 电商比价:自动抓取多个平台的价格
  • 📊 数据录入:自动填写重复性表单
  • 🔍 信息提取:从网页中提取结构化数据

Agent 开发的 5 个踩坑经验

经过实际项目踩坑,总结出以下关键经验:

1. 一定要设最大步骤限制

# ✅ 正确:限制最大循环次数
self.max_steps = 10

# ❌ 错误:不加限制,Agent 可能无限循环
while True:
    response = model.generate(...)

真实案例:我第一次跑 Agent 时,让它「搜索某个话题」,结果它一直在搜索-总结-再搜索之间循环了 47 次,烧了 2 万 token。

2. 工具描述要清晰具体

# ❌ 模糊描述:大模型可能误用
"description": "获取网页信息"

# ✅ 清晰描述:明确工具的能力边界
"description": "抓取指定URL的网页正文内容,返回纯文本格式。不支持JavaScript渲染的页面。返回内容限制在3000字以内。"

3. 错误处理要到位

# ✅ 每个工具都要有 try-except
def browse_web(url: str):
    try:
        resp = requests.get(url, timeout=10)
        resp.raise_for_status()
        # ...处理逻辑
    except requests.Timeout:
        return "错误:网页访问超时,请检查URL是否正确"
    except requests.ConnectionError:
        return "错误:无法连接到该网站"
    except Exception as e:
        return f"未知错误:{str(e)}"

工具出错后,错误信息会返回给大模型,让它决定是重试还是换策略。如果错误信息不够清晰,Agent 会陷入混乱。

4. 结果要做截断

大模型的上下文窗口是有限的。工具返回的内容如果太长(比如一整个网页的全文),会快速耗尽上下文。

# ✅ 截断过长的结果
result = scraper.get_content(url)
if len(result) > 3000:
    result = result[:3000] + "\n...(内容已截断)"

5. 敏感操作需要人类确认

# 发邮件、删除文件等危险操作,加确认步骤
DANGEROUS_TOOLS = {"send_email", "delete_file", "execute_shell"}

if func_name in DANGEROUS_TOOLS:
    confirm = input(f"Agent 想要执行 {func_name},参数:{func_args}。确认执行?(y/n): ")
    if confirm.lower() != "y":
        result = "用户取消了此操作"

2026 AI Agent 方案对比

方案上手难度灵活性适用场景成本
自研 Python Agent⭐⭐⭐⭐⭐⭐⭐⭐需要高度定制API 费用
OpenClaw⭐⭐⭐个人助手、自动化API 费用
Dify + Agent⭐⭐⭐⭐⭐⭐企业级应用开源免费/云版
Page Agent⭐⭐网页自动化API 费用
Hermes Agent⭐⭐⭐⭐⭐多模型编排开源免费

总结

AI Agent 不是什么玄学,本质上就是 大模型 + Function Calling + 循环执行。理解了这个核心模式,你可以用任何编程语言、任何大模型构建自己的 Agent。

2026 年的 AI Agent 生态已经非常成熟:

  • 想省事:用 OpenClaw,3 分钟部署,开箱即用
  • 想定制:自研 Python Agent,100 行核心代码搞定
  • 想企业级:Dify + Agent,拖拽式编排,支持团队协作

重要的是动手开始。从一个最简单的「搜索+总结」Agent 开始,逐步添加工具,逐步提升复杂度。你会发现,AI Agent 给生产力带来的提升,远比你想象的要大。


关于作者

长期关注大模型应用落地与云服务器实战,专注技术在企业场景中的落地实践。

个人博客:yunduancloud.icu —— 持续更新云计算、AI大模型实战教程,欢迎访问交流。