第四次作业
| 这个作业属于哪个课程 | Class56-Grade2024-CS |
|---|---|
| 这个作业要求在哪里 | 作业要求 |
| 这个作业的目标 | 个人 Agent 助手说明文档的梳理与格式规范化:整理项目需求、业务流、实现说明与使用示例,形成结构清晰的 Markdown 文档 |
| 学号 | 3124004075 郑剀元,3124004053 陈梓铤 |
个人 Agent 助手说明文档
项目名称:个人 Agent 助手
项目类型:本地 CLI Agent + Web UI
仓库位置:https://github.com/Zztt417/----3/tree/main/第四次作业
技术栈:Python、FastAPI、Vue 3、Vite、Chroma、sentence-transformers、MCP
使用方式:命令行 / 浏览器
一、需求描述
1.1 项目定位
本项目是一个运行在本机的个人 Agent 助手,核心目标是:
接收用户输入 → LLM 判断是否需要调用工具 → 执行工具 → 把结果返回给 LLM → 继续推理,直到给出最终答案。
项目参考 nanobot 的极简架构,重点拆解并实现以下能力:
- OpenAI 兼容 LLM 接入
- ReAct 工具调用循环
- 本地工具注册与执行
- 短期记忆与长期记忆
- 多会话隔离
- FastAPI 接口
- Vue 3 Web 界面
- MCP 外部工具扩展
1.2 功能需求
| 编号 | 需求 | 说明 |
|---|---|---|
| F1 | CLI 交互 | 命令行 REPL,支持连续对话和退出 |
| F2 | LLM 接入 | 支持 OpenAI 兼容接口,模型从配置读取 |
| F3 | ReAct 循环 | 支持一次返回多个工具调用,结果回填后继续推理 |
| F4 | 本地工具 | 文件读写、Shell 命令、网页搜索、网页抓取、PDF 解析 |
| F5 | 短期记忆 | 每会话独立持久化;滑动窗口 + 溢出摘要压缩 |
| F6 | 长期记忆 | 本地 Chroma 向量库 + bge 中文向量模型 |
| F7 | 会话管理 | 新建、切换、列出、删除、查看历史 |
| F8 | Web UI | Vue 3 聊天界面、会话列表、运行状态面板 |
| F9 | FastAPI | 暴露聊天、会话、历史和状态接口 |
| F10 | MCP 扩展 | 可选接入邮件 MCP 工具 |
| F11 | 安全配置 | 真实密钥不进入仓库,API 默认仅监听本机 |
| F12 | 安全上传 | 提供 GitHub 上传前安全检查脚本 |
1.4 使用约束
本项目默认是个人本地工具:
run_shell可以执行本机命令read_file/write_file可以访问本机文件- FastAPI 默认只监听
127.0.0.1 - 不建议直接暴露到公网
- 真实密钥必须放在仓库外
二、业务流描述
2.1 整体架构
┌──────────────────────┐
│ Vue 3 Web UI │
│ 会话列表 / 聊天 / 状态 │
└──────────┬───────────┘
│ HTTP /api
▼
┌──────────────────────┐
│ FastAPI │
│ 聊天 / 会话 / 历史接口 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Agent │
│ ReAct + Session │
└──────┬───────┬───────┘
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ LLMClient│ │ ToolRegistry │
└──────────┘ └──────┬───────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
文件/Shell 网页/PDF MCP 工具
│ │ │
└─────────────┴─────────────┘
│
▼
短期记忆 / 长期记忆
2.2 启动流程
CLI 启动
python main.py
│
▼
load_config()
│
├─ 读取外部 .env / 项目 .env
├─ 初始化 LLMClient
├─ 初始化短期记忆
├─ 初始化 Chroma 长期记忆
├─ 加载会话索引
├─ 注册工具
├─ 可选连接 MCP
└─ 进入 REPL 循环
Web 启动
python api_server.py
│
▼
FastAPI lifespan
│
├─ 组装 Agent 运行时
├─ 加载模型 / 记忆 / 工具
├─ 可选连接 MCP
├─ 托管 ui/dist
└─ 提供 /api 接口
2.3 单轮对话主流程
用户输入
│
├─ 以 "/" 开头 → CLI 命令处理
│
└─ 普通文本
│
▼
Agent.run(user_input, session_id)
│
├─ 设置当前会话
├─ 短期记忆检查与压缩
├─ 检索当前会话长期记忆
├─ 写入用户消息
├─ 组装 messages + tools
│
▼
for step in range(max_steps)
│
├─ 调用 LLM
├─ 写入 assistant 消息
├─ 无 tool_calls → 返回最终答案
│
└─ 有 tool_calls
├─ 执行每个工具
├─ 写入 tool 消息
└─ 继续下一轮
2.4 ReAct 循环
Agent 每轮最多执行 MAX_STEPS 次“模型调用 + 工具执行”循环。
关键约束:
- assistant 的
tool_calls[].id必须与 tool 消息的tool_call_id对应 - 工具结果必须回填给模型
- 工具异常不能中断整个循环,而是转成错误文本回填
- 工具调用过程默认不展示,只展示最终结论
- 超过最大步数时返回提示,避免无限循环
2.5 短期记忆流程
短期记忆采用:
滑动窗口 + 超出部分摘要压缩
流程:
每轮追加 user / assistant / tool 消息
│
▼
消息条数 > MEMORY_MAX_MESSAGES
│
├─ 否:继续
│
└─ 是:
├─ 找到安全切点
├─ 较早消息交给 LLM 摘要
├─ 摘要写入 system 上下文
├─ 保留最近一段原始消息
└─ 重写 JSONL
切点会尽量对齐到 user 消息,避免切断工具调用与工具结果。
2.6 长期记忆流程
写入:
LLM 调用 memory_save
│
▼
读取当前 session_id
│
▼
bge-small-zh-v1.5 生成向量
│
▼
写入 Chroma
document: 记忆正文
metadata:
session_id: 会话 ID
timestamp: 时间
type: fact / preference / conclusion
tags: 标签
读取:
用户输入
│
▼
生成查询向量
│
▼
Chroma.query(
where={"session_id": 当前会话},
n_results=top_k
)
│
▼
把命中记忆注入本轮 system 上下文
默认会话隔离;跨会话检索必须由用户明确要求。
2.7 会话管理流程
| 动作 | 流程 |
|---|---|
| 新建 | 生成会话 ID → 写入 index.json → 切换 |
| 切换 | 更新最近使用时间 → 加载对应 JSONL |
| 删除 | 删除长期记忆 → 删除 JSONL → 删除索引 |
| 历史 | 读取 JSONL,过滤工具消息,展示用户/助手消息 |
| 自动命名 | 首次对话后用用户输入生成标题 |
2.8 Web 请求流程
Vue 页面
│
├─ GET /api/sessions
├─ GET /api/sessions/{id}/history
├─ POST /api/chat
└─ GET /api/status
│
▼
FastAPI
│
▼
AgentRuntime(进程内单例)
│
▼
Agent + Memory + Tools + LLM
API 使用锁保证同一时间只有一个聊天请求修改 Agent 状态,避免会话历史和记忆索引并发写入冲突。
三、实现说明
3.1 目录结构
个人agent助手/
├── main.py CLI 启动入口
├── api_server.py FastAPI 启动入口
├── mybot/
│ ├── agent.py ReAct 循环
│ ├── api.py FastAPI 接口
│ ├── config.py 配置加载
│ ├── llm.py LLM 客户端
│ ├── memory.py 短期记忆
│ ├── long_memory.py Chroma 长期记忆
│ ├── sessions.py 会话索引
│ ├── session_scope.py ContextVar 会话作用域
│ ├── mcp_client.py MCP 同步桥接
│ └── tools/
│ ├── base.py Tool / ToolResult
│ ├── registry.py 工具注册表
│ ├── builtin.py 文件与 Shell
│ ├── web.py 搜索与网页抓取
│ ├── pdf_tools.py PDF 解析
│ └── memory_tools.py 记忆工具
├── ui/ Vue 3 + Vite 前端
├── models/ 本地 bge 向量模型
├── samples/ 合成 PDF 测试样本
├── scripts/ 安全检查脚本
├── .env.example 配置模板
├── README.md 部署使用说明
├── SECURITY.md 安全说明
└── requirements.txt
3.2 核心模块职责
| 模块 | 职责 |
|---|---|
config.py |
加载 LLM、记忆、MCP、API 配置 |
llm.py |
封装 OpenAI 兼容 Chat Completions 接口 |
agent.py |
组装消息、执行 ReAct 循环、调用工具 |
memory.py |
JSONL 短期记忆、摘要压缩、会话 LRU |
long_memory.py |
Chroma 向量库、bge 嵌入、语义检索 |
sessions.py |
会话索引、标题、时间、增删改查 |
session_scope.py |
用 ContextVar 传递当前会话 ID |
tools/base.py |
工具抽象与统一返回结构 |
tools/registry.py |
工具注册、schema 导出、异常兜底 |
mcp_client.py |
后台线程运行异步 MCP,对外提供同步接口 |
api.py |
FastAPI 路由、运行时装配、静态 UI 托管 |
ui/ |
会话侧栏、聊天区、状态面板、Markdown 渲染 |
3.3 消息结构
系统使用 OpenAI Chat Completions 消息格式:
| role | 说明 |
|---|---|
system |
基础提示词 + 历史摘要 + 长期记忆 |
user |
用户输入 |
assistant |
模型回复,可能带 tool_calls |
tool |
工具执行结果,带 tool_call_id |
工具调用必须保持:
assistant(tool_calls)
→ tool(tool_call_id)
的顺序,否则接口可能报错。
3.4 工具接口
所有工具实现统一接口:
class Tool:
name: str
description: str
parameters: dict
def execute(self, **kwargs) -> ToolResult: ...
工具执行失败时返回 ToolResult.error(...),由 Agent 回填给模型,而不是直接抛出异常。
3.5 关键技术决策
| 决策 | 原因 |
|---|---|
| 工具使用同步接口 | 实现简单,便于调试 |
| MCP 使用后台线程桥接 | MCP SDK 是异步的,但上层工具是同步的 |
| 会话 ID 使用 ContextVar | 工具实例全局唯一,避免模型自行填写会话 ID |
| 摘要压缩只在回合开始 | 避免切断 tool_calls 与 tool 结果 |
| 长期记忆按 session_id 过滤 | 实现会话间物理隔离 |
| PDF 缩进按 x 坐标聚类 | PDF 没有显式缩进结构 |
| FastAPI 使用 RLock | 避免多请求并发修改 Agent 状态 |
| 真实配置放在仓库外 | 防止密钥进入 Git |
ui/dist 随仓库提交 |
不安装前端依赖也能直接启动 Web UI |
3.6 配置读取
配置读取优先级:
MYBOT_ENV_FILE
> 项目根目录 .env
> 用户目录 ~/.mybot/.env
推荐使用仓库外的用户配置:
Windows: C:\Users\<用户名>\.mybot\.env
macOS/Linux: ~/.mybot/.env
3.7 数据存储
| 数据 | 位置 |
|---|---|
| 短期记忆 | MEMORY_DATA_DIR/sessions/*.jsonl |
| 会话索引 | MEMORY_DATA_DIR/sessions/index.json |
| 长期向量库 | MEMORY_DATA_DIR/chroma |
| 嵌入模型 | models/bge-small-zh-v1.5 |
| 运行时日志 | .mybot/mcp.log(启用 MCP 时) |
默认 MEMORY_DATA_DIR 为项目内 data/,可用环境变量覆盖。
3.8 安全实现
.env、.mybot/、data/、数据库文件和个人 PDF 已加入.gitignore.env.example只包含占位符- 提供
scripts/check_github_safety.py上传前扫描 - FastAPI 默认监听
127.0.0.1:8000 - 邮件 MCP 默认关闭
- 邮件授权码保存在 MCP 自己的本地配置库,不进入项目
run_shell等工具没有沙箱,需限制在本机可信环境使用
四、使用示例(功能演示)
4.1 安装
cd "<项目根目录>"
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
macOS / Linux:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
4.2 配置
复制配置模板到用户目录:
New-Item -ItemType Directory -Force "$HOME\.mybot" | Out-Null
Copy-Item .env.example "$HOME\.mybot\.env"
notepad "$HOME\.mybot\.env"
填写:
OPENAI_API_KEY=sk-your-key-here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
也可以使用 DeepSeek:
OPENAI_BASE_URL=https://api.deepseek.com/v1
OPENAI_MODEL=deepseek-flash
4.3 启动 CLI
python main.py
示例:
你 > 帮我读取 README.md,概括这个项目
助手 > 这个项目是一个本地个人 Agent……
会话命令:
/sessions
/new 项目笔记
/switch 1
/history 5
/delete 2 -y
/help
exit
4.4 启动 Web UI
python api_server.py
打开:
http://127.0.0.1:8000
接口文档:
http://127.0.0.1:8000/docs
前端开发模式:
cd ui
npm install
npm run dev
打开:
http://127.0.0.1:5173
4.5 文件读取示例
输入:
读取 requirements.txt,告诉我项目依赖了哪些库
Agent 会调用:
read_file(path="requirements.txt")
最终回答会列出依赖,例如:
openai、python-dotenv、chromadb、sentence-transformers、
pdfplumber、mcp、fastapi、uvicorn
4.6 文件写入示例
输入:
在当前目录创建 notes.txt,内容为“今天完成了 Agent Web UI”
Agent 会调用:
write_file(path="notes.txt", content="今天完成了 Agent Web UI")
4.7 Shell 示例
输入:
运行 python --version,告诉我当前 Python 版本
Agent 会调用:
run_shell(command="python --version")
4.8 网页搜索示例
输入:
搜索 Vue 3 + FastAPI 本地 Agent 的实践文章
Agent 会调用:
web_search(query="Vue 3 FastAPI 本地 Agent")
需要正文时可以继续抓取某个链接:
web_fetch(url="https://example.com/article")
4.9 PDF 解析示例
项目自带合成测试文件:
samples/_synthetic_nested.pdf
输入:
解析 samples/_synthetic_nested.pdf 的第 1 页
Agent 会调用:
pdf_parse(path="samples/_synthetic_nested.pdf", pages="1")
输出会包含正文和 Markdown 表格,并用全角空格保留缩进层级。
扫描件/图片型 PDF 没有文本层,当前工具不支持 OCR。
4.10 短期记忆示例
在同一会话中:
你 > 记住:我的项目使用 FastAPI + Vue 3
助手 > 已记住。
后续:
你 > 我的项目使用什么技术栈?
助手 > 你的项目使用 FastAPI + Vue 3。
4.11 长期记忆示例
你 > 记住:我喜欢喝美式咖啡
助手 > 已记入当前会话的长期记忆。
重启程序或切换到同一会话后:
你 > 我喜欢喝什么?
助手 > 你喜欢喝美式咖啡。
不同会话默认不会共享长期记忆。
4.12 跨会话检索示例
用户明确要求:
查一下我在其他对话里提到过什么饮品
Agent 可能调用:
memory_search_other_sessions(query="饮品")
返回结果会标注来源会话。
4.13 会话管理示例
你 > /sessions
会话列表(最近使用在前):
1. 项目笔记
2. 默认会话
你 > /new 课程表分析
已新建并切换到会话:课程表分析
你 > /history 5
—— 最近 5 条对话 ——
4.14 邮件 MCP 示例(可选)
默认关闭:
MCP_EMAIL_ENABLED=false
需要时先配置:
python -m pip install uv
uvx mcp-email-server@latest ui
在本地 MCP UI 中填写账号、SMTP/IMAP 地址和授权码,然后把本地配置改为:
MCP_EMAIL_ENABLED=true
重启 API 或 CLI 后,可以输入:
给白名单里的邮箱发一封测试邮件,主题是“MCP 测试”
授权码不要写入
.env.example、README、代码或聊天记录。
4.15 API 调用示例
PowerShell:
$body = @{
session_id = "default"
message = "读取 requirements.txt,概括项目依赖"
} | ConvertTo-Json
Invoke-RestMethod `
-Uri "http://127.0.0.1:8000/api/chat" `
-Method Post `
-ContentType "application/json" `
-Body $body
示例返回:
{
"answer": "项目依赖包括……",
"session": {
"id": "default",
"title": "项目依赖说明",
"memory_count": 0
},
"session_id": "default"
}
4.16 常见问题
| 现象 | 处理方法 |
|---|---|
提示未找到 OPENAI_API_KEY |
检查 ~/.mybot/.env 或 MYBOT_ENV_FILE |
| 长期记忆已禁用 | 检查 models/bge-small-zh-v1.5,缺失时运行 python setup_memory.py |
缺少 fastapi |
执行 pip install -r requirements.txt |
| MCP 工具为 0 | 检查 MCP 配置,或设置 MCP_EMAIL_ENABLED=false |
| 8000 端口被占用 | 修改 API_PORT |
| 前端页面打不开 | 检查 ui/dist/index.html,必要时运行 npm run build |
| PDF 无内容 | 可能是扫描件,需要 OCR |
| 上传 GitHub 前不确定是否安全 | 执行 python scripts\check_github_safety.py |
总结
本项目实现了一个可本地运行的 Agent 助手:
- CLI 与 Web UI 共用同一套 Agent 能力
- 通过 ReAct 循环完成真实工具调用
- 使用短期滑动窗口和长期向量记忆维持上下文
- 通过 FastAPI 暴露稳定接口
- 通过 Vue 3 提供可视化操作界面
- 通过 MCP 支持外部工具扩展
- 默认将密钥和运行数据隔离在仓库外
适合用于个人本地自动化、学习 Agent 架构、演示工具调用和二次开发。

浙公网安备 33010602011771号