参照该课程

基础概念

token

token是语言模型的 计费/生成 基本单位,大概理解成每token生成0.75个中文。

API KEY

由于大部分人不会部署模型到本地,所以需要消耗token来调用模型,而调用模型的时候需要使用API Key进行身份验证,所以需要准备一个厂商的API Key。

prompt

prompt是输入给模型的文本,模型会根据这个文本来生成相应的输出。
狭义上,prompt就是我们对大模型说了什么,广义上,还包括了其他属性,比如当前设置的 温度Temperaturetop_p等参数,这些参数会影响模型的输出结果。
例如:

{
    "model": "deepseek-v4-pro",
    "messages": [
        {
        "role": "system",
        "content": "这里输入一些人物设定、场景设定、对话风格等信息,来引导模型生成符合预期的内容,如:你是一个段子高手。"
        },
        {
        "role": "user",
        "content": "这里是用户输入的内容,如:开个玩笑"
        }
    ],
    "temperature": 1.0,
}

接口调用

当拥有一个API Key后,就可以通过HTTP的post请求来调用大模型的接口,向厂家提供的接口发送一个包含promptapi key的请求,模型会根据这个prompt来生成相应的输出。
例如:

curl https://api.deepseek.com/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
  -d '{
        "model": "deepseek-v4-pro",
        "messages": [
          {"role": "system", "content": "You are a helpful assistant."},
          {"role": "user", "content": "Hello!"}
        ],
        "thinking": {"type": "enabled"},
        "reasoning_effort": "high",
        "stream": false
      }'

一些参数

上面prompt除了我们发送给大模型的messages,还有一些参数会影响模型的输出结果,常见的参数有:

  • temperature:控制生成文本的随机程度,值越高,生成的文本越随机,值越低,生成的文本越确定。
  • top_p:与Temperature类似,值越高,生成的文本越多样化,值越低,生成的文本越集中。
    大模型本质是一个概率模型,输入一个字后面有不同概率接不同的字,假如输入“我”,后面可能接40%接“是”、30%接“喜欢”、20%接“想”等等,
    top_p假如说是100%,就会考虑所有的可能性,top_p如果是40%,就只会考虑这里的"是"了。
  • max_tokens:控制生成文本的最大长度,单位是token。
  • stream:控制是否开启流式输出,开启后模型会在生成文本的过程中逐步返回结果,而不是等到生成完成后一次性。

上面的messages列表中可以发现出现了不同的role,常见的有:

  • system:系统角色,设定AI的行为和角色。
  • user:用户角色,表示用户的输入。
  • assistant:助手角色,表示AI的回复。
  • tool:工具调用的消息。

SpringAI

Spring AI 作为 Spring 官方推出的 AI 应用开发框架,其核心设计哲学是将 AI 能力无缝集成到 Spring 生态系统中,为 Java 开发者提供熟悉、一致的编程模型。
Spring AI 不是要重新发明轮子,而是基于 Spring 的核心原则——依赖注入、面向切面编程和模板模式,为各种 AI 服务提供统一的抽象层。

准备

参考:https://springdoc.cn/spring-ai/index.html
版本/依赖:
JDK:17 (官方最低要求17)
SpringAI :1.0.0
SpringBoot : 3.4.0
一个大模型的starter: 我用的deepseek

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>

配置yml文件:

spring:
  ai:
    deepseek:
      api-key: 你的apikey

API - Message

Spring AI 提供消息类型对应promptmessages列表的不同message
常见的消息类型有:

  • SystemMessage:系统消息,用于设定AI的行为和角色。
  • UserMessage:用户消息,表示用户的输入。
  • AssistantMessage:助手消息,表示AI的回复。
  • ToolMessage:工具消息,表示工具调用的消息。

他们都继承自AbstractMessage,包含textContentmessageType两个属性,对应’content’和’role’。

各message之间关系如图:
在这里插入图片描述

一个简单的示例展示构建一个聊天接口:

package com.example.springai.controller;

import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;

@RestController
public class ChatController {
//  注入一个DeepSeekChatModel,这个模型是Spring AI提供的一个接口,封装了调用DeepSeek模型的逻辑,我们可以通过它来调用DeepSeek模型生成文本。
    private final DeepSeekChatModel chatModel;

    @Autowired
    public ChatController(DeepSeekChatModel chatModel) {
        this.chatModel = chatModel;
    }

    @GetMapping("/ai/generate")
    public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        
//      构建messages列表
        UserMessage userMessage = new UserMessage(message);
        SystemMessage systemMessage = new SystemMessage("你是一个段子高手");
        
//      准备构建Prompt(消息列表和相关参数),参数通过DeepSeekChatOptions来设置
        DeepSeekChatOptions deepSeekChatOptions = new DeepSeekChatOptions();
        deepSeekChatOptions.setTemperature(1.4);
        deepSeekChatOptions.setModel("deepseek-v4-flash");
        deepSeekChatOptions.setMaxTokens(2048);

//      通过builder构建DeepSeekChatOptions
        DeepSeekChatOptions chatoptions = DeepSeekChatOptions.builder()
                .model("deepseek-v4-flash")
                .temperature(1.4)
                .maxTokens(2048)
                .build();
        
//        .call()方法通过ctrl+P查看参数,他可以传入三种类型,分别是String、Message、Prompt。
//        例如:
//        return Map.of("generation", chatModel.call("Tell me a joke") ); // 传入string
//        return Map.of("generation", chatModel.call(systemMessage, userMessage) ); // 传入message
        return Map.of("generation", chatModel.call(new Prompt(List.of(systemMessage, userMessage), deepSeekChatOptions)) ); // 传入prompt
    }
}

API - Prompt

刚才的案例中已经初步展示了Prompt的使用,Prompt包括消息列表和相关参数。可以new一个Prompt,包含List.of<Message>DeepSeekChatOptions,也可以通过builder来构建chatoptions

API - ChatResponse

.call()方法的返回值是一个ChatResponse对象,包含generationchatResponseMetadata

其中generation是模型生成的信息,包含assisantMessagechatGenerationMetadatachatResponseMetadata包含一些元信息,如生成的token数量、使用的模型等。
assisantMessage里面包含了生成的文本内容、消息类型等信息。

遇到陌生API快速学习的小tip:

  • 可以打个断点,运行的时候看一下
  • 尝试调用对象的方法(对象.方法)
  • 右键对象,可以 对表达式求值,看看每一个getxxx方法都出来些什么东西

API - ChatModel

结构如图:
在这里插入图片描述

其实流程和prompt/chatresponse的结构上文已经有所提及了,chatmodel就是把我们输入的prompt转换成厂商接口需要的requset格式,调用厂商接口,拿到response后再把结果转换成chatresponse。

需要使用的时候这样注入一下就行了。

private final DeepSeekChatModel chatModel;
@Autowired
public ChatController(DeepSeekChatModel chatModel) { this.chatModel = chatModel; }

此外,前面用的都是一次性的返回,chatmodel还支持流式响应。使用chatModel.stream()方法,返回一个流Flux
Flux<>中的内容取决于入参,如果入参是String或者Message,那么流中的内容就是String,直接返回就行;
如果入参是Prompt,流中的内容是ChatResponse,需要chatResponse.getResult().getOutput().getText()获取文本。
如果是用中文输入,会返回乱码,解决方案是调整编码:

server:
    servlet:
        encoding:
            charset: UTF-8
            enabled: true
            force: true

API - ChatClient

ChatClient可以更简单的调用模型,也提供了更多的高级功能,如:

  • 聊天记忆(Chat Memory)
  • 工具/函数调用(Function Calling)
  • RAG

创建

直接注入:

@RestController
public class ChatController {
    private final ChatClient chatClient;

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

可以选择模型的方式(传入chatModel):

@RestController
public class ChatController {
    private final ChatClient chatClient;
    public ChatController(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel)
                .build();
    }
}

调用模型

使用chatClient.prompt()方法,传入Prompt对象或者直接传入消息和参数,最后调用.call().content()方法获取String或者.call().chatResponse()获取更多信息。
和chatModel不太一样,这里call返回的是一个CallResponseSpec,chatModel的call直接返回ChatResponse
案例:

package com.example.springai.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/chat-client")
public class ChatClientController {
    private final ChatClient chatClient;
//  可以自己选择不同的model
    public ChatClientController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
//        this.chatClient = ChatClient.builder(构造时传入chatModel).build();
    }

    @GetMapping("/test-client")
    public String testClient(String message) {

        DeepSeekChatOptions chatoptions = DeepSeekChatOptions.builder()
                .model("deepseek-v4-flash")
                .temperature(1.4)
                .maxTokens(2048)
                .build();
// -----------------------------------------------------------------------------------------
//        SystemMessage systemMessage = new SystemMessage("You are a helpful assistant.");
//        UserMessage userMessage = new UserMessage(message);
//        Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatoptions);
//        return chatClient.prompt(prompt)
//                .call()
//                .content();

//        或者直接在这里组装prompt
        return chatClient.prompt()
                .system("You are a helpful assistant.")
                .user(message)
                .options(chatoptions)
                .call() // 也可以.stream()返回流式响应
                .content(); // 也可 .chatResponse 返回ChatResponse
    }
}

返回实体

ChatClient还支持返回一个实体对象,比如让大模型返回一本书(对象),属性有名称和作者。
首先定义类:

@Data // lombok注解,自动生成getter/setter等方法
public class Book {
    private String name;
    private String author;
}

然后在调用的时候指定返回类型并传入该类的字节码对象即可:

@GetMapping("/response")
public Book response() {
    return chatClient.prompt()
            .user("给我随机生成一本书,要求书名和作者都是中文")
            .call().entity(Book.class);
}

模型就可以返回:

{
  "name": "时间的玫瑰",
  "author": "林清玄"
}

这个功能的本质是springAI自动加了一段提示词,告诉大模型要返回一个符合Book类结构的json字符串,去掉```以后(因为大模型生成的文本通常像markdown会有···),把这个json字符串转换成Book对象返回。

Advisor(增强器)

Advisor(增强器)是Spring AI提供的一种机制,可以在调用模型之前或之后对输入或输出进行增强处理。
如图:
在这里插入图片描述

ChatClientRequest: 输入+上下文
ChatClientResponse: 响应+上下文

继承体系:
在这里插入图片描述

对同步调用增强—CallAdvisor。对流式调用增强—StreamAdvisor。
都增强可以俩都继承一下,BaseAdvisor就是都增强的,一般只实现BaseAdvisor的beforeafter方法就行了。

使用

新建MyCallAdvisor

package com.example.springai.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import reactor.core.publisher.Flux;
@Slf4j
public class MyCallAdvisor implements CallAdvisor {
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
        log.info("Before call 1"); // 在调用大模型/进入下一个advisor之前先做一些事情
        ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
        log.info("After call 1"); // 在调用大模型/进入下一个advisor之后再做一些事情
        return chatClientResponse;
    }

    @Override
    public String getName() {
        return "MyCallAdvisor";
    }

    @Override
    public int getOrder() {
//      作用:调整advisor的调用顺序。order值越小,优先级越高。
        return 0;
    }
}

然后在chatClient中调用.advisors()方法,传入这个advisor的实例即可:

@GetMapping("/advisor")
    public String advisorChat() {
        return chatClient.prompt()
                .user("")
                .advisors(new MyCallAdvisor()) // order越小越先被调用,后被响应
                .call().content();
    }

如果有多个advisor,ad1,ad2的order分别是0和1,那么进出顺序就是:prompt->ad1.before->ad2.before->调用模型->ad2.after->ad1.after->返回结果

基于Advisor实现简单的对话记忆功能

便于学习先存进内存
前:获取对话id,获取历史记录,把历史记录并入当前请求送给模型
后:获取对话id,把返回的结果存入内存

advisor类:

package com.example.springai.advisor;

import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;

import java.util.*;

public class SimpleMemoryAdvisor implements BaseAdvisor {
    //  建立静态map来存储对话历史记录,key为对话id,value为历史记录列表(仅供示例,这肯定要oom)。
    private static final Map<String, List<Message>> chatMemory = new HashMap<>();

    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
    //  通过传入的chatClientRequest获取上下文获取对话id
        String conversationId = chatClientRequest.context().get("conversationId").toString();

    // 获取历史记录,如果为空则创建一个新的列表
        List<Message> messages = chatMemory.get(conversationId);
        if (messages == null) {
            messages = new ArrayList<>();
        }
    // 把当前请求中的消息添加到历史记录中
        messages.addAll(chatClientRequest.prompt().getInstructions());

    // 把历史消息也放入请求中
        ChatClientRequest processedChatClientRequest =
                chatClientRequest.mutate()
                        .prompt(chatClientRequest.prompt().mutate().messages(messages).build())
                        .build();

    // 获取请求中的消息 保存
        chatMemory.put(conversationId, messages);
        return processedChatClientRequest;
    }


    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse,
                                    AdvisorChain advisorChain) {
        // 获取会话id
        String conversationId =
                chatClientResponse.context().get("conversationId").toString();
        // 通过会话id查询之前的对话记录
        List<Message> hisMessages = chatMemory.get(conversationId);
        if (hisMessages == null) {
            hisMessages = new ArrayList<>();
        }
        // 获取response中ai的消息 添加到对话记录中
        if(Objects.isNull(chatClientResponse)){
            return chatClientResponse;
        }
        AssistantMessage assistantMessage = chatClientResponse.chatResponse()
                .getResult()
                .getOutput();
        hisMessages.add(assistantMessage);
        chatMemory.put(conversationId, hisMessages);
        return chatClientResponse;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

chatClient接口:
.advisors()方法可以传入advisor实例,也可以传入一个Consumer(在里面设置参数),consumer可以进一步省略成lambda表达式。

@GetMapping("/simpleChatMemory")
public String simpleChatMemory(String msg, String conversationId) {
    return chatClient.prompt()
            .user(msg)
            .advisors(new Consumer<ChatClient.AdvisorSpec>() {
                @Override
                public void accept(ChatClient.AdvisorSpec advisorSpec) {
                    advisorSpec.param("conversationId", conversationId);
                }
            }) // 通过Consumer的方式传入参数,advisor里通过chatClientRequest.context().get("conversationId")获取
            .call().content();
}
官方提供的Advisor实现对话记录存储

实际上官方的和刚才实现的差不多。client中方法如下:

 // 官方方式
    @GetMapping("/chatMemoryAdvisor")
    public String chatMemory(String msg, String conversationId) {
        return chatClient.prompt()
                .user(msg)
                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId)) // 传入对话id
                .advisors(MessageChatMemoryAdvisor.
                        builder(MessageWindowChatMemory
                                .builder().build())
                        .conversationId(conversationId).build()
                ) // 官方提供的记忆advisor,window表示有最大窗口限制,默认20
                .call().content();
    }

Prompt模板

当需要可变的输入时,可以使用prompt template。
硬拼接案例:

String name = "小王";
String voice = "幽默";
String userQuestion = "推荐上海的三个景点";
// 硬编码拼接提示词
String promptText = "你是一个有用的人工智能助手,名字是" + name + ",请用" + voice + "的风格回答以下问题:" + userQuestion;

使用prompt template的好处:动态静态内容分离,提高可维护性。
Prompt模板是预定义的文本结构,可以更高效地构建Prompt,Spring AI提供了PromptTemplate接口来定义Prompt模板,使用时可以通过占位符来动态替换内容。
例如:

// 用户提示词
PromptTemplate userPrompt = new PromptTemplate("你是一个有用的人工智能助手,名字是{name}请用{voice}的风格回答以下问题:{userQuestion}");
Message message = userPrompt.createMessage(Map.of("name", "小白", "voice", "幽默", "userQuestion", "推荐上海的三个景点"));
System.out.println(message);
// 系统提示词
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是一个有用的人工智能助手,名字是{name}请用{voice}的风格回答以下问题:{userQuestion}");
Message message2 = systemPromptTemplate.createMessage(Map.of("name", "小白","voice", "幽默", "userQuestion", "推荐上海的三个景点"));
System.out.println(message2);

但是正常情况应该是提示词单独写一个配置文件,然后加载。(待实现)

ToolCalling(工具调用) —— 给agent装手脚

工具调用是指在生成过程中调用外部工具来获取信息或执行操作,以增强模型的能力。
SpringAI中工具调用的流程:

在这里插入图片描述

发送请求时携带了工具定义(包括名字、描述、input schema等信息),模型生成的文本中会包含工具调用的指令(如调用哪个工具,传入什么参数等),
SpringAI会解析这些指令,调用相应的工具,并将工具的输出结果返回给模型,模型可以根据工具的输出继续生成文本。

案例

  1. 定义工具类
    定义好以后在工具类上添加@Tool@ToolParam注解即可:
public class TimeTools {
    @Tool(description = "通过时区id获取当前时间")
    public String getTimeByZoneId(@ToolParam(description = "时区id, 比如Asia/Shanghai") String zoneId) {
        ZoneId zid = ZoneId.of(zoneId);
        ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
        return zonedDateTime.format(formatter);
    }
}
  1. 在ChatClient中使用工具
    使用.tools()方法,传入工具类的实例即可:
@GetMapping("/ask")
public String askQuestion(@RequestParam("question") String question) {
    return chatClient.prompt()
    .user(question) // 比如询问现在的时间是什么?
    .tools(new TimeTools())
    .call()
    .content();
}


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐