跳转至

综合项目:Notebook Service(10/2 – 10/6,≈50h)

一个"个人知识库"HTTP 服务 + 命令行客户端。它只用本仓库学到的 Python 知识,不含任何 LLM 调用;但它的每一块(FastAPI + SSE、SQLite 仓储、Pydantic 模型、httpx 客户端、日志、测试、分层)都是 learn-agent 里 StudyBuddy 要直接复用的积木。做完它,agent 项目里剩下的就只是"LLM 与工具"本身。

位置:projects/notebook/(uv workspace 成员,uv run notebook --helpuv run notebook-server)。骨架预先给出——这是结业项目,从 cd projects && uv init --package notebook 开始自己搭,然后在仓库根 pyproject.tomldependencies 里加 "notebook"、在 [tool.uv.sources] 里加 notebook = { workspace = true }uv sync 后入口点即可用;projects/kbprojects/aggregator 是你的参考实现。运行时依赖(fastapi[standard]、pydantic-settings、httpx、tenacity、typer、rich)在根环境里已经装好,但 projects/notebook/pyproject.toml 仍要如实声明自己的 dependencies


1. 功能清单(MVP 全部必做)

# 功能 接口 / 命令 涉及知识
F1 笔记 CRUD POST /notes GET /notes/{id} PATCH /notes/{id} DELETE /notes/{id} FastAPI、Pydantic、SQLite 仓储
F2 列表与过滤 GET /notes?tag=&q=&limit=&offset= 分页、查询校验
F3 搜索 GET /notes/search?pattern=&regex=true 正则、服务层
F4 批量导入 Markdown 目录 POST /import {"path": "..."} → 返回 job_id,后台执行 BackgroundTasks/asyncio.Task、pathlib、第 1 周管线
F5 导入进度推送 GET /jobs/{job_id}/events(SSE:progress/done/error 事件) fastapi.sse、异步生成器、队列
F6 外部数据拉取 POST /notes/from-url {"url": "..."}:用 httpx 抓取 JSON/文本生成笔记(演示服务调外部服务,带超时与重试) httpx、tenacity、错误分类
F7 健康与统计 GET /healthGET /stats(笔记数、标签分布、导入任务数) 聚合、Counter
F8 鉴权 写操作需 X-API-Key(来自 settings) 依赖注入、pydantic-settings
F9 CLI 客户端 notebook add/list/search/import/watch-job(通过 httpx 调服务) typer、rich、httpx
F10 可观测 请求日志中间件(请求 id、耗时、状态码);文件 JSON 日志轮转;/stats 里含错误计数 logging、中间件

可选扩展(做 1 个):导出 Markdown/JSON 备份;标签重命名事务;简单的 ETag/If-None-MatchWebSocket 版进度;用 uvx ty check 全绿。

2. 架构要求

projects/notebook/
├── pyproject.toml                # name="notebook"; scripts: notebook = "notebook.cli:app", notebook-server = "notebook.main:run"
├── README.md  .env.example  CHANGELOG.md
├── src/notebook/
│   ├── main.py                   # create_app()、lifespan(建库、启动任务表)、异常处理器、中间件、include_router
│   ├── settings.py               # Settings(BaseSettings):db_path、api_key、log_level、import_root、http_timeout
│   ├── logging_config.py
│   ├── domain/                   # dataclass 领域模型:Note、Tag、ImportJob;领域异常 NotebookError/NotFound/Conflict/Invalid
│   ├── schemas/                  # Pydantic 传输模型:NoteIn/NoteOut/NotePatch/ImportRequest/JobOut/StatsOut
│   ├── repositories/             # NoteRepository(Protocol)、JobRepository(Protocol);sqlite 实现 + memory 实现
│   ├── services/                 # NoteService、ImportService(生成器管线 + 进度回调)、FetchService(httpx)
│   ├── api/                      # routers:notes.py jobs.py stats.py health.py;deps.py(get_settings/get_repo/verify_api_key)
│   ├── sse.py                    # 任务进度 → EventSourceResponse 的异步生成器
│   └── cli.py                    # typer 客户端
└── tests/
    ├── conftest.py               # 内存仓储、TestClient、AsyncClient(ASGITransport)、tmp_path 数据
    ├── unit/                     # domain、services(用内存仓储与假 http transport)
    └── api/                      # 路由测试、SSE 测试(读前 N 条事件)、鉴权测试

硬性规则: 1. services/domain/ 不 import FastAPI、sqlite3、httpx 的具体类型——只依赖 Protocol;所有 I/O 实现放 repositories/FetchService 的注入客户端。 2. 领域异常在 main.py 统一映射为 HTTP(NotFound→404Conflict→409Invalid→422)。 3. 所有文件读写 encoding="utf-8";SQLite 一律参数化查询、with conn: 事务。 4. 异步端点内不出现阻塞调用(SQLite 用 to_thread 或同步端点)。 5. 每个公开函数有一句话 docstring;类型注解完整;ruff checkE F W I UP B N D SIM)零警告;Pylance 零错误。 6. 测试:单元 + API + 至少 1 个 SSE 测试 + 1 个鉴权失败测试 + 1 个 httpx MockTransport 测试;pytest --cov=notebook ≥80%。 7. 配置只来自环境变量/.env,仓库里只有 .env.example

3. 里程碑与时间

里程碑 日期 内容 结束时必须有
M1 领域与仓储 10/2(10h) uv init --package 建包;领域模型与异常;NoteRepository 协议 + 内存 + SQLite 实现;NoteService CRUD/搜索;单元测试 pytest tests/unit 全绿;uv run python -m notebook --help 可用
M2 服务与 API 10/3(10h) settings、logging、create_app、routers(F1/F2/F3/F7/F8)、异常映射、中间件、API 测试 fastapi dev 起服务,/docs 能走通 CRUD;API 测试全绿
M3 导入、SSE、外部拉取、CLI 10/4(10h) ImportService(第 1 周管线 + 进度回调)、任务表、/import + /jobs/{id}/events SSE、FetchService(httpx + tenacity)、typer CLI CLI 能 import 一个目录并 watch-job 看到进度条走完
M4 收口 10/5(10h) 覆盖率 ≥80%、ruff/Pylance 零错误、README、.env.exampleCHANGELOG、可选部署到 H20 验收清单前 12 条全勾
M5 评审与发布 10/6(10h) AI 评审(模板 4)→ 重构 ≤5 条 → 回归;uv build --package notebookgit tag v1.0.0;复盘文档 推送 cnb.cool;复盘写进 checklist.md

风险控制:10/3 结束时若 API 没跑通,10/4 砍掉 F6 与 CLI 的 watch-job,先保 F4/F5;10/5 结束时若覆盖率不足,优先补服务层测试而不是路由测试。

4. 验收清单

  • uv syncuv run notebook-server 起服务,/docs 可用,/health 返回 200
  • F1–F10 全部可用(用 CLI 走一遍:add → list → search → import → watch-job → stats)
  • 写操作无 X-API-Key 返回 401,错误 key 返回 403
  • SSE:导入 50 个文件时能看到 progress 事件逐条到达,最后 done
  • 导入目录中有一个损坏/非 UTF-8 文件时任务不崩溃,事件里出现 error 且其余文件导入成功
  • from-url 对不可达地址返回 502/504 类错误而不是 500,日志有分类
  • 删掉数据库文件重启服务能自动建库;.env 缺失时用默认值并警告
  • pytest --cov=notebook --cov-report=term-missing ≥80%,全部通过
  • ruff check projects/notebook 零警告;Pylance 零错误
  • services/domain/grep 不到 fastapisqlite3httpx
  • 请求日志包含请求 id 与耗时;文件日志为 JSON、UTF-8、可轮转
  • README 含:一句话介绍、架构图(文字版)、运行、配置项表、接口表、测试、已知限制、下一步
  • Git 历史 ≥15 次 Conventional Commits;v1.0.0 tag;已推送 cnb.cool
  • AI 评审后改动 ≤5 条并回归通过
  • (可选)在 H20 上运行并通过 ssh -L 在本机访问 /docs

5. 结业复盘模板(写进 checklist.md

  1. 三个最有价值的知识点(各一句话,说明它在 Notebook Service 的哪一行起作用)
  2. 三个最难的 bug(现象 → 根因 → 用哪个工具找到的)
  3. 如果重做一次,架构上会改什么
  4. 对照 learn-agent 计划:哪些 Python 前置已就位(打勾),哪些还要补
  5. 下一个 30 天:只写三件事