0帧起手将腾讯混元大模子集成到Spring AI的全过程解析

汕尾海湾  金牌会员 | 2025-2-12 12:08:12 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 924|帖子 924|积分 2772

在前面,我们已经为各人铺垫了大量的知识点,并深入解析了Spring AI项目标相关内容。本日,我们将正式进入实战环节,从零开始,小雨将带领各人一步步完成将第三方大模子集成到Spring AI中的全过程。为了方便讲解,本次实战的示范将以腾讯的混元大模子为主,我们将逐步向你展示怎样将该大模子嵌入到Spring AI中,并利用其强大的能力,帮助你个性化地完成企业级Agent的智能体开发。
假如你是对Spring AI还比较陌生的小同伴,建议你先回顾一下我之前撰写的几篇入门文章,这些文章能够帮助你快速掌握Spring AI的基本概况和框架结构。相关链接已经放在文章的底部,接待阅读。
好了,废话不多说,我们直接开始本日的实战部门。
环境准备

秘钥信息

在此,我们必要申请混元大模子的秘钥信息,具体操纵步骤如下所示:

部门同学可能认为混元大模子已经兼容OpenAI接口,因而不再必要举行额外的开发实战。然而,假如你真的在Spring AI项目中尝试利用混元大模子的兼容方式来开发智能体,那么你一定遇到过不少bug,且这些bug往往是框架级别的,修复起来困难重重。因此,虽然兼容性是一项上风,但许多定制化需求可能难以通过兼容模式满足。
在我们系列的第一章节中,已经明白说明了必要在哪些包下举行修改,并具体列出了所需的依赖项。在这里,我们将直接举行开发。值得一提的是,Spring AI已经集成了多个大模子接口,尽管OpenAI的接口功能丰富,但对于新手来说,有些功能可能并不得当。因此,我们可以参考Moonshot接口或千帆接口来举行集成。记住,避免重复造轮子,利用已经编写好的代码逻辑,可以帮助你快速集成第三方大模子。
Models

起首请在models目次中创建了spring-ai-hunyuan子项目。如图所示:

接下来必要在最外层的pom.xml文件中添加相关的目次子项目配置,以便将其精确集成到Spring AI的整体结构中。具体操纵步骤如下所示:

依赖信息

我们为子项目配置了以下默认依赖,这些依赖可以为你提供基础的功能支持。当然,根据你的具体需求,你可以在此基础上举行自界说添加或调整,以确保满足项目标特殊要求和使用场景。
  1.     <dependency>
  2.         <groupId>org.springframework.ai</groupId>
  3.         <artifactId>spring-ai-core</artifactId>
  4.         <version>${project.parent.version}</version>
  5.     </dependency>
  6. <dependency>
  7.     <groupId>org.springframework.ai</groupId>
  8.     <artifactId>spring-ai-retry</artifactId>
  9.     <version>${project.parent.version}</version>
  10. </dependency>
  11.    
  12.     <dependency>
  13.         <groupId>org.springframework</groupId>
  14.         <artifactId>spring-context-support</artifactId>
  15.     </dependency>
  16.     <dependency>
  17.         <groupId>org.springframework.boot</groupId>
  18.         <artifactId>spring-boot-starter-logging</artifactId>
  19.     </dependency>
  20.    
  21.     <dependency>
  22.         <groupId>org.springframework.ai</groupId>
  23.         <artifactId>spring-ai-test</artifactId>
  24.         <version>${project.version}</version>
  25.         <scope>test</scope>
  26.     </dependency>
  27. <dependency>
  28.     <groupId>io.micrometer</groupId>
  29.     <artifactId>micrometer-observation-test</artifactId>
  30.     <scope>test</scope>
  31. </dependency>
  32. <dependency>
  33.     <groupId>javax.xml.bind</groupId>
  34.     <artifactId>jaxb-api</artifactId>
  35.     <version>${xml.bind.version}</version>
  36. </dependency>
  37. <dependency>
  38.     <groupId>com.sun.xml.bind</groupId>
  39.     <artifactId>jaxb-impl</artifactId>
  40.     <version>${xml.bind.version}</version>
  41. </dependency>
  42. <dependency>
  43.     <groupId>com.sun.xml.bind</groupId>
  44.     <artifactId>jaxb-core</artifactId>
  45.     <version>${xml.bind.version}</version>
  46. </dependency>
  47. <dependency>
  48.     <groupId>javax.activation</groupId>
  49.     <artifactId>activation</artifactId>
  50.     <version>1.1.1</version>
  51. </dependency>
复制代码
在这里,除了必要集成Spring AI框架所必需的固定依赖外,我们还特殊添加了一些与混元接口加密相关的额外依赖。这些额外依赖用于支持混元系统中的加密功能,确保数据的安全性和接口的正常运行。
接口对接

第一步是,在models目次下开发与混元相关的API接口对接。为了帮助各人更清晰地理解接下来的开发过程,我提供一下项目标整体结构图,帮助各人更好地把握每个模块的关系。如图所示:

在这里,我们将简朴先容一下目次中的各个类,方便各人理解它们的功能和作用,并为后续可能的集成工作提供基础。具体来说,各个类的作用如下:

  • HunYuanRuntimeHints:这是AOT(Ahead-of-Time)编译的固定写法,用于为系统提供静态的运行时提示,以确保在编译时能够精确处理相关的代码路径和资源。
  • HunYuanAuthApi:这是一个混元接口的请求参数加密工具类,用于加密发送给混元系统的请求参数,确保数据传输过程中的安全性和完备性。
  • HunYuanApi:该类主要界说了请求参数和响应参数的字段,并提供了对请求和响应的解析功能。它是与混元系统举行交互的焦点类,负责数据的输入输出。
  • HunYuanConstants:界说了项目中常用的一些常量信息,例如baseUrl等常用配置信息,便于在项目中同一管理和调用。
  • HunYuanStreamFunctionCallingHelper:这个类专注于流式响应中的功能归并,它主要用于处理大规模数据流时对功能的归并操纵,确保响应过程的高效性和准确性。
  • HunYuanUsage:该类负责记录和统计模子的token消耗环境,包罗在与混元系统交互过程中所消耗的资源量,用于优化和监控。
  • HunYuanChatModel:该部门类与Spring AI的自动注入机制相关联,主要负责封装和对接HunYuanApi,以确保与Spring AI框架的兼容性。
  • HunYuanChatOptions:这部门类同样与Spring AI自动注入相关,具体负责封装可操纵的请求参数,例如Model、Temperature等,确保系统能够灵活地调整不同的配置选项。
接下来,我们将深入探讨这些类的具体功能和实现方式,帮助各人更好地理解每个模块的实现细节及其在项目中的应用。
HunYuanRuntimeHints

这一部门的主要目标是实现AOT(Ahead-of-Time)快速打包启动,旨在将Spring项目打包成一个能够迅速启动并实行的可实行文件(.exe)。这种方法可以显著提高项目启动时的性能,并降低启动时间,尤其是在生产环境中必要快速响应的场景中非常有用。具体代码如下:
  1. public class HunYuanRuntimeHints implements RuntimeHintsRegistrar {
  2.     @Override
  3.     public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader classLoader) {
  4.         var mcs = MemberCategory.values();
  5.         for (var tr : findJsonAnnotatedClassesInPackage(HunYuanApi.class)) {
  6.             hints.reflection().registerType(tr, mcs);
  7.         }
  8.     }
  9. }
复制代码
实现RuntimeHintsRegistrar接口重写registerHints方法即可,将HunYuanApi注册进去。
HunYuanAuthApi

这一部门的主要代码以及在实现过程中必要注意的坑点和办理方案,已经具体列出在另一篇文章中,因此在此我们不再重复赘述。文章链接如下:https://www.cnblogs.com/guoxiaoyu/p/18675216
不过,值得特殊强调的是,在这个过程中,您无需额外添加ObjectMapper依赖。因为在Spring AI项目中,Record类转化为JSON的过程有一套固定的写法,Spring框架已经为这一操纵提供了内建的支持和优化。下面是具体的代码实现示例:
String payloadString = ModelOptionsUtils.toJsonString(payload);
假如你要是想让json转对象,也有响应的方法,如下所示:
ChatCompletionResponse chatCompletionResponse = ModelOptionsUtils.jsonToObject(retrieve.getBody(),
ChatCompletionResponse.class);
HunYuanApi

这个类的主要功能是将封装好的请求参数通报给混元大模子接口的URL。除了该功能外,类中的其他部门主要涉及对字段的界说和Record类的使用,Record类在这里起到了封装数据的作用,简化了字段管理与传输。以下是该类的焦点代码结构:
  1. public class HunYuanApi {
  2.   //省略类属性
  3.    
  4.     /**
  5.      * Create a new client api.
  6.      * @param baseUrl api base URL.
  7.      * @param secretKey Hunyuan api Key.
  8.      * @param restClientBuilder RestClient builder.
  9.      * @param responseErrorHandler Response error handler.
  10.      */
  11.     public HunYuanApi(String baseUrl, String secretId, String secretKey, RestClient.Builder restClientBuilder,
  12.             ResponseErrorHandler responseErrorHandler) {
  13.         Consumer<HttpHeaders> jsonContentHeaders = headers -> {
  14.             headers.setContentType(MediaType.APPLICATION_JSON);
  15.         };
  16.         hunyuanAuthApi = new HunYuanAuthApi(secretId, secretKey);
  17.         this.restClient = restClientBuilder.baseUrl(baseUrl)
  18.             .defaultHeaders(jsonContentHeaders)
  19.             .defaultStatusHandler(responseErrorHandler)
  20.             .build();
  21.         this.webClient = WebClient.builder().baseUrl(baseUrl).defaultHeaders(jsonContentHeaders).build();
  22.     }
  23.     /**
  24.      * Creates a model response for the given chat conversation.
  25.      * @param chatRequest The chat completion request.
  26.      * @return Entity response with {@link ChatCompletion} as a body and HTTP status code
  27.      * and headers.
  28.      */
  29.     public ResponseEntity<ChatCompletionResponse> chatCompletionEntity(ChatCompletionRequest chatRequest) {
  30.         Assert.notNull(chatRequest, "The request body can not be null.");
  31.         String service = HunYuanConstants.DEFAULT_SERVICE;
  32.         String host = HunYuanConstants.DEFAULT_CHAT_HOST;
  33.         // String region = "ap-guangzhou";
  34.         String action = HunYuanConstants.DEFAULT_CHAT_ACTION;
  35.         MultiValueMap<String, String> jsonContentHeaders = hunyuanAuthApi.getHttpHeadersConsumer(host, action, service,
  36.                 chatRequest);
  37.         ResponseEntity<String> retrieve = this.restClient.post().uri("/").headers(headers -> {
  38.             headers.addAll(jsonContentHeaders);
  39.         }).body(chatRequest).retrieve().toEntity(String.class);
  40.         // Compatible Return Position text/plain
  41.         logger.info("Response body: {}", retrieve.getBody());
  42.         ChatCompletionResponse chatCompletionResponse = ModelOptionsUtils.jsonToObject(retrieve.getBody(),
  43.                 ChatCompletionResponse.class);
  44.         return ResponseEntity.ok(chatCompletionResponse);
  45.     }
  46.     /**
  47.      * Creates a streaming chat response for the given chat conversation.
  48.      * @param chatRequest The chat completion request. Must have the stream property set
  49.      * to true.
  50.      * @return Returns a {@link Flux} stream from chat completion chunks.
  51.      */
  52.     public Flux<ChatCompletionChunk> chatCompletionStream(ChatCompletionRequest chatRequest) {
  53.         Assert.notNull(chatRequest, "The request body can not be null.");
  54.         Assert.isTrue(chatRequest.stream(), "Request must set the steam property to true.");
  55.         AtomicBoolean isInsideTool = new AtomicBoolean(false);
  56.         String service = HunYuanConstants.DEFAULT_SERVICE;
  57.         String host = HunYuanConstants.DEFAULT_CHAT_HOST;
  58.         // String region = "ap-guangzhou";
  59.         String action = HunYuanConstants.DEFAULT_CHAT_ACTION;
  60.         MultiValueMap<String, String> jsonContentHeaders = hunyuanAuthApi.getHttpHeadersConsumer(host, action, service,
  61.                 chatRequest);
  62.         return this.webClient.post().uri("/").headers(headers -> {
  63.             headers.addAll(jsonContentHeaders);
  64.         })
  65.             .body(Mono.just(chatRequest), ChatCompletionRequest.class)
  66.             .retrieve()
  67.             .bodyToFlux(String.class)
  68.             // cancels the flux stream after the "[DONE]" is received.
  69.             .takeUntil(SSE_DONE_PREDICATE)
  70.             // filters out the "[DONE]" message.
  71.             .filter(SSE_DONE_PREDICATE.negate())
  72.             .map(content -> {
  73.                 // logger.info(content);
  74.                 return ModelOptionsUtils.jsonToObject(content, ChatCompletionChunk.class);
  75.             })
  76.             // Detect is the chunk is part of a streaming function call.
  77.             .map(chunk -> {
  78.                 if (this.chunkMerger.isStreamingToolFunctionCall(chunk)) {
  79.                     isInsideTool.set(true);
  80.                 }
  81.                 return chunk;
  82.             })
  83.             // Group all chunks belonging to the same function call.
  84.             // Flux<ChatCompletionChunk> -> Flux<Flux<ChatCompletionChunk>>
  85.             .windowUntil(chunk -> {
  86.                 if (isInsideTool.get() && this.chunkMerger.isStreamingToolFunctionCallFinish(chunk)) {
  87.                     isInsideTool.set(false);
  88.                     return true;
  89.                 }
  90.                 return !isInsideTool.get();
  91.             })
  92.             // Merging the window chunks into a single chunk.
  93.             // Reduce the inner Flux<ChatCompletionChunk> window into a single
  94.             // Mono<ChatCompletionChunk>,
  95.             // Flux<Flux<ChatCompletionChunk>> -> Flux<Mono<ChatCompletionChunk>>
  96.             .concatMapIterable(window -> {
  97.                 Mono<ChatCompletionChunk> monoChunk = window.reduce(
  98.                         new ChatCompletionChunk(null, null, null, null, null, null, null, null, null, null, null),
  99.                         (previous, current) -> this.chunkMerger.merge(previous, current));
  100.                 return List.of(monoChunk);
  101.             })
  102.             // Flux<Mono<ChatCompletionChunk>> -> Flux<ChatCompletionChunk>
  103.             .flatMap(mono -> mono);
  104.     }
  105. //省略所有Record字段类   
  106. }
复制代码
从代码结构上来看,API类的主要作用是通过构造器将秘钥等关键信息动态注入到类的内部,确保每次调用时能够精确地携带所需的认证信息。终极,该类暴露出了两个焦点方法:一个是流式请求,另一个是壅闭式请求。这两个请求方式在处理逻辑上有些许差异,分别顺应不同的场景。
在壅闭式请求的实现中,混元接口在返回失败时,content-type会被设置为text/plain,而不是常见的application/json,这导致框架在解析返回效果时出现JSON解析失败的环境。为了办理这个题目,我们采取了先将返回效果转化为字符串,再使用ModelOptionsUtils工具类举行对象转换的方式,确保能够精确处理这种非标准的返回范例。
至于流式请求,由于大多数模子的处理方式都是标准化的,因此我们无需做太多的修改。流式请求的处理基本是固定的,适配大多数场景,只必要注意一些特定细节即可。在混元文档中也有相关的特殊说明,提示开发者在使用时必要关注的要点。以下是混元文档中的相关图示:

在这个功能的实现中,返回的数据结构与其他模子的返回格式有显著的区别,所以主要的改动是对Type、Name和Arguments字段举行归并,以Id作为唯一标识来确保数据的一致性和完备性。改动的地方也就是HunYuanStreamFunctionCallingHelper类。
HunYuanStreamFunctionCallingHelper

在该类中,基本必要改动就是各个模子的参数不一致,所以按照参数范例以及名称修改一致即可。我们主要看下这个function归并。代码如下:
  1. private ChatCompletionFunction merge(ChatCompletionFunction previous, ChatCompletionFunction current) {
  2.     if (previous == null) {
  3.         return current;
  4.     }
  5.     String name = (StringUtils.hasText(current.name()) ? current.name() : previous.name());
  6.     StringBuilder arguments = new StringBuilder();
  7.     if (StringUtils.hasText(previous.arguments())) {
  8.         arguments.append(previous.arguments());
  9.     }
  10.     if (StringUtils.hasText(current.arguments())) {
  11.         arguments.append(current.arguments());
  12.     }
  13.     return new ChatCompletionFunction(name, arguments.toString());
  14. }
复制代码
在其他模子的判定过程中,通常是通过!= null来判定字段值的有效性。然而,混元大模子的返回值是空字符串"",而非null。因此,我们在处理混元大模子返回数据时,必要特殊注意这一点。假如不做特殊处理,可能会导致name字段的值为空字符串,进而引发后续的报错或逻辑错误。
此外,值得注意的是,假如你的大模子流式请求中必要归并其他参数字段,建议在HunYuanStreamFunctionCallingHelper类中处理这些归并操纵,而不是比及最外层获取到终极效果后再举行归并。这种做法在处理逻辑时显得更加简洁、清晰,而且能够避免重复的计算或不必要的复杂操纵,避免代码看起来过于笨重。
HunYuanChatModel

这里我们也只是看下关键焦点代码,有些注意的点特殊说明一下。
  1. public class HunYuanChatModel extends AbstractToolCallSupport implements ChatModel, StreamingChatModel {
  2.     //省略部分代码
  3.     @Override
  4.     public ChatResponse call(Prompt prompt) {
  5.         ChatCompletionRequest request = createRequest(prompt, false);
  6.         //省略部分代码
  7.         ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
  8.             .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
  9.                     this.observationRegistry)
  10.             .observe(() -> {
  11.                 ResponseEntity<ChatCompletionResponse> completionEntity = this.retryTemplate
  12.                     .execute(ctx -> this.hunYuanApi.chatCompletionEntity(request));
  13.                 var chatCompletion = completionEntity.getBody().response();
  14.       //省略部分代码
  15.         return response;
  16.     }
  17.   
  18.     @Override
  19.     public Flux<ChatResponse> stream(Prompt prompt) {
  20.         return Flux.deferContextual(contextView -> {
  21.             ChatCompletionRequest request = createRequest(prompt, true);
  22.     Flux<ChatCompletionChunk> completionChunks = this.retryTemplate
  23.                 .execute(ctx -> this.hunYuanApi.chatCompletionStream(request));
  24.             //省略部分代码
  25.             Flux<ChatResponse> chatResponse = completionChunks.map(this::chunkToChatCompletion)
  26.                 .switchMap(chatCompletion -> Mono.just(chatCompletion).map(chatCompletion2 -> {
  27.                     try {
  28.                         //省略部分代码
  29.                 return Flux.just(response);
  30.             })
  31.                 .doOnError(observation::error)
  32.                 .doFinally(signalType -> observation.stop())
  33.                 .contextWrite(ctx -> ctx.put(ObservationThreadLocalAccessor.KEY, observation));
  34.             return new MessageAggregator().aggregate(flux, observationContext::setResponse);
  35.         });
  36.     }
  37.     //省略部分代码
复制代码
在这段代码中,我已经省略了与当前逻辑无关的部门。你可以看到,实际上在hunYuanApi调用之后,我们对返回效果举行了外层包装,将其返回给Spring AI框架举行后续处理。这里必要特殊注意的是,混元模子返回的响应数据是以Response为前缀举行包装的。因此,在处理混元大模子的返回效果时,我们必须兼容这种包装方式。

var chatCompletion = completionEntity.getBody().response();
除了这里还有两个点必要注意下,第一个就是createRequest方法。
  1. List<ChatCompletionMessage> systemMessages = new ArrayList<>();
  2. List<ChatCompletionMessage> chatCompletionMessages = prompt.getInstructions().stream().filter(message -> {
  3.     if (message.getMessageType() == MessageType.SYSTEM) {
  4.         Object content = message.getText();
  5.         systemMessages.add(new ChatCompletionMessage(content, Role.system));
  6.         return false;
  7.     }
  8.     return true;
  9. }).map(message -> {
  10.     if (message.getMessageType() == MessageType.USER) {
  11.         Object content = message.getText();
  12.         if (message instanceof UserMessage userMessage) {
  13.             if (!CollectionUtils.isEmpty(userMessage.getMedia())) {
  14.                 List<ChatContent> contentList = new ArrayList<>(List.of(new ChatContent(message.getText())));
  15.                 contentList.addAll(userMessage.getMedia()
  16.                     .stream()
  17.                     .map(media -> new ChatContent(
  18.                             new ImageUrl(this.fromMediaData(media.getMimeType(), media.getData()))))
  19.                     .toList());
  20.                 return List.of(new ChatCompletionMessage(Role.user, contentList));
  21.             }
  22.         }
  23.         return List.of(new ChatCompletionMessage(content, Role.user));
  24.     }
  25. //省略部分代码
  26. systemMessages.stream().forEach(systemMessage -> {
  27.     chatCompletionMessages.add(0, systemMessage);
  28. });
  29. //省略部分代码
复制代码
可以看出,实际上这段操纵分为两个部门。第一部门是将systemMessage添加到消息列表的首位,因为混元接口的设计要求必须遵循这一顺序,即系统消息必要位于消息列表的最前面才能确保接口能够精确处理。
第二部门则是根据userMessage的信息范例,自己封装图片理解相关请求参数,这属于混元个性化设置,如下所示:

第一个方法是chunkToChatCompletion。由于混元接口的响应内容实际上是通过参数delta返回的,我们必要对其举行重新封装,将其转换为ChatCompletionMessage格式,以便能够精确地通报给Spring AI举行后续处理。下面是相关代码的实现:
  1. private ChatCompletion chunkToChatCompletion(ChatCompletionChunk chunk) {
  2.     List<ChatCompletion.Choice> choices = chunk.choices().stream().map(chunkChoice -> {
  3.         ChatCompletionMessage chatCompletionMessage = null;
  4.         ChatCompletionDelta delta = chunkChoice.delta();
  5.         if (delta == null) {
  6.             chatCompletionMessage = new ChatCompletionMessage("", Role.assistant);
  7.         }
  8.         else {
  9.             chatCompletionMessage = new ChatCompletionMessage(delta.content(), delta.role(), delta.toolCalls());
  10.         }
  11.         return new ChatCompletion.Choice(chunkChoice.index(), chatCompletionMessage, chunkChoice.finishReason(),
  12.                 delta);
  13.     }).toList();
  14.     return new ChatCompletion(chunk.id(), chunk.errorMsg(), chunk.created(), chunk.note(), choices, chunk.usage(),
  15.             chunk.moderationLevel(), chunk.searchInfo(), chunk.replaces(), chunk.recommendedQuestions(),
  16.             chunk.requestId());
  17. }
复制代码
单位测试

在举行单位测试时,必要特殊注意的是,假如你在测试方面的履历还不够丰富,建议可以参考其他成熟模子的测试流程,例如Moonshot等。这些模子的测试方法和流程通常经过了多次验证,具有较高的可靠性,尤其是对于聊天功能模块来说,各种模子的实现方式差异不会特殊大。我们接下来可以具体了解一下怎样举行函数回调的测试以及怎样举行图片理解部门的验证。
图片理解

看下图片理解单位测试相关代码。
  1. @EnabledIfEnvironmentVariable(named = "HUNYUAN_SECRET_ID", matches = ".+")
  2. @EnabledIfEnvironmentVariable(named = "HUNYUAN_SECRET_KEY", matches = ".+")
  3. public class HunYuanApiIT {
  4.   private static final Logger logger = LoggerFactory.getLogger(HunYuanApiIT.class);
  5.   
  6.   HunYuanApi hunyuanApi = new HunYuanApi(System.getenv("HUNYUAN_SECRET_ID"), System.getenv("HUNYUAN_SECRET_KEY"));
  7.   @Test
  8.   void chatCompletionEntityWithPicture() {
  9.       ChatCompletionMessage userMessage = new ChatCompletionMessage(Role.user, List.of(
  10.               new ChatCompletionMessage.ChatContent("text", "Which company's logo is in the picture below?"),
  11.               new ChatCompletionMessage.ChatContent("image_url", new ChatCompletionMessage.ImageUrl(
  12.                       "https://cloudcache.tencent-cloud.com/qcloud/ui/portal-set/build/About/images/bg-product-series_87d.png"))));
  13.       ResponseEntity<HunYuanApi.ChatCompletionResponse> response = this.hunyuanApi
  14.           .chatCompletionEntity(new ChatCompletionRequest(List.of(userMessage),
  15.                   HunYuanApi.ChatModel.HUNYUAN_TURBO_VISION.getValue(), 0.8, false));
  16.   
  17.       logger.info(response.getBody().response().toString());
  18.       assertThat(response).isNotNull();
  19.       assertThat(response.getBody()).isNotNull();
  20.       assertThat(response.getBody().response()).isNotNull();
  21.   }
  22.   @Test
  23.   void chatCompletionStreamWithNativePicture() {
  24.       String imageInfo = "data:image/jpeg;base64,";
  25.       // 读取图片文件
  26.       var imageData = new ClassPathResource("/img.png");
  27.       try (InputStream inputStream = imageData.getInputStream()) {
  28.           byte[] imageBytes = inputStream.readAllBytes();
  29.           // 使用Base64编码图片字节数据
  30.           String encodedImage = Base64.getEncoder().encodeToString(imageBytes);
  31.           // 输出编码后的字符串
  32.           imageInfo += encodedImage;
  33.       }
  34.       catch (IOException e) {
  35.           e.printStackTrace();
  36.       }
  37.       ChatCompletionMessage userMessage = new ChatCompletionMessage(Role.user, List.of(
  38.               new ChatCompletionMessage.ChatContent("text", "Which company's logo is in the picture below?"),
  39.               new ChatCompletionMessage.ChatContent("image_url", new ChatCompletionMessage.ImageUrl(imageInfo))));
  40.       Flux<ChatCompletionChunk> response = this.hunyuanApi.chatCompletionStream(new ChatCompletionRequest(
  41.               List.of(userMessage), HunYuanApi.ChatModel.HUNYUAN_TURBO_VISION.getValue(), 0.8, true));
  42.   
  43.       assertThat(response).isNotNull();
  44.       assertThat(response.collectList().block()).isNotNull();
  45.       logger.info(ModelOptionsUtils.toJsonString(response.collectList().block()));
  46.   }
  47. }
复制代码
在此,我们直接接纳了混元大模子官方提供的案例示例,因此只需将相关的模子参数举行修改后,便可直接调用相应的API。必要特殊强调的是,关于密钥信息的处理,我们建议使用环境变量举行读取,避免直接在配置文件等地方硬编码。这是因为假如将密钥信息直接放入配置文件中,可能会导致提交拉取请求(PR)失败,从而影响版本控制的顺遂举行。
经过运行测试,统统功能正常,如下图所示:

函数回调

因为流式函数调用更改了一些归并逻辑,所以也必要看下函数回调是否正常。
  1. @Test
  2. void streamFunctionCallTest() {
  3.     UserMessage userMessage = new UserMessage(
  4.             "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
  5.     List<Message> messages = new ArrayList<>(List.of(userMessage));
  6.     var promptOptions = HunYuanChatOptions.builder()
  7.         .functionCallbacks(List.of(FunctionCallback.builder()
  8.             .function("getCurrentWeather", new MockWeatherService())
  9.             .description("Get the weather in location")
  10.             .inputType(MockWeatherService.Request.class)
  11.             .build()))
  12.         .build();
  13.     Flux<ChatResponse> response = this.chatModel.stream(new Prompt(messages, promptOptions));
  14.     String content = response.collectList()
  15.         .block()
  16.         .stream()
  17.         .map(ChatResponse::getResults)
  18.         .flatMap(List::stream)
  19.         .map(Generation::getOutput)
  20.         .map(AssistantMessage::getText)
  21.         .filter(Objects::nonNull)
  22.         .collect(Collectors.joining());
  23.     logger.info("Response: {}", content);
  24.     assertThat(content).contains("30", "10", "15");
  25. }
复制代码
运行后统统正常,看下效果如图所示:

自此,我们的混元大模子的底层基座就算是完成了,接下来就是将混元自动注入到Spring AI 的管理中。随后,我们将继承对相关流程举行深入分析,以确保实现最佳效果。
Autoconfigure

找到spring-ai-spring-boot-autoconfigure目次,这内里配置了所有可以自动配置的大模子,我们在内里添加一下hunyuan目次。如图所示:

依赖信息

同样的,创建完目次后,直接在当前目次下的pom中添加HunYuan API项目,如图所示:
  1. <dependency>
  2.     <groupId>org.springframework.ai</groupId>
  3.     <artifactId>spring-ai-hunyuan</artifactId>
  4.     <version>${project.parent.version}</version>
  5.     <optional>true</optional>
  6. </dependency>
复制代码
配置类

配置类的主要功能是为了实现对Spring AI集成第三方模子时所需的API密钥相关信息的准确解析与绑定,具体内容如下所示:
  1. @ConfigurationProperties(HunYuanChatProperties.CONFIG_PREFIX)
  2. public class HunYuanChatProperties extends HunYuanParentProperties {
  3.     public static final String CONFIG_PREFIX = "spring.ai.hunyuan.chat";
  4.     public static final String DEFAULT_CHAT_MODEL = HunYuanApi.ChatModel.HUNYUAN_PRO.getValue();
  5.     private static final Double DEFAULT_TEMPERATURE = 0.7;
  6.   //省略部分代码
  7.     @NestedConfigurationProperty
  8.     private HunYuanChatOptions options = HunYuanChatOptions.builder()
  9.         .model(DEFAULT_CHAT_MODEL)
  10.         .temperature(DEFAULT_TEMPERATURE)
  11.         .build();
  12.   //省略部分代码
  13. }
复制代码
这里我给混元匹配的前缀为spring.ai.hunyuan.chat,他实在主要给模子设置一些option信息来使用的。如下所示:
"spring.ai.hunyuan.chat.options.model=MODEL_XYZ",
"spring.ai.hunyuan.chat.options.temperature=0.55"
虽然它继承了 HunYuanParentProperties,因此能够灵活地配置秘钥及其他相关信息,但假如您的需求仅仅是举行简朴的秘钥配置,实际上还有一种更加简便的配置类可以选择。具体的示例如下所示:
  1. @ConfigurationProperties(HunYuanCommonProperties.CONFIG_PREFIX)
  2. public class HunYuanCommonProperties extends HunYuanParentProperties {
  3. public static final String CONFIG_PREFIX = "spring.ai.hunyuan";
  4. public static final String DEFAULT_BASE_URL = "https://hunyuan.tencentcloudapi.com";
  5. public HunYuanCommonProperties() {
  6.     super.setBaseUrl(DEFAULT_BASE_URL);
  7. }
复制代码
如许你就可以单独写秘钥等信息了。如下所示:
spring.ai.hunyuan.secret-id=123
spring.ai.hunyuan.secret-key=456
自动配置

为了确保我们的项目能够有效地集成到Spring框架中,我们必须实现一个AutoConfiguration类。这是由于Spring的自动配置机制所要求的,它负责在应用启动时自动配置所需的Beans和组件。这一要求与Spring AI项目本身的功能特点并没有直接关联。接下来,让我们具体观察一下自动配置类的具体代码实现:
  1. @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
  2. @EnableConfigurationProperties({ HunYuanCommonProperties.class, HunYuanChatProperties.class })
  3. @ConditionalOnClass(HunYuanApi.class)
  4. public class HunYuanAutoConfiguration {
  5.     @Bean
  6.     @ConditionalOnMissingBean
  7.     @ConditionalOnProperty(prefix = HunYuanChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
  8.             matchIfMissing = true)
  9.     public HunYuanChatModel hunyuanChatModel(HunYuanCommonProperties commonProperties,
  10.             HunYuanChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
  11.             List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver,
  12.             RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
  13.             ObjectProvider<ObservationRegistry> observationRegistry,
  14.             ObjectProvider<ChatModelObservationConvention> observationConvention) {
  15.         var hunyuanApi = hunyuanApi(chatProperties.getSecretId(), commonProperties.getSecretId(),
  16.                 chatProperties.getSecretKey(), commonProperties.getSecretKey(), chatProperties.getBaseUrl(),
  17.                 commonProperties.getBaseUrl(), restClientBuilderProvider.getIfAvailable(RestClient::builder),
  18.                 responseErrorHandler);
  19.         var chatModel = new HunYuanChatModel(hunyuanApi, chatProperties.getOptions(), functionCallbackResolver,
  20.                 toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
  21.         observationConvention.ifAvailable(chatModel::setObservationConvention);
  22.         return chatModel;
  23.     }
  24.     @Bean
  25.     @ConditionalOnMissingBean
  26.     public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
  27.         DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
  28.         manager.setApplicationContext(context);
  29.         return manager;
  30.     }
  31.     private HunYuanApi hunyuanApi(String secretId, String commonSecretId, String secretKey, String commonSecretKey,
  32.             String baseUrl, String commonBaseUrl, RestClient.Builder restClientBuilder,
  33.             ResponseErrorHandler responseErrorHandler) {
  34.         var resolvedSecretId = StringUtils.hasText(secretId) ? secretId : commonSecretId;
  35.         var resolvedSecretKey = StringUtils.hasText(secretKey) ? secretKey : commonSecretKey;
  36.         var resoledBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl;
  37.         Assert.hasText(resolvedSecretId, "HunYuan SecretId must be set");
  38.         Assert.hasText(resolvedSecretKey, "HunYuan SecretKey must be set");
  39.         Assert.hasText(resoledBaseUrl, "HunYuan base URL must be set");
  40.         return new HunYuanApi(resoledBaseUrl, resolvedSecretId, resolvedSecretKey, restClientBuilder,
  41.                 responseErrorHandler);
  42.     }
  43. }
复制代码
由于我们在配置中设置了 matchIfMissing = true,这意味着假如您的项目中存在 HunYuanChatModel 类但未举行相应的配置时,系统将会直接抛出错误,提示您缺少相关的秘钥信息。
接着,你就必要将 HunYuanAutoConfiguration类添加到 imports 文件中,这一变革是 Spring 3.X 版本更新带来的调整。在早期版本中,这一配置文件被称为 spring.factories,具体内容可以参考下图所示。

如许,我们基本上完成了将刚才在 models 下编写的混元接口所对应的 model 和 api 各种 bean 信息整归并纳入 Spring 框架的管理之中。接下来,我们将举行简朴的单位测试,以验证这些组件的功能是否正常。
单位测试

测试报错

在举行自动配置测试的过程中,我持续遇到一个错误,提示不支持读取过大的 JarEntry 文件。
java: 读取oci-java-sdk-shaded-full-3.51.0.jar时出错; Unsupported size: 12408573 for JarEntry META-INF/MANIFEST.MF. Allowed max size: 8000000 bytes
这并不是我们自己编写的代码,而是原有项目所附带的包依赖。我们尝试了多种办理方案,但始终未能奏效。终极发现题目仅仅是由于 JDK 17 的限定所致。为了办理这个题目,我们决定将项目标 JDK 直接升级到 21 版本,效果成功办理了所有相关题目。
函数回调

下面我将对函数回调举行简朴演示,以便各人更好地理解其用法。至于其他方面的测试则相对简朴,各人可以参考一下其他模子的测试方法。
  1. @EnabledIfEnvironmentVariable(named = "HUNYUAN_SECRET_ID", matches = ".+")
  2. @EnabledIfEnvironmentVariable(named = "HUNYUAN_SECRET_KEY", matches = ".+")
  3. public class HunYuanFunctionCallbackIT {
  4.     private final Logger logger = LoggerFactory.getLogger(HunYuanFunctionCallbackIT.class);
  5.     private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
  6.         .withPropertyValues("spring.ai.hunyuan.secret-id=" + System.getenv("HUNYUAN_SECRET_ID"))
  7.         .withPropertyValues("spring.ai.hunyuan.secret-key=" + System.getenv("HUNYUAN_SECRET_KEY"))
  8.         .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
  9.                 RestClientAutoConfiguration.class, HunYuanAutoConfiguration.class))
  10.         .withUserConfiguration(Config.class);
  11.     @Test
  12.     void functionCallTest() {
  13.         this.contextRunner.run(context -> {
  14.             HunYuanChatModel chatModel = context.getBean(HunYuanChatModel.class);
  15.             UserMessage userMessage = new UserMessage(
  16.                     "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius");
  17.             ChatResponse response = chatModel
  18.                 .call(new Prompt(List.of(userMessage), HunYuanChatOptions.builder().function("WeatherInfo").build()));
  19.             logger.info("Response: {}", response);
  20.             assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");
  21.         });
  22.     }
  23.     //省略部分代码
复制代码
主要仍旧是通过环境变量的方式注入秘钥信息,构建 Hunyuan 链接,而且将气候相关的方法函数注入到模子中,以便举行后续调用。经过一系列的测试验证,效果如预期正常,所有功能均体现良好。如图所示:

接下来,我们必要编写一个启动器(starter)。这个过程相对简朴,只需在 pom.xml 文件中添加相关的依赖项即可。
Starter

封装 spring-ai-hunyuan-spring-boot-starter 主要是与 Spring 自动配置相关的功能。通过添加该 Starter 依赖,它会自动将相关的组件和配置引入到项目中,从而让开发者能够方便地使用该功能。实际上,spring-ai-hunyuan-spring-boot-starter 相当于一个桥梁,毗连了 spring-ai-spring-boot-autoconfigure 和项目中实际的功能需求。
我们来看一下它的项目结构,相对简洁,如下图所示:

实在就是一个pom依赖,内容如下:
  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.springframework.boot</groupId>
  4.         <artifactId>spring-boot-starter</artifactId>
  5.     </dependency>
  6.     <dependency>
  7.         <groupId>org.springframework.ai</groupId>
  8.         <artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
  9.         <version>${project.parent.version}</version>
  10.     </dependency>
  11.     <dependency>
  12.         <groupId>org.springframework.ai</groupId>
  13.         <artifactId>spring-ai-hunyuan</artifactId>
  14.         <version>${project.parent.version}</version>
  15.     </dependency>
  16. </dependencies>
复制代码
不过,除了上述步骤之外,你还必要在Spring AI的父级目次下添加该子工程,否则系统将无法精确找到相关的依赖信息。具体操纵如图所示:

这部门没有任何代码,所以不用写任何测试。
Adoc文档

Spring AI 的说明文档使用的是antora,Antora 是一个开源的静态网站天生器,专门用于构建和发布文档网站。它是基于 AsciiDoc 格式的,旨在帮助用户天生结构化、易于导航的技术文档。Antora 被广泛应用于开发者文档、产品文档、技术说明书等场景。
假如感兴趣可以去看下官方文档:https://docs.antora.org/antora/latest/install-and-run-quickstart/
当然我们当地写说明文档,idea是有专门的插件供你安装的,可以当地及时预览并编辑。如图所示:

安装完成后,你就可以及时预览相关的文档内容了。必要注意的是,预览的格式与Markdown语法有所不同,因此为了更高效地举行编辑和检察,建议照旧安装相应的插件,如允许以一边编辑一边及时预览,确保效果更加直观和准确。具体效果如下所示:

我们只必要编写两个文档,因为目前只继承了聊天和函数回调功能,相关目次如下所示:

不必担心从头开始写文档,实在你可以参考现有的说明文档。基本上,几乎所有大模子的说明文档内容在结构和表述上都非常相似。你所必要做的,仅仅是对其中的一些特定内容举行适当修改,具体来说,主要包罗以下几个部门:

  • 文档中的模子名称:必要根据当前使用的模子举行更新和调整。
  • 文档中的秘钥和依赖信息:这一部门必要确保包含精确的API秘钥、依赖包的版本和相关配置。
  • 文档中的options选项说明:根据实际的功能选项,举行相应的修改和补充。
  • 文档底部的测试用例路径:测试用例的路径和文件名可能会有所不同,需根据实际路径举行修改。
总的来说,这些是必要我们重点修改的部门,其余的大部门内容都可以保持不变,因为它们构成了文档的基本框架和结构。如图所示,你可以很容易地识别出必要调整的地方。

末了一步是将该文档的路径添加到根目次下的 nav.adoc 文件中,以便于后续的索引和导航操纵,从而确保文档能够快速、准确地被引用和查找。
文档天生

注意这里不要用windows实行命令
./mvnw -pl spring-ai-docs antora
在实行过程中,系统会出现报错,而且我无法在任何地方找到合适的修复方案。然而,当我在Linux机器上运行相同的操纵时,统统正常,无任何错误,具体环境如图所示。

接下来,只必要摆设一个HTML静态服务即可完成相关设置。在这里,我使用的是宝塔面板举行配置,具体的操纵步骤如下图所示。

起首,确保在根目次中选择了Antora天生的文件所在的精确位置。完成这一步后,接下来启动Apache服务即可。假如你更倾向于使用Nginx,直接配置它也是完全可以的。末了,检查一下终极效果,应该会与下图所示相符。

集成测试

至此,上述所有配置基本完成,接下来只需在当地举行测试,确认系统是否能够正常启动并运行混元大模子。为了确保依赖包能够当地可用,我们只需实行 mvn install 命令,如许会将混元(Hunyuan)依赖包安装到当地Maven堆栈。当其他项目必要引用此依赖时,它们会直接从当地堆栈获取,而无需每次都从Maven长途堆栈拉取。具体效果可以参考下图所示。

紧接着,我们在当地创建一个新的空文件夹,专门用于测试和运行Spring AI的Demo案例。

POM依赖

起首在新项目中依赖修改内容版本,如下所示:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4.     <modelVersion>4.0.0</modelVersion>
  5.     <parent>
  6.         <groupId>org.springframework.boot</groupId>
  7.         <artifactId>spring-boot-starter-parent</artifactId>
  8.         <version>3.3.1</version>
  9.         <relativePath/>
  10.     </parent>
  11.     <groupId>com.example</groupId>
  12.     <artifactId>demo</artifactId>
  13.     <version>0.0.1-SNAPSHOT</version>
  14.     <name>demo</name>
  15.     <description>Demo project for Spring Boot</description>
  16.     <url/>
  17.     <properties>
  18.         <java.version>17</java.version>
  19.         <spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>
  20.     </properties>
  21.     <dependencies>
  22.         <dependency>
  23.             <groupId>org.springframework.boot</groupId>
  24.             <artifactId>spring-boot-starter-actuator</artifactId>
  25.         </dependency>
  26.         <dependency>
  27.             <groupId>org.springframework.boot</groupId>
  28.             <artifactId>spring-boot-starter-web</artifactId>
  29.         </dependency>
  30.         <dependency>
  31.             <groupId>org.springframework.ai</groupId>
  32.             <artifactId>spring-ai-hunyuan-spring-boot-starter</artifactId>
  33.         </dependency>
  34.         <dependency>
  35.             <groupId>com.github.xiaoymin</groupId>
  36.             <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
  37.             <version>4.1.0</version>
  38.         </dependency>
  39.         <dependency>
  40.             <groupId>javax.servlet</groupId>
  41.             <artifactId>javax.servlet-api</artifactId>
  42.             <version>4.0.1</version>
  43.         </dependency>
  44.         <dependency>
  45.             <groupId>org.projectlombok</groupId>
  46.             <artifactId>lombok</artifactId>
  47.             <optional>true</optional>
  48.         </dependency>
  49.         <dependency>
  50.             <groupId>org.springframework.boot</groupId>
  51.             <artifactId>spring-boot-starter-test</artifactId>
  52.             <scope>test</scope>
  53.         </dependency>
  54.     </dependencies>
  55.     <dependencyManagement>
  56.         <dependencies>
  57.             <dependency>
  58.                 <groupId>org.springframework.ai</groupId>
  59.                 <artifactId>spring-ai-bom</artifactId>
  60.                 <version>${spring-ai.version}</version>
  61.                 <type>pom</type>
  62.                 <scope>import</scope>
  63.             </dependency>
  64.         </dependencies>
  65.     </dependencyManagement>
  66.     <build>
  67.         <plugins>
  68.             <plugin>
  69.                 <groupId>org.springframework.boot</groupId>
  70.                 <artifactId>spring-boot-maven-plugin</artifactId>
  71.                 <configuration>
  72.                     <excludes>
  73.                         <exclude>
  74.                             <groupId>org.projectlombok</groupId>
  75.                             <artifactId>lombok</artifactId>
  76.                         </exclude>
  77.                     </excludes>
  78.                 </configuration>
  79.             </plugin>
  80.         </plugins>
  81.     </build>
  82.     <repositories>
  83.         <repository>
  84.             <id>spring-milestones</id>
  85.             <name>Spring Milestones</name>
  86.             <url>https://repo.spring.io/milestone</url>
  87.             <snapshots>
  88.                 <enabled>false</enabled>
  89.             </snapshots>
  90.         </repository>
  91.     </repositories>
  92. </project>
复制代码
紧接着,我们开始配置混元的相关配置信息,包罗系统所需的环境变量、服务端口设置以及其他关键参数,以确保系统能够顺遂运行并与其他组件举行高效的协同工作。
  1. spring.application.name=spring-ai-demo
  2. server.port=9149
  3. spring.ai.hunyuan.base-url=https://api.hunyuan.cloud.tencent.com
  4. # 秘钥信息
  5. spring.ai.hunyuan.secret-id=123
  6. spring.ai.hunyuan.secret-key=123
  7. spring.ai.hunyuan.chat.options.model=hunyuan-pro
复制代码
简朴对话

接下来,我们将继承举行一些基本的测试,以验证我们的正常对话功能、图片理解能力以及函数回调功能是否统统正常。为了便于演示,暂时我只是简朴实现了一个内存级别的消息存储机制。以下是相关的测试代码:
  1. @Slf4j
  2. @RestController
  3. class ChatClientController {
  4.     private final ChatMemory chatMemory = new InMemoryChatMemory();
  5.     private final ChatMemory chatimageMemory = new InMemoryChatMemory();
  6.     private final ChatClient chatClient;
  7.     public ChatClientController(ChatClient.Builder chatClientBuilder) {
  8.         this.chatClient = chatClientBuilder.build();
  9.     }
  10.     /**
  11.      * 当前用户输入后,返回一个文本类型的回答
  12.      * @param userInput
  13.      * @return
  14.      */
  15.     @PostMapping("/ai")
  16.     ChatDataPO generationByText(@RequestParam("userInput")  String userInput) {
  17.         String content = this.chatClient.prompt()
  18.                 .user(userInput).advisors(new MessageChatMemoryAdvisor(chatMemory))
  19.                 .call().content();
  20.         log.info("content: {}", content);
  21.         ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();;
  22.         return chatDataPO;
  23.     }
  24.     @PostMapping("/ai-image")
  25.     ChatDataPO generationByImage(@RequestParam("userInput")  String userInput,@RequestParam("url")  String imageUrl) throws MalformedURLException {
  26.         UserMessage userMessage = new UserMessage(userInput, List.of(Media
  27.                 .builder()
  28.                 .mimeType(MimeTypeUtils.IMAGE_PNG)
  29.                 .data(new URL(
  30.                         imageUrl))
  31.                 .build()));
  32.         ChatOptions chatOptions =  ChatOptions.builder().model("hunyuan-turbo-vision").build();
  33.         String content = this.chatClient.prompt().messages(userMessage).options(chatOptions)
  34.                 .advisors(new MessageChatMemoryAdvisor(chatimageMemory))
  35.                 .call().content();
  36.         ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();;
  37.         return chatDataPO;
  38.     }
  39.     @PostMapping("/ai-function")
  40.     ChatDataPO functionGenerationByText(@RequestParam("userInput")  String userInput) {
  41.         String systemPrompt = """
  42.                 - Role: 个人助理小助手
  43.                 - Background: 用户需要一个多功能的AI助手,可以提供实时的天气信息。
  44.                 - Profile: 你是一个专业的个人助理小助手,具备强大的信息检索能力和数据处理能力,能够为用户提供精确的天气信息。
  45.                 - Skills: 你拥有强大的网络搜索能力、数据处理能力以及用户交互能力,能够快速准确地为用户提供所需信息。
  46.                 - Goals: 提供准确的天气信息。
  47.                 - Constrains: 提供的信息必须准确无误。
  48.                 - OutputFormat: 友好的对话式回复,包含必要的详细信息和格式化的数据。
  49.                 - Workflow:
  50.                   1. 接收用户的天气查询请求,并提供准确的天气信息。
  51.                 """;
  52.         String content = this.chatClient.prompt()
  53.                 .user(userInput).system(systemPrompt)
  54.                 .options(HunYuanChatOptions.builder().temperature(0.9).build())
  55.                 .functions("CurrentWeather")
  56.                 .call()
  57.                 .content();
  58.         log.info("content: {}", content);
  59.         ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();
  60.         return chatDataPO;
  61.     }
  62. }
复制代码
必要特殊注意的是,在版本1.0的快照版本中,假如没有显式声明options参数,函数回调将无法正常触发。这是一个已知的限定,必须确保在调用时通报该参数才能确保回调功能的精确实行。下面是相关的示例代码:
.options(HunYuanChatOptions.builder().temperature(0.9).build())
好的,我们直接举行效果测试,看看实际的体现怎样。为了便于演示,我没有接纳流式返回方式,而是使用了常规的请求方式。接下来,我们可以观察一下正常聊天对话的环境是否统统正常,具体环境如图所示:

图片理解测试已经顺遂完成,效果显示正常。具体环境请参见下方图示:

接下来,我们可以检查一下末了的函数回调是否能够正常实行。请参考下图所示:

目前,所有功能均已正常运行,这意味着我们已基本完成了聊天系统的焦点功能。假如您对系统的实现细节感兴趣,接待检察我已提交的相关源码。您可以通过以下GitHub PR地点访问代码: https://github.com/spring-projects/spring-ai/pull/2091

为了帮助新入门的小同伴更好地理解相关概念与技术,建议起首阅读一系列的基础文章,这些文章将逐步引导您熟悉整个流程和关键知识点。您可以通过以下链接访问这些内容:
总结

在本篇文章中,我们具体先容了怎样将腾讯的混元大模子集成到Spring AI项目中,并通过一系列实战操纵,展示了从环境准备到终极实现的全过程。我们从基础的依赖配置、接口对接,到复杂的流式请求处理和自动配置,逐步深入,确保每一个步骤都清晰明了。
通过深入分析和逐步办理还总结了一些实用的履历和技巧。例如,通过使用ModelOptionsUtils工具类来处理JSON与对象之间的转换,避免了直接操纵可能带来的错误;在处理流式请求时,我们特殊注意了数据归并的逻辑,确保了响应的高效性和准确性。
在完成焦点功能开发后,我们进一步先容了怎样通过Spring Boot的自动配置机制,将混元大模子集成到Spring AI的管理中,并通过编写启动器(starter)和文档,为开发者提供了更加便捷的使用方式。通过Antora工具天生的文档,我们具体记录了混元大模子的使用方法、配置选项以及测试用例,为开发者提供了全面的参考。
末了,通过当地集成测试,我们验证了整个系统的正常运行,展示了混元大模子在Spring AI中的实际应用效果。从简朴的对话到复杂的函数回调,所有功能均体现良好,证明了我们集成工作的成功。
总的来说,通过本文的具体先容,读者不但能够掌握怎样将混元大模子集成到Spring AI中,还能够理解其中的关键技术和实现细节。希望这篇文章能够为正在探索Spring AI和大模子集成的开发者提供有价值的参考和指导。假如你对这个项目感兴趣,大概想要进一步了解相关技术,接待访问我们的GitHub堆栈PR,检察完备的代码和文档。
我是努力的小雨,一个端庄的 Java 东北服务端开发,整天琢磨着 AI 技术这块儿的奥秘。特爱跟人交流技术,喜欢把自己的心得和各人分享。还当上了腾讯云创作之星,阿里云专家博主,华为云云享专家,掘金优秀作者。各种征文、开源比赛的牌子也拿了。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表