Fine tuning Ai to get the response you want and prevent the response you don’t want!

Tuning Your AI App: The “Sweet Spot” Strategy

Tools Used: Ollama + llama3, Spring Ai

Building an AI-driven booking system sounds straightforward until your Kenyan safari guide starts trying to sell trips to Hawaii. After some deep-dive troubleshooting with Spring AI and Vector Stores, I’ve found that the secret to a perfect RAG (Retrieval-Augmented Generation) system isn’t just better data—it’s finding the right Similarity Threshold.


The Problem: The “Over-Helpful” AI

When using a vector store (like SimpleVectorStore), the system looks for the “closest” match to a user’s question. If a user asks for “Hawaii” and your database only contains “Kenya,” the math will still find the closest thing it can—likely a Kenyan beach tour. Without a gatekeeper, your AI sees that “closest” match and assumes it’s relevant, leading to confusing responses.

The Solution: The Similarity Threshold

The Similarity Threshold acts as a filter. It tells the system: “Unless the match is at least this relevant, ignore it.”

  • Setting it too high (e.g., 0.8): The “Nothing works” zone. Even valid searches for “Safari” or “Elephants” get blocked because the mathematical match isn’t “perfect” enough.
  • Setting it too low (e.g., 0.0): The “Hawaii” zone. Everything gets through, including irrelevant data, making your AI hallucinate or provide wrong locations.

Finding the “Sweet Spot”

Through testing with Llama 3, I discovered that the industry-standard numbers (like 0.7) don’t always work for every local setup. In my case, 0.1 was the magic number.

How to Implement It in Spring AI

In the ConciergeChatService, you can enforce this by using a constant and applying it to your SearchRequest. This ensures that if the similarity doesn’t hit your target, the list of documents comes back empty, and you can handle it with a polite “Jambo! I only specialize in Kenya” message.

Java

private static final double MIN_SIMILARITY = 0.1; // The magic number
public ConciergeChatResponse getConversationalResponse(String userQuestion) {
List<Document> docs = vectorStore.similaritySearch(
SearchRequest.builder()
.similarityThreshold(MIN_SIMILARITY)
.topK(5)
.build()
);
if (docs.isEmpty()) {
return new ConciergeChatResponse("I only specialize in Kenyan tours.", docs);
}
// Proceed to generate AI response...
}

Key Takeaway

If your AI is leaking irrelevant results or staying silent when it should speak, don’t assume your code is broken.Instead:

  1. Set your threshold to 0.0 to see what scores your valid terms are getting.
  2. Slowly nudge the number up until the “noise” (like Hawaii) disappears.
  3. Let your Java logic handle the empty results rather than letting the AI guess.

Tuning these numbers is the difference between an AI that guesses and an AI that knows.

Why Enum Singletons Are Safer Than Class Singletons

Class Based Singleton vs Enum Singleton | Security

I’ll discuss two ways of creating a singleton and explain why one is better than the other.

Let’s start with Class Based Singleton. We will create a singleton that should be thread safe by using the keyword synchronized and our singleton instance will be volatile.

Why volatile? The answer is “Double-Checked Locking”!

We want to ensure all threads see the most up-to-date value of the instance from main memory. Without doing so we could have threads seeing an instance is not quite null but also partially initialized.

The code for a class based singleton:

public class ClassBasedSingleton {
private static volatile ClassBasedSingleton plainSingleton;
private ClassBasedSingleton(){
}
public static ClassBasedSingleton getPlainSingleton(){
if(plainSingleton == null){
synchronized (ClassBasedSingleton.class) {
if(plainSingleton == null){
return new ClassBasedSingleton();
}
}
}
return plainSingleton;
}
}

Ok, we achieved our goal. What’s the problem?

The problem is this code is not secure. We want to prevent Reflection Based Instantiation.

Let’s get an instance via reflection using ReflectionBasedInstantiationForClassBasedSingletonTest:

ublic class ReflectionBasedInstantiationForClassBasedSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. Get the Class object for the target class
Class<?> clazz = ClassBasedSingleton.class;
// 2. Get the declared constructor (even if it's private)
Constructor<?> constructor = clazz.getDeclaredConstructor();
// 3. Make the constructor accessible
constructor.setAccessible(true);
// 4. Instantiate the object
ClassBasedSingleton instance = (ClassBasedSingleton) constructor.newInstance();
System.out.println("Instance created: " + instance);
}
}

Output from this code, using the ClassBasedSingleton:

As you can see, we have no errors attempting to access this singleton. Probably not what we want!

Let’s compare this to using an Enum singleton.

Enum singleton code:

public enum EnumSingleton {
SINGLETON;
private int counter = 123;
public synchronized void increment(){
counter++;
}
public int getCounter(){
return counter;
}
}

When we run this through our ReflectionBasedInstantiationForEnumBasedSingletonTest:

public class ReflectionBasedInstantiationForEnumBasedSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. Get the Class object for the target class
Class<?> clazz = EnumSingleton.class;
// 2. Get the declared constructor (even if it's private)
Constructor<?> constructor = clazz.getDeclaredConstructor();
// 3. Make the constructor accessible
constructor.setAccessible(true);
// 4. Instantiate the object
EnumSingleton instance = (EnumSingleton) constructor.newInstance();
System.out.println("Instance created: " + instance);
}
}

this is what we get:

We cannot access the singleton instance using reflection.

Safe!

So what’s happening here?

Java prevents reflection from creating the enum using reflection.

    We can take this a step further and show how Java prevents a 2nd instance creation:

    public class TestEnumSingletonGetSecondInstance {
    public static void main(String[] args){
    EnumSingleton instance1 = EnumSingleton.SINGLETON;
    int counter = instance1.getCounter();
    System.out.println("counter from EnumSingleton: "+counter);
    try {
    // 1. Get the constructor for the enum
    // Note: Enum constructors take (String name, int ordinal)
    Constructor<EnumSingleton> constructor =
    EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
    // 2. Try to bypass the 'private' modifier
    constructor.setAccessible(true);
    // 3. Try to create a NEW instance (This is where it fails!)
    EnumSingleton instance2 = constructor.newInstance("ATTACK_INSTANCE", 1);
    int counter2 = instance2.getCounter();
    System.out.println("counter2 from EnumSingleton: "+counter2);
    } catch (Exception e) {
    System.err.println("\n--- REFLECTION ATTACK FAILED ---");
    System.err.println("Exception: " + e.getClass().getSimpleName());
    System.err.println("Message: " + e.getMessage());
    }
    }
    }

    And we get this printout on the console:

    We get the exception caught in the catch and we do not see the printout from the 2nd enum instance because of the exception.

    Cheers!

    Feeding ElasticSearch with Java Generics and Java Reflection

    If you’ve read my blog before, you know how I feel about ElasticSearch (LOVE).

    When you have to load as much as data as I have to, things become a bit tedious with several sources of:

    • A database
    • A web service
    • A parsed file
    • Etc..

    You are probably storing the data into object. If so, you would be forced to iterate each object based off its specific fields. Every object will have different fields:

    HubspotContact:

    • Firstname
    • Lastname
    • Email

    And your Registration object will have:

    • badgeId
    • registration_date
    • Sessions

    So, for each object type, you would have to iterate those specific fields, either in separate classes or separate methods.

    for(Iterator<HubspotContact> it = registrants.iterator();it.hasNext();) {

    HubspotContact registrant = it.next();

    get fields…….(getEmail(), getFirstname(), etc….)

    }

    So, what’s the solution?

    Java Generics + Java Reflection = Easy ElasticSearch

    Let’s start with your method signature:

    public static <T> void insertData(List<T> items, String indexName, String updateIdField)

    We want our method to accept any object, so we use Java Generics to help achieve that. Notice the method signature contains <T> which stands for Type. You see <T> again in the parameter list as well, List<T>, indicating we will accept a list of objects of any type. This takes care of the method signature.

    Now, using Java Reflection, we can get the fields for the object without knowing them beforehand or having to specifically call their getter methods (getEmail(), getFirstname(), etc…).
    Let’s start with iterating the List<T> objects. We use this HashMap for convenience…to gather all fields and values into a data structure that can be manipulated. I could alphabetize, sort, etc…using the Map.

    for(Iterator<T> it = items.iterator();it.hasNext();) {

    Object o = it.next();

    HashMap<String, Object> fieldMap = new HashMap<String, Object>();
    Next, we use Java Reflection to get a handle on the fields of the object.
    Field[] f = o.getClass().getDeclaredFields();
    We then iterate over the each field and store the field names and values in the Map.
    for (int i = 0; i < o.getClass().getDeclaredFields().length; i++) {
    f[i].setAccessible(true);
    String name = f[i].getName();
    Object value = null;
    try {
    value = f[i].get(o);
    } catch (IllegalArgumentException | IllegalAccessException e) {
    e.printStackTrace();
    }
    fieldMap.put(name, value);
    }
    Then, finally, we setup the ElasticSearch IndexRequest…passing in the map we declared earlier.
    IndexRequest indexRequest = new IndexRequest(indexName, docType, String.valueOf(fieldMap.get(updateIdField))).source(fieldMap);

    Done!

    Pros & Cons

    Pros:

    I can load any simple object without knowing too much about it. Granted I have yet to try this with a complex object, where fields contain lists, maps, etc…it’s still a boost in productivity.

    Cons:

    All fields of the object will be iterated, and all fields will be passed on to ElasticSearch loading function. You could filter the fields you want, but that would have a negative impact on the convenience of this approach.

    Very useful approach that does not call for any special libraries or techniques that are too funky.

    Cheers!

    Design a site like this with WordPress.com
    Get started