这是一个 TypeScript ESM 项目,用 @earendil-works/pi-ai 和 @earendil-works/pi-agent-core 从模型调用逐步构建工具、上下文、memory、workflow、多模型路由以及 Fastify 前后端 Agent 架构。
仓库同时包含两层内容:
exercises/:课程目标、概念和动手任务。src/lessons/:已经完成的可执行课程实现。
要求 Node.js >=22.19.0。
npm install真实 DeepSeek 课程需要项目根目录 .env:
DEEPSEEK_API_KEY=your-key
# 可选,默认 deepseek-v4-flash
DEEPSEEK_MODEL_ID=deepseek-v4-flash将.env.example改成.env,替换成自己的 api_key
运行一个课程:
npm run lesson:agent
npm run lesson:tools
npm run lesson:architecture验证项目:
npm run check普通命令行课程遵循同一条主链:
flowchart LR
CLI["课程 CLI"] --> Config["runtime/deepseek"]
Config --> Agent["Pi Agent"]
Agent --> Provider["DeepSeek provider"]
Agent --> Tools["课程工具"]
Tools --> Transcript["toolResult / transcript"]
Transcript --> Agent
Agent --> Events["Agent events"]
Events --> Output["终端观察输出"]
具体顺序:
dotenv/config加载本地环境变量。- 课程解析 CLI 参数并选择模型。
src/runtime/deepseek.ts校验 key、从 Pi registry 解析模型并提供后端 key resolver。- 课程创建
Agent,注入 system prompt、tools 和 hooks。 agent.prompt()启动 turn;Pi 把上下文转换为 provider request。- 模型可直接回答,或产生 tool call。
- 工具执行结果成为
toolResult,进入 transcript 后触发下一 turn。 AgentEvent被课程观察器记录,用于理解 streaming、工具状态和最终 state。
课程 15 增加浏览器与 Fastify 链路:
flowchart LR
Browser["Browser state"] -->|HTTP command| Fastify["Fastify adapter"]
Fastify --> Session["SessionManager"]
Session --> Runtime["AgentRuntime"]
Runtime --> Agent["Pi Agent"]
Agent --> DeepSeek["DeepSeek"]
Agent --> Tool["Backend tools"]
Agent --> Runtime
Runtime --> Bus["EventEmitter EventBus"]
Bus --> Adapter["BrowserEventProjector"]
Bus --> History["SessionEventStore"]
Adapter --> SSE["SSE listeners"]
SSE --> Browser
Fastify 只处理 HTTP、schema 和 SSE;SessionManager 管理会话,AgentRuntime 封装 Pi Agent 与模型事务,EventEmitter 消息总线负责通知消费者。浏览器只接收经过投影的 JSON 事件,不接触 provider key 或原始 runtime state。
.
├── exercises/ # 00–17 课程讲义与验收目标
├── scripts/ # 本地包路径检查脚本
├── src/
│ ├── lessons/ # 每课的场景、工具、README;课程 15 含完整 Fastify 服务
│ ├── observability/ # 事件计数、压缩和终端增量输出
│ ├── runtime/ # provider 配置与通用消息构造
│ └── utils/ # 基础打印与 Pi 输出展示
└── tests/ # faux provider 与确定性模块测试
deepseek.ts 是 DeepSeek 配置模块,负责:
- 校验
DEEPSEEK_API_KEY。 - 解析并验证模型 id。
- 提供只在后端使用的 key resolver。
- 生成统一的模型摘要。
messages.ts 负责构造标准 user message,避免 session、workflow 和测试重复 provider message 结构。
agent-output.ts 放置不改变业务状态的观察 helper:assistant delta 输出、事件计数、连续事件压缩和文本截断。它只用于教学输出,不参与 Agent 决策。
| 模块 | 功能与职责 |
|---|---|
agent/ |
AgentRuntime abstract interface、Pi Agent adapter 和工具。 |
session/ |
SessionManager abstract interface 与内存 session adapter。 |
messaging/ |
abstract event bus/store,以及基于 node:events 的 EventEmitter adapter。 |
consumer/ |
Browser event projector、前端 reducer 和 Fastify/SSE adapter。 |
bootstrap.ts |
唯一 composition root,选择并连接所有具体 adapter。 |
start-server.ts |
加载配置、监听 HOST/PORT、注册 SIGINT/SIGTERM 优雅退出。 |
这些 seam 让 Agent runtime、session、消息通知、浏览器协议和 HTTP transport 可以独立替换和测试。
每个课程目录只保留与该课概念有关的实现:
- executable 文件保持
load config -> await main()的清晰顶层流程; - 课程工具放在同目录
*-tools.ts; - 可复用策略单独成模块,例如
context-manager.ts、model-router.ts; - 课程 README 解释观察点和设计取舍。
测试尽量跨越与调用方相同的 interface,而不是检查实现内部:
context-manager.test.ts:上下文压缩与最近消息预算。recovery-tool.test.ts:失败恢复和 operation-id 幂等。model-routing.test.ts:fast/strong faux 模型的真实调用顺序。frontend-backend-architecture.test.ts:session 隔离、浏览器事件、Fastify schema 与 key 零泄漏。
| 命令 | 课程主题 | Provider |
|---|---|---|
npm run lesson:deepseek |
pi-ai 首次调用与 payload |
DeepSeek |
npm run lesson:agent |
Agent 多轮基础 | DeepSeek |
npm run lesson:tools |
工具定义与执行 | DeepSeek |
npm run lesson:debug |
events、state、abort | DeepSeek |
npm run lesson:context |
app messages 与 LLM context | DeepSeek |
npm run lesson:mini-research |
复杂研究模式 | DeepSeek |
npm run lesson:memory |
memory 读取、候选与写入 | DeepSeek |
npm run lesson:policy |
tool policy、block 与审计 | DeepSeek |
npm run lesson:workflow |
search → read → synthesize | DeepSeek |
npm run lesson:compaction |
context budget 与摘要 | DeepSeek |
npm run lesson:recovery |
throw、continue 与幂等 | Faux |
npm run lesson:routing |
fast/strong 多模型路由 | Faux |
npm run lesson:architecture |
启动课程 15 Fastify HTTP 服务 | DeepSeek |
完整学习顺序见 exercises/README.md,单课运行参数见对应 src/lessons/<number>/README.md。
项目刻意区分三类状态:
- Agent transcript:user、assistant、toolResult,用于模型继续推理与恢复。
- 浏览器显示状态:streaming draft、loading、tool panel、连接状态,可由事件重建。
- 应用 store:memory、source details、session metadata 和持久化 checkpoint。
transformContext 决定本轮 Agent 应该记住什么;convertToLlm 决定应用消息如何转成 provider 消息;浏览器协议决定哪些运行信息可以公开给 UI。这三处职责不要混合。
新增 executable lesson 时:
- 在
src/lessons/<number>/创建入口和 README。 - 顶层只加载配置并
await main()。 - 优先使用
src/runtime/与src/observability/的共享能力。 - 课程专属工具和策略留在课程目录。
- 两个以上调用方需要相同行为时,再把它提升为共享模块。
- 使用 faux provider 测应用协议,真实 DeepSeek 只用于必要的集成冒烟。
- provider key 只在后端
getApiKeyresolver 中使用。 - 浏览器
sessionId必须配合用户鉴权,不能作为唯一访问凭证。 - SSE 生产部署需要 event id、断点续传、背压和跨实例 session 路由。
- 有副作用的工具使用 operation id 保证幂等。
- 当前 session 和 event history 为内存实现;生产系统需要 TTL、持久化和容量上限。