Flint, 시맨틱 스펙으로 차트를 만드는 AI 시대의 시각화 언어 (feat. Microsoft)

Flint 소개

Flint는 AI 에이전트가 간결하고 사람이 직접 고칠 수 있는 차트 스펙으로부터 표현력 있고 보기 좋은 시각화를 안정적으로 만들어 내도록 설계된 시각화 중간 언어(Visualization Intermediate Language) 입니다. 마이크로소프트는 이 프로젝트에 "AI 시대를 위한 시각화 언어(A Visualization Language for the AI Era)" 라는 부제를 붙였는데, 이 표현은 Flint가 풀려는 문제를 잘 요약합니다. 대규모 언어 모델(LLM)이 데이터로부터 차트를 그리려 할 때, 지금까지는 Vega-LiteECharts, Chart.js 같은 렌더링 라이브러리의 장황한 설정을 직접 생성해야 했습니다. 이 방식은 스케일, 축, 간격, 레이블, 레이아웃처럼 서로 얽힌 저수준 파라미터를 일일이 조율해야 하고, 필드 하나만 바꿔도 스펙 전체가 쉽게 깨진다는 문제가 있었습니다.

Flint의 접근은 다릅니다. 개발자나 에이전트는 각 데이터 필드가 무엇을 의미하는지어떤 차트를 원하는지 만 선언하고, 나머지 저수준 결정(축 유형, 0 기준선, 숫자 포맷, 색상, 크기, 레이아웃)은 Flint 컴파일러가 데이터와 시맨틱 타입으로부터 자동으로 유도합니다. 그 결과로 나오는 것은 에이전트가 안정적으로 생성할 수 있고, 사람이 직접 편집할 수 있으며, 여러 백엔드가 각자의 네이티브 스펙으로 렌더링할 수 있는 압축된 차트 명세입니다. 프로그래밍 언어에서 중간 표현(Intermediate Representation) 이 프로그램 로직과 목표 기계어를 분리하듯, Flint는 데이터 시맨틱(data semantics)차트 의도(chart intent) 를 분리합니다.

Flint는 마이크로소프트 리서치(Microsoft Research)가 중국 런민대학교(Renmin University of China)의 IDEAS Lab과 협업하여 개발했으며, MIT 라이선스로 공개된 오픈소스입니다. JavaScript/TypeScript 라이브러리(flint-chart)와 에이전트용 MCP 서버(flint-chart-mcp)의 두 가지 형태로 제공되며, 하나의 입력으로 Vega-Lite, ECharts, Chart.js에 걸쳐 30종 이상의 차트 타입을 만들어 낼 수 있습니다. Python 패키지는 이후 릴리스에서 제공될 예정이며, 현재는 소스 형태의 프리뷰만 저장소에 포함되어 있습니다. 참고로 Flint를 설명하는 연구 논문도 곧 공개될 예정이라고 합니다.

선언적 문법의 한계, 왜 새로운 중간 언어가 필요했나

Vega-Lite나 ECharts 같은 선언적 시각화 문법(declarative grammar) 은 원시 데이터 타입이 시각적 매핑과 잘 맞아떨어질 때는 훌륭하게 동작합니다. 문제는 데이터의 저장 표현(storage representation)의미(semantic meaning) 가 어긋날 때 발생합니다. Flint 문서는 이런 취약점을 세 가지 예시로 설명합니다.

  • 연월 정수의 오해: 202001이라는 정수는 사실 연월(YearMonth) 을 뜻하지만, 문법은 이를 양적 크기(quantitative magnitude)로 취급해 버리기 쉽습니다.
  • 비가산 측정값의 스택: 온도나 비율처럼 서로 더할 수 없는 값을 누적 막대(stacked bar)로 쌓으면 의미가 왜곡됩니다.
  • 발산형 필드의 잘못된 색상: 음수와 양수를 오가는 발산형(diverging) 필드를 단방향 순차 색상 램프(sequential color ramp)에 매핑하면 데이터의 방향성이 사라집니다.

숙련된 전문가라면 길고 서로 얽힌 스펙으로 이런 경우를 해결할 수 있습니다. 그러나 그렇게 손으로 튜닝한 스펙은 필드를 교체하거나, 히트맵을 회전하거나, 차트 타입을 바꾸는 순간 정확성을 유지하기가 매우 어렵습니다. 이 취약함은 사람에게도 부담이지만, 매번 스펙 전체를 다시 생성해야 하는 LLM 에이전트에게는 특히 치명적입니다. Flint는 바로 이 지점에서 시맨틱 타입을 일급 객체(first-class object) 로 다루고, 인코딩과 레이아웃을 시맨틱과 데이터 특성으로부터 해석해 냄으로써 문제를 근본적으로 다르게 접근합니다.

Flint 스펙, dataSpec과 chartSpec의 분리

Flint 프로그램은 재사용 가능한 두 부분으로 이루어집니다. 하나는 데이터가 무엇인지 를 말하는 dataSpec 이고, 다른 하나는 그것을 어떻게 볼지 를 말하는 chartSpec 입니다. 원시 데이터 행은 data에 담기고, 이 셋이 합쳐져 ChartAssemblyInput이라는 하나의 입력을 이룹니다.

Flint 용어 API 필드 역할
dataSpec semantic_types 필드별 의미를 타입 문자열 또는 확장 애노테이션으로 지정
chartSpec chart_spec 차트 타입과 채널에서 필드로의 바인딩을 지정

아래는 월별 가입자 수를 그리는 가장 작은 Flint 스펙 전체입니다. monthYearMonth이므로 Flint는 2024-01 같은 문자열을 날짜로 다루고, signupsQuantity이므로 숫자 축을 부여합니다.

{
  "data": {
    "values": [
      { "month": "2024-01", "signups": 120 },
      { "month": "2024-02", "signups": 146 },
      { "month": "2024-03", "signups": 168 },
      { "month": "2024-04", "signups": 164 },
      { "month": "2024-05", "signups": 181 }
    ]
  },
  "semantic_types": {
    "month": "YearMonth",
    "signups": "Quantity"
  },
  "chart_spec": {
    "chartType": "Line Chart",
    "encodings": {
      "x": { "field": "month" },
      "y": { "field": "signups" }
    },
    "baseSize": { "width": 420, "height": 280 }
  }
}

이 입력을 컴파일하는 방법은 백엔드마다 함수 하나를 호출하는 것뿐이며, 입력의 형태는 전혀 바꿀 필요가 없습니다. assembleVegaLite(input)은 Vega-Lite v6 스펙을, assembleECharts(input)은 ECharts option을, assembleChartjs(input)은 Chart.js 설정을 반환합니다.

import { assembleVegaLite, assembleECharts, assembleChartjs } from 'flint-chart';

const spec = assembleVegaLite(input);      // → a ready-to-render Vega-Lite spec
const echartsOption = assembleECharts(input);
const chartjsConfig = assembleChartjs(input);

아래 그림은 이 컴파일 과정을 한눈에 보여줍니다. 왼쪽의 압축된 Flint 스펙(chartType: Heatmap, colorScheme: redblue)이 가운데의 장황한 Vega-Lite 스펙으로 확장되고, 오른쪽의 히트맵으로 렌더링됩니다. 사용자가 작성한 것은 왼쪽의 짧은 스펙뿐이지만, Flint가 도메인 계산(domain: [-84108, 84108], domainMid: 0)과 스케일, 마크 설정을 모두 채워 넣은 것을 볼 수 있습니다.

시맨틱 타입은 확장 애노테이션도 지원합니다. 예를 들어 지역 필드에 고유한 정렬 순서를 지정하고 싶다면, 타입 문자열 대신 객체로 sortOrder를 함께 지정할 수 있습니다.

{
  "semantic_types": {
    "region": {
      "semanticType": "Category",
      "sortOrder": ["N", "E", "S", "W"]
    }
  }
}

3단계 컴파일러 파이프라인

Flint의 핵심은 라이브러리에 종속되지 않는 컴파일러입니다. 모든 assemble*() 진입점은 동일한 컴파일러 프론트엔드(compiler frontend)옵티마이저(optimizer) 를 공유하며, 오직 마지막 코드 생성기(code generator) 만 백엔드에 따라 달라집니다. 이 구조 덕분에 새로운 렌더링 백엔드를 추가하려면 마지막 단계만 구현하면 되고, 프론트엔드와 옵티마이저는 그대로 재사용됩니다.

파이프라인은 프로그래밍 언어 컴파일러의 구조를 그대로 빌려옵니다. 프론트엔드가 의미를 해석하고, 옵티마이저가 최적의 레이아웃을 찾고, 코드 생성기가 목표 백엔드의 네이티브 코드를 뽑아냅니다.

assembleVegaLite(input)  // 또는 assembleECharts, assembleChartjs
       │
       ▼
STAGE 1 — 컴파일러 프론트엔드 (core/)
       │  resolveChannelSemantics()  →  ChannelSemantics
       ▼
STAGE 2 — 옵티마이저 (core/)
       │  computeLayout(), filterOverflow()  →  LayoutResult
       ▼
STAGE 3 — 코드 생성기 (백엔드별)
       │  template.instantiate()  →  VL / EC / CJS 스펙
       ▼
   네이티브 스펙 + 선택적 경고(warnings)

1단계 컴파일러 프론트엔드, 의미를 해석하다

프론트엔드는 semantic_types와 데이터로부터 채널별 시맨틱을 해석합니다. 이 해석은 두 층위로 나뉩니다. 필드 속성(field properties) 은 차트와 무관하게 각 열의 포맷 클래스, 집계 역할, 도메인 형태, 발산 힌트, 정렬 순서를 결정합니다. 채널 속성(channel properties) 은 차트 맥락에 근거합니다. 예컨대 같은 YearMonth 필드라도 선형 차트의 x축에서는 시간형(temporal)으로, 다른 뷰의 색상 채널에서는 범주형(categorical)으로 다르게 다뤄집니다. 이 채널 시맨틱이 있기에 연월 정수가 양적 크기로 오해받는 일이 방지됩니다. 최종 산출물은 ChannelSemantics라는 백엔드 중립적 레코드로, 레이아웃과 모든 템플릿이 이를 소비합니다. 시맨틱 타입은 T0 → T1 → T2의 계층 구조를 가지므로, 에이전트가 거친 레이블만 제공해도 우아하게 저하(graceful degradation)됩니다.

2단계 옵티마이저, 캔버스에 레이아웃을 맞추다

옵티마이저는 목표 크기(baseSize)와 선택적 상한(canvasSize)을 받아, 주어진 공간 안에서 차트가 읽기 좋게 유지되는 LayoutResult를 만들어 냅니다. 이 단계가 Flint의 자동 레이아웃(Auto Layout) 능력의 핵심인데, 문서는 물리 기반 비유로 이를 설명합니다.

  • 이산 축(discrete axes): 막대나 히트맵 셀처럼 개수가 정해진 축은 탄력 예산(elastic budget) 모델을 따릅니다. 읽을 수 있는 최소 간격까지 압축하되, 필요하면 캔버스를 늘립니다.
  • 연속 축(continuous axes): 산점도나 선 차트처럼 연속적인 축은 가스 압력(gas pressure) 모델을 따릅니다. 마크 밀도가 겹침 한계를 넘으면 캔버스를 늘려 압력을 해소합니다.
  • 전역 최적화(global optimization): 연결된 마크의 종횡비를 45도 기준으로 조정하는 뱅킹(banking-to-45°), 패싯(facet) 행/열의 줄바꿈, 트리맵이나 게이지, 파이처럼 비(非)직교 차트의 크기 결정이 여기에 속합니다.

이산 카디널리티가 캔버스 예산을 초과하면, 옵티마이저는 읽을 수 없는 차트를 렌더링하는 대신 데이터를 필터링하고 ChartWarning 메타데이터를 첨부합니다. 즉, 과부하가 걸린 차트를 조용히 망가뜨리는 대신 명시적으로 경고 하는 것입니다.

3단계 코드 생성기, 네이티브 스펙을 뽑아내다

chartType은 하나의 동적 템플릿(dynamic template, ChartTemplateDef) 으로 등록됩니다. 템플릿은 공개 이름("Grouped Bar Chart"), 네이티브 스펙 골격, 허용되는 인코딩 채널, 마크의 인지 채널(markCognitiveChannel: position/length/area/color), 그리고 최적화된 맥락을 실제 스펙으로 변환하는 instantiate() 훅을 가집니다. Vega-Lite, ECharts, Chart.js는 각각 별도의 템플릿 레지스트리(vlTemplateDefs, ecTemplateDefs, cjsTemplateDefs)를 갖습니다. 새 백엔드를 추가할 때는 이 3단계만 구현하면 되며, 프론트엔드와 옵티마이저는 손대지 않습니다.

Named View, 스펙을 다시 쓰지 않고 차트를 변형하기

Flint의 흥미로운 설계 중 하나는 네임드 뷰(Named View) 입니다. 사용자나 에이전트에게 차트 스펙을 통째로 다시 쓰라고 요구하는 대신, Flint는 인코딩 배치에 대한 작은 변환을 이름 붙은 뷰로 노출합니다. 축을 뒤집거나, 범주 축과 색상 계열을 맞바꾸거나, 계열을 패싯으로 보내거나, 같은 필드를 형제 차트 타입으로 다시 그리는 식입니다. 호스트는 선택된 상태 ID(chart_spec.chartProperties.pivot)만 저장하고, 컴파일러가 그로부터 결과 인코딩 맵을 다시 계산합니다.

이 모델은 군론적(group-theoretic) 이지만 의도적으로 실용적입니다. 원래 배치에서 출발해 네 개의 연산자가 생성하는 궤도(orbit)를 따라 걷습니다.

기호 생성자 상태 ID 예시 의미
τ transpose flip:x-y 두 축 슬롯을 통째로 뒤집기
σ permute swap:y-color 위치 필드와 같은 프로파일의 보조 채널을 교환
γ shift series:row 이산 계열 필드를 color/group/facet 채널 사이로 이동
θ transition type:Strip Plot 같은 필드를 형제 템플릿으로 다시 렌더링

화면에 보이는 View 컨트롤은 유효성 검사와 중복 제거를 거친 유한한 궤도입니다. 중복 제거는 군론의 안정자 몫(stabilizer quotient)을 구체화한 것으로, 축을 두 번 뒤집으면 기본값으로 돌아오고, 산점도 → 스트립 플롯 → 산점도의 왕복이 원래 산점도로 접히는 식입니다. 호환성 검사도 타입에 기반해서, σ는 같은 필드 프로파일 안에서만(측정값끼리, 범주끼리) 교환하고, 선형 차트는 τ를 생략하므로 Flint는 결코 수직 선형 차트를 제안하지 않습니다. 이 궤도가 Flint의 백엔드 중립 인코딩 IR 위에서 계산되기 때문에, 동일한 View 상태 ID가 Vega-Lite, ECharts, Chart.js에 그대로 적용됩니다.

dataSpec 한 번, chartSpec 여러 번, 탐색 워크플로우

Flint 설계의 실질적 이점은 탐색(exploration) 워크플로우에서 드러납니다. LLM 에이전트는 보통 데이터셋에 대해 semantic_types를 한 번만 추론하고, 그다음부터는 chart_spec만 바꿔 가며 여러 시각을 시도합니다. dataSpec은 고정된 채로, 선형 차트에서 히트맵, 그룹 막대, 워터폴, 선버스트로 옮겨 다니는 것이죠. 백엔드를 Vega-Lite에서 ECharts로 바꾸는 것도 Flint 입력을 다시 쓸 필요 없이 컴파일 함수만 교체하면 됩니다.

아래 그림은 이 흐름을 구체적으로 보여줍니다. 위쪽에서 입력 데이터 테이블(a)과 dataSpec(b), chartSpec(c)이 결합해 지역별 패싯 선형 차트(d)를 만들고, 아래쪽에서는 chartSpec의 chartType과 채널만 바꿔 가며 그룹 막대 → 워터폴 → 히트맵 → (ECharts로 전환한) 선버스트로 같은 데이터를 다르게 조망합니다.

파이프라인 각 단계의 상세한 설명은 아키텍처 문서개요 문서에서 확인할 수 있고, 처음 시작하는 독자는 Getting Started 가이드를 따라가면 좋습니다. 이 구조는 Flint의 온라인 에디터에서 직접 체험할 수 있습니다. JSON을 붙여 넣고 Vega-Lite, ECharts, Chart.js 백엔드를 실시간으로 전환하며 결과를 비교해 볼 수 있으며, 갤러리에서는 모든 템플릿과 백엔드별 커버리지를 확인할 수 있습니다.

시맨틱 타입과 차트 타입, 무엇을 표현할 수 있나

Flint의 표현력은 등록된 시맨틱 타입(semantic type)차트 타입(chart type) 의 카탈로그에서 나옵니다. 시맨틱 타입은 70종 이상이 등록되어 있고, 필드에 가장 구체적인 타입을 붙이는 것이 가장 중요한 작업입니다. 타입 선택 하나만으로 포맷, 0 기준선, 색상 스킴, 스케일 방향이 자동으로 결정되기 때문입니다.

계열 시맨틱 타입 예시
시간(시점) DateTime, Date, Time, Timestamp
시간(단위) Year, Quarter, Month, Week, Day, YearMonth, YearQuarter, Decade
측정(양) Amount, Price, Quantity, Count, Number
측정(비율) Percentage
측정(부호/발산) Profit, PercentageChange, Sentiment, Correlation
측정(물리) Temperature
순위/이산 Rank, Score, ID
지리(좌표) Latitude, Longitude
지리(장소) Country, State, City, Region, ZipCode
범주 Category, Name, Status, Boolean, Direction

타입을 잘 고르면 다음과 같은 것들이 자동으로 따라옵니다. PriceAmount는 통화 포맷과 0 기준선, 순차 색상을 얻고, Temperature는 발산형 색상 스킴을 얻되 0 기준선을 강제하지 않습니다. Correlation은 고정된 [-1, 1] 발산 도메인을, Rank는 1이 위로 오는 역축(reversed axis)을 얻습니다. 무엇을 골라야 할지 모를 때는 숫자에 Quantity, 문자열에 Category, 날짜 형태 값에 Date를 쓰되, 타입 이름을 임의로 지어내서는 안 됩니다.

차트 타입은 Vega-Lite 기준으로 산점도(Scatter Plot), 회귀(Regression), 막대(Bar Chart), 그룹 막대(Grouped Bar Chart), 누적 막대(Stacked Bar Chart), 히트맵(Heatmap), 선형(Line Chart), 스파크라인(Sparkline), 범프(Bump Chart), 영역(Area Chart), 바이올린(Violin Plot), 스트림그래프(Streamgraph), 파이(Pie Chart), 로즈(Rose Chart), 레이더(Radar Chart), 캔들스틱(Candlestick Chart), 워터폴(Waterfall Chart), 지도(Map), 코로플레스(Choropleth) 등 30종 이상이 등록되어 있습니다. ECharts 백엔드는 여기에 캘린더 히트맵, 게이지, 퍼널(Funnel), 트리맵(Treemap), 선버스트(Sunburst), 생키(Sankey), 평행 좌표(Parallel Coordinates), 그래프, 트리를 더합니다. 도넛 차트는 별도 타입 없이 파이 차트에 chartProperties.innerRadius > 0을 주어 만듭니다.

아래는 Flint가 하나의 입력에서 만들어 낼 수 있는 차트들을 Vega-Lite, ECharts, Chart.js 세 백엔드로 렌더링해 한자리에 모은 것입니다. 막대, 선형, 산점도, 히트맵, 도넛, 레이더, 스트림그래프, 박스플롯, 그룹 막대, 로즈, 생키, 트리맵까지 같은 시맨틱 계층 위에서 다양한 표현이 나오는 것을 볼 수 있습니다.

에이전트가 특히 헷갈리기 쉬운 부분은 세 종류의 막대 차트입니다. 세 차트 모두 하나의 이산 범주와 하나의 측정값을 받지만, 두 번째 범주를 표현하는 채널이 서로 다릅니다. 단일 계열이면 Bar Chart, 부분과 전체를 비교하려면 두 번째 범주를 color에 얹는 Stacked Bar Chart, 값을 나란히 비교하려면 두 번째 범주를 group 채널에 얹는 Grouped Bar Chart를 씁니다. 그룹 막대에서 클러스터링 범주를 color가 아니라 group에 두어야 한다는 점이 핵심입니다.

MCP 서버, 에이전트를 위한 실행 엔진

Flint를 에이전트 워크플로우에 붙이는 가장 직접적인 방법은 MCP(Model Context Protocol) 서버 입니다. Flint의 번들 에이전트 스킬이 에이전트에게 ChartAssemblyInput작성하는 법을 가르친다면, flint-chart-mcp 서버는 그 스펙을 실행하는 대응물입니다. 데이터와 시맨틱 차트 스펙 하나를 주면, PNG 또는 SVG로 렌더링된 차트를 Vega-Lite, ECharts, Chart.js에 걸쳐 돌려줍니다. MCP가 처음이라면 MCP 개념 학습 자료를 함께 참고하시면 좋습니다.

여기서 주목할 설계 결정은 작은 도구 표면(small tool surface) 입니다. 대부분의 차트 MCP 서버는 차트 타입마다 도구를 하나씩 노출해 26개가 넘는 도구를 갖고, 사용자의 설정을 원격 렌더 서비스로 업로드합니다. 반면 Flint는 ChartAssemblyInput이라는 단일 스키마 가 40종 가까운 차트 타입과 여러 백엔드를 아우르므로, 다섯 개의 집중된 도구만 노출하고 로컬에서, 인프로세스(in-process)로 렌더링합니다. 데이터가 호스트를 떠나지 않는다는 뜻입니다.

도구 용도
create_chart_view MCP App을 지원하는 호스트에서 선호되는 기본값. 실시간 SVG 미리보기와 차트 옵션이 있는 인터랙티브 뷰를 엶
validate_chart Flint 입력이 유효한지 확인하고 경고, 오류, 계산된 크기를 점검
render_chart 정적 PNG 또는 SVG를 로컬에서 렌더링
compile_chart 백엔드 네이티브 Vega-Lite, ECharts, Chart.js JSON을 반환
list_chart_types 지원되는 차트 타입과 인코딩 채널을 조회

create_chart_view가 여는 인터랙티브 뷰는 특히 인상적입니다. Claude Desktop 같은 MCP App 지원 호스트에서 스펙을 실시간으로 렌더링하고, Flint 자신의 옵션 모델로 만든 커스터마이징 패널(차트 타입, 채널 바인딩, 모서리 둥글기, 스택 모드, 도넛 홀 등)을 함께 보여줍니다. 렌더링과 편집이 전부 호스트 UI 안에서 돌아가므로 데이터는 밖으로 나가지 않습니다.

서버 설정은 npx로 별도 설치 없이 실행할 수 있습니다. VS Code, Claude Desktop, Cursor 등 stdio를 지원하는 모든 MCP 클라이언트에서 동작합니다.

{
  "mcpServers": {
    "flint": {
      "command": "npx",
      "args": ["-y", "flint-chart-mcp"]
    }
  }
}

데이터는 두 가지 방식으로 바인딩됩니다. 작거나 이미 준비된 테이블은 data: { values: [...] }로 직접 임베드하고, 로컬 파일은 data: { url: "..." }.json, .csv, .tsv를 참조합니다. 원격 URL은 SSRF를 막기 위해 절대 가져오지 않습니다. 기본적으로 서버는 호스트를 신뢰하여 에이전트가 지정한 로컬 파일을 읽지만, 신뢰할 수 없는 배포 환경에서는 --disable-file-reference 플래그로 로컬 파일 참조를 완전히 거부하고 인라인 행만 받도록 강화할 수 있습니다. 노출할 백엔드도 --backends vegalite,echarts처럼 시작 시점에 제한할 수 있으며, 행 수와 파일 크기, 캔버스 크기에 대한 DoS 방어 상한이 걸려 있습니다.

렌더링은 브라우저 없이 인프로세스로 이뤄집니다. Vega-Lite는 Vega로 컴파일한 뒤 헤드리스 vega.View로 SVG를 만들고 @resvg/resvg-js로 PNG를 뽑으며, ECharts는 서버사이드 SVG 렌더링을, Chart.js는 @napi-rs/canvas로 PNG를 생성합니다(Chart.js는 SVG 출력이 없어 PNG만 지원). 인프로세스 렌더 코어는 flint-chart-mcp/render로 임포트할 수 있어 빌드 스크립트에서도 재사용할 수 있습니다.

에이전트 워크플로우, Data Formulator 스타일 통합

MCP 도구로 붙이는 방식 외에, Flint는 라이브러리를 직접 임베드하는 제품 통합(product integration) 패턴도 문서로 안내합니다. 이는 마이크로소프트의 Data Formulator처럼, 에이전트가 데이터 가공과 차트 요청을 제안하되 제품(호스트)이 실제로 무엇을 실행하고 저장할지 통제하는 방식입니다. Data Formulator에 관해서는 커뮤니티에 정리된 Data Formulator, AI를 활용한 시각화 도구 글도 함께 참고할 만합니다.

이 패턴의 핵심 원칙은 "에이전트에게 Vega-Lite나 ECharts, 렌더러 코드를 1차 산출물로 작성하게 하지 말고, Flint ChartAssemblyInput을 작성하게 하라" 는 것입니다. 그러면 차트 요청이 작고, 검사 가능하며, 편집 가능해집니다. Flint를 에이전트와 렌더러 사이의 시맨틱 차트 계층(semantic chart layer) 으로 두는 셈이죠. 각 계층의 책임은 다음과 같이 명확히 나뉩니다.

  • 에이전트(Agent): 사용자 요청을 해석하고, 데이터 맥락을 살피고, 변환을 제안하고, 시맨틱 타입을 고르고, chart_spec을 작성하거나 수정합니다. 언어 모델이 유용한 시맨틱 수준의 작업만 맡습니다.

  • 호스트 제품(Host product): 데이터 변환을 실행하고, 행을 바인딩하고, 필드를 검증하고, 정책을 강제하고, 차트 상태를 저장하고, UI 컨트롤을 노출하고, 백엔드를 선택합니다. 실행과 상태, 보안, 사용자 경험의 통제권을 쥡니다.

  • Flint(컴파일러): 시맨틱 차트 요청을 결정론적 디자인 기본값과 함께 백엔드 네이티브 스펙으로 컴파일합니다.

  • 렌더러(Renderer): 백엔드 스펙을 브라우저, 노트북, 서비스, 내보내기 파이프라인에서 그립니다.

문서는 orders 원시 테이블에서 "월별 지역별 매출을 보여줘" 라는 요청을 처리하는 구체적 워크플로우를 예로 듭니다. 사용자는 개별 주문이 아니라 월별 집계를 원했으므로, 제품은 에이전트에게 집계를 Vega-Lite 변환 안에 숨기라고 요구하는 대신 두 가지를 요청합니다. 하나는 차트에 바로 쓸 수 있는 테이블을 만드는 코드(예: pandas)이고, 다른 하나는 그 파생 테이블에 대한 Flint 입력입니다. 에이전트의 좋은 응답은 데이터 변환과 차트 요청을 분리합니다.

{
  "transform_code": "import pandas as pd\nchart_df = orders.copy()\nchart_df['month'] = pd.to_datetime(chart_df['order_date']).dt.to_period('M').astype(str)\nchart_df = chart_df.groupby(['month', 'region'], as_index=False).agg(revenue=('sales', 'sum'))",
  "chart_input": {
    "data": { "values": [] },
    "semantic_types": {
      "month": "YearMonth",
      "region": "Region",
      "revenue": "Amount"
    },
    "chart_spec": {
      "chartType": "Line Chart",
      "encodings": {
        "x": { "field": "month" },
        "y": { "field": "revenue" },
        "color": { "field": "region" }
      }
    }
  }
}

호스트는 이 transform_code를 자신의 신뢰된(또는 샌드박스된) 연산 경로에서 실행하고, 결과 테이블을 검사한 뒤에야 chart_input.data.values에 행을 채워 컴파일합니다. 이 단계에서 코드가 알 수 없는 열을 쓰거나, 너무 많은 행을 만들거나, 정책 검사에 걸리면 호스트가 결과를 거부할 수 있습니다. 에이전트는 연산을 제안하고, 제품은 그것을 실행하고 유지할지 결정 하는 것입니다.

이후 사용자가 "세그먼트별로도 나눠 줘" 라고 하면, 호스트는 에이전트에게 수정을 요청하거나 segmentcolumn, row, color에 추가하는 직접 UI 조작을 노출할 수 있습니다. 어느 쪽이든 변경은 Flint 입력을 바꾼 뒤 재컴파일합니다. 렌더러 고유의 주석이나 참조선처럼 백엔드에 특화된 손질이 필요하면, Flint가 컴파일한 다음 에 최소한의 프레젠테이션 패치를 적용하고, 편집한 백엔드 JSON을 다시 Flint에 먹이지 않는 것이 원칙입니다. Flint 입력을 정본 상태로 유지해야 이식성이 보전되기 때문입니다.

에이전트에게 줄 저작 지침은 저장소의 chart-author 스킬에 정리되어 있습니다. 라이브러리를 설치하지 않았거나 실시간 카탈로그 도구를 호출할 수 없는 상황에서, 정확한 차트 타입 이름과 지원 채널, 차트 속성, 시맨틱 타입, 데이터 바인딩 규칙을 담은 이 스킬이 특히 유용합니다.

라이선스

Flint(flint-chartflint-chart-mcp)는 MIT 라이선스로 배포되고 있어, 연구 목적은 물론 상업적 용도로도 자유롭게 사용 및 수정이 가능합니다. 마이크로소프트 리서치와 런민대학교 IDEAS Lab은 새로운 차트 템플릿이나 렌더링 백엔드를 추가하는 기여를 특히 환영한다고 밝히고 있습니다.

:scroll: Flint 프로젝트 사이트

:github: microsoft/flint-chart GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 글을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 덧글로 알려주시기를 부탁드립니다. :hugs:

:pytorch:파이토치 한국 사용자 모임:south_korea:이 정리한 이 글이 유용하셨나요? 회원으로 가입하시면 주요 글들을 이메일:love_letter:로 보내드립니다! 텔레그램(Telegram)이나 Slack/Discord/Teams/Dooray/GoogleChat 등으로도 새 글 알림을 받으실 수 있습니다. :smiley:

:wrapped_gift: 아래:down_right_arrow:쪽에 좋아요:+1:를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ :star_struck: