├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── main ├── java └── gemini │ └── workshop │ ├── ConversationExample.java │ ├── DocumentProcessingExample.java │ ├── FunctionCallingExample.java │ ├── GroundingWithWebsearchExample.java │ ├── GroundingWithWebsearchSpringAIExample.java │ ├── LocalTestingWithOllamaContainers.java.unused │ ├── MultimodalAudioExample.java │ ├── MultimodalEmbeddingExample.java │ ├── MultimodalImagesExample.java │ ├── MultimodalVideoExample.java │ ├── RAGExample.java │ ├── SentimentAnalysisExample.java │ ├── SimpleChatExample.java │ ├── SimpleChatStreamingExample.java │ ├── StructuredOutputExample.java │ ├── SummarizationExample.java │ ├── TextClassificationExample.java │ ├── TextEmbeddingExample.java │ └── WorkingWithTemplatesExample.java └── resources ├── Aesop-fables-Vol01.mp3 ├── Birds.mp4 ├── Coffee.png ├── The-Wasteland-TSEliot-public.txt ├── TheJungleBook.jpg ├── application.properties ├── attention-is-all-you-need.pdf ├── book-genres.json ├── prompts ├── initial-message.st ├── refine-message.st ├── subsummary-message.st ├── subsummary-overlap-message.st ├── summary-message.st ├── system-message.st └── system-summary-message.st └── the-jungle-book.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | replay_pid* 25 | 26 | target/** 27 | .idea/** 28 | 29 | 30 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gemini in Java with Vertex AI and Spring AI 2 | Gemini workshop for Java developers, using the Spring AI orchestration framework 3 | 4 | > [!NOTE] 5 | > This is the code for [Gemini in Java with Vertex AI and Spring AI]() 6 | > codelab geared towards Java developers to discover [Gemini](https://deepmind.google/technologies/gemini/) 7 | > and its open-source variant [Gemma](https://ai.google.dev/gemma) Large Language Model by Google using [Spring AI](https://docs.spring.io/spring-ai/reference/index.html) 8 | > framework. 9 | 10 | ## Prerequisites 11 | 12 | The code examples have been tested on the following environment: 13 | 14 | * Java 21 15 | * Maven >= 3.9.6 16 | 17 | In order to run these examples, you need to have a Google Cloud account and project ready. 18 | 19 | Before running the examples, you'll need to set up three environment variables: 20 | 21 | ```bash 22 | export VERTEX_AI_GEMINI_PROJECT_ID= 23 | export VERTEX_AI_GEMINI_LOCATION=, ex: us-central1 24 | export VERTEX_AI_GEMINI_MODEL=, ex: gemini-2.0-flash-exp 25 | 26 | # Note: you can test in another region or using the gemini-1.5-flash-001 model 27 | ``` 28 | 29 | > [!IMPORTANT] 30 | > Please update the project ID and location to match your project and select the model of your choice 31 | 32 | Create the Maven wrapper: 33 | 34 | ```bash 35 | mvn wrapper:wrapper 36 | ``` 37 | 38 | ## Codelab Samples 39 | The samples in this codelab are grouped by various capabilities and patterns. You will find, in order: 40 | * Chat 41 | * Simple Q&A with Gemini 42 | * Conversation with Gemini with chat history 43 | * Simple Q&A via streaming 44 | * Multimodality 45 | * Analyzing & extracting image data using Multimodality 46 | * Transcribing audio data using Multimodality 47 | * Transcribing video data using Multimodality 48 | * Capabilities 49 | * Structure prompts with prompt templates 50 | * Extracting structured data from unstructured text 51 | * Grounding responses with Web Search 52 | * Function Calling with Spring AI 53 | * Document utilities 54 | * Document Readers and Splitters 55 | * Embeddings 56 | * Generating Text Embeddings with Vertex AI 57 | * Generating Multimodal Embeddings with Vertex AI 58 | * AI use-cases and patterns 59 | * Retrieval-augmented generation(RAG) 60 | * Text classification with Few-shot prompting 61 | * Sentiment analysis with few-shot prompting 62 | * Summarization Patterns with Gemini: Stuffing, Map-Reduce Patterns 63 | * Local environments 64 | * Running Open-models with Ollama and Testcontainers 65 | 66 | ### Build 67 | > [!TIP] 68 | > Note the profiles `complete` used for the build 69 | 70 | Build the samples in a single JAR, then run them individually for the respective use-case: 71 | ```shell 72 | ./mvnw clean package -Pcomplete 73 | ``` 74 | ### Run 75 | > [!TIP] 76 | > List of samples, by use-case. Each sample can be run independently 77 | 78 | * Chat 79 | * [Simple Q&A with Gemini](src/main/java/gemini/workshop/SimpleChatExample.java) 80 | ```shell 81 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.SimpleChatExample 82 | ``` 83 | 84 | * [Conversation with Gemini with chat history](src/main/java/gemini/workshop/ConversationExample.java) 85 | ```shell 86 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.ConversationExample 87 | ``` 88 | 89 | * [Simple Q&A via streaming](src/main/java/gemini/workshop/SimpleChatStreamingExample.java) 90 | ```shell 91 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.SimpleChatStreamingExample 92 | ``` 93 | 94 | * Multimodality 95 | * [Analyzing & extracting image data using Multimodality](src/main/java/gemini/workshop/MultimodalImagesExample.java) 96 | ```shell 97 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.MultimodalImagesExample 98 | ``` 99 | 100 | * [Transcribing audio data using Multimodality](src/main/java/gemini/workshop/MultimodalAudioExample.java) 101 | ```shell 102 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.MultimodalAudioExample 103 | ``` 104 | 105 | * [Transcribing video data using Multimodality](src/main/java/gemini/workshop/MultimodalVideoExample.java) 106 | ```shell 107 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.MultimodalVideoExample 108 | ``` 109 | 110 | * Capabilities 111 | * [Structure prompts with prompt templates](src/main/java/gemini/workshop/WorkingWithTemplatesExample.java) 112 | ```shell 113 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.WorkingWithTemplatesExample 114 | ``` 115 | 116 | * [Extracting structured data from unstructured text](src/main/java/gemini/workshop/StructuredOutputExample.java) 117 | ```shell 118 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.StructuredOutputExample 119 | ``` 120 | * [Grounding responses with Web Search with Vertex SDK](src/main/java/gemini/workshop/GroundingWithWebsearchExample.java) 121 | ```shell 122 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.GroundingWithWebsearchExample 123 | ``` 124 | 125 | * [Grounding responses with Web Search with SpringAI](src/main/java/gemini/workshop/GroundingWithWebsearchSpringAIExample.java) 126 | ```shell 127 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.GroundingWithWebsearchSpringAIExample 128 | ``` 129 | 130 | * [Function Calling with Spring AI](src/main/java/gemini/workshop/FunctionCallingExample.java) 131 | ```shell 132 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.FunctionCallingExample 133 | ``` 134 | * Document utilities 135 | * [Document Readers and Splitters](src/main/java/gemini/workshop/DocumentProcessingExample.java) 136 | ```shell 137 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.DocumentProcessingExample 138 | ``` 139 | 140 | * Embeddings 141 | * [Generating Text Embeddings with Vertex AI](src/main/java/gemini/workshop/TextEmbeddingExample.java) 142 | ```shell 143 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.TextEmbeddingExample 144 | ``` 145 | 146 | * [Generating Multimodal Embeddings with Vertex AI](src/main/java/gemini/workshop/MultimodalEmbeddingExample.java) 147 | ```shell 148 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.MultimodalEmbeddingExample 149 | ``` 150 | 151 | * AI use-cases and patterns 152 | * [Retrieval-augmented generation(RAG)](src/main/java/gemini/workshop/RAGExample.java) 153 | ```shell 154 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.RAGExample 155 | ``` 156 | 157 | * [Text classification with Few-shot prompting](src/main/java/gemini/workshop/TextClassificationExample.java) 158 | ```shell 159 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.TextClassificationExample 160 | ``` 161 | 162 | * [Sentiment analysis with Few-shot prompting](src/main/java/gemini/workshop/SentimentAnalysisExample.java) 163 | ```shell 164 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.SentimentAnalysisExample 165 | ``` 166 | 167 | * [Summarization Patterns with Gemini: Stuffing, Map-Reduce Patterns](src/main/java/gemini/workshop/SummarizationExample.java) 168 | ```shell 169 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.SummarizationExample 170 | ``` 171 | * Local environments 172 | * [Running Open-models with Ollama and Testcontainers](src/main/java/gemini/workshop/LocalTestingWithOllamaContainers.java.unused) 173 | ```shell 174 | java -cp ./target/spring-ai-workshop-1.0.0-jar-with-dependencies.jar gemini.workshop.LocalTestingWithOllamaContainers 175 | ``` 176 | -------- 177 | This is not an official Google product. 178 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | gemini.workshop 6 | spring-ai-workshop 7 | 1.0.0 8 | spring-ai-workshop 9 | Spring AI Workshop materials 10 | 11 | 21 12 | 1.0.0 13 | 21 14 | 21 15 | UTF-8 16 | 2.17.2 17 | 1.20.4 18 | 19 | 20 | 21 | org.springframework.ai 22 | spring-ai-vertex-ai-gemini 23 | 24 | 25 | org.springframework.ai 26 | spring-ai-vertex-ai-embedding 27 | 28 | 29 | org.springframework.ai 30 | spring-ai-vector-store 31 | 32 | 33 | org.springframework.ai 34 | spring-ai-pdf-document-reader 35 | 36 | 37 | org.springframework.ai 38 | spring-ai-client-chat 39 | ${spring-ai.version} 40 | 41 | 42 | org.testcontainers 43 | testcontainers 44 | ${testcontainers.version} 45 | 46 | 47 | org.testcontainers 48 | ollama 49 | ${testcontainers.version} 50 | 51 | 52 | org.springframework.ai 53 | spring-ai-ollama 54 | true 55 | 56 | 57 | 58 | com.fasterxml.jackson.core 59 | jackson-core 60 | ${jackson.version} 61 | 62 | 63 | com.fasterxml.jackson.core 64 | jackson-databind 65 | ${jackson.version} 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.springframework.ai 73 | spring-ai-bom 74 | ${spring-ai.version} 75 | pom 76 | import 77 | 78 | 79 | com.google.cloud 80 | libraries-bom 81 | import 82 | pom 83 | 26.60.0 84 | 85 | 86 | org.testcontainers 87 | testcontainers-bom 88 | ${testcontainers.version} 89 | pom 90 | import 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | spring-milestones 100 | Spring Milestones 101 | https://repo.spring.io/milestone 102 | 103 | false 104 | 105 | 106 | 107 | spring-snapshots 108 | Spring Snapshots 109 | https://repo.spring.io/snapshot 110 | 111 | false 112 | 113 | 114 | 115 | 116 | 117 | spring-milestones 118 | Spring Milestones 119 | https://repo.spring.io/milestone 120 | 121 | false 122 | 123 | 124 | 125 | 126 | 127 | 128 | complete 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-assembly-plugin 134 | 3.7.0 135 | 136 | 137 | jar-with-dependencies 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | assemble-all 147 | package 148 | 149 | single 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/ConversationExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.util.List; 21 | import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; 22 | import org.springframework.ai.chat.memory.ChatMemory; 23 | import org.springframework.ai.chat.memory.MessageWindowChatMemory; 24 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 25 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 26 | import org.springframework.ai.chat.client.ChatClient; 27 | 28 | public class ConversationExample { 29 | 30 | public static void main(String[] args) { 31 | 32 | VertexAI vertexAI = new VertexAI.Builder() 33 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 34 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 35 | .setTransport(Transport.REST) 36 | .build(); 37 | 38 | var geminiChatModel = VertexAiGeminiChatModel.builder() 39 | .vertexAI(vertexAI) 40 | .defaultOptions(VertexAiGeminiChatOptions.builder() 41 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 42 | .temperature(0.2) 43 | .build()) 44 | .build(); 45 | 46 | long start = System.currentTimeMillis(); 47 | 48 | // use an InMemoryChat approach and inject past responses in the System Message 49 | // prompt in subsequent calls 50 | ChatMemory chatMemory = MessageWindowChatMemory.builder().build(); 51 | 52 | var chatClient = ChatClient.builder(geminiChatModel) 53 | .defaultSystem(""" 54 | You are a helpful AI assistant with extensive literature knowledge. 55 | You are an AI assistant that helps people find information. 56 | You should reply to the user's request in the style of a literary professor. 57 | If you don't know the answer, just say that you don't know, don't try to make up an answer. 58 | """) 59 | .defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) 60 | .build(); 61 | 62 | // iterate over a number of prompts and observe how 63 | // Gemini responses are added to the chat memory 64 | List.of( 65 | "Hello professor!", 66 | "Who is the main character in the The Jungle Book by Rudyard Kipling?", 67 | "what are his main character traits?", 68 | "Describe his relationships with Bagheera and Baloo", 69 | "Before I go, do you mind providing me the list of all characters in the book, friends or foes? ", 70 | "Thank you professor for all your insights!" 71 | ).forEach( message -> { 72 | System.out.println("\nUser: " + message); 73 | String response = chatClient 74 | .prompt() 75 | .user(message) 76 | .call() 77 | .content(); 78 | System.out.println("Gemini: " + response); 79 | }); 80 | System.out.println( 81 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/DocumentProcessingExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import java.util.List; 19 | import org.springframework.ai.document.Document; 20 | import org.springframework.ai.reader.ExtractedTextFormatter; 21 | import org.springframework.ai.reader.JsonReader; 22 | import org.springframework.ai.reader.TextReader; 23 | import org.springframework.ai.reader.pdf.PagePdfDocumentReader; 24 | import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig; 25 | import org.springframework.ai.transformer.splitter.TokenTextSplitter; 26 | import org.springframework.core.io.ClassPathResource; 27 | 28 | public class DocumentProcessingExample { 29 | public static void main(String[] args) { 30 | // read Text in txt format 31 | TextReader textReader = new TextReader("classpath:/the-jungle-book.txt"); 32 | String bookText = textReader.get().getFirst().getText(); 33 | System.out.printf("Read book %s with length %d, CharSet %s\nExcerpt: %s ...\n\n\n", 34 | textReader.getCustomMetadata().get(TextReader.SOURCE_METADATA), 35 | bookText.length(), 36 | textReader.getCustomMetadata().get(TextReader.CHARSET_METADATA), 37 | bookText.substring(0, 200)); 38 | 39 | // Read JSON documents 40 | //-------------------- 41 | // NOTE: it reads a LIST of JSON documents 42 | // To use this reader, provide a list of documents 43 | // in this example: [ {..} ] 44 | ClassPathResource jsonUri = new ClassPathResource("book-genres.json"); 45 | System.out.println("Reading JSON document: " + jsonUri.getFilename()); 46 | JsonReader jsonReader = new JsonReader(jsonUri); 47 | List documents = jsonReader.read(); 48 | for(Document document : documents) 49 | System.out.printf("Read JSON document %s with length %d\n", 50 | document.getText(), 51 | document.getText().length()); 52 | 53 | // Read PDF documents 54 | //-------------------- 55 | PagePdfDocumentReader pdfReader = new PagePdfDocumentReader("classpath:/attention-is-all-you-need.pdf", 56 | PdfDocumentReaderConfig.builder() 57 | .withPageTopMargin(0) 58 | .withPageExtractedTextFormatter(ExtractedTextFormatter.builder() 59 | .withNumberOfTopTextLinesToDelete(0) 60 | .build()) 61 | .withPagesPerDocument(1) 62 | .build()); 63 | List pdfDocument = pdfReader.read(); 64 | for(Document document : pdfDocument) 65 | System.out.printf("Read PDF document %s ... with length %d\n", 66 | document.getText().trim().substring(0, 50), 67 | document.getText().length()); 68 | 69 | 70 | //Test splitting into chunks 71 | //--------------------------- 72 | // override the default chunking values as per your use case: 73 | // The target size of each text chunk in tokens 74 | // private int defaultChunkSize = 800; 75 | // The minimum size of each text chunk in characters 76 | // private int minChunkSizeChars = 350; 77 | // Discard chunks shorter than this 78 | // private int minChunkLengthToEmbed = 5; 79 | // The maximum number of chunks to generate from a text 80 | // private int maxNumChunks = 10000; 81 | TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(5000, 100, 5, 100000, true); 82 | List chunks = tokenTextSplitter.apply(textReader.get()); 83 | 84 | System.out.println("Splitting document: " + textReader.getCustomMetadata().get(TextReader.SOURCE_METADATA)); 85 | System.out.println("Chunks size: " + chunks.size()); 86 | for(Document chunk : chunks) 87 | System.out.printf("Read text document %s ... with length %d\n", 88 | chunk.getText().substring(0, 25), 89 | chunk.getText().length()); 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/FunctionCallingExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.fasterxml.jackson.annotation.JsonClassDescription; 19 | import com.fasterxml.jackson.annotation.JsonInclude; 20 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 21 | import com.fasterxml.jackson.annotation.JsonProperty; 22 | import com.fasterxml.jackson.annotation.JsonPropertyDescription; 23 | import com.google.cloud.vertexai.Transport; 24 | import com.google.cloud.vertexai.VertexAI; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.function.Function; 28 | import org.springframework.ai.chat.messages.Message; 29 | import org.springframework.ai.chat.prompt.Prompt; 30 | import org.springframework.ai.chat.prompt.PromptTemplate; 31 | import org.springframework.ai.chat.prompt.SystemPromptTemplate; 32 | import org.springframework.ai.tool.function.FunctionToolCallback; 33 | import org.springframework.ai.util.json.schema.SchemaType; 34 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 35 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 36 | 37 | public class FunctionCallingExample { 38 | /** 39 | * BookStoreService is a function that checks the availability of a book in the bookstore. 40 | * Invoked by the LLM using the function calling feature. 41 | */ 42 | @JsonClassDescription("Get the book availability in the bookstore") 43 | public static class BookStoreService 44 | implements Function { 45 | 46 | @JsonInclude(Include.NON_NULL) 47 | @JsonClassDescription("BookStore API Request") 48 | public record Request( 49 | @JsonProperty(required = true, value = "title") @JsonPropertyDescription("The title of the book") String title, 50 | @JsonProperty(required = true, value = "author") @JsonPropertyDescription("The author of the book") String author) { 51 | } 52 | @JsonInclude(Include.NON_NULL) 53 | public record Response(String title, String author, String availability) { 54 | } 55 | 56 | @Override 57 | public Response apply(Request request) { 58 | System.out.printf("Function Call: Called getBookAvailability(%s, %s)\n", request.title(), request.author()); 59 | return new Response(request.title(), request.author(), "The book is available for purchase in the book store in paperback format."); 60 | } 61 | } 62 | 63 | public static void main(String[] args) { 64 | 65 | VertexAI vertexAI = new VertexAI.Builder() 66 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 67 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 68 | .setTransport(Transport.REST) 69 | .build(); 70 | 71 | // create system message template 72 | SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(""" 73 | You are a helpful AI assistant. 74 | You are an AI assistant that helps people get high quality literary information. 75 | Use function calling. 76 | Answer with precision. 77 | If the information was not fetched call the function again. Repeat at most 3 times. 78 | """ 79 | ); 80 | Message systemMessage = systemPromptTemplate.createMessage(); 81 | 82 | // create user message template 83 | PromptTemplate userPromptTemplate = PromptTemplate.builder().template(""" 84 | Write a nice note including book author, book title and availability. 85 | Find out if the book with the title {title} by author {author} is available in the bookstore. 86 | Please add also this book summary to the response, with the text available after the column, prefix it with My Book Summary: {summary}" 87 | """) 88 | .variables(Map.of("title", "The Jungle Book", 89 | "author", "Rudyard Kipling", 90 | "summary", "This is the Jungle Book summary")) 91 | .build(); 92 | Message userMessage = userPromptTemplate.createMessage(); 93 | 94 | // build a FunctionCallbackWrapper to regisater the BookStoreService 95 | // as a function 96 | FunctionToolCallback fnWrapper = FunctionToolCallback.builder("bookStoreAvailability", new BookStoreService()) 97 | .description("Get availability of a book in the bookstore") 98 | .inputType(BookStoreService.Request.class) 99 | .build(); 100 | 101 | var geminiChatModel = VertexAiGeminiChatModel.builder() 102 | .vertexAI(vertexAI) 103 | .defaultOptions(VertexAiGeminiChatOptions.builder() 104 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 105 | .temperature(0.2) 106 | .toolCallbacks(List.of(fnWrapper)) 107 | .build()) 108 | .build(); 109 | 110 | long start = System.currentTimeMillis(); 111 | System.out.println("GEMINI: " + geminiChatModel 112 | .call(new Prompt(List.of(userMessage, systemMessage))) 113 | .getResult().getOutput().getText()); 114 | System.out.println( 115 | "VertexAI Gemini call with FunctionCalling took " + (System.currentTimeMillis() - start) + " ms"); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/GroundingWithWebsearchExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import com.google.cloud.vertexai.api.GenerateContentResponse; 21 | import com.google.cloud.vertexai.api.GoogleSearchRetrieval; 22 | import com.google.cloud.vertexai.api.GroundingMetadata; 23 | import com.google.cloud.vertexai.api.Tool; 24 | import com.google.cloud.vertexai.generativeai.GenerativeModel; 25 | import com.google.cloud.vertexai.generativeai.ResponseHandler; 26 | import java.io.IOException; 27 | import java.util.Collections; 28 | import java.util.Optional; 29 | 30 | public class GroundingWithWebsearchExample { 31 | 32 | public static void main(String[] args) throws IOException { 33 | 34 | // Initialize the Vertex client that will be used to send requests. 35 | try (VertexAI vertexAI = new VertexAI.Builder() 36 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 37 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 38 | .setTransport(Transport.REST) 39 | .build()) { 40 | 41 | // Enable using the result from this tool in detecting grounding 42 | Tool googleSearchTool = 43 | Tool.newBuilder() 44 | .setGoogleSearchRetrieval(GoogleSearchRetrieval.newBuilder()) 45 | .build(); 46 | 47 | //--- create 2 models, one with grounding enabled and one without 48 | GenerativeModel nonGroundedModel = new GenerativeModel( 49 | System.getenv("VERTEX_AI_GEMINI_MODEL"), 50 | vertexAI); 51 | 52 | GenerativeModel groundedModel = new GenerativeModel( 53 | System.getenv("VERTEX_AI_GEMINI_MODEL"), 54 | vertexAI) 55 | .withTools(Collections.singletonList(googleSearchTool)); 56 | 57 | String prompt = "Which country won most medals at the Paris 2024 Olympics"; 58 | 59 | // call the 2 models 60 | // observe that the non-grounded call can't provide the requested info 61 | askModel(nonGroundedModel, "Non-grounded model search:", prompt); 62 | // grounded call can provide the requested info 63 | askModel(groundedModel, "Model grounded with web search:", prompt); 64 | } 65 | } 66 | 67 | // Call model with Google Websearch enabled|disabled 68 | private static void askModel(GenerativeModel model, String modelType, String prompt) throws IOException { 69 | long start = System.currentTimeMillis(); 70 | GenerateContentResponse response = model.generateContent(prompt); 71 | GroundingMetadata groundingMetadata = response.getCandidates(0).getGroundingMetadata(); 72 | 73 | String output = ResponseHandler.getText(response); 74 | System.out.println(modelType); 75 | System.out.println("Response: " + output.trim()); 76 | Optional.ofNullable(groundingMetadata.toString()) 77 | .filter(s -> !s.isEmpty()) 78 | .ifPresent(s -> System.out.println("Grounding Metadata: " + s)); 79 | System.out.println( 80 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/GroundingWithWebsearchSpringAIExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import org.springframework.ai.chat.model.ChatResponse; 21 | import org.springframework.ai.chat.prompt.Prompt; 22 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 23 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 24 | 25 | public class GroundingWithWebsearchSpringAIExample { 26 | 27 | public static void main(String[] args) { 28 | 29 | VertexAI vertexAI = new VertexAI.Builder() 30 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 31 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 32 | .setTransport(Transport.REST) 33 | .build(); 34 | 35 | // call the 2 models 36 | // observe that the non-grounded call can't provide the requested info 37 | askModel(vertexAI,"Non-grounded Gemini model", false); 38 | // grounded call can provide the requested info 39 | askModel(vertexAI,"Grounded Gemini model", true); 40 | } 41 | 42 | private static void askModel(VertexAI vertexAI, String modelType, boolean useWebSearch) { 43 | // enable or disable Web Search with Google in the ChatOptions 44 | var geminiChatModel = VertexAiGeminiChatModel.builder() 45 | .vertexAI(vertexAI) 46 | .defaultOptions(VertexAiGeminiChatOptions.builder() 47 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 48 | .temperature(0.2) 49 | .topK(5) 50 | .topP(0.95) 51 | .googleSearchRetrieval(useWebSearch) 52 | .build()) 53 | .build(); 54 | 55 | String prompt = "Which country won most medals at the Paris 2024 Olympics"; 56 | // Spring AI issue - fixed in upcoming release 57 | // Websearch flag must be set in a Prompt object creation 58 | // currently, setting it in the ChatOptions won't copy the flag in the prompt 59 | Prompt promptObject = new Prompt(prompt, VertexAiGeminiChatOptions.builder().googleSearchRetrieval(useWebSearch).build()); 60 | 61 | long start = System.currentTimeMillis(); 62 | System.out.println("Model type: " + modelType); 63 | ChatResponse chatResponse = geminiChatModel.call(promptObject); 64 | 65 | System.out.println("GEMINI: " + chatResponse.getResult().getOutput().getText()); 66 | System.out.println( 67 | "VertexAI " + modelType + " Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/LocalTestingWithOllamaContainers.java.unused: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.github.dockerjava.api.model.Image; 19 | import org.springframework.ai.chat.model.ChatResponse; 20 | import org.springframework.ai.chat.prompt.Prompt; 21 | import org.springframework.ai.ollama.OllamaChatModel; 22 | import org.springframework.ai.ollama.api.OllamaApi; 23 | import org.springframework.ai.ollama.api.OllamaOptions; 24 | import org.testcontainers.DockerClientFactory; 25 | import org.testcontainers.ollama.OllamaContainer; 26 | import org.testcontainers.utility.DockerImageName; 27 | 28 | import java.io.IOException; 29 | import java.util.List; 30 | 31 | // Important: 32 | // Validate that Docker is installed on your machine and running 33 | public class LocalTestingWithOllamaContainers { 34 | 35 | private static final String TC_OLLAMA_GEMMA_2_B = "tc-ollama-gemma2:2b"; 36 | public static final String OLLAMA_VERSION = "0.3.1"; 37 | public static final String MODEL = "gemma2:2b"; 38 | 39 | // Creating an Ollama container with Gemma2:2B if it doesn't exist locally 40 | private static OllamaContainer createGemmaOllamaContainer() throws IOException, InterruptedException { 41 | 42 | // Check if the custom Gemma Ollama image exists; create otherwise 43 | List listImagesCmd = DockerClientFactory.lazyClient() 44 | .listImagesCmd() 45 | .withImageNameFilter(TC_OLLAMA_GEMMA_2_B) 46 | .exec(); 47 | 48 | if (listImagesCmd.isEmpty()) { 49 | System.out.println("Creating a new Ollama container with Gemma2:2B image..."); 50 | OllamaContainer ollama = new OllamaContainer("ollama/ollama:" + OLLAMA_VERSION); 51 | ollama.start(); 52 | ollama.execInContainer("ollama", "pull", MODEL); 53 | ollama.commitToImage(TC_OLLAMA_GEMMA_2_B); 54 | return ollama; 55 | } else { 56 | System.out.println("Using existing Ollama container with Gemma2:2B image..."); 57 | 58 | // Substitute the default Ollama image with the Gemma variant of your choice 59 | return new OllamaContainer( 60 | DockerImageName.parse(TC_OLLAMA_GEMMA_2_B) 61 | .asCompatibleSubstituteFor("ollama/ollama")); 62 | } 63 | } 64 | 65 | public static void main(String[] args) throws IOException, InterruptedException { 66 | OllamaContainer ollama = createGemmaOllamaContainer(); 67 | ollama.start(); 68 | 69 | // URL of the running Ollama container 70 | String baseURL = String.format("http://%s:%d", ollama.getHost(), ollama.getFirstMappedPort()); 71 | 72 | // Create a new Ollama instance at the respective base URL 73 | var ollamaApi = new OllamaApi(baseURL); 74 | 75 | var chatModel = new OllamaChatModel(ollamaApi, 76 | OllamaOptions.create() 77 | .withModel(MODEL) 78 | .withTemperature(0.2)); 79 | 80 | long start = System.currentTimeMillis(); 81 | ChatResponse response = chatModel.call( 82 | new Prompt("Please provide 5 reasons why Gemma2 is a great model for use in local environments")); 83 | System.out.println("Response: " + response.getResult().getOutput().getContent()); 84 | System.out.print("Ollama call with Gemma2 took: " + (System.currentTimeMillis() - start) + " milliseconds"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/MultimodalAudioExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import com.google.cloud.vertexai.api.GenerateContentResponse; 21 | import com.google.cloud.vertexai.generativeai.ContentMaker; 22 | import com.google.cloud.vertexai.generativeai.GenerativeModel; 23 | import com.google.cloud.vertexai.api.GenerationConfig; 24 | import com.google.protobuf.ByteString; 25 | import java.io.IOException; 26 | import org.springframework.core.io.ClassPathResource; 27 | import com.google.cloud.vertexai.generativeai.PartMaker; 28 | import com.google.cloud.vertexai.generativeai.ResponseHandler; 29 | 30 | public class MultimodalAudioExample { 31 | 32 | public static void main(String[] args) throws IOException { 33 | 34 | // Initialize the Vertex client that will be used to send requests. 35 | try (VertexAI vertexAI = new VertexAI.Builder() 36 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 37 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 38 | .setTransport(Transport.REST) 39 | .build()) { 40 | 41 | // Read the MP3 file from the classpath 42 | ClassPathResource audioUri = new ClassPathResource("/Aesop-fables-Vol01.mp3"); 43 | 44 | // Read the MP3 file as a byte array 45 | byte[] audioBytes = audioUri.getInputStream().readAllBytes(); 46 | 47 | // Create a ByteString from the byte array 48 | ByteString audioByteString = ByteString.copyFrom(audioBytes); 49 | 50 | GenerativeModel model = new GenerativeModel( 51 | System.getenv("VERTEX_AI_GEMINI_MODEL"), 52 | vertexAI); 53 | 54 | // Create a GenerationConfig object and set the temperature to 0 for max accuracy 55 | GenerationConfig generationOptions = GenerationConfig.newBuilder() 56 | .setTemperature(0) 57 | .build(); 58 | // add the configuration to the model object 59 | model.withGenerationConfig(generationOptions); 60 | 61 | long start = System.currentTimeMillis(); 62 | GenerateContentResponse response = model.generateContent( 63 | ContentMaker.fromMultiModalData( 64 | "Please transcribe this audiobook with utmost accuracy", 65 | PartMaker.fromMimeTypeAndData("audio/mp3", audioByteString) 66 | )); 67 | 68 | String output = ResponseHandler.getText(response); 69 | System.out.println(output); 70 | System.out.println( 71 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/MultimodalEmbeddingExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.Map; 21 | import org.springframework.ai.content.Media; 22 | import org.springframework.ai.document.Document; 23 | import org.springframework.ai.embedding.DocumentEmbeddingRequest; 24 | import org.springframework.ai.embedding.EmbeddingOptionsBuilder; 25 | import org.springframework.ai.embedding.EmbeddingResponse; 26 | import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; 27 | import org.springframework.ai.vertexai.embedding.multimodal.VertexAiMultimodalEmbeddingModel; 28 | import org.springframework.ai.vertexai.embedding.multimodal.VertexAiMultimodalEmbeddingOptions; 29 | import org.springframework.core.io.ClassPathResource; 30 | import org.springframework.util.MimeType; 31 | import org.springframework.util.MimeTypeUtils; 32 | 33 | public class MultimodalEmbeddingExample { 34 | public static void main(String[] args) { 35 | VertexAiEmbeddingConnectionDetails connectionDetails = 36 | VertexAiEmbeddingConnectionDetails.builder() 37 | .projectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 38 | .location(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 39 | .build(); 40 | 41 | // default multimodal embedding model multimodalembedding@001 42 | VertexAiMultimodalEmbeddingOptions options = VertexAiMultimodalEmbeddingOptions.builder() 43 | .model("multimodalembedding") 44 | .build(); 45 | 46 | var embeddingModel = new VertexAiMultimodalEmbeddingModel(connectionDetails, options); 47 | 48 | Media imageMedia = new Media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/Coffee.png")); 49 | Media videoMedia = new Media(new MimeType("video", "mp4"), new ClassPathResource("/Birds.mp4")); 50 | 51 | var textDocument = Document.builder() 52 | .text("Explain what you see in this image and this video") 53 | .build(); 54 | var imageDocument = Document.builder() 55 | .media(imageMedia) 56 | .build(); 57 | var videoDocument = Document.builder() 58 | .media(videoMedia) 59 | .build(); 60 | 61 | // create a new Embedding Request 62 | DocumentEmbeddingRequest embeddingRequest = new DocumentEmbeddingRequest(List.of(textDocument, imageDocument, videoDocument), 63 | EmbeddingOptionsBuilder.builder().build()); 64 | 65 | // call the embedding model 66 | long start = System.currentTimeMillis(); 67 | EmbeddingResponse embeddingResponse = embeddingModel.call(embeddingRequest); 68 | System.out.println("Embedding response: " + Arrays.toString(embeddingResponse.getResult().getOutput())); 69 | System.out.println( 70 | "VertexAI call took " + (System.currentTimeMillis() - start) + " ms"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/MultimodalImagesExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.io.IOException; 21 | import java.util.List; 22 | import java.util.Map; 23 | import org.springframework.ai.chat.messages.Message; 24 | import org.springframework.ai.chat.messages.UserMessage; 25 | import org.springframework.ai.chat.prompt.Prompt; 26 | import org.springframework.ai.chat.prompt.SystemPromptTemplate; 27 | import org.springframework.ai.content.Media; 28 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 29 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 30 | import org.springframework.core.io.ClassPathResource; 31 | import org.springframework.util.MimeTypeUtils; 32 | 33 | public class MultimodalImagesExample { 34 | 35 | public static void main(String[] args) throws IOException { 36 | 37 | VertexAI vertexAI = new VertexAI.Builder() 38 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 39 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 40 | .setTransport(Transport.REST) 41 | .build(); 42 | 43 | var geminiChatModel = VertexAiGeminiChatModel.builder() 44 | .vertexAI(vertexAI) 45 | .defaultOptions(VertexAiGeminiChatOptions.builder() 46 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 47 | .temperature(0.2) 48 | .topK(5) 49 | .topP(0.95) 50 | .build()) 51 | .build(); 52 | 53 | // create system message template 54 | SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(""" 55 | You are a helpful AI assistant. 56 | You are an AI assistant that helps people get high quality information from media. 57 | Your name is {name} 58 | You should reply to the user's request with your name and also in the style of a {voice}. 59 | """ 60 | ); 61 | Message systemMessage = systemPromptTemplate.createMessage( 62 | Map.of("name", "Researcher Gemini", "voice", "multimedia expert")); 63 | 64 | // read image from classpath 65 | var imageData = new ClassPathResource("/TheJungleBook.jpg"); 66 | 67 | // create user message 68 | String userPrompt = """ 69 | Extract the title and author from the image, strictly in JSON format. 70 | Add a description of the image to the JSON response 71 | """; 72 | Message userMessage = UserMessage.builder() 73 | .text(userPrompt) 74 | .media(List.of(new Media(MimeTypeUtils.IMAGE_JPEG, imageData))) 75 | .build(); 76 | 77 | // send the image to Gemini for multimodal analysis 78 | long start = System.currentTimeMillis(); 79 | System.out.println("GEMINI: " + geminiChatModel 80 | .call(new Prompt(List.of(userMessage, systemMessage))) 81 | .getResult().getOutput().getText()); 82 | System.out.println( 83 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/MultimodalVideoExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import com.google.cloud.vertexai.api.GenerateContentResponse; 21 | import com.google.cloud.vertexai.api.GenerationConfig; 22 | import com.google.cloud.vertexai.generativeai.ContentMaker; 23 | import com.google.cloud.vertexai.generativeai.GenerativeModel; 24 | import com.google.cloud.vertexai.generativeai.PartMaker; 25 | import com.google.cloud.vertexai.generativeai.ResponseHandler; 26 | import com.google.protobuf.ByteString; 27 | import java.io.IOException; 28 | import org.springframework.core.io.ClassPathResource; 29 | 30 | public class MultimodalVideoExample { 31 | 32 | public static void main(String[] args) throws IOException { 33 | 34 | // Initialize the Vertex client that will be used to send requests. 35 | try (VertexAI vertexAI = new VertexAI.Builder() 36 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 37 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 38 | .setTransport(Transport.REST) 39 | .build()) { 40 | 41 | // // Read the MP3 file from the classpath 42 | ClassPathResource audioUri = new ClassPathResource("/Birds.mp4"); 43 | 44 | // Read the MP3 file as a byte array 45 | byte[] audioBytes = audioUri.getInputStream().readAllBytes(); 46 | 47 | // Create a ByteString from the byte array 48 | ByteString audioByteString = ByteString.copyFrom(audioBytes); 49 | 50 | GenerativeModel model = new GenerativeModel( 51 | System.getenv("VERTEX_AI_GEMINI_MODEL"), 52 | vertexAI); 53 | 54 | // Create a GenerationConfig object and set the temperature to 0 for max accuracy 55 | GenerationConfig generationOptions = GenerationConfig.newBuilder() 56 | .setTemperature(0) 57 | .build(); 58 | // add the configuration to the model object 59 | model.withGenerationConfig(generationOptions); 60 | 61 | long start = System.currentTimeMillis(); 62 | GenerateContentResponse response = model.generateContent( 63 | ContentMaker.fromMultiModalData(""" 64 | Provide a description of the video. 65 | The description should also contain anything important which people say in the video. 66 | """, 67 | PartMaker.fromMimeTypeAndData("video/mp4", audioByteString) 68 | )); 69 | 70 | String output = ResponseHandler.getText(response); 71 | System.out.println(output); 72 | System.out.println( 73 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/RAGExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.util.List; 21 | import java.util.Map; 22 | import org.springframework.ai.chat.messages.Message; 23 | import org.springframework.ai.chat.prompt.Prompt; 24 | import org.springframework.ai.chat.prompt.PromptTemplate; 25 | import org.springframework.ai.chat.prompt.SystemPromptTemplate; 26 | import org.springframework.ai.document.Document; 27 | import org.springframework.ai.embedding.EmbeddingModel; 28 | import org.springframework.ai.reader.TextReader; 29 | import org.springframework.ai.transformer.splitter.TokenTextSplitter; 30 | import org.springframework.ai.vectorstore.SearchRequest; 31 | import org.springframework.ai.vectorstore.SimpleVectorStore; 32 | import org.springframework.ai.vectorstore.VectorStore; 33 | import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; 34 | import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingModel; 35 | import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingOptions; 36 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 37 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 38 | import java.util.stream.Collectors; 39 | 40 | public class RAGExample { 41 | public static void main(String[] args) { 42 | VertexAI vertexAI = new VertexAI.Builder() 43 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 44 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 45 | .setTransport(Transport.REST) 46 | .build(); 47 | 48 | var geminiChatModel = VertexAiGeminiChatModel.builder() 49 | .vertexAI(vertexAI) 50 | .defaultOptions(VertexAiGeminiChatOptions.builder() 51 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 52 | .temperature(0.2) 53 | .topK(5) 54 | .topP(0.95) 55 | .build()) 56 | .build(); 57 | 58 | // read Text in txt format 59 | TextReader textReader = new TextReader("classpath:/the-jungle-book.txt"); 60 | String bookText = textReader.get().getFirst().getText(); 61 | System.out.printf("Read book %s with length %d, CharSet %s\nExcerpt: %s ...\n\n\n", 62 | textReader.getCustomMetadata().get(TextReader.SOURCE_METADATA), 63 | bookText.length(), 64 | textReader.getCustomMetadata().get(TextReader.CHARSET_METADATA), 65 | bookText.substring(0, 200)); 66 | 67 | //--------------------------- 68 | //Test splitting into chunks 69 | 70 | // override the default chunking values as per your use case: 71 | // The target size of each text chunk in tokens 72 | // private int defaultChunkSize = 800; 73 | // The minimum size of each text chunk in characters 74 | // private int minChunkSizeChars = 350; 75 | // Discard chunks shorter than this 76 | // private int minChunkLengthToEmbed = 5; 77 | // The maximum number of chunks to generate from a text 78 | // private int maxNumChunks = 10000; 79 | TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(5000, 100, 5, 100000, true); 80 | List chunks = tokenTextSplitter.apply(textReader.get()); 81 | 82 | System.out.println("Splitting document: " + textReader.getCustomMetadata().get(TextReader.SOURCE_METADATA)); 83 | System.out.println("Chunks size: " + chunks.size()); 84 | for(Document chunk : chunks) 85 | System.out.printf("Read text document %s ... with length %d\n", 86 | chunk.getText().substring(0, 25), 87 | chunk.getText().length()); 88 | 89 | VertexAiEmbeddingConnectionDetails connectionDetails = 90 | VertexAiEmbeddingConnectionDetails.builder() 91 | .projectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 92 | .location(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 93 | .build(); 94 | 95 | // Default embedding model: text-embedding-004 96 | VertexAiTextEmbeddingOptions options = VertexAiTextEmbeddingOptions.builder() 97 | .model(VertexAiTextEmbeddingOptions.DEFAULT_MODEL_NAME) 98 | .build(); 99 | 100 | EmbeddingModel embeddingModel = new VertexAiTextEmbeddingModel(connectionDetails, options); 101 | 102 | // create a simple (in memory) vector store, good for education purposes 103 | // for production usage, here's the available list of VectorStore implementations 104 | // https://docs.spring.io/spring-ai/reference/api/vectordbs.html 105 | VectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build(); 106 | vectorStore.add(chunks); 107 | 108 | 109 | // perform a similarity search in the Vector database 110 | String keywords = "friendship, adventure, coming of age"; 111 | String message = String.format("Find the paragraphs mentioning keywords in the following list: {%s} in the book.", 112 | keywords); 113 | 114 | List similarDocuments = vectorStore.similaritySearch( 115 | SearchRequest.builder().query(message).topK(5).build()); 116 | String content = similarDocuments.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator())); 117 | System.out.println("SearchRequest in vector store with the query string: " + message); 118 | System.out.println("Vector search has found " + similarDocuments.size() + " documents"); 119 | 120 | // create system message template 121 | SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(""" 122 | You are a helpful assistant, conversing with a user about the subjects contained in a set of documents. 123 | Use the information from the DOCUMENTS section to provide accurate answers. If unsure or if the answer 124 | isn't found in the DOCUMENTS section, simply state that you don't know the answer and do not mention 125 | the DOCUMENTS section. 126 | 127 | DOCUMENTS: 128 | {documents} 129 | """); 130 | Message systemMessage = systemPromptTemplate.createMessage( 131 | Map.of("documents", content)); 132 | 133 | PromptTemplate userPromptTemplate = new PromptTemplate(""" 134 | Provide an analysis of the book {book} by {author} 135 | with the skills of a literary critic. 136 | What factor do the following {keywords} play in the narrative of the book. 137 | """ 138 | ); 139 | Message userMessage = userPromptTemplate.createMessage(Map.of( 140 | "book", "The Jungle Book", 141 | "author", "Rudyard Kipling", 142 | "keywords", keywords)); 143 | 144 | // call Gemini including the findings of the vector store 145 | long start = System.currentTimeMillis(); 146 | System.out.println("GEMINI: " + geminiChatModel 147 | .call(new Prompt(List.of(userMessage, systemMessage))) 148 | .getResult().getOutput().getText()); 149 | System.out.println( 150 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/SentimentAnalysisExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.util.List; 21 | import org.springframework.ai.chat.client.ChatClient; 22 | import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; 23 | import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; 24 | import org.springframework.ai.chat.memory.ChatMemory; 25 | import org.springframework.ai.chat.memory.MessageWindowChatMemory; 26 | import org.springframework.ai.chat.messages.AssistantMessage; 27 | import org.springframework.ai.chat.messages.Message; 28 | import org.springframework.ai.chat.messages.UserMessage; 29 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 30 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 31 | 32 | public class SentimentAnalysisExample { 33 | 34 | public static void main(String[] args) { 35 | 36 | VertexAI vertexAI = new VertexAI.Builder() 37 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 38 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 39 | .setTransport(Transport.REST) 40 | .build(); 41 | 42 | var geminiChatModel = VertexAiGeminiChatModel.builder() 43 | .vertexAI(vertexAI) 44 | .defaultOptions(VertexAiGeminiChatOptions.builder() 45 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 46 | .temperature(0.2) 47 | .topK(5) 48 | .topP(0.95) 49 | .build()) 50 | .build(); 51 | 52 | // Build Few-shot history 53 | List messages = List.of( 54 | new UserMessage("Lord of the Rings by J.R.R. Tolkien"), 55 | new AssistantMessage("Fantasy"), 56 | new UserMessage("Dune by Frank Herbert"), 57 | new AssistantMessage("Science Fiction"), 58 | new UserMessage("Murder on the Orient Express by Agatha Christie"), 59 | new AssistantMessage("Mistery"), 60 | new UserMessage("Pride and Prejudice by Jane Austen"), 61 | new AssistantMessage("Romance"), 62 | new UserMessage("Dracula by Bram Stoker"), 63 | new AssistantMessage("Horror"), 64 | new UserMessage("Gone With the Wind by Margaret Mitchell"), 65 | new AssistantMessage("Historical Fiction"), 66 | new UserMessage("1984 by George Orwell"), 67 | new AssistantMessage("Dystopian"), 68 | new UserMessage("Catch-22 by Joseph Heller"), 69 | new AssistantMessage("Comedy"), 70 | new UserMessage("The Autobiography of Benjamin Franklin"), 71 | new AssistantMessage("Biography/Autobiography"), 72 | new UserMessage("Sapiens: A Brief History of Humankind by Yuval Noah Harari"), 73 | new AssistantMessage("History"), 74 | new UserMessage("A Brief History of Time by Stephen Hawking"), 75 | new AssistantMessage("Science"), 76 | new UserMessage("How to Win Friends and Influence People by Dale Carnegie"), 77 | new AssistantMessage("Self-Help"), 78 | new UserMessage("The Wealth of Nations by Adam Smith\n"), 79 | new AssistantMessage("Business/Economics"), 80 | new UserMessage("Joy of Cooking"), 81 | new AssistantMessage("Cookbook"), 82 | new UserMessage("Eat, Pray, Love by Elizabeth Gilbert"), 83 | new AssistantMessage("Travel") 84 | ); 85 | 86 | String systemMessage = 87 | """ 88 | You are a helpful AI assistant. 89 | You are an AI assistant that helps people classify the provided text into the following categories 90 | 91 | Fantasy: Includes magic, mythical creatures, and alternate universes. 92 | Science Fiction: Speculative fiction dealing with advanced technology or future settings. 93 | Mystery: Stories involving a crime or puzzle to be solved. 94 | Romance: Focuses on romantic relationships. 95 | Horror: Elicits fear or suspense. 96 | Historical Fiction: Set in a historical period. 97 | Dystopian: Depicts a future society that is often oppressive or undesirable. 98 | Comedy: Intended to be humorous. 99 | 100 | Biography/Autobiography: Accounts of a person's life. 101 | History: Records of past events. 102 | Science: Deals with the natural world and its phenomena. 103 | Self-Help: Offers advice or guidance on personal development. 104 | Business/Economics: Related to business, finance, and economics. 105 | Cookbook: Contains recipes for preparing food. 106 | Travel: Describes places and cultures. 107 | """; 108 | 109 | // create the memory for the few-shot history 110 | ChatMemory chatMemory = MessageWindowChatMemory.builder().build(); 111 | chatMemory.add("examples", messages); 112 | 113 | // use the fluent ChatClient interface and provision chat history 114 | // and finer grained control of building the request 115 | long start = System.currentTimeMillis(); 116 | String response = 117 | ChatClient 118 | .builder(geminiChatModel) 119 | .build() 120 | .prompt() 121 | .system(systemMessage) 122 | .advisors(new SimpleLoggerAdvisor(), MessageChatMemoryAdvisor.builder(chatMemory).build()) 123 | .user(""" 124 | In which category does the jungle book by Rudyard Kipling fit in best? 125 | What is the name of the main character? 126 | Is the main character portrayed in one of the following ways: positive, neutral, ambiguous or negative? 127 | Recommend other books with similar characters. 128 | Return book category, main character name, main character sentiment and book recommendations strictly in JSON format""") 129 | .call() 130 | .content(); 131 | System.out.println("GEMINI: " + response); 132 | System.out.println( 133 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 134 | } 135 | } 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/SimpleChatExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import org.springframework.ai.chat.prompt.Prompt; 21 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 22 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 23 | 24 | public class SimpleChatExample { 25 | 26 | public static void main(String[] args) { 27 | 28 | VertexAI vertexAI = new VertexAI.Builder() 29 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 30 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 31 | .setTransport(Transport.REST) 32 | .build(); 33 | 34 | var geminiChatModel = VertexAiGeminiChatModel.builder() 35 | .vertexAI(vertexAI) 36 | .defaultOptions(VertexAiGeminiChatOptions.builder() 37 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 38 | .temperature(0.2) 39 | .topK(5) 40 | .topP(0.95) 41 | .build()) 42 | .build(); 43 | 44 | String prompt = "Recommend a great book to read during my vacation"; 45 | 46 | // call Gemini in VertexAI 47 | long start = System.currentTimeMillis(); 48 | System.out.println("GEMINI: " + geminiChatModel 49 | .call(new Prompt(prompt)) 50 | .getResult().getOutput().getText()); 51 | System.out.println( 52 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/SimpleChatStreamingExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 21 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 22 | import reactor.core.publisher.Flux; 23 | 24 | public class SimpleChatStreamingExample { 25 | 26 | public static void main(String[] args) { 27 | 28 | VertexAI vertexAI = new VertexAI.Builder() 29 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 30 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 31 | .setTransport(Transport.REST) 32 | .build(); 33 | 34 | var geminiChatModel = VertexAiGeminiChatModel.builder() 35 | .vertexAI(vertexAI) 36 | .defaultOptions(VertexAiGeminiChatOptions.builder() 37 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 38 | .temperature(0.2) 39 | .build()) 40 | .build(); 41 | 42 | String prompt = "Recommend five great fiction books to read during my vacation, while travelling around Europe"; 43 | 44 | // stream responses and print as they are received 45 | long start = System.currentTimeMillis(); 46 | Flux responseStream = geminiChatModel.stream(prompt); 47 | responseStream 48 | .doOnNext(content -> System.out.println("Gemini response chunk: " + content.trim())) 49 | .blockLast(); 50 | System.out.println( 51 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/StructuredOutputExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | import org.springframework.ai.chat.model.Generation; 24 | import org.springframework.ai.chat.prompt.Prompt; 25 | import org.springframework.ai.chat.prompt.PromptTemplate; 26 | import org.springframework.ai.converter.BeanOutputConverter; 27 | import org.springframework.ai.converter.ListOutputConverter; 28 | import org.springframework.ai.converter.MapOutputConverter; 29 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 30 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 31 | import org.springframework.core.convert.support.DefaultConversionService; 32 | 33 | public class StructuredOutputExample { 34 | 35 | public static void mapOutputConverter(VertexAiGeminiChatModel chatClient) { 36 | MapOutputConverter mapOutputConverter = new MapOutputConverter(); 37 | 38 | String format = mapOutputConverter.getFormat(); 39 | String template = """ 40 | You are a helpful AI assistant. 41 | You are an AI assistant that helps people find information. 42 | You should reply to the user's request in the style of a literary professor. 43 | Provide me a quote from a random book, including only {subject} 44 | {format} 45 | """; 46 | PromptTemplate promptTemplate = PromptTemplate.builder() 47 | .template(template) 48 | .variables(Map.of("subject", "book, author and quote", "format", format)) 49 | .build(); 50 | Prompt prompt = new Prompt(promptTemplate.createMessage()); 51 | 52 | long start = System.currentTimeMillis(); 53 | Generation generation = chatClient.call(prompt).getResult(); 54 | 55 | Map result = mapOutputConverter.convert(generation.getOutput().getText()); 56 | 57 | System.out.println("Prompt for MapConverter test:" + prompt.getContents()); 58 | System.out.println("Format this response to a map: " + generation.getOutput().getText()); 59 | System.out.println("Formatted response: " + result); 60 | System.out.println( 61 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 62 | } 63 | 64 | public static void listOutputConverter(VertexAiGeminiChatModel chatClient) { 65 | ListOutputConverter listOutputConverter = new ListOutputConverter(new DefaultConversionService()); 66 | 67 | String format = listOutputConverter.getFormat(); 68 | String template = """ 69 | You are a helpful AI assistant. 70 | You are an AI assistant that helps people find information. 71 | You should reply to the user's request in the style of a literary professor. 72 | List the top ten {subject} 73 | {format} 74 | """; 75 | PromptTemplate promptTemplate = PromptTemplate.builder() 76 | .template(template) 77 | .variables(Map.of("subject", "important fiction books I should read in my lifetime, with book and author", "format", format)) 78 | .build(); 79 | Prompt prompt = new Prompt(promptTemplate.createMessage()); 80 | 81 | long start = System.currentTimeMillis(); 82 | Generation generation = chatClient.call(prompt).getResult(); 83 | 84 | List list = listOutputConverter.convert(generation.getOutput().getText()); 85 | 86 | System.out.println("Prompt for ListConverter test:" + prompt.getContents()); 87 | System.out.println("Format this response to a List: " + generation.getOutput().getText()); 88 | System.out.println("Formatted response: " + list); 89 | System.out.println( 90 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 91 | } 92 | 93 | public static void beanOutputConverter(VertexAiGeminiChatModel chatClient) { 94 | 95 | record BooksAuthor(String writer, List books) {} 96 | 97 | BeanOutputConverter beanOutputConverter = new BeanOutputConverter<>(BooksAuthor.class); 98 | 99 | String format = beanOutputConverter.getFormat(); 100 | String writer = "Gabriel Garcia Marquez"; 101 | 102 | String template = """ 103 | Generate the bibliography of books written by the writer {writer}. 104 | {format} 105 | """; 106 | 107 | Prompt prompt = new Prompt(PromptTemplate.builder().template(template) 108 | .variables(Map.of("writer", writer, "format", format)) 109 | .build() 110 | .createMessage()); 111 | 112 | long start = System.currentTimeMillis(); 113 | Generation generation = chatClient.call(prompt) 114 | .getResult(); 115 | 116 | System.out.println("Prompt for BeanConverter test:" + prompt.getContents()); 117 | String content = generation.getOutput().getText(); 118 | System.out.println("Format this response to a Bean: " + content); 119 | 120 | BooksAuthor writerBooks = beanOutputConverter.convert(content); 121 | 122 | System.out.println("Formatted response: " + writerBooks); 123 | System.out.println( 124 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 125 | } 126 | 127 | public static void main(String[] args) { 128 | VertexAI vertexAI = new VertexAI.Builder().setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 129 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 130 | .setTransport(Transport.REST) 131 | .build(); 132 | 133 | var geminiChatModel = VertexAiGeminiChatModel.builder() 134 | .vertexAI(vertexAI) 135 | .defaultOptions(VertexAiGeminiChatOptions.builder() 136 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 137 | .temperature(0.2) 138 | .topK(5) 139 | .topP(0.95) 140 | .build()) 141 | .build(); 142 | 143 | // convert response to a map 144 | mapOutputConverter(geminiChatModel); 145 | 146 | // convert response to a list 147 | listOutputConverter(geminiChatModel); 148 | 149 | // convert response to a bean 150 | beanOutputConverter(geminiChatModel); 151 | 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/SummarizationExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Map.Entry; 26 | import java.util.TreeMap; 27 | import java.util.concurrent.CompletableFuture; 28 | import java.util.concurrent.ExecutionException; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Executors; 31 | import org.springframework.ai.chat.messages.Message; 32 | import org.springframework.ai.chat.model.ChatResponse; 33 | import org.springframework.ai.chat.prompt.Prompt; 34 | import org.springframework.ai.chat.prompt.PromptTemplate; 35 | import org.springframework.ai.chat.prompt.SystemPromptTemplate; 36 | import org.springframework.ai.reader.TextReader; 37 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 38 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 39 | 40 | public class SummarizationExample { 41 | private static final int CHUNK_SIZE = 10000; // Number of words in each window 42 | private static final int OVERLAP_SIZE = 2000; 43 | 44 | public static void main(String[] args) { 45 | 46 | VertexAI vertexAI = new VertexAI.Builder() 47 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 48 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 49 | .setTransport(Transport.REST) 50 | .build(); 51 | 52 | var geminiChatModel = VertexAiGeminiChatModel.builder() 53 | .vertexAI(vertexAI) 54 | .defaultOptions(VertexAiGeminiChatOptions.builder() 55 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 56 | .temperature(0.2) 57 | .build()) 58 | .build(); 59 | 60 | try{ 61 | // summarization using the Stuffing pattern 62 | summarizationStuffing(geminiChatModel); 63 | 64 | // summarization using the MapReduce pattern 65 | summarizationMapReduce(geminiChatModel); 66 | }catch(IOException | ExecutionException | InterruptedException e){ 67 | System.out.println("Exception encountered while summarizing a document: " + e.getMessage()); 68 | } 69 | } 70 | 71 | private static void summarizationStuffing(VertexAiGeminiChatModel geminiChatModel) throws IOException { 72 | // read book 73 | TextReader textReader = new TextReader("classpath:/The-Wasteland-TSEliot-public.txt"); 74 | String bookText = textReader.get().getFirst().getText(); 75 | 76 | // create system message template 77 | SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(""" 78 | You are a helpful AI assistant. 79 | You are an AI assistant that helps people summarize information. 80 | Your name is {name} 81 | You should reply to the user's request with your name and also in the style of a {voice}. 82 | Strictly ignore Project Gutenberg & ignore copyright notice in summary output. 83 | """ 84 | ); 85 | Message systemMessage = systemPromptTemplate.createMessage( 86 | Map.of("name", "Gemini", "voice", "literary critic")); 87 | String title = "", author = ""; 88 | 89 | // create user message template 90 | PromptTemplate userPromptTemplate = PromptTemplate.builder().template(""" 91 | Please provide a concise summary covering the key points of the book {title} by {author}. 92 | If you do not have the information, use Google web search to ground your answer. 93 | Do not make information up 94 | """) 95 | .variables(Map.of("title", title, "author", author)) 96 | .build(); 97 | Message userMessage = userPromptTemplate.createMessage(); 98 | 99 | // summarize document by stuffing the prompt with the content of the document 100 | long start = System.currentTimeMillis(); 101 | ChatResponse response = geminiChatModel.call(new Prompt(List.of(userMessage, systemMessage), 102 | VertexAiGeminiChatOptions.builder() 103 | .temperature(0.2) 104 | .build())); 105 | 106 | System.out.println("Gemini response - Stuffing Pattern: \n" + response.getResult().getOutput().getText()); 107 | System.out.print("Summarization (stuffing test) took " + (System.currentTimeMillis() - start) + " milliseconds"); 108 | } 109 | 110 | private static void summarizationMapReduce(VertexAiGeminiChatModel geminiChatModel) 111 | throws ExecutionException, InterruptedException { 112 | // read book 113 | TextReader textReader = new TextReader("classpath:/The-Wasteland-TSEliot-public.txt"); 114 | String bookText = textReader.get().getFirst().getText(); 115 | 116 | // create system message template 117 | SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(""" 118 | You are a helpful AI assistant. 119 | You are an AI assistant that helps people summarize information in a concise way. 120 | Strictly ignore Project Gutenberg & ignore copyright notice in summary output. 121 | """ 122 | ); 123 | Message systemMessage = systemPromptTemplate.createMessage(); 124 | 125 | long startTime = System.currentTimeMillis(); 126 | 127 | int length = bookText.length(); 128 | List>> futures = new ArrayList<>(); 129 | ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); 130 | Map resultMap = new TreeMap<>(); // TreeMap to automatically sort by key 131 | 132 | //------ 133 | // Note: test with different values for the CHUNG and OVERLAP_SIZE 134 | //---- 135 | for (int i = 0; i < length; i += (CHUNK_SIZE - OVERLAP_SIZE)) { 136 | final int index = i / (CHUNK_SIZE - OVERLAP_SIZE); // Calculate chunk index 137 | int end = Math.min(i + CHUNK_SIZE, length); 138 | String chunk = bookText.substring(i, end); 139 | 140 | CompletableFuture> future = CompletableFuture.supplyAsync(() -> processChunk(index, 141 | chunk, systemMessage, geminiChatModel), executor); 142 | futures.add(future); 143 | } 144 | 145 | // Wait for all futures to complete and collect the results in resultMap 146 | CompletableFuture allDone = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) 147 | .thenAccept(v -> futures.forEach(f -> f.thenAccept(resultMap::putAll))); 148 | 149 | allDone.get(); // Wait for all processing to complete 150 | 151 | // Build the final context string from the sorted entries in the resultMap 152 | StringBuilder contextBuilder = new StringBuilder(); 153 | for (Entry entry : resultMap.entrySet()) { 154 | // System.out.println("Index " + entry.getKey() + ": " + entry.getValue()); 155 | contextBuilder.append(entry.getValue()).append("\n"); 156 | } 157 | 158 | String context = contextBuilder.toString(); 159 | String output = processSummary(context, systemMessage, geminiChatModel); 160 | System.out.println(output); 161 | System.out.print("Summarization (map-reduce) took " + (System.currentTimeMillis() - startTime) + " milliseconds"); 162 | 163 | executor.shutdown(); // Shutdown the executor 164 | } 165 | 166 | 167 | //--- Helper methods --- 168 | private static String processSummary(String context, Message systemMessage, VertexAiGeminiChatModel geminiChatModel) { 169 | long start = System.currentTimeMillis(); 170 | System.out.println(context+"\n\n"); 171 | 172 | // create user message template 173 | PromptTemplate userPromptTemplate = PromptTemplate.builder().template(""" 174 | Strictly please give me a summary with an introduction, three one sentence bullet points, and a conclusion from the following text delimited by triple backquotes. 175 | 176 | ```Text:{content}``` 177 | 178 | Output starts with SUMMARY: 179 | """).variables(Map.of("content", context)) 180 | .build(); 181 | Message userMessage = userPromptTemplate.createMessage(); 182 | 183 | ChatResponse response = geminiChatModel.call(new Prompt(List.of(userMessage, systemMessage), 184 | VertexAiGeminiChatOptions.builder() 185 | .temperature(0.2) 186 | .build())); 187 | System.out.println("Summarization (final summary) took " + (System.currentTimeMillis() - start) + " milliseconds"); 188 | return response.getResult().getOutput().getText(); 189 | } 190 | 191 | private static Map processChunk( 192 | Integer index, 193 | String chunk, 194 | Message systemMessage, 195 | VertexAiGeminiChatModel geminiChatModel) { 196 | 197 | Map outputWithIndex = new HashMap<>(); 198 | String output = processChunk("", chunk, systemMessage, geminiChatModel); 199 | outputWithIndex.put(index, output); 200 | return outputWithIndex; 201 | } 202 | 203 | private static String processChunk( 204 | String context, 205 | String chunk, 206 | Message systemMessage, 207 | VertexAiGeminiChatModel geminiChatModel) { 208 | long start = System.currentTimeMillis(); 209 | 210 | PromptTemplate userPromptTemplate; 211 | String subSummaryOverlapTemplate = 212 | """ 213 | Write a concise summary of the following text delimited by triple backquotes. 214 | 215 | ```{content}``` 216 | 217 | Output starts with CONCISE SUB-SUMMARY: 218 | """; 219 | 220 | String subSummaryResource = 221 | """ 222 | Taking the following context delimited by triple backquotes into consideration 223 | 224 | ```{context}``` 225 | 226 | Write a concise summary of the following text delimited by triple backquotes. 227 | 228 | ```{content}``` 229 | 230 | Output starts with CONCISE SUB-SUMMARY: 231 | """; 232 | 233 | if(context.trim().isEmpty()) { 234 | userPromptTemplate = PromptTemplate.builder() 235 | .template(subSummaryOverlapTemplate) 236 | .variables(Map.of("content", chunk)) 237 | .build(); 238 | } else { 239 | userPromptTemplate = PromptTemplate.builder() 240 | .template(subSummaryResource) 241 | .variables(Map.of("context", context, "content", chunk)) 242 | .build(); 243 | } 244 | Message userMessage = userPromptTemplate.createMessage(); 245 | 246 | ChatResponse response = geminiChatModel.call(new Prompt(List.of(userMessage, systemMessage), 247 | VertexAiGeminiChatOptions.builder() 248 | .temperature(0.2) 249 | .build())); 250 | System.out.println("Summarization (single chunk) took " + (System.currentTimeMillis() - start) + " milliseconds"); 251 | String output = response.getResult().getOutput().getText(); 252 | System.out.println(output+"\n\n"); 253 | return output; 254 | } 255 | //--- end helper methods --- 256 | } 257 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/TextClassificationExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.util.List; 21 | import org.springframework.ai.chat.client.ChatClient; 22 | import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; 23 | import org.springframework.ai.chat.memory.ChatMemory; 24 | import org.springframework.ai.chat.memory.ChatMemoryRepository; 25 | import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; 26 | import org.springframework.ai.chat.memory.MessageWindowChatMemory; 27 | import org.springframework.ai.chat.messages.AssistantMessage; 28 | import org.springframework.ai.chat.messages.Message; 29 | import org.springframework.ai.chat.messages.UserMessage; 30 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 31 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 32 | 33 | public class TextClassificationExample { 34 | 35 | public static void main(String[] args) { 36 | 37 | VertexAI vertexAI = new VertexAI.Builder() 38 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 39 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 40 | .setTransport(Transport.REST) 41 | .build(); 42 | 43 | var geminiChatModel = VertexAiGeminiChatModel.builder() 44 | .vertexAI(vertexAI) 45 | .defaultOptions(VertexAiGeminiChatOptions.builder() 46 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 47 | .temperature(0.2) 48 | .topK(5) 49 | .topP(0.95) 50 | .build()) 51 | .build(); 52 | 53 | // Build Few-shot history 54 | List messages = List.of( 55 | new UserMessage("Lord of the Rings by J.R.R. Tolkien"), 56 | new AssistantMessage("Fantasy"), 57 | new UserMessage("Dune by Frank Herbert"), 58 | new AssistantMessage("Science Fiction"), 59 | new UserMessage("Murder on the Orient Express by Agatha Christie"), 60 | new AssistantMessage("Mistery"), 61 | new UserMessage("Pride and Prejudice by Jane Austen"), 62 | new AssistantMessage("Romance"), 63 | new UserMessage("Dracula by Bram Stoker"), 64 | new AssistantMessage("Horror"), 65 | new UserMessage("Gone With the Wind by Margaret Mitchell"), 66 | new AssistantMessage("Historical Fiction"), 67 | new UserMessage("1984 by George Orwell"), 68 | new AssistantMessage("Dystopian"), 69 | new UserMessage("Catch-22 by Joseph Heller"), 70 | new AssistantMessage("Comedy"), 71 | new UserMessage("The Autobiography of Benjamin Franklin"), 72 | new AssistantMessage("Biography/Autobiography"), 73 | new UserMessage("Sapiens: A Brief History of Humankind by Yuval Noah Harari"), 74 | new AssistantMessage("History"), 75 | new UserMessage("A Brief History of Time by Stephen Hawking"), 76 | new AssistantMessage("Science"), 77 | new UserMessage("How to Win Friends and Influence People by Dale Carnegie"), 78 | new AssistantMessage("Self-Help"), 79 | new UserMessage("The Wealth of Nations by Adam Smith\n"), 80 | new AssistantMessage("Business/Economics"), 81 | new UserMessage("Joy of Cooking"), 82 | new AssistantMessage("Cookbook"), 83 | new UserMessage("Eat, Pray, Love by Elizabeth Gilbert"), 84 | new AssistantMessage("Travel") 85 | ); 86 | 87 | String systemMessage = 88 | """ 89 | You are a helpful AI assistant. 90 | You are an AI assistant that helps people classify the provided text into the following categories 91 | 92 | Fantasy: Includes magic, mythical creatures, and alternate universes. 93 | Science Fiction: Speculative fiction dealing with advanced technology or future settings. 94 | Mystery: Stories involving a crime or puzzle to be solved. 95 | Romance: Focuses on romantic relationships. 96 | Horror: Elicits fear or suspense. 97 | Historical Fiction: Set in a historical period. 98 | Dystopian: Depicts a future society that is often oppressive or undesirable. 99 | Comedy: Intended to be humorous. 100 | 101 | Biography/Autobiography: Accounts of a person's life. 102 | History: Records of past events. 103 | Science: Deals with the natural world and its phenomena. 104 | Self-Help: Offers advice or guidance on personal development. 105 | Business/Economics: Related to business, finance, and economics. 106 | Cookbook: Contains recipes for preparing food. 107 | Travel: Describes places and cultures. 108 | """; 109 | 110 | // create the memory for the few-shot history 111 | // chatMemory.add("examples", messages); 112 | ChatMemory chatMemory = MessageWindowChatMemory.builder().build(); 113 | chatMemory.add("examples", messages); 114 | 115 | // use the fluent ChatClient interface and provision chat history 116 | // and finer grained control of building the request 117 | long start = System.currentTimeMillis(); 118 | String response = 119 | ChatClient 120 | .builder(geminiChatModel) 121 | .build() 122 | .prompt() 123 | .system(systemMessage) 124 | .advisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) 125 | .user("In which category would The Jungle Book by Rudyard Kipling fit best?") 126 | .call() 127 | .content(); 128 | System.out.println("GEMINI: " + response); 129 | System.out.println( 130 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 131 | } 132 | } 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/TextEmbeddingExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import org.springframework.ai.embedding.EmbeddingResponse; 21 | import org.springframework.ai.reader.TextReader; 22 | import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; 23 | import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingModel; 24 | import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingOptions; 25 | 26 | public class TextEmbeddingExample { 27 | public static void main(String[] args) { 28 | VertexAiEmbeddingConnectionDetails connectionDetails = 29 | VertexAiEmbeddingConnectionDetails.builder() 30 | .projectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 31 | .location(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 32 | .build(); 33 | 34 | // Default embedding model: text-embedding-004 35 | VertexAiTextEmbeddingOptions options = VertexAiTextEmbeddingOptions.builder() 36 | .model(VertexAiTextEmbeddingOptions.DEFAULT_MODEL_NAME) 37 | .build(); 38 | 39 | var embeddingModel = new VertexAiTextEmbeddingModel(connectionDetails, options); 40 | 41 | // read the book to generate embeddings for 42 | TextReader reader = new TextReader("classpath:/the-jungle-book.txt"); 43 | String embedText = reader.get().getFirst().getText(); 44 | 45 | long start = System.currentTimeMillis(); 46 | EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of(embedText)); 47 | System.out.println("Embedding response: " + Arrays.toString(embeddingResponse.getResult().getOutput())); 48 | System.out.println( 49 | "Text embedding call took " + (System.currentTimeMillis() - start) + " ms"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/gemini/workshop/WorkingWithTemplatesExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package gemini.workshop; 17 | 18 | import com.google.cloud.vertexai.Transport; 19 | import com.google.cloud.vertexai.VertexAI; 20 | import java.util.List; 21 | import java.util.Map; 22 | import org.springframework.ai.chat.messages.Message; 23 | import org.springframework.ai.chat.prompt.Prompt; 24 | import org.springframework.ai.chat.prompt.PromptTemplate; 25 | import org.springframework.ai.chat.prompt.SystemPromptTemplate; 26 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; 27 | import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; 28 | import org.springframework.core.io.ClassPathResource; 29 | 30 | public class WorkingWithTemplatesExample { 31 | 32 | public static void main(String[] args) { 33 | 34 | VertexAI vertexAI = new VertexAI.Builder() 35 | .setLocation(System.getenv("VERTEX_AI_GEMINI_LOCATION")) 36 | .setProjectId(System.getenv("VERTEX_AI_GEMINI_PROJECT_ID")) 37 | .setTransport(Transport.REST) 38 | .build(); 39 | 40 | var geminiChatModel = VertexAiGeminiChatModel.builder() 41 | .vertexAI(vertexAI) 42 | .defaultOptions(VertexAiGeminiChatOptions.builder() 43 | .model(System.getenv("VERTEX_AI_GEMINI_MODEL")) 44 | .temperature(0.2) 45 | .topK(5) 46 | .topP(0.95) 47 | .build()) 48 | .build(); 49 | 50 | //------------------------------------------- 51 | // option1 : templates directly as strings 52 | //------------------------------------------- 53 | 54 | // create system message template 55 | SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(""" 56 | You are a helpful AI assistant. 57 | You are an AI assistant that helps people get high quality literary information. 58 | Your name is {name} 59 | You should reply to the user's request with your name and also in the style of a {voice}. 60 | """ 61 | ); 62 | Message systemMessage = systemPromptTemplate.createMessage( 63 | Map.of("name", "Professor Gemini", "voice", "literary professor")); 64 | 65 | // create user message template 66 | PromptTemplate userPromptTemplate = PromptTemplate.builder().template(""" 67 | Please recommend no more than {number} great {genre} book to read during my vacation. 68 | Return to me strictly the name and author 69 | """) 70 | .variables(Map.of("number", "4", "genre", "fiction")) 71 | .build(); 72 | Message userMessage = userPromptTemplate.createMessage(); 73 | 74 | long start = System.currentTimeMillis(); 75 | System.out.println("GEMINI: " + geminiChatModel 76 | .call(new Prompt(List.of(userMessage, systemMessage))) 77 | .getResult().getOutput().getText()); 78 | System.out.println( 79 | "VertexAI Gemini call took " + (System.currentTimeMillis() - start) + " ms"); 80 | 81 | //------------------------------------------- 82 | // option2 : templates as resource files 83 | // Can be governed, versioned, etc 84 | //------------------------------------------- 85 | SystemPromptTemplate templatefromFile = new SystemPromptTemplate(new ClassPathResource("/prompts/system-message.st")); 86 | System.out.println("\n\nTemplate read as resource from disk: \n" + templatefromFile.getTemplate()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/resources/Aesop-fables-Vol01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddobrin/gemini-workshop-for-spring-ai-java-developers/2b37913aadc6fc604c080cb88b20f9e28554cc32/src/main/resources/Aesop-fables-Vol01.mp3 -------------------------------------------------------------------------------- /src/main/resources/Birds.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddobrin/gemini-workshop-for-spring-ai-java-developers/2b37913aadc6fc604c080cb88b20f9e28554cc32/src/main/resources/Birds.mp4 -------------------------------------------------------------------------------- /src/main/resources/Coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddobrin/gemini-workshop-for-spring-ai-java-developers/2b37913aadc6fc604c080cb88b20f9e28554cc32/src/main/resources/Coffee.png -------------------------------------------------------------------------------- /src/main/resources/The-Wasteland-TSEliot-public.txt: -------------------------------------------------------------------------------- 1 | The Project Gutenberg eBook of The Waste Land 2 | 3 | This ebook is for the use of anyone anywhere in the United States and 4 | most other parts of the world at no cost and with almost no restrictions 5 | whatsoever. You may copy it, give it away or re-use it under the terms 6 | of the Project Gutenberg License included with this ebook or online 7 | at www.gutenberg.org. If you are not located in the United States, 8 | you will have to check the laws of the country where you are located 9 | before using this eBook. 10 | 11 | Title: The Waste Land 12 | 13 | Author: T. S. Eliot 14 | 15 | Release date: May 1, 1998 [eBook #1321] 16 | Most recently updated: March 24, 2021 17 | 18 | Language: English 19 | 20 | Credits: An Anonymous Project Gutenberg Volunteer and David Widger 21 | 22 | 23 | *** START OF THE PROJECT GUTENBERG EBOOK THE WASTE LAND *** 24 | 25 | 26 | 27 | 28 | THE WASTE LAND 29 | 30 | By T. S. Eliot 31 | 32 | 33 | 34 | 35 | 36 | Contents 37 | 38 | I. THE BURIAL OF THE DEAD 39 | 40 | II. A GAME OF CHESS 41 | 42 | III. THE FIRE SERMON 43 | 44 | IV. DEATH BY WATER 45 | 46 | V. WHAT THE THUNDER SAID 47 | 48 | NOTES ON “THE WASTE LAND” 49 | 50 | 51 | 52 | 53 | “Nam Sibyllam quidem Cumis ego ipse oculis meis 54 | vidi in ampulla pendere, et cum illi pueri dicerent: 55 | Σίβυλλα τί θέλεις; respondebat illa: ἀποθανεῖν θέλω.” 56 | 57 | _For Ezra Pound 58 | il miglior fabbro_ 59 | 60 | 61 | 62 | 63 | I. THE BURIAL OF THE DEAD 64 | 65 | 66 | April is the cruellest month, breeding 67 | Lilacs out of the dead land, mixing 68 | Memory and desire, stirring 69 | Dull roots with spring rain. 70 | Winter kept us warm, covering 71 | Earth in forgetful snow, feeding 72 | A little life with dried tubers. 73 | Summer surprised us, coming over the Starnbergersee 74 | With a shower of rain; we stopped in the colonnade, 75 | And went on in sunlight, into the Hofgarten, 10 76 | And drank coffee, and talked for an hour. 77 | Bin gar keine Russin, stamm’ aus Litauen, echt deutsch. 78 | And when we were children, staying at the archduke’s, 79 | My cousin’s, he took me out on a sled, 80 | And I was frightened. He said, Marie, 81 | Marie, hold on tight. And down we went. 82 | In the mountains, there you feel free. 83 | I read, much of the night, and go south in the winter. 84 | 85 | What are the roots that clutch, what branches grow 86 | Out of this stony rubbish? Son of man, 20 87 | You cannot say, or guess, for you know only 88 | A heap of broken images, where the sun beats, 89 | And the dead tree gives no shelter, the cricket no relief, 90 | And the dry stone no sound of water. Only 91 | There is shadow under this red rock, 92 | (Come in under the shadow of this red rock), 93 | And I will show you something different from either 94 | Your shadow at morning striding behind you 95 | Or your shadow at evening rising to meet you; 96 | I will show you fear in a handful of dust. 30 97 | _Frisch weht der Wind 98 | Der Heimat zu 99 | Mein Irisch Kind, 100 | Wo weilest du?_ 101 | “You gave me hyacinths first a year ago; 102 | “They called me the hyacinth girl.” 103 | —Yet when we came back, late, from the Hyacinth garden, 104 | Your arms full, and your hair wet, I could not 105 | Speak, and my eyes failed, I was neither 106 | Living nor dead, and I knew nothing, 40 107 | Looking into the heart of light, the silence. 108 | _Oed’ und leer das Meer_. 109 | 110 | Madame Sosostris, famous clairvoyante, 111 | Had a bad cold, nevertheless 112 | Is known to be the wisest woman in Europe, 113 | With a wicked pack of cards. Here, said she, 114 | Is your card, the drowned Phoenician Sailor, 115 | (Those are pearls that were his eyes. Look!) 116 | Here is Belladonna, the Lady of the Rocks, 117 | The lady of situations. 50 118 | Here is the man with three staves, and here the Wheel, 119 | And here is the one-eyed merchant, and this card, 120 | Which is blank, is something he carries on his back, 121 | Which I am forbidden to see. I do not find 122 | The Hanged Man. Fear death by water. 123 | I see crowds of people, walking round in a ring. 124 | Thank you. If you see dear Mrs. Equitone, 125 | Tell her I bring the horoscope myself: 126 | One must be so careful these days. 127 | 128 | Unreal City, 60 129 | Under the brown fog of a winter dawn, 130 | A crowd flowed over London Bridge, so many, 131 | I had not thought death had undone so many. 132 | Sighs, short and infrequent, were exhaled, 133 | And each man fixed his eyes before his feet. 134 | Flowed up the hill and down King William Street, 135 | To where Saint Mary Woolnoth kept the hours 136 | With a dead sound on the final stroke of nine. 137 | There I saw one I knew, and stopped him, crying “Stetson! 138 | “You who were with me in the ships at Mylae! 70 139 | “That corpse you planted last year in your garden, 140 | “Has it begun to sprout? Will it bloom this year? 141 | “Or has the sudden frost disturbed its bed? 142 | “Oh keep the Dog far hence, that’s friend to men, 143 | “Or with his nails he’ll dig it up again! 144 | “You! hypocrite lecteur!—mon semblable,—mon frère!” 145 | 146 | 147 | 148 | 149 | II. A GAME OF CHESS 150 | 151 | 152 | The Chair she sat in, like a burnished throne, 153 | Glowed on the marble, where the glass 154 | Held up by standards wrought with fruited vines 155 | From which a golden Cupidon peeped out 80 156 | (Another hid his eyes behind his wing) 157 | Doubled the flames of sevenbranched candelabra 158 | Reflecting light upon the table as 159 | The glitter of her jewels rose to meet it, 160 | From satin cases poured in rich profusion. 161 | In vials of ivory and coloured glass 162 | Unstoppered, lurked her strange synthetic perfumes, 163 | Unguent, powdered, or liquid—troubled, confused 164 | And drowned the sense in odours; stirred by the air 165 | That freshened from the window, these ascended 90 166 | In fattening the prolonged candle-flames, 167 | Flung their smoke into the laquearia, 168 | Stirring the pattern on the coffered ceiling. 169 | Huge sea-wood fed with copper 170 | Burned green and orange, framed by the coloured stone, 171 | In which sad light a carvèd dolphin swam. 172 | Above the antique mantel was displayed 173 | As though a window gave upon the sylvan scene 174 | The change of Philomel, by the barbarous king 175 | So rudely forced; yet there the nightingale 100 176 | Filled all the desert with inviolable voice 177 | And still she cried, and still the world pursues, 178 | “Jug Jug” to dirty ears. 179 | And other withered stumps of time 180 | Were told upon the walls; staring forms 181 | Leaned out, leaning, hushing the room enclosed. 182 | Footsteps shuffled on the stair. 183 | Under the firelight, under the brush, her hair 184 | Spread out in fiery points 185 | Glowed into words, then would be savagely still. 110 186 | 187 | “My nerves are bad to-night. Yes, bad. Stay with me. 188 | “Speak to me. Why do you never speak. Speak. 189 | “What are you thinking of? What thinking? What? 190 | “I never know what you are thinking. Think.” 191 | 192 | I think we are in rats’ alley 193 | Where the dead men lost their bones. 194 | 195 | “What is that noise?” 196 | The wind under the door. 197 | “What is that noise now? What is the wind doing?” 198 | Nothing again nothing. 120 199 | “Do 200 | “You know nothing? Do you see nothing? Do you remember 201 | “Nothing?” 202 | 203 | I remember 204 | Those are pearls that were his eyes. 205 | “Are you alive, or not? Is there nothing in your head?” 206 | But 207 | O O O O that Shakespeherian Rag— 208 | It’s so elegant 209 | So intelligent 130 210 | “What shall I do now? What shall I do?” 211 | I shall rush out as I am, and walk the street 212 | “With my hair down, so. What shall we do tomorrow? 213 | “What shall we ever do?” 214 | The hot water at ten. 215 | And if it rains, a closed car at four. 216 | And we shall play a game of chess, 217 | Pressing lidless eyes and waiting for a knock upon the door. 218 | 219 | When Lil’s husband got demobbed, I said— 220 | I didn’t mince my words, I said to her myself, 140 221 | HURRY UP PLEASE IT’S TIME 222 | Now Albert’s coming back, make yourself a bit smart. 223 | He’ll want to know what you done with that money he gave you 224 | To get yourself some teeth. He did, I was there. 225 | You have them all out, Lil, and get a nice set, 226 | He said, I swear, I can’t bear to look at you. 227 | And no more can’t I, I said, and think of poor Albert, 228 | He’s been in the army four years, he wants a good time, 229 | And if you don’t give it him, there’s others will, I said. 230 | Oh is there, she said. Something o’ that, I said. 150 231 | Then I’ll know who to thank, she said, and give me a straight look. 232 | HURRY UP PLEASE IT’S TIME 233 | If you don’t like it you can get on with it, I said. 234 | Others can pick and choose if you can’t. 235 | But if Albert makes off, it won’t be for lack of telling. 236 | You ought to be ashamed, I said, to look so antique. 237 | (And her only thirty-one.) 238 | I can’t help it, she said, pulling a long face, 239 | It’s them pills I took, to bring it off, she said. 240 | (She’s had five already, and nearly died of young George.) 160 241 | The chemist said it would be all right, but I’ve never been the same. 242 | You _are_ a proper fool, I said. 243 | Well, if Albert won’t leave you alone, there it is, I said, 244 | What you get married for if you don’t want children? 245 | HURRY UP PLEASE IT’S TIME 246 | Well, that Sunday Albert was home, they had a hot gammon, 247 | And they asked me in to dinner, to get the beauty of it hot— 248 | HURRY UP PLEASE IT’S TIME 249 | HURRY UP PLEASE IT’S TIME 250 | Goonight Bill. Goonight Lou. Goonight May. Goonight. 170 251 | Ta ta. Goonight. Goonight. 252 | Good night, ladies, good night, sweet ladies, good night, good night. 253 | 254 | 255 | 256 | 257 | III. THE FIRE SERMON 258 | 259 | 260 | The river’s tent is broken: the last fingers of leaf 261 | Clutch and sink into the wet bank. The wind 262 | Crosses the brown land, unheard. The nymphs are departed. 263 | Sweet Thames, run softly, till I end my song. 264 | The river bears no empty bottles, sandwich papers, 265 | Silk handkerchiefs, cardboard boxes, cigarette ends 266 | Or other testimony of summer nights. The nymphs are departed. 267 | And their friends, the loitering heirs of city directors; 180 268 | Departed, have left no addresses. 269 | By the waters of Leman I sat down and wept . . . 270 | Sweet Thames, run softly till I end my song, 271 | Sweet Thames, run softly, for I speak not loud or long. 272 | But at my back in a cold blast I hear 273 | The rattle of the bones, and chuckle spread from ear to ear. 274 | A rat crept softly through the vegetation 275 | Dragging its slimy belly on the bank 276 | While I was fishing in the dull canal 277 | On a winter evening round behind the gashouse 190 278 | Musing upon the king my brother’s wreck 279 | And on the king my father’s death before him. 280 | White bodies naked on the low damp ground 281 | And bones cast in a little low dry garret, 282 | Rattled by the rat’s foot only, year to year. 283 | But at my back from time to time I hear 284 | The sound of horns and motors, which shall bring 285 | Sweeney to Mrs. Porter in the spring. 286 | O the moon shone bright on Mrs. Porter 287 | And on her daughter 200 288 | They wash their feet in soda water 289 | _Et O ces voix d’enfants, chantant dans la coupole!_ 290 | 291 | Twit twit twit 292 | Jug jug jug jug jug jug 293 | So rudely forc’d. 294 | Tereu 295 | 296 | Unreal City 297 | Under the brown fog of a winter noon 298 | Mr. Eugenides, the Smyrna merchant 299 | Unshaven, with a pocket full of currants 210 300 | C.i.f. London: documents at sight, 301 | Asked me in demotic French 302 | To luncheon at the Cannon Street Hotel 303 | Followed by a weekend at the Metropole. 304 | 305 | At the violet hour, when the eyes and back 306 | Turn upward from the desk, when the human engine waits 307 | Like a taxi throbbing waiting, 308 | I Tiresias, though blind, throbbing between two lives, 309 | Old man with wrinkled female breasts, can see 310 | At the violet hour, the evening hour that strives 220 311 | Homeward, and brings the sailor home from sea, 312 | The typist home at teatime, clears her breakfast, lights 313 | Her stove, and lays out food in tins. 314 | Out of the window perilously spread 315 | Her drying combinations touched by the sun’s last rays, 316 | On the divan are piled (at night her bed) 317 | Stockings, slippers, camisoles, and stays. 318 | I Tiresias, old man with wrinkled dugs 319 | Perceived the scene, and foretold the rest— 320 | I too awaited the expected guest. 230 321 | He, the young man carbuncular, arrives, 322 | A small house agent’s clerk, with one bold stare, 323 | One of the low on whom assurance sits 324 | As a silk hat on a Bradford millionaire. 325 | The time is now propitious, as he guesses, 326 | The meal is ended, she is bored and tired, 327 | Endeavours to engage her in caresses 328 | Which still are unreproved, if undesired. 329 | Flushed and decided, he assaults at once; 330 | Exploring hands encounter no defence; 240 331 | His vanity requires no response, 332 | And makes a welcome of indifference. 333 | (And I Tiresias have foresuffered all 334 | Enacted on this same divan or bed; 335 | I who have sat by Thebes below the wall 336 | And walked among the lowest of the dead.) 337 | Bestows one final patronising kiss, 338 | And gropes his way, finding the stairs unlit . . . 339 | 340 | She turns and looks a moment in the glass, 341 | Hardly aware of her departed lover; 250 342 | Her brain allows one half-formed thought to pass: 343 | “Well now that’s done: and I’m glad it’s over.” 344 | When lovely woman stoops to folly and 345 | Paces about her room again, alone, 346 | She smooths her hair with automatic hand, 347 | And puts a record on the gramophone. 348 | 349 | “This music crept by me upon the waters” 350 | And along the Strand, up Queen Victoria Street. 351 | O City city, I can sometimes hear 352 | Beside a public bar in Lower Thames Street, 260 353 | The pleasant whining of a mandoline 354 | And a clatter and a chatter from within 355 | Where fishmen lounge at noon: where the walls 356 | Of Magnus Martyr hold 357 | Inexplicable splendour of Ionian white and gold. 358 | 359 | The river sweats 360 | Oil and tar 361 | The barges drift 362 | With the turning tide 363 | Red sails 270 364 | Wide 365 | To leeward, swing on the heavy spar. 366 | The barges wash 367 | Drifting logs 368 | Down Greenwich reach 369 | Past the Isle of Dogs. 370 | Weialala leia 371 | Wallala leialala 372 | Elizabeth and Leicester 373 | Beating oars 280 374 | The stern was formed 375 | A gilded shell 376 | Red and gold 377 | The brisk swell 378 | Rippled both shores 379 | Southwest wind 380 | Carried down stream 381 | The peal of bells 382 | White towers 383 | Weialala leia 290 384 | Wallala leialala 385 | 386 | “Trams and dusty trees. 387 | Highbury bore me. Richmond and Kew 388 | Undid me. By Richmond I raised my knees 389 | Supine on the floor of a narrow canoe.” 390 | 391 | “My feet are at Moorgate, and my heart 392 | Under my feet. After the event 393 | He wept. He promised ‘a new start’. 394 | I made no comment. What should I resent?” 395 | “On Margate Sands. 300 396 | I can connect 397 | Nothing with nothing. 398 | The broken fingernails of dirty hands. 399 | My people humble people who expect 400 | Nothing.” 401 | la la 402 | 403 | To Carthage then I came 404 | 405 | Burning burning burning burning 406 | O Lord Thou pluckest me out 407 | O Lord Thou pluckest 310 408 | 409 | burning 410 | 411 | 412 | 413 | 414 | IV. DEATH BY WATER 415 | 416 | 417 | Phlebas the Phoenician, a fortnight dead, 418 | Forgot the cry of gulls, and the deep sea swell 419 | And the profit and loss. 420 | A current under sea 421 | Picked his bones in whispers. As he rose and fell 422 | He passed the stages of his age and youth 423 | Entering the whirlpool. 424 | Gentile or Jew 425 | O you who turn the wheel and look to windward, 320 426 | Consider Phlebas, who was once handsome and tall as you. 427 | 428 | 429 | 430 | 431 | V. WHAT THE THUNDER SAID 432 | 433 | 434 | After the torchlight red on sweaty faces 435 | After the frosty silence in the gardens 436 | After the agony in stony places 437 | The shouting and the crying 438 | Prison and palace and reverberation 439 | Of thunder of spring over distant mountains 440 | He who was living is now dead 441 | We who were living are now dying 442 | With a little patience 330 443 | 444 | Here is no water but only rock 445 | Rock and no water and the sandy road 446 | The road winding above among the mountains 447 | Which are mountains of rock without water 448 | If there were water we should stop and drink 449 | Amongst the rock one cannot stop or think 450 | Sweat is dry and feet are in the sand 451 | If there were only water amongst the rock 452 | Dead mountain mouth of carious teeth that cannot spit 453 | Here one can neither stand nor lie nor sit 340 454 | There is not even silence in the mountains 455 | But dry sterile thunder without rain 456 | There is not even solitude in the mountains 457 | But red sullen faces sneer and snarl 458 | From doors of mudcracked houses 459 | If there were water 460 | And no rock 461 | If there were rock 462 | And also water 463 | And water 350 464 | A spring 465 | A pool among the rock 466 | If there were the sound of water only 467 | Not the cicada 468 | And dry grass singing 469 | But sound of water over a rock 470 | Where the hermit-thrush sings in the pine trees 471 | Drip drop drip drop drop drop drop 472 | But there is no water 473 | 474 | Who is the third who walks always beside you? 475 | When I count, there are only you and I together 360 476 | But when I look ahead up the white road 477 | There is always another one walking beside you 478 | Gliding wrapt in a brown mantle, hooded 479 | I do not know whether a man or a woman 480 | —But who is that on the other side of you? 481 | 482 | What is that sound high in the air 483 | Murmur of maternal lamentation 484 | Who are those hooded hordes swarming 485 | Over endless plains, stumbling in cracked earth 486 | Ringed by the flat horizon only 370 487 | What is the city over the mountains 488 | Cracks and reforms and bursts in the violet air 489 | Falling towers 490 | Jerusalem Athens Alexandria 491 | Vienna London 492 | Unreal 493 | 494 | A woman drew her long black hair out tight 495 | And fiddled whisper music on those strings 496 | And bats with baby faces in the violet light 497 | Whistled, and beat their wings 380 498 | And crawled head downward down a blackened wall 499 | And upside down in air were towers 500 | Tolling reminiscent bells, that kept the hours 501 | And voices singing out of empty cisterns and exhausted wells. 502 | 503 | In this decayed hole among the mountains 504 | In the faint moonlight, the grass is singing 505 | Over the tumbled graves, about the chapel 506 | There is the empty chapel, only the wind’s home. 507 | It has no windows, and the door swings, 508 | Dry bones can harm no one. 390 509 | Only a cock stood on the rooftree 510 | Co co rico co co rico 511 | In a flash of lightning. Then a damp gust 512 | Bringing rain 513 | 514 | Ganga was sunken, and the limp leaves 515 | Waited for rain, while the black clouds 516 | Gathered far distant, over Himavant. 517 | The jungle crouched, humped in silence. 518 | Then spoke the thunder 519 | DA 400 520 | _Datta:_ what have we given? 521 | My friend, blood shaking my heart 522 | The awful daring of a moment’s surrender 523 | Which an age of prudence can never retract 524 | By this, and this only, we have existed 525 | Which is not to be found in our obituaries 526 | Or in memories draped by the beneficent spider 527 | Or under seals broken by the lean solicitor 528 | In our empty rooms 529 | DA 410 530 | _Dayadhvam:_ I have heard the key 531 | Turn in the door once and turn once only 532 | We think of the key, each in his prison 533 | Thinking of the key, each confirms a prison 534 | Only at nightfall, aetherial rumours 535 | Revive for a moment a broken Coriolanus 536 | DA 537 | _Damyata:_ The boat responded 538 | Gaily, to the hand expert with sail and oar 539 | The sea was calm, your heart would have responded 420 540 | Gaily, when invited, beating obedient 541 | To controlling hands 542 | 543 | I sat upon the shore 544 | Fishing, with the arid plain behind me 545 | Shall I at least set my lands in order? 546 | London Bridge is falling down falling down falling down 547 | _Poi s’ascose nel foco che gli affina 548 | Quando fiam ceu chelidon_ — O swallow swallow 549 | _Le Prince d’Aquitaine à la tour abolie_ 550 | These fragments I have shored against my ruins 430 551 | Why then Ile fit you. Hieronymo’s mad againe. 552 | Datta. Dayadhvam. Damyata. 553 | Shantih shantih shantih 554 | 555 | Line 415 aetherial] aethereal 556 | Line 428 ceu] uti— Editor 557 | 558 | 559 | 560 | 561 | NOTES ON “THE WASTE LAND” 562 | 563 | 564 | Not only the title, but the plan and a good deal of the 565 | incidental symbolism of the poem were suggested by Miss Jessie L. 566 | Weston’s book on the Grail legend: _From Ritual to Romance_ 567 | (Macmillan, Cambridge) Indeed, so deeply am I indebted, Miss 568 | Weston’s book will elucidate the difficulties of the poem much 569 | better than my notes can do; and I recommend it (apart from the 570 | great interest of the book itself) to any who think such 571 | elucidation of the poem worth the trouble. To another work of 572 | anthropology I am indebted in general, one which has influenced 573 | our generation profoundly; I mean _The Golden Bough_; I have used 574 | especially the two volumes _Adonis, Attis, Osiris_. Anyone who is 575 | acquainted with these works will immediately recognise in the 576 | poem certain references to vegetation ceremonies. 577 | 578 | I. THE BURIAL OF THE DEAD 579 | 580 | Line 20. Cf. _Ezekiel_ 2:1. 581 | 582 | 23. Cf. _Ecclesiastes_ 12:5. 583 | 584 | 31. V. _Tristan und Isolde_, i, verses 5-8. 585 | 586 | 42. Id. iii, verse 24. 587 | 588 | 46. I am not familiar with the exact constitution of the Tarot 589 | pack of cards, from which I have obviously departed to suit my 590 | own convenience. The Hanged Man, a member of the traditional 591 | pack, fits my purpose in two ways: because he is associated in 592 | my mind with the Hanged God of Frazer, and because I associate 593 | him with the hooded figure in the passage of the disciples to 594 | Emmaus in Part V. The Phoenician Sailor and the Merchant appear 595 | later; also the “crowds of people,” and Death by Water is 596 | executed in Part IV. The Man with Three Staves (an authentic 597 | member of the Tarot pack) I associate, quite arbitrarily, with 598 | the Fisher King himself. 599 | 600 | 60. Cf. Baudelaire: 601 | 602 | 603 | “Fourmillante cité, cité; pleine de rêves, 604 | Où le spectre en plein jour raccroche le passant.” 605 | 63. Cf. _Inferno_, iii. 55-7. 606 | 607 | 608 | “si lunga tratta 609 | di gente, ch’io non avrei mai creduto 610 | che morte tanta n’avesse disfatta.” 611 | 64. Cf. _Inferno_, iv. 25-7: 612 | 613 | 614 | “Quivi, secondo che per ascoltare, 615 | “non avea pianto, ma’ che di sospiri, 616 | “che l’aura eterna facevan tremare.” 617 | 68. A phenomenon which I have often noticed. 618 | 619 | 74. Cf. the Dirge in Webster’s _White Devil_. 620 | 621 | 76. V. Baudelaire, Preface to _Fleurs du Mal_. 622 | 623 | II. A GAME OF CHESS 624 | 625 | 77. Cf. _Antony and Cleopatra_, II. ii., l. 190. 626 | 627 | 92. Laquearia. V. _Aeneid_, I. 726: 628 | 629 | 630 | dependent lychni laquearibus aureis 631 | incensi, et noctem flammis funalia vincunt. 632 | 98. Sylvan scene. V. Milton, _Paradise Lost_, iv. 140. 633 | 634 | 99. V. Ovid, _Metamorphoses_, vi, Philomela. 635 | 636 | 100. Cf. Part III, l. 204. 637 | 638 | 115. Cf. Part III, l. 195. 639 | 640 | 118. Cf. Webster: “Is the wind in that door still?” 641 | 642 | 126. Cf. Part I, l. 37, 48. 643 | 644 | 138. Cf. the game of chess in Middleton’s _Women beware Women_. 645 | 646 | III. THE FIRE SERMON 647 | 648 | 176. V. Spenser, _Prothalamion_. 649 | 650 | 192. Cf. _The Tempest_, I. ii. 651 | 652 | 196. Cf. Marvell, _To His Coy Mistress_. 653 | 654 | 197. Cf. Day, _Parliament of Bees_: 655 | 656 | 657 | “When of the sudden, listening, you shall hear, 658 | “A noise of horns and hunting, which shall bring 659 | “Actaeon to Diana in the spring, 660 | “Where all shall see her naked skin . . .” 661 | 199. I do not know the origin of the ballad from which these 662 | lines are taken: it was reported to me from Sydney, Australia. 663 | 664 | 202. V. Verlaine, _Parsifal_. 665 | 666 | 210. The currants were quoted at a price “carriage and insurance 667 | free to London”; and the Bill of Lading etc. were to be handed 668 | to the buyer upon payment of the sight draft. 669 | 670 | 210. “Carriage and insurance free”] “cost, insurance and 671 | freight”-Editor. 672 | 673 | 218. Tiresias, although a mere spectator and not indeed a 674 | “character,” is yet the most important personage in the poem, 675 | uniting all the rest. Just as the one-eyed merchant, seller of 676 | currants, melts into the Phoenician Sailor, and the latter is not 677 | wholly distinct from Ferdinand Prince of Naples, so all the women 678 | are one woman, and the two sexes meet in Tiresias. What Tiresias 679 | _sees_, in fact, is the substance of the poem. The whole passage 680 | from Ovid is of great anthropological interest: 681 | 682 | 683 | ‘. . . Cum Iunone iocos et maior vestra profecto est 684 | Quam, quae contingit maribus,’ dixisse, ‘voluptas.’ 685 | Illa negat; placuit quae sit sententia docti 686 | Quaerere Tiresiae: venus huic erat utraque nota. 687 | Nam duo magnorum viridi coeuntia silva 688 | Corpora serpentum baculi violaverat ictu 689 | Deque viro factus, mirabile, femina septem 690 | Egerat autumnos; octavo rursus eosdem 691 | Vidit et ‘est vestrae si tanta potentia plagae,’ 692 | Dixit ‘ut auctoris sortem in contraria mutet, 693 | Nunc quoque vos feriam!’ percussis anguibus isdem 694 | Forma prior rediit genetivaque venit imago. 695 | Arbiter hic igitur sumptus de lite iocosa 696 | Dicta Iovis firmat; gravius Saturnia iusto 697 | Nec pro materia fertur doluisse suique 698 | Iudicis aeterna damnavit lumina nocte, 699 | At pater omnipotens (neque enim licet inrita cuiquam 700 | Facta dei fecisse deo) pro lumine adempto 701 | Scire futura dedit poenamque levavit honore. 702 | 703 | 221. This may not appear as exact as Sappho’s lines, but I had 704 | in mind the “longshore” or “dory” fisherman, who returns at 705 | nightfall. 706 | 707 | 253. V. Goldsmith, the song in _The Vicar of Wakefield_. 708 | 709 | 257. V. _The Tempest_, as above. 710 | 711 | 264. The interior of St. Magnus Martyr is to my mind one of 712 | the finest among Wren’s interiors. See _The Proposed Demolition 713 | of Nineteen City Churches_ (P. S. King & Son, Ltd.). 714 | 715 | 266. The Song of the (three) Thames-daughters begins here. 716 | From line 292 to 306 inclusive they speak in turn. 717 | V. _Götterdämmerung_, III. i: the Rhine-daughters. 718 | 719 | 279. V. Froude, _Elizabeth_, Vol. I, ch. iv, letter of De 720 | Quadra to Philip of Spain: 721 | 722 | “In the afternoon we were in a barge, watching the games on the 723 | river. (The queen) was alone with Lord Robert and myself on the 724 | poop, when they began to talk nonsense, and went so far that Lord 725 | Robert at last said, as I was on the spot there was no reason why 726 | they should not be married if the queen pleased.” 727 | 728 | 293. Cf. _Purgatorio_, v. 133: 729 | 730 | 731 | “Ricorditi di me, che son la Pia; 732 | Siena mi fe’, disfecemi Maremma.” 733 | 734 | 307. V. St. Augustine’s _Confessions_: “to Carthage then I 735 | came, where a cauldron of unholy loves sang all about mine ears.” 736 | 737 | 308. The complete text of the Buddha’s Fire Sermon (which 738 | corresponds in importance to the Sermon on the Mount) from which 739 | these words are taken, will be found translated in the late Henry 740 | Clarke Warren’s _Buddhism in Translation_ (Harvard Oriental 741 | Series). Mr. Warren was one of the great pioneers of Buddhist 742 | studies in the Occident. 743 | 744 | 309. From St. Augustine’s _Confessions_ again. The collocation 745 | of these two representatives of eastern and western asceticism, 746 | as the culmination of this part of the poem, is not an accident. 747 | 748 | V. WHAT THE THUNDER SAID 749 | 750 | In the first part of Part V three themes are employed: 751 | the journey to Emmaus, the approach to the Chapel Perilous 752 | (see Miss Weston’s book) and the present decay of eastern Europe. 753 | 754 | 357. This is _Turdus aonalaschkae pallasii_, the hermit-thrush 755 | which I have heard in Quebec County. Chapman says (_Handbook of 756 | Birds of Eastern North America_) “it is most at home in secluded 757 | woodland and thickety retreats. . . . Its notes are not 758 | remarkable for variety or volume, but in purity and sweetness of 759 | tone and exquisite modulation they are unequalled.” Its 760 | “water-dripping song” is justly celebrated. 761 | 762 | 360. The following lines were stimulated by the account of one 763 | of the Antarctic expeditions (I forget which, but I think one 764 | of Shackleton’s): it was related that the party of explorers, 765 | at the extremity of their strength, had the constant delusion 766 | that there was _one more member_ than could actually be counted. 767 | 768 | 366-76. Cf. Hermann Hesse, _Blick ins Chaos_: 769 | 770 | “Schon ist halb Europa, schon ist zumindest der halbe Osten 771 | Europas auf dem Wege zum Chaos, fährt betrunken im heiligem Wahn 772 | am Abgrund entlang und singt dazu, singt betrunken und hymnisch 773 | wie Dmitri Karamasoff sang. Ueber diese Lieder lacht der Bürger 774 | beleidigt, der Heilige und Seher hört sie mit Tränen.” 775 | 776 | 401. “Datta, dayadhvam, damyata” (Give, sympathize, 777 | control). The fable of the meaning of the Thunder is found 778 | in the _Brihadaranyaka—Upanishad_, 5, 1. A translation is found 779 | in Deussen’s _Sechzig Upanishads des Veda_, p. 489. 780 | 781 | 407. Cf. Webster, _The White Devil_, v. vi: 782 | 783 | 784 | “. . . they’ll remarry 785 | Ere the worm pierce your winding-sheet, ere the spider 786 | Make a thin curtain for your epitaphs.” 787 | 411. Cf. _Inferno_, xxxiii. 46: 788 | 789 | 790 | “ed io sentii chiavar l’uscio di sotto 791 | all’orribile torre.” 792 | Also F. H. Bradley, _Appearance and Reality_, p. 346: 793 | 794 | “My external sensations are no less private to myself than are my 795 | thoughts or my feelings. In either case my experience falls 796 | within my own circle, a circle closed on the outside; and, with 797 | all its elements alike, every sphere is opaque to the others 798 | which surround it. . . . In brief, regarded as an existence which 799 | appears in a soul, the whole world for each is peculiar and 800 | private to that soul.” 801 | 802 | 424. V. Weston, From _Ritual to Romance_; chapter on the Fisher 803 | King. 804 | 805 | 427. V. _Purgatorio_, xxvi. 148. 806 | 807 | 808 | “‘Ara vos prec per aquella valor 809 | ‘que vos guida al som de l’escalina, 810 | ‘sovegna vos a temps de ma dolor.’ 811 | Poi s’ascose nel foco che gli affina.” 812 | 428. V. _Pervigilium Veneris_. Cf. Philomela in Parts II and 813 | III. 814 | 815 | 429. V. Gerard de Nerval, Sonnet _El Desdichado_. 816 | 817 | 431. V. Kyd’s _Spanish Tragedy_. 818 | 819 | 433. Shantih. Repeated as here, a formal ending to an 820 | Upanishad. ‘The Peace which passeth understanding’ is a feeble 821 | translation of the content of this word. 822 | -------------------------------------------------------------------------------- /src/main/resources/TheJungleBook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddobrin/gemini-workshop-for-spring-ai-java-developers/2b37913aadc6fc604c080cb88b20f9e28554cc32/src/main/resources/TheJungleBook.jpg -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=spring-ai-structured-output 2 | 3 | ################################# 4 | # Google VertexAI Gemini 5 | ################################# 6 | spring.ai.vertex.ai.gemini.project-id=${VERTEX_AI_GEMINI_PROJECT_ID} 7 | spring.ai.vertex.ai.gemini.location=${VERTEX_AI_GEMINI_LOCATION} 8 | spring.ai.vertex.ai.gemini.chat.options.model=${VERTEX_AI_GEMINI_MODEL} 9 | spring.ai.vertex.ai.gemini.transport=grpc 10 | -------------------------------------------------------------------------------- /src/main/resources/attention-is-all-you-need.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddobrin/gemini-workshop-for-spring-ai-java-developers/2b37913aadc6fc604c080cb88b20f9e28554cc32/src/main/resources/attention-is-all-you-need.pdf -------------------------------------------------------------------------------- /src/main/resources/book-genres.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "genre": "Fantasy", "description": "Includes magic, mythical creatures, and alternate universes." }, 3 | { "genre": "Science Fiction", "description": "Speculative fiction dealing with advanced technology or future settings." }, 4 | { "genre": "Mystery", "description": "Stories involving a crime or puzzle to be solved." }, 5 | { "genre": "Romance", "description": "Focuses on romantic relationships." }, 6 | { "genre": "Horror", "description": "Elicits fear or suspense." }, 7 | { "genre": "Historical Fiction", "description": "Set in a historical period." }, 8 | { "genre": "Dystopian", "description": "Depicts a future society that is often oppressive or undesirable." }, 9 | { "genre": "Comedy", "description": "Intended to be humorous." }, 10 | { "genre": "Biography/Autobiography", "description": "Accounts of a person's life." }, 11 | { "genre": "History", "description": "Records of past events." }, 12 | { "genre": "Science", "description": "Deals with the natural world and its phenomena." }, 13 | { "genre": "Self-Help", "description": "Offers advice or guidance on personal development." }, 14 | { "genre": "Business/Economics", "description": "Related to business, finance, and economics." }, 15 | { "genre": "Cookbook", "description": "Contains recipes for preparing food." }, 16 | { "genre": "Travel", "description": "Describes places and cultures." } 17 | ] -------------------------------------------------------------------------------- /src/main/resources/prompts/initial-message.st: -------------------------------------------------------------------------------- 1 | "Please provide a concise summary covering the key points of the following text. 2 | TEXT: {content} 3 | SUMMARY: -------------------------------------------------------------------------------- /src/main/resources/prompts/refine-message.st: -------------------------------------------------------------------------------- 1 | "Taking the following context delimited by triple backquotes into consideration ```{context}```, 2 | Write a concise summary, covering the key points of the following text delimited by triple backquotes. ```${content}``` CONCISE SUMMARY:} -------------------------------------------------------------------------------- /src/main/resources/prompts/subsummary-message.st: -------------------------------------------------------------------------------- 1 | Taking the following context delimited by triple backquotes into consideration 2 | 3 | ```{context}``` 4 | 5 | Write a concise summary of the following text delimited by triple backquotes. 6 | 7 | ```{content}``` 8 | 9 | Output starts with CONCISE SUB-SUMMARY: -------------------------------------------------------------------------------- /src/main/resources/prompts/subsummary-overlap-message.st: -------------------------------------------------------------------------------- 1 | Write a concise summary of the following text delimited by triple backquotes. 2 | 3 | ```{content}``` 4 | 5 | Output starts with CONCISE SUB-SUMMARY: -------------------------------------------------------------------------------- /src/main/resources/prompts/summary-message.st: -------------------------------------------------------------------------------- 1 | Strictly please give me a summary with an introduction, three one sentence bullet points, and a conclusion from the following text delimited by triple backquotes. 2 | 3 | ```Text:{content}``` 4 | 5 | Output starts with SUMMARY: -------------------------------------------------------------------------------- /src/main/resources/prompts/system-message.st: -------------------------------------------------------------------------------- 1 | "You are a helpful AI assistant. 2 | You are an AI assistant that helps people summarize information. 3 | Your name is {name} 4 | You should reply to the user's request with your name and also in the style of a {voice}. -------------------------------------------------------------------------------- /src/main/resources/prompts/system-summary-message.st: -------------------------------------------------------------------------------- 1 | "You are a helpful AI assistant. 2 | You are an AI assistant that helps people summarize information in a concise way. 3 | Strictly ignore Project Gutenberg & ignore copyright notice in summary output. --------------------------------------------------------------------------------