十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TypeScript工程化AI应用开发:从配置管理到流式响应实战

TypeScript工程化AI应用开发:从配置管理到流式响应实战 1. 从“Hello World”到“Hello AI”为什么你的第一个AI调用项目需要从工程化开始如果你和我一样是个对AI应用开发充满好奇的前端或全栈开发者当看到ChatGPT API、Claude API或者各种图像生成模型时第一反应可能就是赶紧写几行代码调一下试试这种冲动我太理解了几年前我第一次接触OpenAI的API时也是直接打开Node.js的REPL用几行fetch就开干了。结果呢代码跑通了一张猫的图片生成了我兴奋了五分钟然后问题接踵而至API Key硬编码在代码里怎么办不同环境开发、测试、生产的配置怎么管理请求失败了怎么优雅重试日志怎么打代码结构一团糟根本没法给别人看更别提后续维护和扩展了。这就是为什么我强烈建议你的“第一个AI调用”项目不要从裸写fetch或axios开始而是从一个规范的TypeScript工程初始化与配置管理起步。这听起来可能有点“杀鸡用牛刀”但请相信我这是让你从“玩具代码”迈向“可维护、可协作、可扩展的生产级应用”最关键的一步。今天我们就来手把手搭建一个专为AI应用设计的TypeScript工程脚手架。这不是一个简单的“Hello World”而是一个为你未来所有AI实验和产品打下坚实基础的工程化起点。我们会用到TypeScript、ESLint、Prettier、Husky、环境变量管理、结构化日志等现代前端工程标配并特别针对AI API调用的特性如流式响应、长超时、错误处理进行优化配置。2. 项目初始化不止于npm init很多人觉得初始化项目就是npm init -y然后开始写代码。但对于一个严肃的项目尤其是涉及敏感API密钥的AI应用我们需要考虑得更多。2.1 创建项目与基础包管理首先创建一个干净的目录并初始化package.json。这里我建议不要用-y而是手动填写一些基本信息这对后续的模块发布、文档生成都有好处。mkdir my-ai-project cd my-ai-project npm init在交互式命令行中合理填写项目名、描述、入口文件我们使用dist/index.js、作者等信息。完成后我们安装最核心的依赖TypeScript。作为开发依赖安装因为它只在编译阶段需要。npm install -D typescript types/nodetypes/node非常重要它提供了Node.js环境下的类型定义让我们在写process.env、fs模块等代码时能有完善的类型提示。接下来生成TypeScript的配置文件tsconfig.json。虽然可以用npx tsc --init生成一个默认配置但那个配置包含大量注释且很多选项不适用于现代Node.js项目。我更喜欢创建一个精简且针对性的配置。// tsconfig.json { compilerOptions: { target: ES2022, module: CommonJS, lib: [ES2022], outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: true, declarationDir: ./dist/types }, include: [src/**/*], exclude: [node_modules, dist, **/*.test.ts] }为什么这样配置target: ES2022现代Node.js版本如18对ES2022有很好的支持使用新语法能让代码更简洁。module: CommonJS虽然ES Modules是趋势但当前Node.js生态特别是很多工具链对CommonJS支持更稳定。等未来更加成熟再迁移也不迟。outDir rootDir严格区分源代码src和编译输出dist保持项目结构清晰。declaration: true生成.d.ts类型声明文件。如果你的AI工具函数或类未来可能被发布为NPM包供他人使用这将非常有用。resolveJsonModule: true允许直接导入JSON文件常用于读取配置文件。2.2 代码规范与格式化工具链ESLint Prettier混乱的代码格式和潜在的代码异味是项目腐化的开始。在项目初期就集成代码检查与格式化工具成本最低收益最大。首先安装ESLint及其相关插件。我们选择TypeScript生态最主流的配置方案。npm install -D eslint typescript-eslint/parser typescript-eslint/eslint-plugin创建ESLint配置文件.eslintrc.js使用JS格式可以添加注释更灵活// .eslintrc.js module.exports { parser: typescript-eslint/parser, plugins: [typescript-eslint], extends: [ eslint:recommended, plugin:typescript-eslint/recommended, ], env: { node: true, es2022: true }, rules: { // 这里可以覆盖或添加个人/团队的偏好规则 typescript-eslint/no-unused-vars: [warn, { argsIgnorePattern: ^_ }], no-console: off, // AI项目调试时console.log很常用建议关闭此规则或设为warn }, ignorePatterns: [dist, node_modules] };接着安装并配置Prettier用于代码格式化。ESLint负责代码质量Prettier负责代码风格。npm install -D prettier eslint-config-prettier eslint-plugin-prettiereslint-config-prettier关闭ESLint中所有与Prettier冲突的规则。eslint-plugin-prettier将Prettier作为ESLint规则来运行。更新.eslintrc.js集成Prettier// .eslintrc.js module.exports { parser: typescript-eslint/parser, plugins: [typescript-eslint, prettier], extends: [ eslint:recommended, plugin:typescript-eslint/recommended, plugin:prettier/recommended, // 必须放在最后用于覆盖格式相关规则 ], // ... 其他配置不变 };创建.prettierrc.js配置文件定义团队的代码风格// .prettierrc.js module.exports { semi: true, trailingComma: es5, singleQuote: true, printWidth: 100, tabWidth: 2, endOfLine: lf };现在你可以在package.json中添加几个脚本方便地检查和修复代码// package.json 的 scripts 部分 scripts: { lint: eslint src --ext .ts, lint:fix: eslint src --ext .ts --fix, format: prettier --write \src/**/*.ts\, check: npm run lint npm run format }2.3 Git提交前自动化用Husky守住质量门禁手动运行npm run lint很容易被忘记。我们需要在代码提交到Git仓库前自动执行这些检查。Husky可以让我们方便地管理Git钩子。npm install -D husky lint-staged初始化Husky并设置pre-commit钩子npx husky init这会在项目根目录创建.husky文件夹并生成pre-commit钩子文件。我们需要修改它使其在提交前只对暂存区staged的文件运行检查而不是整个项目这样更快。这就是lint-staged的用武之地。在package.json中配置lint-staged// package.json { // ... lint-staged: { src/**/*.ts: [ eslint --fix, prettier --write ] } }然后修改.husky/pre-commit文件#!/usr/bin/env sh . $(dirname -- $0)/_/husky.sh npx lint-staged这样每次你执行git commit时Husky都会自动触发lint-staged对本次提交涉及的TypeScript文件先进行ESLint修复和Prettier格式化只有全部通过提交才会成功。这确保了仓库中的代码始终符合规范。3. 配置管理安全地处理你的AI命脉——API密钥AI应用的核心是调用远程API而API密钥API Key就是通行证。如何管理这些敏感配置是工程化路上第一个严肃的挑战。3.1 环境变量与.env文件绝对不要硬编码绝对不要将API密钥直接写在源代码里尤其是打算开源或团队协作的项目。最普遍的做法是使用环境变量。dotenv这个库可以帮助我们从.env文件中加载环境变量到process.env。npm install dotenv在项目根目录创建.env文件并把它加入.gitignore确保它不会被意外提交。# .env OPENAI_API_KEYsk-your-actual-openai-api-key-here ANTHROPIC_API_KEYyour-claude-api-key-here LOG_LEVELinfo API_TIMEOUT30000重要提示.env文件中的密钥是明文。请确保该文件的访问权限操作系统文件权限是严格的并且你不会将它分享给任何人或上传到任何地方。对于团队项目通常通过密码管理工具如1Password、LastPass或云服务提供的Secrets管理功能如Vercel Env、AWS Secrets Manager来共享这些密钥每个开发者在本地创建自己的.env文件。3.2 创建类型安全的配置模块直接使用process.env.XXX存在两个问题1. 类型是string | undefined需要到处做空值判断2. 无法在应用启动时就验证必要配置是否存在。我们来构建一个强类型的配置模块。首先在src目录下创建config文件夹和index.ts文件// src/config/index.ts import dotenv from dotenv; import path from path; // 根据NODE_ENV加载不同的.env文件 const envFile process.env.NODE_ENV test ? .env.test : .env; dotenv.config({ path: path.resolve(process.cwd(), envFile) }); // 定义配置接口明确每个环境变量的类型和是否可选 export interface AppConfig { openaiApiKey: string; anthropicApiKey?: string; // 可选如果你不一定用Claude logLevel: debug | info | warn | error; apiTimeout: number; port: number; } // 验证并获取配置的函数 function getConfig(): AppConfig { // 从process.env读取并提供默认值 const rawConfig { openaiApiKey: process.env.OPENAI_API_KEY, anthropicApiKey: process.env.ANTHROPIC_API_KEY, logLevel: process.env.LOG_LEVEL || info, apiTimeout: process.env.API_TIMEOUT ? parseInt(process.env.API_TIMEOUT, 10) : 30000, port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000, }; // 关键步骤验证必需配置 if (!rawConfig.openaiApiKey) { throw new Error(OPENAI_API_KEY is required in environment variables.); } // 可以添加更多验证比如apiTimeout必须是正数等 if (rawConfig.apiTimeout 0) { throw new Error(API_TIMEOUT must be a positive number.); } // 类型断言因为我们已做验证 return rawConfig as AppConfig; } // 导出单例配置对象 export const config getConfig();现在在应用的其他部分你只需要导入config对象即可它已经是类型安全且经过验证的。// src/services/openai-service.ts import { config } from ../config; export class OpenAIService { private apiKey: string; constructor() { this.apiKey config.openaiApiKey; // 类型为string非undefined } // ... 其他方法 }这种模式的好处是启动即验证。如果必需的OPENAI_API_KEY缺失应用会在启动时立即抛出错误而不是在运行到一半调用API时才失败便于快速定位问题。3.3 为不同环境准备不同的配置开发、测试、生产环境的需求通常不同。我们可以通过不同的.env文件和环境变量NODE_ENV来管理。.env.development本地开发环境可以指向测试用的API端点或Mock服务。.env.test自动化测试环境使用测试专用的API Key。.env.production生产环境使用真实、有额度的API Key。在package.json的脚本中指定环境scripts: { dev: NODE_ENVdevelopment ts-node-dev src/index.ts, test: NODE_ENVtest jest, start: NODE_ENVproduction node dist/index.js }我们的配置模块已经根据NODE_ENV动态加载对应的文件了。4. 核心依赖与结构化日志打造可观测的AI应用AI API调用可能很慢也可能失败。没有良好的日志调试就像在黑暗中摸索。我们将使用winston这个强大的日志库并集成一个HTTP客户端。4.1 安装并配置Winston日志库npm install winston创建一个日志工具模块// src/utils/logger.ts import winston from winston; import { config } from ../config; // 定义日志格式 const logFormat winston.format.combine( winston.format.timestamp({ format: YYYY-MM-DD HH:mm:ss.SSS }), winston.format.errors({ stack: true }), // 记录错误堆栈 winston.format.splat(), winston.format.json() // 输出为JSON便于日志收集系统如ELK处理 ); // 创建Logger实例 export const logger winston.createLogger({ level: config.logLevel, // 从配置读取日志级别 format: logFormat, defaultMeta: { service: my-ai-app }, // 默认元数据便于区分不同服务 transports: [ // 控制台输出开发时更友好 new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.printf( ({ timestamp, level, message, service, ...meta }) ${timestamp} [${service}] ${level}: ${message} ${ Object.keys(meta).length ? JSON.stringify(meta) : } ) ), }), // 生产环境可以添加文件传输或日志服务传输 // new winston.transports.File({ filename: logs/error.log, level: error }), // new winston.transports.File({ filename: logs/combined.log }), ], }); // 创建一个stream供像Express这样的框架使用 export const logStream { write: (message: string) logger.info(message.trim()), };现在你可以在任何地方使用结构化的日志import { logger } from ../utils/logger; logger.info(Starting AI service..., { model: gpt-4 }); try { const result await callAI(); logger.debug(AI call succeeded, { resultLength: result.length }); } catch (error) { logger.error(AI call failed, { error: error.message, stack: error.stack }); }JSON格式的日志可以轻松集成到云平台的日志服务中service和自定义元数据meta字段让你能快速过滤和搜索特定请求的日志。4.2 HTTP客户端选型为什么是ofetch对于调用AI API我们需要一个功能强大、易于使用的HTTP客户端。常见的候选有axios、node-fetch、got和ofetch。我推荐使用ofetch它是unjs生态系统的一部分设计非常现代化支持自动重试、超时、拦截器等特性且对TypeScript友好。npm install ofetch我们可以基于ofetch创建一个配置好的、专用于AI服务的客户端实例// src/utils/http-client.ts import { $fetch, FetchOptions } from ofetch; import { config } from ../config; import { logger } from ./logger; // 创建自定义fetch实例统一配置 export const aiFetch $fetch.create({ // 基础配置 timeout: config.apiTimeout, // 使用配置的超时时间 retry: 3, // 失败自动重试3次 retryDelay: 1000, // 重试延迟 // 请求拦截器统一添加认证头、日志 async onRequest({ request, options }) { logger.debug([HTTP] Outgoing Request: ${request}, { options }); // 可以根据请求的URL判断是哪个AI服务添加对应的API Key if (typeof request string request.includes(api.openai.com)) { options.headers { ...options.headers, Authorization: Bearer ${config.openaiApiKey}, }; } // 同理可以添加其他服务的认证头 }, // 响应拦截器处理响应和错误 async onResponse({ request, response, options }) { logger.debug([HTTP] Response for ${request}: Status ${response.status}); }, async onResponseError({ request, response, options }) { logger.error([HTTP] Error for ${request}: ${response.status} ${response.statusText}, { url: request, status: response.status, }); // 这里可以针对特定的HTTP状态码如429速率限制、502错误进行更精细的错误处理或重试逻辑 }, }); // 封装一个更易用的AI调用函数 export async function callAIApiT( endpoint: string, options: FetchOptionsjson { provider: openai | anthropic } ): PromiseT { const { provider, ...fetchOptions } options; let baseURL ; let apiKey ; switch (provider) { case openai: baseURL https://api.openai.com/v1; apiKey config.openaiApiKey; break; case anthropic: baseURL https://api.anthropic.com/v1; apiKey config.anthropicApiKey || ; break; default: throw new Error(Unsupported AI provider: ${provider}); } if (!apiKey) { throw new Error(API key for ${provider} is not configured.); } return aiFetchT(endpoint, { baseURL, headers: { Authorization: Bearer ${apiKey}, Content-Type: application/json, }, ...fetchOptions, }); }这个封装提供了几个关键优势统一配置超时、重试策略集中管理。提供者抽象调用者只需关心provider不用自己拼接URL和认证头。结构化日志所有请求和响应都被记录便于调试和监控。错误处理统一的错误拦截点便于实现复杂的重试逻辑例如遇到429状态码时等待更长时间再重试。5. 第一个AI调用实战从聊天完成到流式响应工程基础打好了现在让我们真正调用一次AI API。我们将实现一个简单的聊天完成接口并重点展示如何处理更复杂的流式响应这是现代AI应用提升用户体验的关键。5.1 实现基础的聊天完成功能首先在src/services/目录下创建openai-service.ts// src/services/openai-service.ts import { callAIApi } from ../utils/http-client; import { logger } from ../utils/logger; export interface ChatMessage { role: system | user | assistant; content: string; } export interface ChatCompletionOptions { model: string; messages: ChatMessage[]; temperature?: number; max_tokens?: number; stream?: boolean; // 是否使用流式响应 } export class OpenAIService { async createChatCompletion(options: ChatCompletionOptions) { const { model, messages, temperature 0.7, max_tokens 500, stream false } options; logger.info(Creating chat completion, { model, messageCount: messages.length, stream }); try { // 使用我们封装好的http客户端 const response await callAIApi{ choices: Array{ message: ChatMessage } }( /chat/completions, { provider: openai, method: POST, body: { model, messages, temperature, max_tokens, stream, }, } ); const assistantMessage response.choices[0]?.message; if (!assistantMessage) { throw new Error(No completion choice returned from API.); } logger.debug(Chat completion successful); return assistantMessage; } catch (error) { logger.error(Chat completion failed, { error: error.message }); // 这里可以细化错误类型比如网络错误、API错误、额度不足等并抛出更友好的错误 throw new Error(OpenAI API call failed: ${error.message}); } } }在src/index.ts中测试它// src/index.ts import { config } from ./config; import { logger } from ./utils/logger; import { OpenAIService, ChatMessage } from ./services/openai-service; async function main() { logger.info(My AI Project starting...); const service new OpenAIService(); const messages: ChatMessage[] [ { role: system, content: You are a helpful assistant. }, { role: user, content: Hello, what is the capital of France? }, ]; try { const response await service.createChatCompletion({ model: gpt-3.5-turbo, messages, temperature: 0.5, }); console.log(Assistant:, response.content); } catch (error) { console.error(Error:, error.message); } } // 启动时检查配置 if (!config.openaiApiKey) { logger.error(Critical configuration missing. Please check your .env file.); process.exit(1); } main();运行npm run dev需要你提前配置好ts-node-dev脚本你应该能看到AI的回复。恭喜你的第一个工程化的AI调用成功了但这只是开始非流式响应在生成长文本时用户需要等待全部完成才能看到结果体验不佳。5.2 实现流式响应处理提升用户体验的关键流式响应Server-Sent Events允许API一边生成内容一边分块返回给客户端。对于前端应用来说可以实现打字机效果用户体验好很多。处理流式响应比普通请求复杂因为我们需要解析特殊的data:格式。我们来升级OpenAIService添加流式处理的方法。这里的关键是使用原生的fetch或支持流的HTTP客户端来读取响应体。ofetch默认返回解析后的JSON对于流式响应我们需要使用它的底层能力或直接使用fetch。我们将创建一个新的方法它返回一个AsyncGenerator逐块产出内容// 在 src/services/openai-service.ts 中添加 import { createFetch } from ofetch; // 使用ofetch的工厂函数创建一个支持自定义处理的fetch实例 const customFetch createFetch({ fetch: globalThis.fetch, // 在Node 18中可用或者使用node-fetch polyfill }); export class OpenAIService { // ... 之前的 createChatCompletion 方法 ... async *createChatCompletionStream(options: ChatCompletionOptions): AsyncGeneratorstring, void, unknown { const { model, messages, temperature 0.7, max_tokens 500 } options; // 强制开启流式 const body { ...options, stream: true }; logger.info(Creating streaming chat completion, { model, messageCount: messages.length }); // 注意这里我们直接使用fetch因为需要精细控制响应流的读取 const response await fetch(https://api.openai.com/v1/chat/completions, { method: POST, headers: { Authorization: Bearer ${config.openaiApiKey}, Content-Type: application/json, }, body: JSON.stringify(body), }); if (!response.ok || !response.body) { const errorText await response.text(); throw new Error(OpenAI API request failed: ${response.status} ${errorText}); } const reader response.body.getReader(); const decoder new TextDecoder(utf-8); let buffer ; try { while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop() || ; // 最后一行可能是不完整的放回buffer for (const line of lines) { const trimmedLine line.trim(); if (!trimmedLine || trimmedLine data: [DONE]) continue; if (trimmedLine.startsWith(data: )) { const jsonStr trimmedLine.slice(6); // 去掉 data: try { const parsed JSON.parse(jsonStr); const chunk parsed.choices[0]?.delta?.content; if (chunk) { yield chunk; // 产出内容块 } } catch (e) { logger.warn(Failed to parse SSE chunk, { chunk: jsonStr, error: e.message }); } } } } } finally { reader.releaseLock(); } } }现在我们可以在主函数中测试流式响应// 在 src/index.ts 的 main 函数中添加 async function testStream() { const service new OpenAIService(); const messages: ChatMessage[] [ { role: system, content: You are a helpful assistant. }, { role: user, content: Write a short poem about TypeScript. }, ]; console.log(Assistant (streaming): ); try { for await (const chunk of service.createChatCompletionStream({ model: gpt-3.5-turbo, messages, })) { process.stdout.write(chunk); // 逐块打印模拟打字机效果 } console.log(\n--- Stream finished ---); } catch (error) { console.error(\nStream error:, error.message); } } // 在main中调用 testStream()运行后你将看到诗句被逐词或逐句打印出来而不是等待很久后一次性出现。这就是流式响应的魅力。在实际的Web或客户端应用中你可以将这些数据块实时推送到前端界面。5.3 错误处理与重试策略的进阶思考AI API调用并不总是稳定的。网络波动、提供商服务限流429状态码、临时过载503错误都可能发生。我们之前在http-client中配置了基础重试但对于AI场景可能需要更聪明的策略。指数退避重试对于429请求过多或5xx错误简单的固定间隔重试可能加重服务器负担。指数退避会在每次重试前等待更长时间例如1秒2秒4秒8秒...。针对特定错误的处理例如如果API返回“上下文长度超限”的错误重试是没用的应该直接向用户反馈提示缩短输入。我们可以进一步完善callAIApi函数中的错误处理逻辑。一个更健壮的重试策略可以放在onResponseError拦截器中实现根据错误类型和状态码决定是否重试及等待时间。由于篇幅限制这里不展开完整代码但思路是捕获错误判断如果是可重试错误网络错误、5xx、429则等待一段时间后重试如果是业务错误如认证失败、额度不足则直接抛出。6. 项目脚本、调试与打包让开发更顺畅一个完整的工程化项目离不开便捷的开发脚本和清晰的构建流程。6.1 完善package.json脚本// package.json 的 scripts 部分 scripts: { dev: NODE_ENVdevelopment ts-node-dev --respawn --transpile-only src/index.ts, build: tsc, start: NODE_ENVproduction node dist/index.js, lint: eslint src --ext .ts, lint:fix: eslint src --ext .ts --fix, format: prettier --write \src/**/*.ts\, check: npm run lint npm run format, clean: rimraf dist, // 需要安装 rimraf: npm install -D rimraf prebuild: npm run clean, postbuild: echo Build completed successfully. }ts-node-dev在开发时使用它监视文件变化并自动重启--transpile-only提升编译速度忽略类型检查类型检查由IDE和npm run lint负责。prebuildpostbuildnpm的生命周期钩子在build前后自动执行清理和提示。6.2 配置Visual Studio Code调试在项目根目录创建.vscode/launch.json让你能直接在VSCode中打断点调试。{ version: 0.2.0, configurations: [ { type: node, request: launch, name: Debug Current File, runtimeExecutable: npm, runtimeArgs: [run, dev], console: integratedTerminal, internalConsoleOptions: neverOpen, skipFiles: [node_internals/**] }, { type: node, request: launch, name: Build and Launch, program: ${workspaceFolder}/dist/index.js, preLaunchTask: npm: build, env: { NODE_ENV: development }, console: integratedTerminal } ] }现在你可以按F5启动调试在src/index.ts或任何服务文件中设置断点观察变量状态这对于调试复杂的AI提示逻辑或流处理逻辑至关重要。6.3 生产环境构建与注意事项运行npm run build会将TypeScript编译成JavaScript到dist目录。对于生产部署有几点需要注意环境变量确保生产服务器上正确设置了所有必要的环境变量如OPENAI_API_KEY通常通过云平台的环境变量配置界面或Docker的env文件实现。Node.js版本确保生产环境Node.js版本与开发环境一致建议使用.nvmrc或engines字段在package.json中指定。依赖安装使用npm ci而不是npm install它能根据package-lock.json精确安装依赖保证环境一致性。进程管理对于长时间运行的服务使用PM2、systemd或容器编排工具来管理进程实现崩溃自动重启、日志轮转等功能。7. 总结与后续扩展方向走到这里你已经拥有了一个结构清晰、配置安全、工具链完善、具备生产级日志和错误处理能力的TypeScript AI项目骨架。这远不止是调通了一个API而是建立了一套可持续开发的工程规范。基于这个骨架你可以轻松地扩展多AI提供商支持轻松集成Claude、Gemini、本地部署的Ollama等只需在配置和http-client中添加新的提供者逻辑。提示词工程模块将复杂的提示词模板、少样本示例管理起来形成独立的prompts模块。异步任务与队列对于耗时的AI生成任务如图像生成、长文写作可以引入Bull或RabbitMQ结合Redis实现任务队列避免HTTP请求超时。API服务器使用Express或Fastify快速搭建一个RESTful API服务器将你的AI能力暴露给前端或其他服务。单元测试与集成测试使用Jest和supertest为你的AI服务层和API路由编写测试用dotenv加载.env.test配置使用模拟mocks来避免在测试中调用真实API产生费用。第一次做这些配置可能会觉得繁琐但一旦完成它将成为你所有AI项目的强大起点。下次当你有一个新的AI点子时直接复制这个项目骨架修改核心业务逻辑即可省去了重复搭建环境、纠结配置的时间让你能更专注于创意和算法本身。这才是工程化带来的真正效率提升。
返回列表