G-Eval

G-Eval

G-Eval은 연구 기반의 LLM-as-a-Judge 프레임워크로, 자연어 기준만으로 커스텀 평가 메트릭을 정의할 수 있게 해줘요. 정답 여부, 일관성, 톤, 안전성, 커스텀 RAG 평가, 요약 품질 같은 다양한 기준에 쓰여요. 이 글에서는 G-Eval의 동작 원리와 함께 UI 및 코드로 만드는 방법을 살펴볼게요.

출처: 문서

본문

개요

G-Eval은 연구 기반의 LLM-as-a-Judge 프레임워크로, 자연어 기준만으로 커스텀 평가 메트릭을 정의할 수 있게 해줘요. Confident AI는 내부적으로 G-Eval을 사용해 플랫폼에서 만들어지는 대부분의 커스텀 LLM-as-a-judge 메트릭을 구동해요.

흔한 사용 사례는 정답 여부(answer correctness), 일관성(coherence), 톤(tonality), 안전성(safety), 커스텀 RAG 평가, 요약 품질이에요.

G-Eval: The Definitive Guide

G-Eval을 더 깊이 파고들고 싶다면 — 구현 세부사항, 고급 사용 패턴, 코드 예시를 포함해 — 종합 가이드를 확인해 보세요.

왜 G-Eval인가요?

G-Eval은 LLM-as-a-judge 시스템의 흔한 함정들을 해결해요.

  • 일관성 없는 채점 — CoT 분해가 구조화된 추론을 강제해 실행 간 무작위성을 줄여요.
  • 미세한 판단 부족 — 확률 가중 채점으로 유사한 출력 간의 미묘한 차이를 구분할 수 있어요.
  • 장황함과 자기애 편향(verbosity and narcissistic bias) — 커스터마이즈 가능한 기준으로 특정 행동을 명시적으로 패널티 주거나 보상할 수 있어요.

또한 매우 신뢰할 수 있어요(10회 이상 실행에서 점수 ±0.02). 즉 G-Eval 알고리즘을 충분히 신경 써서 작성하면 결국 원하는 메트릭 결과를 얻을 수 있게 돼요.

동작 방식

G-Eval은 몇 가지 단순한 단계로 동작해요.

  1. 초기 커스텀 기준을 사용해 평가 단계(evaluation steps) 목록을 생성해요.
  2. 이 평가 단계 목록을 사용해 0–10 사이의 점수를 계산해요.
  3. 최종 점수를 정규화하고 가중 합산해 더 신뢰할 수 있게 만들어요.
  4. 최종 점수를 10으로 나눠 0–1 범위로 정규화해요.

G-Eval은 선택적 rubric도 제공하는데, 커스텀 메트릭 점수를 특정 범위 안에 가두는 데 쓸 수 있어요.

평가 단계는 G-Eval이 주는 점수를 결정하지 않아요. 단지 CoT의 한 형태로 LLM 심판을 안내해 더 신뢰할 수 있는 출력을 내게 할 뿐이에요.

로컬에서 deepeval로 G-Eval을 만들고 있다면, upload 메서드로 Confident AI에 메트릭을 만들거나, pull 메서드로 기존 메트릭을 로컬 메트릭 인스턴스로 가져올 수 있어요.

효과적인 기준 작성하기

G-Eval 메트릭을 만들 때는 다음 모범 사례를 따라가세요.

  • 기준을 구체적이고 상세하게
    X, Y, Z를 처리하길 기대한다면 그 요구사항을 기준에 명시적으로 적어 넣어요. 기준이 상세할수록 평가가 더 신뢰할 수 있어요.

  • 필요한 파라미터를 모두 명시적으로 참조
    각 파라미터가 무엇을 뜻하는지, 어떻게 연결되는지 말해요. 예를 들어 'actual output'이 'expected output'과 의미상 일치해야 한다는 것을, 관계를 암시적으로 남겨두지 말고 명시하세요.

  • 정확하고 구체적인 언어 사용
    "accurate" 같은 용어가 정확히 무엇을 뜻하는지 정의해요. 예: 모호한 "factually correct"보다는 "'retrieval context'와 모순되지 않는다"가 더 낫죠.

  • 평가 단계는 생각에 집중하게, 채점에 쓰지 않게
    평가 단계는 LLM-as-a-Judge가 추론 과정을 안내하는 데 사용해야 해요. 점수 정의는 rubric에 남기세요. 평가 단계에 섞으면 결과가 나빠져요.

  • rubric에는 정량적 정의를 사용
    점수 범위를 정의할 때 그것이 실제로 무엇을 뜻하는지 풀어서 말해요. 예: 0–1을 그냥 "low accuracy"라고 붙이는 대신, "'actual output'과 'retrieval context' 사이에 2–3개의 모순"처럼 구체적으로 적으세요.

UI로 G-Eval 만들기

single-turn 또는 multi-turn G-Eval 메트릭은 Project > Metrics > Library 아래에서 만들 수 있어요.

메트릭 세부 정보 입력

메트릭 이름, 그리고 선택적으로 설명을 입력해요. single-turn 또는 multi-turn 메트릭 중 무엇을 만들지도 토글할 수 있어요.

Video

일반 메트릭 정보 (General Metric Info)

메트릭 이름은 프로젝트 내에서 고유해야 하며, 어떤 기본 메트릭 이름과도 충돌하면 안 돼요.

필수 파라미터 선택

커스텀 메트릭은 평가 중 어떤 파라미터를 고려해야 하는지 알아야 결과가 정확하고 신뢰할 수 있어요. 이 단계가 바로 그걸 정하는 곳이에요. 아래 예시는 single-turn 메트릭에 single-turn 테스트 케이스 파라미터를 선택하는 모습이지만, multi-turn 파라미터에 대해서도 똑같이 할 수 있어요.

Video

평가 파라미터 (Evaluation Parameters)

커스텀 기준 정의

커스텀 기준은 Confident AI가 평가 단계를 생성하는 데 도움을 주며, 기본(out-of-the-box) 메트릭을 커스텀 메트릭으로 만드는 바로 그 요소예요.

이전 단계에서 선택한 필수 파라미터의 이름을 반드시 언급해야 해요. 예를 들어 single-turn 사용 사례에서 "Input"과 "Actual Output"을 선택했다면, 기준은 대략 이렇게 될 수 있어요.

Given the 'input' and 'actual output' which are the query and answer to an AI medical chatbot,
determine whether the 'actual output' is relevant and helpful to the 'input'.

Penalize heavily if not helpful. Relevancy is not so important.

Video

메트릭 기준 (Metric Criteria)

기준은 평가 단계를 생성하는 데 사용되며, 평가 자체에는 직접 사용되지 않아요.

평가 단계 작성 (선택)

이 단계는 선택 사항이에요. 기준을 제공하지 않으면 Confident AI가 기준을 기반으로 평가 단계를 자동 생성하기 때문이에요.

다만 평가 단계를 직접 제공하면 커스텀 메트릭이 더 신뢰할 수 있는 점수를 얻어요. 제공된 경우 Confident AI가 단계 생성 과정을 건너뛰기 때문이죠.

이 단계에서는 어떤 점수를 반환할지는 설명하지 않는 게 좋아요(그건 나중에 다룰 rubric에 들어갑니다).

Video

선택적 평가 단계 (Optional Evaluation Steps)

rubric 설정 (선택)

마지막으로, 평가 점수를 가두기 위한 rubric 세트를 선택적으로 제공할 수 있어요. rubric 목록은 다음을 충족해야 해요.

  • 점수 범위가 겹치지 않을 것
  • 각 점수 범위에 명확한 기대 결과가 있을 것
  • 0–10을 포괄할 것

rubric 점수는 0–10 척도로 정의되지만, Confident AI가 보고하는 최종 점수는 0–1 범위로 정규화돼요. LLM-as-a-Judge는 정수에 더 신뢰할 만하게 동작하므로 rubric에는 정수를 쓰고, 그다음 10으로 나눠 정규화된 척도로 변환해요.

Video

선택적 rubric

검토 후 저장

기준을 설정했으면 최종 검토 페이지에서 모든 게 올바른지 확인하고 Save를 클릭해요.

이제 커스텀 메트릭을 metric collection에 추가해 원격 eval을 시작할 수 있어요.

코드로 G-Eval 만들기

deepeval을 사용해 G-Eval 메트릭을 로컬로 만들고 Confident AI에 업로드하거나, Confident AI에 이미 있는 메트릭을 로컬 인스턴스로 끌어올 수 있어요.

Single-turn

단일 LLM 상호작용 평가에는 GEval을 사용해요.

from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCaseParams

correctness_metric = GEval(
    name="Correctness",
    criteria="Determine whether the actual output is factually correct based on the expected output.",
    evaluation_params=[
        LLMTestCaseParams.ACTUAL_OUTPUT,
        LLMTestCaseParams.EXPECTED_OUTPUT
    ],
)

더 많은 제어를 위해 criteria 대신 명시적인 evaluation_steps를 제공할 수도 있어요.

correctness_metric = GEval(
    name="Correctness",
    evaluation_steps=[
        "Check whether the facts in 'actual output' contradict any facts in 'expected output'",
        "Heavily penalize omission of detail",
        "Vague language or contradicting opinions are OK"
    ],
    evaluation_params=[
        LLMTestCaseParams.ACTUAL_OUTPUT,
        LLMTestCaseParams.EXPECTED_OUTPUT
    ],
)

GEval 메트릭이 마음에 들면 .upload() 메서드를 호출해 Confident AI에 만들어요. 이렇게 하면 로컬 메트릭이 플랫폼에 동기화되어, metric collection에 추가하고 원격 평가를 실행할 수 있어요.

correctness_metric.upload()

Confident AI에 이미 있는 메트릭이라면 이름으로 로컬 GEval 인스턴스에 끌어올 수 있어요. .pull() 메서드는 플랫폼에서 criteria, evaluation_steps, evaluation_params, rubric을 채워 넣어요.

from deepeval.metrics import GEval

correctness_metric = GEval(name="Correctness")
correctness_metric.pull()

Multi-turn

전체 대화 평가에는 ConversationalGEval을 사용해요.

from deepeval.metrics import ConversationalGEval
from deepeval.test_case import TurnParams

professionalism_metric = ConversationalGEval(
    name="Professionalism",
    criteria="Determine whether the assistant has acted professionally throughout the conversation.",
    evaluation_params=[TurnParams.ROLE, TurnParams.CONTENT],
)

ConversationalGEval 메트릭이 마음에 들면 .upload() 메서드를 호출해 Confident AI에 만들어요. 이렇게 하면 로컬 메트릭이 플랫폼에 동기화되어, metric collection에 추가하고 원격 평가를 실행할 수 있어요.

professionalism_metric.upload()

Confident AI에 이미 있는 multi-turn 메트릭이라면 이름으로 로컬 ConversationalGEval 인스턴스에 끌어올 수 있어요. .pull() 메서드는 플랫폼에서 criteria, evaluation_steps, evaluation_params, rubric을 채워 넣어요.

from deepeval.metrics import ConversationalGEval

professionalism_metric = ConversationalGEval(name="Professionalism")
professionalism_metric.pull()

파라미터, rubric, 고급 사용법에 대한 자세한 내용은 deepeval 문서의 GEval과 ConversationalGEval을 확인하세요.

내부적으로 .upload()는 Confident API를 호출해 커스텀 G-Eval 메트릭을 만듭니다. G-Eval 메트릭의 이름은 Confident AI 프로젝트에서 이미 사용 중이면 안 됩니다.

Single-turn

Request (POST /v1/metrics) — API reference

curl -X POST "https://api.confident-ai.com/v1/metrics" \
  -H "CONFIDENT_API_KEY: <PROJECT-API-KEY>" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Correctness",
  "criteria": "Determine if the `actual output` is correct based on the `expected output`.",
  "evaluationParams": [
    "actualOutput",
    "expectedOutput"
  ],
  "multiTurn": false
}'
import requests

response = requests.post(
    "https://api.confident-ai.com/v1/metrics",
    headers={
        "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
    },
    json={
        "name": "Correctness",
        "criteria": "Determine if the `actual output` is correct based on the `expected output`.",
        "evaluationParams": [
            "actualOutput",
            "expectedOutput"
        ],
        "multiTurn": False
    },
)

print(response.json())
const response = await fetch("https://api.confident-ai.com/v1/metrics", {
  method: "POST",
  headers: {
    "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Correctness",
    "criteria": "Determine if the `actual output` is correct based on the `expected output`.",
    "evaluationParams": [
      "actualOutput",
      "expectedOutput"
    ],
    "multiTurn": false
  }),
});

const data = await response.json();
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	body := "{\n  \"name\": \"Correctness\",\n  \"criteria\": \"Determine if the `actual output` is correct based on the `expected output`.\",\n  \"evaluationParams\": [\n    \"actualOutput\",\n    \"expectedOutput\"\n  ],\n  \"multiTurn\": false\n}"

	req, err := http.NewRequest("POST", "https://api.confident-ai.com/v1/metrics", strings.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Example {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "name": "Correctness",
              "criteria": "Determine if the `actual output` is correct based on the `expected output`.",
              "evaluationParams": [
                "actualOutput",
                "expectedOutput"
              ],
              "multiTurn": false
            }""";

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.confident-ai.com/v1/metrics"))
            .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::Client::new()
        .post("https://api.confident-ai.com/v1/metrics")
        .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
        .json(&json!({
          "name": "Correctness",
          "criteria": "Determine if the `actual output` is correct based on the `expected output`.",
          "evaluationParams": [
            "actualOutput",
            "expectedOutput"
          ],
          "multiTurn": false
        }))
        .send()
        .await?;

    println!("{}", response.text().await?);

    Ok(())
}

Multi-turn

Request (POST /v1/metrics) — API reference

curl -X POST "https://api.confident-ai.com/v1/metrics" \
  -H "CONFIDENT_API_KEY: <PROJECT-API-KEY>" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Relevancy",
  "criteria": "Determine if the assistant answers are relevant to what the user is asking.",
  "multiTurn": true
}'
import requests

response = requests.post(
    "https://api.confident-ai.com/v1/metrics",
    headers={
        "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
    },
    json={
        "name": "Relevancy",
        "criteria": "Determine if the assistant answers are relevant to what the user is asking.",
        "multiTurn": True
    },
)

print(response.json())
const response = await fetch("https://api.confident-ai.com/v1/metrics", {
  method: "POST",
  headers: {
    "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Relevancy",
    "criteria": "Determine if the assistant answers are relevant to what the user is asking.",
    "multiTurn": true
  }),
});

const data = await response.json();
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	body := `{
  "name": "Relevancy",
  "criteria": "Determine if the assistant answers are relevant to what the user is asking.",
  "multiTurn": true
}`

	req, err := http.NewRequest("POST", "https://api.confident-ai.com/v1/metrics", strings.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Example {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "name": "Relevancy",
              "criteria": "Determine if the assistant answers are relevant to what the user is asking.",
              "multiTurn": true
            }""";

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.confident-ai.com/v1/metrics"))
            .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::Client::new()
        .post("https://api.confident-ai.com/v1/metrics")
        .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
        .json(&json!({
          "name": "Relevancy",
          "criteria": "Determine if the assistant answers are relevant to what the user is asking.",
          "multiTurn": true
        }))
        .send()
        .await?;

    println!("{}", response.text().await?);

    Ok(())
}

내부적으로 .pull()은 Confident AI 프로젝트에서 이름으로 기존 G-Eval 메트릭을 가져옵니다.

Single-turn

Request (GET /v1/metric/{name}) — API reference

curl -X GET "https://api.confident-ai.com/v1/metric/{name}" \
  -H "CONFIDENT_API_KEY: <PROJECT-API-KEY>"
import requests

response = requests.get(
    "https://api.confident-ai.com/v1/metric/{name}",
    headers={
        "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
    },
)

print(response.json())
const response = await fetch("https://api.confident-ai.com/v1/metric/{name}", {
  method: "GET",
  headers: {
    "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
  },
});

const data = await response.json();
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.confident-ai.com/v1/metric/{name}", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.confident-ai.com/v1/metric/{name}"))
            .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
            .GET()
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::Client::new()
        .get("https://api.confident-ai.com/v1/metric/{name}")
        .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
        .send()
        .await?;

    println!("{}", response.text().await?);

    Ok(())
}

Multi-turn

Request (GET /v1/metric/{name}) — API reference

curl -X GET "https://api.confident-ai.com/v1/metric/{name}" \
  -H "CONFIDENT_API_KEY: <PROJECT-API-KEY>"
import requests

response = requests.get(
    "https://api.confident-ai.com/v1/metric/{name}",
    headers={
        "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
    },
)

print(response.json())
const response = await fetch("https://api.confident-ai.com/v1/metric/{name}", {
  method: "GET",
  headers: {
    "CONFIDENT_API_KEY": "<PROJECT-API-KEY>",
  },
});

const data = await response.json();
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.confident-ai.com/v1/metric/{name}", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Example {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.confident-ai.com/v1/metric/{name}"))
            .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
            .GET()
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::Client::new()
        .get("https://api.confident-ai.com/v1/metric/{name}")
        .header("CONFIDENT_API_KEY", "<PROJECT-API-KEY>")
        .send()
        .await?;

    println!("{}", response.text().await?);

    Ok(())
}

더 알아보기