
一.Typescript1.TS 项目初始化初始化项目目录与 package.json# 创建项目文件夹 mkdir ai-frontend-mastery # 进入项目目录 cd ai-frontend-mastery # 快速生成package.json所有选项默认 npm init -y可指定 package.json中TS和zod的版本然后下载安装核心开发依赖推荐使用 pnpm速度更快、磁盘占用更低也可使用 npm# pnpm方式推荐 pnpm add -D typescript types/node tsx # npm方式 npm install -D typescript types/node tsx编译项目生成并配置 tsconfig.json# 自动生成tsconfig.json文件 npx tsc --init修改定 package.json执行npm run build监听模式核心配置文件tsconfig.json这是 AI 开发中最重要的配置文件错误的配置会导致大量第三方库如 LangChain、OpenAI无法正常使用。{ compilerOptions: { /* 基础配置 - AI后端专属 */ target: ESNext, // AI后端通常运行在较新的Node版本直接输出现代语法性能更好 module: NodeNext, // 关键配合package.json的type: module彻底解决ESM兼容问题 moduleResolution: NodeNext, // 关键确保能正确解析LangChain等纯ESM库 outDir: ./dist, // 编译输出目录 rootDir: ./src, // 源码目录 /* 严格模式 - AI全栈的生命线 */ strict: true, // 开启所有严格检查 noImplicitAny: true, // 拒绝隐式any逼迫你写出高质量的Prompt接口定义 strictNullChecks: true, // 防止数据处理中的Cannot read property of undefined运行时错误 /* 开发体验优化 */ skipLibCheck: true, // 跳过node_modules的类型检查显著提升编译速度 esModuleInterop: true, // 允许默认导入非ESM模块 resolveJsonModule: true, // 允许直接import JSON文件加载配置文件时很常用 forceConsistentCasingInFileNames: true // 防止文件名大小写导致的跨平台构建失败 }, include: [src/**/*], exclude: [node_modules] }关键配置解析module: NodeNext这是 AI 开发中最容易出错的配置。很多同学在引入 langchain 或 openai 库时报错90% 是因为这个没配对。现代 AI 栈强烈依赖 ESM 规范。strict: true在 AI 开发中类型安全尤为重要。大模型返回的数据结构非常动态严格的类型检查能帮你在编译阶段就发现大量潜在错误。调整 package.json为了让 Node.js 识别现代 ES Module 语法使用import而不是require必须在 package.json 中显式声明。{ name: ai-frontend-mastery, type: module, // --- 必须添加声明这是一个ESM项目 scripts: { dev: tsx watch src/index.ts, // 监听模式修改代码自动重启 build: tsc, start: node dist/index.js }, devDependencies: { typescript: ^5.0.0, types/node: ^20.0.0, tsx: ^4.0.0 } }脚本说明npm run dev开发环境使用tsx 会自动监听文件变化并重启服务无需手动编译npm run build生产环境构建将 TypeScript 编译为 JavaScriptnpm run start运行构建后的生产代码推荐目录结构构建一个清晰的目录结构为后续的 Zod Schema 和 LangGraph 逻辑做准备。ai-frontend-mastery/ ├── src/ │ ├── config/ # 存放环境变量、AI模型配置 │ ├── schemas/ # 存放Zod定义Structured Output的核心 │ ├── agents/ # 存放LangGraph智能体逻辑 │ ├── utils/ # 工具函数 │ └── index.ts # 入口文件 ├── package.json ├── tsconfig.json └── .env # 稍后用于存放API Key验证环境编写测试代码验证环境是否正常工作。// src/index.ts interface AIConfig { modelName: string; temperature: number; tags: string[]; } // 模拟一个简单的AI配置对象 const config: AIConfig { modelName: gpt-4o, temperature: 0.7, tags: [coding, assistant] }; console.log( AI Environment Ready! Model: ${config.modelName}); console.log(Running in ${process.env.NODE_ENV || development} mode);运行项目npm run dev如果控制台输出了 AI Environment Ready! ...说明 TypeScript 基础环境已经搭建完毕。2.AI 开发必备 TypeScript 核心语法在 AI 应用开发尤其是 LangChain/LangGraph 源码级开发中你需要处理极其动态的数据结构。本节聚焦 4 个构建 AI 架构最核心的概念掌握这些你才能看懂 AI 框架的源码才能写出优雅的 Zod Schema。1. 核心基础类型在 AI 开发中我们最常打交道的不是简单的字符串而是浮点数温度、概率和向量数组。// 1. 基础原始类型 const modelName: string gpt-4o-mini; const temperature: number 0.7; // 浮点数也是number const isStream: boolean true; // 2. 数组Array- 处理Prompt列表或Tags const stopSequences: string[] [User:, AI:]; // 或者使用泛型写法 const tags: Arraystring [coding, general]; // 3. 元组Tuple- AI开发中极为重要 // 场景向量数据库的Embedding或者是坐标 // 这是一个固定长度、固定类型的数组 const embeddingVector: [number, number, number] [0.12, 0.98, -0.55]; // 4. Any vs Unknown面试官考点 // ❌ try not to use any放弃类型检查甚至会污染后续代码 let badData: any hello; badData 123; // 编译器闭嘴了但运行时可能炸 // ✅ use unknown表示我暂时不知道这是啥但在使用前我必须检查 let apiData: unknown fetchResponse(); if (typeof apiData string) { console.log(apiData.toUpperCase()); // 安全 }2. 接口Interfacevs 类型别名Type这是新手最纠结的问题。在 99% 的应用开发场景下它们可以混用。核心区别Interface接口主要用于定义对象的形状Shape支持继承extends更像面向对象Type类型别名更灵活可以定义对象也可以定义联合类型Union Types实战推荐定义数据模型用interface定义复杂组合逻辑用type。// 定义一个对象结构 interface User { id: string; name: string; } // 继承扩展 interface AdminUser extends User { permissions: string[]; } // 定义一个简单的字面量联合类型Type擅长的领域 // 限制model只能是这两个字符串之一防止拼写错误 type SupportedModel gpt-3.5-turbo | gpt-4;3. 函数类型定义函数是逻辑的载体。在 TS 中我们需要约束入参和返回值。// 基础写法 function calculateCost(tokens: number, pricePerK: number): number { return (tokens / 1000) * pricePerK; } // 箭头函数写法 const buildPrompt (system: string, user: string): string { return ${system}\n\nHuman: ${user}\nAI:; }; // 异步函数返回值自动推导为Promise async function fetchCompletion(): Promisestring { return AI response...; }4. 两个兜底符号? 和处理 AI 接口返回的数据时这两个符号能极大减少代码量。可选属性?有些字段 API 不一定返回比如 usage 统计字段。interface AIResponse { content: string; usage?: { // 加了问号表示该字段可能是undefined tokens: number; }; } const res: AIResponse { content: Hi }; // 不写usage也不报错 // 可选链Optional Chaining- 安全访问 // 如果usage不存在直接返回undefined而不会报错crash console.log(res.usage?.tokens);非空断言!当你比 TS 编译器更清楚 这个变量一定有值 时使用。慎用const apiKey process.env.OPENAI_API_KEY; // TS可能会报错说apiKey可能是undefined // 如果你确定环境变量里配了可以加!强制通过 const client new OpenAI({ apiKey: apiKey! });3. AI 开发必备 TypeScript 高级特性泛型Generics打造通用的 AI 交互协议泛型是 TypeScript 中的 **参数化类型**允许你在定义类型时不指定具体类型而是在使用时动态传入。核心痛点LLM 的返回结果往往有固定的外层结构如status、usage、latency但核心的data字段千变万化可能是文章摘要、代码生成结果、工具调用参数等。如果为每种返回结果都写一个完整的接口代码会极度冗余且难以维护。实战场景定义通用的 LLM 响应包装器// T 代表具体的数据结构默认any但强烈建议传入具体类型 interface LLMResponseT { status: success | error; latency: number; // 接口耗时 usage: { promptTokens: number; completionTokens: number; }; // 核心data的类型由使用时决定 data: T; } // 定义一个具体的业务数据结构文章摘要 interface ArticleSummary { title: string; summary: string; keywords: string[]; } // 使用泛型让TS自动推导data必须符合ArticleSummary结构 function handleAIResponse(res: LLMResponseArticleSummary) { // 此处输入res.data.时编辑器会自动提示title、summary、keywords字段 console.log(res.data.title); console.log(res.data.keywords.join(, )); }工具类型Utility Types精准控制上下文ContextAI 上下文窗口Context Window是极其昂贵的资源。我们经常需要对现有数据类型进行裁剪或弱化只把必要的字段发给 LLM或者处理流式输出的残缺数据。TypeScript 内置了一系列工具类型专门用于快速转换已有类型无需重复定义。实战场景 1发送给 AI 的上下文剔除敏感字段使用OmitT, K工具类型从类型 T 中删除指定的 K 属性。interface UserProfile { id: string; name: string; email: string; // 隐私数据不发给AI passwordHash: string; // 敏感数据绝不可发 preferences: string[]; } // 从UserProfile中删除passwordHash和email字段 type AIContextProfile OmitUserProfile, passwordHash | email; // 现在AIContextProfile只包含id、name、preferences三个字段 const userContext: AIContextProfile { id: user_123, name: 张三, preferences: [编程, AI] };实战场景 2处理流式输出Streaming使用PartialT工具类型将类型 T 的所有属性变为可选?。 因为 AI 是一个字一个字吐出来的解析中间状态的 JSON 时很多字段可能还没生成完成。// 基于上面的AIContextProfile type StreamingProfile PartialAIContextProfile; // 流式输出的中间状态只有name字段生成了其他字段还没到 const currentStream: StreamingProfile { name: User, // preferences可能还没生成出来不会报错 };其他 AI 开发常用工具类型PickT, K与 Omit 相反只保留类型 T 中的 K 属性RequiredT将类型 T 的所有属性变为必选ReadonlyT将类型 T 的所有属性变为只读联合类型与可辨识联合Discriminated UnionsAgent 的状态机这是LangChain/LangGraph 处理消息历史的核心模式。一个智能体的消息可能是用户的输入、AI 的回复也可能是工具Tool调用的结果。如何在类型层面安全地区分它们原理可辨识联合要求联合类型中的每个成员都包含一个相同名称的字面量类型字段称为 可辨识特征 或 标签TypeScript 可以通过这个字段自动收窄类型。实战场景区分不同角色的 Chat Messageinterface UserMessage { role: user; // 核心可辨识的特征字段Discriminant content: string; } interface AIMessage { role: assistant; content: string | null; tool_calls?: any[]; // AI可能会调用工具 } interface ToolMessage { role: tool; tool_call_id: string; // 对应AI的tool_call_id content: string; // 工具执行的结果 } // 定义消息总类型 type ChatMessage UserMessage | AIMessage | ToolMessage; // 渲染不同类型的消息 function renderMessage(msg: ChatMessage) { // TS的强大之处通过判断role自动收窄类型 if (msg.role tool) { // 在这个代码块里msg被自动推导为ToolMessage类型 // 可以安全访问msg.tool_call_id而不用担心报错 console.log(Tool Output [${msg.tool_call_id}]: ${msg.content}); } else if (msg.role assistant) { // 在这个代码块里msg被自动推导为AIMessage类型 console.log(AI: ${msg.content}); if (msg.tool_calls) { console.log(AI调用了${msg.tool_calls.length}个工具); } } else { // 在这个代码块里msg被自动推导为UserMessage类型 console.log(User: ${msg.content}); } }4、typeof 与类型推导为 Zod 做铺垫typeof操作符是实现这一模式的基础它可以从一个已有的值中推导出它的类型。实战场景从配置对象推导类型// 假设这是一个配置对象在Zod中这将是一个Schema const defaultModelConfig { model: gpt-4o, temperature: 0.7, maxTokens: 4096, features: { streaming: true, jsonMode: true } }; // 不需要手动写interface ModelConfig { ... } // 直接让TS帮我们不费吹灰之力推导出来 type ModelConfig typeof defaultModelConfig; // 此时ModelConfig的类型已经是精确的结构了 function initModel(config: ModelConfig) { // 传入的config必须和defaultModelConfig的结构完全一致 console.log(初始化模型${config.model}); console.log(温度${config.temperature}); } // 正确调用 initModel(defaultModelConfig); // 错误调用会在编译阶段报错 // initModel({ model: gpt-3.5-turbo, temperature: 0.7 }); // temperature类型错误二. Zod 基础与进阶AI 开发的运行时类型安全屏障Zod 是AI 开发中不可或缺的核心工具它解决了 TypeScript 最大的痛点类型只存在于编译阶段无法阻挡运行时错误。在大模型返回数据不可靠的 AI 场景下Zod 是保护你应用不崩溃的最后一道防线。传统 Web 开发 vs AI 开发的本质区别传统 Web 开发后端 API 返回通常是确定的类型和字段基本固定AI 开发大模型是概率模型它可能返回错误的字段类型比如你要数字它返回字符串 100缺失的字段多余的无关字段甚至是一段只有一半的无效 JSONZod 的核心哲学先定义 Schema数据蓝图再自动推导出 TS 类型这彻底解决了新手最容易犯的错误写一遍 Interface再写一遍 Validation 逻辑。这种做法不仅累还极易导致两边不一致引入难以排查的 bug。1.Zod 基础单一数据源Single Source of Truth安装 Zodnpm install zod # 或者 pnpm add zod2. 定义 Schema既是验证逻辑也是类型定义Schema 定义了数据的结构、类型和验证规则是 Zod 的核心。import { z } from zod; // 定义简历信息SchemaAI提取简历信息场景 const ResumeSchema z.object({ name: z.string().min(2, 名字太短了), // 字符串且至少2个字符 age: z.number().int().positive(), // 必须是正整数 skills: z.array(z.string()), // 字符串数组 isOpenToWork: z.boolean().default(true), // 默认为true // .optional()表示该字段AI可能不返回 linkedIn: z.string().url().optional(), });3. 自动推导 TypeScript 类型魔法发生的地方不需要手动写interface Resume { ... }Zod 会自动从 Schema 中推导出完全一致的 TypeScript 类型。// 从Schema自动推导类型 type Resume z.infertypeof ResumeSchema; // 此时Resume类型完全等同于 // interface Resume { // name: string; // age: number; // skills: string[]; // isOpenToWork: boolean; // linkedIn?: string | undefined; // }4. 运行时校验.parse () vs .safeParse ()当拿到 LLM 的响应通常是 JSON 字符串后我们需要验证它是否符合预期。Zod 提供了两种校验方式方法行为适用场景.parse(data)验证失败会直接抛出错误Throw Error你确定数据必须正确否则程序无法运行的场景.safeParse(data)AI 开发首选。不会抛出错误而是返回一个包含success状态的对象所有 AI 接口调用场景大模型返回的数据永远不可信// 模拟一个AI返回的脏数据年龄是字符串而不是数字 const aiResponse { name: Alex, age: 28, // 错误类型 skills: [React, AI] }; // 使用safeParse进行校验 const result ResumeSchema.safeParse(aiResponse); if (!result.success) { // 优雅降级处理错误或者让AI重试 console.error(AI格式错误:, result.error.format()); } else { // 这里的data已经是被TS认可的Resume类型 // 编辑器会自动提示所有字段类型安全有保障 console.log(验证通过:, result.data.name); console.log(技能:, result.data.skills.join(, )); }Zod 进阶专门为 AI 场景打造的核心技巧在对接 OpenAI Function Calling 或 Structured Outputs 时以下几个 Zod 特性是必学的。1. z.coerce宽容模式自动纠错大模型经常会犯低级错误比如你想要数字100它给你返回字符串100你想要布尔值true它给你返回字符串true。使用z.coerce可以让 Zod 尝试自动转换这些错误类型大大提高校验成功率。const FlexibleSchema z.object({ // 如果输入是100会自动转为数字100 age: z.coerce.number(), // 如果输入是true会自动转为布尔值true isActive: z.coerce.boolean() }); // 测试字符串28会被自动转为数字28 const result FlexibleSchema.safeParse({ age: 28, isActive: true }); console.log(result.success); // true console.log(result.data.age); // 28 (number类型)2. z.describe ()用代码写 Prompt这是Zod 在 AI 领域最强大的功能。你可以给字段添加描述这些描述会被转换成 JSON Schema 发送给 AI告诉 AI 这个字段的含义和要求。const SentimentSchema z.object({ score: z.number() .min(0).max(10) .describe(情感打分0分代表极度负面10分代表极度正面), // 给AI看的说明书 reasoning: z.string() .describe(简短解释为什么给出这个分数不超过50个字) });当我们配合 LangChain 或 OpenAI 使用时AI 不仅知道要返回number类型还能读懂.describe()里的业务逻辑从而大幅提高输出的准确率。3. 结构化输出Structured Output实战OpenAI 原生支持OpenAI 和 Vercel AI SDK 现在的标准做法是将 Zod Schema 直接转换为 JSON Schema强制大模型按照指定格式输出。这是目前 AI 开发中处理结构化输出的最佳实践能让解析成功率接近 100%。import { zodResponseFormat } from openai/helpers/zod; import OpenAI from openai; const openai new OpenAI(); // 定义步骤Schema const StepSchema z.object({ step_title: z.string(), code_snippet: z.string(), }); // 定义教程Schema const TutorialSchema z.object({ title: z.string(), steps: z.array(StepSchema) }); // 调用OpenAI时直接使用 const completion await openai.chat.completions.create({ model: gpt-4o, messages: [ { role: user, content: 写一个使用React Hooks的简单计数器教程 } ], // 核心强制AI必须按照Zod的定义输出 response_format: zodResponseFormat(TutorialSchema, tutorial_generation), }); const rawContent completion.choices[0].message.content; // 此时parse几乎100%成功因为AI被Zod约束住了 const tutorial TutorialSchema.parse(JSON.parse(rawContent!)); console.log(教程标题:, tutorial.title); console.log(步骤数:, tutorial.steps.length);4. 验证器Refinement编写自定义业务逻辑有时候简单的类型检查不够用我们需要添加自定义的业务逻辑验证比如 结束时间必须晚于开始时间。const DateRangeSchema z.object({ start: z.string().date(), end: z.string().date() }) .refine((data) { // 自定义验证逻辑结束时间必须晚于开始时间 return new Date(data.end) new Date(data.start); }, { message: 结束时间不能早于开始时间, // 错误信息 path: [end] // 错误标记在哪个字段上 }); // 测试结束时间早于开始时间会触发自定义错误 const result DateRangeSchema.safeParse({ start: 2024-01-01, end: 2023-12-31 }); if (!result.success) { console.log(result.error.format().end?._errors); // [结束时间不能早于开始时间] }三. 智能用户信息提取实战Ollama LangChain Zod这是 AI 开发中最经典、最实用的场景之一从用户的自然语言输入中精准提取结构化的用户画像信息。我们将使用本地大模型 Ollama无需 API 密钥配合 LangChain 和 Zod构建一个工业级的信息提取智能体。架构1. 实战目标从用户的自然语言描述如 我是合一今年 18 岁想找个工作看看机会中自动提取出姓名、年龄、潜在意图等结构化信息并保证类型安全。2. 工业级流水线架构我们的智能体将遵循以下标准流程这也是所有 AI 信息提取系统的通用架构步骤名称作用1Input接收用户的自然语言输入2LLM Brain加载本地大模型如 Qwen/Llama负责理解语义3Zod Schema强制模型按照我们定义的结构思考和输出4Validation自动校验数据类型和格式过滤脏数据5Output返回类型安全的 JSON 对象供前端直接渲染或后端入库. 前置准备安装并运行 Ollama从Ollama 官网下载安装然后拉取对 JSON 支持较好的模型ollama pull qwen2.5:7b # 或者使用llama3 # ollama pull llama3安装依赖包npm install langchain/ollama zod # 或者 pnpm add langchain/ollama zodimport { ChatOllama } from langchain/ollama; import { z } from zod; // // Step 1: 定义数据契约The Contract // // 这里的describe非常关键它就是写给AI看的Prompt const PersonInfoSchema z.object({ name: z.string().describe(用户的姓名如果是昵称也提取), age: z.number().int().describe(用户的年龄必须是数字), // 增加意图推断字段展示Zod的强大能力 intent: z.enum([交友, 求职, 闲聊]) .describe(根据用户语气判断其潜在意图) .optional(), }); // 自动推导TypeScript类型供后续业务逻辑使用 type PersonInfo z.infertypeof PersonInfoSchema; // // Step 2: 构建智能体The Agent // const createExtractionAgent async () { // 1. 初始化本地大模型 const model new ChatOllama({ model: qwen2.5:7b, // 建议使用较新的模型以获得更好的指令遵循能力 temperature: 0, // 设为0因为我们需要精确提取不需要发散创造 }); // 2. 绑定结构化输出Structured Output // 这是LangChain现代版本最核心的API // 它会自动将Zod Schema转为Function Calling或JSON Schema发给模型 const structuredLlm model.withStructuredOutput(PersonInfoSchema); return structuredLlm; }; // // Step 3: 执行与验证Execution // const invoke async () { try { console.log( Agent 启动中...); const agent await createExtractionAgent(); // 模拟用户输入 const userInput 你好呀我是合一我今年刚好18岁想找个工作看看机会。; console.log( 用户输入: ${userInput}); // 调用AI // 这里的res已经被TS自动推导为PersonInfo类型 const res await agent.invoke(userInput); // 此时res就是纯粹的JS对象可以直接入库或渲染 console.log(\n✅ 提取成功 (Structured Data):); console.log(JSON.stringify(res, null, 2)); // 演示类型安全的使用 if (res.age 18) { console.log(⚠️ 提示: 用户未成年); } else { console.log( 欢迎 ${res.name} 加入社区! 检测意图: ${res.intent}); } } catch (error) { console.error(❌ 提取失败:, error); } }; // 运行智能体 invoke();1. 为什么使用withStructuredOutput而不是简单的 Prompt在旧版本的 LangChain 中我们需要手动在 Prompt 中写 请返回 JSON 格式...但这种方式有很多问题模型经常不遵守格式返回无效 JSON需要手动处理JSON.parse和异常没有类型安全保障现代最佳实践使用model.withStructuredOutput(schema)原理它利用了 LLM 的 Tool Calling工具调用或 JSON Mode 能力这是模型原生支持的功能优势极大降低了模型 胡说八道 的概率自动完成了JSON.parse和Zod.parse的过程直接返回类型安全的对象无需额外处理2. Zod Schema 在其中的作用作为数据契约同时定义了 AI 的输出格式和 TypeScript 类型作为 Prompt 的一部分.describe()方法的内容会被发送给 AI告诉它每个字段的含义作为验证器即使模型返回了错误的数据Zod 也会在运行时捕获并抛出错误3. 这里的 LangGraph 体现在哪里虽然上述代码是一个单一的 Chain但在复杂的 LangGraph 应用中这个invoke过程就是一个标准的Node节点。你可以很容易地将其扩展为一个完整的工作流Node A提取器运行上述代码提取用户信息Edge判断逻辑如果age 18跳转到 Node B拒绝服务否则跳转到 Node C推荐职位Node B拒绝服务返回 抱歉我们的服务仅限成年人使用Node C推荐职位根据用户的意图和技能推荐合适的职位这正是 AI Agent 能够处理复杂业务逻辑的基础。