Spring AI Alibaba

0.环境

0.1.依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <maven.compiler.compilerVersion>8</maven.compiler.compilerVersion>
  </properties>

  <dependencies>
    <!--引入spring-boot-starter-web,支持web开发-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>3.4.2</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba.cloud.ai</groupId>
      <artifactId>spring-ai-alibaba-starter</artifactId>
      <version>1.0.0-M5.1</version>
    </dependency>
  </dependencies>


  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

0.2.配置文件

spring:
  application:
    name: ai
  ai:
    dashscope:
      api-key: 12345
      chat:
        options:
          model: qwen-max

1.ChatClient

1.1 创建

1.1.1 单聊天自动创建

    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/chat")
    public String chat(@RequestBody String input) {
        return this.chatClient.prompt()
                // 设置用户输入
                .user(input)
                // 调用
                .call()
                // 以字符串获取结果
                .content();
    }

2.1.2 多聊天手动创建

  • 单一模型类型下的多ChatClient实例
spring:
  ai:
    chat:
      client:
        # 禁用自动创建
        enabled: false
    private final ChatClient customerServiceClient;
    private final ChatClient technicalSupportClient;

    public ChatController(ChatClient.Builder builder) {
        // 客服专用客户端
        this.customerServiceClient = builder
                .build();

        // 技术支持专用客户端
        this.technicalSupportClient = builder
                .build();
    }
  • 不同模型类型的 ChatClient 配置

1.2 响应

AI 模型的响应是一种由ChatResponse类型定义的丰富结构。它包含响应生成相关的元数据,同时它还可以包含多个子响应(称为Generation),每个子响应都有自己的元数据。元数据包括用于创建响应的令牌(token)数量信息(在英文中,每个令牌大约为一个单词的 3/4),了解令牌信息很重要,因为 AI 模型根据每个请求使用的令牌数量收费。

        ChatResponse chatResponse = chatClient.prompt()
                .user("Tell me a joke")
                .call()
                .chatResponse();

1.2.1 返回自定义实体

ActorFilms actorFilm = chatClient.prompt()
                .user("Generate the filmography for a random actor.")
                .call()
                .entity(ActorFilms.class);
        System.out.println(actorFilm);

        List<ActorFilms> actorFilms = chatClient.prompt()
                .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
                .call()
                .entity(new ParameterizedTypeReference<List<ActorFilms>>() {
                });
        actorFilms.stream().forEach(System.out::println);

1.2.2 流式响应

    @GetMapping("/streamChat")
    public Flux<String> streamChat(){
        return chatClient.prompt()
                .user("Tell me a joke")
                .stream()
                .content();
    }

1.3 方法返回值

1.3.1 call()

  • content():返回响应的字符串内容
  • chatResponse():返回ChatResponse包含多个代以及有关响应的元数据的对象,例如,使用了多少个令牌来创建响应。
  • entity: 返回 Java 类型
    entity(ParameterizedTypeReference type):用于返回实体类型的集合。
    entity(Class type): 用于返回特定的实体类型。
    entity(StructuredOutputConverter structuredOutputConverter): 用于指定一个实例 StructuredOutputConverter,将 String 转换为实体类型。

1.3.2 stream()

  • Flux content():返回由AI模型生成的字符串的Flux。
  • Flux chatResponse():返回对象的 Flux ChatResponse,其中包含有关响应的附加元数据。

1.4 ChatClient 默认值

  • 配置类
@Configuration
public class ChatConfig {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
                .build();
    }
}
  • 带模版的配置类
@Configuration
public class ChatConfig {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}")
                .build();
    }
}


    @GetMapping("/defaultChat")
    public void defaultChat(){
        chatClient.prompt()
                .system(sp -> sp.param("voice", "Robert DeNiro"))
                .user("Tell me a joke")
                .call()
                .content();
    }

1.5 Advisors

一个常见模式是使用上下文数据附加或扩充 Prompt,最终使用扩充后的 Prompt 与模型交互。

  • 自己的数据:这是 AI 模型尚未训练过的数据,如特定领域知识、产品文档等,即使模型已经看到过类似的数据,附加的上下文数据也会优先生成响应。
  • 对话历史记录:聊天模型的 API 是无状态的,如果您告诉 AI 模型您的姓名,它不会在后续交互中记住它,每次请求都必须发送对话历史记录,以确保在生成响应时考虑到先前的交互

1.5.1 检索增强生成(RAG)

向量数据库存储的是 AI 模型不知道的数据,当用户问题被发送到 AI 模型时,QuestionAnswerAdvisor 会在向量数据库中查询与用户问题相关的文档。
来自向量数据库的响应被附加到用户消息 Prompt 中,为 AI 模型生成响应提供上下文。
假设您已将数据加载到中 VectorStore,则可以通过向 ChatClient 提供 QuestionAnswerAdvisor 实例来执行检索增强生成 (RAG ) 。
SearchRequest.defaults() 将对 Vector 向量数据库中的所有文档执行相似性搜索。

1.5.2 动态过滤表达式

1.5.3 对话记忆

1.5.4 日志

2.Chat Model对话模型

对话模型(Chat Model)接收一系列消息(Message)作为输入,与模型 LLM 服务进行交互,并接收返回的聊天消息(Chat Message)作为输出。相比于普通的程序输入,模型的输入与输出消息(Message)不止支持纯字符文本,还支持包括语音、图片、视频等作为输入输出。
ChatModel提供模型配置,ChatClient基于模型配置创建实际可用的客户端实例

2.1 chat

@RestController
@RequestMapping("/chatModel")
public class ChatModelController {
    private final ChatModel chatModel;

    public ChatModelController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    @RequestMapping("/chat")
    public String chat() {
        ChatResponse response = chatModel.call(new Prompt("tell a joke"));
        return response.getResult().getOutput().getContent();
    }

    @RequestMapping("/streamChat")
    public void streamChat(HttpServletResponse response) throws IOException {
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");

        Prompt prompt = new Prompt("tell a joke");

        // 使用 Flux 处理流式响应
        chatModel.stream(prompt)
                .doOnNext(chunk -> {
                    try {
                        response.getWriter().write("data: " + chunk.getResult().getOutput().getContent() + "\n\n");
                        response.getWriter().flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                })
                .doOnComplete(() -> {
                    try {
                        response.getWriter().write("event: complete\n\n");
                        response.getWriter().flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                })
                .subscribe();
    }

    @RequestMapping("/chatWithOptions")
    public String chatWithOptions(String input) {
        ChatOptions options = ChatOptions.builder()
                .temperature(0.7)
                .maxTokens(150)
                .build();
        Prompt prompt = new Prompt(input, options);
        ChatResponse response = chatModel.call(prompt);
        return response.getResult().getOutput().getContent();
    }
}

2.2 文升图

@RestController
@RequestMapping("/chatModel")
public class ChatModelController {

    private final ImageModel imageModel;


    public ChatModelController(ImageModel imageModel) {
        this.imageModel = imageModel;
    }

    @RequestMapping("/image")
    public String image() {
        ImageOptions options = ImageOptionsBuilder.builder()
                .build();
        // 文生图
        ImagePrompt imagePrompt = new ImagePrompt("生成一朵花", options);
        ImageResponse response = imageModel.call(imagePrompt);
        String imageUrl = response.getResult().getOutput().getUrl();

        return "redirect:" + imageUrl;
    }
}

3.嵌入模型 (Embedding Model)

嵌入(Embedding)的工作原理是将文本、图像和视频转换为称为向量(Vectors)的浮点数数组。

@RestController
@RequestMapping("/embeddingModel")
public class EmbeddingModelController {

    private final EmbeddingModel embeddingModel;

    @Autowired
    public EmbeddingModelController(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }

    @GetMapping("/ai/embedding")
    public Map embed(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        EmbeddingResponse embeddingResponse = this.embeddingModel.embedForResponse(List.of(message));
        return Map.of("embedding", embeddingResponse);
    }
}

4.RAG检索增强生成

Retrieval Augmented Generation,检索增强生成,是一种结合信息检索和文本生成的技术范式。

  • RagConfig
@Configuration
public class RagConfig {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你将作为一名机器人产品的专家,对于用户的使用需求作出解答")
                .build();
    }

    @Bean
    VectorStore vectorStore(EmbeddingModel embeddingModel) {
        SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(embeddingModel)
                .build();

        // 生成一个机器人产品说明书的文档
        List<Document> documents = List.of(
                new Document("产品说明书:产品名称:智能机器人\n" +
                        "产品描述:智能机器人是一个智能设备,能够自动完成各种任务。\n" +
                        "功能:\n" +
                        "1. 自动导航:机器人能够自动导航到指定位置。\n" +
                        "2. 自动抓取:机器人能够自动抓取物品。\n" +
                        "3. 自动放置:机器人能够自动放置物品。\n"));

        simpleVectorStore.add(documents);
        return simpleVectorStore;
    }
}
  • RagController
@RestController
@RequestMapping("/rag")
public class RagController {

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private VectorStore vectorStore;

    @PostMapping(value = "/chat")
    public String generation() {
        // 发起聊天请求并处理响应
        return chatClient.prompt()
                .user("机器人有哪些功能?")
                .advisors(new QuestionAnswerAdvisor(vectorStore))
                .call()
                .content();
    }
}

4.1 高级功能

5.聊天记忆

@RestController
@RequestMapping("/chatMemory")
public class ChatMemoryController {

    private final ChatClient chatClient;

    public ChatMemoryController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/chat")
    public void caht() {

        //对话记忆的唯一标识
        String conversantId = UUID.randomUUID().toString();

        ChatResponse response = chatClient
                .prompt()
                .user("我想去新疆")
                .advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversantId)
                        .param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .call()
                .chatResponse();
        String content = response.getResult().getOutput().getContent();
        System.out.println( content);

        response = chatClient
                .prompt()
                .user("可以帮我推荐一些美食吗")
                .advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversantId)
                        .param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .call()
                .chatResponse();
        content = response.getResult().getOutput().getContent();
        System.out.println(content);
    }
}

6.Function calling

允许大型语言模型(LLM)在必要时调用一个或多个可用的工具,这些工具通常由开发者定义。工具可以是任何东西:网页搜索、对外部 API 的调用,或特定代码的执行等。

  • 配置
@Configuration
public class FunctionConfig {
    @Bean
    @Description("根据用户编号和订单编号查询订单信息")  //function的描述
    public Function<MockOrderService.Request, MockOrderService.Response> getOrderFunction(MockOrderService mockOrderService) {
        return mockOrderService::getOrder;
    }
}
  • 逻辑实现
@Service
public class MockOrderService {
    public Response getOrder(Request request) {
        String productName = "尤尼克斯羽毛球拍";
        return new Response(String.format("%s的订单编号为%s, 购买的商品为: %s", request.userId, request.orderId, productName));
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public record Request(
            //这里的JsonProperty将转换为function的parameters信息, 包括参数名称和参数描述等
            /*
             {
                "orderId": {
                    "type": "string",
                    "description": "订单编号, 比如1001***"
                    },
                "userId": {
                    "type": "string",
                    "description": "用户编号, 比如2001***"
                }
            }
            */
            @JsonProperty(required = true, value = "orderId") @JsonPropertyDescription("订单编号, 比如1001***") String orderId,
            @JsonProperty(required = true, value = "userId") @JsonPropertyDescription("用户编号, 比如2001***") String userId) {
    }

    public record Response(String description) {
    }
}
  • 调用
@RestController
@RequestMapping("/function")
public class FunctionController {

    private final ChatClient chatClient;

    public FunctionController(ChatClient.Builder builder) {
        this.chatClient = builder.defaultFunctions("getOrderFunction").build();
    }

    @GetMapping("/chat")
    public void test() {

        ChatResponse response = chatClient
                .prompt()
                .user("帮我一下订单, 用户编号为1001, 订单编号为2001")
                .call()
                .chatResponse();

        String content = response.getResult().getOutput().getContent();
        System.out.println(content);
    }
}

7.接入阿里云百炼平台

7.1 阿里云百炼发布agent

  • 新建agent
    image
  • 新建知识库
    image
  • 添加知识库
    image

7.2 yml

spring:
  application:
    name: ai
  ai:
    dashscope:
      api-key: 
      agent:
        app-id: 

7.3 应用

@RestController
@RequestMapping("/agent")
public class BailianAgentRagController {

    private final DashScopeAgent agent;

    @Value("${spring.ai.dashscope.agent.app-id}")
    private String appId;

    public BailianAgentRagController(DashScopeAgentApi dashscopeAgentApi) {
        this.agent = new DashScopeAgent(dashscopeAgentApi);
    }

    @GetMapping("/call")
    public String call() {
        ChatResponse response = agent.call(new Prompt("介绍你自己", DashScopeAgentOptions.builder().withAppId(appId).build()));
        AssistantMessage app_output = response.getResult().getOutput();
        return app_output.getContent();
    }

    @GetMapping("/stream/call")
    public Flux<String> stream() {
        return agent.stream(new Prompt("介绍你自己", DashScopeAgentOptions.builder().withAppId(appId).build())).map(response -> {
            AssistantMessage app_output = response.getResult().getOutput();
            return app_output.getContent();
        });
    }
}
posted @ 2025-10-29 23:00  lwx_R  阅读(23)  评论(0)    收藏  举报