diff --git a/.github/workflows/code-format-check.yml b/.github/workflows/code-format-check.yml index ef7a56e5e..7ded317b6 100644 --- a/.github/workflows/code-format-check.yml +++ b/.github/workflows/code-format-check.yml @@ -4,6 +4,9 @@ on: pull_request: branches: - main + push: + branches: + - main jobs: build: @@ -22,4 +25,4 @@ jobs: - name: Source code formatting check run: | - ./mvnw spring-javaformat:validate -pl spring-ai-alibaba-core + ./mvnw spring-javaformat:validate diff --git a/community/document-parsers/document-parser-apache-pdfbox/pom.xml b/community/document-parsers/document-parser-apache-pdfbox/pom.xml index 7a1a0b9dc..f36fafad1 100644 --- a/community/document-parsers/document-parser-apache-pdfbox/pom.xml +++ b/community/document-parsers/document-parser-apache-pdfbox/pom.xml @@ -25,7 +25,6 @@ 17 17 UTF-8 - 2.0.32 @@ -36,15 +35,8 @@ - org.apache.pdfbox - pdfbox - ${pdfbox.version} - - - commons-logging - commons-logging - - + org.springframework.ai + spring-ai-pdf-document-reader diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ApachePdfBoxDocumentParser.java b/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ApachePdfBoxDocumentParser.java deleted file mode 100644 index b35696d04..000000000 --- a/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ApachePdfBoxDocumentParser.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2024-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.cloud.ai.parser.apache.pdfbox; - -import com.alibaba.cloud.ai.document.DocumentParser; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDDocumentInformation; -import org.apache.pdfbox.text.PDFTextStripper; -import org.springframework.ai.document.Document; -import org.springframework.util.Assert; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author HeYQ - * @since 2024-12-08 22:34 - */ - -public class ApachePdfBoxDocumentParser implements DocumentParser { - - private final boolean includeMetadata; - - public ApachePdfBoxDocumentParser() { - this(false); - } - - public ApachePdfBoxDocumentParser(boolean includeMetadata) { - this.includeMetadata = includeMetadata; - } - - @Override - public List parse(InputStream inputStream) { - try (PDDocument pdfDocument = PDDocument.load(inputStream)) { - PDFTextStripper stripper = new PDFTextStripper(); - String text = stripper.getText(pdfDocument); - Assert.notNull(text, "Text cannot be null"); - return includeMetadata ? Collections.singletonList(new Document(text, toMetadata(pdfDocument))) - : Collections.singletonList(new Document(text)); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - private Map toMetadata(PDDocument pdDocument) { - PDDocumentInformation documentInformation = pdDocument.getDocumentInformation(); - Map metadata = new HashMap<>(); - for (String metadataKey : documentInformation.getMetadataKeys()) { - String value = documentInformation.getCustomMetadataValue(metadataKey); - if (value != null) { - metadata.put(metadataKey, value); - } - } - return metadata; - } - -} diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/PagePdfDocumentParser.java b/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/PagePdfDocumentParser.java new file mode 100644 index 000000000..030a64199 --- /dev/null +++ b/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/PagePdfDocumentParser.java @@ -0,0 +1,137 @@ +package com.alibaba.cloud.ai.parser.apache.pdfbox; + +import java.awt.Rectangle; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.alibaba.cloud.ai.document.DocumentParser; +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.reader.pdf.config.PdfDocumentReaderConfig; +import org.springframework.ai.reader.pdf.layout.PDFLayoutTextStripperByArea; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Groups the parsed PDF pages into {@link Document}s. You can group one or more pages + * into a single output document. Use {@link PdfDocumentReaderConfig} for customization + * options. The default configuration is: - pagesPerDocument = 1 - pageTopMargin = 0 - + * pageBottomMargin = 0 + * + * @author HeYQ + */ +public class PagePdfDocumentParser implements DocumentParser { + + public static final String METADATA_START_PAGE_NUMBER = "page_number"; + + public static final String METADATA_END_PAGE_NUMBER = "end_page_number"; + + private static final String PDF_PAGE_REGION = "pdfPageRegion"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PdfDocumentReaderConfig config; + + public PagePdfDocumentParser() { + this(PdfDocumentReaderConfig.defaultConfig()); + } + + public PagePdfDocumentParser(PdfDocumentReaderConfig config) { + this.config = config; + } + + @Override + public List parse(InputStream inputStream) { + + List readDocuments = new ArrayList<>(); + try { + var pdfTextStripper = new PDFLayoutTextStripperByArea(); + + int pageNumber = 0; + int pagesPerDocument = 0; + int startPageNumber = pageNumber; + + List pageTextGroupList = new ArrayList<>(); + PDFParser pdfParser = new PDFParser(new org.apache.pdfbox.io.RandomAccessReadBuffer(inputStream)); + PDDocument document = pdfParser.parse(); + + int totalPages = document.getDocumentCatalog().getPages().getCount(); + // if less than 10 + int logFrequency = totalPages > 10 ? totalPages / 10 : 1; + // pages, print + // each iteration + int counter = 0; + + PDPage lastPage = document.getDocumentCatalog().getPages().iterator().next(); + for (PDPage page : document.getDocumentCatalog().getPages()) { + lastPage = page; + if (counter % logFrequency == 0 && counter / logFrequency < 10) { + logger.info("Processing PDF page: {}", (counter + 1)); + } + counter++; + + pagesPerDocument++; + + if (this.config.pagesPerDocument != PdfDocumentReaderConfig.ALL_PAGES + && pagesPerDocument >= this.config.pagesPerDocument) { + pagesPerDocument = 0; + + var aggregatedPageTextGroup = pageTextGroupList.stream().collect(Collectors.joining()); + if (StringUtils.hasText(aggregatedPageTextGroup)) { + readDocuments.add(toDocument(aggregatedPageTextGroup, startPageNumber, pageNumber)); + } + pageTextGroupList.clear(); + + startPageNumber = pageNumber + 1; + } + int x0 = (int) page.getMediaBox().getLowerLeftX(); + int xW = (int) page.getMediaBox().getWidth(); + + int y0 = (int) page.getMediaBox().getLowerLeftY() + this.config.pageTopMargin; + int yW = (int) page.getMediaBox().getHeight() + - (this.config.pageTopMargin + this.config.pageBottomMargin); + + pdfTextStripper.addRegion(PDF_PAGE_REGION, new Rectangle(x0, y0, xW, yW)); + pdfTextStripper.extractRegions(page); + var pageText = pdfTextStripper.getTextForRegion(PDF_PAGE_REGION); + + if (StringUtils.hasText(pageText)) { + + pageText = this.config.pageExtractedTextFormatter.format(pageText, pageNumber); + + pageTextGroupList.add(pageText); + } + pageNumber++; + pdfTextStripper.removeRegion(PDF_PAGE_REGION); + } + if (!CollectionUtils.isEmpty(pageTextGroupList)) { + readDocuments.add(toDocument(pageTextGroupList.stream().collect(Collectors.joining()), startPageNumber, + pageNumber)); + } + logger.info("Processing {} pages", totalPages); + return readDocuments; + + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected Document toDocument(String docText, int startPageNumber, int endPageNumber) { + Document doc = new Document(docText); + doc.getMetadata().put(METADATA_START_PAGE_NUMBER, startPageNumber); + if (startPageNumber != endPageNumber) { + doc.getMetadata().put(METADATA_END_PAGE_NUMBER, endPageNumber); + } + return doc; + } + +} diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ParagraphPdfDocumentParser.java b/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ParagraphPdfDocumentParser.java new file mode 100644 index 000000000..193cf5c94 --- /dev/null +++ b/community/document-parsers/document-parser-apache-pdfbox/src/main/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ParagraphPdfDocumentParser.java @@ -0,0 +1,205 @@ +package com.alibaba.cloud.ai.parser.apache.pdfbox; + +import java.awt.Rectangle; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.alibaba.cloud.ai.document.DocumentParser; +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.reader.pdf.config.ParagraphManager; +import org.springframework.ai.reader.pdf.config.ParagraphManager.Paragraph; +import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig; +import org.springframework.ai.reader.pdf.layout.PDFLayoutTextStripperByArea; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Uses the PDF catalog (e.g. TOC) information to split the input PDF into text paragraphs + * and output a single {@link Document} per paragraph. This class provides methods for + * reading and processing PDF documents. It uses the Apache PDFBox library for parsing PDF + * content and converting it into text paragraphs. The paragraphs are grouped into + * {@link Document} objects. + * + * @author HeYQ + */ +public class ParagraphPdfDocumentParser implements DocumentParser { + + // Constants for metadata keys + private static final String METADATA_START_PAGE = "page_number"; + + private static final String METADATA_END_PAGE = "end_page_number"; + + private static final String METADATA_TITLE = "title"; + + private static final String METADATA_LEVEL = "level"; + + private static final String METADATA_FILE_NAME = "file_name"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PdfDocumentReaderConfig config; + + /** + * Constructs a ParagraphPdfDocumentParser using a resource URL and a configuration. + */ + public ParagraphPdfDocumentParser() { + this(PdfDocumentReaderConfig.defaultConfig()); + } + + /** + * Constructs a ParagraphPdfDocumentParser using a resource and a configuration. + * @param config The configuration for PDF document processing. + */ + public ParagraphPdfDocumentParser(PdfDocumentReaderConfig config) { + this.config = config; + } + + /** + * Reads and processes the PDF document to extract paragraphs. + * @return A list of {@link Document} objects representing paragraphs. + */ + @Override + public List parse(InputStream inputStream) { + + try { + PDFParser pdfParser = new PDFParser(new org.apache.pdfbox.io.RandomAccessReadBuffer(inputStream)); + PDDocument pdDocument = pdfParser.parse(); + + ParagraphManager paragraphTextExtractor = new ParagraphManager(pdDocument); + + var paragraphs = paragraphTextExtractor.flatten(); + + List documents = new ArrayList<>(paragraphs.size()); + + if (!CollectionUtils.isEmpty(paragraphs)) { + logger.info("Start processing paragraphs from PDF"); + Iterator itr = paragraphs.iterator(); + + var current = itr.next(); + + if (!itr.hasNext()) { + documents.add(toDocument(current, current, pdDocument)); + } + else { + while (itr.hasNext()) { + var next = itr.next(); + Document document = toDocument(current, next, pdDocument); + if (document != null && StringUtils.hasText(document.getContent())) { + documents.add(toDocument(current, next, pdDocument)); + } + current = next; + } + } + } + logger.info("End processing paragraphs from PDF"); + return documents; + + } + catch (IllegalArgumentException iae) { + throw iae; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected Document toDocument(Paragraph from, Paragraph to, PDDocument pdDocument) { + + String docText = this.getTextBetweenParagraphs(from, to, pdDocument); + + if (!StringUtils.hasText(docText)) { + return null; + } + + Document document = new Document(docText); + addMetadata(from, to, document); + + return document; + } + + protected void addMetadata(Paragraph from, Paragraph to, Document document) { + document.getMetadata().put(METADATA_TITLE, from.title()); + document.getMetadata().put(METADATA_START_PAGE, from.startPageNumber()); + document.getMetadata().put(METADATA_END_PAGE, to.startPageNumber()); + document.getMetadata().put(METADATA_LEVEL, from.level()); + } + + public String getTextBetweenParagraphs(Paragraph fromParagraph, Paragraph toParagraph, PDDocument pdDocument) { + + // Page started from index 0, while PDFBOx getPage return them from index 1. + int startPage = fromParagraph.startPageNumber() - 1; + int endPage = toParagraph.startPageNumber() - 1; + + try { + + StringBuilder sb = new StringBuilder(); + + var pdfTextStripper = new PDFLayoutTextStripperByArea(); + pdfTextStripper.setSortByPosition(true); + + for (int pageNumber = startPage; pageNumber <= endPage; pageNumber++) { + + var page = pdDocument.getPage(pageNumber); + + int fromPosition = fromParagraph.position(); + int toPosition = toParagraph.position(); + + if (this.config.reversedParagraphPosition) { + fromPosition = (int) (page.getMediaBox().getHeight() - fromPosition); + toPosition = (int) (page.getMediaBox().getHeight() - toPosition); + } + + int x0 = (int) page.getMediaBox().getLowerLeftX(); + int xW = (int) page.getMediaBox().getWidth(); + + int y0 = (int) page.getMediaBox().getLowerLeftY(); + int yW = (int) page.getMediaBox().getHeight(); + + if (pageNumber == startPage) { + y0 = fromPosition; + yW = (int) page.getMediaBox().getHeight() - y0; + } + if (pageNumber == endPage) { + yW = toPosition - y0; + } + + if ((y0 + yW) == (int) page.getMediaBox().getHeight()) { + yW = yW - this.config.pageBottomMargin; + } + + if (y0 == 0) { + y0 = y0 + this.config.pageTopMargin; + yW = yW - this.config.pageTopMargin; + } + + pdfTextStripper.addRegion("pdfPageRegion", new Rectangle(x0, y0, xW, yW)); + pdfTextStripper.extractRegions(page); + var text = pdfTextStripper.getTextForRegion("pdfPageRegion"); + if (StringUtils.hasText(text)) { + sb.append(text); + } + pdfTextStripper.removeRegion("pdfPageRegion"); + + } + + String text = sb.toString(); + + if (StringUtils.hasText(text)) { + text = this.config.pageExtractedTextFormatter.format(text, startPage); + } + + return text; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ApachePdfBoxDocumentParserTest.java b/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ApachePdfBoxDocumentParserTest.java deleted file mode 100644 index 9facd2157..000000000 --- a/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ApachePdfBoxDocumentParserTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2024-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.cloud.ai.parser.apache.pdfbox; - -import com.alibaba.cloud.ai.document.DocumentParser; -import org.junit.jupiter.api.Test; -import org.springframework.ai.document.Document; - -import java.io.IOException; -import java.io.InputStream; - -import static org.assertj.core.api.Assertions.assertThat; - -class ApachePdfBoxDocumentParserTest { - - @Test - void should_parse_pdf_file() { - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test-file.pdf")) { - DocumentParser parser = new ApachePdfBoxDocumentParser(); - Document document = parser.parse(inputStream).get(0); - - assertThat(document.getContent()).isEqualToIgnoringWhitespace("test content"); - assertThat(document.getMetadata()).isEmpty(); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - void should_parse_pdf_file_include_metadata() { - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test-file.pdf")) { - DocumentParser parser = new ApachePdfBoxDocumentParser(true); - Document document = parser.parse(inputStream).get(0); - - assertThat(document.getContent()).isEqualToIgnoringWhitespace("test content"); - assertThat(document.getMetadata()).containsEntry("Author", "ljuba") - .containsEntry("Creator", "WPS Writer") - .containsEntry("CreationDate", "D:20230608171011+15'10'") - .containsEntry("SourceModified", "D:20230608171011+15'10'"); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - -} \ No newline at end of file diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/PagePdfDocumentParserTests.java b/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/PagePdfDocumentParserTests.java new file mode 100644 index 000000000..d004b94f2 --- /dev/null +++ b/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/PagePdfDocumentParserTests.java @@ -0,0 +1,58 @@ +package com.alibaba.cloud.ai.parser.apache.pdfbox; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.ExtractedTextFormatter; +import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig; +import org.springframework.core.io.DefaultResourceLoader; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author HeYQ + * @since 2024-12-24 00:52 + */ +class PagePdfDocumentParserTests { + + @Test + void classpathRead() throws IOException { + + PagePdfDocumentParser parser = new PagePdfDocumentParser(PdfDocumentReaderConfig.builder() + .withPageTopMargin(0) + .withPageBottomMargin(0) + .withPageExtractedTextFormatter(ExtractedTextFormatter.builder() + .withNumberOfTopTextLinesToDelete(0) + .withNumberOfBottomTextLinesToDelete(3) + .withNumberOfTopPagesToSkipBeforeDelete(0) + .build()) + .withPagesPerDocument(1) + .build()); + + List docs = parser + .parse(new DefaultResourceLoader().getResource("classpath:/sample1.pdf").getInputStream()); + + assertThat(docs).hasSize(4); + + String allText = docs.stream().map(Document::getContent).collect(Collectors.joining(System.lineSeparator())); + System.out.println(allText); + + // assertThat(allText).doesNotContain( + // List.of("Page 1 of 4", "Page 2 of 4", "Page 3 of 4", "Page 4 of 4", "PDF + // Bookmark Sample")); + } + + @Test + void testIndexOutOfBound() throws IOException { + var documents = new PagePdfDocumentParser(PdfDocumentReaderConfig.builder() + .withPageExtractedTextFormatter(ExtractedTextFormatter.builder().build()) + .withPagesPerDocument(1) + .build()).parse(new DefaultResourceLoader().getResource("classpath:/sample2.pdf").getInputStream()); + + assertThat(documents).hasSize(64); + } + +} diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ParagraphPdfDocumentParserTests.java b/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ParagraphPdfDocumentParserTests.java new file mode 100644 index 000000000..a45be2e80 --- /dev/null +++ b/community/document-parsers/document-parser-apache-pdfbox/src/test/java/com/alibaba/cloud/ai/parser/apache/pdfbox/ParagraphPdfDocumentParserTests.java @@ -0,0 +1,36 @@ +package com.alibaba.cloud.ai.parser.apache.pdfbox; + +import org.junit.jupiter.api.Test; + +import org.springframework.ai.reader.ExtractedTextFormatter; +import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig; +import org.springframework.core.io.DefaultResourceLoader; + +import java.io.IOException; + +/** + * @author HeYQ + * @since 2024-12-24 00:52 + */ + +public class ParagraphPdfDocumentParserTests { + + @Test + public void testPdfWithoutToc() throws IOException { + + String content = new ParagraphPdfDocumentParser(PdfDocumentReaderConfig.builder() + .withPageTopMargin(0) + .withPageBottomMargin(0) + .withPageExtractedTextFormatter(ExtractedTextFormatter.builder() + .withNumberOfTopTextLinesToDelete(0) + .withNumberOfBottomTextLinesToDelete(3) + .withNumberOfTopPagesToSkipBeforeDelete(0) + .build()) + .withPagesPerDocument(1) + .build()).parse(new DefaultResourceLoader().getResource("classpath:/sample1.pdf").getInputStream()) + .get(0) + .getContent(); + System.out.println(content); + } + +} diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/test/resources/sample1.pdf b/community/document-parsers/document-parser-apache-pdfbox/src/test/resources/sample1.pdf new file mode 100644 index 000000000..8efd05c3d Binary files /dev/null and b/community/document-parsers/document-parser-apache-pdfbox/src/test/resources/sample1.pdf differ diff --git a/community/document-parsers/document-parser-apache-pdfbox/src/test/resources/sample2.pdf b/community/document-parsers/document-parser-apache-pdfbox/src/test/resources/sample2.pdf new file mode 100644 index 000000000..c6657bf05 Binary files /dev/null and b/community/document-parsers/document-parser-apache-pdfbox/src/test/resources/sample2.pdf differ diff --git a/community/document-parsers/document-parser-markdown/src/main/java/com/alibaba/cloud/ai/parser/markdown/MarkdownDocumentParser.java b/community/document-parsers/document-parser-markdown/src/main/java/com/alibaba/cloud/ai/parser/markdown/MarkdownDocumentParser.java index 08af6a553..887182aee 100644 --- a/community/document-parsers/document-parser-markdown/src/main/java/com/alibaba/cloud/ai/parser/markdown/MarkdownDocumentParser.java +++ b/community/document-parsers/document-parser-markdown/src/main/java/com/alibaba/cloud/ai/parser/markdown/MarkdownDocumentParser.java @@ -147,14 +147,14 @@ public void visit(BlockQuote blockQuote) { } translateLineBreakToSpace(); - this.currentDocumentBuilder.withMetadata("category", "blockquote"); + this.currentDocumentBuilder.metadata("category", "blockquote"); super.visit(blockQuote); } @Override public void visit(Code code) { this.currentParagraphs.add(code.getLiteral()); - this.currentDocumentBuilder.withMetadata("category", "code_inline"); + this.currentDocumentBuilder.metadata("category", "code_inline"); super.visit(code); } @@ -166,8 +166,8 @@ public void visit(FencedCodeBlock fencedCodeBlock) { translateLineBreakToSpace(); this.currentParagraphs.add(fencedCodeBlock.getLiteral()); - this.currentDocumentBuilder.withMetadata("category", "code_block"); - this.currentDocumentBuilder.withMetadata("lang", fencedCodeBlock.getInfo()); + this.currentDocumentBuilder.metadata("category", "code_block"); + this.currentDocumentBuilder.metadata("lang", fencedCodeBlock.getInfo()); buildAndFlush(); @@ -177,8 +177,8 @@ public void visit(FencedCodeBlock fencedCodeBlock) { @Override public void visit(Text text) { if (text.getParent() instanceof Heading heading) { - this.currentDocumentBuilder.withMetadata("category", "header_%d".formatted(heading.getLevel())) - .withMetadata("title", text.getLiteral()); + this.currentDocumentBuilder.metadata("category", "header_%d".formatted(heading.getLevel())) + .metadata("title", text.getLiteral()); } else { this.currentParagraphs.add(text.getLiteral()); @@ -197,9 +197,9 @@ private void buildAndFlush() { if (!this.currentParagraphs.isEmpty()) { String content = String.join("", this.currentParagraphs); - Document.Builder builder = this.currentDocumentBuilder.withContent(content); + Document.Builder builder = this.currentDocumentBuilder.text(content); - this.config.additionalMetadata.forEach(builder::withMetadata); + this.config.additionalMetadata.forEach(builder::metadata); Document document = builder.build(); diff --git a/community/document-readers/feishu-document-reader/src/main/java/com/alibaba/cloud/ai/reader/feishu/config/FeiShuProperties.java b/community/document-readers/feishu-document-reader/src/main/java/com/alibaba/cloud/ai/reader/feishu/config/FeiShuProperties.java deleted file mode 100644 index a11f68e23..000000000 --- a/community/document-readers/feishu-document-reader/src/main/java/com/alibaba/cloud/ai/reader/feishu/config/FeiShuProperties.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.cloud.ai.reader.feishu.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author wblu214 - * @author wblu214 - */ -@ConfigurationProperties(prefix = FeiShuProperties.FEISHU_PROPERTIES_PREFIX) -public class FeiShuProperties { - - public static final String FEISHU_PROPERTIES_PREFIX = "spring.ai.alibaba.plugin.feishu"; - - private Boolean enabled; - - private String appId; - - private String appSecret; - - public Boolean getEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - public String getAppId() { - return appId; - } - - public void setAppId(String appId) { - this.appId = appId; - } - - public String getAppSecret() { - return appSecret; - } - - public void setAppSecret(String appSecret) { - this.appSecret = appSecret; - } - - @Override - public String toString() { - return "FeiShuProperties{" + "enabled=" + enabled + ", appId='" + appId + '\'' + ", AppSecret='" + appSecret - + '\'' + '}'; - } - -} diff --git a/community/document-readers/github-document-reader/pom.xml b/community/document-readers/github-document-reader/pom.xml index ba8a5f1ad..3b9e3fba5 100644 --- a/community/document-readers/github-document-reader/pom.xml +++ b/community/document-readers/github-document-reader/pom.xml @@ -11,7 +11,7 @@ github-document-reader - github-reader + github-document-reader github reader for Spring AI Alibaba jar https://github.com/alibaba/spring-ai-alibaba diff --git a/community/function-calling/README.md b/community/function-calling/README.md index 5b4c4f0f1..878a333df 100644 --- a/community/function-calling/README.md +++ b/community/function-calling/README.md @@ -8,10 +8,11 @@ * Function Impl name 命名为:`${name}Service`,通常是由声明 Bean 注解的方法名确定,如 `baiduSearchService`(建议,请根据插件实际情况确定) * function bean name 命名为:`${xx}Function`,通常使用 动词+修饰词的方式确定,如`getCurrentLocationUseBaiduMapFunction`(建议,请根据插件实际情况确定) 2. 使用 **org.springframework.context.annotation.Description** `@Description("xxx")` 注解描述插件的功能,应提供对插件功能清晰明确的描述,例如:`@Description("百度搜索插件,用于查询百度上的新闻事件等信息")` -3. 如插件自身有配置参数,请使用 `@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.${name}")` 注解,例如: +3. 如果 Function Impl 实现较为复杂,需要使用一些自定义函数,方法命名规范为:`${name}Tool`,例如:`BaiduSearchTool`, 目录层级和实现类保持一致 +4 . 如插件自身有配置参数,请使用 `@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.${name}")` 注解,例如: ```java @ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.baidusearch") public class BaidusearchProperties {} ``` 4. 请在根目录 pom.xml 中添加 module 配置,如 `community/function-calling/spring-ai-alibaba-starter-functioncalling-baidusearch` -5. 请在插件 pom.xml 文件中只保留必须的传递依赖,插件版本应与 Spring AI Alibaba 统一。 +5. 请在插件 pom.xml 文件中只保留必须的传递依赖,插件版本应与 Spring AI Alibaba 统一,插件依赖规范为:序列化与反序列化依赖统一使用 `com.fasterxml.jackson.core:jackson`,日志依赖统一使用 `org.slf4j`,HTTP 客户端依赖统一使用 `org.springframework.boot::webClient`,其余依赖尽可能少引用或不引用 diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/pom.xml b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/pom.xml new file mode 100644 index 000000000..f993caf89 --- /dev/null +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/pom.xml @@ -0,0 +1,67 @@ + + + + + + 4.0.0 + + + com.alibaba.cloud.ai + spring-ai-alibaba + ${revision} + ../../../pom.xml + + + spring-ai-alibaba-starter-function-calling-alitranslate + spring-ai-alibaba-starter-function-calling-alitranslate + Translate tool for Spring AI Alibaba + + + + org.springframework.ai + spring-ai-spring-boot-autoconfigure + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-webflux + + + + com.google.code.gson + gson + + + + com.aliyun + alibabacloud-alimt20181012 + 1.0.2 + + + + + + spring-ai-alibaba-function-calling-alitranslate + + diff --git a/community/document-readers/feishu-document-reader/src/main/java/com/alibaba/cloud/ai/reader/feishu/config/FeiShuPluginConfiguration.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateAutoConfiguration.java similarity index 53% rename from community/document-readers/feishu-document-reader/src/main/java/com/alibaba/cloud/ai/reader/feishu/config/FeiShuPluginConfiguration.java rename to community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateAutoConfiguration.java index 388f8a376..4d046164b 100644 --- a/community/document-readers/feishu-document-reader/src/main/java/com/alibaba/cloud/ai/reader/feishu/config/FeiShuPluginConfiguration.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateAutoConfiguration.java @@ -13,38 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.cloud.ai.reader.feishu.config; +package com.alibaba.cloud.ai.functioncalling.alitranslate; -import com.lark.oapi.Client; -import com.lark.oapi.core.enums.BaseUrlEnum; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description; -import org.springframework.util.Assert; /** - * @author wblu214 - * @author wblu214 + * @author yunlong */ @Configuration -@EnableConfigurationProperties(FeiShuProperties.class) -public class FeiShuPluginConfiguration { +@ConditionalOnClass(AliTranslateService.class) +@EnableConfigurationProperties(AliTranslateProperties.class) +@ConditionalOnProperty(prefix = "spring.ai.alibaba.functioncalling.alitranslate", name = "enabled", + havingValue = "true") +public class AliTranslateAutoConfiguration { @Bean @ConditionalOnMissingBean - @Description("Build FeiShu Client in Spring AI Alibaba") // description - @ConditionalOnProperty(prefix = FeiShuProperties.FEISHU_PROPERTIES_PREFIX, name = "enabled", havingValue = "true") - public Client buildDefaultFeiShuClient(FeiShuProperties feiShuProperties) { - Assert.notNull(feiShuProperties.getAppId(), "FeiShu AppId must not be empty"); - Assert.notNull(feiShuProperties.getAppSecret(), "FeiShu AppSecret must not be empty"); - return Client.newBuilder(feiShuProperties.getAppId(), feiShuProperties.getAppSecret()) - .openBaseUrl(BaseUrlEnum.FeiShu) - .logReqAtDebug(true) - .build(); + @Description("Implement natural language translation capabilities.") + public AliTranslateService aliTranslateFunction(AliTranslateProperties properties) { + return new AliTranslateService(properties); } - // 商店应用自行扩展 } diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateProperties.java new file mode 100644 index 000000000..c410b582c --- /dev/null +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateProperties.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.functioncalling.alitranslate; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author yunlong + * + * [offlinedoc](https://help.aliyun.com/zh/machine-translation/developer-reference/api-alimt-2018-10-12-translategeneral) + */ +@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.alitranslate") +public class AliTranslateProperties { + + private String region; + + private String accessKeyId; + + private String accessKeySecret; + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getAccessKeySecret() { + return accessKeySecret; + } + + public void setAccessKeySecret(String accessKeySecret) { + this.accessKeySecret = accessKeySecret; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + +} diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateService.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateService.java new file mode 100644 index 000000000..2ccebd2d4 --- /dev/null +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/alitranslate/AliTranslateService.java @@ -0,0 +1,128 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.functioncalling.alitranslate; + +import com.aliyun.auth.credentials.Credential; +import com.aliyun.auth.credentials.provider.StaticCredentialProvider; +import com.aliyun.sdk.service.alimt20181012.AsyncClient; +import com.aliyun.sdk.service.alimt20181012.models.TranslateGeneralRequest; +import com.aliyun.sdk.service.alimt20181012.models.TranslateGeneralResponse; +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import darabonba.core.client.ClientOverrideConfiguration; +import org.springframework.util.StringUtils; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +/** + * @author yunlong + */ +public class AliTranslateService implements Function { + + private final AsyncClient client; + + /** + * version of the api + */ + public static final String SCENE = "general"; + + /** + * FormatType text or html + */ + public static final String FORM_TYPE = "text"; + + /** + * offline doc: + * https://help.aliyun.com/zh/machine-translation/support/supported-languages-and-codes?spm=api-workbench.api_explorer.0.0.37a94eecsclZw9 + */ + public static final String LANGUAGE_CODE = "zh"; + + public AliTranslateService(AliTranslateProperties properties) { + assert StringUtils.hasText(properties.getRegion()); + assert StringUtils.hasText(properties.getAccessKeyId()); + assert StringUtils.hasText(properties.getAccessKeySecret()); + StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder() + .accessKeyId(properties.getAccessKeyId()) + .accessKeySecret(properties.getAccessKeySecret()) + .build()); + + this.client = AsyncClient.builder() + .region(properties.getRegion()) // Region ID + .credentialsProvider(provider) + .overrideConfiguration(ClientOverrideConfiguration.create().setEndpointOverride("mt.aliyuncs.com")) + .build(); + } + + @Override + public Response apply(Request request) { + if (request == null || !StringUtils.hasText(request.text) || !StringUtils.hasText(request.targetLanguage)) { + return null; + } + + TranslateGeneralRequest translateGeneralRequest = TranslateGeneralRequest.builder() + .formatType(FORM_TYPE) + .sourceLanguage(LANGUAGE_CODE) + .targetLanguage(request.targetLanguage) + .sourceText(request.text) + .scene(SCENE) + .build(); + + CompletableFuture response = client.translateGeneral(translateGeneralRequest); + + TranslateGeneralResponse resp = null; + try { + resp = response.get(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + // 假设 resp 是一个对象 + String jsonString = new Gson().toJson(resp); + JsonObject jsonObject = new Gson().fromJson(jsonString, JsonObject.class); + String result = jsonObject.get("body") + .getAsJsonObject() + .get("data") + .getAsJsonObject() + .get("translated") + .toString(); + + client.close(); + return new Response(result); + + } + + @JsonClassDescription("Request to alitranslate text to a target language") + public record Request( + @JsonProperty(required = true, + value = "text") @JsonPropertyDescription("Content that needs to be translated") String text, + @JsonProperty(required = false, + value = "targetLanguage") @JsonPropertyDescription("Target language to alitranslate into") String targetLanguage) { + + public Request(@JsonProperty("text") String text) { + this(text, "en"); // 默认目标语言为英语 + } + } + + @JsonClassDescription("Response to alitranslate text to a target language") + public record Response(String translatedTexts) { + } + +} diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..64584bf0b --- /dev/null +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.alibaba.cloud.ai.functioncalling.alitranslate.AliTranslateAutoConfiguration diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-amap/src/main/java/com/alibaba/cloud/ai/functioncalling/amp/AmapProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-amap/src/main/java/com/alibaba/cloud/ai/functioncalling/amp/AmapProperties.java index bcd7d6cd7..0406ae622 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-amap/src/main/java/com/alibaba/cloud/ai/functioncalling/amp/AmapProperties.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-amap/src/main/java/com/alibaba/cloud/ai/functioncalling/amp/AmapProperties.java @@ -20,7 +20,7 @@ /** * @author YunLong */ -@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.gaode") +@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.amap") public class AmapProperties { // Official Document Address: https://lbs.amap.com/api/webservice/summary diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchAutoConfiguration.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchAutoConfiguration.java index b7426c3f8..6e7f8a30d 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchAutoConfiguration.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchAutoConfiguration.java @@ -33,7 +33,6 @@ public class BingSearchAutoConfiguration { @Bean @ConditionalOnMissingBean @Description("Use bing search engine to query for the latest news.") - @ConditionalOnProperty(prefix = "spring.ai.alibaba.plugin.bing", name = "enabled", havingValue = "true") public BingSearchService bingSearchFunction(BingSearchProperties properties) { return new BingSearchService(properties); } diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchProperties.java index 69fefff6e..65f480da8 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchProperties.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-bingsearch/src/main/java/com/alibaba/cloud/ai/functioncalling/bingsearch/BingSearchProperties.java @@ -22,7 +22,7 @@ * @author KrakenZJC **/ @EnableConfigurationProperties -@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.bing") +@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.bingsearch") public class BingSearchProperties { public static final String OCP_APIM_SUBSCRIPTION_KEY = "Ocp-Apim-Subscription-Key"; diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-dingtalk/src/main/java/com/alibaba/cloud/ai/functioncalling/dingtalk/DingTalkProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-dingtalk/src/main/java/com/alibaba/cloud/ai/functioncalling/dingtalk/DingTalkProperties.java index 221639636..8efcf6e83 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-dingtalk/src/main/java/com/alibaba/cloud/ai/functioncalling/dingtalk/DingTalkProperties.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-dingtalk/src/main/java/com/alibaba/cloud/ai/functioncalling/dingtalk/DingTalkProperties.java @@ -20,7 +20,7 @@ /** * @author YunLong */ -@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.dingtalk") +@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.dingtalk") public class DingTalkProperties { private String customRobotAccessToken; diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteAutoConfiguration.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteAutoConfiguration.java index efe71cf8c..da8397a30 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteAutoConfiguration.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteAutoConfiguration.java @@ -27,7 +27,7 @@ */ @EnableConfigurationProperties({ LarkSuiteProperties.class }) @ConditionalOnClass({ LarkSuiteProperties.class }) -@ConditionalOnProperty(prefix = "spring.ai.alibaba.plugin.larksuite", name = "enabled", havingValue = "true") +@ConditionalOnProperty(prefix = "spring.ai.alibaba.functioncalling.larksuite", name = "enabled", havingValue = "true") public class LarkSuiteAutoConfiguration { @Bean diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteProperties.java index 521b99b26..723e841a6 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteProperties.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-larksuite/src/main/java/com/alibaba/cloud/ai/functioncalling/larksuite/LarkSuiteProperties.java @@ -21,7 +21,7 @@ * @author 北极星 */ -@ConfigurationProperties("spring.ai.alibaba.plugin.larksuite") +@ConfigurationProperties("spring.ai.alibaba.functioncalling.larksuite") public class LarkSuiteProperties { /** diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-microsofttranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/microsofttranslate/MicroSoftTranslateProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-microsofttranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/microsofttranslate/MicroSoftTranslateProperties.java index ecdf3e1cb..3f3d5a03b 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-microsofttranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/microsofttranslate/MicroSoftTranslateProperties.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-microsofttranslate/src/main/java/com/alibaba/cloud/ai/functioncalling/microsofttranslate/MicroSoftTranslateProperties.java @@ -20,7 +20,7 @@ /** * @author 31445 */ -@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.microsofttranslate") +@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.microsofttranslate") public class MicroSoftTranslateProperties { public static final String OCP_APIM_SUBSCRIPTION_KEY = "Ocp-Apim-Subscription-Key"; diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-serpapi/src/main/java/com/alibaba/cloud/ai/functioncalling/serpapi/SerpApiProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-serpapi/src/main/java/com/alibaba/cloud/ai/functioncalling/serpapi/SerpApiProperties.java index fb6a797e1..8fd0076a6 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-serpapi/src/main/java/com/alibaba/cloud/ai/functioncalling/serpapi/SerpApiProperties.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-serpapi/src/main/java/com/alibaba/cloud/ai/functioncalling/serpapi/SerpApiProperties.java @@ -20,7 +20,7 @@ /** * @author 北极星 */ -@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.serpapi") +@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.serpapi") public class SerpApiProperties { public static final String SERP_API_URL = "https://serpapi.com/search.json"; diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-sinanews/src/main/java/com/alibaba/cloud/ai/functioncalling/sinanews/SinaNewsAutoConfiguration.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-sinanews/src/main/java/com/alibaba/cloud/ai/functioncalling/sinanews/SinaNewsAutoConfiguration.java index 5bc1697fb..424186c98 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-sinanews/src/main/java/com/alibaba/cloud/ai/functioncalling/sinanews/SinaNewsAutoConfiguration.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-sinanews/src/main/java/com/alibaba/cloud/ai/functioncalling/sinanews/SinaNewsAutoConfiguration.java @@ -28,7 +28,7 @@ */ @Configuration @ConditionalOnClass(SinaNewsService.class) -@ConditionalOnProperty(prefix = "spring.ai.alibaba.plugin.sinanews", name = "enabled", havingValue = "true") +@ConditionalOnProperty(prefix = "spring.ai.alibaba.functioncalling.sinanews", name = "enabled", havingValue = "true") public class SinaNewsAutoConfiguration { @Bean diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-toutiaonews/src/main/java/com/alibaba/cloud/ai/functioncalling/toutiaonews/TiaotiaoNewsAutoConfiguration.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-toutiaonews/src/main/java/com/alibaba/cloud/ai/functioncalling/toutiaonews/TiaotiaoNewsAutoConfiguration.java index 03c0ee47c..dc157a9ec 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-toutiaonews/src/main/java/com/alibaba/cloud/ai/functioncalling/toutiaonews/TiaotiaoNewsAutoConfiguration.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-toutiaonews/src/main/java/com/alibaba/cloud/ai/functioncalling/toutiaonews/TiaotiaoNewsAutoConfiguration.java @@ -28,7 +28,7 @@ */ @Configuration @ConditionalOnClass(ToutiaoNewsService.class) -@ConditionalOnProperty(prefix = "spring.ai.alibaba.plugin.toutiaonews", name = "enabled", havingValue = "true") +@ConditionalOnProperty(prefix = "spring.ai.alibaba.functioncalling.toutiaonews", name = "enabled", havingValue = "true") public class TiaotiaoNewsAutoConfiguration { @Bean diff --git a/community/function-calling/spring-ai-alibaba-starter-function-calling-weather/src/main/java/com/alibaba/cloud/ai/functioncalling/weather/WeatherProperties.java b/community/function-calling/spring-ai-alibaba-starter-function-calling-weather/src/main/java/com/alibaba/cloud/ai/functioncalling/weather/WeatherProperties.java index 8185589bf..a671ef853 100644 --- a/community/function-calling/spring-ai-alibaba-starter-function-calling-weather/src/main/java/com/alibaba/cloud/ai/functioncalling/weather/WeatherProperties.java +++ b/community/function-calling/spring-ai-alibaba-starter-function-calling-weather/src/main/java/com/alibaba/cloud/ai/functioncalling/weather/WeatherProperties.java @@ -20,7 +20,7 @@ /** * @author 31445 */ -@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.weather") +@ConfigurationProperties(prefix = "spring.ai.alibaba.functioncalling.weather") public class WeatherProperties { private String apiKey; diff --git a/pom.xml b/pom.xml index 0b20dd467..312be6d0a 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ community/function-calling/spring-ai-alibaba-starter-function-calling-toutiaonews community/function-calling/spring-ai-alibaba-starter-function-calling-yuque community/function-calling/spring-ai-alibaba-starter-function-calling-googletranslate + community/function-calling/spring-ai-alibaba-starter-function-calling-alitranslate community/document-readers/github-document-reader community/document-readers/poi-document-reader @@ -67,7 +68,7 @@ - 1.0.0-M3.3 + 1.0.0-M5.1-SNAPSHOT UTF-8 UTF-8 @@ -78,7 +79,7 @@ 3.3.3 - 1.0.0-M3 + 1.0.0-M5 2.15.1 diff --git a/spring-ai-alibaba-autoconfigure/src/main/java/com/alibaba/cloud/ai/autoconfigure/dashscope/DashScopeAutoConfiguration.java b/spring-ai-alibaba-autoconfigure/src/main/java/com/alibaba/cloud/ai/autoconfigure/dashscope/DashScopeAutoConfiguration.java index ea76c2991..1fe244e61 100644 --- a/spring-ai-alibaba-autoconfigure/src/main/java/com/alibaba/cloud/ai/autoconfigure/dashscope/DashScopeAutoConfiguration.java +++ b/spring-ai-alibaba-autoconfigure/src/main/java/com/alibaba/cloud/ai/autoconfigure/dashscope/DashScopeAutoConfiguration.java @@ -30,8 +30,9 @@ import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.observation.ChatModelObservationConvention; import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; +import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackContext; +import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -98,7 +99,7 @@ public Transcription transcription() { public DashScopeChatModel dashscopeChatModel(DashScopeConnectionProperties commonProperties, DashScopeChatProperties chatProperties, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, List toolFunctionCallbacks, - FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate, + FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, ObjectProvider observationConvention) { @@ -109,7 +110,7 @@ public DashScopeChatModel dashscopeChatModel(DashScopeConnectionProperties commo var dashscopeApi = dashscopeChatApi(commonProperties, chatProperties, restClientBuilder, webClientBuilder, responseErrorHandler); - var dashscopeModel = new DashScopeChatModel(dashscopeApi, chatProperties.getOptions(), functionCallbackContext, + var dashscopeModel = new DashScopeChatModel(dashscopeApi, chatProperties.getOptions(), functionCallbackResolver, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); observationConvention.ifAvailable(dashscopeModel::setObservationConvention); @@ -280,9 +281,8 @@ public DashScopeAudioTranscriptionModel dashScopeAudioTranscriptionModel( @Bean @ConditionalOnMissingBean - public FunctionCallbackContext springAiFunctionManager(ApplicationContext context) { - - FunctionCallbackContext manager = new FunctionCallbackContext(); + public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { + DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); manager.setApplicationContext(context); return manager; } diff --git a/spring-ai-alibaba-core/pom.xml b/spring-ai-alibaba-core/pom.xml index d39852faf..6274f19d1 100644 --- a/spring-ai-alibaba-core/pom.xml +++ b/spring-ai-alibaba-core/pom.xml @@ -131,11 +131,11 @@ test - - io.micrometer - micrometer-observation-test - test - + + io.micrometer + micrometer-observation-test + test + - \ No newline at end of file + diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/DocumentRetrievalAdvisor.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/DocumentRetrievalAdvisor.java index cfc793a3b..bab863566 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/DocumentRetrievalAdvisor.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/DocumentRetrievalAdvisor.java @@ -19,8 +19,8 @@ import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.document.Document; -import org.springframework.ai.document.DocumentRetriever; -import org.springframework.ai.model.Content; +import org.springframework.ai.rag.Query; +import org.springframework.ai.rag.retrieval.search.DocumentRetriever; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -132,39 +132,39 @@ private AdvisedRequest before(AdvisedRequest request) { var context = new HashMap<>(request.adviseContext()); - List documents = retriever.retrieve(request.userText()); + List documents = retriever.retrieve(new Query(request.userText())); context.put(RETRIEVED_DOCUMENTS, documents); String documentContext = documents.stream() - .map(Content::getContent) + .map(Document::getText) .collect(Collectors.joining(System.lineSeparator())); Map advisedUserParams = new HashMap<>(request.userParams()); advisedUserParams.put("question_answer_context", documentContext); return AdvisedRequest.from(request) - .withUserText(request.userText() + System.lineSeparator() + this.userTextAdvise) - .withUserParams(advisedUserParams) - .withAdviseContext(context) + .userText(request.userText() + System.lineSeparator() + this.userTextAdvise) + .userParams(advisedUserParams) + .adviseContext(context) .build(); } private AdvisedResponse after(AdvisedResponse advisedResponse) { ChatResponseMetadata.Builder metadataBuilder = ChatResponseMetadata.builder(); - metadataBuilder.withKeyValue(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); + metadataBuilder.keyValue(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); ChatResponseMetadata metadata = advisedResponse.response().getMetadata(); if (metadata != null) { - metadataBuilder.withId(metadata.getId()); - metadataBuilder.withModel(metadata.getModel()); - metadataBuilder.withUsage(metadata.getUsage()); - metadataBuilder.withPromptMetadata(metadata.getPromptMetadata()); - metadataBuilder.withRateLimit(metadata.getRateLimit()); + metadataBuilder.id(metadata.getId()); + metadataBuilder.model(metadata.getModel()); + metadataBuilder.usage(metadata.getUsage()); + metadataBuilder.promptMetadata(metadata.getPromptMetadata()); + metadataBuilder.rateLimit(metadata.getRateLimit()); Set> entries = metadata.entrySet(); for (Map.Entry entry : entries) { - metadataBuilder.withKeyValue(entry.getKey(), entry.getValue()); + metadataBuilder.keyValue(entry.getKey(), entry.getValue()); } } diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/RetrievalRerankAdvisor.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/RetrievalRerankAdvisor.java index 3318a2361..f127cd683 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/RetrievalRerankAdvisor.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/advisor/RetrievalRerankAdvisor.java @@ -41,7 +41,6 @@ import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.document.Document; -import org.springframework.ai.model.Content; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.filter.Filter; @@ -106,11 +105,11 @@ public class RetrievalRerankAdvisor implements CallAroundAdvisor, StreamAroundAd public static final String RERANK_SCORE = "rerank_score"; public RetrievalRerankAdvisor(VectorStore vectorStore, RerankModel rerankModel) { - this(vectorStore, rerankModel, SearchRequest.defaults(), DEFAULT_USER_TEXT_ADVISE, DEFAULT_MIN_SCORE); + this(vectorStore, rerankModel, SearchRequest.builder().build(), DEFAULT_USER_TEXT_ADVISE, DEFAULT_MIN_SCORE); } public RetrievalRerankAdvisor(VectorStore vectorStore, RerankModel rerankModel, Double score) { - this(vectorStore, rerankModel, SearchRequest.defaults(), DEFAULT_USER_TEXT_ADVISE, score); + this(vectorStore, rerankModel, SearchRequest.builder().build(), DEFAULT_USER_TEXT_ADVISE, score); } public RetrievalRerankAdvisor(VectorStore vectorStore, RerankModel rerankModel, SearchRequest searchRequest) { @@ -224,8 +223,9 @@ private AdvisedRequest before(AdvisedRequest request) { String advisedUserText = request.userText() + System.lineSeparator() + this.userTextAdvise; var searchRequestToUse = SearchRequest.from(this.searchRequest) - .withQuery(request.userText()) - .withFilterExpression(doGetFilterExpression(context)); + .query(request.userText()) + .filterExpression(doGetFilterExpression(context)) + .build(); // 2. Search for similar documents in the vector store. logger.debug("searchRequestToUse: {}", searchRequestToUse); @@ -239,7 +239,7 @@ private AdvisedRequest before(AdvisedRequest request) { // 4. Create the context from the documents. String documentContext = documents.stream() - .map(Content::getContent) + .map(Document::getText) .collect(Collectors.joining(System.lineSeparator())); // 5. Advise the user parameters. @@ -247,9 +247,9 @@ private AdvisedRequest before(AdvisedRequest request) { advisedUserParams.put("question_answer_context", documentContext); return AdvisedRequest.from(request) - .withUserText(advisedUserText) - .withUserParams(advisedUserParams) - .withAdviseContext(context) + .userText(advisedUserText) + .userParams(advisedUserParams) + .adviseContext(context) .build(); } @@ -258,19 +258,19 @@ private AdvisedResponse after(AdvisedResponse advisedResponse) { // model, usage, etc. This will // be changed once new version of spring ai core is updated. ChatResponseMetadata.Builder metadataBuilder = ChatResponseMetadata.builder(); - metadataBuilder.withKeyValue(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); + metadataBuilder.keyValue(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); ChatResponseMetadata metadata = advisedResponse.response().getMetadata(); if (metadata != null) { - metadataBuilder.withId(metadata.getId()); - metadataBuilder.withModel(metadata.getModel()); - metadataBuilder.withUsage(metadata.getUsage()); - metadataBuilder.withPromptMetadata(metadata.getPromptMetadata()); - metadataBuilder.withRateLimit(metadata.getRateLimit()); + metadataBuilder.id(metadata.getId()); + metadataBuilder.model(metadata.getModel()); + metadataBuilder.usage(metadata.getUsage()); + metadataBuilder.promptMetadata(metadata.getPromptMetadata()); + metadataBuilder.rateLimit(metadata.getRateLimit()); Set> entries = metadata.entrySet(); for (Map.Entry entry : entries) { - metadataBuilder.withKeyValue(entry.getKey(), entry.getValue()); + metadataBuilder.keyValue(entry.getKey(), entry.getValue()); } } diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/agent/DashScopeAgent.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/agent/DashScopeAgent.java index 12fd06c7f..bc852556d 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/agent/DashScopeAgent.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/agent/DashScopeAgent.java @@ -133,7 +133,7 @@ private ChatResponse toChatResponse(DashScopeAgentResponse response) { metadata.put(OUTPUT, output); var assistantMessage = new AssistantMessage(text, metadata); - var generationMetadata = ChatGenerationMetadata.from(output.finishReason(), text); + var generationMetadata = ChatGenerationMetadata.builder().finishReason(output.finishReason()).build(); Generation generation = new Generation(assistantMessage, generationMetadata); return new ChatResponse(List.of(generation)); diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/api/DashScopeApi.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/api/DashScopeApi.java index 65dda7513..5a1c0dca1 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/api/DashScopeApi.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/api/DashScopeApi.java @@ -15,18 +15,6 @@ */ package com.alibaba.cloud.ai.dashscope.api; -import java.io.File; -import java.io.FileInputStream; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Predicate; -import java.util.stream.Collectors; - import com.alibaba.cloud.ai.dashscope.common.DashScopeException; import com.alibaba.cloud.ai.dashscope.common.ErrorCodeEnum; import com.alibaba.cloud.ai.dashscope.rag.DashScopeDocumentRetrieverOptions; @@ -35,9 +23,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.document.Document; import org.springframework.ai.model.ModelOptionsUtils; @@ -45,17 +30,23 @@ import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.InputStreamResource; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.io.File; +import java.io.FileInputStream; +import java.net.URI; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; +import java.util.stream.Collectors; import static com.alibaba.cloud.ai.dashscope.common.DashScopeApiConstants.DEFAULT_BASE_URL; @@ -483,7 +474,7 @@ private ResponseEntity uploadLease(UploadRequest request) { public ResponseEntity documentSplit(Document document, DashScopeDocumentTransformerOptions options) { - DocumentSplitRequest request = new DocumentSplitRequest(document.getContent(), options.getChunkSize(), + DocumentSplitRequest request = new DocumentSplitRequest(document.getText(), options.getChunkSize(), options.getOverlapSize(), options.getFileType(), options.getLanguage(), options.getSeparator()); return this.restClient.post() .uri("/api/v1/indices/component/configed_transformations/spliter") diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModel.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModel.java index 6a700c3ee..e614d2a98 100755 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModel.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModel.java @@ -15,28 +15,12 @@ */ package com.alibaba.cloud.ai.dashscope.chat; -import java.util.ArrayList; -import java.util.Base64; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletion; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionChunk; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionFinishReason; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionMessage; +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.*; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionMessage.ChatCompletionFunction; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionMessage.MediaContent; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionMessage.ToolCall; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionOutput; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionOutput.Choice; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionRequest; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionRequestInput; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.ChatCompletionRequestParameter; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi.FunctionTool; import com.alibaba.cloud.ai.dashscope.chat.observation.DashScopeChatModelObservationConvention; import com.alibaba.cloud.ai.dashscope.common.DashScopeApiConstants; import com.alibaba.cloud.ai.dashscope.metadata.DashScopeAiUsage; @@ -45,33 +29,32 @@ 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.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.MessageType; 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.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.model.*; import org.springframework.ai.chat.observation.ChatModelObservationContext; import org.springframework.ai.chat.observation.ChatModelObservationConvention; import org.springframework.ai.chat.observation.ChatModelObservationDocumentation; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.model.function.FunctionCallbackContext; +import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.retry.RetryUtils; import org.springframework.http.ResponseEntity; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; /** * {@link ChatModel} implementation for {@literal Alibaba DashScope} backed by @@ -122,14 +105,14 @@ public DashScopeChatModel(DashScopeApi dashscopeApi, DashScopeChatOptions option } public DashScopeChatModel(DashScopeApi dashscopeApi, DashScopeChatOptions options, - FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate) { - this(dashscopeApi, options, functionCallbackContext, retryTemplate, ObservationRegistry.NOOP); + FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate) { + this(dashscopeApi, options, functionCallbackResolver, retryTemplate, ObservationRegistry.NOOP); } public DashScopeChatModel(DashScopeApi dashscopeApi, DashScopeChatOptions options, - FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate, + FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate, ObservationRegistry observationRegistry) { - super(functionCallbackContext); + super(functionCallbackResolver); Assert.notNull(dashscopeApi, "DashScopeApi must not be null"); Assert.notNull(options, "Options must not be null"); Assert.notNull(retryTemplate, "RetryTemplate must not be null"); @@ -293,7 +276,7 @@ private static Generation buildGeneration(Choice choice, Map met var assistantMessage = new AssistantMessage(choice.message().content(), metadata, toolCalls); String finishReason = (choice.finishReason() != null ? choice.finishReason().name() : ""); - var generationMetadata = ChatGenerationMetadata.from(finishReason, null); + var generationMetadata = ChatGenerationMetadata.builder().finishReason(finishReason).build(); return new Generation(assistantMessage, generationMetadata); } @@ -310,9 +293,9 @@ private ChatCompletion chunkToChatCompletion(ChatCompletionChunk chunk) { private ChatResponseMetadata from(ChatCompletion result) { Assert.notNull(result, "DashScopeAi ChatCompletionResult must not be null"); return ChatResponseMetadata.builder() - .withId(result.requestId()) - .withUsage(DashScopeAiUsage.from(result.usage())) - .withModel("") + .id(result.requestId()) + .usage(DashScopeAiUsage.from(result.usage())) + .model("") .build(); } @@ -343,7 +326,7 @@ ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { List chatCompletionMessages = prompt.getInstructions().stream().map(message -> { if (message.getMessageType() == MessageType.USER || message.getMessageType() == MessageType.SYSTEM) { - Object content = message.getContent(); + Object content = message.getText(); if (message instanceof UserMessage userMessage) { if (!CollectionUtils.isEmpty(userMessage.getMedia())) { content = convertMediaContent(userMessage); diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVector.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVector.java index e85ab092f..053af6e51 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVector.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVector.java @@ -15,36 +15,22 @@ */ package com.alibaba.cloud.ai.dashscope.rag; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.aliyun.gpdb20160503.Client; -import com.aliyun.gpdb20160503.models.CreateCollectionRequest; -import com.aliyun.gpdb20160503.models.CreateNamespaceRequest; -import com.aliyun.gpdb20160503.models.DeleteCollectionDataRequest; -import com.aliyun.gpdb20160503.models.DeleteCollectionDataResponse; -import com.aliyun.gpdb20160503.models.DescribeCollectionRequest; -import com.aliyun.gpdb20160503.models.DescribeNamespaceRequest; -import com.aliyun.gpdb20160503.models.InitVectorDatabaseRequest; -import com.aliyun.gpdb20160503.models.InitVectorDatabaseResponse; -import com.aliyun.gpdb20160503.models.QueryCollectionDataRequest; -import com.aliyun.gpdb20160503.models.QueryCollectionDataResponse; -import com.aliyun.gpdb20160503.models.QueryCollectionDataResponseBody; -import com.aliyun.gpdb20160503.models.UpsertCollectionDataRequest; -import com.aliyun.teaopenapi.models.Config; +import com.aliyun.gpdb20160503.models.*; import com.aliyun.tea.TeaException; +import com.aliyun.teaopenapi.models.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.fastjson.JSON; import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * @author HeYQ @@ -60,12 +46,16 @@ public class AnalyticdbVector implements VectorStore { private Client client; - public AnalyticdbVector(String collectionName, AnalyticdbConfig config) throws Exception { + private final EmbeddingModel embeddingModel; + + public AnalyticdbVector(String collectionName, AnalyticdbConfig config, EmbeddingModel embeddingModel) + throws Exception { // collection_name must be updated every time this.collectionName = collectionName.toLowerCase(); this.config = config; Config clientConfig = Config.build(this.config.toAnalyticdbClientParams()); this.client = new Client(clientConfig); + this.embeddingModel = embeddingModel; initialize(); logger.debug("created AnalyticdbVector client success"); } @@ -160,15 +150,17 @@ private void createCollectionIfNotExists(Long embeddingDimension) throws Excepti public void add(List documents) { List rows = new ArrayList<>(10); for (Document doc : documents) { - float[] floatEmbeddings = doc.getEmbedding(); - List embedding = new ArrayList<>(floatEmbeddings.length); - for (float floatEmbedding : floatEmbeddings) { - embedding.add((double) floatEmbedding); - } + logger.info("Calling EmbeddingModel for document id = {}", doc.getId()); + float[] floatEmbeddings = this.embeddingModel.embed(doc); Map metadata = new HashMap<>(); metadata.put("refDocId", (String) doc.getMetadata().get("docId")); - metadata.put("content", doc.getContent()); + metadata.put("content", doc.getText()); metadata.put("metadata", JSON.toJSONString(doc.getMetadata())); + + List embedding = IntStream.range(0, floatEmbeddings.length) + .mapToObj(i -> (double) floatEmbeddings[i]) // 将每个 float 转为 Double + .toList(); + rows.add(new UpsertCollectionDataRequest.UpsertCollectionDataRequestRows().setVector(embedding) .setMetadata(metadata)); } @@ -214,7 +206,7 @@ public Optional delete(List ids) { @Override public List similaritySearch(String query) { - return similaritySearch(SearchRequest.query(query)); + return this.similaritySearch(SearchRequest.builder().query(query).build()); } @@ -233,7 +225,7 @@ public List similaritySearch(SearchRequest searchRequest) { .setIncludeValues(includeValues) .setMetrics(this.config.getMetrics()) .setVector(null) - .setContent(searchRequest.query) + .setContent(searchRequest.getQuery()) .setTopK((long) topK) .setFilter(null); try { diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeCloudStore.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeCloudStore.java index eabfdac8c..7ecb3b790 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeCloudStore.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeCloudStore.java @@ -15,21 +15,18 @@ */ package com.alibaba.cloud.ai.dashscope.rag; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.common.DashScopeException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.springframework.ai.document.Document; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + /** * @author nuocheng.lxm * @since 2024/8/6 15:42 @@ -94,7 +91,7 @@ public List similaritySearch(SearchRequest request) { searchOption = new DashScopeDocumentRetrieverOptions(); } searchOption.setRerankTopN(request.getTopK()); - return dashScopeApi.retriever(pipelineId, request.query, searchOption); + return dashScopeApi.retriever(pipelineId, request.getQuery(), searchOption); } } diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetrievalAdvisor.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetrievalAdvisor.java index cf0f36868..489001992 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetrievalAdvisor.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetrievalAdvisor.java @@ -20,7 +20,8 @@ import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.document.Document; -import org.springframework.ai.document.DocumentRetriever; +import org.springframework.ai.rag.Query; +import org.springframework.ai.rag.retrieval.search.DocumentRetriever; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -156,7 +157,7 @@ private AdvisedRequest before(AdvisedRequest request) { var context = new HashMap<>(request.adviseContext()); - List documents = retriever.retrieve(request.userText()); + List documents = retriever.retrieve(new Query(request.userText())); Map documentMap = new HashMap<>(); StringBuffer documentContext = new StringBuffer(); @@ -168,7 +169,7 @@ private AdvisedRequest before(AdvisedRequest request) { 【标题】%s 【正文】%s """, indexId, document.getMetadata().get("doc_name"), document.getMetadata().get("title"), - document.getContent()); + document.getText()); documentContext.append(docInfo); documentContext.append(System.lineSeparator()); @@ -183,9 +184,9 @@ private AdvisedRequest before(AdvisedRequest request) { advisedUserParams.put(RETRIEVED_DOCUMENTS, documentContext); return AdvisedRequest.from(request) - .withUserText(request.userText() + System.lineSeparator() + this.userTextAdvise) - .withUserParams(advisedUserParams) - .withAdviseContext(context) + .userText(request.userText() + System.lineSeparator() + this.userTextAdvise) + .userParams(advisedUserParams) + .adviseContext(context) .build(); } @@ -233,19 +234,19 @@ private AdvisedResponse after(AdvisedResponse advisedResponse) { // model, usage, etc. This will // be changed once new version of spring ai core is updated. ChatResponseMetadata.Builder metadataBuilder = ChatResponseMetadata.builder(); - metadataBuilder.withKeyValue(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); + metadataBuilder.keyValue(RETRIEVED_DOCUMENTS, advisedResponse.adviseContext().get(RETRIEVED_DOCUMENTS)); ChatResponseMetadata metadata = advisedResponse.response().getMetadata(); if (metadata != null) { - metadataBuilder.withId(metadata.getId()); - metadataBuilder.withModel(metadata.getModel()); - metadataBuilder.withUsage(metadata.getUsage()); - metadataBuilder.withPromptMetadata(metadata.getPromptMetadata()); - metadataBuilder.withRateLimit(metadata.getRateLimit()); + metadataBuilder.id(metadata.getId()); + metadataBuilder.model(metadata.getModel()); + metadataBuilder.usage(metadata.getUsage()); + metadataBuilder.promptMetadata(metadata.getPromptMetadata()); + metadataBuilder.rateLimit(metadata.getRateLimit()); Set> entries = metadata.entrySet(); for (Map.Entry entry : entries) { - metadataBuilder.withKeyValue(entry.getKey(), entry.getValue()); + metadataBuilder.keyValue(entry.getKey(), entry.getValue()); } } diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetriever.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetriever.java index 066d36ae1..8e5cf4259 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetriever.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/DashScopeDocumentRetriever.java @@ -15,15 +15,15 @@ */ package com.alibaba.cloud.ai.dashscope.rag; -import java.util.List; - import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.common.DashScopeException; - import org.springframework.ai.document.Document; -import org.springframework.ai.document.DocumentRetriever; +import org.springframework.ai.rag.Query; +import org.springframework.ai.rag.retrieval.search.DocumentRetriever; import org.springframework.util.Assert; +import java.util.List; + /** * @author nuocheng.lxm * @since 2024/8/5 14:42 @@ -42,12 +42,12 @@ public DashScopeDocumentRetriever(DashScopeApi dashScopeApi, DashScopeDocumentRe } @Override - public List retrieve(String query) { + public List retrieve(Query query) { String pipelineId = dashScopeApi.getPipelineIdByName(options.getIndexName()); if (pipelineId == null) { throw new DashScopeException("Index:" + options.getIndexName() + " NotExist"); } - List documentList = dashScopeApi.retriever(pipelineId, query, options); + List documentList = dashScopeApi.retriever(pipelineId, query.text(), options); return documentList; } diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/OpenSearchVector.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/OpenSearchVector.java index 67bb4bd96..414ba7ef0 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/OpenSearchVector.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rag/OpenSearchVector.java @@ -32,6 +32,8 @@ public class OpenSearchVector implements VectorStore { private static final String METADATA_FIELD_NAME = "metadata"; + private static final String EMPTY_TEXT = ""; + private final String tableName; private final String pKField; @@ -93,7 +95,7 @@ public void add(List documents) { // Insert document content information, key-value pairs matching. // The field_pk field must be consistent with the pkField configuration. documentFields.put(ID_FIELD_NAME, document.getId()); - documentFields.put(CONTENT_FIELD_NAME, document.getContent()); + documentFields.put(CONTENT_FIELD_NAME, document.getText()); // Convert metadata to JSON documentFields.put(METADATA_FIELD_NAME, JSON.toJSONString(document.getMetadata())); @@ -127,7 +129,7 @@ public Optional delete(List idList) { @Override public List similaritySearch(String query) { - return this.similaritySearch(SearchRequest.query(query)); + return this.similaritySearch(SearchRequest.builder().query(query).build()); } @Override @@ -434,12 +436,12 @@ private static String extractContent(JSONObject jsonDocument) { JSONObject fields = jsonDocument.getJSONObject(FIELDS_KEY); String content = fields.getString(CONTENT_FIELD_NAME); if (content == null || content.isEmpty()) { - return Document.EMPTY_TEXT; + return EMPTY_TEXT; } return content; } - return Document.EMPTY_TEXT; + return EMPTY_TEXT; } /** @@ -450,7 +452,7 @@ private static String extractContent(JSONObject jsonDocument) { private static String extractId(JSONObject jsonDocument) { String id = jsonDocument.getString(ID_FIELD_NAME); if (id == null || id.isEmpty()) { - return Document.EMPTY_TEXT; + return EMPTY_TEXT; } return id; } diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModel.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModel.java index 64c6c8a73..d9c102e1f 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModel.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModel.java @@ -32,14 +32,12 @@ package com.alibaba.cloud.ai.dashscope.rerank; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; -import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.metadata.DashScopeAiUsage; import com.alibaba.cloud.ai.document.DocumentWithScore; import com.alibaba.cloud.ai.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.document.Document; -import org.springframework.ai.embedding.EmbeddingOptions; import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.retry.RetryUtils; import org.springframework.http.ResponseEntity; @@ -47,13 +45,9 @@ import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; -import java.util.Collection; import java.util.Collections; import java.util.List; -import static java.util.Comparator.comparingInt; -import static java.util.stream.Collectors.toList; - /** * Title Dashscope rerank model.
* Description Dashscope rerank model.
@@ -126,7 +120,7 @@ public RerankResponse call(RerankRequest request) { } private DashScopeApi.RerankRequest createRequest(RerankRequest request, DashScopeRerankOptions requestOptions) { - List docs = request.getInstructions().stream().map(Document::getContent).toList(); + List docs = request.getInstructions().stream().map(Document::getText).toList(); DashScopeApi.RerankRequestParameter parameter = new DashScopeApi.RerankRequestParameter( requestOptions.getTopN(), requestOptions.getReturnDocuments()); diff --git a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/evaluation/LaajEvaluator.java b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/evaluation/LaajEvaluator.java index 25a0dc895..162854008 100644 --- a/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/evaluation/LaajEvaluator.java +++ b/spring-ai-alibaba-core/src/main/java/com/alibaba/cloud/ai/evaluation/LaajEvaluator.java @@ -17,9 +17,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.document.Document; import org.springframework.ai.evaluation.EvaluationRequest; import org.springframework.ai.evaluation.Evaluator; -import org.springframework.ai.model.Content; import java.util.List; import java.util.stream.Collectors; @@ -67,10 +67,10 @@ protected String doGetResponse(EvaluationRequest evaluationRequest) { } public String doGetSupportingData(EvaluationRequest evaluationRequest) { - List data = evaluationRequest.getDataList(); + List data = evaluationRequest.getDataList(); return data.stream() - .filter(node -> node != null && node.getContent() != null) - .map(Content::getContent) + .filter(node -> node != null && node.getText() != null) + .map(Document::getText) .collect(Collectors.joining(System.lineSeparator())); } diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/agent/Mcp.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/agent/Mcp.java deleted file mode 100644 index 5824889bb..000000000 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/agent/Mcp.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.alibaba.cloud.ai.agent; - -/** - * @author 北极星 - */ -public class Mcp { - - void testWithFC() { - - } - - void testWithFCToolContext() { - } - -} diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/autoconfig/dashscope/DashScopeAutoConfiguration.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/autoconfig/dashscope/DashScopeAutoConfiguration.java index 4fdc31bbb..c8f9f004c 100644 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/autoconfig/dashscope/DashScopeAutoConfiguration.java +++ b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/autoconfig/dashscope/DashScopeAutoConfiguration.java @@ -27,8 +27,9 @@ import com.alibaba.dashscope.audio.tts.SpeechSynthesizer; import org.jetbrains.annotations.NotNull; import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackContext; +import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -94,7 +95,7 @@ public Transcription transcription() { public DashScopeChatModel dashscopeChatModel(DashScopeConnectionProperties commonProperties, DashScopeChatProperties chatProperties, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, List toolFunctionCallbacks, - FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate, + FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler) { if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) { @@ -104,7 +105,7 @@ public DashScopeChatModel dashscopeChatModel(DashScopeConnectionProperties commo var dashscopeApi = dashscopeChatApi(commonProperties, chatProperties, restClientBuilder, webClientBuilder, responseErrorHandler); - return new DashScopeChatModel(dashscopeApi, chatProperties.getOptions(), functionCallbackContext, + return new DashScopeChatModel(dashscopeApi, chatProperties.getOptions(), functionCallbackResolver, retryTemplate); } @@ -265,9 +266,8 @@ public DashScopeAudioTranscriptionModel dashScopeAudioTranscriptionModel( @Bean @ConditionalOnMissingBean - public FunctionCallbackContext springAiFunctionManager(ApplicationContext context) { - - FunctionCallbackContext manager = new FunctionCallbackContext(); + public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { + DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); manager.setApplicationContext(context); return manager; } diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/DashscopeAiTestConfiguration.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/DashscopeAiTestConfiguration.java index ed91350d4..86feb992c 100755 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/DashscopeAiTestConfiguration.java +++ b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/DashscopeAiTestConfiguration.java @@ -34,7 +34,6 @@ import io.micrometer.observation.tck.TestObservationRegistry; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.model.function.FunctionCallbackContext; import org.springframework.ai.retry.RetryUtils; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModelIT.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModelIT.java index b30d46247..a36b647a8 100644 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModelIT.java +++ b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModelIT.java @@ -42,11 +42,11 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; 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.ai.parser.BeanOutputParser; -import org.springframework.ai.parser.ListOutputParser; -import org.springframework.ai.parser.MapOutputParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; @@ -111,9 +111,9 @@ void roleTest() { @Test void outputParser() { DefaultConversionService conversionService = new DefaultConversionService(); - ListOutputParser outputParser = new ListOutputParser(conversionService); + ListOutputConverter outputConverter = new ListOutputConverter(conversionService); - String format = outputParser.getFormat(); + String format = outputConverter.getFormat(); String template = """ List five {subject} {format} @@ -123,16 +123,16 @@ void outputParser() { Prompt prompt = new Prompt(promptTemplate.createMessage()); org.springframework.ai.chat.model.Generation generation = this.dashscopeChatModel.call(prompt).getResult(); - List list = outputParser.parse(generation.getOutput().getContent()); + List list = outputConverter.convert(generation.getOutput().getContent()); assertThat(list).hasSize(5); } @Test void mapOutputParser() { - MapOutputParser outputParser = new MapOutputParser(); + MapOutputConverter mapOutputConverter = new MapOutputConverter(); - String format = outputParser.getFormat(); + String format = mapOutputConverter.getFormat(); String template = """ Provide me a List of {subject} {format} @@ -143,7 +143,7 @@ void mapOutputParser() { org.springframework.ai.chat.model.Generation generation = dashscopeChatModel.call(prompt).getResult(); String generationText = generation.getOutput().getContent().replace("```json", "").replace("```", ""); - Map result = outputParser.parse(generationText); + Map result = mapOutputConverter.convert(generationText); assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); } @@ -151,7 +151,7 @@ void mapOutputParser() { @Test void beanStreamOutputParserRecords() { - BeanOutputParser outputParser = new BeanOutputParser<>(ActorsFilmsRecord.class); + BeanOutputConverter outputParser = new BeanOutputConverter<>(ActorsFilmsRecord.class); String format = outputParser.getFormat(); String template = """ @@ -172,7 +172,7 @@ void beanStreamOutputParserRecords() { .collect(Collectors.joining()); generationTextFromStream = generationTextFromStream.replace("```json", "").replace("```", ""); - ActorsFilmsRecord actorsFilms = outputParser.parse(generationTextFromStream); + ActorsFilmsRecord actorsFilms = outputParser.convert(generationTextFromStream); logger.info("" + actorsFilms); assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks"); assertThat(actorsFilms.movies()).hasSize(5); @@ -239,7 +239,7 @@ void functionCallTest() { @Test void usageInStream() { DefaultConversionService conversionService = new DefaultConversionService(); - ListOutputParser outputParser = new ListOutputParser(conversionService); + ListOutputConverter outputParser = new ListOutputConverter(conversionService); String format = outputParser.getFormat(); String template = """ diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/client/DashScopeChatClientIT.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/client/DashScopeChatClientIT.java index 74bb4bb50..e8bfed6bb 100644 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/client/DashScopeChatClientIT.java +++ b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/chat/client/DashScopeChatClientIT.java @@ -38,8 +38,8 @@ import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.document.Document; import org.springframework.ai.document.DocumentReader; -import org.springframework.ai.document.DocumentRetriever; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.rag.retrieval.search.DocumentRetriever; import org.springframework.ai.reader.JsonReader; import org.springframework.ai.vectorstore.SimpleVectorStore; import org.springframework.ai.vectorstore.VectorStore; @@ -388,7 +388,7 @@ void callRagWithRerank() { // Step 2 - Create embeddings and save to vector store logger.info("Creating Embeddings..."); - VectorStore vectorStore = new SimpleVectorStore(dashscopeEmbeddingModel); + VectorStore vectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel).build(); vectorStore.add(documents); // Step3 - Retrieve and llm generate diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVectorTest.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVectorTest.java index e112bdb60..39422c07b 100644 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVectorTest.java +++ b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rag/AnalyticdbVectorTest.java @@ -27,7 +27,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; @SpringBootTest @EnabledIfEnvironmentVariable(named = "ANALYTICDB_SECRET_KEY", matches = ".+") @@ -47,7 +46,9 @@ public void init() throws Exception { config.setNamespace("llama"); config.setNamespacePassword("llamapassword"); config.setEmbeddingDimension(3L); - analyticdbVector = new AnalyticdbVector("test_llama", config); + + // TODO 需要修改 + analyticdbVector = new AnalyticdbVector("test_llama", config, null); } @Test @@ -59,15 +60,15 @@ void testGetInstance() throws Exception { int length = 1536; // Array length float min = 0f; // smallest value float max = 1f; // the largest value - float[] em = new float[length]; // create float array - Random random = new Random(); - for (int i = 0; i < length; i++) { - em[i] = min + (max - min) * random.nextFloat(); - } - document.setEmbedding(em); + // float[] em = new float[length]; // create float array + // Random random = new Random(); + // for (int i = 0; i < length; i++) { + // em[i] = min + (max - min) * random.nextFloat(); + // } + // document.setEmbedding(em); list.add(document); analyticdbVector.add(list); - SearchRequest searchRequest = SearchRequest.query("hello"); + SearchRequest searchRequest = SearchRequest.builder().query("hello").build(); List documents = analyticdbVector.similaritySearch(searchRequest); System.out.println(documents.get(0).getContent()); diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModelTest.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModelTest.java index f5674e4ba..d0935edf6 100644 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModelTest.java +++ b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/dashscope/rerank/DashScopeRerankModelTest.java @@ -70,10 +70,10 @@ public class DashScopeRerankModelTest { void testRerank() { String query = "什么是文本排序模型"; List documents = new ArrayList<>(); - documents.add(Document.builder().withContent("文本排序模型广泛用于搜索引擎和推荐系统中,它们根据文本相关性对候选文本进行排序").build()); - documents.add(Document.builder().withContent("量子计算是计算科学的一个前沿领域").build()); - documents.add(Document.builder().withContent("预训练语言模型的发展给文本排序模型带来了新的进展").build()); - documents.add(Document.builder().withContent("文本排序模型能够帮助检索增强生成提升效果").build()); + documents.add(Document.builder().text("文本排序模型广泛用于搜索引擎和推荐系统中,它们根据文本相关性对候选文本进行排序").build()); + documents.add(Document.builder().text("量子计算是计算科学的一个前沿领域").build()); + documents.add(Document.builder().text("预训练语言模型的发展给文本排序模型带来了新的进展").build()); + documents.add(Document.builder().text("文本排序模型能够帮助检索增强生成提升效果").build()); RerankRequest request = new RerankRequest(query, documents); RerankResponse response = dashscopeRerankModel.call(request); diff --git a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/evaluation/EvaluationIT.java b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/evaluation/EvaluationIT.java index 33d825097..ea7b326c6 100644 --- a/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/evaluation/EvaluationIT.java +++ b/spring-ai-alibaba-core/src/test/java/com/alibaba/cloud/ai/evaluation/EvaluationIT.java @@ -30,10 +30,9 @@ import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.document.Document; -import org.springframework.ai.document.DocumentRetriever; import org.springframework.ai.evaluation.EvaluationRequest; import org.springframework.ai.evaluation.EvaluationResponse; -import org.springframework.ai.model.Content; +import org.springframework.ai.rag.retrieval.search.DocumentRetriever; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -45,7 +44,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** * Title React agent test cases.
@@ -97,7 +95,7 @@ void correctnessEvaluateTest() throws IOException { AnswerCorrectnessEvaluator evaluator = new AnswerCorrectnessEvaluator(ChatClient.builder(dashscopeChatModel), correctnessResource.getContentAsString(StandardCharsets.UTF_8)); EvaluationRequest evaluationRequest = new EvaluationRequest(userText, - List.of(new AssistantMessage(expectedResult)), content); + List.of(Document.builder().text(expectedResult).build()), content); EvaluationResponse evaluationResponse = evaluator.evaluate(evaluationRequest); Assertions.assertTrue(evaluationResponse.isPass()); @@ -123,7 +121,7 @@ void relevanceEvaluateTest() throws IOException { AnswerRelevancyEvaluator evaluator = new AnswerRelevancyEvaluator(ChatClient.builder(dashscopeChatModel), relevancyResource.getContentAsString(StandardCharsets.UTF_8), objectMapper); EvaluationRequest evaluationRequest = new EvaluationRequest(userText, - List.of(new AssistantMessage(expectedResult)), content); + List.of(Document.builder().text(expectedResult).build()), content); EvaluationResponse evaluationResponse = evaluator.evaluate(evaluationRequest); Assertions.assertTrue(evaluationResponse.isPass());