From 2ab9f320a331eaed0773866124b56a32494e2647 Mon Sep 17 00:00:00 2001 From: Peter Dolukhanov Date: Wed, 23 Apr 2025 12:57:08 -0700 Subject: [PATCH] Added a Developer message, as used by OpenAI replacing the system message. Signed-off-by: Peter Dolukhanov --- .../ai/chat/messages/DeveloperMessage.java | 127 ++++++++++++++++++ .../ai/chat/messages/MessageType.java | 7 + 2 files changed, 134 insertions(+) create mode 100644 spring-ai-model/src/main/java/org/springframework/ai/chat/messages/DeveloperMessage.java diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/DeveloperMessage.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/DeveloperMessage.java new file mode 100644 index 00000000000..f2b7a314592 --- /dev/null +++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/DeveloperMessage.java @@ -0,0 +1,127 @@ +/* + * Copyright 2023-2025 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.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * A message of the type 'developer' passed as input. The developer message gives + * instructions or requests from developers using the API. This role typically provides + * detailed instructions for the model to follow, such as specific actions or formats. + */ +public class DeveloperMessage extends AbstractMessage { + + public DeveloperMessage(String textContent) { + this(textContent, Map.of()); + } + + public DeveloperMessage(Resource resource) { + this(MessageUtils.readResource(resource), Map.of()); + } + + private DeveloperMessage(String textContent, Map metadata) { + super(MessageType.DEVELOPER, textContent, metadata); + } + + @Override + @NonNull + public String getText() { + return this.textContent; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DeveloperMessage that)) { + return false; + } + if (!super.equals(o)) { + return false; + } + return Objects.equals(this.textContent, that.textContent); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), this.textContent); + } + + @Override + public String toString() { + return "DeveloperMessage{" + "textContent='" + this.textContent + '\'' + ", messageType=" + this.messageType + + ", metadata=" + this.metadata + '}'; + } + + public DeveloperMessage copy() { + return new DeveloperMessage(getText(), Map.copyOf(this.metadata)); + } + + public Builder mutate() { + return new Builder().text(this.textContent).metadata(this.metadata); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + @Nullable + private String textContent; + + @Nullable + private Resource resource; + + private Map metadata = new HashMap<>(); + + public Builder text(String textContent) { + this.textContent = textContent; + return this; + } + + public Builder text(Resource resource) { + this.resource = resource; + return this; + } + + public Builder metadata(Map metadata) { + this.metadata = metadata; + return this; + } + + public DeveloperMessage build() { + if (StringUtils.hasText(textContent) && resource != null) { + throw new IllegalArgumentException("textContent and resource cannot be set at the same time"); + } + else if (resource != null) { + this.textContent = MessageUtils.readResource(resource); + } + return new DeveloperMessage(this.textContent, this.metadata); + } + + } + +} diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/MessageType.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/MessageType.java index 4ceb76b3a43..3a4b4eb0b0e 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/MessageType.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/MessageType.java @@ -44,6 +44,13 @@ public enum MessageType { */ SYSTEM("system"), + /** + * A {@link Message} of type {@literal developer} passed as input {@link Message} + * Messages containing instructions or requests from developers using the API. + * @see DeveloperMessage + */ + DEVELOPER("developer"), + /** * A {@link Message} of type {@literal function} passed as input {@link Message * Messages} with function content in a chat application.