{"id":25939062,"url":"https://github.com/k8s-spring-ai-workshop/talent-arena","last_synced_at":"2026-05-04T17:34:53.004Z","repository":{"id":276082404,"uuid":"927857856","full_name":"k8s-spring-ai-workshop/talent-arena","owner":"k8s-spring-ai-workshop","description":"Workshop for Talent Arena 2025","archived":false,"fork":false,"pushed_at":"2025-03-05T10:05:19.000Z","size":2930,"stargazers_count":7,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-01T17:43:00.815Z","etag":null,"topics":["java","spring","spring-ai","spring-ai-ollama","spring-ai-openai","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/k8s-spring-ai-workshop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-05T16:55:45.000Z","updated_at":"2025-05-21T10:18:42.000Z","dependencies_parsed_at":"2025-02-23T12:33:41.818Z","dependency_job_id":null,"html_url":"https://github.com/k8s-spring-ai-workshop/talent-arena","commit_stats":null,"previous_names":["k8s-spring-ai-workshop/talent-arena"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/k8s-spring-ai-workshop/talent-arena","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k8s-spring-ai-workshop%2Ftalent-arena","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k8s-spring-ai-workshop%2Ftalent-arena/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k8s-spring-ai-workshop%2Ftalent-arena/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k8s-spring-ai-workshop%2Ftalent-arena/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k8s-spring-ai-workshop","download_url":"https://codeload.github.com/k8s-spring-ai-workshop/talent-arena/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k8s-spring-ai-workshop%2Ftalent-arena/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32618003,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-04T10:08:07.713Z","status":"ssl_error","status_checked_at":"2026-05-04T10:08:02.005Z","response_time":58,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","spring","spring-ai","spring-ai-ollama","spring-ai-openai","spring-boot"],"created_at":"2025-03-04T04:15:36.525Z","updated_at":"2026-05-04T17:34:52.986Z","avatar_url":"https://github.com/k8s-spring-ai-workshop.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cimg src=\"images/img.png\"  alt=\"Talent Arena\" height=\"173\"/\u003e\n\u003cimg src=\"images/bcn-jug.png\" alt=\"Barcelona JUG\" height=\"173\"/\u003e\n\n# Workshop: Enhancing Java Applications with Spring AI\n\n## Project Requirements\n- [Java](https://sdkman.io/) 21+\n- [Maven](https://maven.apache.org/download.cgi) 3.6+\n- [Docker](https://www.docker.com/)\n- [curl](https://curl.se/) or any other REST client ([Postman](https://www.postman.com/), [HTTPie](https://httpie.io/), [VS code Rest client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client), [Jetbrains Http Client](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html))\n- An [OpenAI API](https://platform.openai.com/docs/overview) key or [Ollama](https://ollama.com/) installed locally\n\n\n## Exercise 1—chatbot\n\n1. Create your spring boot application using the [Spring Initializr](https://start.spring.io/#!type=maven-project\u0026language=java\u0026platformVersion=3.4.3\u0026packaging=jar\u0026jvmVersion=21\u0026groupId=workshop.springai\u0026artifactId=talent-arena\u0026name=talent-arena\u0026description=Demo%20project%20for%20Spring%20Boot\u0026packageName=workshop.springai\u0026dependencies=web,spring-ai-openai,actuator,lombok).\nThis link will create a project with the following dependencies:\n  * Spring Web\n  * Spring AI OpenAI\n  * Spring Boot Actuator\n  * Lombok\n\nAnd the following configuration:\n  * Java 21\n  * Spring Boot 3.4.3\n  * Spring AI 1.0.0-M6\n  * Maven\n2. Click on the `Generate` button to download the project.\n3. Unzip the project and open it in your favorite IDE.\n4. Create a `.env.local` file in the root of the project with the following content:\n```properties\nAPI_KEY='YOUR_OPEN_AI_KEY'\n```\n5. Create an [OpenAI key](https://platform.openai.com/settings/organization/api-keys) in your account and add it to the `.env.local` file.\n6. Change the `application.properties` file to use the OpenAI API key and the GPT-4o-mini model:\n```properties\nspring.application.name=talent-arena\n\n#import variables from .env.local file as properties\nspring.config.import=file:.env.local[.properties]\n\n# Properties for the OpenAI API\nspring.ai.openai.api-key=${API_KEY}\nspring.ai.openai.chat.options.model=gpt-4o-mini\n\nlogging.level.org.springframework.ai.chat.client.advisor=INFO\n```\n7. Create a `ChatClientConfig` class with the following content:\n```java\npackage workshop.springai.config;\n\nimport org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class ChatClientConfig {\n    \n    @Bean(name = \"chatClient\")\n    public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {\n        return chatClientBuilder\n                .defaultAdvisors(\n                        new SimpleLoggerAdvisor())\n                .build();\n    }\n    \n}\n```\n8. Create a `ChatController` class with the following content:\n```java\npackage workshop.springai.chat;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\n@Slf4j\n@RestController\npublic class ChatController {\n\n    private final ChatClient chatClient;\n\n    public ChatController(@Qualifier(\"chatClient\") ChatClient chatClient) {\n        this.chatClient = chatClient;\n    }\n\n    @GetMapping(value = \"/chat\", produces = \"text/plain\", consumes = \"text/plain\")\n    String chat(@RequestBody String message) {\n        log.info(\"Chat with message: {}\", message);\n\n        return chatClient.prompt()\n                .user(message)\n                .call()\n                .content();\n    }\n\n}\n```\n9. Build the project using Maven:\n```shell\nmvn clean install\n```\n10. Run the project using Maven:\n```shell\nmvn spring-boot:run\n```\n11. Open a new terminal and test the chatbot using curl:\n```shell\ncurl -X GET http://localhost:8080/chat -H \"Content-Type: text/plain\" -d \"What is the capital of Brazil ?\"\n```\n12. Open a new terminal and test the chatbot using curl, asking about the previous answer:\n```shell\ncurl -X GET http://localhost:8080/chat -H \"Content-Type: text/plain\" -d \"Could you repeat the previous answer ?\"\n```\n\u003cdetails\u003e\n\u003csummary\u003eOptional - running the Application with Ollama and DeepSeek\u003c/summary\u003e\n\n13. Install [Ollama](https://ollama.com/download) in your local machine.\n\n14. Add the following properties inside the `application.properties` file to use Ollama with the DeepSeek model:\n```properties\n# Properties for the Ollama API\nspring.ai.ollama.init.pull-model-strategy=always\nspring.ai.ollama.chat.options.model=deepseek-r1:1.5b\n```\n15. Change the `pom.xml` to add the new dependency `spring-ai-ollama-spring-boot-starter` in a \nspecific maven profile and the `spring-ai-openai-spring-boot-starter` in a default maven profile.\n\nThe final version of the `pom.xml` should look like this:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n    \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n    \u003cparent\u003e\n        \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n        \u003cartifactId\u003espring-boot-starter-parent\u003c/artifactId\u003e\n        \u003cversion\u003e3.4.3\u003c/version\u003e\n        \u003crelativePath/\u003e \u003c!-- lookup parent from repository --\u003e\n    \u003c/parent\u003e\n    \u003cgroupId\u003eworkshop.springai\u003c/groupId\u003e\n    \u003cartifactId\u003etalent-arena\u003c/artifactId\u003e\n    \u003cversion\u003e0.0.1-SNAPSHOT\u003c/version\u003e\n    \u003cname\u003etalent-arena\u003c/name\u003e\n    \u003cdescription\u003eDemo project for Spring Boot\u003c/description\u003e\n    \u003curl/\u003e\n    \u003clicenses\u003e\n        \u003clicense/\u003e\n    \u003c/licenses\u003e\n    \u003cdevelopers\u003e\n        \u003cdeveloper/\u003e\n    \u003c/developers\u003e\n    \u003cscm\u003e\n        \u003cconnection/\u003e\n        \u003cdeveloperConnection/\u003e\n        \u003ctag/\u003e\n        \u003curl/\u003e\n    \u003c/scm\u003e\n    \u003cproperties\u003e\n        \u003cjava.version\u003e21\u003c/java.version\u003e\n        \u003cspring-ai.version\u003e1.0.0-M6\u003c/spring-ai.version\u003e\n    \u003c/properties\u003e\n    \u003cdependencies\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-actuator\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-web\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n            \u003coptional\u003etrue\u003c/optional\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-test\u003c/artifactId\u003e\n            \u003cscope\u003etest\u003c/scope\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n    \u003cdependencyManagement\u003e\n        \u003cdependencies\u003e\n            \u003cdependency\u003e\n                \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                \u003cartifactId\u003espring-ai-bom\u003c/artifactId\u003e\n                \u003cversion\u003e${spring-ai.version}\u003c/version\u003e\n                \u003ctype\u003epom\u003c/type\u003e\n                \u003cscope\u003eimport\u003c/scope\u003e\n            \u003c/dependency\u003e\n        \u003c/dependencies\u003e\n    \u003c/dependencyManagement\u003e\n\n    \u003cbuild\u003e\n        \u003cplugins\u003e\n            \u003cplugin\u003e\n                \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n                \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n                \u003cconfiguration\u003e\n                    \u003cannotationProcessorPaths\u003e\n                        \u003cpath\u003e\n                            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n                            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n                        \u003c/path\u003e\n                    \u003c/annotationProcessorPaths\u003e\n                \u003c/configuration\u003e\n            \u003c/plugin\u003e\n            \u003cplugin\u003e\n                \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n                \u003cartifactId\u003espring-boot-maven-plugin\u003c/artifactId\u003e\n                \u003cconfiguration\u003e\n                    \u003cexcludes\u003e\n                        \u003cexclude\u003e\n                            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n                            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n                        \u003c/exclude\u003e\n                    \u003c/excludes\u003e\n                \u003c/configuration\u003e\n            \u003c/plugin\u003e\n        \u003c/plugins\u003e\n    \u003c/build\u003e\n\n    \u003cprofiles\u003e\n        \u003cprofile\u003e\n            \u003cid\u003eopenai\u003c/id\u003e\n            \u003cactivation\u003e\n                \u003cactiveByDefault\u003etrue\u003c/activeByDefault\u003e\n            \u003c/activation\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                    \u003cartifactId\u003espring-ai-openai-spring-boot-starter\u003c/artifactId\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/profile\u003e\n\n        \u003cprofile\u003e\n            \u003cid\u003eollama\u003c/id\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                    \u003cartifactId\u003espring-ai-ollama-spring-boot-starter\u003c/artifactId\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/profile\u003e\n\n    \u003c/profiles\u003e\n\n\u003c/project\u003e\n\n```\n16. Build the project using Maven with the Ollama profile:\n```shell\nmvn clean install -Pollama\n```\n17. Run the project using Maven with the Ollama profile:\n```shell\nmvn spring-boot:run -Pollama\n```\n18. Open a new terminal and test the chatbot using curl:\n```shell\ncurl -X GET http://localhost:8080/chat -H \"Content-Type: text/plain\" -d \"What is the capital of Brazil ?\"\n```\n19. Open a new terminal and test the chatbot using curl, asking about the previous answer:\n```shell\ncurl -X GET http://localhost:8080/chat -H \"Content-Type: text/plain\" -d \"Could you repeat the previous answer ?\"\n```\n\u003c/details\u003e\n\n## Exercise 2—chatbot with memory\n\n1. Open the `ChatClientConfig` class and add the following bean methods:\n```java\n    @Bean\n    public ChatMemory chatMemory() {\n        return new InMemoryChatMemory();\n    }\n\n    @Bean(name = \"chatClientWithMemory\")\n    public ChatClient chatClientWithMemory(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {\n        return chatClientBuilder\n                .defaultAdvisors(\n                        new MessageChatMemoryAdvisor(chatMemory),\n                        new SimpleLoggerAdvisor())\n                .build();\n    }\n```\n2. Create a `ChatMemoryController` class with the following content:\n```java\npackage workshop.springai.chat;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\n@Slf4j\n@RestController\npublic class ChatMemoryController {\n\n    private final ChatClient chatClient;\n\n    public ChatMemoryController(@Qualifier(\"chatClientWithMemory\") ChatClient chatClient) {\n        this.chatClient = chatClient;\n    }\n\n    @GetMapping(value = \"/chat/memory\", produces = \"text/plain\", consumes = \"text/plain\")\n    String chat(@RequestBody String message) {\n        log.info(\"Chat Memory with message: {}\", message);\n\n        return chatClient.prompt()\n                .user(message)\n                .call()\n                .content();\n    }\n\n}\n```\n3. Build the project using Maven:\n```shell\nmvn clean install\n```\n4. Run the project using Maven:\n```shell\nmvn spring-boot:run\n```\n5. Open a new terminal and test the chatbot using curl:\n```shell\ncurl -X GET http://localhost:8080/chat/memory -H \"Content-Type: text/plain\" -d \"What is the capital of Brazil ?\"\n```\n6. Open a new terminal and test the chatbot using curl, asking about the previous answer:\n```shell\ncurl -X GET http://localhost:8080/chat/memory -H \"Content-Type: text/plain\" -d \"Could you repeat the previous answer ?\"\n```\n\u003cdetails\u003e\n\u003csummary\u003eOptional - running the Application with Ollama and DeepSeek\u003c/summary\u003e\n\n7. Install [Ollama](https://ollama.com/download) in your local machine.\n\n8. Add the following properties inside the `application.properties` file to use Ollama with the DeepSeek model:\n```properties\n# Properties for the Ollama API\nspring.ai.ollama.init.pull-model-strategy=always\nspring.ai.ollama.chat.options.model=deepseek-r1:1.5b\n```\n9. Change the `pom.xml` to add the new dependency `spring-ai-ollama-spring-boot-starter` in a\n    specific maven profile and the `spring-ai-openai-spring-boot-starter` in a default maven profile.\n\nThe final version of the `pom.xml` should look like this:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n    \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n    \u003cparent\u003e\n        \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n        \u003cartifactId\u003espring-boot-starter-parent\u003c/artifactId\u003e\n        \u003cversion\u003e3.4.3\u003c/version\u003e\n        \u003crelativePath/\u003e \u003c!-- lookup parent from repository --\u003e\n    \u003c/parent\u003e\n    \u003cgroupId\u003eworkshop.springai\u003c/groupId\u003e\n    \u003cartifactId\u003etalent-arena\u003c/artifactId\u003e\n    \u003cversion\u003e0.0.1-SNAPSHOT\u003c/version\u003e\n    \u003cname\u003etalent-arena\u003c/name\u003e\n    \u003cdescription\u003eDemo project for Spring Boot\u003c/description\u003e\n    \u003curl/\u003e\n    \u003clicenses\u003e\n        \u003clicense/\u003e\n    \u003c/licenses\u003e\n    \u003cdevelopers\u003e\n        \u003cdeveloper/\u003e\n    \u003c/developers\u003e\n    \u003cscm\u003e\n        \u003cconnection/\u003e\n        \u003cdeveloperConnection/\u003e\n        \u003ctag/\u003e\n        \u003curl/\u003e\n    \u003c/scm\u003e\n    \u003cproperties\u003e\n        \u003cjava.version\u003e21\u003c/java.version\u003e\n        \u003cspring-ai.version\u003e1.0.0-M6\u003c/spring-ai.version\u003e\n    \u003c/properties\u003e\n    \u003cdependencies\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-actuator\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-web\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n            \u003coptional\u003etrue\u003c/optional\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-test\u003c/artifactId\u003e\n            \u003cscope\u003etest\u003c/scope\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n    \u003cdependencyManagement\u003e\n        \u003cdependencies\u003e\n            \u003cdependency\u003e\n                \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                \u003cartifactId\u003espring-ai-bom\u003c/artifactId\u003e\n                \u003cversion\u003e${spring-ai.version}\u003c/version\u003e\n                \u003ctype\u003epom\u003c/type\u003e\n                \u003cscope\u003eimport\u003c/scope\u003e\n            \u003c/dependency\u003e\n        \u003c/dependencies\u003e\n    \u003c/dependencyManagement\u003e\n\n    \u003cbuild\u003e\n        \u003cplugins\u003e\n            \u003cplugin\u003e\n                \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n                \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n                \u003cconfiguration\u003e\n                    \u003cannotationProcessorPaths\u003e\n                        \u003cpath\u003e\n                            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n                            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n                        \u003c/path\u003e\n                    \u003c/annotationProcessorPaths\u003e\n                \u003c/configuration\u003e\n            \u003c/plugin\u003e\n            \u003cplugin\u003e\n                \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n                \u003cartifactId\u003espring-boot-maven-plugin\u003c/artifactId\u003e\n                \u003cconfiguration\u003e\n                    \u003cexcludes\u003e\n                        \u003cexclude\u003e\n                            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n                            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n                        \u003c/exclude\u003e\n                    \u003c/excludes\u003e\n                \u003c/configuration\u003e\n            \u003c/plugin\u003e\n        \u003c/plugins\u003e\n    \u003c/build\u003e\n\n    \u003cprofiles\u003e\n        \u003cprofile\u003e\n            \u003cid\u003eopenai\u003c/id\u003e\n            \u003cactivation\u003e\n                \u003cactiveByDefault\u003etrue\u003c/activeByDefault\u003e\n            \u003c/activation\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                    \u003cartifactId\u003espring-ai-openai-spring-boot-starter\u003c/artifactId\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/profile\u003e\n\n        \u003cprofile\u003e\n            \u003cid\u003eollama\u003c/id\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                    \u003cartifactId\u003espring-ai-ollama-spring-boot-starter\u003c/artifactId\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/profile\u003e\n\n    \u003c/profiles\u003e\n\n\u003c/project\u003e\n\n```\n10. Build the project using Maven with the Ollama profile:\n```shell\nmvn clean install -Pollama\n```\n11. Run the project using Maven with the Ollama profile:\n```shell\nmvn spring-boot:run -Pollama\n```\n12. Open a new terminal and test the chatbot using curl:\n```shell\ncurl -X GET http://localhost:8080/chat/memory -H \"Content-Type: text/plain\" -d \"What is the capital of Brazil ?\"\n```\n13. Open a new terminal and test the chatbot using curl, asking about the previous answer:\n```shell\ncurl -X GET http://localhost:8080/chat/memory -H \"Content-Type: text/plain\" -d \"Could you repeat the previous answer ?\"\n```\n\u003c/details\u003e\n\n## Exercise 3—chatbot with RAG\n\n1. Open the `pom.xml` and add the following dependencies:\n```xml\n\u003cdependencies\u003e    \n    \u003c!-- Spring AI RAG dependency - to process PDFs, DOCs, HTMLs --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n        \u003cartifactId\u003espring-ai-tika-document-reader\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n    \u003c!-- Spring AI RAG dependency - to work with PG Vector Database --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n        \u003cartifactId\u003espring-ai-pgvector-store-spring-boot-starter\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n    \u003c!-- Dependency to start-up the PG Vector Database using docker-compose --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n        \u003cartifactId\u003espring-boot-docker-compose\u003c/artifactId\u003e\n        \u003cscope\u003eruntime\u003c/scope\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n2. Create a `compose.yml` file in the root of the project with the following content:\n```yaml\nservices:\n  database:\n    image: 'pgvector/pgvector:pg16'\n    ports:\n      - '5432'\n    environment:\n      - 'POSTGRES_USER=myuser'\n      - 'POSTGRES_DB=mydatabase'\n      - 'POSTGRES_PASSWORD=secret'\n    labels:\n      org.springframework.boot.service-connection: postgres\n```\n3. Add the following properties inside the `application.properties` file, to create the PG Vector Schema at initialization \nand the Talent Arena websites that will be used during the Ingestion process of the RAG:\n```properties\n#PostgreSQL vector database properties\nspring.ai.vectorstore.pgvector.initialize-schema=true\n\n# Talent Arena properties\ntalent-arena.websites.workshops=https://talentarena.tech/workshops-agenda/\ntalent-arena.websites.talks=https://talentarena.tech/talks-agenda/\ntalent-arena.websites.conference=https://talentarena.tech/conference-agenda/\n\n# Spring Boot docker-compose properties\nspring.docker.compose.stop.command=down\nspring.docker.compose.skip.in-tests=false\n```\n4. Create the following Configuration properties classes in the `workshop.springai.config` package:\n```java\npackage workshop.springai.config;\n\npublic record Website(String url) {\n}\n```\n```java\npackage workshop.springai.config;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\nimport java.util.Map;\n\n@ConfigurationProperties(\"talent-arena\")\npublic record TalentArenaProperties(Map\u003cString, Website\u003e websites) {\n}\n```\n5. Add the following annotation `@EnableConfigurationProperties(TalentArenaProperties.class)` to the `TalentArenaApplication` class,\nthe final version of the class should look like this:\n```java\npackage workshop.springai;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport workshop.springai.config.TalentArenaProperties;\n\n@EnableConfigurationProperties({TalentArenaProperties.class})\n@SpringBootApplication\npublic class TalentArenaApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(TalentArenaApplication.class, args);\n    }\n\n}\n```\n\n6. Open the `ChatClientConfig` class and add the following bean methods:\n```java\n    @Bean\n    public TextSplitter textSplitter() {\n        return new TokenTextSplitter();\n    }\n\n    @Bean(name = \"chatClientWithRag\")\n    public ChatClient chatClientWithRag(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {\n        return chatClientBuilder\n                .defaultAdvisors(\n                        new QuestionAnswerAdvisor(vectorStore),\n                        new SimpleLoggerAdvisor())\n                .build();\n    }\n```\n7. Create a `IngestionService` class with the following content, to ingest the content of the Talent-Arena websites into the PG Vector Database:\n```java\npackage workshop.springai.rag;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ai.reader.tika.TikaDocumentReader;\nimport org.springframework.ai.transformer.splitter.TextSplitter;\nimport org.springframework.ai.vectorstore.VectorStore;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.stereotype.Service;\nimport workshop.springai.config.TalentArenaProperties;\n\n@Slf4j\n@Service\npublic class IngestionService implements CommandLineRunner {\n\n    private final VectorStore vectorStore;\n    private final TextSplitter textSplitter;\n    private final TalentArenaProperties talentArenaProperties;\n\n    public IngestionService(VectorStore vectorStore, TextSplitter textSplitter,\n                            TalentArenaProperties talentArenaProperties) {\n        this.vectorStore = vectorStore;\n        this.textSplitter = textSplitter;\n        this.talentArenaProperties = talentArenaProperties;\n    }\n\n    @Override\n    public void run(String... args) {\n        log.info(\"Ingesting data for Talent Arena\");\n\n        talentArenaProperties.websites().forEach((key, website) -\u003e {\n            log.info(\"Ingesting data for {} with the url {}\", key, website.url());\n            TikaDocumentReader textReader = new TikaDocumentReader(website.url());\n            var documents = textSplitter.apply(textReader.get());\n            vectorStore.accept(documents);\n        });\n\n        log.info(\"VectorStore Loaded with data!\");\n    }\n}\n\n```\n8. Create a `RagController` class with the following content:\n```java\npackage workshop.springai.rag;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\n@Slf4j\n@RestController\npublic class RagController {\n\n    private final ChatClient chatClient;\n\n    public RagController(@Qualifier(value = \"chatClientWithRag\") ChatClient chatClient) {\n        this.chatClient = chatClient;\n    }\n\n    @GetMapping(value = \"/chat/rag/talent-arena/\", produces = \"text/plain\", consumes = \"text/plain\")\n    public String chat(@RequestBody String message) {\n        log.info(\"Chat Rag with message: {}\", message);\n\n        return chatClient.prompt()\n                .system(\"\"\"\n                        You are a talent arena assistant.\n                        You will only reply queries about this event, and reply in English.\n                        Your tone should be enthusiastic and positive.\n                        in case you don't know the answer, invite the user to visit the event page https://talentarena.tech.\n                        \"\"\")\n                .user(message)\n                .call()\n                .content();\n    }\n}\n\n```\n9. Build the project using Maven:\n```shell\nmvn clean install\n```\n10. Run the project using Maven:\n```shell\nmvn spring-boot:run\n```\n11. Open a new terminal and test the chatbot using curl:\n```shell\ncurl -X GET http://localhost:8080/chat/rag/talent-arena/ -H \"Content-Type: text/plain\" -d \"What are the content related to Artificial Intelligence in Talent Arena 2025 ?\"\n```\n\u003cdetails\u003e\n\u003csummary\u003eOptional - running the Application with Ollama and DeepSeek\u003c/summary\u003e\n\n12. Install [Ollama](https://ollama.com/download) in your local machine.\n\n13. Add the following properties inside the `application.properties` file to use Ollama with the DeepSeek model:\n```properties\n# Properties for the Ollama API\nspring.ai.ollama.init.pull-model-strategy=always\nspring.ai.ollama.init.embedding.additional-models=mxbai-embed-large\nspring.ai.ollama.chat.options.model=deepseek-r1:1.5b\n```\n14. Change the `pom.xml` to add the new dependency `spring-ai-ollama-spring-boot-starter` in a\n   specific maven profile and the `spring-ai-openai-spring-boot-starter` in a default maven profile.\n\nThe final version of the `pom.xml` should look like this:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n    \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n    \u003cparent\u003e\n        \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n        \u003cartifactId\u003espring-boot-starter-parent\u003c/artifactId\u003e\n        \u003cversion\u003e3.4.3\u003c/version\u003e\n        \u003crelativePath/\u003e \u003c!-- lookup parent from repository --\u003e\n    \u003c/parent\u003e\n    \u003cgroupId\u003eworkshop.springai\u003c/groupId\u003e\n    \u003cartifactId\u003etalent-arena\u003c/artifactId\u003e\n    \u003cversion\u003e0.0.1-SNAPSHOT\u003c/version\u003e\n    \u003cname\u003etalent-arena\u003c/name\u003e\n    \u003cdescription\u003eDemo project for Spring Boot\u003c/description\u003e\n    \u003curl/\u003e\n    \u003clicenses\u003e\n        \u003clicense/\u003e\n    \u003c/licenses\u003e\n    \u003cdevelopers\u003e\n        \u003cdeveloper/\u003e\n    \u003c/developers\u003e\n    \u003cscm\u003e\n        \u003cconnection/\u003e\n        \u003cdeveloperConnection/\u003e\n        \u003ctag/\u003e\n        \u003curl/\u003e\n    \u003c/scm\u003e\n    \u003cproperties\u003e\n        \u003cjava.version\u003e21\u003c/java.version\u003e\n        \u003cspring-ai.version\u003e1.0.0-M6\u003c/spring-ai.version\u003e\n    \u003c/properties\u003e\n    \u003cdependencies\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-actuator\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-web\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n            \u003coptional\u003etrue\u003c/optional\u003e\n        \u003c/dependency\u003e\n        \u003c!-- Spring AI RAG dependency - to process PDFs, DOCs, HTMLs --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n            \u003cartifactId\u003espring-ai-tika-document-reader\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n        \u003c!-- Spring AI RAG dependency - to work with PG Vector Database --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n            \u003cartifactId\u003espring-ai-pgvector-store-spring-boot-starter\u003c/artifactId\u003e\n        \u003c/dependency\u003e\n        \u003c!-- Dependency to start-up the PG Vector Database using docker-compose --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-docker-compose\u003c/artifactId\u003e\n            \u003cscope\u003eruntime\u003c/scope\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n            \u003cartifactId\u003espring-boot-starter-test\u003c/artifactId\u003e\n            \u003cscope\u003etest\u003c/scope\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n    \u003cdependencyManagement\u003e\n        \u003cdependencies\u003e\n            \u003cdependency\u003e\n                \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                \u003cartifactId\u003espring-ai-bom\u003c/artifactId\u003e\n                \u003cversion\u003e${spring-ai.version}\u003c/version\u003e\n                \u003ctype\u003epom\u003c/type\u003e\n                \u003cscope\u003eimport\u003c/scope\u003e\n            \u003c/dependency\u003e\n        \u003c/dependencies\u003e\n    \u003c/dependencyManagement\u003e\n\n    \u003cbuild\u003e\n        \u003cplugins\u003e\n            \u003cplugin\u003e\n                \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n                \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n                \u003cconfiguration\u003e\n                    \u003cannotationProcessorPaths\u003e\n                        \u003cpath\u003e\n                            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n                            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n                        \u003c/path\u003e\n                    \u003c/annotationProcessorPaths\u003e\n                \u003c/configuration\u003e\n            \u003c/plugin\u003e\n            \u003cplugin\u003e\n                \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n                \u003cartifactId\u003espring-boot-maven-plugin\u003c/artifactId\u003e\n                \u003cconfiguration\u003e\n                    \u003cexcludes\u003e\n                        \u003cexclude\u003e\n                            \u003cgroupId\u003eorg.projectlombok\u003c/groupId\u003e\n                            \u003cartifactId\u003elombok\u003c/artifactId\u003e\n                        \u003c/exclude\u003e\n                    \u003c/excludes\u003e\n                \u003c/configuration\u003e\n            \u003c/plugin\u003e\n        \u003c/plugins\u003e\n    \u003c/build\u003e\n\n    \u003cprofiles\u003e\n        \u003cprofile\u003e\n            \u003cid\u003eopenai\u003c/id\u003e\n            \u003cactivation\u003e\n                \u003cactiveByDefault\u003etrue\u003c/activeByDefault\u003e\n            \u003c/activation\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                    \u003cartifactId\u003espring-ai-openai-spring-boot-starter\u003c/artifactId\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/profile\u003e\n\n        \u003cprofile\u003e\n            \u003cid\u003eollama\u003c/id\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n                    \u003cartifactId\u003espring-ai-ollama-spring-boot-starter\u003c/artifactId\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/profile\u003e\n\n    \u003c/profiles\u003e\n\n\u003c/project\u003e\n\n```\n15. Build the project using Maven with the Ollama profile:\n```shell\nmvn clean install -Pollama\n```\n16. Run the project using Maven with the Ollama profile:\n```shell\nmvn spring-boot:run -Pollama\n```\n17. Open a new terminal and test the chatbot using curl:\n```shell\ncurl -X GET http://localhost:8080/chat/rag/talent-arena/ -H \"Content-Type: text/plain\" -d \"What are the content related to Artificial Intelligence in Talent Arena 2025 ?\"\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eOptional - Start the PGVector database with testContainer instead of docker-compose\u003c/summary\u003e\n\n18. Open the `pom.xml` and add the following dependencies:\n```xml\n\u003cdependencies\u003e    \n    \u003c!-- TestContainers dependencies --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n        \u003cartifactId\u003espring-boot-testcontainers\u003c/artifactId\u003e\n        \u003cscope\u003etest\u003c/scope\u003e\n    \u003c/dependency\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.springframework.ai\u003c/groupId\u003e\n        \u003cartifactId\u003espring-ai-spring-boot-testcontainers\u003c/artifactId\u003e\n        \u003cscope\u003etest\u003c/scope\u003e\n    \u003c/dependency\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.testcontainers\u003c/groupId\u003e\n        \u003cartifactId\u003ejunit-jupiter\u003c/artifactId\u003e\n        \u003cscope\u003etest\u003c/scope\u003e\n    \u003c/dependency\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.testcontainers\u003c/groupId\u003e\n        \u003cartifactId\u003epostgresql\u003c/artifactId\u003e\n        \u003cscope\u003etest\u003c/scope\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n19. Create a `TestContainersConfiguration` class with the following content, in the `src/test/java` folder in the `workshop.springai.config` package:\n```java\npackage workshop.springai.config;\n\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.boot.testcontainers.service.connection.ServiceConnection;\nimport org.springframework.context.annotation.Bean;\nimport org.testcontainers.containers.PostgreSQLContainer;\nimport org.testcontainers.utility.DockerImageName;\n\n@TestConfiguration(proxyBeanMethods = false)\npublic class TestContainersConfiguration {\n\n    @Bean\n    @ServiceConnection\n    PostgreSQLContainer\u003c?\u003e pgvectorContainer() {\n        return new PostgreSQLContainer\u003c\u003e(DockerImageName.parse(\"pgvector/pgvector:pg16\"));\n    }\n\n}\n\n```\n\n20. Create a `TestTalentArenaApplication` class with the following content, in the `src/test/java` folder in the `workshop.springai` package:\n```java\npackage workshop.springai;\n\nimport org.springframework.boot.SpringApplication;\nimport workshop.springai.config.TestContainersConfiguration;\n\npublic class TestTalentArenaApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.from(TalentArenaApplication::main)\n                .with(TestContainersConfiguration.class)\n                .run(args);\n    }\n\n}\n```\n21. Add the following annotation `@Import(TestContainersConfiguration.class)` to the `TalentArenaApplicationTests` class, the final version of the class should look like this:\n```java\npackage workshop.springai;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Import;\nimport workshop.springai.config.TestContainersConfiguration;\n\n@Import(TestContainersConfiguration.class)\n@SpringBootTest\nclass TalentArenaApplicationTests {\n\n    @Test\n    void contextLoads() {\n    }\n\n}\n```\n22. Change the value of the `spring.docker.compose.skip.in-tests` property inside the `application.properties`\n    file to `true`, with this configuration the docker-compose will not be started during the tests:\n```properties\n# Spring Boot docker-compose properties\nspring.docker.compose.skip.in-tests=true\n```\n23. Build the project using Maven:\n```shell\nmvn clean install\n```\n24. Run the project using Maven, but this time with test-run:\n```shell\nmvn spring-boot:test-run\n```\n25. Open a new terminal and test the chatbot using curl:\n```shell\ncurl -X GET http://localhost:8080/chat/rag/talent-arena/ -H \"Content-Type: text/plain\" -d \"What are the content related to Artificial Intelligence in Talent Arena 2025 ?\"\n```\n\u003c/details\u003e\n\n## Exercise 4—image recognition\n\n1. Create a service `ImageRecognitionService` with the following method:\n* `processImage`, receiving a multipart file and returning a `String` with the image description.\n* Create a `Resource` from the input stream.\n* Convert the `Resource` to a `Media`.\n* The prompt needs to pass the image as part of the `UserMessage` media.\n* Include a `SystemMessage` giving instructions on thw answer format.\n* Pass the `UserMessage` and `SystemMessage` to the `chatClient` as part of the **messages** and return the string response.\n\n\u003cdetails\u003e\n\u003csummary\u003eImageRecognition service — Code example\u003c/summary\u003e\n\n### ImageRecognition service\n```java\npackage workshop.springai.services;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.ai.chat.messages.Message;\nimport org.springframework.ai.chat.messages.SystemMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.model.Media;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.core.io.InputStreamResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.MimeTypeUtils;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.IOException;\nimport java.util.List;\n\n@Slf4j\n@Service\npublic class ImageRecognitionService {\n\n    private final ChatClient chatClient;\n\n    public ImageRecognitionService(@Qualifier(\"chatClient\") ChatClient chatClient) {\n        this.chatClient = chatClient;\n    }\n\n    public String processImage(MultipartFile file) throws IOException {\n        log.info(\"image recognition service started processing: {}\", file.getOriginalFilename());\n\n        Resource image = new InputStreamResource(file.getInputStream());\n        log.info(\"image recognition service: {} file type\", image.getFilename());\n        Media myMedia = new Media(MimeTypeUtils.IMAGE_JPEG, image);\n        Message userMessage = new UserMessage(\"What is this image?\", List.of(myMedia));\n        Message systemMessage = new SystemMessage(\"Use the following format to answer the question: \u003cimage\u003e \u003cquestion\u003e \u003canswer\u003e\");\n        return chatClient.prompt().messages(List.of(userMessage, systemMessage)).call().content();\n\n    }\n}\n```\n\u003c/details\u003e\n\n2. Create a controller `ImageRecognitionController` with the following methods:\n  * `processImage`, receiving a multipart file as a `@RequestParam` and returning a `ResponseEntity\u003cString\u003e` with the image description.\n\n\u003cdetails\u003e\n\u003csummary\u003eImageRecognition controller — Code example\u003c/summary\u003e\n\n### ImageRecognition controller\n\n```java\n   package workshop.springai.chat;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.multipart.MultipartFile;\nimport workshop.springai.services.ImageRecognitionService;\n\n@Slf4j\n@RestController\npublic class ImageRecognitionController {\n\n    private final ImageRecognitionService imageRecognitionService;\n\n    public ImageRecognitionController(ImageRecognitionService imageRecognitionService) {\n        this.imageRecognitionService = imageRecognitionService;\n    }\n\n    @PostMapping(value = \"/image/recognition\", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)\n    public ResponseEntity\u003cString\u003e processImage(@RequestParam(\"file\") MultipartFile file) {\n        log.info(\"Image processing: process started\");\n        try {\n            return ResponseEntity.ok(imageRecognitionService.processImage(file));\n        } catch (Exception e) {\n            return ResponseEntity.internalServerError().body(\"Image processing: process failed\" + e.getMessage());\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n\n3. Create a directory of `images` at the project's root and copy these [images](https://github.com/k8s-spring-ai-workshop/talent-arena/tree/exercise4-image-recognition/images) there.\n\n\n4. Use your favorite HTTP client and make a POST request to the `/image/recognition` endpoint with a multipart file containing an image. The response should be a string with the image description.\n\n#### HTTP request\n```http request\nPOST http://localhost:8080/image/recognition\nContent-Type: multipart/form-data;boundary=WebAppBoundary\n\n--WebAppBoundary\nContent-Disposition: form-data; name=\"file\"; filename=\"bcn-jug.png\"\n\n\u003c images/bcn-jug.png\n--WebAppBoundary--  \n```\n\n#### curl\n```shell\ncurl -X POST http://localhost:8080/image/recognition \\\n  -H \"Content-Type: multipart/form-data\" \\\n  -F \"file=@images/bcn-jug.png\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk8s-spring-ai-workshop%2Ftalent-arena","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk8s-spring-ai-workshop%2Ftalent-arena","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk8s-spring-ai-workshop%2Ftalent-arena/lists"}