前端调用大模型实战:Vite 全栈配置 + Fetch 请求最佳实践

169 阅读6分钟

在 AI 原生应用开发中,前端直接调用大模型 API 已成为高频场景 —— 无需后端中转即可快速实现对话、生成等核心功能。本文结合工程化实践,从 Vite 全栈项目初始化到 Fetch 复杂请求封装,手把手教你搭建可落地的前端大模型调用方案,干货密集,新手也能直接复用!

一、项目初始化:Vite 全栈脚手架搭建

相比原生 HTML/CSS/JS 项目,Vite 的热更新、工程化支持、全栈适配能力更适合生产级项目。以下是完整初始化流程:

1. 基础项目创建

bash

运行

# 初始化Vite项目(支持Vue/React/原生JS,这里以原生TS为例)
npm create vite@latest llm-frontend -- --template vanilla-ts
cd llm-frontend
npm install

# 安装可选依赖(请求工具增强、类型提示)
npm install axios  # 可选,本文以原生Fetch为主,axios作为补充方案
npm install -D @types/node  # 环境变量类型支持

2. 核心 Vite 配置(vite.config.ts)

重点解决跨域问题(前端直接调用第三方 LLM API 必踩坑)和环境变量暴露,配置如下:

typescript

运行

import { defineConfig, loadEnv } from 'vite';
import path from 'path';

export default defineConfig(({ mode }) => {
  // 加载环境变量(区分开发/生产环境)
  const env = loadEnv(mode, process.cwd(), 'VITE_');
  
  return {
    // 项目基础配置
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src'), // 路径别名,方便导入
      },
    },
    // 开发服务器配置(解决跨域)
    server: {
      proxy: {
        // 配置代理前缀,避免直接暴露第三方API地址
        '/api/llm': {
          target: env.VITE_LLM_API_BASE_URL, // 目标API地址(如https://api.deepseek.com)
          changeOrigin: true, // 开启跨域代理
          rewrite: (path) => path.replace(/^/api/llm/, ''), // 重写路径(去掉代理前缀)
        },
      },
    },
    // 生产环境构建配置
    build: {
      outDir: 'dist',
      sourcemap: mode === 'development', // 开发环境生成sourcemap
    },
  };
});

3. 目录结构设计(工程化核心)

plaintext

llm-frontend/
├─ src/
│  ├─ api/            # API封装目录(工程化关键)
│  │  └─ llm.ts       # 大模型请求封装
│  ├─ utils/          # 工具函数
│  │  └─ env.ts       # 环境变量工具
│  ├─ types/          # 类型定义(TS必备)
│  │  └─ llm.ts       # 大模型请求/响应类型
│  ├─ main.ts         # 入口文件
│  └─ style.css       # 样式文件
├─ .env.development   # 开发环境变量
├─ .env.production    # 生产环境变量
├─ .gitignore         # 忽略文件(关键:排除.env)
└─ vite.config.ts     # Vite配置

二、核心实战:Fetch 调用 LLM API(复杂请求封装)

大模型 API 调用属于POST 复杂请求,需严格配置请求行、请求头、请求体,且必须处理异步逻辑和错误捕获。

1. 环境变量配置(.env 文件)

创建.env.development(开发环境)和.env.production(生产环境),API 密钥必须放在环境变量中,避免硬编码泄露:

env

# .env.development
VITE_LLM_API_BASE_URL=https://api.deepseek.com
VITE_LLM_API_KEY=your-deepseek-api-key  # 替换为你的密钥
VITE_LLM_MODEL=deepseek-chat  # 模型名称(根据API文档配置)

⚠️ 关键提醒:在.gitignore中添加.env*,禁止提交环境变量文件:

gitignore

# .gitignore
node_modules/
dist/
.env
.env.*
!.env.example  # 可选:保留示例文件(不含真实密钥)

2. 类型定义(types/llm.ts,TS 提升开发体验)

提前定义请求和响应类型,避免类型混乱:

typescript

运行

// src/types/llm.ts
/** 大模型请求参数类型 */
export interface LLMRequest {
  model: string;
  messages: Array<{
    role: 'user' | 'assistant' | 'system'; // 角色(用户/助手/系统)
    content: string; // 消息内容
  }>;
  temperature?: number; // 随机性(0-1)
  max_tokens?: number; // 最大响应长度
}

/** 大模型响应类型 */
export interface LLMResponse {
  id: string;
  object: string;
  created: number;
  model: string;
  choices: Array<{
    message: {
      role: 'assistant';
      content: string;
    };
    finish_reason: string;
    index: number;
  }>;
  usage: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
}

/** 错误响应类型 */
export interface LLMErrorResponse {
  error: {
    message: string;
    code: string;
  };
}

3. Fetch 请求封装(api/llm.ts,工程化核心)

封装通用请求函数,处理请求配置、JSON 序列化、异步逻辑、错误捕获,避免重复代码:

typescript

运行

// src/api/llm.ts
import { LLMRequest, LLMResponse, LLMErrorResponse } from '@/types/llm';

// 从环境变量获取配置
const LLM_API_BASE_URL = import.meta.env.VITE_LLM_API_BASE_URL;
const LLM_API_KEY = import.meta.env.VITE_LLM_API_KEY;
const LLM_MODEL = import.meta.env.VITE_LLM_MODEL;

/**
 * 调用大模型API(通用封装)
 * @param messages 对话消息数组
 * @param options 额外配置(temperature/max_tokens等)
 */
export async function callLLM(
  messages: LLMRequest['messages'],
  options?: Omit<LLMRequest, 'model' | 'messages'>
): Promise<LLMResponse['choices'][0]['message']['content']> {
  // 1. 校验必要配置
  if (!LLM_API_KEY || !LLM_API_BASE_URL) {
    throw new Error('请配置LLM_API_KEY和LLM_API_BASE_URL环境变量');
  }

  // 2. 构造请求参数
  const requestData: LLMRequest = {
    model: LLM_MODEL,
    messages,
    temperature: options?.temperature || 0.7,
    max_tokens: options?.max_tokens || 1024,
    ...options,
  };

  try {
    // 3. 发送Fetch请求(复杂请求核心配置)
    const response = await fetch('/api/llm/chat/completions', {
      method: 'POST', // 必须POST
      headers: {
        'Authorization': `Bearer ${LLM_API_KEY}`, // 令牌固定前缀Bearer
        'Content-Type': 'application/json', // 必须JSON格式
        'Accept': 'application/json',
      },
      body: JSON.stringify(requestData), // 序列化JSON对象(关键:不能直接传对象)
    });

    // 4. 处理响应(区分成功/失败)
    const data = await response.json() as LLMResponse | LLMErrorResponse;

    if (!response.ok) {
      // 解析错误信息
      const errorMsg = (data as LLMErrorResponse).error?.message || '请求失败';
      throw new Error(`LLM API错误: ${errorMsg}`);
    }

    // 5. 提取响应内容(简化返回结果)
    return (data as LLMResponse).choices[0].message.content;
  } catch (error) {
    // 6. 统一错误捕获
    console.error('LLM调用失败:', error);
    throw error; // 抛出错误,让调用方处理
  }
}

4. 调用示例(main.ts)

使用async/await简化异步逻辑(比.then更清晰):

typescript

运行

// src/main.ts
import { callLLM } from '@/api/llm';

// 立即执行函数(ES模块中无法直接写顶层await,需包裹)
(async () => {
  try {
    // 调用大模型(用户消息+系统提示)
    const messages = [
      { role: 'system', content: '你是一个前端助手,回答简洁明了' },
      { role: 'user', content: '如何用前端调用大模型API?' },
    ];

    console.log('正在请求大模型...');
    const result = await callLLM(messages, { temperature: 0.5 });
    console.log('大模型响应:', result);

    // 渲染到页面
    document.body.innerHTML = `
      <div style="max-width: 800px; margin: 2rem auto; padding: 0 1rem;">
        <h2>前端大模型调用示例</h2>
        <div style="margin: 1rem 0; padding: 1rem; background: #f5f5f5; border-radius: 8px;">
          <p><strong>用户:</strong> 如何用前端调用大模型API?</p>
          <p><strong>助手:</strong> ${result}</p>
        </div>
      </div>
    `;
  } catch (error) {
    document.body.innerHTML = `<div style="color: red; margin: 2rem;">调用失败: ${(error as Error).message}</div>`;
  }
})();

三、工程化进阶:让代码更可维护

1. 环境变量工具封装(utils/env.ts)

统一处理环境变量,避免重复写import.meta.env

typescript

运行

// src/utils/env.ts
/** 获取环境变量(带默认值) */
export function getEnv<T = string>(key: string, defaultValue?: T): T {
  const value = import.meta.env[key] as unknown as T;
  if (value === undefined && defaultValue === undefined) {
    throw new Error(`环境变量${key}未配置`);
  }
  return value ?? defaultValue!;
}

// 常用环境变量快捷访问
export const llmEnv = {
  apiBaseUrl: getEnv('VITE_LLM_API_BASE_URL'),
  apiKey: getEnv('VITE_LLM_API_KEY'),
  model: getEnv('VITE_LLM_MODEL', 'deepseek-chat'),
};

2. 扩展:Axios 替代方案(可选)

如果项目中已使用 Axios,可替换 Fetch(优势:拦截器、取消请求):

typescript

运行

// src/api/llm-axios.ts
import axios from 'axios';
import { llmEnv } from '@/utils/env';
import { LLMRequest } from '@/types/llm';

// 创建Axios实例
const llmAxios = axios.create({
  baseURL: '/api/llm',
  headers: {
    'Authorization': `Bearer ${llmEnv.apiKey}`,
    'Content-Type': 'application/json',
  },
});

// 请求拦截器(统一添加配置)
llmAxios.interceptors.request.use((config) => {
  config.data = config.data || {};
  config.data.model = llmEnv.model;
  return config;
});

// 响应拦截器(统一处理响应)
llmAxios.interceptors.response.use(
  (response) => response.data.choices[0].message.content,
  (error) => {
    const errorMsg = error.response?.data?.error?.message || '请求失败';
    console.error('LLM调用失败:', errorMsg);
    throw new Error(errorMsg);
  }
);

// 调用函数
export async function callLLMAxios(messages: LLMRequest['messages'], options?: Omit<LLMRequest, 'model' | 'messages'>) {
  return llmAxios.post('/chat/completions', { messages, ...options });
}

3. 安全最佳实践

  • 密钥安全:前端环境变量仍可能被破解,生产环境建议通过后端中转请求(前端调用自己的后端,后端再调用 LLM API,密钥存储在后端)。
  • 请求限制:添加请求频率限制,避免滥用 API。
  • 输入过滤:对用户输入进行安全过滤,防止注入攻击。

四、避坑指南(高频问题解决)

  1. 跨域报错:确保 Vite 代理配置正确,changeOrigin: true必须开启,且代理路径重写正确。
  2. 401 Unauthorized:检查 API 密钥是否正确,Authorization头格式是否为Bearer + 空格 + 密钥
  3. 请求体格式错误:必须用JSON.stringify序列化请求体,且字段名与 API 文档一致(如messages不能写成message)。
  4. 环境变量未生效:Vite 环境变量必须以VITE_为前缀,且修改.env后需重启开发服务器。

五、扩展方向

  • 流式响应:大模型支持 SSE(Server-Sent Events),可实现打字机效果(需修改 Fetch 为response.body.getReader())。
  • 缓存策略:缓存重复请求,减少 API 调用成本(如使用localStoragesessionStorage)。
  • 多模型支持:封装通用 API,支持切换 DeepSeek、OpenAI、Anthropic 等不同模型。
  • UI 组件化:将调用逻辑封装为 Vue/React 组件(如ChatInputChatMessage)。

总结

前端调用大模型的核心是规范的 HTTP 请求配置+工程化的代码组织:Vite 解决了跨域和环境变量问题,Fetch/Axios 负责请求封装,TypeScript 提升类型安全,环境变量和目录结构保证可维护性。按照本文方案,你可以快速搭建一个稳定、安全、可扩展的前端大模型调用方案,直接应用到生产项目中。

如果需要进一步优化(如流式响应实现、后端中转方案、Vue/React 组件封装),欢迎在评论区留言交流!🚀