Commit f6ea1bda7685870ae3d7672df4b24bb2185eabcf

Authored by cherishsince
1 parent 37790d5f

从spring-ai 迁移 chat 模块和依赖模块过来

Showing 63 changed files with 4327 additions and 0 deletions
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>cn.iocoder.boot</groupId>
  8 + <artifactId>yudao-module-ai</artifactId>
  9 + <version>${revision}</version>
  10 + </parent>
  11 +
  12 + <artifactId>yudao-spring-boot-starter-ai</artifactId>
  13 +
  14 + <properties>
  15 + <maven.compiler.source>21</maven.compiler.source>
  16 + <maven.compiler.target>21</maven.compiler.target>
  17 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18 + </properties>
  19 + <dependencies>
  20 + <dependency>
  21 + <groupId>org.springframework</groupId>
  22 + <artifactId>spring-core</artifactId>
  23 + </dependency>
  24 + <dependency>
  25 + <groupId>com.fasterxml.jackson.core</groupId>
  26 + <artifactId>jackson-databind</artifactId>
  27 + </dependency>
  28 + <dependency>
  29 + <groupId>org.springframework</groupId>
  30 + <artifactId>spring-context</artifactId>
  31 + </dependency>
  32 + <dependency>
  33 + <groupId>net.jodah</groupId>
  34 + <artifactId>typetools</artifactId>
  35 + <version>0.6.3</version>
  36 + <scope>compile</scope>
  37 + </dependency>
  38 + <dependency>
  39 + <groupId>com.github.victools</groupId>
  40 + <artifactId>jsonschema-module-jackson</artifactId>
  41 + <version>4.31.1</version>
  42 + <scope>compile</scope>
  43 + </dependency>
  44 + <dependency>
  45 + <groupId>com.github.victools</groupId>
  46 + <artifactId>jsonschema-module-swagger-2</artifactId>
  47 + <version>4.33.1</version>
  48 + </dependency>
  49 + <dependency>
  50 + <groupId>com.github.victools</groupId>
  51 + <artifactId>jsonschema-generator</artifactId>
  52 + <version>4.31.1</version>
  53 + <scope>compile</scope>
  54 + </dependency>
  55 + <dependency>
  56 + <groupId>io.projectreactor</groupId>
  57 + <artifactId>reactor-core</artifactId>
  58 + </dependency>
  59 + <dependency>
  60 + <groupId>org.springframework.cloud</groupId>
  61 + <artifactId>spring-cloud-function-context</artifactId>
  62 + <version>4.1.0</version>
  63 + <scope>compile</scope>
  64 + </dependency>
  65 + <dependency>
  66 + <groupId>org.antlr</groupId>
  67 + <artifactId>stringtemplate</artifactId>
  68 + <version>4.0.2</version>
  69 + <scope>compile</scope>
  70 + </dependency>
  71 + <dependency>
  72 + <groupId>org.projectlombok</groupId>
  73 + <artifactId>lombok</artifactId>
  74 + </dependency>
  75 + <dependency>
  76 + <groupId>org.springframework</groupId>
  77 + <artifactId>spring-web</artifactId>
  78 + </dependency>
  79 + <dependency>
  80 + <groupId>org.springframework</groupId>
  81 + <artifactId>spring-webflux</artifactId>
  82 + </dependency>
  83 + <dependency>
  84 + <groupId>org.springframework.retry</groupId>
  85 + <artifactId>spring-retry</artifactId>
  86 + </dependency>
  87 + <dependency>
  88 + <groupId>junit</groupId>
  89 + <artifactId>junit</artifactId>
  90 + <scope>test</scope>
  91 + </dependency>
  92 + <dependency>
  93 + <groupId>cn.hutool</groupId>
  94 + <artifactId>hutool-all</artifactId>
  95 + <scope>test</scope>
  96 + </dependency>
  97 + <dependency>
  98 + <groupId>com.squareup.okhttp3</groupId>
  99 + <artifactId>okhttp</artifactId>
  100 + <version>4.12.0</version>
  101 + <scope>test</scope>
  102 + </dependency>
  103 + </dependencies>
  104 +
  105 +</project>
0 106 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatClient.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat;
  18 +
  19 +
  20 +import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage;
  21 +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
  22 +import cn.iocoder.yudao.framework.ai.model.ModelClient;
  23 +
  24 +@FunctionalInterface
  25 +public interface ChatClient extends ModelClient<Prompt, ChatResponse> {
  26 +
  27 + default String call(String message) {
  28 + Prompt prompt = new Prompt(new UserMessage(message));
  29 + Generation generation = call(prompt).getResult();
  30 + return (generation != null) ? generation.getOutput().getContent() : "";
  31 + }
  32 +
  33 + @Override
  34 + ChatResponse call(Prompt prompt);
  35 +
  36 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatResponse.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package cn.iocoder.yudao.framework.ai.chat;
  17 +
  18 +import cn.iocoder.yudao.framework.ai.chat.metadata.ChatResponseMetadata;
  19 +import cn.iocoder.yudao.framework.ai.model.ModelResponse;
  20 +import org.springframework.util.CollectionUtils;
  21 +
  22 +import java.util.List;
  23 +import java.util.Objects;
  24 +
  25 +/**
  26 + * 人工智能提供商返回的聊天完成(例如生成)响应。
  27 + *
  28 + * The chat completion (e.g. generation) response returned by an AI provider.
  29 + */
  30 +public class ChatResponse implements ModelResponse<Generation> {
  31 +
  32 + private final ChatResponseMetadata chatResponseMetadata;
  33 +
  34 + /**
  35 + * List of generated messages returned by the AI provider.
  36 + */
  37 + private final List<Generation> generations;
  38 +
  39 + /**
  40 + * Construct a new {@link ChatResponse} instance without metadata.
  41 + * @param generations the {@link List} of {@link Generation} returned by the AI
  42 + * provider.
  43 + */
  44 + public ChatResponse(List<Generation> generations) {
  45 + this(generations, ChatResponseMetadata.NULL);
  46 + }
  47 +
  48 + /**
  49 + * Construct a new {@link ChatResponse} instance.
  50 + * @param generations the {@link List} of {@link Generation} returned by the AI
  51 + * provider.
  52 + * @param chatResponseMetadata {@link ChatResponseMetadata} containing information
  53 + * about the use of the AI provider's API.
  54 + */
  55 + public ChatResponse(List<Generation> generations, ChatResponseMetadata chatResponseMetadata) {
  56 + this.chatResponseMetadata = chatResponseMetadata;
  57 + this.generations = List.copyOf(generations);
  58 + }
  59 +
  60 + /**
  61 + * The {@link List} of {@link Generation generated outputs}.
  62 + * <p>
  63 + * It is a {@link List} of {@link List lists} because the Prompt could request
  64 + * multiple output {@link Generation generations}.
  65 + * @return the {@link List} of {@link Generation generated outputs}.
  66 + */
  67 +
  68 + @Override
  69 + public List<Generation> getResults() {
  70 + return this.generations;
  71 + }
  72 +
  73 + /**
  74 + * @return Returns the first {@link Generation} in the generations list.
  75 + */
  76 + public Generation getResult() {
  77 + if (CollectionUtils.isEmpty(this.generations)) {
  78 + return null;
  79 + }
  80 + return this.generations.get(0);
  81 + }
  82 +
  83 + /**
  84 + * @return Returns {@link ChatResponseMetadata} containing information about the use
  85 + * of the AI provider's API.
  86 + */
  87 + @Override
  88 + public ChatResponseMetadata getMetadata() {
  89 + return this.chatResponseMetadata;
  90 + }
  91 +
  92 + @Override
  93 + public String toString() {
  94 + return "ChatResponse [metadata=" + chatResponseMetadata + ", generations=" + generations + "]";
  95 + }
  96 +
  97 + @Override
  98 + public boolean equals(Object o) {
  99 + if (this == o)
  100 + return true;
  101 + if (!(o instanceof ChatResponse that))
  102 + return false;
  103 + return Objects.equals(chatResponseMetadata, that.chatResponseMetadata)
  104 + && Objects.equals(generations, that.generations);
  105 + }
  106 +
  107 + @Override
  108 + public int hashCode() {
  109 + return Objects.hash(chatResponseMetadata, generations);
  110 + }
  111 +
  112 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/Generation.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.messages.AssistantMessage;
  20 +import cn.iocoder.yudao.framework.ai.chat.metadata.ChatGenerationMetadata;
  21 +import cn.iocoder.yudao.framework.ai.model.ModelResult;
  22 +import org.springframework.lang.Nullable;
  23 +
  24 +import java.util.Map;
  25 +import java.util.Objects;
  26 +
  27 +/**
  28 + * 表示AI返回的响应。
  29 + *
  30 + * Represents a response returned by the AI.
  31 + */
  32 +public class Generation implements ModelResult<AssistantMessage> {
  33 +
  34 + private AssistantMessage assistantMessage;
  35 +
  36 + private ChatGenerationMetadata chatGenerationMetadata;
  37 +
  38 + public Generation(String text) {
  39 + this.assistantMessage = new AssistantMessage(text);
  40 + }
  41 +
  42 + public Generation(String text, Map<String, Object> properties) {
  43 + this.assistantMessage = new AssistantMessage(text, properties);
  44 + }
  45 +
  46 + @Override
  47 + public AssistantMessage getOutput() {
  48 + return this.assistantMessage;
  49 + }
  50 +
  51 + @Override
  52 + public ChatGenerationMetadata getMetadata() {
  53 + ChatGenerationMetadata chatGenerationMetadata = this.chatGenerationMetadata;
  54 + return chatGenerationMetadata != null ? chatGenerationMetadata : ChatGenerationMetadata.NULL;
  55 + }
  56 +
  57 + public Generation withGenerationMetadata(@Nullable ChatGenerationMetadata chatGenerationMetadata) {
  58 + this.chatGenerationMetadata = chatGenerationMetadata;
  59 + return this;
  60 + }
  61 +
  62 + @Override
  63 + public boolean equals(Object o) {
  64 + if (this == o)
  65 + return true;
  66 + if (!(o instanceof Generation that))
  67 + return false;
  68 + return Objects.equals(assistantMessage, that.assistantMessage)
  69 + && Objects.equals(chatGenerationMetadata, that.chatGenerationMetadata);
  70 + }
  71 +
  72 + @Override
  73 + public int hashCode() {
  74 + return Objects.hash(assistantMessage, chatGenerationMetadata);
  75 + }
  76 +
  77 + @Override
  78 + public String toString() {
  79 + return "Generation{" + "assistantMessage=" + assistantMessage + ", chatGenerationMetadata="
  80 + + chatGenerationMetadata + '}';
  81 + }
  82 +
  83 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/StreamingChatClient.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
  20 +import cn.iocoder.yudao.framework.ai.model.StreamingModelClient;
  21 +import reactor.core.publisher.Flux;
  22 +
  23 +@FunctionalInterface
  24 +public interface StreamingChatClient extends StreamingModelClient<Prompt, ChatResponse> {
  25 +
  26 + @Override
  27 + Flux<ChatResponse> stream(Prompt prompt);
  28 +
  29 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AbstractMessage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import org.springframework.core.io.Resource;
  20 +import org.springframework.util.Assert;
  21 +import org.springframework.util.StreamUtils;
  22 +
  23 +import java.io.IOException;
  24 +import java.io.InputStream;
  25 +import java.nio.charset.Charset;
  26 +import java.util.ArrayList;
  27 +import java.util.Collections;
  28 +import java.util.List;
  29 +import java.util.Map;
  30 +
  31 +public abstract class AbstractMessage implements Message {
  32 +
  33 + protected final MessageType messageType;
  34 +
  35 + protected final String textContent;
  36 +
  37 + protected final List<MediaData> mediaData;
  38 +
  39 + /**
  40 + * Additional options for the message to influence the response, not a generative map.
  41 + */
  42 + protected final Map<String, Object> properties;
  43 +
  44 + protected AbstractMessage(MessageType messageType, String content) {
  45 + this(messageType, content, Map.of());
  46 + }
  47 +
  48 + protected AbstractMessage(MessageType messageType, String content, Map<String, Object> messageProperties) {
  49 + Assert.notNull(messageType, "Message type must not be null");
  50 + // Assert.notNull(content, "Content must not be null");
  51 + this.messageType = messageType;
  52 + this.textContent = content;
  53 + this.mediaData = new ArrayList<>();
  54 + this.properties = messageProperties;
  55 + }
  56 +
  57 + protected AbstractMessage(MessageType messageType, String textContent, List<MediaData> mediaData) {
  58 + this(messageType, textContent, mediaData, Map.of());
  59 + }
  60 +
  61 + protected AbstractMessage(MessageType messageType, String textContent, List<MediaData> mediaData,
  62 + Map<String, Object> messageProperties) {
  63 +
  64 + Assert.notNull(messageType, "Message type must not be null");
  65 + Assert.notNull(textContent, "Content must not be null");
  66 + Assert.notNull(mediaData, "media data must not be null");
  67 +
  68 + this.messageType = messageType;
  69 + this.textContent = textContent;
  70 + this.mediaData = new ArrayList<>(mediaData);
  71 + this.properties = messageProperties;
  72 + }
  73 +
  74 + protected AbstractMessage(MessageType messageType, Resource resource) {
  75 + this(messageType, resource, Collections.emptyMap());
  76 + }
  77 +
  78 + @SuppressWarnings("null")
  79 + protected AbstractMessage(MessageType messageType, Resource resource, Map<String, Object> messageProperties) {
  80 + Assert.notNull(messageType, "Message type must not be null");
  81 + Assert.notNull(resource, "Resource must not be null");
  82 +
  83 + this.messageType = messageType;
  84 + this.properties = messageProperties;
  85 + this.mediaData = new ArrayList<>();
  86 +
  87 + try (InputStream inputStream = resource.getInputStream()) {
  88 + this.textContent = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
  89 + }
  90 + catch (IOException ex) {
  91 + throw new RuntimeException("Failed to read resource", ex);
  92 + }
  93 + }
  94 +
  95 + @Override
  96 + public String getContent() {
  97 + return this.textContent;
  98 + }
  99 +
  100 + @Override
  101 + public List<MediaData> getMediaData() {
  102 + return this.mediaData;
  103 + }
  104 +
  105 + @Override
  106 + public Map<String, Object> getProperties() {
  107 + return this.properties;
  108 + }
  109 +
  110 + @Override
  111 + public MessageType getMessageType() {
  112 + return this.messageType;
  113 + }
  114 +
  115 + @Override
  116 + public int hashCode() {
  117 + final int prime = 31;
  118 + int result = 1;
  119 + result = prime * result + ((mediaData == null) ? 0 : mediaData.hashCode());
  120 + result = prime * result + ((properties == null) ? 0 : properties.hashCode());
  121 + result = prime * result + ((messageType == null) ? 0 : messageType.hashCode());
  122 + return result;
  123 + }
  124 +
  125 + @Override
  126 + public boolean equals(Object obj) {
  127 + if (this == obj)
  128 + return true;
  129 + if (obj == null)
  130 + return false;
  131 + if (getClass() != obj.getClass())
  132 + return false;
  133 + AbstractMessage other = (AbstractMessage) obj;
  134 + if (mediaData == null) {
  135 + if (other.mediaData != null)
  136 + return false;
  137 + }
  138 + else if (!mediaData.equals(other.mediaData))
  139 + return false;
  140 + if (properties == null) {
  141 + if (other.properties != null)
  142 + return false;
  143 + }
  144 + else if (!properties.equals(other.properties))
  145 + return false;
  146 + if (messageType != other.messageType)
  147 + return false;
  148 + return true;
  149 + }
  150 +
  151 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AssistantMessage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import java.util.Map;
  20 +
  21 +/**
  22 + * 让生成人员知道内容是作为对用户的响应生成的。
  23 + * 此角色指示生成者先前在会话中生成的消息。
  24 + * 通过包括该系列中的辅助消息,您可以为生成的关于提供上下文之前在谈话中的交流。
  25 + *
  26 + * Lets the generative know the content was generated as a response to the user. This role
  27 + * indicates messages that the generative has previously generated in the conversation. By
  28 + * including assistant messages in the series, you provide context to the generative about
  29 + * prior exchanges in the conversation.
  30 + */
  31 +public class AssistantMessage extends AbstractMessage {
  32 +
  33 + public AssistantMessage(String content) {
  34 + super(MessageType.ASSISTANT, content);
  35 + }
  36 +
  37 + public AssistantMessage(String content, Map<String, Object> properties) {
  38 + super(MessageType.ASSISTANT, content, properties);
  39 + }
  40 +
  41 + @Override
  42 + public String toString() {
  43 + return "AssistantMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
  44 + + messageType + '}';
  45 + }
  46 +
  47 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/ChatMessage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import java.util.Map;
  20 +
  21 +public class ChatMessage extends AbstractMessage {
  22 +
  23 + public ChatMessage(String role, String content) {
  24 + super(MessageType.valueOf(role), content);
  25 + }
  26 +
  27 + public ChatMessage(String role, String content, Map<String, Object> properties) {
  28 + super(MessageType.valueOf(role), content, properties);
  29 + }
  30 +
  31 + public ChatMessage(MessageType messageType, String content) {
  32 + super(messageType, content);
  33 + }
  34 +
  35 + public ChatMessage(MessageType messageType, String content, Map<String, Object> properties) {
  36 + super(messageType, content, properties);
  37 + }
  38 +
  39 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/FunctionMessage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import java.util.Map;
  20 +
  21 +public class FunctionMessage extends AbstractMessage {
  22 +
  23 + public FunctionMessage(String content) {
  24 + super(MessageType.SYSTEM, content);
  25 + }
  26 +
  27 + public FunctionMessage(String content, Map<String, Object> properties) {
  28 + super(MessageType.SYSTEM, content, properties);
  29 + }
  30 +
  31 + @Override
  32 + public String toString() {
  33 + return "FunctionMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
  34 + + messageType + '}';
  35 + }
  36 +
  37 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MediaData.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import org.springframework.util.Assert;
  20 +import org.springframework.util.MimeType;
  21 +
  22 +/**
  23 + * @author Christian Tzolov
  24 + */
  25 +public class MediaData {
  26 +
  27 + private final MimeType mimeType;
  28 +
  29 + private final Object data;
  30 +
  31 + public MediaData(MimeType mimeType, Object data) {
  32 + Assert.notNull(mimeType, "MimeType must not be null");
  33 + // Assert.notNull(data, "Data must not be null");
  34 + this.mimeType = mimeType;
  35 + this.data = data;
  36 + }
  37 +
  38 + public MimeType getMimeType() {
  39 + return this.mimeType;
  40 + }
  41 +
  42 + public Object getData() {
  43 + return this.data;
  44 + }
  45 +
  46 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/Message.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import java.util.List;
  20 +import java.util.Map;
  21 +
  22 +public interface Message {
  23 +
  24 + String getContent();
  25 +
  26 + List<MediaData> getMediaData();
  27 +
  28 + Map<String, Object> getProperties();
  29 +
  30 + MessageType getMessageType();
  31 +
  32 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MessageType.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package cn.iocoder.yudao.framework.ai.chat.messages;
  17 +
  18 +public enum MessageType {
  19 +
  20 + // 用户消息
  21 + USER("user"),
  22 +
  23 + // 之前会话的消息
  24 + ASSISTANT("assistant"),
  25 +
  26 + // 根据注释说明:您可以使用系统消息来指示具有生成性,表现得像某个角色或以特定的方式提供答案总体安排
  27 + // 简单理解:在对话前,发送一条具有角色的信息让模型理解(如:你现在是一个10年拍摄经验的导演,拥有丰富的经验。 这样你就可以去问他,怎么拍一个短视频可以在抖音上火)
  28 + SYSTEM("system"),
  29 +
  30 + // 函数?根据引用现在不支持,会抛出一个异常 ---> throw new IllegalArgumentException("Tool execution results are not supported for Bedrock models");
  31 + FUNCTION("function");
  32 +
  33 + private final String value;
  34 +
  35 + MessageType(String value) {
  36 + this.value = value;
  37 + }
  38 +
  39 + public String getValue() {
  40 + return value;
  41 + }
  42 +
  43 + public static MessageType fromValue(String value) {
  44 + for (MessageType messageType : MessageType.values()) {
  45 + if (messageType.getValue().equals(value)) {
  46 + return messageType;
  47 + }
  48 + }
  49 + throw new IllegalArgumentException("Invalid MessageType value: " + value);
  50 + }
  51 +
  52 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/SystemMessage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import org.springframework.core.io.Resource;
  20 +
  21 +/**
  22 + * 作为输入传递的“system”类型的消息。系统消息给出高级别对话说明。
  23 + * 此角色通常提供高级说明对话。
  24 + * 例如,您可以使用系统消息来指示具有生成性,表现得像某个角色或以特定的方式提供答案总体安排
  25 + *
  26 + * A message of the type 'system' passed as input. The system message gives high level
  27 + * instructions for the conversation. This role typically provides high-level instructions
  28 + * for the conversation. For example, you might use a system message to instruct the
  29 + * generative to behave like a certain character or to provide answers in a specific
  30 + * format.
  31 + */
  32 +public class SystemMessage extends AbstractMessage {
  33 +
  34 + public SystemMessage(String content) {
  35 + super(MessageType.SYSTEM, content);
  36 + }
  37 +
  38 + public SystemMessage(Resource resource) {
  39 + super(MessageType.SYSTEM, resource);
  40 + }
  41 +
  42 + @Override
  43 + public String toString() {
  44 + return "SystemMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
  45 + + messageType + '}';
  46 + }
  47 +
  48 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/UserMessage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.messages;
  18 +
  19 +import org.springframework.core.io.Resource;
  20 +
  21 +import java.util.List;
  22 +
  23 +/**
  24 + * 作为输入传递的“user”类型的消息具有用户角色的消息来自最终用户或开发者。
  25 + * 它们表示问题、提示或您想要的任何输入产生反应的。
  26 + *
  27 + * A message of the type 'user' passed as input Messages with the user role are from the
  28 + * end-user or developer. They represent questions, prompts, or any input that you want
  29 + * the generative to respond to.
  30 + */
  31 +public class UserMessage extends AbstractMessage {
  32 +
  33 + public UserMessage(String message) {
  34 + super(MessageType.USER, message);
  35 + }
  36 +
  37 + public UserMessage(Resource resource) {
  38 + super(MessageType.USER, resource);
  39 + }
  40 +
  41 + public UserMessage(String textContent, List<MediaData> mediaDataList) {
  42 + super(MessageType.USER, textContent, mediaDataList);
  43 + }
  44 +
  45 + @Override
  46 + public String toString() {
  47 + return "UserMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
  48 + + messageType + '}';
  49 + }
  50 +
  51 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatGenerationMetadata.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.metadata;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.model.ResultMetadata;
  20 +import org.springframework.lang.Nullable;
  21 +
  22 +/**
  23 + * Abstract Data Type (ADT) encapsulating information on the completion choices in the AI
  24 + * response.
  25 + *
  26 + * @author John Blum
  27 + * @since 0.7.0
  28 + */
  29 +public interface ChatGenerationMetadata extends ResultMetadata {
  30 +
  31 + ChatGenerationMetadata NULL = ChatGenerationMetadata.from(null, null);
  32 +
  33 + /**
  34 + * Factory method used to construct a new {@link ChatGenerationMetadata} from the
  35 + * given {@link String finish reason} and content filter metadata.
  36 + * @param finishReason {@link String} contain the reason for the choice completion.
  37 + * @param contentFilterMetadata underlying AI provider metadata for filtering applied
  38 + * to generation content.
  39 + * @return a new {@link ChatGenerationMetadata} from the given {@link String finish
  40 + * reason} and content filter metadata.
  41 + */
  42 + static ChatGenerationMetadata from(String finishReason, Object contentFilterMetadata) {
  43 + return new ChatGenerationMetadata() {
  44 +
  45 + @Override
  46 + @SuppressWarnings("unchecked")
  47 + public <T> T getContentFilterMetadata() {
  48 + return (T) contentFilterMetadata;
  49 + }
  50 +
  51 + @Override
  52 + public String getFinishReason() {
  53 + return finishReason;
  54 + }
  55 + };
  56 + }
  57 +
  58 + /**
  59 + * Returns the underlying AI provider metadata for filtering applied to generation
  60 + * content.
  61 + * @param <T> {@link Class Type} used to cast the filtered content metadata into the
  62 + * AI provider-specific type.
  63 + * @return the underlying AI provider metadata for filtering applied to generation
  64 + * content.
  65 + */
  66 + @Nullable
  67 + <T> T getContentFilterMetadata();
  68 +
  69 + /**
  70 + * Get the {@link String reason} this choice completed for the generation.
  71 + * @return the {@link String reason} this choice completed for the generation.
  72 + */
  73 + String getFinishReason();
  74 +
  75 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatResponseMetadata.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.metadata;
  18 +
  19 +
  20 +import cn.iocoder.yudao.framework.ai.model.ResponseMetadata;
  21 +
  22 +/**
  23 + * Abstract Data Type (ADT) modeling common AI provider metadata returned in an AI
  24 + * response.
  25 + *
  26 + * 抽象数据类型(ADT)建模AI响应中返回的常见AI提供者元数据。
  27 + *
  28 + * @author John Blum
  29 + * @since 0.7.0
  30 + */
  31 +public interface ChatResponseMetadata extends ResponseMetadata {
  32 +
  33 + ChatResponseMetadata NULL = new ChatResponseMetadata() {
  34 + };
  35 +
  36 + /**
  37 + * Returns AI provider specific metadata on rate limits.
  38 + * @return AI provider specific metadata on rate limits.
  39 + * @see RateLimit
  40 + */
  41 + default RateLimit getRateLimit() {
  42 + return new EmptyRateLimit();
  43 + }
  44 +
  45 + /**
  46 + * Returns AI provider specific metadata on API usage.
  47 + * @return AI provider specific metadata on API usage.
  48 + * @see Usage
  49 + */
  50 + default Usage getUsage() {
  51 + return new EmptyUsage();
  52 + }
  53 +
  54 + default PromptMetadata getPromptMetadata() {
  55 + return PromptMetadata.empty();
  56 + }
  57 +
  58 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyRateLimit.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.metadata;
  18 +
  19 +import java.time.Duration;
  20 +
  21 +/**
  22 + * A RateLimit implementation that returns zero for all property getters
  23 + *
  24 + * @author John Blum
  25 + * @since 0.7.0
  26 + */
  27 +public class EmptyRateLimit implements RateLimit {
  28 +
  29 + @Override
  30 + public Long getRequestsLimit() {
  31 + return 0L;
  32 + }
  33 +
  34 + @Override
  35 + public Long getRequestsRemaining() {
  36 + return 0L;
  37 + }
  38 +
  39 + @Override
  40 + public Duration getRequestsReset() {
  41 + return Duration.ZERO;
  42 + }
  43 +
  44 + @Override
  45 + public Long getTokensLimit() {
  46 + return 0L;
  47 + }
  48 +
  49 + @Override
  50 + public Long getTokensRemaining() {
  51 + return 0L;
  52 + }
  53 +
  54 + @Override
  55 + public Duration getTokensReset() {
  56 + return Duration.ZERO;
  57 + }
  58 +
  59 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyUsage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.metadata;
  18 +
  19 +/**
  20 + * A EmpytUsage implementation that returns zero for all property getters
  21 + *
  22 + * @author John Blum
  23 + * @since 0.7.0
  24 + */
  25 +public class EmptyUsage implements Usage {
  26 +
  27 + @Override
  28 + public Long getPromptTokens() {
  29 + return 0L;
  30 + }
  31 +
  32 + @Override
  33 + public Long getGenerationTokens() {
  34 + return 0L;
  35 + }
  36 +
  37 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/PromptMetadata.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package cn.iocoder.yudao.framework.ai.chat.metadata;
  17 +
  18 +import org.springframework.util.Assert;
  19 +
  20 +import java.util.Arrays;
  21 +import java.util.Optional;
  22 +import java.util.stream.StreamSupport;
  23 +
  24 +/**
  25 + * Abstract Data Type (ADT) modeling metadata gathered by the AI during request
  26 + * processing.
  27 + *
  28 + * @author John Blum
  29 + * @since 0.7.0
  30 + */
  31 +@FunctionalInterface
  32 +public interface PromptMetadata extends Iterable<PromptMetadata.PromptFilterMetadata> {
  33 +
  34 + /**
  35 + * Factory method used to create empty {@link PromptMetadata} when the information is
  36 + * not supplied by the AI provider.
  37 + * @return empty {@link PromptMetadata}.
  38 + */
  39 + static PromptMetadata empty() {
  40 + return of();
  41 + }
  42 +
  43 + /**
  44 + * Factory method used to create a new {@link PromptMetadata} composed of an array of
  45 + * {@link PromptFilterMetadata}.
  46 + * @param array array of {@link PromptFilterMetadata} used to compose the
  47 + * {@link PromptMetadata}.
  48 + * @return a new {@link PromptMetadata} composed of an array of
  49 + * {@link PromptFilterMetadata}.
  50 + */
  51 + static <T> PromptMetadata of(PromptFilterMetadata... array) {
  52 + return of(Arrays.asList(array));
  53 + }
  54 +
  55 + /**
  56 + * Factory method used to create a new {@link PromptMetadata} composed of an
  57 + * {@link Iterable} of {@link PromptFilterMetadata}.
  58 + * @param iterable {@link Iterable} of {@link PromptFilterMetadata} used to compose
  59 + * the {@link PromptMetadata}.
  60 + * @return a new {@link PromptMetadata} composed of an {@link Iterable} of
  61 + * {@link PromptFilterMetadata}.
  62 + */
  63 + static PromptMetadata of(Iterable<PromptFilterMetadata> iterable) {
  64 + Assert.notNull(iterable, "An Iterable of PromptFilterMetadata must not be null");
  65 + return iterable::iterator;
  66 + }
  67 +
  68 + /**
  69 + * Returns an {@link Optional} {@link PromptFilterMetadata} at the given index.
  70 + * @param promptIndex index of the {@link PromptFilterMetadata} contained in this
  71 + * {@link PromptMetadata}.
  72 + * @return {@link Optional} {@link PromptFilterMetadata} at the given index.
  73 + * @throws IllegalArgumentException if the prompt index is less than 0.
  74 + */
  75 + default Optional<PromptFilterMetadata> findByPromptIndex(int promptIndex) {
  76 +
  77 + Assert.isTrue(promptIndex > -1, "Prompt index [%d] must be greater than equal to 0".formatted(promptIndex));
  78 +
  79 + return StreamSupport.stream(this.spliterator(), false)
  80 + .filter(promptFilterMetadata -> promptFilterMetadata.getPromptIndex() == promptIndex)
  81 + .findFirst();
  82 + }
  83 +
  84 + /**
  85 + * Abstract Data Type (ADT) modeling filter metadata for all prompts sent during an AI
  86 + * request.
  87 + */
  88 + interface PromptFilterMetadata {
  89 +
  90 + /**
  91 + * Factory method used to construct a new {@link PromptFilterMetadata} with the
  92 + * given prompt index and content filter metadata.
  93 + * @param promptIndex index of the prompt filter metadata contained in the AI
  94 + * response.
  95 + * @param contentFilterMetadata underlying AI provider metadata for filtering
  96 + * applied to prompt content.
  97 + * @return a new instance of {@link PromptFilterMetadata} with the given prompt
  98 + * index and content filter metadata.
  99 + */
  100 + static PromptFilterMetadata from(int promptIndex, Object contentFilterMetadata) {
  101 +
  102 + return new PromptFilterMetadata() {
  103 +
  104 + @Override
  105 + public int getPromptIndex() {
  106 + return promptIndex;
  107 + }
  108 +
  109 + @Override
  110 + @SuppressWarnings("unchecked")
  111 + public <T> T getContentFilterMetadata() {
  112 + return (T) contentFilterMetadata;
  113 + }
  114 + };
  115 + }
  116 +
  117 + /**
  118 + * Index of the prompt filter metadata contained in the AI response.
  119 + * @return an {@link Integer index} fo the prompt filter metadata contained in the
  120 + * AI response.
  121 + */
  122 + int getPromptIndex();
  123 +
  124 + /**
  125 + * Returns the underlying AI provider metadata for filtering applied to prompt
  126 + * content.
  127 + * @param <T> {@link Class Type} used to cast the filtered content metadata into
  128 + * the AI provider-specific type.
  129 + * @return the underlying AI provider metadata for filtering applied to prompt
  130 + * content.
  131 + */
  132 + <T> T getContentFilterMetadata();
  133 +
  134 + }
  135 +
  136 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/RateLimit.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.metadata;
  18 +
  19 +import java.time.Duration;
  20 +
  21 +/**
  22 + * Abstract Data Type (ADT) encapsulating metadata from an AI provider's API rate limits
  23 + * granted to the API key in use and the API key's current balance.
  24 + *
  25 + * @author John Blum
  26 + * @since 0.7.0
  27 + */
  28 +public interface RateLimit {
  29 +
  30 + /**
  31 + * Returns the maximum number of requests that are permitted before exhausting the
  32 + * rate limit.
  33 + * @return an {@link Long} with the maximum number of requests that are permitted
  34 + * before exhausting the rate limit.
  35 + * @see #getRequestsRemaining()
  36 + */
  37 + Long getRequestsLimit();
  38 +
  39 + /**
  40 + * Returns the remaining number of requests that are permitted before exhausting the
  41 + * {@link #getRequestsLimit() rate limit}.
  42 + * @return an {@link Long} with the remaining number of requests that are permitted
  43 + * before exhausting the {@link #getRequestsLimit() rate limit}.
  44 + * @see #getRequestsLimit()
  45 + */
  46 + Long getRequestsRemaining();
  47 +
  48 + /**
  49 + * Returns the {@link Duration time} until the rate limit (based on requests) resets
  50 + * to its {@link #getRequestsLimit() initial state}.
  51 + * @return a {@link Duration} representing the time until the rate limit (based on
  52 + * requests) resets to its {@link #getRequestsLimit() initial state}.
  53 + * @see #getRequestsLimit()
  54 + */
  55 + Duration getRequestsReset();
  56 +
  57 + /**
  58 + * Returns the maximum number of tokens that are permitted before exhausting the rate
  59 + * limit.
  60 + * @return an {@link Long} with the maximum number of tokens that are permitted before
  61 + * exhausting the rate limit.
  62 + * @see #getTokensRemaining()
  63 + */
  64 + Long getTokensLimit();
  65 +
  66 + /**
  67 + * Returns the remaining number of tokens that are permitted before exhausting the
  68 + * {@link #getTokensLimit() rate limit}.
  69 + * @return an {@link Long} with the remaining number of tokens that are permitted
  70 + * before exhausting the {@link #getTokensLimit() rate limit}.
  71 + * @see #getTokensLimit()
  72 + */
  73 + Long getTokensRemaining();
  74 +
  75 + /**
  76 + * Returns the {@link Duration time} until the rate limit (based on tokens) resets to
  77 + * its {@link #getTokensLimit() initial state}.
  78 + * @return a {@link Duration} with the time until the rate limit (based on tokens)
  79 + * resets to its {@link #getTokensLimit() initial state}.
  80 + * @see #getTokensLimit()
  81 + */
  82 + Duration getTokensReset();
  83 +
  84 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/Usage.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.metadata;
  18 +
  19 +/**
  20 + * 抽象数据类型(ADT)封装关于人工智能提供商API使用的元数据根据AI请求。
  21 + *
  22 + * Abstract Data Type (ADT) encapsulating metadata on the usage of an AI provider's API
  23 + * per AI request.
  24 + *
  25 + * @author John Blum
  26 + * @since 0.7.0
  27 + */
  28 +public interface Usage {
  29 +
  30 + /**
  31 + * 返回AI请求的{@literal prompt}中使用的令牌数。
  32 + * @返回一个{@link Long},其中包含在的{@literal提示符}中使用的令牌数AI请求。
  33 + *
  34 + * Returns the number of tokens used in the {@literal prompt} of the AI request.
  35 + * @return an {@link Long} with the number of tokens used in the {@literal prompt} of
  36 + * the AI request.
  37 + * @see #getGenerationTokens()
  38 + */
  39 + Long getPromptTokens();
  40 +
  41 + /**
  42 + * Returns the number of tokens returned in the {@literal generation (aka completion)}
  43 + * of the AI's response.
  44 + * @return an {@link Long} with the number of tokens returned in the
  45 + * {@literal generation (aka completion)} of the AI's response.
  46 + * @see #getPromptTokens()
  47 + */
  48 + Long getGenerationTokens();
  49 +
  50 + /**
  51 + * Return the total number of tokens from both the {@literal prompt} of an AI request
  52 + * and {@literal generation} of the AI's response.
  53 + * @return the total number of tokens from both the {@literal prompt} of an AI request
  54 + * and {@literal generation} of the AI's response.
  55 + * @see #getPromptTokens()
  56 + * @see #getGenerationTokens()
  57 + */
  58 + default Long getTotalTokens() {
  59 + Long promptTokens = getPromptTokens();
  60 + promptTokens = promptTokens != null ? promptTokens : 0;
  61 + Long completionTokens = getGenerationTokens();
  62 + completionTokens = completionTokens != null ? completionTokens : 0;
  63 + return promptTokens + completionTokens;
  64 + }
  65 +
  66 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/package-info.java 0 → 100644
  1 +/**
  2 + * The org.sf.ai.chat package represents the bounded context for the Chat Model within the
  3 + * AI generative model domain. This package extends the core domain defined in
  4 + * org.sf.ai.generative, providing implementations specific to chat-based generative AI
  5 + * interactions.
  6 + *
  7 + * In line with Domain-Driven Design principles, this package includes implementations of
  8 + * entities and value objects specific to the chat context, such as ChatPrompt and
  9 + * ChatResponse, adhering to the ubiquitous language of chat interactions in AI models.
  10 + *
  11 + * This bounded context is designed to encapsulate all aspects of chat-based AI
  12 + * functionalities, maintaining a clear boundary from other contexts within the AI domain.
  13 + */
  14 +package cn.iocoder.yudao.framework.ai.chat;
0 15 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/AssistantPromptTemplate.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.messages.AssistantMessage;
  20 +import cn.iocoder.yudao.framework.ai.chat.messages.Message;
  21 +import org.springframework.core.io.Resource;
  22 +
  23 +import java.util.Map;
  24 +
  25 +public class AssistantPromptTemplate extends PromptTemplate {
  26 +
  27 + public AssistantPromptTemplate(String template) {
  28 + super(template);
  29 + }
  30 +
  31 + public AssistantPromptTemplate(Resource resource) {
  32 + super(resource);
  33 + }
  34 +
  35 + @Override
  36 + public Prompt create() {
  37 + return new Prompt(new AssistantMessage(render()));
  38 + }
  39 +
  40 + @Override
  41 + public Prompt create(Map<String, Object> model) {
  42 + return new Prompt(new AssistantMessage(render(model)));
  43 + }
  44 +
  45 + @Override
  46 + public Message createMessage() {
  47 + return new AssistantMessage(render());
  48 + }
  49 +
  50 + @Override
  51 + public Message createMessage(Map<String, Object> model) {
  52 + return new AssistantMessage(render(model));
  53 + }
  54 +
  55 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptions.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.model.ModelOptions;
  20 +
  21 +/**
  22 + * 聊天选项代表了常见的选项,可在不同的聊天模式中移植。
  23 + *
  24 + * The ChatOptions represent the common options, portable across different chat models.
  25 + */
  26 +public interface ChatOptions extends ModelOptions {
  27 +
  28 + Float getTemperature();
  29 +
  30 + void setTemperature(Float temperature);
  31 +
  32 + Float getTopP();
  33 +
  34 + void setTopP(Float topP);
  35 +
  36 + Integer getTopK();
  37 +
  38 + void setTopK(Integer topK);
  39 +
  40 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptionsBuilder.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +public class ChatOptionsBuilder {
  20 +
  21 + private class ChatOptionsImpl implements ChatOptions {
  22 +
  23 + private Float temperature;
  24 +
  25 + private Float topP;
  26 +
  27 + private Integer topK;
  28 +
  29 + @Override
  30 + public Float getTemperature() {
  31 + return temperature;
  32 + }
  33 +
  34 + @Override
  35 + public void setTemperature(Float temperature) {
  36 + this.temperature = temperature;
  37 + }
  38 +
  39 + @Override
  40 + public Float getTopP() {
  41 + return topP;
  42 + }
  43 +
  44 + @Override
  45 + public void setTopP(Float topP) {
  46 + this.topP = topP;
  47 + }
  48 +
  49 + @Override
  50 + public Integer getTopK() {
  51 + return topK;
  52 + }
  53 +
  54 + @Override
  55 + public void setTopK(Integer topK) {
  56 + this.topK = topK;
  57 + }
  58 +
  59 + }
  60 +
  61 + private final ChatOptionsImpl options = new ChatOptionsImpl();
  62 +
  63 + private ChatOptionsBuilder() {
  64 + }
  65 +
  66 + public static ChatOptionsBuilder builder() {
  67 + return new ChatOptionsBuilder();
  68 + }
  69 +
  70 + public ChatOptionsBuilder withTemperature(Float temperature) {
  71 + options.setTemperature(temperature);
  72 + return this;
  73 + }
  74 +
  75 + public ChatOptionsBuilder withTopP(Float topP) {
  76 + options.setTopP(topP);
  77 + return this;
  78 + }
  79 +
  80 + public ChatOptionsBuilder withTopK(Integer topK) {
  81 + options.setTopK(topK);
  82 + return this;
  83 + }
  84 +
  85 + public ChatOptions build() {
  86 + return options;
  87 + }
  88 +
  89 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatPromptTemplate.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.messages.Message;
  20 +
  21 +import java.util.ArrayList;
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +
  25 +/**
  26 + * PromptTemplate,用于将角色指定为字符串实现及其角色不足以满足您的需求。
  27 + *
  28 + * A PromptTemplate that lets you specify the role as a string should the current
  29 + * implementations and their roles not suffice for your needs.
  30 + */
  31 +public class ChatPromptTemplate implements PromptTemplateActions, PromptTemplateChatActions {
  32 +
  33 + private final List<PromptTemplate> promptTemplates;
  34 +
  35 + public ChatPromptTemplate(List<PromptTemplate> promptTemplates) {
  36 + this.promptTemplates = promptTemplates;
  37 + }
  38 +
  39 + @Override
  40 + public String render() {
  41 + StringBuilder sb = new StringBuilder();
  42 + for (PromptTemplate promptTemplate : promptTemplates) {
  43 + sb.append(promptTemplate.render());
  44 + }
  45 + return sb.toString();
  46 + }
  47 +
  48 + @Override
  49 + public String render(Map<String, Object> model) {
  50 + StringBuilder sb = new StringBuilder();
  51 + for (PromptTemplate promptTemplate : promptTemplates) {
  52 + sb.append(promptTemplate.render(model));
  53 + }
  54 + return sb.toString();
  55 + }
  56 +
  57 + @Override
  58 + public List<Message> createMessages() {
  59 + List<Message> messages = new ArrayList<>();
  60 + for (PromptTemplate promptTemplate : promptTemplates) {
  61 + messages.add(promptTemplate.createMessage());
  62 + }
  63 + return messages;
  64 + }
  65 +
  66 + @Override
  67 + public List<Message> createMessages(Map<String, Object> model) {
  68 + List<Message> messages = new ArrayList<>();
  69 + for (PromptTemplate promptTemplate : promptTemplates) {
  70 + messages.add(promptTemplate.createMessage(model));
  71 + }
  72 + return messages;
  73 + }
  74 +
  75 + @Override
  76 + public Prompt create() {
  77 + List<Message> messages = createMessages();
  78 + return new Prompt(messages);
  79 + }
  80 +
  81 + @Override
  82 + public Prompt create(Map<String, Object> model) {
  83 + List<Message> messages = createMessages(model);
  84 + return new Prompt(messages);
  85 + }
  86 +
  87 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/FunctionPromptTemplate.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +public class FunctionPromptTemplate extends PromptTemplate {
  20 +
  21 + private String name;
  22 +
  23 + public FunctionPromptTemplate(String template) {
  24 + super(template);
  25 + }
  26 +
  27 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/Prompt.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.messages.Message;
  20 +import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage;
  21 +import cn.iocoder.yudao.framework.ai.model.ModelOptions;
  22 +import cn.iocoder.yudao.framework.ai.model.ModelRequest;
  23 +
  24 +import java.util.Collections;
  25 +import java.util.List;
  26 +import java.util.Objects;
  27 +
  28 +/**
  29 + * 文字内容
  30 + */
  31 +public class Prompt implements ModelRequest<List<Message>> {
  32 +
  33 + private final List<Message> messages;
  34 +
  35 + private ChatOptions modelOptions;
  36 +
  37 + public Prompt(String contents) {
  38 + this(new UserMessage(contents));
  39 + }
  40 +
  41 + public Prompt(Message message) {
  42 + this(Collections.singletonList(message));
  43 + }
  44 +
  45 + public Prompt(List<Message> messages) {
  46 + this.messages = messages;
  47 + }
  48 +
  49 + public Prompt(String contents, ChatOptions modelOptions) {
  50 + this(new UserMessage(contents), modelOptions);
  51 + }
  52 +
  53 + public Prompt(Message message, ChatOptions modelOptions) {
  54 + this(Collections.singletonList(message), modelOptions);
  55 + }
  56 +
  57 + public Prompt(List<Message> messages, ChatOptions modelOptions) {
  58 + this.messages = messages;
  59 + this.modelOptions = modelOptions;
  60 + }
  61 +
  62 + public String getContents() {
  63 + StringBuilder sb = new StringBuilder();
  64 + for (Message message : getInstructions()) {
  65 + sb.append(message.getContent());
  66 + }
  67 + return sb.toString();
  68 + }
  69 +
  70 + @Override
  71 + public ModelOptions getOptions() {
  72 + return this.modelOptions;
  73 + }
  74 +
  75 + @Override
  76 + public List<Message> getInstructions() {
  77 + return this.messages;
  78 + }
  79 +
  80 + @Override
  81 + public String toString() {
  82 + return "Prompt{" + "messages=" + this.messages + ", modelOptions=" + this.modelOptions + '}';
  83 + }
  84 +
  85 + @Override
  86 + public boolean equals(Object o) {
  87 + if (this == o)
  88 + return true;
  89 + if (!(o instanceof Prompt prompt))
  90 + return false;
  91 + return Objects.equals(this.messages, prompt.messages) && Objects.equals(this.modelOptions, prompt.modelOptions);
  92 + }
  93 +
  94 + @Override
  95 + public int hashCode() {
  96 + return Objects.hash(this.messages, this.modelOptions);
  97 + }
  98 +
  99 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplate.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.messages.Message;
  20 +import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage;
  21 +import cn.iocoder.yudao.framework.ai.parser.OutputParser;
  22 +import org.antlr.runtime.Token;
  23 +import org.antlr.runtime.TokenStream;
  24 +import org.springframework.core.io.Resource;
  25 +import org.springframework.util.StreamUtils;
  26 +import org.stringtemplate.v4.ST;
  27 +import org.stringtemplate.v4.compiler.STLexer;
  28 +
  29 +import java.io.IOException;
  30 +import java.io.InputStream;
  31 +import java.nio.charset.Charset;
  32 +import java.util.*;
  33 +import java.util.Map.Entry;
  34 +import java.util.stream.Collectors;
  35 +import java.util.stream.IntStream;
  36 +
  37 +/**
  38 + * 用户输入的提示内容模板
  39 + *
  40 + * 实现:提示词模板操作 提示词模板message相关操作
  41 + */
  42 +public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
  43 +
  44 + private ST st;
  45 +
  46 + private Map<String, Object> dynamicModel = new HashMap<>();
  47 +
  48 + protected String template;
  49 +
  50 + protected TemplateFormat templateFormat = TemplateFormat.ST;
  51 +
  52 + private OutputParser outputParser;
  53 +
  54 + public PromptTemplate(Resource resource) {
  55 + try (InputStream inputStream = resource.getInputStream()) {
  56 + this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
  57 + }
  58 + catch (IOException ex) {
  59 + throw new RuntimeException("Failed to read resource", ex);
  60 + }
  61 + try {
  62 + this.st = new ST(this.template, '{', '}');
  63 + }
  64 + catch (Exception ex) {
  65 + throw new IllegalArgumentException("The template string is not valid.", ex);
  66 + }
  67 + }
  68 +
  69 + public PromptTemplate(String template) {
  70 + this.template = template;
  71 + // If the template string is not valid, an exception will be thrown
  72 + try {
  73 + this.st = new ST(this.template, '{', '}');
  74 + }
  75 + catch (Exception ex) {
  76 + throw new IllegalArgumentException("The template string is not valid.", ex);
  77 + }
  78 + }
  79 +
  80 + public PromptTemplate(String template, Map<String, Object> model) {
  81 + this.template = template;
  82 + // If the template string is not valid, an exception will be thrown
  83 + try {
  84 + this.st = new ST(this.template, '{', '}');
  85 + for (Entry<String, Object> entry : model.entrySet()) {
  86 + add(entry.getKey(), entry.getValue());
  87 + dynamicModel.put(entry.getKey(), entry.getValue());
  88 + }
  89 + }
  90 + catch (Exception ex) {
  91 + throw new IllegalArgumentException("The template string is not valid.", ex);
  92 + }
  93 + }
  94 +
  95 + public PromptTemplate(Resource resource, Map<String, Object> model) {
  96 + try (InputStream inputStream = resource.getInputStream()) {
  97 + this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
  98 + }
  99 + catch (IOException ex) {
  100 + throw new RuntimeException("Failed to read resource", ex);
  101 + }
  102 + // If the template string is not valid, an exception will be thrown
  103 + try {
  104 + this.st = new ST(this.template, '{', '}');
  105 + for (Entry<String, Object> entry : model.entrySet()) {
  106 + add(entry.getKey(), entry.getValue());
  107 + dynamicModel.put(entry.getKey(), entry.getValue());
  108 + }
  109 + }
  110 + catch (Exception ex) {
  111 + throw new IllegalArgumentException("The template string is not valid.", ex);
  112 + }
  113 + }
  114 +
  115 + public OutputParser getOutputParser() {
  116 + return outputParser;
  117 + }
  118 +
  119 + public void setOutputParser(OutputParser outputParser) {
  120 + Objects.requireNonNull(outputParser, "Output Parser can not be null");
  121 + this.outputParser = outputParser;
  122 + }
  123 +
  124 + public void add(String name, Object value) {
  125 + this.st.add(name, value);
  126 + this.dynamicModel.put(name, value);
  127 + }
  128 +
  129 + public String getTemplate() {
  130 + return this.template;
  131 + }
  132 +
  133 + public TemplateFormat getTemplateFormat() {
  134 + return this.templateFormat;
  135 + }
  136 +
  137 + // Render Methods
  138 + @Override
  139 + public String render() {
  140 + validate(this.dynamicModel);
  141 + return st.render();
  142 + }
  143 +
  144 + @Override
  145 + public String render(Map<String, Object> model) {
  146 + validate(model);
  147 + for (Entry<String, Object> entry : model.entrySet()) {
  148 + if (st.getAttribute(entry.getKey()) != null) {
  149 + st.remove(entry.getKey());
  150 + }
  151 + if (entry.getValue() instanceof Resource) {
  152 + st.add(entry.getKey(), renderResource((Resource) entry.getValue()));
  153 + }
  154 + else {
  155 + st.add(entry.getKey(), entry.getValue());
  156 + }
  157 +
  158 + }
  159 + return st.render();
  160 + }
  161 +
  162 + private String renderResource(Resource resource) {
  163 + try {
  164 + return resource.getContentAsString(Charset.defaultCharset());
  165 + }
  166 + catch (IOException e) {
  167 + throw new RuntimeException(e);
  168 + }
  169 + // try (InputStream inputStream = resource.getInputStream()) {
  170 + // return StreamUtils.copyToString(inputStream, Charset.defaultCharset());
  171 + // }
  172 + // catch (IOException ex) {
  173 + // throw new RuntimeException(ex);
  174 + // }
  175 + }
  176 +
  177 + @Override
  178 + public Message createMessage() {
  179 + return new UserMessage(render());
  180 + }
  181 +
  182 + @Override
  183 + public Message createMessage(Map<String, Object> model) {
  184 + return new UserMessage(render(model));
  185 + }
  186 +
  187 + @Override
  188 + public Prompt create() {
  189 + return new Prompt(render(new HashMap<>()));
  190 + }
  191 +
  192 + @Override
  193 + public Prompt create(Map<String, Object> model) {
  194 + return new Prompt(render(model));
  195 + }
  196 +
  197 + public Set<String> getInputVariables() {
  198 + TokenStream tokens = this.st.impl.tokens;
  199 + return IntStream.range(0, tokens.range())
  200 + .mapToObj(tokens::get)
  201 + .filter(token -> token.getType() == STLexer.ID)
  202 + .map(Token::getText)
  203 + .collect(Collectors.toSet());
  204 + }
  205 +
  206 + protected void validate(Map<String, Object> model) {
  207 + Set<String> dynamicVariableNames = new HashSet<>(this.dynamicModel.keySet());
  208 + Set<String> modelVariables = new HashSet<>(model.keySet());
  209 + modelVariables.addAll(dynamicVariableNames);
  210 + Set<String> missingEntries = new HashSet<>(getInputVariables());
  211 + missingEntries.removeAll(modelVariables);
  212 + if (!missingEntries.isEmpty()) {
  213 + throw new IllegalStateException(
  214 + "All template variables were not replaced. Missing variable names are " + missingEntries);
  215 + }
  216 + }
  217 +
  218 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateActions.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +import java.util.Map;
  20 +
  21 +/**
  22 + * 提示词模板操作
  23 + */
  24 +public interface PromptTemplateActions extends PromptTemplateStringActions {
  25 +
  26 + /**
  27 + * 创建 Prompt
  28 + * @return
  29 + */
  30 + Prompt create();
  31 +
  32 + Prompt create(Map<String, Object> model);
  33 +
  34 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateChatActions.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.chat.messages.Message;
  4 +
  5 +import java.util.List;
  6 +import java.util.Map;
  7 +
  8 +/**
  9 + * 聊天操作
  10 + *
  11 + */
  12 +public interface PromptTemplateChatActions {
  13 +
  14 + List<Message> createMessages();
  15 +
  16 + List<Message> createMessages(Map<String, Object> model);
  17 +
  18 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateMessageActions.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.chat.messages.Message;
  4 +
  5 +import java.util.Map;
  6 +
  7 +/**
  8 + * 用户输入的提示内容 模板信息操作
  9 + */
  10 +public interface PromptTemplateMessageActions {
  11 +
  12 + /**
  13 + * 创建一个 message
  14 + * @return
  15 + */
  16 + Message createMessage();
  17 +
  18 + /**
  19 + * 创建一个 message
  20 + * @return
  21 + */
  22 + Message createMessage(Map<String, Object> model);
  23 +
  24 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateStringActions.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  2 +
  3 +import java.util.Map;
  4 +
  5 +/**
  6 + * 提示次模板字符串操作
  7 + */
  8 +public interface PromptTemplateStringActions {
  9 +
  10 + String render();
  11 +
  12 + String render(Map<String, Object> model);
  13 +
  14 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/SystemPromptTemplate.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.messages.Message;
  20 +import cn.iocoder.yudao.framework.ai.chat.messages.SystemMessage;
  21 +import org.springframework.core.io.Resource;
  22 +
  23 +import java.util.Map;
  24 +
  25 +public class SystemPromptTemplate extends PromptTemplate {
  26 +
  27 + public SystemPromptTemplate(String template) {
  28 + super(template);
  29 + }
  30 +
  31 + public SystemPromptTemplate(Resource resource) {
  32 + super(resource);
  33 + }
  34 +
  35 + @Override
  36 + public Message createMessage() {
  37 + return new SystemMessage(render());
  38 + }
  39 +
  40 + @Override
  41 + public Message createMessage(Map<String, Object> model) {
  42 + return new SystemMessage(render(model));
  43 + }
  44 +
  45 + @Override
  46 + public Prompt create() {
  47 + return new Prompt(new SystemMessage(render()));
  48 + }
  49 +
  50 + @Override
  51 + public Prompt create(Map<String, Object> model) {
  52 + return new Prompt(new SystemMessage(render(model)));
  53 + }
  54 +
  55 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/TemplateFormat.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.chat.prompt;
  18 +
  19 +public enum TemplateFormat {
  20 +
  21 + ST("ST");
  22 +
  23 + private final String value;
  24 +
  25 + TemplateFormat(String value) {
  26 + this.value = value;
  27 + }
  28 +
  29 + public String getValue() {
  30 + return value;
  31 + }
  32 +
  33 + public static TemplateFormat fromValue(String value) {
  34 + for (TemplateFormat templateFormat : TemplateFormat.values()) {
  35 + if (templateFormat.getValue().equals(value)) {
  36 + return templateFormat;
  37 + }
  38 + }
  39 + throw new IllegalArgumentException("Invalid TemplateFormat value: " + value);
  40 + }
  41 +
  42 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelClient.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +/**
  20 + * The ModelClient interface provides a generic API for invoking AI models. It is designed
  21 + * to handle the interaction with various types of AI models by abstracting the process of
  22 + * sending requests and receiving responses. The interface uses Java generics to
  23 + * accommodate different types of requests and responses, enhancing flexibility and
  24 + * adaptability across different AI model implementations.
  25 + *
  26 + * @param <TReq> the generic type of the request to the AI model
  27 + * @param <TRes> the generic type of the response from the AI model
  28 + * @author Mark Pollack
  29 + * @since 0.8.0
  30 + */
  31 +public interface ModelClient<TReq extends ModelRequest<?>, TRes extends ModelResponse<?>> {
  32 +
  33 + /**
  34 + * Executes a method call to the AI model.
  35 + * @param request the request object to be sent to the AI model
  36 + * @return the response from the AI model
  37 + */
  38 + TRes call(TReq request) throws Throwable;
  39 +
  40 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptions.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +/**
  20 + * Interface representing the customizable options for AI model interactions. This marker
  21 + * interface allows for the specification of various settings and parameters that can
  22 + * influence the behavior and output of AI models. It is designed to provide flexibility
  23 + * and adaptability in different AI scenarios, ensuring that the AI models can be
  24 + * fine-tuned according to specific requirements.
  25 + *
  26 + * @author Mark Pollack
  27 + * @since 0.8.0
  28 + */
  29 +public interface ModelOptions {
  30 +
  31 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptionsUtils.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import com.fasterxml.jackson.core.JsonProcessingException;
  21 +import com.fasterxml.jackson.core.type.TypeReference;
  22 +import com.fasterxml.jackson.databind.DeserializationFeature;
  23 +import com.fasterxml.jackson.databind.JsonNode;
  24 +import com.fasterxml.jackson.databind.ObjectMapper;
  25 +import com.fasterxml.jackson.databind.SerializationFeature;
  26 +import com.fasterxml.jackson.databind.node.ArrayNode;
  27 +import com.fasterxml.jackson.databind.node.ObjectNode;
  28 +import com.github.victools.jsonschema.generator.*;
  29 +import com.github.victools.jsonschema.module.jackson.JacksonModule;
  30 +import com.github.victools.jsonschema.module.jackson.JacksonOption;
  31 +import com.github.victools.jsonschema.module.swagger2.Swagger2Module;
  32 +import org.springframework.beans.BeanWrapper;
  33 +import org.springframework.beans.BeanWrapperImpl;
  34 +import org.springframework.util.Assert;
  35 +import org.springframework.util.CollectionUtils;
  36 +
  37 +import java.beans.PropertyDescriptor;
  38 +import java.lang.reflect.Field;
  39 +import java.util.*;
  40 +import java.util.concurrent.ConcurrentHashMap;
  41 +import java.util.concurrent.atomic.AtomicReference;
  42 +import java.util.stream.Collectors;
  43 +
  44 +/**
  45 + * Utility class for manipulating {@link ModelOptions} objects.
  46 + *
  47 + * @author Christian Tzolov
  48 + * @since 0.8.0
  49 + */
  50 +public final class ModelOptionsUtils {
  51 +
  52 + private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper()
  53 + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
  54 + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
  55 +
  56 + private final static List<String> BEAN_MERGE_FIELD_EXCISIONS = List.of("class");
  57 +
  58 + private static ConcurrentHashMap<Class<?>, List<String>> REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap<Class<?>, List<String>>();
  59 +
  60 + private static AtomicReference<SchemaGenerator> SCHEMA_GENERATOR_CACHE = new AtomicReference<>();
  61 +
  62 + private ModelOptionsUtils() {
  63 +
  64 + }
  65 +
  66 + /**
  67 + * Converts the given JSON string to a Map of String and Object.
  68 + * @param json the JSON string to convert to a Map.
  69 + * @return the converted Map.
  70 + */
  71 + public static Map<String, Object> jsonToMap(String json) {
  72 + try {
  73 + return OBJECT_MAPPER.readValue(json, MAP_TYPE_REF);
  74 + }
  75 + catch (Exception e) {
  76 + throw new RuntimeException(e);
  77 + }
  78 + }
  79 +
  80 + private static TypeReference<HashMap<String, Object>> MAP_TYPE_REF = new TypeReference<HashMap<String, Object>>() {
  81 + };
  82 +
  83 + /**
  84 + * Converts the given JSON string to an Object of the given type.
  85 + * @param <T> the type of the object to return.
  86 + * @param json the JSON string to convert to an object.
  87 + * @param type the type of the object to return.
  88 + * @return Object instance of the given type.
  89 + */
  90 + public static <T> T jsonToObject(String json, Class<T> type) {
  91 + try {
  92 + return OBJECT_MAPPER.readValue(json, type);
  93 + }
  94 + catch (Exception e) {
  95 + throw new RuntimeException("Failed to json: " + json, e);
  96 + }
  97 + }
  98 +
  99 + /**
  100 + * Converts the given object to a JSON string.
  101 + * @param object the object to convert to a JSON string.
  102 + * @return the JSON string.
  103 + */
  104 + public static String toJsonString(Object object) {
  105 + try {
  106 + return OBJECT_MAPPER.writeValueAsString(object);
  107 + }
  108 + catch (JsonProcessingException e) {
  109 + throw new RuntimeException(e);
  110 + }
  111 + }
  112 +
  113 + /**
  114 + * Merges the source object into the target object and returns an object represented
  115 + * by the given class. The JSON property names are used to match the fields to merge.
  116 + * The source non-null values override the target values with the same field name. The
  117 + * source null values are ignored. If the acceptedFieldNames is not empty, only the
  118 + * fields with the given names are merged and returned. If the acceptedFieldNames is
  119 + * empty, use the {@code @JsonProperty} names, inferred from the provided clazz.
  120 + * @param <T> they type of the class to return.
  121 + * @param source the source object to merge.
  122 + * @param target the target object to merge into.
  123 + * @param clazz the class to return.
  124 + * @param acceptedFieldNames the list of field names accepted for the target object.
  125 + * @return the merged object represented by the given class.
  126 + */
  127 + public static <T> T merge(Object source, Object target, Class<T> clazz, List<String> acceptedFieldNames) {
  128 +
  129 + if (source == null) {
  130 + source = Map.of();
  131 + }
  132 +
  133 + List<String> requestFieldNames = CollectionUtils.isEmpty(acceptedFieldNames)
  134 + ? REQUEST_FIELD_NAMES_PER_CLASS.computeIfAbsent(clazz, ModelOptionsUtils::getJsonPropertyValues)
  135 + : acceptedFieldNames;
  136 +
  137 + if (CollectionUtils.isEmpty(requestFieldNames)) {
  138 + throw new IllegalArgumentException("No @JsonProperty fields found in the " + clazz.getName());
  139 + }
  140 +
  141 + Map<String, Object> sourceMap = ModelOptionsUtils.objectToMap(source);
  142 + Map<String, Object> targetMap = ModelOptionsUtils.objectToMap(target);
  143 +
  144 + targetMap.putAll(sourceMap.entrySet()
  145 + .stream()
  146 + .filter(e -> e.getValue() != null)
  147 + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));
  148 +
  149 + targetMap = targetMap.entrySet()
  150 + .stream()
  151 + .filter(e -> requestFieldNames.contains(e.getKey()))
  152 + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
  153 +
  154 + return ModelOptionsUtils.mapToClass(targetMap, clazz);
  155 + }
  156 +
  157 + /**
  158 + * Merges the source object into the target object and returns an object represented
  159 + * by the given class. The JSON property names are used to match the fields to merge.
  160 + * The source non-null values override the target values with the same field name. The
  161 + * source null values are ignored. Returns the only field names that match the
  162 + * {@code @JsonProperty} names, inferred from the provided clazz.
  163 + * @param <T> they type of the class to return.
  164 + * @param source the source object to merge.
  165 + * @param target the target object to merge into.
  166 + * @param clazz the class to return.
  167 + * @return the merged object represented by the given class.
  168 + */
  169 + public static <T> T merge(Object source, Object target, Class<T> clazz) {
  170 + return ModelOptionsUtils.merge(source, target, clazz, null);
  171 + }
  172 +
  173 + /**
  174 + * Converts the given object to a Map.
  175 + * @param source the object to convert to a Map.
  176 + * @return the converted Map.
  177 + */
  178 + public static Map<String, Object> objectToMap(Object source) {
  179 + if (source == null) {
  180 + return new HashMap<>();
  181 + }
  182 + try {
  183 + String json = OBJECT_MAPPER.writeValueAsString(source);
  184 + return OBJECT_MAPPER.readValue(json, new TypeReference<Map<String, Object>>() {
  185 + })
  186 + .entrySet()
  187 + .stream()
  188 + .filter(e -> e.getValue() != null)
  189 + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
  190 + }
  191 + catch (JsonProcessingException e) {
  192 + throw new RuntimeException(e);
  193 + }
  194 + }
  195 +
  196 + /**
  197 + * Converts the given Map to the given class.
  198 + * @param <T> the type of the class to return.
  199 + * @param source the Map to convert to the given class.
  200 + * @param clazz the class to convert the Map to.
  201 + * @return the converted class.
  202 + */
  203 + public static <T> T mapToClass(Map<String, Object> source, Class<T> clazz) {
  204 + try {
  205 + String json = OBJECT_MAPPER.writeValueAsString(source);
  206 + return OBJECT_MAPPER.readValue(json, clazz);
  207 + }
  208 + catch (JsonProcessingException e) {
  209 + throw new RuntimeException(e);
  210 + }
  211 + }
  212 +
  213 + /**
  214 + * Returns the list of name values of the {@link JsonProperty} annotations.
  215 + * @param clazz the class that contains fields annotated with {@link JsonProperty}.
  216 + * @return the list of values of the {@link JsonProperty} annotations.
  217 + */
  218 + public static List<String> getJsonPropertyValues(Class<?> clazz) {
  219 + List<String> values = new ArrayList<>();
  220 + Field[] fields = clazz.getDeclaredFields();
  221 + for (Field field : fields) {
  222 + JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class);
  223 + if (jsonPropertyAnnotation != null) {
  224 + values.add(jsonPropertyAnnotation.value());
  225 + }
  226 + }
  227 + return values;
  228 + }
  229 +
  230 + /**
  231 + * Returns a new instance of the targetBeanClazz that copies the bean values from the
  232 + * sourceBean instance.
  233 + * @param sourceBean the source bean to copy the values from.
  234 + * @param sourceInterfaceClazz the source interface class. Only the fields with the
  235 + * same name as the interface methods are copied. This allow the source object to be a
  236 + * subclass of the source interface with additional, non-interface fields.
  237 + * @param targetBeanClazz the target class, a subclass of the ChatOptions, to convert
  238 + * into.
  239 + * @param <T> the target class type.
  240 + * @return a new instance of the targetBeanClazz with the values from the sourceBean
  241 + * instance.
  242 + */
  243 + public static <I, S extends I, T extends S> T copyToTarget(S sourceBean, Class<I> sourceInterfaceClazz,
  244 + Class<T> targetBeanClazz) {
  245 +
  246 + Assert.notNull(sourceInterfaceClazz, "SourceOptionsClazz must not be null");
  247 + Assert.notNull(targetBeanClazz, "TargetOptionsClazz must not be null");
  248 +
  249 + if (sourceBean == null) {
  250 + return null;
  251 + }
  252 +
  253 + if (sourceBean.getClass().isAssignableFrom(targetBeanClazz)) {
  254 + return (T) sourceBean;
  255 + }
  256 +
  257 + try {
  258 + T targetOptions = targetBeanClazz.getConstructor().newInstance();
  259 +
  260 + ModelOptionsUtils.mergeBeans(sourceBean, targetOptions, sourceInterfaceClazz, true);
  261 +
  262 + return targetOptions;
  263 + }
  264 + catch (Exception e) {
  265 + throw new RuntimeException(
  266 + "Failed to convert the " + sourceInterfaceClazz.getName() + " into " + targetBeanClazz.getName(),
  267 + e);
  268 + }
  269 + }
  270 +
  271 + /**
  272 + * Merges the source object into the target object. The source null values are
  273 + * ignored. Only objects with Getter and Setter methods are supported.
  274 + * @param <T> the type of the source and target object.
  275 + * @param source the source object to merge.
  276 + * @param target the target object to merge into.
  277 + * @param sourceInterfaceClazz the source interface class. Only the fields with the
  278 + * same name as the interface methods are merged. This allow the source object to be a
  279 + * subclass of the source interface with additional, non-interface fields.
  280 + * @param overrideNonNullTargetValues if true, the source non-null values override the
  281 + * target values with the same field name. If false, the source non-null values are
  282 + * ignored.
  283 + * @return the merged target object.
  284 + */
  285 + public static <I, S extends I, T extends S> T mergeBeans(S source, T target, Class<I> sourceInterfaceClazz,
  286 + boolean overrideNonNullTargetValues) {
  287 + Assert.notNull(source, "Source object must not be null");
  288 + Assert.notNull(target, "Target object must not be null");
  289 +
  290 + BeanWrapper sourceBeanWrap = new BeanWrapperImpl(source);
  291 + BeanWrapper targetBeanWrap = new BeanWrapperImpl(target);
  292 +
  293 + List<String> interfaceNames = Arrays.stream(sourceInterfaceClazz.getMethods()).map(m -> m.getName()).toList();
  294 +
  295 + for (PropertyDescriptor descriptor : sourceBeanWrap.getPropertyDescriptors()) {
  296 +
  297 + if (!BEAN_MERGE_FIELD_EXCISIONS.contains(descriptor.getName())
  298 + && interfaceNames.contains(toGetName(descriptor.getName()))) {
  299 +
  300 + String propertyName = descriptor.getName();
  301 + Object value = sourceBeanWrap.getPropertyValue(propertyName);
  302 +
  303 + // Copy value to the target object
  304 + if (value != null) {
  305 + var targetValue = targetBeanWrap.getPropertyValue(propertyName);
  306 +
  307 + if (targetValue == null || overrideNonNullTargetValues) {
  308 + targetBeanWrap.setPropertyValue(propertyName, value);
  309 + }
  310 + }
  311 + }
  312 + }
  313 +
  314 + return target;
  315 + }
  316 +
  317 + private static String toGetName(String name) {
  318 + return "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
  319 + }
  320 +
  321 + /**
  322 + * Generates JSON Schema (version 2020_12) for the given class.
  323 + * @param clazz the class to generate JSON Schema for.
  324 + * @param toUpperCaseTypeValues if true, the type values are converted to upper case.
  325 + * @return the generated JSON Schema as a String.
  326 + */
  327 + public static String getJsonSchema(Class<?> clazz, boolean toUpperCaseTypeValues) {
  328 +
  329 + if (SCHEMA_GENERATOR_CACHE.get() == null) {
  330 +
  331 + JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED);
  332 + Swagger2Module swaggerModule = new Swagger2Module();
  333 +
  334 + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12,
  335 + OptionPreset.PLAIN_JSON)
  336 + .with(Option.EXTRA_OPEN_API_FORMAT_VALUES)
  337 + .with(Option.PLAIN_DEFINITION_KEYS)
  338 + .with(swaggerModule)
  339 + .with(jacksonModule);
  340 +
  341 + SchemaGeneratorConfig config = configBuilder.build();
  342 + SchemaGenerator generator = new SchemaGenerator(config);
  343 + SCHEMA_GENERATOR_CACHE.compareAndSet(null, generator);
  344 + }
  345 +
  346 + ObjectNode node = SCHEMA_GENERATOR_CACHE.get().generateSchema(clazz);
  347 + if (toUpperCaseTypeValues) { // Required for OpenAPI 3.0 (at least Vertex AI
  348 + // version of it).
  349 + toUpperCaseTypeValues(node);
  350 + }
  351 +
  352 + return node.toPrettyString();
  353 + }
  354 +
  355 + public static void toUpperCaseTypeValues(ObjectNode node) {
  356 + if (node == null) {
  357 + return;
  358 + }
  359 + if (node.isObject()) {
  360 + node.fields().forEachRemaining(entry -> {
  361 + JsonNode value = entry.getValue();
  362 + if (value.isObject()) {
  363 + toUpperCaseTypeValues((ObjectNode) value);
  364 + }
  365 + else if (value.isArray()) {
  366 + ((ArrayNode) value).elements().forEachRemaining(element -> {
  367 + if (element.isObject() || element.isArray()) {
  368 + toUpperCaseTypeValues((ObjectNode) element);
  369 + }
  370 + });
  371 + }
  372 + else if (value.isTextual() && entry.getKey().equals("type")) {
  373 + String oldValue = ((ObjectNode) node).get("type").asText();
  374 + ((ObjectNode) node).put("type", oldValue.toUpperCase());
  375 + }
  376 + });
  377 + }
  378 + else if (node.isArray()) {
  379 + node.elements().forEachRemaining(element -> {
  380 + if (element.isObject() || element.isArray()) {
  381 + toUpperCaseTypeValues((ObjectNode) element);
  382 + }
  383 + });
  384 + }
  385 + }
  386 +
  387 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelRequest.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +/**
  20 + * 表示对AI模型的请求的接口。此接口封装了 与人工智能模型交互所需的必要信息,包括指令或 输入(通用类型T)和附加模型选项。它提供了一种标准化的方式
  21 + * 向人工智能模型发送请求,确保包括所有必要的细节,并且可以易于管理。
  22 + *
  23 + * Interface representing a request to an AI model. This interface encapsulates the
  24 + * necessary information required to interact with an AI model, including instructions or
  25 + * inputs (of generic type T) and additional model options. It provides a standardized way
  26 + * to send requests to AI models, ensuring that all necessary details are included and can
  27 + * be easily managed.
  28 + *
  29 + * @param <T> the type of instructions or input required by the AI model
  30 + * @author Mark Pollack
  31 + * @since 0.8.0
  32 + */
  33 +public interface ModelRequest<T> {
  34 +
  35 + /**
  36 + * 检索AI模型所需的指令或输入。 返回AI模型所需的指令或输入
  37 + *
  38 + * Retrieves the instructions or input required by the AI model.
  39 + * @return the instructions or input required by the AI model
  40 + */
  41 + T getInstructions(); // required input
  42 +
  43 + /**
  44 + * 检索人工智能模型交互的可自定义选项。 返回AI模型交互的自定义选项
  45 + *
  46 + * Retrieves the customizable options for AI model interactions.
  47 + * @return the customizable options for AI model interactions
  48 + */
  49 + ModelOptions getOptions();
  50 +
  51 +}
0 52 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResponse.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +import java.util.List;
  20 +
  21 +/**
  22 + *
  23 + * 表示从AI模型接收到的响应的接口。此接口提供 访问AI模型生成的主要结果或结果列表的方法,以及 以及响应元数据。它是封装和管理的标准化方式
  24 + * 人工智能模型的输出,确保轻松检索和处理生成的信息
  25 + *
  26 + * Interface representing the response received from an AI model. This interface provides
  27 + * methods to access the main result or a list of results generated by the AI model, along
  28 + * with the response metadata. It serves as a standardized way to encapsulate and manage
  29 + * the output from AI models, ensuring easy retrieval and processing of the generated
  30 + * information.
  31 + *
  32 + * @param <T> the type of the result(s) provided by the AI model
  33 + * @author Mark Pollack
  34 + * @since 0.8.0
  35 + */
  36 +public interface ModelResponse<T extends ModelResult<?>> {
  37 +
  38 + /**
  39 + * 检索AI模型的结果。
  40 + *
  41 + * Retrieves the result of the AI model.
  42 + * @return the result generated by the AI model
  43 + */
  44 + T getResult();
  45 +
  46 + /**
  47 + * 检索AI模型生成的输出列表。
  48 + *
  49 + * Retrieves the list of generated outputs by the AI model.
  50 + * @return the list of generated outputs
  51 + */
  52 + List<T> getResults();
  53 +
  54 + /**
  55 + * 检索与AI模型的响应相关联的响应元数据。
  56 + *
  57 + * Retrieves the response metadata associated with the AI model's response.
  58 + * @return the response metadata
  59 + */
  60 + ResponseMetadata getMetadata();
  61 +
  62 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResult.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +/**
  20 + * This interface provides methods to access the main output of the AI model and the
  21 + * metadata associated with this result. It is designed to offer a standardized and
  22 + * comprehensive way to handle and interpret the outputs generated by AI models, catering
  23 + * to diverse AI applications and use cases.
  24 + *
  25 + * @param <T> the type of the output generated by the AI model
  26 + * @author Mark Pollack
  27 + * @since 0.8.0
  28 + */
  29 +public interface ModelResult<T> {
  30 +
  31 + /**
  32 + * Retrieves the output generated by the AI model.
  33 + * @return the output generated by the AI model
  34 + */
  35 + T getOutput();
  36 +
  37 + /**
  38 + * Retrieves the metadata associated with the result of an AI model.
  39 + * @return the metadata associated with the result
  40 + */
  41 + ResultMetadata getMetadata();
  42 +
  43 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResponseMetadata.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +/**
  20 + * 表示与AI模型的响应相关联的元数据的接口。此接口 旨在提供有关人工智能生成反应的附加信息 模型,包括处理细节和模型特定数据。它是一种价值
  21 + * 核心领域内的对象,增强对人工智能模型的理解和管理 在各种应用中的响应。
  22 + *
  23 + * Interface representing metadata associated with an AI model's response. This interface
  24 + * is designed to provide additional information about the generative response from an AI
  25 + * model, including processing details and model-specific data. It serves as a value
  26 + * object within the core domain, enhancing the understanding and management of AI model
  27 + * responses in various applications.
  28 + *
  29 + * @author Mark Pollack
  30 + * @since 0.8.0
  31 + */
  32 +public interface ResponseMetadata {
  33 +
  34 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResultMetadata.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +/**
  20 + * Interface representing metadata associated with the results of an AI model. This
  21 + * interface focuses on providing additional context and insights into the results
  22 + * generated by AI models. It could include information like computation time, model
  23 + * version, or other relevant details that enhance understanding and management of AI
  24 + * model outputs in various applications.
  25 + *
  26 + * @author Mark Pollack
  27 + * @since 0.8.0
  28 + */
  29 +public interface ResultMetadata {
  30 +
  31 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/StreamingModelClient.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model;
  18 +
  19 +import reactor.core.publisher.Flux;
  20 +
  21 +/**
  22 + * The StreamingModelClient interface provides a generic API for invoking a AI models with
  23 + * streaming response. It abstracts the process of sending requests and receiving a
  24 + * streaming responses. The interface uses Java generics to accommodate different types of
  25 + * requests and responses, enhancing flexibility and adaptability across different AI
  26 + * model implementations.
  27 + *
  28 + * @param <TReq> the generic type of the request to the AI model
  29 + * @param <TResChunk> the generic type of a single item in the streaming response from the
  30 + * AI model
  31 + * @author Christian Tzolov
  32 + * @since 0.8.0
  33 + */
  34 +public interface StreamingModelClient<TReq extends ModelRequest<?>, TResChunk extends ModelResponse<?>> {
  35 +
  36 + /**
  37 + * Executes a method call to the AI model.
  38 + * @param request the request object to be sent to the AI model
  39 + * @return the streaming response from the AI model
  40 + */
  41 + Flux<TResChunk> stream(TReq request);
  42 +
  43 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallSupport.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model.function;
  18 +
  19 +import org.springframework.util.CollectionUtils;
  20 +
  21 +import java.util.*;
  22 +import java.util.concurrent.ConcurrentHashMap;
  23 +
  24 +/**
  25 + * @author Christian Tzolov
  26 + */
  27 +public abstract class AbstractFunctionCallSupport<Msg, Req, Resp> {
  28 +
  29 + protected final static boolean IS_RUNTIME_CALL = true;
  30 +
  31 + /**
  32 + * The function callback register is used to resolve the function callbacks by name.
  33 + */
  34 + protected final Map<String, FunctionCallback> functionCallbackRegister = new ConcurrentHashMap<>();
  35 +
  36 + /**
  37 + * The function callback context is used to resolve the function callbacks by name
  38 + * from the Spring context. It is optional and usually used with Spring
  39 + * auto-configuration.
  40 + */
  41 + protected final FunctionCallbackContext functionCallbackContext;
  42 +
  43 + public AbstractFunctionCallSupport(FunctionCallbackContext functionCallbackContext) {
  44 + this.functionCallbackContext = functionCallbackContext;
  45 + }
  46 +
  47 + public Map<String, FunctionCallback> getFunctionCallbackRegister() {
  48 + return this.functionCallbackRegister;
  49 + }
  50 +
  51 + protected Set<String> handleFunctionCallbackConfigurations(FunctionCallingOptions options, boolean isRuntimeCall) {
  52 +
  53 + Set<String> functionToCall = new HashSet<>();
  54 +
  55 + if (options != null) {
  56 + if (!CollectionUtils.isEmpty(options.getFunctionCallbacks())) {
  57 + options.getFunctionCallbacks().stream().forEach(functionCallback -> {
  58 +
  59 + // Register the tool callback.
  60 + if (isRuntimeCall) {
  61 + this.functionCallbackRegister.put(functionCallback.getName(), functionCallback);
  62 + }
  63 + else {
  64 + this.functionCallbackRegister.putIfAbsent(functionCallback.getName(), functionCallback);
  65 + }
  66 +
  67 + // Automatically enable the function, usually from prompt callback.
  68 + if (isRuntimeCall) {
  69 + functionToCall.add(functionCallback.getName());
  70 + }
  71 + });
  72 + }
  73 +
  74 + // Add the explicitly enabled functions.
  75 + if (!CollectionUtils.isEmpty(options.getFunctions())) {
  76 + functionToCall.addAll(options.getFunctions());
  77 + }
  78 + }
  79 +
  80 + return functionToCall;
  81 + }
  82 +
  83 + /**
  84 + * Resolve the function callbacks by name. Retrieve them from the registry or try to
  85 + * resolve them from the Application Context.
  86 + * @param functionNames Name of function callbacks to retrieve.
  87 + * @return list of resolved FunctionCallbacks.
  88 + */
  89 + protected List<FunctionCallback> resolveFunctionCallbacks(Set<String> functionNames) {
  90 +
  91 + List<FunctionCallback> retrievedFunctionCallbacks = new ArrayList<>();
  92 +
  93 + for (String functionName : functionNames) {
  94 + if (!this.functionCallbackRegister.containsKey(functionName)) {
  95 +
  96 + if (this.functionCallbackContext != null) {
  97 + FunctionCallback functionCallback = this.functionCallbackContext.getFunctionCallback(functionName,
  98 + null);
  99 + if (functionCallback != null) {
  100 + this.functionCallbackRegister.put(functionName, functionCallback);
  101 + }
  102 + else {
  103 + throw new IllegalStateException(
  104 + "No function callback [" + functionName + "] fund in tht FunctionCallbackContext");
  105 + }
  106 + }
  107 + else {
  108 + throw new IllegalStateException("No function callback found for name: " + functionName);
  109 + }
  110 + }
  111 + FunctionCallback functionCallback = this.functionCallbackRegister.get(functionName);
  112 +
  113 + retrievedFunctionCallbacks.add(functionCallback);
  114 + }
  115 +
  116 + return retrievedFunctionCallbacks;
  117 + }
  118 +
  119 + ///
  120 + protected Resp callWithFunctionSupport(Req request) {
  121 + Resp response = this.doChatCompletion(request);
  122 + return this.handleFunctionCallOrReturn(request, response);
  123 + }
  124 +
  125 + protected Resp handleFunctionCallOrReturn(Req request, Resp response) {
  126 +
  127 + if (!this.isToolFunctionCall(response)) {
  128 + return response;
  129 + }
  130 +
  131 + // The chat completion tool call requires the complete conversation
  132 + // history. Including the initial user message.
  133 + List<Msg> conversationHistory = new ArrayList<>();
  134 +
  135 + conversationHistory.addAll(this.doGetUserMessages(request));
  136 +
  137 + Msg responseMessage = this.doGetToolResponseMessage(response);
  138 +
  139 + // Add the assistant response to the message conversation history.
  140 + conversationHistory.add(responseMessage);
  141 +
  142 + Req newRequest = this.doCreateToolResponseRequest(request, responseMessage, conversationHistory);
  143 +
  144 + return this.callWithFunctionSupport(newRequest);
  145 + }
  146 +
  147 + abstract protected Req doCreateToolResponseRequest(Req previousRequest, Msg responseMessage,
  148 + List<Msg> conversationHistory);
  149 +
  150 + abstract protected List<Msg> doGetUserMessages(Req request);
  151 +
  152 + abstract protected Msg doGetToolResponseMessage(Resp response);
  153 +
  154 + abstract protected Resp doChatCompletion(Req request);
  155 +
  156 + abstract protected boolean isToolFunctionCall(Resp response);
  157 +
  158 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallback.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model.function;
  18 +
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import org.springframework.util.Assert;
  22 +
  23 +import java.util.function.Function;
  24 +
  25 +/**
  26 + * Abstract implementation of the {@link FunctionCallback} for interacting with the
  27 + * Model's function calling protocol and a {@link Function} wrapping the interaction with
  28 + * the 3rd party service/function.
  29 + *
  30 + * Implement the {@code O apply(I request) } method to implement the interaction with the
  31 + * 3rd party service/function.
  32 + *
  33 + * The {@link #responseConverter} function is responsible to convert the 3rd party
  34 + * function's output type into a string expected by the LLM model.
  35 + *
  36 + * @param <I> the 3rd party service input type.
  37 + * @param <O> the 3rd party service output type.
  38 + * @author Christian Tzolov
  39 + */
  40 +abstract class AbstractFunctionCallback<I, O> implements Function<I, O>, FunctionCallback {
  41 +
  42 + private final String name;
  43 +
  44 + private final String description;
  45 +
  46 + private final Class<I> inputType;
  47 +
  48 + private final String inputTypeSchema;
  49 +
  50 + private final ObjectMapper objectMapper;
  51 +
  52 + private final Function<O, String> responseConverter;
  53 +
  54 + /**
  55 + * Constructs a new {@link AbstractFunctionCallback} with the given name, description,
  56 + * input type and default object mapper.
  57 + * @param name Function name. Should be unique within the ChatClient's function
  58 + * registry.
  59 + * @param description Function description. Used as a "system prompt" by the model to
  60 + * decide if the function should be called.
  61 + * @param inputTypeSchema Used to compute, the argument's Schema (such as JSON Schema
  62 + * or OpenAPI Schema)required by the Model's function calling protocol.
  63 + * @param inputType Used to compute, the argument's JSON schema required by the
  64 + * Model's function calling protocol.
  65 + * @param responseConverter Used to convert the function's output type to a string.
  66 + * @param objectMapper Used to convert the function's input and output types to and
  67 + * from JSON.
  68 + */
  69 + protected AbstractFunctionCallback(String name, String description, String inputTypeSchema, Class<I> inputType,
  70 + Function<O, String> responseConverter, ObjectMapper objectMapper) {
  71 + Assert.notNull(name, "Name must not be null");
  72 + Assert.notNull(description, "Description must not be null");
  73 + Assert.notNull(inputType, "InputType must not be null");
  74 + Assert.notNull(inputTypeSchema, "InputTypeSchema must not be null");
  75 + Assert.notNull(responseConverter, "ResponseConverter must not be null");
  76 + Assert.notNull(objectMapper, "ObjectMapper must not be null");
  77 + this.name = name;
  78 + this.description = description;
  79 + this.inputType = inputType;
  80 + this.inputTypeSchema = inputTypeSchema;
  81 + this.responseConverter = responseConverter;
  82 + this.objectMapper = objectMapper;
  83 + }
  84 +
  85 + @Override
  86 + public String getName() {
  87 + return this.name;
  88 + }
  89 +
  90 + @Override
  91 + public String getDescription() {
  92 + return this.description;
  93 + }
  94 +
  95 + @Override
  96 + public String getInputTypeSchema() {
  97 + return this.inputTypeSchema;
  98 + }
  99 +
  100 + @Override
  101 + public String call(String functionArguments) {
  102 +
  103 + // Convert the tool calls JSON arguments into a Java function request object.
  104 + I request = fromJson(functionArguments, inputType);
  105 +
  106 + // extend conversation with function response.
  107 + return this.andThen(this.responseConverter).apply(request);
  108 + }
  109 +
  110 + private <T> T fromJson(String json, Class<T> targetClass) {
  111 + try {
  112 + return this.objectMapper.readValue(json, targetClass);
  113 + }
  114 + catch (JsonProcessingException e) {
  115 + throw new RuntimeException(e);
  116 + }
  117 + }
  118 +
  119 + @Override
  120 + public int hashCode() {
  121 + final int prime = 31;
  122 + int result = 1;
  123 + result = prime * result + ((name == null) ? 0 : name.hashCode());
  124 + result = prime * result + ((description == null) ? 0 : description.hashCode());
  125 + result = prime * result + ((inputType == null) ? 0 : inputType.hashCode());
  126 + return result;
  127 + }
  128 +
  129 + @Override
  130 + public boolean equals(Object obj) {
  131 + if (this == obj)
  132 + return true;
  133 + if (obj == null)
  134 + return false;
  135 + if (getClass() != obj.getClass())
  136 + return false;
  137 + AbstractFunctionCallback other = (AbstractFunctionCallback) obj;
  138 + if (name == null) {
  139 + if (other.name != null)
  140 + return false;
  141 + }
  142 + else if (!name.equals(other.name))
  143 + return false;
  144 + if (description == null) {
  145 + if (other.description != null)
  146 + return false;
  147 + }
  148 + else if (!description.equals(other.description))
  149 + return false;
  150 + if (inputType == null) {
  151 + if (other.inputType != null)
  152 + return false;
  153 + }
  154 + else if (!inputType.equals(other.inputType))
  155 + return false;
  156 + return true;
  157 + }
  158 +
  159 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallback.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model.function;
  18 +
  19 +/**
  20 + * Represents a model function call handler. Implementations are registered with the
  21 + * Models and called on prompts that trigger the function call.
  22 + *
  23 + * @author Christian Tzolov
  24 + */
  25 +public interface FunctionCallback {
  26 +
  27 + /**
  28 + * @return Returns the Function name. Unique within the model.
  29 + */
  30 + public String getName();
  31 +
  32 + /**
  33 + * @return Returns the function description. This description is used by the model do
  34 + * decide if the function should be called or not.
  35 + */
  36 + public String getDescription();
  37 +
  38 + /**
  39 + * @return Returns the JSON schema of the function input type.
  40 + */
  41 + public String getInputTypeSchema();
  42 +
  43 + /**
  44 + * Called when a model detects and triggers a function call. The model is responsible
  45 + * to pass the function arguments in the pre-configured JSON schema format.
  46 + * @param functionInput JSON string with the function arguments to be passed to the
  47 + * function. The arguments are defined as JSON schema usually registered with the the
  48 + * model.
  49 + * @return String containing the function call response.
  50 + */
  51 + public String call(String functionInput);
  52 +
  53 +}
0 54 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackContext.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package cn.iocoder.yudao.framework.ai.model.function;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonClassDescription;
  19 +import org.springframework.beans.BeansException;
  20 +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
  21 +import org.springframework.cloud.function.context.config.FunctionContextUtils;
  22 +import org.springframework.context.ApplicationContext;
  23 +import org.springframework.context.ApplicationContextAware;
  24 +import org.springframework.context.annotation.Description;
  25 +import org.springframework.context.support.GenericApplicationContext;
  26 +import org.springframework.lang.NonNull;
  27 +import org.springframework.lang.Nullable;
  28 +import org.springframework.util.StringUtils;
  29 +
  30 +import java.lang.reflect.Type;
  31 +import java.util.function.Function;
  32 +
  33 +/**
  34 + * A Spring {@link ApplicationContextAware} implementation that provides a way to retrieve
  35 + * a {@link Function} from the Spring context and wrap it into a {@link FunctionCallback}.
  36 + *
  37 + * The name of the function is determined by the bean name.
  38 + *
  39 + * The description of the function is determined by the following rules:
  40 + * <ul>
  41 + * <li>Provided as a default description</li>
  42 + * <li>Provided as a {@code @Description} annotation on the bean</li>
  43 + * <li>Provided as a {@code @JsonClassDescription} annotation on the input class</li>
  44 + * </ul>
  45 + *
  46 + * @author Christian Tzolov
  47 + * @author Christopher Smith
  48 + */
  49 +public class FunctionCallbackContext implements ApplicationContextAware {
  50 +
  51 + private GenericApplicationContext applicationContext;
  52 +
  53 + private FunctionCallbackWrapper.Builder.SchemaType schemaType = FunctionCallbackWrapper.Builder.SchemaType.JSON_SCHEMA;
  54 +
  55 + public void setSchemaType(FunctionCallbackWrapper.Builder.SchemaType schemaType) {
  56 + this.schemaType = schemaType;
  57 + }
  58 +
  59 + @Override
  60 + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
  61 + this.applicationContext = (GenericApplicationContext) applicationContext;
  62 + }
  63 +
  64 + @SuppressWarnings({ "rawtypes", "unchecked" })
  65 + public FunctionCallback getFunctionCallback(@NonNull String beanName, @Nullable String defaultDescription) {
  66 +
  67 + Type beanType = FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), beanName);
  68 +
  69 + if (beanType == null) {
  70 + throw new IllegalArgumentException(
  71 + "Functional bean with name: " + beanName + " does not exist in the context.");
  72 + }
  73 +
  74 + if (!Function.class.isAssignableFrom(FunctionTypeUtils.getRawType(beanType))) {
  75 + throw new IllegalArgumentException(
  76 + "Function call Bean must be of type Function. Found: " + beanType.getTypeName());
  77 + }
  78 +
  79 + Type functionInputType = TypeResolverHelper.getFunctionArgumentType(beanType, 0);
  80 +
  81 + Class<?> functionInputClass = FunctionTypeUtils.getRawType(functionInputType);
  82 + String functionName = beanName;
  83 + String functionDescription = defaultDescription;
  84 +
  85 + if (!StringUtils.hasText(functionDescription)) {
  86 + // Look for a Description annotation on the bean
  87 + Description descriptionAnnotation = applicationContext.findAnnotationOnBean(beanName, Description.class);
  88 +
  89 + if (descriptionAnnotation != null) {
  90 + functionDescription = descriptionAnnotation.value();
  91 + }
  92 +
  93 + if (!StringUtils.hasText(functionDescription)) {
  94 + // Look for a JsonClassDescription annotation on the input class
  95 + JsonClassDescription jsonClassDescriptionAnnotation = functionInputClass
  96 + .getAnnotation(JsonClassDescription.class);
  97 + if (jsonClassDescriptionAnnotation != null) {
  98 + functionDescription = jsonClassDescriptionAnnotation.value();
  99 + }
  100 + }
  101 +
  102 + if (!StringUtils.hasText(functionDescription)) {
  103 + throw new IllegalStateException("Could not determine function description."
  104 + + "Please provide a description either as a default parameter, via @Description annotation on the bean "
  105 + + "or @JsonClassDescription annotation on the input class.");
  106 + }
  107 + }
  108 +
  109 + Object bean = this.applicationContext.getBean(beanName);
  110 +
  111 + if (bean instanceof Function<?, ?> function) {
  112 + return FunctionCallbackWrapper.builder(function)
  113 + .withName(functionName)
  114 + .withSchemaType(this.schemaType)
  115 + .withDescription(functionDescription)
  116 + .withInputType(functionInputClass)
  117 + .build();
  118 + }
  119 + else {
  120 + throw new IllegalArgumentException("Bean must be of type Function");
  121 + }
  122 + }
  123 +
  124 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackWrapper.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.model.function;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.model.ModelOptionsUtils;
  4 +import com.fasterxml.jackson.databind.DeserializationFeature;
  5 +import com.fasterxml.jackson.databind.ObjectMapper;
  6 +import org.springframework.util.Assert;
  7 +
  8 +import java.util.function.Function;
  9 +
  10 +/**
  11 + * Note that the underlying function is responsible for converting the output into format
  12 + * that can be consumed by the Model. The default implementation converts the output into
  13 + * String before sending it to the Model. Provide a custom function responseConverter
  14 + * implementation to override this.
  15 + *
  16 + */
  17 +public class FunctionCallbackWrapper<I, O> extends AbstractFunctionCallback<I, O> {
  18 +
  19 + private final Function<I, O> function;
  20 +
  21 + private FunctionCallbackWrapper(String name, String description, String inputTypeSchema, Class<I> inputType,
  22 + Function<O, String> responseConverter, Function<I, O> function) {
  23 + super(name, description, inputTypeSchema, inputType, responseConverter,
  24 + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false));
  25 + Assert.notNull(function, "Function must not be null");
  26 + this.function = function;
  27 + }
  28 +
  29 + @SuppressWarnings("unchecked")
  30 + private static <I, O> Class<I> resolveInputType(Function<I, O> function) {
  31 + return (Class<I>) TypeResolverHelper.getFunctionInputClass((Class<Function<I, O>>) function.getClass());
  32 + }
  33 +
  34 + @Override
  35 + public O apply(I input) {
  36 + return this.function.apply(input);
  37 + }
  38 +
  39 + public static <I, O> Builder<I, O> builder(Function<I, O> function) {
  40 + return new Builder<>(function);
  41 + }
  42 +
  43 + public static class Builder<I, O> {
  44 +
  45 + public enum SchemaType {
  46 +
  47 + JSON_SCHEMA, OPEN_API_SCHEMA
  48 +
  49 + }
  50 +
  51 + private String name;
  52 +
  53 + private String description;
  54 +
  55 + private Class<I> inputType;
  56 +
  57 + private final Function<I, O> function;
  58 +
  59 + private SchemaType schemaType = SchemaType.JSON_SCHEMA;
  60 +
  61 + public Builder(Function<I, O> function) {
  62 + Assert.notNull(function, "Function must not be null");
  63 + this.function = function;
  64 + }
  65 +
  66 + // By default the response is converted to a JSON string.
  67 + private Function<O, String> responseConverter = (response) -> ModelOptionsUtils.toJsonString(response);
  68 +
  69 + private String inputTypeSchema;
  70 +
  71 + private ObjectMapper objectMapper = new ObjectMapper()
  72 + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  73 +
  74 + public Builder<I, O> withName(String name) {
  75 + Assert.hasText(name, "Name must not be empty");
  76 + this.name = name;
  77 + return this;
  78 + }
  79 +
  80 + public Builder<I, O> withDescription(String description) {
  81 + Assert.hasText(description, "Description must not be empty");
  82 + this.description = description;
  83 + return this;
  84 + }
  85 +
  86 + @SuppressWarnings("unchecked")
  87 + public Builder<I, O> withInputType(Class<?> inputType) {
  88 + this.inputType = (Class<I>) inputType;
  89 + return this;
  90 + }
  91 +
  92 + public Builder<I, O> withResponseConverter(Function<O, String> responseConverter) {
  93 + Assert.notNull(responseConverter, "ResponseConverter must not be null");
  94 + this.responseConverter = responseConverter;
  95 + return this;
  96 + }
  97 +
  98 + public Builder<I, O> withInputTypeSchema(String inputTypeSchema) {
  99 + Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty");
  100 + this.inputTypeSchema = inputTypeSchema;
  101 + return this;
  102 + }
  103 +
  104 + public Builder<I, O> withObjectMapper(ObjectMapper objectMapper) {
  105 + Assert.notNull(objectMapper, "ObjectMapper must not be null");
  106 + this.objectMapper = objectMapper;
  107 + return this;
  108 + }
  109 +
  110 + public Builder<I, O> withSchemaType(SchemaType schemaType) {
  111 + Assert.notNull(schemaType, "SchemaType must not be null");
  112 + this.schemaType = schemaType;
  113 + return this;
  114 + }
  115 +
  116 + public FunctionCallbackWrapper<I, O> build() {
  117 +
  118 + Assert.hasText(this.name, "Name must not be empty");
  119 + Assert.hasText(this.description, "Description must not be empty");
  120 + // Assert.notNull(this.inputType, "InputType must not be null");
  121 + Assert.notNull(this.function, "Function must not be null");
  122 + Assert.notNull(this.responseConverter, "ResponseConverter must not be null");
  123 + Assert.notNull(this.objectMapper, "ObjectMapper must not be null");
  124 +
  125 + if (this.inputType == null) {
  126 + this.inputType = resolveInputType(this.function);
  127 + }
  128 +
  129 + if (this.inputTypeSchema == null) {
  130 + boolean upperCaseTypeValues = this.schemaType == SchemaType.OPEN_API_SCHEMA;
  131 + this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues);
  132 + }
  133 +
  134 + return new FunctionCallbackWrapper<>(this.name, this.description, this.inputTypeSchema, this.inputType,
  135 + this.responseConverter, this.function);
  136 + }
  137 +
  138 + }
  139 +
  140 +}
0 141 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptions.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model.function;
  18 +
  19 +import java.util.List;
  20 +import java.util.Set;
  21 +
  22 +/**
  23 + * @author Christian Tzolov
  24 + */
  25 +public interface FunctionCallingOptions {
  26 +
  27 + /**
  28 + * Function Callbacks to be registered with the ChatClient. For Prompt Options the
  29 + * functionCallbacks are automatically enabled for the duration of the prompt
  30 + * execution. For Default Options the FunctionCallbacks are registered but disabled by
  31 + * default. You have to use "functions" property to list the function names from the
  32 + * ChatClient registry to be used in the chat completion requests.
  33 + * @return Return the Function Callbacks to be registered with the ChatClient.
  34 + */
  35 + List<FunctionCallback> getFunctionCallbacks();
  36 +
  37 + /**
  38 + * Set the Function Callbacks to be registered with the ChatClient.
  39 + * @param functionCallbacks the Function Callbacks to be registered with the
  40 + * ChatClient.
  41 + */
  42 + void setFunctionCallbacks(List<FunctionCallback> functionCallbacks);
  43 +
  44 + /**
  45 + * @return List of function names from the ChatClient registry to be used in the next
  46 + * chat completion requests.
  47 + */
  48 + Set<String> getFunctions();
  49 +
  50 + /**
  51 + * Set the list of function names from the ChatClient registry to be used in the next
  52 + * chat completion requests.
  53 + * @param functions the list of function names from the ChatClient registry to be used
  54 + * in the next chat completion requests.
  55 + */
  56 + void setFunctions(Set<String> functions);
  57 +
  58 + /**
  59 + * @return Returns FunctionCallingOptionsBuilder to create a new instance of
  60 + * FunctionCallingOptions.
  61 + */
  62 + public static FunctionCallingOptionsBuilder builder() {
  63 + return new FunctionCallingOptionsBuilder();
  64 + }
  65 +
  66 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptionsBuilder.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model.function;
  18 +
  19 +import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions;
  20 +import org.springframework.util.Assert;
  21 +
  22 +import java.util.ArrayList;
  23 +import java.util.HashSet;
  24 +import java.util.List;
  25 +import java.util.Set;
  26 +
  27 +/**
  28 + * Builder for {@link FunctionCallingOptions}. Using the {@link FunctionCallingOptions}
  29 + * permits options portability between different AI providers that support
  30 + * function-calling.
  31 + *
  32 + * @author Christian Tzolov
  33 + * @since 0.8.1
  34 + */
  35 +public class FunctionCallingOptionsBuilder {
  36 +
  37 + private final PortableFunctionCallingOptions options;
  38 +
  39 + public FunctionCallingOptionsBuilder() {
  40 + this.options = new PortableFunctionCallingOptions();
  41 + }
  42 +
  43 + public FunctionCallingOptionsBuilder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
  44 + this.options.setFunctionCallbacks(functionCallbacks);
  45 + return this;
  46 + }
  47 +
  48 + public FunctionCallingOptionsBuilder withFunctionCallback(FunctionCallback functionCallback) {
  49 + Assert.notNull(functionCallback, "FunctionCallback must not be null");
  50 + this.options.getFunctionCallbacks().add(functionCallback);
  51 + return this;
  52 + }
  53 +
  54 + public FunctionCallingOptionsBuilder withFunctions(Set<String> functions) {
  55 + this.options.setFunctions(functions);
  56 + return this;
  57 + }
  58 +
  59 + public FunctionCallingOptionsBuilder withFunction(String function) {
  60 + Assert.notNull(function, "Function must not be null");
  61 + this.options.getFunctions().add(function);
  62 + return this;
  63 + }
  64 +
  65 + public FunctionCallingOptionsBuilder withTemperature(Float temperature) {
  66 + this.options.setTemperature(temperature);
  67 + return this;
  68 + }
  69 +
  70 + public FunctionCallingOptionsBuilder withTopP(Float topP) {
  71 + this.options.setTopP(topP);
  72 + return this;
  73 + }
  74 +
  75 + public FunctionCallingOptionsBuilder withTopK(Integer topK) {
  76 + this.options.setTopK(topK);
  77 + return this;
  78 + }
  79 +
  80 + public PortableFunctionCallingOptions build() {
  81 + return this.options;
  82 + }
  83 +
  84 + public static class PortableFunctionCallingOptions implements FunctionCallingOptions, ChatOptions {
  85 +
  86 + private List<FunctionCallback> functionCallbacks = new ArrayList<>();
  87 +
  88 + private Set<String> functions = new HashSet<>();
  89 +
  90 + private Float temperature;
  91 +
  92 + private Float topP;
  93 +
  94 + private Integer topK;
  95 +
  96 + @Override
  97 + public List<FunctionCallback> getFunctionCallbacks() {
  98 + return this.functionCallbacks;
  99 + }
  100 +
  101 + @Override
  102 + public void setFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
  103 + Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null");
  104 + this.functionCallbacks = functionCallbacks;
  105 + }
  106 +
  107 + @Override
  108 + public Set<String> getFunctions() {
  109 + return this.functions;
  110 + }
  111 +
  112 + @Override
  113 + public void setFunctions(Set<String> functions) {
  114 + Assert.notNull(functions, "Functions must not be null");
  115 + this.functions = functions;
  116 + }
  117 +
  118 + @Override
  119 + public Float getTemperature() {
  120 + return this.temperature;
  121 + }
  122 +
  123 + @Override
  124 + public void setTemperature(Float temperature) {
  125 + this.temperature = temperature;
  126 + }
  127 +
  128 + @Override
  129 + public Float getTopP() {
  130 + return this.topP;
  131 + }
  132 +
  133 + @Override
  134 + public void setTopP(Float topP) {
  135 + this.topP = topP;
  136 + }
  137 +
  138 + @Override
  139 + public Integer getTopK() {
  140 + return this.topK;
  141 + }
  142 +
  143 + @Override
  144 + public void setTopK(Integer topK) {
  145 + this.topK = topK;
  146 + }
  147 +
  148 + }
  149 +
  150 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/TypeResolverHelper.java 0 → 100644
  1 +/*
  2 + * Copyright 2024-2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.model.function;
  18 +
  19 +import net.jodah.typetools.TypeResolver;
  20 +
  21 +import java.lang.reflect.GenericArrayType;
  22 +import java.lang.reflect.ParameterizedType;
  23 +import java.lang.reflect.Type;
  24 +import java.util.function.Function;
  25 +
  26 +/**
  27 + * @author Christian Tzolov
  28 + */
  29 +public class TypeResolverHelper {
  30 +
  31 + public static Class<?> getFunctionInputClass(Class<? extends Function<?, ?>> functionClass) {
  32 + return getFunctionArgumentClass(functionClass, 0);
  33 + }
  34 +
  35 + public static Class<?> getFunctionOutputClass(Class<? extends Function<?, ?>> functionClass) {
  36 + return getFunctionArgumentClass(functionClass, 1);
  37 + }
  38 +
  39 + public static Class<?> getFunctionArgumentClass(Class<? extends Function<?, ?>> functionClass, int argumentIndex) {
  40 + Type type = TypeResolver.reify(Function.class, functionClass);
  41 +
  42 + var argumentType = type instanceof ParameterizedType
  43 + ? ((ParameterizedType) type).getActualTypeArguments()[argumentIndex] : Object.class;
  44 +
  45 + return toRawClass(argumentType);
  46 + }
  47 +
  48 + public static Type getFunctionInputType(Class<? extends Function<?, ?>> functionClass) {
  49 + return getFunctionArgumentType(functionClass, 0);
  50 + }
  51 +
  52 + public static Type getFunctionOutputType(Class<? extends Function<?, ?>> functionClass) {
  53 + return getFunctionArgumentType(functionClass, 1);
  54 + }
  55 +
  56 + public static Type getFunctionArgumentType(Class<? extends Function<?, ?>> functionClass, int argumentIndex) {
  57 + Type functionType = TypeResolver.reify(Function.class, functionClass);
  58 + return getFunctionArgumentType(functionType, argumentIndex);
  59 + }
  60 +
  61 + public static Type getFunctionArgumentType(Type functionType, int argumentIndex) {
  62 + var argumentType = functionType instanceof ParameterizedType
  63 + ? ((ParameterizedType) functionType).getActualTypeArguments()[argumentIndex] : Object.class;
  64 +
  65 + return argumentType;
  66 + }
  67 +
  68 + /**
  69 + * Effectively converts {@link Type} which could be {@link ParameterizedType} to raw
  70 + * Class (no generics).
  71 + * @param type actual {@link Type} instance
  72 + * @return instance of {@link Class} as raw representation of the provided
  73 + * {@link Type}
  74 + */
  75 + public static Class<?> toRawClass(Type type) {
  76 + return type != null
  77 + ? TypeResolver.resolveRawClass(type instanceof GenericArrayType ? type : TypeResolver.reify(type), null)
  78 + : null;
  79 + }
  80 +
  81 + // public static void main(String[] args) {
  82 + // Class<? extends Function<?, ?>> clazz = MockWeatherService.class;
  83 + // System.out.println(getFunctionInputType(clazz));
  84 +
  85 + // }
  86 +
  87 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/package-info.java 0 → 100644
  1 +/**
  2 + * Provides a set of interfaces and classes for a generic API designed to interact with
  3 + * various AI models. This package includes interfaces for handling AI model calls,
  4 + * requests, responses, results, and associated metadata. It is designed to offer a
  5 + * flexible and adaptable framework for interacting with different types of AI models,
  6 + * abstracting the complexities involved in model invocation and result processing. The
  7 + * use of generics enhances the API's capability to work with a wide range of models,
  8 + * ensuring a broad applicability across diverse AI scenarios.
  9 + *
  10 + */
  11 +package cn.iocoder.yudao.framework.ai.model;
0 12 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/package-info.java 0 → 100644
  1 +/**
  2 + * author: fansili
  3 + * time: 2024/3/12 20:29
  4 + */
  5 +package cn.iocoder.yudao.framework.ai;
0 6 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractConversionServiceOutputParser.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.parser;
  18 +
  19 +import org.springframework.core.convert.support.DefaultConversionService;
  20 +
  21 +/**
  22 + * Abstract {@link OutputParser} implementation that uses a pre-configured
  23 + * {@link DefaultConversionService} to convert the LLM output into the desired type
  24 + * format.
  25 + *
  26 + * @param <T> Specifies the desired response type.
  27 + * @author Mark Pollack
  28 + * @author Christian Tzolov
  29 + */
  30 +public abstract class AbstractConversionServiceOutputParser<T> implements OutputParser<T> {
  31 +
  32 + private final DefaultConversionService conversionService;
  33 +
  34 + public AbstractConversionServiceOutputParser(DefaultConversionService conversionService) {
  35 + this.conversionService = conversionService;
  36 + }
  37 +
  38 + public DefaultConversionService getConversionService() {
  39 + return conversionService;
  40 + }
  41 +
  42 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractMessageConverterOutputParser.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.parser;
  18 +
  19 +import org.springframework.messaging.converter.MessageConverter;
  20 +
  21 +/**
  22 + * Abstract {@link OutputParser} implementation that uses a pre-configured
  23 + * {@link MessageConverter} to convert the LLM output into the desired type format.
  24 + *
  25 + * @param <T> Specifies the desired response type.
  26 + * @author Mark Pollack
  27 + * @author Christian Tzolov
  28 + */
  29 +public abstract class AbstractMessageConverterOutputParser<T> implements OutputParser<T> {
  30 +
  31 + private MessageConverter messageConverter;
  32 +
  33 + public AbstractMessageConverterOutputParser(MessageConverter messageConverter) {
  34 + this.messageConverter = messageConverter;
  35 + }
  36 +
  37 + public MessageConverter getMessageConverter() {
  38 + return this.messageConverter;
  39 + }
  40 +
  41 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/BeanOutputParser.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.parser;
  18 +
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.core.util.DefaultIndenter;
  21 +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
  22 +import com.fasterxml.jackson.databind.DeserializationFeature;
  23 +import com.fasterxml.jackson.databind.JsonNode;
  24 +import com.fasterxml.jackson.databind.ObjectMapper;
  25 +import com.fasterxml.jackson.databind.ObjectWriter;
  26 +import com.github.victools.jsonschema.generator.SchemaGenerator;
  27 +import com.github.victools.jsonschema.generator.SchemaGeneratorConfig;
  28 +import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
  29 +import com.github.victools.jsonschema.module.jackson.JacksonModule;
  30 +
  31 +import java.util.Map;
  32 +import java.util.Objects;
  33 +
  34 +import static com.github.victools.jsonschema.generator.OptionPreset.PLAIN_JSON;
  35 +import static com.github.victools.jsonschema.generator.SchemaVersion.DRAFT_2020_12;
  36 +
  37 +/**
  38 + * An implementation of {@link OutputParser} that transforms the LLM output to a specific
  39 + * object type using JSON schema. This parser works by generating a JSON schema based on a
  40 + * given Java class, which is then used to validate and transform the LLM output into the
  41 + * desired type.
  42 + *
  43 + * @param <T> The target type to which the output will be converted.
  44 + * @author Mark Pollack
  45 + * @author Christian Tzolov
  46 + * @author Sebastian Ullrich
  47 + * @author Kirk Lund
  48 + */
  49 +public class BeanOutputParser<T> implements OutputParser<T> {
  50 +
  51 + /** Holds the generated JSON schema for the target type. */
  52 + private String jsonSchema;
  53 +
  54 + /** The Java class representing the target type. */
  55 + @SuppressWarnings({ "FieldMayBeFinal", "rawtypes" })
  56 + private Class<T> clazz;
  57 +
  58 + /** The object mapper used for deserialization and other JSON operations. */
  59 + @SuppressWarnings("FieldMayBeFinal")
  60 + private ObjectMapper objectMapper;
  61 +
  62 + /**
  63 + * Constructor to initialize with the target type's class.
  64 + * @param clazz The target type's class.
  65 + */
  66 + public BeanOutputParser(Class<T> clazz) {
  67 + this(clazz, null);
  68 + }
  69 +
  70 + /**
  71 + * Constructor to initialize with the target type's class, a custom object mapper, and
  72 + * a line endings normalizer to ensure consistent line endings on any platform.
  73 + * @param clazz The target type's class.
  74 + * @param objectMapper Custom object mapper for JSON operations. endings.
  75 + */
  76 + public BeanOutputParser(Class<T> clazz, ObjectMapper objectMapper) {
  77 + Objects.requireNonNull(clazz, "Java Class cannot be null;");
  78 + this.clazz = clazz;
  79 + this.objectMapper = objectMapper != null ? objectMapper : getObjectMapper();
  80 + generateSchema();
  81 + }
  82 +
  83 + /**
  84 + * Generates the JSON schema for the target type.
  85 + */
  86 + private void generateSchema() {
  87 + JacksonModule jacksonModule = new JacksonModule();
  88 + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(DRAFT_2020_12, PLAIN_JSON)
  89 + .with(jacksonModule);
  90 + SchemaGeneratorConfig config = configBuilder.build();
  91 + SchemaGenerator generator = new SchemaGenerator(config);
  92 + JsonNode jsonNode = generator.generateSchema(this.clazz);
  93 + ObjectWriter objectWriter = new ObjectMapper()
  94 + .writer(new DefaultPrettyPrinter().withObjectIndenter(new DefaultIndenter().withLinefeed("\n")));
  95 + try {
  96 + this.jsonSchema = objectWriter.writeValueAsString(jsonNode);
  97 + }
  98 + catch (JsonProcessingException e) {
  99 + throw new RuntimeException("Could not pretty print json schema for " + this.clazz, e);
  100 + }
  101 + }
  102 +
  103 + @Override
  104 + /**
  105 + * Parses the given text to transform it to the desired target type.
  106 + * @param text The LLM output in string format.
  107 + * @return The parsed output in the desired target type.
  108 + */
  109 + public T parse(String text) {
  110 + try {
  111 + // If the response is a JSON Schema, extract the properties and use them as
  112 + // the response.
  113 + text = this.jsonSchemaToInstance(text);
  114 + return (T) this.objectMapper.readValue(text, this.clazz);
  115 + }
  116 + catch (JsonProcessingException e) {
  117 + throw new RuntimeException(e);
  118 + }
  119 + }
  120 +
  121 + /**
  122 + * Converts a JSON Schema to an instance based on a given text.
  123 + * @param text The JSON Schema in string format.
  124 + * @return The JSON instance generated from the JSON Schema, or the original text if
  125 + * the input is not a JSON Schema.
  126 + */
  127 + private String jsonSchemaToInstance(String text) {
  128 + try {
  129 + Map<String, Object> map = this.objectMapper.readValue(text, Map.class);
  130 + if (map.containsKey("$schema")) {
  131 + return this.objectMapper.writeValueAsString(map.get("properties"));
  132 + }
  133 + }
  134 + catch (Exception e) {
  135 + }
  136 + return text;
  137 + }
  138 +
  139 + /**
  140 + * Configures and returns an object mapper for JSON operations.
  141 + * @return Configured object mapper.
  142 + */
  143 + protected ObjectMapper getObjectMapper() {
  144 + ObjectMapper mapper = new ObjectMapper();
  145 + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  146 + return mapper;
  147 + }
  148 +
  149 + /**
  150 + * Provides the expected format of the response, instructing that it should adhere to
  151 + * the generated JSON schema.
  152 + * @return The instruction format string.
  153 + */
  154 + @Override
  155 + public String getFormat() {
  156 + String template = """
  157 + Your response should be in JSON format.
  158 + Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
  159 + Do not include markdown code blocks in your response.
  160 + Here is the JSON Schema instance your output must adhere to:
  161 + ```%s```
  162 + """;
  163 + return String.format(template, this.jsonSchema);
  164 + }
  165 +
  166 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/FormatProvider.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.parser;
  18 +
  19 +/**
  20 + * Implementations of this interface provides instructions for how the output of a
  21 + * language generative should be formatted.
  22 + *
  23 + * @author Mark Pollack
  24 + */
  25 +public interface FormatProvider {
  26 +
  27 + /**
  28 + * @return Returns a string containing instructions for how the output of a language
  29 + * generative should be formatted.
  30 + */
  31 + String getFormat();
  32 +
  33 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/ListOutputParser.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package cn.iocoder.yudao.framework.ai.parser;
  17 +
  18 +import org.springframework.core.convert.support.DefaultConversionService;
  19 +
  20 +import java.util.List;
  21 +
  22 +/**
  23 + * {@link OutputParser} implementation that uses a {@link DefaultConversionService} to
  24 + * convert the LLM output into a {@link List} instance.
  25 + *
  26 + * @author Mark Pollack
  27 + * @author Christian Tzolov
  28 + */
  29 +public class ListOutputParser extends AbstractConversionServiceOutputParser<List<String>> {
  30 +
  31 + public ListOutputParser(DefaultConversionService defaultConversionService) {
  32 + super(defaultConversionService);
  33 + }
  34 +
  35 + @Override
  36 + public String getFormat() {
  37 + return """
  38 + Your response should be a list of comma separated values
  39 + eg: `foo, bar, baz`
  40 + """;
  41 + }
  42 +
  43 + @Override
  44 + public List<String> parse(String text) {
  45 + return getConversionService().convert(text, List.class);
  46 + }
  47 +
  48 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/MapOutputParser.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.parser;
  18 +
  19 +import org.springframework.messaging.Message;
  20 +import org.springframework.messaging.converter.MappingJackson2MessageConverter;
  21 +import org.springframework.messaging.support.MessageBuilder;
  22 +
  23 +import java.nio.charset.StandardCharsets;
  24 +import java.util.HashMap;
  25 +import java.util.Map;
  26 +
  27 +/**
  28 + * {@link OutputParser} implementation that uses a pre-configured
  29 + * {@link MappingJackson2MessageConverter} to convert the LLM output into a
  30 + * java.util.Map&lt;String, Object&gt; instance.
  31 + *
  32 + * @author Mark Pollack
  33 + * @author Christian Tzolov
  34 + */
  35 +public class MapOutputParser extends AbstractMessageConverterOutputParser<Map<String, Object>> {
  36 +
  37 + public MapOutputParser() {
  38 + super(new MappingJackson2MessageConverter());
  39 + }
  40 +
  41 + @Override
  42 + public Map<String, Object> parse(String text) {
  43 + Message<?> message = MessageBuilder.withPayload(text.getBytes(StandardCharsets.UTF_8)).build();
  44 + return (Map) getMessageConverter().fromMessage(message, HashMap.class);
  45 + }
  46 +
  47 + @Override
  48 + public String getFormat() {
  49 + String raw = """
  50 + Your response should be in JSON format.
  51 + The data structure for the JSON should match this Java class: %s
  52 + Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
  53 + """;
  54 + return String.format(raw, "java.util.HashMap");
  55 + }
  56 +
  57 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/OutputParser.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.parser;
  18 +
  19 +/**
  20 + * Converts the (raw) LLM output into a structured responses of type. The
  21 + * {@link FormatProvider#getFormat()} method should provide the LLM prompt description of
  22 + * the desired format.
  23 + *
  24 + * @param <T> Specifies the desired response type.
  25 + * @author Mark Pollack
  26 + * @author Christian Tzolov
  27 + */
  28 +public interface OutputParser<T> extends Parser<T>, FormatProvider {
  29 +
  30 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/Parser.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package cn.iocoder.yudao.framework.ai.parser;
  18 +
  19 +@FunctionalInterface
  20 +public interface Parser<T> {
  21 +
  22 + T parse(String text);
  23 +
  24 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/README.md 0 → 100644
  1 +# Output Parsing
  2 +
  3 +* [Documentation](https://docs.spring.io/spring-ai/reference/concepts.html#_output_parsing)
  4 +* [Usage examples](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIT.java)
  5 +
  6 +The output of AI models traditionally arrives as a java.util.String, even if you ask for the reply to be in JSON. It may be the correct JSON, but it isn’t a JSON data structure. It is just a string. Also, asking "for JSON" as part of the prompt isn’t 100% accurate.
  7 +
  8 +This intricacy has led to the emergence of a specialized field involving the creation of prompts to yield the intended output, followed by parsing the resulting simple string into a usable data structure for application integration.
  9 +
  10 +Output parsing employs meticulously crafted prompts, often necessitating multiple interactions with the model to achieve the desired formatting.
  11 +
  12 +This challenge has prompted OpenAI to introduce 'OpenAI Functions' as a means to specify the desired output format from the model precisely.
... ...