构建一个能说会做的智能体
能说会做的课程任务智能体设计与实现
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS |
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/16761 |
| 这个作业的目标 | 构建一个具备自然语言对话和工具调用能力的 AI 智能体,让它不仅能“说”,还能调用本地工具完成任务,并完成需求描述、业务流、实现说明、使用示例、GitHub 链接和个人心得等文档。 |
| 学号 | 3124004235 |
| 姓名 | 蔡栩跃 |
GitHub 项目地址:https://github.com/pqbwdqm2vq-hub/course-agent-demo
- 需求描述
本次作业要求实现一个“能说会做”的 AI Agent。所谓“能说”,是指智能体能接收中文自然语言输入,并给出自然语言回复;所谓“会做”,是指智能体不能只聊天,还要能调用工具完成实际操作。
我选择做一个面向学生日常学习场景的课程任务与笔记智能体。它可以帮助用户完成以下事情:
- 用自然语言添加课程待办任务;
- 查看当前待办任务列表;
- 根据编号完成某个任务;
- 保存学习笔记;
- 查询城市天气并给出简单出行建议;
- 执行安全的四则运算;
- 把任务清单导出为本地文本文件。
这个智能体不依赖第三方 API,也不需要联网,适合课堂演示和提交作业。后续如果接入大模型,也可以在现有工具层上扩展成真正的大模型驱动 Agent。
- 业务流描述
本项目采用经典 Agent 循环:
用户自然语言输入
→ 意图识别
→ 实体提取
→ 选择工具
→ 调用工具执行
→ 观察执行结果
→ 生成自然语言回复
具体流程如下:
- 用户输入一句中文,例如:“帮我记下来周五18:00前交软件工程作业”。
- 智能体识别出用户想添加任务。
- 从句子中提取任务标题“交软件工程作业”和截止时间“周五18:00”。
- 调用
add_task工具,把任务写入本地 JSON 文件。 - 工具返回执行结果。
- 智能体把结果转换成中文回复:“已记下任务:交软件工程作业,截止时间:周五18:00,优先级:普通。”
如果用户输入“我有哪些任务”,智能体会识别出查询意图,读取本地任务文件并返回任务列表。
如果用户输入“完成第1个任务”,智能体会提取编号 1,调用完成任务工具,把对应任务标记为已完成。
- 实现说明
3.1 技术选型
本项目使用 Python 标准库实现,不安装额外依赖,因此运行环境要求低,老师拿到代码后可以直接运行。
核心模块如下:
| 模块 | 作用 |
|---|---|
OfficeAgent |
智能体主类,负责理解输入、选择工具、生成回复 |
TaskStore |
本地文件存储层,负责读写任务和笔记 JSON 文件 |
understand() |
自然语言意图识别 |
add_task() |
添加任务工具 |
list_tasks() |
查询任务工具 |
finish_task() |
完成任务工具 |
save_note() |
保存笔记工具 |
query_weather() |
天气查询工具 |
calculate() |
安全四则运算工具 |
export_tasks() |
导出任务清单工具 |
3.2 Agent 主循环
智能体的核心不是聊天内容本身,而是“理解意图后执行工具”。主循环如下:
def run(self, user_text: str) -> str:
intent, arguments = self.understand(user_text)
if intent == "reply":
return arguments["text"]
handlers = {
"add_task": self.add_task,
"list_tasks": self.list_tasks,
"finish_task": self.finish_task,
"save_note": self.save_note,
"query_weather": self.query_weather,
"calculate": self.calculate,
"export_tasks": self.export_tasks,
}
return handlers[intent](**arguments)
这段代码体现了 Agent 的基本结构:先理解用户输入,再选择对应工具,最后把工具执行结果返回给用户。
3.3 自然语言意图识别
为了避免依赖外部大模型 API,本项目当前使用规则识别自然语言。例如,如果句子中出现“作业”“实验”“报告”“考试”“复习”等词,就判断用户想添加任务:
task_words = ["作业", "实验", "报告", "会议", "考试", "复习", "提交", "项目", "答辩"]
if any(word in text for word in task_words):
due = self._extract_due(text)
title = re.sub(r"^(帮我|请)?(记下来|记一下|提醒我|要|需要|想|计划|准备|创建?)", "", text)
title = title.replace(due, "").replace("前", "")
return "add_task", {"title": title, "due": due, "priority": "普通"}
这种方式虽然不如大模型灵活,但优点是稳定、离线、可复现。
3.4 工具层设计
每个功能都被封装成独立函数。例如添加任务:
def add_task(self, title: str, due: str = "未设置", priority: str = "普通") -> str:
tasks = self.store.load_tasks()
next_id = max((task.task_id for task in tasks), default=0) + 1
tasks.append(Task(next_id, title, due, priority, False))
self.store.save_tasks(tasks)
return f"已记下任务:{title},截止时间:{due},优先级:{priority}。"
把功能封装成工具的好处是:后续如果要接入 MCP 或大模型 Function Calling,只需要把这些函数注册成工具,不需要重写业务逻辑。
3.5 安全计算
四则运算没有直接使用危险的 eval(),而是通过 ast 模块限制表达式类型,只允许数字、加减乘除、括号和一元正负号:
if isinstance(node, ast.BinOp) and type(node.op) in BINARY_OPERATORS:
return BINARY_OPERATORS[type(node.op)](visit(node.left), visit(node.right))
这样可以避免用户输入恶意代码,也比直接使用 eval() 更安全。
3.6 本地数据存储
任务和笔记分别保存在本地 JSON 文件中:
data/tasks.json
data/notes.json
这样做的好处是结构简单,调试方便,不需要安装数据库。虽然当前使用本地文件,但存储层和业务层是分开的,后续可以替换成数据库或云端存储。
- 使用示例
4.1 运行环境
Python 3.10 或更高版本
无需安装第三方依赖
4.2 运行演示
python3 office_agent.py --demo
实际运行结果如下:
你:帮我记下来 周五18:00前交软件工程作业
智能体:已记下任务:交软件工程作业,截止时间:周五18:00,优先级:普通。
你:帮我记一下 明天上午复习智能体课件
智能体:已记下任务:复习智能体课件,截止时间:明天上午,优先级:普通。
你:我有哪些任务
智能体:当前任务清单:
1. [待完成] 交软件工程作业(截止:周五18:00,优先级:普通)
2. [待完成] 复习智能体课件(截止:明天上午,优先级:普通)
你:完成第1个任务
智能体:已完成:交软件工程作业。
你:广州天气怎么样
智能体:广州当前多云,26~33℃。建议:午后有分散阵雨,出门建议带折叠伞。
你:计算 (12 + 8) * 3 / 2
智能体:计算结果:(12 + 8) * 3 / 2 = 30.0
你:记一下:Agent循环是理解、工具调用、观察、回复
智能体:已保存第 1 条笔记:Agent循环是理解、工具调用、观察、回复
你:导出任务
智能体:任务清单已导出到:tasks_export.txt
4.3 单轮调用
python3 office_agent.py --say "帮我记下来 周五18:00前交软件工程作业"
4.4 交互式对话
python3 office_agent.py
输入 退出 即可结束对话。
- 测试情况
本次测试覆盖以下场景:
| 测试编号 | 输入 | 预期结果 | 实际结果 |
|---|---|---|---|
| 1 | 帮我记下来周五18:00前交软件工程作业 | 成功添加任务,并提取截止时间 | 通过 |
| 2 | 帮我记一下明天上午复习智能体课件 | 成功添加任务,并提取“明天上午” | 通过 |
| 3 | 我有哪些任务 | 返回任务列表 | 通过 |
| 4 | 完成第1个任务 | 第一条任务变为已完成 | 通过 |
| 5 | 广州天气怎么样 | 返回广州天气和建议 | 通过 |
| 6 | 计算 (12 + 8) * 3 / 2 | 返回 30.0 | 通过 |
| 7 | 记一下:Agent循环是理解、工具调用、观察、回复 | 成功保存笔记 | 通过 |
| 8 | 导出任务 | 生成 tasks_export.txt |
通过 |
- 个人心得
这次作业让我真正理解了 Agent 和普通聊天程序的区别。普通聊天程序更像是“你问我答”,而 Agent 的关键在于:它能够理解用户想做什么,然后选择合适的工具去执行,最后把执行结果反馈给用户。
在实现过程中,我最先犯的错误是把“记一下”都当成笔记保存,结果“帮我记一下明天上午复习智能体课件”被错误识别成了笔记。后来我调整了意图判断顺序:先识别任务关键词,再处理带冒号的笔记,这个问题才解决。这个过程让我意识到,自然语言处理看起来简单,实际上有很多边界情况。
另一个收获是工具层设计。一开始我把所有逻辑都写在一个函数里,后来把添加任务、查询任务、完成任务、天气查询、计算等功能拆成独立函数,代码结构立刻清晰了很多。如果以后要把这些工具接入 MCP 或大模型 Function Calling,这种封装方式会很有帮助。
最后,我认为一个合格的 Agent 不一定一开始就要接入很复杂的大模型。先把“理解意图—选择工具—执行工具—返回结果”这个基本循环做正确,再逐步替换底层模型,反而是更稳妥的开发方式。





浙公网安备 33010602011771号