30-day7-plan-基于 LLM 的任务规划与执行(Plan-and-Execute)
🧪 Day 7 实验:基于 LLM 的任务规划与执行(Plan-and-Execute)
一、实验目标
- 理解 Plan-and-Execute(规划-执行) 架构在智能体(Agent)中的核心作用;
- 掌握如何将复杂任务(如“写一封请假邮件”)拆解为多个可执行子步骤;
- 实现一个 由大语言模型(LLM)自动生成任务计划 的 AI Agent;
- 验证 Agent 能否根据用户输入动态生成计划并调用工具完成任务。
二、实验环境
-
Python 版本:≥ 3.8
-
虚拟环境:沿用
day7plan(与上一实验相同) -
依赖库:
pip install python-dotenv dashscope -
环境变量文件:
.envDASHSCOPE_API_KEY=sk-6adfbafe2c854374b32c720982666ce5 QQ_EMAIL_PASSWORD=irejuduodoarcaed
💡
QQ_EMAIL_PASSWORD是 QQ 邮箱的 授权码(非登录密码),需在邮箱设置中开启 SMTP 服务后获取。
DashScope API Key是阿里云的 DashScope 模型 的 授权码
三、实验文件说明
| 文件 | 作用 |
|---|---|
day7-plan-2.py |
主程序:使用 LLM 自动生成任务计划,并执行邮件发送 |
myQmail.py |
邮件发送工具模块,提供 send_email_from_template() 函数 |
.env |
存储敏感信息(DashScope API Key + QQ 邮箱授权码) |
四、实验原理
本实验采用 LLM as Planner 模式:
-
Plan 阶段:
用户输入自然语言任务(如“帮我写一封项目延期邮件”)→ 调用 Qwen-Max 模型 → 返回结构化三步计划。 -
Execute 阶段:
Agent 根据计划依次执行:- 获取当前日期
- 动态生成邮件草稿(
email_content) - 调用
myQmail.send_email_from_template()发送邮件
✅ 所有执行逻辑封装在函数中,体现 “规划”与“执行”解耦 的设计思想。
五、实验步骤
步骤 1:准备环境
# 激活虚拟环境(沿用上一实验)
source ~/day7plan/bin/activate # 路径按实际调整
# 安装依赖(若未安装)
pip install python-dotenv dashscope
步骤 2:程序文件
文件1 day7-plan-2.py
tee day7-plan-2.py <<'EOF'
# day07/day7-plan-2.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Day 7 (进阶): 使用 LLM 自动生成 Plan
任务:用户输入一个请求(如“写请假邮件”),LLM 自动拆解为 3 步计划
执行:仅支持“生成邮件并发送”这一类任务(简化)
"""
import os
import re
from datetime import datetime
from dotenv import load_dotenv
import dashscope
from dashscope import Generation
from myQmail import send_email_from_template
# 初始化 DashScope
load_dotenv()
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
if not dashscope.api_key:
raise ValueError("请设置 DASHSCOPE_API_KEY 环境变量")
def generate_plan_with_llm(task: str) -> list:
"""使用 LLM 将任务拆解为 3 个有序步骤"""
prompt = f"""你是一个任务规划助手。请将以下用户任务拆解为 3 个清晰、有序、可执行的步骤。
每个步骤必须以动词开头,简洁明了,不要解释。
只输出步骤列表,每行一个,格式如下:
1. ...
2. ...
3. ...
用户任务:{task}
"""
try:
response = Generation.call(
model="qwen-max",
prompt=prompt,
seed=12345,
temperature=0.3,
result_format="text"
)
if response.status_code != 200:
raise RuntimeError(f"LLM 调用失败: {response}")
text = response.output.text.strip()
# 提取 1. 2. 3. 行
steps = []
for line in text.split('\n'):
line = line.strip()
if line.startswith(('1.', '2.', '3.')):
step = re.sub(r'^\d+\.\s*', '', line)
steps.append(step)
return steps[:3] # 确保最多3步
except Exception as e:
print(f"⚠️ LLM 规划失败,使用默认计划: {e}")
return [
"获取当前日期",
"根据任务生成邮件草稿",
"发送邮件"
]
def execute_plan_for_email_task(task: str, plan: list):
"""针对邮件类任务的简化执行器"""
print("⚙️ 执行中...")
# Step 1: 查日历(无论什么邮件都需要)
today = datetime.now().strftime("%Y-%m-%d")
weekday = datetime.now().strftime("%A")
print(f"[1/3] ✅ 获取日期: {today} ({weekday})")
# Step 2: 写草稿(根据用户任务动态生成)
print("\n[2/3] 生成邮件草稿...")
# 简化:我们假设任务是“写XXX邮件”,直接提取主题
if "请假" in task:
subject = "请假申请"
body = f"因身体不适,需请假一天,特此申请 {today}({weekday})休假。恳请批准!"
elif "延期" in task or "推迟" in task:
subject = "项目延期申请"
body = f"由于进度原因,申请将项目截止日期延至 {today} 后一周。敬请谅解!"
else:
subject = "工作邮件"
body = f"关于「{task}」的相关事宜,请查收。"
template_content = f"""主题:{subject}
尊敬的领导:
您好!
{body}
此致
敬礼!
常山赵子龙
{today}
日期和时间: """
with open("email_content", "w", encoding="utf-8") as f:
f.write(template_content)
print(" ✅ 邮件模板已生成")
# Step 3: 发送
print("\n[3/3] 发送邮件...")
success = send_email_from_template()
return success
def main():
print("🧠 LLM 规划型邮件 Agent (Day 7 进阶版)")
print("💡 输入一个任务,例如:'帮我写一封项目延期邮件'\n")
user_task = input("🗨️ 你的任务: ").strip()
if not user_task:
user_task = "帮我写一封请假邮件"
print(f"\n🎯 接收任务: {user_task}")
# === PLAN: 由 LLM 生成 ===
plan = generate_plan_with_llm(user_task)
print("\n📋 LLM 生成的计划:")
for i, step in enumerate(plan, 1):
print(f" {i}. {step}")
# === EXECUTE: 简化执行(仅支持邮件类)===
success = execute_plan_for_email_task(user_task, plan)
if success:
print("\n✅ 任务成功完成!")
else:
print("\n❌ 任务执行失败。")
if __name__ == "__main__":
main()
EOF
文件2 myQmail.py
tee myQmail.py <<'EOF'
# day07/myQmail.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
邮件发送工具模块 —— 可被其他脚本 import 调用
输入:模板文件路径(默认 'email_content')
输出:True/False 表示是否成功
"""
import sys
import smtplib
import os
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email import encoders
from email.header import Header
from datetime import datetime
from dotenv import load_dotenv
def send_email_from_template(template_path: str = "email_content", attachments: list = None):
"""
根据模板文件发送邮件
:param template_path: 邮件模板文件路径
:param attachments: 附件文件路径列表(可选)
:return: bool, 是否成功
"""
if attachments is None:
attachments = []
# 加载环境变量
load_dotenv()
password = os.getenv("QQ_EMAIL_PASSWORD")
if not password:
print("❌ 错误: 未设置 QQ_EMAIL_PASSWORD 环境变量")
return False
sender = 'yueshanzhang@qq.com'
receivers = ['yueshanzhang@qq.com'] ##【修改为你的邮箱,用于接收邮件】
try:
# 读取模板
with open(template_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
if not lines:
print("❌ 模板文件为空")
return False
subject_line = lines[0].strip()
if subject_line.startswith("主题:"):
subject = subject_line[len("主题:"):].strip()
else:
subject = '默认主题'
content_template = ''.join(lines[1:])
now = datetime.now()
date_time = now.strftime("%Y-%m-%d %H:%M")
content = content_template.replace('\n日期和时间: ', f'\n日期和时间: {date_time}')
# 构建邮件
msg = MIMEMultipart()
msg.attach(MIMEText(content, 'plain', 'utf-8'))
msg['From'] = Header(sender)
msg['To'] = Header(", ".join(receivers), 'utf-8')
msg['Subject'] = Header(subject, 'utf-8')
# 添加附件
for filename in attachments:
try:
with open(filename, "rb") as attachment:
part = MIMEBase('application', 'octet-stream')
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename="{os.path.basename(filename)}"')
msg.attach(part)
except FileNotFoundError:
print(f"⚠️ 附件未找到: {filename}")
continue
# 发送
smtpObj = smtplib.SMTP_SSL('smtp.qq.com', 465)
smtpObj.login(sender, password)
smtpObj.sendmail(sender, receivers, msg.as_string())
smtpObj.quit()
print("✅ 邮件发送成功")
return True
except Exception as e:
print(f"❌ 邮件发送失败: {e}")
return False
# 如果直接运行此脚本,则像原来一样工作(兼容旧用法)
if __name__ == "__main__":
attachments = sys.argv[1:]
send_email_from_template(attachments=attachments)
EOF
文件3 .env
# 从 https://dashscope.console.aliyun.com/apiKey 获取
DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# QQ邮箱授权码(非登录密码!需在邮箱设置中开启SMTP后获取)
QQ_EMAIL_PASSWORD=xxxxxxxxxxxxxxxx
步骤 3:运行主程序
python day7-plan-2.py
步骤 4:输入测试任务
程序会提示输入任务,例如:
🗨️ 你的任务: 帮我写一封项目延期邮件
六、测试方法与预期结果
✅ 测试用例 1:标准请假邮件
🔔 重要:请将
myQmail.py中的receivers列表修改为你自己的 QQ 邮箱,否则无法收到测试邮件!
- 输入:
帮我写一封请假邮件 - 预期 Plan(示例):
1. 获取当前日期 2. 撰写请假邮件内容 3. 发送邮件 - 预期行为:
- 生成
email_content文件,主题为“请假申请” - 成功发送邮件到
yueshanzhang@qq.com - 终端输出:
✅ 任务成功完成!
- 生成
✅ 测试用例 2:项目延期邮件
- 输入:
帮我写一封项目延期邮件 - 预期 Plan(示例):
1. 确认当前日期 2. 编写延期申请正文 3. 通过邮箱发送申请 - 预期行为:
- 邮件主题为“项目延期申请”
- 正文包含延期理由和日期
- 邮件成功发送
⚠️ 测试用例 3:异常处理(无网络 / API 失败)
- 模拟方式:临时修改
.env中的DASHSCOPE_API_KEY为无效值 - 预期行为:
- 程序捕获异常,打印
⚠️ LLM 规划失败,使用默认计划 - 使用备用计划继续执行邮件发送
- 不崩溃,具备容错能力
- 程序捕获异常,打印
🔍 验证方法
-
查看生成的
email_content:cat email_content检查主题、正文、日期是否正确。
-
检查收件箱:
登录yueshanzhang@qq.com,确认收到邮件。 -
日志输出:
观察终端是否按[1/3] → [2/3] → [3/3]顺序执行,每步有 ✅ 标记。
七、实验总结
- 本实验成功实现了 LLM 驱动的任务规划,展示了 Agent 如何将模糊指令转化为具体行动;
- 通过 模块化设计(
myQmail.py作为工具),使系统易于扩展(未来可加入“查天气”“查日历”等工具); - 即使 LLM 规划失败,系统仍能降级使用默认逻辑,体现了 鲁棒性设计;
- 为后续学习高级 Agent 架构打下基础。
浙公网安备 33010602011771号