教程整体目标
通过逐章可运行的增量,理解 Agent 的模型、上下文、任务状态、规划、工具、Skills 与 Agent Loop 等关键组件,并从零构建一个由 Harness 约束、不依赖 Agent 框架的可用 Agent。
最小模型调用
建立可替换的模型接口,完成第一次结构化调用。
预计 20 分钟本页目录
已经完成第 00 章;真实 API Key 仅用于可选的在线验证。
package.json.env.gitignoresrc/model.tssrc/fake-model.tssrc/model-factory.tssrc/index.tstests/model.test.ts
Step 1安装 OpenAI SDK
pnpm add openai@7.15.0SDK 只存在于模型适配器中。Harness 不会直接依赖它。
Step 2配置本地环境
真实调用按 token 计费,具体价格和可用模型会随账号与时间变化;运行前查看 OpenAI API Pricing,设置项目预算与用量告警。模型能力要求是:支持文本生成、结构化输出和 function calling,并有足够的上下文窗口。本教程不要求某个固定模型 ID,学习阶段优先选择支持这些能力的低成本模型。
如果要进行在线验证,创建 .env 并填写账号可用的模型 ID。不要把 API Key 写进源码或提交到 Git。
OPENAI_API_KEY=your-local-keyOPENAI_MODEL=your-model-id同时创建 .gitignore:
.envnode_modulesdistStep 3完成第一次 Responses API 调用
创建 src/model.ts。官方 Responses API 使用 input 接收文本,并可通过 output_text 取得聚合后的文本输出。
import OpenAI from "openai";const client = new OpenAI();export async function askModel(input: string): Promise<string> { const model = process.env.OPENAI_MODEL; if (!model) throw new Error("缺少 OPENAI_MODEL"); const response = await client.responses.create({ model, input }); return response.output_text;}在 src/index.ts 中调用它:
import { askModel } from "./model.js";const task = process.argv.slice(2).join(" ").trim();if (!task) throw new Error("请提供任务");console.log(await askModel(task));Step 4抽出最小 Model 接口
用下面内容替换 src/model.ts 的临时实现,为后面的假模型测试和提供商替换留下边界。Client 只能在选择真实模式后创建,不能留在模块顶层,否则 Fake 模式仅仅 import 工厂也可能要求 API Key。
import OpenAI from "openai";export interface Model { generate(input: string): Promise<string>;}export function createOpenAIModel(): Model { const client = new OpenAI(); return { async generate(input) { const model = process.env.OPENAI_MODEL; if (!model) throw new Error("缺少 OPENAI_MODEL"); const response = await client.responses.create({ model, input }); return response.output_text; }, };}Step 5先用 Fake Model 离线验证
创建 src/fake-model.ts。Fake Model 是确定性的系统边界替身,不读取 API Key、不访问网络,也不产生费用。
import type { Model } from "./model.js";export class FakeModel implements Model { constructor(private readonly replies: string[]) {} async generate(_input: string): Promise<string> { const reply = this.replies.shift(); if (!reply) throw new Error("fake_model_has_no_reply"); return reply; }}最后在 src/model-factory.ts 建立唯一装配入口,避免 CLI 只描述 MODEL_MODE 却没有真正实现分支:
import { FakeModel } from "./fake-model.js";import { createOpenAIModel, type Model } from "./model.js";export function createModel(fakeReplies: string[] = ["模型已连接"]): Model { const mode = process.env.MODEL_MODE ?? "fake"; if (mode === "fake") return new FakeModel(fakeReplies); if (mode === "openai") return createOpenAIModel(); throw new Error(`不支持的 MODEL_MODE: ${mode}`);}src/index.ts 调用 createModel(),再把任务交给返回的接口。只有 MODEL_MODE=openai 才走真实适配器;默认离线路径不读取 Key、不访问网络。在 tests/model.test.ts 注入 new FakeModel(["模型已连接"]),断言输入经过相同 Model 接口并得到固定回复。