langchain4j官方文档解读

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
  • 简单形式

    kotlin 复制代码
    interface Friend {
        @SystemMessage("You are a good friend of mine. Answer using slang.")
        String chat(String userMessage);
    }
  • 从外部文件获取系统消息

    typescript 复制代码
    interface Friend {
        @SystemMessage(fromResource = "my-prompt-template.txt"
        String chat(String userMessage);
    }
  • 动态使用系统消息(根据memoryId获取不同的系统消息)

    vbnet 复制代码
    Friend 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 and ToolSpecification 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 from langchain4j-document-loader-github
    • SeleniumDocumentLoader from langchain4j-document-loader-selenium
  • Document Parser

    • TextDocumentParser
    • ApachePdfBoxDocumentParser from langchain4j-document-parser-apache-pdfbox
    • ApachePoiDocumentParser from langchain4j-document-parser-apache-poi
    • ApacheTikaDocumentParser from langchain4j-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 from langchain4j-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:

  1. The user produces a UserMessage, which is converted into a Query
  2. The QueryTransformer transforms the Query into one or multiple Querys
  3. Each Query is routed by the QueryRouter to one or more ContentRetrievers
  4. Each ContentRetriever retrieves relevant Contents for each Query
  5. The ContentAggregator combines all retrieved Contents into a single final ranked list
  6. This list of Contents is injected into the original UserMessage
  7. Finally, the UserMessage, containing the original query along with the injected relevant content, is sent to the LLM
API
  • RetrievalAugmentor

    • RAG pipeline的入口,负责使用从不同源检索到的消息增强用户原有消息。默认实现是:DefaultRetrievalAugmentor
  • Query

    • 表示用户的原始消息,以及Metadata
  • QueryTransformer:将Query转换为一个或者多个Query

    • DefaultQueryTransformer,啥都不做
    • CompressingQueryTransformer,利用LLM对原始Query进行压缩
    • ExpandingQueryTransformer,利用LLM对原始Query进行扩展
  • Content:epresents the content relevant to the user Query

  • 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 routing Query to the appropriate ContentRetriever(s).

    • DefaultQueryRouter:路由到所有的ContentRetriever
    • LanguageModelQueryRouter:使用LLM来决定路由到哪些ContentRetriever
  • ContentAggregator:对所有内容进行聚合

    • DefaultContentAggregator,默认实现,基于two-stage Reciprocal Rank Fusion (RRF)
    • ReRankingContentAggregator,基于ScoringModel来进行聚合
  • ContentInjector:负责将聚合后的内容注入用户消息中

    • DefaultContentInjector is the default implementation of ContentInjector that simply appends Contents to the end of a UserMessage with the prefix Answer using the following information:

      less 复制代码
      RetrievalAugmentor 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

通过设置ChatRequestparameters来在对话时指定输出格式。

scss 复制代码
ChatRequest chatRequest = ChatRequest.builder()
  .messages(userMessage)
  // 通过参数指定输出格式
  .parameters(ChatRequestParameters.builder()
              .responseFormat(ResponseFormat.JSON)
              .build())
  .build());
​
model.chat(chatRequest);
High-Level

在AI Service方法中指定除了StringAiMessageMap<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不相上下。他们两个都提供了高级抽象,让程序员可以聚焦业务领域,而不用关心底层与模型的具体交互过程。同时,也支持一定程度的扩展。

相关推荐
zzzyzh8 分钟前
Work【2】:PGP-SAM —— 无需额外提示的自动化 SAM!
人工智能·深度学习·计算机视觉·sam·medical·image segment
极客 - L U14 分钟前
机器学习 : 训练过程
人工智能·机器学习
鱼樱前端15 分钟前
Mac M1安装MySQL步骤
java·后端
醒李23 分钟前
AP AR
人工智能
今天炼丹了吗26 分钟前
RTDETR融合[CVPR2025]ARConv中的自适应矩阵卷积
人工智能·深度学习·计算机视觉
白衣神棍26 分钟前
【八股文】ArrayList和LinkedList的区别
java
啥都想学的又啥都不会的研究生30 分钟前
Redis设计与实现-数据持久化
java·数据库·redis·笔记·缓存·面试
pen-ai1 小时前
【NLP】 5. Word Analogy Task(词类比任务)与 Intrinsic Metric(内在度量)
人工智能·自然语言处理·word
大湾区经济门户网1 小时前
科技工作者之家建设扬帆起航,为科技人才提供更多优质服务
大数据·人工智能·科技·媒体
爱嘿嘿的小黑1 小时前
宇宙厂学到的思维模型,工作学习必备
前端·人工智能·面试