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);
    }
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐