个人 AI 助理后端 · 变更日志与交付总结
| # | 需求 | 状态 | 落地证据 |
|---|---|---|---|
| 1 | 项目骨架(uv / docker-compose PG16 / .env / Makefile) | ✓ | pyproject.toml, docker-compose.yml, Makefile, .env.example |
| 2 | 配置 + DB session + Alembic 初始化 | ✓ | alfred/config.py, alfred/db.py, alembic(4 个 migration) |
| 3 | 实体表 + entity_link + 审计 的 SQLModel 与首迁 | ✓* | 17 张表已建;软删除 archived_at 替代独立审计表(见偏差) |
| 4 | Provider 抽象(LLM/Vision/STT)+ OpenAI + Fake | ✓ | alfred/providers/*,FakeProvider 用于测试 |
| 5 | GET /health, GET /v1/llm-ping | ✓ | alfred/main.py, alfred/router/v1.py |
| 6 | 8 个 Action 原语 | ✓ | alfred/services/executor.py |
| 7 | 技能框架(manifest/registry/render/retry/executor) | ✓ | alfred/skills/_runtime.py, _registry.py |
| 8 | 3 个内置技能(extract_job / capture_note / coffee_chat) | ✓ | alfred/skills/{extract_job,capture_note,coffee_chat} |
| 9 | POST /v1/attachments + STT/vision | ✓ | alfred/router/v1.py, alfred/services/attachments.py |
| 10 | POST /v1/ingest(4 模态)+ 状态查询/重试 | ✓ | alfred/router/v1.py, alfred/ingest.py |
| 11 | 基础 CRUD(companies/positions/people/notes) | ✓ | alfred/router/v1.py |
| 12 | pytest 体系(service / skill / API contract) | ✓ | 9 个测试文件,57 用例全绿 |
archived_at 贯穿所有表extract_job:解析招聘信息 → 公司/职位/笔记capture_note:笔记抽取 + fallbackcoffee_chat:脚本式 + 分块抽取人物/公司/话题并关联
仓库已含 .venv,依赖已装好;无需 API key 即可跑测试(走 FakeProvider)。下面是 PowerShell 命令:
# 1) 跑全部测试(SQLite 内存库 + FakeProvider,无需外部依赖)
.venv\Scripts\python -m pytest -q
# 或用 uv(若已安装)
uv run pytest -q
# 2) 起交互式文档 / 开发服务器(默认 sqlite;接真实 LLM 需 .env 的 OPENAI_API_KEY)
cp .env.example .env # 编辑 OPENAI_API_KEY
.venv\Scripts\python -m alfred.main
# 或:uv run uvicorn alfred.main:app --reload --port 8000
# 打开 http://localhost:8000/docs 查看 OpenAPI
# 3) 接 PostgreSQL(可选)
docker compose up -d db
uv run alembic upgrade head
uv run uvicorn alfred.main:app --reload --port 8000
验证点:测试全绿(57 passed)即代表 service / skill / API contract 三层都通过;
/v1/llm-ping 验证 LLM 连通;/v1/skills/coffee_chat/dry-run 走 dry-run 不落库。
archived_at 软删除。如需 append-only 审计日志请告知。SkillManifest.match 实为单个 MatchRule(inputs/keywords/url_patterns/priority),并非文档写的 kind/intent/overrides。IngestIn 字段是 text,不是文档写的 content。pytest_out.txt(含中途失败快照),已删除以免误导。