LangChain4j实战六:结构化输出
·
1. 关于结构化输出

2. 准备工作
2.2. 构建项目,添加pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath/>
</parent>
<groupId>cn.cjc</groupId>
<artifactId>springboot-ai</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/dev.langchain4j/langchain4j -->
<!-- LangChain4j 核心库 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.9.1</version>
</dependency>
<!-- AiService依赖 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.1-beta6</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<version>1.9.1</version>
</dependency>
<!-- LangChain4j OpenAI支持(可用于通义千问的OpenAI兼容接口) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.9.1-beta17</version>
</dependency>
<!-- 集成原生阿里云通义千问 (DashScope) -->
<!-- <dependency>-->
<!-- <groupId>dev.langchain4j</groupId>-->
<!-- <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>-->
<!-- <version>1.9.1-beta17</version>-->
<!-- </dependency>-->
<!-- 导入响应式编程依赖包-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.9.1-beta17</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
</project>
2.3. 配置文件
spring:
application:
name: springboot-ai
main:
allow-bean-definition-overriding: true
langchain4j:
open-ai:
chat-model:
api-key: ******
model-name: qwen-plus
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
2.4. 提示词请求类
@Data
public class PromptRequest {
private String prompt;
private int userId;
}
3. 接口HistoryEvent类
@Data
public class HistoryEvent {
@JsonProperty("main_characters")
private List<String> mainCharacters;
@JsonProperty("year")
private String year;
@JsonProperty("description")
private String description;
// 创建静态的ObjectMapper实例,避免重复创建
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 将JSON字符串反序列化为HistoryEvent对象
*
* @param json JSON字符串
* @return HistoryEvent对象
* @throws IOException 如果JSON处理或映射失败
*/
public static HistoryEvent fromJson(String json) throws IOException {
return objectMapper.readValue(json, HistoryEvent.class);
}
}
4. JSON输出实现方式
4.1. 提示词

- 配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "langchain4j.open-ai.chat-model")
public class LangChain4jConfig {
private String apiKey;
private String modelName;
private String baseUrl;
/**
* 创建并配置OpenAiChatModel实例(使用通义千问的OpenAI兼容接口)
*
* @return OpenAiChatModel实例
*/
@Bean("openAiChatModel")
public OpenAiChatModel openAiChatModel() {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.baseUrl(baseUrl)
.build();
}
// 使用了@AiService就无需注入bean
// @Bean
// public StructureAssistant structureAssistant(@Qualifier("openAiChatModel") OpenAiChatModel chatModel, HistoryEventTool historyEventTool) {
// return AiServices.builder(StructureAssistant.class)
// .chatModel(chatModel)
// .tools(historyEventTool)
// .build();
// }
}
- 服务接口
@AiService(wiringMode = AiServiceWiringMode.EXPLICIT, chatModel = "openAiChatModel")
public interface StructureAssistant {
/**
* 通过提示词range大模型返回JSON格式的内容
*
* @param userMessage 用户消息
* @return 助手生成的回答
*/
String byPrompt(String userMessage);
}
- 服务类
package cn.cjc.ai.service.impl;
import cn.cjc.ai.entity.HistoryEvent;
import cn.cjc.ai.service.Assistant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Slf4j
@Service
public class StructureChatService {
@Autowired
private StructureAssistant structureAssistant;
/**
* 通过提示词range大模型返回JSON格式的内容
*/
public HistoryEvent byPrompt(String prompt) {
String answer = structureAssistant.byPrompt(prompt);
log.info("响应:{}", answer);
HistoryEvent historyEvent = null;
// 用大模型返回的字符串直接反序列化成对象
try {
historyEvent = HistoryEvent.fromJson(answer);
log.info("反序列化后的对象:{}", historyEvent);
} catch (IOException e) {
log.error("反序列化失败", e);
}
return historyEvent;
}
}
4.2. function call
-
把HistoryEvent类中JSON注解注释
-
自定义函数
在一个bean中准备好自定义方法createHistoryEvent,用Tool注解修饰该方法,这样LangChain4j就会通过该方法生成OpenAI-style function definition
import cn.cjc.ai.entity.HistoryEvent;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 历史事件提取工具,用于从文本中提取历史事件信息
*/
@Component
public class HistoryEventTool {
/**
* 从文本中提取历史事件信息
* 注意,可以理解为:LangChain4j会使用这个方法的签名来构建function call,而不是实际执行这个方法
*
* @param mainCharacters 主要人物列表
* @param year 发生年份
* @param description 事件描述
* @return 历史事件对象
*/
@Tool("创建历史事件对象,包含主要人物、发生年份和事件描述")
public HistoryEvent createHistoryEvent(List<String> mainCharacters, int year, String description) {
return null;
}
}
- 服务接口
public interface StructureAssistant {
/**
* 通过提示词range大模型返回JSON格式的内容
* @param userMessage 用户消息
* @return 助手生成的HistoryEvent对象
*/
HistoryEvent byFunctionCall(@UserMessage String userMessage);
}
-
配置类
调用AiServices.builder创建高级API服务实例的时候,要调用tools方法把HistoryEventTool实例传给LangChain4j,这样在对话时LangChain4j才会把OpenAI-style function definition发给LLM
@Data
@Configuration
@ConfigurationProperties(prefix = "langchain4j.open-ai.chat-model")
public class LangChain4jConfig {
private String apiKey;
private String modelName;
private String baseUrl;
/**
* 创建并配置OpenAiChatModel实例(使用通义千问的OpenAI兼容接口)
*
* @return OpenAiChatModel实例
*/
@Bean("openAiChatModel")
public OpenAiChatModel openAiChatModel() {
ChatModelListener logger = new ChatModelListener() {
@Override
public void onRequest(ChatModelRequestContext reqCtx) {
// 1. 拿到 List<ChatMessage>
List<ChatMessage> messages = reqCtx.chatRequest().messages();
System.out.println("→ 请求: " + messages);
}
@Override
public void onResponse(ChatModelResponseContext respCtx) {
// 2. 先取 ChatModelResponse
ChatResponse response = respCtx.chatResponse();
// 3. 再取 AiMessage
AiMessage aiMessage = response.aiMessage();
// 4. 工具调用
List<ToolExecutionRequest> tools = aiMessage.toolExecutionRequests();
for (ToolExecutionRequest t : tools) {
System.out.println("← tool : " + t.name());
System.out.println("← arguments : " + t.arguments()); // 原始 JSON
}
// 5. 纯文本
if (aiMessage.text() != null) {
System.out.println("← text : " + aiMessage.text());
}
}
@Override
public void onError(ChatModelErrorContext errorCtx) {
errorCtx.error().printStackTrace();
}
};
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.baseUrl(baseUrl)
.listeners(List.of(logger))
.build();
}
@Bean
public StructureAssistant structureAssistant(@Qualifier("openAiChatModel") OpenAiChatModel chatModel, HistoryEventTool historyEventTool) {
return AiServices.builder(StructureAssistant.class)
.chatModel(chatModel)
.tools(historyEventTool)
.build();
}
}
- 服务类
@Slf4j
@Service
public class StructureChatService {
@Autowired
private StructureAssistant structureAssistant;
/**
* 通过提示词range大模型返回JSON格式的内容
*/
public HistoryEvent byFunctionCall(String prompt) {
HistoryEvent event = structureAssistant.byFunctionCall(prompt);
log.info("响应:{}", event);
return event;
}
}
4.3. 自动推导
- 配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "langchain4j.open-ai.chat-model")
public class LangChain4jConfig {
private String apiKey;
private String modelName;
private String baseUrl;
@Bean("modelWithJSONFormat")
public OpenAiChatModel modelFromObject() {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.baseUrl(baseUrl)
.responseFormat(ResponseFormat.JSON)
.build();
}
@Bean("assistantWithModelFromObject")
public StructureAssistant assistantWithModelFromObject(@Qualifier("modelWithJSONFormat") OpenAiChatModel modelWithJSONFormat) {
return AiServices.create(StructureAssistant.class, modelWithJSONFormat);
}
}
- 服务接口
public interface StructureAssistant {
HistoryEvent simpleChat(String userMessage);
}
- 服务类
@Slf4j
@Service
public class StructureChatService {
@Autowired
private StructureAssistant assistantWithModelFromObject;
public HistoryEvent chatByModelFromObject(String message) {
HistoryEvent historyEvent = assistantWithModelFromObject.simpleChat(message);
log.info("1. 收到响应对象: {}", historyEvent);
return historyEvent;
}
}
4.4. 指定Schema
- 配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "langchain4j.open-ai.chat-model")
public class LangChain4jConfig {
private String apiKey;
private String modelName;
private String baseUrl;
@Bean("modelFromSchema")
public OpenAiChatModel modelFromSchema() {
JsonSchema jsonSchema = JsonSchema.builder()
.name("HistoryEvent") // OpenAI 要求顶层 schema 有名字
.rootElement(
JsonObjectSchema.builder()
.addProperty("mainCharacters", // 字符串数组
JsonArraySchema.builder()
.items(new JsonStringSchema())
.build())
.addProperty("year", new JsonIntegerSchema())
.addProperty("description", new JsonStringSchema())
.required("mainCharacters", "year", "description")
.build())
.build();
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.baseUrl(baseUrl)
.responseFormat(ResponseFormat.builder()
.type(ResponseFormatType.JSON)
.jsonSchema(jsonSchema)
.build())
.build();
}
@Bean("assistantWithModelFromSchema")
public StructureAssistant assistantWithModelFromSchema(@Qualifier("modelFromSchema") OpenAiChatModel modelFromSchema) {
return AiServices.create(StructureAssistant.class, modelFromSchema);
}
}
- 服务接口
public interface StructureAssistant {
HistoryEvent simpleChat(String userMessage);
}
- 服务类
@Slf4j
@Service
public class StructureChatService {
@Autowired
private StructureAssistant assistantWithModelFromSchema;
public HistoryEvent chatByModelFromSchema(String message) {
HistoryEvent historyEvent = assistantWithModelFromSchema.simpleChat(message);
log.info("2. 收到响应对象: {}", historyEvent);
return historyEvent;
}
}
5. controller
@RestController
@RequestMapping("/api/structureChat")
public class StructureChatController {
@Autowired
private StructureChatService structureChatService;
/**
* 1.提示词指定输出格式
* {
* "prompt": "给出伊阙之战的关键信息,以json格式输出,字段包括:主要人物、时间、简介,格式是{\"main_characters\":[\"\",\"\"],\"year\":2000,\"description\":\"\"}"
* }
*/
@PostMapping("/output/byPrompt")
public ResponseEntity<HistoryEvent> byPrompt(@RequestBody PromptRequest request) {
return ResponseEntity.ok(structureChatService.byPrompt(request.getPrompt()));
}
/**
* 2. function call
* {
* "prompt": "介绍昆阳之战"
* }
*/
@PostMapping("/output/byFunctionCall")
public ResponseEntity<HistoryEvent> byFunctionCall(@RequestBody PromptRequest request) {
return ResponseEntity.ok(structureChatService.byFunctionCall(request.getPrompt()));
}
/**
* 封装一个通用方法,根据isModelFromSchema参数调用不同的服务方法
*/
private ResponseEntity<HistoryEvent> chat(PromptRequest request, boolean isModelFromSchema) {
try {
HistoryEvent historyEvent = null;
if (isModelFromSchema) {
historyEvent = structureChatService.chatByModelFromSchema(request.getPrompt());
} else {
historyEvent = structureChatService.chatByModelFromObject(request.getPrompt());
}
return ResponseEntity.ok(historyEvent);
} catch (Exception e) {
HistoryEvent errRlt = new HistoryEvent();
errRlt.setDescription("请求处理失败: " + e.getMessage());
// 捕获异常并返回错误信息
return ResponseEntity.status(500).body(errRlt);
}
}
/**
* 3. 自动推导
*/
@PostMapping("/output/modelFromObject")
public ResponseEntity<HistoryEvent> modelFromObject(@RequestBody PromptRequest request) {
return chat(request, false);
}
/**
* 4. 指定Schema
*/
@PostMapping("/output/modelFromSchema")
public ResponseEntity<HistoryEvent> modelFromSchema(@RequestBody PromptRequest request) {
return chat(request, true);
}
}
更多推荐



所有评论(0)