在Gemini与LangChain4j的结合场景中,如何实现结构化输出的同时获取请求元数据(如Token使用量)
在Gemini与LangChain4j的结合场景中,如何实现结构化输出的同时获取请求元数据(如Token使用量)
嘿,我刚好碰到过类似的需求,其实LangChain4j和Gemini的组合里,要同时拿结构化输出和Token元数据很简单,只是需要调整一下调用方式——原来的AiServices代理帮你封装了太多细节,我们得稍微“钻”到底层一点,直接拿到包含元数据的AiResponse就行。下面给你一步步拆解实现方法:
核心思路
原来的AiServices生成的代理会直接返回你定义的Person对象,但请求的元数据(比如Token使用量)其实藏在ChatModel返回的AiResponse里。所以我们需要:
- 手动构建请求Prompt
- 调用
ChatModel.generate()拿到完整的AiResponse - 从
AiResponse里分别提取Token元数据和解析结构化的Person对象
完整代码实现
直接上修改后的可运行代码,每一步都加了注释:
import dev.langchain4j.model.ai.AiResponse; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.google.gemini.GeminiChatModel; import dev.langchain4j.model.google.gemini.GeminiResponseMetadata; import dev.langchain4j.model.output.Prompt; import dev.langchain4j.model.output.structured.StructuredOutputParser; import java.time.LocalDate; import java.util.Map; // 保持你原来的Person record不变 public record Person(String nom, LocalDate dateOfBirth, LocalDate dateOfDeath, String mainFact) { } public class HistorianExample { public static void main(String[] args) { // 1. 初始化Gemini ChatModel(和你原来的方式一致) ChatModel model = GeminiChatModel.builder() .apiKey("YOUR_GEMINI_API_KEY") // 替换成你的API Key .build(); // 2. 构建Prompt,和你原来@UserMessage里的内容一致,同时明确要求返回JSON(保证结构化解析) String promptTemplate = """ You are a historian who is collecting information about a person whose name is {{name}}. Please return the information in strict JSON format matching this structure: {"nom": "person name", "dateOfBirth": "yyyy-MM-dd", "dateOfDeath": "yyyy-MM-dd", "mainFact": "key fact"} """; Prompt prompt = Prompt.from(promptTemplate, Map.of("name", "Napoléon")); // 3. 调用模型拿到完整的AiResponse(这是核心!元数据就在这里面) AiResponse aiResponse = model.generate(prompt); // 4. 提取Token使用量元数据 // GeminiResponseMetadata是Gemini专属的元数据类,包含所有Token统计 GeminiResponseMetadata geminiMetadata = (GeminiResponseMetadata) aiResponse.metadata(); System.out.println("📊 Token使用统计:"); System.out.println("提问Token数:" + geminiMetadata.usage().promptTokenCount()); System.out.println("回答Token数:" + geminiMetadata.usage().candidatesTokenCount()); System.out.println("总Token数:" + geminiMetadata.usage().totalTokenCount()); // 5. 解析结构化的Person对象,和你原来的效果完全一致 StructuredOutputParser<Person> parser = StructuredOutputParser.create(Person.class); Person person = parser.parse(aiResponse.content().text()); // 6. 输出结构化结果 System.out.println("\n👤 人物信息:"); System.out.println("姓名:" + person.nom()); System.out.println("出生日期:" + person.dateOfBirth()); System.out.println("主要事迹:" + person.mainFact()); } }
关键细节说明
- 为什么不用原来的AiServices代理?
原来的代理确实方便,但它把AiResponse的细节都封装了,只返回最终的结构化对象。我们要拿元数据,就必须直接和ChatModel的generate()方法打交道,因为只有它会返回包含所有信息的AiResponse。 GeminiResponseMetadata是什么?
这是LangChain4j专门为Gemini模型提供的元数据类,里面的usage()方法返回的对象包含了所有Token相关的统计数据,完全覆盖你的需求。- 为什么要在Prompt里要求JSON格式?
和你原来用@UserMessage+Personrecord的原理一样,必须让模型返回结构化的JSON,StructuredOutputParser才能正确解析成Java对象,这一步不能省哦。
总结
其实核心就是绕过AiServices的封装,直接拿AiResponse——这样既保留了结构化解析的能力,又能拿到所有你需要的请求元数据。这个方法既简单又灵活,你还可以从AiResponse里拿到其他元数据,比如模型的响应ID、生成时间之类的。




