├── .gitignore ├── LICENSE ├── README.md ├── img ├── AI_icon.svg ├── kotlin_icon.svg └── ktn_plugin_icon.svg ├── notebooks ├── agents │ ├── Evaluator-Optimizer Workflow.ipynb │ ├── Orchestrator-Workers Workflow.ipynb │ ├── Parallelization Workflow.ipynb │ ├── Prompt-Chaining Workflow.ipynb │ ├── Routing Workflow.ipynb │ └── image │ │ ├── agent.svg │ │ ├── evaluator_optimizer.svg │ │ ├── orchestrator_workers.svg │ │ ├── parallelization.svg │ │ ├── prompt_chaining.svg │ │ └── routing.svg ├── arc │ ├── SummarizerAgent.ipynb │ ├── TaskManagerAgent.ipynb │ └── WeatherAgent.ipynb ├── kinference │ ├── KIClassification.ipynb │ ├── KIGPT2.ipynb │ ├── ORTClassification.ipynb │ └── ORTGPT2.ipynb ├── langchain4j │ ├── LangChain4j_Overview.ipynb │ ├── SummarizingDocuments.ipynb │ └── data │ │ └── artificial_intelligence_wikipedia.txt ├── openai │ ├── OpenAI Image Recognition.ipynb │ ├── OpenAI Java SDK Overview.ipynb │ └── data │ │ └── cat.jpg ├── spring-ai │ ├── SpringAI_Overview.ipynb │ └── tutorials │ │ ├── 1. Intro.ipynb │ │ ├── 10. Local model.ipynb │ │ ├── 2. Prompts.ipynb │ │ ├── 3. Streaming.ipynb │ │ ├── 4. Tools.ipynb │ │ ├── 5. Structured Outputs.ipynb │ │ ├── 6. Advisors.ipynb │ │ ├── 7. RAG.ipynb │ │ ├── 8. text-to-image.ipynb │ │ ├── 9. text-to-audio.ipynb │ │ ├── data │ │ ├── black_holes.mp3 │ │ ├── harvard.wav │ │ └── kotlinFAQ.md │ │ └── images │ │ └── framework-manager.jpg └── xefAI │ └── xefAI_Overview.ipynb └── projects ├── langchain4j └── langchain4j-spring-boot │ ├── README.md │ ├── build.gradle.kts │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ └── main │ ├── kotlin │ └── io │ │ └── github │ │ └── devcrocod │ │ └── example │ │ └── langchain4j │ │ ├── Application.kt │ │ ├── aiservice │ │ ├── Assistant.kt │ │ ├── AssistantConfiguration.kt │ │ ├── AssistantController.kt │ │ ├── AssistantTools.kt │ │ └── StreamingAssistant.kt │ │ └── lowlevel │ │ └── ChatModelController.kt │ └── resources │ └── application.properties ├── mcp ├── brave │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── io │ │ └── github │ │ └── devcrocod │ │ └── example │ │ └── main.kt └── mcp-demo │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── composeApp │ ├── build.gradle.kts │ └── src │ │ ├── commonMain │ │ └── composeResources │ │ │ └── drawable │ │ │ └── compose-multiplatform.xml │ │ └── desktopMain │ │ └── kotlin │ │ └── io │ │ └── github │ │ └── devcrocod │ │ ├── App.kt │ │ ├── MCPClient.kt │ │ └── main.kt │ ├── gradle.properties │ ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── image │ ├── claude_desktop_answer.png │ ├── claude_desktop_mcp_hammer_icon.svg │ ├── claude_desktop_settings.png │ ├── claude_desktop_tools.png │ ├── claude_for_desktop_client.gif │ ├── compose_client.png │ ├── compose_mcp_client.gif │ ├── inspector_connect.png │ ├── inspector_test.png │ ├── mcp_demo.svg │ └── wizard.png │ ├── server │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── io │ │ └── github │ │ └── devcrocod │ │ └── example │ │ ├── data.kt │ │ ├── financialModelingPrep.kt │ │ ├── main.kt │ │ └── server.kt │ └── settings.gradle.kts └── spring-ai ├── helloworld ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ ├── kotlin │ └── io │ │ └── github │ │ └── devcrocod │ │ └── example │ │ └── aihelloworld │ │ ├── AIController.kt │ │ ├── Application.kt │ │ └── Config.kt │ └── resources │ └── application.properties ├── playground-flight-booking ├── .gitignore ├── README.md ├── build.gradle.kts ├── diagram.jpg ├── docker-compose.yml ├── frontend │ ├── components │ │ ├── Message.tsx │ │ ├── MessageList.tsx │ │ └── SeatSelection.tsx │ ├── index.html │ ├── themes │ │ └── customer-support-agent │ │ │ ├── styles.css │ │ │ └── theme.json │ └── views │ │ └── @index.tsx ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package-lock.json ├── package.json ├── settings.gradle.kts ├── src │ ├── main │ │ ├── kotlin │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── devcrocod │ │ │ │ └── example │ │ │ │ └── playground │ │ │ │ ├── Application.kt │ │ │ │ ├── client │ │ │ │ ├── AssistantService.kt │ │ │ │ └── BookingService.kt │ │ │ │ ├── data │ │ │ │ ├── Booking.kt │ │ │ │ ├── BookingClass.kt │ │ │ │ ├── BookingData.kt │ │ │ │ ├── BookingDetails.kt │ │ │ │ ├── BookingStatus.kt │ │ │ │ └── Customer.kt │ │ │ │ └── services │ │ │ │ ├── BookingTools.kt │ │ │ │ ├── CustomerSupportAssistant.kt │ │ │ │ ├── FlightBookingService.kt │ │ │ │ └── SeatChangeQueue.kt │ │ └── resources │ │ │ ├── application.properties │ │ │ ├── logback-spring.xml │ │ │ ├── mcp-servers-config2.json │ │ │ └── rag │ │ │ └── terms-of-service.txt │ └── test │ │ └── resources │ │ └── standalone_embed.sh ├── tsconfig.json ├── types.d.ts └── vite.config.ts ├── spring-ai-examples ├── .gitignore ├── build.gradle.kts ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ ├── kotlin │ └── io │ │ └── github │ │ └── devcrocod │ │ └── example │ │ ├── Application.kt │ │ ├── Config.kt │ │ ├── helloworld │ │ └── SimpleAiController.kt │ │ ├── output │ │ ├── ActorsFilms.kt │ │ └── OutputParserController.kt │ │ ├── prompttemplate │ │ └── PromptTemplateController.kt │ │ ├── rag │ │ ├── RagController.kt │ │ ├── RagService.kt │ │ └── config │ │ │ └── RagConfiguration.kt │ │ ├── roles │ │ └── RoleController.kt │ │ └── stuff │ │ ├── Completion.kt │ │ └── StuffController.kt │ └── resources │ ├── application.properties │ ├── data │ └── bikes.json │ ├── docs │ └── wikipedia-curling.md │ └── prompts │ ├── joke-prompt.st │ ├── qa-prompt.st │ ├── system-message.st │ └── system-qa.st ├── spring-ai-mcp-server-example ├── .gitignore ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── main.kt └── springAI-demo ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image ├── qdrant_collections.png ├── qdrant_loaded.png ├── qdrant_start.png ├── start_spring.png └── welcome_qdrant_dashboard.png ├── settings.gradle.kts └── src └── main ├── kotlin └── com │ └── example │ └── springai │ └── demo │ ├── KotlinSTDController.kt │ └── SpringAiDemoApplication.kt └── resources └── application.properties /.gitignore: -------------------------------------------------------------------------------- 1 | ### Kotlin ### 2 | *.class 3 | *.log 4 | *.ctxt 5 | .mtj.tmp/ 6 | *.jar 7 | *.war 8 | *.nar 9 | *.ear 10 | *.zip 11 | *.tar.gz 12 | *.rar 13 | hs_err_pid* 14 | replay_pid* 15 | 16 | ### Gradle ### 17 | .gradle 18 | **/build/ 19 | !src/**/build/ 20 | gradle-app.setting 21 | !gradle-wrapper.jar 22 | .gradletasknamecache 23 | 24 | ### Maven ### 25 | target/ 26 | pom.xml.tag 27 | pom.xml.releaseBackup 28 | pom.xml.versionsBackup 29 | pom.xml.next 30 | release.properties 31 | dependency-reduced-pom.xml 32 | buildNumber.properties 33 | .mvn/timing.properties 34 | 35 | ### IntelliJ IDEA ### 36 | .idea/ 37 | *.iws 38 | *.iml 39 | *.ipr 40 | out/ 41 | !**/src/main/**/out/ 42 | !**/src/test/**/out/ 43 | 44 | ### Eclipse ### 45 | .apt_generated 46 | .classpath 47 | .factorypath 48 | .project 49 | .settings 50 | .springBeans 51 | .sts4-cache 52 | bin/ 53 | !**/src/main/**/bin/ 54 | !**/src/test/**/bin/ 55 | 56 | ### NetBeans ### 57 | /nbproject/private/ 58 | /nbbuild/ 59 | /dist/ 60 | /nbdist/ 61 | /.nb-gradle/ 62 | 63 | ### VS Code ### 64 | .vscode/ 65 | 66 | ### Mac OS ### 67 | .DS_Store 68 | .AppleDouble 69 | .LSOverride 70 | Icon 71 | ._* 72 | .DocumentRevisions-V100 73 | .fseventsd 74 | .Spotlight-V100 75 | .TemporaryItems 76 | .Trashes 77 | .VolumeIcon.icns 78 | .com.apple.timemachine.donotpresent 79 | 80 | ### Windows ### 81 | Thumbs.db 82 | Thumbs.db:encryptable 83 | ehthumbs.db 84 | ehthumbs_vista.db 85 | *.stackdump 86 | [Dd]esktop.ini 87 | $RECYCLE.BIN/ 88 | *.lnk 89 | 90 | ### Linux ### 91 | *~ 92 | .fuse_hidden* 93 | .directory 94 | .Trash-* 95 | .nfs* 96 | 97 | ### Project Specific ### 98 | *.log 99 | logs/ 100 | temp/ 101 | tmp/ 102 | .env 103 | .env.local 104 | .output.txt 105 | node_modules/ 106 | dist/ 107 | coverage/ -------------------------------------------------------------------------------- /img/AI_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /img/kotlin_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /img/ktn_plugin_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notebooks/arc/SummarizerAgent.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "metadata": {}, 5 | "cell_type": "markdown", 6 | "source": [ 7 | "# Summarizer Agent\n", 8 | "\n", 9 | "In this notebook, we showcase how to create and use a Summarizer Agent that processes external web pages to produce concise summaries. The core idea involves:\n", 10 | "\n", 11 | "1. **Filtering the Input**\n", 12 | " The agent checks for a web page URL in the user’s initial message. If a valid URL is present, it fetches the HTML content of that page.\n", 13 | "\n", 14 | "2. **Preprocessing the Page**\n", 15 | " Before summarizing, the agent strips away HTML tags or other unnecessary data for efficient handling.\n", 16 | "\n", 17 | "3. **Augmenting the Prompt**\n", 18 | " The agent then appends the cleaned-up page text to the user’s question, ensuring all necessary information is effectively included in the conversation.\n", 19 | "\n", 20 | "4. **Producing a Short Summary**\n", 21 | " With the web page data in place, the agent can generate a concise summary of the page. If the user follows up with more questions about the same page, the agent can incorporate the previously retrieved content to continue the discussion.\n", 22 | "\n", 23 | "By following this approach, the Summarizer Agent seamlessly combines downloading content with large language model capabilities, providing succinct answers that capture the essence of the requested web resource." 24 | ] 25 | }, 26 | { 27 | "metadata": { 28 | "ExecuteTime": { 29 | "end_time": "2025-02-07T16:42:50.124188Z", 30 | "start_time": "2025-02-07T16:42:49.207589Z" 31 | } 32 | }, 33 | "cell_type": "code", 34 | "source": [ 35 | "%useLatestDescriptors\n", 36 | "@file:DependsOn(\"ai.ancf.lmos:arc-langchain4j-client:0.120.0\")\n", 37 | "@file:DependsOn(\"ai.ancf.lmos:arc-reader-html:0.120.0\")\n", 38 | "@file:DependsOn(\"dev.langchain4j:langchain4j-open-ai:1.0.0-beta1\")" 39 | ], 40 | "outputs": [], 41 | "execution_count": 3 42 | }, 43 | { 44 | "metadata": { 45 | "ExecuteTime": { 46 | "end_time": "2025-02-07T16:42:52.563368Z", 47 | "start_time": "2025-02-07T16:42:52.497961Z" 48 | } 49 | }, 50 | "cell_type": "code", 51 | "source": [ 52 | "import ai.ancf.lmos.arc.agents.llm.ChatCompleter\n", 53 | "import ai.ancf.lmos.arc.agents.llm.ChatCompletionSettings\n", 54 | "import ai.ancf.lmos.arc.client.langchain4j.LangChainClient\n", 55 | "import ai.ancf.lmos.arc.client.langchain4j.LangChainConfig\n", 56 | "import dev.langchain4j.model.openai.OpenAiChatModel\n", 57 | "\n", 58 | "val openAiApiKey = System.getenv(\"OPENAI_API_KEY\") ?: \"YOUR-OPENAI-API-KEY\"\n", 59 | "\n", 60 | "val chatCompleterProvider: (String?) -> ChatCompleter = {\n", 61 | " LangChainClient(\n", 62 | " languageModel = LangChainConfig(\n", 63 | " modelName = \"gpt-4\",\n", 64 | " url = null,\n", 65 | " apiKey = openAiApiKey,\n", 66 | " accessKeyId = null,\n", 67 | " secretAccessKey = null,\n", 68 | " ), clientBuilder = { config, _ ->\n", 69 | " OpenAiChatModel.builder()\n", 70 | " .modelName(config.modelName)\n", 71 | " .apiKey(config.apiKey)\n", 72 | " .build()\n", 73 | " }\n", 74 | " )\n", 75 | "}" 76 | ], 77 | "outputs": [], 78 | "execution_count": 4 79 | }, 80 | { 81 | "metadata": { 82 | "ExecuteTime": { 83 | "end_time": "2025-02-07T16:43:10.658020Z", 84 | "start_time": "2025-02-07T16:43:10.413556Z" 85 | } 86 | }, 87 | "cell_type": "code", 88 | "source": [ 89 | "import ai.ancf.lmos.arc.agents.DSLAgents\n", 90 | "import ai.ancf.lmos.arc.agents.dsl.extensions.debug\n", 91 | "import ai.ancf.lmos.arc.agents.dsl.extensions.extractUrl\n", 92 | "import ai.ancf.lmos.arc.agents.dsl.extensions.html\n", 93 | "import ai.ancf.lmos.arc.core.getOrThrow\n", 94 | "\n", 95 | "val agentName = \"summarizer-agent\"\n", 96 | "val agentBuilder = DSLAgents.init(chatCompleterProvider).apply {\n", 97 | " define {\n", 98 | " agent {\n", 99 | " name = agentName\n", 100 | " description = \"Agent that summarizes web pages.\"\n", 101 | " prompt {\n", 102 | " \"\"\"\n", 103 | " You are a helpful agent.\n", 104 | " You help customers by summarizing webpages.\n", 105 | " Keep your answer short and concise.\n", 106 | " \"\"\"\n", 107 | " }\n", 108 | " filterInput {\n", 109 | " val url = extractUrl(inputMessage).firstOrNull()\n", 110 | " if (url != null) {\n", 111 | " debug(\"Loading url: $url\")\n", 112 | " val html = html(url).getOrThrow()\n", 113 | " inputMessage = inputMessage.update(\n", 114 | " \"\"\"\n", 115 | " User question: ${inputMessage.content}\n", 116 | " The webpage $url contains the following text:\n", 117 | " $html\n", 118 | " \"\"\"\n", 119 | " )\n", 120 | " }\n", 121 | " }\n", 122 | " }\n", 123 | " }\n", 124 | "}" 125 | ], 126 | "outputs": [], 127 | "execution_count": 5 128 | }, 129 | { 130 | "metadata": {}, 131 | "cell_type": "code", 132 | "outputs": [ 133 | { 134 | "data": { 135 | "text/plain": [ 136 | "[ChatAgent(name='summarizer-agent', description='Agent that summarizes web pages.')]" 137 | ] 138 | }, 139 | "execution_count": 6, 140 | "metadata": {}, 141 | "output_type": "execute_result" 142 | } 143 | ], 144 | "execution_count": 6, 145 | "source": [ 146 | "val agents = agentBuilder.getAgents()\n", 147 | "agents" 148 | ] 149 | }, 150 | { 151 | "metadata": { 152 | "ExecuteTime": { 153 | "end_time": "2025-02-07T16:46:45.443603Z", 154 | "start_time": "2025-02-07T16:46:37.735360Z" 155 | } 156 | }, 157 | "cell_type": "code", 158 | "source": [ 159 | "import ai.ancf.lmos.arc.agents.ChatAgent\n", 160 | "import ai.ancf.lmos.arc.agents.User\n", 161 | "import ai.ancf.lmos.arc.agents.conversation.Conversation\n", 162 | "import ai.ancf.lmos.arc.agents.conversation.UserMessage\n", 163 | "import ai.ancf.lmos.arc.agents.getAgentByName\n", 164 | "import ai.ancf.lmos.arc.core.getOrNull\n", 165 | "import kotlinx.coroutines.runBlocking\n", 166 | "\n", 167 | "val articleUrl = \"https://blog.jetbrains.com/kotlin/2025/02/kodees-kotlin-roundup-fresh-picks/\"\n", 168 | "\n", 169 | "val agent = agentBuilder.getAgentByName(agentName) as ChatAgent? ?: error(\"Agent not found!\")\n", 170 | "val conversation = Conversation(User(\"userOrClientId\")) + UserMessage(\"Please summarize the following article: $articleUrl\")\n", 171 | "runBlocking {\n", 172 | " agent.execute(conversation).getOrNull()?.transcript?.getOrNull(1)?.content\n", 173 | "}" 174 | ], 175 | "outputs": [ 176 | { 177 | "data": { 178 | "text/plain": [ 179 | "The blog post on JetBrains titled \"Kodee’s Kotlin Roundup: Fresh Picks to Begin 2025\" discusses recent developments and updates in the Kotlin language. Highlights include the release of Kotlin 2.1.0 and the subsequent arrival of Kotlin 2.1.20-Beta2, which brings in tooling updates, new language features, and performance improvements. The blog also announces the introduction of klibs.io for easier discovery of Kotlin Multiplatform libraries and the winners of the Kotlin Multiplatform Contest. Additionally, it provides information about Kotlin's presence in the Google Summer of Code 2024 and a program for server-side Kotlin content creators. Finally, it mentions upcoming events, including KotlinConf 2025 and Kotlin workshops on May 21.\n" 180 | ] 181 | }, 182 | "execution_count": 9, 183 | "metadata": {}, 184 | "output_type": "execute_result" 185 | } 186 | ], 187 | "execution_count": 9 188 | } 189 | ], 190 | "metadata": { 191 | "kernelspec": { 192 | "display_name": "Kotlin", 193 | "language": "kotlin", 194 | "name": "kotlin" 195 | }, 196 | "language_info": { 197 | "name": "kotlin", 198 | "version": "1.9.23", 199 | "mimetype": "text/x-kotlin", 200 | "file_extension": ".kt", 201 | "pygments_lexer": "kotlin", 202 | "codemirror_mode": "text/x-kotlin", 203 | "nbconvert_exporter": "" 204 | } 205 | }, 206 | "nbformat": 4, 207 | "nbformat_minor": 0 208 | } 209 | -------------------------------------------------------------------------------- /notebooks/arc/WeatherAgent.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "metadata": {}, 5 | "cell_type": "markdown", 6 | "source": [ 7 | "# Weather Agent\n", 8 | "\n", 9 | "In this notebook, demonstrate how to create and use a `meteorologist`\n", 10 | "agent that retrieves real-time weather data using a \"get_weather\" function.\n", 11 | "We will define the agent's capabilities and see how it processes user queries to\n", 12 | "provide accurate and current weather information for a given location." 13 | ] 14 | }, 15 | { 16 | "metadata": { 17 | "ExecuteTime": { 18 | "end_time": "2025-02-07T16:23:07.302761Z", 19 | "start_time": "2025-02-07T16:23:06.966529Z" 20 | } 21 | }, 22 | "cell_type": "code", 23 | "source": [ 24 | "%useLatestDescriptors\n", 25 | "@file:DependsOn(\"ai.ancf.lmos:arc-langchain4j-client:0.120.0\")\n", 26 | "@file:DependsOn(\"dev.langchain4j:langchain4j-open-ai:1.0.0-beta1\")" 27 | ], 28 | "outputs": [], 29 | "execution_count": 1 30 | }, 31 | { 32 | "metadata": {}, 33 | "cell_type": "markdown", 34 | "source": [ 35 | "In this cell, we configure our environment with two key variables:\n", 36 | "* `openAiApiKey` for [OpenAI services](https://platform.openai.com/docs/api-reference/authentication) (required to interact with GPT-based models)\n", 37 | "* `weatherApiKey` for [WeatherAPI](https://www.weatherapi.com/) (required to fetch real-time weather data)\n", 38 | "\n", 39 | "Make sure to replace the placeholder values (e.g., `\"YOUR-OPENAI-API-KEY\"`) with your actual keys before running the notebook." 40 | ] 41 | }, 42 | { 43 | "metadata": { 44 | "ExecuteTime": { 45 | "end_time": "2025-02-07T16:23:07.580620Z", 46 | "start_time": "2025-02-07T16:23:07.425446Z" 47 | } 48 | }, 49 | "cell_type": "code", 50 | "source": [ 51 | "import ai.ancf.lmos.arc.agents.llm.ChatCompleter\n", 52 | "import ai.ancf.lmos.arc.agents.llm.ChatCompletionSettings\n", 53 | "import ai.ancf.lmos.arc.client.langchain4j.LangChainClient\n", 54 | "import ai.ancf.lmos.arc.client.langchain4j.LangChainConfig\n", 55 | "import dev.langchain4j.model.openai.OpenAiChatModel\n", 56 | "\n", 57 | "val openAiApiKey = System.getenv(\"OPENAI_API_KEY\") ?: \"YOUR-OPENAI-API-KEY\"\n", 58 | "val weatherApiKey = System.getenv(\"WEATHER_API_KEY\") ?: \"YOUR-WEATHERAPI-API-KEY\"\n", 59 | "\n", 60 | "val chatCompleterProvider: (String?) -> ChatCompleter = {\n", 61 | " LangChainClient(\n", 62 | " languageModel = LangChainConfig(\n", 63 | " modelName = \"gpt-4\",\n", 64 | " url = null,\n", 65 | " apiKey = openAiApiKey,\n", 66 | " accessKeyId = null,\n", 67 | " secretAccessKey = null,\n", 68 | " ), clientBuilder = { config, _ ->\n", 69 | " OpenAiChatModel.builder()\n", 70 | " .modelName(config.modelName)\n", 71 | " .apiKey(config.apiKey)\n", 72 | " .build()\n", 73 | " }\n", 74 | " )\n", 75 | "}" 76 | ], 77 | "outputs": [], 78 | "execution_count": 2 79 | }, 80 | { 81 | "metadata": {}, 82 | "cell_type": "markdown", 83 | "source": [ 84 | "Here, we define a “meteorologist” agent using DSLAgents.\n", 85 | "This agent has a prompt describing its purpose (to provide weather data) and a custom function,\n", 86 | "`get_weather`, which fetches real-time weather information.\n", 87 | "It leverages our defined `weatherApiKey` to make an HTTP request to WeatherAPI.\n", 88 | "The agent can then respond with up-to-date weather details for any location requested by the user." 89 | ] 90 | }, 91 | { 92 | "metadata": { 93 | "ExecuteTime": { 94 | "end_time": "2025-02-07T16:24:59.201143Z", 95 | "start_time": "2025-02-07T16:24:58.955175Z" 96 | } 97 | }, 98 | "cell_type": "code", 99 | "source": [ 100 | "import ai.ancf.lmos.arc.agents.DSLAgents\n", 101 | "\n", 102 | "val agentName = \"meteorologist\"\n", 103 | "val agentBuilder = DSLAgents.init(chatCompleterProvider).apply {\n", 104 | " define {\n", 105 | " agent {\n", 106 | " name = \"meteorologist\"\n", 107 | " description = \"A meteorologist can tell you the current weather in a given location.\"\n", 108 | " prompt {\n", 109 | " \"\"\"\n", 110 | " You are a professional weather service.\n", 111 | " You provide weather data to you users.\n", 112 | " You have access to real-time weather data with the get_weather function.\n", 113 | " Use 'unknown' if the location is not provided.\n", 114 | " Always state the location used in the response.\n", 115 | "\n", 116 | " # Instructions\n", 117 | " - Use the get_weather function to get the weather data.\n", 118 | " - Use multiple function calls if more locations are specified.\n", 119 | " \"\"\"\n", 120 | " }\n", 121 | " tools = listOf(\"get_weather\")\n", 122 | " }\n", 123 | " }\n", 124 | "\n", 125 | " defineFunctions {\n", 126 | " function(\n", 127 | " name = \"get_weather\",\n", 128 | " description = \"Get the weather for a given location\",\n", 129 | " params = types(string(\"location\", \"the location\")),\n", 130 | " ) { (location) ->\n", 131 | " val apiUrl = \"http://api.weatherapi.com/v1/current.json\"\n", 132 | " httpGet(\"$apiUrl?key=$weatherApiKey&q=$location\")\n", 133 | " }\n", 134 | " }\n", 135 | "}" 136 | ], 137 | "outputs": [], 138 | "execution_count": 3 139 | }, 140 | { 141 | "metadata": { 142 | "ExecuteTime": { 143 | "end_time": "2025-02-07T16:25:02.111147Z", 144 | "start_time": "2025-02-07T16:25:02.055025Z" 145 | } 146 | }, 147 | "cell_type": "code", 148 | "source": [ 149 | "val agents = agentBuilder.getAgents()\n", 150 | "agents" 151 | ], 152 | "outputs": [ 153 | { 154 | "data": { 155 | "text/plain": [ 156 | "[ChatAgent(name='meteorologist', description='A meteorologist can tell you the current weather in a given location.')]" 157 | ] 158 | }, 159 | "execution_count": 4, 160 | "metadata": {}, 161 | "output_type": "execute_result" 162 | } 163 | ], 164 | "execution_count": 4 165 | }, 166 | { 167 | "metadata": {}, 168 | "cell_type": "markdown", 169 | "source": [ 170 | "Let's retrieve the weather for Berlin by sending the user's question to our \"meteorologist\" agent.\n", 171 | "We define a new conversation containing the user's message and then execute the conversation, getting the weather data in the agent's response." 172 | ] 173 | }, 174 | { 175 | "metadata": { 176 | "ExecuteTime": { 177 | "end_time": "2025-02-07T16:26:20.464622Z", 178 | "start_time": "2025-02-07T16:26:11.585053Z" 179 | } 180 | }, 181 | "cell_type": "code", 182 | "source": [ 183 | "import ai.ancf.lmos.arc.agents.ChatAgent\n", 184 | "import ai.ancf.lmos.arc.agents.User\n", 185 | "import ai.ancf.lmos.arc.agents.conversation.Conversation\n", 186 | "import ai.ancf.lmos.arc.agents.conversation.UserMessage\n", 187 | "import ai.ancf.lmos.arc.agents.getAgentByName\n", 188 | "import ai.ancf.lmos.arc.core.getOrNull\n", 189 | "import kotlinx.coroutines.runBlocking\n", 190 | "\n", 191 | "val agent = agentBuilder.getAgentByName(\"meteorologist\") as ChatAgent? ?: error(\"Agent not found!\")\n", 192 | "val conversation = Conversation(User(\"userOrClientId\")) + UserMessage(\"What is the weather like in Berlin?\")\n", 193 | "runBlocking {\n", 194 | " agent.execute(conversation).getOrNull()?.transcript?.getOrNull(1)?.content\n", 195 | "}" 196 | ], 197 | "outputs": [ 198 | { 199 | "data": { 200 | "text/plain": [ 201 | "The current weather in Berlin, Germany is overcast with a temperature of 3.3°C (37.9°F). The wind is coming from the East at a speed of 18.7 kph (11.6 mph). The air pressure is 1033.0 mb. The humidity level is at 75%. The visibility is 10.0 km (6.0 miles). The UV index is 0.0." 202 | ] 203 | }, 204 | "execution_count": 5, 205 | "metadata": {}, 206 | "output_type": "execute_result" 207 | } 208 | ], 209 | "execution_count": 5 210 | } 211 | ], 212 | "metadata": { 213 | "kernelspec": { 214 | "display_name": "Kotlin", 215 | "language": "kotlin", 216 | "name": "kotlin" 217 | }, 218 | "language_info": { 219 | "name": "kotlin", 220 | "version": "1.9.23", 221 | "mimetype": "text/x-kotlin", 222 | "file_extension": ".kt", 223 | "pygments_lexer": "kotlin", 224 | "codemirror_mode": "text/x-kotlin", 225 | "nbconvert_exporter": "" 226 | }, 227 | "ktnbPluginMetadata": { 228 | "projectLibraries": false 229 | } 230 | }, 231 | "nbformat": 4, 232 | "nbformat_minor": 0 233 | } 234 | -------------------------------------------------------------------------------- /notebooks/openai/data/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/notebooks/openai/data/cat.jpg -------------------------------------------------------------------------------- /notebooks/spring-ai/tutorials/5. Structured Outputs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "metadata": {}, 5 | "cell_type": "markdown", 6 | "source": [ 7 | "# Structured Outputs\n", 8 | "\n", 9 | "Structured outputs are a capability that allows language models to format responses in specific, well-defined structures rather than just generating free-form text.\n", 10 | "This enables LLMs to deliver answers in formats like **JSON**, **XML**, and others.\n", 11 | "\n", 12 | "**How It Works**:\n", 13 | "1. Define a schema that describes the exact format you want\n", 14 | "2. The LLM generates content that strictly adheres to this predefined structure\n", 15 | "3. You get consistently formatted responses that are easier to parse and use in applications\n", 16 | "\n", 17 | "There are two ways to ask an LLM to format a response in a specific schema:\n", 18 | "1. Describe the desired result in your prompt\n", 19 | "2. Use structured output capabilities and pass a response schema\n", 20 | "\n", 21 | "Let's start by adding our dependencies, apiKey, and `ChatClient`" 22 | ] 23 | }, 24 | { 25 | "metadata": { 26 | "ExecuteTime": { 27 | "end_time": "2025-05-11T18:29:15.544329Z", 28 | "start_time": "2025-05-11T18:29:14.296612Z" 29 | } 30 | }, 31 | "cell_type": "code", 32 | "source": [ 33 | "%useLatestDescriptors\n", 34 | "%use spring-ai-openai\n", 35 | "USE { dependencies { implementation(\"com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2\") } }" 36 | ], 37 | "outputs": [], 38 | "execution_count": 1 39 | }, 40 | { 41 | "metadata": { 42 | "ExecuteTime": { 43 | "end_time": "2025-05-11T18:29:15.841Z", 44 | "start_time": "2025-05-11T18:29:15.548584Z" 45 | } 46 | }, 47 | "cell_type": "code", 48 | "source": [ 49 | "val apiKey = System.getenv(\"OPENAI_API_KEY\") ?: \"YOUR_OPENAI_API_KEY\"\n", 50 | "\n", 51 | "val openAiApi = OpenAiApi.builder().apiKey(apiKey).build()\n", 52 | "val openAiOptions = OpenAiChatOptions.builder()\n", 53 | " .model(OpenAiApi.ChatModel.GPT_4_O_MINI)\n", 54 | " .temperature(0.7)\n", 55 | " .build()\n", 56 | "\n", 57 | "val chatClient = ChatClient.create(\n", 58 | " OpenAiChatModel.builder()\n", 59 | " .openAiApi(openAiApi)\n", 60 | " .defaultOptions(openAiOptions)\n", 61 | " .build()\n", 62 | ")" 63 | ], 64 | "outputs": [], 65 | "execution_count": 2 66 | }, 67 | { 68 | "metadata": {}, 69 | "cell_type": "markdown", 70 | "source": "First, let's try describing our desired response format in the prompt and see what happens:" 71 | }, 72 | { 73 | "metadata": { 74 | "ExecuteTime": { 75 | "end_time": "2025-05-11T18:29:16.842779Z", 76 | "start_time": "2025-05-11T18:29:15.844680Z" 77 | } 78 | }, 79 | "cell_type": "code", 80 | "source": [ 81 | "val response = chatClient\n", 82 | " .prompt()\n", 83 | " .system(\"The response must be a valid JSON object.\")\n", 84 | " .user(\n", 85 | " \"\"\"\n", 86 | " What is the firstName and lastName of the person in this sentence?\n", 87 | "\n", 88 | " \"Aurora Skyfield announced her candidacy for the local city council yesterday.\"\n", 89 | " \"\"\"\n", 90 | " )\n", 91 | " .call()\n", 92 | " .content()\n", 93 | "\n", 94 | "response" 95 | ], 96 | "outputs": [ 97 | { 98 | "data": { 99 | "text/plain": [ 100 | "{\n", 101 | " \"firstName\": \"Aurora\",\n", 102 | " \"lastName\": \"Skyfield\"\n", 103 | "}" 104 | ] 105 | }, 106 | "execution_count": 3, 107 | "metadata": {}, 108 | "output_type": "execute_result" 109 | } 110 | ], 111 | "execution_count": 3 112 | }, 113 | { 114 | "metadata": {}, 115 | "cell_type": "markdown", 116 | "source": "As we can see, the result is JSON, so we can convert it to a Kotlin class:" 117 | }, 118 | { 119 | "metadata": { 120 | "ExecuteTime": { 121 | "end_time": "2025-05-11T18:29:17.109261Z", 122 | "start_time": "2025-05-11T18:29:16.847749Z" 123 | } 124 | }, 125 | "cell_type": "code", 126 | "source": [ 127 | "import kotlinx.serialization.Serializable\n", 128 | "import kotlinx.serialization.json.Json\n", 129 | "\n", 130 | "@Serializable\n", 131 | "data class Person(val firstName: String, val lastName: String)\n", 132 | "\n", 133 | "Json.decodeFromString(response)" 134 | ], 135 | "outputs": [ 136 | { 137 | "data": { 138 | "text/plain": [ 139 | "Person(firstName=Aurora, lastName=Skyfield)" 140 | ] 141 | }, 142 | "execution_count": 4, 143 | "metadata": {}, 144 | "output_type": "execute_result" 145 | } 146 | ], 147 | "execution_count": 4 148 | }, 149 | { 150 | "metadata": {}, 151 | "cell_type": "markdown", 152 | "source": [ 153 | "However, this approach for getting data isn't reliable,\n", 154 | "as the prompt doesn't guarantee that the result will be returned exactly in this format.\n", 155 | "\n", 156 | "So let's use a method that will definitely return a structured response —\n", 157 | "and directly as a Kotlin class.\n", 158 | "\n", 159 | "First, let's define the class we need:" 160 | ] 161 | }, 162 | { 163 | "metadata": { 164 | "ExecuteTime": { 165 | "end_time": "2025-05-11T18:29:17.156954Z", 166 | "start_time": "2025-05-11T18:29:17.112941Z" 167 | } 168 | }, 169 | "cell_type": "code", 170 | "source": [ 171 | "data class MobileDevice(\n", 172 | " val name: String,\n", 173 | " val price: Double,\n", 174 | " val category: String,\n", 175 | " val features: List = emptyList()\n", 176 | ")" 177 | ], 178 | "outputs": [], 179 | "execution_count": 5 180 | }, 181 | { 182 | "metadata": {}, 183 | "cell_type": "markdown", 184 | "source": "Now we can simply pass it as the expected result:" 185 | }, 186 | { 187 | "metadata": { 188 | "ExecuteTime": { 189 | "end_time": "2025-05-11T18:29:19.217213Z", 190 | "start_time": "2025-05-11T18:29:17.169662Z" 191 | } 192 | }, 193 | "cell_type": "code", 194 | "source": [ 195 | "import org.springframework.ai.chat.client.entity\n", 196 | "\n", 197 | "chatClient\n", 198 | " .prompt(\"Tell me about the latest smartphone\")\n", 199 | " .call()\n", 200 | " .entity()" 201 | ], 202 | "outputs": [ 203 | { 204 | "data": { 205 | "text/plain": [ 206 | "MobileDevice(name=XYZ Pro Max 12, price=1099.99, category=smartphone, features=[5G connectivity, 108 MP camera, 120Hz refresh rate display, Fast charging, Water and dust resistance])" 207 | ] 208 | }, 209 | "execution_count": 6, 210 | "metadata": {}, 211 | "output_type": "execute_result" 212 | } 213 | ], 214 | "execution_count": 6 215 | } 216 | ], 217 | "metadata": { 218 | "kernelspec": { 219 | "display_name": "Kotlin", 220 | "language": "kotlin", 221 | "name": "kotlin" 222 | }, 223 | "language_info": { 224 | "name": "kotlin", 225 | "version": "1.9.23", 226 | "mimetype": "text/x-kotlin", 227 | "file_extension": ".kt", 228 | "pygments_lexer": "kotlin", 229 | "codemirror_mode": "text/x-kotlin", 230 | "nbconvert_exporter": "" 231 | } 232 | }, 233 | "nbformat": 4, 234 | "nbformat_minor": 0 235 | } 236 | -------------------------------------------------------------------------------- /notebooks/spring-ai/tutorials/7. RAG.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "metadata": {}, 5 | "cell_type": "markdown", 6 | "source": [ 7 | "# RAG\n", 8 | "\n", 9 | "Retrieval-Augmented Generation (RAG) is a capability that allows LLMs to access relevant,\n", 10 | "private data that wasn't available during model training.\n", 11 | "It also enables more personalized responses.\n", 12 | "RAG solves the problem of model knowledge becoming outdated,\n", 13 | "while being more cost-effective than retraining the model.\n", 14 | "\n", 15 | "## How RAG Works\n", 16 | "1. First, you convert your documents, knowledge base, or other content into special vector embeddings (think of them as digital fingerprints of information)\n", 17 | "2. When a question comes in,\n", 18 | " RAG searches these fingerprints to find the most relevant pieces of information\n", 19 | "3. The system adds this relevant information to the AI's prompt\n", 20 | "4. The AI creates a response using both its training and this fresh, specific information\n", 21 | "\n", 22 | "As in previous notebooks, let's start with some initial setup" 23 | ] 24 | }, 25 | { 26 | "metadata": { 27 | "ExecuteTime": { 28 | "end_time": "2025-05-11T18:41:13.950443Z", 29 | "start_time": "2025-05-11T18:41:12.693148Z" 30 | } 31 | }, 32 | "cell_type": "code", 33 | "source": [ 34 | "%useLatestDescriptors\n", 35 | "%use spring-ai-openai\n", 36 | "USE { dependencies { implementation(\"org.springframework.ai:spring-ai-advisors-vector-store:1.0.0-M8\") } }" 37 | ], 38 | "outputs": [], 39 | "execution_count": 1 40 | }, 41 | { 42 | "metadata": { 43 | "ExecuteTime": { 44 | "end_time": "2025-05-11T18:41:14.206746Z", 45 | "start_time": "2025-05-11T18:41:13.962082Z" 46 | } 47 | }, 48 | "cell_type": "code", 49 | "source": [ 50 | "val apiKey = System.getenv(\"OPENAI_API_KEY\") ?: \"YOUR_OPENAI_API_KEY\"\n", 51 | "\n", 52 | "val openAiApi = OpenAiApi.builder().apiKey(apiKey).build()\n", 53 | "val openAiOptions = OpenAiChatOptions.builder()\n", 54 | " .model(OpenAiApi.ChatModel.GPT_4_O_MINI)\n", 55 | " .temperature(0.7)\n", 56 | " .build()" 57 | ], 58 | "outputs": [], 59 | "execution_count": 2 60 | }, 61 | { 62 | "metadata": {}, 63 | "cell_type": "markdown", 64 | "source": [ 65 | "As mentioned above, documents need to be converted into vectors.\n", 66 | "\n", 67 | "For this, we'll need an `EmbeddingModel`." 68 | ] 69 | }, 70 | { 71 | "metadata": { 72 | "ExecuteTime": { 73 | "end_time": "2025-05-11T18:41:15.511846Z", 74 | "start_time": "2025-05-11T18:41:15.479384Z" 75 | } 76 | }, 77 | "cell_type": "code", 78 | "source": "val embeddingModel = OpenAiEmbeddingModel(openAiApi)", 79 | "outputs": [], 80 | "execution_count": 3 81 | }, 82 | { 83 | "metadata": {}, 84 | "cell_type": "markdown", 85 | "source": [ 86 | "We now have an `EmbeddingModel`,\n", 87 | "but we need somewhere to store the vector representations of documents.\n", 88 | "This is what vector stores are designed for.\n", 89 | "In our example, we'll use a simple in-memory implementation of a vector store." 90 | ] 91 | }, 92 | { 93 | "metadata": { 94 | "ExecuteTime": { 95 | "end_time": "2025-05-11T18:41:18.042772Z", 96 | "start_time": "2025-05-11T18:41:17.952079Z" 97 | } 98 | }, 99 | "cell_type": "code", 100 | "source": [ 101 | "import org.springframework.ai.vectorstore.SimpleVectorStore\n", 102 | "\n", 103 | "val vectoreStore = SimpleVectorStore.builder(embeddingModel).build()" 104 | ], 105 | "outputs": [], 106 | "execution_count": 4 107 | }, 108 | { 109 | "metadata": {}, 110 | "cell_type": "markdown", 111 | "source": [ 112 | "Now we just need to add a document to our store.\n", 113 | "\n", 114 | "Let's use a Kotlin FAQ" 115 | ] 116 | }, 117 | { 118 | "metadata": { 119 | "ExecuteTime": { 120 | "end_time": "2025-05-11T18:41:22.574539Z", 121 | "start_time": "2025-05-11T18:41:20.814250Z" 122 | } 123 | }, 124 | "cell_type": "code", 125 | "source": [ 126 | "import java.io.File\n", 127 | "\n", 128 | "val doc = Document(File(\"data/kotlinFAQ.md\").readText())\n", 129 | "vectoreStore.add(listOf(doc))" 130 | ], 131 | "outputs": [], 132 | "execution_count": 5 133 | }, 134 | { 135 | "metadata": {}, 136 | "cell_type": "markdown", 137 | "source": [ 138 | "Now that we've prepared everything necessary,\n", 139 | "let's use the `QuestionAnswerAdvisor`, which implements RAG in Spring-AI.\n", 140 | "\n", 141 | "Here's what will happen:\n", 142 | "1. Send a query\n", 143 | "2. The query gets vectorized\n", 144 | "3. The system searches for the closest match to our query vector in the vector store\n", 145 | "4. The closest results are added to the original query as additional context\n", 146 | "5. Original query along with this additional context is sent to the LLM\n", 147 | "6. Receive an answer" 148 | ] 149 | }, 150 | { 151 | "metadata": { 152 | "ExecuteTime": { 153 | "end_time": "2025-05-11T18:41:32.560363Z", 154 | "start_time": "2025-05-11T18:41:30.160435Z" 155 | } 156 | }, 157 | "cell_type": "code", 158 | "source": [ 159 | "import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor\n", 160 | "\n", 161 | "ChatClient.create(\n", 162 | " OpenAiChatModel.builder()\n", 163 | " .openAiApi(openAiApi)\n", 164 | " .defaultOptions(openAiOptions)\n", 165 | " .build()\n", 166 | ")\n", 167 | " .prompt()\n", 168 | " .advisors(QuestionAnswerAdvisor(vectoreStore))\n", 169 | " .user(\"current version of Kotlin?\")\n", 170 | " .call()\n", 171 | " .content()" 172 | ], 173 | "outputs": [ 174 | { 175 | "data": { 176 | "text/plain": [ 177 | "The current version of Kotlin is 2.1.20, which was published on March 20, 2025. For more information, you can visit the [Kotlin GitHub page](https://github.com/jetbrains/kotlin)." 178 | ] 179 | }, 180 | "execution_count": 7, 181 | "metadata": {}, 182 | "output_type": "execute_result" 183 | } 184 | ], 185 | "execution_count": 7 186 | } 187 | ], 188 | "metadata": { 189 | "kernelspec": { 190 | "display_name": "Kotlin", 191 | "language": "kotlin", 192 | "name": "kotlin" 193 | }, 194 | "language_info": { 195 | "name": "kotlin", 196 | "version": "1.9.23", 197 | "mimetype": "text/x-kotlin", 198 | "file_extension": ".kt", 199 | "pygments_lexer": "kotlin", 200 | "codemirror_mode": "text/x-kotlin", 201 | "nbconvert_exporter": "" 202 | } 203 | }, 204 | "nbformat": 4, 205 | "nbformat_minor": 0 206 | } 207 | -------------------------------------------------------------------------------- /notebooks/spring-ai/tutorials/data/black_holes.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/notebooks/spring-ai/tutorials/data/black_holes.mp3 -------------------------------------------------------------------------------- /notebooks/spring-ai/tutorials/data/harvard.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/notebooks/spring-ai/tutorials/data/harvard.wav -------------------------------------------------------------------------------- /notebooks/spring-ai/tutorials/images/framework-manager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/notebooks/spring-ai/tutorials/images/framework-manager.jpg -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/README.md: -------------------------------------------------------------------------------- 1 | # LangChain4j Spring Boot Example in Kotlin 2 | 3 | This project is a rewritten Kotlin example from 4 | the [langchain4j-examples](https://github.com/langchain4j/langchain4j-examples/tree/main/spring-boot-example) 5 | repository. It demonstrates how to use `langchain4j` with Spring Boot in a Kotlin application. 6 | 7 | ## Requirements 8 | 9 | - Java 17 or higher 10 | - Kotlin 2.0.x or higher 11 | - Gradle 8.x or higher 12 | - OpenAI API key (set as environment variable OPENAI_API_KEY) 13 | - IDE with Kotlin support (IntelliJ IDEA recommended) 14 | - Git for version control 15 | 16 | ## Technologies Used 17 | 18 | - Spring Boot 3.2.4 19 | - spring-boot-starter-web 20 | - spring-boot-starter-webflux 21 | - Kotlin 2.0.x 22 | - kotlinx-coroutines-reactor 23 | - kotlin-reflect 24 | - Langchain4j 0.35.0 25 | - langchain4j-spring-boot-starter 26 | - langchain4j-open-ai-spring-boot-starter 27 | - OpenAI API integration 28 | - WebFlux for reactive programming 29 | - Kotlin Coroutines for asynchronous programming 30 | 31 | ## Running the Project 32 | 33 | 1. Clone the repository: 34 | ```bash 35 | git clone https://github.com/devcrocod/kotlin-ai-examples.git 36 | ``` 37 | 38 | 2. Navigate to the project directory: 39 | ```bash 40 | cd kotlin-ai-examples/projects/langchain4j/langchain4j-spring-boot 41 | ``` 42 | 43 | 3. Build and run the project using Gradle: 44 | 45 | [MacOS/Linux] 46 | ```bash 47 | ./gradlew bootRun 48 | ``` 49 | [Windows] 50 | ```shell 51 | gradlew.bat bootRun 52 | ``` 53 | 54 | 4. The project will start by default at `http://localhost:8080`. 55 | 56 | ## Project Structure 57 | 58 | - **`build.gradle.kts`**: The project configuration, including all necessary dependencies and plugins for Kotlin, Spring 59 | Boot, and langchain4j. 60 | - **`src/main/kotlin`**: The source code of the project written in Kotlin. 61 | - **`src/main/resources`**: The project's resources, such as configuration files. 62 | 63 | ## Main Dependencies 64 | 65 | - `langchain4j-spring-boot-starter`: For integrating LangChain into Spring Boot. 66 | - `langchain4j-open-ai-spring-boot-starter`: For working with the OpenAI API. 67 | - `spring-boot-starter-web` and `spring-boot-starter-webflux`: For creating REST APIs and supporting asynchronous 68 | programming. 69 | 70 | ## How to Use 71 | 72 | 1. Set up the OpenAI API by adding the required parameters in `application.properties`: 73 | ```properties 74 | langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY} 75 | ``` 76 | 77 | 2. The project supports asynchronous operations using `WebFlux` and `Reactor`. 78 | 79 | ## Additional Information 80 | 81 | - The project utilizes Kotlin Coroutines and integrates with Spring Boot for asynchronous interaction with the OpenAI 82 | API. 83 | - This example demonstrates how to use the `langchain4j` and `langchain4j-open-ai` libraries in a Kotlin project with 84 | Spring Boot. 85 | 86 | ## Links 87 | 88 | - Original Java 89 | example: [langchain4j-examples/spring-boot-example](https://github.com/langchain4j/langchain4j-examples/tree/main/spring-boot-example) 90 | - [Langchain4j Documentation](https://github.com/langchain4j/langchain4j) 91 | -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.21" 3 | kotlin("plugin.spring") version "2.1.21" 4 | id("org.springframework.boot") version "3.5.0" 5 | id("io.spring.dependency-management") version "1.1.7" 6 | } 7 | 8 | group = "io.github.devcrocod.example" 9 | version = "0.0.1-SNAPSHOT" 10 | 11 | java { 12 | toolchain { 13 | languageVersion = JavaLanguageVersion.of(17) 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | val langchain4jVersion = "1.0.1-beta6" 22 | val coroutinesVersion = "1.10.2" 23 | 24 | dependencies { 25 | implementation("org.jetbrains.kotlin:kotlin-reflect") 26 | implementation("org.springframework.boot:spring-boot-starter-web") 27 | implementation("org.springframework.boot:spring-boot-starter-webflux") 28 | implementation("dev.langchain4j:langchain4j-open-ai-spring-boot-starter:$langchain4jVersion") 29 | implementation("dev.langchain4j:langchain4j-spring-boot-starter:${langchain4jVersion}") 30 | implementation("dev.langchain4j:langchain4j-kotlin:$langchain4jVersion") 31 | implementation("dev.langchain4j:langchain4j-reactor:$langchain4jVersion") 32 | 33 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") 34 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion") 35 | } 36 | 37 | kotlin { 38 | compilerOptions { 39 | freeCompilerArgs.addAll("-Xjsr305=strict") 40 | } 41 | } 42 | 43 | tasks.withType { 44 | useJUnitPlatform() 45 | } 46 | -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/langchain4j/langchain4j-spring-boot/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "langchain4j-spring-boot" 2 | -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/kotlin/io/github/devcrocod/example/langchain4j/Application.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.langchain4j 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication 7 | class Application 8 | 9 | fun main(args: Array) { 10 | runApplication(*args) 11 | } -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/kotlin/io/github/devcrocod/example/langchain4j/aiservice/Assistant.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.langchain4j.aiservice 2 | 3 | import dev.langchain4j.service.SystemMessage 4 | import dev.langchain4j.service.spring.AiService 5 | 6 | @AiService 7 | interface Assistant { 8 | 9 | @SystemMessage("You are a polite assistant") 10 | fun chat(userMessage: String): String 11 | } -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/kotlin/io/github/devcrocod/example/langchain4j/aiservice/AssistantConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.langchain4j.aiservice 2 | 3 | import dev.langchain4j.memory.ChatMemory 4 | import dev.langchain4j.memory.chat.MessageWindowChatMemory 5 | import org.springframework.context.annotation.Bean 6 | import org.springframework.context.annotation.Configuration 7 | 8 | @Configuration 9 | class AssistantConfiguration { 10 | 11 | /** 12 | * This chat memory will be used by an [Assistant] 13 | */ 14 | @Bean 15 | fun chatMemory(): ChatMemory = MessageWindowChatMemory.withMaxMessages(10) 16 | } -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/kotlin/io/github/devcrocod/example/langchain4j/aiservice/AssistantController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.langchain4j.aiservice 2 | 3 | import org.springframework.web.bind.annotation.RestController 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.reactive.asFlow 6 | import org.springframework.web.bind.annotation.GetMapping 7 | import org.springframework.web.bind.annotation.RequestParam 8 | 9 | /** 10 | * This is an example of using an [dev.langchain4j.service.spring.AiService], a high-level LangChain4j API 11 | */ 12 | @RestController 13 | class AssistantController( 14 | private val assistant: Assistant, 15 | private val streamingAssistant: StreamingAssistant 16 | ) { 17 | 18 | @GetMapping("/assistant") 19 | fun assistant(@RequestParam(value = "message", defaultValue = "What is the time now?") message: String): String { 20 | return assistant.chat(message) 21 | } 22 | 23 | @GetMapping("/streamingAssistant") 24 | fun streamingAssistant( 25 | @RequestParam(value = "message", defaultValue = "What is the time now?") message: String 26 | ): Flow { 27 | return streamingAssistant.chat(message).asFlow() 28 | } 29 | } -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/kotlin/io/github/devcrocod/example/langchain4j/aiservice/AssistantTools.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.langchain4j.aiservice 2 | 3 | import dev.langchain4j.agent.tool.Tool 4 | import org.springframework.stereotype.Component 5 | import java.time.LocalTime 6 | 7 | @Component 8 | class AssistantTools { 9 | 10 | /** 11 | * This tool is available to [Assistant] 12 | */ 13 | @Tool 14 | fun currentTime(): String = LocalTime.now().toString() 15 | } -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/kotlin/io/github/devcrocod/example/langchain4j/aiservice/StreamingAssistant.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.langchain4j.aiservice 2 | 3 | import dev.langchain4j.service.SystemMessage 4 | import dev.langchain4j.service.spring.AiService 5 | import reactor.core.publisher.Flux 6 | 7 | @AiService 8 | interface StreamingAssistant { 9 | 10 | @SystemMessage("You are a polite assistant") 11 | fun chat(userMessage: String): Flux 12 | } -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/kotlin/io/github/devcrocod/example/langchain4j/lowlevel/ChatModelController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.langchain4j.lowlevel 2 | 3 | import dev.langchain4j.model.chat.ChatModel 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.RequestParam 6 | import org.springframework.web.bind.annotation.RestController 7 | 8 | /** 9 | * This is an example of using a [dev.langchain4j.model.chat.ChatModel], a low-level LangChain4j API 10 | */ 11 | @RestController 12 | class ChatModelController(private val chatModel: ChatModel) { 13 | 14 | @GetMapping("/model") 15 | fun model( 16 | @RequestParam(value = "message", defaultValue = "Hello") message: String 17 | ): String = chatModel.chat(message) 18 | } -------------------------------------------------------------------------------- /projects/langchain4j/langchain4j-spring-boot/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY} 2 | langchain4j.open-ai.chat-model.model-name=gpt-4o-mini 3 | 4 | langchain4j.open-ai.streaming-chat-model.api-key=${OPENAI_API_KEY} 5 | langchain4j.open-ai.streaming-chat-model.model-name=gpt-4o-mini 6 | 7 | langchain4j.open-ai.chat-model.log-requests=true 8 | langchain4j.open-ai.chat-model.log-responses=true 9 | logging.level.dev.langchain4j=DEBUG 10 | logging.level.dev.ai4j.openai4j=DEBUG -------------------------------------------------------------------------------- /projects/mcp/brave/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | /target/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | 40 | ### Kotlin ### 41 | .kotlin 42 | 43 | # The following files are automatically generated/updated 44 | node_modules/ 45 | frontend/generated/ 46 | .npmrc 47 | vite.generated.ts 48 | 49 | 50 | # Eclipse store 51 | storage/ 52 | 53 | !**/src/test/resources/volumes 54 | 55 | src/main/bundles 56 | -------------------------------------------------------------------------------- /projects/mcp/brave/README.md: -------------------------------------------------------------------------------- 1 | # Brave Search MCP Client 2 | 3 | This project demonstrates how to integrate the [MCP Kotlin SDK](https://github.com/modelcontextprotocol/kotlin-sdk) with 4 | a Brave Search server using [Ktor](https://ktor.io). The application launches the Brave Search server via `npx` (passing 5 | the API key via an environment variable), connects to it using the MCP protocol over standard I/O streams, and exposes a 6 | simple HTTP endpoint (`/chat`) to process natural language queries. 7 | 8 | ## Overview 9 | 10 | When a POST request is made to the `/chat` endpoint with a text question in the body, the application: 11 | 12 | 1. Connects to the Brave Search MCP server. 13 | 2. Retrieves the list of available tools. 14 | 3. Calls the first available tool with the provided query. 15 | 4. Returns the tool's response as the HTTP response. 16 | 17 | ## Prerequisites 18 | 19 | - **Java Development Kit (JDK) 11+** (or any version supported by your environment) 20 | - **npx**: Make sure you have Node.js and npm installed. You can install npx via npm if needed: 21 | ```shell 22 | npm install -g npx 23 | ``` 24 | - **BRAVE_API_KEY**: Obtain your Brave Search API key and set it as an environment variable. 25 | 26 | ## Setup 27 | 28 | 1. Clone the repository 29 | 30 | ```shell 31 | git clone git@github.com:devcrocod/Kotlin-AI-Examples.git 32 | cd Kotlin-AI-Examples/projects/mcp/brave 33 | ``` 34 | 35 | 2. Set the environment variable 36 | On Unix/Linux/MacOS: 37 | 38 | ```shell 39 | export BRAVE_API_KEY="your-brave-api-key" 40 | ``` 41 | 42 | On Windows (Command Prompt): 43 | 44 | ```shell 45 | set BRAVE_API_KEY=your-brave-api-key 46 | ``` 47 | 48 | 3. Build the project 49 | 50 | On Unix/Linux/MacOS 51 | ```shell 52 | ./gradlew build 53 | ``` 54 | 55 | On Windows: 56 | ```shell 57 | gradlew.bat build 58 | ``` 59 | 60 | ## Running the application 61 | 62 | Start the application using Gradle: 63 | 64 | Unix/Linux/MacOS: 65 | ```shell 66 | ./gradlew run 67 | ``` 68 | 69 | Windows: 70 | ```shell 71 | gradlew.bat run 72 | ``` 73 | 74 | The embedded Ktor server will start on port 8080. 75 | 76 | ## Usage 77 | 78 | Send a POST request to the `/chat` endpoint with your query in the request body. For example, using `curl`: 79 | 80 | ```shell 81 | curl -X POST -d "Does Kotlin support the Model Context Protocol? Please provide some references." http://localhost:8080/chat 82 | ``` 83 | 84 | The server will forward the query to the Brave Search MCP server, invoke the appropriate tool, and return the result as 85 | plain text. 86 | 87 | ## How it works 88 | 89 | 1. Process Initialization 90 | The application starts the Brave Search MCP server using: 91 | ```kotlin 92 | ProcessBuilder("npx", "-y", "@modelcontextprotocol/server-brave-search") 93 | ``` 94 | 95 | The API key is injected into the process environment via the `BRAVE_API_KEY` variable. 96 | 97 | 2. MCP Client Setup 98 | A `StdioClientTransport` is created from the process's input/output streams using the Kotlin I/O utilities. 99 | This transport is used to initialize the MCP client: 100 | ```kotlin 101 | val client = Client(clientInfo = Implementation(name = "brave-client", version = "1.0.0")) 102 | runBlocking { client.connect(transport) } 103 | ``` 104 | The handshake with the server is performed during the connection. 105 | 3. Handling Chat Requests 106 | The /chat endpoint: 107 | - Reads the query from the request body. 108 | - Retrieves the list of available tools from the server. 109 | - Calls the first available tool with the query as an argument. 110 | Returns the result back to the caller. 111 | 112 | ## Dependencies 113 | 114 | Key dependencies used in this project include: 115 | 116 | - [Ktor](https://ktor.io/) for the HTTP server. 117 | - [MCP Kotlin SDK](https://github.com/modelcontextprotocol/kotlin-sdk) for MCP client functionality. 118 | - [kotlinx-io](https://github.com/Kotlin/kotlinx-io) for stream buffering. -------------------------------------------------------------------------------- /projects/mcp/brave/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | application 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation(libs.mcp.kotlin) 12 | implementation(libs.slf4j) 13 | } 14 | 15 | java { 16 | toolchain { 17 | languageVersion = JavaLanguageVersion.of(17) 18 | } 19 | } 20 | 21 | application { 22 | mainClass = "io.github.devcrocod.example.MainKt" 23 | } 24 | 25 | tasks.named("test") { 26 | // Use JUnit Platform for unit tests. 27 | useJUnitPlatform() 28 | } 29 | -------------------------------------------------------------------------------- /projects/mcp/brave/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by the Gradle 'init' task. 2 | # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format 3 | 4 | [versions] 5 | kotlin = "2.1.10" 6 | mcp = "0.3.0" 7 | slf4j = "2.0.9" 8 | 9 | [libraries] 10 | mcp-kotlin = { group = "io.modelcontextprotocol", name = "kotlin-sdk", version.ref = "mcp" } 11 | slf4j = { group = "org.slf4j", name = "slf4j-nop", version.ref = "slf4j"} 12 | 13 | [plugins] 14 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 15 | -------------------------------------------------------------------------------- /projects/mcp/brave/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/brave/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/mcp/brave/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/mcp/brave/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/mcp/brave/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.10.1/userguide/multi_project_builds.html in the Gradle documentation. 6 | */ 7 | 8 | plugins { 9 | // Apply the foojay-resolver plugin to allow automatic download of JDKs 10 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 11 | } 12 | 13 | rootProject.name = "brave" 14 | include("app") 15 | -------------------------------------------------------------------------------- /projects/mcp/brave/src/main/kotlin/io/github/devcrocod/example/main.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example 2 | 3 | import io.ktor.server.application.Application 4 | import io.ktor.server.cio.CIO 5 | import io.ktor.server.engine.embeddedServer 6 | import io.ktor.server.request.receiveText 7 | import io.ktor.server.response.respondText 8 | import io.ktor.server.routing.post 9 | import io.ktor.server.routing.routing 10 | import io.modelcontextprotocol.kotlin.sdk.Implementation 11 | import io.modelcontextprotocol.kotlin.sdk.client.Client 12 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport 13 | import kotlinx.coroutines.runBlocking 14 | import kotlinx.io.asSink 15 | import kotlinx.io.asSource 16 | import kotlinx.io.buffered 17 | import org.slf4j.LoggerFactory 18 | 19 | 20 | fun main() { 21 | embeddedServer(CIO, port = 8080, module = Application::module).start(wait = true) 22 | } 23 | 24 | fun Application.module() { 25 | val logger = LoggerFactory.getLogger("BraveSearchKtorApp") 26 | 27 | // Get the Brave Search API key from environment variables 28 | val braveApiKey = System.getenv("BRAVE_API_KEY") 29 | ?: error("The environment variable BRAVE_API_KEY is not set") 30 | 31 | // Starting the Brave Search process via npx 32 | val process = ProcessBuilder("npx", "-y", "@modelcontextprotocol/server-brave-search") 33 | .apply { environment()["BRAVE_API_KEY"] = braveApiKey } 34 | .start() 35 | 36 | // Create transport using the standard streams of a running process 37 | val transport = StdioClientTransport( 38 | input = process.inputStream.asSource().buffered(), 39 | output = process.outputStream.asSink().buffered() 40 | ) 41 | 42 | // Initialize the MCP client with client information 43 | val client = Client( 44 | clientInfo = Implementation(name = "brave-client", version = "1.0.0") 45 | ) 46 | 47 | // Connecting to the server (handshake is performed in connecting) 48 | runBlocking { 49 | client.connect(transport) 50 | logger.info("MCP Client connected. Server: ${client.serverVersion}") 51 | } 52 | 53 | // Defining Ktor routing 54 | routing { 55 | post("/chat") { 56 | // Get the question text from the request body 57 | val question = call.receiveText() 58 | logger.info("The following question has been received: $question") 59 | 60 | // Request a list of available tools from the server. 61 | val tools = runBlocking { client.listTools()?.tools } ?: emptyList() 62 | if (tools.isEmpty()) { 63 | call.respondText("Tools not found") 64 | return@post 65 | } 66 | 67 | // For example, let's choose the first available tool. 68 | val toolName = tools.first().name 69 | 70 | // Call the tool, passing the "query" field with the question text as an argument 71 | val toolResult = runBlocking { client.callTool(toolName, mapOf("query" to question)) } 72 | val resultContent = toolResult?.content?.toString() ?: "No result" 73 | 74 | // Returning the result to the client 75 | call.respondText(resultContent) 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .kotlin 3 | .gradle 4 | **/build/ 5 | xcuserdata 6 | !src/**/build/ 7 | local.properties 8 | .idea 9 | .DS_Store 10 | captures 11 | .externalNativeBuild 12 | .cxx 13 | *.xcodeproj/* 14 | !*.xcodeproj/project.pbxproj 15 | !*.xcodeproj/xcshareddata/ 16 | !*.xcodeproj/project.xcworkspace/ 17 | !*.xcworkspace/contents.xcworkspacedata 18 | **/xcshareddata/WorkspaceSettings.xcsettings 19 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.composeMultiplatform) apply false 3 | alias(libs.plugins.composeCompiler) apply false 4 | alias(libs.plugins.kotlinMultiplatform) apply false 5 | } -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/composeApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | 3 | plugins { 4 | alias(libs.plugins.kotlinMultiplatform) 5 | alias(libs.plugins.composeMultiplatform) 6 | alias(libs.plugins.composeCompiler) 7 | } 8 | 9 | kotlin { 10 | jvm("desktop") 11 | 12 | sourceSets { 13 | val desktopMain by getting 14 | 15 | commonMain.dependencies { 16 | implementation(compose.runtime) 17 | implementation(compose.foundation) 18 | implementation(compose.material3) 19 | implementation(compose.ui) 20 | implementation(compose.components.resources) 21 | implementation(compose.components.uiToolingPreview) 22 | implementation(libs.androidx.lifecycle.viewmodel) 23 | implementation(libs.androidx.lifecycle.runtime.compose) 24 | 25 | implementation(libs.mcp.kotlin) 26 | implementation(libs.slf4j) 27 | implementation(libs.ktor.client.content.negotation) 28 | implementation(libs.ktor.serialization) 29 | implementation(libs.openai.java) 30 | } 31 | desktopMain.dependencies { 32 | implementation(compose.desktop.currentOs) 33 | implementation(libs.kotlinx.coroutines.swing) 34 | } 35 | } 36 | } 37 | 38 | 39 | compose.desktop { 40 | application { 41 | mainClass = "io.github.devcrocod.MainKt" 42 | 43 | nativeDistributions { 44 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 45 | packageName = "io.github.devcrocod" 46 | packageVersion = "1.0.0" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 24 | 30 | 36 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/composeApp/src/desktopMain/kotlin/io/github/devcrocod/main.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod 2 | 3 | import androidx.compose.ui.window.Window 4 | import androidx.compose.ui.window.application 5 | 6 | fun main(): Unit = application { 7 | Window(onCloseRequest = ::exitApplication, title = "MCP Client") { 8 | App() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/gradle.properties: -------------------------------------------------------------------------------- 1 | #Kotlin 2 | kotlin.code.style=official 3 | kotlin.daemon.jvmargs=-Xmx2048M 4 | 5 | #Gradle 6 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidx-lifecycle = "2.9.0" 3 | compose-multiplatform = "1.8.1" 4 | junit = "4.13.2" 5 | kotlin = "2.1.21" 6 | kotlinx-coroutines = "1.10.2" 7 | mcp = "0.5.0" 8 | slf4j = "2.0.17" 9 | ktor = "3.1.1" 10 | shadow = "8.1.1" 11 | openai = "2.2.0" 12 | 13 | [libraries] 14 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 15 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 16 | junit = { group = "junit", name = "junit", version.ref = "junit" } 17 | androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } 18 | androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } 19 | kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } 20 | mcp-kotlin = { group = "io.modelcontextprotocol", name = "kotlin-sdk", version.ref = "mcp" } 21 | slf4j = { group = "org.slf4j", name = "slf4j-nop", version.ref = "slf4j" } 22 | ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } 23 | ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } 24 | ktor-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } 25 | ktor-client-content-negotation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } 26 | ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } 27 | openai-java = { group = "com.openai", name = "openai-java", version.ref = "openai" } 28 | 29 | [plugins] 30 | composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } 31 | composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 32 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 33 | kotlinJvm = { id = "org.jetbrains.kotlin.jvm" } 34 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 35 | shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/claude_desktop_answer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/claude_desktop_answer.png -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/claude_desktop_mcp_hammer_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/claude_desktop_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/claude_desktop_settings.png -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/claude_desktop_tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/claude_desktop_tools.png -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/claude_for_desktop_client.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/claude_for_desktop_client.gif -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/compose_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/compose_client.png -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/compose_mcp_client.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/compose_mcp_client.gif -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/inspector_connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/inspector_connect.png -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/inspector_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/inspector_test.png -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/image/wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/mcp/mcp-demo/image/wizard.png -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlinJvm) 3 | alias(libs.plugins.kotlin.serialization) 4 | alias(libs.plugins.shadow) 5 | application 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | implementation(libs.mcp.kotlin) 14 | implementation(libs.slf4j) 15 | implementation(libs.ktor.client.core) 16 | implementation(libs.ktor.client.okhttp) 17 | implementation(libs.ktor.client.content.negotation) 18 | implementation(libs.ktor.serialization) 19 | implementation(libs.ktor.client.logging) 20 | } 21 | 22 | java { 23 | toolchain { 24 | languageVersion = JavaLanguageVersion.of(17) 25 | } 26 | } 27 | 28 | application { 29 | mainClass = "io.github.devcrocod.example.MainKt" 30 | } 31 | 32 | tasks.shadowJar { 33 | archiveFileName.set("serverAll.jar") 34 | archiveClassifier.set("") 35 | manifest { 36 | attributes["Main-Class"] = "io.github.devcrocod.example.MainKt" 37 | } 38 | } 39 | 40 | tasks.named("test") { 41 | // Use JUnit Platform for unit tests. 42 | useJUnitPlatform() 43 | } 44 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/server/src/main/kotlin/io/github/devcrocod/example/data.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Represents a financial quote with various details about a stock. 7 | * 8 | * @property symbol The stock ticker symbol or financial instrument identifier. 9 | * @property name The full name of the financial instrument. 10 | * @property price The current price of the financial instrument. 11 | * @property changesPercentage The percentage change in price for the financial instrument. 12 | * @property dayLow The lowest trading price of the day. 13 | * @property dayHigh The highest trading price of the day. 14 | * @property yearHigh The highest price over the last year. 15 | * @property yearLow The lowest price over the last year. 16 | * @property marketCap The market capitalization value. 17 | * @property priceAvg50 The 50-day average price. 18 | * @property priceAvg200 The 200-day average price. 19 | * @property exchange The name of the exchange where the instrument is traded. 20 | * @property volume The trading volume of the financial instrument. 21 | * @property open The opening price at the start of the trading day. 22 | * @property previousClose The closing price from the previous trading day. 23 | * @property eps The earnings per share (EPS) value. 24 | * @property pe The price-to-earnings (P/E) ratio. 25 | * @property earningsAnnouncement The date and time of the last earnings announcement. 26 | * @property sharesOutstanding The total number of shares outstanding. 27 | * @property timestamp The timestamp of when the quote data was last updated. 28 | */ 29 | @Serializable 30 | data class Quote( 31 | val symbol: String, 32 | val name: String, 33 | val price: Double, 34 | val changesPercentage: Double, 35 | val dayLow: Double, 36 | val dayHigh: Double, 37 | val yearHigh: Double, 38 | val yearLow: Double, 39 | val marketCap: Long, 40 | val priceAvg50: Double, 41 | val priceAvg200: Double, 42 | val exchange: String, 43 | val volume: Long, 44 | val open: Double, 45 | val previousClose: Double, 46 | val eps: Double, 47 | val pe: Double, 48 | val earningsAnnouncement: String, 49 | val sharesOutstanding: Long, 50 | val timestamp: Long 51 | ) 52 | 53 | /** 54 | * Represents historical stock data for a specific date. 55 | * 56 | * @property date The date associated with the historical data in YYYY-MM-DD format. 57 | * @property open The opening price of the stock on the specified date. 58 | * @property high The highest price reached by the stock on the specified date. 59 | * @property low The lowest price reached by the stock on the specified date. 60 | * @property close The closing price of the stock on the specified date. 61 | */ 62 | @Serializable 63 | data class Historical( 64 | val date: String, 65 | val open: Double, 66 | val high: Double, 67 | val low: Double, 68 | val close: Double, 69 | ) 70 | 71 | /** 72 | * Represents historical price data for a specific financial asset. 73 | * 74 | * @property symbol The ticker symbol of the asset. 75 | * @property historical A list of historical data points, each represented by the `Historical` data class. 76 | */ 77 | @Serializable 78 | data class HistoricalPrice( 79 | val symbol: String, 80 | val historical: List 81 | ) -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/server/src/main/kotlin/io/github/devcrocod/example/financialModelingPrep.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.call.* 5 | import io.ktor.client.request.* 6 | import io.ktor.http.* 7 | 8 | private val fmpApiKey = System.getenv("FMP_API_KEY") ?: error("FMP_API_KEY environment variable is not set") 9 | 10 | /** 11 | * Retrieves the current price of a stock 12 | * 13 | * @param symbol The stock ticker symbol or financial instrument identifier for which to fetch the price. 14 | * @return A [Quote] object containing the details of the financial instrument's current price, or null if the request is unsuccessful or the symbol is invalid. 15 | */ 16 | suspend fun HttpClient.getCurrentPrice(symbol: String): Quote? { 17 | val response = this.get("quote/${symbol.uppercase()}") { 18 | parameter("apikey", fmpApiKey) 19 | }.takeIf { it.status == HttpStatusCode.OK } ?: run { 20 | System.err.println("Failed to retrieve data for $symbol") 21 | null 22 | } 23 | 24 | return response?.body>()?.firstOrNull() 25 | } 26 | 27 | /** 28 | * Fetches the historical price data for a given stock 29 | * 30 | * @param symbol The stock ticker symbol of the financial asset to retrieve historical price data for. 31 | * @param from An optional parameter specifying the start date of the historical data range in YYYY-MM-DD format. 32 | * @param to An optional parameter specifying the end date of the historical data range in YYYY-MM-DD format. 33 | * @return A [HistoricalPrice] object containing the historical price data for the specified symbol, or null if the data could not be retrieved. 34 | */ 35 | suspend fun HttpClient.getHistoricalPrice(symbol: String, from: String? = null, to: String? = null): HistoricalPrice? { 36 | val response = this.get("historical-price-full/${symbol.uppercase()}") { 37 | url { 38 | with(parameters) { 39 | from?.let { append("from", it) } 40 | to?.let { append("to", it) } 41 | append("apikey", fmpApiKey) 42 | } 43 | } 44 | }.takeIf { it.status == HttpStatusCode.OK } ?: run { 45 | System.err.println("Failed to retrieve data for $symbol") 46 | null 47 | } 48 | 49 | return response?.body() 50 | } 51 | -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/server/src/main/kotlin/io/github/devcrocod/example/main.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example 2 | 3 | /** 4 | * Entry point. 5 | * It initializes and runs the appropriate server mode based on the input arguments. 6 | * 7 | * Command-line arguments passed to the application: 8 | * - args[0]: Specifies the server mode. Supported values are: 9 | * - "--sse-server": Runs the SSE MCP server. 10 | * - "--stdio": Runs the MCP server using standard input/output. 11 | * Defaults to "--sse-server" if not provided. 12 | * - args[1]: Specifies the port number for the server. Defaults to 3001 if not provided or invalid. 13 | */ 14 | fun main(args: Array) { 15 | val command = args.firstOrNull() ?: "--sse-server" 16 | val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 17 | when (command) { 18 | "--sse-server" -> `run sse mcp server`(port) 19 | "--stdio" -> `run mcp server using stdio`() 20 | else -> { 21 | System.err.println("Unknown command: $command") 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /projects/mcp/mcp-demo/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "mcp-demo" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | repositories { 6 | google { 7 | mavenContent { 8 | includeGroupAndSubgroups("androidx") 9 | includeGroupAndSubgroups("com.android") 10 | includeGroupAndSubgroups("com.google") 11 | } 12 | } 13 | mavenCentral() 14 | gradlePluginPortal() 15 | } 16 | } 17 | 18 | dependencyResolutionManagement { 19 | repositories { 20 | google { 21 | mavenContent { 22 | includeGroupAndSubgroups("androidx") 23 | includeGroupAndSubgroups("com.android") 24 | includeGroupAndSubgroups("com.google") 25 | } 26 | } 27 | mavenCentral() 28 | } 29 | } 30 | 31 | include(":composeApp") 32 | include(":server") -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Kotlin ### 40 | .kotlin 41 | -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/README.md: -------------------------------------------------------------------------------- 1 | # Spring AI with OpenAI (Kotlin Version) 2 | 3 | This project is a Kotlin version of the original project: [ai-openai-helloworld](https://github.com/rd-1-2022/ai-openai-helloworld/blob/main/README.md). 4 | 5 | It contains a web service that accepts HTTP GET requests at `http://localhost:8080/ai/simple`. 6 | 7 | There is an optional `message` parameter whose default value is "Tell me a joke". 8 | 9 | The response to the request is generated using the OpenAI ChatGPT Service. 10 | 11 | ## Prerequisites 12 | 13 | Before using the AI commands, make sure you have a developer token from OpenAI. 14 | 15 | 1. Create an account at [OpenAI Signup](https://platform.openai.com/signup). 16 | 2. Generate the token at [API Keys](https://platform.openai.com/account/api-keys). 17 | 18 | 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`. 19 | 20 | Exporting an environment variable is one way to set that configuration property: 21 | 22 | [MacOS/Linux] 23 | ```shell 24 | export OPENAI_API_KEY= 25 | ``` 26 | 27 | [Windows] 28 | ```shell 29 | set OPENAI_API_KEY= 30 | ``` 31 | 32 | Setting the API key is all you need to run the application. 33 | 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/chat/openai-chat.html). 34 | 35 | ## Building and running 36 | 37 | [MacOS/Linux] 38 | ```shell 39 | ./gradlew bootRun 40 | ``` 41 | 42 | [Windows] 43 | ```shell 44 | gradlew.bat bootRun 45 | ``` 46 | 47 | ## Access the endpoint 48 | 49 | To get a response to the default request of "Tell me a joke" 50 | 51 | ```shell 52 | curl localhost:8080/ai 53 | ``` 54 | 55 | A sample response is 56 | 57 | ```text 58 | Sure, here's a classic one for you: 59 | 60 | Why don't scientists trust atoms? 61 | 62 | Because they make up everything! 63 | ``` 64 | 65 | Now using the `message` request parameter 66 | ```shell 67 | curl --get --data-urlencode 'message=Tell me a joke about a cow.' localhost:8080/ai 68 | ``` 69 | 70 | A sample response is 71 | 72 | ```text 73 | Why did the cow go to space? 74 | 75 | Because it wanted to see the mooooon! 76 | ``` 77 | 78 | Alternatively use the [httpie](https://httpie.io/) client 79 | ```shell 80 | http localhost:8080/ai message=='Tell me a joke about a cow.' 81 | ``` -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.21" 3 | kotlin("plugin.spring") version "2.1.21" 4 | id("org.springframework.boot") version "3.5.0" 5 | id("io.spring.dependency-management") version "1.1.7" 6 | } 7 | 8 | group = "io.github.devcrocod.example" 9 | version = "0.0.1-SNAPSHOT" 10 | description = "Simple AI Application using OpenAPI Service" 11 | 12 | java { 13 | toolchain { 14 | languageVersion = JavaLanguageVersion.of(17) 15 | } 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | extra["springAiVersion"] = "1.0.0" 23 | 24 | dependencyManagement { 25 | imports { 26 | mavenBom("org.springframework.ai:spring-ai-bom:${property("springAiVersion")}") 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation("org.jetbrains.kotlin:kotlin-reflect") 32 | implementation("org.springframework.boot:spring-boot-starter-web") 33 | implementation("org.springframework.boot:spring-boot-starter-actuator") 34 | implementation("org.springframework.ai:spring-ai-starter-model-openai") 35 | 36 | testImplementation("org.springframework.boot:spring-boot-starter-test") 37 | } 38 | 39 | kotlin { 40 | compilerOptions { 41 | freeCompilerArgs.addAll("-Xjsr305=strict") 42 | } 43 | } 44 | 45 | tasks.withType { 46 | useJUnitPlatform() 47 | } 48 | -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/helloworld/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "helloworld" 2 | -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/src/main/kotlin/io/github/devcrocod/example/aihelloworld/AIController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.aihelloworld 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.RequestParam 6 | import org.springframework.web.bind.annotation.RestController 7 | 8 | @RestController 9 | class AIController(private val chatClient: ChatClient) { 10 | 11 | @GetMapping("/ai") 12 | fun completion( 13 | @RequestParam(value = "message", defaultValue = "Tell me a joke") message: String 14 | ): Map { 15 | return mapOf( 16 | "completion" to 17 | chatClient.prompt() 18 | .user(message) 19 | .call() 20 | .content()!! 21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/src/main/kotlin/io/github/devcrocod/example/aihelloworld/Application.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.aihelloworld 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication 7 | class Application 8 | 9 | fun main(args: Array) { 10 | runApplication(*args) 11 | } -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/src/main/kotlin/io/github/devcrocod/example/aihelloworld/Config.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.aihelloworld 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.context.annotation.Bean 5 | import org.springframework.context.annotation.Configuration 6 | 7 | @Configuration 8 | class Config { 9 | 10 | @Bean 11 | fun chatClient(builder: ChatClient.Builder): ChatClient = builder.build() 12 | } -------------------------------------------------------------------------------- /projects/spring-ai/helloworld/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=helloworld 2 | 3 | spring.ai.openai.api-key=${OPENAI_API_KEY} 4 | spring.ai.openai.chat.options.model=gpt-4o-mini 5 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | /target/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | 40 | ### Kotlin ### 41 | .kotlin 42 | 43 | # The following files are automatically generated/updated 44 | node_modules/ 45 | frontend/generated/ 46 | .npmrc 47 | vite.generated.ts 48 | 49 | 50 | # Eclipse store 51 | storage/ 52 | 53 | !**/src/test/resources/volumes 54 | 55 | src/main/bundles 56 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/README.md: -------------------------------------------------------------------------------- 1 | # AI-powered Expert System Demo (Kotlin Version) 2 | 3 | This is a Kotlin reimplementation of the project: https://github.com/tzolov/playground-flight-booking 4 | 5 | This project demonstrates how to use [Spring AI](https://github.com/spring-projects/spring-ai) to build an AI-powered system that: 6 | 7 | - Has access to terms and conditions (retrieval augmented generation, RAG) 8 | - Can call methods (functions) to perform actions (Function Calling) 9 | - Uses an LLM to interact with the user 10 | 11 | ![alt text](diagram.jpg) 12 | 13 | ## Requirements 14 | 15 | - Java 17+ 16 | - OpenAI API key in the `OPENAI_API_KEY` environment variable 17 | 18 | ## Running 19 | 20 | Run the application by executing `Application.kt` in your IDE or by running `./gradlew bootRun` 21 | (`gradlew.bat bootRun` for Windows) in the command line. 22 | 23 | ### With OpenAI Chat 24 | 25 | Add the Spring AI OpenAI dependency to `build.gradle.kts`: 26 | 27 | ```kotlin 28 | implementation("org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-SNAPSHOT") 29 | ``` 30 | 31 | Add the OpenAI configuration to `application.properties`: 32 | ``` 33 | spring.ai.openai.api-key=${OPENAI_API_KEY} 34 | spring.ai.openai.chat.options.model=gpt-4o 35 | ``` 36 | 37 | ### With VertexAI Gemini Chat 38 | Add the Spring AI VertexAI Gemini and Onnx Transformer Embedding dependencies to `build.gradle.kts`: 39 | ```kotlin 40 | implementation("org.springframework.ai:spring-ai-vertex-ai-gemini-spring-boot-starter:1.0.0-SNAPSHOT") 41 | implementation("org.springframework.ai:spring-ai-transformers-spring-boot-starter:1.0.0-SNAPSHOT") 42 | ``` 43 | 44 | Add the VertexAI Gemini configuration to `application.properties`: 45 | ``` 46 | spring.ai.vertex.ai.gemini.project-id=${VERTEX_AI_GEMINI_PROJECT_ID} 47 | spring.ai.vertex.ai.gemini.location=${VERTEX_AI_GEMINI_LOCATION} 48 | spring.ai.vertex.ai.gemini.chat.options.model=gemini-1.5-pro-001 49 | # spring.ai.vertex.ai.gemini.chat.options.model=gemini-1.5-flash-001 50 | ``` 51 | 52 | ### With Azure OpenAI Chat 53 | Add the Spring AI Azure OpenAI dependency to `build.gradle.kts`: 54 | ```kotlin 55 | implementation("org.springframework.ai:spring-ai-azure-openai-spring-boot-starter:1.0.0-SNAPSHOT") 56 | ``` 57 | 58 | Add the Azure OpenAI configuration to `application.properties`: 59 | ``` 60 | spring.ai.azure.openai.api-key=${AZURE_OPENAI_API_KEY} 61 | spring.ai.azure.openai.endpoint=${AZURE_OPENAI_ENDPOINT} 62 | spring.ai.azure.openai.chat.options.deployment-name=gpt-4o 63 | ``` 64 | 65 | ### With Groq Chat 66 | Groq reuses the OpenAI Chat client but points to the Groq endpoint. 67 | 68 | Add the Spring AI OpenAI dependency to `build.gradle.kts`: 69 | ```kotlin 70 | implementation("org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-SNAPSHOT") 71 | implementation("org.springframework.ai:spring-ai-transformers-spring-boot-starter:1.0.0-SNAPSHOT") 72 | ``` 73 | 74 | Add the Groq configuration to `application.properties`: 75 | ``` 76 | spring.ai.openai.api-key=${GROQ_API_KEY} 77 | spring.ai.openai.base-url=https://api.groq.com/openai 78 | spring.ai.openai.chat.options.model=llama3-70b-8192 79 | ``` 80 | 81 | ### With Anthropic Claude 3 Chat 82 | Add the Spring AI Anthropic Claude and Onnx Transformer Embedding dependencies to `build.gradle.kts`: 83 | ```kotlin 84 | implementation("org.springframework.ai:spring-ai-anthropic-spring-boot-starter:1.0.0-SNAPSHOT") 85 | implementation("org.springframework.ai:spring-ai-transformers-spring-boot-starter:1.0.0-SNAPSHOT") 86 | ``` 87 | 88 | Add the Anthropic configuration to `application.properties`: 89 | ``` 90 | spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY} 91 | spring.ai.openai.chat.options.model=llama3-70b-8192 92 | spring.ai.anthropic.chat.options.model=claude-3-5-sonnet-20240620 93 | ``` 94 | 95 | ## Build Jar 96 | [MacOS/Linux] 97 | ```shell 98 | ./gradlew clean build -Pproduction 99 | ``` 100 | 101 | [Windows] 102 | ```shell 103 | gradlew.bat clean build -Pproduction 104 | ``` 105 | 106 | [MacOS/Linux] 107 | ```shell 108 | java -jar ./build/libs/playground-flight-booking-0.0.1-SNAPSHOT.jar 109 | ``` 110 | 111 | [Windows] 112 | ```shell 113 | java -jar .\build\libs\playground-flight-booking-0.0.1-SNAPSHOT.jar 114 | ``` 115 | 116 | ## Using Docker 117 | ```shell 118 | docker run -it --rm --name postgres -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres ankane/pgvector 119 | ``` -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.21" 3 | kotlin("plugin.spring") version "2.1.21" 4 | id("org.springframework.boot") version "3.5.0" 5 | id("io.spring.dependency-management") version "1.1.7" 6 | id("com.vaadin") version "24.6.5" 7 | } 8 | 9 | group = "io.github.devcrocod.example" 10 | version = "0.0.1-SNAPSHOT" 11 | 12 | java { 13 | toolchain { 14 | languageVersion = JavaLanguageVersion.of(17) 15 | } 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | maven { url = uri("https://maven.vaadin.com/vaadin-prereleases") } 21 | } 22 | 23 | val vaadinVersion = "24.6.5" 24 | val springAiVersion = "1.0.0" 25 | val coroutinesVersion = "1.10.2" 26 | 27 | dependencies { 28 | implementation("org.jetbrains.kotlin:kotlin-reflect") 29 | implementation(platform("org.springframework.ai:spring-ai-bom:$springAiVersion")) 30 | implementation(platform("com.vaadin:vaadin-bom:$vaadinVersion")) 31 | 32 | /* ----------------------------- Spring AI ------------------------------ */ 33 | implementation("org.springframework.ai:spring-ai-starter-model-openai") 34 | /* --------------------------- Vector Stores ---------------------------- */ 35 | implementation("org.springframework.ai:spring-ai-starter-vector-store-chroma") 36 | implementation("org.springframework.ai:spring-ai-advisors-vector-store") 37 | 38 | /* ------------------------------ Vaadin -------------------------------- */ 39 | implementation("com.vaadin:vaadin-spring-boot-starter") 40 | 41 | /* --------------------------- Spring Starters -------------------------- */ 42 | implementation("org.springframework.boot:spring-boot-starter-webflux") 43 | implementation("org.springframework.boot:spring-boot-starter-validation") 44 | developmentOnly("org.springframework.boot:spring-boot-devtools") 45 | 46 | /* ---------------------------- Observability --------------------------- */ 47 | implementation("io.micrometer:micrometer-tracing-bridge-brave") 48 | implementation("io.zipkin.reporter2:zipkin-reporter-brave") 49 | implementation("org.springframework.boot:spring-boot-starter-actuator") 50 | runtimeOnly("io.micrometer:micrometer-registry-prometheus") 51 | 52 | /* ------------------------------ Logging ------------------------------- */ 53 | implementation("com.github.loki4j:loki-logback-appender:1.5.1") 54 | 55 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}") 56 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}") 57 | 58 | /* ------------------------------ Testing ------------------------------- */ 59 | testImplementation("org.springframework.boot:spring-boot-starter-test") 60 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") 61 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 62 | } 63 | 64 | kotlin { 65 | compilerOptions { 66 | freeCompilerArgs.addAll("-Xjsr305=strict") 67 | } 68 | } 69 | 70 | tasks.withType { 71 | useJUnitPlatform() 72 | } 73 | 74 | vaadin { 75 | productionMode = project.hasProperty("production") 76 | } 77 | 78 | tasks.named("bootRun") { 79 | jvmArgs( 80 | "-Xdebug", 81 | "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5247" 82 | ) 83 | } 84 | 85 | defaultTasks("bootRun") -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/playground-flight-booking/diagram.jpg -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/docker-compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | net: 3 | driver: bridge 4 | services: 5 | server: 6 | image: ghcr.io/chroma-core/chroma:latest 7 | environment: 8 | - IS_PERSISTENT=TRUE 9 | volumes: 10 | # Default configuration for persist_directory in chromadb/config.py 11 | # Currently it's located in "/chroma/chroma/" 12 | - chroma-data:/chroma/chroma/ 13 | ports: 14 | - 8000:8000 15 | networks: 16 | - net 17 | 18 | volumes: 19 | chroma-data: 20 | driver: local -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/frontend/components/Message.tsx: -------------------------------------------------------------------------------- 1 | import Markdown from "react-markdown"; 2 | 3 | export interface MessageItem { 4 | role: 'user' | 'assistant'; 5 | content: string; 6 | } 7 | 8 | interface MessageProps { 9 | message: MessageItem; 10 | } 11 | 12 | export default function Message({message}: MessageProps) { 13 | return ( 14 |
15 |
{message.role === 'user' ? '🧑‍💻 You' : '🤖 Assistant'}
16 |
17 | 18 | {message.content} 19 | 20 |
21 |
22 | ) 23 | }; -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/frontend/components/MessageList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react'; 2 | import Message, {MessageItem} from './Message'; 3 | 4 | interface MessageListProps { 5 | messages: MessageItem[]; 6 | className?: string; 7 | } 8 | 9 | export default function MessageList({ messages, className }: MessageListProps) { 10 | const endOfMessagesRef = useRef(null); 11 | 12 | // Automatically scroll down whenever the messages change 13 | useEffect(() => { 14 | if (endOfMessagesRef.current) { 15 | endOfMessagesRef.current.scrollIntoView({ behavior: 'smooth' }); 16 | } 17 | }, [messages]); 18 | 19 | return ( 20 |
21 | {messages.map((msg, index) => ( 22 | 23 | ))} 24 |
25 |
26 | ); 27 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/frontend/components/SeatSelection.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | 3 | type SeatSelectionProps = { 4 | selectedSeat?: string; 5 | onSeatSelect?: (seatNumber: string) => void; 6 | }; 7 | 8 | export default function SeatSelection({selectedSeat, onSeatSelect}: SeatSelectionProps) { 9 | // Define the seat layout 10 | const rows = 12; 11 | const seatsPerRow = 6; 12 | const seatLetters = ['A', 'B', 'C', 'D', 'E', 'F']; 13 | 14 | // Local state for selected seat 15 | const [currentSelectedSeat, setCurrentSelectedSeat] = useState(selectedSeat || null); 16 | 17 | // Update local selected seat if prop changes 18 | useEffect(() => { 19 | setCurrentSelectedSeat(selectedSeat || null); 20 | }, [selectedSeat]); 21 | 22 | // Handler for seat selection 23 | const handleSeatClick = (seatId: string) => { 24 | setCurrentSelectedSeat(seatId); 25 | if (onSeatSelect) { 26 | onSeatSelect(seatId); 27 | } 28 | }; 29 | 30 | // Render the seats 31 | const renderSeats = () => { 32 | const seatRows = []; 33 | for (let row = 1; row <= rows; row++) { 34 | const seatRow = []; 35 | for (let seatIndex = 0; seatIndex < seatsPerRow; seatIndex++) { 36 | const seatLetter = seatLetters[seatIndex]; 37 | const seatId = `${row}${seatLetter}`; 38 | const isSelected = currentSelectedSeat === seatId; 39 | seatRow.push( 40 |
handleSeatClick(seatId)} 43 | style={{ 44 | width: 30, 45 | height: 30, 46 | margin: 2, 47 | backgroundColor: isSelected ? 'blue' : 'lightgray', 48 | color: isSelected ? 'white' : 'black', 49 | textAlign: 'center', 50 | lineHeight: '30px', 51 | cursor: 'pointer', 52 | }} 53 | > 54 | {seatLetter} 55 |
56 | ); 57 | // Insert aisle after seat C 58 | if (seatLetter === 'C') { 59 | seatRow.push( 60 |
61 | ); 62 | } 63 | } 64 | seatRows.push( 65 |
66 |
{row}
67 | {seatRow} 68 |
69 | ); 70 | } 71 | return seatRows; 72 | }; 73 | 74 | return ( 75 |
76 | {renderSeats()} 77 |
78 | ); 79 | }; -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Funnair customer support 7 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/frontend/themes/customer-support-agent/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/playground-flight-booking/frontend/themes/customer-support-agent/styles.css -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/frontend/themes/customer-support-agent/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ] 3 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/frontend/views/@index.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from "react"; 2 | import {AssistantService, BookingService} from "../generated/endpoints"; 3 | import BookingDetails from "../generated/ai/spring/demo/ai/playground/services/BookingTools/BookingDetails"; 4 | import {GridColumn} from "@vaadin/react-components/GridColumn"; 5 | import {Grid} from "@vaadin/react-components/Grid"; 6 | import {MessageInput} from "@vaadin/react-components/MessageInput"; 7 | import {nanoid} from "nanoid"; 8 | import {SplitLayout} from "@vaadin/react-components/SplitLayout"; 9 | import Message, {MessageItem} from "../components/Message"; 10 | import MessageList from "../components/MessageList"; 11 | import {Dialog} from "@vaadin/react-components"; 12 | import SeatSelection from "../components/SeatSelection"; 13 | 14 | export default function Index() { 15 | const [chatId, setChatId] = useState(nanoid()); 16 | const [seatSelectionOpen, setSeatSelectionOpen] = useState(false); 17 | const [seatSelectionRequestId, setSeatSelectionRequestId] = useState(''); 18 | const [working, setWorking] = useState(false); 19 | const [bookings, setBookings] = useState([]); 20 | const [messages, setMessages] = useState([{ 21 | role: 'assistant', 22 | content: 'Welcome to Funnair! How can I help you?' 23 | }]); 24 | 25 | useEffect(() => { 26 | AssistantService.seatChangeRequests(chatId).onNext(request => { 27 | setSeatSelectionRequestId(request.requestId); 28 | setSeatSelectionOpen(true); 29 | }) 30 | }, []); 31 | 32 | useEffect(() => { 33 | // Update bookings when we have received the full response 34 | if (!working) { 35 | BookingService.getBookings().then(setBookings); 36 | } 37 | }, [working]); 38 | 39 | function addMessage(message: MessageItem) { 40 | setMessages(messages => [...messages, message]); 41 | } 42 | 43 | function appendToLatestMessage(chunk: string) { 44 | setMessages(messages => { 45 | const latestMessage = messages[messages.length - 1]; 46 | latestMessage.content += chunk; 47 | return [...messages.slice(0, -1), latestMessage]; 48 | }); 49 | } 50 | 51 | async function sendMessage(message: string) { 52 | setWorking(true); 53 | addMessage({ 54 | role: 'user', 55 | content: message 56 | }); 57 | let first = true; 58 | AssistantService.chat(chatId, message) 59 | .onNext(token => { 60 | if (first && token) { 61 | addMessage({ 62 | role: 'assistant', 63 | content: token 64 | }); 65 | 66 | first = false; 67 | } else { 68 | appendToLatestMessage(token); 69 | } 70 | }) 71 | .onError(() => setWorking(false)) 72 | .onComplete(() => setWorking(false)); 73 | } 74 | 75 | return ( 76 | <> 77 | 78 |
79 |

Funnair support

80 | 81 | sendMessage(e.detail.value)} className="px-0" disabled={working}/> 82 |
83 |
84 |

Bookings database

85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | {({item}) => item.bookingStatus === "CONFIRMED" ? "✅" : "❌"} 95 | 96 | 97 | 98 |
99 |
100 | 101 | 103 | { 104 | AssistantService.completeSeatChangeRequest(seatSelectionRequestId, seatNumber); 105 | setSeatSelectionOpen(false); 106 | setSeatSelectionRequestId(''); 107 | }}/> 108 | 109 | 110 | ); 111 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/playground-flight-booking/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "no-name", 3 | "license": "UNLICENSED", 4 | "type": "module", 5 | "dependencies": { 6 | "@polymer/polymer": "3.5.2", 7 | "@vaadin/bundles": "24.6.4", 8 | "@vaadin/common-frontend": "0.0.19", 9 | "@vaadin/hilla-file-router": "24.6.5", 10 | "@vaadin/hilla-frontend": "24.6.5", 11 | "@vaadin/hilla-lit-form": "24.6.5", 12 | "@vaadin/hilla-react-auth": "24.6.5", 13 | "@vaadin/hilla-react-crud": "24.6.5", 14 | "@vaadin/hilla-react-form": "24.6.5", 15 | "@vaadin/hilla-react-i18n": "24.6.5", 16 | "@vaadin/hilla-react-signals": "24.6.5", 17 | "@vaadin/polymer-legacy-adapter": "24.6.4", 18 | "@vaadin/react-components": "24.6.4", 19 | "@vaadin/vaadin-development-mode-detector": "2.0.7", 20 | "@vaadin/vaadin-lumo-styles": "24.6.4", 21 | "@vaadin/vaadin-material-styles": "24.6.4", 22 | "@vaadin/vaadin-themable-mixin": "24.6.4", 23 | "@vaadin/vaadin-usage-statistics": "2.1.3", 24 | "construct-style-sheets-polyfill": "3.1.0", 25 | "date-fns": "2.29.3", 26 | "lit": "3.2.1", 27 | "nanoid": "^5.0.6", 28 | "react": "18.3.1", 29 | "react-dom": "18.3.1", 30 | "react-markdown": "^9.0.1", 31 | "react-router-dom": "6.29.0" 32 | }, 33 | "devDependencies": { 34 | "@babel/preset-react": "7.26.3", 35 | "@preact/signals-react-transform": "0.5.1", 36 | "@rollup/plugin-replace": "6.0.2", 37 | "@rollup/pluginutils": "5.1.4", 38 | "@types/react": "18.3.18", 39 | "@types/react-dom": "18.3.5", 40 | "@vaadin/hilla-generator-cli": "24.6.5", 41 | "@vaadin/hilla-generator-core": "24.6.5", 42 | "@vaadin/hilla-generator-plugin-backbone": "24.6.5", 43 | "@vaadin/hilla-generator-plugin-barrel": "24.6.5", 44 | "@vaadin/hilla-generator-plugin-client": "24.6.5", 45 | "@vaadin/hilla-generator-plugin-model": "24.6.5", 46 | "@vaadin/hilla-generator-plugin-push": "24.6.5", 47 | "@vaadin/hilla-generator-plugin-signals": "24.6.5", 48 | "@vaadin/hilla-generator-plugin-subtypes": "24.6.5", 49 | "@vaadin/hilla-generator-utils": "24.6.5", 50 | "@vitejs/plugin-react": "4.3.4", 51 | "async": "3.2.6", 52 | "glob": "10.4.5", 53 | "rollup-plugin-brotli": "3.1.0", 54 | "rollup-plugin-visualizer": "5.14.0", 55 | "strip-css-comments": "5.0.0", 56 | "transform-ast": "2.4.4", 57 | "typescript": "5.7.3", 58 | "vite": "6.0.11", 59 | "vite-plugin-checker": "0.8.0", 60 | "workbox-build": "7.3.0", 61 | "workbox-core": "7.3.0", 62 | "workbox-precaching": "7.3.0" 63 | }, 64 | "vaadin": { 65 | "dependencies": { 66 | "@polymer/polymer": "3.5.2", 67 | "@vaadin/bundles": "24.6.4", 68 | "@vaadin/common-frontend": "0.0.19", 69 | "@vaadin/hilla-file-router": "24.6.5", 70 | "@vaadin/hilla-frontend": "24.6.5", 71 | "@vaadin/hilla-lit-form": "24.6.5", 72 | "@vaadin/hilla-react-auth": "24.6.5", 73 | "@vaadin/hilla-react-crud": "24.6.5", 74 | "@vaadin/hilla-react-form": "24.6.5", 75 | "@vaadin/hilla-react-i18n": "24.6.5", 76 | "@vaadin/hilla-react-signals": "24.6.5", 77 | "@vaadin/polymer-legacy-adapter": "24.6.4", 78 | "@vaadin/react-components": "24.6.4", 79 | "@vaadin/vaadin-development-mode-detector": "2.0.7", 80 | "@vaadin/vaadin-lumo-styles": "24.6.4", 81 | "@vaadin/vaadin-material-styles": "24.6.4", 82 | "@vaadin/vaadin-themable-mixin": "24.6.4", 83 | "@vaadin/vaadin-usage-statistics": "2.1.3", 84 | "construct-style-sheets-polyfill": "3.1.0", 85 | "date-fns": "2.29.3", 86 | "lit": "3.2.1", 87 | "react": "18.3.1", 88 | "react-dom": "18.3.1", 89 | "react-router-dom": "6.29.0" 90 | }, 91 | "devDependencies": { 92 | "@babel/preset-react": "7.26.3", 93 | "@preact/signals-react-transform": "0.5.1", 94 | "@rollup/plugin-replace": "6.0.2", 95 | "@rollup/pluginutils": "5.1.4", 96 | "@types/react": "18.3.18", 97 | "@types/react-dom": "18.3.5", 98 | "@vaadin/hilla-generator-cli": "24.6.5", 99 | "@vaadin/hilla-generator-core": "24.6.5", 100 | "@vaadin/hilla-generator-plugin-backbone": "24.6.5", 101 | "@vaadin/hilla-generator-plugin-barrel": "24.6.5", 102 | "@vaadin/hilla-generator-plugin-client": "24.6.5", 103 | "@vaadin/hilla-generator-plugin-model": "24.6.5", 104 | "@vaadin/hilla-generator-plugin-push": "24.6.5", 105 | "@vaadin/hilla-generator-plugin-signals": "24.6.5", 106 | "@vaadin/hilla-generator-plugin-subtypes": "24.6.5", 107 | "@vaadin/hilla-generator-utils": "24.6.5", 108 | "@vitejs/plugin-react": "4.3.4", 109 | "async": "3.2.6", 110 | "glob": "10.4.5", 111 | "rollup-plugin-brotli": "3.1.0", 112 | "rollup-plugin-visualizer": "5.14.0", 113 | "strip-css-comments": "5.0.0", 114 | "transform-ast": "2.4.4", 115 | "typescript": "5.7.3", 116 | "vite": "6.0.11", 117 | "vite-plugin-checker": "0.8.0", 118 | "workbox-build": "7.3.0", 119 | "workbox-core": "7.3.0", 120 | "workbox-precaching": "7.3.0" 121 | }, 122 | "hash": "7f6a652bf4cddeee68c910556ab002143fef15b1b150e19f572c8d40f7faefea" 123 | }, 124 | "overrides": { 125 | "@vaadin/bundles": "$@vaadin/bundles", 126 | "@vaadin/common-frontend": "$@vaadin/common-frontend", 127 | "construct-style-sheets-polyfill": "$construct-style-sheets-polyfill", 128 | "lit": "$lit", 129 | "@polymer/polymer": "$@polymer/polymer", 130 | "nanoid": "$nanoid", 131 | "react-markdown": "$react-markdown", 132 | "@vaadin/polymer-legacy-adapter": "$@vaadin/polymer-legacy-adapter", 133 | "@vaadin/vaadin-development-mode-detector": "$@vaadin/vaadin-development-mode-detector", 134 | "@vaadin/vaadin-usage-statistics": "$@vaadin/vaadin-usage-statistics", 135 | "@vaadin/react-components": "$@vaadin/react-components", 136 | "react-dom": "$react-dom", 137 | "@vaadin/hilla-frontend": "$@vaadin/hilla-frontend", 138 | "@vaadin/hilla-react-auth": "$@vaadin/hilla-react-auth", 139 | "react": "$react", 140 | "@vaadin/hilla-react-crud": "$@vaadin/hilla-react-crud", 141 | "@vaadin/hilla-file-router": "$@vaadin/hilla-file-router", 142 | "react-router-dom": "$react-router-dom", 143 | "@vaadin/hilla-react-i18n": "$@vaadin/hilla-react-i18n", 144 | "@vaadin/hilla-lit-form": "$@vaadin/hilla-lit-form", 145 | "@vaadin/hilla-react-form": "$@vaadin/hilla-react-form", 146 | "@vaadin/hilla-react-signals": "$@vaadin/hilla-react-signals", 147 | "date-fns": "$date-fns", 148 | "@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin", 149 | "@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles", 150 | "@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles" 151 | } 152 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "playground-flight-booking" 2 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/Application.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground 2 | 3 | import com.vaadin.flow.component.page.AppShellConfigurator 4 | import com.vaadin.flow.theme.Theme 5 | import io.micrometer.observation.ObservationPredicate 6 | import org.springframework.ai.chat.memory.ChatMemory 7 | import org.springframework.ai.chat.memory.MessageWindowChatMemory 8 | import org.springframework.ai.reader.TextReader 9 | import org.springframework.ai.transformer.splitter.TokenTextSplitter 10 | import org.springframework.ai.vectorstore.VectorStore 11 | import org.springframework.beans.factory.annotation.Value 12 | import org.springframework.boot.CommandLineRunner 13 | import org.springframework.boot.autoconfigure.SpringBootApplication 14 | import org.springframework.boot.runApplication 15 | import org.springframework.context.annotation.Bean 16 | import org.springframework.core.io.Resource 17 | import org.springframework.http.server.observation.ServerRequestObservationContext 18 | 19 | @SpringBootApplication 20 | @Theme(value = "customer-support-agent") 21 | class Application : AppShellConfigurator { 22 | /** Index the Terms-of-Service document into the vector store. */ 23 | @Bean 24 | fun ingestTermOfServiceToVectorStore( 25 | vectorStore: VectorStore, 26 | @Value("classpath:rag/terms-of-service.txt") termsOfServiceDocs: Resource 27 | ): CommandLineRunner = CommandLineRunner { 28 | vectorStore.write( 29 | TokenTextSplitter().transform( 30 | TextReader(termsOfServiceDocs).read() 31 | ) 32 | ) 33 | } 34 | 35 | /** Default in-memory chat window. */ 36 | @Bean 37 | fun chatMemory(): ChatMemory = 38 | MessageWindowChatMemory.builder().build() 39 | 40 | /** 41 | * Optionally suppress actuator and static-asset endpoints from micrometer 42 | * `http.server.requests` observations 43 | */ 44 | @Bean 45 | fun noActuatorServerObservations(): ObservationPredicate = 46 | ObservationPredicate { name, context -> 47 | if (name == "http.server.requests" && context is ServerRequestObservationContext) { 48 | val uri = context.carrier.requestURI 49 | uri.run { 50 | !startsWith("/actuator") && 51 | !startsWith("/VAADIN") && 52 | !startsWith("/HILLA") && 53 | !startsWith("/connect") && 54 | !startsWith("/**") && 55 | !equals("/", ignoreCase = true) 56 | } 57 | } else { 58 | true 59 | } 60 | } 61 | } 62 | 63 | fun main(args: Array) { 64 | runApplication(*args) 65 | } 66 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/client/AssistantService.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.client 2 | 3 | import com.vaadin.flow.server.auth.AnonymousAllowed 4 | import com.vaadin.hilla.BrowserCallable 5 | import io.github.devcrocod.example.playground.services.CustomerSupportAssistant 6 | import io.github.devcrocod.example.playground.services.SeatChangeQueue 7 | import io.github.devcrocod.example.playground.services.SeatChangeQueue.SeatChangeRequest 8 | import reactor.core.publisher.Flux 9 | import reactor.core.publisher.Sinks 10 | 11 | 12 | @BrowserCallable 13 | @AnonymousAllowed 14 | class AssistantService( 15 | private val assistant: CustomerSupportAssistant, 16 | private val seatChangeQueue: SeatChangeQueue 17 | ) { 18 | 19 | /** Send a user message to the chat assistant and stream its response. */ 20 | fun chat(chatId: String, userMessage: String): Flux = 21 | assistant.chat(chatId, userMessage) 22 | 23 | /** Reactive stream of seat-change requests for the given chat session. */ 24 | fun seatChangeRequests(chatId: String): Flux = 25 | seatChangeQueue.seatChangeRequests 26 | .computeIfAbsent(chatId) { Sinks.many().unicast().onBackpressureBuffer() } 27 | .asFlux() 28 | 29 | /** Completes a pending seat-change request with the chosen seat. */ 30 | fun completeSeatChangeRequest(requestId: String, seat: String) { 31 | seatChangeQueue.pendingRequests 32 | .remove(requestId) 33 | ?.complete(seat) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/client/BookingService.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.client 2 | 3 | import com.vaadin.flow.server.auth.AnonymousAllowed 4 | import com.vaadin.hilla.BrowserCallable 5 | import io.github.devcrocod.example.playground.data.BookingDetails 6 | import io.github.devcrocod.example.playground.services.FlightBookingService 7 | 8 | @BrowserCallable 9 | @AnonymousAllowed 10 | class BookingService(private val flightBookingService: FlightBookingService) { 11 | 12 | fun getBookings(): List = 13 | flightBookingService.getBookings() 14 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/data/Booking.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.data 2 | 3 | import java.time.LocalDate 4 | 5 | data class Booking( 6 | var bookingNumber: String, 7 | var date: LocalDate, 8 | var bookingTo: LocalDate? = null, 9 | var customer: Customer, 10 | var from: String, 11 | var to: String, 12 | var bookingStatus: BookingStatus, 13 | var seatNumber: String, 14 | var bookingClass: BookingClass, 15 | ) -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/data/BookingClass.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.data 2 | 3 | enum class BookingClass { 4 | ECONOMY, PREMIUM_ECONOMY, BUSINESS 5 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/data/BookingData.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.data 2 | 3 | data class BookingData( 4 | var customers: List = emptyList(), 5 | var bookings: List = emptyList() 6 | ) -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/data/BookingDetails.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.data 2 | 3 | import java.time.LocalDate 4 | 5 | data class BookingDetails( 6 | val bookingNumber: String, 7 | val firstName: String, 8 | val lastName: String, 9 | val date: LocalDate?, 10 | val bookingStatus: BookingStatus?, 11 | val from: String?, 12 | val to: String?, 13 | val seatNumber: String?, 14 | val bookingClass: String? 15 | ) -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/data/BookingStatus.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.data 2 | 3 | enum class BookingStatus { 4 | CONFIRMED, COMPLETED, CANCELLED 5 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/data/Customer.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.data 2 | 3 | data class Customer( 4 | var firstName: String, 5 | var lastName: String, 6 | var bookings: MutableList = mutableListOf() 7 | ) -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/services/BookingTools.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.services 2 | 3 | import io.github.devcrocod.example.playground.data.BookingDetails 4 | import org.slf4j.LoggerFactory 5 | import org.springframework.ai.chat.model.ToolContext 6 | import org.springframework.ai.tool.annotation.Tool 7 | import org.springframework.core.NestedExceptionUtils 8 | import org.springframework.stereotype.Service 9 | import java.util.concurrent.CompletableFuture 10 | 11 | @Service 12 | class BookingTools( 13 | private val flightBookingService: FlightBookingService, 14 | private val seatChangeQueue: SeatChangeQueue 15 | ) { 16 | companion object { 17 | private val logger = LoggerFactory.getLogger(BookingTools::class.java) 18 | } 19 | 20 | /** Get booking details, falling back to a “blank” record on error. */ 21 | @Tool(description = "Get booking details") 22 | fun getBookingDetails( 23 | bookingNumber: String, 24 | firstName: String, 25 | lastName: String, 26 | toolContext: ToolContext 27 | ): BookingDetails = 28 | try { 29 | flightBookingService.getBookingDetails(bookingNumber, firstName, lastName) 30 | } catch (e: Exception) { 31 | logger.warn("Booking details: ${NestedExceptionUtils.getMostSpecificCause(e).message}") 32 | BookingDetails(bookingNumber, firstName, lastName, null, null, null, null, null, null) 33 | } 34 | 35 | /** Change a booking’s travel dates. */ 36 | @Tool(description = "change booking dates") 37 | fun changeBooking( 38 | bookingNumber: String, 39 | firstName: String, 40 | lastName: String, 41 | newDate: String, 42 | from: String, 43 | to: String, 44 | toolContext: ToolContext 45 | ) { 46 | flightBookingService.changeBooking(bookingNumber, firstName, lastName, newDate, from, to) 47 | } 48 | 49 | /** Cancel an existing booking. */ 50 | @Tool(description = "Cancel booking") 51 | fun cancelBooking(bookingNumber: String, firstName: String, lastName: String, toolContext: ToolContext) { 52 | flightBookingService.cancelBooking(bookingNumber, firstName, lastName) 53 | } 54 | 55 | /** 56 | * Initiate an interactive seat-change flow. 57 | * Blocks until the asynchronous seat selection is completed elsewhere. 58 | */ 59 | @Tool(description = "Change seat") 60 | fun changeSeat( 61 | bookingNumber: String, 62 | firstName: String, 63 | lastName: String, 64 | seat: String, 65 | toolContext: ToolContext 66 | ) { 67 | logger.info("Changing seat for $bookingNumber to a $seat") 68 | 69 | val chatId = toolContext.context["chat_id"].toString() 70 | val future = CompletableFuture() 71 | 72 | // Ask every sink to emit a request for this chat session 73 | seatChangeQueue.seatChangeRequests.values.forEach { sink -> 74 | sink.tryEmitNext(SeatChangeQueue.SeatChangeRequest(chatId)) 75 | } 76 | 77 | val seat = try { 78 | future.get() // blocks until completeSeatChangeRequest() supplies the seat 79 | } catch (e: Exception) { 80 | throw RuntimeException("Seat selection interrupted", e) 81 | } 82 | 83 | flightBookingService.changeSeat(bookingNumber, firstName, lastName, seat) 84 | } 85 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/services/CustomerSupportAssistant.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.services 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor 5 | import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor 6 | import org.springframework.ai.chat.memory.ChatMemory 7 | import org.springframework.ai.vectorstore.VectorStore 8 | import org.springframework.stereotype.Service 9 | import reactor.core.publisher.Flux 10 | 11 | 12 | @Service 13 | class CustomerSupportAssistant( 14 | chatClientBuilder: ChatClient.Builder, 15 | bookingTools: BookingTools, 16 | vectorStore: VectorStore, 17 | chatMemory: ChatMemory 18 | ) { 19 | private val chatClient: ChatClient = chatClientBuilder 20 | .defaultSystem( 21 | """ 22 | You are a customer chat support agent of an airline named "Funnair". 23 | Respond in a friendly, helpful, and joyful manner. 24 | You are interacting with customers through an online chat system. 25 | Before answering a question about a booking or cancelling a booking, you MUST always 26 | get the following information from the user: booking number, customer first name and last name. 27 | If you can not retrieve the status of my flight, please just say "I am sorry, I can not find the booking details". 28 | Check the message history for booking details before asking the user. 29 | Before changing a booking you MUST ensure it is permitted by the terms. 30 | If there is a charge for the change, you MUST ask the user to consent before proceeding. 31 | Use the provided functions to fetch booking details, change bookings, and cancel bookings. 32 | """.trimIndent() 33 | ) 34 | .defaultAdvisors( 35 | MessageChatMemoryAdvisor.builder(chatMemory).build(), 36 | QuestionAnswerAdvisor.builder(vectorStore).build() 37 | ) 38 | .defaultTools(bookingTools) 39 | .build() 40 | 41 | fun chat(chatId: String, userMessage: String): Flux { 42 | return chatClient.prompt() 43 | .user(userMessage) 44 | .toolContext(mapOf("chat_id" to chatId)) 45 | .advisors { it.param(ChatMemory.CONVERSATION_ID, chatId) } 46 | .stream() 47 | .content() 48 | // .asFlow() // Check flow 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/services/FlightBookingService.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.services 2 | 3 | import io.github.devcrocod.example.playground.data.Booking 4 | import io.github.devcrocod.example.playground.data.BookingClass 5 | import io.github.devcrocod.example.playground.data.BookingData 6 | import io.github.devcrocod.example.playground.data.BookingDetails 7 | import io.github.devcrocod.example.playground.data.BookingStatus 8 | import io.github.devcrocod.example.playground.data.Customer 9 | import org.springframework.stereotype.Service 10 | import java.time.LocalDate 11 | import kotlin.random.Random 12 | 13 | 14 | /** 15 | * In-memory flight-booking “backend” with demo data and simple business rules. 16 | */ 17 | @Service 18 | class FlightBookingService { 19 | private val db: BookingData = BookingData() 20 | 21 | init { 22 | initDemoData() 23 | } 24 | 25 | /** Populate the database with five random demo bookings. */ 26 | private fun initDemoData() { 27 | val firstNames = listOf("John", "Jane", "Michael", "Sarah", "Robert") 28 | val lastNames = listOf("Doe", "Smith", "Johnson", "Williams", "Taylor") 29 | val airportCodes = 30 | listOf("LAX", "SFO", "JFK", "LHR", "CDG", "ARN", "HEL", "TXL", "MUC", "FRA", "MAD", "FUN", "SJC") 31 | val random: Random = Random 32 | 33 | val customers = mutableListOf() 34 | val bookings = mutableListOf() 35 | 36 | repeat(5) { 37 | val customer = Customer(firstName = firstNames[it], lastName = lastNames[it]) 38 | 39 | val booking = Booking( 40 | bookingNumber = "10${it + 1}", 41 | date = LocalDate.now().plusDays(2L * it), 42 | customer = customer, 43 | bookingStatus = BookingStatus.CONFIRMED, 44 | from = airportCodes.random(random), 45 | to = airportCodes.random(random), 46 | seatNumber = "${random.nextInt(19) + 1}A", 47 | bookingClass = BookingClass.entries.random(random) 48 | ) 49 | 50 | customer.bookings.add(booking) 51 | customers += customer 52 | bookings += booking 53 | } 54 | 55 | // replace the database contents on every startup 56 | db.customers = customers 57 | db.bookings = bookings 58 | } 59 | 60 | fun getBookings(): List = 61 | db.bookings.map(::toBookingDetails) 62 | 63 | fun getBookingDetails(bookingNumber: String, firstName: String, lastName: String): BookingDetails = 64 | toBookingDetails(findBooking(bookingNumber, firstName, lastName)) 65 | 66 | fun changeBooking( 67 | bookingNumber: String, firstName: String, lastName: String, 68 | newDate: String, from: String, to: String 69 | ) { 70 | val booking = findBooking(bookingNumber, firstName, lastName) 71 | require(booking.date.isAfter(LocalDate.now().plusDays(1))) { 72 | "Booking cannot be changed within 24 hours of the start date." 73 | } 74 | 75 | booking.apply { 76 | date = LocalDate.parse(newDate) 77 | this.from = from 78 | this.to = to 79 | } 80 | } 81 | 82 | fun cancelBooking(bookingNumber: String, firstName: String, lastName: String) { 83 | val booking = findBooking(bookingNumber, firstName, lastName) 84 | 85 | require(booking.date.isAfter(LocalDate.now().plusDays(2))) { 86 | "Booking cannot be cancelled within 48 hours of the start date." 87 | } 88 | 89 | booking.bookingStatus = BookingStatus.CANCELLED 90 | } 91 | 92 | fun changeSeat(bookingNumber: String, firstName: String, lastName: String, seatNumber: String) { 93 | findBooking(bookingNumber, firstName, lastName).seatNumber = seatNumber 94 | } 95 | 96 | private fun findBooking(bookingNumber: String, firstName: String, lastName: String): Booking = 97 | db.bookings.firstOrNull { 98 | it.bookingNumber.equals(bookingNumber, ignoreCase = true) && 99 | it.customer.firstName.equals(firstName, ignoreCase = true) && 100 | it.customer.lastName.equals(lastName, ignoreCase = true) 101 | } ?: throw IllegalArgumentException("Booking not found") 102 | 103 | private fun toBookingDetails(booking: Booking): BookingDetails = 104 | BookingDetails( 105 | booking.bookingNumber, 106 | booking.customer.firstName, 107 | booking.customer.lastName, 108 | booking.date, 109 | booking.bookingStatus, 110 | booking.from, 111 | booking.to, 112 | booking.seatNumber, 113 | booking.bookingClass.toString() 114 | ) 115 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/kotlin/io/github/devcrocod/example/playground/services/SeatChangeQueue.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.playground.services 2 | 3 | import org.springframework.stereotype.Service 4 | import reactor.core.publisher.Sinks 5 | import java.util.concurrent.CompletableFuture 6 | import java.util.concurrent.ConcurrentHashMap 7 | 8 | 9 | /** 10 | * Manages seat-change requests flowing between the chat agent and the UI. 11 | */ 12 | @Service 13 | class SeatChangeQueue { 14 | 15 | /** Simple “envelope” for a seat-change request. */ 16 | data class SeatChangeRequest(val requestId: String) 17 | 18 | /** Futures that are awaiting a seat choice keyed by chat/session ID. */ 19 | val pendingRequests = ConcurrentHashMap>() 20 | 21 | /** Reactive sinks that broadcast seat-change requests to subscribers, keyed by chat/session ID. */ 22 | val seatChangeRequests = ConcurrentHashMap>() 23 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=flight-booking-assistant 2 | 3 | # spring.ai.chat.client.enabled=false 4 | 5 | spring.threads.virtual.enabled=true 6 | 7 | 8 | ################### 9 | # Anthropic Claude 3 10 | ################### 11 | 12 | #spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY} 13 | #spring.ai.anthropic.chat.options.model=claude-3-7-sonnet-20250219 14 | 15 | 16 | ################### 17 | # Groq 18 | ################### 19 | 20 | # spring.ai.openai.api-key=${GROQ_API_KEY} 21 | # spring.ai.openai.base-url=https://api.groq.com/openai 22 | # spring.ai.openai.chat.options.model=llama3-70b-8192 23 | 24 | 25 | ################### 26 | # OpenAI 27 | ################### 28 | spring.ai.openai.api-key=${OPENAI_API_KEY} 29 | spring.ai.openai.chat.options.model=gpt-4o 30 | 31 | # spring.ai.openai.chat.options.functions=getBookingDetails,changeBooking,cancelBooking 32 | 33 | ################### 34 | # Chroma 35 | ################### 36 | spring.ai.vectorstore.chroma.client.host=http://localhost 37 | spring.ai.vectorstore.chroma.client.port=8000 38 | spring.ai.vectorstore.chroma.initialize-schema=true 39 | 40 | 41 | # Disable the OpenAI embedding when the local huggingface embedding (e.g. spring-ai-transformers-spring-boot-starter) is used. 42 | # spring.ai.openai.embedding.enabled=false 43 | 44 | ################### 45 | # Azure OpenAI 46 | ################### 47 | # spring.ai.azure.openai.api-key=${AZURE_OPENAI_API_KEY} 48 | # spring.ai.azure.openai.endpoint=${AZURE_OPENAI_ENDPOINT} 49 | # spring.ai.azure.openai.chat.options.deployment-name=gpt-4o 50 | 51 | ################### 52 | # Mistral AI 53 | ################### 54 | 55 | # spring.ai.mistralai.api-key=${MISTRAL_AI_API_KEY} 56 | # spring.ai.mistralai.chat.options.model=mistral-small-latest 57 | 58 | # spring.ai.mistralai.chat.options.model=mistral-small-latest 59 | # spring.ai.mistralai.chat.options.functions=getBookingDetails,changeBooking,cancelBooking 60 | # # spring.ai.retry.on-client-errors=true 61 | # # spring.ai.retry.exclude-on-http-codes=429 62 | 63 | ################### 64 | # Vertex AI Gemini 65 | ################### 66 | 67 | # spring.ai.vertex.ai.gemini.project-id=${VERTEX_AI_GEMINI_PROJECT_ID} 68 | # spring.ai.vertex.ai.gemini.location=${VERTEX_AI_GEMINI_LOCATION} 69 | # spring.ai.vertex.ai.gemini.chat.options.model=gemini-1.5-pro-001 70 | # # spring.ai.vertex.ai.gemini.chat.options.model=gemini-1.5-flash-001 71 | # spring.ai.vertex.ai.gemini.chat.options.transport-type=REST 72 | 73 | # spring.ai.vertex.ai.gemini.chat.options.functions=getBookingDetails,changeBooking,cancelBooking 74 | 75 | ################### 76 | # Milvus Vector Store 77 | ################### 78 | # Change the dimentions to 384 if the local huggingface embedding (e.g. spring-ai-transformers-spring-boot-starter) is used. 79 | # spring.ai.vectorstore.milvus.embedding-dimension=384 80 | 81 | ################### 82 | # PGVector 83 | ################### 84 | # spring.datasource.url=jdbc:postgresql://localhost:5432/postgres 85 | # spring.datasource.username=postgres 86 | # spring.datasource.password=postgres 87 | # spring.ai.vectorstore.pgvector.initialize-schema=true 88 | 89 | ################### 90 | # QDrant 91 | ################### 92 | # spring.ai.vectorstore.qdrant.host=localhost 93 | # spring.ai.vectorstore.qdrant.port=6334 94 | 95 | 96 | 97 | # Enable context propagation for Reactor (required for Observability with streaming) 98 | spring.reactor.context-propagation=auto 99 | 100 | 101 | ## metrics 102 | management.endpoints.web.exposure.include=health, info, metrics, prometheus 103 | management.metrics.distribution.percentiles-histogram.http.server.requests=true 104 | management.observations.key-values.application=flight-booking-assistant 105 | 106 | ## percentiles histogram 107 | management.metrics.distribution.percentiles-histogram.gen_ai.client.operation=true 108 | management.metrics.distribution.percentiles-histogram.db.vector.client.operation=true 109 | management.metrics.distribution.percentiles-histogram.spring.ai.chat.client=true 110 | management.metrics.distribution.percentiles-histogram.spring.ai.tool=true 111 | 112 | ## logging 113 | # logging.pattern.correlation=[${spring.application.name:},%X{traceId:-},%X{spanId:-}] 114 | 115 | ## tracing 116 | management.tracing.sampling.probability=1.0 117 | management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans 118 | 119 | 120 | # disable PDF reader logs 121 | logging.level.com.zaxxer.hikari=ERROR 122 | logging.level.org.springframework.ai=ERROR 123 | logging.level.org.apache.pdfbox.pdmodel.font=OFF 124 | logging.level.org.apache.fontbox.ttf=OFF 125 | logging.level.org.atmosphere=OFF 126 | 127 | 128 | ###################################### 129 | # Spring AI observability settings 130 | ###################################### 131 | 132 | spring.ai.tools.observations.include-content=true 133 | 134 | ## Include the Chatclient input in observations 135 | spring.ai.chat.client.observation.log-input=true 136 | 137 | ## Include the VectorStore query and response in observations 138 | spring.ai.vectorstore.observations.log-query-response=true 139 | 140 | ## Include prompt and completion contents in observations 141 | spring.ai.chat.observations.log-prompt=true 142 | spring.ai.chat.observations.log-completion=true 143 | 144 | ## Include error logging in observations (note: not needed for Spring Web apps) 145 | spring.ai.chat.observations.include-error-logging=true 146 | 147 | logging.level.org.springframework.ai.chat.observation=DEBUG 148 | 149 | ## 150 | # spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config2.json 151 | # spring.ai.mcp.client.toolcallback.enabled=true -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | http://localhost:3100/loki/api/v1/push 10 | 11 | 12 | 15 | 16 | ${FILE_LOG_PATTERN} 17 | 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/resources/mcp-servers-config2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "brave-search": { 4 | "command": "npx", 5 | "args": [ 6 | "-y", 7 | "@modelcontextprotocol/server-brave-search" 8 | ], 9 | "env": { 10 | } 11 | }, 12 | "filesystem": { 13 | "command": "npx", 14 | "args": [ 15 | "-y", 16 | "@modelcontextprotocol/server-filesystem", 17 | "/Users/christiantzolov/Desktop/tmp" 18 | ] 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/main/resources/rag/terms-of-service.txt: -------------------------------------------------------------------------------- 1 | These Terms of Service govern your experience with Funnair. By booking a flight, you agree to these terms. 2 | 3 | 1. Booking Flights 4 | - Book via our website or mobile app. 5 | - Full payment required at booking. 6 | - Ensure accuracy of personal information (Name, ID, etc.) as corrections may incur a $25 fee. 7 | 8 | 2. Changing Bookings 9 | - Changes allowed up to 24 hours before flight. 10 | - Change via online or contact our support. 11 | - Change fee: $50 for Economy, $30 for Premium Economy, Free for Business Class. 12 | 13 | 3. Cancelling Bookings 14 | - Cancel up to 48 hours before flight. 15 | - Cancellation fees: $75 for Economy, $50 for Premium Economy, $25 for Business Class. 16 | - Refunds processed within 7 business days. -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/src/test/resources/standalone_embed.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Licensed to the LF AI & Data foundation under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | run_embed() { 20 | cat << EOF > embedEtcd.yaml 21 | listen-client-urls: http://0.0.0.0:2379 22 | advertise-client-urls: http://0.0.0.0:2379 23 | EOF 24 | 25 | sudo docker run -d \ 26 | --name milvus-standalone \ 27 | --security-opt seccomp:unconfined \ 28 | -e ETCD_USE_EMBED=true \ 29 | -e ETCD_DATA_DIR=/var/lib/milvus/etcd \ 30 | -e ETCD_CONFIG_PATH=/milvus/configs/embedEtcd.yaml \ 31 | -e COMMON_STORAGETYPE=local \ 32 | -v $(pwd)/volumes/milvus:/var/lib/milvus \ 33 | -v $(pwd)/embedEtcd.yaml:/milvus/configs/embedEtcd.yaml \ 34 | -p 19530:19530 \ 35 | -p 9091:9091 \ 36 | -p 2379:2379 \ 37 | --health-cmd="curl -f http://localhost:9091/healthz" \ 38 | --health-interval=30s \ 39 | --health-start-period=90s \ 40 | --health-timeout=20s \ 41 | --health-retries=3 \ 42 | milvusdb/milvus:v2.3.9 \ 43 | milvus run standalone 1> /dev/null 44 | } 45 | 46 | wait_for_milvus_running() { 47 | echo "Wait for Milvus Starting..." 48 | while true 49 | do 50 | res=`sudo docker ps|grep milvus-standalone|grep healthy|wc -l` 51 | if [ $res -eq 1 ] 52 | then 53 | echo "Start successfully." 54 | break 55 | fi 56 | sleep 1 57 | done 58 | } 59 | 60 | start() { 61 | res=`sudo docker ps|grep milvus-standalone|grep healthy|wc -l` 62 | if [ $res -eq 1 ] 63 | then 64 | echo "Milvus is running." 65 | exit 0 66 | fi 67 | 68 | res=`sudo docker ps -a|grep milvus-standalone|wc -l` 69 | if [ $res -eq 1 ] 70 | then 71 | sudo docker start milvus-standalone 1> /dev/null 72 | else 73 | run_embed 74 | fi 75 | 76 | if [ $? -ne 0 ] 77 | then 78 | echo "Start failed." 79 | exit 1 80 | fi 81 | 82 | wait_for_milvus_running 83 | } 84 | 85 | stop() { 86 | sudo docker stop milvus-standalone 1> /dev/null 87 | 88 | if [ $? -ne 0 ] 89 | then 90 | echo "Stop failed." 91 | exit 1 92 | fi 93 | echo "Stop successfully." 94 | 95 | } 96 | 97 | delete() { 98 | res=`sudo docker ps|grep milvus-standalone|wc -l` 99 | if [ $res -eq 1 ] 100 | then 101 | echo "Please stop Milvus service before delete." 102 | exit 1 103 | fi 104 | sudo docker rm milvus-standalone 1> /dev/null 105 | if [ $? -ne 0 ] 106 | then 107 | echo "Delete failed." 108 | exit 1 109 | fi 110 | sudo rm -rf $(pwd)/volumes 111 | sudo rm -rf $(pwd)/embedEtcd.yaml 112 | echo "Delete successfully." 113 | } 114 | 115 | 116 | case $1 in 117 | start) 118 | start 119 | ;; 120 | stop) 121 | stop 122 | ;; 123 | delete) 124 | delete 125 | ;; 126 | *) 127 | echo "please use bash standalone_embed.sh start|stop|delete" 128 | ;; 129 | esac -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/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": "es2020", 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 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.css' { 2 | declare const styles: Record; 3 | export default styles; 4 | } 5 | declare module '*.module.sass' { 6 | declare const styles: Record; 7 | export default styles; 8 | } 9 | declare module '*.module.scss' { 10 | declare const styles: Record; 11 | export default styles; 12 | } 13 | declare module '*.module.less' { 14 | declare const classes: Record; 15 | export default classes; 16 | } 17 | declare module '*.module.styl' { 18 | declare const classes: Record; 19 | export default classes; 20 | } 21 | 22 | /* CSS FILES */ 23 | declare module '*.css'; 24 | declare module '*.sass'; 25 | declare module '*.scss'; 26 | declare module '*.less'; 27 | declare module '*.styl'; 28 | 29 | /* IMAGES */ 30 | declare module '*.svg' { 31 | const ref: string; 32 | export default ref; 33 | } 34 | declare module '*.bmp' { 35 | const ref: string; 36 | export default ref; 37 | } 38 | declare module '*.gif' { 39 | const ref: string; 40 | export default ref; 41 | } 42 | declare module '*.jpg' { 43 | const ref: string; 44 | export default ref; 45 | } 46 | declare module '*.jpeg' { 47 | const ref: string; 48 | export default ref; 49 | } 50 | declare module '*.png' { 51 | const ref: string; 52 | export default ref; 53 | } 54 | declare module '*.avif' { 55 | const ref: string; 56 | export default ref; 57 | } 58 | declare module '*.webp' { 59 | const ref: string; 60 | export default ref; 61 | } 62 | declare module '*.css?inline' { 63 | import type { CSSResultGroup } from 'lit'; 64 | const content: CSSResultGroup; 65 | export default content; 66 | } 67 | 68 | declare module 'csstype' { 69 | interface Properties { 70 | [index: `--${string}`]: any; 71 | } 72 | } -------------------------------------------------------------------------------- /projects/spring-ai/playground-flight-booking/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); -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | /target/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | 40 | ### Kotlin ### 41 | .kotlin 42 | 43 | # The following files are automatically generated/updated 44 | node_modules/ 45 | frontend/generated/ 46 | .npmrc 47 | vite.generated.ts 48 | 49 | 50 | # Eclipse store 51 | storage/ 52 | 53 | !**/src/test/resources/volumes 54 | 55 | src/main/bundles 56 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.21" 3 | kotlin("plugin.spring") version "2.1.21" 4 | id("org.springframework.boot") version "3.5.0" 5 | id("io.spring.dependency-management") version "1.1.7" 6 | } 7 | 8 | group = "io.github.devcrocod.example" 9 | version = "0.0.1-SNAPSHOT" 10 | 11 | java { 12 | toolchain { 13 | languageVersion = JavaLanguageVersion.of(17) 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | val springAiVersion = "1.0.0" 22 | 23 | dependencyManagement { 24 | imports { 25 | mavenBom("org.springframework.ai:spring-ai-bom:$springAiVersion") 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation("org.springframework.boot:spring-boot-starter-web") 31 | implementation("org.springframework.boot:spring-boot-starter-actuator") 32 | implementation("org.springframework.ai:spring-ai-starter-model-openai") 33 | implementation("org.springframework.ai:spring-ai-advisors-vector-store") 34 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin") 35 | 36 | testImplementation("org.springframework.boot:spring-boot-starter-test") 37 | } 38 | 39 | kotlin { 40 | compilerOptions { 41 | freeCompilerArgs.addAll("-Xjsr305=strict") 42 | } 43 | } 44 | 45 | tasks.withType { 46 | useJUnitPlatform() 47 | } 48 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/spring-ai-examples/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "spring-ai-examples" 2 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/Application.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication 7 | class Application 8 | 9 | fun main(args: Array) { 10 | runApplication(*args) 11 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/Config.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.context.annotation.Bean 5 | import org.springframework.context.annotation.Configuration 6 | 7 | @Configuration 8 | class Config { 9 | 10 | @Bean 11 | fun chatClient(builder: ChatClient.Builder): ChatClient = builder.build() 12 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/helloworld/SimpleAiController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.helloworld 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.RequestParam 6 | import org.springframework.web.bind.annotation.RestController 7 | 8 | @RestController 9 | class SimpleAiController(private val chatClient: ChatClient) { 10 | 11 | @GetMapping("/ai/simple") 12 | fun generation( 13 | @RequestParam(value = "message", defaultValue = "Tell me a joke") message: String 14 | ): Map { 15 | return mapOf("generation" to chatClient.prompt().user(message).call().content()!!) 16 | } 17 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/output/ActorsFilms.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.output 2 | 3 | 4 | data class ActorsFilms constructor( 5 | val actor: String, 6 | val movies: List // works only with jackson annotation 7 | ) { 8 | override fun toString(): String = "ActorsFilms{actor='$actor', movies=$movies}" 9 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/output/OutputParserController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.output 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.ai.chat.client.entity 5 | import org.springframework.ai.chat.prompt.PromptTemplate 6 | import org.springframework.web.bind.annotation.GetMapping 7 | import org.springframework.web.bind.annotation.RequestParam 8 | import org.springframework.web.bind.annotation.RestController 9 | 10 | @RestController 11 | class OutputParserController(private val chatClient: ChatClient) { 12 | @GetMapping("ai/output") 13 | fun generate(@RequestParam(value = "actor", defaultValue = "Jeff Bridges") actor: String): ActorsFilms { 14 | val userMessage = """ 15 | Generate the filmography for the actor $actor. 16 | """.trimIndent() 17 | 18 | val promptTemplate = PromptTemplate.builder() 19 | .template(userMessage) 20 | .variables(mapOf("actor" to actor)) 21 | .build() 22 | val prompt = promptTemplate.create() 23 | val generation = chatClient.prompt(prompt).call().entity() 24 | return generation 25 | } 26 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/prompttemplate/PromptTemplateController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.prompttemplate 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.ai.chat.messages.AssistantMessage 5 | import org.springframework.ai.chat.prompt.PromptTemplate 6 | import org.springframework.beans.factory.annotation.Value 7 | import org.springframework.web.bind.annotation.RestController 8 | import org.springframework.core.io.Resource 9 | import org.springframework.web.bind.annotation.GetMapping 10 | import org.springframework.web.bind.annotation.RequestParam 11 | 12 | @RestController 13 | class PromptTemplateController(private val chatClient: ChatClient) { 14 | @Value("classpath:/prompts/joke-prompt.st") 15 | lateinit var jokeResource: Resource 16 | 17 | @GetMapping("/ai/prompt") 18 | fun completion( 19 | @RequestParam(value = "adjective", defaultValue = "funny") adjective: String, 20 | @RequestParam(value = "topic", defaultValue = "cows") topic: String 21 | ): AssistantMessage { 22 | val promptTemplate = PromptTemplate(jokeResource) 23 | val prompt = promptTemplate.create(mapOf("adjective" to adjective, "topic" to topic)) 24 | return chatClient.prompt(prompt).call().chatResponse()!!.result.output 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/rag/RagController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.rag 2 | 3 | import org.springframework.ai.chat.messages.AssistantMessage 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.RequestParam 6 | import org.springframework.web.bind.annotation.RestController 7 | 8 | @RestController 9 | class RagController(private val ragService: RagService) { 10 | 11 | @GetMapping("/ai/rag") 12 | fun generate( 13 | @RequestParam(value = "message", defaultValue = "What bike is good for city commuting?") message: String 14 | ): AssistantMessage { 15 | return ragService.retrieve(message) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/rag/RagService.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.rag 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import org.springframework.ai.chat.client.ChatClient 6 | import org.springframework.ai.chat.messages.AssistantMessage 7 | import org.springframework.ai.chat.messages.Message 8 | import org.springframework.ai.chat.messages.UserMessage 9 | import org.springframework.ai.chat.prompt.Prompt 10 | import org.springframework.ai.chat.prompt.SystemPromptTemplate 11 | import org.springframework.ai.document.Document 12 | import org.springframework.ai.embedding.EmbeddingModel 13 | import org.springframework.ai.reader.JsonReader 14 | import org.springframework.ai.vectorstore.SimpleVectorStore 15 | import org.springframework.beans.factory.annotation.Value 16 | import org.springframework.core.io.Resource 17 | import org.springframework.stereotype.Service 18 | 19 | @Service 20 | class RagService( 21 | private val chatClient: ChatClient, 22 | private val embeddingClient: EmbeddingModel 23 | ) { 24 | @Value("classpath:/data/bikes.json") 25 | private lateinit var bikesResource: Resource 26 | 27 | @Value("classpath:/prompts/system-qa.st") 28 | private lateinit var systemBikePrompt: Resource 29 | 30 | companion object { 31 | private val logger: Logger = LoggerFactory.getLogger(RagService::class.java) 32 | } 33 | 34 | fun retrieve(message: String): AssistantMessage { 35 | // Step 1 - Load JSON document as Documents 36 | logger.info("Loading JSON as Documents") 37 | val jsonReader = JsonReader(bikesResource, "name", "price", "shortDescription", "description") 38 | val documents = jsonReader.get() 39 | logger.info("JSON loaded as Documents") 40 | 41 | // Step 2 - Create embeddings and save to vector store 42 | logger.info("Creating Embeddings...") 43 | val vectorStore = SimpleVectorStore.builder(embeddingClient).build() 44 | vectorStore.add(documents) 45 | logger.info("Embeddings created.") 46 | 47 | // Step 3 retrieve related documents to query 48 | logger.info("Retrieving relevant documents") 49 | val similarDocuments = vectorStore.similaritySearch(message)!! 50 | logger.info("Found ${similarDocuments.size} relevant documents.") 51 | 52 | // Step 4 Embed documents into SystemMessage with the `system-qa.st` prompt 53 | // template 54 | val systemMessage = getSystemMessage(similarDocuments) 55 | val userMessage = UserMessage(message) 56 | 57 | // Step 5 - Ask the AI model 58 | logger.info("Asking AI model to reply to question.") 59 | val prompt = Prompt(listOf(systemMessage, userMessage)) 60 | logger.info(prompt.toString()) 61 | 62 | val chatResponse = chatClient.prompt(prompt).call().chatResponse()!! 63 | logger.info("AI responded.") 64 | 65 | logger.info(chatResponse.result.output.text) 66 | return chatResponse.result.output!! 67 | } 68 | 69 | private fun getSystemMessage(similarDocuments: List): Message { 70 | val documents = similarDocuments.joinToString("\n") { it.text ?: "" } 71 | val systemPromptTemplate = SystemPromptTemplate(systemBikePrompt) 72 | return systemPromptTemplate.createMessage(mapOf("documents" to documents)) 73 | } 74 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/rag/config/RagConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.rag.config 2 | 3 | import io.github.devcrocod.example.rag.RagService 4 | import org.springframework.ai.chat.client.ChatClient 5 | import org.springframework.ai.embedding.EmbeddingModel 6 | import org.springframework.context.annotation.Bean 7 | import org.springframework.context.annotation.Configuration 8 | 9 | @Configuration 10 | class RagConfiguration { 11 | 12 | @Bean 13 | fun ragService(chatClient: ChatClient, embeddingClient: EmbeddingModel): RagService = 14 | RagService(chatClient, embeddingClient) 15 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/roles/RoleController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.roles 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.ai.chat.messages.AssistantMessage 5 | import org.springframework.ai.chat.messages.UserMessage 6 | import org.springframework.ai.chat.prompt.Prompt 7 | import org.springframework.ai.chat.prompt.SystemPromptTemplate 8 | import org.springframework.beans.factory.annotation.Value 9 | import org.springframework.core.io.Resource 10 | import org.springframework.web.bind.annotation.GetMapping 11 | import org.springframework.web.bind.annotation.RequestParam 12 | import org.springframework.web.bind.annotation.RestController 13 | 14 | //@RestController 15 | //public class RoleController { 16 | // 17 | // private final ChatClient chatClient; 18 | // 19 | // @Value("classpath:/prompts/system-message.st") 20 | // private Resource systemResource; 21 | // 22 | // @Autowired 23 | // public RoleController(ChatClient chatClient) { 24 | // this.chatClient = chatClient; 25 | // } 26 | // 27 | // @GetMapping("/ai/roles") 28 | // public AssistantMessage generate(@RequestParam(value = "message", 29 | // defaultValue = "Tell me about three famous pirates from the Golden Age of Piracy and why they did. Write at least a sentence for each pirate.") String message, 30 | // @RequestParam(value = "name", defaultValue = "Bob") String name, 31 | // @RequestParam(value = "voice", defaultValue = "pirate") String voice) { 32 | // UserMessage userMessage = new UserMessage(message); 33 | // SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource); 34 | // Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice)); 35 | // Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); 36 | // return chatClient.call(prompt).getResult().getOutput(); 37 | // } 38 | // 39 | //} 40 | 41 | @RestController 42 | class RoleController(private val chatClient: ChatClient) { 43 | 44 | @Value("classpath:/prompts/system-message.st") 45 | private lateinit var systemResource: Resource 46 | 47 | @GetMapping("/ai/roles") 48 | fun generate( 49 | @RequestParam( 50 | value = "message", 51 | defaultValue = "Tell me about three famous pirates from the Golden Age of Piracy and why they did. Write at least a sentence for each pirate." 52 | ) message: String, 53 | @RequestParam(value = "name", defaultValue = "Bob") name: String, 54 | @RequestParam(value = "voice", defaultValue = "pirate") voice: String 55 | ): AssistantMessage { 56 | val userMessage = UserMessage(message) 57 | val systemPromptTemplate = SystemPromptTemplate(systemResource) 58 | val systemMessage = systemPromptTemplate.createMessage(mapOf("name" to name, "voice" to voice)) 59 | val prompt = Prompt(listOf(userMessage, systemMessage)) 60 | return chatClient.prompt(prompt).call().chatResponse()!!.result.output 61 | } 62 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/stuff/Completion.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.stuff 2 | 3 | 4 | data class Completion constructor( 5 | val completion: String? 6 | ) // exception with null -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/kotlin/io/github/devcrocod/example/stuff/StuffController.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.stuff 2 | 3 | import org.springframework.ai.chat.client.ChatClient 4 | import org.springframework.ai.chat.prompt.PromptTemplate 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.core.io.Resource 7 | import org.springframework.web.bind.annotation.GetMapping 8 | import org.springframework.web.bind.annotation.RequestParam 9 | import org.springframework.web.bind.annotation.RestController 10 | 11 | @RestController 12 | class StuffController(private val chatClient: ChatClient) { 13 | 14 | @Value("classpath:/docs/wikipedia-curling.md") 15 | private lateinit var docsToStuffResource: Resource 16 | 17 | @Value("classpath:/prompts/qa-prompt.st") 18 | private lateinit var qaPromptResource: Resource 19 | 20 | @GetMapping("/ai/stuff") 21 | fun completion( 22 | @RequestParam( 23 | value = "message", 24 | defaultValue = "Which athletes won the mixed doubles gold medal in curling at the 2022 Winter Olympics?" 25 | ) message: String, 26 | @RequestParam(value = "stuffit", defaultValue = "false") stuffit: Boolean 27 | ): Completion { 28 | val promptTemplate = PromptTemplate(qaPromptResource) 29 | val map = mutableMapOf("question" to message) 30 | if (stuffit) { 31 | map["context"] = docsToStuffResource 32 | } else { 33 | map["context"] = "" 34 | } 35 | val prompt = promptTemplate.create(map) 36 | return Completion(chatClient.prompt(prompt).call().content()) 37 | // return chatClient.prompt(prompt).call().entity(Completion::class.java) 38 | } 39 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=spring-ai-examples 2 | 3 | spring.ai.openai.api-key=${OPENAI_API_KEY} 4 | spring.ai.openai.chat.options.model=gpt-4o-mini 5 | spring.ai.openai.embedding.options.model=text-embedding-ada-002 6 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/resources/prompts/joke-prompt.st: -------------------------------------------------------------------------------- 1 | Tell me a {adjective} joke about {topic} -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/resources/prompts/qa-prompt.st: -------------------------------------------------------------------------------- 1 | Use the following pieces of context to answer the question at the end. 2 | If you don't know the answer, just say that you don't know, don't try to make up an answer. 3 | 4 | {context} 5 | 6 | Question: {question} 7 | Helpful Answer: -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/resources/prompts/system-message.st: -------------------------------------------------------------------------------- 1 | You are a helpful AI assistant. 2 | You are an AI assistant that helps people find information. 3 | Your name is {name} 4 | You should reply to the user's request with your name and also in the style of a {voice}. -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-examples/src/main/resources/prompts/system-qa.st: -------------------------------------------------------------------------------- 1 | You're assisting with questions about products in a bicycle catalog. 2 | Use the information from the DOCUMENTS section to provide accurate answers. 3 | If the answer involves referring to the price or the dimension of the bicycle, include the bicycle name in the response. 4 | If unsure, simply state that you don't know. 5 | 6 | DOCUMENTS: 7 | {documents} -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | /target/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | 40 | ### Kotlin ### 41 | .kotlin 42 | 43 | # The following files are automatically generated/updated 44 | node_modules/ 45 | frontend/generated/ 46 | .npmrc 47 | vite.generated.ts 48 | 49 | 50 | # Eclipse store 51 | storage/ 52 | 53 | !**/src/test/resources/volumes 54 | 55 | src/main/bundles 56 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.21" 3 | kotlin("plugin.spring") version "2.1.21" 4 | id("org.springframework.boot") version "3.5.0" 5 | id("io.spring.dependency-management") version "1.1.7" 6 | application 7 | } 8 | 9 | application { 10 | mainClass.set("MainKt") 11 | } 12 | 13 | group = "io.github.devcrocod.example" 14 | version = "0.1.0" 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation("org.springframework.boot:spring-boot-starter-web") 22 | implementation("org.springframework.ai:spring-ai-mcp-server-spring-boot-starter:1.0.0-M6") 23 | 24 | } 25 | 26 | tasks.test { 27 | useJUnitPlatform() 28 | } 29 | 30 | kotlin { 31 | jvmToolchain(21) 32 | } -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/spring-ai-mcp-server-example/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "spring-ai-mcp-server-example" 2 | -------------------------------------------------------------------------------- /projects/spring-ai/spring-ai-mcp-server-example/src/main/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | package io.github.devcrocod.example.mcpserver 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.modelcontextprotocol.server.McpServer 5 | import io.modelcontextprotocol.server.McpServerFeatures 6 | import io.modelcontextprotocol.server.McpSyncServer 7 | import io.modelcontextprotocol.server.transport.HttpServletSseServerTransport 8 | import io.modelcontextprotocol.server.transport.StdioServerTransport 9 | import io.modelcontextprotocol.spec.McpSchema 10 | 11 | 12 | fun main(args: Array) { 13 | val command = args.firstOrNull() ?: "--sse-server" 14 | val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 15 | when (command) { 16 | "--stdio" -> `run mcp server using stdio`() 17 | "--sse-server" -> `run sse mcp server with plain configuration`(port) 18 | else -> { 19 | System.err.println("Unknown command: $command") 20 | } 21 | } 22 | } 23 | 24 | fun McpSyncServer.configureServer(): McpSyncServer { 25 | val kotlinPrompt = McpServerFeatures.SyncPromptRegistration( 26 | McpSchema.Prompt("Kotlin Developer", "Develop small kotlin applications", null) 27 | ) { request -> 28 | McpSchema.GetPromptResult( 29 | "Kotlin project development prompt", 30 | listOf( 31 | McpSchema.PromptMessage( 32 | McpSchema.Role.ASSISTANT, 33 | McpSchema.TextContent("I will help you develop a Kotlin project.") 34 | ) 35 | ) 36 | ) 37 | } 38 | 39 | val calculatorTool = McpServerFeatures.SyncToolRegistration( 40 | McpSchema.Tool("testTool", "A test tool", """{"type":"string"}""") 41 | ) { _ -> 42 | McpSchema.CallToolResult(listOf(McpSchema.TextContent("Hello world!")), false) 43 | } 44 | 45 | val searchResource = McpServerFeatures.SyncResourceRegistration( 46 | McpSchema.Resource("https://search.com/", "Web Search", "Web search engine", "text/html", null) 47 | ) { request -> 48 | McpSchema.ReadResourceResult( 49 | listOf(McpSchema.TextResourceContents("Search results", request.uri, "text/html")) 50 | ) 51 | } 52 | 53 | this.addPrompt(kotlinPrompt) 54 | this.addTool(calculatorTool) 55 | this.addResource(searchResource) 56 | 57 | return this 58 | } 59 | 60 | fun `run mcp server using stdio`() { 61 | val server = McpServer.sync(StdioServerTransport()) 62 | .serverInfo("mcp test server", "0.1.0") 63 | .capabilities( 64 | McpSchema.ServerCapabilities.builder() 65 | .prompts(true) 66 | .resources(true, true) 67 | .tools(true) 68 | .build() 69 | ) 70 | .build() 71 | 72 | server.configureServer() 73 | 74 | println("Server running on stdio") 75 | } 76 | 77 | fun `run sse mcp server with plain configuration`(port: Int) { 78 | println("Starting SSE server on port $port.") 79 | println("Use inspector to connect to the http://localhost:$port/sse") 80 | 81 | val transport = HttpServletSseServerTransport(ObjectMapper(), "/message", "/sse") 82 | val server = McpServer.sync(transport) 83 | .serverInfo("mcp test server", "0.1.0") 84 | .capabilities( 85 | McpSchema.ServerCapabilities.builder() 86 | .prompts(true) 87 | .resources(true, true) 88 | .tools(true) 89 | .build() 90 | ) 91 | .build() 92 | 93 | server.configureServer() 94 | 95 | println("SSE server is running on http://localhost:$port/sse") 96 | } 97 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Kotlin ### 40 | .kotlin 41 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.21" 3 | kotlin("plugin.spring") version "2.1.21" 4 | id("org.springframework.boot") version "3.5.0" 5 | id("io.spring.dependency-management") version "1.1.7" 6 | } 7 | 8 | group = "com.example" 9 | version = "0.0.1-SNAPSHOT" 10 | 11 | java { 12 | toolchain { 13 | languageVersion = JavaLanguageVersion.of(17) 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | val springAiVersion = "1.0.0" 22 | 23 | dependencyManagement { 24 | imports { 25 | mavenBom("org.springframework.ai:spring-ai-bom:$springAiVersion") 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation("org.springframework.boot:spring-boot-starter-web") 31 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin") 32 | implementation("org.jetbrains.kotlin:kotlin-reflect") 33 | implementation("org.springframework.ai:spring-ai-starter-model-openai") 34 | implementation("org.springframework.ai:spring-ai-starter-vector-store-qdrant") 35 | implementation("org.springframework.ai:spring-ai-advisors-vector-store") 36 | 37 | testImplementation("org.springframework.boot:spring-boot-starter-test") 38 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") 39 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 40 | } 41 | 42 | kotlin { 43 | compilerOptions { 44 | freeCompilerArgs.addAll("-Xjsr305=strict") 45 | } 46 | } 47 | 48 | tasks.withType { 49 | useJUnitPlatform() 50 | } 51 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/springAI-demo/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/image/qdrant_collections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/springAI-demo/image/qdrant_collections.png -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/image/qdrant_loaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/springAI-demo/image/qdrant_loaded.png -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/image/qdrant_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/springAI-demo/image/qdrant_start.png -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/image/start_spring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/springAI-demo/image/start_spring.png -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/image/welcome_qdrant_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/Kotlin-AI-Examples/f2c54e09a989951409ed4fcbc90f9de4dfb807c2/projects/spring-ai/springAI-demo/image/welcome_qdrant_dashboard.png -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "springAI-demo" 2 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/src/main/kotlin/com/example/springai/demo/KotlinSTDController.kt: -------------------------------------------------------------------------------- 1 | package com.example.springai.demo 2 | 3 | import org.slf4j.LoggerFactory 4 | import org.springframework.ai.chat.client.ChatClient 5 | import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor 6 | import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor 7 | import org.springframework.ai.chat.prompt.Prompt 8 | import org.springframework.ai.chat.prompt.PromptTemplate 9 | import org.springframework.ai.document.Document 10 | import org.springframework.ai.vectorstore.SearchRequest 11 | import org.springframework.ai.vectorstore.VectorStore 12 | import org.springframework.web.bind.annotation.GetMapping 13 | import org.springframework.web.bind.annotation.PostMapping 14 | import org.springframework.web.bind.annotation.RequestBody 15 | import org.springframework.web.bind.annotation.RequestMapping 16 | import org.springframework.web.bind.annotation.RequestParam 17 | import org.springframework.web.bind.annotation.RestController 18 | import org.springframework.web.client.RestTemplate 19 | import kotlin.uuid.ExperimentalUuidApi 20 | import kotlin.uuid.Uuid 21 | 22 | // Data class representing the chat request payload. 23 | data class ChatRequest(val query: String, val topK: Int = 3) 24 | 25 | @RestController 26 | @RequestMapping("/kotlin") 27 | class KotlinSTDController( 28 | private val chatClientBuilder: ChatClient.Builder, 29 | private val restTemplate: RestTemplate, 30 | private val vectorStore: VectorStore, 31 | ) { 32 | 33 | // Logger for debugging and tracing actions. 34 | private val logger = LoggerFactory.getLogger(this::class.java) 35 | 36 | // Build the chat client with a simple logging advisor. 37 | private val chatClient = chatClientBuilder.defaultAdvisors(SimpleLoggerAdvisor()).build() 38 | 39 | @OptIn(ExperimentalUuidApi::class) 40 | @PostMapping("/load-docs") 41 | fun load() { 42 | // List of topics to load from the Kotlin website documentation. 43 | val kotlinStdTopics = listOf( 44 | "collections-overview", "constructing-collections", "iterators", "ranges", "sequences", 45 | "collection-operations", "collection-transformations", "collection-filtering", "collection-plus-minus", 46 | "collection-grouping", "collection-parts", "collection-elements", "collection-ordering", 47 | "collection-aggregate", "collection-write", "list-operations", "set-operations", 48 | "map-operations", "read-standard-input", "opt-in-requirements", "scope-functions", "time-measurement", 49 | ) 50 | // Base URL for the documents. 51 | val url = "https://raw.githubusercontent.com/JetBrains/kotlin-web-site/refs/heads/master/docs/topics/" 52 | // Retrieve each document from the URL and add it to the vector store. 53 | kotlinStdTopics.forEach { topic -> 54 | val data = restTemplate.getForObject("$url$topic.md", String::class.java) 55 | data?.let { it -> 56 | val doc = Document.builder() 57 | // Build a Document with a random UUID, the text content, and metadata. 58 | .id(Uuid.random().toString()) 59 | .text(it) 60 | .metadata("topic", topic) 61 | .build() 62 | vectorStore.add(listOf(doc)) 63 | logger.info("Document $topic loaded.") 64 | } ?: logger.warn("Failed to load document for topic: $topic") 65 | } 66 | } 67 | 68 | @GetMapping("docs") 69 | fun query( 70 | @RequestParam query: String = "operations, filtering, and transformations", 71 | @RequestParam topK: Int = 2 72 | ): List? { 73 | val searchRequest = SearchRequest.builder() 74 | .query(query) 75 | .topK(topK) 76 | .build() 77 | val results = vectorStore.similaritySearch(searchRequest) 78 | logger.info("Found ${results?.size ?: 0} documents for query: '$query'") 79 | return results 80 | } 81 | 82 | @PostMapping("/chat/ask") 83 | fun chatAsk(@RequestBody request: ChatRequest): String? { 84 | // Define the prompt template with placeholders {query} and {target}. 85 | val promptTemplate = PromptTemplate( 86 | """ 87 | {query}. 88 | Please provide a concise answer based on the "Kotlin standard library" documentation. 89 | """.trimIndent() 90 | ) 91 | 92 | // Create the prompt by substituting placeholders with actual values. 93 | val prompt: Prompt = 94 | promptTemplate.create(mapOf("query" to request.query)) 95 | 96 | // Configure the retrieval advisor to augment the query with relevant documents. 97 | val retrievalAdvisor = QuestionAnswerAdvisor.builder(vectorStore) 98 | .searchRequest( 99 | SearchRequest.builder() 100 | .similarityThreshold(0.7) 101 | .topK(request.topK) 102 | .build() 103 | ) 104 | .promptTemplate(promptTemplate) 105 | .build() 106 | 107 | // Send the prompt to the LLM with the retrieval advisor and get the response. 108 | val response = chatClient.prompt(prompt) 109 | .advisors(retrievalAdvisor) 110 | .call() 111 | .content() 112 | logger.info("Chat response generated for query: '${request.query}'") 113 | return response 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/src/main/kotlin/com/example/springai/demo/SpringAiDemoApplication.kt: -------------------------------------------------------------------------------- 1 | package com.example.springai.demo 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | import org.springframework.context.annotation.Bean 6 | import org.springframework.web.client.RestTemplate 7 | 8 | 9 | @SpringBootApplication 10 | class SpringAiDemoApplication { 11 | 12 | @Bean 13 | fun restTemplate(): RestTemplate = RestTemplate() 14 | } 15 | 16 | fun main(args: Array) { 17 | runApplication(*args) 18 | } 19 | -------------------------------------------------------------------------------- /projects/spring-ai/springAI-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=springAI-demo 2 | # OpenAI 3 | spring.ai.openai.api-key=${OPENAI_API_KEY} 4 | spring.ai.openai.chat.options.model=gpt-4o-mini 5 | spring.ai.openai.embedding.options.model=text-embedding-ada-002 6 | # Qdrant 7 | spring.ai.vectorstore.qdrant.host=localhost 8 | spring.ai.vectorstore.qdrant.port=6334 9 | spring.ai.vectorstore.qdrant.collection-name=kotlinDocs 10 | spring.ai.vectorstore.qdrant.initialize-schema=true --------------------------------------------------------------------------------