Introduce checkstyle plugin
- Based on https://github.com/spring-io/spring-javaformat - In this iteration, checkstyles are only enabled for spring-ai-core
This commit is contained in:
committed by
Mark Pollack
parent
33a72417e1
commit
8e758dbd00
@@ -1,5 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -x
|
||||
|
||||
az extension add --name spring
|
||||
|
||||
@@ -8,3 +8,5 @@ indent_style = tab
|
||||
indent_size = 4
|
||||
continuation_indent_size = 8
|
||||
end_of_line = lf
|
||||
|
||||
insert_final_newline = true
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>fr.jcgay.maven</groupId>
|
||||
|
||||
27
.mvn/wrapper/maven-wrapper.properties
vendored
27
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,18 +1,17 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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
|
||||
# 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.
|
||||
#
|
||||
# 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.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
@@ -1,18 +1,45 @@
|
||||
package org.springframework.ai.reader.markdown;
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import org.commonmark.node.*;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.document.DocumentReader;
|
||||
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
package org.springframework.ai.reader.markdown;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.commonmark.node.AbstractVisitor;
|
||||
import org.commonmark.node.BlockQuote;
|
||||
import org.commonmark.node.Code;
|
||||
import org.commonmark.node.FencedCodeBlock;
|
||||
import org.commonmark.node.HardLineBreak;
|
||||
import org.commonmark.node.Heading;
|
||||
import org.commonmark.node.ListItem;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.SoftLineBreak;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.node.ThematicBreak;
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.document.DocumentReader;
|
||||
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Reads the given Markdown resource and groups headers, paragraphs, or text divided by
|
||||
* horizontal lines (depending on the
|
||||
@@ -58,10 +85,10 @@ public class MarkdownDocumentReader implements DocumentReader {
|
||||
*/
|
||||
@Override
|
||||
public List<Document> get() {
|
||||
try (var input = markdownResource.getInputStream()) {
|
||||
Node node = parser.parseReader(new InputStreamReader(input));
|
||||
try (var input = this.markdownResource.getInputStream()) {
|
||||
Node node = this.parser.parseReader(new InputStreamReader(input));
|
||||
|
||||
DocumentVisitor documentVisitor = new DocumentVisitor(config);
|
||||
DocumentVisitor documentVisitor = new DocumentVisitor(this.config);
|
||||
node.accept(documentVisitor);
|
||||
|
||||
return documentVisitor.getDocuments();
|
||||
@@ -90,7 +117,7 @@ public class MarkdownDocumentReader implements DocumentReader {
|
||||
|
||||
@Override
|
||||
public void visit(org.commonmark.node.Document document) {
|
||||
currentDocumentBuilder = Document.builder();
|
||||
this.currentDocumentBuilder = Document.builder();
|
||||
super.visit(document);
|
||||
}
|
||||
|
||||
@@ -102,7 +129,7 @@ public class MarkdownDocumentReader implements DocumentReader {
|
||||
|
||||
@Override
|
||||
public void visit(ThematicBreak thematicBreak) {
|
||||
if (config.horizontalRuleCreateDocument) {
|
||||
if (this.config.horizontalRuleCreateDocument) {
|
||||
buildAndFlush();
|
||||
}
|
||||
super.visit(thematicBreak);
|
||||
@@ -128,32 +155,32 @@ public class MarkdownDocumentReader implements DocumentReader {
|
||||
|
||||
@Override
|
||||
public void visit(BlockQuote blockQuote) {
|
||||
if (!config.includeBlockquote) {
|
||||
if (!this.config.includeBlockquote) {
|
||||
buildAndFlush();
|
||||
}
|
||||
|
||||
translateLineBreakToSpace();
|
||||
currentDocumentBuilder.withMetadata("category", "blockquote");
|
||||
this.currentDocumentBuilder.withMetadata("category", "blockquote");
|
||||
super.visit(blockQuote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Code code) {
|
||||
currentParagraphs.add(code.getLiteral());
|
||||
currentDocumentBuilder.withMetadata("category", "code_inline");
|
||||
this.currentParagraphs.add(code.getLiteral());
|
||||
this.currentDocumentBuilder.withMetadata("category", "code_inline");
|
||||
super.visit(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(FencedCodeBlock fencedCodeBlock) {
|
||||
if (!config.includeCodeBlock) {
|
||||
if (!this.config.includeCodeBlock) {
|
||||
buildAndFlush();
|
||||
}
|
||||
|
||||
translateLineBreakToSpace();
|
||||
currentParagraphs.add(fencedCodeBlock.getLiteral());
|
||||
currentDocumentBuilder.withMetadata("category", "code_block");
|
||||
currentDocumentBuilder.withMetadata("lang", fencedCodeBlock.getInfo());
|
||||
this.currentParagraphs.add(fencedCodeBlock.getLiteral());
|
||||
this.currentDocumentBuilder.withMetadata("category", "code_block");
|
||||
this.currentDocumentBuilder.withMetadata("lang", fencedCodeBlock.getInfo());
|
||||
|
||||
buildAndFlush();
|
||||
|
||||
@@ -163,11 +190,11 @@ public class MarkdownDocumentReader implements DocumentReader {
|
||||
@Override
|
||||
public void visit(Text text) {
|
||||
if (text.getParent() instanceof Heading heading) {
|
||||
currentDocumentBuilder.withMetadata("category", "header_%d".formatted(heading.getLevel()))
|
||||
this.currentDocumentBuilder.withMetadata("category", "header_%d".formatted(heading.getLevel()))
|
||||
.withMetadata("title", text.getLiteral());
|
||||
}
|
||||
else {
|
||||
currentParagraphs.add(text.getLiteral());
|
||||
this.currentParagraphs.add(text.getLiteral());
|
||||
}
|
||||
|
||||
super.visit(text);
|
||||
@@ -176,29 +203,29 @@ public class MarkdownDocumentReader implements DocumentReader {
|
||||
public List<Document> getDocuments() {
|
||||
buildAndFlush();
|
||||
|
||||
return documents;
|
||||
return this.documents;
|
||||
}
|
||||
|
||||
private void buildAndFlush() {
|
||||
if (!currentParagraphs.isEmpty()) {
|
||||
String content = String.join("", currentParagraphs);
|
||||
if (!this.currentParagraphs.isEmpty()) {
|
||||
String content = String.join("", this.currentParagraphs);
|
||||
|
||||
Document.Builder builder = currentDocumentBuilder.withContent(content);
|
||||
Document.Builder builder = this.currentDocumentBuilder.withContent(content);
|
||||
|
||||
config.additionalMetadata.forEach(builder::withMetadata);
|
||||
this.config.additionalMetadata.forEach(builder::withMetadata);
|
||||
|
||||
Document document = builder.build();
|
||||
|
||||
documents.add(document);
|
||||
this.documents.add(document);
|
||||
|
||||
currentParagraphs.clear();
|
||||
this.currentParagraphs.clear();
|
||||
}
|
||||
currentDocumentBuilder = Document.builder();
|
||||
this.currentDocumentBuilder = Document.builder();
|
||||
}
|
||||
|
||||
private void translateLineBreakToSpace() {
|
||||
if (!currentParagraphs.isEmpty()) {
|
||||
currentParagraphs.add(" ");
|
||||
if (!this.currentParagraphs.isEmpty()) {
|
||||
this.currentParagraphs.add(" ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
/*
|
||||
* 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.reader.markdown.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Common configuration for the {@link MarkdownDocumentReader}.
|
||||
*
|
||||
@@ -23,10 +39,10 @@ public class MarkdownDocumentReaderConfig {
|
||||
public final Map<String, Object> additionalMetadata;
|
||||
|
||||
public MarkdownDocumentReaderConfig(Builder builder) {
|
||||
horizontalRuleCreateDocument = builder.horizontalRuleCreateDocument;
|
||||
includeCodeBlock = builder.includeCodeBlock;
|
||||
includeBlockquote = builder.includeBlockquote;
|
||||
additionalMetadata = builder.additionalMetadata;
|
||||
this.horizontalRuleCreateDocument = builder.horizontalRuleCreateDocument;
|
||||
this.includeCodeBlock = builder.includeCodeBlock;
|
||||
this.includeBlockquote = builder.includeBlockquote;
|
||||
this.additionalMetadata = builder.additionalMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
package org.springframework.ai.reader.markdown;
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
|
||||
package org.springframework.ai.reader.markdown;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.groups.Tuple.tuple;
|
||||
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -24,9 +25,9 @@ import java.util.stream.Collectors;
|
||||
import org.apache.pdfbox.pdfparser.PDFParser;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.document.DocumentReader;
|
||||
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
|
||||
@@ -46,22 +47,22 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class PagePdfDocumentReader implements DocumentReader {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private static final String PDF_PAGE_REGION = "pdfPageRegion";
|
||||
|
||||
public static final String METADATA_START_PAGE_NUMBER = "page_number";
|
||||
|
||||
public static final String METADATA_END_PAGE_NUMBER = "end_page_number";
|
||||
|
||||
public static final String METADATA_FILE_NAME = "file_name";
|
||||
|
||||
private static final String PDF_PAGE_REGION = "pdfPageRegion";
|
||||
|
||||
protected final PDDocument document;
|
||||
|
||||
private PdfDocumentReaderConfig config;
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected String resourceFileName;
|
||||
|
||||
private PdfDocumentReaderConfig config;
|
||||
|
||||
public PagePdfDocumentReader(String resourceUrl) {
|
||||
this(new DefaultResourceLoader().getResource(resourceUrl));
|
||||
}
|
||||
@@ -103,15 +104,15 @@ public class PagePdfDocumentReader implements DocumentReader {
|
||||
|
||||
int totalPages = this.document.getDocumentCatalog().getPages().getCount();
|
||||
int logFrequency = totalPages > 10 ? totalPages / 10 : 1; // if less than 10
|
||||
// pages, print
|
||||
// each iteration
|
||||
// pages, print
|
||||
// each iteration
|
||||
int counter = 0;
|
||||
|
||||
PDPage lastPage = this.document.getDocumentCatalog().getPages().iterator().next();
|
||||
for (PDPage page : this.document.getDocumentCatalog().getPages()) {
|
||||
lastPage = page;
|
||||
if (counter % logFrequency == 0 && counter / logFrequency < 10) {
|
||||
logger.info("Processing PDF page: {}", (counter + 1));
|
||||
this.logger.info("Processing PDF page: {}", (counter + 1));
|
||||
}
|
||||
counter++;
|
||||
|
||||
@@ -153,7 +154,7 @@ public class PagePdfDocumentReader implements DocumentReader {
|
||||
readDocuments.add(toDocument(lastPage, pageTextGroupList.stream().collect(Collectors.joining()),
|
||||
startPageNumber, pageNumber));
|
||||
}
|
||||
logger.info("Processing {} pages", totalPages);
|
||||
this.logger.info("Processing {} pages", totalPages);
|
||||
return readDocuments;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,18 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdfparser.PDFParser;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.document.DocumentReader;
|
||||
import org.springframework.ai.reader.pdf.config.ParagraphManager;
|
||||
@@ -48,8 +49,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class ParagraphPdfDocumentReader implements DocumentReader {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
// Constants for metadata keys
|
||||
private static final String METADATA_START_PAGE = "page_number";
|
||||
|
||||
@@ -61,14 +60,16 @@ public class ParagraphPdfDocumentReader implements DocumentReader {
|
||||
|
||||
private static final String METADATA_FILE_NAME = "file_name";
|
||||
|
||||
private final ParagraphManager paragraphTextExtractor;
|
||||
|
||||
protected final PDDocument document;
|
||||
|
||||
private PdfDocumentReaderConfig config;
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final ParagraphManager paragraphTextExtractor;
|
||||
|
||||
protected String resourceFileName;
|
||||
|
||||
private PdfDocumentReaderConfig config;
|
||||
|
||||
/**
|
||||
* Constructs a ParagraphPdfDocumentReader using a resource URL.
|
||||
* @param resourceUrl The URL of the PDF resource.
|
||||
@@ -132,7 +133,7 @@ public class ParagraphPdfDocumentReader implements DocumentReader {
|
||||
List<Document> documents = new ArrayList<>(paragraphs.size());
|
||||
|
||||
if (!CollectionUtils.isEmpty(paragraphs)) {
|
||||
logger.info("Start processing paragraphs from PDF");
|
||||
this.logger.info("Start processing paragraphs from PDF");
|
||||
Iterator<Paragraph> itr = paragraphs.iterator();
|
||||
|
||||
var current = itr.next();
|
||||
@@ -151,7 +152,7 @@ public class ParagraphPdfDocumentReader implements DocumentReader {
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("End processing paragraphs from PDF");
|
||||
this.logger.info("End processing paragraphs from PDF");
|
||||
return documents;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,15 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf.aot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The PdfReaderRuntimeHints class is responsible for registering runtime hints for PDFBox
|
||||
* resources.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf.config;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -39,34 +40,6 @@ import org.springframework.util.CollectionUtils;
|
||||
*/
|
||||
public class ParagraphManager {
|
||||
|
||||
/**
|
||||
* Represents a document paragraph metadata and hierarchy.
|
||||
*
|
||||
* @param parent Parent paragraph that will contain a children paragraphs.
|
||||
* @param title Paragraph title as it appears in the PDF document.
|
||||
* @param level The TOC deepness level for this paragraph. The root is at level 0.
|
||||
* @param startPageNumber The page number in the PDF where this paragraph begins.
|
||||
* @param endPageNumber The page number in the PDF where this paragraph ends.
|
||||
* @param children Sub-paragraphs for this paragraph.
|
||||
*/
|
||||
public record Paragraph(Paragraph parent, String title, int level, int startPageNumber, int endPageNumber,
|
||||
int position, List<Paragraph> children) {
|
||||
|
||||
public Paragraph(Paragraph parent, String title, int level, int startPageNumber, int endPageNumber,
|
||||
int position) {
|
||||
this(parent, title, level, startPageNumber, endPageNumber, position, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String indent = (level < 0) ? "" : new String(new char[level * 2]).replace('\0', ' ');
|
||||
|
||||
return indent + " " + level + ") " + title + " [" + startPageNumber + "," + endPageNumber + "], children = "
|
||||
+ children.size() + ", pos = " + position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Root of the paragraphs tree.
|
||||
*/
|
||||
@@ -90,7 +63,7 @@ public class ParagraphManager {
|
||||
new Paragraph(null, "root", -1, 1, this.document.getNumberOfPages(), 0),
|
||||
this.document.getDocumentCatalog().getDocumentOutline(), 0);
|
||||
|
||||
printParagraph(rootParagraph, System.out);
|
||||
printParagraph(this.rootParagraph, System.out);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -203,4 +176,32 @@ public class ParagraphManager {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a document paragraph metadata and hierarchy.
|
||||
*
|
||||
* @param parent Parent paragraph that will contain a children paragraphs.
|
||||
* @param title Paragraph title as it appears in the PDF document.
|
||||
* @param level The TOC deepness level for this paragraph. The root is at level 0.
|
||||
* @param startPageNumber The page number in the PDF where this paragraph begins.
|
||||
* @param endPageNumber The page number in the PDF where this paragraph ends.
|
||||
* @param children Sub-paragraphs for this paragraph.
|
||||
*/
|
||||
public record Paragraph(Paragraph parent, String title, int level, int startPageNumber, int endPageNumber,
|
||||
int position, List<Paragraph> children) {
|
||||
|
||||
public Paragraph(Paragraph parent, String title, int level, int startPageNumber, int endPageNumber,
|
||||
int position) {
|
||||
this(parent, title, level, startPageNumber, endPageNumber, position, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String indent = (this.level < 0) ? "" : new String(new char[this.level * 2]).replace('\0', ' ');
|
||||
|
||||
return indent + " " + this.level + ") " + this.title + " [" + this.startPageNumber + ","
|
||||
+ this.endPageNumber + "], children = " + this.children.size() + ", pos = " + this.position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf.config;
|
||||
|
||||
import org.springframework.ai.reader.ExtractedTextFormatter;
|
||||
@@ -40,6 +41,14 @@ public class PdfDocumentReaderConfig {
|
||||
|
||||
public final ExtractedTextFormatter pageExtractedTextFormatter;
|
||||
|
||||
private PdfDocumentReaderConfig(PdfDocumentReaderConfig.Builder builder) {
|
||||
this.pagesPerDocument = builder.pagesPerDocument;
|
||||
this.pageBottomMargin = builder.pageBottomMargin;
|
||||
this.pageTopMargin = builder.pageTopMargin;
|
||||
this.pageExtractedTextFormatter = builder.pageExtractedTextFormatter;
|
||||
this.reversedParagraphPosition = builder.reversedParagraphPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building a new configuration.
|
||||
* @return The entry point for creating a new configuration.
|
||||
@@ -56,14 +65,6 @@ public class PdfDocumentReaderConfig {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
private PdfDocumentReaderConfig(PdfDocumentReaderConfig.Builder builder) {
|
||||
this.pagesPerDocument = builder.pagesPerDocument;
|
||||
this.pageBottomMargin = builder.pageBottomMargin;
|
||||
this.pageTopMargin = builder.pageTopMargin;
|
||||
this.pageExtractedTextFormatter = builder.pageExtractedTextFormatter;
|
||||
this.reversedParagraphPosition = builder.reversedParagraphPosition;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private int pagesPerDocument = 1;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -180,8 +180,9 @@ public class ForkPDFLayoutTextStripper extends PDFTextStripper {
|
||||
double height = textPosition.getHeight();
|
||||
int numberOfLines = (int) (Math.floor(textYPosition - previousTextYPosition) / height);
|
||||
numberOfLines = Math.max(1, numberOfLines - 1); // exclude current new line
|
||||
if (DEBUG)
|
||||
if (DEBUG) {
|
||||
System.out.println(height + " " + numberOfLines);
|
||||
}
|
||||
return numberOfLines;
|
||||
}
|
||||
else {
|
||||
@@ -191,7 +192,7 @@ public class ForkPDFLayoutTextStripper extends PDFTextStripper {
|
||||
|
||||
private TextLine addNewLine() {
|
||||
TextLine textLine = new TextLine(this.getCurrentPageWidth());
|
||||
textLineList.add(textLine);
|
||||
this.textLineList.add(textLine);
|
||||
return textLine;
|
||||
}
|
||||
|
||||
@@ -248,7 +249,7 @@ class TextLine {
|
||||
}
|
||||
|
||||
public String getLine() {
|
||||
return line;
|
||||
return this.line;
|
||||
}
|
||||
|
||||
private int computeIndexForCharacter(final Character character) {
|
||||
@@ -313,7 +314,7 @@ class TextLine {
|
||||
|
||||
private void completeLineWithSpaces() {
|
||||
for (int i = 0; i < this.getLineLength(); ++i) {
|
||||
line += SPACE_CHARACTER;
|
||||
this.line += SPACE_CHARACTER;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,8 +351,9 @@ class Character {
|
||||
this.isFirstCharacterOfAWord = isFirstCharacterOfAWord;
|
||||
this.isCharacterAtTheBeginningOfNewLine = isCharacterAtTheBeginningOfNewLine;
|
||||
this.isCharacterCloseToPreviousWord = isCharacterPartOfASentence;
|
||||
if (ForkPDFLayoutTextStripper.DEBUG)
|
||||
if (ForkPDFLayoutTextStripper.DEBUG) {
|
||||
System.out.println(this.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public char getCharacterValue() {
|
||||
@@ -384,14 +386,14 @@ class Character {
|
||||
|
||||
public String toString() {
|
||||
String toString = "";
|
||||
toString += index;
|
||||
toString += this.index;
|
||||
toString += " ";
|
||||
toString += characterValue;
|
||||
toString += " isCharacterPartOfPreviousWord=" + isCharacterPartOfPreviousWord;
|
||||
toString += " isFirstCharacterOfAWord=" + isFirstCharacterOfAWord;
|
||||
toString += " isCharacterAtTheBeginningOfNewLine=" + isCharacterAtTheBeginningOfNewLine;
|
||||
toString += " isCharacterPartOfASentence=" + isCharacterCloseToPreviousWord;
|
||||
toString += " isCharacterCloseToPreviousWord=" + isCharacterCloseToPreviousWord;
|
||||
toString += this.characterValue;
|
||||
toString += " isCharacterPartOfPreviousWord=" + this.isCharacterPartOfPreviousWord;
|
||||
toString += " isFirstCharacterOfAWord=" + this.isFirstCharacterOfAWord;
|
||||
toString += " isCharacterAtTheBeginningOfNewLine=" + this.isCharacterAtTheBeginningOfNewLine;
|
||||
toString += " isCharacterPartOfASentence=" + this.isCharacterCloseToPreviousWord;
|
||||
toString += " isCharacterCloseToPreviousWord=" + this.isCharacterCloseToPreviousWord;
|
||||
return toString;
|
||||
}
|
||||
|
||||
@@ -424,12 +426,12 @@ class CharacterFactory {
|
||||
this.isCharacterCloseToPreviousWord = this.isCharacterCloseToPreviousWord(textPosition);
|
||||
char character = this.getCharacterFromTextPosition(textPosition);
|
||||
int index = (int) textPosition.getX() / ForkPDFLayoutTextStripper.OUTPUT_SPACE_CHARACTER_WIDTH_IN_PT;
|
||||
return new Character(character, index, isCharacterPartOfPreviousWord, isFirstCharacterOfAWord,
|
||||
isCharacterAtTheBeginningOfNewLine, isCharacterCloseToPreviousWord);
|
||||
return new Character(character, index, this.isCharacterPartOfPreviousWord, this.isFirstCharacterOfAWord,
|
||||
this.isCharacterAtTheBeginningOfNewLine, this.isCharacterCloseToPreviousWord);
|
||||
}
|
||||
|
||||
private boolean isCharacterAtTheBeginningOfNewLine(final TextPosition textPosition) {
|
||||
if (!firstCharacterOfLineFound) {
|
||||
if (!this.firstCharacterOfLineFound) {
|
||||
return true;
|
||||
}
|
||||
TextPosition previousTextPosition = this.getPreviousTextPosition();
|
||||
@@ -438,18 +440,18 @@ class CharacterFactory {
|
||||
}
|
||||
|
||||
private boolean isFirstCharacterOfAWord(final TextPosition textPosition) {
|
||||
if (!firstCharacterOfLineFound) {
|
||||
if (!this.firstCharacterOfLineFound) {
|
||||
return true;
|
||||
}
|
||||
double numberOfSpaces = this.numberOfSpacesBetweenTwoCharacters(previousTextPosition, textPosition);
|
||||
double numberOfSpaces = this.numberOfSpacesBetweenTwoCharacters(this.previousTextPosition, textPosition);
|
||||
return (numberOfSpaces > 1) || this.isCharacterAtTheBeginningOfNewLine(textPosition);
|
||||
}
|
||||
|
||||
private boolean isCharacterCloseToPreviousWord(final TextPosition textPosition) {
|
||||
if (!firstCharacterOfLineFound) {
|
||||
if (!this.firstCharacterOfLineFound) {
|
||||
return false;
|
||||
}
|
||||
double numberOfSpaces = this.numberOfSpacesBetweenTwoCharacters(previousTextPosition, textPosition);
|
||||
double numberOfSpaces = this.numberOfSpacesBetweenTwoCharacters(this.previousTextPosition, textPosition);
|
||||
return (numberOfSpaces > 1 && numberOfSpaces <= ForkPDFLayoutTextStripper.OUTPUT_SPACE_CHARACTER_WIDTH_IN_PT);
|
||||
}
|
||||
|
||||
@@ -485,4 +487,4 @@ class CharacterFactory {
|
||||
this.previousTextPosition = previousTextPosition;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf.layout;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
@@ -70,8 +71,8 @@ public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
|
||||
* java coordinates (y == 0 is top), not PDF coordinates (y == 0 is bottom).
|
||||
*/
|
||||
public void addRegion(String regionName, Rectangle2D rect) {
|
||||
regions.add(regionName);
|
||||
regionArea.put(regionName, rect);
|
||||
this.regions.add(regionName);
|
||||
this.regionArea.put(regionName, rect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +81,8 @@ public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
|
||||
* @param regionName The name of the region to delete.
|
||||
*/
|
||||
public void removeRegion(String regionName) {
|
||||
regions.remove(regionName);
|
||||
regionArea.remove(regionName);
|
||||
this.regions.remove(regionName);
|
||||
this.regionArea.remove(regionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +90,7 @@ public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
|
||||
* @return A list of java.lang.String objects to identify the region names.
|
||||
*/
|
||||
public List<String> getRegions() {
|
||||
return regions;
|
||||
return this.regions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +99,7 @@ public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
|
||||
* @return The text that was identified in that region.
|
||||
*/
|
||||
public String getTextForRegion(String regionName) {
|
||||
StringWriter text = regionText.get(regionName);
|
||||
StringWriter text = this.regionText.get(regionName);
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
@@ -108,14 +109,14 @@ public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
|
||||
* @throws IOException If there is an error while extracting text.
|
||||
*/
|
||||
public void extractRegions(PDPage page) throws IOException {
|
||||
for (String regionName : regions) {
|
||||
for (String regionName : this.regions) {
|
||||
setStartPage(getCurrentPageNo());
|
||||
setEndPage(getCurrentPageNo());
|
||||
// reset the stored text for the region so this class can be reused.
|
||||
ArrayList<List<TextPosition>> regionCharactersByArticle = new ArrayList<List<TextPosition>>();
|
||||
regionCharactersByArticle.add(new ArrayList<TextPosition>());
|
||||
regionCharacterList.put(regionName, regionCharactersByArticle);
|
||||
regionText.put(regionName, new StringWriter());
|
||||
this.regionCharacterList.put(regionName, regionCharactersByArticle);
|
||||
this.regionText.put(regionName, new StringWriter());
|
||||
}
|
||||
|
||||
if (page.hasContents()) {
|
||||
@@ -128,10 +129,10 @@ public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
|
||||
*/
|
||||
@Override
|
||||
protected void processTextPosition(TextPosition text) {
|
||||
for (Map.Entry<String, Rectangle2D> regionAreaEntry : regionArea.entrySet()) {
|
||||
for (Map.Entry<String, Rectangle2D> regionAreaEntry : this.regionArea.entrySet()) {
|
||||
Rectangle2D rect = regionAreaEntry.getValue();
|
||||
if (rect.contains(text.getX(), text.getY())) {
|
||||
charactersByArticle = regionCharacterList.get(regionAreaEntry.getKey());
|
||||
this.charactersByArticle = this.regionCharacterList.get(regionAreaEntry.getKey());
|
||||
super.processTextPosition(text);
|
||||
}
|
||||
}
|
||||
@@ -143,9 +144,9 @@ public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
|
||||
*/
|
||||
@Override
|
||||
protected void writePage() throws IOException {
|
||||
for (String region : regionArea.keySet()) {
|
||||
charactersByArticle = regionCharacterList.get(region);
|
||||
output = regionText.get(region);
|
||||
for (String region : this.regionArea.keySet()) {
|
||||
this.charactersByArticle = this.regionCharacterList.get(region);
|
||||
this.output = this.regionText.get(region);
|
||||
super.writePage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,10 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.pdf.aot;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
|
||||
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.resource;
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.tika;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.reader.tika;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -28,6 +29,9 @@ import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.AnthropicMessage;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest;
|
||||
@@ -42,7 +46,11 @@ import org.springframework.ai.chat.messages.ToolResponseMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
|
||||
import org.springframework.ai.chat.model.*;
|
||||
import org.springframework.ai.chat.model.AbstractToolCallSupport;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.MessageAggregator;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationContext;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationDocumentation;
|
||||
@@ -61,9 +69,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* The {@link ChatModel} implementation for the Anthropic service.
|
||||
*
|
||||
@@ -76,16 +81,21 @@ import reactor.core.publisher.Mono;
|
||||
*/
|
||||
public class AnthropicChatModel extends AbstractToolCallSupport implements ChatModel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnthropicChatModel.class);
|
||||
|
||||
private static final ChatModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultChatModelObservationConvention();
|
||||
|
||||
public static final String DEFAULT_MODEL_NAME = AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue();
|
||||
|
||||
public static final Integer DEFAULT_MAX_TOKENS = 500;
|
||||
|
||||
public static final Double DEFAULT_TEMPERATURE = 0.8;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnthropicChatModel.class);
|
||||
|
||||
private static final ChatModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultChatModelObservationConvention();
|
||||
|
||||
/**
|
||||
* The retry template used to retry the OpenAI API calls.
|
||||
*/
|
||||
public final RetryTemplate retryTemplate;
|
||||
|
||||
/**
|
||||
* The lower-level API for the Anthropic service.
|
||||
*/
|
||||
@@ -96,11 +106,6 @@ public class AnthropicChatModel extends AbstractToolCallSupport implements ChatM
|
||||
*/
|
||||
private final AnthropicChatOptions defaultOptions;
|
||||
|
||||
/**
|
||||
* The retry template used to retry the OpenAI API calls.
|
||||
*/
|
||||
public final RetryTemplate retryTemplate;
|
||||
|
||||
/**
|
||||
* Observation registry used for instrumentation.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -91,91 +92,24 @@ public class AnthropicChatOptions implements ChatOptions, FunctionCallingOptions
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final AnthropicChatOptions options = new AnthropicChatOptions();
|
||||
|
||||
public Builder withModel(String model) {
|
||||
this.options.model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withModel(AnthropicApi.ChatModel model) {
|
||||
this.options.model = model.getValue();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.maxTokens = maxTokens;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMetadata(ChatCompletionRequest.Metadata metadata) {
|
||||
this.options.metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.stopSequences = stopSequences;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.topK = topK;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.options.functionCallbacks = functionCallbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctions(Set<String> functionNames) {
|
||||
Assert.notNull(functionNames, "Function names must not be null");
|
||||
this.options.functions = functionNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunction(String functionName) {
|
||||
Assert.hasText(functionName, "Function name must not be empty");
|
||||
this.options.functions.add(functionName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withProxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.options.proxyToolCalls = proxyToolCalls;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withToolContext(Map<String, Object> toolContext) {
|
||||
if (this.options.toolContext == null) {
|
||||
this.options.toolContext = toolContext;
|
||||
}
|
||||
else {
|
||||
this.options.toolContext.putAll(toolContext);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnthropicChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) {
|
||||
return builder().withModel(fromOptions.getModel())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withMetadata(fromOptions.getMetadata())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withFunctionCallbacks(fromOptions.getFunctionCallbacks())
|
||||
.withFunctions(fromOptions.getFunctions())
|
||||
.withProxyToolCalls(fromOptions.getProxyToolCalls())
|
||||
.withToolContext(fromOptions.getToolContext())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return model;
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
@@ -293,19 +227,86 @@ public class AnthropicChatOptions implements ChatOptions, FunctionCallingOptions
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) {
|
||||
return builder().withModel(fromOptions.getModel())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withMetadata(fromOptions.getMetadata())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withFunctionCallbacks(fromOptions.getFunctionCallbacks())
|
||||
.withFunctions(fromOptions.getFunctions())
|
||||
.withProxyToolCalls(fromOptions.getProxyToolCalls())
|
||||
.withToolContext(fromOptions.getToolContext())
|
||||
.build();
|
||||
public static class Builder {
|
||||
|
||||
private final AnthropicChatOptions options = new AnthropicChatOptions();
|
||||
|
||||
public Builder withModel(String model) {
|
||||
this.options.model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withModel(AnthropicApi.ChatModel model) {
|
||||
this.options.model = model.getValue();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.maxTokens = maxTokens;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMetadata(ChatCompletionRequest.Metadata metadata) {
|
||||
this.options.metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.stopSequences = stopSequences;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.topK = topK;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.options.functionCallbacks = functionCallbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctions(Set<String> functionNames) {
|
||||
Assert.notNull(functionNames, "Function names must not be null");
|
||||
this.options.functions = functionNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunction(String functionName) {
|
||||
Assert.hasText(functionName, "Function name must not be empty");
|
||||
this.options.functions.add(functionName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withProxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.options.proxyToolCalls = proxyToolCalls;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withToolContext(Map<String, Object> toolContext) {
|
||||
if (this.options.toolContext == null) {
|
||||
this.options.toolContext = toolContext;
|
||||
}
|
||||
else {
|
||||
this.options.toolContext.putAll(toolContext);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnthropicChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.aot;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -23,6 +24,14 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.anthropic.api.StreamHelper.ChatCompletionResponseBuilder;
|
||||
import org.springframework.ai.model.ChatModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
@@ -38,15 +47,6 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @author Mariusz Bernacki
|
||||
@@ -57,12 +57,6 @@ public class AnthropicApi {
|
||||
|
||||
public static final String PROVIDER_NAME = AiProvider.ANTHROPIC.value();
|
||||
|
||||
private static final String HEADER_X_API_KEY = "x-api-key";
|
||||
|
||||
private static final String HEADER_ANTHROPIC_VERSION = "anthropic-version";
|
||||
|
||||
private static final String HEADER_ANTHROPIC_BETA = "anthropic-beta";
|
||||
|
||||
public static final String DEFAULT_BASE_URL = "https://api.anthropic.com";
|
||||
|
||||
public static final String DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
||||
@@ -71,10 +65,18 @@ public class AnthropicApi {
|
||||
|
||||
public static final String BETA_MAX_TOKENS = "max-tokens-3-5-sonnet-2024-07-15";
|
||||
|
||||
private static final String HEADER_X_API_KEY = "x-api-key";
|
||||
|
||||
private static final String HEADER_ANTHROPIC_VERSION = "anthropic-version";
|
||||
|
||||
private static final String HEADER_ANTHROPIC_BETA = "anthropic-beta";
|
||||
|
||||
private static final Predicate<String> SSE_DONE_PREDICATE = "[DONE]"::equals;
|
||||
|
||||
private final RestClient restClient;
|
||||
|
||||
private final StreamHelper streamHelper = new StreamHelper();
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
/**
|
||||
@@ -141,6 +143,74 @@ public class AnthropicApi {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a model response for the given chat conversation.
|
||||
* @param chatRequest The chat completion request.
|
||||
* @return Entity response with {@link ChatCompletionResponse} as a body and HTTP
|
||||
* status code and headers.
|
||||
*/
|
||||
public ResponseEntity<ChatCompletionResponse> chatCompletionEntity(ChatCompletionRequest chatRequest) {
|
||||
|
||||
Assert.notNull(chatRequest, "The request body can not be null.");
|
||||
Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false.");
|
||||
|
||||
return this.restClient.post()
|
||||
.uri("/v1/messages")
|
||||
.body(chatRequest)
|
||||
.retrieve()
|
||||
.toEntity(ChatCompletionResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a streaming chat response for the given chat conversation.
|
||||
* @param chatRequest The chat completion request. Must have the stream property set
|
||||
* to true.
|
||||
* @return Returns a {@link Flux} stream from chat completion chunks.
|
||||
*/
|
||||
public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest chatRequest) {
|
||||
|
||||
Assert.notNull(chatRequest, "The request body can not be null.");
|
||||
Assert.isTrue(chatRequest.stream(), "Request must set the stream property to true.");
|
||||
|
||||
AtomicBoolean isInsideTool = new AtomicBoolean(false);
|
||||
|
||||
AtomicReference<ChatCompletionResponseBuilder> chatCompletionReference = new AtomicReference<>();
|
||||
|
||||
return this.webClient.post()
|
||||
.uri("/v1/messages")
|
||||
.body(Mono.just(chatRequest), ChatCompletionRequest.class)
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
.takeUntil(SSE_DONE_PREDICATE)
|
||||
.filter(SSE_DONE_PREDICATE.negate())
|
||||
.map(content -> ModelOptionsUtils.jsonToObject(content, StreamEvent.class))
|
||||
.filter(event -> event.type() != EventType.PING)
|
||||
// Detect if the chunk is part of a streaming function call.
|
||||
.map(event -> {
|
||||
if (this.streamHelper.isToolUseStart(event)) {
|
||||
isInsideTool.set(true);
|
||||
}
|
||||
return event;
|
||||
})
|
||||
// Group all chunks belonging to the same function call.
|
||||
.windowUntil(event -> {
|
||||
if (isInsideTool.get() && this.streamHelper.isToolUseFinish(event)) {
|
||||
isInsideTool.set(false);
|
||||
return true;
|
||||
}
|
||||
return !isInsideTool.get();
|
||||
})
|
||||
// Merging the window chunks into a single chunk.
|
||||
.concatMapIterable(window -> {
|
||||
Mono<StreamEvent> monoChunk = window.reduce(new ToolUseAggregationEvent(),
|
||||
this.streamHelper::mergeToolUseEvents);
|
||||
return List.of(monoChunk);
|
||||
})
|
||||
.flatMap(mono -> mono)
|
||||
.map(event -> this.streamHelper.eventToChatCompletionResponse(event, chatCompletionReference))
|
||||
.filter(chatCompletionResponse -> chatCompletionResponse.type() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the <a href="https://docs.anthropic.com/claude/docs/models-overview">Models
|
||||
* overview</a> and <a href=
|
||||
@@ -185,13 +255,93 @@ public class AnthropicApi {
|
||||
*/
|
||||
public enum Role {
|
||||
|
||||
// @formatter:off
|
||||
// @formatter:off
|
||||
@JsonProperty("user") USER,
|
||||
@JsonProperty("assistant") ASSISTANT
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The evnt type of the streamed chunk.
|
||||
*/
|
||||
public enum EventType {
|
||||
|
||||
/**
|
||||
* Message start event. Contains a Message object with empty content.
|
||||
*/
|
||||
@JsonProperty("message_start")
|
||||
MESSAGE_START,
|
||||
|
||||
/**
|
||||
* Message delta event, indicating top-level changes to the final Message object.
|
||||
*/
|
||||
@JsonProperty("message_delta")
|
||||
MESSAGE_DELTA,
|
||||
|
||||
/**
|
||||
* A final message stop event.
|
||||
*/
|
||||
@JsonProperty("message_stop")
|
||||
MESSAGE_STOP,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("content_block_start")
|
||||
CONTENT_BLOCK_START,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("content_block_delta")
|
||||
CONTENT_BLOCK_DELTA,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("content_block_stop")
|
||||
CONTENT_BLOCK_STOP,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("error")
|
||||
ERROR,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("ping")
|
||||
PING,
|
||||
|
||||
/**
|
||||
* Artifically created event to aggregate tool use events.
|
||||
*/
|
||||
TOOL_USE_AGGREATE;
|
||||
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type",
|
||||
visible = true)
|
||||
@JsonSubTypes({ @JsonSubTypes.Type(value = ContentBlockStartEvent.class, name = "content_block_start"),
|
||||
@JsonSubTypes.Type(value = ContentBlockDeltaEvent.class, name = "content_block_delta"),
|
||||
@JsonSubTypes.Type(value = ContentBlockStopEvent.class, name = "content_block_stop"),
|
||||
|
||||
@JsonSubTypes.Type(value = PingEvent.class, name = "ping"),
|
||||
|
||||
@JsonSubTypes.Type(value = ErrorEvent.class, name = "error"),
|
||||
|
||||
@JsonSubTypes.Type(value = MessageStartEvent.class, name = "message_start"),
|
||||
@JsonSubTypes.Type(value = MessageDeltaEvent.class, name = "message_delta"),
|
||||
@JsonSubTypes.Type(value = MessageStopEvent.class, name = "message_stop") })
|
||||
public interface StreamEvent {
|
||||
|
||||
@JsonProperty("type")
|
||||
EventType type();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param model The model that will complete your prompt. See the list of
|
||||
* <a href="https://docs.anthropic.com/claude/docs/models-overview">models</a> for
|
||||
@@ -257,6 +407,14 @@ public class AnthropicApi {
|
||||
this(model, messages, system, maxTokens, null, stopSequences, stream, temperature, null, null, null);
|
||||
}
|
||||
|
||||
public static ChatCompletionRequestBuilder builder() {
|
||||
return new ChatCompletionRequestBuilder();
|
||||
}
|
||||
|
||||
public static ChatCompletionRequestBuilder from(ChatCompletionRequest request) {
|
||||
return new ChatCompletionRequestBuilder(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userId An external identifier for the user who is associated with the
|
||||
* request. This should be a uuid, hash value, or other opaque identifier.
|
||||
@@ -265,15 +423,9 @@ public class AnthropicApi {
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record Metadata(@JsonProperty("user_id") String userId) {
|
||||
|
||||
}
|
||||
|
||||
public static ChatCompletionRequestBuilder builder() {
|
||||
return new ChatCompletionRequestBuilder();
|
||||
}
|
||||
|
||||
public static ChatCompletionRequestBuilder from(ChatCompletionRequest request) {
|
||||
return new ChatCompletionRequestBuilder(request);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChatCompletionRequestBuilder {
|
||||
@@ -378,12 +530,16 @@ public class AnthropicApi {
|
||||
}
|
||||
|
||||
public ChatCompletionRequest build() {
|
||||
return new ChatCompletionRequest(model, messages, system, maxTokens, metadata, stopSequences, stream,
|
||||
temperature, topP, topK, tools);
|
||||
return new ChatCompletionRequest(this.model, this.messages, this.system, this.maxTokens, this.metadata,
|
||||
this.stopSequences, this.stream, this.temperature, this.topP, this.topK, this.tools);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
/// ERROR EVENT
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Input messages.
|
||||
*
|
||||
@@ -535,9 +691,15 @@ public class AnthropicApi {
|
||||
public Source(String mediaType, String data) {
|
||||
this("base64", mediaType, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
/// CONTENT_BLOCK EVENTS
|
||||
///////////////////////////////////////
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record Tool(// @formatter:off
|
||||
@JsonProperty("name") String name,
|
||||
@@ -546,6 +708,8 @@ public class AnthropicApi {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// CB START EVENT
|
||||
|
||||
/**
|
||||
* @param id Unique object identifier. The format and length of IDs may change over
|
||||
* time.
|
||||
@@ -572,6 +736,8 @@ public class AnthropicApi {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// CB DELTA EVENT
|
||||
|
||||
/**
|
||||
* Usage statistics.
|
||||
*
|
||||
@@ -585,94 +751,7 @@ public class AnthropicApi {
|
||||
// @formatter:off
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////
|
||||
/// ERROR EVENT
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The evnt type of the streamed chunk.
|
||||
*/
|
||||
public enum EventType {
|
||||
|
||||
/**
|
||||
* Message start event. Contains a Message object with empty content.
|
||||
*/
|
||||
@JsonProperty("message_start")
|
||||
MESSAGE_START,
|
||||
|
||||
/**
|
||||
* Message delta event, indicating top-level changes to the final Message object.
|
||||
*/
|
||||
@JsonProperty("message_delta")
|
||||
MESSAGE_DELTA,
|
||||
|
||||
/**
|
||||
* A final message stop event.
|
||||
*/
|
||||
@JsonProperty("message_stop")
|
||||
MESSAGE_STOP,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("content_block_start")
|
||||
CONTENT_BLOCK_START,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("content_block_delta")
|
||||
CONTENT_BLOCK_DELTA,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("content_block_stop")
|
||||
CONTENT_BLOCK_STOP,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("error")
|
||||
ERROR,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@JsonProperty("ping")
|
||||
PING,
|
||||
|
||||
/**
|
||||
* Artifically created event to aggregate tool use events.
|
||||
*/
|
||||
TOOL_USE_AGGREATE;
|
||||
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type",
|
||||
visible = true)
|
||||
@JsonSubTypes({ @JsonSubTypes.Type(value = ContentBlockStartEvent.class, name = "content_block_start"),
|
||||
@JsonSubTypes.Type(value = ContentBlockDeltaEvent.class, name = "content_block_delta"),
|
||||
@JsonSubTypes.Type(value = ContentBlockStopEvent.class, name = "content_block_stop"),
|
||||
|
||||
@JsonSubTypes.Type(value = PingEvent.class, name = "ping"),
|
||||
|
||||
@JsonSubTypes.Type(value = ErrorEvent.class, name = "error"),
|
||||
|
||||
@JsonSubTypes.Type(value = MessageStartEvent.class, name = "message_start"),
|
||||
@JsonSubTypes.Type(value = MessageDeltaEvent.class, name = "message_delta"),
|
||||
@JsonSubTypes.Type(value = MessageStopEvent.class, name = "message_stop") })
|
||||
public interface StreamEvent {
|
||||
|
||||
@JsonProperty("type")
|
||||
EventType type();
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
/// CONTENT_BLOCK EVENTS
|
||||
///////////////////////////////////////
|
||||
/// ECB STOP
|
||||
|
||||
/**
|
||||
* Special event used to aggregate multiple tool use events into a single event with
|
||||
@@ -736,13 +815,17 @@ public class AnthropicApi {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EventToolUseBuilder [index=" + index + ", id=" + id + ", name=" + name + ", partialJson="
|
||||
+ partialJson + ", toolUseMap=" + toolContentBlocks + "]";
|
||||
return "EventToolUseBuilder [index=" + this.index + ", id=" + this.id + ", name=" + this.name + ", partialJson="
|
||||
+ this.partialJson + ", toolUseMap=" + this.toolContentBlocks + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CB START EVENT
|
||||
///////////////////////////////////////
|
||||
/// MESSAGE EVENTS
|
||||
///////////////////////////////////////
|
||||
|
||||
// MESSAGE START EVENT
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record ContentBlockStartEvent(// @formatter:off
|
||||
@@ -773,7 +856,7 @@ public class AnthropicApi {
|
||||
}
|
||||
}// @formatter:on
|
||||
|
||||
// CB DELTA EVENT
|
||||
// MESSAGE DELTA EVENT
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record ContentBlockDeltaEvent(// @formatter:off
|
||||
@@ -803,7 +886,7 @@ public class AnthropicApi {
|
||||
}
|
||||
}// @formatter:on
|
||||
|
||||
/// ECB STOP
|
||||
// MESSAGE STOP EVENT
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record ContentBlockStopEvent(// @formatter:off
|
||||
@@ -811,20 +894,12 @@ public class AnthropicApi {
|
||||
@JsonProperty("index") Integer index) implements StreamEvent {
|
||||
}// @formatter:on
|
||||
|
||||
///////////////////////////////////////
|
||||
/// MESSAGE EVENTS
|
||||
///////////////////////////////////////
|
||||
|
||||
// MESSAGE START EVENT
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record MessageStartEvent(// @formatter:off
|
||||
@JsonProperty("type") EventType type,
|
||||
@JsonProperty("message") ChatCompletionResponse message) implements StreamEvent {
|
||||
}// @formatter:on
|
||||
|
||||
// MESSAGE DELTA EVENT
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record MessageDeltaEvent(// @formatter:off
|
||||
@JsonProperty("type") EventType type,
|
||||
@@ -843,8 +918,6 @@ public class AnthropicApi {
|
||||
}
|
||||
}// @formatter:on
|
||||
|
||||
// MESSAGE STOP EVENT
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record MessageStopEvent(// @formatter:off
|
||||
@JsonProperty("type") EventType type) implements StreamEvent {
|
||||
@@ -873,74 +946,4 @@ public class AnthropicApi {
|
||||
@JsonProperty("type") EventType type) implements StreamEvent {
|
||||
}// @formatter:on
|
||||
|
||||
/**
|
||||
* Creates a model response for the given chat conversation.
|
||||
* @param chatRequest The chat completion request.
|
||||
* @return Entity response with {@link ChatCompletionResponse} as a body and HTTP
|
||||
* status code and headers.
|
||||
*/
|
||||
public ResponseEntity<ChatCompletionResponse> chatCompletionEntity(ChatCompletionRequest chatRequest) {
|
||||
|
||||
Assert.notNull(chatRequest, "The request body can not be null.");
|
||||
Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false.");
|
||||
|
||||
return this.restClient.post()
|
||||
.uri("/v1/messages")
|
||||
.body(chatRequest)
|
||||
.retrieve()
|
||||
.toEntity(ChatCompletionResponse.class);
|
||||
}
|
||||
|
||||
private final StreamHelper streamHelper = new StreamHelper();
|
||||
|
||||
/**
|
||||
* Creates a streaming chat response for the given chat conversation.
|
||||
* @param chatRequest The chat completion request. Must have the stream property set
|
||||
* to true.
|
||||
* @return Returns a {@link Flux} stream from chat completion chunks.
|
||||
*/
|
||||
public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest chatRequest) {
|
||||
|
||||
Assert.notNull(chatRequest, "The request body can not be null.");
|
||||
Assert.isTrue(chatRequest.stream(), "Request must set the stream property to true.");
|
||||
|
||||
AtomicBoolean isInsideTool = new AtomicBoolean(false);
|
||||
|
||||
AtomicReference<ChatCompletionResponseBuilder> chatCompletionReference = new AtomicReference<>();
|
||||
|
||||
return this.webClient.post()
|
||||
.uri("/v1/messages")
|
||||
.body(Mono.just(chatRequest), ChatCompletionRequest.class)
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
.takeUntil(SSE_DONE_PREDICATE)
|
||||
.filter(SSE_DONE_PREDICATE.negate())
|
||||
.map(content -> ModelOptionsUtils.jsonToObject(content, StreamEvent.class))
|
||||
.filter(event -> event.type() != EventType.PING)
|
||||
// Detect if the chunk is part of a streaming function call.
|
||||
.map(event -> {
|
||||
if (this.streamHelper.isToolUseStart(event)) {
|
||||
isInsideTool.set(true);
|
||||
}
|
||||
return event;
|
||||
})
|
||||
// Group all chunks belonging to the same function call.
|
||||
.windowUntil(event -> {
|
||||
if (isInsideTool.get() && this.streamHelper.isToolUseFinish(event)) {
|
||||
isInsideTool.set(false);
|
||||
return true;
|
||||
}
|
||||
return !isInsideTool.get();
|
||||
})
|
||||
// Merging the window chunks into a single chunk.
|
||||
.concatMapIterable(window -> {
|
||||
Mono<StreamEvent> monoChunk = window.reduce(new ToolUseAggregationEvent(),
|
||||
this.streamHelper::mergeToolUseEvents);
|
||||
return List.of(monoChunk);
|
||||
})
|
||||
.flatMap(mono -> mono)
|
||||
.map(event -> streamHelper.eventToChatCompletionResponse(event, chatCompletionReference))
|
||||
.filter(chatCompletionResponse -> chatCompletionResponse.type() != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -22,22 +23,22 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock.Type;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Role;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Usage;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ToolUseAggregationEvent;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.MessageDeltaEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.MessageStartEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.StreamEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent.ContentBlockDeltaJson;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent.ContentBlockDeltaText;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent.ContentBlockText;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent.ContentBlockToolUse;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.EventType;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.MessageDeltaEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.MessageStartEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Role;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.StreamEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ToolUseAggregationEvent;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Usage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Helper class to support streaming function calling.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.metadata;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.metadata;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
@@ -27,10 +28,6 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class AnthropicUsage implements Usage {
|
||||
|
||||
public static AnthropicUsage from(AnthropicApi.Usage usage) {
|
||||
return new AnthropicUsage(usage);
|
||||
}
|
||||
|
||||
private final AnthropicApi.Usage usage;
|
||||
|
||||
protected AnthropicUsage(AnthropicApi.Usage usage) {
|
||||
@@ -38,6 +35,10 @@ public class AnthropicUsage implements Usage {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public static AnthropicUsage from(AnthropicApi.Usage usage) {
|
||||
return new AnthropicUsage(usage);
|
||||
}
|
||||
|
||||
protected AnthropicApi.Usage getUsage() {
|
||||
return this.usage;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,9 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -30,11 +29,12 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.anthropic.api.tool.MockWeatherService;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.model.Media;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
@@ -47,6 +47,7 @@ import org.springframework.ai.chat.prompt.SystemPromptTemplate;
|
||||
import org.springframework.ai.converter.BeanOutputConverter;
|
||||
import org.springframework.ai.converter.ListOutputConverter;
|
||||
import org.springframework.ai.converter.MapOutputConverter;
|
||||
import org.springframework.ai.model.Media;
|
||||
import org.springframework.ai.model.function.FunctionCallbackWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -59,7 +60,7 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(classes = AnthropicChatModelIT.Config.class, properties = "spring.ai.retry.on-http-codes=429")
|
||||
@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
|
||||
@@ -76,17 +77,25 @@ class AnthropicChatModelIT {
|
||||
@Value("classpath:/prompts/system-message.st")
|
||||
private Resource systemResource;
|
||||
|
||||
private static void validateChatResponseMetadata(ChatResponse response, String model) {
|
||||
assertThat(response.getMetadata().getId()).isNotEmpty();
|
||||
assertThat(response.getMetadata().getModel()).containsIgnoringCase(model);
|
||||
assertThat(response.getMetadata().getUsage().getPromptTokens()).isPositive();
|
||||
assertThat(response.getMetadata().getUsage().getGenerationTokens()).isPositive();
|
||||
assertThat(response.getMetadata().getUsage().getTotalTokens()).isPositive();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} : {displayName} ")
|
||||
@ValueSource(strings = { "claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307",
|
||||
"claude-3-5-sonnet-20241022" })
|
||||
void roleTest(String modelName) {
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"Tell me about 3 famous pirates from the Golden Age of Piracy and why they did.");
|
||||
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
|
||||
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource);
|
||||
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate"));
|
||||
Prompt prompt = new Prompt(List.of(userMessage, systemMessage),
|
||||
AnthropicChatOptions.builder().withModel(modelName).build());
|
||||
ChatResponse response = chatModel.call(prompt);
|
||||
ChatResponse response = this.chatModel.call(prompt);
|
||||
assertThat(response.getResults()).hasSize(1);
|
||||
assertThat(response.getMetadata().getUsage().getGenerationTokens()).isGreaterThan(0);
|
||||
assertThat(response.getMetadata().getUsage().getPromptTokens()).isGreaterThan(0);
|
||||
@@ -103,17 +112,17 @@ class AnthropicChatModelIT {
|
||||
void testMessageHistory() {
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"Tell me about 3 famous pirates from the Golden Age of Piracy and why they did.");
|
||||
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
|
||||
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource);
|
||||
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate"));
|
||||
Prompt prompt = new Prompt(List.of(userMessage, systemMessage),
|
||||
AnthropicChatOptions.builder().withModel("claude-3-sonnet-20240229").build());
|
||||
|
||||
ChatResponse response = chatModel.call(prompt);
|
||||
ChatResponse response = this.chatModel.call(prompt);
|
||||
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard", "Bartholomew");
|
||||
|
||||
var promptWithMessageHistory = new Prompt(List.of(new UserMessage("Dummy"), response.getResult().getOutput(),
|
||||
new UserMessage("Repeat the last assistant message.")));
|
||||
response = chatModel.call(promptWithMessageHistory);
|
||||
response = this.chatModel.call(promptWithMessageHistory);
|
||||
|
||||
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard", "Bartholomew");
|
||||
}
|
||||
@@ -167,16 +176,13 @@ class AnthropicChatModelIT {
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template,
|
||||
Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
Generation generation = chatModel.call(prompt).getResult();
|
||||
Generation generation = this.chatModel.call(prompt).getResult();
|
||||
|
||||
Map<String, Object> result = mapOutputConverter.convert(generation.getOutput().getContent());
|
||||
assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||
|
||||
}
|
||||
|
||||
record ActorsFilmsRecord(String actor, List<String> movies) {
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanOutputConverterRecords() {
|
||||
|
||||
@@ -189,7 +195,7 @@ class AnthropicChatModelIT {
|
||||
""";
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
Generation generation = chatModel.call(prompt).getResult();
|
||||
Generation generation = this.chatModel.call(prompt).getResult();
|
||||
|
||||
ActorsFilmsRecord actorsFilms = beanOutputConverter.convert(generation.getOutput().getContent());
|
||||
logger.info("" + actorsFilms);
|
||||
@@ -210,7 +216,7 @@ class AnthropicChatModelIT {
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
|
||||
String generationTextFromStream = streamingChatModel.stream(prompt)
|
||||
String generationTextFromStream = this.streamingChatModel.stream(prompt)
|
||||
.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
@@ -234,7 +240,7 @@ class AnthropicChatModelIT {
|
||||
var userMessage = new UserMessage("Explain what do you see on this picture?",
|
||||
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData)));
|
||||
|
||||
var response = chatModel.call(new Prompt(List.of(userMessage)));
|
||||
var response = this.chatModel.call(new Prompt(List.of(userMessage)));
|
||||
|
||||
logger.info(response.getResult().getOutput().getContent());
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("banan", "apple", "basket");
|
||||
@@ -257,7 +263,7 @@ class AnthropicChatModelIT {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ChatResponse response = chatModel.call(new Prompt(messages, promptOptions));
|
||||
ChatResponse response = this.chatModel.call(new Prompt(messages, promptOptions));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
@@ -284,7 +290,7 @@ class AnthropicChatModelIT {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
Flux<ChatResponse> response = chatModel.stream(new Prompt(messages, promptOptions));
|
||||
Flux<ChatResponse> response = this.chatModel.stream(new Prompt(messages, promptOptions));
|
||||
|
||||
String content = response.collectList()
|
||||
.block()
|
||||
@@ -301,7 +307,7 @@ class AnthropicChatModelIT {
|
||||
void validateCallResponseMetadata() {
|
||||
String model = AnthropicApi.ChatModel.CLAUDE_2_1.getName();
|
||||
// @formatter:off
|
||||
ChatResponse response = ChatClient.create(chatModel).prompt()
|
||||
ChatResponse response = ChatClient.create(this.chatModel).prompt()
|
||||
.options(AnthropicChatOptions.builder().withModel(model).build())
|
||||
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
|
||||
.call()
|
||||
@@ -316,7 +322,7 @@ class AnthropicChatModelIT {
|
||||
void validateStreamCallResponseMetadata() {
|
||||
String model = AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName();
|
||||
// @formatter:off
|
||||
ChatResponse response = ChatClient.create(chatModel).prompt()
|
||||
ChatResponse response = ChatClient.create(this.chatModel).prompt()
|
||||
.options(AnthropicChatOptions.builder().withModel(model).build())
|
||||
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
|
||||
.stream()
|
||||
@@ -328,12 +334,8 @@ class AnthropicChatModelIT {
|
||||
validateChatResponseMetadata(response, model);
|
||||
}
|
||||
|
||||
private static void validateChatResponseMetadata(ChatResponse response, String model) {
|
||||
assertThat(response.getMetadata().getId()).isNotEmpty();
|
||||
assertThat(response.getMetadata().getModel()).containsIgnoringCase(model);
|
||||
assertThat(response.getMetadata().getUsage().getPromptTokens()).isPositive();
|
||||
assertThat(response.getMetadata().getUsage().getGenerationTokens()).isPositive();
|
||||
assertThat(response.getMetadata().getUsage().getTotalTokens()).isPositive();
|
||||
record ActorsFilmsRecord(String actor, List<String> movies) {
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@@ -360,4 +362,4 @@ class AnthropicChatModelIT {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,16 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
@@ -39,9 +42,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for observation instrumentation in {@link AnthropicChatModel}.
|
||||
@@ -61,7 +62,7 @@ public class AnthropicChatModelObservationIT {
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
observationRegistry.clear();
|
||||
this.observationRegistry.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,7 +78,7 @@ public class AnthropicChatModelObservationIT {
|
||||
|
||||
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
|
||||
|
||||
ChatResponse chatResponse = chatModel.call(prompt);
|
||||
ChatResponse chatResponse = this.chatModel.call(prompt);
|
||||
assertThat(chatResponse.getResult().getOutput().getContent()).isNotEmpty();
|
||||
|
||||
ChatResponseMetadata responseMetadata = chatResponse.getMetadata();
|
||||
@@ -99,7 +100,7 @@ public class AnthropicChatModelObservationIT {
|
||||
|
||||
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
|
||||
|
||||
Flux<ChatResponse> chatResponseFlux = chatModel.stream(prompt);
|
||||
Flux<ChatResponse> chatResponseFlux = this.chatModel.stream(prompt);
|
||||
|
||||
List<ChatResponse> responses = chatResponseFlux.collectList().block();
|
||||
assertThat(responses).isNotEmpty();
|
||||
@@ -121,7 +122,7 @@ public class AnthropicChatModelObservationIT {
|
||||
}
|
||||
|
||||
private void validate(ChatResponseMetadata responseMetadata, String finishReasons) {
|
||||
TestObservationRegistryAssert.assertThat(observationRegistry)
|
||||
TestObservationRegistryAssert.assertThat(this.observationRegistry)
|
||||
.doesNotHaveAnyRemainingCurrentObservation()
|
||||
.hasObservationWithNameEqualTo(DefaultChatModelObservationConvention.DEFAULT_NAME)
|
||||
.that()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
/*
|
||||
* 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.anthropic;
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
package org.springframework.ai.anthropic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.StreamEvent;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
@@ -44,6 +45,7 @@ public class EventParsingTests {
|
||||
.getContentAsString(Charset.defaultCharset());
|
||||
|
||||
List<StreamEvent> events = new ObjectMapper().readerFor(new TypeReference<List<StreamEvent>>() {
|
||||
|
||||
}).readValue(json);
|
||||
|
||||
logger.info(events.toString());
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,16 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.aot;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.springframework.ai.aot.AiRuntimeHints.findJsonAnnotatedClassesInPackage;
|
||||
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,14 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.anthropic.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
package org.springframework.ai.anthropic.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.AnthropicMessage;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
|
||||
@@ -28,7 +29,7 @@ import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Role;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
@@ -43,7 +44,7 @@ public class AnthropicApiIT {
|
||||
|
||||
AnthropicMessage chatCompletionMessage = new AnthropicMessage(List.of(new ContentBlock("Tell me a Joke?")),
|
||||
Role.USER);
|
||||
ResponseEntity<ChatCompletionResponse> response = anthropicApi
|
||||
ResponseEntity<ChatCompletionResponse> response = this.anthropicApi
|
||||
.chatCompletionEntity(new ChatCompletionRequest(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue(),
|
||||
List.of(chatCompletionMessage), null, 100, 0.8, false));
|
||||
|
||||
@@ -58,7 +59,7 @@ public class AnthropicApiIT {
|
||||
AnthropicMessage chatCompletionMessage = new AnthropicMessage(List.of(new ContentBlock("Tell me a Joke?")),
|
||||
Role.USER);
|
||||
|
||||
Flux<ChatCompletionResponse> response = anthropicApi.chatCompletionStream(new ChatCompletionRequest(
|
||||
Flux<ChatCompletionResponse> response = this.anthropicApi.chatCompletionStream(new ChatCompletionRequest(
|
||||
AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue(), List.of(chatCompletionMessage), null, 100, 0.8, true));
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.api.tool;
|
||||
|
||||
import java.util.List;
|
||||
@@ -25,10 +26,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.AnthropicMessage;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Role;
|
||||
import org.springframework.ai.anthropic.api.tool.XmlHelper.FunctionCalls;
|
||||
import org.springframework.ai.anthropic.api.tool.XmlHelper.Tools;
|
||||
@@ -60,10 +61,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SuppressWarnings("null")
|
||||
public class AnthropicApiLegacyToolIT {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnthropicApiLegacyToolIT.class);
|
||||
|
||||
AnthropicApi anthropicApi = new AnthropicApi(System.getenv("ANTHROPIC_API_KEY"));
|
||||
|
||||
public static final String TOO_SYSTEM_PROMPT_TEMPLATE = """
|
||||
In this environment you have access to a set of tools you can use to answer the user's question.
|
||||
|
||||
@@ -84,9 +81,9 @@ public class AnthropicApiLegacyToolIT {
|
||||
|
||||
public static final ConcurrentHashMap<String, Function> FUNCTIONS = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
FUNCTIONS.put("getCurrentWeather", new MockWeatherService());
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnthropicApiLegacyToolIT.class);
|
||||
|
||||
AnthropicApi anthropicApi = new AnthropicApi(System.getenv("ANTHROPIC_API_KEY"));
|
||||
|
||||
@Test
|
||||
void toolCalls() {
|
||||
@@ -120,7 +117,7 @@ public class AnthropicApiLegacyToolIT {
|
||||
|
||||
private ResponseEntity<ChatCompletionResponse> doCall(ChatCompletionRequest chatCompletionRequest) {
|
||||
|
||||
ResponseEntity<ChatCompletionResponse> response = anthropicApi.chatCompletionEntity(chatCompletionRequest);
|
||||
ResponseEntity<ChatCompletionResponse> response = this.anthropicApi.chatCompletionEntity(chatCompletionRequest);
|
||||
|
||||
FunctionCalls functionCalls = XmlHelper.extractFunctionCalls(response.getBody().content().get(0).text());
|
||||
|
||||
@@ -150,4 +147,8 @@ public class AnthropicApiLegacyToolIT {
|
||||
List.of(chatCompletionMessage2), null, 500, 0.8, false));
|
||||
}
|
||||
|
||||
static {
|
||||
FUNCTIONS.put("getCurrentWeather", new MockWeatherService());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.api.tool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -26,11 +27,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.AnthropicMessage;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock.Type;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.AnthropicMessage;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Role;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi.Tool;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
@@ -53,16 +54,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SuppressWarnings("null")
|
||||
public class AnthropicApiToolIT {
|
||||
|
||||
public static final ConcurrentHashMap<String, Function> FUNCTIONS = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnthropicApiLegacyToolIT.class);
|
||||
|
||||
AnthropicApi anthropicApi = new AnthropicApi(System.getenv("ANTHROPIC_API_KEY"));
|
||||
|
||||
public static final ConcurrentHashMap<String, Function> FUNCTIONS = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
FUNCTIONS.put("getCurrentWeather", new MockWeatherService());
|
||||
}
|
||||
|
||||
List<Tool> tools = List.of(new Tool("getCurrentWeather",
|
||||
"Get the weather in location. Return temperature in 30°F or 30°C format.", ModelOptionsUtils.jsonToMap("""
|
||||
{
|
||||
@@ -109,10 +106,10 @@ public class AnthropicApiToolIT {
|
||||
.withMessages(messageConversation)
|
||||
.withMaxTokens(1500)
|
||||
.withTemperature(0.8)
|
||||
.withTools(tools)
|
||||
.withTools(this.tools)
|
||||
.build();
|
||||
|
||||
ResponseEntity<ChatCompletionResponse> response = anthropicApi.chatCompletionEntity(chatCompletionRequest);
|
||||
ResponseEntity<ChatCompletionResponse> response = this.anthropicApi.chatCompletionEntity(chatCompletionRequest);
|
||||
|
||||
List<ContentBlock> toolToUseList = response.getBody()
|
||||
.content()
|
||||
@@ -155,4 +152,8 @@ public class AnthropicApiToolIT {
|
||||
return doCall(messageConversation);
|
||||
}
|
||||
|
||||
static {
|
||||
FUNCTIONS.put("getCurrentWeather", new MockWeatherService());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.api.tool;
|
||||
|
||||
import java.util.function.Function;
|
||||
@@ -28,14 +29,21 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
*/
|
||||
public class MockWeatherService implements Function<MockWeatherService.Request, MockWeatherService.Response> {
|
||||
|
||||
/**
|
||||
* Weather Function request.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonClassDescription("Weather API request")
|
||||
public record Request(@JsonProperty(required = true,
|
||||
value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location,
|
||||
@JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) {
|
||||
@Override
|
||||
public Response apply(Request request) {
|
||||
|
||||
double temperature = 0;
|
||||
if (request.location().contains("Paris")) {
|
||||
temperature = 15;
|
||||
}
|
||||
else if (request.location().contains("Tokyo")) {
|
||||
temperature = 10;
|
||||
}
|
||||
else if (request.location().contains("San Francisco")) {
|
||||
temperature = 30;
|
||||
}
|
||||
|
||||
return new Response(temperature, Unit.C);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,27 +71,22 @@ public class MockWeatherService implements Function<MockWeatherService.Request,
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather Function request.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonClassDescription("Weather API request")
|
||||
public record Request(@JsonProperty(required = true,
|
||||
value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location,
|
||||
@JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather Function response.
|
||||
*/
|
||||
public record Response(double temp, Unit unit) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(Request request) {
|
||||
|
||||
double temperature = 0;
|
||||
if (request.location().contains("Paris")) {
|
||||
temperature = 15;
|
||||
}
|
||||
else if (request.location().contains("Tokyo")) {
|
||||
temperature = 10;
|
||||
}
|
||||
else if (request.location().contains("San Francisco")) {
|
||||
temperature = 30;
|
||||
}
|
||||
|
||||
return new Response(temperature, Unit.C);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.anthropic.api.tool;
|
||||
|
||||
import java.util.List;
|
||||
@@ -45,45 +46,6 @@ public class XmlHelper {
|
||||
|
||||
private static final XmlMapper xmlMapper = new XmlMapper();
|
||||
|
||||
@JsonInclude(Include.NON_NULL) // @formatter:off
|
||||
@JacksonXmlRootElement(localName = "tools")
|
||||
public record Tools(
|
||||
@JacksonXmlElementWrapper(useWrapping = false) @JsonProperty("tool_description") List<ToolDescription> toolDescriptions) {
|
||||
|
||||
public record ToolDescription(
|
||||
@JsonProperty("tool_name") String toolName,
|
||||
@JsonProperty("description") String description,
|
||||
@JacksonXmlElementWrapper(localName = "parameters") @JsonProperty("parameter") List<Parameter> parameters) {
|
||||
|
||||
@JacksonXmlRootElement(localName = "parameter")
|
||||
public record Parameter(
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("description") String description) {
|
||||
}
|
||||
}
|
||||
} // @formatter:on
|
||||
|
||||
@JsonInclude(Include.NON_NULL) // @formatter:off
|
||||
@JacksonXmlRootElement(localName = "function_calls")
|
||||
public record FunctionCalls(@JsonProperty("invoke") Invoke invoke) {
|
||||
public record Invoke(
|
||||
@JsonProperty("tool_name") String toolName,
|
||||
@JsonProperty("parameters") Map<String, Object> parameters) {
|
||||
}
|
||||
} // @formatter:on
|
||||
|
||||
@JsonInclude(Include.NON_NULL) // @formatter:off
|
||||
@JacksonXmlRootElement(localName = "function_results")
|
||||
public record FunctionResults(
|
||||
@JacksonXmlElementWrapper(useWrapping = false) @JsonProperty("result") List<Result> result) {
|
||||
|
||||
public record Result(
|
||||
@JsonProperty("tool_name") String toolName,
|
||||
@JsonProperty("stdout") Object stdout) {
|
||||
}
|
||||
} // @formatter:on
|
||||
|
||||
public static String extractFunctionCallsXmlBlock(String text) {
|
||||
if (!StringUtils.hasText(text)) {
|
||||
return "";
|
||||
@@ -149,4 +111,43 @@ public class XmlHelper {
|
||||
|
||||
}
|
||||
|
||||
@JsonInclude(Include.NON_NULL) // @formatter:off
|
||||
@JacksonXmlRootElement(localName = "tools")
|
||||
public record Tools(
|
||||
@JacksonXmlElementWrapper(useWrapping = false) @JsonProperty("tool_description") List<ToolDescription> toolDescriptions) {
|
||||
|
||||
public record ToolDescription(
|
||||
@JsonProperty("tool_name") String toolName,
|
||||
@JsonProperty("description") String description,
|
||||
@JacksonXmlElementWrapper(localName = "parameters") @JsonProperty("parameter") List<Parameter> parameters) {
|
||||
|
||||
@JacksonXmlRootElement(localName = "parameter")
|
||||
public record Parameter(
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("description") String description) {
|
||||
}
|
||||
}
|
||||
} // @formatter:on
|
||||
|
||||
@JsonInclude(Include.NON_NULL) // @formatter:off
|
||||
@JacksonXmlRootElement(localName = "function_calls")
|
||||
public record FunctionCalls(@JsonProperty("invoke") Invoke invoke) {
|
||||
public record Invoke(
|
||||
@JsonProperty("tool_name") String toolName,
|
||||
@JsonProperty("parameters") Map<String, Object> parameters) {
|
||||
}
|
||||
} // @formatter:on
|
||||
|
||||
@JsonInclude(Include.NON_NULL) // @formatter:off
|
||||
@JacksonXmlRootElement(localName = "function_results")
|
||||
public record FunctionResults(
|
||||
@JacksonXmlElementWrapper(useWrapping = false) @JsonProperty("result") List<Result> result) {
|
||||
|
||||
public record Result(
|
||||
@JsonProperty("tool_name") String toolName,
|
||||
@JsonProperty("stdout") Object stdout) {
|
||||
}
|
||||
} // @formatter:on
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,9 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.anthropic.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
package org.springframework.ai.anthropic.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
@@ -31,6 +30,8 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.anthropic.AnthropicTestConfiguration;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
@@ -51,7 +52,7 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(classes = AnthropicTestConfiguration.class, properties = "spring.ai.retry.on-http-codes=429")
|
||||
@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
|
||||
@@ -66,16 +67,13 @@ class AnthropicChatClientIT {
|
||||
@Value("classpath:/prompts/system-message.st")
|
||||
private Resource systemTextResource;
|
||||
|
||||
record ActorsFilms(String actor, List<String> movies) {
|
||||
}
|
||||
|
||||
@Test
|
||||
void call() {
|
||||
|
||||
// @formatter:off
|
||||
ChatResponse response = ChatClient.create(chatModel).prompt()
|
||||
ChatResponse response = ChatClient.create(this.chatModel).prompt()
|
||||
.advisors(new SimpleLoggerAdvisor())
|
||||
.system(s -> s.text(systemTextResource)
|
||||
.system(s -> s.text(this.systemTextResource)
|
||||
.param("name", "Bob")
|
||||
.param("voice", "pirate"))
|
||||
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
|
||||
@@ -91,7 +89,7 @@ class AnthropicChatClientIT {
|
||||
@Test
|
||||
void listOutputConverterString() {
|
||||
// @formatter:off
|
||||
List<String> collection = ChatClient.create(chatModel).prompt()
|
||||
List<String> collection = ChatClient.create(this.chatModel).prompt()
|
||||
.user(u -> u.text("List five {subject}")
|
||||
.param("subject", "ice cream flavors"))
|
||||
.call()
|
||||
@@ -106,7 +104,7 @@ class AnthropicChatClientIT {
|
||||
void listOutputConverterBean() {
|
||||
|
||||
// @formatter:off
|
||||
List<ActorsFilms> actorsFilms = ChatClient.create(chatModel).prompt()
|
||||
List<ActorsFilms> actorsFilms = ChatClient.create(this.chatModel).prompt()
|
||||
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
|
||||
.call()
|
||||
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
|
||||
@@ -123,7 +121,7 @@ class AnthropicChatClientIT {
|
||||
var toStringListConverter = new ListOutputConverter(new DefaultConversionService());
|
||||
|
||||
// @formatter:off
|
||||
List<String> flavors = ChatClient.create(chatModel).prompt()
|
||||
List<String> flavors = ChatClient.create(this.chatModel).prompt()
|
||||
.user(u -> u.text("List five {subject}")
|
||||
.param("subject", "ice cream flavors"))
|
||||
.call()
|
||||
@@ -138,7 +136,7 @@ class AnthropicChatClientIT {
|
||||
@Test
|
||||
void mapOutputConverter() {
|
||||
// @formatter:off
|
||||
Map<String, Object> result = ChatClient.create(chatModel).prompt()
|
||||
Map<String, Object> result = ChatClient.create(this.chatModel).prompt()
|
||||
.user(u -> u.text("Provide me a List of {subject}")
|
||||
.param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
|
||||
.call()
|
||||
@@ -153,7 +151,7 @@ class AnthropicChatClientIT {
|
||||
void beanOutputConverter() {
|
||||
|
||||
// @formatter:off
|
||||
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
|
||||
ActorsFilms actorsFilms = ChatClient.create(this.chatModel).prompt()
|
||||
.user("Generate the filmography for a random actor.")
|
||||
.call()
|
||||
.entity(ActorsFilms.class);
|
||||
@@ -167,7 +165,7 @@ class AnthropicChatClientIT {
|
||||
void beanOutputConverterRecords() {
|
||||
|
||||
// @formatter:off
|
||||
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
|
||||
ActorsFilms actorsFilms = ChatClient.create(this.chatModel).prompt()
|
||||
.user("Generate the filmography of 5 movies for Tom Hanks.")
|
||||
.call()
|
||||
.entity(ActorsFilms.class);
|
||||
@@ -184,7 +182,7 @@ class AnthropicChatClientIT {
|
||||
BeanOutputConverter<ActorsFilms> outputConverter = new BeanOutputConverter<>(ActorsFilms.class);
|
||||
|
||||
// @formatter:off
|
||||
Flux<String> chatResponse = ChatClient.create(chatModel)
|
||||
Flux<String> chatResponse = ChatClient.create(this.chatModel)
|
||||
.prompt()
|
||||
.advisors(new SimpleLoggerAdvisor())
|
||||
.user(u -> u
|
||||
@@ -211,7 +209,7 @@ class AnthropicChatClientIT {
|
||||
void functionCallTest() {
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
String response = ChatClient.create(this.chatModel).prompt()
|
||||
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius."))
|
||||
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
|
||||
.call()
|
||||
@@ -227,7 +225,7 @@ class AnthropicChatClientIT {
|
||||
void defaultFunctionCallTest() {
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.builder(chatModel)
|
||||
String response = ChatClient.builder(this.chatModel)
|
||||
.defaultFunction("getCurrentWeather", "Get the weather in location", new MockWeatherService())
|
||||
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius."))
|
||||
.build()
|
||||
@@ -245,7 +243,7 @@ class AnthropicChatClientIT {
|
||||
void streamFunctionCallTest() {
|
||||
|
||||
// @formatter:off
|
||||
Flux<String> response = ChatClient.create(chatModel).prompt()
|
||||
Flux<String> response = ChatClient.create(this.chatModel).prompt()
|
||||
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
|
||||
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
|
||||
.stream()
|
||||
@@ -264,7 +262,7 @@ class AnthropicChatClientIT {
|
||||
void multiModalityEmbeddedImage(String modelName) throws IOException {
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
String response = ChatClient.create(this.chatModel).prompt()
|
||||
.options(AnthropicChatOptions.builder().withModel(modelName).build())
|
||||
.user(u -> u.text("Explain what do you see on this picture?")
|
||||
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/test.png")))
|
||||
@@ -287,7 +285,7 @@ class AnthropicChatClientIT {
|
||||
URL url = new URL("https://docs.spring.io/spring-ai/reference/1.0.0-SNAPSHOT/_images/multimodal.test.png");
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
String response = ChatClient.create(this.chatModel).prompt()
|
||||
// TODO consider adding model(...) method to ChatClient as a shortcut to
|
||||
.options(AnthropicChatOptions.builder().withModel(modelName).build())
|
||||
.user(u -> u.text("Explain what do you see on this picture?").media(MimeTypeUtils.IMAGE_PNG, url))
|
||||
@@ -304,7 +302,7 @@ class AnthropicChatClientIT {
|
||||
void streamingMultiModality() throws IOException {
|
||||
|
||||
// @formatter:off
|
||||
Flux<String> response = ChatClient.create(chatModel).prompt()
|
||||
Flux<String> response = ChatClient.create(this.chatModel).prompt()
|
||||
.options(AnthropicChatOptions.builder().withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
|
||||
.build())
|
||||
.user(u -> u.text("Explain what do you see on this picture?")
|
||||
@@ -320,4 +318,8 @@ class AnthropicChatClientIT {
|
||||
assertThat(content).containsAnyOf("bowl", "basket");
|
||||
}
|
||||
|
||||
}
|
||||
record ActorsFilms(String actor, List<String> movies) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,13 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.models.AudioTranscriptionFormat;
|
||||
import com.azure.ai.openai.models.AudioTranscriptionOptions;
|
||||
import com.azure.ai.openai.models.AudioTranscriptionTimestampGranularity;
|
||||
import com.azure.core.http.rest.Response;
|
||||
|
||||
import org.springframework.ai.audio.transcription.AudioTranscription;
|
||||
import org.springframework.ai.audio.transcription.AudioTranscriptionPrompt;
|
||||
import org.springframework.ai.audio.transcription.AudioTranscriptionResponse;
|
||||
@@ -35,9 +40,6 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AzureOpenAI audio transcription client implementation for backed by
|
||||
* {@link OpenAIClient}. You provide as input the audio file you want to transcribe and
|
||||
@@ -61,6 +63,15 @@ public class AzureOpenAiAudioTranscriptionModel implements Model<AudioTranscript
|
||||
this.defaultOptions = options;
|
||||
}
|
||||
|
||||
private static byte[] toBytes(Resource resource) {
|
||||
try {
|
||||
return resource.getInputStream().readAllBytes();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to read resource: " + resource, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String call(Resource audioResource) {
|
||||
AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(audioResource);
|
||||
return call(transcriptionRequest).getResult().getOutput();
|
||||
@@ -73,7 +84,7 @@ public class AzureOpenAiAudioTranscriptionModel implements Model<AudioTranscript
|
||||
|
||||
AudioTranscriptionFormat responseFormat = audioTranscriptionOptions.getResponseFormat();
|
||||
if (JSON_FORMATS.contains(responseFormat)) {
|
||||
var audioTranscription = openAIClient.getAudioTranscription(deploymentOrModelName, FILENAME_MARKER,
|
||||
var audioTranscription = this.openAIClient.getAudioTranscription(deploymentOrModelName, FILENAME_MARKER,
|
||||
audioTranscriptionOptions);
|
||||
|
||||
List<Word> words = null;
|
||||
@@ -108,7 +119,7 @@ public class AzureOpenAiAudioTranscriptionModel implements Model<AudioTranscript
|
||||
return new AudioTranscriptionResponse(transcript, metadata);
|
||||
}
|
||||
else {
|
||||
Response<String> audioTranscription = openAIClient.getAudioTranscriptionTextWithResponse(
|
||||
Response<String> audioTranscription = this.openAIClient.getAudioTranscriptionTextWithResponse(
|
||||
deploymentOrModelName, FILENAME_MARKER, audioTranscriptionOptions, null);
|
||||
String text = audioTranscription.getValue();
|
||||
AudioTranscription transcript = new AudioTranscription(text);
|
||||
@@ -119,7 +130,7 @@ public class AzureOpenAiAudioTranscriptionModel implements Model<AudioTranscript
|
||||
private String getDeploymentName(AudioTranscriptionPrompt audioTranscriptionPrompt) {
|
||||
var runtimeOptions = audioTranscriptionPrompt.getOptions();
|
||||
|
||||
if (defaultOptions != null) {
|
||||
if (this.defaultOptions != null) {
|
||||
runtimeOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions,
|
||||
AzureOpenAiAudioTranscriptionOptions.class);
|
||||
}
|
||||
@@ -189,13 +200,4 @@ public class AzureOpenAiAudioTranscriptionModel implements Model<AudioTranscript
|
||||
return audioTranscriptionOptions;
|
||||
}
|
||||
|
||||
private static byte[] toBytes(Resource resource) {
|
||||
try {
|
||||
return resource.getInputStream().readAllBytes();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to read resource: " + resource, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,18 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.ai.openai.models.AudioTranscriptionFormat;
|
||||
import com.azure.ai.openai.models.AudioTranscriptionTimestampGranularity;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.audio.transcription.AudioTranscriptionOptions;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Piotr Olaszewski
|
||||
*/
|
||||
@@ -66,6 +68,171 @@ public class AzureOpenAiAudioTranscriptionOptions implements AudioTranscriptionO
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getDeploymentName() {
|
||||
return this.deploymentName;
|
||||
}
|
||||
|
||||
public void setDeploymentName(String deploymentName) {
|
||||
this.deploymentName = deploymentName;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getPrompt() {
|
||||
return this.prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public Float getTemperature() {
|
||||
return this.temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(Float temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public TranscriptResponseFormat getResponseFormat() {
|
||||
return this.responseFormat;
|
||||
}
|
||||
|
||||
public void setResponseFormat(TranscriptResponseFormat responseFormat) {
|
||||
this.responseFormat = responseFormat;
|
||||
}
|
||||
|
||||
public List<GranularityType> getGranularityType() {
|
||||
return this.granularityType;
|
||||
}
|
||||
|
||||
public void setGranularityType(List<GranularityType> granularityType) {
|
||||
this.granularityType = granularityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this.model == null) ? 0 : this.model.hashCode());
|
||||
result = prime * result + ((this.prompt == null) ? 0 : this.prompt.hashCode());
|
||||
result = prime * result + ((this.language == null) ? 0 : this.language.hashCode());
|
||||
result = prime * result + ((this.responseFormat == null) ? 0 : this.responseFormat.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;
|
||||
AzureOpenAiAudioTranscriptionOptions other = (AzureOpenAiAudioTranscriptionOptions) obj;
|
||||
if (this.model == null) {
|
||||
if (other.model != null)
|
||||
return false;
|
||||
}
|
||||
else if (!this.model.equals(other.model))
|
||||
return false;
|
||||
if (this.prompt == null) {
|
||||
if (other.prompt != null)
|
||||
return false;
|
||||
}
|
||||
else if (!this.prompt.equals(other.prompt))
|
||||
return false;
|
||||
if (this.language == null) {
|
||||
if (other.language != null)
|
||||
return false;
|
||||
}
|
||||
else if (!this.language.equals(other.language))
|
||||
return false;
|
||||
if (this.responseFormat == null) {
|
||||
return other.responseFormat==null;
|
||||
}
|
||||
else return this.responseFormat.equals(other.responseFormat);
|
||||
}
|
||||
|
||||
public enum WhisperModel {
|
||||
|
||||
// @formatter:off
|
||||
@JsonProperty("whisper") WHISPER("whisper");
|
||||
// @formatter:on
|
||||
|
||||
public final String value;
|
||||
|
||||
WhisperModel(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum TranscriptResponseFormat {
|
||||
|
||||
// @formatter:off
|
||||
@JsonProperty("json") JSON(AudioTranscriptionFormat.JSON, StructuredResponse.class),
|
||||
@JsonProperty("text") TEXT(AudioTranscriptionFormat.TEXT, String.class),
|
||||
@JsonProperty("srt") SRT(AudioTranscriptionFormat.SRT, String.class),
|
||||
@JsonProperty("verbose_json") VERBOSE_JSON(AudioTranscriptionFormat.VERBOSE_JSON, StructuredResponse.class),
|
||||
@JsonProperty("vtt") VTT(AudioTranscriptionFormat.VTT, String.class);
|
||||
|
||||
public final AudioTranscriptionFormat value;
|
||||
|
||||
public final Class<?> responseType;
|
||||
|
||||
TranscriptResponseFormat(AudioTranscriptionFormat value, Class<?> responseType) {
|
||||
this.value = value;
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
public AudioTranscriptionFormat getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public Class<?> getResponseType() {
|
||||
return this.responseType;
|
||||
}
|
||||
}
|
||||
|
||||
public enum GranularityType {
|
||||
|
||||
// @formatter:off
|
||||
@JsonProperty("word") WORD(AudioTranscriptionTimestampGranularity.WORD),
|
||||
@JsonProperty("segment") SEGMENT(AudioTranscriptionTimestampGranularity.SEGMENT);
|
||||
// @formatter:on
|
||||
|
||||
public final AudioTranscriptionTimestampGranularity value;
|
||||
|
||||
GranularityType(AudioTranscriptionTimestampGranularity value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public AudioTranscriptionTimestampGranularity getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
protected AzureOpenAiAudioTranscriptionOptions options;
|
||||
@@ -114,134 +281,14 @@ public class AzureOpenAiAudioTranscriptionOptions implements AudioTranscriptionO
|
||||
}
|
||||
|
||||
public AzureOpenAiAudioTranscriptionOptions build() {
|
||||
Assert.hasText(options.model, "model must not be empty");
|
||||
Assert.notNull(options.responseFormat, "response_format must not be null");
|
||||
Assert.hasText(this.options.model, "model must not be empty");
|
||||
Assert.notNull(this.options.responseFormat, "response_format must not be null");
|
||||
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getDeploymentName() {
|
||||
return deploymentName;
|
||||
}
|
||||
|
||||
public void setDeploymentName(String deploymentName) {
|
||||
this.deploymentName = deploymentName;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getPrompt() {
|
||||
return this.prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public Float getTemperature() {
|
||||
return this.temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(Float temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
|
||||
public TranscriptResponseFormat getResponseFormat() {
|
||||
return this.responseFormat;
|
||||
}
|
||||
|
||||
public void setResponseFormat(TranscriptResponseFormat responseFormat) {
|
||||
this.responseFormat = responseFormat;
|
||||
}
|
||||
|
||||
public List<GranularityType> getGranularityType() {
|
||||
return this.granularityType;
|
||||
}
|
||||
|
||||
public void setGranularityType(List<GranularityType> granularityType) {
|
||||
this.granularityType = granularityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((model == null) ? 0 : model.hashCode());
|
||||
result = prime * result + ((prompt == null) ? 0 : prompt.hashCode());
|
||||
result = prime * result + ((language == null) ? 0 : language.hashCode());
|
||||
result = prime * result + ((responseFormat == null) ? 0 : responseFormat.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;
|
||||
AzureOpenAiAudioTranscriptionOptions other = (AzureOpenAiAudioTranscriptionOptions) obj;
|
||||
if (this.model == null) {
|
||||
if (other.model != null)
|
||||
return false;
|
||||
}
|
||||
else if (!model.equals(other.model))
|
||||
return false;
|
||||
if (this.prompt == null) {
|
||||
if (other.prompt != null)
|
||||
return false;
|
||||
}
|
||||
else if (!this.prompt.equals(other.prompt))
|
||||
return false;
|
||||
if (this.language == null) {
|
||||
if (other.language != null)
|
||||
return false;
|
||||
}
|
||||
else if (!this.language.equals(other.language))
|
||||
return false;
|
||||
if (this.responseFormat == null) {
|
||||
return other.responseFormat==null;
|
||||
}
|
||||
else return this.responseFormat.equals(other.responseFormat);
|
||||
}
|
||||
|
||||
public enum WhisperModel {
|
||||
|
||||
// @formatter:off
|
||||
@JsonProperty("whisper") WHISPER("whisper");
|
||||
// @formatter:on
|
||||
|
||||
public final String value;
|
||||
|
||||
WhisperModel(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param language The language of the transcribed text.
|
||||
* @param duration The duration of the audio in seconds.
|
||||
@@ -308,51 +355,6 @@ public class AzureOpenAiAudioTranscriptionOptions implements AudioTranscriptionO
|
||||
@JsonProperty("no_speech_prob") Float noSpeechProb) {
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
public enum TranscriptResponseFormat {
|
||||
|
||||
// @formatter:off
|
||||
@JsonProperty("json") JSON(AudioTranscriptionFormat.JSON, StructuredResponse.class),
|
||||
@JsonProperty("text") TEXT(AudioTranscriptionFormat.TEXT, String.class),
|
||||
@JsonProperty("srt") SRT(AudioTranscriptionFormat.SRT, String.class),
|
||||
@JsonProperty("verbose_json") VERBOSE_JSON(AudioTranscriptionFormat.VERBOSE_JSON, StructuredResponse.class),
|
||||
@JsonProperty("vtt") VTT(AudioTranscriptionFormat.VTT, String.class);
|
||||
|
||||
public final AudioTranscriptionFormat value;
|
||||
|
||||
public final Class<?> responseType;
|
||||
|
||||
TranscriptResponseFormat(AudioTranscriptionFormat value, Class<?> responseType) {
|
||||
this.value = value;
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
public AudioTranscriptionFormat getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public Class<?> getResponseType() {
|
||||
return this.responseType;
|
||||
}
|
||||
}
|
||||
|
||||
public enum GranularityType {
|
||||
|
||||
// @formatter:off
|
||||
@JsonProperty("word") WORD(AudioTranscriptionTimestampGranularity.WORD),
|
||||
@JsonProperty("segment") SEGMENT(AudioTranscriptionTimestampGranularity.SEGMENT);
|
||||
// @formatter:on
|
||||
|
||||
public final AudioTranscriptionTimestampGranularity value;
|
||||
|
||||
GranularityType(AudioTranscriptionTimestampGranularity value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public AudioTranscriptionTimestampGranularity getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@@ -16,13 +16,49 @@
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.azure.ai.openai.OpenAIAsyncClient;
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.models.*;
|
||||
import com.azure.ai.openai.models.ChatChoice;
|
||||
import com.azure.ai.openai.models.ChatCompletions;
|
||||
import com.azure.ai.openai.models.ChatCompletionsFunctionToolCall;
|
||||
import com.azure.ai.openai.models.ChatCompletionsFunctionToolDefinition;
|
||||
import com.azure.ai.openai.models.ChatCompletionsJsonResponseFormat;
|
||||
import com.azure.ai.openai.models.ChatCompletionsOptions;
|
||||
import com.azure.ai.openai.models.ChatCompletionsResponseFormat;
|
||||
import com.azure.ai.openai.models.ChatCompletionsTextResponseFormat;
|
||||
import com.azure.ai.openai.models.ChatCompletionsToolCall;
|
||||
import com.azure.ai.openai.models.ChatCompletionsToolDefinition;
|
||||
import com.azure.ai.openai.models.ChatMessageContentItem;
|
||||
import com.azure.ai.openai.models.ChatMessageImageContentItem;
|
||||
import com.azure.ai.openai.models.ChatMessageImageUrl;
|
||||
import com.azure.ai.openai.models.ChatMessageTextContentItem;
|
||||
import com.azure.ai.openai.models.ChatRequestAssistantMessage;
|
||||
import com.azure.ai.openai.models.ChatRequestMessage;
|
||||
import com.azure.ai.openai.models.ChatRequestSystemMessage;
|
||||
import com.azure.ai.openai.models.ChatRequestToolMessage;
|
||||
import com.azure.ai.openai.models.ChatRequestUserMessage;
|
||||
import com.azure.ai.openai.models.CompletionsFinishReason;
|
||||
import com.azure.ai.openai.models.ContentFilterResultsForPrompt;
|
||||
import com.azure.ai.openai.models.FunctionCall;
|
||||
import com.azure.ai.openai.models.FunctionDefinition;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.azure.openai.metadata.AzureOpenAiUsage;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@@ -54,20 +90,6 @@ import org.springframework.ai.observation.conventions.AiProvider;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* {@link ChatModel} implementation for {@literal Microsoft Azure AI} backed by
|
||||
* {@link OpenAIClient}.
|
||||
@@ -153,6 +175,19 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
public static ChatResponseMetadata from(ChatCompletions chatCompletions, PromptMetadata promptFilterMetadata) {
|
||||
Assert.notNull(chatCompletions, "Azure OpenAI ChatCompletions must not be null");
|
||||
String id = chatCompletions.getId();
|
||||
Usage usage = (chatCompletions.getUsage() != null) ? AzureOpenAiUsage.from(chatCompletions) : new EmptyUsage();
|
||||
return ChatResponseMetadata.builder()
|
||||
.withId(id)
|
||||
.withUsage(usage)
|
||||
.withModel(chatCompletions.getModel())
|
||||
.withPromptMetadata(promptFilterMetadata)
|
||||
.withKeyValue("system-fingerprint", chatCompletions.getSystemFingerprint())
|
||||
.build();
|
||||
}
|
||||
|
||||
public AzureOpenAiChatOptions getDefaultOptions() {
|
||||
return AzureOpenAiChatOptions.fromOptions(this.defaultOptions);
|
||||
}
|
||||
@@ -302,19 +337,6 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
return new Generation(assistantMessage, generationMetadata);
|
||||
}
|
||||
|
||||
public static ChatResponseMetadata from(ChatCompletions chatCompletions, PromptMetadata promptFilterMetadata) {
|
||||
Assert.notNull(chatCompletions, "Azure OpenAI ChatCompletions must not be null");
|
||||
String id = chatCompletions.getId();
|
||||
Usage usage = (chatCompletions.getUsage() != null) ? AzureOpenAiUsage.from(chatCompletions) : new EmptyUsage();
|
||||
return ChatResponseMetadata.builder()
|
||||
.withId(id)
|
||||
.withUsage(usage)
|
||||
.withModel(chatCompletions.getModel())
|
||||
.withPromptMetadata(promptFilterMetadata)
|
||||
.withKeyValue("system-fingerprint", chatCompletions.getSystemFingerprint())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test access.
|
||||
*/
|
||||
@@ -332,8 +354,9 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
|
||||
options = this.merge(options, this.defaultOptions);
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions()))
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
|
||||
if (prompt.getOptions() != null) {
|
||||
AzureOpenAiChatOptions updatedRuntimeOptions;
|
||||
@@ -428,14 +451,16 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
|
||||
private String getMediaUrl(Media media) {
|
||||
Object data = media.getData();
|
||||
if (data instanceof String dataUrl)
|
||||
if (data instanceof String dataUrl) {
|
||||
return dataUrl;
|
||||
}
|
||||
else if (data instanceof byte[] dataBytes) {
|
||||
String base64EncodedData = Base64.getEncoder().encodeToString(dataBytes);
|
||||
return "data:" + media.getMimeType() + ";base64," + base64EncodedData;
|
||||
}
|
||||
else
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown media data type " + data.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
private ChatGenerationMetadata generateChoiceMetadata(ChatChoice choice) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@@ -22,18 +22,18 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.azure.ai.openai.models.AzureChatEnhancementConfiguration;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The configuration information for a chat completions request. Completions support a
|
||||
* wide variety of tasks and generate text that continues from or "completes" provided
|
||||
@@ -206,129 +206,26 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions, ChatOptio
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
protected AzureOpenAiChatOptions options;
|
||||
|
||||
public Builder() {
|
||||
this.options = new AzureOpenAiChatOptions();
|
||||
}
|
||||
|
||||
public Builder(AzureOpenAiChatOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public Builder withDeploymentName(String deploymentName) {
|
||||
this.options.deploymentName = deploymentName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFrequencyPenalty(Double frequencyPenalty) {
|
||||
this.options.frequencyPenalty = frequencyPenalty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLogitBias(Map<String, Integer> logitBias) {
|
||||
this.options.logitBias = logitBias;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.maxTokens = maxTokens;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withN(Integer n) {
|
||||
this.options.n = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPresencePenalty(Double presencePenalty) {
|
||||
this.options.presencePenalty = presencePenalty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStop(List<String> stop) {
|
||||
this.options.stop = stop;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUser(String user) {
|
||||
this.options.user = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.options.functionCallbacks = functionCallbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctions(Set<String> functionNames) {
|
||||
Assert.notNull(functionNames, "Function names must not be null");
|
||||
this.options.functions = functionNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunction(String functionName) {
|
||||
Assert.hasText(functionName, "Function name must not be empty");
|
||||
this.options.functions.add(functionName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withResponseFormat(AzureOpenAiResponseFormat responseFormat) {
|
||||
this.options.responseFormat = responseFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withProxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.options.proxyToolCalls = proxyToolCalls;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSeed(Long seed) {
|
||||
this.options.seed = seed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLogprobs(Boolean logprobs) {
|
||||
this.options.logprobs = logprobs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopLogprobs(Integer topLogprobs) {
|
||||
this.options.topLogProbs = topLogprobs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withEnhancements(AzureChatEnhancementConfiguration enhancements) {
|
||||
this.options.enhancements = enhancements;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withToolContext(Map<String, Object> toolContext) {
|
||||
if (this.options.toolContext == null) {
|
||||
this.options.toolContext = toolContext;
|
||||
}
|
||||
else {
|
||||
this.options.toolContext.putAll(toolContext);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AzureOpenAiChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public static AzureOpenAiChatOptions fromOptions(AzureOpenAiChatOptions fromOptions) {
|
||||
return builder().withDeploymentName(fromOptions.getDeploymentName())
|
||||
.withFrequencyPenalty(fromOptions.getFrequencyPenalty() != null ? fromOptions.getFrequencyPenalty() : null)
|
||||
.withLogitBias(fromOptions.getLogitBias())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withN(fromOptions.getN())
|
||||
.withPresencePenalty(fromOptions.getPresencePenalty() != null ? fromOptions.getPresencePenalty() : null)
|
||||
.withStop(fromOptions.getStop())
|
||||
.withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withUser(fromOptions.getUser())
|
||||
.withFunctionCallbacks(fromOptions.getFunctionCallbacks())
|
||||
.withFunctions(fromOptions.getFunctions())
|
||||
.withResponseFormat(fromOptions.getResponseFormat())
|
||||
.withSeed(fromOptions.getSeed())
|
||||
.withLogprobs(fromOptions.isLogprobs())
|
||||
.withTopLogprobs(fromOptions.getTopLogProbs())
|
||||
.withEnhancements(fromOptions.getEnhancements())
|
||||
.withToolContext(fromOptions.getToolContext())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -526,26 +423,129 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions, ChatOptio
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static AzureOpenAiChatOptions fromOptions(AzureOpenAiChatOptions fromOptions) {
|
||||
return builder().withDeploymentName(fromOptions.getDeploymentName())
|
||||
.withFrequencyPenalty(fromOptions.getFrequencyPenalty() != null ? fromOptions.getFrequencyPenalty() : null)
|
||||
.withLogitBias(fromOptions.getLogitBias())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withN(fromOptions.getN())
|
||||
.withPresencePenalty(fromOptions.getPresencePenalty() != null ? fromOptions.getPresencePenalty() : null)
|
||||
.withStop(fromOptions.getStop())
|
||||
.withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withUser(fromOptions.getUser())
|
||||
.withFunctionCallbacks(fromOptions.getFunctionCallbacks())
|
||||
.withFunctions(fromOptions.getFunctions())
|
||||
.withResponseFormat(fromOptions.getResponseFormat())
|
||||
.withSeed(fromOptions.getSeed())
|
||||
.withLogprobs(fromOptions.isLogprobs())
|
||||
.withTopLogprobs(fromOptions.getTopLogProbs())
|
||||
.withEnhancements(fromOptions.getEnhancements())
|
||||
.withToolContext(fromOptions.getToolContext())
|
||||
.build();
|
||||
public static class Builder {
|
||||
|
||||
protected AzureOpenAiChatOptions options;
|
||||
|
||||
public Builder() {
|
||||
this.options = new AzureOpenAiChatOptions();
|
||||
}
|
||||
|
||||
public Builder(AzureOpenAiChatOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public Builder withDeploymentName(String deploymentName) {
|
||||
this.options.deploymentName = deploymentName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFrequencyPenalty(Double frequencyPenalty) {
|
||||
this.options.frequencyPenalty = frequencyPenalty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLogitBias(Map<String, Integer> logitBias) {
|
||||
this.options.logitBias = logitBias;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.maxTokens = maxTokens;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withN(Integer n) {
|
||||
this.options.n = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPresencePenalty(Double presencePenalty) {
|
||||
this.options.presencePenalty = presencePenalty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStop(List<String> stop) {
|
||||
this.options.stop = stop;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUser(String user) {
|
||||
this.options.user = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.options.functionCallbacks = functionCallbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunctions(Set<String> functionNames) {
|
||||
Assert.notNull(functionNames, "Function names must not be null");
|
||||
this.options.functions = functionNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFunction(String functionName) {
|
||||
Assert.hasText(functionName, "Function name must not be empty");
|
||||
this.options.functions.add(functionName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withResponseFormat(AzureOpenAiResponseFormat responseFormat) {
|
||||
this.options.responseFormat = responseFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withProxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.options.proxyToolCalls = proxyToolCalls;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSeed(Long seed) {
|
||||
this.options.seed = seed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLogprobs(Boolean logprobs) {
|
||||
this.options.logprobs = logprobs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopLogprobs(Integer topLogprobs) {
|
||||
this.options.topLogProbs = topLogprobs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withEnhancements(AzureChatEnhancementConfiguration enhancements) {
|
||||
this.options.enhancements = enhancements;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withToolContext(Map<String, Object> toolContext) {
|
||||
if (this.options.toolContext == null) {
|
||||
this.options.toolContext = toolContext;
|
||||
}
|
||||
else {
|
||||
this.options.toolContext.putAll(toolContext);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AzureOpenAiChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,17 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.models.EmbeddingItem;
|
||||
import com.azure.ai.openai.models.Embeddings;
|
||||
import com.azure.ai.openai.models.EmbeddingsOptions;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.azure.openai.metadata.AzureOpenAiEmbeddingUsage;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.document.MetadataMode;
|
||||
@@ -41,9 +44,6 @@ import org.springframework.ai.observation.conventions.AiProvider;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Azure Open AI Embedding Model implementation.
|
||||
*
|
||||
@@ -56,14 +56,14 @@ public class AzureOpenAiEmbeddingModel extends AbstractEmbeddingModel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AzureOpenAiEmbeddingModel.class);
|
||||
|
||||
private static final EmbeddingModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultEmbeddingModelObservationConvention();
|
||||
|
||||
private final OpenAIClient azureOpenAiClient;
|
||||
|
||||
private final AzureOpenAiEmbeddingOptions defaultOptions;
|
||||
|
||||
private final MetadataMode metadataMode;
|
||||
|
||||
private static final EmbeddingModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultEmbeddingModelObservationConvention();
|
||||
|
||||
/**
|
||||
* Observation registry used for instrumentation.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,11 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import org.springframework.ai.embedding.EmbeddingOptions;
|
||||
|
||||
/**
|
||||
@@ -58,6 +60,61 @@ public class AzureOpenAiEmbeddingOptions implements EmbeddingOptions {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getModel() {
|
||||
return getDeploymentName();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setModel(String model) {
|
||||
setDeploymentName(model);
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getDeploymentName() {
|
||||
return this.deploymentName;
|
||||
}
|
||||
|
||||
public void setDeploymentName(String deploymentName) {
|
||||
this.deploymentName = deploymentName;
|
||||
}
|
||||
|
||||
public String getInputType() {
|
||||
return this.inputType;
|
||||
}
|
||||
|
||||
public void setInputType(String inputType) {
|
||||
this.inputType = inputType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDimensions() {
|
||||
return this.dimensions;
|
||||
}
|
||||
|
||||
public void setDimensions(Integer dimensions) {
|
||||
this.dimensions = dimensions;
|
||||
}
|
||||
|
||||
public com.azure.ai.openai.models.EmbeddingsOptions toAzureOptions(List<String> instructions) {
|
||||
|
||||
var azureOptions = new com.azure.ai.openai.models.EmbeddingsOptions(instructions);
|
||||
azureOptions.setModel(this.getDeploymentName());
|
||||
azureOptions.setUser(this.getUser());
|
||||
azureOptions.setInputType(this.getInputType());
|
||||
azureOptions.setDimensions(this.getDimensions());
|
||||
|
||||
return azureOptions;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final AzureOpenAiEmbeddingOptions options = new AzureOpenAiEmbeddingOptions();
|
||||
@@ -125,59 +182,4 @@ public class AzureOpenAiEmbeddingOptions implements EmbeddingOptions {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getModel() {
|
||||
return getDeploymentName();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setModel(String model) {
|
||||
setDeploymentName(model);
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getDeploymentName() {
|
||||
return this.deploymentName;
|
||||
}
|
||||
|
||||
public void setDeploymentName(String deploymentName) {
|
||||
this.deploymentName = deploymentName;
|
||||
}
|
||||
|
||||
public String getInputType() {
|
||||
return this.inputType;
|
||||
}
|
||||
|
||||
public void setInputType(String inputType) {
|
||||
this.inputType = inputType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDimensions() {
|
||||
return this.dimensions;
|
||||
}
|
||||
|
||||
public void setDimensions(Integer dimensions) {
|
||||
this.dimensions = dimensions;
|
||||
}
|
||||
|
||||
public com.azure.ai.openai.models.EmbeddingsOptions toAzureOptions(List<String> instructions) {
|
||||
|
||||
var azureOptions = new com.azure.ai.openai.models.EmbeddingsOptions(instructions);
|
||||
azureOptions.setModel(this.getDeploymentName());
|
||||
azureOptions.setUser(this.getUser());
|
||||
azureOptions.setInputType(this.getInputType());
|
||||
azureOptions.setDimensions(this.getDimensions());
|
||||
|
||||
return azureOptions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
/*
|
||||
* 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.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.models.ImageGenerationOptions;
|
||||
import com.azure.ai.openai.models.ImageGenerationQuality;
|
||||
@@ -13,6 +31,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.azure.openai.metadata.AzureOpenAiImageGenerationMetadata;
|
||||
import org.springframework.ai.azure.openai.metadata.AzureOpenAiImageResponseMetadata;
|
||||
import org.springframework.ai.image.Image;
|
||||
@@ -25,8 +44,6 @@ import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.util.JacksonUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
@@ -68,22 +85,22 @@ public class AzureOpenAiImageModel implements ImageModel {
|
||||
}
|
||||
|
||||
public AzureOpenAiImageOptions getDefaultOptions() {
|
||||
return defaultOptions;
|
||||
return this.defaultOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageResponse call(ImagePrompt imagePrompt) {
|
||||
ImageGenerationOptions imageGenerationOptions = toOpenAiImageOptions(imagePrompt);
|
||||
String deploymentOrModelName = getDeploymentName(imagePrompt);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Azure ImageGenerationOptions call {} with the following options : {} ", deploymentOrModelName,
|
||||
toPrettyJson(imageGenerationOptions));
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Azure ImageGenerationOptions call {} with the following options : {} ",
|
||||
deploymentOrModelName, toPrettyJson(imageGenerationOptions));
|
||||
}
|
||||
|
||||
var images = openAIClient.getImageGenerations(deploymentOrModelName, imageGenerationOptions);
|
||||
var images = this.openAIClient.getImageGenerations(deploymentOrModelName, imageGenerationOptions);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Azure ImageGenerations: {}", toPrettyJson(images));
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Azure ImageGenerations: {}", toPrettyJson(images));
|
||||
}
|
||||
|
||||
List<ImageGeneration> imageGenerations = images.getData().stream().map(entry -> {
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
/*
|
||||
* 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.azure.openai;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
|
||||
/**
|
||||
* The configuration information for a image generation request.
|
||||
*
|
||||
@@ -89,9 +105,13 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
@JsonProperty("user")
|
||||
private String user;
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getN() {
|
||||
return n;
|
||||
return this.n;
|
||||
}
|
||||
|
||||
public void setN(Integer n) {
|
||||
@@ -100,7 +120,7 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return model;
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
@@ -109,7 +129,7 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
|
||||
@Override
|
||||
public Integer getWidth() {
|
||||
return width;
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public void setWidth(Integer width) {
|
||||
@@ -119,7 +139,7 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
|
||||
@Override
|
||||
public Integer getHeight() {
|
||||
return height;
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public void setHeight(Integer height) {
|
||||
@@ -129,7 +149,7 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
|
||||
@Override
|
||||
public String getResponseFormat() {
|
||||
return responseFormat;
|
||||
return this.responseFormat;
|
||||
}
|
||||
|
||||
public void setResponseFormat(String responseFormat) {
|
||||
@@ -148,7 +168,7 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
@@ -156,7 +176,7 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
}
|
||||
|
||||
public String getQuality() {
|
||||
return quality;
|
||||
return this.quality;
|
||||
}
|
||||
|
||||
public void setQuality(String quality) {
|
||||
@@ -165,7 +185,7 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
|
||||
@Override
|
||||
public String getStyle() {
|
||||
return style;
|
||||
return this.style;
|
||||
}
|
||||
|
||||
public void setStyle(String style) {
|
||||
@@ -173,95 +193,40 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
}
|
||||
|
||||
public String getDeploymentName() {
|
||||
return deploymentName;
|
||||
return this.deploymentName;
|
||||
}
|
||||
|
||||
public void setDeploymentName(String deploymentName) {
|
||||
this.deploymentName = deploymentName;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
if (this == o) {
|
||||
return true;
|
||||
if (!(o instanceof AzureOpenAiImageOptions that))
|
||||
}
|
||||
if (!(o instanceof AzureOpenAiImageOptions that)) {
|
||||
return false;
|
||||
return Objects.equals(n, that.n) && Objects.equals(model, that.model)
|
||||
&& Objects.equals(deploymentName, that.deploymentName) && Objects.equals(width, that.width)
|
||||
&& Objects.equals(height, that.height) && Objects.equals(quality, that.quality)
|
||||
&& Objects.equals(responseFormat, that.responseFormat) && Objects.equals(size, that.size)
|
||||
&& Objects.equals(style, that.style) && Objects.equals(user, that.user);
|
||||
}
|
||||
return Objects.equals(this.n, that.n) && Objects.equals(this.model, that.model)
|
||||
&& Objects.equals(this.deploymentName, that.deploymentName) && Objects.equals(this.width, that.width)
|
||||
&& Objects.equals(this.height, that.height) && Objects.equals(this.quality, that.quality)
|
||||
&& Objects.equals(this.responseFormat, that.responseFormat) && Objects.equals(this.size, that.size)
|
||||
&& Objects.equals(this.style, that.style) && Objects.equals(this.user, that.user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(n, model, deploymentName, width, height, quality, responseFormat, size, style, user);
|
||||
return Objects.hash(this.n, this.model, this.deploymentName, this.width, this.height, this.quality,
|
||||
this.responseFormat, this.size, this.style, this.user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AzureOpenAiImageOptions{" + "n=" + n + ", model='" + model + '\'' + ", deploymentName='"
|
||||
+ deploymentName + '\'' + ", width=" + width + ", height=" + height + ", quality='" + quality + '\''
|
||||
+ ", responseFormat='" + responseFormat + '\'' + ", size='" + size + '\'' + ", style='" + style + '\''
|
||||
+ ", user='" + user + '\'' + '}';
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final AzureOpenAiImageOptions options;
|
||||
|
||||
private Builder() {
|
||||
this.options = new AzureOpenAiImageOptions();
|
||||
}
|
||||
|
||||
public Builder withN(Integer n) {
|
||||
options.setN(n);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withModel(String model) {
|
||||
options.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDeploymentName(String deploymentName) {
|
||||
options.setDeploymentName(deploymentName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withResponseFormat(String responseFormat) {
|
||||
options.setResponseFormat(responseFormat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withWidth(Integer width) {
|
||||
options.setWidth(width);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withHeight(Integer height) {
|
||||
options.setHeight(height);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUser(String user) {
|
||||
options.setUser(user);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AzureOpenAiImageOptions build() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public Builder withStyle(String style) {
|
||||
options.setStyle(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
return "AzureOpenAiImageOptions{" + "n=" + this.n + ", model='" + this.model + '\'' + ", deploymentName='"
|
||||
+ this.deploymentName + '\'' + ", width=" + this.width + ", height=" + this.height + ", quality='"
|
||||
+ this.quality + '\'' + ", responseFormat='" + this.responseFormat + '\'' + ", size='" + this.size
|
||||
+ '\'' + ", style='" + this.style + '\'' + ", user='" + this.user + '\'' + '}';
|
||||
}
|
||||
|
||||
public enum ImageModel {
|
||||
@@ -290,4 +255,58 @@ public class AzureOpenAiImageOptions implements ImageOptions {
|
||||
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final AzureOpenAiImageOptions options;
|
||||
|
||||
private Builder() {
|
||||
this.options = new AzureOpenAiImageOptions();
|
||||
}
|
||||
|
||||
public Builder withN(Integer n) {
|
||||
this.options.setN(n);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withModel(String model) {
|
||||
this.options.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDeploymentName(String deploymentName) {
|
||||
this.options.setDeploymentName(deploymentName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withResponseFormat(String responseFormat) {
|
||||
this.options.setResponseFormat(responseFormat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withWidth(Integer width) {
|
||||
this.options.setWidth(width);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withHeight(Integer height) {
|
||||
this.options.setHeight(height);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUser(String user) {
|
||||
this.options.setUser(user);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AzureOpenAiImageOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public Builder withStyle(String style) {
|
||||
this.options.setStyle(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -49,6 +50,15 @@ import org.springframework.util.CollectionUtils;
|
||||
*/
|
||||
public class MergeUtils {
|
||||
|
||||
private static final Class<?>[] CHAT_COMPLETIONS_CONSTRUCTOR_ARG_TYPES = new Class<?>[] { String.class,
|
||||
OffsetDateTime.class, List.class, CompletionsUsage.class };
|
||||
|
||||
private static final Class<?>[] chatChoiceConstructorArgumentTypes = new Class<?>[] {
|
||||
ChatChoiceLogProbabilityInfo.class, int.class, CompletionsFinishReason.class };
|
||||
|
||||
private static final Class<?>[] chatResponseMessageConstructorArgumentTypes = new Class<?>[] { ChatRole.class,
|
||||
String.class };
|
||||
|
||||
/**
|
||||
* Create a new instance of the given class using the constructor at the given index.
|
||||
* Can be used to create instances with private constructors.
|
||||
@@ -106,9 +116,6 @@ public class MergeUtils {
|
||||
return chatCompletionsInstance;
|
||||
}
|
||||
|
||||
private static final Class<?>[] CHAT_COMPLETIONS_CONSTRUCTOR_ARG_TYPES = new Class<?>[] { String.class,
|
||||
OffsetDateTime.class, List.class, CompletionsUsage.class };
|
||||
|
||||
/**
|
||||
* Merge two ChatCompletions instances into a single ChatCompletions instance.
|
||||
* @param left the left ChatCompletions instance.
|
||||
@@ -158,9 +165,6 @@ public class MergeUtils {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static final Class<?>[] chatChoiceConstructorArgumentTypes = new Class<?>[] {
|
||||
ChatChoiceLogProbabilityInfo.class, int.class, CompletionsFinishReason.class };
|
||||
|
||||
/**
|
||||
* Merge two ChatChoice instances into a single ChatChoice instance.
|
||||
* @param left the left ChatChoice instance to merge.
|
||||
@@ -211,9 +215,6 @@ public class MergeUtils {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static final Class<?>[] chatResponseMessageConstructorArgumentTypes = new Class<?>[] { ChatRole.class,
|
||||
String.class };
|
||||
|
||||
/**
|
||||
* Merge two ChatResponseMessage instances into a single ChatResponseMessage instance.
|
||||
* @param left the left ChatResponseMessage instance to merge.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.aot;
|
||||
|
||||
import com.azure.ai.openai.OpenAIAsyncClient;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.metadata;
|
||||
|
||||
import org.springframework.ai.audio.transcription.AudioTranscriptionResponseMetadata;
|
||||
@@ -26,10 +27,14 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class AzureOpenAiAudioTranscriptionResponseMetadata extends AudioTranscriptionResponseMetadata {
|
||||
|
||||
public static final AzureOpenAiAudioTranscriptionResponseMetadata NULL = new AzureOpenAiAudioTranscriptionResponseMetadata() {
|
||||
|
||||
};
|
||||
|
||||
protected static final String AI_METADATA_STRING = "{ @type: %1$s }";
|
||||
|
||||
public static final AzureOpenAiAudioTranscriptionResponseMetadata NULL = new AzureOpenAiAudioTranscriptionResponseMetadata() {
|
||||
};
|
||||
protected AzureOpenAiAudioTranscriptionResponseMetadata() {
|
||||
}
|
||||
|
||||
public static AzureOpenAiAudioTranscriptionResponseMetadata from(
|
||||
AzureOpenAiAudioTranscriptionOptions.StructuredResponse result) {
|
||||
@@ -42,9 +47,6 @@ public class AzureOpenAiAudioTranscriptionResponseMetadata extends AudioTranscri
|
||||
return new AzureOpenAiAudioTranscriptionResponseMetadata();
|
||||
}
|
||||
|
||||
protected AzureOpenAiAudioTranscriptionResponseMetadata() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return AI_METADATA_STRING.formatted(getClass().getName());
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,9 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.metadata;
|
||||
|
||||
import com.azure.ai.openai.models.EmbeddingsUsage;
|
||||
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -27,11 +29,6 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class AzureOpenAiEmbeddingUsage implements Usage {
|
||||
|
||||
public static AzureOpenAiEmbeddingUsage from(EmbeddingsUsage usage) {
|
||||
Assert.notNull(usage, "EmbeddingsUsage must not be null");
|
||||
return new AzureOpenAiEmbeddingUsage(usage);
|
||||
}
|
||||
|
||||
private final EmbeddingsUsage usage;
|
||||
|
||||
public AzureOpenAiEmbeddingUsage(EmbeddingsUsage usage) {
|
||||
@@ -39,6 +36,11 @@ public class AzureOpenAiEmbeddingUsage implements Usage {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public static AzureOpenAiEmbeddingUsage from(EmbeddingsUsage usage) {
|
||||
Assert.notNull(usage, "EmbeddingsUsage must not be null");
|
||||
return new AzureOpenAiEmbeddingUsage(usage);
|
||||
}
|
||||
|
||||
protected EmbeddingsUsage getUsage() {
|
||||
return this.usage;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
/*
|
||||
* 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.azure.openai.metadata;
|
||||
|
||||
import org.springframework.ai.image.ImageGenerationMetadata;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.ai.image.ImageGenerationMetadata;
|
||||
|
||||
/**
|
||||
* Represents the metadata for image generation using Azure OpenAI.
|
||||
*
|
||||
@@ -19,25 +35,27 @@ public class AzureOpenAiImageGenerationMetadata implements ImageGenerationMetada
|
||||
}
|
||||
|
||||
public String getRevisedPrompt() {
|
||||
return revisedPrompt;
|
||||
return this.revisedPrompt;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "AzureOpenAiImageGenerationMetadata{" + "revisedPrompt='" + revisedPrompt + '\'' + '}';
|
||||
return "AzureOpenAiImageGenerationMetadata{" + "revisedPrompt='" + this.revisedPrompt + '\'' + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
if (this == o) {
|
||||
return true;
|
||||
if (!(o instanceof AzureOpenAiImageGenerationMetadata that))
|
||||
}
|
||||
if (!(o instanceof AzureOpenAiImageGenerationMetadata that)) {
|
||||
return false;
|
||||
return Objects.equals(revisedPrompt, that.revisedPrompt);
|
||||
}
|
||||
return Objects.equals(this.revisedPrompt, that.revisedPrompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(revisedPrompt);
|
||||
return Objects.hash(this.revisedPrompt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
/*
|
||||
* 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.azure.openai.metadata;
|
||||
|
||||
import com.azure.ai.openai.models.ImageGenerations;
|
||||
import org.springframework.ai.image.ImageResponseMetadata;
|
||||
import org.springframework.ai.model.MutableResponseMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.azure.ai.openai.models.ImageGenerations;
|
||||
|
||||
import org.springframework.ai.image.ImageResponseMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Represents metadata associated with an image response from the Azure OpenAI image
|
||||
* model. It provides additional information about the generative response from the Azure
|
||||
@@ -20,15 +35,15 @@ public class AzureOpenAiImageResponseMetadata extends ImageResponseMetadata {
|
||||
|
||||
private final Long created;
|
||||
|
||||
protected AzureOpenAiImageResponseMetadata(Long created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public static AzureOpenAiImageResponseMetadata from(ImageGenerations openAiImageResponse) {
|
||||
Assert.notNull(openAiImageResponse, "OpenAiImageResponse must not be null");
|
||||
return new AzureOpenAiImageResponseMetadata(openAiImageResponse.getCreatedAt().toEpochSecond());
|
||||
}
|
||||
|
||||
protected AzureOpenAiImageResponseMetadata(Long created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreated() {
|
||||
return this.created;
|
||||
@@ -36,21 +51,23 @@ public class AzureOpenAiImageResponseMetadata extends ImageResponseMetadata {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AzureOpenAiImageResponseMetadata{" + "created=" + created + '}';
|
||||
return "AzureOpenAiImageResponseMetadata{" + "created=" + this.created + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
if (this == o) {
|
||||
return true;
|
||||
if (!(o instanceof AzureOpenAiImageResponseMetadata that))
|
||||
}
|
||||
if (!(o instanceof AzureOpenAiImageResponseMetadata that)) {
|
||||
return false;
|
||||
return Objects.equals(created, that.created);
|
||||
}
|
||||
return Objects.equals(this.created, that.created);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(created);
|
||||
return Objects.hash(this.created);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.metadata;
|
||||
|
||||
import com.azure.ai.openai.models.ChatCompletions;
|
||||
@@ -30,6 +31,13 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class AzureOpenAiUsage implements Usage {
|
||||
|
||||
private final CompletionsUsage usage;
|
||||
|
||||
public AzureOpenAiUsage(CompletionsUsage usage) {
|
||||
Assert.notNull(usage, "CompletionsUsage must not be null");
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public static AzureOpenAiUsage from(ChatCompletions chatCompletions) {
|
||||
Assert.notNull(chatCompletions, "ChatCompletions must not be null");
|
||||
return from(chatCompletions.getUsage());
|
||||
@@ -39,13 +47,6 @@ public class AzureOpenAiUsage implements Usage {
|
||||
return new AzureOpenAiUsage(usage);
|
||||
}
|
||||
|
||||
private final CompletionsUsage usage;
|
||||
|
||||
public AzureOpenAiUsage(CompletionsUsage usage) {
|
||||
Assert.notNull(usage, "CompletionsUsage must not be null");
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
protected CompletionsUsage getUsage() {
|
||||
return this.usage;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.models.AzureChatEnhancementConfiguration;
|
||||
import com.azure.ai.openai.models.AzureChatOCREnhancementConfiguration;
|
||||
import com.azure.ai.openai.models.ChatCompletionsJsonResponseFormat;
|
||||
import com.azure.ai.openai.models.ChatCompletionsTextResponseFormat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -30,10 +32,6 @@ import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@@ -42,6 +40,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
public class AzureChatCompletionsOptionsTests {
|
||||
|
||||
private static Stream<Arguments> providePresencePenaltyAndFrequencyPenaltyTest() {
|
||||
return Stream.of(Arguments.of(0.0, 0.0), Arguments.of(0.0, 1.0), Arguments.of(1.0, 0.0), Arguments.of(1.0, 1.0),
|
||||
Arguments.of(1.0, null), Arguments.of(null, 1.0), Arguments.of(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRequestWithChatOptions() {
|
||||
|
||||
@@ -132,11 +135,6 @@ public class AzureChatCompletionsOptionsTests {
|
||||
assertThat(requestOptions.getResponseFormat()).isInstanceOf(ChatCompletionsJsonResponseFormat.class);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> providePresencePenaltyAndFrequencyPenaltyTest() {
|
||||
return Stream.of(Arguments.of(0.0, 0.0), Arguments.of(0.0, 1.0), Arguments.of(1.0, 0.0), Arguments.of(1.0, 1.0),
|
||||
Arguments.of(1.0, null), Arguments.of(null, 1.0), Arguments.of(null, null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("providePresencePenaltyAndFrequencyPenaltyTest")
|
||||
public void createChatOptionsWithPresencePenaltyAndFrequencyPenalty(Double presencePenalty,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.azure.openai;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
@@ -6,6 +22,7 @@ import com.azure.ai.openai.OpenAIServiceVersion;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
import org.springframework.ai.audio.transcription.AudioTranscriptionPrompt;
|
||||
import org.springframework.ai.audio.transcription.AudioTranscriptionResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -38,8 +55,9 @@ class AzureOpenAiAudioTranscriptionModelIT {
|
||||
.withResponseFormat(AzureOpenAiAudioTranscriptionOptions.TranscriptResponseFormat.TEXT)
|
||||
.withTemperature(0f)
|
||||
.build();
|
||||
AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(audioFile, transcriptionOptions);
|
||||
AudioTranscriptionResponse response = transcriptionModel.call(transcriptionRequest);
|
||||
AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(this.audioFile,
|
||||
transcriptionOptions);
|
||||
AudioTranscriptionResponse response = this.transcriptionModel.call(transcriptionRequest);
|
||||
assertThat(response.getResults()).hasSize(1);
|
||||
assertThat(response.getResults().get(0).getOutput().toLowerCase().contains("fellow")).isTrue();
|
||||
}
|
||||
@@ -54,8 +72,9 @@ class AzureOpenAiAudioTranscriptionModelIT {
|
||||
.withTemperature(0f)
|
||||
.withResponseFormat(responseFormat)
|
||||
.build();
|
||||
AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(audioFile, transcriptionOptions);
|
||||
AudioTranscriptionResponse response = transcriptionModel.call(transcriptionRequest);
|
||||
AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(this.audioFile,
|
||||
transcriptionOptions);
|
||||
AudioTranscriptionResponse response = this.transcriptionModel.call(transcriptionRequest);
|
||||
assertThat(response.getResults()).hasSize(1);
|
||||
assertThat(response.getResults().get(0).getOutput().toLowerCase().contains("fellow")).isTrue();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -16,15 +16,17 @@
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import static com.azure.core.http.policy.HttpLogDetailLevel.BODY_AND_HEADERS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.OpenAIServiceVersion;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.http.policy.HttpLogOptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||
@@ -35,13 +37,10 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.OpenAIServiceVersion;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.http.policy.HttpLogOptions;
|
||||
import org.springframework.core.io.Resource;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static com.azure.core.http.policy.HttpLogDetailLevel.BODY_AND_HEADERS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Soby Chacko
|
||||
@@ -57,16 +56,13 @@ public class AzureOpenAiChatClientIT {
|
||||
@Value("classpath:/prompts/system-message.st")
|
||||
private Resource systemTextResource;
|
||||
|
||||
record ActorsFilms(String actor, List<String> movies) {
|
||||
}
|
||||
|
||||
@Test
|
||||
void call() {
|
||||
|
||||
// @formatter:off
|
||||
ChatResponse response = chatClient.prompt()
|
||||
ChatResponse response = this.chatClient.prompt()
|
||||
.advisors(new SimpleLoggerAdvisor())
|
||||
.system(s -> s.text(systemTextResource)
|
||||
.system(s -> s.text(this.systemTextResource)
|
||||
.param("name", "Bob")
|
||||
.param("voice", "pirate"))
|
||||
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
|
||||
@@ -84,7 +80,7 @@ public class AzureOpenAiChatClientIT {
|
||||
BeanOutputConverter<ActorsFilms> outputConverter = new BeanOutputConverter<>(ActorsFilms.class);
|
||||
|
||||
// @formatter:off
|
||||
Flux<ChatResponse> chatResponse = chatClient
|
||||
Flux<ChatResponse> chatResponse = this.chatClient
|
||||
.prompt()
|
||||
.advisors(new SimpleLoggerAdvisor())
|
||||
.user(u -> u
|
||||
@@ -117,12 +113,12 @@ public class AzureOpenAiChatClientIT {
|
||||
+ "List them with a numerical index. Do not use any abbreviations in state or capitals.";
|
||||
|
||||
// Imperative call
|
||||
String rawDataFromImperativeCall = chatClient.prompt(prompt).call().content();
|
||||
String rawDataFromImperativeCall = this.chatClient.prompt(prompt).call().content();
|
||||
String imperativeStatesData = extractStatesData(rawDataFromImperativeCall);
|
||||
String formattedImperativeResponse = formatResponse(imperativeStatesData);
|
||||
|
||||
// Streaming call
|
||||
String stitchedResponseFromStream = chatClient.prompt(prompt)
|
||||
String stitchedResponseFromStream = this.chatClient.prompt(prompt)
|
||||
.stream()
|
||||
.content()
|
||||
.collectList()
|
||||
@@ -150,6 +146,10 @@ public class AzureOpenAiChatClientIT {
|
||||
return String.join("\n", Arrays.stream(response.split("\n")).map(String::strip).toArray(String[]::new));
|
||||
}
|
||||
|
||||
record ActorsFilms(String actor, List<String> movies) {
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
public static class TestConfiguration {
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,8 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.OpenAIServiceVersion;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
@@ -23,6 +32,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@@ -44,14 +54,6 @@ import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.azure.core.http.policy.HttpLogDetailLevel.BODY_AND_HEADERS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -77,7 +79,7 @@ class AzureOpenAiChatModelIT {
|
||||
UserMessage userMessage = new UserMessage("Generate the names of 5 famous pirates.");
|
||||
|
||||
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
|
||||
ChatResponse response = chatModel.call(prompt);
|
||||
ChatResponse response = this.chatModel.call(prompt);
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard");
|
||||
}
|
||||
|
||||
@@ -96,12 +98,12 @@ class AzureOpenAiChatModelIT {
|
||||
|
||||
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
|
||||
|
||||
ChatResponse response = chatModel.call(prompt);
|
||||
ChatResponse response = this.chatModel.call(prompt);
|
||||
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard");
|
||||
|
||||
var promptWithMessageHistory = new Prompt(List.of(new UserMessage("Dummy"), response.getResult().getOutput(),
|
||||
new UserMessage("Repeat the last assistant message.")));
|
||||
response = chatModel.call(promptWithMessageHistory);
|
||||
response = this.chatModel.call(promptWithMessageHistory);
|
||||
|
||||
System.out.println(response.getResult().getOutput().getContent());
|
||||
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard");
|
||||
@@ -120,7 +122,7 @@ class AzureOpenAiChatModelIT {
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template,
|
||||
Map.of("subject", "ice cream flavors", "format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
Generation generation = chatModel.call(prompt).getResult();
|
||||
Generation generation = this.chatModel.call(prompt).getResult();
|
||||
|
||||
List<String> list = outputConverter.convert(generation.getOutput().getContent());
|
||||
assertThat(list).hasSize(5);
|
||||
@@ -139,7 +141,7 @@ class AzureOpenAiChatModelIT {
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template,
|
||||
Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
Generation generation = chatModel.call(prompt).getResult();
|
||||
Generation generation = this.chatModel.call(prompt).getResult();
|
||||
|
||||
Map<String, Object> result = outputConverter.convert(generation.getOutput().getContent());
|
||||
assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||
@@ -158,7 +160,7 @@ class AzureOpenAiChatModelIT {
|
||||
""";
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
Generation generation = chatModel.call(prompt).getResult();
|
||||
Generation generation = this.chatModel.call(prompt).getResult();
|
||||
|
||||
ActorsFilms actorsFilms = outputConverter.convert(generation.getOutput().getContent());
|
||||
assertThat(actorsFilms.actor()).isNotNull();
|
||||
@@ -176,7 +178,7 @@ class AzureOpenAiChatModelIT {
|
||||
""";
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
Generation generation = chatModel.call(prompt).getResult();
|
||||
Generation generation = this.chatModel.call(prompt).getResult();
|
||||
|
||||
ActorsFilmsRecord actorsFilms = outputConverter.convert(generation.getOutput().getContent());
|
||||
logger.info("" + actorsFilms);
|
||||
@@ -197,7 +199,7 @@ class AzureOpenAiChatModelIT {
|
||||
PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format));
|
||||
Prompt prompt = new Prompt(promptTemplate.createMessage());
|
||||
|
||||
String generationTextFromStream = chatModel.stream(prompt)
|
||||
String generationTextFromStream = this.chatModel.stream(prompt)
|
||||
.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
@@ -221,7 +223,7 @@ class AzureOpenAiChatModelIT {
|
||||
URL url = new URL("https://docs.spring.io/spring-ai/reference/_images/multimodal.test.png");
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
String response = ChatClient.create(this.chatModel).prompt()
|
||||
.options(AzureOpenAiChatOptions.builder().withDeploymentName("gpt-4o").build())
|
||||
.user(u -> u.text("Explain what do you see on this picture?").media(MimeTypeUtils.IMAGE_PNG, url))
|
||||
.call()
|
||||
@@ -239,7 +241,7 @@ class AzureOpenAiChatModelIT {
|
||||
Resource resource = new ClassPathResource("multimodality/multimodal.test.png");
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
String response = ChatClient.create(this.chatModel).prompt()
|
||||
.options(AzureOpenAiChatOptions.builder().withDeploymentName("gpt-4o").build())
|
||||
.user(u -> u.text("Explain what do you see on this picture?").media(MimeTypeUtils.IMAGE_PNG, resource))
|
||||
.call()
|
||||
@@ -252,9 +254,11 @@ class AzureOpenAiChatModelIT {
|
||||
}
|
||||
|
||||
record ActorsFilms(String actor, List<String> movies) {
|
||||
|
||||
}
|
||||
|
||||
record ActorsFilmsRecord(String actor, List<String> movies) {
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,17 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import static com.azure.core.http.policy.HttpLogDetailLevel.BODY_AND_HEADERS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.OpenAIServiceVersion;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.http.policy.HttpLogOptions;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
@@ -37,13 +42,8 @@ import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.OpenAIServiceVersion;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.http.policy.HttpLogOptions;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import static com.azure.core.http.policy.HttpLogDetailLevel.BODY_AND_HEADERS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Soby Chacko
|
||||
@@ -54,14 +54,14 @@ import reactor.core.publisher.Flux;
|
||||
class AzureOpenAiChatModelObservationIT {
|
||||
|
||||
@Autowired
|
||||
private AzureOpenAiChatModel chatModel;
|
||||
TestObservationRegistry observationRegistry;
|
||||
|
||||
@Autowired
|
||||
TestObservationRegistry observationRegistry;
|
||||
private AzureOpenAiChatModel chatModel;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
observationRegistry.clear();
|
||||
this.observationRegistry.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,7 +78,7 @@ class AzureOpenAiChatModelObservationIT {
|
||||
|
||||
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
|
||||
|
||||
ChatResponse chatResponse = chatModel.call(prompt);
|
||||
ChatResponse chatResponse = this.chatModel.call(prompt);
|
||||
assertThat(chatResponse.getResult().getOutput().getContent()).isNotEmpty();
|
||||
|
||||
ChatResponseMetadata responseMetadata = chatResponse.getMetadata();
|
||||
@@ -102,7 +102,7 @@ class AzureOpenAiChatModelObservationIT {
|
||||
|
||||
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
|
||||
|
||||
Flux<ChatResponse> chatResponseFlux = chatModel.stream(prompt);
|
||||
Flux<ChatResponse> chatResponseFlux = this.chatModel.stream(prompt);
|
||||
List<ChatResponse> responses = chatResponseFlux.collectList().block();
|
||||
assertThat(responses).isNotEmpty();
|
||||
assertThat(responses).hasSizeGreaterThan(10);
|
||||
@@ -123,7 +123,7 @@ class AzureOpenAiChatModelObservationIT {
|
||||
|
||||
private void validate(ChatResponseMetadata responseMetadata, boolean checkModel) {
|
||||
|
||||
TestObservationRegistryAssert.That that = TestObservationRegistryAssert.assertThat(observationRegistry)
|
||||
TestObservationRegistryAssert.That that = TestObservationRegistryAssert.assertThat(this.observationRegistry)
|
||||
.doesNotHaveAnyRemainingCurrentObservation()
|
||||
.hasObservationWithNameEqualTo(DefaultChatModelObservationConvention.DEFAULT_NAME);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
@@ -22,6 +23,7 @@ import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
import org.springframework.ai.document.MetadataMode;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -41,18 +43,18 @@ class AzureOpenAiEmbeddingModelIT {
|
||||
|
||||
@Test
|
||||
void singleEmbedding() {
|
||||
assertThat(embeddingModel).isNotNull();
|
||||
EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of("Hello World"));
|
||||
assertThat(this.embeddingModel).isNotNull();
|
||||
EmbeddingResponse embeddingResponse = this.embeddingModel.embedForResponse(List.of("Hello World"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(1);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
System.out.println(embeddingModel.dimensions());
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1536);
|
||||
System.out.println(this.embeddingModel.dimensions());
|
||||
assertThat(this.embeddingModel.dimensions()).isEqualTo(1536);
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchEmbedding() {
|
||||
assertThat(embeddingModel).isNotNull();
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
assertThat(this.embeddingModel).isNotNull();
|
||||
EmbeddingResponse embeddingResponse = this.embeddingModel
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
@@ -60,7 +62,7 @@ class AzureOpenAiEmbeddingModelIT {
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1536);
|
||||
assertThat(this.embeddingModel.dimensions()).isEqualTo(1536);
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,14 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
import org.springframework.ai.document.MetadataMode;
|
||||
import org.springframework.ai.embedding.EmbeddingRequest;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
@@ -35,12 +40,7 @@ import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for observation instrumentation in {@link AzureOpenAiEmbeddingModel}.
|
||||
@@ -69,13 +69,13 @@ public class AzureOpenAiEmbeddingModelObservationIT {
|
||||
|
||||
EmbeddingRequest embeddingRequest = new EmbeddingRequest(List.of("Here comes the sun"), options);
|
||||
|
||||
EmbeddingResponse embeddingResponse = embeddingModel.call(embeddingRequest);
|
||||
EmbeddingResponse embeddingResponse = this.embeddingModel.call(embeddingRequest);
|
||||
assertThat(embeddingResponse.getResults()).isNotEmpty();
|
||||
|
||||
EmbeddingResponseMetadata responseMetadata = embeddingResponse.getMetadata();
|
||||
assertThat(responseMetadata).isNotNull();
|
||||
|
||||
TestObservationRegistryAssert.assertThat(observationRegistry)
|
||||
TestObservationRegistryAssert.assertThat(this.observationRegistry)
|
||||
.doesNotHaveAnyRemainingCurrentObservation()
|
||||
.hasObservationWithNameEqualTo(DefaultEmbeddingModelObservationConvention.DEFAULT_NAME)
|
||||
.that()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,9 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -26,8 +25,14 @@ import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import okio.Buffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -43,11 +48,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import okio.Buffer;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Spring {@link Configuration} for AI integration testing using mock objects.
|
||||
@@ -205,22 +206,22 @@ public class MockAiTestConfiguration {
|
||||
*/
|
||||
static class MockWebServerFactoryBean implements FactoryBean<MockWebServer>, InitializingBean, DisposableBean {
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass().getName());
|
||||
|
||||
private MockWebServer mockWebServer;
|
||||
|
||||
private final Queue<MockResponse> queuedResponses = new ConcurrentLinkedDeque<>();
|
||||
|
||||
public void setDispatcher(@Nullable Dispatcher dispatcher) {
|
||||
this.dispatcher = dispatcher;
|
||||
}
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
private MockWebServer mockWebServer;
|
||||
|
||||
protected Optional<Dispatcher> getDispatcher() {
|
||||
return Optional.ofNullable(this.dispatcher);
|
||||
}
|
||||
|
||||
public void setDispatcher(@Nullable Dispatcher dispatcher) {
|
||||
this.dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
protected Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,10 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -24,10 +27,6 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
/**
|
||||
* {@link SpringBootConfiguration} for testing {@literal Azure OpenAI's} API using mock
|
||||
* objects.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.aot;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.function;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -22,21 +23,21 @@ import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.function.FunctionCallbackWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -44,7 +45,6 @@ import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -69,7 +69,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
List<Message> messages = new ArrayList<>(List.of(userMessage));
|
||||
|
||||
var promptOptions = AzureOpenAiChatOptions.builder()
|
||||
.withDeploymentName(selectedModel)
|
||||
.withDeploymentName(this.selectedModel)
|
||||
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
|
||||
.withName("getCurrentWeather")
|
||||
.withDescription("Get the current weather in a given location")
|
||||
@@ -77,7 +77,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ChatResponse response = chatModel.call(new Prompt(messages, promptOptions));
|
||||
ChatResponse response = this.chatModel.call(new Prompt(messages, promptOptions));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
@@ -93,7 +93,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
List<Message> messages = new ArrayList<>(List.of(userMessage));
|
||||
|
||||
var promptOptions = AzureOpenAiChatOptions.builder()
|
||||
.withDeploymentName(selectedModel)
|
||||
.withDeploymentName(this.selectedModel)
|
||||
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
|
||||
.withName("getCurrentWeather")
|
||||
.withDescription("Get the current weather in a given location")
|
||||
@@ -101,7 +101,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ChatResponse response = chatModel.call(new Prompt(messages, promptOptions));
|
||||
ChatResponse response = this.chatModel.call(new Prompt(messages, promptOptions));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
@@ -115,7 +115,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
List<Message> messages = new ArrayList<>(List.of(userMessage));
|
||||
|
||||
var promptOptions = AzureOpenAiChatOptions.builder()
|
||||
.withDeploymentName(selectedModel)
|
||||
.withDeploymentName(this.selectedModel)
|
||||
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
|
||||
.withName("getCurrentWeather")
|
||||
.withDescription("Get the current weather in a given location")
|
||||
@@ -123,7 +123,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
Flux<ChatResponse> response = chatModel.stream(new Prompt(messages, promptOptions));
|
||||
Flux<ChatResponse> response = this.chatModel.stream(new Prompt(messages, promptOptions));
|
||||
|
||||
final var counter = new AtomicInteger();
|
||||
String content = response.doOnEach(listSignal -> counter.getAndIncrement())
|
||||
@@ -152,7 +152,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
List<Message> messages = new ArrayList<>(List.of(userMessage));
|
||||
|
||||
var promptOptions = AzureOpenAiChatOptions.builder()
|
||||
.withDeploymentName(selectedModel)
|
||||
.withDeploymentName(this.selectedModel)
|
||||
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
|
||||
.withName("getCurrentWeather")
|
||||
.withDescription("Get the current weather in a given location")
|
||||
@@ -160,7 +160,7 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
var response = chatModel.stream(new Prompt(messages, promptOptions));
|
||||
var response = this.chatModel.stream(new Prompt(messages, promptOptions));
|
||||
|
||||
final var counter = new AtomicInteger();
|
||||
String content = response.doOnEach(listSignal -> counter.getAndIncrement())
|
||||
@@ -182,6 +182,16 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
@SpringBootConfiguration
|
||||
public static class TestConfiguration {
|
||||
|
||||
public static String getDeploymentName() {
|
||||
String deploymentName = System.getenv("AZURE_OPENAI_DEPLOYMENT_NAME");
|
||||
if (StringUtils.hasText(deploymentName)) {
|
||||
return deploymentName;
|
||||
}
|
||||
else {
|
||||
return "gpt-4o";
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OpenAIClientBuilder openAIClient() {
|
||||
return new OpenAIClientBuilder().credential(new AzureKeyCredential(System.getenv("AZURE_OPENAI_API_KEY")))
|
||||
@@ -199,16 +209,6 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
return Optional.ofNullable(System.getenv("AZURE_OPENAI_MODEL")).orElse(getDeploymentName());
|
||||
}
|
||||
|
||||
public static String getDeploymentName() {
|
||||
String deploymentName = System.getenv("AZURE_OPENAI_DEPLOYMENT_NAME");
|
||||
if (StringUtils.hasText(deploymentName)) {
|
||||
return deploymentName;
|
||||
}
|
||||
else {
|
||||
return "gpt-4o";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,29 +13,37 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.function;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonClassDescription;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class MockWeatherService implements Function<MockWeatherService.Request, MockWeatherService.Response> {
|
||||
|
||||
/**
|
||||
* Weather Function request.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonClassDescription("Weather API request")
|
||||
public record Request(@JsonProperty(required = true,
|
||||
value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location,
|
||||
@JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) {
|
||||
@Override
|
||||
public Response apply(Request request) {
|
||||
|
||||
double temperature = 0;
|
||||
if (request.location().contains("Paris")) {
|
||||
temperature = 15;
|
||||
}
|
||||
else if (request.location().contains("Tokyo")) {
|
||||
temperature = 10;
|
||||
}
|
||||
else if (request.location().contains("San Francisco")) {
|
||||
temperature = 30;
|
||||
}
|
||||
|
||||
return new Response(temperature, 15, 20, 2, 53, 45, Unit.C);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,28 +71,23 @@ public class MockWeatherService implements Function<MockWeatherService.Request,
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather Function request.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonClassDescription("Weather API request")
|
||||
public record Request(@JsonProperty(required = true,
|
||||
value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location,
|
||||
@JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather Function response.
|
||||
*/
|
||||
public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity,
|
||||
Unit unit) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(Request request) {
|
||||
|
||||
double temperature = 0;
|
||||
if (request.location().contains("Paris")) {
|
||||
temperature = 15;
|
||||
}
|
||||
else if (request.location().contains("Tokyo")) {
|
||||
temperature = 10;
|
||||
}
|
||||
else if (request.location().contains("San Francisco")) {
|
||||
temperature = 30;
|
||||
}
|
||||
|
||||
return new Response(temperature, 15, 20, 2, 53, 45, Unit.C);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.azure.openai.image;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
@@ -6,6 +22,7 @@ import com.azure.core.credential.AzureKeyCredential;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiImageModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiImageOptions;
|
||||
import org.springframework.ai.azure.openai.metadata.AzureOpenAiImageGenerationMetadata;
|
||||
@@ -39,7 +56,7 @@ public class AzureOpenAiImageModelIT {
|
||||
|
||||
ImagePrompt imagePrompt = new ImagePrompt(instructions, options);
|
||||
|
||||
ImageResponse imageResponse = imageModel.call(imagePrompt);
|
||||
ImageResponse imageResponse = this.imageModel.call(imagePrompt);
|
||||
|
||||
assertThat(imageResponse.getResults()).hasSize(1);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai.metadata;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -25,14 +26,14 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.MockAzureOpenAiTestConfiguration;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
|
||||
import org.springframework.ai.chat.metadata.EmptyRateLimit;
|
||||
import org.springframework.ai.chat.metadata.PromptMetadata;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock;
|
||||
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi.AmazonBedrockInvocationMetrics;
|
||||
@@ -27,10 +28,6 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class BedrockUsage implements Usage {
|
||||
|
||||
public static BedrockUsage from(AmazonBedrockInvocationMetrics usage) {
|
||||
return new BedrockUsage(usage);
|
||||
}
|
||||
|
||||
private final AmazonBedrockInvocationMetrics usage;
|
||||
|
||||
protected BedrockUsage(AmazonBedrockInvocationMetrics usage) {
|
||||
@@ -38,6 +35,10 @@ public class BedrockUsage implements Usage {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public static BedrockUsage from(AmazonBedrockInvocationMetrics usage) {
|
||||
return new BedrockUsage(usage);
|
||||
}
|
||||
|
||||
protected AmazonBedrockInvocationMetrics getUsage() {
|
||||
return this.usage;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock;
|
||||
|
||||
import java.util.List;
|
||||
@@ -33,12 +34,12 @@ public class MessageToPromptConverter {
|
||||
|
||||
private static final String ASSISTANT_PROMPT = "Assistant:";
|
||||
|
||||
private final String lineSeparator;
|
||||
|
||||
private String humanPrompt = HUMAN_PROMPT;
|
||||
|
||||
private String assistantPrompt = ASSISTANT_PROMPT;
|
||||
|
||||
private final String lineSeparator;
|
||||
|
||||
private MessageToPromptConverter(String lineSeparator) {
|
||||
this.lineSeparator = lineSeparator;
|
||||
}
|
||||
@@ -84,9 +85,9 @@ public class MessageToPromptConverter {
|
||||
case SYSTEM:
|
||||
return message.getContent();
|
||||
case USER:
|
||||
return humanPrompt + " " + message.getContent();
|
||||
return this.humanPrompt + " " + message.getContent();
|
||||
case ASSISTANT:
|
||||
return assistantPrompt + " " + message.getContent();
|
||||
return this.assistantPrompt + " " + message.getContent();
|
||||
case TOOL:
|
||||
throw new IllegalArgumentException("Tool execution results are not supported for Bedrock models");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.anthropic;
|
||||
|
||||
import java.util.List;
|
||||
@@ -20,11 +21,10 @@ import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
@@ -75,44 +75,14 @@ public class AnthropicChatOptions implements ChatOptions {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final AnthropicChatOptions options = new AnthropicChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokensToSample(Integer maxTokensToSample) {
|
||||
this.options.setMaxTokensToSample(maxTokensToSample);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.setTopK(topK);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAnthropicVersion(String anthropicVersion) {
|
||||
this.options.setAnthropicVersion(anthropicVersion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnthropicChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withMaxTokensToSample(fromOptions.getMaxTokensToSample())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withAnthropicVersion(fromOptions.getAnthropicVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,14 +171,44 @@ public class AnthropicChatOptions implements ChatOptions {
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withMaxTokensToSample(fromOptions.getMaxTokensToSample())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withAnthropicVersion(fromOptions.getAnthropicVersion())
|
||||
.build();
|
||||
public static class Builder {
|
||||
|
||||
private final AnthropicChatOptions options = new AnthropicChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokensToSample(Integer maxTokensToSample) {
|
||||
this.options.setMaxTokensToSample(maxTokensToSample);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.setTopK(topK);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAnthropicVersion(String anthropicVersion) {
|
||||
this.options.setAnthropicVersion(anthropicVersion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnthropicChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,22 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.anthropic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.bedrock.MessageToPromptConverter;
|
||||
import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi;
|
||||
import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatRequest;
|
||||
import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatResponse;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.anthropic.api;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -118,6 +119,54 @@ public class AnthropicChatBedrockApi extends
|
||||
|
||||
// Anthropic Claude models: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html
|
||||
|
||||
@Override
|
||||
public AnthropicChatResponse chatCompletion(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocation(anthropicRequest, AnthropicChatResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<AnthropicChatResponse> chatCompletionStream(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocationStream(anthropicRequest, AnthropicChatResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Anthropic models version.
|
||||
*/
|
||||
public enum AnthropicChatModel implements ChatModelDescription {
|
||||
/**
|
||||
* anthropic.claude-instant-v1
|
||||
*/
|
||||
CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"),
|
||||
/**
|
||||
* anthropic.claude-v2
|
||||
*/
|
||||
CLAUDE_V2("anthropic.claude-v2"),
|
||||
/**
|
||||
* anthropic.claude-v2:1
|
||||
*/
|
||||
CLAUDE_V21("anthropic.claude-v2:1");
|
||||
|
||||
private final String id;
|
||||
|
||||
AnthropicChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AnthropicChatRequest encapsulates the request parameters for the Anthropic chat model.
|
||||
* https://docs.anthropic.com/claude/reference/complete_post
|
||||
@@ -196,13 +245,13 @@ public class AnthropicChatBedrockApi extends
|
||||
|
||||
public AnthropicChatRequest build() {
|
||||
return new AnthropicChatRequest(
|
||||
prompt,
|
||||
temperature,
|
||||
maxTokensToSample,
|
||||
topK,
|
||||
topP,
|
||||
stopSequences,
|
||||
anthropicVersion
|
||||
this.prompt,
|
||||
this.temperature,
|
||||
this.maxTokensToSample,
|
||||
this.topK,
|
||||
this.topP,
|
||||
this.stopSequences,
|
||||
this.anthropicVersion
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -225,53 +274,5 @@ public class AnthropicChatBedrockApi extends
|
||||
@JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Anthropic models version.
|
||||
*/
|
||||
public enum AnthropicChatModel implements ChatModelDescription {
|
||||
/**
|
||||
* anthropic.claude-instant-v1
|
||||
*/
|
||||
CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"),
|
||||
/**
|
||||
* anthropic.claude-v2
|
||||
*/
|
||||
CLAUDE_V2("anthropic.claude-v2"),
|
||||
/**
|
||||
* anthropic.claude-v2:1
|
||||
*/
|
||||
CLAUDE_V21("anthropic.claude-v2:1");
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
AnthropicChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnthropicChatResponse chatCompletion(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocation(anthropicRequest, AnthropicChatResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<AnthropicChatResponse> chatCompletionStream(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocationStream(anthropicRequest, AnthropicChatResponse.class);
|
||||
}
|
||||
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,15 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.anthropic3;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
/**
|
||||
* @author Ben Middleton
|
||||
@@ -74,44 +76,14 @@ public class Anthropic3ChatOptions implements ChatOptions {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Anthropic3ChatOptions options = new Anthropic3ChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.setMaxTokens(maxTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.setTopK(topK);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAnthropicVersion(String anthropicVersion) {
|
||||
this.options.setAnthropicVersion(anthropicVersion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Anthropic3ChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public static Anthropic3ChatOptions fromOptions(Anthropic3ChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withAnthropicVersion(fromOptions.getAnthropicVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -190,14 +162,44 @@ public class Anthropic3ChatOptions implements ChatOptions {
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static Anthropic3ChatOptions fromOptions(Anthropic3ChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withAnthropicVersion(fromOptions.getAnthropicVersion())
|
||||
.build();
|
||||
public static class Builder {
|
||||
|
||||
private final Anthropic3ChatOptions options = new Anthropic3ChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.setMaxTokens(maxTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.setTopK(topK);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAnthropicVersion(String anthropicVersion) {
|
||||
this.options.setAnthropicVersion(anthropicVersion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Anthropic3ChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.anthropic3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -21,11 +22,6 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
|
||||
import org.springframework.ai.chat.metadata.DefaultUsage;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi;
|
||||
@@ -35,13 +31,18 @@ import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.An
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.ChatCompletionMessage;
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.ChatCompletionMessage.Role;
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.MediaContent;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.MessageType;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
|
||||
import org.springframework.ai.chat.metadata.DefaultUsage;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.MessageType;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,24 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.anthropic3.api;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Flux;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatRequest;
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatResponse;
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatStreamingResponse;
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi;
|
||||
import org.springframework.ai.model.ChatModelDescription;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Based on Bedrock's <a href=
|
||||
@@ -122,6 +124,76 @@ public class Anthropic3ChatBedrockApi extends
|
||||
|
||||
// Anthropic Claude models: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html
|
||||
|
||||
@Override
|
||||
public AnthropicChatResponse chatCompletion(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocation(anthropicRequest, AnthropicChatResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<AnthropicChatStreamingResponse> chatCompletionStream(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocationStream(anthropicRequest, AnthropicChatStreamingResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Anthropic models version.
|
||||
*/
|
||||
public enum AnthropicChatModel implements ChatModelDescription {
|
||||
|
||||
/**
|
||||
* anthropic.claude-instant-v1
|
||||
*/
|
||||
CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"),
|
||||
/**
|
||||
* anthropic.claude-v2
|
||||
*/
|
||||
CLAUDE_V2("anthropic.claude-v2"),
|
||||
/**
|
||||
* anthropic.claude-v2:1
|
||||
*/
|
||||
CLAUDE_V21("anthropic.claude-v2:1"),
|
||||
/**
|
||||
* anthropic.claude-3-sonnet-20240229-v1:0
|
||||
*/
|
||||
CLAUDE_V3_SONNET("anthropic.claude-3-sonnet-20240229-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-haiku-20240307-v1:0
|
||||
*/
|
||||
CLAUDE_V3_HAIKU("anthropic.claude-3-haiku-20240307-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-opus-20240229-v1:0
|
||||
*/
|
||||
CLAUDE_V3_OPUS("anthropic.claude-3-opus-20240229-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-5-sonnet-20240620-v1:0
|
||||
*/
|
||||
CLAUDE_V3_5_SONNET("anthropic.claude-3-5-sonnet-20240620-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-5-sonnet-20241022-v2:0
|
||||
*/
|
||||
CLAUDE_V3_5_SONNET_V2("anthropic.claude-3-5-sonnet-20241022-v2:0");
|
||||
|
||||
private final String id;
|
||||
|
||||
AnthropicChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* AnthropicChatRequest encapsulates the request parameters for the Anthropic messages model.
|
||||
* https://docs.anthropic.com/claude/reference/messages_post
|
||||
@@ -208,14 +280,14 @@ public class Anthropic3ChatBedrockApi extends
|
||||
|
||||
public AnthropicChatRequest build() {
|
||||
return new AnthropicChatRequest(
|
||||
messages,
|
||||
system,
|
||||
temperature,
|
||||
maxTokens,
|
||||
topK,
|
||||
topP,
|
||||
stopSequences,
|
||||
anthropicVersion
|
||||
this.messages,
|
||||
this.system,
|
||||
this.temperature,
|
||||
this.maxTokens,
|
||||
this.topK,
|
||||
this.topP,
|
||||
this.stopSequences,
|
||||
this.anthropicVersion
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -286,7 +358,9 @@ public class Anthropic3ChatBedrockApi extends
|
||||
public Source(String mediaType, String data) {
|
||||
this("base64", mediaType, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,6 +391,7 @@ public class Anthropic3ChatBedrockApi extends
|
||||
ASSISTANT
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,6 +404,7 @@ public class Anthropic3ChatBedrockApi extends
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record AnthropicUsage(@JsonProperty("input_tokens") Integer inputTokens,
|
||||
@JsonProperty("output_tokens") Integer outputTokens) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +432,7 @@ public class Anthropic3ChatBedrockApi extends
|
||||
@JsonProperty("stop_reason") String stopReason, @JsonProperty("stop_sequence") String stopSequence,
|
||||
@JsonProperty("usage") AnthropicUsage usage,
|
||||
@JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { // formatter:on
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,77 +509,9 @@ public class Anthropic3ChatBedrockApi extends
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record Delta(@JsonProperty("type") String type, @JsonProperty("text") String text,
|
||||
@JsonProperty("stop_reason") String stopReason, @JsonProperty("stop_sequence") String stopSequence) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Anthropic models version.
|
||||
*/
|
||||
public enum AnthropicChatModel implements ChatModelDescription {
|
||||
|
||||
/**
|
||||
* anthropic.claude-instant-v1
|
||||
*/
|
||||
CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"),
|
||||
/**
|
||||
* anthropic.claude-v2
|
||||
*/
|
||||
CLAUDE_V2("anthropic.claude-v2"),
|
||||
/**
|
||||
* anthropic.claude-v2:1
|
||||
*/
|
||||
CLAUDE_V21("anthropic.claude-v2:1"),
|
||||
/**
|
||||
* anthropic.claude-3-sonnet-20240229-v1:0
|
||||
*/
|
||||
CLAUDE_V3_SONNET("anthropic.claude-3-sonnet-20240229-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-haiku-20240307-v1:0
|
||||
*/
|
||||
CLAUDE_V3_HAIKU("anthropic.claude-3-haiku-20240307-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-opus-20240229-v1:0
|
||||
*/
|
||||
CLAUDE_V3_OPUS("anthropic.claude-3-opus-20240229-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-5-sonnet-20240620-v1:0
|
||||
*/
|
||||
CLAUDE_V3_5_SONNET("anthropic.claude-3-5-sonnet-20240620-v1:0"),
|
||||
/**
|
||||
* anthropic.claude-3-5-sonnet-20241022-v2:0
|
||||
*/
|
||||
CLAUDE_V3_5_SONNET_V2("anthropic.claude-3-5-sonnet-20241022-v2:0");
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
AnthropicChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnthropicChatResponse chatCompletion(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocation(anthropicRequest, AnthropicChatResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<AnthropicChatStreamingResponse> chatCompletionStream(AnthropicChatRequest anthropicRequest) {
|
||||
Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null");
|
||||
return this.internalInvocationStream(anthropicRequest, AnthropicChatStreamingResponse.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.aot;
|
||||
|
||||
import org.springframework.ai.bedrock.anthropic.AnthropicChatOptions;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -175,24 +175,6 @@ public abstract class AbstractBedrockApi<I, O, SO> {
|
||||
return this.region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the metrics about the model invocation.
|
||||
* https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html
|
||||
*
|
||||
* @param inputTokenCount The number of tokens in the input prompt.
|
||||
* @param firstByteLatency The time in milliseconds between the request being sent and the first byte of the
|
||||
* response being received.
|
||||
* @param outputTokenCount The number of tokens in the generated text.
|
||||
* @param invocationLatency The time in milliseconds between the request being sent and the response being received.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record AmazonBedrockInvocationMetrics(
|
||||
@JsonProperty("inputTokenCount") Long inputTokenCount,
|
||||
@JsonProperty("firstByteLatency") Long firstByteLatency,
|
||||
@JsonProperty("outputTokenCount") Long outputTokenCount,
|
||||
@JsonProperty("invocationLatency") Long invocationLatency) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the embedding for the given text.
|
||||
*
|
||||
@@ -337,5 +319,23 @@ public abstract class AbstractBedrockApi<I, O, SO> {
|
||||
|
||||
return eventSink.asFlux();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the metrics about the model invocation.
|
||||
* https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html
|
||||
*
|
||||
* @param inputTokenCount The number of tokens in the input prompt.
|
||||
* @param firstByteLatency The time in milliseconds between the request being sent and the first byte of the
|
||||
* response being received.
|
||||
* @param outputTokenCount The number of tokens in the generated text.
|
||||
* @param invocationLatency The time in milliseconds between the request being sent and the response being received.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record AmazonBedrockInvocationMetrics(
|
||||
@JsonProperty("inputTokenCount") Long inputTokenCount,
|
||||
@JsonProperty("firstByteLatency") Long firstByteLatency,
|
||||
@JsonProperty("outputTokenCount") Long outputTokenCount,
|
||||
@JsonProperty("invocationLatency") Long invocationLatency) {
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.cohere;
|
||||
|
||||
import java.util.List;
|
||||
@@ -24,13 +25,13 @@ import org.springframework.ai.bedrock.MessageToPromptConverter;
|
||||
import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi;
|
||||
import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest;
|
||||
import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatResponse;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.cohere;
|
||||
|
||||
import java.util.List;
|
||||
@@ -85,59 +86,17 @@ public class BedrockCohereChatOptions implements ChatOptions {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final BedrockCohereChatOptions options = new BedrockCohereChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.setTopK(topK);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.setMaxTokens(maxTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withReturnLikelihoods(ReturnLikelihoods returnLikelihoods) {
|
||||
this.options.setReturnLikelihoods(returnLikelihoods);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withNumGenerations(Integer numGenerations) {
|
||||
this.options.setNumGenerations(numGenerations);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLogitBias(LogitBias logitBias) {
|
||||
this.options.setLogitBias(logitBias);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTruncate(Truncate truncate) {
|
||||
this.options.setTruncate(truncate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockCohereChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public static BedrockCohereChatOptions fromOptions(BedrockCohereChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withReturnLikelihoods(fromOptions.getReturnLikelihoods())
|
||||
.withNumGenerations(fromOptions.getNumGenerations())
|
||||
.withLogitBias(fromOptions.getLogitBias())
|
||||
.withTruncate(fromOptions.getTruncate())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -240,17 +199,59 @@ public class BedrockCohereChatOptions implements ChatOptions {
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static BedrockCohereChatOptions fromOptions(BedrockCohereChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withReturnLikelihoods(fromOptions.getReturnLikelihoods())
|
||||
.withNumGenerations(fromOptions.getNumGenerations())
|
||||
.withLogitBias(fromOptions.getLogitBias())
|
||||
.withTruncate(fromOptions.getTruncate())
|
||||
.build();
|
||||
public static class Builder {
|
||||
|
||||
private final BedrockCohereChatOptions options = new BedrockCohereChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topK) {
|
||||
this.options.setTopK(topK);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
this.options.setMaxTokens(maxTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withReturnLikelihoods(ReturnLikelihoods returnLikelihoods) {
|
||||
this.options.setReturnLikelihoods(returnLikelihoods);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withNumGenerations(Integer numGenerations) {
|
||||
this.options.setNumGenerations(numGenerations);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLogitBias(LogitBias logitBias) {
|
||||
this.options.setLogitBias(logitBias);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTruncate(Truncate truncate) {
|
||||
this.options.setTruncate(truncate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockCohereChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.cohere;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.cohere;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
@@ -52,26 +53,6 @@ public class BedrockCohereEmbeddingOptions implements EmbeddingOptions {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private BedrockCohereEmbeddingOptions options = new BedrockCohereEmbeddingOptions();
|
||||
|
||||
public Builder withInputType(InputType inputType) {
|
||||
this.options.setInputType(inputType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTruncate(Truncate truncate) {
|
||||
this.options.setTruncate(truncate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockCohereEmbeddingOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public InputType getInputType() {
|
||||
return this.inputType;
|
||||
}
|
||||
@@ -100,4 +81,24 @@ public class BedrockCohereEmbeddingOptions implements EmbeddingOptions {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private BedrockCohereEmbeddingOptions options = new BedrockCohereEmbeddingOptions();
|
||||
|
||||
public Builder withInputType(InputType inputType) {
|
||||
this.options.setInputType(inputType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTruncate(Truncate truncate) {
|
||||
this.options.setTruncate(truncate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockCohereEmbeddingOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -109,6 +109,52 @@ public class CohereChatBedrockApi extends
|
||||
super(modelId, credentialsProvider, region, objectMapper, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CohereChatResponse chatCompletion(CohereChatRequest request) {
|
||||
Assert.isTrue(!request.stream(), "The request must be configured to return the complete response!");
|
||||
return this.internalInvocation(request, CohereChatResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<CohereChatResponse.Generation> chatCompletionStream(CohereChatRequest request) {
|
||||
Assert.isTrue(request.stream(), "The request must be configured to stream the response!");
|
||||
return this.internalInvocationStream(request, CohereChatResponse.Generation.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohere models version.
|
||||
*/
|
||||
public enum CohereChatModel implements ChatModelDescription {
|
||||
|
||||
/**
|
||||
* cohere.command-light-text-v14
|
||||
*/
|
||||
COHERE_COMMAND_LIGHT_V14("cohere.command-light-text-v14"),
|
||||
|
||||
/**
|
||||
* cohere.command-text-v14
|
||||
*/
|
||||
COHERE_COMMAND_V14("cohere.command-text-v14");
|
||||
|
||||
private final String id;
|
||||
|
||||
CohereChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CohereChatRequest encapsulates the request parameters for the Cohere command model.
|
||||
*
|
||||
@@ -143,15 +189,12 @@ public class CohereChatBedrockApi extends
|
||||
@JsonProperty("truncate") Truncate truncate) {
|
||||
|
||||
/**
|
||||
* Prevents the model from generating unwanted tokens or incentivize the model to include desired tokens.
|
||||
*
|
||||
* @param token The token likelihoods.
|
||||
* @param bias A float between -10 and 10.
|
||||
* Get CohereChatRequest builder.
|
||||
* @param prompt compulsory request prompt parameter.
|
||||
* @return CohereChatRequest builder.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record LogitBias(
|
||||
@JsonProperty("token") String token,
|
||||
@JsonProperty("bias") Float bias) {
|
||||
public static Builder builder(String prompt) {
|
||||
return new Builder(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,12 +235,15 @@ public class CohereChatBedrockApi extends
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CohereChatRequest builder.
|
||||
* @param prompt compulsory request prompt parameter.
|
||||
* @return CohereChatRequest builder.
|
||||
* Prevents the model from generating unwanted tokens or incentivize the model to include desired tokens.
|
||||
*
|
||||
* @param token The token likelihoods.
|
||||
* @param bias A float between -10 and 10.
|
||||
*/
|
||||
public static Builder builder(String prompt) {
|
||||
return new Builder(prompt);
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record LogitBias(
|
||||
@JsonProperty("token") String token,
|
||||
@JsonProperty("bias") Float bias) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,17 +318,17 @@ public class CohereChatBedrockApi extends
|
||||
|
||||
public CohereChatRequest build() {
|
||||
return new CohereChatRequest(
|
||||
prompt,
|
||||
temperature,
|
||||
topP,
|
||||
topK,
|
||||
maxTokens,
|
||||
stopSequences,
|
||||
returnLikelihoods,
|
||||
stream,
|
||||
numGenerations,
|
||||
logitBias,
|
||||
truncate
|
||||
this.prompt,
|
||||
this.temperature,
|
||||
this.topP,
|
||||
this.topK,
|
||||
this.maxTokens,
|
||||
this.stopSequences,
|
||||
this.returnLikelihoods,
|
||||
this.stream,
|
||||
this.numGenerations,
|
||||
this.logitBias,
|
||||
this.truncate
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -331,16 +377,6 @@ public class CohereChatBedrockApi extends
|
||||
@JsonProperty("index") Integer index,
|
||||
@JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) {
|
||||
|
||||
/**
|
||||
* @param token The token.
|
||||
* @param likelihood The likelihood of the token.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record TokenLikelihood(
|
||||
@JsonProperty("token") String token,
|
||||
@JsonProperty("likelihood") Float likelihood) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The reason the response finished being generated.
|
||||
*/
|
||||
@@ -363,53 +399,17 @@ public class CohereChatBedrockApi extends
|
||||
*/
|
||||
ERROR_TOXIC
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token The token.
|
||||
* @param likelihood The likelihood of the token.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record TokenLikelihood(
|
||||
@JsonProperty("token") String token,
|
||||
@JsonProperty("likelihood") Float likelihood) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohere models version.
|
||||
*/
|
||||
public enum CohereChatModel implements ChatModelDescription {
|
||||
|
||||
/**
|
||||
* cohere.command-light-text-v14
|
||||
*/
|
||||
COHERE_COMMAND_LIGHT_V14("cohere.command-light-text-v14"),
|
||||
|
||||
/**
|
||||
* cohere.command-text-v14
|
||||
*/
|
||||
COHERE_COMMAND_V14("cohere.command-text-v14");
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
CohereChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CohereChatResponse chatCompletion(CohereChatRequest request) {
|
||||
Assert.isTrue(!request.stream(), "The request must be configured to return the complete response!");
|
||||
return this.internalInvocation(request, CohereChatResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<CohereChatResponse.Generation> chatCompletionStream(CohereChatRequest request) {
|
||||
Assert.isTrue(request.stream(), "The request must be configured to stream the response!");
|
||||
return this.internalInvocationStream(request, CohereChatResponse.Generation.class);
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -109,6 +109,39 @@ public class CohereEmbeddingBedrockApi extends
|
||||
super(modelId, credentialsProvider, region, objectMapper, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CohereEmbeddingResponse embedding(CohereEmbeddingRequest request) {
|
||||
return this.internalInvocation(request, CohereEmbeddingResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohere Embedding model ids. https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html
|
||||
*/
|
||||
public enum CohereEmbeddingModel {
|
||||
/**
|
||||
* cohere.embed-multilingual-v3
|
||||
*/
|
||||
COHERE_EMBED_MULTILINGUAL_V1("cohere.embed-multilingual-v3"),
|
||||
/**
|
||||
* cohere.embed-english-v3
|
||||
*/
|
||||
COHERE_EMBED_ENGLISH_V3("cohere.embed-english-v3");
|
||||
|
||||
private final String id;
|
||||
|
||||
CohereEmbeddingModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The Cohere Embed model request.
|
||||
*
|
||||
@@ -190,38 +223,5 @@ public class CohereEmbeddingBedrockApi extends
|
||||
@JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cohere Embedding model ids. https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html
|
||||
*/
|
||||
public enum CohereEmbeddingModel {
|
||||
/**
|
||||
* cohere.embed-multilingual-v3
|
||||
*/
|
||||
COHERE_EMBED_MULTILINGUAL_V1("cohere.embed-multilingual-v3"),
|
||||
/**
|
||||
* cohere.embed-english-v3
|
||||
*/
|
||||
COHERE_EMBED_ENGLISH_V3("cohere.embed-english-v3");
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
CohereEmbeddingModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CohereEmbeddingResponse embedding(CohereEmbeddingRequest request) {
|
||||
return this.internalInvocation(request, CohereEmbeddingResponse.class);
|
||||
}
|
||||
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -19,10 +19,10 @@ package org.springframework.ai.bedrock.jurassic2;
|
||||
import org.springframework.ai.bedrock.MessageToPromptConverter;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatRequest;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
@@ -57,6 +57,10 @@ public class BedrockAi21Jurassic2ChatModel implements ChatModel {
|
||||
.build());
|
||||
}
|
||||
|
||||
public static Builder builder(Ai21Jurassic2ChatBedrockApi chatApi) {
|
||||
return new Builder(chatApi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
var request = createRequest(prompt);
|
||||
@@ -88,8 +92,9 @@ public class BedrockAi21Jurassic2ChatModel implements ChatModel {
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Builder builder(Ai21Jurassic2ChatBedrockApi chatApi) {
|
||||
return new Builder(chatApi);
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return BedrockAi21Jurassic2ChatOptions.fromOptions(this.defaultOptions);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@@ -108,15 +113,10 @@ public class BedrockAi21Jurassic2ChatModel implements ChatModel {
|
||||
}
|
||||
|
||||
public BedrockAi21Jurassic2ChatModel build() {
|
||||
return new BedrockAi21Jurassic2ChatModel(chatApi,
|
||||
options != null ? options : BedrockAi21Jurassic2ChatOptions.builder().build());
|
||||
return new BedrockAi21Jurassic2ChatModel(this.chatApi,
|
||||
this.options != null ? this.options : BedrockAi21Jurassic2ChatOptions.builder().build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return BedrockAi21Jurassic2ChatOptions.fromOptions(this.defaultOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
package org.springframework.ai.bedrock.jurassic2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
/**
|
||||
* Request body for the /complete endpoint of the Jurassic-2 API.
|
||||
@@ -101,12 +102,31 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
|
||||
// Getters and setters
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static BedrockAi21Jurassic2ChatOptions fromOptions(BedrockAi21Jurassic2ChatOptions fromOptions) {
|
||||
return builder().withPrompt(fromOptions.getPrompt())
|
||||
.withNumResults(fromOptions.getNumResults())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withMinTokens(fromOptions.getMinTokens())
|
||||
.withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withFrequencyPenaltyOptions(fromOptions.getFrequencyPenaltyOptions())
|
||||
.withPresencePenaltyOptions(fromOptions.getPresencePenaltyOptions())
|
||||
.withCountPenaltyOptions(fromOptions.getCountPenaltyOptions())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the prompt text for the model to continue.
|
||||
* @return The prompt text.
|
||||
*/
|
||||
public String getPrompt() {
|
||||
return prompt;
|
||||
return this.prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +142,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
* @return The number of results.
|
||||
*/
|
||||
public Integer getNumResults() {
|
||||
return numResults;
|
||||
return this.numResults;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,7 +159,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
*/
|
||||
@Override
|
||||
public Integer getMaxTokens() {
|
||||
return maxTokens;
|
||||
return this.maxTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +175,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
* @return The minimum number of tokens.
|
||||
*/
|
||||
public Integer getMinTokens() {
|
||||
return minTokens;
|
||||
return this.minTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +192,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
*/
|
||||
@Override
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
return this.temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +210,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
*/
|
||||
@Override
|
||||
public Double getTopP() {
|
||||
return topP;
|
||||
return this.topP;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +228,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
*/
|
||||
@Override
|
||||
public Integer getTopK() {
|
||||
return topK;
|
||||
return this.topK;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,7 +245,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
*/
|
||||
@Override
|
||||
public List<String> getStopSequences() {
|
||||
return stopSequences;
|
||||
return this.stopSequences;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +274,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
* @return The frequency penalty object.
|
||||
*/
|
||||
public Penalty getFrequencyPenaltyOptions() {
|
||||
return frequencyPenaltyOptions;
|
||||
return this.frequencyPenaltyOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +303,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
* @return The presence penalty object.
|
||||
*/
|
||||
public Penalty getPresencePenaltyOptions() {
|
||||
return presencePenaltyOptions;
|
||||
return this.presencePenaltyOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,7 +319,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
* @return The count penalty object.
|
||||
*/
|
||||
public Penalty getCountPenaltyOptions() {
|
||||
return countPenaltyOptions;
|
||||
return this.countPenaltyOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,8 +336,9 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
@Override
|
||||
public BedrockAi21Jurassic2ChatOptions copy() {
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@@ -325,62 +346,62 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
private final BedrockAi21Jurassic2ChatOptions request = new BedrockAi21Jurassic2ChatOptions();
|
||||
|
||||
public Builder withPrompt(String prompt) {
|
||||
request.setPrompt(prompt);
|
||||
this.request.setPrompt(prompt);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withNumResults(Integer numResults) {
|
||||
request.setNumResults(numResults);
|
||||
this.request.setNumResults(numResults);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
request.setMaxTokens(maxTokens);
|
||||
this.request.setMaxTokens(maxTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMinTokens(Integer minTokens) {
|
||||
request.setMinTokens(minTokens);
|
||||
this.request.setMinTokens(minTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
request.setTemperature(temperature);
|
||||
this.request.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
request.setTopP(topP);
|
||||
this.request.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
request.setStopSequences(stopSequences);
|
||||
this.request.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topKReturn) {
|
||||
request.setTopK(topKReturn);
|
||||
this.request.setTopK(topKReturn);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFrequencyPenaltyOptions(BedrockAi21Jurassic2ChatOptions.Penalty frequencyPenalty) {
|
||||
request.setFrequencyPenaltyOptions(frequencyPenalty);
|
||||
this.request.setFrequencyPenaltyOptions(frequencyPenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPresencePenaltyOptions(BedrockAi21Jurassic2ChatOptions.Penalty presencePenalty) {
|
||||
request.setPresencePenaltyOptions(presencePenalty);
|
||||
this.request.setPresencePenaltyOptions(presencePenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withCountPenaltyOptions(BedrockAi21Jurassic2ChatOptions.Penalty countPenalty) {
|
||||
request.setCountPenaltyOptions(countPenalty);
|
||||
this.request.setCountPenaltyOptions(countPenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockAi21Jurassic2ChatOptions build() {
|
||||
return request;
|
||||
return this.request;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -446,31 +467,12 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions {
|
||||
}
|
||||
|
||||
public Penalty build() {
|
||||
return new Penalty(scale, applyToNumbers, applyToPunctuations, applyToStopwords, applyToWhitespaces,
|
||||
applyToEmojis);
|
||||
return new Penalty(this.scale, this.applyToNumbers, this.applyToPunctuations, this.applyToStopwords,
|
||||
this.applyToWhitespaces, this.applyToEmojis);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BedrockAi21Jurassic2ChatOptions copy() {
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static BedrockAi21Jurassic2ChatOptions fromOptions(BedrockAi21Jurassic2ChatOptions fromOptions) {
|
||||
return builder().withPrompt(fromOptions.getPrompt())
|
||||
.withNumResults(fromOptions.getNumResults())
|
||||
.withMaxTokens(fromOptions.getMaxTokens())
|
||||
.withMinTokens(fromOptions.getMinTokens())
|
||||
.withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withTopK(fromOptions.getTopK())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.withFrequencyPenaltyOptions(fromOptions.getFrequencyPenaltyOptions())
|
||||
.withPresencePenaltyOptions(fromOptions.getPresencePenaltyOptions())
|
||||
.withCountPenaltyOptions(fromOptions.getCountPenaltyOptions())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -22,16 +22,15 @@ import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatRequest;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatResponse;
|
||||
import org.springframework.ai.model.ChatModelDescription;
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
/**
|
||||
* Java client for the Bedrock Jurassic2 chat model.
|
||||
* https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html
|
||||
@@ -110,6 +109,45 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
super(modelId, credentialsProvider, region, objectMapper, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ai21Jurassic2ChatResponse chatCompletion(Ai21Jurassic2ChatRequest request) {
|
||||
return this.internalInvocation(request, Ai21Jurassic2ChatResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ai21 Jurassic2 models version.
|
||||
*/
|
||||
public enum Ai21Jurassic2ChatModel implements ChatModelDescription {
|
||||
|
||||
/**
|
||||
* ai21.j2-mid-v1
|
||||
*/
|
||||
AI21_J2_MID_V1("ai21.j2-mid-v1"),
|
||||
|
||||
/**
|
||||
* ai21.j2-ultra-v1
|
||||
*/
|
||||
AI21_J2_ULTRA_V1("ai21.j2-ultra-v1");
|
||||
|
||||
private final String id;
|
||||
|
||||
Ai21Jurassic2ChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AI21 Jurassic2 chat request parameters.
|
||||
*
|
||||
@@ -141,6 +179,10 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
@JsonProperty("presencePenalty") FloatScalePenalty presencePenalty,
|
||||
@JsonProperty("frequencyPenalty") IntegerScalePenalty frequencyPenalty) {
|
||||
|
||||
public static Builder builder(String prompt) {
|
||||
return new Builder(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Penalty with integer scale value.
|
||||
*
|
||||
@@ -192,11 +234,6 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
@JsonProperty("applyToEmojis") boolean applyToEmojis) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Builder builder(String prompt) {
|
||||
return new Builder(prompt);
|
||||
}
|
||||
public static class Builder {
|
||||
private String prompt;
|
||||
private Double temperature;
|
||||
@@ -248,14 +285,14 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
|
||||
public Ai21Jurassic2ChatRequest build() {
|
||||
return new Ai21Jurassic2ChatRequest(
|
||||
prompt,
|
||||
temperature,
|
||||
topP,
|
||||
maxTokens,
|
||||
stopSequences,
|
||||
countPenalty,
|
||||
presencePenalty,
|
||||
frequencyPenalty
|
||||
this.prompt,
|
||||
this.temperature,
|
||||
this.topP,
|
||||
this.maxTokens,
|
||||
this.stopSequences,
|
||||
this.countPenalty,
|
||||
this.presencePenalty,
|
||||
this.frequencyPenalty
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -370,45 +407,6 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ai21 Jurassic2 models version.
|
||||
*/
|
||||
public enum Ai21Jurassic2ChatModel implements ChatModelDescription {
|
||||
|
||||
/**
|
||||
* ai21.j2-mid-v1
|
||||
*/
|
||||
AI21_J2_MID_V1("ai21.j2-mid-v1"),
|
||||
|
||||
/**
|
||||
* ai21.j2-ultra-v1
|
||||
*/
|
||||
AI21_J2_ULTRA_V1("ai21.j2-ultra-v1");
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
Ai21Jurassic2ChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ai21Jurassic2ChatResponse chatCompletion(Ai21Jurassic2ChatRequest request) {
|
||||
return this.internalInvocation(request, Ai21Jurassic2ChatResponse.class);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.llama;
|
||||
|
||||
import java.util.List;
|
||||
@@ -23,13 +24,13 @@ import org.springframework.ai.bedrock.MessageToPromptConverter;
|
||||
import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi;
|
||||
import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatRequest;
|
||||
import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatResponse;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.llama;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
@@ -22,8 +25,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
@@ -52,29 +53,11 @@ public class BedrockLlamaChatOptions implements ChatOptions {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private BedrockLlamaChatOptions options = new BedrockLlamaChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxGenLen(Integer maxGenLen) {
|
||||
this.options.setMaxGenLen(maxGenLen);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockLlamaChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public static BedrockLlamaChatOptions fromOptions(BedrockLlamaChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withMaxGenLen(fromOptions.getMaxGenLen())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -149,11 +132,29 @@ public class BedrockLlamaChatOptions implements ChatOptions {
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static BedrockLlamaChatOptions fromOptions(BedrockLlamaChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withMaxGenLen(fromOptions.getMaxGenLen())
|
||||
.build();
|
||||
public static class Builder {
|
||||
|
||||
private BedrockLlamaChatOptions options = new BedrockLlamaChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxGenLen(Integer maxGenLen) {
|
||||
this.options.setMaxGenLen(maxGenLen);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockLlamaChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.llama.api;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
@@ -28,8 +31,6 @@ import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatReq
|
||||
import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatResponse;
|
||||
import org.springframework.ai.model.ChatModelDescription;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
// @formatter:off
|
||||
/**
|
||||
* Java client for the Bedrock Llama chat model.
|
||||
@@ -109,100 +110,14 @@ public class LlamaChatBedrockApi extends
|
||||
super(modelId, credentialsProvider, region, objectMapper, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* LlamaChatRequest encapsulates the request parameters for the Meta Llama chat model.
|
||||
*
|
||||
* @param prompt The prompt to use for the chat.
|
||||
* @param temperature The temperature value controls the randomness of the generated text. Use a lower value to
|
||||
* decrease randomness in the response.
|
||||
* @param topP The topP value controls the diversity of the generated text. Use a lower value to ignore less
|
||||
* probable options. Set to 0 or 1.0 to disable.
|
||||
* @param maxGenLen The maximum length of the generated text.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record LlamaChatRequest(
|
||||
@JsonProperty("prompt") String prompt,
|
||||
@JsonProperty("temperature") Double temperature,
|
||||
@JsonProperty("top_p") Double topP,
|
||||
@JsonProperty("max_gen_len") Integer maxGenLen) {
|
||||
|
||||
/**
|
||||
* Create a new LlamaChatRequest builder.
|
||||
* @param prompt compulsory prompt parameter.
|
||||
* @return a new LlamaChatRequest builder.
|
||||
*/
|
||||
public static Builder builder(String prompt) {
|
||||
return new Builder(prompt);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String prompt;
|
||||
private Double temperature;
|
||||
private Double topP;
|
||||
private Integer maxGenLen;
|
||||
|
||||
public Builder(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxGenLen(Integer maxGenLen) {
|
||||
this.maxGenLen = maxGenLen;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LlamaChatRequest build() {
|
||||
return new LlamaChatRequest(
|
||||
prompt,
|
||||
temperature,
|
||||
topP,
|
||||
maxGenLen
|
||||
);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public LlamaChatResponse chatCompletion(LlamaChatRequest request) {
|
||||
return this.internalInvocation(request, LlamaChatResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* LlamaChatResponse encapsulates the response parameters for the Meta Llama chat model.
|
||||
*
|
||||
* @param generation The generated text.
|
||||
* @param promptTokenCount The number of tokens in the prompt.
|
||||
* @param generationTokenCount The number of tokens in the response.
|
||||
* @param stopReason The reason why the response stopped generating text. Possible values are: (1) stop – The model
|
||||
* has finished generating text for the input prompt. (2) length – The length of the tokens for the generated text
|
||||
* exceeds the value of max_gen_len in the call. The response is truncated to max_gen_len tokens. Consider
|
||||
* increasing the value of max_gen_len and trying again.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record LlamaChatResponse(
|
||||
@JsonProperty("generation") String generation,
|
||||
@JsonProperty("prompt_token_count") Integer promptTokenCount,
|
||||
@JsonProperty("generation_token_count") Integer generationTokenCount,
|
||||
@JsonProperty("stop_reason") StopReason stopReason,
|
||||
@JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) {
|
||||
|
||||
/**
|
||||
* The reason the response finished being generated.
|
||||
*/
|
||||
public enum StopReason {
|
||||
/**
|
||||
* The model has finished generating text for the input prompt.
|
||||
*/
|
||||
@JsonProperty("stop") STOP,
|
||||
/**
|
||||
* The response was truncated because of the response length you set.
|
||||
*/
|
||||
@JsonProperty("length") LENGTH
|
||||
}
|
||||
@Override
|
||||
public Flux<LlamaChatResponse> chatCompletionStream(LlamaChatRequest request) {
|
||||
return this.internalInvocationStream(request, LlamaChatResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,15 +182,15 @@ public class LlamaChatBedrockApi extends
|
||||
|
||||
private final String id;
|
||||
|
||||
LlamaChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The model id.
|
||||
*/
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
LlamaChatModel(String value) {
|
||||
this.id = value;
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -284,14 +199,100 @@ public class LlamaChatBedrockApi extends
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LlamaChatResponse chatCompletion(LlamaChatRequest request) {
|
||||
return this.internalInvocation(request, LlamaChatResponse.class);
|
||||
/**
|
||||
* LlamaChatRequest encapsulates the request parameters for the Meta Llama chat model.
|
||||
*
|
||||
* @param prompt The prompt to use for the chat.
|
||||
* @param temperature The temperature value controls the randomness of the generated text. Use a lower value to
|
||||
* decrease randomness in the response.
|
||||
* @param topP The topP value controls the diversity of the generated text. Use a lower value to ignore less
|
||||
* probable options. Set to 0 or 1.0 to disable.
|
||||
* @param maxGenLen The maximum length of the generated text.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record LlamaChatRequest(
|
||||
@JsonProperty("prompt") String prompt,
|
||||
@JsonProperty("temperature") Double temperature,
|
||||
@JsonProperty("top_p") Double topP,
|
||||
@JsonProperty("max_gen_len") Integer maxGenLen) {
|
||||
|
||||
/**
|
||||
* Create a new LlamaChatRequest builder.
|
||||
* @param prompt compulsory prompt parameter.
|
||||
* @return a new LlamaChatRequest builder.
|
||||
*/
|
||||
public static Builder builder(String prompt) {
|
||||
return new Builder(prompt);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String prompt;
|
||||
private Double temperature;
|
||||
private Double topP;
|
||||
private Integer maxGenLen;
|
||||
|
||||
public Builder(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxGenLen(Integer maxGenLen) {
|
||||
this.maxGenLen = maxGenLen;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LlamaChatRequest build() {
|
||||
return new LlamaChatRequest(
|
||||
this.prompt,
|
||||
this.temperature,
|
||||
this.topP,
|
||||
this.maxGenLen
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<LlamaChatResponse> chatCompletionStream(LlamaChatRequest request) {
|
||||
return this.internalInvocationStream(request, LlamaChatResponse.class);
|
||||
/**
|
||||
* LlamaChatResponse encapsulates the response parameters for the Meta Llama chat model.
|
||||
*
|
||||
* @param generation The generated text.
|
||||
* @param promptTokenCount The number of tokens in the prompt.
|
||||
* @param generationTokenCount The number of tokens in the response.
|
||||
* @param stopReason The reason why the response stopped generating text. Possible values are: (1) stop – The model
|
||||
* has finished generating text for the input prompt. (2) length – The length of the tokens for the generated text
|
||||
* exceeds the value of max_gen_len in the call. The response is truncated to max_gen_len tokens. Consider
|
||||
* increasing the value of max_gen_len and trying again.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public record LlamaChatResponse(
|
||||
@JsonProperty("generation") String generation,
|
||||
@JsonProperty("prompt_token_count") Integer promptTokenCount,
|
||||
@JsonProperty("generation_token_count") Integer generationTokenCount,
|
||||
@JsonProperty("stop_reason") StopReason stopReason,
|
||||
@JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) {
|
||||
|
||||
/**
|
||||
* The reason the response finished being generated.
|
||||
*/
|
||||
public enum StopReason {
|
||||
/**
|
||||
* The model has finished generating text for the input prompt.
|
||||
*/
|
||||
@JsonProperty("stop") STOP,
|
||||
/**
|
||||
* The response was truncated because of the response length you set.
|
||||
*/
|
||||
@JsonProperty("length") LENGTH
|
||||
}
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.titan;
|
||||
|
||||
import java.util.List;
|
||||
@@ -24,13 +25,13 @@ import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi;
|
||||
import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatRequest;
|
||||
import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponse;
|
||||
import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponseChunk;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.bedrock.titan;
|
||||
|
||||
import java.util.List;
|
||||
@@ -20,11 +21,10 @@ import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
@@ -59,39 +59,17 @@ public class BedrockTitanChatOptions implements ChatOptions {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private BedrockTitanChatOptions options = new BedrockTitanChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokenCount(Integer maxTokenCount) {
|
||||
this.options.maxTokenCount = maxTokenCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.stopSequences = stopSequences;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockTitanChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public static BedrockTitanChatOptions fromOptions(BedrockTitanChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withMaxTokenCount(fromOptions.getMaxTokenCount())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
return this.temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(Double temperature) {
|
||||
@@ -100,7 +78,7 @@ public class BedrockTitanChatOptions implements ChatOptions {
|
||||
|
||||
@Override
|
||||
public Double getTopP() {
|
||||
return topP;
|
||||
return this.topP;
|
||||
}
|
||||
|
||||
public void setTopP(Double topP) {
|
||||
@@ -119,7 +97,7 @@ public class BedrockTitanChatOptions implements ChatOptions {
|
||||
}
|
||||
|
||||
public Integer getMaxTokenCount() {
|
||||
return maxTokenCount;
|
||||
return this.maxTokenCount;
|
||||
}
|
||||
|
||||
public void setMaxTokenCount(Integer maxTokenCount) {
|
||||
@@ -128,7 +106,7 @@ public class BedrockTitanChatOptions implements ChatOptions {
|
||||
|
||||
@Override
|
||||
public List<String> getStopSequences() {
|
||||
return stopSequences;
|
||||
return this.stopSequences;
|
||||
}
|
||||
|
||||
public void setStopSequences(List<String> stopSequences) {
|
||||
@@ -164,12 +142,34 @@ public class BedrockTitanChatOptions implements ChatOptions {
|
||||
return fromOptions(this);
|
||||
}
|
||||
|
||||
public static BedrockTitanChatOptions fromOptions(BedrockTitanChatOptions fromOptions) {
|
||||
return builder().withTemperature(fromOptions.getTemperature())
|
||||
.withTopP(fromOptions.getTopP())
|
||||
.withMaxTokenCount(fromOptions.getMaxTokenCount())
|
||||
.withStopSequences(fromOptions.getStopSequences())
|
||||
.build();
|
||||
public static class Builder {
|
||||
|
||||
private BedrockTitanChatOptions options = new BedrockTitanChatOptions();
|
||||
|
||||
public Builder withTemperature(Double temperature) {
|
||||
this.options.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Double topP) {
|
||||
this.options.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMaxTokenCount(Integer maxTokenCount) {
|
||||
this.options.maxTokenCount = maxTokenCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(List<String> stopSequences) {
|
||||
this.options.stopSequences = stopSequences;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockTitanChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user