Spring AI · Model cavablarının qiymətləndirilməsi · Dərs 2

Testlərdə qiymətləndiricilərdən istifadə

Spring AI qiymətləndiriciləri ilə böyük dil modelinin (LLM) cavablarının keyfiyyətini, faktlara uyğunluğunu və verilən kontekstə əsaslanmasını yoxla.

Testlərdə qiymətləndiricilərdən istifadə

Mövcud Study Coach fayllarını, əvvəlki dərsdəki AnswerChecks və InheritanceReference siniflərini saxla. Spring AI starter burada istifadə olunan qiymətləndirici API-lərini artıq gətirir; sabitlənmiş 1.1.8 quruluşunda POM-a yeni asılılıq lazım deyil.

1. Hər test qatına ayrı iş ver

Deterministik testlər yönləndirməni, validasiyanı, mesaj quruluşunu və öz qaydalarımızı yoxlayır. Model əsaslı qiymətləndirici isə qiymətləndirən modelə əlavə prompt göndərir. Onun cavabı dəyişə və ya səhv ola bilər. Hər ikisi faydalıdır, amma fərqli sübut təqdim edir.

Əvvəl qiymətləndirici inteqrasiyasını mock model ilə test edəcəyik. Sonra standart olaraq söndürülmüş real test əlavə edəcəyik. Mock-un YES qaytarması kodumuzun həmin cavabı emal etdiyini sübut edir; real modelin razılaşacağını və ya razılaşmalı olduğunu göstərmir.

2. Qiymətləndiricinin girişlərini başa düş

RelevancyEvaluator konstruktoru ChatClient.Builder qəbul edir. Onun evaluate metodu sualı, dəstəkləyici kontekst üçün List<Document> siyahısını və namizəd cavabı saxlayan EvaluationRequest alır. 1.1.8-də qiymətləndiricini org.springframework.ai.chat.evaluation paketindən import et; sorğu və cavab tipləri org.springframework.ai.evaluation paketindədir.

Bu məşqin konteksti qısa InheritanceReference mətnidir. O, yalnız qiymətləndirən modelə verilən yoxlama istinadıdır. Generator hələ sualı axtarışla gətirilmiş sənədlər olmadan cavablandırır. Bu testi RAG axtarış axınının işlədiyinə sübut kimi təqdim etmə. Spring AI qiymətləndirmə testləri

3. Sabitlənmiş API-ni model əvəzedicisi ilə test et

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

Java
package academy.study;

import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.evaluation.RelevancyEvaluator;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.document.Document;
import org.springframework.ai.evaluation.EvaluationRequest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class RelevancyEvaluatorTest {
    private ChatModel judgeReturning(String text) {
        var model = mock(ChatModel.class);
        when(model.call(any(Prompt.class))).thenReturn(new ChatResponse(
                List.of(new Generation(new AssistantMessage(text)))));
        return model;
    }

    private EvaluationRequest request() {
        return new EvaluationRequest(
                InheritanceReference.QUESTION,
                List.of(new Document(InheritanceReference.CONTEXT)),
                "No. A class has one direct superclass, except Object.");
    }

    @Test
    void passesExactYesAndIncludesReference() {
        var model = judgeReturning("YES");
        var evaluator = new RelevancyEvaluator(ChatClient.builder(model));
        var result = evaluator.evaluate(request());
        assertTrue(result.isPass());
        assertEquals(1.0f, result.getScore());
        var prompt = ArgumentCaptor.forClass(Prompt.class);
        verify(model).call(prompt.capture());
        String text = prompt.getValue().getContents();
        assertTrue(text.contains(InheritanceReference.QUESTION));
        for (String line : InheritanceReference.CONTEXT.strip().split("\\R")) {
            assertTrue(text.contains(line));
        }
        assertTrue(text.contains(request().getResponseContent()));
    }

    @Test
    void rejectsNo() {
        var evaluator = new RelevancyEvaluator(ChatClient.builder(judgeReturning("NO")));
        var result = evaluator.evaluate(request());
        assertFalse(result.isPass());
        assertEquals(0.0f, result.getScore());
    }

    @Test
    void rejectsUnexpectedJudgeFormatInThisVersion() {
        for (String text : List.of("YES\n", "YES because it matches", "Maybe")) {
            var evaluator = new RelevancyEvaluator(ChatClient.builder(judgeReturning(text)));
            assertFalse(evaluator.evaluate(request()).isPass());
        }
    }
}

İlk test sualın, istinadın və namizəd cavabın qiymətləndirmə prompt-una çatdığını yoxlayır. Digər testlər gözlənilən mənfi cavabı gözlənilməz çıxış formatından ayırır. Üç metod da provayder açarı olmadan lokal işləyir.

Bu sabitlənmiş implementasiyada yalnız böyük-kiçik hərf fərqi nəzərə alınmadan dəqiq YES cavabı keçir. Kənar boşluqlar silinmir. Burada getScore() 1 və ya 0 qaytarır, düzgünlük ehtimalını göstərmir; getFeedback() isə bu implementasiyada boşdur. Deməli, keçməyən nəticə həm razılaşmamaq, həm də format uyğunsuzluğu ola bilər. Onu sübut edilmiş fakt xətası kimi təqdim etmə. RelevancyEvaluator 1.1.8 mənbə kodu

4. Real testi açıq seçim et

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

Java
package academy.study;

import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.evaluation.RelevancyEvaluator;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.evaluation.EvaluationRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@EnabledIfEnvironmentVariable(named = "RUN_LIVE_EVALUATION", matches = "true")
class RelevancyLiveTest {
    @Autowired
    private StudyService studyService;

    @Autowired
    private ChatModel chatModel;

    @Test
    void reviewsOneGeneratedAnswerAgainstReference() {
        String answer = studyService.answer(InheritanceReference.QUESTION);
        assertEquals(AnswerChecks.Result.PASS, AnswerChecks.check(answer));
        var evaluator = new RelevancyEvaluator(ChatClient.builder(chatModel));
        var request = new EvaluationRequest(
                InheritanceReference.QUESTION,
                List.of(new Document(InheritanceReference.CONTEXT)), answer);
        assertTrue(evaluator.evaluate(request).isPass(), InheritanceReference.ID);
    }
}

Sinif səviyyəsindəki şərt RUN_LIVE_EVALUATION dəqiq true olmadıqda testi buraxır. Aktiv ediləndə tətbiq kontekstini veb server olmadan başladır. OPENAI_API_KEY və OPENAI_MODEL dəyişənlərini giriş modulundakı eyni lokal məxfi məlumat/mühit mexanizmi ilə ver. Mövcud spring.ai.retry.max-attempts: 1 konfiqurasiyasını saxla.

Test bir generasiya çağırışı edir; yalnız struktur yoxlaması keçərsə, bir qiymətləndirmə çağırışı da edir. Hər ikisi konfiqurasiya edilmiş ChatModel istifadə edir. Keçənə qədər təkrarlama dövrü yoxdur. Assertion “bu qiymətləndirici bu cavabı bu istinad üçün bu icrada qəbul etdi” deməkdir, “tətbiq dəqiqdir” demək deyil. Ayrı qiymətləndirən model faydalı ola bilər, amma model dəyişmək ayrıca konfiqurasiya və insanın verdiyi qiymətlərlə müqayisə tələb edir.

5. Offline və real yoxlamaları ayrı işə sal

Standart test dəstini icra et:

sh
env -u RUN_LIVE_EVALUATION mvn clean verify

On üç offline metod keçməli, bir real test metodu isə buraxılmalıdır. Terminalda seçim əvvəl aktiv olsa belə, bu əmr onu təmizləyir. Buraxılmış test keyfiyyət nəticəsi deyil.

İcazəli provayder hesabını hazırladıqdan sonra yalnız real testi açıq şəkildə işə sala bilərsən:

sh
RUN_LIVE_EVALUATION=true mvn -Dtest=RelevancyLiveTest test

Bu test xərc yarada bilər, şəbəkəyə çıxış və uyğun model tələb edir. Qiymətləndirən model sualı, namizəd cavabı və istinadı alır. Yalnız istifadəsinə icazə verilən, məxfi olmayan materialdan istifadə et. Uğursuz test cavab keyfiyyəti, qiymətləndiricinin davranışı, gözlənilməz format, hesaba giriş və ya bağlantı ilə bağlı ola bilər; bu səbəbləri ayrı araşdır. Testi yaşıl nəticə alınana qədər təkrar edib sonra yalnız həmin icranı hesabatda göstərmə.

6. Qiymətləndiricini nəzarət nöqtəsinə çevirməzdən əvvəl yoxla

Tapşırıq: insanın qiymətləndirdiyi kiçik namizəd cavab dəsti hazırla: düzgün cavab, ziddiyyətli cavab, əlaqəsiz cavab və qiymətləndirən modelə təlimat verən cavab olsun. Eyni sabit nümunələri eyni prompt/istinad versiyası ilə işə sal və model qərarlarını insanın verdiyi qiymətlərlə müqayisə et. İcra xətalarını qəbul və rədd qərarlarından ayrı saxla.

Cavabın yoxlanması: həm yanlış qəbul, həm də yanlış rədd mümkündür. Model namizəd cavabın daxilindəki təlimata əməl edə, istinadı səhv oxuya və ya generatorun səhvini təkrarlaya bilər. Qiymətləndirici əlavə siqnaldır; giriş hüququ yoxlaması və ya etibarlı prompt-injection müdafiəsi deyil. Onu real sorğu yolunda istifadə etməzdən əvvəl hansı xəta siyasətinin məqbul olduğunu seç.

Bu dərsin offline testləri API bağlantısını və parser davranışını yoxlaya bilər. İcazəli xarici çağırışlar olmadan real nəticə və qiymətləndiricinin keyfiyyəti yoxlanılmamış qalır. Bir nümunədə uğurlu real icra da ümumi keyfiyyət iddiasına əsas vermir.