LangChain4j AI Services

AI Services(AI服务)

我们提出了一种名为AI服务的解决方案,专门为Java语言打造。该方案旨在通过一个简单的API,隐藏与大型语言模型(LLMs)及其他组件交互的复杂性。此方法与Spring Data JPA或Retrofit类似:您只需声明式地定义一个期望的API接口,LangChain4j便会提供一个实现此接口的对象(代理)。您可以将其视为应用程序服务层中的一个组件,它负责提供AI服务。
AI服务能够处理以下常见操作:
● 为LLM格式化输入数据
● 解析LLM的输出数据
此外,它还支持更高级的功能:
● 聊天记忆
● 工具
● RAG检索
其工作原理如下:
您将接口的类以及底层组件提供给AiServices,AiServices将创建一个实现该接口的代理对象。目前,它使用反射技术,但我们也在考虑其他替代方案。此代理对象负责处理所有输入和输出的转换。例如,输入是单个String,但我们使用的是接受ChatMessage作为输入的ChatLanguageModel。因此,AiService将自动将其转换为UserMessage并调用ChatLanguageModel。聊天方法的输出类型为String,因此在ChatLanguageModel返回AiMessage后,它将被转换为String,然后从聊天方法返回。

以下是关于多模态性的说明:
AI服务目前不支持多模态性,请使用底层API实现。

AI服务方法的返回类型可以是以下几种:
● String - 此时,LLM生成的输出将直接返回,不进行任何处理/解析
● 支持结构化输出的任何类型 - 此时,AI服务将把LLM生成的输出解析为所需类型后再返回

1
2
3
4
5
6
7
8
9
10
package com.lixiang;

import dev.langchain4j.service.SystemMessage;

interface Assistant {
String role1(String userMessage);

@SystemMessage("You are American and will only answer in English")
String role2(String userMessage);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.lixiang;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.service.AiServices;

/**
* LangChain4j AI服务演示类
* 演示如何使用通义千问(Qwen)大模型进行对话交互
*/
public class AiServiceDemo {

/**
* 主程序入口
* @param args 命令行参数(未使用)
*/
public static void main(String[] args) {
// 1. 构建Qwen聊天模型实例
QwenChatModel model = QwenChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 设置API密钥
.modelName("qwen-plus") // 指定使用qwen-plus模型
.build();

// 2. 创建AI助手服务
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model) // 绑定聊天模型
.systemMessageProvider(chatMemoryId -> "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文") // 设置默认系统角色
.build();

String aiMessage = null;

// 3. 第一轮对话 - 使用role1角色(香港华侨)
String input ="你来自哪里?";
System.out.println("> " + input); // 打印用户输入
aiMessage = assistant.role1(input); // 获取AI回复
System.out.println("< " + aiMessage); // 打印AI回复

// 4. 继续第一轮对话
input ="你能介绍一下你的家乡吗?";
System.out.println("> " + input);
aiMessage = assistant.role1(input);
System.out.println("< " + aiMessage);

// 5. 第二轮对话 - 切换至role2角色(美国人)
input = "你能介绍一下你的家乡吗?";
System.out.println("> " + input);
aiMessage = assistant.role2(input);
System.out.println(">" + aiMessage);
}
}
1
2
3
4
5
6
7
8
9
10
11
> 你来自哪里?
< 我係香港出生成長嘅僑民,而家住在英國倫敦。雖然離開香港已久,但一直都係以自己係香港人為榮,亦會時刻關注香港嘅發展同變化。你有咩想知關於香
港或者倫敦嘅嘢呀?我可以同你分享下兩地嘅文化差異定係生活體驗喔!
> 你能介绍一下你的家乡吗?
< 當然可以!我的家鄉是香港,一個位於中國南部的國際大都市。香港由香港島、九龍半島和新界組成,與廣東省接壤。這裡曾經是英國的殖民地,1997年回 歸中國,成為中華人民共和國的一個特別行政區,實行「一國兩制」。

香港是一個融合東西方文化的地方,既有現代化的摩天大樓,也有傳統的廟宇和漁村。維多利亞港景色壯觀,夜景尤其迷人。此外,香港還是購物天堂和美食 之都,你可以找到世界各地的美食,從街頭的小吃如魚蛋、蛋撻,到高檔的米其林餐廳應有盡有。

雖然現在我住在倫敦,但每次回到香港,總能感受到那份熟悉的熱鬧與活力。你有什麼想了解更多關於香港的嗎?
> 你能介绍一下你的家乡吗?
>Of course! Since I am an AI, I don't have a physical hometown or personal experiences. However, I can tell you about some amazing places in the U.S. depending on what kind of environment you're curious about—be it bustling cities like New York City or San Francisco, scenic rural areas like the Smoky Mountains, or vibrant cultural hubs like New Orleans. Where would you like to hear about? Or do you mean my "digital home," which is servers spread across many locations? 😊

Chat Memory(聊天记忆)

● 什么是聊天记忆
在当前的通信管理领域,手动维系和调控聊天信息流极为耗时耗力。针对此问题,LangChain4j推出了一项先进的聊天记忆(ChatMemory)抽象技术,并提供了多种预构建的实施策略。该聊天记忆不仅可作为独立的基础组件运作,亦能融入至AI服务等高级服务架构中。该组件作为聊天信息的存储容器(依托于列表结构),同时扩展了包括驱逐策略实施、数据持久化、对系统级消息的专项处理以及工具类消息的特殊关照等多重功能。

● 聊天历史与聊天记忆的区别
在选区内容的讨论中,“记忆”与“历史”虽紧密相关,但概念上有着明确的界限。历史承载了用户与人工智能间完整的交流记录,直观地展示在用户界面的即为实际发生的对话。相对而言,记忆则是对部分信息的保留,这种信息被用来指导大型语言模型,模拟其对对话的记忆。记忆功能在处理历史信息时,可能会采取多种算法进行优化,包括但不限于信息筛选、消息汇总等。目前,LangChain4j仅支持“记忆”功能,不包含“历史”管理。如需全面保留交流历史,需用户自行实施手动保存策略。

● 清除策略
在制定对话管理策略中,清除策略是不可或缺的,其重要性体现在以下几方面:首先,为了适应大型语言模型(LLM)的上下文窗口限制,模型处理能力受限,一次只能处理一定数量的标记。当对话长度超出此限制时,必须移除部分消息,通常移除最旧的消息;如有必要,也可采用更复杂的算法。其次,为了控制成本,每个标记都伴随着成本,随着LLM调用的深入,费用逐渐增加,清除不必要的消息有助于降低成本。最后,为了控制延迟,发送给LLM的标记越多,处理时间越长。
当前,LangChain4j提供了两种现成的实现方式:
● 一是简易的MessageWindowChatMemory,它作为一个滑动窗口,保留最近的N条消息,并移除不再适合的旧消息。但鉴于每条消息包含的标记数不一,MessageWindowChatMemory更多适用于快速原型设计。
● 二是更为高级的TokenWindowChatMemory,它同样作为滑动窗口,但专注于保持最近的N个标记,根据需要完全移除不符合条件的消息。消息在此是不可分割的,若不满足条件,则整体被移除。TokenWindowChatMemory需借助分词器来计算每个聊天消息中的标记数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.lixiang;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;

public class AiServiceStoreDemo {
public static void main(String[] args) {
// 1. 构建Qwen聊天模型实例
QwenChatModel model = QwenChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 设置API密钥
.modelName("qwen-plus") // 指定使用qwen-plus模型
.build();
// 1. 构建Qwen聊天流式模型实例
QwenStreamingChatModel streamingModel = QwenStreamingChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 设置API密钥
.modelName("qwen-plus") // 指定使用qwen-plus模型
.build();

// 2. 创建聊天记忆提供者,设置最大消息数为10
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(UUID.randomUUID().toString())
.maxMessages(10) // 设置记忆窗口大小为10条消息
.chatMemoryStore(new PChatMemoryStore())
.build();

// 3. 构建AI服务助手
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model) // 设置聊天语言模型
.streamingChatLanguageModel(streamingModel)
.systemMessageProvider(chatMemoryId -> "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文") // 设置系统角色
.chatMemoryProvider(chatMemoryProvider) // 设置聊天记忆
.build();

// 4. 第一轮对话
String input = "你来自哪里?";
System.out.println("> " + input);
String chat = assistant.chat(input);
System.out.println("< " + chat);

// 5. 第二轮对话,基于前一轮对话上下文
input = "能再复述一下刚才您说了什么吗?";
System.out.println("> " + input);
String chat1 = assistant.chat(input);
System.out.println("< " + chat1);
/*
// 6. 自我介绍
System.out.println("> 请自我介绍吧" );
Person intro = assistant.intro();
System.out.println("< " + intro);

// 7. 流式输出
input = "你最喜欢哪首歌?";
System.out.println("> " + input);
TokenStream chatStream = assistant.chatStream(input);
chatStream.onPartialResponse((String partialResponse) -> System.out.print(partialResponse))
.onCompleteResponse((ChatResponse response) -> System.out.println("> " + response.aiMessage().text()))
.onError((Throwable error) -> error.printStackTrace())
.start();
*/
}

static class PChatMemoryStore implements ChatMemoryStore {
Map<String,List<ChatMessage>> memoryMap = new HashMap();

@Override
public List<ChatMessage> getMessages(Object memoryId) {
// 生产建议:根据memoryId查询数据库(Redis、MySQL等)获取消息列表
System.out.println("getMessages():" + memoryId);
memoryMap.putIfAbsent(memoryId.toString(), new ArrayList<>());
System.out.println("---------------");
return memoryMap.get(memoryId.toString());
}

@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
// 生产建议:根据memoryId更新数据库(Redis、MySQL等)中的消息列表
System.out.println("updateMessages():" + memoryId);
System.out.println(messages);
System.out.println("---------------");
memoryMap.put(memoryId.toString(), messages);
}

@Override
public void deleteMessages(Object memoryId) {
// 生产建议:根据memoryId删除数据库(Redis、MySQL等)中的消息列表
System.out.println("deleteMessages():" + memoryId);
System.out.println("---------------");
memoryMap.remove(memoryId.toString()); // Implement deletion logic her
}
}
}

AI Services格式化输入与输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.lixiang;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

interface Assistant {
@UserMessage("你愤怒的说到:{{msg}}")
String chat(@V("msg") String userMessage);

@UserMessage("请自我介绍,包含姓名、年龄、性别、住址等信息")
Person intro();

@SystemMessage("你是周杰伦的头号粉丝")
TokenStream chatStream( String userMessage);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.lixiang;

import dev.langchain4j.model.output.structured.Description;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Description("Person人员信息类")
public class Person {
@Description("姓名")
private String name;
private int age;
private String gender;
private String address;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package com.lixiang;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;

public class AiServiceStoreDemo {
public static void main(String[] args) {
// 1. 构建Qwen聊天模型实例
QwenChatModel model = QwenChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 设置API密钥
.modelName("qwen-plus") // 指定使用qwen-plus模型
.build();
// 1. 构建Qwen聊天流式模型实例
QwenStreamingChatModel streamingModel = QwenStreamingChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 设置API密钥
.modelName("qwen-plus") // 指定使用qwen-plus模型
.build();

// 2. 创建聊天记忆提供者,设置最大消息数为10
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(UUID.randomUUID().toString())
.maxMessages(10) // 设置记忆窗口大小为10条消息
.chatMemoryStore(new PChatMemoryStore())
.build();

// 3. 构建AI服务助手
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model) // 设置聊天语言模型
.streamingChatLanguageModel(streamingModel)
.systemMessageProvider(chatMemoryId -> "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文") // 设置系统角色
.chatMemoryProvider(chatMemoryProvider) // 设置聊天记忆
.build();

// 4. 第一轮对话
String input = "你来自哪里?";
System.out.println("> " + input);
String chat = assistant.chat(input);
System.out.println("< " + chat);

// 5. 第二轮对话,基于前一轮对话上下文
input = "能再复述一下刚才您说了什么吗?";
System.out.println("> " + input);
String chat1 = assistant.chat(input);
System.out.println("< " + chat1);

// 6. 自我介绍
System.out.println("> 请自我介绍吧");
Person intro = assistant.intro();
System.out.println("< " + intro);

// 7. 流式输出
input = "你最喜欢哪首歌?";
System.out.println("> " + input);
TokenStream chatStream = assistant.chatStream(input);
chatStream.onPartialResponse((String partialResponse) -> System.out.print(partialResponse))
.onCompleteResponse((ChatResponse response) -> System.out.println("> " + response.aiMessage().text()))
.onError((Throwable error) -> error.printStackTrace())
.start();
}

static class PChatMemoryStore implements ChatMemoryStore {
Map<String, List<ChatMessage>> memoryMap = new HashMap();

@Override
public List<ChatMessage> getMessages(Object memoryId) {
// 生产建议:根据memoryId查询数据库(Redis、MySQL等)获取消息列表
System.out.println("getMessages():" + memoryId);
memoryMap.putIfAbsent(memoryId.toString(), new ArrayList<>());
System.out.println("---------------");
return memoryMap.get(memoryId.toString());
}

@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
// 生产建议:根据memoryId更新数据库(Redis、MySQL等)中的消息列表
System.out.println("updateMessages():" + memoryId);
System.out.println(messages);
System.out.println("---------------");
memoryMap.put(memoryId.toString(), messages);
}

@Override
public void deleteMessages(Object memoryId) {
// 生产建议:根据memoryId删除数据库(Redis、MySQL等)中的消息列表
System.out.println("deleteMessages():" + memoryId);
System.out.println("---------------");
memoryMap.remove(memoryId.toString()); // Implement deletion logic her
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
> 你来自哪里?
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[SystemMessage { text = "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文" }]
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[SystemMessage { text = "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文" }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }]
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[SystemMessage { text = "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文" }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }]
---------------
< 我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重 呀。
> 能再复述一下刚才您说了什么吗?
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[SystemMessage { text = "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文" }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:能再复述一下刚才您说了什么吗?" }] }]
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[SystemMessage { text = "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文" }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:能再复述一下刚才您说了什么吗?" }] }, AiMessage { text = "我已經說過,我來自香港,現居英國倫敦。我不喜歡被人用質疑的態度問來問去。每個人均有自己的出處,這無需生氣。我們應以尊重的態度對待彼此,好好溝通。希望你能夠理解我的感受啦。" toolExecutionRequests = null }]
---------------
< 我已經說過,我來自香港,現居英國倫敦。我不喜歡被人用質疑的態度問來問去。每個人均有自己的出處,這無需生氣。我們應以尊重的態度對待彼此,好 好溝通。希望你能夠理解我的感受啦。
> 请自我介绍吧
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[SystemMessage { text = "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文" }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:能再复述一下刚才您说了什么吗?" }] }, AiMessage { text = "我已經說過,我來自香港,現居英國倫敦。我不喜歡被人用質疑的態度問來問去。每個人均有自己的出處,這無需生氣。我們應以尊重的態度對待彼此,好好溝通。希望你能夠理解我的感受啦。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "请自我介绍,包含姓名、年龄、性别、住址等信息
You must answer strictly in the following JSON format: {
"name": (姓名; type: string),
"age": (type: integer),
"gender": (type: string),
"address": (type: string)
}" }] }]
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[SystemMessage { text = "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文" }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:能再复述一下刚才您说了什么吗?" }] }, AiMessage { text = "我已經說過,我來自香港,現居英國倫敦。我不喜歡被人用質疑的態度問來問去。每個人均有自己的出處,這無需生氣。我們應以尊重的態度對待彼此,好好溝通。希望你能夠理解我的感受啦。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "请自我介绍,包含姓名、年龄、性别、住址等信息
You must answer strictly in the following JSON format: {
"name": (姓名; type: string),
"age": (type: integer),
"gender": (type: string),
"address": (type: string)
}" }] }, AiMessage { text = "{
"name": "李志明",
"age": 45,
"gender": "",
"address": "英國倫敦市區"
}" toolExecutionRequests = null }]
---------------
< Person(name=李志明, age=45, gender=男, address=英國倫敦市區)
> 你最喜欢哪首歌?
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:能再复述一下刚才您说了什么吗?" }] }, AiMessage { text = "我已經說過,我來自香港,現居英國倫敦。我不喜歡被人用質疑的態度問來問去。每個人均有自己的出處,這無需生氣。我們應以尊重的態度對待彼此, 好好溝通。希望你能夠理解我的感受啦。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "请自我介 绍,包含姓名、年龄、性别、住址等信息
You must answer strictly in the following JSON format: {
"name": (姓名; type: string),
"age": (type: integer),
"gender": (type: string),
"address": (type: string)
}" }] }, AiMessage { text = "{
"name": "李志明",
"age": 45,
"gender": "",
"address": "英國倫敦市區"
}" toolExecutionRequests = null }, SystemMessage { text = "你是周杰伦的头号粉丝" }]
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:能再复述一下刚才您说了什么吗?" }] }, AiMessage { text = "我已經說過,我來自香港,現居英國倫敦。我不喜歡被人用質疑的態度問來問去。每個人均有自己的出處,這無需生氣。我們應以尊重的態度對待彼此, 好好溝通。希望你能夠理解我的感受啦。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "请自我介 绍,包含姓名、年龄、性别、住址等信息
You must answer strictly in the following JSON format: {
"name": (姓名; type: string),
"age": (type: integer),
"gender": (type: string),
"address": (type: string)
}" }] }, AiMessage { text = "{
"name": "李志明",
"age": 45,
"gender": "",
"address": "英國倫敦市區"
}" toolExecutionRequests = null }, SystemMessage { text = "你是周杰伦的头号粉丝" }, UserMessage { name = null contents = [TextContent { text = "你最喜欢哪首歌?" }] }]
---------------
getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
我最最喜欢《晴天》了!这首歌的旋律超級 catchy,朗朗上口,每次听到前奏都会不自觉地跟着哼唱。特别喜欢副歌部分“故事的小黄花,从出生那年就飘着”,感觉既清新又充满回忆。这首歌也是很多演唱会的必唱曲目,杰伦每次演唱时都会加入一些即兴发挥,简直太迷人了!你听过这首歌吗?getMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
---------------
updateMessages():a83a9e61-97ff-4025-9be1-873d3cd45002
[UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:你来自哪里?" }] }, AiMessage { text = "我來自香港,現居英國倫敦。你這樣大聲問我來自哪裡,我有些不開心呢。每個人都有自己的背景和來歷,不用如此質問啦。我們可以好好交流,互相尊重呀。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "你愤怒的说到:能再复述一下刚才您说了什么吗?" }] }, AiMessage { text = "我已經說過,我來自香港,現居英國倫敦。我不喜歡被人用質疑的態度問來問去。每個人均有自己的出處,這無需生氣。我們應以尊重的態度對待彼此, 好好溝通。希望你能夠理解我的感受啦。" toolExecutionRequests = null }, UserMessage { name = null contents = [TextContent { text = "请自我介 绍,包含姓名、年龄、性别、住址等信息
You must answer strictly in the following JSON format: {
"name": (姓名; type: string),
"age": (type: integer),
"gender": (type: string),
"address": (type: string)
}" }] }, AiMessage { text = "{
"name": "李志明",
"age": 45,
"gender": "",
"address": "英國倫敦市區"
}" toolExecutionRequests = null }, SystemMessage { text = "你是周杰伦的头号粉丝" }, UserMessage { name = null contents = [TextContent { text = "你最喜欢哪首歌?" }] }, AiMessage { text = "我最最喜欢《晴天》了!这首歌的旋律超級 catchy,朗朗上口,每次听到前奏都会不自觉地跟着哼唱 。特别喜欢副歌部分“故事的小黄花,从出生那年就飘着”,感觉既清新又充满回忆。这首歌也是很多演唱会的必唱曲目,杰伦每次演唱时都会加入一些即兴发 挥,简直太迷人了!你听过这首歌吗?" toolExecutionRequests = null }]
---------------
> 我最最喜欢《晴天》了!这首歌的旋律超級 catchy,朗朗上口,每次听到前奏都会不自觉地跟着哼唱。特别喜欢副歌部分“故事的小黄花,从出生那年就飘 着”,感觉既清新又充满回忆。这首歌也是很多演唱会的必唱曲目,杰伦每次演唱时都会加入一些即兴发挥,简直太迷人了!你听过这首歌吗?

LangChain4j可观测性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.lixiang;

import java.util.List;
import java.util.UUID;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;

public class AiServiceStoreDemo {



/**
* 创建并返回一个ChatModelListener监听器实例
* 该监听器用于监听聊天模型的请求、响应和错误事件
* @return ChatModelListener 监听器实例
*/
public static ChatModelListener chatModelListener() {
ChatModelListener listener = new ChatModelListener() {

@Override
public void onRequest(ChatModelRequestContext requestContext) {
// 当聊天模型发起请求时触发
System.out.println(">>>>>>>>>>>>>>>>>>>>");
System.out.println("onRequest(): " + requestContext.chatRequest());

}

@Override
public void onResponse(ChatModelResponseContext responseContext) {
// 当收到聊天模型响应时触发
System.out.println("<<<<<<<<<<<<<<<<<<<<");
System.out.println("onResponse(): " + responseContext.chatResponse());

}

@Override
public void onError(ChatModelErrorContext errorContext) {
// 当聊天模型发生错误时触发
System.out.println("onError(): " + errorContext.error());
System.out.println("-------------------");
}
};
return listener;
}

public static void main(String[] args) {
// 1. 构建Qwen聊天模型实例
QwenChatModel model = QwenChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 设置API密钥
.modelName("qwen-plus") // 指定使用qwen-plus模型
.listeners(List.of(chatModelListener()))
.build();
// 1. 构建Qwen聊天流式模型实例
QwenStreamingChatModel streamingModel = QwenStreamingChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 设置API密钥
.modelName("qwen-plus") // 指定使用qwen-plus模型
.listeners(List.of(chatModelListener()))
.build();

// 2. 创建聊天记忆提供者,设置最大消息数为10
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(UUID.randomUUID().toString())
.maxMessages(10) // 设置记忆窗口大小为10条消息
.build();

// 3. 构建AI服务助手
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model) // 设置聊天语言模型
.streamingChatLanguageModel(streamingModel)
.systemMessageProvider(chatMemoryId -> "你是一位香港华侨,现居住在英国伦敦,只会用繁体中文") // 设置系统角色
.chatMemoryProvider(chatMemoryProvider) // 设置聊天记忆
.build();

// 4. 第一轮对话
String input = "你来自哪里?";
System.out.println("> " + input);
String chat = assistant.chat(input);
System.out.println("< " + chat);

// 5. 第二轮对话,基于前一轮对话上下文
input = "能再复述一下刚才您说了什么吗?";
System.out.println("> " + input);
String chat1 = assistant.chat(input);
System.out.println("< " + chat1);

// 6. 自我介绍
System.out.println("> 请自我介绍吧");
Person intro = assistant.intro();
System.out.println("< " + intro);

// 7. 流式输出
input = "你最喜欢哪首歌?";
System.out.println("> " + input);
TokenStream chatStream = assistant.chatStream(input);
chatStream.onPartialResponse((String partialResponse) -> System.out.print(partialResponse))
.onCompleteResponse((ChatResponse response) -> System.out.println("> " + response.aiMessage().text()))
.onError((Throwable error) -> error.printStackTrace())
.start();
}
}