第四次作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/16760
这个作业的目标 使用 MCP 等现代 AI 框架,从零构建一个同时具备"自然语言对话(会说)"和"工具调用/操作执行(会做)"能力的智能体,为课程大项目打基础
学号 蔡志冲3124004197 、林华杰3124004213

用 MCP 打造「能说会做」的待办清单智能体 —— TodoPal 开发全记录

课程:软件工程 · 团队作业(智能体专项)
仓库:https://github.com/it-cc/3124004197/tree/main/3124004197/agent
关键词:AI Agent · MCP · Function Calling · FastAPI · 离线语音交互


一、作业要求回顾

本次作业要求构建一个具备 语言交互(会说) 与 行动执行(会做) 能力的智能体。我们小组做的是一个待办清单智能助理 TodoPal——你用中文跟它说话,它理解意图后通过 MCP 协议调用工具,真正地增删改查任务数据、查询天气,再用自然语言把结果汇报给你。

作业要求 本项目的落实情况
场景自选,构建一个智能体 待办清单智能助理 TodoPal
推荐使用 MCP 框架 基于官方 MCP Python SDK(FastMCP)实现,客户端与服务器通过 stdio 通信
能说:自然语言对话 中文多轮对话,system 提示动态注入当天日期
会做:执行操作 / 调用工具 8 个 MCP 工具:任务增删改查 / 统计 / 清理 + 外部天气 API
加分项:多轮对话记忆 对话历史自动持久化到本地 JSON,重启程序后仍记得之前聊过什么
加分项:语音输入 / 输出 CLI 端 vosk 离线识别 + pyttsx3 本地播报;网页端浏览器 Web Speech API 朗读
加分项:与外部 API / 数据库交互 调用 Open-Meteo 天气 API(免费、无需密钥);任务数据通过存储层持久化
加分项:前端界面 FastAPI + 原生 HTML/CSS/JS 聊天网页,右侧实时任务栏
博客园随笔 + GitHub 链接 + 分工 + 心得 即本篇博文

二、需求描述

2.1 背景与动机

学生日常的琐事又多又碎:作业 DDL、组会、班级事务……传统待办 App 需要自己打开、点按钮、填表单,记一条待办的操作成本太高,很多人坚持不下来。我们希望把记录成本降到说一句话就行:对着它说"我明天上午十点要开组会",它就自动建好带截止时间的任务;说"明天广州会下雨吗",它去查天气,还能顺带建议你把户外任务改期。

2.2 一句话需求

用户用中文自然语言与 TodoPal 对话,TodoPal 理解意图后通过 MCP 协议调用工具,真实地操作任务数据、查询外部天气服务,并把结果用自然语言汇报给用户。

2.3 功能性需求

能说(语言交互)

  • 中文自然语言多轮对话,支持一句话包含多个任务("明天要开会,周五要交作业")
  • 支持"明天 / 后天 / 周五 / 下周三"等相对时间,自动换算成具体日期
  • 多轮对话记忆:上下文在会话内连续,且重启程序后自动恢复(输入 /clear 可主动清空)

会做(行动执行)——8 个 MCP 工具

工具 功能 关键参数
add_task 新增任务 标题、截止时间、优先级(高/中/低)、标签
list_tasks 查看清单(逾期自动标⚠️提示) all / pending / done
complete_task 标记完成 任务编号
delete_task 删除任务 任务编号
update_task 修改任务任意字段 编号 + 要改的字段
clear_completed 一键清理已完成 无
task_stats 统计总数 / 未完成 / 已完成 / 逾期 无
query_weather 外部 API:Open-Meteo 天气预报 城市名、预报天数 1~7

两种入口 + 语音(加分项)

  • 终端 CLI:/tasks 直连查看(不经过大模型)、/voice 播报开关、/mic 语音输入开关
  • 网页版:聊天界面 + 实时任务侧边栏 + 浏览器朗读按钮
  • 语音输入采用 vosk 离线中文识别(首次自动下载约 40MB 模型,之后完全离线);语音输出采用 pyttsx3 调用操作系统本地 TTS——都不需要申请任何云服务密钥

2.4 非功能性需求

  • 隐私:任务数据全部存本地 tasks.json,不上传任何服务器
  • 可移植:只要一个 OpenAI 兼容接口就能跑,智谱 GLM / DeepSeek / Kimi / 通义 一键切换(改 .env 三个变量即可)
  • 优雅降级:没装语音依赖、天气服务不可用时,核心功能完全不受影响,且给出友好提示
  • 可测试:不依赖网络和大模型 Key 也能跑全部自动化测试

三、业务流描述

3.1 总体架构

![总体架构图]image

系统分四层:

  1. 用户入口层:终端 CLI(agent.py)或浏览器前端(static/ + webapp.py);
  2. 智能体核心层:TodoAgent 负责对话管理、工具调度和多轮记忆(chat_history.json);
  3. 能力层(MCP 服务器):server.py 以 stdio 方式提供 8 个工具;
  4. 数据 / 外部服务层:TaskStore 读写 tasks.json,server.py 调用 Open-Meteo 天气 API;核心层调用大模型 API 完成理解与决策。

3.2 一次对话的完整业务流

![业务流程图]image

以用户说 "我明天上午十点要开组会,周五要交软件工程作业,帮我记一下" 为例:

  1. 客户端组装上下文:system 提示(动态注入今天日期与星期)+ 对话历史 + 8 个工具的 JSON Schema;
  2. 调用大模型,模型理解到"一句话包含两个任务",一轮返回两次 tool_calls:
    add_task(title="开组会", deadline="2026-10-26 10:00") 和
    add_task(title="交软件工程作业", deadline="2026-10-30")——相对时间由模型结合 system 提示里的今天日期完成换算;
  3. 客户端解析 tool_calls,通过 MCP 协议(stdio JSON-RPC)调用 server.py 对应工具;
  4. server.py 调用 TaskStore 校验参数(日期格式、优先级合法性)并写入 tasks.json,返回确认文本;
  5. 工具结果作为 tool 消息追加进对话历史,并持久化到记忆文件;
  6. 循环回大模型:两个任务都处理完后,模型生成最终自然语言回复;
  7. 客户端把回复展示给用户(并按需朗读)。

如果用户问天气,第 2 步模型会改调 query_weather,由 server.py 通过 HTTP 调用 Open-Meteo API(先地理编码把"广州"换成经纬度,再查预报),把天气结果文本回喂给模型总结。


四、实现说明

4.1 技术选型

层次 选型 理由
语言 Python 3.10+ 异步生态成熟,团队最熟悉
智能体协议 官方 MCP Python SDK(FastMCP),stdio 传输 作业推荐;工具与客户端解耦,天然支持多客户端复用
大模型接入 OpenAI SDK + OpenAI 兼容接口 换供应商只改 .env 三个变量
网页后端 FastAPI + uvicorn 原生 async,可与 MCP 客户端共用同一个事件循环
网页前端 原生 HTML / CSS / JS 无构建工具,代码可读,适合教学展示
语音 vosk(离线识别)+ pyttsx3(本地合成) 全离线、零密钥、零费用
任务存储 JSON 文件 + TaskStore 存储层 轻量可移植;存储层隔离后未来可无痛换 SQLite
测试 pytest 44 个用例覆盖存储层 / MCP 协议 / 记忆 / 外部 API

4.2 目录结构

agent/
├── agent.py               # 智能体核心 + 终端客户端(对话循环、语音集成)
├── webapp.py              # Web 后端:/api/chat、/api/tasks、/api/clear
├── server.py              # MCP 服务器:8 个工具
├── storage.py             # TaskStore 存储层(校验、排序、逾期判断、JSON 持久化)
├── memory.py              # 多轮对话记忆(持久化 + 非法历史清洗)
├── voice.py               # 语音输入/输出(可选,缺依赖自动降级)
├── static/                # 网页前端三件套(index.html / style.css / app.js)
├── docs/                  # 架构图 / 业务流程图(graphviz 源码 + PNG)
├── tests/                 # 44 个自动化测试
└── .env.example           # 配置模板

4.3 关键实现 ①:MCP 服务器把"会做"变成标准接口

用 FastMCP 把每个能力注册成工具,docstring 就是给大模型看的说明书——写得越清楚,模型选工具、传参数就越准:

mcp = FastMCP("todo-pal")
store = TaskStore(DATA_FILE)

@mcp.tool()
def add_task(title: str, deadline: str = "", priority: str = "", tags: str = "") -> str:
    """添加一条待办任务并返回任务编号。
    deadline:截止时间(可选),格式 YYYY-MM-DD 或 YYYY-MM-DD HH:MM。
    用户说"明天下午三点"等相对时间时,必须结合今天的日期换算成上述格式。"""
    task = store.add_task(title, deadline=deadline, priority=priority, tags=tags)
    return f"✅ 已添加任务 #{task['id']}:{task['title']},截止 {task['deadline']}"

if __name__ == "__main__":
    mcp.run(transport="stdio")

设计要点:

  • 工具返回文本而非数据结构:返回"✅ 已添加任务 #3:写作业"这样的自然语言,模型可以直接复述给用户,也可以据此决定下一步;
  • 业务错误用 isError=True 的文本返回(如"截止时间格式不正确"),模型收到后能自行纠正参数重试,服务器永不崩溃;
  • query_weather 工具内部先调 Open-Meteo 地理编码接口把"广州"变经纬度,再查预报,超时 / 断网统一降级为友好提示文本。

4.4 关键实现 ②:智能体的"大脑"——工具调用循环

客户端把 MCP 工具列表转成 OpenAI function-calling 格式,然后在"模型决策 → 执行 → 回喂结果"的循环里跑,直到模型认为可以直接回答:

async def _loop(self) -> str:
    for _ in range(MAX_TOOL_ROUNDS):                     # 防失控:最多 6 轮
        response = await self.client.chat.completions.create(
            model=MODEL,
            messages=[{"role": "system", "content": SYSTEM_PROMPT}] + self.messages,
            tools=self.tools_schema, temperature=0.5)
        msg = response.choices[0].message
        if not msg.tool_calls:                           # 模型认为可以直接回答了
            answer = (msg.content or "").strip()
            self.messages.append({"role": "assistant", "content": answer})
            return answer
        self.messages.append(...)                        # 记录 assistant 的 tool_calls
        for tc in msg.tool_calls:
            result_text = await self._execute_tool(tc)   # 真正通过 MCP 执行
            self.messages.append({"role": "tool", "tool_call_id": tc.id,
                                  "content": result_text})

几个容易踩的坑我们都处理了:

  • 相对时间换算:在 system 提示里动态注入今天日期(含星期几),"明天上午十点"就能被正确换算成 2026-10-26 10:00;
  • 历史裁剪不能剪断工具链:裁剪后强制以 user 消息开头,避免出现没有配对 assistant 的孤儿 tool 消息导致 API 报错;
  • 异常回滚:本轮对话中途出错时回滚消息快照,坏历史绝不留在记忆里。

4.5 关键实现 ③:多轮对话记忆(加分项)

memory.py 把对话历史原子写入 chat_history.json(用 os.replace 保证不会写一半被截断),重启时加载并做三道校验:角色合法性 → 孤儿 tool 消息丢弃 → 中断的工具调用链整体移除。所以无论程序在哪一刻被 Ctrl+C 杀掉,下次启动恢复的历史都是合法的:

self.memory = ChatMemory("chat_history.json", max_messages=40)
self.messages = self.memory.load()      # 启动恢复:"🧠 已恢复 12 条历史对话记忆。"
...
answer = await self._loop()
self.memory.save(self.messages)         # 每轮成功后持久化

4.6 关键实现 ④:语音输入 / 输出(加分项)

语音模块 voice.py 全部懒加载 + 优雅降级:没装依赖时相关命令提示安装方法,纯文字交互零影响。

  • 语音输出:pyttsx3 调操作系统本地 TTS(Windows 的 SAPI5 自带中文 Huihui 音色),后台线程朗读,朗读中收到新回复自动跳过,不阻塞对话;
  • 语音输入:sounddevice 采集 16kHz PCM → vosk 离线中文识别,首次使用自动下载小模型(带进度条),之后完全离线;交互形式是"按回车开始说话 → 说完按回车结束",识别结果回显确认后进入对话流程。

网页端则用浏览器内置的 Web Speech API(speechSynthesis)做一键朗读,零后端成本。

4.7 关键实现 ⑤:网页前端(加分项)

后端用 FastAPI,巧妙的一点是把 uvicorn 和 MCP 客户端放进同一个 asyncio 事件循环,请求处理函数里直接 await agent.chat(message),完全不需要跨线程桥接:

@asynccontextmanager
async def lifespan(app: FastAPI):
    async with AsyncExitStack() as stack:
        await agent.connect(stack)      # 启动 MCP 服务器子进程并握手
        app.state.chat_lock = asyncio.Lock()
        yield

@app.post("/api/chat")
async def chat(req: ChatRequest):
    async with app.state.chat_lock:
        reply = await agent.chat(req.message)
    return {"reply": reply}

前端是一个无依赖的单页应用:聊天气泡、输入框、右侧任务清单侧边栏(每轮对话后自动刷新,数据来自 /api/tasks 直连 MCP 工具)、提示词快捷入口、朗读开关、清空记忆按钮。所有用户输入用 textContent 渲染,天然防 XSS。

4.8 测试与质量保障

44 个自动化测试(python -m pytest,几秒跑完),全部不依赖大模型 Key:

  • 存储层单元测试:增删改查、优先级别名归一化、逾期判定、排序规则、编号不复用、损坏文件自动备份……
  • MCP 协议集成测试:以真实子进程 + stdio 握手启动 server.py,走完整 MCP 协议调用工具——这是"会做"这一半的端到端验证;
  • 对话记忆测试:存取回环、非法历史清洗、中断工具链修复、原子写入;
  • 外部 API 测试:真实调用 Open-Meteo 查北京天气(断网环境自动 skip)。
$ python -m pytest -v
... 44 passed in 6.23s

五、使用示例(功能演示)

5.1 快速开始

cd agent
python -m venv .venv
.venv\Scripts\activate                  # Windows;Linux/macOS 用 source .venv/bin/activate
pip install -r requirements.txt          # 可选:pip install -r requirements-voice.txt
copy .env.example .env                  # 编辑 .env 填入你的 LLM_API_KEY
python agent.py                         # 终端版;或 python webapp.py 网页版

大模型推荐用智谱 glm-4-flash(免费),DeepSeek / Kimi / 通义 改 .env 三行即可切换。

5.2 终端版示例对话

已连接 MCP 服务器,注册工具:add_task, list_tasks, complete_task, delete_task,
update_task, clear_completed, task_stats, query_weather
对话模型:glm-4-flash( https://open.bigmodel.cn/api/paas/v4 )

你 > 我明天上午十点要开组会,周五要交软件工程作业,帮我记一下

TodoPal > 好的,已经帮你记下两条任务:
          1. 开组会,明天(2026-10-26)上午 10:00
          2. 交软件工程作业,本周五(2026-10-30)

你 > 组会那个标成高优先级
TodoPal > 已把「开组会」标为高优先级 ✏️

你 > 看看我还有哪些事没做完
TodoPal > 你现在有 2 条未完成任务:
          ⬜ #1 开组会[优先级:高] | 截止:2026-10-26 10:00
          ⬜ #2 交软件工程作业 | 截止:2026-10-30

你 > 明天广州会下雨吗?我要不要带伞
TodoPal > 查了一下:广州明天小雨,24~29℃,降水概率 80%,建议带伞 ☔
          要不要我把你的户外任务改到后天?

你 > 1 号任务完成了
TodoPal > 收到,任务 #1「开组会」已完成!🎉 剩下 1 条:周五前交软件工程作业。

你 > /tasks                       ← 不经过大模型,直连 MCP 服务器
📋 共 2 条任务:未完成 1,已完成 1
⬜ #2 交软件工程作业 | 截止:2026-10-30
✅ #1 开组会[优先级:高] | 完成于 2026-10-25 22:31

体现的能力:一句话记两件事(一轮多工具调用)、相对时间自动换算、外部天气 API、任务状态联动、/tasks 直连通道。

af81aee6f4f2c9394cc299a82fd4fa39
95bd8deb733880468ad7a49b514788fe

5.3 多轮对话记忆演示

$ python agent.py
🧠 已恢复 8 条历史对话记忆。        ← 程序重启后自动恢复上下文

你 > 我刚才让你记的作业是哪天的?
TodoPal > 是软件工程作业,截止这周五(2026-10-30)。

5.4 网页版与语音演示

网页版(python webapp.py → 浏览器打开 http://127.0.0.1:8000):左侧聊天区对话,右侧任务清单每轮自动刷新,顶部按钮可开启浏览器朗读。

语音:终端里输入 /mic 开启语音输入(按回车说话、说完再按回车,离线识别),输入 /voice 开启回复播报;网页端点击"🔊 播报开"朗读回复。

5c00a28262d12b3eca476b9376b4e5ef


六、GitHub 链接


七、小组分工

成员 学号 分工
蔡志冲(组长) 3124004197 总体设计、MCP 服务器与存储层、工具调用循环、测试
林华杰 3124004213 对话记忆模块、博文撰写、业务流程图绘制、功能演示与测试

说明:git 提交记录目前主要由组长提交,其他组员的本地改动请合并推送后再在本表中对应其分工。


八、成员心得

蔡志冲(组长):这次作业让我第一次真正理解了"智能体 = 大模型 + 工具调用"的含义。最深的体会是 MCP 把工具变成了标准接口——服务器和客户端完全解耦,我们甚至可以用 /tasks 命令绕过大模型直连工具。这种分层设计在以后的大项目里肯定还能复用。

林华杰:写工具调用循环时踩了不少坑——历史裁剪会剪断 tool_calls 配对、中断的对话会留下非法消息……解决这些问题的过程让我对大模型 API 的消息协议理解深了很多。


九、学习资源推荐

MCP 入门(官方)

智能体平台体验

实战教程

我们小组额外用到的资料


十、总结与展望

TodoPal 完整跑通了"自然语言 → 意图理解 → MCP 工具执行 → 自然语言汇报"的智能体闭环,并额外实现了对话记忆、语音交互、外部 API、Web 界面四个加分项。它也为我们最后的大项目打好了地基,接下来可以自然扩展出:

  • 定时提醒:结合截止时间做到期推送(任务数据里已经有 deadline 字段);
  • 日程视图:按周 / 日聚合展示,接入日历 API;
  • 多用户:TaskStore 换 SQLite + 用户登录,记忆按用户隔离;
  • 更强工具集:邮件、课程表、番茄钟等新 MCP 工具即插即用——这正是 MCP 架构的优势。

作业题目说"大项目会强制要求体现智能体"——这次我们把地基打得足够扎实,大项目只需要在能力层继续加工具就可以了。

posted @ 2026-10-25 23:52  forest132  阅读(22)  评论(0)    收藏  举报