Spring AI · Sorğular, şablonlar və cavab formatları · Dərs 1

Sorğu şablonlarının hazırlanması

Modelə sorğu göndərməyin müxtəlif üsullarını öyrən, cavab formatını müəyyən et və əlavə kontekstlə RAG üçün zəmin hazırla.

Sorğu şablonlarının hazırlanması

Qiymətləndirmə modulundakı AnswerChecks və InheritanceReference daxil olmaqla eyni Study Coach layihəsində davam et. Java 21, Spring Boot 3.5.9 və Spring AI 1.1.8 versiyalarını saxla. Bu dərs təkrar istifadə olunan prompt factory əlavə edir; mövcud HTTP endpoint-ləri əvvəlki servisləri ilə işləməyə davam edir.

1. Təlimatları sorğu məlumatından ayır

Prompt şablonu tətbiqə aid, adlı dəyişənləri olan mətndir. Təkrarlanan təlimatları ardıcıl saxlamağa kömək edir, amma modeli deterministik etmir. Seçilmiş cavab dilini şablona yerləşdirəcək, öyrənənin sualını isə ayrıca user mesajında saxlayacağıq.

Tətbiq dili enum-dan seçir. What does class Sample {} mean? kimi sual məlumatdır. Onun mötərizələri şablon dəyişənlərinə çevrilməməlidir. Bu yolların ayrı olması testləri də asanlaşdırır: model çağırılmamış yekun mesajları yoxlaya bilərik.

2. Nəzarət edilən dəyişəni şablona yerləşdir

src/main/java/academy/study/StudyPromptFactory.java faylını saxla:

Java
package academy.study;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;

public final class StudyPromptFactory {
    public enum Language { ENGLISH, AZERBAIJANI }

    private static final String SYSTEM_TEMPLATE = """
            You are a study coach for Java backend learners.
            Answer in {language} with a short explanation and one small example.
            State uncertainty and do not claim access to unavailable course files.
            """;

    private StudyPromptFactory() {}

    public static Prompt create(String question, Language language) {
        if (question == null || question.isBlank() || question.length() > 2000) {
            throw new IllegalArgumentException("Invalid question");
        }
        String languageName = switch (Objects.requireNonNull(language)) {
            case ENGLISH -> "English";
            case AZERBAIJANI -> "Azerbaijani";
        };
        String system = new PromptTemplate(SYSTEM_TEMPLATE)
                .render(Map.of("language", languageName));
        return new Prompt(List.of(new SystemMessage(system),
                new UserMessage(question.strip())));
    }
}

PromptTemplate.render(Map) dəyişənlər yerləşdirilmiş sətri qaytarır; new Prompt(...) isə system və user mesajlarını birləşdirir. Standart şablon sintaksisində dəyişən adları fiqurlu mötərizələrdə yazılır. Tətbiq hər emalda language dəyərini verir. Çatışmayan dəyişən testdə tutulmalı proqramlaşdırma xətasıdır, yarımçıq prompt-u provayderə göndərmək üçün səbəb deyil. Spring AI sorğu şablonları

Language tətbiqin dəstəklədiyi seçimləri məhdudlaşdırır; modelin həmin dildə cavab verəcəyinə zəmanət vermir. Sualın limiti giriş modulundakı kimi 2 000 UTF-16 kod vahididir. Yoxlama kənar boşluqlar silinməzdən əvvəl aparılır. Hər çağırış üçün söhbət vəziyyəti saxlamayan yeni Prompt qurulur.

3. Kod mətnini şablon sintaksisindən kənarda saxla

Factory yalnız öz system şablonunu emal edir. Sualı birbaşa UserMessage-ə ötürdüyü üçün sualdakı Java mötərizələri və dəyişənə oxşayan mətn olduğu kimi qalır. İstifadəçi girişini şablonun mənbə mətninə birləşdirib sonra parse etmə.

Gələcək şablonun özündə JSON nümunəsi varsa, şablon mexanizminin ayırıcılarını nəzərə al. Spring AI konfiqurasiya edilən StTemplateRenderer dəstəkləyir; ayırıcıları dəyişmək sintaksis seçimidir, təhlükəsizlik nəzarəti deyil. Mötərizələri escape etməyin prompt injection qarşısını aldığını düşünmə. Öyrənən user mesajında yenə təlimat yaza, model də ona səhvən əməl edə bilər.

System mesajı tədris qaydasını ifadə edir. Ödənişli məzmuna giriş hüququ vermir və faktların düzgünlüyünü təmin etmir. Qapalı məlumatı provayderə göndərməzdən əvvəl giriş qaydalarını Java-da tətbiq etməyə davam et.

4. Qurulmuş mesajları test et

src/test/java/academy/study/StudyPromptFactoryTest.java faylını saxla:

Java
package academy.study;

import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.MessageType;

import static academy.study.StudyPromptFactory.Language.*;
import static org.junit.jupiter.api.Assertions.*;

class StudyPromptFactoryTest {
    @Test
    void preservesQuestionBracesAndMessageRoles() {
        String question = "What does class Sample {} mean?";
        var messages = StudyPromptFactory.create(question, ENGLISH).getInstructions();
        assertEquals(2, messages.size());
        assertEquals(MessageType.SYSTEM, messages.get(0).getMessageType());
        assertEquals(MessageType.USER, messages.get(1).getMessageType());
        assertTrue(messages.get(0).getText().contains("English"));
        assertEquals(question, messages.get(1).getText());
    }

    @Test
    void rendersLanguageWithoutSharingQuestionState() {
        var first = StudyPromptFactory.create("First question", AZERBAIJANI);
        var second = StudyPromptFactory.create("  Second question  ", ENGLISH);
        assertTrue(first.getInstructions().get(0).getText().contains("Azerbaijani"));
        assertEquals("First question", first.getInstructions().get(1).getText());
        assertEquals("Second question", second.getInstructions().get(1).getText());
    }

    @Test
    void enforcesInputBoundary() {
        assertThrows(IllegalArgumentException.class,
                () -> StudyPromptFactory.create(null, ENGLISH));
        assertThrows(IllegalArgumentException.class,
                () -> StudyPromptFactory.create(" \n", ENGLISH));
        assertThrows(IllegalArgumentException.class,
                () -> StudyPromptFactory.create("x".repeat(2001), ENGLISH));
        assertDoesNotThrow(() -> StudyPromptFactory.create("x".repeat(2000), ENGLISH));
        assertThrows(NullPointerException.class,
                () -> StudyPromptFactory.create("Question", null));
    }
}

Testlər mesaj rollarını, mötərizələrin qorunmasını, hər iki dil seçimini, ayrı sorğuları və giriş sərhədlərini yoxlayır. Modelin dəqiq cümləsini yoxlamır. Model və provayder açarı olmadan işləyir.

5. Factory-ni model sərhədində təkrar istifadə et

Növbəti dərs alınmış Prompt ilə chatClient.prompt(prompt) çağıracaq. Bu, prompt qurulmasını şəbəkə icrasından ayırır. Təlimatlar dəyişəndə yekun mesajları müqayisə et və mövcud qiymətləndirmə nümunələrini yenidən işə sal; kiçik söz dəyişikliyi model davranışını dəyişə bilər.

Təcrübə qeydlərində əhəmiyyətli prompt dəyişikliklərinə sabit identifikator ver. Qiymətləndirmə nəticəsi ilə yanaşı dili, nümunə ID-sini və model konfiqurasiyasını qeyd et. Öyrənənin suallarını standart olaraq tam loglama. Şablonun özü zərərsiz olsa da, yekun prompt-un surətində qapalı məlumat ola bilər.

6. Bir təhlükəsiz dəyişikliyi yoxla

Bütün offline testləri işə sal:

sh
env -u RUN_LIVE_EVALUATION mvn clean verify

İyirmi iki offline metod keçməli, bir real qiymətləndirmə metodu buraxılmalıdır. Bu dərs üçün yeni asılılıq lazım deyil.

Tapşırıq: ingilis dilində cavab təlimatını iki qısa abzas istəyəcək şəkildə dəyiş, sonra system mesajında həmin təlimatın olduğunu yoxlayan deterministik assertion əlavə et. Bu assertion-un nəyi sübut etmədiyini izah et. Davam etməzdən əvvəl ilkin şablonu bərpa et ki, növbəti dərsin nümunələri uyğun gəlsin.

Cavabın yoxlanması: assertion təlimatın prompt-a çatdığını sübut edir. Cavabın ona əməl edib-etmədiyini yalnız ayrıca model icrası və qiymətləndirmə ilə yoxlamaq olar. Şablon testi, model keyfiyyəti testi və giriş hüququ yoxlaması ayrı məsuliyyətlər olaraq qalır.