【重构】AI:替换 spring-ai 实现

This commit is contained in:
YunaiV 2024-05-16 22:42:39 +08:00
parent fe63bda4c7
commit 7fca38ce1e
96 changed files with 99 additions and 5416 deletions

View File

@ -28,7 +28,7 @@ public class AiChatMessageSendRespVO {
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
private String content; private String content;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51") @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime; private LocalDateTime createTime;
} }

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.ai.dal.vo; package cn.iocoder.yudao.module.ai.dal.vo;
import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;

View File

@ -6,10 +6,8 @@ import cn.iocoder.yudao.framework.ai.core.exception.AiException;
import org.springframework.ai.image.ImageGeneration; import org.springframework.ai.image.ImageGeneration;
import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse; import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.models.openai.OpenAiImageClient; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum;
import org.springframework.ai.models.openai.OpenAiImageOptions; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum;
import org.springframework.ai.models.openai.enums.OpenAiImageModelEnum;
import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum;
import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi; import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi;
import org.springframework.ai.models.midjourney.api.req.ReRollReq; import org.springframework.ai.models.midjourney.api.req.ReRollReq;
import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter; import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter;
@ -29,6 +27,8 @@ import cn.iocoder.yudao.module.ai.service.AiImageService;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.openai.OpenAiImageClient;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -95,8 +95,8 @@ public class AiImageServiceImpl implements AiImageService {
try { try {
// 转换openai 参数 // 转换openai 参数
OpenAiImageOptions openAiImageOptions = new OpenAiImageOptions(); OpenAiImageOptions openAiImageOptions = new OpenAiImageOptions();
openAiImageOptions.setModel(openAiImageModelEnum); openAiImageOptions.setModel(openAiImageModelEnum.getModel());
openAiImageOptions.setStyle(openAiImageStyleEnum); openAiImageOptions.setStyle(openAiImageStyleEnum.getStyle());
openAiImageOptions.setSize(req.getSize()); openAiImageOptions.setSize(req.getSize());
ImageResponse imageResponse = openAiImageClient.call(new ImagePrompt(req.getPrompt(), openAiImageOptions)); ImageResponse imageResponse = openAiImageClient.call(new ImagePrompt(req.getPrompt(), openAiImageOptions));
// 发送 // 发送

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.midjourneyHandler;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import org.springframework.ai.models.midjourney.MidjourneyMessage; import org.springframework.ai.models.midjourney.MidjourneyMessage;
import org.springframework.ai.models.midjourney.constants.MidjourneyGennerateStatusEnum; import org.springframework.ai.models.midjourney.constants.MidjourneyGennerateStatusEnum;
import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler; import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler;
@ -11,7 +12,6 @@ import cn.iocoder.yudao.module.ai.convert.AiImageConvert;
import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
import cn.iocoder.yudao.module.ai.dal.mysql.AiImageMapper; import cn.iocoder.yudao.module.ai.dal.mysql.AiImageMapper;
import cn.iocoder.yudao.module.ai.enums.AiImageDrawingStatusEnum; import cn.iocoder.yudao.module.ai.enums.AiImageDrawingStatusEnum;
import com.alibaba.fastjson2.JSON;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -12,139 +12,48 @@
<artifactId>yudao-spring-boot-starter-ai</artifactId> <artifactId>yudao-spring-boot-starter-ai</artifactId>
<!-- TODO 芋艿:这里需要进一步减少 --> <!-- TODO 芋艿:这里需要进一步减少 -->
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies> <dependencies>
<!-- TODO fan这里包要进一步减少 -->
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>io.springboot.ai</groupId>
<artifactId>spring-core</artifactId> <artifactId>spring-ai-core</artifactId>
<version>1.0.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>io.springboot.ai</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>spring-ai-openai</artifactId>
<version>1.0.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>cn.iocoder.boot</groupId>
<artifactId>spring-context</artifactId> <artifactId>yudao-common</artifactId>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>typetools</artifactId>
<version>0.6.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-module-jackson</artifactId>
<version>4.31.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-module-swagger-2</artifactId>
<version>4.33.1</version>
</dependency>
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
<version>4.31.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
<version>4.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>stringtemplate</artifactId>
<version>4.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency> </dependency>
<!-- 阿里云 通义千问 --> <!-- 阿里云 通义千问 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId> <artifactId>dashscope-sdk-java</artifactId>
<version>2.11.0</version> <version>2.11.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>junit</groupId>
<artifactId>httpclient</artifactId> <artifactId>junit</artifactId>
<version>4.5.14</version> <scope>test</scope>
<scope>compile</scope>
</dependency> </dependency>
<!-- TODO fan这里包要进一步减少 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
<!-- JSON 处理库,如 Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.45</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp --> <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency> <dependency>
<groupId>com.squareup.okhttp3</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId> <artifactId>okhttp</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency> <dependency>
<groupId>net.dv8tion</groupId> <groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId> <artifactId>JDA</artifactId>
@ -156,10 +65,6 @@
<!-- </exclusion>--> <!-- </exclusion>-->
<!-- </exclusions>--> <!-- </exclusions>-->
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -11,9 +11,6 @@ import org.springframework.ai.models.xinghuo.api.XingHuoApi;
import org.springframework.ai.models.yiyan.YiYanChatClient; import org.springframework.ai.models.yiyan.YiYanChatClient;
import org.springframework.ai.models.yiyan.YiYanOptions; import org.springframework.ai.models.yiyan.YiYanOptions;
import org.springframework.ai.models.yiyan.api.YiYanApi; import org.springframework.ai.models.yiyan.api.YiYanApi;
import org.springframework.ai.models.openai.OpenAiImageApi;
import org.springframework.ai.models.openai.OpenAiImageClient;
import org.springframework.ai.models.openai.OpenAiImageOptions;
import org.springframework.ai.models.midjourney.MidjourneyConfig; import org.springframework.ai.models.midjourney.MidjourneyConfig;
import org.springframework.ai.models.midjourney.MidjourneyMessage; import org.springframework.ai.models.midjourney.MidjourneyMessage;
import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi; import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi;
@ -22,6 +19,10 @@ import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketSta
import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener; import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.ai.openai.OpenAiImageClient;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -72,10 +73,10 @@ public class YudaoAiAutoConfiguration {
YudaoAiProperties.QianWenProperties qianWenProperties = yudaoAiProperties.getQianwen(); YudaoAiProperties.QianWenProperties qianWenProperties = yudaoAiProperties.getQianwen();
// 转换配置 // 转换配置
QianWenOptions qianWenOptions = new QianWenOptions(); QianWenOptions qianWenOptions = new QianWenOptions();
qianWenOptions.setTopK(qianWenProperties.getTopK()); // qianWenOptions.setTopK(qianWenProperties.getTopK()); TODO 芋艿后续弄
qianWenOptions.setTopP(qianWenProperties.getTopP()); qianWenOptions.setTopP(qianWenProperties.getTopP());
qianWenOptions.setMaxTokens(qianWenProperties.getMaxTokens()); qianWenOptions.setMaxTokens(qianWenProperties.getMaxTokens());
qianWenOptions.setTemperature(qianWenProperties.getTemperature()); // qianWenOptions.setTemperature(qianWenProperties.getTemperature()); TODO 芋艿后续弄
return new QianWenChatClient( return new QianWenChatClient(
new QianWenApi( new QianWenApi(
qianWenProperties.getApiKey(), qianWenProperties.getApiKey(),
@ -91,7 +92,7 @@ public class YudaoAiAutoConfiguration {
YudaoAiProperties.YiYanProperties yiYanProperties = yudaoAiProperties.getYiyan(); YudaoAiProperties.YiYanProperties yiYanProperties = yudaoAiProperties.getYiyan();
// 转换配置 // 转换配置
YiYanOptions yiYanOptions = new YiYanOptions(); YiYanOptions yiYanOptions = new YiYanOptions();
yiYanOptions.setTopK(yiYanProperties.getTopK()); // yiYanOptions.setTopK(yiYanProperties.getTopK()); TODO 芋艿后续弄
yiYanOptions.setTopP(yiYanProperties.getTopP()); yiYanOptions.setTopP(yiYanProperties.getTopP());
yiYanOptions.setTemperature(yiYanProperties.getTemperature()); yiYanOptions.setTemperature(yiYanProperties.getTemperature());
yiYanOptions.setMaxOutputTokens(yiYanProperties.getMaxTokens()); yiYanOptions.setMaxOutputTokens(yiYanProperties.getMaxTokens());
@ -106,19 +107,19 @@ public class YudaoAiAutoConfiguration {
); );
} }
@Bean @Bean
@ConditionalOnProperty(value = "yudao.ai.openAiImage.enable", havingValue = "true") @ConditionalOnProperty(value = "yudao.ai.openAiImage.enable", havingValue = "true")
public OpenAiImageClient openAiImageClient(YudaoAiProperties yudaoAiProperties) { public OpenAiImageClient openAiImageClient(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.OpenAiImageProperties openAiImageProperties = yudaoAiProperties.getOpenAiImage(); YudaoAiProperties.OpenAiImageProperties openAiImageProperties = yudaoAiProperties.getOpenAiImage();
OpenAiImageOptions openAiImageOptions = new OpenAiImageOptions();
openAiImageOptions.setModel(openAiImageProperties.getModel().getModel());
openAiImageOptions.setStyle(openAiImageProperties.getStyle().getStyle());
openAiImageOptions.setResponseFormat("url"); // TODO 芋艿OpenAiImageOptions.ResponseFormatEnum.URL.getValue()
// 创建 client // 创建 client
return new OpenAiImageClient( return new OpenAiImageClient(
new OpenAiImageApi(openAiImageProperties.getApiKey()), new OpenAiImageApi(openAiImageProperties.getApiKey()),
new OpenAiImageOptions() openAiImageOptions,
.setModel(openAiImageProperties.getModel()) RetryUtils.DEFAULT_RETRY_TEMPLATE);
.setResponseFormat(OpenAiImageOptions.ResponseFormatEnum.URL.getValue())
.setStyle(openAiImageProperties.getStyle())
);
} }
@Bean @Bean
@ -157,7 +158,6 @@ public class YudaoAiAutoConfiguration {
return new MidjourneyInteractionsApi(midjourneyConfig); return new MidjourneyInteractionsApi(midjourneyConfig);
} }
private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext, private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext,
YudaoAiProperties.MidjourneyProperties midjourneyProperties) { YudaoAiProperties.MidjourneyProperties midjourneyProperties) {
Map<String, String> requestTemplates = new HashMap<>(); Map<String, String> requestTemplates = new HashMap<>();

View File

@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.ai.config;
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
import org.springframework.ai.models.xinghuo.XingHuoChatModel; import org.springframework.ai.models.xinghuo.XingHuoChatModel;
import org.springframework.ai.models.yiyan.YiYanChatModel; import org.springframework.ai.models.yiyan.YiYanChatModel;
import org.springframework.ai.models.openai.enums.OpenAiImageModelEnum; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum;
import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@ -95,6 +95,7 @@ public class YudaoAiProperties {
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public static class OpenAiImageProperties { public static class OpenAiImageProperties {
private boolean enable = false; private boolean enable = false;
/** /**
@ -109,6 +110,7 @@ public class YudaoAiProperties {
* 风格 * 风格
*/ */
private OpenAiImageStyleEnum style = OpenAiImageStyleEnum.VIVID; private OpenAiImageStyleEnum style = OpenAiImageStyleEnum.VIVID;
} }
@Data @Data

View File

@ -1,8 +1,9 @@
package org.springframework.ai.models.openai.enums; package cn.iocoder.yudao.framework.ai.core.enums;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
// TODO 芋艿待梳理
/** /**
* open ai * open ai
* *

View File

@ -1,8 +1,9 @@
package org.springframework.ai.models.openai.enums; package cn.iocoder.yudao.framework.ai.core.enums;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
// TODO 芋艿待梳理
/** /**
* open ai image style * open ai image style
* *

View File

@ -1,4 +1,4 @@
package org.springframework.ai.chat; package cn.iocoder.yudao.framework.ai.core.exception;
/** /**
* 聊天异常 * 聊天异常

View File

@ -12,4 +12,4 @@
* 2.4 openai OpenAIChatGPT拷贝 spring-ai 提供的 models/openai * 2.4 openai OpenAIChatGPT拷贝 spring-ai 提供的 models/openai
* 2.5 midjourney Midjourney参考 https://github.com/novicezk/midjourney-proxy 实现 * 2.5 midjourney Midjourney参考 https://github.com/novicezk/midjourney-proxy 实现
*/ */
package org.springframework.ai; package cn.iocoder.yudao.framework.ai;

View File

@ -1,36 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.ModelClient;
@FunctionalInterface
public interface ChatClient extends ModelClient<Prompt, ChatResponse> {
default String call(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
Generation generation = call(prompt).getResult();
return (generation != null) ? generation.getOutput().getContent() : "";
}
@Override
ChatResponse call(Prompt prompt);
}

View File

@ -1,118 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat;
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.model.ModelResponse;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* 人工智能提供商返回的聊天完成例如生成响应
*
* The chat completion (e.g. generation) response returned by an AI provider.
*/
public class ChatResponse implements ModelResponse<Generation> {
private final ChatResponseMetadata chatResponseMetadata;
/**
* List of generated messages returned by the AI provider.
*/
private final List<Generation> generations;
/**
* Construct a new {@link ChatResponse} instance without metadata.
* @param generations the {@link List} of {@link Generation} returned by the AI
* provider.
*/
public ChatResponse(List<Generation> generations) {
this(generations, ChatResponseMetadata.NULL);
}
/**
* Construct a new {@link ChatResponse} instance.
* @param generations the {@link List} of {@link Generation} returned by the AI
* provider.
* @param chatResponseMetadata {@link ChatResponseMetadata} containing information
* about the use of the AI provider's API.
*/
public ChatResponse(List<Generation> generations, ChatResponseMetadata chatResponseMetadata) {
this.chatResponseMetadata = chatResponseMetadata;
// this.generations = List.copyOf(generations);
this.generations = Collections.unmodifiableList(generations);
}
/**
* The {@link List} of {@link Generation generated outputs}.
* <p>
* It is a {@link List} of {@link List lists} because the Prompt could request
* multiple output {@link Generation generations}.
* @return the {@link List} of {@link Generation generated outputs}.
*/
@Override
public List<Generation> getResults() {
return this.generations;
}
/**
* @return Returns the first {@link Generation} in the generations list.
*/
public Generation getResult() {
if (CollectionUtils.isEmpty(this.generations)) {
return null;
}
return this.generations.get(0);
}
/**
* @return Returns {@link ChatResponseMetadata} containing information about the use
* of the AI provider's API.
*/
@Override
public ChatResponseMetadata getMetadata() {
return this.chatResponseMetadata;
}
@Override
public String toString() {
return "ChatResponse [metadata=" + chatResponseMetadata + ", generations=" + generations + "]";
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
// if (!(o instanceof ChatResponse that))
// return false;
if (!(o instanceof ChatResponse)) {
return false;
}
ChatResponse that = (ChatResponse) o;
return Objects.equals(chatResponseMetadata, that.chatResponseMetadata)
&& Objects.equals(generations, that.generations);
}
@Override
public int hashCode() {
return Objects.hash(chatResponseMetadata, generations);
}
}

View File

@ -1,86 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
import org.springframework.ai.model.ModelResult;
import org.springframework.lang.Nullable;
import java.util.Map;
import java.util.Objects;
/**
* 表示AI返回的响应
*
* Represents a response returned by the AI.
*/
public class Generation implements ModelResult<AssistantMessage> {
private AssistantMessage assistantMessage;
private ChatGenerationMetadata chatGenerationMetadata;
public Generation(String text) {
this.assistantMessage = new AssistantMessage(text);
}
public Generation(String text, Map<String, Object> properties) {
this.assistantMessage = new AssistantMessage(text, properties);
}
@Override
public AssistantMessage getOutput() {
return this.assistantMessage;
}
@Override
public ChatGenerationMetadata getMetadata() {
ChatGenerationMetadata chatGenerationMetadata = this.chatGenerationMetadata;
return chatGenerationMetadata != null ? chatGenerationMetadata : ChatGenerationMetadata.NULL;
}
public Generation withGenerationMetadata(@Nullable ChatGenerationMetadata chatGenerationMetadata) {
this.chatGenerationMetadata = chatGenerationMetadata;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Generation)) {
return false;
}
Generation that = (Generation) o;
return Objects.equals(assistantMessage, that.assistantMessage)
&& Objects.equals(chatGenerationMetadata, that.chatGenerationMetadata);
}
@Override
public int hashCode() {
return Objects.hash(assistantMessage, chatGenerationMetadata);
}
@Override
public String toString() {
return "Generation{" + "assistantMessage=" + assistantMessage + ", chatGenerationMetadata="
+ chatGenerationMetadata + '}';
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.StreamingModelClient;
import reactor.core.publisher.Flux;
@FunctionalInterface
public interface StreamingChatClient extends StreamingModelClient<Prompt, ChatResponse> {
@Override
Flux<ChatResponse> stream(Prompt prompt);
}

View File

@ -1,151 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public abstract class AbstractMessage implements Message {
protected final MessageType messageType;
protected final String textContent;
protected final List<MediaData> mediaData;
/**
* Additional options for the message to influence the response, not a generative map.
*/
protected final Map<String, Object> properties;
protected AbstractMessage(MessageType messageType, String content) {
this(messageType, content, Map.of());
}
protected AbstractMessage(MessageType messageType, String content, Map<String, Object> messageProperties) {
Assert.notNull(messageType, "Message type must not be null");
// Assert.notNull(content, "Content must not be null");
this.messageType = messageType;
this.textContent = content;
this.mediaData = new ArrayList<>();
this.properties = messageProperties;
}
protected AbstractMessage(MessageType messageType, String textContent, List<MediaData> mediaData) {
this(messageType, textContent, mediaData, Map.of());
}
protected AbstractMessage(MessageType messageType, String textContent, List<MediaData> mediaData,
Map<String, Object> messageProperties) {
Assert.notNull(messageType, "Message type must not be null");
Assert.notNull(textContent, "Content must not be null");
Assert.notNull(mediaData, "media data must not be null");
this.messageType = messageType;
this.textContent = textContent;
this.mediaData = new ArrayList<>(mediaData);
this.properties = messageProperties;
}
protected AbstractMessage(MessageType messageType, Resource resource) {
this(messageType, resource, Collections.emptyMap());
}
@SuppressWarnings("null")
protected AbstractMessage(MessageType messageType, Resource resource, Map<String, Object> messageProperties) {
Assert.notNull(messageType, "Message type must not be null");
Assert.notNull(resource, "Resource must not be null");
this.messageType = messageType;
this.properties = messageProperties;
this.mediaData = new ArrayList<>();
try (InputStream inputStream = resource.getInputStream()) {
this.textContent = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
}
catch (IOException ex) {
throw new RuntimeException("Failed to read resource", ex);
}
}
@Override
public String getContent() {
return this.textContent;
}
@Override
public List<MediaData> getMediaData() {
return this.mediaData;
}
@Override
public Map<String, Object> getProperties() {
return this.properties;
}
@Override
public MessageType getMessageType() {
return this.messageType;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mediaData == null) ? 0 : mediaData.hashCode());
result = prime * result + ((properties == null) ? 0 : properties.hashCode());
result = prime * result + ((messageType == null) ? 0 : messageType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractMessage other = (AbstractMessage) obj;
if (mediaData == null) {
if (other.mediaData != null)
return false;
}
else if (!mediaData.equals(other.mediaData))
return false;
if (properties == null) {
if (other.properties != null)
return false;
}
else if (!properties.equals(other.properties))
return false;
if (messageType != other.messageType)
return false;
return true;
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import java.util.Map;
/**
* 让生成人员知道内容是作为对用户的响应生成的
* 此角色指示生成者先前在会话中生成的消息
* 通过包括该系列中的辅助消息您可以为生成的关于提供上下文之前在谈话中的交流
*
* Lets the generative know the content was generated as a response to the user. This role
* indicates messages that the generative has previously generated in the conversation. By
* including assistant messages in the series, you provide context to the generative about
* prior exchanges in the conversation.
*/
public class AssistantMessage extends AbstractMessage {
public AssistantMessage(String content) {
super(MessageType.ASSISTANT, content);
}
public AssistantMessage(String content, Map<String, Object> properties) {
super(MessageType.ASSISTANT, content, properties);
}
@Override
public String toString() {
return "AssistantMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
+ messageType + '}';
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import java.util.Map;
public class ChatMessage extends AbstractMessage {
public ChatMessage(String role, String content) {
super(MessageType.valueOf(role), content);
}
public ChatMessage(String role, String content, Map<String, Object> properties) {
super(MessageType.valueOf(role), content, properties);
}
public ChatMessage(MessageType messageType, String content) {
super(messageType, content);
}
public ChatMessage(MessageType messageType, String content, Map<String, Object> properties) {
super(messageType, content, properties);
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import java.util.Map;
public class FunctionMessage extends AbstractMessage {
public FunctionMessage(String content) {
super(MessageType.SYSTEM, content);
}
public FunctionMessage(String content, Map<String, Object> properties) {
super(MessageType.SYSTEM, content, properties);
}
@Override
public String toString() {
return "FunctionMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
+ messageType + '}';
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
/**
* @author Christian Tzolov
*/
public class MediaData {
private final MimeType mimeType;
private final Object data;
public MediaData(MimeType mimeType, Object data) {
Assert.notNull(mimeType, "MimeType must not be null");
// Assert.notNull(data, "Data must not be null");
this.mimeType = mimeType;
this.data = data;
}
public MimeType getMimeType() {
return this.mimeType;
}
public Object getData() {
return this.data;
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import java.util.List;
import java.util.Map;
public interface Message {
String getContent();
List<MediaData> getMediaData();
Map<String, Object> getProperties();
MessageType getMessageType();
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
public enum MessageType {
// 用户消息
USER("user"),
// 之前会话的消息
ASSISTANT("assistant"),
// 根据注释说明您可以使用系统消息来指示具有生成性表现得像某个角色或以特定的方式提供答案总体安排
// 简单理解在对话前发送一条具有角色的信息让模型理解你现在是一个10年拍摄经验的导演拥有丰富的经验 这样你就可以去问他怎么拍一个短视频可以在抖音上火
SYSTEM("system"),
// 函数根据引用现在不支持会抛出一个异常 ---> throw new IllegalArgumentException("Tool execution results are not supported for Bedrock models");
FUNCTION("function");
private final String value;
MessageType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MessageType fromValue(String value) {
for (MessageType messageType : MessageType.values()) {
if (messageType.getValue().equals(value)) {
return messageType;
}
}
throw new IllegalArgumentException("Invalid MessageType value: " + value);
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import org.springframework.core.io.Resource;
/**
* 作为输入传递的system类型的消息系统消息给出高级别对话说明
* 此角色通常提供高级说明对话
* 例如您可以使用系统消息来指示具有生成性表现得像某个角色或以特定的方式提供答案总体安排
*
* A message of the type 'system' passed as input. The system message gives high level
* instructions for the conversation. This role typically provides high-level instructions
* for the conversation. For example, you might use a system message to instruct the
* generative to behave like a certain character or to provide answers in a specific
* format.
*/
public class SystemMessage extends AbstractMessage {
public SystemMessage(String content) {
super(MessageType.SYSTEM, content);
}
public SystemMessage(Resource resource) {
super(MessageType.SYSTEM, resource);
}
@Override
public String toString() {
return "SystemMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
+ messageType + '}';
}
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.messages;
import org.springframework.core.io.Resource;
import java.util.List;
/**
* 作为输入传递的user类型的消息具有用户角色的消息来自最终用户或开发者
* 它们表示问题提示或您想要的任何输入产生反应的
*
* A message of the type 'user' passed as input Messages with the user role are from the
* end-user or developer. They represent questions, prompts, or any input that you want
* the generative to respond to.
*/
public class UserMessage extends AbstractMessage {
public UserMessage(String message) {
super(MessageType.USER, message);
}
public UserMessage(Resource resource) {
super(MessageType.USER, resource);
}
public UserMessage(String textContent, List<MediaData> mediaDataList) {
super(MessageType.USER, textContent, mediaDataList);
}
@Override
public String toString() {
return "UserMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
+ messageType + '}';
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
import org.springframework.ai.model.ResultMetadata;
import org.springframework.lang.Nullable;
/**
* Abstract Data Type (ADT) encapsulating information on the completion choices in the AI
* response.
*
* @author John Blum
* @since 0.7.0
*/
public interface ChatGenerationMetadata extends ResultMetadata {
ChatGenerationMetadata NULL = ChatGenerationMetadata.from(null, null);
/**
* Factory method used to construct a new {@link ChatGenerationMetadata} from the
* given {@link String finish reason} and content filter metadata.
* @param finishReason {@link String} contain the reason for the choice completion.
* @param contentFilterMetadata underlying AI provider metadata for filtering applied
* to generation content.
* @return a new {@link ChatGenerationMetadata} from the given {@link String finish
* reason} and content filter metadata.
*/
static ChatGenerationMetadata from(String finishReason, Object contentFilterMetadata) {
return new ChatGenerationMetadata() {
@Override
@SuppressWarnings("unchecked")
public <T> T getContentFilterMetadata() {
return (T) contentFilterMetadata;
}
@Override
public String getFinishReason() {
return finishReason;
}
};
}
/**
* Returns the underlying AI provider metadata for filtering applied to generation
* content.
* @param <T> {@link Class Type} used to cast the filtered content metadata into the
* AI provider-specific type.
* @return the underlying AI provider metadata for filtering applied to generation
* content.
*/
@Nullable
<T> T getContentFilterMetadata();
/**
* Get the {@link String reason} this choice completed for the generation.
* @return the {@link String reason} this choice completed for the generation.
*/
String getFinishReason();
}

View File

@ -1,58 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
import org.springframework.ai.model.ResponseMetadata;
/**
* Abstract Data Type (ADT) modeling common AI provider metadata returned in an AI
* response.
*
* 抽象数据类型ADT建模AI响应中返回的常见AI提供者元数据
*
* @author John Blum
* @since 0.7.0
*/
public interface ChatResponseMetadata extends ResponseMetadata {
ChatResponseMetadata NULL = new ChatResponseMetadata() {
};
/**
* Returns AI provider specific metadata on rate limits.
* @return AI provider specific metadata on rate limits.
* @see RateLimit
*/
default RateLimit getRateLimit() {
return new EmptyRateLimit();
}
/**
* Returns AI provider specific metadata on API usage.
* @return AI provider specific metadata on API usage.
* @see Usage
*/
default Usage getUsage() {
return new EmptyUsage();
}
default PromptMetadata getPromptMetadata() {
return PromptMetadata.empty();
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
import java.time.Duration;
/**
* A RateLimit implementation that returns zero for all property getters
*
* @author John Blum
* @since 0.7.0
*/
public class EmptyRateLimit implements RateLimit {
@Override
public Long getRequestsLimit() {
return 0L;
}
@Override
public Long getRequestsRemaining() {
return 0L;
}
@Override
public Duration getRequestsReset() {
return Duration.ZERO;
}
@Override
public Long getTokensLimit() {
return 0L;
}
@Override
public Long getTokensRemaining() {
return 0L;
}
@Override
public Duration getTokensReset() {
return Duration.ZERO;
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
/**
* A EmpytUsage implementation that returns zero for all property getters
*
* @author John Blum
* @since 0.7.0
*/
public class EmptyUsage implements Usage {
@Override
public Long getPromptTokens() {
return 0L;
}
@Override
public Long getGenerationTokens() {
return 0L;
}
}

View File

@ -1,136 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.StreamSupport;
/**
* Abstract Data Type (ADT) modeling metadata gathered by the AI during request
* processing.
*
* @author John Blum
* @since 0.7.0
*/
@FunctionalInterface
public interface PromptMetadata extends Iterable<PromptMetadata.PromptFilterMetadata> {
/**
* Factory method used to create empty {@link PromptMetadata} when the information is
* not supplied by the AI provider.
* @return empty {@link PromptMetadata}.
*/
static PromptMetadata empty() {
return of();
}
/**
* Factory method used to create a new {@link PromptMetadata} composed of an array of
* {@link PromptFilterMetadata}.
* @param array array of {@link PromptFilterMetadata} used to compose the
* {@link PromptMetadata}.
* @return a new {@link PromptMetadata} composed of an array of
* {@link PromptFilterMetadata}.
*/
static <T> PromptMetadata of(PromptFilterMetadata... array) {
return of(Arrays.asList(array));
}
/**
* Factory method used to create a new {@link PromptMetadata} composed of an
* {@link Iterable} of {@link PromptFilterMetadata}.
* @param iterable {@link Iterable} of {@link PromptFilterMetadata} used to compose
* the {@link PromptMetadata}.
* @return a new {@link PromptMetadata} composed of an {@link Iterable} of
* {@link PromptFilterMetadata}.
*/
static PromptMetadata of(Iterable<PromptFilterMetadata> iterable) {
Assert.notNull(iterable, "An Iterable of PromptFilterMetadata must not be null");
return iterable::iterator;
}
/**
* Returns an {@link Optional} {@link PromptFilterMetadata} at the given index.
* @param promptIndex index of the {@link PromptFilterMetadata} contained in this
* {@link PromptMetadata}.
* @return {@link Optional} {@link PromptFilterMetadata} at the given index.
* @throws IllegalArgumentException if the prompt index is less than 0.
*/
default Optional<PromptFilterMetadata> findByPromptIndex(int promptIndex) {
Assert.isTrue(promptIndex > -1, "Prompt index [%d] must be greater than equal to 0".formatted(promptIndex));
return StreamSupport.stream(this.spliterator(), false)
.filter(promptFilterMetadata -> promptFilterMetadata.getPromptIndex() == promptIndex)
.findFirst();
}
/**
* Abstract Data Type (ADT) modeling filter metadata for all prompts sent during an AI
* request.
*/
interface PromptFilterMetadata {
/**
* Factory method used to construct a new {@link PromptFilterMetadata} with the
* given prompt index and content filter metadata.
* @param promptIndex index of the prompt filter metadata contained in the AI
* response.
* @param contentFilterMetadata underlying AI provider metadata for filtering
* applied to prompt content.
* @return a new instance of {@link PromptFilterMetadata} with the given prompt
* index and content filter metadata.
*/
static PromptFilterMetadata from(int promptIndex, Object contentFilterMetadata) {
return new PromptFilterMetadata() {
@Override
public int getPromptIndex() {
return promptIndex;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getContentFilterMetadata() {
return (T) contentFilterMetadata;
}
};
}
/**
* Index of the prompt filter metadata contained in the AI response.
* @return an {@link Integer index} fo the prompt filter metadata contained in the
* AI response.
*/
int getPromptIndex();
/**
* Returns the underlying AI provider metadata for filtering applied to prompt
* content.
* @param <T> {@link Class Type} used to cast the filtered content metadata into
* the AI provider-specific type.
* @return the underlying AI provider metadata for filtering applied to prompt
* content.
*/
<T> T getContentFilterMetadata();
}
}

View File

@ -1,84 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
import java.time.Duration;
/**
* Abstract Data Type (ADT) encapsulating metadata from an AI provider's API rate limits
* granted to the API key in use and the API key's current balance.
*
* @author John Blum
* @since 0.7.0
*/
public interface RateLimit {
/**
* Returns the maximum number of requests that are permitted before exhausting the
* rate limit.
* @return an {@link Long} with the maximum number of requests that are permitted
* before exhausting the rate limit.
* @see #getRequestsRemaining()
*/
Long getRequestsLimit();
/**
* Returns the remaining number of requests that are permitted before exhausting the
* {@link #getRequestsLimit() rate limit}.
* @return an {@link Long} with the remaining number of requests that are permitted
* before exhausting the {@link #getRequestsLimit() rate limit}.
* @see #getRequestsLimit()
*/
Long getRequestsRemaining();
/**
* Returns the {@link Duration time} until the rate limit (based on requests) resets
* to its {@link #getRequestsLimit() initial state}.
* @return a {@link Duration} representing the time until the rate limit (based on
* requests) resets to its {@link #getRequestsLimit() initial state}.
* @see #getRequestsLimit()
*/
Duration getRequestsReset();
/**
* Returns the maximum number of tokens that are permitted before exhausting the rate
* limit.
* @return an {@link Long} with the maximum number of tokens that are permitted before
* exhausting the rate limit.
* @see #getTokensRemaining()
*/
Long getTokensLimit();
/**
* Returns the remaining number of tokens that are permitted before exhausting the
* {@link #getTokensLimit() rate limit}.
* @return an {@link Long} with the remaining number of tokens that are permitted
* before exhausting the {@link #getTokensLimit() rate limit}.
* @see #getTokensLimit()
*/
Long getTokensRemaining();
/**
* Returns the {@link Duration time} until the rate limit (based on tokens) resets to
* its {@link #getTokensLimit() initial state}.
* @return a {@link Duration} with the time until the rate limit (based on tokens)
* resets to its {@link #getTokensLimit() initial state}.
* @see #getTokensLimit()
*/
Duration getTokensReset();
}

View File

@ -1,66 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.metadata;
/**
* 抽象数据类型ADT封装关于人工智能提供商API使用的元数据根据AI请求
*
* Abstract Data Type (ADT) encapsulating metadata on the usage of an AI provider's API
* per AI request.
*
* @author John Blum
* @since 0.7.0
*/
public interface Usage {
/**
* 返回AI请求的@literal prompt中使用的令牌数
* @返回一个@link Long其中包含在的@literal提示符中使用的令牌数AI请求
*
* Returns the number of tokens used in the {@literal prompt} of the AI request.
* @return an {@link Long} with the number of tokens used in the {@literal prompt} of
* the AI request.
* @see #getGenerationTokens()
*/
Long getPromptTokens();
/**
* Returns the number of tokens returned in the {@literal generation (aka completion)}
* of the AI's response.
* @return an {@link Long} with the number of tokens returned in the
* {@literal generation (aka completion)} of the AI's response.
* @see #getPromptTokens()
*/
Long getGenerationTokens();
/**
* Return the total number of tokens from both the {@literal prompt} of an AI request
* and {@literal generation} of the AI's response.
* @return the total number of tokens from both the {@literal prompt} of an AI request
* and {@literal generation} of the AI's response.
* @see #getPromptTokens()
* @see #getGenerationTokens()
*/
default Long getTotalTokens() {
Long promptTokens = getPromptTokens();
promptTokens = promptTokens != null ? promptTokens : 0;
Long completionTokens = getGenerationTokens();
completionTokens = completionTokens != null ? completionTokens : 0;
return promptTokens + completionTokens;
}
}

View File

@ -1,14 +0,0 @@
/**
* The org.sf.ai.chat package represents the bounded context for the Chat Model within the
* AI generative model domain. This package extends the core domain defined in
* org.sf.ai.generative, providing implementations specific to chat-based generative AI
* interactions.
*
* In line with Domain-Driven Design principles, this package includes implementations of
* entities and value objects specific to the chat context, such as ChatPrompt and
* ChatResponse, adhering to the ubiquitous language of chat interactions in AI models.
*
* This bounded context is designed to encapsulate all aspects of chat-based AI
* functionalities, maintaining a clear boundary from other contexts within the AI domain.
*/
package org.springframework.ai.chat;

View File

@ -1,55 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.core.io.Resource;
import java.util.Map;
public class AssistantPromptTemplate extends PromptTemplate {
public AssistantPromptTemplate(String template) {
super(template);
}
public AssistantPromptTemplate(Resource resource) {
super(resource);
}
@Override
public Prompt create() {
return new Prompt(new AssistantMessage(render()));
}
@Override
public Prompt create(Map<String, Object> model) {
return new Prompt(new AssistantMessage(render(model)));
}
@Override
public Message createMessage() {
return new AssistantMessage(render());
}
@Override
public Message createMessage(Map<String, Object> model) {
return new AssistantMessage(render(model));
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
import org.springframework.ai.model.ModelOptions;
/**
* 聊天选项代表了常见的选项可在不同的聊天模式中移植
*
* The ChatOptions represent the common options, portable across different chat models.
*/
public interface ChatOptions extends ModelOptions {
Float getTemperature();
void setTemperature(Float temperature);
Float getTopP();
void setTopP(Float topP);
Integer getTopK();
void setTopK(Integer topK);
}

View File

@ -1,89 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
public class ChatOptionsBuilder {
private class ChatOptionsImpl implements ChatOptions {
private Float temperature;
private Float topP;
private Integer topK;
@Override
public Float getTemperature() {
return temperature;
}
@Override
public void setTemperature(Float temperature) {
this.temperature = temperature;
}
@Override
public Float getTopP() {
return topP;
}
@Override
public void setTopP(Float topP) {
this.topP = topP;
}
@Override
public Integer getTopK() {
return topK;
}
@Override
public void setTopK(Integer topK) {
this.topK = topK;
}
}
private final ChatOptionsImpl options = new ChatOptionsImpl();
private ChatOptionsBuilder() {
}
public static ChatOptionsBuilder builder() {
return new ChatOptionsBuilder();
}
public ChatOptionsBuilder withTemperature(Float temperature) {
options.setTemperature(temperature);
return this;
}
public ChatOptionsBuilder withTopP(Float topP) {
options.setTopP(topP);
return this;
}
public ChatOptionsBuilder withTopK(Integer topK) {
options.setTopK(topK);
return this;
}
public ChatOptions build() {
return options;
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
import org.springframework.ai.chat.messages.Message;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* PromptTemplate用于将角色指定为字符串实现及其角色不足以满足您的需求
*
* A PromptTemplate that lets you specify the role as a string should the current
* implementations and their roles not suffice for your needs.
*/
public class ChatPromptTemplate implements PromptTemplateActions, PromptTemplateChatActions {
private final List<PromptTemplate> promptTemplates;
public ChatPromptTemplate(List<PromptTemplate> promptTemplates) {
this.promptTemplates = promptTemplates;
}
@Override
public String render() {
StringBuilder sb = new StringBuilder();
for (PromptTemplate promptTemplate : promptTemplates) {
sb.append(promptTemplate.render());
}
return sb.toString();
}
@Override
public String render(Map<String, Object> model) {
StringBuilder sb = new StringBuilder();
for (PromptTemplate promptTemplate : promptTemplates) {
sb.append(promptTemplate.render(model));
}
return sb.toString();
}
@Override
public List<Message> createMessages() {
List<Message> messages = new ArrayList<>();
for (PromptTemplate promptTemplate : promptTemplates) {
messages.add(promptTemplate.createMessage());
}
return messages;
}
@Override
public List<Message> createMessages(Map<String, Object> model) {
List<Message> messages = new ArrayList<>();
for (PromptTemplate promptTemplate : promptTemplates) {
messages.add(promptTemplate.createMessage(model));
}
return messages;
}
@Override
public Prompt create() {
List<Message> messages = createMessages();
return new Prompt(messages);
}
@Override
public Prompt create(Map<String, Object> model) {
List<Message> messages = createMessages(model);
return new Prompt(messages);
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
public class FunctionPromptTemplate extends PromptTemplate {
private String name;
public FunctionPromptTemplate(String template) {
super(template);
}
}

View File

@ -1,103 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.model.ModelOptions;
import org.springframework.ai.model.ModelRequest;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* 文字内容
*/
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages;
private ChatOptions modelOptions;
public Prompt(String contents) {
this(new UserMessage(contents));
}
public Prompt(Message message) {
this(Collections.singletonList(message));
}
public Prompt(List<Message> messages) {
this.messages = messages;
}
public Prompt(String contents, ChatOptions modelOptions) {
this(new UserMessage(contents), modelOptions);
}
public Prompt(Message message, ChatOptions modelOptions) {
this(Collections.singletonList(message), modelOptions);
}
public Prompt(List<Message> messages, ChatOptions modelOptions) {
this.messages = messages;
this.modelOptions = modelOptions;
}
public String getContents() {
StringBuilder sb = new StringBuilder();
for (Message message : getInstructions()) {
sb.append(message.getContent());
}
return sb.toString();
}
@Override
public ModelOptions getOptions() {
return this.modelOptions;
}
@Override
public List<Message> getInstructions() {
return this.messages;
}
@Override
public String toString() {
return "Prompt{" + "messages=" + this.messages + ", modelOptions=" + this.modelOptions + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
// if (!(o instanceof Prompt prompt))
// return false;
if (!(o instanceof Prompt)) {
return false;
}
Prompt prompt = (Prompt) o;
return Objects.equals(this.messages, prompt.messages) && Objects.equals(this.modelOptions, prompt.modelOptions);
}
@Override
public int hashCode() {
return Objects.hash(this.messages, this.modelOptions);
}
}

View File

@ -1,218 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.parser.OutputParser;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.compiler.STLexer;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 用户输入的提示内容模板
*
* 实现提示词模板操作 提示词模板message相关操作
*/
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
private ST st;
private Map<String, Object> dynamicModel = new HashMap<>();
protected String template;
protected TemplateFormat templateFormat = TemplateFormat.ST;
private OutputParser outputParser;
public PromptTemplate(Resource resource) {
try (InputStream inputStream = resource.getInputStream()) {
this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
}
catch (IOException ex) {
throw new RuntimeException("Failed to read resource", ex);
}
try {
this.st = new ST(this.template, '{', '}');
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
public PromptTemplate(String template) {
this.template = template;
// If the template string is not valid, an exception will be thrown
try {
this.st = new ST(this.template, '{', '}');
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
public PromptTemplate(String template, Map<String, Object> model) {
this.template = template;
// If the template string is not valid, an exception will be thrown
try {
this.st = new ST(this.template, '{', '}');
for (Entry<String, Object> entry : model.entrySet()) {
add(entry.getKey(), entry.getValue());
dynamicModel.put(entry.getKey(), entry.getValue());
}
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
public PromptTemplate(Resource resource, Map<String, Object> model) {
try (InputStream inputStream = resource.getInputStream()) {
this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
}
catch (IOException ex) {
throw new RuntimeException("Failed to read resource", ex);
}
// If the template string is not valid, an exception will be thrown
try {
this.st = new ST(this.template, '{', '}');
for (Entry<String, Object> entry : model.entrySet()) {
add(entry.getKey(), entry.getValue());
dynamicModel.put(entry.getKey(), entry.getValue());
}
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
public OutputParser getOutputParser() {
return outputParser;
}
public void setOutputParser(OutputParser outputParser) {
Objects.requireNonNull(outputParser, "Output Parser can not be null");
this.outputParser = outputParser;
}
public void add(String name, Object value) {
this.st.add(name, value);
this.dynamicModel.put(name, value);
}
public String getTemplate() {
return this.template;
}
public TemplateFormat getTemplateFormat() {
return this.templateFormat;
}
// Render Methods
@Override
public String render() {
validate(this.dynamicModel);
return st.render();
}
@Override
public String render(Map<String, Object> model) {
validate(model);
for (Entry<String, Object> entry : model.entrySet()) {
if (st.getAttribute(entry.getKey()) != null) {
st.remove(entry.getKey());
}
if (entry.getValue() instanceof Resource) {
st.add(entry.getKey(), renderResource((Resource) entry.getValue()));
}
else {
st.add(entry.getKey(), entry.getValue());
}
}
return st.render();
}
private String renderResource(Resource resource) {
try {
return resource.getContentAsString(Charset.defaultCharset());
}
catch (IOException e) {
throw new RuntimeException(e);
}
// try (InputStream inputStream = resource.getInputStream()) {
// return StreamUtils.copyToString(inputStream, Charset.defaultCharset());
// }
// catch (IOException ex) {
// throw new RuntimeException(ex);
// }
}
@Override
public Message createMessage() {
return new UserMessage(render());
}
@Override
public Message createMessage(Map<String, Object> model) {
return new UserMessage(render(model));
}
@Override
public Prompt create() {
return new Prompt(render(new HashMap<>()));
}
@Override
public Prompt create(Map<String, Object> model) {
return new Prompt(render(model));
}
public Set<String> getInputVariables() {
TokenStream tokens = this.st.impl.tokens;
return IntStream.range(0, tokens.range())
.mapToObj(tokens::get)
.filter(token -> token.getType() == STLexer.ID)
.map(Token::getText)
.collect(Collectors.toSet());
}
protected void validate(Map<String, Object> model) {
Set<String> dynamicVariableNames = new HashSet<>(this.dynamicModel.keySet());
Set<String> modelVariables = new HashSet<>(model.keySet());
modelVariables.addAll(dynamicVariableNames);
Set<String> missingEntries = new HashSet<>(getInputVariables());
missingEntries.removeAll(modelVariables);
if (!missingEntries.isEmpty()) {
throw new IllegalStateException(
"All template variables were not replaced. Missing variable names are " + missingEntries);
}
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
import java.util.Map;
/**
* 提示词模板操作
*/
public interface PromptTemplateActions extends PromptTemplateStringActions {
/**
* 创建 Prompt
* @return
*/
Prompt create();
Prompt create(Map<String, Object> model);
}

View File

@ -1,18 +0,0 @@
package org.springframework.ai.chat.prompt;
import org.springframework.ai.chat.messages.Message;
import java.util.List;
import java.util.Map;
/**
* 聊天操作
*
*/
public interface PromptTemplateChatActions {
List<Message> createMessages();
List<Message> createMessages(Map<String, Object> model);
}

View File

@ -1,24 +0,0 @@
package org.springframework.ai.chat.prompt;
import org.springframework.ai.chat.messages.Message;
import java.util.Map;
/**
* 用户输入的提示内容 模板信息操作
*/
public interface PromptTemplateMessageActions {
/**
* 创建一个 message
* @return
*/
Message createMessage();
/**
* 创建一个 message
* @return
*/
Message createMessage(Map<String, Object> model);
}

View File

@ -1,14 +0,0 @@
package org.springframework.ai.chat.prompt;
import java.util.Map;
/**
* 提示次模板字符串操作
*/
public interface PromptTemplateStringActions {
String render();
String render(Map<String, Object> model);
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.core.io.Resource;
import java.util.Map;
public class SystemPromptTemplate extends PromptTemplate {
public SystemPromptTemplate(String template) {
super(template);
}
public SystemPromptTemplate(Resource resource) {
super(resource);
}
@Override
public Message createMessage() {
return new SystemMessage(render());
}
@Override
public Message createMessage(Map<String, Object> model) {
return new SystemMessage(render(model));
}
@Override
public Prompt create() {
return new Prompt(new SystemMessage(render()));
}
@Override
public Prompt create(Map<String, Object> model) {
return new Prompt(new SystemMessage(render(model)));
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.prompt;
public enum TemplateFormat {
ST("ST");
private final String value;
TemplateFormat(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static TemplateFormat fromValue(String value) {
for (TemplateFormat templateFormat : TemplateFormat.values()) {
if (templateFormat.getValue().equals(value)) {
return templateFormat;
}
}
throw new IllegalArgumentException("Invalid TemplateFormat value: " + value);
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import java.util.Objects;
public class Image {
/**
* 可以访问图像的URL
*
* The URL where the image can be accessed.
*/
private String url;
/**
* Base64编码的图像字符串
*
* Base64 encoded image string.
*/
private String b64Json;
public Image(String url, String b64Json) {
this.url = url;
this.b64Json = b64Json;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getB64Json() {
return b64Json;
}
public void setB64Json(String b64Json) {
this.b64Json = b64Json;
}
@Override
public String toString() {
return "Image{" + "url='" + url + '\'' + ", b64Json='" + b64Json + '\'' + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Image image))
return false;
return Objects.equals(url, image.url) && Objects.equals(b64Json, image.b64Json);
}
@Override
public int hashCode() {
return Objects.hash(url, b64Json);
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import org.springframework.ai.model.ModelClient;
public interface ImageClient extends ModelClient<ImagePrompt, ImageResponse> {
/**
* chat一样
* @param imagePrompt the request object to be sent to the AI model
* @return
*/
ImageResponse call(ImagePrompt imagePrompt);
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import org.springframework.ai.model.ModelResult;
public class ImageGeneration implements ModelResult<Image> {
// metadata 信息为空现在
private ImageGenerationMetadata imageGenerationMetadata;
private Image image;
public ImageGeneration(Image image) {
this.image = image;
}
public ImageGeneration(Image image, ImageGenerationMetadata imageGenerationMetadata) {
this.image = image;
this.imageGenerationMetadata = imageGenerationMetadata;
}
@Override
public Image getOutput() {
return image;
}
@Override
public ImageGenerationMetadata getMetadata() {
return imageGenerationMetadata;
}
@Override
public String toString() {
return "ImageGeneration{" + "imageGenerationMetadata=" + imageGenerationMetadata + ", image=" + image + '}';
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import org.springframework.ai.model.ResultMetadata;
public interface ImageGenerationMetadata extends ResultMetadata {
}

View File

@ -1,63 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import java.util.Objects;
public class ImageMessage {
private String text;
private Float weight;
public ImageMessage(String text) {
this.text = text;
}
public ImageMessage(String text, Float weight) {
this.text = text;
this.weight = weight;
}
public String getText() {
return text;
}
public Float getWeight() {
return weight;
}
@Override
public String toString() {
return "mageMessage{" + "text='" + text + '\'' + ", weight=" + weight + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ImageMessage that))
return false;
return Objects.equals(text, that.text) && Objects.equals(weight, that.weight);
}
@Override
public int hashCode() {
return Objects.hash(text, weight);
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import org.springframework.ai.model.ModelOptions;
/**
* ImageOptions represent the common options, portable across different image generation
* models.
*/
public interface ImageOptions extends ModelOptions {
Integer getN();
String getModel();
Integer getWidth();
Integer getHeight();
String getResponseFormat(); // openai - url or base64 : stability ai byte[] or base64
}

View File

@ -1,119 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
public class ImageOptionsBuilder {
private class ImageModelOptionsImpl implements ImageOptions {
private Integer n;
private String model;
private Integer width;
private Integer height;
private String responseFormat;
@Override
public Integer getN() {
return n;
}
public void setN(Integer n) {
this.n = n;
}
@Override
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
@Override
public String getResponseFormat() {
return responseFormat;
}
public void setResponseFormat(String responseFormat) {
this.responseFormat = responseFormat;
}
@Override
public Integer getWidth() {
return width;
}
public void setWidth(Integer width) {
this.width = width;
}
@Override
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
}
private final ImageModelOptionsImpl options = new ImageModelOptionsImpl();
private ImageOptionsBuilder() {
}
public static ImageOptionsBuilder builder() {
return new ImageOptionsBuilder();
}
public ImageOptionsBuilder withN(Integer n) {
options.setN(n);
return this;
}
public ImageOptionsBuilder withModel(String model) {
options.setModel(model);
return this;
}
public ImageOptionsBuilder withResponseFormat(String responseFormat) {
options.setResponseFormat(responseFormat);
return this;
}
public ImageOptionsBuilder withWidth(Integer width) {
options.setWidth(width);
return this;
}
public ImageOptionsBuilder withHeight(Integer height) {
options.setHeight(height);
return this;
}
public ImageOptions build() {
return options;
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import org.springframework.ai.model.ModelRequest;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* 图片内容
*/
public class ImagePrompt implements ModelRequest<List<ImageMessage>> {
private final List<ImageMessage> messages;
private ImageOptions imageModelOptions;
public ImagePrompt(List<ImageMessage> messages) {
this.messages = messages;
}
public ImagePrompt(List<ImageMessage> messages, ImageOptions imageModelOptions) {
this.messages = messages;
this.imageModelOptions = imageModelOptions;
}
public ImagePrompt(ImageMessage imageMessage, ImageOptions imageOptions) {
this(Collections.singletonList(imageMessage), imageOptions);
}
public ImagePrompt(String instructions, ImageOptions imageOptions) {
this(new ImageMessage(instructions), imageOptions);
}
public ImagePrompt(String instructions) {
// this(new ImageMessage(instructions), ImageOptionsBuilder.builder().build());
this(new ImageMessage(instructions), null);
}
@Override
public List<ImageMessage> getInstructions() {
return messages;
}
@Override
public ImageOptions getOptions() {
return imageModelOptions;
}
@Override
public String toString() {
return "NewImagePrompt{" + "messages=" + messages + ", imageModelOptions=" + imageModelOptions + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ImagePrompt that))
return false;
return Objects.equals(messages, that.messages) && Objects.equals(imageModelOptions, that.imageModelOptions);
}
@Override
public int hashCode() {
return Objects.hash(messages, imageModelOptions);
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import org.springframework.ai.model.ModelResponse;
import java.util.List;
import java.util.Objects;
public class ImageResponse implements ModelResponse<ImageGeneration> {
private final ImageResponseMetadata imageResponseMetadata;
private final List<ImageGeneration> imageGenerations;
public ImageResponse(List<ImageGeneration> generations) {
this(generations, ImageResponseMetadata.NULL);
}
public ImageResponse(List<ImageGeneration> generations, ImageResponseMetadata imageResponseMetadata) {
this.imageResponseMetadata = imageResponseMetadata;
this.imageGenerations = List.copyOf(generations);
}
@Override
public ImageGeneration getResult() {
return imageGenerations.get(0);
}
@Override
public List<ImageGeneration> getResults() {
return imageGenerations;
}
@Override
public ImageResponseMetadata getMetadata() {
return imageResponseMetadata;
}
@Override
public String toString() {
return "ImageResponse{" + "imageResponseMetadata=" + imageResponseMetadata + ", imageGenerations="
+ imageGenerations + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ImageResponse that))
return false;
return Objects.equals(imageResponseMetadata, that.imageResponseMetadata)
&& Objects.equals(imageGenerations, that.imageGenerations);
}
@Override
public int hashCode() {
return Objects.hash(imageResponseMetadata, imageGenerations);
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.image;
import org.springframework.ai.model.ResponseMetadata;
public interface ImageResponseMetadata extends ResponseMetadata {
ImageResponseMetadata NULL = new ImageResponseMetadata() {
};
default Long created() {
return System.currentTimeMillis();
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
/**
* The ModelClient interface provides a generic API for invoking AI models. It is designed
* to handle the interaction with various types of AI models by abstracting the process of
* sending requests and receiving responses. The interface uses Java generics to
* accommodate different types of requests and responses, enhancing flexibility and
* adaptability across different AI model implementations.
*
* @param <TReq> the generic type of the request to the AI model
* @param <TRes> the generic type of the response from the AI model
* @author Mark Pollack
* @since 0.8.0
*/
public interface ModelClient<TReq extends ModelRequest<?>, TRes extends ModelResponse<?>> {
/**
* Executes a method call to the AI model.
* @param request the request object to be sent to the AI model
* @return the response from the AI model
*/
TRes call(TReq request) throws Throwable;
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
/**
* Interface representing the customizable options for AI model interactions. This marker
* interface allows for the specification of various settings and parameters that can
* influence the behavior and output of AI models. It is designed to provide flexibility
* and adaptability in different AI scenarios, ensuring that the AI models can be
* fine-tuned according to specific requirements.
*
* @author Mark Pollack
* @since 0.8.0
*/
public interface ModelOptions {
}

View File

@ -1,387 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.victools.jsonschema.generator.*;
import com.github.victools.jsonschema.module.jackson.JacksonModule;
import com.github.victools.jsonschema.module.jackson.JacksonOption;
import com.github.victools.jsonschema.module.swagger2.Swagger2Module;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* Utility class for manipulating {@link ModelOptions} objects.
*
* @author Christian Tzolov
* @since 0.8.0
*/
public final class ModelOptionsUtils {
private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
private final static List<String> BEAN_MERGE_FIELD_EXCISIONS = List.of("class");
private static ConcurrentHashMap<Class<?>, List<String>> REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap<Class<?>, List<String>>();
private static AtomicReference<SchemaGenerator> SCHEMA_GENERATOR_CACHE = new AtomicReference<>();
private ModelOptionsUtils() {
}
/**
* Converts the given JSON string to a Map of String and Object.
* @param json the JSON string to convert to a Map.
* @return the converted Map.
*/
public static Map<String, Object> jsonToMap(String json) {
try {
return OBJECT_MAPPER.readValue(json, MAP_TYPE_REF);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static TypeReference<HashMap<String, Object>> MAP_TYPE_REF = new TypeReference<HashMap<String, Object>>() {
};
/**
* Converts the given JSON string to an Object of the given type.
* @param <T> the type of the object to return.
* @param json the JSON string to convert to an object.
* @param type the type of the object to return.
* @return Object instance of the given type.
*/
public static <T> T jsonToObject(String json, Class<T> type) {
try {
return OBJECT_MAPPER.readValue(json, type);
}
catch (Exception e) {
throw new RuntimeException("Failed to json: " + json, e);
}
}
/**
* Converts the given object to a JSON string.
* @param object the object to convert to a JSON string.
* @return the JSON string.
*/
public static String toJsonString(Object object) {
try {
return OBJECT_MAPPER.writeValueAsString(object);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* Merges the source object into the target object and returns an object represented
* by the given class. The JSON property names are used to match the fields to merge.
* The source non-null values override the target values with the same field name. The
* source null values are ignored. If the acceptedFieldNames is not empty, only the
* fields with the given names are merged and returned. If the acceptedFieldNames is
* empty, use the {@code @JsonProperty} names, inferred from the provided clazz.
* @param <T> they type of the class to return.
* @param source the source object to merge.
* @param target the target object to merge into.
* @param clazz the class to return.
* @param acceptedFieldNames the list of field names accepted for the target object.
* @return the merged object represented by the given class.
*/
public static <T> T merge(Object source, Object target, Class<T> clazz, List<String> acceptedFieldNames) {
if (source == null) {
source = Map.of();
}
List<String> requestFieldNames = CollectionUtils.isEmpty(acceptedFieldNames)
? REQUEST_FIELD_NAMES_PER_CLASS.computeIfAbsent(clazz, ModelOptionsUtils::getJsonPropertyValues)
: acceptedFieldNames;
if (CollectionUtils.isEmpty(requestFieldNames)) {
throw new IllegalArgumentException("No @JsonProperty fields found in the " + clazz.getName());
}
Map<String, Object> sourceMap = ModelOptionsUtils.objectToMap(source);
Map<String, Object> targetMap = ModelOptionsUtils.objectToMap(target);
targetMap.putAll(sourceMap.entrySet()
.stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));
targetMap = targetMap.entrySet()
.stream()
.filter(e -> requestFieldNames.contains(e.getKey()))
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
return ModelOptionsUtils.mapToClass(targetMap, clazz);
}
/**
* Merges the source object into the target object and returns an object represented
* by the given class. The JSON property names are used to match the fields to merge.
* The source non-null values override the target values with the same field name. The
* source null values are ignored. Returns the only field names that match the
* {@code @JsonProperty} names, inferred from the provided clazz.
* @param <T> they type of the class to return.
* @param source the source object to merge.
* @param target the target object to merge into.
* @param clazz the class to return.
* @return the merged object represented by the given class.
*/
public static <T> T merge(Object source, Object target, Class<T> clazz) {
return ModelOptionsUtils.merge(source, target, clazz, null);
}
/**
* Converts the given object to a Map.
* @param source the object to convert to a Map.
* @return the converted Map.
*/
public static Map<String, Object> objectToMap(Object source) {
if (source == null) {
return new HashMap<>();
}
try {
String json = OBJECT_MAPPER.writeValueAsString(source);
return OBJECT_MAPPER.readValue(json, new TypeReference<Map<String, Object>>() {
})
.entrySet()
.stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* Converts the given Map to the given class.
* @param <T> the type of the class to return.
* @param source the Map to convert to the given class.
* @param clazz the class to convert the Map to.
* @return the converted class.
*/
public static <T> T mapToClass(Map<String, Object> source, Class<T> clazz) {
try {
String json = OBJECT_MAPPER.writeValueAsString(source);
return OBJECT_MAPPER.readValue(json, clazz);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* Returns the list of name values of the {@link JsonProperty} annotations.
* @param clazz the class that contains fields annotated with {@link JsonProperty}.
* @return the list of values of the {@link JsonProperty} annotations.
*/
public static List<String> getJsonPropertyValues(Class<?> clazz) {
List<String> values = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class);
if (jsonPropertyAnnotation != null) {
values.add(jsonPropertyAnnotation.value());
}
}
return values;
}
/**
* Returns a new instance of the targetBeanClazz that copies the bean values from the
* sourceBean instance.
* @param sourceBean the source bean to copy the values from.
* @param sourceInterfaceClazz the source interface class. Only the fields with the
* same name as the interface methods are copied. This allow the source object to be a
* subclass of the source interface with additional, non-interface fields.
* @param targetBeanClazz the target class, a subclass of the ChatOptions, to convert
* into.
* @param <T> the target class type.
* @return a new instance of the targetBeanClazz with the values from the sourceBean
* instance.
*/
public static <I, S extends I, T extends S> T copyToTarget(S sourceBean, Class<I> sourceInterfaceClazz,
Class<T> targetBeanClazz) {
Assert.notNull(sourceInterfaceClazz, "SourceOptionsClazz must not be null");
Assert.notNull(targetBeanClazz, "TargetOptionsClazz must not be null");
if (sourceBean == null) {
return null;
}
if (sourceBean.getClass().isAssignableFrom(targetBeanClazz)) {
return (T) sourceBean;
}
try {
T targetOptions = targetBeanClazz.getConstructor().newInstance();
ModelOptionsUtils.mergeBeans(sourceBean, targetOptions, sourceInterfaceClazz, true);
return targetOptions;
}
catch (Exception e) {
throw new RuntimeException(
"Failed to convert the " + sourceInterfaceClazz.getName() + " into " + targetBeanClazz.getName(),
e);
}
}
/**
* Merges the source object into the target object. The source null values are
* ignored. Only objects with Getter and Setter methods are supported.
* @param <T> the type of the source and target object.
* @param source the source object to merge.
* @param target the target object to merge into.
* @param sourceInterfaceClazz the source interface class. Only the fields with the
* same name as the interface methods are merged. This allow the source object to be a
* subclass of the source interface with additional, non-interface fields.
* @param overrideNonNullTargetValues if true, the source non-null values override the
* target values with the same field name. If false, the source non-null values are
* ignored.
* @return the merged target object.
*/
public static <I, S extends I, T extends S> T mergeBeans(S source, T target, Class<I> sourceInterfaceClazz,
boolean overrideNonNullTargetValues) {
Assert.notNull(source, "Source object must not be null");
Assert.notNull(target, "Target object must not be null");
BeanWrapper sourceBeanWrap = new BeanWrapperImpl(source);
BeanWrapper targetBeanWrap = new BeanWrapperImpl(target);
List<String> interfaceNames = Arrays.stream(sourceInterfaceClazz.getMethods()).map(m -> m.getName()).toList();
for (PropertyDescriptor descriptor : sourceBeanWrap.getPropertyDescriptors()) {
if (!BEAN_MERGE_FIELD_EXCISIONS.contains(descriptor.getName())
&& interfaceNames.contains(toGetName(descriptor.getName()))) {
String propertyName = descriptor.getName();
Object value = sourceBeanWrap.getPropertyValue(propertyName);
// Copy value to the target object
if (value != null) {
var targetValue = targetBeanWrap.getPropertyValue(propertyName);
if (targetValue == null || overrideNonNullTargetValues) {
targetBeanWrap.setPropertyValue(propertyName, value);
}
}
}
}
return target;
}
private static String toGetName(String name) {
return "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
}
/**
* Generates JSON Schema (version 2020_12) for the given class.
* @param clazz the class to generate JSON Schema for.
* @param toUpperCaseTypeValues if true, the type values are converted to upper case.
* @return the generated JSON Schema as a String.
*/
public static String getJsonSchema(Class<?> clazz, boolean toUpperCaseTypeValues) {
if (SCHEMA_GENERATOR_CACHE.get() == null) {
JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED);
Swagger2Module swaggerModule = new Swagger2Module();
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12,
OptionPreset.PLAIN_JSON)
.with(Option.EXTRA_OPEN_API_FORMAT_VALUES)
.with(Option.PLAIN_DEFINITION_KEYS)
.with(swaggerModule)
.with(jacksonModule);
SchemaGeneratorConfig config = configBuilder.build();
SchemaGenerator generator = new SchemaGenerator(config);
SCHEMA_GENERATOR_CACHE.compareAndSet(null, generator);
}
ObjectNode node = SCHEMA_GENERATOR_CACHE.get().generateSchema(clazz);
if (toUpperCaseTypeValues) { // Required for OpenAPI 3.0 (at least Vertex AI
// version of it).
toUpperCaseTypeValues(node);
}
return node.toPrettyString();
}
public static void toUpperCaseTypeValues(ObjectNode node) {
if (node == null) {
return;
}
if (node.isObject()) {
node.fields().forEachRemaining(entry -> {
JsonNode value = entry.getValue();
if (value.isObject()) {
toUpperCaseTypeValues((ObjectNode) value);
}
else if (value.isArray()) {
((ArrayNode) value).elements().forEachRemaining(element -> {
if (element.isObject() || element.isArray()) {
toUpperCaseTypeValues((ObjectNode) element);
}
});
}
else if (value.isTextual() && entry.getKey().equals("type")) {
String oldValue = ((ObjectNode) node).get("type").asText();
((ObjectNode) node).put("type", oldValue.toUpperCase());
}
});
}
else if (node.isArray()) {
node.elements().forEachRemaining(element -> {
if (element.isObject() || element.isArray()) {
toUpperCaseTypeValues((ObjectNode) element);
}
});
}
}
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
/**
* 表示对AI模型的请求的接口此接口封装了 与人工智能模型交互所需的必要信息包括指令或 输入通用类型T和附加模型选项它提供了一种标准化的方式
* 向人工智能模型发送请求确保包括所有必要的细节并且可以易于管理
*
* Interface representing a request to an AI model. This interface encapsulates the
* necessary information required to interact with an AI model, including instructions or
* inputs (of generic type T) and additional model options. It provides a standardized way
* to send requests to AI models, ensuring that all necessary details are included and can
* be easily managed.
*
* @param <T> the type of instructions or input required by the AI model
* @author Mark Pollack
* @since 0.8.0
*/
public interface ModelRequest<T> {
/**
* 检索AI模型所需的指令或输入 返回AI模型所需的指令或输入
*
* Retrieves the instructions or input required by the AI model.
* @return the instructions or input required by the AI model
*/
T getInstructions(); // required input
/**
* 检索人工智能模型交互的可自定义选项 返回AI模型交互的自定义选项
*
* Retrieves the customizable options for AI model interactions.
* @return the customizable options for AI model interactions
*/
ModelOptions getOptions();
}

View File

@ -1,62 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
import java.util.List;
/**
*
* 表示从AI模型接收到的响应的接口此接口提供 访问AI模型生成的主要结果或结果列表的方法以及 以及响应元数据它是封装和管理的标准化方式
* 人工智能模型的输出确保轻松检索和处理生成的信息
*
* Interface representing the response received from an AI model. This interface provides
* methods to access the main result or a list of results generated by the AI model, along
* with the response metadata. It serves as a standardized way to encapsulate and manage
* the output from AI models, ensuring easy retrieval and processing of the generated
* information.
*
* @param <T> the type of the result(s) provided by the AI model
* @author Mark Pollack
* @since 0.8.0
*/
public interface ModelResponse<T extends ModelResult<?>> {
/**
* 检索AI模型的结果
*
* Retrieves the result of the AI model.
* @return the result generated by the AI model
*/
T getResult();
/**
* 检索AI模型生成的输出列表
*
* Retrieves the list of generated outputs by the AI model.
* @return the list of generated outputs
*/
List<T> getResults();
/**
* 检索与AI模型的响应相关联的响应元数据
*
* Retrieves the response metadata associated with the AI model's response.
* @return the response metadata
*/
ResponseMetadata getMetadata();
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
/**
* This interface provides methods to access the main output of the AI model and the
* metadata associated with this result. It is designed to offer a standardized and
* comprehensive way to handle and interpret the outputs generated by AI models, catering
* to diverse AI applications and use cases.
*
* @param <T> the type of the output generated by the AI model
* @author Mark Pollack
* @since 0.8.0
*/
public interface ModelResult<T> {
/**
* Retrieves the output generated by the AI model.
* @return the output generated by the AI model
*/
T getOutput();
/**
* Retrieves the metadata associated with the result of an AI model.
* @return the metadata associated with the result
*/
ResultMetadata getMetadata();
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
/**
* 表示与AI模型的响应相关联的元数据的接口此接口 旨在提供有关人工智能生成反应的附加信息 模型包括处理细节和模型特定数据它是一种价值
* 核心领域内的对象增强对人工智能模型的理解和管理 在各种应用中的响应
*
* Interface representing metadata associated with an AI model's response. This interface
* is designed to provide additional information about the generative response from an AI
* model, including processing details and model-specific data. It serves as a value
* object within the core domain, enhancing the understanding and management of AI model
* responses in various applications.
*
* @author Mark Pollack
* @since 0.8.0
*/
public interface ResponseMetadata {
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
/**
* Interface representing metadata associated with the results of an AI model. This
* interface focuses on providing additional context and insights into the results
* generated by AI models. It could include information like computation time, model
* version, or other relevant details that enhance understanding and management of AI
* model outputs in various applications.
*
* @author Mark Pollack
* @since 0.8.0
*/
public interface ResultMetadata {
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model;
import reactor.core.publisher.Flux;
/**
* The StreamingModelClient interface provides a generic API for invoking a AI models with
* streaming response. It abstracts the process of sending requests and receiving a
* streaming responses. The interface uses Java generics to accommodate different types of
* requests and responses, enhancing flexibility and adaptability across different AI
* model implementations.
*
* @param <TReq> the generic type of the request to the AI model
* @param <TResChunk> the generic type of a single item in the streaming response from the
* AI model
* @author Christian Tzolov
* @since 0.8.0
*/
public interface StreamingModelClient<TReq extends ModelRequest<?>, TResChunk extends ModelResponse<?>> {
/**
* Executes a method call to the AI model.
* @param request the request object to be sent to the AI model
* @return the streaming response from the AI model
*/
Flux<TResChunk> stream(TReq request);
}

View File

@ -1,163 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Christian Tzolov
*/
public abstract class AbstractFunctionCallSupport<Msg, Req, Resp> {
protected final static boolean IS_RUNTIME_CALL = true;
/**
* 函数回调寄存器用于按名称解析函数回调
*
* The function callback register is used to resolve the function callbacks by name.
*/
protected final Map<String, FunctionCallback> functionCallbackRegister = new ConcurrentHashMap<>();
/**
* 函数回调上下文用于按名称解析函数回调来自Spring上下文
* 它是可选的通常与Spring一起使用自动配置
*
* The function callback context is used to resolve the function callbacks by name
* from the Spring context. It is optional and usually used with Spring
* auto-configuration.
*/
protected final FunctionCallbackContext functionCallbackContext;
public AbstractFunctionCallSupport(FunctionCallbackContext functionCallbackContext) {
this.functionCallbackContext = functionCallbackContext;
}
public Map<String, FunctionCallback> getFunctionCallbackRegister() {
return this.functionCallbackRegister;
}
protected Set<String> handleFunctionCallbackConfigurations(FunctionCallingOptions options, boolean isRuntimeCall) {
Set<String> functionToCall = new HashSet<>();
if (options != null) {
if (!CollectionUtils.isEmpty(options.getFunctionCallbacks())) {
options.getFunctionCallbacks().stream().forEach(functionCallback -> {
// Register the tool callback.
if (isRuntimeCall) {
this.functionCallbackRegister.put(functionCallback.getName(), functionCallback);
}
else {
this.functionCallbackRegister.putIfAbsent(functionCallback.getName(), functionCallback);
}
// Automatically enable the function, usually from prompt callback.
if (isRuntimeCall) {
functionToCall.add(functionCallback.getName());
}
});
}
// Add the explicitly enabled functions.
if (!CollectionUtils.isEmpty(options.getFunctions())) {
functionToCall.addAll(options.getFunctions());
}
}
return functionToCall;
}
/**
* Resolve the function callbacks by name. Retrieve them from the registry or try to
* resolve them from the Application Context.
* @param functionNames Name of function callbacks to retrieve.
* @return list of resolved FunctionCallbacks.
*/
protected List<FunctionCallback> resolveFunctionCallbacks(Set<String> functionNames) {
List<FunctionCallback> retrievedFunctionCallbacks = new ArrayList<>();
for (String functionName : functionNames) {
if (!this.functionCallbackRegister.containsKey(functionName)) {
if (this.functionCallbackContext != null) {
FunctionCallback functionCallback = this.functionCallbackContext.getFunctionCallback(functionName,
null);
if (functionCallback != null) {
this.functionCallbackRegister.put(functionName, functionCallback);
}
else {
throw new IllegalStateException(
"No function callback [" + functionName + "] fund in tht FunctionCallbackContext");
}
}
else {
throw new IllegalStateException("No function callback found for name: " + functionName);
}
}
FunctionCallback functionCallback = this.functionCallbackRegister.get(functionName);
retrievedFunctionCallbacks.add(functionCallback);
}
return retrievedFunctionCallbacks;
}
///
protected Resp callWithFunctionSupport(Req request) {
Resp response = this.doChatCompletion(request);
return this.handleFunctionCallOrReturn(request, response);
}
protected Resp handleFunctionCallOrReturn(Req request, Resp response) {
if (!this.isToolFunctionCall(response)) {
return response;
}
// The chat completion tool call requires the complete conversation
// history. Including the initial user message.
List<Msg> conversationHistory = new ArrayList<>();
conversationHistory.addAll(this.doGetUserMessages(request));
Msg responseMessage = this.doGetToolResponseMessage(response);
// Add the assistant response to the message conversation history.
conversationHistory.add(responseMessage);
Req newRequest = this.doCreateToolResponseRequest(request, responseMessage, conversationHistory);
return this.callWithFunctionSupport(newRequest);
}
abstract protected Req doCreateToolResponseRequest(Req previousRequest, Msg responseMessage,
List<Msg> conversationHistory);
abstract protected List<Msg> doGetUserMessages(Req request);
abstract protected Msg doGetToolResponseMessage(Resp response);
abstract protected Resp doChatCompletion(Req request);
abstract protected boolean isToolFunctionCall(Resp response);
}

View File

@ -1,159 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.Assert;
import java.util.function.Function;
/**
* Abstract implementation of the {@link FunctionCallback} for interacting with the
* Model's function calling protocol and a {@link Function} wrapping the interaction with
* the 3rd party service/function.
*
* Implement the {@code O apply(I request) } method to implement the interaction with the
* 3rd party service/function.
*
* The {@link #responseConverter} function is responsible to convert the 3rd party
* function's output type into a string expected by the LLM model.
*
* @param <I> the 3rd party service input type.
* @param <O> the 3rd party service output type.
* @author Christian Tzolov
*/
abstract class AbstractFunctionCallback<I, O> implements Function<I, O>, FunctionCallback {
private final String name;
private final String description;
private final Class<I> inputType;
private final String inputTypeSchema;
private final ObjectMapper objectMapper;
private final Function<O, String> responseConverter;
/**
* Constructs a new {@link AbstractFunctionCallback} with the given name, description,
* input type and default object mapper.
* @param name Function name. Should be unique within the ChatClient's function
* registry.
* @param description Function description. Used as a "system prompt" by the model to
* decide if the function should be called.
* @param inputTypeSchema Used to compute, the argument's Schema (such as JSON Schema
* or OpenAPI Schema)required by the Model's function calling protocol.
* @param inputType Used to compute, the argument's JSON schema required by the
* Model's function calling protocol.
* @param responseConverter Used to convert the function's output type to a string.
* @param objectMapper Used to convert the function's input and output types to and
* from JSON.
*/
protected AbstractFunctionCallback(String name, String description, String inputTypeSchema, Class<I> inputType,
Function<O, String> responseConverter, ObjectMapper objectMapper) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(description, "Description must not be null");
Assert.notNull(inputType, "InputType must not be null");
Assert.notNull(inputTypeSchema, "InputTypeSchema must not be null");
Assert.notNull(responseConverter, "ResponseConverter must not be null");
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.name = name;
this.description = description;
this.inputType = inputType;
this.inputTypeSchema = inputTypeSchema;
this.responseConverter = responseConverter;
this.objectMapper = objectMapper;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getDescription() {
return this.description;
}
@Override
public String getInputTypeSchema() {
return this.inputTypeSchema;
}
@Override
public String call(String functionArguments) {
// Convert the tool calls JSON arguments into a Java function request object.
I request = fromJson(functionArguments, inputType);
// extend conversation with function response.
return this.andThen(this.responseConverter).apply(request);
}
private <T> T fromJson(String json, Class<T> targetClass) {
try {
return this.objectMapper.readValue(json, targetClass);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((inputType == null) ? 0 : inputType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractFunctionCallback other = (AbstractFunctionCallback) obj;
if (name == null) {
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
if (description == null) {
if (other.description != null)
return false;
}
else if (!description.equals(other.description))
return false;
if (inputType == null) {
if (other.inputType != null)
return false;
}
else if (!inputType.equals(other.inputType))
return false;
return true;
}
}

View File

@ -1,60 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
/**
*
* 表示模型函数调用处理程序实现已向注册对触发函数调用的提示进行建模和调用
*
* https://blog.csdn.net/weixin_37546425/article/details/136402740
*
* https://www.163.com/dy/article/ICE2S20P05119NPR.html
*
* Represents a model function call handler. Implementations are registered with the
* Models and called on prompts that trigger the function call.
*
* @author Christian Tzolov
*/
public interface FunctionCallback {
/**
* @return Returns the Function name. Unique within the model.
*/
public String getName();
/**
* @return Returns the function description. This description is used by the model do
* decide if the function should be called or not.
*/
public String getDescription();
/**
* @return Returns the JSON schema of the function input type.
*/
public String getInputTypeSchema();
/**
* Called when a model detects and triggers a function call. The model is responsible
* to pass the function arguments in the pre-configured JSON schema format.
* @param functionInput JSON string with the function arguments to be passed to the
* function. The arguments are defined as JSON schema usually registered with the the
* model.
* @return String containing the function call response.
*/
public String call(String functionInput);
}

View File

@ -1,126 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import org.springframework.beans.BeansException;
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
import org.springframework.cloud.function.context.config.FunctionContextUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Description;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import java.lang.reflect.Type;
import java.util.function.Function;
/**
* A Spring {@link ApplicationContextAware} implementation that provides a way to retrieve
* a {@link Function} from the Spring context and wrap it into a {@link FunctionCallback}.
*
* The name of the function is determined by the bean name.
*
* The description of the function is determined by the following rules:
* <ul>
* <li>Provided as a default description</li>
* <li>Provided as a {@code @Description} annotation on the bean</li>
* <li>Provided as a {@code @JsonClassDescription} annotation on the input class</li>
* </ul>
*
* @author Christian Tzolov
* @author Christopher Smith
*/
public class FunctionCallbackContext implements ApplicationContextAware {
private GenericApplicationContext applicationContext;
private FunctionCallbackWrapper.Builder.SchemaType schemaType = FunctionCallbackWrapper.Builder.SchemaType.JSON_SCHEMA;
public void setSchemaType(FunctionCallbackWrapper.Builder.SchemaType schemaType) {
this.schemaType = schemaType;
}
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (GenericApplicationContext) applicationContext;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public FunctionCallback getFunctionCallback(@NonNull String beanName, @Nullable String defaultDescription) {
Type beanType = FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), beanName);
if (beanType == null) {
throw new IllegalArgumentException(
"Functional bean with name: " + beanName + " does not exist in the context.");
}
if (!Function.class.isAssignableFrom(FunctionTypeUtils.getRawType(beanType))) {
throw new IllegalArgumentException(
"Function call Bean must be of type Function. Found: " + beanType.getTypeName());
}
Type functionInputType = TypeResolverHelper.getFunctionArgumentType(beanType, 0);
Class<?> functionInputClass = FunctionTypeUtils.getRawType(functionInputType);
String functionName = beanName;
String functionDescription = defaultDescription;
if (!StringUtils.hasText(functionDescription)) {
// Look for a Description annotation on the bean
Description descriptionAnnotation = applicationContext.findAnnotationOnBean(beanName, Description.class);
if (descriptionAnnotation != null) {
functionDescription = descriptionAnnotation.value();
}
if (!StringUtils.hasText(functionDescription)) {
// Look for a JsonClassDescription annotation on the input class
JsonClassDescription jsonClassDescriptionAnnotation = functionInputClass
.getAnnotation(JsonClassDescription.class);
if (jsonClassDescriptionAnnotation != null) {
functionDescription = jsonClassDescriptionAnnotation.value();
}
}
if (!StringUtils.hasText(functionDescription)) {
throw new IllegalStateException("Could not determine function description."
+ "Please provide a description either as a default parameter, via @Description annotation on the bean "
+ "or @JsonClassDescription annotation on the input class.");
}
}
Object bean = this.applicationContext.getBean(beanName);
// TODO: 2024/3/16 fansili 适配jdk8
return null;
// if (bean instanceof Function<?, ?> function) {
// return FunctionCallbackWrapper.builder(function)
// .withName(functionName)
// .withSchemaType(this.schemaType)
// .withDescription(functionDescription)
// .withInputType(functionInputClass)
// .build();
// }
// else {
// throw new IllegalArgumentException("Bean must be of type Function");
// }
}
}

View File

@ -1,140 +0,0 @@
package org.springframework.ai.model.function;
import org.springframework.ai.model.ModelOptionsUtils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.Assert;
import java.util.function.Function;
/**
* Note that the underlying function is responsible for converting the output into format
* that can be consumed by the Model. The default implementation converts the output into
* String before sending it to the Model. Provide a custom function responseConverter
* implementation to override this.
*
*/
public class FunctionCallbackWrapper<I, O> extends AbstractFunctionCallback<I, O> {
private final Function<I, O> function;
private FunctionCallbackWrapper(String name, String description, String inputTypeSchema, Class<I> inputType,
Function<O, String> responseConverter, Function<I, O> function) {
super(name, description, inputTypeSchema, inputType, responseConverter,
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false));
Assert.notNull(function, "Function must not be null");
this.function = function;
}
@SuppressWarnings("unchecked")
private static <I, O> Class<I> resolveInputType(Function<I, O> function) {
return (Class<I>) TypeResolverHelper.getFunctionInputClass((Class<Function<I, O>>) function.getClass());
}
@Override
public O apply(I input) {
return this.function.apply(input);
}
public static <I, O> Builder<I, O> builder(Function<I, O> function) {
return new Builder<>(function);
}
public static class Builder<I, O> {
public enum SchemaType {
JSON_SCHEMA, OPEN_API_SCHEMA
}
private String name;
private String description;
private Class<I> inputType;
private final Function<I, O> function;
private SchemaType schemaType = SchemaType.JSON_SCHEMA;
public Builder(Function<I, O> function) {
Assert.notNull(function, "Function must not be null");
this.function = function;
}
// By default the response is converted to a JSON string.
private Function<O, String> responseConverter = (response) -> ModelOptionsUtils.toJsonString(response);
private String inputTypeSchema;
private ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
public Builder<I, O> withName(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
return this;
}
public Builder<I, O> withDescription(String description) {
Assert.hasText(description, "Description must not be empty");
this.description = description;
return this;
}
@SuppressWarnings("unchecked")
public Builder<I, O> withInputType(Class<?> inputType) {
this.inputType = (Class<I>) inputType;
return this;
}
public Builder<I, O> withResponseConverter(Function<O, String> responseConverter) {
Assert.notNull(responseConverter, "ResponseConverter must not be null");
this.responseConverter = responseConverter;
return this;
}
public Builder<I, O> withInputTypeSchema(String inputTypeSchema) {
Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty");
this.inputTypeSchema = inputTypeSchema;
return this;
}
public Builder<I, O> withObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
return this;
}
public Builder<I, O> withSchemaType(SchemaType schemaType) {
Assert.notNull(schemaType, "SchemaType must not be null");
this.schemaType = schemaType;
return this;
}
public FunctionCallbackWrapper<I, O> build() {
Assert.hasText(this.name, "Name must not be empty");
Assert.hasText(this.description, "Description must not be empty");
// Assert.notNull(this.inputType, "InputType must not be null");
Assert.notNull(this.function, "Function must not be null");
Assert.notNull(this.responseConverter, "ResponseConverter must not be null");
Assert.notNull(this.objectMapper, "ObjectMapper must not be null");
if (this.inputType == null) {
this.inputType = resolveInputType(this.function);
}
if (this.inputTypeSchema == null) {
boolean upperCaseTypeValues = this.schemaType == SchemaType.OPEN_API_SCHEMA;
this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues);
}
return new FunctionCallbackWrapper<>(this.name, this.description, this.inputTypeSchema, this.inputType,
this.responseConverter, this.function);
}
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import java.util.List;
import java.util.Set;
/**
* @author Christian Tzolov
*/
public interface FunctionCallingOptions {
/**
* Function Callbacks to be registered with the ChatClient. For Prompt Options the
* functionCallbacks are automatically enabled for the duration of the prompt
* execution. For Default Options the FunctionCallbacks are registered but disabled by
* default. You have to use "functions" property to list the function names from the
* ChatClient registry to be used in the chat completion requests.
* @return Return the Function Callbacks to be registered with the ChatClient.
*/
List<FunctionCallback> getFunctionCallbacks();
/**
* Set the Function Callbacks to be registered with the ChatClient.
* @param functionCallbacks the Function Callbacks to be registered with the
* ChatClient.
*/
void setFunctionCallbacks(List<FunctionCallback> functionCallbacks);
/**
* @return List of function names from the ChatClient registry to be used in the next
* chat completion requests.
*/
Set<String> getFunctions();
/**
* Set the list of function names from the ChatClient registry to be used in the next
* chat completion requests.
* @param functions the list of function names from the ChatClient registry to be used
* in the next chat completion requests.
*/
void setFunctions(Set<String> functions);
/**
* @return Returns FunctionCallingOptionsBuilder to create a new instance of
* FunctionCallingOptions.
*/
public static FunctionCallingOptionsBuilder builder() {
return new FunctionCallingOptionsBuilder();
}
}

View File

@ -1,150 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Builder for {@link FunctionCallingOptions}. Using the {@link FunctionCallingOptions}
* permits options portability between different AI providers that support
* function-calling.
*
* @author Christian Tzolov
* @since 0.8.1
*/
public class FunctionCallingOptionsBuilder {
private final PortableFunctionCallingOptions options;
public FunctionCallingOptionsBuilder() {
this.options = new PortableFunctionCallingOptions();
}
public FunctionCallingOptionsBuilder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
this.options.setFunctionCallbacks(functionCallbacks);
return this;
}
public FunctionCallingOptionsBuilder withFunctionCallback(FunctionCallback functionCallback) {
Assert.notNull(functionCallback, "FunctionCallback must not be null");
this.options.getFunctionCallbacks().add(functionCallback);
return this;
}
public FunctionCallingOptionsBuilder withFunctions(Set<String> functions) {
this.options.setFunctions(functions);
return this;
}
public FunctionCallingOptionsBuilder withFunction(String function) {
Assert.notNull(function, "Function must not be null");
this.options.getFunctions().add(function);
return this;
}
public FunctionCallingOptionsBuilder withTemperature(Float temperature) {
this.options.setTemperature(temperature);
return this;
}
public FunctionCallingOptionsBuilder withTopP(Float topP) {
this.options.setTopP(topP);
return this;
}
public FunctionCallingOptionsBuilder withTopK(Integer topK) {
this.options.setTopK(topK);
return this;
}
public PortableFunctionCallingOptions build() {
return this.options;
}
public static class PortableFunctionCallingOptions implements FunctionCallingOptions, ChatOptions {
private List<FunctionCallback> functionCallbacks = new ArrayList<>();
private Set<String> functions = new HashSet<>();
private Float temperature;
private Float topP;
private Integer topK;
@Override
public List<FunctionCallback> getFunctionCallbacks() {
return this.functionCallbacks;
}
@Override
public void setFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null");
this.functionCallbacks = functionCallbacks;
}
@Override
public Set<String> getFunctions() {
return this.functions;
}
@Override
public void setFunctions(Set<String> functions) {
Assert.notNull(functions, "Functions must not be null");
this.functions = functions;
}
@Override
public Float getTemperature() {
return this.temperature;
}
@Override
public void setTemperature(Float temperature) {
this.temperature = temperature;
}
@Override
public Float getTopP() {
return this.topP;
}
@Override
public void setTopP(Float topP) {
this.topP = topP;
}
@Override
public Integer getTopK() {
return this.topK;
}
@Override
public void setTopK(Integer topK) {
this.topK = topK;
}
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import net.jodah.typetools.TypeResolver;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.Function;
/**
* @author Christian Tzolov
*/
public class TypeResolverHelper {
public static Class<?> getFunctionInputClass(Class<? extends Function<?, ?>> functionClass) {
return getFunctionArgumentClass(functionClass, 0);
}
public static Class<?> getFunctionOutputClass(Class<? extends Function<?, ?>> functionClass) {
return getFunctionArgumentClass(functionClass, 1);
}
public static Class<?> getFunctionArgumentClass(Class<? extends Function<?, ?>> functionClass, int argumentIndex) {
Type type = TypeResolver.reify(Function.class, functionClass);
var argumentType = type instanceof ParameterizedType
? ((ParameterizedType) type).getActualTypeArguments()[argumentIndex] : Object.class;
return toRawClass(argumentType);
}
public static Type getFunctionInputType(Class<? extends Function<?, ?>> functionClass) {
return getFunctionArgumentType(functionClass, 0);
}
public static Type getFunctionOutputType(Class<? extends Function<?, ?>> functionClass) {
return getFunctionArgumentType(functionClass, 1);
}
public static Type getFunctionArgumentType(Class<? extends Function<?, ?>> functionClass, int argumentIndex) {
Type functionType = TypeResolver.reify(Function.class, functionClass);
return getFunctionArgumentType(functionType, argumentIndex);
}
public static Type getFunctionArgumentType(Type functionType, int argumentIndex) {
var argumentType = functionType instanceof ParameterizedType
? ((ParameterizedType) functionType).getActualTypeArguments()[argumentIndex] : Object.class;
return argumentType;
}
/**
* Effectively converts {@link Type} which could be {@link ParameterizedType} to raw
* Class (no generics).
* @param type actual {@link Type} instance
* @return instance of {@link Class} as raw representation of the provided
* {@link Type}
*/
public static Class<?> toRawClass(Type type) {
return type != null
? TypeResolver.resolveRawClass(type instanceof GenericArrayType ? type : TypeResolver.reify(type), null)
: null;
}
// public static void main(String[] args) {
// Class<? extends Function<?, ?>> clazz = MockWeatherService.class;
// System.out.println(getFunctionInputType(clazz));
// }
}

View File

@ -1,11 +0,0 @@
/**
* Provides a set of interfaces and classes for a generic API designed to interact with
* various AI models. This package includes interfaces for handling AI model calls,
* requests, responses, results, and associated metadata. It is designed to offer a
* flexible and adaptable framework for interacting with different types of AI models,
* abstracting the complexities involved in model invocation and result processing. The
* use of generics enhances the API's capability to work with a wide range of models,
* ensuring a broad applicability across diverse AI scenarios.
*
*/
package org.springframework.ai.model;

View File

@ -1,14 +1,14 @@
package org.springframework.ai.models.midjourney.api; package org.springframework.ai.models.midjourney.api;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.ai.models.midjourney.MidjourneyConfig; import org.springframework.ai.models.midjourney.MidjourneyConfig;
import org.springframework.ai.models.midjourney.api.req.AttachmentsReq; import org.springframework.ai.models.midjourney.api.req.AttachmentsReq;
import org.springframework.ai.models.midjourney.api.req.DescribeReq; import org.springframework.ai.models.midjourney.api.req.DescribeReq;
import org.springframework.ai.models.midjourney.api.req.ReRollReq; import org.springframework.ai.models.midjourney.api.req.ReRollReq;
import org.springframework.ai.models.midjourney.api.res.UploadAttachmentsRes; import org.springframework.ai.models.midjourney.api.res.UploadAttachmentsRes;
import org.springframework.ai.models.midjourney.util.MidjourneyUtil; import org.springframework.ai.models.midjourney.util.MidjourneyUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;

View File

@ -1,89 +0,0 @@
package org.springframework.ai.models.openai;
import cn.hutool.json.JSONUtil;
import org.springframework.ai.models.openai.api.OpenAiImageRequest;
import org.springframework.ai.models.openai.api.OpenAiImageResponse;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import io.netty.channel.ChannelOption;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
/**
* open ai image
* <p>
* author: fansili
* time: 2024/3/17 09:53
*/
@Slf4j
public class OpenAiImageApi {
private static final String DEFAULT_BASE_URL = "https://api.openai.com";
private String apiKey = "your-api-key";
// 发送请求 webClient
private final WebClient webClient;
private CloseableHttpClient httpclient = HttpClients.createDefault();
public OpenAiImageApi(String apiKey) {
this.apiKey = apiKey;
// 创建一个HttpClient实例并设置超时
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(300)) // 设置响应超时时间为30秒
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 * 100); // 设置连接超时为5秒
this.webClient = WebClient.builder()
.baseUrl(DEFAULT_BASE_URL)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
public OpenAiImageResponse createImage(OpenAiImageRequest request) {
HttpPost httpPost = new HttpPost();
httpPost.setURI(URI.create(DEFAULT_BASE_URL.concat("/v1/images/generations")));
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Authorization", "Bearer " + apiKey);
httpPost.setEntity(new StringEntity(JsonUtils.toJsonString(request), "UTF-8"));
CloseableHttpResponse response= null;
try {
response = httpclient.execute(httpPost);
HttpEntity entity = response.getEntity();
String resultJson = EntityUtils.toString(entity);
log.info("openai 图片生成结果: {}", resultJson);
return JSONUtil.toBean(resultJson, OpenAiImageResponse.class);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// String res = webClient.post()
// .uri(uriBuilder -> uriBuilder.path("/v1/images/generations").build())
// .header("Content-Type", "application/json")
// .header("Authorization", "Bearer " + apiKey)
// // 设置请求体这里假设jsonStr是一个JSON格式的字符串
// .body(BodyInserters.fromValue(JacksonUtil.toJson(request)))
// // 发送请求并获取响应体
// .retrieve()
// // 转换响应体为String类型
// .bodyToMono(String.class)
// .block();
}
}

View File

@ -1,101 +0,0 @@
package org.springframework.ai.models.openai;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import org.springframework.ai.chat.ChatException;
import org.springframework.ai.models.yiyan.exception.YiYanApiException;
import cn.iocoder.yudao.framework.ai.core.exception.AiException;
import org.springframework.ai.models.openai.api.OpenAiImageRequest;
import org.springframework.ai.models.openai.api.OpenAiImageResponse;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.ai.image.*;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.support.RetryTemplate;
import java.time.Duration;
/**
* open ai 生成 image
*
* author: fansili
* time: 2024/3/17 09:51
*/
@Slf4j
public class OpenAiImageClient implements ImageClient {
/**
* open image ai
*/
private OpenAiImageApi openAiImageApi;
/**
* 默认使用的 ImageOptions
*/
private OpenAiImageOptions defaultImageOptions;
public final RetryTemplate retryTemplate = RetryTemplate.builder()
// 最大重试次数 10
.maxAttempts(10)
.retryOn(YiYanApiException.class)
// 最大重试5次第一次间隔3000ms第二次3000ms * 2第三次3000ms * 3以此类推最大间隔3 * 60000ms
.exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000))
.withListener(new RetryListener() {
@Override
public <T extends Object, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
log.warn("重试异常:" + context.getRetryCount(), throwable);
};
})
.build();
public OpenAiImageClient(OpenAiImageApi openAiImageApi, OpenAiImageOptions defaultImageOptions) {
this.openAiImageApi = openAiImageApi;
this.defaultImageOptions = defaultImageOptions;
}
@Override
public ImageResponse call(ImagePrompt imagePrompt) {
return this.retryTemplate.execute(ctx -> {
OpenAiImageOptions openAiImageOptions = getOpenAiImageOptions(imagePrompt);
// 创建请求
OpenAiImageRequest request = new OpenAiImageRequest();
BeanUtil.copyProperties(openAiImageOptions, request);
request.setPrompt(imagePrompt.getInstructions().get(0).getText());
request.setModel(openAiImageOptions.getModel());
request.setStyle(openAiImageOptions.getStyle().getStyle());
request.setSize(openAiImageOptions.getSize());
// 发送请求
OpenAiImageResponse response = openAiImageApi.createImage(request);
if (response.getError() != null && !StrUtil.isBlank(response.getError().getMessage())) {
// code 错误没有编码就先根据 message 来进行判断
throw new AiException("openAi 图片生成失败! " + response.getError().getMessage());
}
return new ImageResponse(response.getData().stream().map(res -> {
byte[] bytes = HttpUtil.downloadBytes(res.getUrl());
String base64 = Base64.encode(bytes);
return new ImageGeneration(new Image(res.getUrl(), base64));
}).toList());
});
}
private @NotNull OpenAiImageOptions getOpenAiImageOptions(ImagePrompt imagePrompt) {
// 检查是否配置了 OpenAiImageOptions
if (defaultImageOptions == null && imagePrompt.getOptions() == null) {
throw new ChatException("OpenAiImageOptions 未配置参数!");
}
// 优先使用 request 中的 ImageOptions
ImageOptions useImageOptions = imagePrompt.getOptions() == null ? defaultImageOptions : imagePrompt.getOptions();
if (!(useImageOptions instanceof OpenAiImageOptions)) {
throw new ChatException("配置信息不正确,传入的必须是 OpenAiImageOptions!");
}
// 转换 OpenAiImageOptions
OpenAiImageOptions openAiImageOptions = (OpenAiImageOptions) useImageOptions;
return openAiImageOptions;
}
}

View File

@ -1,95 +0,0 @@
package org.springframework.ai.models.openai;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.models.openai.enums.OpenAiImageModelEnum;
import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum;
import lombok.Data;
import lombok.Getter;
import lombok.experimental.Accessors;
/**
* open ai 配置文件
*
* 文档地址https://platform.openai.com/docs/api-reference/images/create
*
* author: fansili
* time: 2024/3/17 09:53
*/
@Data
@Accessors(chain = true)
public class OpenAiImageOptions implements ImageOptions {
// 必填字段用于描述期望生成图像的文字说明对于dall-e-2模型最大长度为1000个字符对于dall-e-3模型最大长度为4000个字符
private String prompt;
// 可选字段默认为dall-e-2
// 指定用于生成图像的模型名称
private OpenAiImageModelEnum model = OpenAiImageModelEnum.DALL_E_2;
// 可选字段默认为1
// 生成图像的数量必须在1到10之间对于dall-e-3模型目前仅支持n=1
private Integer n = 1;
// 可选字段默认为standard
// 设置生成图像的质量hd质量将创建细节更丰富图像整体一致性更高的图片该参数仅对dall-e-3模型有效
private String quality = "standard";
// 可选字段默认为url
// 返回生成图像的格式必须是url或b64_json中的一种URL链接的有效期是从生成图像后开始计算的60分钟内有效
private String responseFormat = "url";
// 可选字段默认为1024x1024
// 生成图像的尺寸大小对于dall-e-2模型尺寸可为256x256, 512x512, 1024x1024对于dall-e-3模型尺寸可为1024x1024, 1792x1024, 1024x1792
private String size = "1024x1024";
// 可选字段默认为vivid
// 图像生成的风格可为vivid生动或natural自然vivid会使模型偏向生成超现实和戏剧性的图像而natural则会让模型产出更自然不那么超现实的图像该参数仅对dall-e-3模型有效
private OpenAiImageStyleEnum style = OpenAiImageStyleEnum.VIVID;
// 可选字段
// 代表您的终端用户的唯一标识符有助于OpenAI监控并检测滥用行为了解更多信息请参考官方文档
private String endUserId = "UID123456";
@Getter
public enum ResponseFormatEnum {
URL("url"),
BASE64("b64_json"),
;
ResponseFormatEnum(String value) {
this.value = value;
}
private String value;
}
//
// 适配 spring ai
@Override
public Integer getN() {
return this.n;
}
@Override
public String getModel() {
return this.model.getModel();
}
@Override
public Integer getWidth() {
return null;
}
@Override
public Integer getHeight() {
return null;
}
@Override
public String getResponseFormat() {
return this.responseFormat;
}
}

View File

@ -1,58 +0,0 @@
package org.springframework.ai.models.openai.api;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* open ai 配置文件
*
* 文档地址https://platform.openai.com/docs/api-reference/images/create
*
* author: fansili
* time: 2024/3/17 09:53
*/
@Data
@Accessors(chain = true)
public class OpenAiImageRequest {
// 必填字段用于描述期望生成图像的文字说明对于dall-e-2模型最大长度为1000个字符对于dall-e-3模型最大长度为4000个字符
@JsonProperty("prompt")
private String prompt;
// 可选字段默认为dall-e-2dall-e-3
// 指定用于生成图像的模型名称
@JsonProperty("model")
private String model = "dall-e-2";
// 可选字段默认为1
// 生成图像的数量必须在1到10之间对于dall-e-3模型目前仅支持n=1
@JsonProperty("n")
private Integer n = 1;
// 可选字段默认为standard
// 设置生成图像的质量hd质量将创建细节更丰富图像整体一致性更高的图片该参数仅对dall-e-3模型有效
@JsonProperty("quality")
private String quality = "standard";
// 可选字段默认为url
// 返回生成图像的格式必须是url或b64_json中的一种URL链接的有效期是从生成图像后开始计算的60分钟内有效
@JsonProperty("response_format")
private String responseFormat = "url";
// 可选字段默认为1024x1024
// 生成图像的尺寸大小对于dall-e-2模型尺寸可为256x256, 512x512, 1024x1024对于dall-e-3模型尺寸可为1024x1024, 1792x1024, 1024x1792
@JsonProperty("size")
private String size = "1024x1024";
// 可选字段默认为vivid
// 图像生成的风格可为vivid生动或natural自然vivid会使模型偏向生成超现实和戏剧性的图像而natural则会让模型产出更自然不那么超现实的图像该参数仅对dall-e-3模型有效
@JsonProperty("style")
private String style = "vivid";
// 可选字段
// 代表您的终端用户的唯一标识符有助于OpenAI监控并检测滥用行为了解更多信息请参考官方文档
@JsonProperty("user")
private String endUserId = "UID123123";
}

View File

@ -1,39 +0,0 @@
package org.springframework.ai.models.openai.api;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* image 返回
*
* author: fansili
* time: 2024/3/17 10:27
*/
@Data
@Accessors(chain = true)
public class OpenAiImageResponse {
private long created;
private List<Item> data;
private Error error;
@Data
@Accessors(chain = true)
public static class Item {
private String url;
private String b64_json;
}
@Data
@Accessors(chain = true)
public static class Error {
private String code;
private String message;
private String param;
private String type;
}
}

View File

@ -1,5 +1,6 @@
package org.springframework.ai.models.tongyi; package org.springframework.ai.models.tongyi;
import cn.iocoder.yudao.framework.ai.core.exception.ChatException;
import org.springframework.ai.chat.*; import org.springframework.ai.chat.*;
import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;

View File

@ -41,25 +41,25 @@ public class QianWenOptions implements ChatOptions {
return null; return null;
} }
@Override // @Override
public void setTemperature(Float temperature) { // public void setTemperature(Float temperature) {
//
} // }
//
@Override // @Override
public void setTopP(Float topP) { // public void setTopP(Float topP) {
this.topP = topP; // this.topP = topP;
} // }
@Override @Override
public Integer getTopK() { public Integer getTopK() {
return null; return null;
} }
@Override // @Override
public void setTopK(Integer topK) { // public void setTopK(Integer topK) {
//
} // }
@Data @Data
@Accessors @Accessors

View File

@ -2,6 +2,7 @@ package org.springframework.ai.models.xinghuo;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.exceptions.ExceptionUtil;
import cn.iocoder.yudao.framework.ai.core.exception.ChatException;
import org.springframework.ai.chat.*; import org.springframework.ai.chat.*;
import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;

View File

@ -49,28 +49,29 @@ public class XingHuoOptions implements ChatOptions {
return this.temperature; return this.temperature;
} }
@Override // @Override
public void setTemperature(Float temperature) { // public void setTemperature(Float temperature) {
this.temperature = temperature; // this.temperature = temperature;
} // }
@Override @Override
public Float getTopP() { public Float getTopP() {
return null; return null;
} }
@Override // @Override
public void setTopP(Float topP) { // public void setTopP(Float topP) {
//
} // }
@Override @Override
public Integer getTopK() { public Integer getTopK() {
return this.topK; return this.topK;
} }
@Override // @Override
public void setTopK(Integer topK) { // public void setTopK(Integer topK) {
this.topK = topK; // this.topK = topK;
} // }
} }

View File

@ -1,6 +1,7 @@
package org.springframework.ai.models.yiyan; package org.springframework.ai.models.yiyan;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.framework.ai.core.exception.ChatException;
import org.springframework.ai.chat.*; import org.springframework.ai.chat.*;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType; import org.springframework.ai.chat.messages.MessageType;

View File

@ -114,20 +114,20 @@ public class YiYanOptions implements ChatOptions {
return this.temperature; return this.temperature;
} }
@Override // @Override
public void setTemperature(Float temperature) { // public void setTemperature(Float temperature) {
this.temperature = temperature; // this.temperature = temperature;
} // }
@Override @Override
public Float getTopP() { public Float getTopP() {
return topP; return topP;
} }
@Override // @Override
public void setTopP(Float topP) { // public void setTopP(Float topP) {
this.topP = topP; // this.topP = topP;
} // }
// 百度么有 topK // 百度么有 topK
@ -136,7 +136,7 @@ public class YiYanOptions implements ChatOptions {
return null; return null;
} }
@Override // @Override
public void setTopK(Integer topK) { // public void setTopK(Integer topK) {
} // }
} }

View File

@ -1,42 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
import org.springframework.core.convert.support.DefaultConversionService;
/**
* Abstract {@link OutputParser} implementation that uses a pre-configured
* {@link DefaultConversionService} to convert the LLM output into the desired type
* format.
*
* @param <T> Specifies the desired response type.
* @author Mark Pollack
* @author Christian Tzolov
*/
public abstract class AbstractConversionServiceOutputParser<T> implements OutputParser<T> {
private final DefaultConversionService conversionService;
public AbstractConversionServiceOutputParser(DefaultConversionService conversionService) {
this.conversionService = conversionService;
}
public DefaultConversionService getConversionService() {
return conversionService;
}
}

View File

@ -1,41 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
import org.springframework.messaging.converter.MessageConverter;
/**
* Abstract {@link OutputParser} implementation that uses a pre-configured
* {@link MessageConverter} to convert the LLM output into the desired type format.
*
* @param <T> Specifies the desired response type.
* @author Mark Pollack
* @author Christian Tzolov
*/
public abstract class AbstractMessageConverterOutputParser<T> implements OutputParser<T> {
private MessageConverter messageConverter;
public AbstractMessageConverterOutputParser(MessageConverter messageConverter) {
this.messageConverter = messageConverter;
}
public MessageConverter getMessageConverter() {
return this.messageConverter;
}
}

View File

@ -1,171 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.github.victools.jsonschema.generator.SchemaGenerator;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfig;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
import com.github.victools.jsonschema.module.jackson.JacksonModule;
import java.util.Map;
import java.util.Objects;
import static com.github.victools.jsonschema.generator.OptionPreset.PLAIN_JSON;
import static com.github.victools.jsonschema.generator.SchemaVersion.DRAFT_2020_12;
/**
* An implementation of {@link OutputParser} that transforms the LLM output to a specific
* object type using JSON schema. This parser works by generating a JSON schema based on a
* given Java class, which is then used to validate and transform the LLM output into the
* desired type.
*
* @param <T> The target type to which the output will be converted.
* @author Mark Pollack
* @author Christian Tzolov
* @author Sebastian Ullrich
* @author Kirk Lund
*/
public class BeanOutputParser<T> implements OutputParser<T> {
/** Holds the generated JSON schema for the target type. */
private String jsonSchema;
/** The Java class representing the target type. */
@SuppressWarnings({ "FieldMayBeFinal", "rawtypes" })
private Class<T> clazz;
/** The object mapper used for deserialization and other JSON operations. */
@SuppressWarnings("FieldMayBeFinal")
private ObjectMapper objectMapper;
/**
* Constructor to initialize with the target type's class.
* @param clazz The target type's class.
*/
public BeanOutputParser(Class<T> clazz) {
this(clazz, null);
}
/**
* Constructor to initialize with the target type's class, a custom object mapper, and
* a line endings normalizer to ensure consistent line endings on any platform.
* @param clazz The target type's class.
* @param objectMapper Custom object mapper for JSON operations. endings.
*/
public BeanOutputParser(Class<T> clazz, ObjectMapper objectMapper) {
Objects.requireNonNull(clazz, "Java Class cannot be null;");
this.clazz = clazz;
this.objectMapper = objectMapper != null ? objectMapper : getObjectMapper();
generateSchema();
}
/**
* Generates the JSON schema for the target type.
*/
private void generateSchema() {
JacksonModule jacksonModule = new JacksonModule();
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(DRAFT_2020_12, PLAIN_JSON)
.with(jacksonModule);
SchemaGeneratorConfig config = configBuilder.build();
SchemaGenerator generator = new SchemaGenerator(config);
JsonNode jsonNode = generator.generateSchema(this.clazz);
ObjectWriter objectWriter = new ObjectMapper()
.writer(new DefaultPrettyPrinter().withObjectIndenter(new DefaultIndenter().withLinefeed("\n")));
try {
this.jsonSchema = objectWriter.writeValueAsString(jsonNode);
}
catch (JsonProcessingException e) {
throw new RuntimeException("Could not pretty print json schema for " + this.clazz, e);
}
}
@Override
/**
* Parses the given text to transform it to the desired target type.
* @param text The LLM output in string format.
* @return The parsed output in the desired target type.
*/
public T parse(String text) {
try {
// If the response is a JSON Schema, extract the properties and use them as
// the response.
text = this.jsonSchemaToInstance(text);
return (T) this.objectMapper.readValue(text, this.clazz);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* Converts a JSON Schema to an instance based on a given text.
* @param text The JSON Schema in string format.
* @return The JSON instance generated from the JSON Schema, or the original text if
* the input is not a JSON Schema.
*/
private String jsonSchemaToInstance(String text) {
try {
Map<String, Object> map = this.objectMapper.readValue(text, Map.class);
if (map.containsKey("$schema")) {
return this.objectMapper.writeValueAsString(map.get("properties"));
}
}
catch (Exception e) {
}
return text;
}
/**
* Configures and returns an object mapper for JSON operations.
* @return Configured object mapper.
*/
protected ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
/**
* Provides the expected format of the response, instructing that it should adhere to
* the generated JSON schema.
* @return The instruction format string.
*/
@Override
public String getFormat() {
// String template = """
// Your response should be in JSON format.
// Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
// Do not include markdown code blocks in your response.
// Here is the JSON Schema instance your output must adhere to:
// ```%s```
// """;
String template = "Your response should be in JSON format.\n" +
"\t\t\t\tDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\n" +
"\t\t\t\tDo not include markdown code blocks in your response.\n" +
"\t\t\t\tHere is the JSON Schema instance your output must adhere to:\n" +
"\t\t\t\t```%s```";
return String.format(template, this.jsonSchema);
}
}

View File

@ -1,33 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
/**
* Implementations of this interface provides instructions for how the output of a
* language generative should be formatted.
*
* @author Mark Pollack
*/
public interface FormatProvider {
/**
* @return Returns a string containing instructions for how the output of a language
* generative should be formatted.
*/
String getFormat();
}

View File

@ -1,50 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
import org.springframework.core.convert.support.DefaultConversionService;
import java.util.List;
/**
* {@link OutputParser} implementation that uses a {@link DefaultConversionService} to
* convert the LLM output into a {@link List} instance.
*
* @author Mark Pollack
* @author Christian Tzolov
*/
public class ListOutputParser extends AbstractConversionServiceOutputParser<List<String>> {
public ListOutputParser(DefaultConversionService defaultConversionService) {
super(defaultConversionService);
}
@Override
public String getFormat() {
// return """
// Your response should be a list of comma separated values
// eg: `foo, bar, baz`
// """;
return "Your response should be a list of comma separated values\n" +
"\t\t\t\teg: `foo, bar, baz`";
}
@Override
public List<String> parse(String text) {
return getConversionService().convert(text, List.class);
}
}

View File

@ -1,60 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.support.MessageBuilder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* {@link OutputParser} implementation that uses a pre-configured
* {@link MappingJackson2MessageConverter} to convert the LLM output into a
* java.util.Map&lt;String, Object&gt; instance.
*
* @author Mark Pollack
* @author Christian Tzolov
*/
public class MapOutputParser extends AbstractMessageConverterOutputParser<Map<String, Object>> {
public MapOutputParser() {
super(new MappingJackson2MessageConverter());
}
@Override
public Map<String, Object> parse(String text) {
Message<?> message = MessageBuilder.withPayload(text.getBytes(StandardCharsets.UTF_8)).build();
return (Map) getMessageConverter().fromMessage(message, HashMap.class);
}
@Override
public String getFormat() {
// String raw = """
// Your response should be in JSON format.
// The data structure for the JSON should match this Java class: %s
// Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
// """;
String raw = "Your response should be in JSON format.\n" +
"\t\t\t\tThe data structure for the JSON should match this Java class: %s\n" +
"\t\t\t\tDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.";
return String.format(raw, "java.util.HashMap");
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
/**
* Converts the (raw) LLM output into a structured responses of type. The
* {@link FormatProvider#getFormat()} method should provide the LLM prompt description of
* the desired format.
*
* @param <T> Specifies the desired response type.
* @author Mark Pollack
* @author Christian Tzolov
*/
public interface OutputParser<T> extends Parser<T>, FormatProvider {
}

View File

@ -1,24 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.parser;
@FunctionalInterface
public interface Parser<T> {
T parse(String text);
}

View File

@ -1,12 +0,0 @@
# Output Parsing
* [Documentation](https://docs.spring.io/spring-ai/reference/concepts.html#_output_parsing)
* [Usage examples](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIT.java)
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 isnt a JSON data structure. It is just a string. Also, asking "for JSON" as part of the prompt isnt 100% accurate.
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.
Output parsing employs meticulously crafted prompts, often necessitating multiple interactions with the model to achieve the desired formatting.
This challenge has prompted OpenAI to introduce 'OpenAI Functions' as a means to specify the desired output format from the model precisely.

View File

@ -37,8 +37,8 @@ public class QianWenChatClientTests {
QianWenApi qianWenApi = new QianWenApi("sk-Zsd81gZYg7", QianWenChatModal.QWEN_72B_CHAT); QianWenApi qianWenApi = new QianWenApi("sk-Zsd81gZYg7", QianWenChatModal.QWEN_72B_CHAT);
QianWenOptions qianWenOptions = new QianWenOptions(); QianWenOptions qianWenOptions = new QianWenOptions();
qianWenOptions.setTopP(0.8F); qianWenOptions.setTopP(0.8F);
qianWenOptions.setTopK(3); // qianWenOptions.setTopK(3); TODO 芋艿临时处理
qianWenOptions.setTemperature(0.6F); // qianWenOptions.setTemperature(0.6F); TODO 芋艿临时处理
qianWenChatClient = new QianWenChatClient( qianWenChatClient = new QianWenChatClient(
qianWenApi, qianWenApi,
qianWenOptions qianWenOptions

View File

@ -1,12 +1,12 @@
package cn.iocoder.yudao.framework.ai.openAiImage; package cn.iocoder.yudao.framework.ai.openAiImage;
import org.springframework.ai.models.openai.OpenAiImageApi;
import org.springframework.ai.models.openai.OpenAiImageClient;
import org.springframework.ai.models.openai.OpenAiImageOptions;
import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse; import org.springframework.ai.image.ImageResponse;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.ai.openai.OpenAiImageClient;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.openai.api.OpenAiImageApi;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.*;
@ -29,8 +29,8 @@ public class OpenAiImageClientTests {
public void setup() { public void setup() {
// 初始化 openAiImageClient // 初始化 openAiImageClient
this.openAiImageClient = new OpenAiImageClient( this.openAiImageClient = new OpenAiImageClient(
new OpenAiImageApi(""), new OpenAiImageApi("")
new OpenAiImageOptions().setResponseFormat(OpenAiImageOptions.ResponseFormatEnum.URL.getValue()) // new OpenAiImageOptions().setResponseFormat(OpenAiImageOptions.ResponseFormatEnum.URL.getValue()) TODO 芋艿临时处理
); );
} }