001package org.nasdanika.ai; 002 003import java.awt.image.BufferedImage; 004import java.io.ByteArrayOutputStream; 005import java.io.File; 006import java.io.IOException; 007import java.io.InputStream; 008import java.lang.module.ModuleDescriptor.Version; 009import java.net.URL; 010import java.time.Duration; 011import java.util.ArrayList; 012import java.util.Arrays; 013import java.util.Collections; 014import java.util.Date; 015import java.util.List; 016import java.util.Optional; 017 018import javax.imageio.ImageIO; 019 020import reactor.core.publisher.Mono; 021 022public interface Chat extends Model { 023 024 public static final Chat ECHO = new Chat() { 025 026 @Override 027 public int getMaxInputTokens() { 028 return Integer.MAX_VALUE; 029 } 030 031 @Override 032 public String getProvider() { 033 return "Nasdanika"; 034 } 035 036 @Override 037 public String getName() { 038 return "Echo"; 039 } 040 041 @Override 042 public String getVersion() { 043 Optional<Version> moduleVersion = getClass().getModule().getDescriptor().version(); 044 return moduleVersion == null ? new Date().toString() : moduleVersion.toString(); 045 } 046 047 @Override 048 public Mono<List<? extends ResponseMessage>> chatAsync(List<Message> messages) { 049 List<? extends ResponseMessage> responseMessages = messages.stream().map(m -> new ResponseMessage() { 050 051 @Override 052 public String getRole() { 053 return Role.assistant.name(); 054 } 055 056 @Override 057 public String getContent() { 058 return m.getContent(); 059 } 060 061 @Override 062 public String getRefusal() { 063 return null; 064 } 065 066 @Override 067 public String getFinishReason() { 068 return "stop"; 069 } 070 }) 071 .toList(); 072 Mono<List<? extends ResponseMessage>> mono = Mono.just(responseMessages); 073 return mono.delayElement(Duration.ofSeconds(1)); 074 } 075 076 @Override 077 public int getMaxOutputTokens() { 078 return Integer.MAX_VALUE; 079 } 080 081 }; 082 083 public static final String LOREM_IPSUM_TEXT = 084 """ 085 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tempus ac nibh a convallis. 086 Phasellus tristique, ex ac maximus iaculis, nulla magna aliquam turpis, a vestibulum ligula metus varius lectus. 087 Duis rhoncus suscipit odio, vel porttitor metus egestas quis. Nunc quis tristique orci. 088 Phasellus tellus mi, pellentesque id aliquam a, eleifend nec nunc. Curabitur volutpat feugiat vestibulum. Maecenas sit amet dapibus velit. 089 Fusce scelerisque, nisl a fermentum vulputate, nisi enim hendrerit orci, a vestibulum sem leo at lacus. Duis vel rhoncus odio. 090 Nam facilisis est in ullamcorper consectetur. Donec aliquet velit quis dolor accumsan maximus. Aenean sodales mattis sem sed tincidunt. 091 Aliquam tristique augue nec tristique lobortis. Vivamus id metus in justo dignissim viverra. 092 Pellentesque ipsum lectus, ultricies in vehicula efficitur, aliquam vel purus. 093 Pellentesque lacus metus, vestibulum at convallis non, sodales sed ante. 094 095 Donec tincidunt elit eros, sit amet blandit ex posuere sed. 096 Pellentesque pharetra magna lacus, rhoncus placerat risus faucibus id. Duis non euismod turpis. 097 Vestibulum interdum dictum velit sit amet sagittis. Proin velit eros, interdum a nibh sed, viverra luctus ex. 098 Integer commodo diam id arcu varius pharetra. Proin porta justo lorem, quis sodales ex fermentum a. 099 Sed at consequat dolor. In quis consectetur leo, non aliquet mauris. 100 101 Aenean laoreet dui a facilisis efficitur. Maecenas consectetur ligula non magna porta congue. Etiam a ornare lectus. 102 Nulla at ligula et tortor mattis bibendum id quis sapien. Aenean eget condimentum enim. 103 Duis ullamcorper malesuada sapien et egestas. In hac habitasse platea dictumst. Quisque sed fermentum tortor. In vehicula auctor felis. 104 Donec fringilla turpis eget tortor lobortis posuere quis ac erat. Morbi mattis elementum felis, sit amet scelerisque lectus finibus a. 105 Donec ac est odio. Sed sollicitudin, arcu sit amet pulvinar pulvinar, lorem nisl malesuada odio, nec dapibus metus dui porta urna. 106 In viverra rhoncus est, sed interdum elit faucibus placerat. Proin ut ligula venenatis, bibendum massa in, fermentum sapien. 107 Mauris tempor eros ligula, id placerat eros vulputate quis. 108 109 Vestibulum quis dignissim urna. Pellentesque eros turpis, laoreet vel justo vel, tincidunt suscipit elit. Nullam eget cursus nulla. 110 Nulla pellentesque molestie sem, sed dictum lacus efficitur eu. Quisque orci lectus, egestas eget nunc quis, placerat blandit ligula. 111 Cras quis dictum sapien. Duis varius metus sapien, quis venenatis lectus dapibus ac. Sed eget sem ac leo elementum rhoncus. 112 Nunc dolor eros, dapibus a porta eget, vestibulum vel nunc. Aliquam erat volutpat. Nam imperdiet libero velit, vel ullamcorper nisi lacinia nec. 113 Suspendisse blandit dolor ut odio aliquam sodales. 114 115 In nec augue pulvinar, semper felis fringilla, vehicula dui. 116 Praesent vulputate tellus ac ante varius, sit amet ultricies felis ultrices. Pellentesque egestas lectus non nibh viverra, vitae tristique orci ultrices. 117 Donec non sem imperdiet neque varius sodales ut ut odio. Sed vehicula pellentesque nulla, ut dignissim turpis blandit non. Donec fermentum pellentesque nisi in facilisis. 118 Aenean mollis purus vel ex porttitor, sed bibendum ante mollis. Fusce molestie justo turpis, luctus sollicitudin augue pulvinar quis. 119 Nunc id tincidunt erat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. 120 Cras accumsan laoreet velit. Vestibulum sit amet nisl mauris. 121 """; 122 123 124 public static final Chat LOREM_IPSUM = new Chat() { 125 126 @Override 127 public int getMaxInputTokens() { 128 return Integer.MAX_VALUE; 129 } 130 131 @Override 132 public String getProvider() { 133 return "Nasdanika"; 134 } 135 136 @Override 137 public String getName() { 138 return "Lorem Impsum"; 139 } 140 141 @Override 142 public String getVersion() { 143 Optional<Version> moduleVersion = getClass().getModule().getDescriptor().version(); 144 return moduleVersion == null ? new Date().toString() : moduleVersion.toString(); 145 } 146 147 @Override 148 public Mono<List<? extends ResponseMessage>> chatAsync(List<Message> messages) { 149 Mono<List<? extends ResponseMessage>> ret = Mono.just(List.of(new ResponseMessage() { 150 151 @Override 152 public String getRole() { 153 return Role.assistant.name(); 154 } 155 156 @Override 157 public String getContent() { 158 return LOREM_IPSUM_TEXT; 159 } 160 161 @Override 162 public String getRefusal() { 163 return null; 164 } 165 166 @Override 167 public String getFinishReason() { 168 return "stop"; 169 } 170 })); 171 172 return ret.delayElement(Duration.ofSeconds(1)); 173 } 174 175 @Override 176 public int getMaxOutputTokens() { 177 return Integer.MAX_VALUE; 178 } 179 180 }; 181 182 183 /** 184 * Chat requirement. 185 * String attributes match any value if null. 186 */ 187 record Requirement( 188 String provider, 189 String model, 190 String version) {} 191 192 interface Message { 193 194 String getRole(); 195 196 String getContent(); 197 198 /** 199 * Images encoded as base64 url 200 * @return 201 */ 202 List<String> getImages(); 203 204 /** 205 * Adds an image encoded as base64 data URL 206 * @param dataUrl 207 * @return this message 208 */ 209 Message addImage(String dataUrl); 210 211 default Message addImage(File file) { 212 try { 213 return addImage(ImageIO.read(file)); 214 } catch (IOException e) { 215 throw new IllegalArgumentException("Cannot read image from file '" + file.getAbsolutePath() + "': " + e, e); 216 } 217 } 218 219 default Message addImage(InputStream inputStream) { 220 try { 221 return addImage(ImageIO.read(inputStream)); 222 } catch (IOException e) { 223 throw new IllegalArgumentException("Cannot read image from input stream: " + e, e); 224 } 225 } 226 227 default Message addImage(URL url) { 228 try { 229 return addImage(ImageIO.read(url)); 230 } catch (IOException e) { 231 throw new IllegalArgumentException("Cannot read image from URL '" + url + "': " + e, e); 232 } 233 } 234 235 default Message addImage(BufferedImage image) { 236 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 237 try { 238 try (baos) { 239 ImageIO.write(image, "PNG", baos); 240 } 241 String base64Image = java.util.Base64.getEncoder().encodeToString(baos.toByteArray()); 242 return addImage("data:image/png;base64," + base64Image); 243 } catch (Exception e) { 244 throw new IllegalArgumentException("Cannot write image: " + e, e); 245 } 246 247 } 248 249 /** 250 * Creates a message 251 * @param role 252 * @param content Message content. Can be null. 253 * @return 254 */ 255 static Message create(String role, String content) { 256 257 return new Message() { 258 259 @Override 260 public String getRole() { 261 return role; 262 } 263 264 @Override 265 public String getContent() { 266 return content; 267 } 268 269 private List<String> images = new ArrayList<>(); 270 271 @Override 272 public List<String> getImages() { 273 return Collections.unmodifiableList(images); 274 } 275 276 @Override 277 public Message addImage(String dataUrl) { 278 images.add(dataUrl); 279 return this; 280 } 281 282 }; 283 284 } 285 286 } 287 288 interface ResponseMessage extends Message { 289 290 String getRefusal(); 291 292 String getFinishReason(); 293 294 @Override 295 default Message addImage(String dataUrl) { 296 throw new UnsupportedOperationException(); 297 } 298 299 @Override 300 default List<String> getImages() { 301 return Collections.emptyList(); 302 } 303 304 } 305 306 enum Role { 307 308 system, 309 assistant, 310 user, 311 function, 312 tool, 313 developer; 314 315 public Message createMessage(String content) { 316 return Message.create(name(), content); 317 } 318 319 } 320 321 Mono<List<? extends ResponseMessage>> chatAsync(List<Message> messages); 322 323 default Mono<List<? extends ResponseMessage>> chatAsync(Message... messages) { 324 return chatAsync(Arrays.asList(messages)); 325 } 326 327 default List<? extends ResponseMessage> chat(List<Message> messages) { 328 return chatAsync(messages).block(); 329 } 330 331 default List<? extends ResponseMessage> chat(Message... messages) { 332 return chat(Arrays.asList(messages)); 333 } 334 335 int getMaxOutputTokens(); 336 337}