LangChain4j之AiService源码分析

article/2025/6/8 20:12:45

这一节我们主要理解的逻辑为:

  1. 代理对象的创建流程
  2. 代理对象的方法执行流程

代理对象的创建流程
创建代理对象是通过AiServices.create(Coder.JavaCoder.class, model)进行的,由于AiServices是一个抽象类,源码中有一个默认的子类DefaultAiServices,核心实现源码都在DefaultAiServices中。
DefaultAiServices的build方法就是用来创建指定接口的代理对象:

 public T build() {// 验证是否配置了ChatLanguageModelthis.performBasicValidation();//检查当前上下文是否有配置聊天记忆功能,确保当服务需要聊天记忆功能时,必须正确配置相关的记忆提供者if (!this.context.hasChatMemory() && ChatMemoryAccess.class.isAssignableFrom(this.context.aiServiceClass)) {throw IllegalConfigurationException.illegalConfiguration("In order to have a service implementing ChatMemoryAccess, please configure the ChatMemoryProvider on the '%s'.", new Object[]{this.context.aiServiceClass.getName()});} else {//获取AI服务类的所有方法,然后依次遍历Method[] var1 = this.context.aiServiceClass.getMethods();int var2 = var1.length;for(int var3 = 0; var3 < var2; ++var3) {// 验证接口中是否有方法上加了@Moderate,但是又没有配置ModerationModelMethod method = var1[var3];if (method.isAnnotationPresent(Moderate.class) && this.context.moderationModel == null) {throw IllegalConfigurationException.illegalConfiguration("The @Moderate annotation is present, but the moderationModel is not set up. Please ensure a valid moderationModel is configured before using the @Moderate annotation.");}if (method.getReturnType() == Result.class || method.getReturnType() == List.class || method.getReturnType() == Set.class) {TypeUtils.validateReturnTypesAreProperlyParametrized(method.getName(), method.getGenericReturnType());}if (!this.context.hasChatMemory()) {Parameter[] var5 = method.getParameters();int var6 = var5.length;for(int var7 = 0; var7 < var6; ++var7) {Parameter parameter = var5[var7];if (parameter.isAnnotationPresent(MemoryId.class)) {throw IllegalConfigurationException.illegalConfiguration("In order to use @MemoryId, please configure the ChatMemoryProvider on the '%s'.", new Object[]{this.context.aiServiceClass.getName()});}}}}//基于JDK动态代理创建代理对象Object proxyInstance = Proxy.newProxyInstance(this.context.aiServiceClass.getClassLoader(), new Class[]{this.context.aiServiceClass}, new InvocationHandler() {// 使用缓存线程池执行异步任务private final ExecutorService executor = Executors.newCachedThreadPool();public Object invoke(Object proxy, Method method, Object[] args) throws Exception {// 处理Object类的方法if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);// 处理ChatMemoryAccess接口的方法} else if (method.getDeclaringClass() == ChatMemoryAccess.class) {Object var10000;switch (method.getName()) {case "getChatMemory" -> var10000 = DefaultAiServices.this.context.chatMemoryService.getChatMemory(args[0]);case "evictChatMemory" -> var10000 = DefaultAiServices.this.context.chatMemoryService.evictChatMemory(args[0]) != null;default -> throw new UnsupportedOperationException("Unknown method on ChatMemoryAccess class : " + method.getName());}return var10000;} else {// 验证方法参数DefaultAiServices.validateParameters(method);// 查找memoryId,默认为"default"Object memoryId = DefaultAiServices.findMemoryId(method, args).orElse("default");// 获取或创建聊天记忆ChatMemory chatMemory = DefaultAiServices.this.context.hasChatMemory() ? DefaultAiServices.this.context.chatMemoryService.getOrCreateChatMemory(memoryId) : null;// 准备系统消息Optional<SystemMessage> systemMessage = DefaultAiServices.this.prepareSystemMessage(memoryId, method, args);// 准备用户消息dev.langchain4j.data.message.UserMessage userMessage = DefaultAiServices.prepareUserMessage(method, args);// 增强结果(用于检索增强)AugmentationResult augmentationResult = null;if (DefaultAiServices.this.context.retrievalAugmentor != null) {List<ChatMessage> chatMemoryMessages = chatMemory != null ? chatMemory.messages() : null;Metadata metadata = Metadata.from(userMessage, memoryId, chatMemoryMessages);AugmentationRequest augmentationRequest = new AugmentationRequest(userMessage, metadata);augmentationResult = DefaultAiServices.this.context.retrievalAugmentor.augment(augmentationRequest);userMessage = (dev.langchain4j.data.message.UserMessage)augmentationResult.chatMessage();}// 获取返回类型并检查是否为流式响应Type returnType = method.getGenericReturnType();boolean streaming = returnType == TokenStream.class || this.canAdaptTokenStreamTo(returnType);boolean supportsJsonSchema = this.supportsJsonSchema();// 检查是否支持JSON SchemaOptional<JsonSchema> jsonSchema = Optional.empty();if (supportsJsonSchema && !streaming) {jsonSchema = DefaultAiServices.this.serviceOutputParser.jsonSchema(returnType);}// 如果不支持JSON Schema或不是流式响应,则添加输出格式指令if ((!supportsJsonSchema || jsonSchema.isEmpty()) && !streaming) {userMessage = this.appendOutputFormatInstructions(returnType, userMessage);}Object messages;if (chatMemory != null) {Objects.requireNonNull(chatMemory);systemMessage.ifPresent(chatMemory::add);chatMemory.add(userMessage);messages = chatMemory.messages();} else {// 构建消息列表messages = new ArrayList();Objects.requireNonNull(messages);systemMessage.ifPresent(messages::add);((List)messages).add(userMessage);}// 触发内容审核(如果方法有@Moderate注解)Future<Moderation> moderationFuture = this.triggerModerationIfNeeded(method, (List)messages);// 准备工具执行上下文ToolExecutionContext toolExecutionContext = DefaultAiServices.this.context.toolService.executionContext(memoryId, userMessage);// 处理流式响应if (streaming) {TokenStream tokenStream = new AiServiceTokenStream(AiServiceTokenStreamParameters.builder().messages((List)messages).toolSpecifications(toolExecutionContext.toolSpecifications()).toolExecutors(toolExecutionContext.toolExecutors()).retrievedContents(augmentationResult != null ? augmentationResult.contents() : null).context(DefaultAiServices.this.context).memoryId(memoryId).build());return returnType == TokenStream.class ? tokenStream : this.adapt(tokenStream, returnType);} else {// 处理非流式响应ResponseFormat responseFormat = null;if (supportsJsonSchema && jsonSchema.isPresent()) {responseFormat = ResponseFormat.builder().type(ResponseFormatType.JSON).jsonSchema((JsonSchema)jsonSchema.get()).build();}// 构建聊天请求ChatRequestParameters parameters = ChatRequestParameters.builder().toolSpecifications(toolExecutionContext.toolSpecifications()).responseFormat(responseFormat).build();ChatRequest chatRequest = ChatRequest.builder().messages((List)messages).parameters(parameters).build();// 执行聊天请求ChatResponse chatResponse = DefaultAiServices.this.context.chatModel.chat(chatRequest);// 验证内容审核结果 AiServices.verifyModerationIfNeeded(moderationFuture);// 执行工具调用循环ToolExecutionResult toolExecutionResult = DefaultAiServices.this.context.toolService.executeInferenceAndToolsLoop(chatResponse, parameters, (List)messages, DefaultAiServices.this.context.chatModel, chatMemory, memoryId, toolExecutionContext.toolExecutors());chatResponse = toolExecutionResult.chatResponse();FinishReason finishReason = chatResponse.metadata().finishReason();// 构建响应Response<AiMessage> response = Response.from(chatResponse.aiMessage(), toolExecutionResult.tokenUsageAccumulator(), finishReason);// 解析响应Object parsedResponse = DefaultAiServices.this.serviceOutputParser.parse(response, returnType);// 如果返回类型是Result,则构建Result对象return TypeUtils.typeHasRawClass(returnType, Result.class) ? Result.builder().content(parsedResponse).tokenUsage(toolExecutionResult.tokenUsageAccumulator()).sources(augmentationResult == null ? null : augmentationResult.contents()).finishReason(finishReason).toolExecutions(toolExecutionResult.toolExecutions()).build() : parsedResponse;}}}// 检查是否可以适配TokenStream到指定类型private boolean canAdaptTokenStreamTo(Type returnType) {Iterator var2 = DefaultAiServices.this.tokenStreamAdapters.iterator();TokenStreamAdapter tokenStreamAdapter;do {if (!var2.hasNext()) {return false;}tokenStreamAdapter = (TokenStreamAdapter)var2.next();} while(!tokenStreamAdapter.canAdaptTokenStreamTo(returnType));return true;}// 适配TokenStream到指定类型private Object adapt(TokenStream tokenStream, Type returnType) {Iterator var3 = DefaultAiServices.this.tokenStreamAdapters.iterator();TokenStreamAdapter tokenStreamAdapter;do {if (!var3.hasNext()) {throw new IllegalStateException("Can't find suitable TokenStreamAdapter");}tokenStreamAdapter = (TokenStreamAdapter)var3.next();} while(!tokenStreamAdapter.canAdaptTokenStreamTo(returnType));return tokenStreamAdapter.adapt(tokenStream);}// 检查是否支持JSON Schemaprivate boolean supportsJsonSchema() {return DefaultAiServices.this.context.chatModel != null && DefaultAiServices.this.context.chatModel.supportedCapabilities().contains(Capability.RESPONSE_FORMAT_JSON_SCHEMA);}// 添加输出格式指令到用户消息private dev.langchain4j.data.message.UserMessage appendOutputFormatInstructions(Type returnType, dev.langchain4j.data.message.UserMessage userMessage) {String outputFormatInstructions = DefaultAiServices.this.serviceOutputParser.outputFormatInstructions(returnType);String var10000 = userMessage.singleText();String text = var10000 + outputFormatInstructions;if (Utils.isNotNullOrBlank(userMessage.name())) {userMessage = dev.langchain4j.data.message.UserMessage.from(userMessage.name(), text);} else {userMessage = dev.langchain4j.data.message.UserMessage.from(text);}return userMessage;}// 触发内容审核(如果需要)private Future<Moderation> triggerModerationIfNeeded(Method method, List<ChatMessage> messages) {return method.isAnnotationPresent(Moderate.class) ? this.executor.submit(() -> {List<ChatMessage> messagesToModerate = AiServices.removeToolMessages(messages);return (Moderation)DefaultAiServices.this.context.moderationModel.moderate(messagesToModerate).content();}) : null;}});return proxyInstance;}}

可以发现,其实就是用的JDK动态代理机制创建的代理对象,只不过在创建代理对象之前有三步验证:

  1. 验证是否配置了ChatLanguageModel:这一步不难理解,如果代理对象没有配置ChatLanguageModel,那就利用不上大模型的能力了
  2. 验证接口中是否有方法上加了@Moderate,但是又没有配置ModerationModel
  3. 验证接口提供了@MemoryId 但是没提供ChatMemoryProvider

@Moderate和ModerationModel
Moderate是温和的意思,这是一种安全机制,比如

@SystemMessage("请扮演一名资深的JAVA专家,根据输入的任务要求写一段高效可维护可扩展的代码")
@Moderate
String code(String task);

我们在code()方法上加了@Moderate注解,那么当调用code()方法时,会调用两次大模型:

  1. 首先是配置的ModerationModel,如果没有配置则创建代理对象都不会成功,ModerationModel会对方法的输入进行审核,看是否涉及敏感、不安全的内容。
  2. 然后才是配置的ChatLanguageModel

配置ModerationModel的方式如下:

OpenAiChatModel model = OpenAiChatModel.builder().apiKey(apiKey).httpClientBuilder(new SpringRestClientBuilder()).modelName("gpt-4o-mini").build();
ModerationModel moderationModel = OpenAiModerationModel.builder().apiKey(apiKey).httpClientBuilder(new SpringRestClientBuilder()).modelName("gpt-4o-mini").build();interface JavaCoder extends Coder {@SystemMessage("请扮演一名资深的JAVA专家,根据输入的任务要求写一段高效可维护可扩展的代码")@ModerateString code(String task);static JavaCoder create() {return AiServices.builder(JavaCoder.class).chatLanguageModel(model).moderationModel(moderationModel).build();}
}

虽然OpenAiChatModel 和OpenAiModerationModel都是OpenAi,但是你可以理解为OpenAiModerationModel在安全方面更近专业。

代理对象的方法执行流程
代理对象创建出来之后,就可以指定代理对象的方法了,而一旦执行代理对象的方法就是进入到上述源码中InvocationHandler的invoke()方法,而这个invoke()方法是LangChain4j中的最为重要的,里面涉及的组件、功能是非常多的,而本节我们只关心是怎么解析@SystemMessage得到系统提示词,然后组合用户输入的内容,最后发送给大模型得到响应结果的。

在invoke()方法的源码中有这么两行代码

  // 准备系统消息
Optional<SystemMessage> systemMessage = DefaultAiServices.this.prepareSystemMessage(memoryId, method, args);// 准备用户消息
dev.langchain4j.data.message.UserMessage userMessage = DefaultAiServices.prepareUserMessage(method, args);

分别调用了prepareSystemMessage()和prepareUserMessage()两个方法,而入参都是代理对象当前正在执行的方法和参数。

在看prepareSystemMessage()方法之前,我们需要再了解一个跟@SystemMessage有关的功能,前面我们是这么定义SystemMessage的:

@SystemMessage("请扮演一名资深的JAVA专家,根据输入的任务要求写一段高效可维护可扩展的代码")

其中JAVA专家是固定的,但是作为一名AI程序员,不可能只会Java,而这个应该都用户来指定,也就是说JAVA专家应该得是个变量,那么我们可以这么做:

@SystemMessage("请扮演一名资深的{{userRole}}专家,根据输入的任务要求写一段高效可维护可扩展的代码")
@UserMessage("{{task}}")
String code(@V("task") String task, @V("userRole") String userRole);

其中{task}就是变量,该变量的值由用户在调用code方法时指定,注意由于code()有两个参数了,需要在task参数前面定义@UserMessage,表示task是用户消息 或者用@V注解 @UserMessage注解引入

知道了这个场景,我们再来看prepareSystemMessage()方法的实现:

    private Optional<SystemMessage> prepareSystemMessage(Object memoryId, Method method, Object[] args) {return this.findSystemMessageTemplate(memoryId, method).map((systemMessageTemplate) -> {return PromptTemplate.from(systemMessageTemplate).apply(findTemplateVariables(systemMessageTemplate, method, args)).toSystemMessage();});}private Optional<String> findSystemMessageTemplate(Object memoryId, Method method) {//获取方法上的SystemMessage注解dev.langchain4j.service.SystemMessage annotation = (dev.langchain4j.service.SystemMessage)method.getAnnotation(dev.langchain4j.service.SystemMessage.class);//内容模板也可以从配置文件获得例如 @UserMessage(fromResource = "my-prompt-template.txt")return annotation != null ? Optional.of(getTemplate(method, "System", annotation.fromResource(), annotation.value(), annotation.delimiter())) : (Optional)this.context.systemMessageProvider.apply(memoryId);}private static Map<String, Object> findTemplateVariables(String template, Method method, Object[] args) {// 得到当前正在执行的方法参数Parameter[] parameters = method.getParameters();Map<String, Object> variables = new HashMap();for(int i = 0; i < parameters.length; ++i) {String variableName = getVariableName(parameters[i]);Object variableValue = args[i];variables.put(variableName, variableValue);}if (template.contains("{{it}}") && !variables.containsKey("it")) {String itValue = getValueOfVariableIt(parameters, args);variables.put("it", itValue);}return variables;}private static String getVariableName(Parameter parameter) {V annotation = (V)parameter.getAnnotation(V.class);return annotation != null ? annotation.value() : parameter.getName();}private static String getValueOfVariableIt(Parameter[] parameters, Object[] args) {if (parameters.length == 1) {Parameter parameter = parameters[0];//解析方法上的@UserMessage注解、@MemoryId注解、@UserName注解if (!parameter.isAnnotationPresent(MemoryId.class) && !parameter.isAnnotationPresent(UserMessage.class) && !parameter.isAnnotationPresent(UserName.class) && (!parameter.isAnnotationPresent(V.class) || isAnnotatedWithIt(parameter))) {return toString(args[0]);}}for(int i = 0; i < parameters.length; ++i) {if (isAnnotatedWithIt(parameters[i])) {return toString(args[i]);}}throw IllegalConfigurationException.illegalConfiguration("Error: cannot find the value of the prompt template variable \"{{it}}\".");}//解析方法上的@V注解,值为itprivate static boolean isAnnotatedWithIt(Parameter parameter) {V annotation = (V)parameter.getAnnotation(V.class);return annotation != null && "it".equals(annotation.value());}

从源码看出@SystemMessage注解的value属性是一个String[]:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package dev.langchain4j.service;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemMessage {String[] value() default {""};String delimiter() default "\n";String fromResource() default "";
}

表示如果系统提示词比较长,可以写成多个String,不过最后会使用delimiter的值将这多个String拼接为一个SystemMessage,并且在拼接完以后会根据@V的值填充SystemMessage中的变量,从而得到最终的SystemMessage。

再来看prepareUserMessage()方法,本节我们只关心:

/*** 准备用户消息(UserMessage)对象* @param method 调用的方法* @param args 方法参数* @return 构建好的UserMessage对象*/
private static dev.langchain4j.data.message.UserMessage prepareUserMessage(Method method, Object[] args) {// 1. 获取用户消息模板String template = getUserMessageTemplate(method, args);// 2. 从方法和参数中提取模板变量Map<String, Object> variables = findTemplateVariables(template, method, args);// 3. 应用模板变量生成Prompt对象Prompt prompt = PromptTemplate.from(template).apply(variables);// 4. 查找是否有@UserName注解标记的用户名Optional<String> maybeUserName = findUserName(method.getParameters(), args);// 5. 如果有用户名,创建带用户名的UserMessage;否则使用默认方式创建Optional var10000 = maybeUserName.map((userName) -> {return dev.langchain4j.data.message.UserMessage.from(userName, prompt.text());});Objects.requireNonNull(prompt);return (dev.langchain4j.data.message.UserMessage)var10000.orElseGet(prompt::toUserMessage);}/*** 获取用户消息模板内容* 优先级:方法注解 > 参数注解 > 唯一参数值*/private static String getUserMessageTemplate(Method method, Object[] args) {// 1. 从方法上的@UserMessage注解获取模板Optional<String> templateFromMethodAnnotation = findUserMessageTemplateFromMethodAnnotation(method);// 2. 从参数上的@UserMessage注解获取模板Optional<String> templateFromParameterAnnotation = findUserMessageTemplateFromAnnotatedParameter(method.getParameters(), args);// 3. 检查模板来源冲突if (templateFromMethodAnnotation.isPresent() && templateFromParameterAnnotation.isPresent()) {throw IllegalConfigurationException.illegalConfiguration("Error: The method '%s' has multiple @UserMessage annotations. Please use only one.", new Object[]{method.getName()});// 4. 优先使用方法注解模板} else if (templateFromMethodAnnotation.isPresent()) {return (String)templateFromMethodAnnotation.get();// 5. 其次使用参数注解模板} else if (templateFromParameterAnnotation.isPresent()) {return (String)templateFromParameterAnnotation.get();// 6. 最后尝试从唯一参数获取} else {Optional<String> templateFromTheOnlyArgument = findUserMessageTemplateFromTheOnlyArgument(method.getParameters(), args);if (templateFromTheOnlyArgument.isPresent()) {return (String)templateFromTheOnlyArgument.get();} else {throw IllegalConfigurationException.illegalConfiguration("Error: The method '%s' does not have a user message defined.", new Object[]{method.getName()});}}}/*** 从方法注解中提取用户消息模板*/private static Optional<String> findUserMessageTemplateFromMethodAnnotation(Method method) {return Optional.ofNullable((UserMessage)method.getAnnotation(UserMessage.class)).map((a) -> {// 处理模板资源文件或直接值return getTemplate(method, "User", a.fromResource(), a.value(), a.delimiter());});}/*** 从参数注解中提取用户消息模板*/private static Optional<String> findUserMessageTemplateFromAnnotatedParameter(Parameter[] parameters, Object[] args) {for(int i = 0; i < parameters.length; ++i) {// 检查参数是否有@UserMessage注解if (parameters[i].isAnnotationPresent(UserMessage.class)) {// 直接使用参数值作为模板return Optional.of(toString(args[i]));}}return Optional.empty();}/*** 当没有明确注解时,尝试从唯一参数获取模板*/private static Optional<String> findUserMessageTemplateFromTheOnlyArgument(Parameter[] parameters, Object[] args) {// 仅当有且只有一个无注解参数时生效return parameters != null && parameters.length == 1 && parameters[0].getAnnotations().length == 0 ? Optional.of(toString(args[0])) : Optional.empty();}/*** 查找被@UserName注解标记的参数值*/private static Optional<String> findUserName(Parameter[] parameters, Object[] args) {for(int i = 0; i < parameters.length; ++i) {if (parameters[i].isAnnotationPresent(UserName.class)) {return Optional.of(args[i].toString());}}return Optional.empty();}

还是比较简单的:

  1. 如果有多个参数,获取加了@UserMessage注解参数的值作为UserMessage
  2. 如果只有一个参数,则直接使用该参数值作为UserMessage
  3. 方法上添加了@UserMessage 提取消息模板

这样就得到了最终的SystemMessage和UserMessage,那么如何将他们组装在一起呢?

  Object messages;if (chatMemory != null) {Objects.requireNonNull(chatMemory);systemMessage.ifPresent(chatMemory::add);chatMemory.add(userMessage);messages = chatMemory.messages();} else {messages = new ArrayList();Objects.requireNonNull(messages);systemMessage.ifPresent(messages::add);((List)messages).add(userMessage);}

我们还没有设置ChatMemory,所以组装的逻辑其实就是按顺序将SystemMessage和UserMessage添加到一个List中,后续只要将这个List传入给ChatLanguageModel的chat()方法就可以了。

那么ChatLanguageModel的chat()方法是如何处理List的呢?会拼接为一个字符串吗?并不会,我们看看OpenAiChatModel的实现:
首先 看日志是这样的

DEBUG: Writing [{"model" : "gpt-4o-mini","messages" : [ {"role" : "system","content" : "请扮演一名资深的java专家,根据输入的任务要求写一段高效可维护可扩展的代码"}, {"role" : "user","content" : "写一个冒泡排序"} ],"stream" : false
}] as "application/json" with org.springframework.http.converter.StringHttpMessageConverter

在这里插入图片描述

 public static List<Message> toOpenAiMessages(List<ChatMessage> messages) {return (List)messages.stream().map(InternalOpenAiHelper::toOpenAiMessage).collect(Collectors.toList());}public static Message toOpenAiMessage(ChatMessage message) {if (message instanceof SystemMessage) {return dev.langchain4j.model.openai.internal.chat.SystemMessage.from(((SystemMessage)message).text());} else if (message instanceof UserMessage) {UserMessage userMessage = (UserMessage)message;return userMessage.hasSingleText() ? dev.langchain4j.model.openai.internal.chat.UserMessage.builder().content(userMessage.singleText()).name(userMessage.name()).build() : dev.langchain4j.model.openai.internal.chat.UserMessage.builder().content((List)userMessage.contents().stream().map(InternalOpenAiHelper::toOpenAiContent).collect(Collectors.toList())).name(userMessage.name()).build();} else if (message instanceof AiMessage) {AiMessage aiMessage = (AiMessage)message;if (!aiMessage.hasToolExecutionRequests()) {return AssistantMessage.from(aiMessage.text());} else {ToolExecutionRequest toolExecutionRequest = (ToolExecutionRequest)aiMessage.toolExecutionRequests().get(0);if (toolExecutionRequest.id() == null) {FunctionCall functionCall = FunctionCall.builder().name(toolExecutionRequest.name()).arguments(toolExecutionRequest.arguments()).build();return AssistantMessage.builder().functionCall(functionCall).build();} else {List<ToolCall> toolCalls = (List)aiMessage.toolExecutionRequests().stream().map((it) -> {return ToolCall.builder().id(it.id()).type(ToolType.FUNCTION).function(FunctionCall.builder().name(it.name()).arguments(it.arguments()).build()).build();}).collect(Collectors.toList());return AssistantMessage.builder().content(aiMessage.text()).toolCalls(toolCalls).build();}}} else if (message instanceof ToolExecutionResultMessage) {ToolExecutionResultMessage toolExecutionResultMessage = (ToolExecutionResultMessage)message;return (Message)(toolExecutionResultMessage.id() == null ? FunctionMessage.from(toolExecutionResultMessage.toolName(), toolExecutionResultMessage.text()) : ToolMessage.from(toolExecutionResultMessage.id(), toolExecutionResultMessage.text()));} else {throw Exceptions.illegalArgument("Unknown message type: " + String.valueOf(message.type()), new Object[0]);}}private static Content toOpenAiContent(dev.langchain4j.data.message.Content content) {if (content instanceof TextContent) {return toOpenAiContent((TextContent)content);} else if (content instanceof ImageContent) {return toOpenAiContent((ImageContent)content);} else if (content instanceof AudioContent) {AudioContent audioContent = (AudioContent)content;return toOpenAiContent(audioContent);} else {throw Exceptions.illegalArgument("Unknown content type: " + String.valueOf(content), new Object[0]);}}private static Content toOpenAiContent(TextContent content) {return Content.builder().type(ContentType.TEXT).text(content.text()).build();}private static Content toOpenAiContent(ImageContent content) {return Content.builder().type(ContentType.IMAGE_URL).imageUrl(ImageUrl.builder().url(toUrl(content.image())).detail(toDetail(content.detailLevel())).build()).build();}private static Content toOpenAiContent(AudioContent audioContent) {return Content.builder().type(ContentType.AUDIO).inputAudio(InputAudio.builder().data(ValidationUtils.ensureNotBlank(audioContent.audio().base64Data(), "audio.base64Data")).format(extractSubtype(ValidationUtils.ensureNotBlank(audioContent.audio().mimeType(), "audio.mimeType"))).build()).build();}

在正式调用OpenAi的接口之前,OpenAiChatModel会利用toOpenAiMessages来处理List,首先包装成一个ChatRequest然后调用OpenAiChatModel的doChat方法

public ChatResponse doChat(ChatRequest chatRequest) {OpenAiChatRequestParameters parameters = (OpenAiChatRequestParameters)chatRequest.parameters();InternalOpenAiHelper.validate(parameters);ChatCompletionRequest openAiRequest = InternalOpenAiHelper.toOpenAiChatRequest(chatRequest, parameters, this.strictTools, this.strictJsonSchema).build();ChatCompletionResponse openAiResponse = (ChatCompletionResponse)RetryUtils.withRetryMappingExceptions(() -> {return (ChatCompletionResponse)this.client.chatCompletion(openAiRequest).execute();}, this.maxRetries);OpenAiChatResponseMetadata responseMetadata = ((OpenAiChatResponseMetadata.Builder)((OpenAiChatResponseMetadata.Builder)((OpenAiChatResponseMetadata.Builder)((OpenAiChatResponseMetadata.Builder)OpenAiChatResponseMetadata.builder().id(openAiResponse.id())).modelName(openAiResponse.model())).tokenUsage(InternalOpenAiHelper.tokenUsageFrom(openAiResponse.usage()))).finishReason(InternalOpenAiHelper.finishReasonFrom(((ChatCompletionChoice)openAiResponse.choices().get(0)).finishReason()))).created(openAiResponse.created()).serviceTier(openAiResponse.serviceTier()).systemFingerprint(openAiResponse.systemFingerprint()).build();return ChatResponse.builder().aiMessage(InternalOpenAiHelper.aiMessageFrom(openAiResponse)).metadata(responseMetadata).build();}

这段代码里面利用建造者模式构建了一个ChatCompletionRequest 包含了模型、还有一个 List messages;
关键代码

ChatCompletionRequest openAiRequest = InternalOpenAiHelper.toOpenAiChatRequest(chatRequest, parameters, this.strictTools, this.strictJsonSchema).build();

我们看下转换之后的内容
在这里插入图片描述
content确实没有区别,多了role属性,意义其实是一样的,SYSTEM表示系统提示词,USER表示用户提示词,那为什么这样可以呢?是因为OpenAi提供的接口本来就支持通过这种方式来设置系统提示词,比如:
注意从o1之后的模型api接口sysytem消息用 developer消息代替
在这里插入图片描述
在这里插入图片描述

大家可以访问https://platform.openai.com/docs/api-reference/chat/create详细了解,同时大家也要注意到OpenAi的接口还支持Assistant message和Tool message两种类型,这两种类型是跟工具机制有关系的,我们后续会进行分析。


http://www.hkcw.cn/article/VIQuUBkLuQ.shtml

相关文章

多合一箱变保护测控装置,助力箱变实现“无人值守,少人值班”

箱式变压器&#xff08;简称“箱变”&#xff09;将传统变压器集中设计在箱式壳体中&#xff0c;因其结构紧凑、安装简单、运行稳定等优势被广泛应用于光伏及风电系统。但是&#xff0c;由于箱变安装位置偏远且分散、运行环境恶劣&#xff0c;箱内设备种类多、需要实时掌握运行…

国际Modelica协会主席Dirk Zimmer博士到访同元软控,共话Modelica技术未来

5月28日&#xff0c;国际Modelica协会主席Dirk Zimmer博士到访同元软控苏州总部&#xff0c;双方围绕Modelica技术未来发展与开放生态建设&#xff0c;展开了深入的探讨与交流。 左&#xff1a;Modelica协会主席Dirk Zimmer博士 右&#xff1a;同元软控董事长周凡利 01 Dirk …

【论文笔记】High-Resolution Representations for Labeling Pixels and Regions

【题目】&#xff1a;High-Resolution Representations for Labeling Pixels and Regions 【引用格式】&#xff1a;Sun K, Zhao Y, Jiang B, et al. High-resolution representations for labeling pixels and regions[J]. arXiv preprint arXiv:1904.04514, 2019. 【网址】…

Redis:常用数据结构 单线程模型

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Redis &#x1f525; 常用数据结构 &#x1f433; Redis 当中常用的数据结构如下所示&#xff1a; Redis 在底层实现上述数据结构的过程中&#xff0c;会在源码的角度上对于上述的内容进行特定的…

HTTP连接管理——短连接,长连接,HTTP 流水线

连接管理是一个 HTTP 的关键话题&#xff1a;打开和保持连接在很大程度上影响着网站和 Web 应用程序的性能。在 HTTP/1.x 里有多种模型&#xff1a;短连接、_长连接_和 HTTP 流水线。 下面分别来详细解释 短连接 HTTP 协议最初&#xff08;0.9/1.0&#xff09;是个非常简单的…

【Typst】1.Typst概述

概述 Typst是一种用于排版文档的标记语言&#xff0c;可以用于排版各种精美的论文、文章、书籍、报告和作业等。它是LaTex的精神续作&#xff0c;但是运行环境和编译速度都要更简单、更快捷。 它设计了一种脚本结合简单的标记语法实现复杂的排版效果。并且支持模板创建、文件…

预警功能深度测评:系统如何降低设备突发故障率?

在设备密集型行业中&#xff0c;设备突发故障不仅会导致生产停滞&#xff0c;还可能引发安全事故&#xff0c;给企业带来巨大损失。设备管理系统凭借其强大的预警功能&#xff0c;成为众多企业降低设备突发故障率的选择工具。本文将深度测评该系统的预警功能&#xff0c;探讨其…

ABAP设计模式之---“高内聚,低耦合(High Cohesion Low Coupling)”

“高内聚、低耦合”是面向对象编程中非常重要的设计原则&#xff0c;它有助于提高代码的可维护性、扩展性和复用性。 1. 初衷&#xff1a;为什么会有这个原则&#xff1f; 在软件开发中&#xff0c;随着业务需求的复杂化&#xff0c;代码难免会变得越来越庞大。如果开发者将一…

贪心算法应用:边着色问题详解

贪心算法应用&#xff1a;边着色问题详解 贪心算法是一种在每一步选择中都采取当前状态下最优的选择&#xff0c;从而希望导致结果是全局最优的算法策略。边着色问题是图论中的一个经典问题&#xff0c;贪心算法可以有效地解决它。下面我将从基础概念到具体实现&#xff0c;全…

基于 Amazon Q Developer CLI 和 Amazon Bedrock Knowledge Bases 实现智能问答系统

1. 引言 传统企业通常将常见问题&#xff08;FAQ&#xff09;发布在网站上&#xff0c;方便客户自助查找信息。然而&#xff0c;随着生成式 AI 技术的迅速发展与商业渗透&#xff0c;这些企业正积极探索构建智能问答系统的新途径。这类系统不仅能显著提升客户体验&#xff0c;…

ElasticStack对接kafka集群

背景 在当代数字化浪潮中&#xff0c;日志数据的高效处理对于企业运维监控和数据分析至关重要。本博文聚焦于ELK&#xff08;Elasticsearch、Logstash、Kibana&#xff09;技术栈与Kafka集群的深度对接&#xff0c;旨在探讨如何通过这一架构优化&#xff0c;实现高效、可靠且可…

【云计算】基础篇,含云测试

一、云计算中的底层原理 1.1 数学原理 云计算的高效运行依赖于多种数学原理的协同支撑,其核心数学原理: 1.1.1、分布式计算的数学基础 ​分治与并行模型​ ​MapReduce​:将大数据集分割为独立子任务(Map阶段),通过哈希函数分发到分布式节点并行处理,再聚合结果(Redu…

高效易用的 MAC 版 SVN 客户端:macSvn 使用体验

高效易用的 MAC 版 SVN 客户端&#xff1a;macSvn 使用体验 下载安装使用总结 最近有个项目要使用svn, 但是mac缺乏一款像 Windows 平台 TortoiseSVN 那样全面、高效且便捷的 SVN 客户端工具, 直到博主找到了该工具本文将结合实际使用体验&#xff0c;详细介绍 macSvn工具的核心…

从0到1认识EFK

一、ES集群部署 操作系统Ubuntu22.04LTS/主机名IP地址主机配置elk9110.0.0.91/244Core8GB100GB磁盘elk9210.0.0.92/244Core8GB100GB磁盘elk9310.0.0.93/244Core8GB100GB磁盘 1. 什么是ElasticStack? # 官网 https://www.elastic.co/ ElasticStack早期名称为elk。 elk分别…

TDengine 的 AI 应用实战——运维异常检测

作者&#xff1a; derekchen Demo数据集准备 我们使用公开的 NAB数据集 里亚马逊 AWS 东海岸数据中心一次 API 网关故障中&#xff0c;某个服务器上的 CPU 使用率数据。数据的频率为 5min&#xff0c;单位为占用率。由于 API 网关的故障&#xff0c;会导致服务器上的相关应用…

VMWare安装常见问题

如果之前安装过VMWare软件&#xff0c;只要是 15/16 版本的&#xff0c;可以正常使用的&#xff0c;不用卸载&#xff01;&#xff01;&#xff01; 如果之前安装过&#xff0c;卸载了&#xff0c;一定要保证通过正常的渠道去卸载&#xff08;通过控制面板卸载软件&#xff09…

MyBatis02——mybatis基础使用|缓存机制|sqlMapper文件|单参数和多参数传递|Statement和PreparedStatement

目录 一、搭建环境 二、核心配置文件 三、核心类 &#xff08;测试类&#xff09; 四、缓存机制 一级缓存 二级缓存 清理缓存 五、sqlMapper文件 六、单参数和多参数的传递 6.1取别名 6.2 测试新增返回自增主键 七、mybatis中Statement和PreparedStatement 作业 1…

Grafana-State timeline状态时间线

显示随时间推移的状态变化 状态区域&#xff1a;即状态时间线上的状态显示的条或带&#xff0c;区域长度表示状态持续时间或频率 数据格式要求&#xff08;可视化效果最佳&#xff09;&#xff1a; 时间戳实体名称&#xff08;即&#xff1a;正在监控的目标对应名称&#xf…

便捷高效能源服务触手可及,能耗监测系统赋能智能建筑与智慧城市

在建筑行业迈向智能化、精细化管理的进程中&#xff0c;传统建筑管理模式因信息割裂、数据利用不足等问题&#xff0c;逐渐难以满足现代建筑复杂的运营需求。楼宇自控系统实现了建筑设备的智能调控&#xff0c;BIM技术则构建了建筑的三维数字化模型&#xff0c;当两者相遇&…

论文阅读:CLIP:Learning Transferable Visual Models From Natural Language Supervision

从自然语言监督中学习可迁移的视觉模型 虽然有点data/gpu is all you need的味道&#xff0c;但是整体实验和谈论丰富度上还是很多的&#xff0c;也是一篇让我多次想放弃的文章&#xff0c;因为真的是非常长的原文和超级多的实验讨论&#xff0c;隔着屏幕感受到了实验的工作量之…