侧边栏壁纸
博主头像
米老鼠吃薯片 博主等级

行动起来,活在当下

  • 累计撰写 7 篇文章
  • 累计创建 6 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
ai

3.Ai Service

Administrator
2026-04-26 / 0 评论 / 1 点赞 / 4 阅读 / 0 字

解决的问题:

1.将组件的连接封装起来,对外只提供接口方法调用,具体实现用代理封装,调用变简单
2.自动处理Prompt模板,使用@SystemMessage @UserMessage注解、{{}}占位符来拼装完整的Prompt
3.可配置自动管理聊天记忆
4.自动处理Tool的调用
5.自动处理返回的结构化

基础用法:

1.一个接口:

public interface AiServiceDemo {
    @SystemMessage("你是一个旅游小助手") //系统Prompt
//    @SystemMessage(fromResource = "system-prompt.txt") //从resources下的system-prompt.txt加载消息模板
    @UserMessage("你好,我是张三,这是我的问题:{{question}}") //userPrompt模板
    String chat(@V("question") String userMessage); //使用@V指定占位符的变量名
}

2.简单使用:

OpenAiChatModel chatModel = OpenAiChatModel.builder()
        .apiKey(System.getenv("AI_KEY"))
        .modelName("qwen-plus")
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
        .build();
//简单封装       
//注意:会将AiServiceDemo接口内的所有方法都做一次代理
AiServiceDemo service = AiServices.create(AiServiceDemo.class, chatModel);
String response = service.chat("1+1=几?");
System.out.println(response);

2.1.AiServices的另一种创建方式:

AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
                .chatModel(chatModel)
                .systemMessageProvider(chatMemoryId -> "你是一个机票小助手")//可手动注入systemPrompt
                .build();

不同的响应

1.可获取除内容外的原信息

//使用Result包裹响应,可得到除了响应内容以外的一些元信息
Result<List<String>> chat2(String userMessage);
Result<List<String>> response = service.chat2("你好");

//响应内容
System.out.println(response.content());

//ai服务调用期间消耗的token总数
System.out.println(response.tokenUsage());
//在 RAG 检索期间检索到的 Content
System.out.println(response.sources());
//AI 服务调用过程中执行过的工具记录
System.out.println(response.toolExecutions());
//模型这次生成结束的原因
System.out.println(response.finishReason());

2.自定义Java对象响应

1.接口层:

//!!!返回类型信息会在请求时携带给llm,如下
//You must answer strictly in the following JSON format:
// {\n\"name\": (type: string),\n\"age\": (type: integer),\n\"birthDate\": (type: date string (2023-12-31)),\n\"address\":
// (type: com.demo.AiServiceDemo$Person$Address: {\n\"street\": (type: string),\n\"streetNumber\": (type: integer),\n\"city\": (type: string)\n})\n}"

//响应时为json数据,如下
//"content":"{\n  \"name\": \"小明\",\n  \"age\": 20,\n  \"birthDate\": \"2006-04-03\",\n  \"address\": {\n    \"street\": \"山村街道\",\n    \"streetNumber\": 200,\n    \"city\": \"北京市\"\n  }\n}","role":"assistant"}
@UserMessage("帮我提取一下用户信息为用户信息对象,信息如下{{it}}")
Person extratPersonInfo(String userInfoStr);

2.响应对象层:

@Data
@Description("一个人的信息")
class Person{
    String name;
    int age;
    LocalDate birthDate;
    Address address;

    @Data
    @Description("地址信息")
    static class Address{
        String street;
        Integer streetNumber;
        String city;
    }
}

3.对话层:

OpenAiChatModel chatModel = OpenAiChatModel.builder()
        .apiKey(System.getenv("AI_KEY"))
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
        .modelName("qwen-plus")
        .logRequests(true)
        .logResponses(true)
        .build();

AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
        .chatModel(chatModel)
        .systemMessageProvider(chatMemoryId -> "你是一个提取信息小助手")
        .build();

String personInfo = """
        小明今年20岁,出生于2006年4月3日,住在北京市山村街道200号
        """;

//自动将响应解析为Java对象
AiServiceDemo.Person person = service.extratPersonInfo(personInfo);
System.out.println(person);

3.流式处理响应

方式1:

@SystemMessage("你是一个旅行规划小助手")
TokenStream streamChat(String userMessage); //返回类型为:TokenStream
//OpenAiStreamingChatModel为流式处理ChatModel
OpenAiStreamingChatModel streamingChatModel = OpenAiStreamingChatModel.builder()
        .apiKey(System.getenv("AI_KEY"))
        .modelName("qwen-plus")
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
        .build();

CountDownLatch latch = new CountDownLatch(1);

AiServiceDemo service = AiServices.create(AiServiceDemo.class, streamingChatModel);
TokenStream tokenStream = service.streamChat("你好");
tokenStream.onPartialResponse(System.out::print) // 模型流式生成部分文本片段时触发
        .onCompleteResponse(r-> {System.out.println("已完成"); latch.countDown();}) // 本次模型响应完整结束后触发,可拿到完整 ChatResponse
        .onError(e-> {e.printStackTrace();latch.countDown();}) //在报错时触发的函数
        .onRetrieved(System.out::println) // RAG 检索完成后触发,拿到本次从知识库召回的 Content 列表/片段
        .onToolExecuted(System.out::println)  //工具执行完成后触发,拿到本次工具调用的执行记录,如工具名、入参、返回结果
        .start();

latch.await();//阻塞代码,防止程序在响应前终止

方式2:

需引入依赖:
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-reactor</artifactId>
    <version>1.12.2-beta22</version>
</dependency>
//返回Flux<String>类型
@SystemMessage("你是一个旅行规划小助手")
Flux<String> fluxChat(String userMessage);
OpenAiStreamingChatModel streamingChatModel = OpenAiStreamingChatModel.builder()
        .apiKey(System.getenv("AI_KEY"))
        .modelName("qwen-plus")
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
        .build();
AiServiceDemo service = AiServices.create(AiServiceDemo.class, streamingChatModel);
//流式处理方式2
Flux<String> fluxResponse = service.fluxChat("你好");
fluxResponse.doOnNext(System.out::print) // 模型流式生成部分文本片段时触发
        .doOnComplete(() -> System.out.println("\n已完成")) // 本次模型响应完整结束后触发,可拿到完整 ChatResponse
        .doOnError(Throwable::printStackTrace)  //在报错时触发的函数
        .blockLast(); //阻塞住

对话记忆

简要知识:

记忆与历史的区别:

历史:真正的完整对话记录,保存用户与ai的完整交互内容,可用于审计、回放、问题追踪。
记忆:从历史记录、用户画像、业务数据或 RAG 检索结果中提炼/筛选出的上下文信息,用于在当前对话中帮助模型理解用户意图和背景。

淘汰策略

原因:
 llm模型的对话上下文窗口大小有限,过大的上下文无法处理;
 太长的上下文会导致延迟增加(控制延迟);
 一些无用的上下文会浪费token(控制成本)
 
langchain4j自带的实现:
 1.MessageWindowChatMemory: 以消息数量为限制,保留最近的N条消息;特点:适合简单场景,实现直观,但不能精确控制 token 数量
 2.TokenWindowChatMemory:以token数量为限制,在 token 上限内尽量保留最近的完整消息,消息通常是不可分割的,如果加入某条消息后超过 token 上限,会淘汰更早的消息,而不是把某条消息截断成一半。

消息持久化(历史对话)

默认情况下,ChatMemory实现在内存中存储ChatMessage

自定义:实现ChatMemoryStore做自定义持久化

自定义历史对话持久化

public class ChatMemoryDemo implements ChatMemoryStore {

    //目前是demo,真实场景可改为MySQL/PgSQL存储
    private final Map<Object,List<ChatMessage>> testMsgStore = new ConcurrentHashMap<>();

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        System.out.println("获取历史对话...");
        List<ChatMessage> chatMessages = testMsgStore.get(memoryId);
        return chatMessages == null || chatMessages.isEmpty() ? List.of() : chatMessages;
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> list) {
        System.out.println("更新历史对话...");
        testMsgStore.put(memoryId, list);
    }

    @Override
    public void deleteMessages(Object memoryId) {
        testMsgStore.remove(memoryId);
    }

}

使用

1.直接使用Langchain4j官方Memory

//方法使用memoryId入参
String memoryChat(@MemoryId int memoryId,@UserMessage String userMessage);
AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
        .chatModel(chatModel)
        //chatMemoryProvider需要配合带有memoryId入参的chat方法
        .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) 
        .build();

//验证:
String response1 = service.memoryChat(1,"你好,你现在是张三");
System.out.println("回答1:"+response1);
System.out.println("-------------------------------------------");
String response2 = service.memoryChat(1,"你好,你是谁");
System.out.println("回答2:"+response2);

2.使用自定义持久化(共享会话记忆版)

//使用不带memoryId入参的会话方法
String memoryChat2(String userMessage);
//使用MessageWindowChatMemory包装自定义持久memory(共享会话记忆)
ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        //自定义持久化历史记录
        .chatMemoryStore(new ChatMemoryDemo()) 
        .build();

AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
        .chatModel(chatModel)
        //共享会话记忆
        //chatMemory配合不带memoryId的chat方法,只能共享会话记忆
        .chatMemory(chatMemory)
        .build();


//验证:
String response1 = service.memoryChat2("你好,你现在是张三");
System.out.println("回答1:"+response1);
System.out.println("-------------------------------------------");
String response2 = service.memoryChat2("你好,你是谁");
System.out.println("回答2:"+response2);

3.使用自定义持久化(根据memoryId隔离记忆版)

//方法使用memoryId入参
String memoryChat(@MemoryId int memoryId,@UserMessage String userMessage);
//包装自定义的持久化
ChatMemoryProvider chatMemoryProvider = memoryId ->  MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(10)
                .chatMemoryStore(new ChatMemoryDemo())
                .build();

AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
        .chatModel(chatModel)
        //隔离会话记忆使用
        .chatMemoryProvider(chatMemoryProvider)
        .build();

//验证:
String response1 = service.memoryChat(1,"你好,你现在是张三");
System.out.println("回答1:"+response1);
System.out.println("-------------------------------------------");
String response2 = service.memoryChat(1,"你好,你是谁");
System.out.println("回答2:"+response2);
1

评论区