1. 为什么需要将Gemini响应转为JSON
在NestJS应用中处理Google Gemini的API响应时,JSON格式转换是一个关键环节。Gemini作为Google最新推出的大型语言模型,其API默认返回的是结构化数据对象,但在实际开发中我们往往需要将其转换为标准JSON格式,主要基于以下考虑:
-
前后端数据交互标准化:现代Web应用普遍采用JSON作为前后端通信的数据格式,NestJS默认也使用JSON作为响应格式。将Gemini的响应转为JSON可以保持整个应用数据格式的统一性。
-
数据处理的便利性:JSON格式可以直接被JavaScript/TypeScript原生解析,方便进行数据提取、转换和操作。相比处理原始对象,JSON提供了更直观的数据访问方式。
-
缓存和持久化存储:JSON字符串可以轻松地存储到数据库或文件系统中,也可以作为缓存内容。这是处理原始对象无法直接实现的。
-
跨平台兼容性:当需要将Gemini的响应传递给其他服务或系统时,JSON是最通用的数据交换格式,几乎被所有编程语言和平台支持。
提示:虽然Gemini的响应对象本身已经包含结构化数据,但直接返回该对象会导致NestJS的序列化过程不可控,可能暴露内部数据结构或引发循环引用问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. NestJS中的JSON序列化基础
2.1 NestJS的响应处理机制
NestJS默认使用Express或Fastify作为底层HTTP服务器,这两种框架都支持自动将JavaScript对象序列化为JSON响应。当控制器方法返回一个对象时,框架会通过以下步骤处理:
- 执行控制器方法获取返回值
- 调用
JSON.stringify()进行序列化 - 设置
Content-Type: application/json头部 - 发送响应到客户端
这种默认行为对于简单对象工作良好,但在处理Gemini响应时可能会遇到以下问题:
- 大模型响应包含大量元数据和复杂结构
- 某些属性可能包含不可序列化的特殊对象
- 需要控制最终输出的字段和结构
2.2 自定义响应转换的几种方式
在NestJS中,我们有多种方式可以实现对Gemini响应的JSON转换:
-
手动序列化:在控制器中显式调用
JSON.stringify()typescript复制@Get() async getGeminiResponse() { const response = await geminiModel.generateContent(); return JSON.stringify(response); } -
使用类转换器(class-transformer):
typescript复制import { plainToInstance } from 'class-transformer'; class GeminiResponseDTO { // 定义需要的字段 } @Get() async getGeminiResponse() { const response = await geminiModel.generateContent(); return plainToInstance(GeminiResponseDTO, response); } -
拦截器方案:创建自定义拦截器统一处理响应格式
typescript复制@Injectable() class JsonResponseInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler) { return next.handle().pipe( map(data => ({ data: JSON.parse(JSON.stringify(data)) })) ); } } -
异常处理:确保错误响应也保持JSON格式
typescript复制@Catch() export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, message: exception.message, }); } }
3. 集成Google Gemini SDK
3.1 初始设置与配置
要在NestJS应用中集成Google Gemini API,首先需要完成基础配置:
-
安装必要的依赖:
bash复制
npm install @google/generative-ai -
创建Gemini服务模块:
typescript复制// gemini/gemini.module.ts @Module({ providers: [GeminiService], exports: [GeminiService], }) export class GeminiModule {} -
实现基础服务:
typescript复制// gemini/gemini.service.ts import { Injectable } from '@nestjs/common'; import { GoogleGenerativeAI } from '@google/generative-ai'; @Injectable() export class GeminiService { private genAI: GoogleGenerativeAI; private model: any; constructor() { this.genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); this.model = this.genAI.getGenerativeModel({ model: "gemini-pro" }); } async generateContent(prompt: string) { const result = await this.model.generateContent(prompt); return result.response; } }
3.2 处理Gemini的响应结构
Gemini API返回的响应对象包含多层嵌套结构,典型响应如下:
typescript复制{
candidates: [
{
content: {
parts: [
{ text: "这是Gemini生成的响应内容..." }
],
role: "model"
},
finishReason: "STOP",
index: 0,
safetyRatings: [...]
}
],
promptFeedback: { ... }
}
我们需要设计合适的DTO来规范输出JSON的结构:
typescript复制// dto/gemini-response.dto.ts
import { Expose, Type } from 'class-transformer';
class ContentPart {
@Expose()
text: string;
}
class Content {
@Expose()
@Type(() => ContentPart)
parts: ContentPart[];
@Expose()
role: string;
}
class Candidate {
@Expose()
@Type(() => Content)
content: Content;
@Expose()
finishReason: string;
}
export class GeminiResponseDTO {
@Expose()
@Type(() => Candidate)
candidates: Candidate[];
@Expose()
promptFeedback?: any;
}
4. 实现JSON响应转换
4.1 控制器层实现
在控制器中,我们可以组合使用服务和DTO来返回格式化的JSON响应:
typescript复制// gemini/gemini.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { GeminiService } from './gemini.service';
import { plainToInstance } from 'class-transformer';
import { GeminiResponseDTO } from '../dto/gemini-response.dto';
@Controller('gemini')
export class GeminiController {
constructor(private readonly geminiService: GeminiService) {}
@Get()
async getResponse(@Query('prompt') prompt: string) {
const response = await this.geminiService.generateContent(prompt);
return plainToInstance(GeminiResponseDTO, response, {
excludeExtraneousValues: true,
});
}
}
4.2 高级序列化技巧
对于更复杂的场景,可以考虑以下进阶技巧:
-
自定义序列化函数:
typescript复制function serializeGeminiResponse(response: any) { return { text: response.candidates?.[0]?.content?.parts?.[0]?.text || '', metadata: { finishReason: response.candidates?.[0]?.finishReason, safetyRatings: response.candidates?.[0]?.safetyRatings, } }; } -
使用RxJS操作符进行流式处理:
typescript复制@Get('stream') streamResponse(@Query('prompt') prompt: string) { return from(this.geminiService.generateContent(prompt)).pipe( map(response => JSON.parse(JSON.stringify(response))), catchError(error => throwError(() => new HttpException( { error: 'Gemini处理失败', details: error.message }, HttpStatus.INTERNAL_SERVER_ERROR ))) ); } -
性能优化:延迟加载和大响应处理:
typescript复制@Get('large') async getLargeResponse(@Query('prompt') prompt: string, @Res() res: Response) { const response = await this.geminiService.generateContent(prompt); res.setHeader('Content-Type', 'application/json'); // 使用流式JSON序列化处理大响应 const stringifier = stringify({ type: 'gemini-response', data: response }, null, 2); stringifier.pipe(res); }
5. 错误处理与调试
5.1 常见错误场景
在处理Gemini响应转为JSON的过程中,可能会遇到以下典型问题:
-
循环引用错误:当响应对象包含循环引用时,直接调用
JSON.stringify()会抛出异常。 -
大响应内存问题:处理非常大的响应时,可能导致内存不足或响应延迟。
-
类型不匹配:Gemini返回的数据结构与预期DTO不匹配,导致转换失败。
-
API限制错误:超过速率限制或配额时,需要适当处理错误响应。
5.2 调试技巧与实践
-
使用NestJS内置日志:
typescript复制private readonly logger = new Logger(GeminiService.name); async generateContent(prompt: string) { try { const result = await this.model.generateContent(prompt); this.logger.debug('Gemini响应原始结构:', result.response); return result.response; } catch (error) { this.logger.error('Gemini API调用失败:', error); throw new HttpException( '无法处理请求', HttpStatus.INTERNAL_SERVER_ERROR ); } } -
响应结构验证中间件:
typescript复制@Injectable() export class GeminiResponseMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { const originalJson = res.json; res.json = function (body) { if (body?.candidates) { console.log('验证Gemini响应结构:', body); } originalJson.call(this, body); }; next(); } } -
单元测试策略:
typescript复制describe('GeminiService', () => { let service: GeminiService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [GeminiService], }).compile(); service = module.get<GeminiService>(GeminiService); }); it('应该返回有效的JSON响应', async () => { const mockResponse = { candidates: [{ content: { parts: [{ text: '测试响应' }] } }] }; jest.spyOn(service, 'generateContent').mockResolvedValue(mockResponse); const result = await service.generateContent('测试提示'); expect(JSON.parse(JSON.stringify(result))).toEqual(mockResponse); }); });
6. 性能优化与最佳实践
6.1 缓存策略实现
对于频繁请求的相同提示,可以实现响应缓存:
typescript复制import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
@Injectable()
export class GeminiService {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache
) {}
async generateContent(prompt: string) {
const cacheKey = `gemini:${md5(prompt)}`;
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
return cached;
}
const result = await this.model.generateContent(prompt);
const response = result.response;
await this.cacheManager.set(cacheKey, response, 3600); // 缓存1小时
return response;
}
}
6.2 响应压缩
对于大型JSON响应,启用压缩可以显著减少网络传输时间:
typescript复制// main.ts
import * as compression from 'compression';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(compression());
// ...其他配置
await app.listen(3000);
}
6.3 分页与流式传输
处理超长响应时,可以考虑分页或流式传输:
typescript复制@Get('paged')
async getPagedResponse(
@Query('prompt') prompt: string,
@Query('page') page = 1,
@Query('limit') limit = 10
) {
const fullResponse = await this.geminiService.generateContent(prompt);
const text = fullResponse.candidates[0].content.parts[0].text;
const lines = text.split('\n');
return {
data: lines.slice((page - 1) * limit, page * limit),
meta: {
total: lines.length,
page,
limit,
}
};
}
在实际项目中,我发现将Gemini响应转为JSON时,最关键的挑战在于平衡响应结构的完整性与简洁性。经过多次实践,我总结出以下经验:
-
按需转换:不要盲目转换整个响应对象,只提取前端真正需要的字段。
-
错误处理前置:在序列化前验证响应结构,避免转换过程中出现意外错误。
-
性能监控:对于高频使用的Gemini接口,添加响应时间日志,及时发现性能瓶颈。
-
版本兼容:Gemini API可能会更新响应结构,DTO设计要预留扩展空间。
-
安全考虑:过滤掉响应中可能存在的敏感信息或内部错误详情。
