Zum Inhalt springen

Spring AI / OpenAI Tutorial

    Frage via Spring AI an OpenAI senden und die Antwort anzeigen

    OpenAI Key erstellen
    https://platform.openai.com/settings/organization/api-keys

    Anschließend den Key als Umgebungsvariable setzen: OPENAI_API_KEY

    Neues Spring Boot Projekt erstellen:
    https://start.spring.io/

    Innerhalb der Spring Boot Anwendung bzw. der „application.properties“ Datei den OpenAI Key bzw. Umgebungsvariable (OPENAI_API_KEY) referenzieren.

    spring.application.name=springai
    spring.ai.openai.api-key=${OPENAI_API_KEY}
    application.properties
    package de.aaron.springai.services;

    public interface OpenAIService {

    String getAnswer(String question);
    }
    OpenAIService.java
    package de.aaron.springai.services;

    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.chat.prompt.PromptTemplate;
    import org.springframework.stereotype.Service;

    @Service
    public class OpenAIServiceImpl implements OpenAIService {

    private final ChatModel chatModel;

    public OpenAIServiceImpl(ChatModel chatModel) {
    this.chatModel = chatModel;
    }

    @Override
    public String getAnswer(String question) {

    PromptTemplate promptTemplate = new PromptTemplate(question);
    Prompt prompt = promptTemplate.create();
    ChatResponse chatResponse = chatModel.call(prompt);

    return chatResponse.getResult().getOutput().getContent();
    }

    }
    OpenAIServiceImpl.java
    package de.aaron.springai.services;

    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;

    @SpringBootTest
    class OpenAIServiceImplTest {

    @Autowired
    OpenAIService openAIService;

    @Test
    void getAnswer() {
    String answer = openAIService.getAnswer("Who would win in a fight between Superman and Chuck Norris?");
    System.out.println("Answer: " answer);
    }

    }
    OpenAIServiceImplTest.java

    Nachdem wir das Interface und die Klassen erstellt haben, sollte die Projektstruktur wie folgt aussehen:

    Nachdem Ausführen des Unit-Tests, sollte auf die Frage „Who would win in a fight between Superman and Chuck Norris?“ die Antwort angezeigt werden. In diesem Fall:

    „Answer: In a hypothetical fight between Superman and Chuck Norris, it’s important to consider their respective attributes. Superman, being a fictional character from DC Comics, possesses superhuman strength, speed, flight, invulnerability, and various other powers, including heat vision and freezing breath. These abilities make him one of the most powerful superheroes.

    Chuck Norris, on the other hand, is a real person known for his martial arts expertise and roles in action films. While he has a legendary status in popular culture, often exaggerated through jokes and memes about his toughness, he is still human and lacks the superhuman abilities that Superman has.

    In a purely fictional context, Superman’s array of superpowers would likely give him the upper hand in a physical confrontation. However, the outcome of such a fight would ultimately depend on the context and the rules set within that fictional scenario.“

    REST-Controller verwenden

    REST-Controller erstellen.
    Der Endpoint ist z.B. via Postman unter der folgenden URL localhost:8080/ask zu erreichen.

    package de.aaron.springai.controllers;

    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;

    import de.aaron.springai.models.Answer;
    import de.aaron.springai.models.Question;
    import de.aaron.springai.services.OpenAIService;

    @RestController
    public class QuestionController {

    private final OpenAIService openAIService;

    public QuestionController(OpenAIService openAIService) {
    this.openAIService = openAIService;
    }

    @PostMapping("/ask")
    public Answer answerQuestion(@RequestBody Question question) {
    return openAIService.getAnswer(question);
    }

    }
    QuestionController.java
    package de.aaron.springai.services;

    import de.aaron.springai.models.Answer;
    import de.aaron.springai.models.Question;

    public interface OpenAIService {

    // String getAnswer(String question);
    Answer getAnswer(Question question);

    }
    OpenAIService.java
    package de.aaron.springai.services;

    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.chat.prompt.PromptTemplate;
    import org.springframework.stereotype.Service;

    import de.aaron.springai.models.Answer;
    import de.aaron.springai.models.Question;

    @Service
    public class OpenAIServiceImpl implements OpenAIService {

    private final ChatModel chatModel;

    public OpenAIServiceImpl(ChatModel chatModel) {
    this.chatModel = chatModel;
    }

    @Override
    public Answer getAnswer(Question question) {

    PromptTemplate promptTemplate = new PromptTemplate(question.question());
    Prompt prompt = promptTemplate.create();
    ChatResponse chatResponse = chatModel.call(prompt);

    return new Answer(chatResponse.getResult().getOutput().getContent());
    }

    }
    OpenAIServiceImpl.java
    package de.aaron.springai.models;

    public record Answer(String answer) {

    }
    Answer.java
    package de.aaron.springai.models;

    public record Question(String question) {

    }
    Question.java

    Die Projektstruktur sollte wie folgt aussehen:

    Endpoint via Postman aufrufen:

    Prompt-Template verwenden

    Ein Prompt-Template in Spring AI ist eine Vorlage, die Platzhalter für dynamische Eingaben enthält, um strukturierte Prompts für KI-Modelle zu erstellen. Es ermöglicht die flexible Generierung von Anfragen, indem Variablen wie Benutzereingaben oder Kontextinformationen eingefügt werden.

    In diesem Fall möchte ich via Postman lediglich den Namen „Superman“ an den neuen Endpoint „template“ übertragen. Im Hintergrund wird das Wort „Superman“ in dem folgenden Sazt/Frage/Template eingesetzt und beantwortet: „What is the real name of {value}?“.

    What is the real name of {value}?
    prompt-template.st
    package de.aaron.springai.controllers;

    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;

    import de.aaron.springai.models.Answer;
    import de.aaron.springai.models.Question;
    import de.aaron.springai.models.TemplateRequest;
    import de.aaron.springai.services.OpenAIService;

    @RestController
    public class QuestionController {

    private final OpenAIService openAIService;

    public QuestionController(OpenAIService openAIService) {
    this.openAIService = openAIService;
    }

    @PostMapping("/ask")
    public Answer answerQuestion(@RequestBody Question question) {
    return openAIService.getAnswer(question);
    }

    @PostMapping("/template")
    public Answer answerQuestionTemplate(@RequestBody TemplateRequest request) {
    return openAIService.getTemplateAnswer(request);
    }

    }
    QuestionController.java
    package de.aaron.springai.models;

    public record TemplateRequest(String value) {

    }
    TemplateRequest.java (Model)
    package de.aaron.springai.services;

    import de.aaron.springai.models.Answer;
    import de.aaron.springai.models.Question;
    import de.aaron.springai.models.TemplateRequest;

    public interface OpenAIService {

    // String getAnswer(String question);
    Answer getAnswer(Question question);

    Answer getTemplateAnswer(TemplateRequest request);

    }
    OpenAIService.java
    package de.aaron.springai.services;

    import java.util.Map;

    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.chat.prompt.Prompt;
    import org.springframework.ai.chat.prompt.PromptTemplate;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.io.Resource;
    import org.springframework.stereotype.Service;

    import de.aaron.springai.models.Answer;
    import de.aaron.springai.models.Question;
    import de.aaron.springai.models.TemplateRequest;

    @Service
    public class OpenAIServiceImpl implements OpenAIService {

    private final ChatModel chatModel;

    public OpenAIServiceImpl(ChatModel chatModel) {
    this.chatModel = chatModel;
    }

    /*/
    @Override
    public String getAnswer(String question) {

    PromptTemplate promptTemplate = new PromptTemplate(question);
    Prompt prompt = promptTemplate.create();
    ChatResponse chatResponse = chatModel.call(prompt);

    return chatResponse.getResult().getOutput().getContent();
    }
    //*/

    @Override
    public Answer getAnswer(Question question) {

    PromptTemplate promptTemplate = new PromptTemplate(question.question());
    Prompt prompt = promptTemplate.create();
    ChatResponse chatResponse = chatModel.call(prompt);

    return new Answer(chatResponse.getResult().getOutput().getContent());
    }


    @Value("classpath:templates/prompt-template.st")
    private Resource getPromptTemplate;

    @Override
    public Answer getTemplateAnswer(TemplateRequest request) {

    PromptTemplate promptTemplate = new PromptTemplate(getPromptTemplate);
    Prompt prompt = promptTemplate.create(Map.of("value", request.value()));

    ChatResponse chatResponse = chatModel.call(prompt);

    return new Answer(chatResponse.getResult().getOutput().getContent());
    }
    }
    OpenAIServiceImpl.java

    Die Projektstruktur sollte wie folgt aussehen:

    What is the real name of {value}?

    His name on earth '''name'''.
    His name on krypton '''name'''.
    His mother's name '''name'''.
    His adoptive mother's name '''name'''.
    His father's name '''name'''.
    His adoptive father's name '''name'''.
    His age '''age'''
    prompt-template.st (Template Erweiterung)

    System-Prompt verwenden

    package de.aaron.springai.services;

    import de.aaron.springai.models.Answer;
    import de.aaron.springai.models.Question;
    import de.aaron.springai.models.TemplateRequest;

    public interface OpenAIService {

    ....

    Answer getSystemPromptAnswer(Question request);

    }
    OpenAIService.java
    	@Override
    public Answer getSystemPromptAnswer(Question question) {

    String systemPrompt = "Translate the answer into German language.";
    SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
    Message systemMessage = systemPromptTemplate.createMessage();


    PromptTemplate promptTemplate = new PromptTemplate(question.question());
    Message userMessage = promptTemplate.createMessage();

    List<Message> messages = List.of(systemMessage, userMessage);

    Prompt prompt = new Prompt(messages);

    ChatResponse chatResponse = chatModel.call(prompt);

    return new Answer(chatResponse.getResult().getOutput().getContent());
    }
    OpenAIServiceImpl.java
    @PostMapping("/systemAnswer")
    public Answer answerQuestionSystemPrompt(@RequestBody Question question) {
    return openAIService.getSystemPromptAnswer(question);
    }
    QuestionController.java