Posts [LLM] JSON 스키마 LLM 연동
Post
Cancel

[LLM] JSON 스키마 LLM 연동

LLM 기반 인터뷰 기능을 개발하면서 질문에 대한 사용자 답변이 지정된 평가 기준들에 부합하는지를 LLM 을 통해 피드백 받는 로직을 구현하면서 LLM 응답을 단순 텍스트 파싱처리외에 좀 더 안전하게 처리할 수 있는 방법이 없을까 고민하다 Structured Outputs(구조화된 출력)에 대해 알게 되었다.

또한 이전까지 제품에서의 대부분은 LLM 기능은 응답을 텍스트로만 받아 파싱하여 처리하는 구조다보니 가끔 잘못된 형식을 응답받을때가 있어 애플리케이션 로직상에서 예외처리를 해줬었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
== prompt ===

### Role

### Task

### Constraint

### Example

### Input
xxx

### Output

#### Feedback 
aaa

#### CORRESPOND 
bbb

== response ==
#### Feedback aaa // 간헐적으로 의도치 않은 포맷으로 응답

#### CORRESPOND
bbb

하지만 json_schema 를 지정함으로써 여러 필드를 응답받을때 좀 더 신뢰성 있는 구조를 가져갈수있다.(텍스트 파싱 처리보단 좀 더 안전하게 구현 가능하다)

Structured Outputs (구조화된 출력)

Structured Outputs(구조화된 출력)은 모델이 항상 제공된 JSON 스키마를 준수하는 응답을 생성하도록 보장하는 기능이므로 모델이 필수 키를 생략하거나 잘못된 열거형 값을 나타내는 것에 대해 걱정할 필요가 없게된다.

구조화된 출력의 이점은 아래와 같다.

  • 1. 신뢰할 수 있는 type 안전성: 잘못 포맷화된 응답을 검증하거나 다시 시도할 필요가 없다.
  • 2. 명시적 거부: 안전 기반 모델 거부는 이제 프로그래밍 방식으로 감지할 수 있다.
  • 3. 더 간단한 프롬프트: 일관된 형식을 달성하기 위해 강력한 표현의 프롬프트가 필요하지 않는다.

지원 모델

Structured Outputs 은 ‘GPT-4o’ 모델부터 사용 가능하다.

  • ‘gpt-4o-mini-2024-07-18’ 이후 모델
  • ‘gpt-4o-2024-08-06’ 이후 모델

사용 방법

Structured Outputs는 OpenAI API에서 두 가지 형태로 제공된다.

  • 1)function calling: 시스템의 도구, 함수, 데이터 등에 모델을 연결하는 경우에 사용한다.
  • 2)json_schema: 사용자에게 응답할 때 모델의 출력을 구조화하여 응답 형식을 지정하는 경우에 사용한다.

OpenAI API에서 function calling과 response_format을 통한 구조화된 출력을 사용할 때, 각각의 접근 방식이 적합한 상황을 이해하는 것이 중요하다. 다음은 각 방법에 대한 상세한 설명과 예시입니다.

Function Calling

Function calling은 언어 모델을 외부 도구, 시스템 또는 데이터 소스와 연결해야 할 때 이상적이다. 이 접근 방식은 모델이 컨텍스트에 따라 함수 호출을 제안할 수 있게 하며, 이를 애플리케이션 내에서 실행할 수 있다. 다음은 function calling이 유용한 시나리오이다.

  • 실시간 데이터 접근: 날씨 업데이트나 주식 가격과 같은 실시간 데이터가 필요한 애플리케이션에서는 이러한 데이터 소스를 액세스하는 함수를 정의할 수 있다. 모델은 필요한 매개변수를 포함하여 적절한 함수 호출을 생성할 수 있다.
  • 데이터베이스 쿼리: 사용자 주문 세부 정보나 재고 정보를 검색해야 하는 애플리케이션에서는 function calling을 통해 모델이 실행 가능한 쿼리를 생성할 수 있다.
  • 인터랙티브 애플리케이션: 회의 일정 잡기나 이메일 보내기와 같이 사용자 입력에 따라 작업을 수행해야 하는 애플리케이션에서는 function calling을 통해 프로그램적으로 처리할 수 있는 구조화된 호출을 생성할 수 있다.

예시

날씨 정보를 제공하는 챗봇을 가정해보자. “get_weather”라는 함수를 정의하고 위치를 매개변수로 받는다. 사용자가 파리의 날씨를 물어보면, 모델은 “파리”를 인수로 하는 이 함수 호출을 생성한다. 애플리케이션은 이 함수를 실행하여 사용자에게 날씨 데이터를 제공한다.

response_format을 통한 구조화된 출력

response_format을 통한 구조화된 출력은 모델의 응답이 특정 JSON 스키마를 따르도록 하고 싶을 때 적합하다. 이 방법은 출력이 일관되게 포맷되도록 보장하며, 특히 구조화된 데이터를 표시하거나 다른 시스템에 통합해야 하는 애플리케이션에 유용하다.

  • UI 생성: 모델 출력에 기반하여 UI를 생성하는 애플리케이션에서는 구조화된 응답 형식을 사용하여 응답의 각 부분이 미리 정의된 UI 구성 요소에 맞도록 보장한다.
  • 일관된 데이터 포맷팅: 수학 튜터링이나 감정 분석처럼 일관된 출력 구조가 중요한 애플리케이션에서는 JSON 스키마를 정의함으로써 응답의 일관성을 유지할 수 있다.

예시

수학 튜터링 애플리케이션에서 “문제 설명”, “해결 단계”, “최종 답변”과 같은 특정 필드를 포함하는 응답이 필요하다고 가정해보자. 이러한 필드를 가진 JSON 스키마를 정의함으로써, 모델의 모든 응답이 이 정보를 구조화된 형식으로 포함하도록 할 수 있다.

Java로 response_format 을 JSON 으로 지정하여 OpenAI API 연동하기

먼저, OpenAI의 Java 클라이언트를 사용하여 API를 호출할 수 있다. 이 예제에서는 JSON 스키마를 사용하여 레시피 데이터를 생성하는 방법을 보여준다.

메이븐 Dependency 설정

Java 프로젝트에서 OpenAI API를 사용하려면 openai-java 라이브러리를 추가해야 합니다. Maven을 사용하는 경우 pom.xml 파일에 다음 의존성을 추가한다.

1
2
3
4
5
<dependency>
    <groupId>com.openai</groupId>
    <artifactId>openai-java</artifactId>
    <version>1.0.0</version>
</dependency>

JSON 스키마 정의

JSON 스키마는 생성할 데이터의 구조를 정의하는것이다. 아래는 레시피 생성 데이터를 위한 JSON 스키마의 예이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  "type": "object",
  "properties": {
    "ingredients": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {"type": "string"},
          "amount": {"type": "number"},
          "unit": {"type": "string", "enum": ["grams", "ml", "cups", "pieces"]}
        },
        "additionalProperties": false,
        "required": ["name", "amount", "unit"]
      }
    },
    "instructions": {
      "type": "array",
      "items": {"type": "string"}
    },
    "time_to_cook": {"type": "number"}
  },
  "additionalProperties": false,
  "required": ["ingredients", "instructions", "time_to_cook"]
}

API 호출 클라이언트 코드

아래는 Java로 OpenAI API를 호출하여 지정된 JSON 스키마 기반으로 응답을 받는 예제 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.util.HashMap;
import java.util.Map;

public class JsonSchema {

    public static Map<String, Object> getRecipeSchema() {
        Map<String, Object> schema = new HashMap<>();
        schema.put("type", "object");

        Map<String, Object> properties = new HashMap<>();

        // Ingredients array
        Map<String, Object> ingredients = new HashMap<>();
        ingredients.put("type", "array");
        
        Map<String, Object> ingredientItem = new HashMap<>();
        ingredientItem.put("type", "object");
        ingredientItem.put("properties", Map.of(
            "name", Map.of("type", "string"),
            "amount", Map.of("type", "number"),
            "unit", Map.of("type", "string", "enum", new String[]{"grams", "ml", "cups", "pieces"})
        ));
        ingredientItem.put("required", new String[]{"name", "amount", "unit"});
        ingredientItem.put("additionalProperties", false);  // Prevent additional properties
        ingredients.put("items", ingredientItem);

        // Instructions array
        Map<String, Object> instructions = new HashMap<>();
        instructions.put("type", "array");
        instructions.put("items", Map.of("type", "string"));

        // Time to cook
        Map<String, Object> timeToCook = new HashMap<>();
        timeToCook.put("type", "number");

        // Add all properties to schema
        properties.put("ingredients", ingredients);
        properties.put("instructions", instructions);
        properties.put("time_to_cook", timeToCook);

        schema.put("properties", properties);
        schema.put("required", new String[]{"ingredients", "instructions", "time_to_cook"});
        
        // Prevent additional properties at the root level
        schema.put("additionalProperties", false);

        return schema;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import com.openai.api.OpenAiApi;
import com.openai.api.models.CompletionRequest;
import com.openai.api.models.CompletionResponse;

import java.util.List;
import java.util.Map;

public class OpenAiJsonClient {

    public static void main(String[] args) {
        // OpenAI API 키 설정
        String apiKey = System.getenv("OPENAI_API_KEY");
        OpenAiApi openAiApi = new OpenAiApi(apiKey);

        // JSON 스키마 가져오기
        Map<String, Object> schema = JsonSchema.getRecipeSchema();

        // ChatCompletion 요청 생성
        CompletionRequest request = new CompletionRequest.Builder()
            .model("gpt-4o-2024-08-06")
            .messages(List.of(
                Map.of("role", "system", "content", "You are a helpful assistant."),
                Map.of("role", "user", "content",
                       "Provide a recipe for spaghetti bolognese in JSON format.")
            ))
            .responseFormat(Map.of(
                "type", "json_schema",
                "json_schema", schema // 스키마 지정
            ))
            .temperature(0)
            .build();

        try {
            // API 호출 및 응답 처리
            CompletionResponse response = openAiApi.createCompletion(request);
            String jsonResponse = response.getChoices().get(0).getMessage().getContent();
            System.out.println(jsonResponse);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

출처

This post is licensed under CC BY 4.0 by the author.

[AWS] AMI & EBS 를 활용한 백업

[AWS] ECS