diff --git a/.gitignore b/.gitignore index 0939aa7..6fbdeca 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,9 @@ sonar-project.properties __pycache__/ *.py[cod] *$py.class -env.sh +spring_ai/src/main/resources/data/sandbox_settings.json +spring_ai/target/** +spring_ai/create_user.sql +spring_ai/drop.sql +start.sh +spring_ai/env.sh diff --git a/app/src/modules/st_common.py b/app/src/modules/st_common.py index 078e872..fbc3fc8 100644 --- a/app/src/modules/st_common.py +++ b/app/src/modules/st_common.py @@ -6,6 +6,11 @@ import json import copy +import io +import zipfile +import tempfile +import shutil +import os # Streamlit import streamlit as st @@ -18,8 +23,13 @@ import modules.help as custom_help from modules.chatbot import ChatCmd -logger = logging_config.logging.getLogger("modules.st_common") +from langchain_huggingface import HuggingFaceEndpointEmbeddings +from langchain_openai import OpenAIEmbeddings +from langchain_ollama import OllamaEmbeddings +from langchain_cohere import CohereEmbeddings + +logger = logging_config.logging.getLogger("modules.st_common") def clear_initialized(): """Reset the initialization of the ChatBot""" @@ -90,6 +100,7 @@ def reset_rag(): # Set the RAG Prompt set_prompt() + state.show_download_spring_ai = False def initialize_rag(): @@ -238,6 +249,223 @@ def initialize_chatbot(ll_model): return cmd +def get_yaml_obaas(session_state_json,provider): + + ########## OBAAS VERSION ########## + # eureka: + # instance: + # hostname: ${spring.application.name} + # preferIpAddress: true + # client: + # service-url: + # defaultZone: ${eureka.service-url} + # fetch-registry: true + # register-with-eureka: true + # enabled: true + vector_table,_= utilities.get_vs_table(session_state_json["rag_params"]["model"],session_state_json["rag_params"]["chunk_size"],session_state_json["rag_params"]["chunk_overlap"],session_state_json["rag_params"]["distance_metric"]) + instr_context=session_state_json["lm_instr_config"][session_state_json["lm_instr_prompt"]]["prompt"] + + yaml_base=f""" +server: + servlet: + context-path: /v1 +spring: + datasource: + url: ${{spring.datasource.url}} + username: ${{spring.datasource.username}} + password: ${{spring.datasource.password}} + ai: + vectorstore: + oracle: + distance-type: {session_state_json["rag_params"]["distance_metric"]} + remove-existing-vector-store-table: True + initialize-schema: True + index-type: None + """ + + yaml_base_aims=f""" +aims: + context_instr: \"{instr_context}\" + vectortable: + name: {vector_table} + rag_params: + search_type: Similarity + top_k: {session_state_json.get("rag_params", {}).get("top_k",4)} +""" + openai_yaml=f""" + openai: + base-url: \"{session_state_json["ll_model_config"][session_state_json["ll_model"]]["url"]}\" + api-key: + chat: + options: + temperature: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["temperature"][0]} + frequencyPenalty: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["frequency_penalty"][0]} + presencePenalty: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["presence_penalty"][0]} + maxTokens: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["max_tokens"][0]} + topP: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["top_p"][0]} + model: {session_state_json["ll_model"]} + embedding: + options: + model: {session_state_json["rag_params"]["model"]} + """ + + ollama_yaml=f""" + ollama: + base-url: "http://ollama.ollama.svc.cluster.local:11434" + chat: + options: + top-p: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["top_p"][0]} + presence-penalty: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["presence_penalty"][0]} + frequency-penalty: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["frequency_penalty"][0]} + temperature: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["temperature"][0]} + num-predict: {session_state_json["ll_model_config"][session_state_json["ll_model"]]["max_tokens"][0]} + model: \"{session_state_json["ll_model"]}\" + embedding: + options: + model: \"{session_state_json["rag_params"]["model"]}\" + """ + + if provider=="ollama": + yaml = yaml_base + ollama_yaml + yaml_base_aims + else: + yaml = yaml_base + openai_yaml + yaml_base_aims + + return yaml + +def get_yaml_env(session_state_json,provider): + + OLLAMA_MODEL="llama3.1" + + instr_context=session_state_json["lm_instr_config"][session_state_json["lm_instr_prompt"]]["prompt"] + vector_table,_= utilities.get_vs_table(session_state_json["rag_params"]["model"],session_state_json["rag_params"]["chunk_size"],session_state_json["rag_params"]["chunk_overlap"],session_state_json["rag_params"]["distance_metric"]) + + logger.info("ll_model selected: %s",session_state_json["ll_model"]) + logger.info(session_state_json["ll_model"] != OLLAMA_MODEL) + + if session_state_json["ll_model"] != OLLAMA_MODEL: + env_vars_LLM= f""" + export OPENAI_CHAT_MODEL={session_state_json["ll_model"]} + export OPENAI_EMBEDDING_MODEL={session_state_json["rag_params"]["model"]} + export OPENAI_URL=\"{session_state_json["ll_model_config"][session_state_json["ll_model"]]["url"]}\" + export OP_TEMPERATURE={session_state_json["ll_model_config"][session_state_json["ll_model"]]["temperature"][0]} + export OP_FREQUENCY_PENALTY={session_state_json["ll_model_config"][session_state_json["ll_model"]]["frequency_penalty"][0]} + export OP_PRESENCE_PENALTY={session_state_json["ll_model_config"][session_state_json["ll_model"]]["presence_penalty"][0]} + export OP_MAX_TOKENS={session_state_json["ll_model_config"][session_state_json["ll_model"]]["max_tokens"][0]} + export OP_TOP_P={session_state_json["ll_model_config"][session_state_json["ll_model"]]["top_p"][0]} + export OL_TEMPERATURE= + export OL_FREQUENCY_PENALTY= + export OL_PRESENCE_PENALTY= + export OL_MAX_TOKENS= + export OL_TOP_P= + export OLLAMA_CHAT_MODEL=\"\" + export OLLAMA_EMBEDDING_MODEL=\"\" + """ + + else: + env_vars_LLM= f""" + export OPENAI_CHAT_MODEL=\"\" + export OPENAI_EMBEDDING_MODEL=\"\" + export OPENAI_URL=\"\" + export OLLAMA_CHAT_MODEL=\"{session_state_json["ll_model"]}\" + export OLLAMA_EMBEDDING_MODEL=\"{session_state_json["rag_params"]["model"]}\" + export OL_TEMPERATURE={session_state_json["ll_model_config"][session_state_json["ll_model"]]["temperature"][0]} + export OL_FREQUENCY_PENALTY={session_state_json["ll_model_config"][session_state_json["ll_model"]]["frequency_penalty"][0]} + export OL_PRESENCE_PENALTY={session_state_json["ll_model_config"][session_state_json["ll_model"]]["presence_penalty"][0]} + export OL_MAX_TOKENS={session_state_json["ll_model_config"][session_state_json["ll_model"]]["max_tokens"][0]} + export OL_TOP_P={session_state_json["ll_model_config"][session_state_json["ll_model"]]["top_p"][0]} + export OP_TEMPERATURE= + export OP_FREQUENCY_PENALTY= + export OP_PRESENCE_PENALTY= + export OP_MAX_TOKENS= + export OP_TOP_P= + """ + + env_vars= f""" + export SPRING_AI_OPENAI_API_KEY=$OPENAI_API_KEY + export DB_DSN=\"jdbc:oracle:thin:@{session_state_json["db_config"]["dsn"]}\" + export DB_USERNAME=\"{session_state_json["db_config"]["user"]}\" + export DB_PASSWORD=\"{session_state_json["db_config"]["password"]}\" + export DISTANCE_TYPE={session_state_json["rag_params"]["distance_metric"]} + export OLLAMA_BASE_URL=\"{session_state_json["ll_model_config"][OLLAMA_MODEL]["url"]}\" + export CONTEXT_INSTR=\"{instr_context}\" + export TOP_K={session_state_json.get("rag_params", {}).get("top_k",4)} + + + + export VECTOR_STORE={vector_table} + export PROVIDER={provider} + mvn spring-boot:run -P {provider} + """ + logger.info(env_vars_LLM+env_vars) + + return env_vars_LLM+env_vars + + +def create_zip(state_dict_filt, provider): + # Source directory that you want to copy + toCopy=["mvnw","mvnw.cmd","pom.xml","README.md"] + source_dir_root = '../../spring_ai' + source_dir = '../../spring_ai/src' + + logger.info(f"Local dir : {os.getcwd()}") + # Using TemporaryDirectory + with tempfile.TemporaryDirectory() as temp_dir: + destination_dir= os.path.join(temp_dir, "spring_ai") + + shutil.copytree(source_dir, os.path.join(temp_dir, "spring_ai/src")) + for item in toCopy: + shutil.copy(os.path.join(source_dir_root,item), os.path.join(temp_dir, "spring_ai")) + logger.info(f"Temporary directory created and copied: {temp_dir}") + + zip_buffer = io.BytesIO() + + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for foldername, subfolders, filenames in os.walk(destination_dir): + for filename in filenames: + file_path = os.path.join(foldername, filename) + + arcname = os.path.relpath(file_path, destination_dir) # Make the path relative + zip_file.write(file_path, arcname) + env_content = get_yaml_env(state_dict_filt, provider) + obaas_yaml = get_yaml_obaas(state_dict_filt,provider) + zip_file.writestr('env.sh', env_content) + zip_file.writestr('src/main/resources/application-obaas.yml', obaas_yaml) + zip_buffer.seek(0) + return zip_buffer + +# Check if the conf is full ollama or openai, currently supported for springai export +def check_hybrid_conf(session_state_json): + + embedding_models = meta.embedding_models() + chat_models = meta.ll_models() + + embModel = embedding_models.get(session_state_json["rag_params"].get("model")) + chatModel = chat_models.get(session_state_json["ll_model"]) + logger.info("Model: %s",session_state_json["ll_model"]) + logger.info("Embedding Model embModel: %s",embModel) + logger.info("Chat Model: %s",chatModel) + if (chatModel is not None) and (embModel is not None): + + logger.info("Embeddings to string: %s", str(embModel["api"])) + + if ("OpenAI" in str(embModel["api"])) and (chatModel["api"]== "OpenAI"): + logger.info("RETURN openai") + logger.info("chatModel[api]: %s",chatModel["api"]) + logger.info("Embedding Model embModel[api]: %s",embModel["api"]) + return "openai" + else: + if ("Ollama" in str(embModel["api"])) and (chatModel["api"]== "ChatOllama"): + logger.info("RETURN ollama") + logger.info("chatModel[api]: %s",chatModel["api"]) + logger.info("Embedding Model embModel[api]: %s",embModel["api"]) + return "ollama" + else: + return "hybrid" + else: + return "hybrid" + + + ################################### # Language Model Sidebar ################################### @@ -582,6 +810,8 @@ def empty_key(obj): state_dict_filt = empty_key(state_dict_filt) session_state_json = json.dumps(state_dict_filt, indent=4) # Only allow exporting settings if tools/admin is enabled + + if not state.disable_tools and not state.disable_admin: st.sidebar.download_button( label="Download Settings", @@ -589,3 +819,16 @@ def empty_key(obj): file_name="sandbox_settings.json", use_container_width=True, ) + + + state.provider= check_hybrid_conf(state_dict_filt) + logger.info("Provider type: %s", state.provider) + + if not state.disable_tools and not state.disable_admin and state.provider != "hybrid": + st.sidebar.download_button( + label="Download SpringAI", + data=create_zip(state_dict_filt, state.provider), # Generate zip on the fly + file_name="spring_ai.zip", # Zip file name + mime="application/zip", # Mime type for zip file + use_container_width=True, + ) diff --git a/spring_ai/README.md b/spring_ai/README.md new file mode 100644 index 0000000..a33e022 --- /dev/null +++ b/spring_ai/README.md @@ -0,0 +1,226 @@ +# Spring AI template + +## How to run: +Prepare two configurations in the `oaim-sandbox`, based on vector stores created using this kind of configuration: + +* OLLAMA: + * Embbeding model: mxbai-embed-large + * Chunk size: 512 + * overlap: 103 + * distance: COSINE + +* OPENAI: + * Embdeding model: text-embedding-3-small + * Chunk size: 8191 + * overlap: 1639 + * distance: COSINE + +and loading a document like [Oracle® Database +Get Started with Java Development](https://docs.oracle.com/en/database/oracle/oracle-database/23/tdpjd/get-started-java-development.pdf). + +Download one of them through the `Download SpringAI` button. Unzip the content and set the executable permission on the `env.sh` with `chmod 755 ./env.sh`. + +Edit `env.sh` to add only the DB_PASSWORD not exported, as in this example: +``` +export SPRING_AI_OPENAI_API_KEY=$OPENAI_API_KEY +export DB_DSN="jdbc:oracle:thin:@localhost:1521/FREEPDB1" +export DB_USERNAME= +export DB_PASSWORD="" +export DISTANCE_TYPE=COSINE +export OPENAI_CHAT_MODEL=gpt-4o-mini +export OPENAI_EMBEDDING_MODEL=text-embedding-3-small +export OLLAMA_CHAT_MODEL="llama3.1" +export OLLAMA_EMBEDDING_MODEL=mxbai-embed-large +export OLLAMA_BASE_URL="http://:11434" +export CONTEXT_INSTR=" You are an assistant for question-answering tasks. Use the retrieved Documents and history to answer the question as accurately and comprehensively as possible. Keep your answer grounded in the facts of the Documents, be concise, and reference the Documents where possible. If you don't know the answer, just say that you are sorry as you don't haven't enough information. " +export TOP_K=4 +export VECTOR_STORE=TEXT_EMBEDDING_3_SMALL_8191_1639_COSINE +export PROVIDER=openai +mvn spring-boot:run -P openai +``` + +Drop the table `SPRING_AI_VECTORS`, if exists, running in sql: + +``` +DROP TABLE SPRING_AI_VECTORS CASCADE CONSTRAINTS; +COMMIT; +``` + +Start with: + +``` +./env.sh +``` + +This project contains a web service that will accept HTTP GET requests at + +* `http://localhost:8080/v1/chat/completions`: to use RAG via OpenAI REST API + +* `http://localhost:8080/v1/service/llm` : to chat straight with the LLM used +* `http://localhost:8080/v1/service/search/`: to search for document similar to the message provided + + +RAG call example with `openai` build profile: + +``` +curl -X POST "localhost:8080/v1/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{"message": "Can I use any kind of development environment to run the example?"}' | jq . +``` + +the response with RAG: + +``` +{ + "choices": [ + { + "message": { + "content": "Yes, you can use any kind of development environment to run the example, but for ease of development, the guide specifically mentions using an integrated development environment (IDE). It uses IntelliJ IDEA Community version as an example for creating and updating the files for the application (see Document 96EECD7484D3B56C). However, you are not limited to this IDE and can choose any development environment that suits your needs." + } + } + ] +} +``` + +or the request without RAG: +``` +curl --get --data-urlencode 'message=Can I use any kind of development environment to run the example?' localhost:8080/v1/service/llm | jq . +``` + +response not grounded: + +``` +{ + "completion": "Yes, you can use various development environments to run examples, depending on the programming language and the specific example you are working with. Here are some common options:\n\n1. **Integrated Development Environments (IDEs)**:\n - **Visual Studio Code**: A versatile code editor that supports many languages through extensions.\n - **PyCharm**: Great for Python development.\n - **Eclipse**: Commonly used for Java development.\n - **IntelliJ IDEA**: Another popular choice for Java and other languages.\n - **Xcode**: For macOS and iOS development (Swift, Objective-C).\n\n2. **Text Editors**:\n - **Sublime Text**: A lightweight text editor with support for many languages.\n - **Atom**: A hackable text editor for the 21st century.\n - **Notepad++**: A free source code editor for Windows.\n\n3. **Command Line Interfaces**:\n - You can run" +} +``` + +## Oracle Backend for Microservices and AI + + + +* Add in `application-obaas.yml` the **OPENAI_API_KEY**, if the deployement is based on the OpenAI LLM services: +``` + openai: + base-url: + api-key: +``` + +* Build, depending the provider ``: + +``` +mvn clean package -DskipTests -P -Dspring-boot.run.profiles=obaas +``` + +* Set, one time only, the ollama server running in the **Oracle Backend for Microservices and AI**. Prepare an `ollama-values.yaml`: +``` +ollama: + gpu: + enabled: true + type: 'nvidia' + number: 1 + models: + - llama3.1 + - mxbai-embed-large +nodeSelector: + node.kubernetes.io/instance-type: VM.GPU.A10.1 +``` + +* execute: +``` +kubectl create ns ollama +helm install ollama ollama-helm/ollama --namespace ollama --values ollama-values.yaml +``` +* check: +``` +kubectl -n ollama exec svc/ollama -- ollama ls +``` +it should be: +``` +NAME ID SIZE MODIFIED +llama3.1:latest 42182419e950 4.7 GB About a minute ago +mxbai-embed-large:latest 468836162de7 669 MB About a minute ago +``` +* test a single LLM: +``` +kubectl -n ollama exec svc/ollama -- ollama run "llama3.1" "what is spring boot?" +``` + +* **NOTE**: The Microservices will access to the ADB23ai on which the vector store table should be created as done in the local desktop example shown before. To access the OAIM-Sandbox running on **Oracle Backend for Microservices and AI** and create the same configuration, let's do: + * tunnel: + ``` + kubectl -n oaim-sandbox port-forward svc/oaim-sandbox 8181:8501 + ``` + * on localhost: + ``` + http://localhost:8181/ai-sandbox + ``` + +* Deploy with `oractl` on a new schema `vector`: + * tunnel: + ``` + kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 + ``` + + * oractl: + ``` + create --app-name rag + bind --app-name rag --service-name myspringai --username vector + ``` + + +* the `bind` will create the new user, if not exists, but to have the `SPRING_AI_VECTORS` table compatible with SpringAI Oracle vector store adapter, the microservices need to access to the vector store table created by the OAIM-sandbox with user ADMIN on ADB: +``` +GRANT SELECT ON ADMIN.MXBAI_EMBED_LARGE_512_103_COSINE TO vector; +``` +* then deploy: +``` +deploy --app-name rag --service-name myspringai --artifact-path /target/myspringai-0.0.1-SNAPSHOT.jar --image-version 0.0.1 --java-version ghcr.io/oracle/graalvm-native-image-obaas:21 --service-profile obaas +``` +* test: +``` +kubectl -n rag port-forward svc/myspringai 9090:8080 +``` +* from shell: +``` +curl -X POST "http://localhost:9090/v1/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{"message": "Can I use any kind of development environment to run the example?"}' | jq . +``` +it should return: +``` +{ + "choices": [ + { + "message": { + "content": "Based on the provided documents, it seems that a specific development environment (IDE) is recommended for running the example.\n\nIn document \"67D5C08DF7F7480F\", it states: \"This guide uses IntelliJ Idea community version to create and update the files for this application.\" (page 17)\n\nHowever, there is no information in the provided documents that explicitly prohibits using other development environments. In fact, one of the articles mentions \"Application. Use these instructions as a reference.\" without specifying any particular IDE.\n\nTherefore, while it appears that IntelliJ Idea community version is recommended, I couldn't find any definitive statement ruling out the use of other development environments entirely.\n\nIf you'd like to run the example with a different environment, it might be worth investigating further or consulting additional resources. Sorry if this answer isn't more conclusive!" + } + } + ] +} +``` + + + + + + + +## Prerequisites + +Before using the AI commands, make sure you have a developer token from OpenAI. + +Create an account at [OpenAI Signup](https://platform.openai.com/signup) and generate the token at [API Keys](https://platform.openai.com/account/api-keys). + +The Spring AI project defines a configuration property named `spring.ai.openai.api-key` that you should set to the value of the `API Key` obtained from `openai.com`. + +Exporting an environment variable is one way to set that configuration property. +```shell +export SPRING_AI_OPENAI_API_KEY= +``` + +Setting the API key is all you need to run the application. +However, you can find more information on setting started in the [Spring AI reference documentation section on OpenAI Chat](https://docs.spring.io/spring-ai/reference/api/clients/openai-chat.html). + diff --git a/spring_ai/mvnw b/spring_ai/mvnw new file mode 100755 index 0000000..eb65ff2 --- /dev/null +++ b/spring_ai/mvnw @@ -0,0 +1,305 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ "$MVNW_REPOURL" = true]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring_ai/mvnw.cmd b/spring_ai/mvnw.cmd new file mode 100755 index 0000000..4f5150a --- /dev/null +++ b/spring_ai/mvnw.cmd @@ -0,0 +1,172 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" + ) + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/spring_ai/pom.xml b/spring_ai/pom.xml new file mode 100644 index 0000000..4e29186 --- /dev/null +++ b/spring_ai/pom.xml @@ -0,0 +1,163 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.0 + + + com.example + myspringai + 0.0.1-SNAPSHOT + myspringai + Simple AI Application using OpenAPI Service + + 17 + 1.0.0-SNAPSHOT + + + + + org.springframework.ai + spring-ai-bom + 1.0.0-SNAPSHOT + pom + import + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.ai + spring-ai-pdf-document-reader + ${spring-ai.version} + + + org.springframework.ai + spring-ai-tika-document-reader + ${spring-ai.version} + + + + + org.springframework.ai + spring-ai-oracle-store-spring-boot-starter + + + + com.oracle.database.jdbc + ojdbc11 + 23.5.0.24.07 + + + + org.springframework.boot + spring-boot-starter-jdbc + + + com.oracle.database.spring + oracle-spring-boot-starter-wallet + 23.4.0 + + + + + + + + + + ollama + + false + + + + org.springframework.ai + spring-ai-ollama-spring-boot-starter + + + + + + + openai + + true + + + + org.springframework.ai + spring-ai-openai-spring-boot-starter + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + \ No newline at end of file diff --git a/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/AIController.java b/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/AIController.java new file mode 100644 index 0000000..4fa6c43 --- /dev/null +++ b/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/AIController.java @@ -0,0 +1,242 @@ +package org.springframework.ai.openai.samples.helloworld; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.reader.ExtractedTextFormatter; +import org.springframework.ai.reader.pdf.PagePdfDocumentReader; +import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig; +import org.springframework.ai.transformer.splitter.TokenTextSplitter; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.SimpleVectorStore.Similarity; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.ai.vectorstore.OracleVectorStore; + +import jakarta.annotation.PostConstruct; + +import org.springframework.core.io.Resource; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; + +import java.util.Iterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@RestController +class AIController { + + @Autowired + private final OracleVectorStore vectorStore; + + @Autowired + private final EmbeddingModel embeddingModel; + + @Autowired + private final ChatClient chatClient; + + @Value("${aims.vectortable.name}") + private String legacyTable; + + @Value("${aims.context_instr}") + private String contextInstr; + + @Value("${aims.rag_params.search_type}") + private String searchType; + + @Value("${aims.rag_params.top_k}") + private int TOPK; + + @Autowired + private JdbcTemplate jdbcTemplate; + + private static final Logger logger = LoggerFactory.getLogger(AIController.class); + + AIController(ChatClient chatClient, EmbeddingModel embeddingModel, OracleVectorStore vectorStore) { + + this.chatClient = chatClient; + this.embeddingModel = embeddingModel; + this.vectorStore = vectorStore; + + } + + @GetMapping("/service/llm") + Map completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { + + return Map.of( + "completion", + chatClient.prompt() + .user(message) + .call() + .content()); + } + + @PostConstruct + public void insertData() { + String sqlUser = "SELECT USER FROM DUAL"; + String user = ""; + String sql = ""; + + user = jdbcTemplate.queryForObject(sqlUser, String.class); + if (doesTableExist(legacyTable,user)!=-1) { + // RUNNING LOCAL + logger.info("Running local with user: " + user); + sql = "INSERT INTO " + user + ".SPRING_AI_VECTORS (ID, CONTENT, METADATA, EMBEDDING) " + + "SELECT ID, TEXT, METADATA, EMBEDDING FROM " + user + "." + legacyTable; + } else { + // RUNNING in OBAAS + logger.info("Running on OBaaS with user: " + user); + sql = "INSERT INTO " + user + ".SPRING_AI_VECTORS (ID, CONTENT, METADATA, EMBEDDING) " + + "SELECT ID, TEXT, METADATA, EMBEDDING FROM ADMIN." + legacyTable; + } + // Execute the insert + logger.info("doesExist"+ user + ": "+ doesTableExist("SPRING_AI_VECTORS",user)); + if (countRecordsInTable("SPRING_AI_VECTORS",user)==0) { + // First microservice execution + logger.info("Table " + user + ".SPRING_AI_VECTORS doesn't exist: create from ADMIN/USER." + legacyTable); + jdbcTemplate.update(sql); + } else { + // Table conversion already done + logger.info("Table SPRING_AI_VECTORS exists: drop before if you want use new contents " + legacyTable); + } + } + + public int countRecordsInTable(String tableName, String schemaName) { + // Dynamically construct the SQL query with the table and schema names + String sql = String.format("SELECT COUNT(*) FROM %s.%s", schemaName.toUpperCase(), tableName.toUpperCase()); + logger.info("Checking if table is empty: " + tableName + " in schema: " + schemaName); + + try { + // Execute the query and get the count of records in the table + Integer count = jdbcTemplate.queryForObject(sql, Integer.class); + + // Return the count if it's not null, otherwise return -1 + return count != null ? count : -1; + } catch (Exception e) { + logger.error("Error checking table record count: " + e.getMessage()); + return -1; // Return -1 in case of an error + } + } + + public int doesTableExist(String tableName, String schemaName) { + String sql = "SELECT COUNT(*) FROM all_tables WHERE table_name = ? AND owner = ?"; + logger.info("Checking if table exists: " + tableName + " in schema: " + schemaName); + + try { + // Query the system catalog to check for the existence of the table in the given + // schema + Integer count = jdbcTemplate.queryForObject(sql, Integer.class, tableName.toUpperCase(), + schemaName.toUpperCase()); + + if (count != null && count > 0) { return count;} + else {return -1;} + } catch (Exception e) { + logger.error("Error checking table existence: " + e.getMessage()); + return -1; + } + } + + public Prompt promptEngineering(String message, String contextInstr) { + + String template = """ + DOCUMENTS: + {documents} + + QUESTION: + {question} + + INSTRUCTIONS:"""; + + String default_Instr = """ + Answer the users question using the DOCUMENTS text above. + Keep your answer ground in the facts of the DOCUMENTS. + If the DOCUMENTS doesn’t contain the facts to answer the QUESTION, return: + I'm sorry but I haven't enough information to answer. + """; + + template = template + "\n" + contextInstr; + + List similarDocuments = this.vectorStore.similaritySearch( + SearchRequest.query(message).withTopK(TOPK)); + + StringBuilder context = createContext(similarDocuments); + + PromptTemplate promptTemplate = new PromptTemplate(template); + + Prompt prompt = promptTemplate.create(Map.of("documents", context, "question", message)); + + logger.info(prompt.toString()); + + return prompt; + + } + + StringBuilder createContext(List similarDocuments) { + String START = "\n
\n"; + String STOP = "\n
\n"; + + Iterator iterator = similarDocuments.iterator(); + StringBuilder context = new StringBuilder(); + while (iterator.hasNext()) { + Document document = iterator.next(); + context.append(document.getId() + "."); + context.append(START + document.getFormattedContent() + STOP); + } + return context; + } + + @PostMapping("/chat/completions") + Map completionRag(@RequestBody Map requestBody) { + + String message = requestBody.getOrDefault("message", "Tell me a joke"); + Prompt prompt = promptEngineering(message, contextInstr); + logger.info(prompt.getContents()); + try { + String content = chatClient.prompt(prompt).call().content(); + Map messageMap = Map.of("content", content); + Map choicesMap = Map.of("message", messageMap); + List> choicesList = List.of(choicesMap); + + return Map.of("choices", choicesList); + + } catch (Exception e) { + logger.error("Error while fetching completion", e); + return Map.of("error", "Failed to fetch completion"); + } + } + + @GetMapping("/service/search") + List> search(@RequestParam(value = "message", defaultValue = "Tell me a joke") String query, + @RequestParam(value = "topk", defaultValue = "5") Integer topK) { + + List similarDocs = vectorStore.similaritySearch(SearchRequest.defaults() + .withQuery(query) + .withTopK(topK)); + + List> resultList = new ArrayList<>(); + for (Document d : similarDocs) { + Map metadata = d.getMetadata(); + Map doc = new HashMap<>(); + doc.put("id", d.getId()); + resultList.add(doc); + } + ; + return resultList; + } +} diff --git a/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Application.java b/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Application.java new file mode 100644 index 0000000..48e14d1 --- /dev/null +++ b/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Application.java @@ -0,0 +1,24 @@ +package org.springframework.ai.openai.samples.helloworld; + +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.vectorstore.OracleVectorStore; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.core.JdbcTemplate; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + OracleVectorStore vectorStore(EmbeddingModel ec, JdbcTemplate t) { + OracleVectorStore ovs = new OracleVectorStore(t, ec, true); + return ovs; + } + +} diff --git a/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Config.java b/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Config.java new file mode 100644 index 0000000..b740e35 --- /dev/null +++ b/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Config.java @@ -0,0 +1,15 @@ +package org.springframework.ai.openai.samples.helloworld; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +class Config { + + @Bean + ChatClient chatClient(ChatClient.Builder builder) { + return builder.build(); + } +} diff --git a/spring_ai/src/main/resources/application-dev.yml b/spring_ai/src/main/resources/application-dev.yml new file mode 100644 index 0000000..090830c --- /dev/null +++ b/spring_ai/src/main/resources/application-dev.yml @@ -0,0 +1,50 @@ +server: + servlet: + context-path: /v1 +spring: + datasource: + url: ${DB_DSN} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + ai: + vectorstore: + oracle: + distance-type: ${DISTANCE_TYPE} + remove-existing-vector-store-table: True + initialize-schema: True + index-type: None + openai: + base-url: {OPENAI_URL} + api-key: {OPENAI_API_KEY} + chat: + options: + temperature: {OP_TEMPERATURE} + frequencyPenalty: {OP_FREQUENCY_PENALTY} + presencePenalty: {OP_PRESENCE_PENALTY} + maxTokens: {OP_MAX_TOKENS} + topP: {OP_TOP_P} + model: ${OPENAI_CHAT_MODEL} + embedding: + options: + model: ${OPENAI_EMBEDDING_MODEL} + ollama: + base-url: ${OLLAMA_BASE_URL} + chat: + options: + top-p: {OL_TOP_P} + presence-penalty: {OL_PRESENCE_PENALTY} + frequency-penalty: {OL_FREQUENCY_PENALTY} + temperature: {OL_TEMPERATURE} + num-predict: {OL_MAX_TOKENS} + model: ${OLLAMA_CHAT_MODEL} + embedding: + options: + model: ${OLLAMA_EMBEDDING_MODEL} +aims: + context_instr: ${CONTEXT_INSTR} + vectortable: + name: ${VECTOR_STORE} + rag_params: + search_type: Similarity + top_k: ${TOP_K} + diff --git a/spring_ai/src/main/resources/application.yml b/spring_ai/src/main/resources/application.yml new file mode 100644 index 0000000..3d7808a --- /dev/null +++ b/spring_ai/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev