LinOnward / Agent TutorialAgent Tutorial
章节2 / 18
教程整体目标

通过逐章可运行的增量,理解 Agent 的模型、上下文、任务状态、规划、工具、Skills 与 Agent Loop 等关键组件,并从零构建一个由 Harness 约束、不依赖 Agent 框架的可用 Agent。

01

最小模型调用

建立可替换的模型接口,完成第一次结构化调用。

本页目录
本章任务先离线验证“只回复:模型已连接”,再选择是否用真实 API 重跑。
章节目标CLI 能通过同一 Model 接口运行确定性的离线 Fake Model,并可选择连接真实模型。
开始之前

已经完成第 00 章;真实 API Key 仅用于可选的在线验证。

本章涉及文件
  • package.json
  • .env
  • .gitignore
  • src/model.ts
  • src/fake-model.ts
  • src/model-factory.ts
  • src/index.ts
  • tests/model.test.ts

Step 1安装 OpenAI SDK

shell
pnpm add openai@7.15.0

SDK 只存在于模型适配器中。Harness 不会直接依赖它。

Step 2配置本地环境

真实调用按 token 计费,具体价格和可用模型会随账号与时间变化;运行前查看 OpenAI API Pricing,设置项目预算与用量告警。模型能力要求是:支持文本生成、结构化输出和 function calling,并有足够的上下文窗口。本教程不要求某个固定模型 ID,学习阶段优先选择支持这些能力的低成本模型。

如果要进行在线验证,创建 .env 并填写账号可用的模型 ID。不要把 API Key 写进源码或提交到 Git。

dotenv
OPENAI_API_KEY=your-local-keyOPENAI_MODEL=your-model-id

同时创建 .gitignore

gitignore
.envnode_modulesdist

Step 3完成第一次 Responses API 调用

创建 src/model.ts官方 Responses API 使用 input 接收文本,并可通过 output_text 取得聚合后的文本输出。

typescript
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 中调用它:

typescript
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。

typescript
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、不访问网络,也不产生费用。

typescript
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 却没有真正实现分支:

typescript
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 接口并得到固定回复。