Spring AI Prompt Engineering Tutorial

Introduction

In generative AI, creating effective prompts is crucial for achieving desired outputs. The quality and structure of these prompts significantly influence the AI's performance. Thoughtful, prompt engineering can enhance the effectiveness of AI models, making them more reliable and useful.

Sharing and discussing prompts within the AI community fosters a collaborative learning environment. Research often involves analyzing and comparing different prompts to assess their effectiveness. For instance, starting a prompt with "Take a deep breath and work on this problem step by step" has been shown to significantly enhance problem-solving efficiency.

Understanding and mastering prompt engineering is an ongoing challenge, especially with the rapid advancement of AI technologies. Utilizing insights from community discussions and research can improve your prompt creation strategies.

Key Components of Effective Prompts

When developing prompts, integrate the following key components to ensure clarity and effectiveness:

  1. Instructions: Provide clear and direct instructions to the AI, similar to how you would communicate with a person. Clarity helps the AI understand what is expected.

  2. External Context: Include relevant background information or specific guidance for the AI’s response when necessary. This frames the prompt and aids the AI in grasping the overall scenario.

  3. User Input: This is the straightforward part – the user’s direct request or question forming the core of the prompt.

  4. Output Indicator: Specify the desired format for the AI’s response, such as JSON. However, be aware that the AI might not always adhere strictly to this format.

Providing the AI with examples of the anticipated question-and-answer format can be highly beneficial. This practice helps the AI understand the structure and intent of your query, leading to more precise and relevant responses.

Let's create a Spring Boot project and discuss how to achieve prompt engineering by using Spring AI.

1. Setting Up the Project

Step 1: Create a New Spring Boot Project

Use Spring Initializr to create a new Spring Boot project. Include dependencies for Spring Web and Spring AI.

Using Spring Initializr:

  • Go to start.spring.io
  • Select:
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 3.0.0 (or latest)
    • Dependencies: Spring Web
  • Generate the project and unzip it.

Step 2: Add Dependencies

In your project's pom.xml, add the necessary dependencies for Spring AI.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

2. Configuring Spring AI

Step 1: Add API Key to Configuration

Add your OpenAI API key to application.properties or application.yml.

For application.properties:

openai.api.key=your_openai_api_key

For application.yml:

openai:
  api:
    key: your_openai_api_key

Step 2: Configure Spring Beans

Create a configuration class to set up all necessary Spring beans, including the OpenAiClient and ChatClient.

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ai.openai.OpenAiClient;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.openai.ChatClient;

@Configuration
public class AppConfig {

    @Bean
    public OpenAiClient openAiClient() {
        return new OpenAiClient();
    }

    @Bean
    public ChatClient chatClient(OpenAiClient openAiClient) {
        return new OpenAiChatClient(openAiClient);
    }
}

3. Creating Effective Prompts

Example 1: Providing Clear Instructions

package com.example.demo.service;

import org.springframework.ai.openai.ChatClient;
import org.springframework.ai.openai.model.ChatMessage;
import org.springframework.ai.openai.model.ChatRequest;
import org.springframework.ai.openai.model.ChatResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PromptService {

    private final ChatClient chatClient;

    @Autowired
    public PromptService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    public String provideInstructions(String userMessage) {
        ChatMessage systemMessage = new ChatMessage("system", "Please provide a detailed and concise response to the user's query.");
        ChatMessage userMsg = new ChatMessage("user", userMessage);

        ChatRequest request = new ChatRequest();
        request.setMessages(List.of(systemMessage, userMsg));

        ChatResponse response = chatClient.sendMessage(request);
        return response.getReply();
    }
}

Example 2: Including External Context

public String provideContextualResponse(String userMessage, String context) {
    ChatMessage systemMessage = new ChatMessage("system", "Consider the following context: " + context);
    ChatMessage userMsg = new ChatMessage("user", userMessage);

    ChatRequest request = new ChatRequest();
    request.setMessages(List.of(systemMessage, userMsg));

    ChatResponse response = chatClient.sendMessage(request);
    return response.getReply();
}

Example 3: Specifying Output Format

public String specifyOutputFormat(String userMessage, String format) {
    ChatMessage systemMessage = new ChatMessage("system", "Please provide the response in the following format: " + format);
    ChatMessage userMsg = new ChatMessage("user", userMessage);

    ChatRequest request = new ChatRequest();
    request.setMessages(List.of(systemMessage, userMsg));

    ChatResponse response = chatClient.sendMessage(request);
    return response.getReply();
}

4. Demonstrating Use Cases

Step 1: Define Use Cases in the Service

@Service
public class UseCaseService {

    private final ChatClient chatClient;

    @Autowired
    public UseCaseService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    public String provideInformation(String userMessage) {
        ChatMessage systemMessage = new ChatMessage("system", "You are a knowledgeable assistant.");
        ChatMessage userMsg = new ChatMessage("user", userMessage);

        ChatRequest request = new ChatRequest();
        request.setMessages(List.of(systemMessage, userMsg));

        ChatResponse response = chatClient.sendMessage(request);
        return response.getReply();
    }

    public String performCalculation(String userMessage) {
        ChatMessage systemMessage = new ChatMessage("system", "You are an assistant that can perform calculations.");
        ChatMessage userMsg = new ChatMessage("user", userMessage);

        ChatRequest request = new ChatRequest();
        request.setMessages(List.of(systemMessage, userMsg));

        ChatResponse response = chatClient.sendMessage(request);
        return response.getReply();
    }

    public String generateContent(String adjective, String topic) {
        String userMessage = "Tell me a " + adjective + " joke about " + topic;
        ChatMessage systemMessage = new ChatMessage("system", "You are an assistant that can generate creative content.");
        ChatMessage userMsg = new ChatMessage("user", userMessage);

        ChatRequest request = new ChatRequest();
        request.setMessages(List.of(systemMessage, userMsg));

        ChatResponse response = chatClient.sendMessage(request);
        return response.getReply();
    }
}

Step 2: Create a Controller to Expose the Service

package com.example.demo.controller;

import com.example.demo.service.UseCaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UseCaseController {

    private final UseCaseService useCaseService;

    @Autowired
    public UseCaseController(UseCaseService useCaseService) {
        this.useCaseService = useCaseService;
    }

    @GetMapping("/provideInformation")
    public String provideInformation(@RequestParam String userMessage) {
        return useCaseService.provideInformation(userMessage);
    }

    @GetMapping("/performCalculation")
    public String performCalculation(@RequestParam String userMessage) {
        return useCaseService.performCalculation(userMessage);
    }

    @GetMapping("/generateContent")
    public String generateContent(@RequestParam String adjective, @RequestParam String topic) {
        return useCaseService.generateContent(adjective, topic);
    }
}

5. Testing the Integration

Step 1: Run the Application

Run your Spring Boot application. Ensure the application starts without errors.

Step 2: Access the Interaction Endpoints

Use Postman, curl, or your browser to test the endpoints with different roles and inputs. For example:

  1. Providing Information:

    http://localhost:8080/provideInformation?userMessage=What is the capital of France?
    
  2. Performing Calculations:

    http://localhost:8080/performCalculation?userMessage=What is 10 + 15?
    
  3. Generating Content:

    http://localhost:8080/generateContent?adjective=funny&topic=technology
    

You should receive responses generated by the AI model based on the role-based messages.

Conclusion

This tutorial demonstrated how to set up and use roles in Spring AI to manage AI interactions in a Spring Boot application. You learned how to define system, user, assistant, and function roles, use them in a service, and expose endpoints to interact with the AI model. Different use cases were provided to show how roles can be used effectively in various scenarios. 

Comments