본문 바로가기

Spring

Beyond JSON: Spring AI에서 TOON·XML·CSV·YAML로 툴 응답 포맷을 전환하는 방법

728x90
반응형
728x170

대부분의 LLM Tool Calling 응답은 JSON을 기본으로 사용하지만, 최근 개발자들 사이에서는 TOON, XML, CSV, YAML과 같은 다양한 포맷이 성능이나 토큰 효율에서 더 나은 선택이 될 수 있다는 논의가 이어지고 있다. 실제로 어떤 포맷이 유리한지는 상황에 따라 달라지기 때문에, 프로젝트 안에서 직접 실험하며 비교해보는 과정이 필요하다.

이 글에서는 Spring AI 환경에서 툴 응답을 JSON뿐 아니라 TOON, XML, CSV, YAML로 변환해 사용하는 방법을 두 가지 방식으로 설명한다. 개별 툴 단위로 변환하는 방식과 전체 툴에 일괄 적용하는 방식 모두 다루며, 실제 코드를 통해 바로 적용할 수 있도록 정리했다.

반응형

Spring AI Tool Calling 구조 간단 정리

응답 포맷 변환을 이해하기 위해 우선 Spring AI에서 Tool Calling이 어떻게 동작하는지 살펴볼 필요가 있다.

  1. 툴 정의(name, description, parameter schema)를 모델에 전달한다.
  2. 모델이 툴 실행을 결정하면, 해당 툴 이름과 JSON 형태의 입력을 반환한다.
  3. Spring AI는 같은 이름의 ToolCallback을 찾고 해당 툴을 실행한다.
  4. 툴이 실제 기능을 수행하고 결과를 반환한다.
  5. Spring AI는 이 결과를 JSON으로 직렬화한 뒤 모델에게 다시 전달한다.
  6. 모델은 툴 결과를 참고하여 최종 답변을 생성한다.

이 과정에서 응답 포맷을 JSON으로 고정하지 않고 바꿀 수 있는 지점이 두 곳 존재한다.

  • 툴이 결과를 반환한 직후(JSON 직렬화 이전)
  • JSON으로 직렬화된 뒤 모델에 전달하기 직전

이 글에서는 이 두 지점을 활용한 두 가지 방식의 변환 방법을 설명한다.


1. ToolCallResultConverter를 활용한 개별 툴 단위 포맷 변환

첫 번째 방식은 특정 툴에만 응답 포맷 변환을 적용하는 구조다.
ToolCallResultConverter 인터페이스를 구현하여 JSON 대신 원하는 포맷을 직접 생성하는 방식이다.

이 방식은 다음과 같은 순서로 동작한다.

  1. 툴이 실제 결과(Object)를 반환한다.
  2. 지정한 Converter가 직접 JSON으로 변환하거나, JSON으로 변환한 후 TOON, YAML 등으로 다시 변환한다.
  3. 변환된 결과가 모델로 전달된다.

예시로 TOON 포맷을 생성하는 Converter는 아래와 같이 구현할 수 있다.

public static class ToonToolCallResultConverter implements ToolCallResultConverter {

    private ToolCallResultConverter delegate = new DefaultToolCallResultConverter();
    
    @Override
    public String convert(@Nullable Object result, @Nullable Type returnType) {
        String json = this.delegate.convert(result, returnType);
        return JToon.encodeJson(json);
    }
}

툴 정의에 converter를 지정하면 적용된다.

@Tool(
    description = "Get random titanic passengers", 
    resultConverter = ToonToolCallResultConverter.class
)
public List<String> randomTitanicToon(
    @ToolParam(description = "Number of records to return") int count) {
    return TitanicData.getRandomTitanicPassengers(count);
}

장점

  • 툴 단위로 세밀한 제어 가능
  • 포맷별 맞춤 변환 가능

단점

  • MCP Tools(@McpTool)에는 적용 불가
  • 여러 툴을 변환할 경우 설정 중복
  • 유지보수 비용 증가

이 방식은 제한적이지만 필요한 툴에만 선택적으로 적용할 때 유용하다.


2. DelegatorToolCallbackProvider를 사용하는 전역 포맷 일괄 적용

두 번째 방식은 모든 툴 응답을 일괄적으로 변환한다. ToolCallbackProvider를 감싸는 DelegatorToolCallbackProvider를 구현하여 모든 툴 응답을 가로채고 JSON 결과를 변환하는 방식이다.

DelegatorToolCallbackProvider

public class DelegatorToolCallbackProvider implements ToolCallbackProvider {
    private final ToolCallbackProvider delegate;
    private final ResponseConverter.Format format;
    
    public DelegatorToolCallbackProvider(ToolCallbackProvider delegate, 
                                         ResponseConverter.Format format) {
        this.delegate = delegate;
        this.format = format;
    }
    
    @Override
    public ToolCallback[] getToolCallbacks() {
        return Stream.of(this.delegate.getToolCallbacks())
            .map(callback -> new DelegatorToolCallback(callback, this.format))
            .toArray(ToolCallback[]::new);
    }
}

DelegatorToolCallback

public static class DelegatorToolCallback implements ToolCallback {
    private final ToolCallback delegate;
    private final ResponseConverter.Format format;
    
    public DelegatorToolCallback(ToolCallback delegate, 
                                ResponseConverter.Format format) {
        this.delegate = delegate;
        this.format = format;
    }
    
    @Override
    public ToolDefinition getToolDefinition() {
        return this.delegate.getToolDefinition();
    }
    
    @Override
    public String call(String toolInput) {
        String jsonResponse = this.delegate.call(toolInput);
        return ResponseConverter.convert(jsonResponse, this.format);
    }
}

ResponseConverter 예시

public class ResponseConverter {
    
    public enum Format {
        TOON, YAML, XML, CSV, JSON
    }
    
    public static String convert(String json, Format format) {
        switch (format) {
            case TOON: return jsonToToon(json);
            case YAML: return jsonToYaml(toJsonNode(json));
            case XML: return jsonToXml(toJsonNode(json));
            case CSV: return jsonToCsv(toJsonNode(json));
            case JSON: return json;
        }
        throw new IllegalStateException("Unsupported format: " + format);
    }
}

적용 예시

var provider = new DelegatorToolCallbackProvider(
    toolCallbackProvider, 
    ResponseConverter.Format.TOON
);

var chatClient = chatClientBuilder
    .defaultToolCallbacks(provider)
    .build();

이제 모든 툴 응답이 TOON으로 변환된다.
XML, YAML, CSV로 변경하려면 Format만 바꾸면 된다.


포맷별 출력 비교

각 포맷이 실제로 어떤 구조를 가지는지 간단하게 비교할 필요가 있다.

JSON (기본값)

표준적이고 가장 안전하다. 모든 LLM이 잘 처리한다.

TOON

Token 사용량을 줄일 수 있다는 장점이 있지만 구조가 다소 특이하다.

XML

계층 구조는 명확하지만 토큰 사용량이 증가한다.

YAML

가독성은 좋지만 들여쓰기 오류가 발생하기 쉽다.

CSV

테이블 형태 데이터에는 효율적이지만 중첩 구조에서 부적합하다.


토큰 사용량 비교

Format Prompt Tokens Completion Tokens Total
CSV 293 522 815
TOON 308 538 846
JSON 447 545 992
YAML 548 380 928
XML 599 572 1171

CSV와 TOON이 가장 낮은 편이며 XML이 가장 높게 나타난다.
하지만 성능은 상황에 따라 달라지기 때문에 직접 실험이 필요하다.


베스트 프랙티스

  • 기본값은 JSON으로 시작하는 것이 가장 안전하다.
  • 특정 포맷이 성능이 더 좋을 것이라 가정하지 말고 반드시 자체 환경에서 벤치마크해야 한다.
  • 데이터가 복잡하면 CSV나 TOON 변환은 피하는 것이 좋다.
  • 변환 실패 대비 JSON fallback을 준비해두는 것이 안전하다.
  • 로깅과 모니터링을 통해 포맷별 성능 차이를 지속적으로 기록하는 것이 좋다.

728x90

Spring AI는 기본적으로 JSON을 사용하지만, 설계 방식이 유연하기 때문에 TOON, XML, YAML, CSV 같은 다양한 포맷으로 툴 응답을 변환할 수 있다.
툴 단위로 제어하고 싶다면 ToolCallResultConverter 방식을,
전체 툴을 일괄적으로 관리하고 싶다면 DelegatorToolCallbackProvider 방식을 선택하면 된다.

포맷별 토큰 사용량과 구조적 특성도 각기 다르기 때문에, 자신의 환경과 애플리케이션 특성에 맞춰 직접 실험해보는 것이 가장 좋은 판단 기준이 된다.

GitHub 예제를 활용해, JSON 외 다양한 포맷을 적용해보고 성능과 효율성을 비교해보길 권한다.

300x250

https://spring.io/blog/2025/11/25/spring-ai-tool-response-formats

 

Beyond JSON: Converting Spring AI Tool Response Formats to TOON, XML, CSV, YAML, ...

JSON is the go-to format for LLM tool responses, but recent discussions around alternative formats like TOON (Token-Oriented Object Notation) claim potential benefits in token efficiency and performance. While the debate continues—with critical analyses

spring.io

728x90
반응형
그리드형