Commit f6ea1bda7685870ae3d7672df4b24bb2185eabcf
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
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
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<String, Object> 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. | ... | ... |