从意图识别到人工确认:用 FastAPI 搭建一个可控的个人知识 Agent
智能体最近的智能体讨论,已经从“模型能不能聊天”转向“模型能不能在边界内完成一段工作”。OpenAI Agents SDK 的公开文档把 Agent、工具、交接、Guardrails、人工介入和追踪列为核心积木;MCP 官方文档则把它定义为连接 AI 应用与外部数据源、工具和工作流的开放标准。对个人项目来说,最值得借鉴的不是堆概念,而是把一段真实流程拆成可检查的步骤。
本文用我正在维护的 Personal Ledger 做一次轻量实践:把“记账、学习积累、知识整理和查询”看成几个工具,由后端先识别意图,再路由到对应服务;涉及写入知识库时,先生成草稿,用户确认后才落库。它不是一个完全自主的 Agent,也没有把项目包装成已经接入 MCP,而是展示一条更容易维护的 Agent 化路线。
先看一条完整链路:输入、路由、工具、确认
项目的入口保持为一个文本框。用户可以输入一段记录,也可以直接问问题:
今天午餐 32 元,晚上听英语 30 分钟
总结一下本周项目进展,并整理成知识卡片
这个月餐饮大概花了多少?
后端先执行意图识别,再选择处理路径:
输入文本
|
v
意图路由 /api/intent
|-- 记录写入 -> /api/records/ingest
|-- 统计查询 -> /api/monthly-summary 或 /api/analytics
|-- 知识整理 -> /api/knowledge/summarize
|-- 综合分析 -> /api/analyze
这一步的价值在于:查询不会被误当成新记录,知识整理也不会直接改写原始数据。它更像一个有明确边界的工作流控制器,而不是把所有请求都交给模型自由发挥。

意图路由:先用规则挡住高确定性请求
在 app/main.py 中,分析请求会先经过 _analysis_route。金额、日期、时长和常见查询词等高确定性信息,优先由本地规则处理;只有长文本或语义不明确的内容,才交给可选的模型服务。
这种“规则优先、模型补充”的方式有三个好处:
- 外部模型暂时不可用时,基础记录仍能保存。
- 重要字段的格式和类型由 Pydantic 校验,错误更容易定位。
- 每个路由都能单独测试,不必用一条大提示词覆盖全部场景。
示意代码如下,实际项目还会继续做日期和金额的结构化校验:
def route_question(question: str) -> str:
text = question.strip()
if any(word in text for word in ("花了多少", "支出", "收入", "账单")):
return "ledger_query"
if any(word in text for word in ("总结", "复盘", "知识卡片", "项目进展")):
return "knowledge"
return "general_analysis"
工具边界:每个动作只负责一件事
把后端接口当成工具时,关键不是工具数量,而是工具的职责要清晰。项目目前可以按下面的方式理解:
| 工具 | 作用 | 是否写入 |
|---|---|---|
POST /api/records/ingest |
把口述内容解析为账目或积累记录 | 是 |
GET /api/monthly-summary |
返回月度汇总和趋势 | 否 |
GET /api/accumulation/summary |
查询学习、阅读和运动投入 | 否 |
POST /api/knowledge/summarize |
生成知识文档草稿 | 只生成草稿 |
POST /api/knowledge/drafts/{draft_id}/confirm |
用户确认后写入知识库 | 是 |
POST /api/analyze |
组合账目、积累和知识检索结果 | 默认不写入 |
工具接口的输入和输出都用 Pydantic schema 描述,SQLite 负责保存原始记录、批次信息和知识全文索引。这样做的一个实际收益是:以后即使接入 MCP,也可以先把这些稳定接口包装成 MCP tools,而不需要重写核心业务。

知识整理:把“自动生成”和“最终写入”分开
Agent 最容易出问题的地方,往往不是生成一段文字,而是生成内容后直接覆盖数据。项目的知识整理接口采用两阶段流程:
/api/knowledge/summarize根据输入生成标题、正文、主题、标签和事件草稿。- 页面展示草稿,用户可以修改日期、领域、主题和标签。
/api/knowledge/drafts/{draft_id}/confirm只在确认后写入knowledge_notes。
这就是一个很实用的人在回路(Human-in-the-loop)边界:模型可以整理,用户决定是否入库。对于个人复盘、项目笔记和工作记录,这个确认按钮比“全自动写入”更重要,因为它让错误可见、可撤回,也方便之后追踪来源。

数据层:SQLite 适合小型、可自托管的 Agent 原型
这个项目没有引入复杂的分布式存储,而是使用 SQLite 保存交易、积累记录、知识草稿和批次信息,知识检索使用 FTS5 全文索引。对于单用户或小团队的内部工具,这种选择足够直接:
- 数据库文件可以随应用一起备份。
- Docker Compose 只需要挂载一个持久化目录。
- 读写路径清晰,调试时可以直接检查 SQL 和 schema。
部署时把代码目录、数据目录和备份目录分开,模型服务的密钥只放在环境变量中。文章截图使用的是脱敏演示数据,不包含真实账号、地址或访问凭据。

从 MCP 热点得到的一个工程启发
MCP 官方文档的核心表述是“让 AI 应用连接到外部系统”,并强调数据源、工具和工作流的标准化连接。这个项目暂时没有实现 MCP Server,因此不能把它写成“MCP 实战”;但它已经具备一个重要前提:工具边界稳定、输入输出有 schema、写入动作有确认点。
如果下一步要扩展,可以按这个顺序做:
- 为月度汇总、知识搜索等只读接口补充稳定的 JSON schema。
- 把只读接口包装成 MCP resources 或 tools,先验证查询链路。
- 对写入型工具增加权限、幂等键和审计日志。
- 在真正接入外部系统前,保留人工确认和失败回滚。
本地运行与验证
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload
启动后打开 http://127.0.0.1:8000,先用演示文本测试路由,再查看历史、积累和知识页面。生产部署使用 Docker Compose,并把 SQLite 数据目录挂载到宿主机。
建议至少补三类测试:意图路由测试、知识草稿确认测试、模型服务不可用时的降级测试。Agent 的可靠性不是靠一句“请谨慎回答”获得的,而是靠接口边界、数据校验和可回滚流程积累出来的。
事实来源与边界说明
- OpenAI Agents SDK 文档:https://openai.github.io/openai-agents-python/
- Model Context Protocol 文档:https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro
- 本文项目:
D:\Desktop\app,以当前代码和脱敏演示数据为依据。
文中的官方能力描述来自上述公开页面;项目部分是本地代码阅读和界面实测,不代表项目已经具备文中提到的全部 SDK 或 MCP 能力。涉及个人数据时,请先做备份、权限控制和脱敏处理。
浙公网安备 33010602011771号