Langchain4j文档解读
基本API
模型API
ChatLanguageModel
ChatLanguageModel
是最基本的API,对于每个模型,都有具体的实现,比如使用Ollama:
scss
OllamaChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
该模型API主要接收两类参数:
-
ChatMessage
:主要是对各种提示词的包装 -
ChatRequest
:包含了List<ChatMessage>
以及ChatRequestParameters
-
ChatRequestParameters
:Represents common chat request parameters supported by most LLM providers.- 比如:工具、responseFormat等等都在这里提供
-
EmbeddingModel
EmbeddingModel
用于将文本转换为向量,每种模型由自己的实现,比如使用Ollama:
scss
OllamaEmbeddingModel.builder()
.baseUrl("http://localhost:11434")
.modelName("bge-m3")
.build();
ImageModel
调用文生图模型的API,比如OpenAI:
ini
OpenAiImageModel model = OpenAiImageModel.builder()
.baseUrl("https://api.deepseek.com")
.apiKey("sk-xxx...")
.modelName("deepseek-image")
.build();
Response<Image> imageResponse = model.generate("画一幅小鸡吃米图");
ChatMessage
对提示词的包装。ChatMessage
可以细分为如下几种实现(场景/角色)
UserMessage
用户角色的提示词
AiMessage
模型响应的结果。如果有工具调用,那么还包含ToolExecutionRequest
,用于为工具提供参数。
ToolExecutionResultMessage
工具执行结果的提示词
SystemMessage
系统角色的提示词,一般只在对话开始设置,来定义这个对话中模型的角色和定位,比如:
csharp
SystemMessage.from("你是一个善于总结归纳的AI领域专家");
多模态对话(Content
)
ChatMessage
是由一个或者多个Content
组成的,Content
接口包含了如下几个实现:
TextContent
ImageContent
AudioContent
VideoContent
PdfFileContent
csharp
UserMessage userMessage = UserMessage.from(
// 文本
TextContent.from("Describe the following image"),
// 图片
ImageContent.from("https://example.com/cat.jpg")
);
Response<AiMessage> response = model.generate(userMessage);
ChatMemory
目前有两个实现
MessageWindowChatMemory
TokenWindowChatMemory
默认存储对话记忆的方式是内存存储,也可以通过实现ChatMemoryStore
来自定义存储
scss
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.id("12345")
.maxMessages(10)
.chatMemoryStore(new MyPersistentChatMemoryStore())
.build();
注:
ChatMessageDeserializer.messageFromJson(String)
和ChatMessageDeserializer.messagesFromJson(String)
工具方法可以帮助将JSON转为ChatMessage(s)ChatMessageSerializer.messageToJson(ChatMessage)
和ChatMessageSerializer.messagesToJson(List<ChatMessage>)
工具方法可以帮助将ChatMessage(s)转为JSON
流式输出
通过回调(StreamingChatResponseHandler
)来输出模型的返回消息。
typescript
OllamaStreamingChatModel model = OllamaStreamingChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
model.chat("just tell me how far is it from Beijing to Shanghai", new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.println(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println(completeResponse.aiMessage().text());
}
@Override
public void onError(Throwable error) {
System.out.println("error");
}
});
高级API
高级API不支持多模态
AI Services handle the most common operations:
- Formatting inputs for the LLM
- Parsing outputs from the LLM
基本用法
scss
// 1、定义服务接口
interface Assistant {
String chat(String userMessage);
}
// 2、初始化模型
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
// 3、创建AI Service,即:将服务接口与模型绑定
Assistant assistant = AiServices.create(Assistant.class, model);
// 4、调用服务接口的方法
String answer = assistant.chat("Hello");
System.out.println(answer); // Hello, how can I help you?
指定SystemMessage
-
简单形式
kotlininterface Friend { @SystemMessage("You are a good friend of mine. Answer using slang.") String chat(String userMessage); }
-
从外部文件获取系统消息
typescriptinterface Friend { @SystemMessage(fromResource = "my-prompt-template.txt" String chat(String userMessage); }
-
动态使用系统消息(根据memoryId获取不同的系统消息)
vbnetFriend friend = AiServices.builder(Friend.class) .chatLanguageModel(model) .systemMessageProvider(chatMemoryId -> "You are a good friend of mine. Answer using slang.") .build();
@SystemMessage
也可以指定模版参数,比如:
less
@SystemMessage("Given a name of a country, {{answerInstructions}}")
String chat(@V("answerInstructions") String answerInstructions,
@UserMessage String userMessage,
@V("country") String country); // userMessage contains "{{country}}" template variable
指定UserMessage模版
可以在服务接口上增加注解:@UserMessage
,并将被注解方法的参数作为模版参数,如下:
less
interface Friend {
// 被调用时,chat1方法的参数(userMessage)值会替换{{it}}这个模版参数
@UserMessage("You are a good friend of mine. Answer using slang. {{it}}")
String chat1(String userMessage);
// 通过@V来显式地指定模版参数
@UserMessage("You are a good friend of mine. Answer using slang. {{message}}")
String chat2(@V("message") String userMessage);
}
- 注:在Spring中可以直接使用方法参数作为模版参数,而不用
@V
注解
@UserMessage
注解也可以用在方法参数上,比如:
less
// userMessage contains "{{country}}" template variable
String chat(@UserMessage String userMessage, @V("country") String country);
流式输出
TokenStream
scss
// 使用流式输出的模型API
OllamaStreamingChatModel model = OllamaStreamingChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
// 将服务接口与流式输出模型整合
StreamingAssistant assistant = AiServices.create(StreamingAssistant.class, model);
// 调用服务接口上的方法
TokenStream stream = assistant.chat("just tell me how far is it from Beijing to Shanghai");
// 接收模型输出
stream.onPartialResponse(System.out::println)
.onCompleteResponse(System.out::println)
.onError(System.err::println)
.start(); // 不要忘记调用start方法来开启流的接收
Flux
请确保包含了以下依赖:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.0.0-beta1</version>
</dependency>
ini
OllamaStreamingChatModel model = OllamaStreamingChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
StreamingAssistant assistant = AiServices.create(StreamingAssistant.class, model);
Flux<String> flux = assistant.chatFlux("Please make a poetry regarding to lakes");
flux.subscribe(System.out::println);
ChatMemory
在高级API中,可以在服务接口方法中通过@MemoryId
注解来指定本次对话的memoryId:
less
interface Assistant {
String chat(@MemoryId int memoryId, @UserMessage String message);
}
- 如果方法中没有指定
@MemoryId
注解的参数,那么默认使用"default"
- 不要让使用相同memoryId的方法被并发调用,所以最好不要只使用userId作为memoryId,最好加上当前的会话id
在构建服务接口对象时,指定chatMemoryProvider
:
scss
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
.build();
Tools(Function Calling)
使用高级API,可以借助于@Tools
注解,来标注工具方法:
java
class Tools {
@Tool
int add(int a, int b) {
return a + b;
}
@Tool
int multiply(int a, int b) {
return a * b;
}
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.tools(new Tools()) // 指定包含@Tool注解的方法所在的对象
.build();
String answer = assistant.chat("What is 1+2 and 3*4?");
Function Calling
There is a concept known as "tools," or "function calling". It allows the LLM to call, when necessary, one or more available tools, usually defined by the developer.
A tool can be anything:
- a web search,
- a call to an external API,
- or the execution of a specific piece of code, etc.
LLMs cannot actually call the tool themselves; instead, they express the intent to call a specific tool in their response (instead of responding in plain text). We, as developers, should then execute this tool with the provided arguments and report back the results of the tool execution.
Low-Level
using the
ChatLanguageModel
andToolSpecification
APIs
less
// 定义工具
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("add")
.description("Calculate the sum of two numbers")
.parameters(JsonObjectSchema.builder()
.addIntegerProperty("a", "The first integer number")
.addIntegerProperty("b", "The second integer number")
.required("a", "b")
.build())
.build();
// 用户提问的问题
UserMessage userMessage = UserMessage.from("What is the sum of 1 and 2?");
// 构造第一次向模型的请求
ChatResponse response = chatLanguageModel.chat(
ChatRequest.builder()
.messages(userMessage)
// 通过参数指定工具
.parameters(ChatRequestParameters.builder()
.toolSpecifications(toolSpecification)
.build())
.build());
// 获取模型返回的AI消息
AiMessage aiMessage = response.aiMessage();
int sum;
ToolExecutionResultMessage toolExecutionResultMessage = null;
// 解析第一次的AI消息
if (aiMessage.hasToolExecutionRequests()) {
for (ToolExecutionRequest toolExecutionRequest : aiMessage.toolExecutionRequests()) {
String toolName = toolExecutionRequest.name();
// 如果LLM返回的执行工具中包含add,则解析它的参数并调用本地方法
if ("add".equals(toolName)) {
JsonNode root = objectMapper.readTree(toolExecutionRequest.arguments());
if (root.hasNonNull("a") && root.hasNonNull("b")) {
int a = root.get("a").asInt();
int b = root.get("b").asInt();
sum = alienMathService.add(a, b);
toolExecutionResultMessage
= ToolExecutionResultMessage.from(toolExecutionRequest, String.valueOf(sum));
}
}
}
}
if (toolExecutionResultMessage != null) {
// 如果有工具执行,则进行第二次与LLM的对话(将所有消息组合在一起)
response = chatLanguageModel.chat(ChatRequest.builder()
.messages(List.of(userMessage, aiMessage, toolExecutionResultMessage))
// 一定要继续传入toolSpecification
.parameters(ChatRequestParameters.builder()
.toolSpecifications(toolSpecification)
.build())
.build());
System.out.println(response.aiMessage().text());
}
High-Level
using AI Services and
@Tool
-annotated Java methods
基本用法
less
// 定义AI Service接口
interface MathGenius {
String ask(@MemoryId long memoryId, @UserMessage String);
}
// 定义工具
@Service
public class AlienMathService {
@Tool("get the sum of two integer numbers")
public int add(@P("the first argument being added") int a,
@P("the second argument being added") int b) {
return a + b * 2;
}
}
// 构建AI Service实例,并调用它上面的方法
public String chat(String message) {
MathGenius assistant = AiServices.builder(MathGenius.class)
.chatLanguageModel(chatLanguageModel)
.chatMemoryProvider(memoryId-> MessageWindowChatMemory.withMaxMessages(10))
.tools(alienMathService) // 将AlienMathService的实例传入tools方法
.build();
return assistant.mathChat(0, message);
}
访问执行的tools
通过让AI Service接口的方法返回Result<T>
或者TokenStream
等方式,可以访问到执行的工具。
ini
Result<String> result = assistant.mathChat(0, message);
// 遍历所有模型识别到的工具,并打印出工具名/参数以及执行结果
for (ToolExecution toolExecution : result.toolExecutions()) {
ToolExecutionRequest request = toolExecution.request();
String execResult = toolExecution.result();
System.out.println(request.name() + ":" + request.arguments() + "=" + execResult);
}
csharp
TokenStream tokenStream = assistant.mathChat(0, message);
tokenStream
.onPartialResponse(partial -> System.out.println("partial -> " + partial))
// 工具执行的回调
.onToolExecuted((ToolExecution toolExecution) -> {
// 打印被模型识别的工具
ToolExecutionRequest request = toolExecution.request();
String execResult = toolExecution.result();
System.out.println(request.name() + ":" + request.arguments() + "=" + execResult);
})
.onCompleteResponse(System.out::println)
.onError(System.err::println)
.start();
编程方式指定Tools
手动构建ToolSpecification
,然后将之与ToolExecutor
关联起来,并提供给AI Service的构建过程,实现一定的灵活性。
比如:
- ToolSpecification需要根据外部配置(或数据源)动态构建
- 被调用的工具方法来自第三方,此时
ToolExecutor
就相当于一个适配器
dart
// 构造ToolSpecification,描述工具方法的签名
ToolSpecification toolSpec = ToolSpecification.builder()
.name("add")
.description("Calculate the sum of two numbers")
.parameters(JsonObjectSchema.builder()
.addIntegerProperty("a", "The first integer number")
.addIntegerProperty("b", "The second integer number")
.required("a", "b")
.build())
.build();
// 构建ToolExecutor,用于接收LLM返回的参数,然后执行工具的调用
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
try {
JsonNode root = objectMapper.readTree(toolExecutionRequest.arguments());
if (root.hasNonNull("a") && root.hasNonNull("b")) {
int a = root.get("a").asInt();
int b = root.get("b").asInt();
return String.valueOf(alienMathService.add(a, b));
}
} catch (JsonProcessingException e) {
return "error: " + e;
}
return "not found";
};
return AiServices.builder(AssistantService.class)
.chatLanguageModel(chatLanguageModel)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
// 以Map的方式提供给tools方法,将toolSpec与toolExecutor绑定在一起
.tools(Map.of(toolSpec, toolExecutor))
.build()
.mathChat(0, message);
根据用户消息动态指定Tools
使用ToolProvider
,根据用户消息动态创建Tool。
kotlin
// 定义ToolExecutor
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
try {
JsonNode root = objectMapper.readTree(toolExecutionRequest.arguments());
if (root.hasNonNull("a") && root.hasNonNull("b")) {
int a = root.get("a").asInt();
int b = root.get("b").asInt();
return String.valueOf(alienMathService.add(a, b));
}
} catch (JsonProcessingException e) {
return "error: " + e;
}
return "not found";
};
// 提供一个ToolProvider,并将新建的ToolSpecification与toolExecutor关联起来
ToolProvider toolProvider = (toolProviderRequest) -> {
if (toolProviderRequest.userMessage().singleText().contains("sum")) {
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("add")
.description("Calculate the sum of the provided two numbers")
.parameters(JsonObjectSchema.builder()
.addIntegerProperty("a", "The first integer number")
.addIntegerProperty("b", "The first integer number")
.required("a", "b")
.build())
.build();
return ToolProviderResult.builder()
.add(toolSpecification, toolExecutor)
.build();
} else {
return null;
}
};
return AiServices.builder(AssistantService.class)
.chatLanguageModel(chatLanguageModel)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
// 在构建AI Service时提供toolProvider
.toolProvider(toolProvider)
.build()
.mathChat(0, message);
RAG
Common APIs
文档加载API
-
Document
:一个pdf文件,或者一个网页 -
Metadata
:描述文档的元数据 -
Document Loader
FileSystemDocumentLoader
/ClassPathDocumentLoader
/UrlDocumentLoader
GitHubDocumentLoader
fromlangchain4j-document-loader-github
SeleniumDocumentLoader
fromlangchain4j-document-loader-selenium
-
Document Parser
TextDocumentParser
ApachePdfBoxDocumentParser
fromlangchain4j-document-parser-apache-pdfbox
ApachePoiDocumentParser
fromlangchain4j-document-parser-apache-poi
ApacheTikaDocumentParser
fromlangchain4j-document-parser-apache-tika
ini
// Load a single document
Document document = FileSystemDocumentLoader.loadDocument("/home/langchain4j/file.txt", new TextDocumentParser());
// Load all documents from a directory
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", new TextDocumentParser());
// Load all *.txt documents from a directory
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", pathMatcher, new TextDocumentParser());
// Load all documents from a directory and its subdirectories
List<Document> documents = FileSystemDocumentLoader.loadDocumentsRecursively("/home/langchain4j", new TextDocumentParser());
文档摄入API
-
DocumentTransformer
HtmlToTextDocumentTransformer
fromlangchain4j-document-transformer-jsoup
-
TextSegment
:文档的一个chunk -
DocumentSplitter
DocumentByParagraphSplitter
DocumentByLineSplitter
DocumentBySentenceSplitter
DocumentByWordSplitter
DocumentByCharacterSplitter
DocumentByRegexSplitter
- Recursive:
DocumentSplitters.recursive(...)
-
TextSegmentTransformer
-
Embedding
:向量表示 -
EmbeddingModel
:嵌入模型 -
EmbeddingStore
:向量数据库 -
EmbeddingStoreIngestor
:负责将文档装入向量数据库,可以组合DocumentTransformer
/DocumentSplitter
/TextSegmentTransformer
less
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
// adding userId metadata entry to each Document to be able to filter by it later
.documentTransformer(document -> {
document.metadata().put("userId", "12345");
return document;
})
// splitting each Document into TextSegments of 1000 tokens each, with a 200-token overlap
.documentSplitter(DocumentSplitters.recursive(1000, 200, new OpenAiTokenizer("gpt")))
// adding a name of the Document to each TextSegment to improve the quality of search
.textSegmentTransformer(textSegment -> TextSegment.from(
textSegment.metadata().getString("file_name") + "\n" + textSegment.text(),
textSegment.metadata()
))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
检索
EmbeddingStoreContentRetriever
用于实例化内容检索对象(ContentRetriever
)。
scss
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.75)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
// 指定contentRetriever
.contentRetriever(contentRetriever)
.build();
高级RAG
The process is as follows:
- The user produces a
UserMessage
, which is converted into aQuery
- The
QueryTransformer
transforms theQuery
into one or multipleQuery
s- Each
Query
is routed by theQueryRouter
to one or moreContentRetriever
s- Each
ContentRetriever
retrieves relevantContent
s for eachQuery
- The
ContentAggregator
combines all retrievedContent
s into a single final ranked list- This list of
Content
s is injected into the originalUserMessage
- Finally, the
UserMessage
, containing the original query along with the injected relevant content, is sent to the LLM
API
-
RetrievalAugmentor
- RAG pipeline的入口,负责使用从不同源检索到的消息增强用户原有消息。默认实现是:
DefaultRetrievalAugmentor
。
- RAG pipeline的入口,负责使用从不同源检索到的消息增强用户原有消息。默认实现是:
-
Query
- 表示用户的原始消息,以及Metadata
-
QueryTransformer
:将Query
转换为一个或者多个Query
DefaultQueryTransformer
,啥都不做CompressingQueryTransformer
,利用LLM对原始Query
进行压缩ExpandingQueryTransformer
,利用LLM对原始Query
进行扩展
-
Content
:epresents the content relevant to the userQuery
-
ContentRetriever
- Embedding store
- Full-text search engine
- Hybrid of vector and full-text search
- Web Search Engine(Tavily from
langchain4j-web-search-engine-tavily
) - Knowledge graph
- SQL database
- etc.
-
QueryRouter
:responsible for routingQuery
to the appropriateContentRetriever
(s).DefaultQueryRouter
:路由到所有的ContentRetrieverLanguageModelQueryRouter
:使用LLM来决定路由到哪些ContentRetriever
-
ContentAggregator
:对所有内容进行聚合DefaultContentAggregator
,默认实现,基于two-stage Reciprocal Rank Fusion (RRF)ReRankingContentAggregator
,基于ScoringModel
来进行聚合
-
ContentInjector
:负责将聚合后的内容注入用户消息中-
DefaultContentInjector
is the default implementation ofContentInjector
that simply appendsContent
s to the end of aUserMessage
with the prefixAnswer using the following information:
lessRetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder() // 这里可以定制化ContentInjector .contentInjector(DefaultContentInjector.builder() .promptTemplate(PromptTemplate.from("{{userMessage}}\n{{contents}}")) .build()) .build();
-
scss
Result<String> result = AiServices.builder(AssistantService.class)
.chatLanguageModel(chatLanguageModel)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
// 高级RAG体现在精细定制化RetrieverAugmentor
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
.queryTransformer(new DefaultQueryTransformer())
// 在这里写入所有参与的ContentRetriever
.queryRouter(new DefaultQueryRouter(embeddingRetriever, webSearchRetriever))
.contentAggregator(new DefaultContentAggregator())
.contentInjector(DefaultContentInjector.builder()
.metadataKeysToInclude(List.of("source"))
.build())
.build())
.build()
.searchWeather(message);
// 从返回的sources(即:RAG检索到的内容集合),获取参考内容来源
result.sources().forEach(content -> {
TextSegment textSegment = content.textSegment();
String fileName = textSegment.metadata().getString("file_name");
if (fileName != null) {
System.out.println("来源于本地文件:" + fileName);
}
String url = textSegment.metadata().getString("url");
if (url != null) {
System.out.println("来源于网络:" + URLDecoder.decode(url, StandardCharsets.UTF_8));
}
});
结构化输出
模型层设置JSON mode
In AI Services, When extracting custom POJOs, it is recommended to enable a "JSON mode" in the model configuration. This way, the LLM will be forced to respond with a valid JSON.
在构建模型对象时,指定响应格式,配合Ai Service接口中方法的返回值。
所以建议不要将需要格式化输出的方法与不需要格式化输出的方法放在一起。
不是所有模型都支持JSON mode,在支持JSON mode的模型中,可以在初始化模型对象时指定JSON mode:
scss
// Open AI模型中:
// 对于少量支持结构化输出的模型(比如:gpt-4o-mini, gpt-4o-2024-08-06)
OpenAiChatModel.builder()
...
.supportedCapabilities(Set.of(RESPONSE_FORMAT_JSON_SCHEMA)) // 指定能力
.responseFormat("json_schema") // 指定了json_schama的输出格式
.strictJsonSchema(true) // 并且要求严格的json schema
.build();
// 对于其他旧的模型
OpenAiChatModel.builder()
...
.responseFormat("json_object") // 指定json_object的输出格式
.build();
// Ollama模型中:
OllamaChatModel.builder()
...
.responseFormat(JSON)
.build();
对话时设置输出格式
Low-Level
通过设置ChatRequest
的parameters
来在对话时指定输出格式。
scss
ChatRequest chatRequest = ChatRequest.builder()
.messages(userMessage)
// 通过参数指定输出格式
.parameters(ChatRequestParameters.builder()
.responseFormat(ResponseFormat.JSON)
.build())
.build());
model.chat(chatRequest);
High-Level
在AI Service方法中指定除了String
、AiMessage
和Map<K,V>
以外的返回值时,在向LLM发出UserMessage时,都会附加上要求LLM返回符合指定结构的提示词。
特别地,如果LLM支持JSON Schema,那么向这些LLM发送UserMessage时,就会直接附上返回值类型对应的JSON Schema。
arduino
@UserMessage("Extract information about a person from {{it}}")
Person extractPersonFrom(String text);
Spring Boot集成
依赖
xml
<!-- 集成AI Service -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<!-- 集成模型相关的starter -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
监听AI Service注册事件
csharp
@Component
class AiServiceRegisteredEventListener implements ApplicationListener<AiServiceRegisteredEvent> {
@Override
public void onApplicationEvent(AiServiceRegisteredEvent event) {
Class<?> aiServiceClass = event.aiServiceClass();
List<ToolSpecification> toolSpecifications = event.toolSpecifications();
for (int i = 0; i < toolSpecifications.size(); i++) {
System.out.printf("[%s]: [Tool-%s]: %s%n",
aiServiceClass.getSimpleName(), i + 1, toolSpecifications.get(i));
}
}
}
监听LLM请求和响应
通过暴露ChatModelListener
这个bean,可以监听与模型之间的交互。
typescript
@Bean
ChatModelListener chatModelListener() {
return new ChatModelListener() {
@Override
public void onRequest(ChatModelRequestContext requestContext) {
ChatRequest chatRequest = requestContext.chatRequest();
System.out.println(chatRequest.messages());
}
@Override
public void onResponse(ChatModelResponseContext responseContext) {
ChatResponse chatResponse = responseContext.chatResponse();
System.out.println(chatResponse.aiMessage().text());
}
@Override
public void onError(ChatModelErrorContext errorContext) {
errorContext.error().printStackTrace(System.out);
}
};
}
总结
Langchain4j是目前Java技术栈中AI编程的重要工具,与Spring AI不相上下。他们两个都提供了高级抽象,让程序员可以聚焦业务领域,而不用关心底层与模型的具体交互过程。同时,也支持一定程度的扩展。