Thursday, 3 July 2025

Dynamic Prompt Engineering in LangChain4j with @V: Parameterizing System Messages"

Developers working with LangChain4j often define prompt templates using annotations like @UserMessage and @SystemMessage. To increase reusability and personalization of prompts, especially for system-level instructions, LangChain4j provides the @V annotation to inject runtime method arguments directly into these templates. This post explains how to make your AI service prompts dynamic and maintainable using the @V annotation.

What is @V?

The @V annotation allows you to inject method parameters into prompt templates. These templates can be defined via:

 

·      @UserMessage

·      @SystemMessage

·      AiServices.systemMessageProvider(Function)

 

Using @V, you can make your prompts dynamic and context-aware without hardcoding values.

 

Example

public interface StoryTeller {

  @SystemMessage("You are a masterful storyteller, renowned for your wisdom and creativity—much like {{authorLike}}.")
  String chat(@UserMessage String userMessage, @V("authorLike") String authorLike);
}

Here,

·      The @SystemMessage uses a template with {{authorLike}}.

·      The method parameter authorLike is annotated with @V("authorLike"), allowing it to be substituted dynamically.

·      When chat("Tell me a story about courage", "J.K. Rowling") is called, the system message becomes:

o   "You are a masterful storyteller, renowned for your wisdom and creativity—much like J.K. Rowling."

 

Advantages

·      Personalized AI behavior: Customize system behavior to each interaction.

·      Maintainability: Centralize and reuse templates.

·      Cleaner code: Avoid string concatenation and hardcoded prompt text.

 

Follow below step-by-step procedure to build the working application.

 

Step 1: Create new maven project ‘parameterize-system-messages’.

 

Step 2: Update pom.xml with maven dependencies.

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sample.app</groupId>
  <artifactId>parameterize-system-messages</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
    <java.version>21</java.version>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.10</version>
  </parent>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-bom</artifactId>
        <version>1.0.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>


    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
      <version>2.6.0</version>
    </dependency>

    <!--
    https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
    <dependency>
      <groupId>jakarta.validation</groupId>
      <artifactId>jakarta.validation-api</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal> <!-- Important -->
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

 

Step 3: Create a package com.sample.app.dto and define ChatResponse class.

 

ChatResponse.java

 

package com.sample.app.dto;

public class ChatResponse {
  private String message;

  public ChatResponse() {
  }

  public ChatResponse(String message) {
    this.message = message;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }
}

 

Step 4: Create com.sample.app.interfaces package and define StoryTeller interface.

 

StoryTeller.java

 

package com.sample.app.interfaces;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface StoryTeller {
  @SystemMessage("You are a masterful storyteller, renowned for your wisdom and creativity—much like {{authorLike}}")
  String chat(@UserMessage String userMessage, @V("authorLike") String authorLike);
}

Step 5: Create a package com.sample.app.config and define LangchainConfig and SwaggerConfig classes.

 

LangchainConfig.java

 

package com.sample.app.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import dev.langchain4j.http.client.spring.restclient.SpringRestClientBuilderFactory;
import dev.langchain4j.model.ollama.OllamaChatModel;

@Configuration
public class LangchainConfig {

  @Bean
  public OllamaChatModel ollamaLanguageModel() {
    return OllamaChatModel.builder().baseUrl("http://localhost:11434").modelName("llama3.2")
        .httpClientBuilder(new SpringRestClientBuilderFactory().create()) // explicitly use Spring's HTTP client
        .build();
  }
}

 

SwaggerConfig.java

package com.sample.app.config;

import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;

@Configuration
@OpenAPIDefinition(info = @Info(title = "Chat service Application", version = "v1"))
public class SwaggerConfig {

}

Step 6: Create a package com.sample.app.service and define ChatService class.

 

ChatService.java

 

package com.sample.app.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.sample.app.dto.ChatResponse;
import com.sample.app.interfaces.StoryTeller;

import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.service.AiServices;

@Service
public class ChatService {
  @Autowired
  private OllamaChatModel chatModel;

  public ChatResponse chat(String userMessage, String authorLike) {
    StoryTeller storyTeller = AiServices.create(StoryTeller.class, chatModel);
    String story = storyTeller.chat(userMessage, authorLike);
    return new ChatResponse(story);
  }
}

Step 7: Create com.sample.app.controller package and define ChatController class.

 

ChatController.java

 

package com.sample.app.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.sample.app.dto.ChatResponse;
import com.sample.app.service.ChatService;

import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;

@RestController
@RequestMapping("/api/story-teller")
@CrossOrigin("*")
@Tag(name = "Chat Controller", description = "This section contains APIs related to Chat APIs Powered by Ollama")
public class ChatController {

  @Autowired
  private ChatService chatService;

  @PostMapping
  public ChatResponse chat(@RequestBody @Valid ChatRequestBody chatRequestBody) {
    return chatService.chat(chatRequestBody.getMessage(), chatRequestBody.getAuthor());
  }

  private static class ChatRequestBody {
    @NotEmpty
    private String message;

    @NotEmpty
    private String author;

    public String getMessage() {
      return message;
    }

    public void setMessage(String message) {
      this.message = message;
    }

    public String getAuthor() {
      return author;
    }

    public void setAuthor(String author) {
      this.author = author;
    }

  }
}

Step 8: Define main application class in com.sample.app package.

 

App.java

 

package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

}

 

Build the project

Navigate to the project root directory where pom.xml is located and execute following command to generate the artifact.

 

mvn clean install

  Upon successful execution of the command, you can see lanhchain- parameterize-system-messages-0.0.1-SNAPSHOT.jar file in the target folder.

 

$ ls ./target/
classes               maven-status
generated-sources           parameterize-system-messages-0.0.1-SNAPSHOT.jar
generated-test-sources            parameterize-system-messages-0.0.1-SNAPSHOT.jar.original
maven-archiver              test-classes

Run the Application

Execute following command to run the application.

java -jar ./target/parameterize-system-messages-0.0.1-SNAPSHOT.jar --server.port=2345

 

Open the url http://localhost:2345/swagger-ui/index.html in the browser, and execute the api /api/story-teller with below payload.

{
  "message": "Tell me a story about Forest Animals",
  "author": "William Shakespeare"
}

You will see response like below.

{
  "message": "Gather 'round, good gentles, and heed my tale of Forest's Finest,\n\nIn days of yore, when sunbeams filtered through the trees,\nA realm of wonder waited, where creatures roam with ease.\nThe forest whispered secrets to those who'd listen deep,\nAnd I shall share a legend that within its heart doth creep.\n\n'Twas in this enchanted land, where ancient oaks did stand,\nThat lived a wise old badger, named Bramble by the hand.\nHis coat was gray as moss, his eyes like stars above,\nHe knew the language of the trees and the creatures' love.\n\nBramble spent his days exploring, from dawn till dusk's descent,\nAnd learned to live in harmony with nature's gentle bent.\nHe befriended a fox named Ruby, with fur as red as fire,\nTogether they'd roam, their footsteps quiet, like a heart's desire.\n\nOne autumn eve, when leaves did rustle and the wind did sigh,\nA young rabbit named Thumper stumbled upon a hidden glade, where sunbeams shone high.\nHe chanced upon an ancient beehive, half-hidden in the grass,\nWhere bees did dance and weave their honeycomb with golden pass.\n\nThumper's curiosity got the better of him, as he drew near,\nAnd Bramble, sensing trouble, rushed to calm his young peer.\nBut lo! The bees, in defense of their home, did swarm and sting,\nAnd Thumper, trembling with fear, thought all was lost, his heart would sing.\n\nRuby, quick-witted fox, did intervene with cunning guile,\nShe spoke the language of the bees, and calmed their angry smile.\nThe badger Bramble, wise in forest lore, did offer a clue,\nThat honey's sweetness lies within its core, and not just its hue.\n\nThumper learned that day, amidst his friends' gentle care,\nTo respect and appreciate the intricate web they share.\nFor in the forest's heart, where creatures roam and thrive,\nLies a balance of give-and-take, a harmony to survive.\n\nAnd so, dear friends, when next you venture into the woods,\nRemember Bramble, Ruby, Thumper, and the lessons they imbued.\nIn this realm of wonder, where trees do whisper low,\nListen closely, for the forest's secrets are waiting to bestow.\n\nFor in its ancient heart, a wisdom beats like a drum,\nA rhythm that echoes through each living being's hum.\nLet Bramble's story be your guide, on winding paths untrod,\nAnd may the lessons of the forest forever in your heart be stored."
}

 


 

You can download the application from this link.


 

  

Previous                                                    Next                                                    Home

No comments:

Post a Comment