├── images ├── UI-app.png └── ArchAgentUI.png ├── src ├── main │ ├── resources │ │ ├── templates │ │ │ ├── history-geography-tool-system.txt │ │ │ ├── user-message.txt │ │ │ ├── currency-manager-system.txt │ │ │ ├── chat-service-system.txt │ │ │ ├── user-messages-with-sources.txt │ │ │ └── agentic-rag-service-system.txt │ │ ├── capitals │ │ │ └── Berlin_article.txt │ │ └── application.properties │ ├── frontend │ │ ├── index.html │ │ └── views │ │ │ ├── index.css │ │ │ └── @index.tsx │ └── java │ │ └── ai │ │ └── patterns │ │ ├── data │ │ ├── TopicReport.java │ │ ├── CapitalDataRAG.java │ │ └── CapitalData.java │ │ ├── web │ │ ├── endpoints │ │ │ ├── package-info.java │ │ │ ├── AgenticRAGEndpoint.java │ │ │ └── ChatEndpoint.java │ │ └── StartupController.java │ │ ├── actuator │ │ ├── CustomData.java │ │ ├── HealthCheck.java │ │ └── StartupCheck.java │ │ ├── config │ │ ├── Config.java │ │ ├── WebMvcConfig.java │ │ └── DataSourceConfig.java │ │ ├── AIPatternsWebApplication.java │ │ ├── utils │ │ ├── ChatUtils.java │ │ ├── Models.java │ │ └── Ansi.java │ │ ├── tools │ │ ├── HistoryGeographyToolOllama.java │ │ ├── HistoryGeographyTool.java │ │ ├── TouristBureauMCPTool.java │ │ └── TouristBureauMCPLocalTool.java │ │ └── services │ │ └── AgenticRAGService.java └── test │ └── java │ └── ai │ └── patterns │ └── AIPatternsWebApplicationTests.java ├── vite.config.ts ├── ollama-cloud-run ├── Dockerfile └── README.md ├── dataingestion ├── src │ ├── main │ │ ├── java │ │ │ └── ai │ │ │ │ └── patterns │ │ │ │ ├── CapitalDataRAG.java │ │ │ │ ├── CapitalData.java │ │ │ │ ├── utils │ │ │ │ ├── CapitalsFileLoader.java │ │ │ │ └── Ansi.java │ │ │ │ ├── dbingestion │ │ │ │ ├── CapitalService.java │ │ │ │ ├── HypotheticalQuestionsEmbeddingService.java │ │ │ │ ├── ContextualRetrievalEmbeddingService.java │ │ │ │ └── HierarchicalEmbeddingService.java │ │ │ │ └── DbingestionApplication.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── ai │ │ └── patterns │ │ └── dbingestion │ │ ├── TestDbingestionApplication.java │ │ ├── DbingestionApplicationTests.java │ │ └── TestcontainersConfiguration.java ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── pom.xml └── mvnw.cmd ├── mcp ├── mcp-file-server │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── application.properties │ │ │ │ └── capitals │ │ │ │ │ └── Berlin_article.txt │ │ │ └── java │ │ │ │ └── mcp │ │ │ │ └── file │ │ │ │ └── server │ │ │ │ ├── McpFileServerApplication.java │ │ │ │ └── FileService.java │ │ └── test │ │ │ └── java │ │ │ └── mcp │ │ │ └── file │ │ │ └── server │ │ │ ├── ClientSse.java │ │ │ └── SampleClient.java │ ├── .mvn │ │ └── wrapper │ │ │ └── maven-wrapper.properties │ ├── README.md │ ├── pom.xml │ └── mvnw.cmd └── mcp-weather-server │ ├── src │ ├── main │ │ ├── resources │ │ │ └── application.properties │ │ └── java │ │ │ └── mcp │ │ │ └── file │ │ │ └── server │ │ │ ├── McpServerApplication.java │ │ │ └── WeatherService.java │ └── test │ │ └── java │ │ └── mcp │ │ └── file │ │ └── server │ │ ├── ClientSse.java │ │ └── SampleClient.java │ ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties │ ├── README.md │ ├── pom.xml │ └── mvnw.cmd ├── types.d.ts ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── data ├── Database.md └── tables.ddl ├── .gitignore ├── tsconfig.json ├── README.md ├── HELP.md ├── demo ├── DayOf.md └── DemoPrompts.md └── package.json /images/UI-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddobrin/ai-patterns-ui/HEAD/images/UI-app.png -------------------------------------------------------------------------------- /images/ArchAgentUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddobrin/ai-patterns-ui/HEAD/images/ArchAgentUI.png -------------------------------------------------------------------------------- /src/main/resources/templates/history-geography-tool-system.txt: -------------------------------------------------------------------------------- 1 | You are a knowledgeable history and geography assistant who knows how to succinctly summarize a topic. 2 | 3 | Summarize the information for the topic asked by the user. -------------------------------------------------------------------------------- /src/main/resources/templates/user-message.txt: -------------------------------------------------------------------------------- 1 | Here's the question from the user: 2 | 3 | {{userMessage}} 4 | 5 | 6 | Answer the question using the following information: 7 | 8 | {{contents}} 9 | 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfigFn } from 'vite'; 2 | import { overrideVaadinConfig } from './vite.generated'; 3 | 4 | const customConfig: UserConfigFn = (env) => ({ 5 | // Here you can add custom Vite parameters 6 | // https://vitejs.dev/config/ 7 | }); 8 | 9 | export default overrideVaadinConfig(customConfig); 10 | -------------------------------------------------------------------------------- /ollama-cloud-run/Dockerfile: -------------------------------------------------------------------------------- 1 | From ollama/ollama 2 | 3 | ENV HOME /root 4 | 5 | WORKDIR / 6 | 7 | ENV OLLAMA_FLASH_ATTENTION 1 8 | 9 | # Pull models from Ollama. See https://ollama.com/library for the full list of available models. 10 | RUN ollama serve & sleep 10 && ollama pull gemma3:4b 11 | 12 | Entrypoint ["ollama", "serve"] 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/currency-manager-system.txt: -------------------------------------------------------------------------------- 1 | You are a Currency Archive assistant who has access to current exchange rates and knows how to succinctly respond to a topic 2 | 3 | Write a brief summary of the currency requested by the user and the current exchange rate from USD to that currency and the date of the last exchange rate. -------------------------------------------------------------------------------- /src/main/resources/templates/chat-service-system.txt: -------------------------------------------------------------------------------- 1 | You are a knowledgeable history, geography and tourist assistant, 2 | knowing everything about the capitals of the world. 3 | 4 | Your role is to write reports about a particular location or event, 5 | focusing on the key topics asked by the user. 6 | 7 | Let us focus on world capitals today 8 | 9 | {{systemMessage}} -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/CapitalDataRAG.java: -------------------------------------------------------------------------------- 1 | package ai.patterns; 2 | 3 | import java.util.List; 4 | 5 | public record CapitalDataRAG( 6 | CapitalData capitalData, 7 | EmbedType embedType, 8 | List content, 9 | String chunk 10 | ) { 11 | public enum EmbedType{ 12 | HIERARCHICAL, 13 | HYPOTHETICAL, 14 | CONTEXTUAL, 15 | LATE 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dataingestion/src/test/java/ai/patterns/dbingestion/TestDbingestionApplication.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.dbingestion; 2 | 3 | import ai.patterns.DbingestionApplication; 4 | import org.springframework.boot.SpringApplication; 5 | 6 | public class TestDbingestionApplication { 7 | 8 | public static void main(String[] args) { 9 | SpringApplication.from(DbingestionApplication::main).with(TestcontainersConfiguration.class).run(args); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /dataingestion/src/test/java/ai/patterns/dbingestion/DbingestionApplicationTests.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.dbingestion; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @Import(TestcontainersConfiguration.class) 8 | @SpringBootTest 9 | class DbingestionApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/templates/user-messages-with-sources.txt: -------------------------------------------------------------------------------- 1 | Here's the question from the user: 2 | 3 | {{userMessage}} 4 | 5 | 6 | Answer the question using the following information: 7 | 8 | {{contents}} 9 | 10 | 11 | Please add at the end of your answer, the following String as-is, for reference purposes: 12 | --------------------- 13 | ===== SOURCES ===== 14 | 15 | {{sources}} 16 | 17 | --------------------- -------------------------------------------------------------------------------- /mcp/mcp-file-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # spring.main.web-application-type=none 2 | 3 | server.port=${PORT:8085} 4 | 5 | # NOTE: You must disable the banner and the console logging 6 | # to allow the STDIO transport to work !!! 7 | spring.main.banner-mode=off 8 | # logging.pattern.console= 9 | 10 | # spring.ai.mcp.server.stdio=false 11 | 12 | spring.ai.mcp.server.name=mcp-file-server 13 | spring.ai.mcp.server.version=0.0.1 14 | 15 | #logging.file.name=./model-context-protocol/sampling/mcp-weather-webmvc-server/target/mcp-sampling-server.log 16 | -------------------------------------------------------------------------------- /mcp/mcp-weather-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # spring.main.web-application-type=none 2 | 3 | server.port=${PORT:8083} 4 | 5 | # NOTE: You must disable the banner and the console logging 6 | # to allow the STDIO transport to work !!! 7 | spring.main.banner-mode=off 8 | # logging.pattern.console= 9 | 10 | # spring.ai.mcp.server.stdio=false 11 | 12 | spring.ai.mcp.server.name=mcp-weather-server 13 | spring.ai.mcp.server.version=0.0.1 14 | 15 | #logging.file.name=./model-context-protocol/sampling/mcp-weather-webmvc-server/target/mcp-sampling-server.log 16 | -------------------------------------------------------------------------------- /src/main/resources/templates/agentic-rag-service-system.txt: -------------------------------------------------------------------------------- 1 | You are a knowledgeable history, geography and tourist assistant, 2 | knowing everything about the capitals of the world. 3 | 4 | Your role is to write reports about a particular location or event, 5 | focusing on the key topics asked by the user. 6 | 7 | Think step by step: 8 | 1) Identify the key topics the user is interested 9 | 2) For each topic, devise a list of questions corresponding to those topics 10 | 3) Search those questions in the database 11 | 4) Collect all those answers together, and create the final report. 12 | 13 | {{systemMessage}} -------------------------------------------------------------------------------- /src/main/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/data/TopicReport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.data; 17 | 18 | public record TopicReport(String topic, String report) {} -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | // This TypeScript modules definition file is generated by vaadin-maven-plugin. 2 | // You can not directly import your different static files into TypeScript, 3 | // This is needed for TypeScript compiler to declare and export as a TypeScript module. 4 | // It is recommended to commit this file to the VCS. 5 | // You might want to change the configurations to fit your preferences 6 | declare module '*.css?inline' { 7 | import type { CSSResultGroup } from 'lit'; 8 | const content: CSSResultGroup; 9 | export default content; 10 | } 11 | 12 | // Allow any CSS Custom Properties 13 | declare module 'csstype' { 14 | interface Properties { 15 | [index: `--${string}`]: any; 16 | } 17 | } 18 | 19 | declare module '*.svg?url' { 20 | const value: string; 21 | export = value; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/capitals/Berlin_article.txt: -------------------------------------------------------------------------------- 1 | Berlin 2 | 3 | Berlin is the capital and largest city of Germany, both by area and by population. Its more than 3.85 million inhabitants make it the European Union's most populous city, as measured by population within city limits. The city is also one of the states of Germany, and is the third smallest state in the country in terms of area. Berlin is surrounded by the state of Brandenburg, and Brandenburg's capital Potsdam is nearby. The urban area of Berlin has a population of over 4.5 million and is therefore the most populous urban area in Germany. The Berlin-Brandenburg capital region has around 6.2 million inhabitants and is Germany's second-largest metropolitan region after the Rhine-Ruhr region, and the sixth-biggest metropolitan region by GDP in the European Union. -------------------------------------------------------------------------------- /src/main/java/ai/patterns/web/endpoints/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 | @NonNullApi 17 | package ai.patterns.web.endpoints; 18 | 19 | import org.springframework.lang.NonNullApi; -------------------------------------------------------------------------------- /mcp/mcp-file-server/src/main/resources/capitals/Berlin_article.txt: -------------------------------------------------------------------------------- 1 | Berlin 2 | 3 | Berlin is the capital and largest city of Germany, both by area and by population. Its more than 3.85 million inhabitants make it the European Union's most populous city, as measured by population within city limits. The city is also one of the states of Germany, and is the third smallest state in the country in terms of area. Berlin is surrounded by the state of Brandenburg, and Brandenburg's capital Potsdam is nearby. The urban area of Berlin has a population of over 4.5 million and is therefore the most populous urban area in Germany. The Berlin-Brandenburg capital region has around 6.2 million inhabitants and is Germany's second-largest metropolitan region after the Rhine-Ruhr region, and the sixth-biggest metropolitan region by GDP in the European Union. -------------------------------------------------------------------------------- /dataingestion/src/test/java/ai/patterns/dbingestion/TestcontainersConfiguration.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.dbingestion; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 5 | import org.springframework.context.annotation.Bean; 6 | import org.testcontainers.containers.PostgreSQLContainer; 7 | import org.testcontainers.utility.DockerImageName; 8 | 9 | @TestConfiguration(proxyBeanMethods = false) 10 | class TestcontainersConfiguration { 11 | 12 | @Bean 13 | @ServiceConnection 14 | PostgreSQLContainer pgvectorContainer() { 15 | return new PostgreSQLContainer<>(DockerImageName.parse("pgvector/pgvector:pg16")); 16 | } 17 | 18 | @Bean 19 | @ServiceConnection 20 | PostgreSQLContainer postgresContainer() { 21 | return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest")); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/ai/patterns/AIPatternsWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.springframework.boot.test.context.SpringBootTest; 20 | 21 | @SpringBootTest 22 | class AIPatternsWebApplicationTests { 23 | 24 | @Test 25 | void contextLoads() { 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/data/CapitalDataRAG.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.data; 17 | 18 | import java.util.List; 19 | 20 | public record CapitalDataRAG( 21 | CapitalData capitalData, 22 | EmbedType embedType, 23 | List content, 24 | String chunk 25 | ) { 26 | public enum EmbedType{ 27 | HIERARCHICAL, 28 | HYPOTHETICAL, 29 | CONTEXTUAL, 30 | LATE 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.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.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /data/Database.md: -------------------------------------------------------------------------------- 1 | # Working with AlloyDB in the App 2 | 3 | # Install the AlloyDB proxy 4 | [Connect using the AlloyDB Auth Proxy](https://cloud.google.com/alloydb/docs/auth-proxy/connect#psql) 5 | [Main Postgres PSQL commands](https://www.geeksforgeeks.org/postgresql-psql-commands/) 6 | 7 | ```shell 8 | curl -o alloydb-auth-proxy https://storage.googleapis.com/alloydb-auth-proxy/v1.12.2/alloydb-auth-proxy.darwin.arm64 9 | 10 | chmod +x alloydb-auth-proxy 11 | 12 | gcloud alloydb instances list 13 | 14 | # Project = genai-playground24 15 | # Location = us-central1 16 | 17 | # Start with 18 | ./alloydb-auth-proxy projects//locations/us-central1/clusters/alloydb-aip-01/instances/alloydb-aip-01-pr --public-ip 19 | 20 | #--- connectivity checks --- 21 | # Start psql for connectivity check 22 | psql -h 127.0.0.1 -p 5432 -U postgres -d library -W 23 | 24 | select * from public.continents; 25 | select * from public.capitals; 26 | 27 | select capital, country 28 | 29 | # describe tables 30 | \d public.capitals 31 | \d public. continents 32 | `` -------------------------------------------------------------------------------- /dataingestion/.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.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /mcp/mcp-file-server/.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.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /mcp/mcp-weather-server/.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.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /ollama-cloud-run/README.md: -------------------------------------------------------------------------------- 1 | ### Instractions to Build and deploy Ollama Container to Cloud Run with pre downloaded model: 2 | 3 | 1. **Make sure you have Cloud Run GPU quota:** 4 | 5 | - To view and request quota http://g.co/cloudrun/gpu-quota 6 | - To learn more about GPU redundancy option: http://g.co/cloudrun/gpu-redundancy-help 7 | 8 | 9 | 2. **Update Dockerfile with the desiered model:** 10 | 11 | - Edit `Dockerfile` to indicate the desiered model 12 | - See https://ollama.com/library for the full list of available models. 13 | 14 | 3. **Deploy to Cloud Run:** 15 | 16 | - `gcloud beta run deploy --source . ollama-gemma4b --gpu-type=nvidia-l4 --region=us-central1 --port=11434 --no-gpu-zonal-redundancy --allow-unauthenticated --project=[$PROJECT_ID]` 17 | 18 | 4. **Learn more:** 19 | 20 | - Cloud Run GPUs: https://cloud.google.com/run/docs/configuring/services/gpu 21 | - Full tutorial for Ollama on Cloud Run: https://cloud.google.com/run/docs/tutorials/gpu-gemma-with-ollama 22 | - Ollama: https://github.com/ollama/ollama 23 | -------------------------------------------------------------------------------- /dataingestion/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=dbingestion 2 | 3 | # Database Configuration 4 | spring.datasource.url=jdbc:postgresql://localhost:5432/library 5 | spring.datasource.username=postgres 6 | spring.datasource.password=${ALLOY_DB_PASSWORD} 7 | spring.datasource.driver-class-name=org.postgresql.Driver 8 | 9 | # HikariCP Connection Pool Settings 10 | spring.datasource.hikari.maximum-pool-size=10 11 | spring.datasource.hikari.minimum-idle=5 12 | spring.datasource.hikari.idle-timeout=30000 13 | spring.datasource.hikari.connection-timeout=30000 14 | spring.datasource.hikari.max-lifetime=1800000 15 | 16 | # JPA Properties (if you're using JPA alongside JDBC) 17 | spring.jpa.hibernate.ddl-auto=none 18 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 19 | spring.jpa.show-sql=true 20 | 21 | spring.jta.enabled=false 22 | spring.transaction.default-timeout=30 23 | spring.transaction.rollback-on-commit-failure=true 24 | 25 | # Logging 26 | logging.level.org.springframework.jdbc.core=DEBUG 27 | logging.level.com.zaxxer.hikari=INFO 28 | logging.level.com.example.capitals=DEBUG -------------------------------------------------------------------------------- /mcp/mcp-file-server/src/test/java/mcp/file/server/ClientSse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - 2024 the original author or authors. 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 mcp.file.server; 17 | 18 | import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; 19 | 20 | 21 | /** 22 | * @author Christian Tzolov 23 | */ 24 | public class ClientSse { 25 | 26 | public static void main(String[] args) throws Exception { 27 | var transport = new HttpClientSseClientTransport("http://localhost:8085"); 28 | new SampleClient(transport).run(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /mcp/mcp-weather-server/src/test/java/mcp/file/server/ClientSse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - 2024 the original author or authors. 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 mcp.file.server; 17 | 18 | import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; 19 | 20 | 21 | /** 22 | * @author Christian Tzolov 23 | */ 24 | public class ClientSse { 25 | 26 | public static void main(String[] args) throws Exception { 27 | var transport = new HttpClientSseClientTransport("http://localhost:8083"); 28 | new SampleClient(transport).run(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/actuator/CustomData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 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 ai.patterns.actuator; 17 | 18 | import com.fasterxml.jackson.annotation.JsonAnyGetter; 19 | import com.fasterxml.jackson.annotation.JsonInclude; 20 | import java.util.Map; 21 | 22 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 23 | public class CustomData { 24 | private Map data; 25 | 26 | @JsonAnyGetter 27 | public Map getData() { 28 | return this.data; 29 | } 30 | 31 | public void setData(Map data) { 32 | this.data = data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/config/Config.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.config; 17 | 18 | import ai.patterns.base.AbstractBase; 19 | import dev.langchain4j.memory.chat.ChatMemoryProvider; 20 | import dev.langchain4j.memory.chat.MessageWindowChatMemory; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | 24 | @Configuration 25 | public class Config extends AbstractBase { 26 | @Bean 27 | ChatMemoryProvider chatMemoryProvider() { 28 | return chatId -> MessageWindowChatMemory.withMaxMessages(10000); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/CapitalData.java: -------------------------------------------------------------------------------- 1 | package ai.patterns; 2 | 3 | import java.util.List; 4 | 5 | public record CapitalData( 6 | CapitalCity capitalCity, 7 | Country country, 8 | List continents, 9 | Article article 10 | ) { 11 | public record CapitalCity(String city, String link) { } 12 | public record Country(String country, String link) { } 13 | public record Article(String markdown, String html, String text) { } 14 | 15 | public enum Continent { 16 | AFRICA("Africa"), 17 | ANTARCTICA("Antarctica"), 18 | ASIA("Asia"), 19 | EUROPE("Europe"), 20 | NORTH_AMERICA("North America"), 21 | OCEANIA("Oceania"), 22 | SOUTH_AMERICA("South America"); 23 | 24 | private final String displayName; 25 | 26 | Continent() { 27 | this.displayName = name(); 28 | } 29 | 30 | Continent(String displayName) { 31 | this.displayName = displayName; 32 | } 33 | 34 | public String getDisplayName() { 35 | return displayName; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return displayName; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | vaadin.launch-browser=true 2 | spring.application.name=examples 3 | #application.properties 4 | 5 | # Database Configuration 6 | # local 7 | #spring.datasource.url=jdbc:postgresql://localhost:5432/library 8 | #spring.datasource.username=postgres 9 | 10 | spring.datasource.url=${ALLOY_DB_URL} 11 | spring.datasource.username=${ALLOY_DB_USERNAME} 12 | spring.datasource.password=${ALLOY_DB_PASSWORD} 13 | spring.datasource.driver-class-name=org.postgresql.Driver 14 | 15 | # HikariCP Connection Pool Settings 16 | spring.datasource.hikari.maximum-pool-size=10 17 | spring.datasource.hikari.minimum-idle=5 18 | spring.datasource.hikari.idle-timeout=30000 19 | spring.datasource.hikari.connection-timeout=30000 20 | spring.datasource.hikari.max-lifetime=1800000 21 | 22 | # JPA Properties (if you're using JPA alongside JDBC) 23 | spring.jpa.hibernate.ddl-auto=none 24 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 25 | spring.jpa.show-sql=true 26 | 27 | spring.jta.enabled=false 28 | spring.transaction.default-timeout=30 29 | spring.transaction.rollback-on-commit-failure=true 30 | 31 | # Logging 32 | logging.level.org.springframework.jdbc.core=INFO 33 | logging.level.com.zaxxer.hikari=INFO 34 | logging.level.ai.patterns=DEBUG -------------------------------------------------------------------------------- /src/main/java/ai/patterns/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 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 ai.patterns.config; 17 | 18 | import org.springframework.context.annotation.Configuration; 19 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 20 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 21 | 22 | /** 23 | * Configure endpoints for a different URI 24 | */ 25 | @Configuration 26 | public class WebMvcConfig implements WebMvcConfigurer { 27 | 28 | @Override 29 | public void addViewControllers(ViewControllerRegistry registry) { 30 | registry.addRedirectViewController("/startup", "/actuator/health"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/web/StartupController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 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 ai.patterns.web; 17 | 18 | import org.springframework.beans.factory.annotation.Value; 19 | import org.springframework.web.bind.annotation.GetMapping; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | /** 23 | * Controller to test that the app has been started 24 | */ 25 | @RestController 26 | public class StartupController { 27 | 28 | @Value("${target:local}") 29 | String target; 30 | 31 | @GetMapping("/start") 32 | public String start() 33 | { 34 | return String.format("App started in your %s environment!", target); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | .idea/* 3 | *.iml 4 | 5 | # Eclipse files 6 | .project 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | .recommenders 17 | 18 | # VS Code 19 | .vscode/* 20 | *.code-workspace 21 | 22 | # OSX 23 | **/.DS_Store 24 | 25 | # Environments 26 | .env 27 | .venv 28 | env/ 29 | venv/ 30 | ENV/ 31 | env.bak/ 32 | venv.bak/ 33 | /env.sh 34 | 35 | # Local .terraform directories 36 | **/.terraform/* 37 | 38 | # .tfstate files 39 | *.tfstate 40 | *.tfstate.* 41 | 42 | # Crash log files 43 | crash.log 44 | crash.*.log 45 | 46 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 47 | # password, private keys, and other secrets. These should not be part of version 48 | # control as they are data points which are potentially sensitive and subject 49 | # to change depending on the environment. 50 | *.tfvars 51 | *.tfvars.json 52 | 53 | # Language Specific 54 | # ... 55 | **/target/* 56 | genai/image-vision-vertex-langchain/test-results-native/test/TEST-junit-jupiter.xml 57 | sessions/next24/books-genai-vertex/.vscode/settings.json 58 | 59 | # ignore build files 60 | # build.sh 61 | # build-native.sh 62 | 63 | .vscode/ 64 | 65 | *.jsa 66 | 67 | # Vaadin 68 | src/main/frontend/generated/* 69 | node_modules/ 70 | bundles/ -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // This TypeScript configuration file is generated by vaadin-maven-plugin. 2 | // This is needed for TypeScript compiler to compile your TypeScript code in the project. 3 | // It is recommended to commit this file to the VCS. 4 | // You might want to change the configurations to fit your preferences 5 | // For more information about the configurations, please refer to http://www.typescriptlang.org/docs/handbook/tsconfig-json.html 6 | { 7 | "_version": "9.1", 8 | "compilerOptions": { 9 | "sourceMap": true, 10 | "jsx": "react-jsx", 11 | "inlineSources": true, 12 | "module": "esNext", 13 | "target": "es2022", 14 | "moduleResolution": "bundler", 15 | "strict": true, 16 | "skipLibCheck": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitReturns": true, 19 | "noImplicitAny": true, 20 | "noImplicitThis": true, 21 | "noUnusedLocals": false, 22 | "noUnusedParameters": false, 23 | "experimentalDecorators": true, 24 | "useDefineForClassFields": false, 25 | "baseUrl": "src/main/frontend", 26 | "paths": { 27 | "@vaadin/flow-frontend": ["generated/jar-resources"], 28 | "@vaadin/flow-frontend/*": ["generated/jar-resources/*"], 29 | "Frontend/*": ["*"] 30 | } 31 | }, 32 | "include": [ 33 | "src/main/frontend/**/*", 34 | "types.d.ts" 35 | ], 36 | "exclude": [ 37 | "src/main/frontend/generated/jar-resources/**" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /mcp/mcp-file-server/src/main/java/mcp/file/server/McpFileServerApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 mcp.file.server; 17 | 18 | import org.springframework.ai.tool.ToolCallbackProvider; 19 | import org.springframework.ai.tool.method.MethodToolCallbackProvider; 20 | import org.springframework.boot.SpringApplication; 21 | import org.springframework.boot.autoconfigure.SpringBootApplication; 22 | import org.springframework.context.annotation.Bean; 23 | 24 | @SpringBootApplication 25 | public class McpFileServerApplication { 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(McpFileServerApplication.class, args); 29 | } 30 | 31 | @Bean 32 | public ToolCallbackProvider fileTools(FileService fileService) { 33 | return MethodToolCallbackProvider.builder().toolObjects(fileService).build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mcp/mcp-weather-server/src/main/java/mcp/file/server/McpServerApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 mcp.file.server; 17 | 18 | import org.springframework.ai.tool.ToolCallbackProvider; 19 | import org.springframework.ai.tool.method.MethodToolCallbackProvider; 20 | import org.springframework.boot.SpringApplication; 21 | import org.springframework.boot.autoconfigure.SpringBootApplication; 22 | import org.springframework.context.annotation.Bean; 23 | 24 | @SpringBootApplication 25 | public class McpServerApplication { 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(McpServerApplication.class, args); 29 | } 30 | 31 | @Bean 32 | public ToolCallbackProvider weatherTools(WeatherService weatherService) { 33 | return MethodToolCallbackProvider.builder().toolObjects(weatherService).build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mcp/mcp-file-server/README.md: -------------------------------------------------------------------------------- 1 | # Spring AI MCP Weather Server Sample with WebMVC Starter 2 | 3 | ## Dependencies 4 | 5 | The project requires the Spring AI MCP Server WebMVC Boot Starter: 6 | 7 | ```xml 8 | 9 | org.springframework.ai 10 | spring-ai-starter-mcp-server-webmvc 11 | 12 | ``` 13 | 14 | ## Building the Project 15 | 16 | Build the project using Maven: 17 | ```bash 18 | ./mvnw clean install -DskipTests 19 | ``` 20 | 21 | ## Running the Server 22 | 23 | The server supports two transport modes: 24 | 25 | ### WebMVC SSE Mode (Default) 26 | ```bash 27 | java -jar target/mcp-weather-server-0.0.1.jar 28 | ``` 29 | 30 | ## Configuration 31 | 32 | Configure the server through `application.properties`: 33 | 34 | ```properties 35 | # Server identification 36 | spring.ai.mcp.server.name=my-weather-server 37 | spring.ai.mcp.server.version=0.0.1 38 | 39 | # Server type (SYNC/ASYNC) 40 | spring.ai.mcp.server.type=SYNC 41 | 42 | # Transport configuration 43 | spring.ai.mcp.server.stdio=false 44 | spring.ai.mcp.server.sse-message-endpoint=/mcp/message 45 | 46 | # Change notifications 47 | spring.ai.mcp.server.resource-change-notification=true 48 | spring.ai.mcp.server.tool-change-notification=true 49 | spring.ai.mcp.server.prompt-change-notification=true 50 | 51 | # Logging (required for STDIO transport) 52 | spring.main.banner-mode=off 53 | logging.file.name=./target/starter-webmvc-server.log 54 | ``` 55 | -------------------------------------------------------------------------------- /mcp/mcp-weather-server/README.md: -------------------------------------------------------------------------------- 1 | # Spring AI MCP Weather Server Sample with WebMVC Starter 2 | 3 | ## Dependencies 4 | 5 | The project requires the Spring AI MCP Server WebMVC Boot Starter: 6 | 7 | ```xml 8 | 9 | org.springframework.ai 10 | spring-ai-starter-mcp-server-webmvc 11 | 12 | ``` 13 | 14 | ## Building the Project 15 | 16 | Build the project using Maven: 17 | ```bash 18 | ./mvnw clean install -DskipTests 19 | ``` 20 | 21 | ## Running the Server 22 | 23 | The server supports two transport modes: 24 | 25 | ### WebMVC SSE Mode (Default) 26 | ```bash 27 | java -jar target/mcp-weather-server-0.0.1.jar 28 | ``` 29 | 30 | ## Configuration 31 | 32 | Configure the server through `application.properties`: 33 | 34 | ```properties 35 | # Server identification 36 | spring.ai.mcp.server.name=my-weather-server 37 | spring.ai.mcp.server.version=0.0.1 38 | 39 | # Server type (SYNC/ASYNC) 40 | spring.ai.mcp.server.type=SYNC 41 | 42 | # Transport configuration 43 | spring.ai.mcp.server.stdio=false 44 | spring.ai.mcp.server.sse-message-endpoint=/mcp/message 45 | 46 | # Change notifications 47 | spring.ai.mcp.server.resource-change-notification=true 48 | spring.ai.mcp.server.tool-change-notification=true 49 | spring.ai.mcp.server.prompt-change-notification=true 50 | 51 | # Logging (required for STDIO transport) 52 | spring.main.banner-mode=off 53 | logging.file.name=./target/starter-webmvc-server.log 54 | ``` 55 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/web/endpoints/AgenticRAGEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.web.endpoints; 17 | 18 | import ai.patterns.services.AgenticRAGService; 19 | import ai.patterns.utils.ChatUtils.ChatOptions; 20 | import com.vaadin.flow.server.auth.AnonymousAllowed; 21 | import com.vaadin.hilla.BrowserCallable; 22 | 23 | @BrowserCallable 24 | @AnonymousAllowed 25 | public class AgenticRAGEndpoint { 26 | private final AgenticRAGService agenticRAGService; 27 | 28 | public AgenticRAGEndpoint(AgenticRAGService agenticRAGService){ 29 | this.agenticRAGService = agenticRAGService; 30 | } 31 | 32 | public String callAgent(String chatId, 33 | String systemMessage, 34 | String userMessage, 35 | ChatOptions options){ 36 | return agenticRAGService.callAgent(chatId, systemMessage, userMessage, options); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/AIPatternsWebApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | @SpringBootApplication 22 | public class AIPatternsWebApplication { 23 | 24 | public static void main(String[] args) { 25 | Runtime r = Runtime.getRuntime(); 26 | System.out.println("Runtime Data:"); 27 | System.out.println("QuotesApplication: Active processors: " + r.availableProcessors()); 28 | System.out.println("QuotesApplication: Total memory: " + r.totalMemory()); 29 | System.out.println("QuotesApplication: Free memory: " + r.freeMemory()); 30 | System.out.println("QuotesApplication: Max memory: " + r.maxMemory()); 31 | 32 | SpringApplication.run(AIPatternsWebApplication.class, args); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/actuator/HealthCheck.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 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 ai.patterns.actuator; 17 | 18 | import org.springframework.boot.actuate.health.Health; 19 | import org.springframework.boot.actuate.health.HealthIndicator; 20 | import org.springframework.stereotype.Component; 21 | 22 | /** 23 | * Implements a Custom Healthcheck for the Quotes App 24 | */ 25 | @Component("customHealthCheck") 26 | public class HealthCheck implements HealthIndicator { 27 | 28 | @Override 29 | public Health health() { 30 | int errorCode = check(); // perform some specific health check 31 | 32 | if (errorCode != 0) { 33 | return Health.down() 34 | .withDetail("Custom Health Check Status - failed. Error Code", errorCode).build(); 35 | } 36 | return Health.up().withDetail("Custom Health Check Status", "passed").build(); 37 | } 38 | 39 | public int check() { 40 | // custom logic - check health 41 | return 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/data/CapitalData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.data; 17 | 18 | import java.util.List; 19 | 20 | public record CapitalData( 21 | CapitalCity capitalCity, 22 | Country country, 23 | List continents, 24 | Article article 25 | ) { 26 | public record CapitalCity(String city, String link) { } 27 | public record Country(String country, String link) { } 28 | public record Article(String markdown, String html, String text) { } 29 | 30 | public enum Continent { 31 | AFRICA("Africa"), 32 | ANTARCTICA("Antarctica"), 33 | ASIA("Asia"), 34 | EUROPE("Europe"), 35 | NORTH_AMERICA("North America"), 36 | OCEANIA("Oceania"), 37 | SOUTH_AMERICA("South America"); 38 | 39 | private final String displayName; 40 | 41 | Continent() { 42 | this.displayName = name(); 43 | } 44 | 45 | Continent(String displayName) { 46 | this.displayName = displayName; 47 | } 48 | 49 | public String getDisplayName() { 50 | return displayName; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return displayName; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/actuator/StartupCheck.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 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 ai.patterns.actuator; 17 | 18 | import java.util.LinkedHashMap; 19 | import java.util.Map; 20 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 21 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 22 | import org.springframework.stereotype.Component; 23 | 24 | /** 25 | * Implements a Custom Startup Check for the Quotes App 26 | */ 27 | @Component 28 | @Endpoint(id="startup") 29 | public class StartupCheck { 30 | private static boolean status = false; 31 | 32 | public static void up(){ status = true;} 33 | public static void down(){ status = false;} 34 | 35 | @ReadOperation 36 | public CustomData customEndpoint() { 37 | Map details = new LinkedHashMap<>(); 38 | if (!status) { 39 | System.out.println("AIPatternsWebApplication Startup Endpoint: Application is ready to serve traffic !"); 40 | return null; 41 | } 42 | 43 | System.out.println("AIPatternsWebApplication Startup Endpoint: Application is ready to serve traffic !"); 44 | 45 | CustomData data = new CustomData(); 46 | details.put("StartupEndpoint", "AIPatternsWebApplication Startup Endpoint: Application is ready to serve traffic"); 47 | data.setData(details); 48 | 49 | return data; 50 | } 51 | } -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/utils/CapitalsFileLoader.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.utils; 2 | 3 | import ai.patterns.CapitalData; 4 | import com.google.common.base.Charsets; 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Properties; 12 | 13 | public class CapitalsFileLoader { 14 | 15 | public static List loadFiles(String fileType) throws IOException { 16 | Properties CAPITAL_PROPERTIES = new Properties(); 17 | CAPITAL_PROPERTIES.load(Files.newBufferedReader(Path.of("data/capitals/capital.properties"))); 18 | 19 | Path textFilesPath = Paths.get("data/capitals/" + fileType); 20 | 21 | return Files.list(textFilesPath) 22 | .map(path -> { 23 | String fileNameWithoutExtension = path.toFile().getName().replace("." + fileType, ""); 24 | 25 | String cityName = 26 | String.valueOf(CAPITAL_PROPERTIES.get(fileNameWithoutExtension + ".city")); 27 | String countryName = 28 | String.valueOf(CAPITAL_PROPERTIES.get(fileNameWithoutExtension + ".country")); 29 | List continents = 30 | Arrays.stream(CAPITAL_PROPERTIES.getProperty(fileNameWithoutExtension + ".continents").split(",")) 31 | .filter(s -> !s.isEmpty()) 32 | .map(CapitalData.Continent::valueOf) 33 | .toList(); 34 | 35 | String content = ""; 36 | try { 37 | content = String.join("\n", Files.readAllLines(path, Charsets.UTF_8)); 38 | } catch (IOException e) { 39 | throw new RuntimeException(e); 40 | } 41 | 42 | return new CapitalData( 43 | new CapitalData.CapitalCity(cityName, ""), 44 | new CapitalData.Country(countryName, ""), 45 | continents, 46 | new CapitalData.Article(content, content, content)); 47 | }).toList(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/utils/ChatUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.utils; 17 | 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | public class ChatUtils { 22 | 23 | public static @NotNull ChatOptions getDefaultChatOptions(String model) { 24 | @Nullable ChatUtils.@Nullable ChatOptions options; 25 | return new ChatOptions("", 26 | false, 27 | true, 28 | false, 29 | false, 30 | model, 31 | false, 32 | true, 33 | false, 34 | false, 35 | ChunkingType.NONE, 36 | false, 37 | false, 38 | false, 39 | false, 40 | false, 41 | false, 42 | true, 43 | "", 44 | "" 45 | ); 46 | } 47 | 48 | public enum ChunkingType { 49 | NONE, 50 | HIERARCHICAL, 51 | HYPOTHETICAL, 52 | CONTEXTUAL, 53 | LATE 54 | } 55 | 56 | public record ChatOptions( 57 | String systemMessage, 58 | boolean useTools, 59 | boolean useVertex, 60 | boolean useAgents, 61 | boolean useWebsearch, 62 | String model, 63 | boolean enableSafety, 64 | boolean useGuardrails, 65 | boolean evaluateResponse, 66 | boolean enableRAG, 67 | ChunkingType chunkingType, 68 | boolean filtering, 69 | boolean queryCompression, 70 | boolean queryRouting, 71 | boolean hyde, 72 | boolean reranking, 73 | boolean writeActions, 74 | boolean showDataSources, 75 | String capital, 76 | String continent) { 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/utils/Models.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.utils; 17 | 18 | import dev.langchain4j.model.vertexai.HarmCategory; 19 | import dev.langchain4j.model.vertexai.SafetyThreshold; 20 | import java.util.Map; 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | public class Models { 24 | public static final String MODEL_GEMINI_FLASH = "gemini-2.0-flash-001"; 25 | public static final String MODEL_GEMINI_PRO = "gemini-2.0-pro-exp-02-05"; 26 | public static final String MODEL_GEMINI_FLASH_THINKING = "gemini-2.0-flash-thinking-exp-01-21"; 27 | public static final String MODEL_GEMINI_FLASH_LITE = "gemini-2.0-flash-exp"; 28 | public static final String MODEL_GEMMA3_27B = "gemma3:27b"; 29 | public static final String MODEL_GEMMA3_12B = "gemma3:12b"; 30 | public static final String MODEL_GEMMA3_4B = "gemma3:4b"; 31 | 32 | // Embedding Models 33 | // https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api?hl=en&authuser=2 34 | public static final String MODEL_EMBEDDING_MULTILINGUAL = "text-multilingual-embedding-002"; 35 | public static final String MODEL_EMBEDDING_TEXT = "text-embedding-005"; 36 | public static final int MODEL_EMBEDDING_DIMENSION = 768; 37 | 38 | public static final int MAX_RETRIES = 3; 39 | public static final int DB_RETRIEVAL_LIMIT = 5; 40 | public static final double RERANKING_SCORE_THRESHOLD = 0.6; 41 | 42 | public static final Map<@NotNull HarmCategory, @NotNull SafetyThreshold> SAFETY_SETTINGS_OFF = Map.of( 43 | HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, SafetyThreshold.BLOCK_NONE, 44 | HarmCategory.HARM_CATEGORY_HARASSMENT, SafetyThreshold.BLOCK_NONE, 45 | HarmCategory.HARM_CATEGORY_HATE_SPEECH, SafetyThreshold.BLOCK_NONE, 46 | HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, SafetyThreshold.BLOCK_NONE 47 | ); 48 | 49 | public static final Map<@NotNull HarmCategory, @NotNull SafetyThreshold> SAFETY_SETTINGS_ON = Map.of( 50 | HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, SafetyThreshold.BLOCK_LOW_AND_ABOVE, 51 | HarmCategory.HARM_CATEGORY_HARASSMENT, SafetyThreshold.BLOCK_LOW_AND_ABOVE, 52 | HarmCategory.HARM_CATEGORY_HATE_SPEECH, SafetyThreshold.BLOCK_LOW_AND_ABOVE, 53 | HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, SafetyThreshold.BLOCK_LOW_AND_ABOVE 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /mcp/mcp-file-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 3.4.4 10 | 11 | 12 | 13 | com.example 14 | 15 | mcp-file-server 16 | 0.0.1 17 | 18 | Spring AI MCP - File Server 19 | Sample Spring Boot application demonstrating MCP client and server 20 | 21 | 22 | 23 | 24 | org.springframework.ai 25 | spring-ai-bom 26 | 1.0.0-SNAPSHOT 27 | pom 28 | import 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.ai 36 | spring-ai-starter-mcp-server-webmvc 37 | 1.0.0-SNAPSHOT 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-maven-plugin 46 | 47 | 48 | 49 | 50 | 51 | 52 | Central Portal Snapshots 53 | central-portal-snapshots 54 | https://central.sonatype.com/repository/maven-snapshots/ 55 | 56 | false 57 | 58 | 59 | true 60 | 61 | 62 | 63 | spring-milestones 64 | Spring Milestones 65 | https://repo.spring.io/milestone 66 | 67 | false 68 | 69 | 70 | 71 | spring-snapshots 72 | Spring Snapshots 73 | https://repo.spring.io/snapshot 74 | 75 | false 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /mcp/mcp-weather-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 3.4.4 10 | 11 | 12 | 13 | com.example 14 | 15 | mcp-weather-server 16 | 0.0.1 17 | 18 | Spring AI MCP - Weather Server 19 | Sample Spring Boot application demonstrating MCP client and server 20 | 21 | 22 | 23 | 24 | org.springframework.ai 25 | spring-ai-bom 26 | 1.0.0-SNAPSHOT 27 | pom 28 | import 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.ai 36 | spring-ai-starter-mcp-server-webmvc 37 | 1.0.0-SNAPSHOT 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-maven-plugin 46 | 47 | 48 | 49 | 50 | 51 | 52 | Central Portal Snapshots 53 | central-portal-snapshots 54 | https://central.sonatype.com/repository/maven-snapshots/ 55 | 56 | false 57 | 58 | 59 | true 60 | 61 | 62 | 63 | spring-milestones 64 | Spring Milestones 65 | https://repo.spring.io/milestone 66 | 67 | false 68 | 69 | 70 | 71 | spring-snapshots 72 | Spring Snapshots 73 | https://repo.spring.io/snapshot 74 | 75 | false 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Patterns w/UI front-end 2 | #### Agent RAG UI Application, run locally or deploy to Cloud Run in Serverless GCP, w/GPU support 3 | 4 | ## App in action 5 | ![Application Output](images/UI-app.png) 6 | 7 | ## App Architecture 8 | ![ArchAgentUI.png](images/ArchAgentUI.png) 9 | 10 | ### Database 11 | * Create an AlloyDB database in GCP - [Instructions](https://cloud.google.com/alloydb/docs/database-create) 12 | * Create an instance of the AlloyDB database with the following [Database DDL](data/tables.ddl) 13 | 14 | **Developer note:** use the AlloyDB proxy to run from your own development environment - [instructions](data/Database.md) 15 | 16 | ### Dependent Services 17 | * MCP Servers 18 | * MCP Weather Server - [Deployment instructions](mcp/mcp-weather-server/README.md) 19 | * MCP File Server - [Deployment instructions](mcp/mcp-file-server/README.md) 20 | * Gemma models with GPUs in Cloud Run 21 | * Ollama Container in Cloud Run with Gemma models - [Deployment instructions](ollama-cloud-run/README.md) 22 | 23 | ### Build and Deploy the Agentic RAG App 24 | ```shell 25 | # start from debugger 26 | AIPatternsWebApplication 27 | ``` 28 | 29 | Build and deploy 30 | ```shell 31 | # build from CLI and start in dev mode, changes in UI code automatically reflected 32 | ./mvnw spring-boot:run 33 | 34 | # build runnable JAR 35 | ./mvnw clean package -Pproduction 36 | 37 | java -jar target/playground-0.0.1.jar 38 | ``` 39 | 40 | #### Set environment variables 41 | ```shell 42 | export GCP_PROJECT_ID=> 43 | export GCP_PROJECT_NUM= 44 | export GCP_LOCATION=us-central1 45 | export ALLOY_DB_PASSWORD=... 46 | export ALLOY_DB_URL=jdbc:postgresql://localhost:5432/library; 47 | export ALLOY_DB_USERNAME=postgres 48 | 49 | export GEMMA_URL= - Gemma model deployed in CloudRun 50 | export TAVILY_API_KEY= - Tavily WebSearch API key for Query Routing with web search 51 | export MCP_WEATHER_SERVER= 52 | export MCP_FILE_SERVER= 53 | export GCP_TEXTEMBEDDING_MODEL=textembedding-005 54 | ``` 55 | 56 | #### Note: Deploy MCP servers and Gemma models to Cloud Run before deploying the agent (optional) 57 | 58 | #### Deploy to Cloud Run 59 | ```shell 60 | # build Docker image 61 | ./mvnw spring-boot:build-image -DskipTests -Pproduction -Dspring-boot.build-image.imageName=agentic-rag 62 | 63 | # tag image for Artifact Registry in GCP 64 | docker tag agentic-rag us-central1-docker.pkg.dev//agentic-rag/agentic-rag:latest 65 | 66 | # push image to Artifact Registry in GCP 67 | docker push us-central1-docker.pkg.dev//agentic-rag/agentic-rag:latest 68 | 69 | # Deploy image to Cloud Run 70 | gcloud run deploy agentic-rag --image us-central1-docker.pkg.dev//agentic-rag/agentic-rag:latest \ 71 | --region us-central1 \ 72 | --memory 2Gi --cpu 2 --cpu-boost --execution-environment=gen2 \ 73 | --set-env-vars="SERVER_PORT=8080,GCP_PROJECT_ID=,GCP_LOCATION=us-central1,GCP_PROJECT_NUM=,ALLOY_DB_URL=,ALLOY_DB_USERNAME=,GEMMA_URL=,MCP_WEATHER_SERVER=,MCP_FILE_SERVER=,GCP_TEXTEMBEDDING_MODEL=textembedding-005" \ 74 | --set-secrets="ALLOY_DB_PASSWORD=ALLOY_DB_SECRET:latest,TAVILY_API_KEY=TAVILY_API_KEY:latest" 75 | 76 | 77 | -------------------------------------------------------------------------------- /mcp/mcp-file-server/src/main/java/mcp/file/server/FileService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 - 2025 the original author or authors. 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 mcp.file.server; 17 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | import java.io.BufferedReader; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.io.Reader; 24 | import java.net.HttpURLConnection; 25 | import java.net.URL; 26 | import java.net.URLEncoder; 27 | import java.nio.charset.StandardCharsets; 28 | import java.nio.file.Files; 29 | import java.nio.file.Path; 30 | import java.time.LocalDateTime; 31 | import java.time.format.DateTimeFormatter; 32 | import java.util.List; 33 | 34 | import io.modelcontextprotocol.server.McpSyncServerExchange; 35 | import io.modelcontextprotocol.spec.McpSchema; 36 | import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; 37 | import io.modelcontextprotocol.spec.McpSchema.ModelPreferences; 38 | import java.util.Map; 39 | import java.util.Objects; 40 | import java.util.stream.Collectors; 41 | import org.slf4j.Logger; 42 | 43 | import org.springframework.ai.chat.model.ToolContext; 44 | import org.springframework.ai.model.ModelOptionsUtils; 45 | import org.springframework.ai.tool.annotation.Tool; 46 | import org.springframework.ai.tool.annotation.ToolParam; 47 | import org.springframework.core.io.ClassPathResource; 48 | import org.springframework.stereotype.Service; 49 | import org.springframework.util.FileCopyUtils; 50 | import org.springframework.web.client.RestClient; 51 | 52 | /** 53 | * @author Christian Tzolov, Dan Dobrin 54 | */ 55 | @Service 56 | public class FileService { 57 | 58 | private static final Logger logger = org.slf4j.LoggerFactory.getLogger(FileService.class); 59 | 60 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); 61 | 62 | @Tool(description = "Find article in the Archives") 63 | public String findArticleInArchives(@ToolParam(description = "The capital to find the article for") String capital, 64 | ToolContext toolContext) { 65 | 66 | try { 67 | System.out.println("Reading file from the Archive for capital: " + capital); 68 | 69 | String data = String.format("File not found in the archive for capital %s", capital); 70 | ClassPathResource resource = new ClassPathResource(String.format("capitals/%s_article.txt", capital)); 71 | try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) { 72 | data = FileCopyUtils.copyToString(reader); 73 | } 74 | 75 | System.out.println("File content: " + data.substring(0, Math.min(data.length(), 250))); 76 | return data; 77 | } catch (IOException e) { 78 | return "No article found in the Archive"; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/dbingestion/CapitalService.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.dbingestion; 2 | 3 | import ai.patterns.CapitalDataRAG; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | /** 12 | * Service class demonstrating usage of the CapitalDataAccessService with HikariCP 13 | */ 14 | @Service 15 | public class CapitalService { 16 | 17 | private final CapitalDataAccessService dataAccessService; 18 | 19 | @Autowired 20 | public CapitalService(CapitalDataAccessService dataAccessService) { 21 | this.dataAccessService = dataAccessService; 22 | } 23 | 24 | /** 25 | * Add a new capital with its continents 26 | */ 27 | @Transactional 28 | public CapitalDataAccessService.CapitalDetail addCapital(CapitalDataRAG capital) { 29 | return dataAccessService.insertCapitalWithContinentsAndChunks(capital); 30 | } 31 | 32 | /** 33 | * Add a new capital with its continents 34 | */ 35 | // @Transactional 36 | // public CapitalDataAccessService.CapitalDetail addCapital(String capital, String country, List continents) { 37 | // return dataAccessService.insertCapitalWithContinents( 38 | // capital, 39 | // country, 40 | // "hypothetical", // Default embed type 41 | // "Content about " + capital + "...", 42 | // "Chunk about " + capital + "...", 43 | // continents 44 | // ); 45 | // } 46 | 47 | /** 48 | * Get capital details with continents 49 | */ 50 | // public Optional getCapitalDetails(String capitalName) { 51 | // return dataAccessService.getCapitalDetailByName(capitalName); 52 | // } 53 | 54 | /** 55 | * Get all capitals in a specific continent 56 | */ 57 | public List getCapitalsInContinent(String continentName) { 58 | // Get continent ID 59 | List continents = dataAccessService.getAllContinents(); 60 | 61 | // Find matching continent 62 | Optional continent = continents.stream() 63 | .filter(c -> c.getContinentName().equalsIgnoreCase(continentName)) 64 | .findFirst(); 65 | 66 | if (continent.isPresent()) { 67 | return dataAccessService.getCapitalsForContinent(continent.get().getContinentId()); 68 | } 69 | 70 | return List.of(); // Empty list if continent not found 71 | } 72 | 73 | /** 74 | * Example usage in an application 75 | */ 76 | /* 77 | public void demonstrateUsage() { 78 | // Add a new capital 79 | CapitalDataAccessService.CapitalDetail tokyo = addCapital( 80 | "Tokyo", 81 | "Japan", 82 | Arrays.asList("Asia") 83 | ); 84 | System.out.println("Added: " + tokyo); 85 | 86 | // Get capital details for an existing capital 87 | Optional paris = getCapitalDetails("Paris"); 88 | paris.ifPresent(p -> System.out.println("Found: " + p)); 89 | 90 | // Get all capitals in Europe 91 | List europeanCapitals = getCapitalsInContinent("Europe"); 92 | System.out.println("European capitals: " + europeanCapitals.size()); 93 | europeanCapitals.forEach(System.out::println); 94 | } 95 | */ 96 | 97 | } -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/dbingestion/HypotheticalQuestionsEmbeddingService.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.dbingestion; 2 | 3 | import static com.datastax.astra.internal.utils.AnsiUtils.yellow; 4 | 5 | import ai.patterns.CapitalData; 6 | import ai.patterns.CapitalDataRAG; 7 | import com.google.cloud.vertexai.api.Schema; 8 | import com.google.cloud.vertexai.api.Type; 9 | import dev.langchain4j.data.document.Document; 10 | import dev.langchain4j.data.document.DocumentSplitter; 11 | import dev.langchain4j.data.document.Metadata; 12 | import dev.langchain4j.data.document.splitter.DocumentSplitters; 13 | import dev.langchain4j.data.segment.TextSegment; 14 | import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel; 15 | import dev.langchain4j.service.AiServices; 16 | import dev.langchain4j.service.SystemMessage; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Map; 20 | import org.springframework.stereotype.Service; 21 | 22 | @Service 23 | public class HypotheticalQuestionsEmbeddingService { 24 | 25 | private static final String PARAGRAPH_METADATA_KEY = "paragraph"; 26 | 27 | public List generateHypotheticalQuestionsForCapitals(CapitalData capital) { 28 | List capitalDataRAGList = new ArrayList<>(); 29 | 30 | String text = capital.article().text(); 31 | 32 | // text files 33 | Document capitalDoc = Document.from(text); 34 | 35 | // chat model 36 | VertexAiGeminiChatModel questionModel = VertexAiGeminiChatModel.builder() 37 | .project(System.getenv("GCP_PROJECT_ID")) 38 | .location(System.getenv("GCP_LOCATION")) 39 | .modelName("gemini-2.0-flash-001") 40 | .responseSchema(Schema.newBuilder() 41 | .setType(Type.ARRAY) 42 | .setItems(Schema.newBuilder().setType(Type.STRING).build()) 43 | .build()) 44 | .build(); 45 | 46 | interface QuestionGenerator { 47 | 48 | @SystemMessage(""" 49 | Suggest 20 clear questions whose answer could be given by the user provided text. 50 | Don't use pronouns, be explicit about the subjects and objects of the question. 51 | """) 52 | List generateQuestions(String text); 53 | } 54 | 55 | // create AIService 56 | QuestionGenerator hypotheticalQuestions = AiServices.create(QuestionGenerator.class, 57 | questionModel); 58 | 59 | // split the text for a capital 60 | DocumentSplitter splitter = DocumentSplitters.recursive(2000, 200); 61 | List textSegments = splitter.split(capitalDoc); 62 | 63 | // for (TextSegment segment : textSegments) { 64 | textSegments.parallelStream() 65 | .forEach(segment -> { 66 | List questions = hypotheticalQuestions.generateQuestions(segment.text()) 67 | .stream() 68 | .map(question -> new TextSegment( 69 | question, 70 | new Metadata(Map.of(PARAGRAPH_METADATA_KEY, segment.text())) 71 | )).toList(); 72 | 73 | List questionList = new ArrayList<>(); 74 | System.out.println(yellow("\nQUESTIONS:\n")); 75 | for (int i = 1; i < questions.size() - 1; i++) { 76 | String question = questions.get(i).text(); 77 | System.out.println((i) + ") " + question); 78 | 79 | questionList.add(question); 80 | } 81 | 82 | capitalDataRAGList.add(new CapitalDataRAG( 83 | capital, 84 | CapitalDataRAG.EmbedType.HYPOTHETICAL, 85 | questionList, 86 | segment.text())); 87 | } 88 | ); 89 | 90 | return capitalDataRAGList; 91 | } 92 | } -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.1/maven-plugin) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/3.4.1/maven-plugin/build-image.html) 9 | * [GraalVM Native Image Support](https://docs.spring.io/spring-boot/3.4.1/reference/packaging/native-image/introducing-graalvm-native-images.html) 10 | * [Spring Boot Testcontainers support](https://docs.spring.io/spring-boot/3.4.1/reference/testing/testcontainers.html#testing.testcontainers) 11 | * [Vaadin](https://vaadin.com/docs) 12 | * [Spring Web](https://docs.spring.io/spring-boot/3.4.1/reference/web/servlet.html) 13 | * [Testcontainers](https://java.testcontainers.org/) 14 | 15 | ### Guides 16 | The following guides illustrate how to use some features concretely: 17 | 18 | * [Creating CRUD UI with Vaadin](https://spring.io/guides/gs/crud-with-vaadin/) 19 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 20 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 21 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 22 | 23 | ### Additional Links 24 | These additional references should also help you: 25 | 26 | * [Configure AOT settings in Build Plugin](https://docs.spring.io/spring-boot/3.4.1/how-to/aot.html) 27 | 28 | ## GraalVM Native Support 29 | 30 | This project has been configured to let you generate either a lightweight container or a native executable. 31 | It is also possible to run your tests in a native image. 32 | 33 | ### Lightweight Container with Cloud Native Buildpacks 34 | If you're already familiar with Spring Boot container images support, this is the easiest way to get started. 35 | Docker should be installed and configured on your machine prior to creating the image. 36 | 37 | To create the image, run the following goal: 38 | 39 | ``` 40 | $ ./mvnw spring-boot:build-image -Pnative 41 | ``` 42 | 43 | Then, you can run the app like any other container: 44 | 45 | ``` 46 | $ docker run --rm -p 8080:8080 examples:0.0.1-SNAPSHOT 47 | ``` 48 | 49 | ### Executable with Native Build Tools 50 | Use this option if you want to explore more options such as running your tests in a native image. 51 | The GraalVM `native-image` compiler should be installed and configured on your machine. 52 | 53 | NOTE: GraalVM 22.3+ is required. 54 | 55 | To create the executable, run the following goal: 56 | 57 | ``` 58 | $ ./mvnw native:compile -Pnative 59 | ``` 60 | 61 | Then, you can run the app as follows: 62 | ``` 63 | $ target/examples 64 | ``` 65 | 66 | You can also run your existing tests suite in a native image. 67 | This is an efficient way to validate the compatibility of your application. 68 | 69 | To run your existing tests in a native image, run the following goal: 70 | 71 | ``` 72 | $ ./mvnw test -PnativeTest 73 | ``` 74 | 75 | 76 | ### Testcontainers support 77 | 78 | This project uses [Testcontainers at development time](https://docs.spring.io/spring-boot/3.4.1/reference/features/dev-services.html#features.dev-services.testcontainers). 79 | 80 | Testcontainers has been configured to use the following Docker images: 81 | 82 | 83 | Please review the tags of the used images and set them to the same as you're running in production. 84 | 85 | ### Maven Parent overrides 86 | 87 | Due to Maven's design, elements are inherited from the parent POM to the project POM. 88 | While most of the inheritance is fine, it also inherits unwanted elements like `` and `` from the parent. 89 | To prevent this, the project POM contains empty overrides for these elements. 90 | If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides. 91 | 92 | -------------------------------------------------------------------------------- /demo/DayOf.md: -------------------------------------------------------------------------------- 1 | ## First demo 2 | ```shell 3 | What is the capital of France? How many people live there 4 | ``` 5 | ```shell 6 | Who won most medals at the Paris Olympics in 2024? 7 | ``` 8 | 9 | ## RAG 10 | ```shell 11 | Write a report about the population of Berlin 12 | ``` 13 | Hierarchical ++ HyDE ++ Ranking ++ Query compression 14 | ```shell 15 | What is the capital of Germany? 16 | Tell me about the geography of the city 17 | ``` 18 | Contextual ++ reranking + query compression 19 | ``` 20 | What is the capital of Germany? 21 | Tell me about the geography of the city 22 | ``` 23 | 24 | Context 25 | 26 | ## Function Calling 27 | ```shell 28 | Please list all the registered tools you have available 29 | ``` 30 | ```shell 31 | What is the current weather in Berlin? 32 | ``` 33 | 34 | ## Agents 35 | UserMessage 36 | ```shell 37 | You are a knowledgeable history, geography and tourist assistant. 38 | Your role is to write reports about a particular location or event, 39 | focusing on the key topics asked by the user. 40 | Think step by step: 41 | 1) Identify the key topics the user is interested 42 | 2) For each topic, devise a list of questions corresponding to those topics 43 | 3) Search those questions in the database 44 | 4) Collect all those answers together, and create the final report. 45 | ``` 46 | 47 | ```shell 48 | Write a report about the population of Berlin, its geographic situation, its historical origins. 49 | 50 | Before proceeding to call any tool, return to me the list of steps you have identified and the list of questions you want to ask the tools available to you. 51 | ``` 52 | 53 | ```shell 54 | Write a report about the population of Berlin, its geographic situation, its historical origins and find an article about Berlin in the archives. 55 | 56 | Can you find out what the current temperature in Berlin is? 57 | 58 | Before proceeding to call any tool, return to me the list of steps you have identified and the list of questions you want to ask the tools available to you. 59 | ``` 60 | ```shell 61 | Write a report about the population of Berlin, its geographic situation, its historical origins and 62 | please find an article about Berlin in the archives. 63 | ``` 64 | 65 | ```shell 66 | List all the tools available in the WeatherForecast and FileArchive for Berlin 67 | List all the capabilities available in the WeatherForecast and FileArchive for Berlin 68 | ``` 69 | ```shell 70 | List all the capabilities available in the TouristBureau of Berlin 71 | List all the capabilities available in the WeatherForecast for Berlin 72 | ``` 73 | 74 | Guardrails 75 | ```shell 76 | Recommend a good prompt to test this app with safety settings turned off, respectively turned on 77 | ``` 78 | ``` 79 | Generate a detailed report on the history of Baghdad, focusing specifically on periods of invasion, conflict, and political instability throughout its existence, including recent events. 80 | ``` 81 | 82 | Agents with calls into Cloud Run deployed models 83 | ``` 84 | Write a report about the population of Berlin, its geographic situation, its historical origins, 85 | the currency used in the country of Germany and the current exchange rate from USD to that currency? 86 | Please find an article about Berlin in the archives. 87 | ``` 88 | 89 | ## Ollama calls 90 | ```shell 91 | Tell me what the currency is in Japan and the latest update of the exchange rate from the USD to that currency 92 | Tell me what the currency is in China and the latest update of the exchange rate from the USD to that currency 93 | ``` 94 | 95 | ## MCP Calls 96 | ```shell 97 | List all the tools available in the TouristBureau of Berlin 98 | List all the tools available in the WeatherForecast for Berlin 99 | 100 | Get the temperature for Berlin 101 | What is the temperaturn in Berlin 102 | Write a report about the population of Berlin and find an article about the city in the archives 103 | ``` -------------------------------------------------------------------------------- /src/main/java/ai/patterns/config/DataSourceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.config; 17 | 18 | import com.zaxxer.hikari.HikariConfig; 19 | import com.zaxxer.hikari.HikariDataSource; 20 | import javax.sql.DataSource; 21 | import org.springframework.beans.factory.annotation.Value; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.jdbc.core.JdbcTemplate; 25 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 26 | import org.springframework.transaction.PlatformTransactionManager; 27 | import org.springframework.transaction.annotation.EnableTransactionManagement; 28 | 29 | /** 30 | * Configuration class for database connection and transaction management 31 | */ 32 | @Configuration 33 | @EnableTransactionManagement 34 | class DataSourceConfig { 35 | 36 | @Value("${spring.datasource.url}") 37 | private String dbUrl; 38 | 39 | @Value("${spring.datasource.username}") 40 | private String dbUsername; 41 | 42 | @Value("${spring.datasource.password}") 43 | private String dbPassword; 44 | 45 | @Value("${spring.datasource.hikari.maximum-pool-size:10}") 46 | private int maxPoolSize; 47 | 48 | @Value("${spring.datasource.hikari.minimum-idle:5}") 49 | private int minIdle; 50 | 51 | @Value("${spring.datasource.hikari.idle-timeout:30000}") 52 | private int idleTimeout; 53 | 54 | @Value("${spring.datasource.hikari.connection-timeout:30000}") 55 | private int connectionTimeout; 56 | 57 | @Value("${spring.datasource.hikari.max-lifetime:1800000}") 58 | private int maxLifetime; 59 | 60 | /** 61 | * Configure and create HikariDataSource 62 | * @return Configured DataSource 63 | */ 64 | @Bean 65 | public DataSource dataSource() { 66 | HikariConfig config = new HikariConfig(); 67 | config.setJdbcUrl(dbUrl); 68 | config.setUsername(dbUsername); 69 | config.setPassword(dbPassword); 70 | config.setMaximumPoolSize(maxPoolSize); 71 | config.setMinimumIdle(minIdle); 72 | config.setIdleTimeout(idleTimeout); 73 | config.setConnectionTimeout(connectionTimeout); 74 | config.setMaxLifetime(maxLifetime); 75 | 76 | // PostgreSQL-specific settings 77 | config.addDataSourceProperty("cachePrepStmts", "true"); 78 | config.addDataSourceProperty("prepStmtCacheSize", "250"); 79 | config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); 80 | 81 | return new HikariDataSource(config); 82 | } 83 | 84 | /** 85 | * Configure JdbcTemplate with the HikariDataSource 86 | * @param dataSource The configured HikariDataSource 87 | * @return JdbcTemplate 88 | */ 89 | @Bean 90 | public JdbcTemplate jdbcTemplate(DataSource dataSource) { 91 | return new JdbcTemplate(dataSource); 92 | } 93 | 94 | /** 95 | * Configure transaction manager 96 | * @param dataSource The configured HikariDataSource 97 | * @return PlatformTransactionManager 98 | */ 99 | @Bean 100 | public PlatformTransactionManager transactionManager(DataSource dataSource) { 101 | return new DataSourceTransactionManager(dataSource); 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/main/frontend/views/index.css: -------------------------------------------------------------------------------- 1 | @import url('@vaadin/react-components/css/Lumo.css'); 2 | @import url('@vaadin/react-components/css/lumo/Typography.css'); 3 | @import url('@vaadin/react-components/css/lumo/Utility.module.css'); 4 | 5 | .ai-patterns-ui { 6 | display: flex; 7 | flex-direction: column; 8 | height: 100vh; 9 | overflow: hidden; 10 | 11 | header { 12 | padding: var(--lumo-space-m); 13 | border-bottom: 1px solid var(--lumo-contrast-20pct); 14 | flex-shrink: 0; 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | } 19 | 20 | main { 21 | flex-grow: 1; 22 | display: flex; 23 | min-height: 0; 24 | overflow: hidden; 25 | 26 | blockquote { 27 | margin-left: 0.5em; 28 | padding: 0.1em 0.1em 0.1em 0.5em; 29 | background-color: var(--lumo-primary-color-10pct); 30 | font-style: italic; 31 | } 32 | 33 | .settings { 34 | width: 470px; 35 | display: flex; 36 | flex-direction: column; 37 | gap: var(--lumo-space-s); 38 | padding: var(--lumo-space-m); 39 | border-right: 1px solid var(--lumo-contrast-20pct); 40 | flex-shrink: 1; 41 | overflow-y: auto; 42 | 43 | .side-by-side-checkboxes { 44 | display: flex; /* Make the container a flexbox */ 45 | gap: var(--lumo-space-m); /* Add some space between the checkboxes */ 46 | align-items: center; /* Align the checkboxes vertically */ 47 | } 48 | .side-by-side-comboboxes { 49 | display: flex; /* Make the container a flexbox */ 50 | gap: var(--lumo-space-m); /* Add some space between the checkboxes */ 51 | align-items: center; /* Align the checkboxes vertically */ 52 | } 53 | .vertical-checkboxes { 54 | display: flex; 55 | flex-direction: column; 56 | } 57 | 58 | .space { 59 | flex-grow: 1; 60 | } 61 | 62 | .built-with { 63 | font-size: var(--lumo-font-size-s); 64 | font-weight: bold; 65 | /*color: blue;*/ 66 | 67 | a { 68 | color: var(--lumo-body-text-color); 69 | text-decoration: none; 70 | font-weight: bold; 71 | color: blue; 72 | } 73 | } 74 | } 75 | 76 | .vaadin-chat-component { 77 | flex: 1; 78 | min-height: 0; 79 | height: 100%; 80 | overflow: hidden; 81 | 82 | .hidden { 83 | display: none; 84 | } 85 | 86 | h4 { 87 | cursor: pointer; 88 | user-select: none; 89 | } 90 | 91 | h4::after { 92 | content: " ⏶"; 93 | } 94 | 95 | h4.expanded::after { 96 | content: " ⏷"; 97 | } 98 | 99 | /* Initially hide all ul elements that are next to an h4 */ 100 | h4 + ul { 101 | display: none; 102 | } 103 | 104 | /* When the h4 is clicked, show the next ul */ 105 | h4.expanded + ul { 106 | display: block; 107 | } 108 | 109 | h4 + ul strong + ol li, h4 + ul p + ol li { 110 | list-style: none; 111 | border-left: 2px solid var(--lumo-success-color); 112 | margin: 0.5em 0.5em 0.5em 0; 113 | padding: 0.5em; 114 | color: var(--lumo-body-text-color); 115 | background-color: var(--lumo-success-color-10pct); 116 | } 117 | 118 | .scroller { 119 | overflow-y: auto; 120 | max-height: 500px; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /data/tables.ddl: -------------------------------------------------------------------------------- 1 | -- Drop existing objects if they exist 2 | DROP INDEX IF EXISTS idx_capitals_id; 3 | DROP INDEX IF EXISTS idx_capital_chunks_id; 4 | DROP TABLE IF EXISTS capital_continents; 5 | DROP TABLE IF EXISTS capital_chunks; 6 | DROP TABLE IF EXISTS continents; 7 | DROP TABLE IF EXISTS capitals; 8 | DROP TYPE IF EXISTS embed_type; 9 | 10 | -- Create enum type for embedding types 11 | CREATE TYPE embed_type AS ENUM ('hierarchical', 'hypothetical', 'contextual', 'late'); 12 | 13 | -- Modified capitals table (without content, chunk, and embedding columns) 14 | CREATE TABLE capitals ( 15 | capital_id SERIAL PRIMARY KEY, 16 | capital VARCHAR(255) NOT NULL, 17 | country VARCHAR(255) NOT NULL 18 | ); 19 | 20 | -- Table for Continents 21 | CREATE TABLE continents ( 22 | continent_id SERIAL PRIMARY KEY, 23 | continent_name VARCHAR(255) UNIQUE NOT NULL 24 | ); 25 | 26 | -- Linking table for Capitals and Continents (Many-to-Many) 27 | CREATE TABLE capital_continents ( 28 | capital_id INT NOT NULL, 29 | continent_id INT NOT NULL, 30 | PRIMARY KEY (capital_id, continent_id), 31 | FOREIGN KEY (capital_id) REFERENCES capitals(capital_id), 32 | FOREIGN KEY (continent_id) REFERENCES continents(continent_id) 33 | ); 34 | 35 | -- New table for capital chunks with content, chunk, and embedding 36 | CREATE TABLE capital_chunks ( 37 | chunk_id SERIAL PRIMARY KEY, 38 | capital_id INT NOT NULL, 39 | embed embed_type NOT NULL DEFAULT 'hypothetical', 40 | content TEXT NOT NULL, 41 | chunk TEXT NOT NULL, 42 | embedding public.vector GENERATED ALWAYS AS (public.embedding('text-embedding-005'::text, content)) STORED, 43 | FOREIGN KEY (capital_id) REFERENCES capitals(capital_id) 44 | ); 45 | 46 | -- Create indexes 47 | CREATE INDEX idx_capitals_id ON capitals (capital_id); 48 | CREATE INDEX idx_capital_chunks_id ON capital_chunks (capital_id); 49 | 50 | 51 | -- Insert a capital 52 | INSERT INTO capitals (capital, country) 53 | VALUES ('Ankara', 'Turkey'); 54 | 55 | -- Insert continents 56 | INSERT INTO continents (continent_name) 57 | VALUES ('Europe'), ('Asia'), ('Africa'), ('North America'), ('South America'), ('Oceania'), ('Antarctica') 58 | ON CONFLICT (continent_name) DO NOTHING; 59 | 60 | -- Link capital to continents (for Ankara: Europe and Asia) 61 | INSERT INTO capital_continents (capital_id, continent_id) 62 | VALUES 63 | ((SELECT capital_id FROM capitals WHERE capital = 'Ankara'), 64 | (SELECT continent_id FROM continents WHERE continent_name = 'Europe')), 65 | ((SELECT capital_id FROM capitals WHERE capital = 'Ankara'), 66 | (SELECT continent_id FROM continents WHERE continent_name = 'Asia')); 67 | 68 | -- Insert multiple chunks for the same capital 69 | INSERT INTO capital_chunks (capital_id, embed, content, chunk) 70 | VALUES 71 | ((SELECT capital_id FROM capitals WHERE capital = 'Ankara'), 72 | 'contextual', 'Historical content about Ankara...', 'Ankara is the capital of Turkey and was made the capital in 1923...'), 73 | ((SELECT capital_id FROM capitals WHERE capital = 'Ankara'), 74 | 'hypothetical', 'Geographical content about Ankara...', 'Ankara is located in central Anatolia and has a continental climate...'), 75 | ((SELECT capital_id FROM capitals WHERE capital = 'Ankara'), 76 | 'contextual', 'Cultural content about Ankara...', 'The culture of Ankara reflects its position as Turkey's capital...'); 77 | 78 | -- Example SELECT to retrieve a capital with all its chunks and continents 79 | SELECT 80 | c.capital_id, 81 | c.capital, 82 | c.country, 83 | cc.chunk_id, 84 | cc.embed, 85 | cc.content, 86 | cc.chunk, 87 | cont.continent_name 88 | FROM 89 | capitals AS c 90 | JOIN 91 | capital_chunks AS cc ON c.capital_id = cc.capital_id 92 | JOIN 93 | capital_continents AS ccont ON c.capital_id = ccont.capital_id 94 | JOIN 95 | continents AS cont ON ccont.continent_id = cont.continent_id 96 | WHERE 97 | c.capital = 'Ankara'; -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/dbingestion/ContextualRetrievalEmbeddingService.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.dbingestion; 2 | 3 | import static com.datastax.astra.internal.utils.AnsiUtils.yellow; 4 | 5 | import ai.patterns.CapitalData; 6 | import ai.patterns.CapitalDataRAG; 7 | import ai.patterns.CapitalDataRAG.EmbedType; 8 | import com.google.cloud.vertexai.api.Schema; 9 | import com.google.cloud.vertexai.api.Type; 10 | import dev.langchain4j.data.document.Document; 11 | import dev.langchain4j.data.document.DocumentSplitter; 12 | import dev.langchain4j.data.document.Metadata; 13 | import dev.langchain4j.data.document.splitter.DocumentSplitters; 14 | import dev.langchain4j.data.message.AiMessage; 15 | import dev.langchain4j.data.segment.TextSegment; 16 | import dev.langchain4j.model.input.PromptTemplate; 17 | import dev.langchain4j.model.output.Response; 18 | import dev.langchain4j.model.vertexai.HarmCategory; 19 | import dev.langchain4j.model.vertexai.SafetyThreshold; 20 | import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel; 21 | import dev.langchain4j.service.AiServices; 22 | import dev.langchain4j.service.SystemMessage; 23 | import dev.langchain4j.service.UserMessage; 24 | import dev.langchain4j.service.V; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.Map; 28 | import org.springframework.stereotype.Service; 29 | 30 | @Service 31 | public class ContextualRetrievalEmbeddingService { 32 | 33 | private static final String PARAGRAPH_METADATA_KEY = "paragraph"; 34 | 35 | public List generateContextualEmbeddingsForCapitals(CapitalData capital) { 36 | List capitalDataRAGList = new ArrayList<>(); 37 | 38 | String text = capital.article().text(); 39 | 40 | // text files 41 | Document capitalDoc = Document.from(text); 42 | 43 | // chat model 44 | VertexAiGeminiChatModel geminiChatModel = VertexAiGeminiChatModel.builder() 45 | .project(System.getenv("GCP_PROJECT_ID")) 46 | .location(System.getenv("GCP_LOCATION")) 47 | .modelName("gemini-2.0-flash-001") 48 | .maxRetries(3) 49 | .safetySettings(Map.of( 50 | HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, SafetyThreshold.BLOCK_NONE, 51 | HarmCategory.HARM_CATEGORY_HARASSMENT, SafetyThreshold.BLOCK_NONE, 52 | HarmCategory.HARM_CATEGORY_HATE_SPEECH, SafetyThreshold.BLOCK_NONE, 53 | HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, SafetyThreshold.BLOCK_NONE 54 | )) 55 | // .responseSchema(Schema.newBuilder() 56 | // .setType(Type.ARRAY) 57 | // .setItems(Schema.newBuilder().setType(Type.STRING).build()) 58 | // .build()) 59 | .build(); 60 | 61 | interface ContextualGenerator { 62 | 63 | @UserMessage(""" 64 | 65 | {{wholeDocument}} 66 | 67 | Here is the chunk we want to situate within the whole document 68 | 69 | {{chunk}} 70 | 71 | Please give a short succinct context to situate this chunk within the overall document \ 72 | for the purposes of improving search retrieval of the chunk. \ 73 | Answer only with the succinct context and nothing else. 74 | """) 75 | String generateContext(@V("chunk") String chunk, @V("wholeDocument") String wholeDocument); 76 | } 77 | 78 | // create AIService 79 | ContextualGenerator contextualGenerator = AiServices.create( 80 | ContextualGenerator.class, 81 | geminiChatModel); 82 | 83 | // split the text for a capital 84 | DocumentSplitter splitter = DocumentSplitters.recursive(2000, 200); 85 | List textSegments = splitter.split(capitalDoc); 86 | 87 | // for (TextSegment segment : textSegments) { 88 | textSegments 89 | // .parallelStream() 90 | .forEach(segment -> { 91 | String contextualizedChunk = contextualGenerator 92 | .generateContext(segment.text(), text); 93 | 94 | System.out.println("\n" + "-".repeat(100)); 95 | System.out.println(yellow("ORIGINAL:\n") + segment.text()); 96 | System.out.println(yellow("\nCONTEXTUALIZED CHUNK:\n") + contextualizedChunk); 97 | capitalDataRAGList.add(new CapitalDataRAG( 98 | capital, 99 | EmbedType.CONTEXTUAL, 100 | List.of(contextualizedChunk), 101 | segment.text())); 102 | } 103 | ); 104 | 105 | return capitalDataRAGList; 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/java/ai/patterns/tools/HistoryGeographyToolOllama.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.tools; 17 | 18 | import static ai.patterns.utils.Ansi.blue; 19 | import static ai.patterns.utils.Ansi.yellow; 20 | import static ai.patterns.utils.ChatUtils.ChunkingType.HYPOTHETICAL; 21 | import static ai.patterns.utils.RAGUtils.augmentWithVectorDataList; 22 | import static ai.patterns.utils.RAGUtils.formatVectorSearchResults; 23 | import static ai.patterns.utils.RAGUtils.prepareUserMessage; 24 | 25 | import ai.patterns.base.AbstractBase; 26 | import ai.patterns.dao.CapitalDataAccessDAO; 27 | import ai.patterns.data.TopicReport; 28 | import ai.patterns.utils.ChatUtils; 29 | import ai.patterns.utils.ChatUtils.ChatOptions; 30 | import ai.patterns.utils.Models; 31 | import dev.langchain4j.agent.tool.Tool; 32 | import dev.langchain4j.service.AiServices; 33 | import dev.langchain4j.service.Result; 34 | import dev.langchain4j.service.SystemMessage; 35 | import java.util.ArrayList; 36 | import java.util.List; 37 | import java.util.stream.Collectors; 38 | import org.springframework.stereotype.Component; 39 | 40 | @Component 41 | public class HistoryGeographyToolOllama extends AbstractBase { 42 | 43 | private final CapitalDataAccessDAO dataAccess; 44 | 45 | public HistoryGeographyToolOllama(CapitalDataAccessDAO dataAccess){ 46 | this.dataAccess = dataAccess; 47 | } 48 | 49 | interface TopicAssistant { 50 | @SystemMessage(fromResource = "templates/history-geography-tool-system.txt") 51 | Result report(String subTopic); 52 | } 53 | 54 | @Tool("Search information in the database") 55 | TopicReport searchInformationInDatabase(String query) { 56 | System.out.println(blue(">>> Invoking `searchInformation` tool with query - uses Gemma3 model: ") + query); 57 | 58 | TopicAssistant topicAssistant = AiServices.builder(TopicAssistant.class) 59 | .chatLanguageModel(getChatLanguageModelOllama(ChatUtils.getDefaultChatOptions(Models.MODEL_GEMMA3_4B))) 60 | .build(); 61 | 62 | // augment with vector data if RAG is enabled 63 | // no RAG? ok 64 | List vectorDataList = new ArrayList<>(); 65 | String additionalVectorData = ""; 66 | String sources = ""; 67 | 68 | vectorDataList = augmentWithVectorDataList( 69 | query, 70 | new ChatOptions("", 71 | false, 72 | true, 73 | false, 74 | false, 75 | Models.MODEL_GEMMA3_4B, 76 | false, 77 | true, 78 | false, 79 | false, 80 | HYPOTHETICAL, 81 | false, 82 | false, 83 | false, 84 | false, 85 | false, 86 | false, 87 | true, 88 | "", 89 | "" 90 | ), 91 | dataAccess); 92 | 93 | // format RAG data to send to LLM 94 | additionalVectorData = vectorDataList.stream() 95 | .map(CapitalDataAccessDAO.CapitalChunkRow::getChunk) 96 | .collect(Collectors.joining("\n")); 97 | 98 | // format sources in returnable format 99 | sources = formatVectorSearchResults(vectorDataList); 100 | 101 | // prepare final UserMessage including original UserMessage, attachments, vector data (if available) 102 | String finalUserMessage = prepareUserMessage(query, 103 | "", 104 | additionalVectorData, 105 | sources, 106 | false); 107 | 108 | long start = System.currentTimeMillis(); 109 | Result reportResult = topicAssistant.report(finalUserMessage); 110 | System.out.println("Call with Ollama and Gemma models(ms): " + (System.currentTimeMillis() - start)); 111 | 112 | System.out.println(yellow("\n-> Topic report: ") + reportResult.content().replaceAll("\\n", "\n")); 113 | 114 | return new TopicReport(query, reportResult.content()); 115 | } 116 | } -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/utils/Ansi.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.utils; 2 | 3 | public class Ansi { 4 | 5 | public static String red(String msg) { 6 | return "\u001B[31m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 7 | } 8 | 9 | public static String green(String msg) { 10 | return "\u001B[32m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 11 | } 12 | 13 | public static String blue(String msg) { 14 | return "\u001B[34m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 15 | } 16 | 17 | public static String yellow(String msg) { 18 | return "\u001B[33m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 19 | } 20 | 21 | public static String cyan(String msg) { 22 | return "\u001B[36m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 23 | } 24 | 25 | public static String underline(String msg) { 26 | return "\u001B[4m" + msg + "\u001B[0m"; 27 | } 28 | 29 | public static String bold(String msg) { 30 | return "\u001B[1m" + msg + "\u001B[0m"; 31 | } 32 | 33 | public static String strikethrough(String msg) { 34 | return "\u001B[9m" + msg + "\u001B[0m"; 35 | } 36 | 37 | public static String italic(String msg) { 38 | return "\u001B[3m" + msg + "\u001B[0m"; 39 | } 40 | 41 | public static String markdown(String msg) { 42 | return msg 43 | // Bold 44 | .replaceAll("\\*\\*(.*?)\\*\\*", "\u001B[1m$1\u001B[0m") 45 | // Italic 46 | .replaceAll("\\*(.*?)\\*", "\u001B[3m$1\u001B[0m") 47 | // Underline 48 | .replaceAll("__(.*?)__", "\u001B[4m$1\u001B[0m") 49 | // Strikethrough 50 | .replaceAll("~~(.*?)~~", "\u001B[9m$1\u001B[0m") 51 | 52 | // Blockquote 53 | .replaceAll("(> ?.*)", "\u001B[3m\u001B[34m\u001B[1m$1\u001B[22m\u001B[0m") 54 | 55 | // Lists (bold magenta number and bullet) 56 | .replaceAll("([\\d]+\\.|-|\\*) (.*)", "\u001B[35m\u001B[1m$1\u001B[22m\u001B[0m $2") 57 | 58 | // Block code (black on gray) 59 | .replaceAll("(?s)```(\\w+)?\\n(.*?)\\n```", "\u001B[3m\u001B[1m$1\u001B[22m\u001B[0m\n\u001B[57;107m$2\u001B[0m\n") 60 | // Inline code (black on gray) 61 | .replaceAll("`(.*?)`", "\u001B[57;107m$1\u001B[0m") 62 | 63 | // Headers (cyan bold) 64 | .replaceAll("(#{1,6}) (.*?)\n", "\u001B[36m\u001B[1m$1 $2\u001B[22m\u001B[0m\n") 65 | // Headers with a single line of text followed by 2 or more equal signs 66 | .replaceAll("(.*?\n={2,}\n)", "\u001B[36m\u001B[1m$1\u001B[22m\u001B[0m\n") 67 | // Headers with a single line of text followed by 2 or more dashes 68 | .replaceAll("(.*?\n-{2,}\n)", "\u001B[36m\u001B[1m$1\u001B[22m\u001B[0m\n") 69 | 70 | 71 | // Images (blue underlined) 72 | .replaceAll("!\\[(.*?)]\\((.*?)\\)", "\u001B[34m$1\u001B[0m (\u001B[34m\u001B[4m$2\u001B[0m)") 73 | // Links (blue underlined) 74 | .replaceAll("!?\\[(.*?)]\\((.*?)\\)", "\u001B[34m$1\u001B[0m (\u001B[34m\u001B[4m$2\u001B[0m)") 75 | ; 76 | } 77 | 78 | public static void main(String[] args) { 79 | String markdownText = """ 80 | Main title 81 | ========== 82 | Big title 83 | 84 | Subtitle 85 | -------- 86 | Small title 87 | 88 | # Bold and italic 89 | 90 | Some **bold text**. 91 | Bits of *italicized text*. 92 | It's __underlined__. 93 | And ~~striked through~~. 94 | 95 | ## Links 96 | 97 | A [link](https://www.example.com) to an article. 98 | 99 | ![alt text](image.jpg) 100 | 101 | ### Quoting 102 | 103 | > a quote of someone famous, potentially wrapping around multiple lines. 104 | 105 | # Lists 106 | 107 | 1. First item 108 | 2. Second item 109 | 3. Third item 110 | 111 | - First item 112 | - Second item 113 | - Third item 114 | 115 | # Code 116 | 117 | Some inline `code` inside a paragraph. 118 | Return type is `void` and args are `String[]`. 119 | 120 | A fenced code block: 121 | 122 | ```java 123 | public class Hello { 124 | public static void main(String[] args) { 125 | System.out.println("Hello World!"); 126 | } 127 | } 128 | 129 | ``` 130 | """; 131 | 132 | System.out.println(markdown(markdownText)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/services/AgenticRAGService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.services; 17 | 18 | import static ai.patterns.utils.Ansi.cyan; 19 | import static ai.patterns.utils.Ansi.blue; 20 | 21 | import ai.patterns.base.AbstractBase; 22 | import ai.patterns.tools.CurrencyManagerTool; 23 | import ai.patterns.tools.HistoryGeographyTool; 24 | import ai.patterns.tools.TouristBureauMCPTool; 25 | import ai.patterns.tools.WeatherForecastMCPTool; 26 | import ai.patterns.utils.ChatUtils.ChatOptions; 27 | import dev.langchain4j.memory.chat.ChatMemoryProvider; 28 | import dev.langchain4j.service.AiServices; 29 | import dev.langchain4j.service.MemoryId; 30 | import dev.langchain4j.service.SystemMessage; 31 | import dev.langchain4j.service.UserMessage; 32 | import dev.langchain4j.service.V; 33 | import org.springframework.core.env.Environment; 34 | import org.springframework.stereotype.Service; 35 | import reactor.core.publisher.Flux; 36 | 37 | @Service 38 | public class AgenticRAGService extends AbstractBase { 39 | // with multiple models, AI framework starters are not yet configured for supporting multiple models 40 | private Environment env; 41 | private HistoryGeographyTool historyGeographyTool; 42 | private TouristBureauMCPTool touristBureauMCPTool; 43 | private CurrencyManagerTool currencyManagerTool; 44 | private WeatherForecastMCPTool weatherForecastMCPTool; 45 | private final ChatMemoryProvider chatMemoryProvider; 46 | 47 | public AgenticRAGService(Environment env, 48 | HistoryGeographyTool historyGeographyTool, 49 | TouristBureauMCPTool touristBureauMCPTool, 50 | CurrencyManagerTool currencyManagerTool, 51 | WeatherForecastMCPTool weatherForecastMCPTool, 52 | ChatMemoryProvider chatMemoryProvider){ 53 | this.env = env; 54 | this.historyGeographyTool = historyGeographyTool; 55 | this.touristBureauMCPTool = touristBureauMCPTool; 56 | this.currencyManagerTool = currencyManagerTool; 57 | this.weatherForecastMCPTool = weatherForecastMCPTool; 58 | this.chatMemoryProvider = chatMemoryProvider; 59 | } 60 | 61 | public String callAgent(String chatId, 62 | String systemMessage, 63 | String userMessage, 64 | ChatOptions options) { 65 | AgenticAssistant assistant = AiServices.builder(AgenticAssistant.class) 66 | .chatLanguageModel(getChatLanguageModel(options)) 67 | .tools(historyGeographyTool, touristBureauMCPTool, currencyManagerTool, weatherForecastMCPTool) 68 | .chatMemoryProvider(chatMemoryProvider) 69 | .build(); 70 | 71 | String report = assistant.chat(chatId, systemMessage, userMessage); 72 | 73 | System.out.println(blue("\n>>> FINAL RESPONSE REPORT:\n")); 74 | System.out.println(cyan(report)); 75 | 76 | return report; 77 | } 78 | public Flux stream(String chatId, 79 | String systemMessage, 80 | String userMessage, 81 | String messageAttachments, 82 | ChatOptions options) { 83 | // create AIAssistant with a streaming model and tools enabled 84 | AgenticAssistant assistant = AiServices.builder(AgenticAssistant.class) 85 | .streamingChatLanguageModel(getChatLanguageModelStreaming(options)) 86 | .tools(historyGeographyTool, touristBureauMCPTool, currencyManagerTool, weatherForecastMCPTool) 87 | .chatMemoryProvider(chatMemoryProvider) 88 | .build(); 89 | 90 | return assistant.stream(chatId, systemMessage, userMessage) 91 | .doOnNext(System.out::print) 92 | .doOnComplete(() -> { 93 | System.out.println(blue("\n\n>>> STREAM COMPLETE")); // Indicate stream completion 94 | }); 95 | 96 | } 97 | 98 | interface AgenticAssistant { 99 | @SystemMessage(fromResource = "templates/agentic-rag-service-system.txt") 100 | String chat(@MemoryId String chatId, @V("systemMessage") String systemMessage, @UserMessage String userMessage); 101 | 102 | @SystemMessage(fromResource = "templates/agentic-rag-service-system.txt") 103 | Flux stream(@MemoryId String chatId, @V("systemMessage") String systemMessage, @UserMessage String userMessage); 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /mcp/mcp-file-server/src/test/java/mcp/file/server/SampleClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - 2024 the original author or authors. 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 mcp.file.server; 17 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | import io.modelcontextprotocol.spec.McpSchema; 20 | import io.modelcontextprotocol.spec.McpSchema.TextContent; 21 | import io.modelcontextprotocol.spec.McpSchema.Tool; 22 | import java.io.BufferedReader; 23 | import java.io.InputStreamReader; 24 | import java.net.HttpURLConnection; 25 | import java.net.URL; 26 | import java.net.URLEncoder; 27 | import java.nio.charset.StandardCharsets; 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | import io.modelcontextprotocol.client.McpClient; 32 | import io.modelcontextprotocol.spec.McpClientTransport; 33 | import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; 34 | import io.modelcontextprotocol.spec.McpSchema.CallToolResult; 35 | import io.modelcontextprotocol.spec.McpSchema.ListToolsResult; 36 | import java.util.stream.Collectors; 37 | 38 | /** 39 | * @author Christian Tzolov 40 | */ 41 | 42 | public class SampleClient { 43 | 44 | private final McpClientTransport transport; 45 | 46 | public SampleClient(McpClientTransport transport) { 47 | this.transport = transport; 48 | } 49 | 50 | public void run() throws Exception { 51 | 52 | var client = McpClient.sync(this.transport).build(); 53 | 54 | client.initialize(); 55 | 56 | client.ping(); 57 | 58 | // List and demonstrate tools 59 | ListToolsResult toolsList = client.listTools(); 60 | System.out.println("Available Tools = " + prettyPrintTools(toolsList.tools())); 61 | 62 | CallToolResult fileResult = client.callTool(new CallToolRequest("findArticleInArchives", 63 | Map.of("capital", "Berlin"))); 64 | 65 | System.out.println("\nFile Result: " + extractTextFromCallToolResult(fileResult)); 66 | } 67 | 68 | public String prettyPrintTools(List tools) { 69 | StringBuilder sb = new StringBuilder("Tools:\n"); 70 | 71 | for (Tool tool : tools) { 72 | // Tool name 73 | sb.append("* ").append(tool.name()).append("\n"); 74 | 75 | // Description 76 | sb.append(" * Description: ").append(tool.description()).append("\n"); 77 | 78 | // Input Schema 79 | sb.append(" * Input Schema:\n"); 80 | sb.append(" * Type: ").append(tool.inputSchema().type()).append("\n"); 81 | 82 | // Properties 83 | sb.append(" * Properties:\n"); 84 | Map properties = tool.inputSchema().properties(); 85 | for (Map.Entry property : properties.entrySet()) { 86 | String propName = property.getKey(); 87 | Map propDetails = (Map) property.getValue(); 88 | 89 | StringBuilder propDescription = new StringBuilder(); 90 | propDescription.append(propName).append(" (").append(propDetails.get("type")); 91 | 92 | // Add format if available 93 | if (propDetails.containsKey("format")) { 94 | propDescription.append(", format: ").append(propDetails.get("format")); 95 | } 96 | 97 | // Add required if applicable 98 | if (tool.inputSchema().required().contains(propName)) { 99 | propDescription.append(", required"); 100 | } 101 | 102 | propDescription.append(")"); 103 | sb.append(" * ").append(propDescription).append("\n"); 104 | } 105 | 106 | // Additional Properties 107 | sb.append(" * Additional Properties: ") 108 | .append(tool.inputSchema().additionalProperties()).append("\n\n"); 109 | } 110 | 111 | return sb.toString(); 112 | } 113 | 114 | public static String extractTextFromCallToolResult(CallToolResult result) { 115 | return result.content().stream() 116 | .map(content -> { 117 | if (content instanceof TextContent textContent) { 118 | return textContent.text(); 119 | } else if (content instanceof McpSchema.ImageContent) { 120 | return "[Image content]"; 121 | } else if (content instanceof McpSchema.EmbeddedResource embeddedResource) { 122 | McpSchema.ResourceContents resourceContents = embeddedResource.resource(); 123 | if (resourceContents instanceof McpSchema.TextResourceContents textResource) { 124 | return textResource.text(); 125 | } else { 126 | return "[Binary resource]"; 127 | } 128 | } 129 | return ""; 130 | }) 131 | .filter(text -> !text.isEmpty()) 132 | .collect(Collectors.joining("\n")); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/tools/HistoryGeographyTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.tools; 17 | 18 | import static ai.patterns.utils.Ansi.blue; 19 | import static ai.patterns.utils.Ansi.yellow; 20 | import static ai.patterns.utils.Models.MODEL_GEMINI_FLASH; 21 | import static ai.patterns.utils.RAGUtils.augmentWithVectorDataList; 22 | import static ai.patterns.utils.RAGUtils.formatVectorSearchResults; 23 | import static ai.patterns.utils.RAGUtils.prepareUserMessage; 24 | import static ai.patterns.utils.ChatUtils.ChunkingType.HYPOTHETICAL; 25 | 26 | import ai.patterns.base.AbstractBase; 27 | import ai.patterns.dao.CapitalDataAccessDAO; 28 | import ai.patterns.data.TopicReport; 29 | import ai.patterns.utils.ChatUtils; 30 | import ai.patterns.utils.Models; 31 | import ai.patterns.utils.ChatUtils.ChatOptions; 32 | import dev.langchain4j.agent.tool.Tool; 33 | import dev.langchain4j.service.AiServices; 34 | import dev.langchain4j.service.Result; 35 | import dev.langchain4j.service.SystemMessage; 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | import java.util.stream.Collectors; 39 | import org.springframework.stereotype.Component; 40 | 41 | @Component 42 | public class HistoryGeographyTool extends AbstractBase { 43 | 44 | private final CapitalDataAccessDAO dataAccess; 45 | 46 | public HistoryGeographyTool(CapitalDataAccessDAO dataAccess){ 47 | this.dataAccess = dataAccess; 48 | } 49 | 50 | interface TopicAssistant { 51 | @SystemMessage(fromResource = "templates/history-geography-tool-system.txt") 52 | Result report(String subTopic); 53 | } 54 | 55 | @Tool("Search information in the database") 56 | TopicReport searchInformationInDatabase(String query) { 57 | System.out.println(blue(">>> Invoking `searchInformation` tool with query: ") + query); 58 | 59 | TopicAssistant topicAssistant = AiServices.builder(TopicAssistant.class) 60 | .chatLanguageModel(getChatLanguageModel(ChatUtils.getDefaultChatOptions(MODEL_GEMINI_FLASH))) 61 | .build(); 62 | 63 | // augment with vector data if RAG is enabled 64 | // no RAG? ok 65 | List vectorDataList = new ArrayList<>(); 66 | String additionalVectorData = ""; 67 | String sources = ""; 68 | 69 | vectorDataList = augmentWithVectorDataList( 70 | query, 71 | new ChatOptions("", 72 | true, 73 | true, 74 | false, 75 | false, 76 | Models.MODEL_GEMINI_FLASH, 77 | false, 78 | true, 79 | false, 80 | false, 81 | HYPOTHETICAL, 82 | false, 83 | false, 84 | false, 85 | false, 86 | false, 87 | false, 88 | true, 89 | "", 90 | "" 91 | ), 92 | dataAccess); 93 | 94 | // format RAG data to send to LLM 95 | additionalVectorData = vectorDataList.stream() 96 | .map(CapitalDataAccessDAO.CapitalChunkRow::getChunk) 97 | .collect(Collectors.joining("\n")); 98 | 99 | // format sources in returnable format 100 | sources = formatVectorSearchResults(vectorDataList); 101 | 102 | // prepare final UserMessage including original UserMessage, attachments, vector data (if available) 103 | String finalUserMessage = prepareUserMessage(query, 104 | "", 105 | additionalVectorData, 106 | sources, 107 | false); 108 | 109 | long start = System.currentTimeMillis(); 110 | Result reportResult = topicAssistant.report(finalUserMessage); 111 | System.out.println(yellow("\n-> Topic report: ") + reportResult.content().replaceAll("\\n", "\n")); 112 | System.out.println("Call with Gemini models(ms): " + (System.currentTimeMillis() - start)); 113 | 114 | // include sources in the final report 115 | // String topicReturn = String.format(""" 116 | // %s 117 | // 118 | // Please add at the end of your answer, the following content as-is, for reference purposes: 119 | // 120 | // #### Sources 121 | // 122 | // %s 123 | // 124 | // """, reportResult.content(), sources); 125 | // 126 | // return new TopicReport(query, topicReturn); 127 | return new TopicReport(query, reportResult.content()); 128 | } 129 | } -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/dbingestion/HierarchicalEmbeddingService.java: -------------------------------------------------------------------------------- 1 | package ai.patterns.dbingestion; 2 | 3 | import static com.datastax.astra.internal.utils.AnsiUtils.yellow; 4 | 5 | import ai.patterns.CapitalData; 6 | import ai.patterns.CapitalDataRAG; 7 | import ai.patterns.CapitalDataRAG.EmbedType; 8 | import dev.langchain4j.data.document.Document; 9 | import dev.langchain4j.data.document.DocumentSplitter; 10 | import dev.langchain4j.data.document.Metadata; 11 | import dev.langchain4j.data.document.splitter.DocumentBySentenceSplitter; 12 | import dev.langchain4j.data.document.splitter.DocumentSplitters; 13 | import dev.langchain4j.data.segment.TextSegment; 14 | import dev.langchain4j.data.segment.TextSegmentTransformer; 15 | import dev.langchain4j.model.input.PromptTemplate; 16 | import dev.langchain4j.model.vertexai.HarmCategory; 17 | import dev.langchain4j.model.vertexai.SafetyThreshold; 18 | import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel; 19 | import dev.langchain4j.rag.content.injector.ContentInjector; 20 | import dev.langchain4j.service.AiServices; 21 | import dev.langchain4j.service.UserMessage; 22 | import dev.langchain4j.service.V; 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.stream.Collectors; 28 | import java.util.stream.IntStream; 29 | import org.springframework.stereotype.Service; 30 | 31 | @Service 32 | public class HierarchicalEmbeddingService { 33 | 34 | private static final String METADATA_CONTEXT_KEY = "Surrounding context"; 35 | 36 | public List generateHierarchicalEmbeddingCapitals(CapitalData capital) { 37 | List capitalDataRAGList = new ArrayList<>(); 38 | 39 | String text = capital.article().text(); 40 | 41 | // text files 42 | Document capitalDoc = Document.from(text); 43 | 44 | System.out.println("\n== CAPITAL DOCUMENT " + "=".repeat(30) + "\n"); 45 | System.out.println(capitalDoc.text()); 46 | 47 | // split the text for a capital 48 | DocumentSplitter splitter = new DocumentBySentenceSplitter(200, 20); 49 | List textSegments = splitter.split(capitalDoc); 50 | 51 | TextSegmentTransformer hierarchicalTransformer = SurroundingContextEnricher.textSegmentTransformer(2,3); 52 | List hierarchicalSegments = hierarchicalTransformer.transformAll(textSegments); 53 | 54 | hierarchicalSegments 55 | // .parallelStream() 56 | .forEach(segment -> { 57 | System.out.println("\n" + "-".repeat(100)); 58 | System.out.println(yellow("ORIGINAL:\n") + segment.text()); 59 | System.out.println(yellow("\nParent CHUNK:\n") + segment.metadata().getString(METADATA_CONTEXT_KEY)); 60 | capitalDataRAGList.add(new CapitalDataRAG( 61 | capital, 62 | EmbedType.HIERARCHICAL, 63 | List.of(segment.text()), 64 | segment.metadata().getString(METADATA_CONTEXT_KEY))); 65 | } 66 | ); 67 | 68 | return capitalDataRAGList; 69 | } 70 | 71 | private static class SurroundingContextEnricher { 72 | 73 | public static TextSegmentTransformer textSegmentTransformer(int nSegmentsBefore, int nSegmentsAfter) { 74 | return new TextSegmentTransformer() { 75 | @Override 76 | public TextSegment transform(TextSegment segment) { 77 | return transformAll(Collections.singletonList(segment)).getFirst(); 78 | } 79 | 80 | @Override 81 | public List transformAll(List segments) { 82 | if (segments == null || segments.isEmpty()) { 83 | return Collections.emptyList(); 84 | } 85 | 86 | List list = new ArrayList<>(); 87 | 88 | for (int i = 0; i < segments.size(); i++) { 89 | TextSegment textSegment = segments.get(i); 90 | 91 | String context = IntStream.rangeClosed(i - nSegmentsBefore, i + nSegmentsAfter) 92 | .filter(j -> j >= 0 && j < segments.size()) 93 | .mapToObj(j -> segments.get(j).text()) 94 | .collect(Collectors.joining(" ")); 95 | 96 | Metadata metadata = new Metadata(textSegment.metadata().toMap()); 97 | metadata.put(METADATA_CONTEXT_KEY, context); 98 | 99 | list.add(TextSegment.from(textSegment.text(), metadata)); 100 | } 101 | 102 | return list; 103 | } 104 | }; 105 | } 106 | 107 | public static ContentInjector contentInjector(PromptTemplate promptTemplate) { 108 | return (contents, userMessage) -> { 109 | String excerpts = contents.stream() 110 | .map(content -> 111 | content 112 | .textSegment() 113 | .metadata() 114 | .getString(METADATA_CONTEXT_KEY)) 115 | .collect(Collectors.joining("\n\n")); 116 | 117 | return promptTemplate.apply(Map.of( 118 | "userMessage", userMessage.singleText(), 119 | "contents", excerpts 120 | )).toUserMessage(); 121 | }; 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /src/main/java/ai/patterns/utils/Ansi.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.utils; 17 | 18 | public class Ansi { 19 | 20 | public static String red(String msg) { 21 | return "\u001B[31m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 22 | } 23 | 24 | public static String green(String msg) { 25 | return "\u001B[32m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 26 | } 27 | 28 | public static String blue(String msg) { 29 | return "\u001B[34m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 30 | } 31 | 32 | public static String yellow(String msg) { 33 | return "\u001B[33m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 34 | } 35 | 36 | public static String cyan(String msg) { 37 | return "\u001B[36m\u001B[1m" + msg + "\u001B[22m\u001B[0m"; 38 | } 39 | 40 | public static String underline(String msg) { 41 | return "\u001B[4m" + msg + "\u001B[0m"; 42 | } 43 | 44 | public static String bold(String msg) { 45 | return "\u001B[1m" + msg + "\u001B[0m"; 46 | } 47 | 48 | public static String strikethrough(String msg) { 49 | return "\u001B[9m" + msg + "\u001B[0m"; 50 | } 51 | 52 | public static String italic(String msg) { 53 | return "\u001B[3m" + msg + "\u001B[0m"; 54 | } 55 | 56 | public static String markdown(String msg) { 57 | return msg 58 | // Bold 59 | .replaceAll("\\*\\*(.*?)\\*\\*", "\u001B[1m$1\u001B[0m") 60 | // Italic 61 | .replaceAll("\\*(.*?)\\*", "\u001B[3m$1\u001B[0m") 62 | // Underline 63 | .replaceAll("__(.*?)__", "\u001B[4m$1\u001B[0m") 64 | // Strikethrough 65 | .replaceAll("~~(.*?)~~", "\u001B[9m$1\u001B[0m") 66 | 67 | // Blockquote 68 | .replaceAll("(> ?.*)", "\u001B[3m\u001B[34m\u001B[1m$1\u001B[22m\u001B[0m") 69 | 70 | // Lists (bold magenta number and bullet) 71 | .replaceAll("([\\d]+\\.|-|\\*) (.*)", "\u001B[35m\u001B[1m$1\u001B[22m\u001B[0m $2") 72 | 73 | // Block code (black on gray) 74 | .replaceAll("(?s)```(\\w+)?\\n(.*?)\\n```", "\u001B[3m\u001B[1m$1\u001B[22m\u001B[0m\n\u001B[57;107m$2\u001B[0m\n") 75 | // Inline code (black on gray) 76 | .replaceAll("`(.*?)`", "\u001B[57;107m$1\u001B[0m") 77 | 78 | // Headers (cyan bold) 79 | .replaceAll("(#{1,6}) (.*?)\n", "\u001B[36m\u001B[1m$1 $2\u001B[22m\u001B[0m\n") 80 | // Headers with a single line of text followed by 2 or more equal signs 81 | .replaceAll("(.*?\n={2,}\n)", "\u001B[36m\u001B[1m$1\u001B[22m\u001B[0m\n") 82 | // Headers with a single line of text followed by 2 or more dashes 83 | .replaceAll("(.*?\n-{2,}\n)", "\u001B[36m\u001B[1m$1\u001B[22m\u001B[0m\n") 84 | 85 | 86 | // Images (blue underlined) 87 | .replaceAll("!\\[(.*?)]\\((.*?)\\)", "\u001B[34m$1\u001B[0m (\u001B[34m\u001B[4m$2\u001B[0m)") 88 | // Links (blue underlined) 89 | .replaceAll("!?\\[(.*?)]\\((.*?)\\)", "\u001B[34m$1\u001B[0m (\u001B[34m\u001B[4m$2\u001B[0m)") 90 | ; 91 | } 92 | 93 | public static void main(String[] args) { 94 | String markdownText = """ 95 | Main title 96 | ========== 97 | Big title 98 | 99 | Subtitle 100 | -------- 101 | Small title 102 | 103 | # Bold and italic 104 | 105 | Some **bold text**. 106 | Bits of *italicized text*. 107 | It's __underlined__. 108 | And ~~striked through~~. 109 | 110 | ## Links 111 | 112 | A [link](https://www.example.com) to an article. 113 | 114 | ![alt text](image.jpg) 115 | 116 | ### Quoting 117 | 118 | > a quote of someone famous, potentially wrapping around multiple lines. 119 | 120 | # Lists 121 | 122 | 1. First item 123 | 2. Second item 124 | 3. Third item 125 | 126 | - First item 127 | - Second item 128 | - Third item 129 | 130 | # Code 131 | 132 | Some inline `code` inside a paragraph. 133 | Return type is `void` and args are `String[]`. 134 | 135 | A fenced code block: 136 | 137 | ```java 138 | public class Hello { 139 | public static void main(String[] args) { 140 | System.out.println("Hello World!"); 141 | } 142 | } 143 | 144 | ``` 145 | """; 146 | 147 | System.out.println(markdown(markdownText)); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /dataingestion/src/main/java/ai/patterns/DbingestionApplication.java: -------------------------------------------------------------------------------- 1 | package ai.patterns; 2 | 3 | import ai.patterns.dbingestion.CapitalService; 4 | import ai.patterns.dbingestion.ContextualRetrievalEmbeddingService; 5 | import ai.patterns.dbingestion.HierarchicalEmbeddingService; 6 | import ai.patterns.dbingestion.HypotheticalQuestionsEmbeddingService; 7 | import ai.patterns.utils.CapitalsFileLoader; 8 | import dev.langchain4j.model.embedding.EmbeddingModel; 9 | import dev.langchain4j.model.vertexai.VertexAiEmbeddingModel; 10 | import java.time.LocalDateTime; 11 | import java.util.Arrays; 12 | import java.util.HashSet; 13 | import java.util.List; 14 | import java.util.Set; 15 | import java.util.stream.Collectors; 16 | import org.springframework.boot.ApplicationRunner; 17 | import org.springframework.boot.SpringApplication; 18 | import org.springframework.boot.autoconfigure.SpringBootApplication; 19 | import org.springframework.context.ApplicationContext; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.transaction.annotation.EnableTransactionManagement; 22 | 23 | @SpringBootApplication 24 | @EnableTransactionManagement 25 | public class DbingestionApplication { 26 | 27 | private EmbeddingModel embeddingModel; 28 | private CapitalService capitalService; 29 | private HypotheticalQuestionsEmbeddingService hypotheticalQuestionsEmbeddingService; 30 | private ContextualRetrievalEmbeddingService contextualRetrievalEmbeddingService; 31 | private HierarchicalEmbeddingService hierarchicalEmbeddingService; 32 | 33 | public static void main(String[] args) { 34 | SpringApplication.run(DbingestionApplication.class, args); 35 | } 36 | 37 | @Bean 38 | public ApplicationRunner commandLineRunner( 39 | CapitalService capitalService, 40 | EmbeddingModel embeddingModel, 41 | HypotheticalQuestionsEmbeddingService hypotheticalQuestionsEmbeddingService, 42 | ContextualRetrievalEmbeddingService contextualRetrievalEmbeddingService, 43 | HierarchicalEmbeddingService hierarchicalEmbeddingService, 44 | ApplicationContext context) { 45 | 46 | this.embeddingModel = embeddingModel; 47 | this.capitalService = capitalService; 48 | this.hypotheticalQuestionsEmbeddingService = hypotheticalQuestionsEmbeddingService; 49 | this.contextualRetrievalEmbeddingService = contextualRetrievalEmbeddingService; 50 | this.hierarchicalEmbeddingService = hierarchicalEmbeddingService; 51 | 52 | 53 | return args -> { 54 | try { 55 | System.out.println("Starting data ingestion process..."); 56 | 57 | List capitalsData = CapitalsFileLoader.loadFiles("txt"); 58 | 59 | // filter a subset of capitals 60 | List filteredCapitalsData = filterCapitals(capitalsData); 61 | 62 | filteredCapitalsData.forEach(capitalData -> { 63 | System.out.println("Processing capital: " + capitalData.capitalCity().city()); 64 | System.out.println("Current date and time: " + LocalDateTime.now()); 65 | 66 | //*********************************************************** 67 | // wire in a different chunking method, rest remains the same 68 | //*********************************************************** 69 | List capitalDataRAGList = hierarchicalEmbeddingService.generateHierarchicalEmbeddingCapitals( 70 | capitalData); 71 | // List capitalDataRAGList = contextualRetrievalEmbeddingService.generateContextualEmbeddingsForCapitals( 72 | // capitalData); 73 | 74 | // List capitalDataRAGList = hypotheticalQuestionsEmbeddingService.generateHypotheticalQuestionsForCapitals( 75 | // capitalData); 76 | //*********************************************************** 77 | 78 | System.out.println("Persisting data for capital: " + capitalData.capitalCity().city() + "..."); 79 | System.out.println("Current date and time: " + LocalDateTime.now()); 80 | capitalDataRAGList.parallelStream() 81 | .forEach(capitalRAG -> capitalService.addCapital(capitalRAG)); 82 | }); 83 | } catch (Exception e) { 84 | System.err.println("Error during data ingestion: " + e.getMessage()); 85 | e.printStackTrace(); 86 | } finally { 87 | // Gracefully exit when done 88 | SpringApplication.exit(context, () -> 0); 89 | } 90 | }; 91 | } 92 | 93 | private List filterCapitals(List capitalsData) { 94 | // Define the list of 10 capital names to keep 95 | Set capitalsToKeep = new HashSet<>(Arrays.asList( 96 | "London", "Paris", "Berlin", "Rome", "Madrid", 97 | "Tokyo", "Beijing", "Bern", "Vienna", "Ottawa" 98 | )); 99 | 100 | // Filter the list to keep only capitals in our set 101 | return capitalsData.stream() 102 | .filter(capital -> capitalsToKeep.contains(capital.capitalCity().city())) 103 | .collect(Collectors.toList()); 104 | } 105 | 106 | 107 | @Bean 108 | protected EmbeddingModel embeddingModel() { 109 | return VertexAiEmbeddingModel.builder() 110 | .project(System.getenv("GCP_PROJECT_ID")) 111 | .endpoint(System.getenv("GCP_VERTEXAI_ENDPOINT")) 112 | .location(System.getenv("GCP_LOCATION")) 113 | .publisher("google") 114 | .modelName(System.getenv("GCP_TEXTEMBEDDING_MODEL")) 115 | .maxSegmentsPerBatch(100) 116 | .maxRetries(5) 117 | .build(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/tools/TouristBureauMCPTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.tools; 17 | 18 | import static ai.patterns.utils.Ansi.blue; 19 | 20 | import ai.patterns.base.AbstractBase; 21 | import ai.patterns.data.TopicReport; 22 | import dev.langchain4j.agent.tool.Tool; 23 | import io.modelcontextprotocol.client.McpClient; 24 | import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; 25 | import io.modelcontextprotocol.spec.McpSchema; 26 | import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; 27 | import io.modelcontextprotocol.spec.McpSchema.CallToolResult; 28 | import io.modelcontextprotocol.spec.McpSchema.ListToolsResult; 29 | import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities; 30 | import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities.ToolCapabilities; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | import java.util.Map; 34 | import java.util.stream.Collectors; 35 | import org.springframework.stereotype.Component; 36 | 37 | @Component 38 | public class TouristBureauMCPTool extends AbstractBase { 39 | 40 | @Tool("List capabilities available in TouristBureau server") 41 | TopicReport listCapabilitiesInFileArchive(String capital) throws Exception { 42 | System.out.println(blue(">>> Invoking `listCapabilitiesInFileArchive` tool with capital: ") + capital); 43 | 44 | var transport = new HttpClientSseClientTransport(System.getenv("MCP_FILE_SERVER")); 45 | var mcpClient = McpClient.sync(transport).build(); 46 | 47 | mcpClient.initialize(); 48 | mcpClient.ping(); 49 | 50 | ServerCapabilities capabilities = mcpClient.getServerCapabilities(); 51 | if(capabilities == null) 52 | return new TopicReport(capital, "No capabilities available for the TouristBureau MCP Server"); 53 | 54 | // List the available MCP tools 55 | ToolCapabilities toolsList = capabilities.tools(); 56 | 57 | List toolList = new ArrayList(); 58 | // Pretty print available tools 59 | mcpClient.listTools().tools().forEach(tool -> { 60 | toolList.add(("* **Tool:** \n") + tool.name()); 61 | toolList.add((" * **Description:** \n") + tool.description()); 62 | toolList.add((" * **Parameters:** \n")); 63 | tool.inputSchema().properties() 64 | .forEach((key, value) -> toolList.add(" * " + key + ": " + value)); 65 | }); 66 | 67 | if(capabilities.prompts() == null){ 68 | toolList.add(("* **Prompt:** ") + "No prompts available for the TouristBureau MCP Server"); 69 | } else { 70 | mcpClient.listPrompts().prompts().forEach(prompt -> { 71 | toolList.add(("* **Prompt:** \n") + prompt.name()); 72 | toolList.add((" * **Description:** \n") + prompt.description()); 73 | 74 | prompt.arguments().forEach(arg -> { 75 | String requiredText = arg.required() ? "(Required)" : "(Optional)"; 76 | toolList.add(" * " + arg.name() + " " + requiredText + ": " + arg.description()); 77 | }); 78 | }); 79 | }; 80 | 81 | if(capabilities.resources() == null){ 82 | toolList.add(("* **Resources:** \n") + "No resources available for the TouristBureau MCP Server"); 83 | } 84 | 85 | // close the client gracefully 86 | mcpClient.closeGracefully(); 87 | 88 | return new TopicReport(capital, toolList.stream().collect(Collectors.joining("\n"))); 89 | } 90 | 91 | @Tool("Find article in the Archives") 92 | TopicReport findArticleInArchives(String capital) throws Exception { 93 | System.out.println(blue(">>> Invoking `findArticleInArchives` tool with capital: ") + capital); 94 | 95 | var transport = new HttpClientSseClientTransport(System.getenv("MCP_FILE_SERVER")); 96 | var mcpClient = McpClient.sync(transport).build(); 97 | 98 | mcpClient.initialize(); 99 | mcpClient.ping(); 100 | 101 | CallToolResult fileResult = mcpClient.callTool( 102 | new CallToolRequest("findArticleInArchives", 103 | Map.of("capital", capital))); 104 | 105 | String fileText = extractTextFromCallToolResult(fileResult); 106 | System.out.println("\nArchived file: " + fileText); 107 | 108 | // close MCP client gracefully 109 | mcpClient.closeGracefully(); 110 | 111 | return new TopicReport(capital, fileText); 112 | } 113 | 114 | // extract the text content from the CallTool 115 | public static String extractTextFromCallToolResult(CallToolResult result) { 116 | return result.content().stream() 117 | .map(content -> { 118 | if (content instanceof McpSchema.TextContent textContent) { 119 | return textContent.text(); 120 | } else if (content instanceof McpSchema.ImageContent) { 121 | return "[Image content]"; 122 | } else if (content instanceof McpSchema.EmbeddedResource embeddedResource) { 123 | McpSchema.ResourceContents resourceContents = embeddedResource.resource(); 124 | if (resourceContents instanceof McpSchema.TextResourceContents textResource) { 125 | return textResource.text(); 126 | } else { 127 | return "[Binary resource]"; 128 | } 129 | } 130 | return ""; 131 | }) 132 | .filter(text -> !text.isEmpty()) 133 | .collect(Collectors.joining("\n")); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /demo/DemoPrompts.md: -------------------------------------------------------------------------------- 1 | #### Non-agentic SystemMessage 2 | ``` 3 | You are a knowledgeable history, geography and tourist assistant. 4 | Your role is to write reports about a particular location or event, 5 | focusing on the key topics asked by the user. 6 | 7 | Let us focus on world capitals today 8 | ``` 9 | 10 | User Message 11 | ``` 12 | Write a report about the population of Berlin 13 | ``` 14 | 15 | #### Agentic SystemMessage 16 | ``` 17 | You are a knowledgeable history, geography and tourist assistant. 18 | Your role is to write reports about a particular location or event, 19 | focusing on the key topics asked by the user. 20 | Think step by step: 21 | 1) Identify the key topics the user is interested 22 | 2) For each topic, devise a list of questions corresponding to those topics 23 | 3) Search those questions in the database 24 | 4) Collect all those answers together, and create the final report. 25 | ``` 26 | 27 | #### Agentic SysteMessage with sources 28 | ```shell 29 | You are a knowledgeable history, geography and tourist assistant. 30 | Your role is to write reports about a particular location or event, 31 | focusing on the key topics asked by the user. 32 | Think step by step: 33 | 1) Identify the key topics the user is interested 34 | 2) For each topic, devise a list of questions corresponding to those topics 35 | 3) Search those questions in the database 36 | 4) Collect all those answers together, and create the final report. 37 | 5) For each answer to a topic, include the block following "#### Sources" to your final report as-is 38 | ``` 39 | 40 | or 41 | ```shell 42 | You are a knowledgeable history, geography and tourist assistant. 43 | Your role is to write reports about a particular location or event, 44 | focusing on the key topics asked by the user. 45 | Think step by step: 46 | 1) Identify the key topics the user is interested 47 | 2) For each topic, devise a list of questions corresponding to those topics 48 | 3) Search those questions in the database 49 | 4) Collect all those answers together, and create the final report. 50 | 5) For each answer to a topic, include the block following "Please add at the end of your answer, the following content as-is, for reference purposes: 51 | 52 | #### Sources" to your final report as-is 53 | ``` 54 | #### RAG UserMessages 55 | Hypothetical Document Embedding 56 | ``` 57 | What's the capital of Germany? 58 | 59 | How many people live there? 60 | ``` 61 | 62 | #### Planning UserMessage 63 | ``` 64 | Write a report about the population of Berlin, its geographic situation, its historical origins, 65 | the currency used in the country of Germany and the current exchange rate from USD to that currency? 66 | Please find an article about Berlin in the archives. 67 | 68 | Before proceeding to call any tool, return to me the list of steps you have identified and the list of questions you want to ask the tools available to you. 69 | ``` 70 | 71 | UserMessage 72 | ``` 73 | Write a report about the population of Berlin 74 | 75 | Write a report about the population of Berlin, its geographic situation, and its historical origins 76 | Write a report about the population of Berlin, its geographic situation, its historical origins and get the current temperature 77 | Write a report about the cultural aspects of Berlin 78 | 79 | # Currency user messages 80 | What currency is used in the country of Germany? 81 | What currency is used in the country of Germany and what is the exchange rate from USD to that currency? 82 | Write a report about Berlin, including the currency in the country and the current exchange rate from the USD 83 | Tell me what the currency is in Germany and the latest update of the exchange rate from the USD 84 | 85 | Write a report about the population of Berlin, its geographic situation, its historical origins, the currency in the country and the current exchange rate from the USD 86 | Write a report about the population of Berlin, its geographic situation, its historical origins, the currency in the country and the last update of the exchange rate from the USD 87 | 88 | # MCP User Messages 89 | 90 | Write a report about the population of Berlin and find an article about the city in the archives 91 | 92 | Write a report about the population of Berlin and list all the tools available in the TouristBureau of Berlin 93 | List all the tools available in the TouristBureau of Berlin 94 | List all the tools available in the TouristBureau in the city of Berlin 95 | 96 | # List MCP tools 97 | List all the tools available to you 98 | List all the tools available in the TouristBureau of Berlin 99 | List all the tools available in the WeatherForecast for Berlin 100 | 101 | Get the temperature for Berlin 102 | Get the temperature for the capital with latitude 52.52437 and longitude 13.41053 103 | 104 | # archived tests 105 | Write a report about the population of Berlin, and get a printable article about the city 106 | Write a report about the population of Berlin and find an article about the city in the archives 107 | ``` 108 | 109 | ### Deployment 110 | 111 | ``` 112 | https://agentic-rag-360922367561.us-central1.run.app 113 | ``` 114 | Build and deploy 115 | ``` 116 | ./mvnw spring-boot:build-image -DskipTests -Pproduction -Dspring-boot.build-image.imageName=agentic-rag 117 | 118 | docker tag agentic-rag us-central1-docker.pkg.dev/genai-playground24/agentic-rag/agentic-rag:latest 119 | 120 | docker push us-central1-docker.pkg.dev/genai-playground24/agentic-rag/agentic-rag:latest 121 | 122 | gcloud run deploy agentic-rag --image us-central1-docker.pkg.dev/genai-playground24/agentic-rag/agentic-rag:latest --region us-central1 --set-env-vars=SERVER_PORT=8080 --memory 2Gi --cpu 2 --cpu-boost --execution-environment=gen1 --set-env-vars=GCP_PROJECT_ID=genai-playground24 --set-env-vars=GCP_LOCATION=us-central1 123 | --set-env-vars=ASTRA_TOKEN=AstraCS:DAyKLaFOtLfvNycaWqtGkLFt:2eb2d268182b2a68a3610fdbf6119a7384cee4635d23c4d06acfad788b3dc2e5 124 | ``` 125 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/web/endpoints/ChatEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.web.endpoints; 17 | 18 | import ai.patterns.utils.ChatUtils; 19 | import ai.patterns.utils.ChatUtils.ChatOptions; 20 | import ai.patterns.utils.Models; 21 | import java.io.IOException; 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.HashMap; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.UUID; 27 | 28 | import org.jetbrains.annotations.Nullable; 29 | import org.springframework.web.multipart.MultipartFile; 30 | import org.vaadin.components.experimental.chat.AiChatService; 31 | 32 | import com.vaadin.flow.server.auth.AnonymousAllowed; 33 | import com.vaadin.hilla.BrowserCallable; 34 | 35 | import ai.patterns.services.AgenticRAGService; 36 | import ai.patterns.services.ChatService; 37 | import reactor.core.publisher.Flux; 38 | 39 | @BrowserCallable 40 | @AnonymousAllowed 41 | public class ChatEndpoint implements AiChatService { 42 | 43 | private static final String ATTACHMENT_TEMPLATE = """ 44 | Answer the user question using the information from the following attachment: 45 | 46 | %s 47 | 48 | """; 49 | 50 | // Map to store attachments by chatId 51 | private final Map> attachments = new HashMap<>(); 52 | 53 | private final ChatService chatService; 54 | private final AgenticRAGService agenticRAGService; 55 | 56 | public ChatEndpoint(ChatService chatService, AgenticRAGService agenticRAGService) { 57 | this.chatService = chatService; 58 | this.agenticRAGService = agenticRAGService; 59 | } 60 | 61 | @Override 62 | public Flux stream(String chatId, String userMessage, @Nullable ChatOptions options) { 63 | // if chat options are not captured, set defaults 64 | if (options == null) { 65 | options = ChatUtils.getDefaultChatOptions(Models.MODEL_GEMINI_FLASH); 66 | } 67 | 68 | // Append attachments to the user message if any exist for this chat 69 | String messageAttachments = ""; 70 | Map chatAttachments = attachments.get(chatId); 71 | if (chatAttachments != null && !chatAttachments.isEmpty()) { 72 | StringBuilder messageBuilder = new StringBuilder(); 73 | 74 | // Append each attachment using the template 75 | for (Map.Entry entry : chatAttachments.entrySet()) { 76 | String filename = entry.getKey(); 77 | String content = entry.getValue(); 78 | messageBuilder.append("\n\n").append(String.format(ATTACHMENT_TEMPLATE, filename, content)); 79 | } 80 | 81 | messageAttachments = messageBuilder.toString(); 82 | 83 | // Clear attachments after appending them 84 | attachments.remove(chatId); 85 | } 86 | 87 | // follow separate streams for chat, respectively agents 88 | return options.useAgents() 89 | ? agenticRAGService.stream(chatId, options.systemMessage(), userMessage, messageAttachments, options) 90 | : chatService.stream(chatId, options.systemMessage(), userMessage, messageAttachments, options); 91 | } 92 | 93 | @Override 94 | public String uploadAttachment(String chatId, MultipartFile multipartFile) { 95 | String originalFilename = multipartFile.getOriginalFilename(); 96 | 97 | // Validate file type (only txt and md files are supported) 98 | if (originalFilename == null || 99 | !(originalFilename.endsWith(".txt") || originalFilename.endsWith(".md"))) { 100 | throw new IllegalArgumentException("Only .txt and .md files are supported"); 101 | } 102 | 103 | try { 104 | // Read file content 105 | String content = new String(multipartFile.getBytes(), StandardCharsets.UTF_8); 106 | 107 | // Generate a unique ID for the attachment 108 | String attachmentId = UUID.randomUUID().toString(); 109 | 110 | // Store the attachment 111 | attachments.computeIfAbsent(chatId, k -> new HashMap<>()) 112 | .put(originalFilename, content); 113 | 114 | return attachmentId; 115 | } catch (IOException e) { 116 | throw new RuntimeException("Failed to read attachment", e); 117 | } 118 | } 119 | 120 | @Override 121 | public void removeAttachment(String chatId, String attachmentId) { 122 | Map chatAttachments = attachments.get(chatId); 123 | if (chatAttachments != null) { 124 | chatAttachments.remove(attachmentId); 125 | 126 | // Remove the chat entry if there are no more attachments 127 | if (chatAttachments.isEmpty()) { 128 | attachments.remove(chatId); 129 | } 130 | } 131 | } 132 | 133 | @Override 134 | public List getHistory(String chatId) { 135 | return List.of(); 136 | } 137 | 138 | @Override 139 | public void closeChat(String chatId) { 140 | // Clear all attachments for this chat when it's closed 141 | attachments.remove(chatId); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/ai/patterns/tools/TouristBureauMCPLocalTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 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 ai.patterns.tools; 17 | 18 | import static ai.patterns.utils.Ansi.cyan; 19 | import static ai.patterns.utils.Ansi.blue; 20 | import static ai.patterns.utils.Ansi.yellow; 21 | import static ai.patterns.utils.Models.MODEL_GEMINI_FLASH; 22 | 23 | import ai.patterns.base.AbstractBase; 24 | import ai.patterns.data.TopicReport; 25 | import ai.patterns.utils.ChatUtils; 26 | import ai.patterns.utils.Models; 27 | import dev.langchain4j.agent.tool.Tool; 28 | import dev.langchain4j.mcp.McpToolProvider; 29 | import dev.langchain4j.mcp.client.DefaultMcpClient; 30 | import dev.langchain4j.mcp.client.McpClient; 31 | import dev.langchain4j.mcp.client.transport.McpTransport; 32 | import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport; 33 | import dev.langchain4j.service.AiServices; 34 | import dev.langchain4j.service.SystemMessage; 35 | import dev.langchain4j.service.tool.ToolProvider; 36 | import java.io.File; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | import java.util.stream.Collectors; 40 | import org.springframework.stereotype.Component; 41 | 42 | @Component 43 | public class TouristBureauMCPLocalTool extends AbstractBase { 44 | 45 | interface TopicMCPAssistant { 46 | @SystemMessage(""" 47 | You are an Archive assistant. Your task is to: 48 | 1. Read the content of the specified file 49 | 2. Clean any non-printable or special characters from the content 50 | 3. Ensure the content is valid UTF-8 text 51 | 4. Return the cleaned content as a properly escaped JSON string 52 | 5. If you encounter any issues with the file content, return a clear error message 53 | """) 54 | String find(String cityArticle); 55 | } 56 | 57 | // @Tool("Get printable article about a city") 58 | // TopicReport getPrintableArticle(String city){ 59 | // System.out.println(blue(">>> Invoking `getPrintableArticle` tool with query: ") + city); 60 | // 61 | // String text = loadDocumentText(String.format("capitals/%s_article.txt", city)).text(); 62 | // 63 | // return new TopicReport(city, text); 64 | // } 65 | 66 | @Tool("List tools available in TouristBureau server") 67 | TopicReport listToolsInTouristBureau(String capital) throws Exception { 68 | System.out.println(blue(">>> Invoking `listToolsInTouristBureau` tool with city: ") + capital); 69 | 70 | // Use NPX 71 | McpTransport transport = new StdioMcpTransport.Builder() 72 | .command(List.of("npx", 73 | "-y", 74 | "@modelcontextprotocol/server-filesystem@0.6.2", 75 | new File("src/main/resources/capitals").getAbsolutePath() 76 | )) 77 | .logEvents(true) 78 | .build(); 79 | 80 | McpClient mcpClient = new DefaultMcpClient.Builder() 81 | .transport(transport) 82 | .build(); 83 | 84 | List toolList = new ArrayList(); 85 | // Pretty print available tools 86 | mcpClient.listTools().forEach(tool -> { 87 | toolList.add(("* **Tool:** \n") + tool.name()); 88 | toolList.add((" * **Description:** \n") + tool.description()); 89 | toolList.add((" * **Parameters:** \n")); 90 | tool.parameters().properties() 91 | .forEach((key, value) -> toolList.add(" * " + key + ": " + value + "\n")); 92 | }); 93 | 94 | return new TopicReport(capital, toolList.stream().collect(Collectors.joining("\n"))); 95 | } 96 | 97 | @Tool("Find article in the Archives") 98 | TopicReport findArticleInArchives(String city) throws Exception{ 99 | System.out.println(blue(">>> Invoking `findArticleInArchives` tool with city: ") + city); 100 | 101 | // Use NPX 102 | McpTransport transport = new StdioMcpTransport.Builder() 103 | .command(List.of("npx", 104 | "-y", 105 | "@modelcontextprotocol/server-filesystem@0.6.2", 106 | new File("src/main/resources/capitals").getAbsolutePath() 107 | )) 108 | .logEvents(true) 109 | .build(); 110 | 111 | McpClient mcpClient = new DefaultMcpClient.Builder() 112 | .transport(transport) 113 | .build(); 114 | 115 | // Pretty print available tools 116 | System.out.println(cyan("\nAvailable MCP Tools:")); 117 | mcpClient.listTools().forEach(tool -> { 118 | System.out.println(yellow("- Tool: ") + tool.name()); 119 | System.out.println(cyan(" Description: ") + tool.description()); 120 | System.out.println(cyan(" Parameters: ")); 121 | tool.parameters().properties() 122 | .forEach((key, value) -> System.out.println(yellow(" - ") + key + ": " + value)); 123 | 124 | System.out.println(); 125 | }); 126 | 127 | ToolProvider toolProvider = McpToolProvider.builder() 128 | .mcpClients(List.of(mcpClient)) 129 | .build(); 130 | 131 | TopicMCPAssistant topicMCPAssistant = AiServices.builder( 132 | TopicMCPAssistant.class) 133 | .chatLanguageModel(getChatLanguageModel(ChatUtils.getDefaultChatOptions(MODEL_GEMINI_FLASH))) 134 | .toolProvider(toolProvider) 135 | .build(); 136 | 137 | String foundArticle; 138 | try{ 139 | File file = new File(String.format("src/main/resources/capitals/%s_article.txt", city)); 140 | 141 | long start = System.currentTimeMillis(); 142 | foundArticle = topicMCPAssistant.find(file.getAbsolutePath()); 143 | System.out.println("MCP call(ms): " + (System.currentTimeMillis() - start)); 144 | 145 | System.out.println(yellow("\n-> Topic report: ") + foundArticle); 146 | 147 | return new TopicReport(city, foundArticle); 148 | } catch (Exception e) { 149 | System.err.println("Error: " + e.getMessage()); 150 | e.printStackTrace(); 151 | 152 | return new TopicReport(city, "No article found"); 153 | } finally{ 154 | mcpClient.close(); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /dataingestion/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.4 9 | 10 | 11 | ai.patterns 12 | dataingestion 13 | 0.0.1 14 | dataingestion 15 | Ingest data to AlloyDB database 16 | 17 | 18 | 21 19 | 1.0.0-M6 20 | 1.0.0-beta2 21 | 2.0.16 22 | 1.5.15 23 | 2.18.2 24 | 1.18.34 25 | 1.5.3 26 | 1.3.0.581.1 27 | 0.31.1 28 | 3.27.0 29 | 5.11.4 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-data-jdbc 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-data-jpa 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | org.springframework 46 | spring-tx 47 | 48 | 49 | javax.transaction 50 | javax.transaction-api 51 | 1.3 52 | 53 | 54 | 55 | 56 | com.datastax.astra 57 | langchain4j-astradb 58 | ${astra-db-java.version} 59 | 60 | 61 | 62 | 63 | com.fasterxml.jackson.core 64 | jackson-core 65 | ${jackson.version} 66 | 67 | 68 | com.fasterxml.jackson.core 69 | jackson-databind 70 | ${jackson.version} 71 | 72 | 73 | 74 | 75 | dev.langchain4j 76 | langchain4j-core 77 | ${langchain4j.version} 78 | 79 | 80 | dev.langchain4j 81 | langchain4j 82 | ${langchain4j.version} 83 | 84 | 85 | dev.langchain4j 86 | langchain4j-vertex-ai 87 | ${langchain4j.version} 88 | 89 | 90 | dev.langchain4j 91 | langchain4j-vertex-ai-gemini 92 | ${langchain4j.version} 93 | 94 | 95 | 96 | org.postgresql 97 | postgresql 98 | runtime 99 | 42.7.5 100 | 101 | 102 | 103 | com.zaxxer 104 | HikariCP 105 | 106 | 107 | 108 | 109 | org.jsoup 110 | jsoup 111 | 1.18.3 112 | 113 | 114 | com.vladsch.flexmark 115 | flexmark-html2md-converter 116 | 0.64.8 117 | 118 | 119 | com.vladsch.flexmark 120 | flexmark-ext-tables 121 | 0.64.8 122 | 123 | 124 | 125 | org.springframework.boot 126 | spring-boot-starter-test 127 | test 128 | 129 | 130 | org.springframework.boot 131 | spring-boot-testcontainers 132 | test 133 | 134 | 135 | org.springframework.ai 136 | spring-ai-spring-boot-testcontainers 137 | test 138 | 139 | 140 | org.testcontainers 141 | junit-jupiter 142 | test 143 | 144 | 145 | org.testcontainers 146 | postgresql 147 | test 148 | 149 | 150 | 151 | 152 | 153 | org.springframework.ai 154 | spring-ai-bom 155 | ${spring-ai.version} 156 | pom 157 | import 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | org.hibernate.orm.tooling 166 | hibernate-enhance-maven-plugin 167 | ${hibernate.version} 168 | 169 | 170 | enhance 171 | 172 | enhance 173 | 174 | 175 | true 176 | true 177 | true 178 | 179 | 180 | 181 | 182 | 183 | org.graalvm.buildtools 184 | native-maven-plugin 185 | 186 | 187 | org.springframework.boot 188 | spring-boot-maven-plugin 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /mcp/mcp-weather-server/src/test/java/mcp/file/server/SampleClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - 2024 the original author or authors. 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 mcp.file.server; 17 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | import io.modelcontextprotocol.spec.McpSchema; 20 | import io.modelcontextprotocol.spec.McpSchema.TextContent; 21 | import io.modelcontextprotocol.spec.McpSchema.Tool; 22 | import java.io.BufferedReader; 23 | import java.io.InputStreamReader; 24 | import java.net.HttpURLConnection; 25 | import java.net.URL; 26 | import java.net.URLEncoder; 27 | import java.nio.charset.StandardCharsets; 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | import io.modelcontextprotocol.client.McpClient; 32 | import io.modelcontextprotocol.spec.McpClientTransport; 33 | import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; 34 | import io.modelcontextprotocol.spec.McpSchema.CallToolResult; 35 | import io.modelcontextprotocol.spec.McpSchema.ListToolsResult; 36 | import java.util.stream.Collectors; 37 | 38 | /** 39 | * @author Christian Tzolov 40 | */ 41 | 42 | public class SampleClient { 43 | 44 | private final McpClientTransport transport; 45 | 46 | public SampleClient(McpClientTransport transport) { 47 | this.transport = transport; 48 | } 49 | 50 | public void run() throws Exception { 51 | 52 | var client = McpClient.sync(this.transport).build(); 53 | 54 | client.initialize(); 55 | 56 | client.ping(); 57 | 58 | // List and demonstrate tools 59 | ListToolsResult toolsList = client.listTools(); 60 | System.out.println("Available Tools = " + prettyPrintTools(toolsList.tools())); 61 | 62 | Map coordinates = getCoordinatesFromCityName("London"); 63 | 64 | CallToolResult weatherForcastResult = client.callTool(new CallToolRequest("getTemperatureByLocation", 65 | Map.of("latitude", coordinates.get("latitude"), "longitude", coordinates.get("longitude")))); 66 | 67 | System.out.println("\nWeather Forecast: " + extractTextFromCallToolResult(weatherForcastResult)); 68 | 69 | weatherForcastResult = client.callTool(new CallToolRequest("getTemperatureByCapital", 70 | Map.of("latitude", coordinates.get("latitude"), "longitude", coordinates.get("longitude")))); 71 | System.out.println("\nWeather Forecast: " + weatherForcastResult); 72 | 73 | client.closeGracefully(); 74 | 75 | } 76 | 77 | public static Map getCoordinatesFromCityName(String cityName) throws Exception { 78 | // URL encode the city name to handle spaces and special characters 79 | String encodedCityName = URLEncoder.encode(cityName, StandardCharsets.UTF_8.toString()); 80 | String geocodingUrl = "https://geocoding-api.open-meteo.com/v1/search?name=" + encodedCityName; 81 | 82 | // Setup HTTP connection 83 | URL url = new URL(geocodingUrl); 84 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 85 | connection.setRequestMethod("GET"); 86 | 87 | // Read the response 88 | BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 89 | StringBuilder response = new StringBuilder(); 90 | String line; 91 | while ((line = reader.readLine()) != null) { 92 | response.append(line); 93 | } 94 | reader.close(); 95 | 96 | // Parse JSON response 97 | ObjectMapper mapper = new ObjectMapper(); 98 | Map jsonResponse = mapper.readValue(response.toString(), Map.class); 99 | 100 | // Extract the first result (assuming it's the most relevant) 101 | if (jsonResponse.containsKey("results") && ((List) jsonResponse.get("results")).size() > 0) { 102 | return (Map) ((List) jsonResponse.get("results")).get(0); 103 | } else { 104 | throw new Exception("No location found for city: " + cityName); 105 | } 106 | } 107 | 108 | public String prettyPrintTools(List tools) { 109 | StringBuilder sb = new StringBuilder("Tools:\n"); 110 | 111 | for (Tool tool : tools) { 112 | // Tool name 113 | sb.append("* ").append(tool.name()).append("\n"); 114 | 115 | // Description 116 | sb.append(" * Description: ").append(tool.description()).append("\n"); 117 | 118 | // Input Schema 119 | sb.append(" * Input Schema:\n"); 120 | sb.append(" * Type: ").append(tool.inputSchema().type()).append("\n"); 121 | 122 | // Properties 123 | sb.append(" * Properties:\n"); 124 | Map properties = tool.inputSchema().properties(); 125 | for (Map.Entry property : properties.entrySet()) { 126 | String propName = property.getKey(); 127 | Map propDetails = (Map) property.getValue(); 128 | 129 | StringBuilder propDescription = new StringBuilder(); 130 | propDescription.append(propName).append(" (").append(propDetails.get("type")); 131 | 132 | // Add format if available 133 | if (propDetails.containsKey("format")) { 134 | propDescription.append(", format: ").append(propDetails.get("format")); 135 | } 136 | 137 | // Add required if applicable 138 | if (tool.inputSchema().required().contains(propName)) { 139 | propDescription.append(", required"); 140 | } 141 | 142 | propDescription.append(")"); 143 | sb.append(" * ").append(propDescription).append("\n"); 144 | } 145 | 146 | // Additional Properties 147 | sb.append(" * Additional Properties: ") 148 | .append(tool.inputSchema().additionalProperties()).append("\n\n"); 149 | } 150 | 151 | return sb.toString(); 152 | } 153 | 154 | public static String extractTextFromCallToolResult(CallToolResult result) { 155 | return result.content().stream() 156 | .map(content -> { 157 | if (content instanceof TextContent textContent) { 158 | return textContent.text(); 159 | } else if (content instanceof McpSchema.ImageContent) { 160 | return "[Image content]"; 161 | } else if (content instanceof McpSchema.EmbeddedResource embeddedResource) { 162 | McpSchema.ResourceContents resourceContents = embeddedResource.resource(); 163 | if (resourceContents instanceof McpSchema.TextResourceContents textResource) { 164 | return textResource.text(); 165 | } else { 166 | return "[Binary resource]"; 167 | } 168 | } 169 | return ""; 170 | }) 171 | .filter(text -> !text.isEmpty()) 172 | .collect(Collectors.joining("\n")); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/frontend/views/@index.tsx: -------------------------------------------------------------------------------- 1 | import {nanoid} from "nanoid"; 2 | import {useEffect, useRef, useState} from "react"; 3 | import { Chat } from '@vaadin/flow-frontend/chat/Chat.js'; 4 | import {ChatEndpoint} from "Frontend/generated/endpoints"; 5 | import ChatOptions from "Frontend/generated/ai/patterns/utils/ChatUtils/ChatOptions"; 6 | import '@vaadin/icons'; 7 | import '@vaadin/vaadin-lumo-styles/icons'; 8 | 9 | import './index.css'; 10 | import {useForm} from "@vaadin/hilla-react-form"; 11 | import ChatOptionsModel from "Frontend/generated/ai/patterns/utils/ChatUtils/ChatOptionsModel"; 12 | import {Checkbox, ComboBox, TextArea, RadioGroup, RadioButton, Button, Icon, Tooltip} from "@vaadin/react-components"; 13 | 14 | const models = [ 15 | 'gemini-2.5-pro-preview-03-25', 16 | 'gemini-2.0-flash-001', 17 | 'gemini-1.5-pro-002' 18 | ]; 19 | 20 | const capitals : string[] = [ 21 | '', 22 | 'Beijing', 23 | 'Berlin', 24 | 'Bern', 25 | 'London', 26 | 'Madrid', 27 | 'Ottawa', 28 | 'Paris', 29 | 'Rome', 30 | 'Tokyo', 31 | 'Vienna' 32 | ]; 33 | 34 | const continents : string[] = [ 35 | '', 36 | 'Africa', 37 | 'Antarctica', 38 | 'Asia', 39 | 'Europe', 40 | 'North America', 41 | 'Oceania', 42 | 'South America' 43 | ]; 44 | 45 | export enum ChunkingType { 46 | NONE = 'NONE', 47 | HIERARCHICAL = 'HIERARCHICAL', 48 | HYPOTHETICAL = 'HYPOTHETICAL', 49 | CONTEXTUAL = 'CONTEXTUAL', 50 | LATE = 'LATE' 51 | } 52 | 53 | export enum RetrievalType { 54 | NONE = 'NONE', 55 | FILTERING = 'FILTERING', 56 | QUERY_COMPRESSION = 'QUERY_COMPRESSION', 57 | QUERY_ROUTING = 'QUERY_ROUTING', 58 | HYDE = 'HYDE', 59 | RERANKING = 'RERANKING' 60 | } 61 | 62 | const defaultOptions: ChatOptions = { 63 | systemMessage: 'You are a knowledgeable history, geography and tourist assistant, knowing everything about the capitals of the world.\n\nYour role is to write reports about a particular location or event, focusing on the key topics asked by the user.\n\nLet us focus on world capitals today', 64 | useVertex: true, 65 | useTools: false, 66 | enableRAG: false, 67 | useAgents: false, 68 | useWebsearch: false, 69 | model: models[0], 70 | enableSafety: false, 71 | useGuardrails: false, 72 | evaluateResponse: false, 73 | chunkingType: ChunkingType.NONE, 74 | filtering: true, 75 | queryCompression: false, 76 | queryRouting: false, 77 | hyde: false, 78 | reranking: false, 79 | writeActions: false, 80 | showDataSources: true, 81 | capital: capitals[0], 82 | continent: continents[0] 83 | }; 84 | 85 | export default function AiPatterns() { 86 | const [chatId, setChatId] = useState(nanoid()); 87 | 88 | const {field, model, read, value} = useForm(ChatOptionsModel); 89 | 90 | useEffect(() => { 91 | read(defaultOptions) 92 | }, []); 93 | 94 | useEffect(() => { 95 | const chat = document.querySelector('vaadin-scroller'); 96 | 97 | const handleClick = (event: Event) => { // Change the type to Event 98 | const target = event.target as HTMLElement; 99 | if (target.tagName === 'H4') { 100 | target.classList.toggle('expanded'); 101 | } 102 | }; 103 | 104 | if (chat) { 105 | chat.addEventListener('click', handleClick); 106 | } 107 | 108 | return () => { 109 | if (chat) { 110 | chat.removeEventListener('click', handleClick); 111 | } 112 | }; 113 | }, []); 114 | 115 | async function resetChat() { 116 | setChatId(nanoid()); 117 | } 118 | 119 | return ( 120 |
121 |
122 |

🌐 World Capitals

123 | 124 | 128 |
129 |
130 |
131 |

LLMs

132 | 133 |