Build Your Own AI Butler - A Scheduled Agent That Runs Itself!

三周前,我给自己做了一个 AI Butler。

它每天早上 8 点自动运行,帮我:
1. 搜索行业最新动态
2. 整理昨天的工作日志
3. 生成今天的任务清单
4. 把摘要发到我的飞书

整个过程不需要我做任何事。它自己运行,自己完成任务,自己汇报结果。

这个 AI Butler 不是大厂的产品,不是要花几千块的 SaaS,是我花了两天在树莓派上搭的一个小工具。

这篇文章教你怎么做一个属于自己的 AI Butler。

什么是 Scheduled Agent

在说怎么实现之前,先解释一下什么是 Scheduled Agent。

普通的 AI(比如 ChatGPT)是被动响应的:你问它答,你不说它不动。

Scheduled Agent 是主动运行的:你可以设定一个时间表(比如每天早上 8 点),到了时间,它自动启动,帮你完成任务。

这两个的区别就像是:
- 普通的 AI = 出租车:你叫车它才来
- Scheduled Agent = 公交:你买月票,它每天按点来接你

如果你每天都有固定的任务要处理(比如看行业新闻、整理工作日志、生成任务清单),用 Scheduled Agent 比每次手动问 AI 方便得多。

实现方案:整体架构

我做的 AI Butler 架构是这样的:

┌─────────────────────────────────────────────────────┐
│                   AI Butler                          │
├─────────────────────────────────────────────────────┤
│  Scheduler Layer(调度层)                           │
│  - cron 定时触发                                     │
│  - 检查是否该运行                                    │
│  - 管理运行状态                                      │
├─────────────────────────────────────────────────────┤
│  Memory Layer(记忆层)                              │
│  - 保存历史任务记录                                  │
│  - 保存上下文信息                                    │
│  - 跨任务保持连贯性                                  │
├─────────────────────────────────────────────────────┤
│  Agent Layer(智能体层)                              │
│  - 理解任务目标                                      │
│  - 制定执行计划                                      │
│  - 调用工具完成任务                                  │
├─────────────────────────────────────────────────────┤
│  Tools Layer(工具层)                               │
│  - 搜索网页                                          │
│  - 发送邮件/消息                                    │
│  - 读写文件                                          │
│  - 执行代码                                          │
└─────────────────────────────────────────────────────┘

分层的好处是:每层只做一件事,出了问题容易排查,扩展也方便。

核心代码实现

1. 调度层:定时触发

调度层我用的是 Python 的 schedule 库,它比 crontab 好用,因为可以直接在 Python 里写,不用管系统的 cron 配置。

import schedule
import time
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class Scheduler:
    def __init__(self, agent):
        self.agent = agent
        self.running = False

    def job_morning_brief(self):
        """每天早上 8 点运行:行业动态 + 任务清单"""
        logger.info("🕗 开始执行早间简报任务")
        try:
            result = self.agent.run([
                "搜索今天的 AI 行业最新动态",
                "整理昨天的工作日志摘要",
                "生成今天的任务清单,优先级从高到低"
            ])
            self.send_notification(result)
            logger.info("✅ 早间简报任务完成")
        except Exception as e:
            logger.error(f"❌ 任务执行失败: {e}")
            self.send_error_alert(str(e))

    def job_daily_report(self):
        """每天下午 6 点运行:生成工作日报"""
        logger.info("🕕 开始执行工作日报任务")
        try:
            result = self.agent.run([
                "整理今天完成的所有工作",
                "统计时间分配",
                "生成明天的计划建议"
            ])
            self.save_report(result, date.today().isoformat())
            logger.info("✅ 工作日报任务完成")
        except Exception as e:
            logger.error(f"❌ 任务执行失败: {e}")

    def send_notification(self, content):
        """发送飞书通知"""
        # 飞书 webhook 或者 API
        pass

    def send_error_alert(self, error_message):
        """发送错误告警"""
        pass

    def save_report(self, content, date_str):
        """保存报告到本地"""
        with open(f"reports/{date_str}.md", "w") as f:
            f.write(content)

    def start(self):
        """启动调度器"""
        # 早上 8 点
        schedule.every().day.at("08:00").do(self.job_morning_brief)

        # 下午 6 点
        schedule.every().day.at("18:00").do(self.job_daily_report)

        # 每小时的健康检查
        schedule.every().hour.do(self.health_check)

        logger.info("⏰ 调度器已启动")
        logger.info("📅 任务安排:")
        logger.info("  - 08:00 早间简报")
        logger.info("  - 18:00 工作日报")
        logger.info("  - 每小时健康检查")

        while True:
            schedule.run_pending()
            time.sleep(60)  # 每分钟检查一次

    def health_check(self):
        """健康检查"""
        import psutil
        memory = psutil.virtual_memory()
        if memory.percent > 90:
            logger.warning(f"⚠️ 内存使用率过高: {memory.percent}%")

这个调度器支持:
1. 多个定时任务(早间简报、工作日报、健康检查)
2. 错误处理和告警
3. 运行日志记录

2. 记忆层:跨任务保持连贯

AI Butler 需要记住历史,不然每次任务都是孤立的。

import json
import os
from datetime import datetime

class Memory:
    """记忆层:保存历史任务和上下文"""

    def __init__(self, memory_dir="memory"):
        self.memory_dir = memory_dir
        os.makedirs(memory_dir, exist_ok=True)
        self.short_term = []  # 短期记忆:最近的任务
        self.long_term = self._load_long_term()  # 长期记忆:总结的经验

    def _load_long_term(self):
        """加载长期记忆"""
        long_term_file = os.path.join(self.memory_dir, "long_term.json")
        if os.path.exists(long_term_file):
            with open(long_term_file, "r") as f:
                return json.load(f)
        return {
            "user_preferences": {},
            "repeated_tasks": [],
            "learned_patterns": [],
            "important_facts": []
        }

    def save_long_term(self):
        """保存长期记忆"""
        long_term_file = os.path.join(self.memory_dir, "long_term.json")
        with open(long_term_file, "w") as f:
            json.dump(self.long_term, f, indent=2, ensure_ascii=False)

    def add_task_record(self, task: str, result: str, success: bool):
        """记录任务执行"""
        record = {
            "timestamp": datetime.now().isoformat(),
            "task": task,
            "result_length": len(result),
            "success": success
        }
        self.short_term.append(record)

        # 超过 100 条记录,做一次总结,把重要信息移到长期记忆
        if len(self.short_term) > 100:
            self._consolidate_memory()

    def _consolidate_memory(self):
        """整合记忆:把短期记忆的重要信息移到长期记忆"""
        # 分析最近的任务模式
        recent_tasks = self.short_term[-50:]
        success_rate = sum(1 for t in recent_tasks if t["success"]) / len(recent_tasks)

        # 如果某个任务反复成功,记住它
        task_counts = {}
        for task in recent_tasks:
            # 简化任务描述作为 key
            key = task["task"][:50]
            task_counts[key] = task_counts.get(key, 0) + 1

        repeated = [k for k, v in task_counts.items() if v >= 3]
        if repeated:
            self.long_term["repeated_tasks"].extend(repeated[:10])

        # 清理短期记忆
        self.short_term = self.short_term[-50:]  # 只保留最近 50 条

        self.save_long_term()

    def get_context(self) -> str:
        """获取当前上下文,用于给 Agent 参考"""
        context_parts = []

        # 最近的任务
        if self.short_term:
            recent = self.short_term[-5:]
            context_parts.append("最近完成的任务:")
            for task in recent:
                status = "✅" if task["success"] else "❌"
                context_parts.append(
                    f"  {status} {task['timestamp'][:10]}: {task['task'][:80]}"
                )

        # 用户的偏好
        if self.long_term.get("user_preferences"):
            prefs = self.long_term["user_preferences"]
            context_parts.append(f"\n你的偏好:{prefs}")

        # 重要的facts
        if self.long_term.get("important_facts"):
            facts = self.long_term["important_facts"]
            context_parts.append(f"\n重要事实:{facts}")

        return "\n".join(context_parts)

记忆层的好处是:
1. 跨任务连贯性:今天的工作日报可以参考昨天的工作内容
2. 学习用户习惯:AI 会记住用户喜欢什么样的报告格式
3. 长期知识积累:重要的事实不会丢失

3. Agent 层:理解任务并执行

Agent 层是核心,负责理解任务、制定计划、执行操作。

import openai
from typing import List

class AIAgent:
    """AI Agent:理解任务、制定计划、执行操作"""

    def __init__(self, memory: Memory, tools: list):
        self.client = openai.OpenAI()
        self.memory = memory
        self.tools = tools
        self.system_prompt = self._build_system_prompt()

    def _build_system_prompt(self) -> str:
        return """你是我的个人 AI Butler,帮助我处理日常任务。

你的职责:
1. 搜索和整理信息
2. 生成报告和摘要
3. 提醒重要事项
4. 自动化日常任务

你有以下工具可以使用:
- search_web(query): 搜索网页获取信息
- send_message(to, content): 发送消息
- save_file(path, content): 保存文件
- read_file(path): 读取文件

工作原则:
1. 每一步都要清楚你在做什么
2. 如果不确定,先搜索再行动
3. 重要的操作要有确认
4. 完成后简洁汇报结果

记住我的偏好:
- 报告要简洁,突出重点
- 不要说废话
- 如果任务复杂,分解成小步骤"""

    def run(self, tasks: List[str]) -> str:
        """执行任务列表"""
        results = []

        for task in tasks:
            result = self._execute_task(task)
            results.append(result)

            # 记录到记忆
            self.memory.add_task_record(task, result, success=True)

        return "\n\n".join(results)

    def _execute_task(self, task: str) -> str:
        """执行单个任务"""
        # 获取上下文
        context = self.memory.get_context()

        # 构建消息
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "system", "content": f"当前上下文:\n{context}"},
            {"role": "user", "content": f"任务:{task}\n\n请完成这个任务,并汇报结果。"}
        ]

        # 调用 LLM
        response = self.client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=self._get_tools_spec(),
            tool_choice="auto"
        )

        # 处理工具调用
        full_response = self._handle_tool_calls(response)

        return full_response

    def _get_tools_spec(self):
        """获取工具定义"""
        return [
            {
                "type": "function",
                "function": {
                    "name": "search_web",
                    "description": "搜索网页获取信息",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {"type": "string", "description": "搜索关键词"}
                        },
                        "required": ["query"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "send_message",
                    "description": "发送消息给用户",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "to": {"type": "string", "description": "收件人"},
                            "content": {"type": "string", "description": "消息内容"}
                        },
                        "required": ["to", "content"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "save_file",
                    "description": "保存内容到文件",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "path": {"type": "string", "description": "文件路径"},
                            "content": {"type": "string", "description": "文件内容"}
                        },
                        "required": ["path", "content"]
                    }
                }
            }
        ]

    def _handle_tool_calls(self, response) -> str:
        """处理工具调用"""
        message = response.choices[0].message

        if not message.tool_calls:
            return message.content

        # 执行工具调用
        tool_results = []
        for tool_call in message.tool_calls:
            tool_name = tool_call.function.name
            tool_args = json.loads(tool_call.function.arguments)

            # 根据工具名称调用不同的处理函数
            if tool_name == "search_web":
                result = self._search_web(tool_args["query"])
            elif tool_name == "send_message":
                result = self._send_message(tool_args["to"], tool_args["content"])
            elif tool_name == "save_file":
                result = self._save_file(tool_args["path"], tool_args["content"])
            else:
                result = f"Unknown tool: {tool_name}"

            tool_results.append({
                "tool_call_id": tool_call.id,
                "tool_name": tool_name,
                "result": result
            })

        # 把工具结果返回给 LLM,让它继续
        messages = [
            {"role": "user", "content": f"工具执行结果:{tool_results}"}
        ]

        follow_up = self.client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages
        )

        return follow_up.choices[0].message.content

    def _search_web(self, query: str) -> str:
        """搜索网页"""
        # 这里可以接入真实的搜索引擎 API
        # 比如 Google Custom Search、Bing API,或者 DuckDuckGo
        return f"[模拟搜索结果] 关于 '{query}' 的信息:xxx"

    def _send_message(self, to: str, content: str) -> str:
        """发送消息"""
        # 这里可以接入飞书、企业微信、Slack 等消息渠道
        return f"消息已发送给 {to}"

    def _save_file(self, path: str, content: str) -> str:
        """保存文件"""
        os.makedirs(os.path.dirname(path), exist_ok=True)
        with open(path, "w") as f:
            f.write(content)
        return f"文件已保存到 {path}"

这个 Agent 的特点是:
1. 有记忆:可以参考历史任务和上下文
2. 有工具:可以搜索、发送消息、保存文件
3. 自主决策:LLM 决定什么时候调用什么工具

4. 启动脚本

把所有东西串起来:

#!/usr/bin/env python3
"""
AI Butler - 个人 AI 助手
启动脚本
"""

import logging
from memory import Memory
from agent import AIAgent
from scheduler import Scheduler

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def main():
    logger.info("🚀 启动 AI Butler...")

    # 初始化各层
    memory = Memory()
    tools = ["search_web", "send_message", "save_file"]
    agent = AIAgent(memory=memory, tools=tools)
    scheduler = Scheduler(agent)

    # 启动调度器
    scheduler.start()

if __name__ == "__main__":
    main()

启动命令:

# 在服务器上后台运行
nohup python ai_butler.py > ai_butler.log 2>&1 &

# 或者用 systemd 管理(更可靠)
# /etc/systemd/system/ai-butler.service
[Unit]
Description=AI Butler - Personal AI Assistant
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/ai-butler
ExecStart=/usr/bin/python3 ai_butler.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

实际效果

我的 AI Butler 跑了三周,效果怎么样?

早间简报

每天早上 8 点,我打开飞书,会看到 AI Butler 发来的消息:

🤖 AI Butler 早间简报 (2026-05-07)

📰 今日 AI 行业动态:
1. OpenAI 发布 GPT-5,性能提升 40%
2. Meta 开源 LLaMA-4,7B 参数跑出 70B 效果
3. 斯坦福发布 AI Safety 报告,提出新框架

📋 昨日工作摘要:
- 完成了 PPT Agent v2.0 的架构设计
- 修复了 RAG 检索的 bug
- 参加了产品评审会议

✅ 今日任务清单(按优先级):
1. [高] 代码 review:PPT Agent 的 Agent 层实现
2. [高] 测试:RAG 系统的混合检索功能
3. [中] 文档:更新技术架构文档
4. [低] 优化:LLM 调用成本分析

⏰ 当前时间:08:00,祝你今天高效工作!

这个简报我以前要花 30 分钟整理,现在 AI Butler 3 分钟生成。

工作日报

每天下午 6 点,AI Butler 会整理我一天的工作,生成日报并存档。

以前写日报要 15 分钟,现在 AI Butler 自动生成,我只需要花 2 分钟 review 和修改。

踩过的坑

坑一:时区问题

AI Butler 跑在树莓派上,树莓派的时区默认是 UTC。我设置的是早上 8 点运行,但实际是下午 4 点运行。

解决:在 Python 里明确设置时区

import pytz

tz = pytz.timezone('Asia/Shanghai')
schedule.every().day.at("08:00", tz).do(self.job_morning_brief)

坑二:内存泄漏

跑了 3 天后,发现内存占用从 200MB 涨到了 800MB。

原因:短期记忆列表无限增长,每次任务都往里加记录。

解决:加了记忆整合逻辑,超过 100 条记录就做一次总结,把重要信息移到长期记忆,清理短期记忆。

坑三:API 超时

有时候 LLM API 调用超时,任务就失败了。

解决:加了重试逻辑,API 超时自动重试 3 次。

def _execute_task_with_retry(self, task, max_retries=3):
    for attempt in range(max_retries):
        try:
            return self._execute_task(task)
        except Exception as e:
            if attempt < max_retries - 1:
                logger.warning(f"任务执行失败,重试中... ({attempt + 1}/{max_retries})")
                time.sleep(2 ** attempt)  # 指数退避
            else:
                logger.error(f"任务执行失败,已重试 {max_retries} 次")
                raise

我的观点

做这个 AI Butler 之前,我觉得 AI Agent 都是大厂的东西,个人做不来。

做完之后我发现:个人真的可以做出很有用的 AI 工具

关键是:
1. 不要想着一开始就做完美:先做一个能跑的小版本,再迭代改进
2. 专注于解决实际问题:我这个 Butler 没有花哨的功能,但它解决了我每天 45 分钟的工作
3. 保持简单:架构分层清晰,出了问题容易排查
4. 让它自己跑:不需要我干预,它每天自动完成工作

AI Butler 不是科幻电影里的机器人,它就是一个每天帮你处理琐事的工具。

它不性感,但它有用。


如果你也想做一个自己的 AI Butler,建议从最小可用的版本开始:先做一个每天早上给你发天气和任务清单的简单版本,跑通了再加功能。