Ollama의 구조화된 출력 지원
(ollama.com)- 로컬 모델 응답을 JSON Schema에 맞춰 제한할 수 있어, 후처리 파싱 부담을 줄이고 데이터 추출 결과를 더 일관되게 만들 수 있음
- 사용자는 cURL, Python, JavaScript에서
format파라미터에 스키마를 넘겨 같은 방식으로 구조화된 출력을 요청함 - Python은 Pydantic, JavaScript는 Zod를 함께 쓰면 스키마 정의와 응답 검증을 코드 레벨에서 연결하기 쉬움
- 예시는 국가 정보, 반려동물 텍스트,
llama3.2-vision이미지 분석 결과를 정해진 JSON 필드로 반환하고 검증함 - 안정적인 결과가 필요하면 프롬프트에 “return as JSON”을 넣고 temperature를 0으로 낮추는 설정이 권장됨
JSON Schema로 모델 응답 제한
- Ollama는 모델 출력이 특정 JSON Schema 형식을 따르도록 제한하는 구조화된 출력을 지원함
- Ollama Python 및 JavaScript 라이브러리도 구조화된 출력을 지원하도록 업데이트됨
- 이 기능은 다음 작업에 활용할 수 있음
- 문서에서 데이터 파싱
- 이미지에서 데이터 추출
- 언어 모델 응답의 구조화
- JSON mode보다 높은 신뢰성과 일관성 확보
설치와 호출 방식
- 최신 버전의 Ollama를 다운로드해야 함
- Python 라이브러리는 다음 명령으로 최신 버전으로 업데이트함
pip install -U ollama
- JavaScript 라이브러리는 다음 명령으로 설치함
npm i ollama
- 구조화된 출력을 요청할 때는 cURL 요청이나 Python·JavaScript 라이브러리에서
format파라미터에 스키마를 전달함
cURL로 JSON Schema 전달
- cURL 예시는
http://localhost:11434/api/chat에 요청을 보내며,model은llama3.1,stream은false로 설정함 format에는name,capital,languages를 가진 객체 스키마를 넣음name: 문자열capital: 문자열languages: 문자열 배열- 세 필드는 모두
required로 지정됨
- 응답은 요청에 포함된 JSON Schema 형식으로 반환됨
{
"capital": "Ottawa",
"languages": [
"English",
"French"
],
"name": "Canada"
}
Python과 JavaScript 라이브러리 사용
-
Python
- Ollama Python library는 스키마를 JSON 객체로
format파라미터에 전달함 - 스키마는
dict로 넘길 수 있으며, 권장 방식은 Pydantic의model_json_schema()로 직렬화하는 것임 - 예시는
Country모델에name,capital,languages필드를 정의하고, 응답을Country.model_validate_json()으로 검증함
- Ollama Python library는 스키마를 JSON 객체로
country = Country.model_validate_json(response.message.content)
- 예시 출력은
Canada,Ottawa,English,French가 정의된 필드에 맞춰 반환됨 -
JavaScript
- Ollama JavaScript library는 스키마를 JSON 객체로
format파라미터에 전달함 - 스키마는
object로 넘길 수 있고, 권장 방식은 Zod와zodToJsonSchema()를 사용하는 것임 - 예시는
Country스키마를 Zod 객체로 정의하고, 응답 본문을JSON.parse()한 뒤Country.parse()로 검증함
- Ollama JavaScript library는 스키마를 JSON 객체로
const country = Country.parse(JSON.parse(response.message.content));
텍스트와 이미지에서 데이터 추출
-
반려동물 텍스트 추출
- 구조화된 출력은 텍스트에서 필요한 정보를 뽑아낼 때 사용할 수 있음
- 예시는
Pet과PetListPydantic 모델을 정의해 반려동물 정보를 JSON 구조로 반환함Pet:name,animal,age,color,favorite_toyPetList:pets배열
- 입력 텍스트에는 두 마리의 고양이 정보가 포함됨
- Luna: 5살, 회색 털, yarn을 좋아함
- Loki: 2살, 검은색, tennis balls를 좋아함
- 출력은 정의된 스키마에 맞춰
Pet객체 목록으로 검증됨
-
비전 모델 이미지 설명
- 구조화된 출력은 비전 모델과 함께 사용할 수 있음
- 예시는
llama3.2-vision으로 이미지를 분석하고ImageDescription스키마에 맞춘 결과를 반환함 - 스키마에는 다음 필드가 포함됨
summaryobjectsscenecolorstime_of_daysettingtext_content
- 예시 요청은 이미지에서 객체, 장면, 색상, 감지 가능한 텍스트를 분석하도록 지시함
- 옵션에서
temperature를 0으로 설정해 더 결정적인 출력을 사용함 - 예시 출력은 해변의 야자수 장면을 요약하고,
tree와beach객체, 색상, 시간대, 실외 설정을 구조화된 필드로 반환함
OpenAI 호환 API 사용
- OpenAI 호환 예시는
OpenAI클라이언트를base_url="http://localhost:11434/v1"와api_key="ollama"로 설정함 client.beta.chat.completions.parse()를 사용해response_format에 Pydantic 모델인PetList를 전달함- 응답에서
completion.choices[0].message를 확인하고,parsed가 있으면 파싱된 결과를 출력함 refusal이 있으면 거부 응답을 출력하고,openai.LengthFinishReasonError는 토큰이 너무 많은 경우로 처리함
안정적인 출력 설정과 향후 계획
- 응답 스키마 정의에는 Python의 Pydantic 또는 JavaScript의 Zod 사용이 권장됨
- 모델이 요청을 이해하도록 프롬프트에 “return as JSON”을 추가하는 것이 좋음
- 더 결정적인 출력을 원하면 temperature를 0으로 설정함
- 앞으로의 계획은 다음과 같음
- 제어된 생성을 위한 logits 노출
- 구조화된 출력의 성능 및 정확도 개선
- 샘플링을 위한 GPU 가속
- JSON Schema를 넘어서는 추가 형식 지원
댓글과 토론
Hacker News 의견들
-
더 강한 출력 제약이 필요하면 llama.cpp가 GBNF를 지원함
https://github.com/ggerganov/llama.cpp/blob/master/grammars/...- 바로 그걸 쓰고 있는 것 같음
- 임의의 문법에 대한 출력 품질이 만족스러웠는지 궁금함
단순히 보면 모델들이 JSON을 훨씬 많이 봤기 때문에 다른 형식보다 JSON을 더 잘 만들 것 같다는 생각이 듦 - 어떤 점에서 더 강력한지 궁금함
-
좋은 소식임
CSV 데이터를 만들 때 앞뒤에 "Here is your data"나 "Please note blah blah" 같은 불필요한 문구 없이 일반 프롬프트를 어떻게 구성해야 할지 고민하고 있었는데, 이제 원하는 반환 형식을 정확히 정의해서 구조화된 출력을 바로 CSV로 넘길 수 있어 반가움- 그래도 프롬프트가 올바른 맥락에 들어가도록 CSV를 생성하라는 지시는 여전히 넣어야 함
그렇지 않으면 기술적으로는 CSV 형식이지만 의미가 없는 출력이 나올 수 있음. 모델은 사실 문단형 답변을 쓰려 했는데 토큰 샘플러가 모델이 별로 말하고 싶어 하지 않는 낮은 확률의 토큰을 고른 결과일 수 있음 - 많은 경우 출력 시작을
\n으로 미리 채워 두고에서 멈추게 하면 이런 문제를 막을 수 있음
- 그래도 프롬프트가 올바른 맥락에 들어가도록 CSV를 생성하라는 지시는 여전히 넣어야 함
-
동작함. gemma2:2b에 아래 문장을 넣었더니 원하는 JSON이 나왔음
You have spent 190 at Fresh Mart. Current balance: 5098
결과는{"amount": 190, "balance": 5098, "category": "Shopping", "place": "Fresh Mart"}였음- 꽤 일관성 없는 JSON이지만, 아주 작은 모델이고 게다가 gemma라면 그럴 만도 함
-
정말 놀라움. 실제로 원하던 기능 중 하나였음
ollama가 좋은 이유는 LLM을 다른 UNIX 프로그램처럼 쓰는 느낌을 주기 때문이고, LLM이 UNIX에 자연스럽게 속하는 것처럼 느껴지게 함
다만 AMD GPU에서 잘 돌려 본 사람이 있는지 궁금함. 더 어렵다고 들었지만 내년에 카드를 살 때 경쟁사를 지원하고 싶음- 가능함. 내장 GPU에서도 됨
780M이 있는 미니 PC에서 BIOS를 통해 공유 메모리 16GB를 할당했더니 꽤 잘 돌아갔음
- 가능함. 내장 GPU에서도 됨
-
이런 제약이 LLM 출력 품질에 어떤 영향을 주는지 궁금함
어떤 경우에는 출력 품질이 더 높다면 Markdown이나 일반 텍스트를 파싱하는 편을 택하고 싶음- OpenAI 모델을 쓸 때는 추가 토큰 비용을 감당할 수 있다면 두 번 통과시키는 전략이 아주 좋았음
첫 번째는 무거운 모델과 자연어를 사용해 Markdown 섹션으로 추론을 다루고 최종 자연어 답변을 제공하게 함. 가능하면 Markdown 헤더로 명확히 라벨링함
두 번째는 더 저렴하고 빠른 모델로 그 답변을 구조화된 출력 형식으로 바꿔, 파이프라인의 비LLM 부분이 소비하게 함
기본적으로 JSON 스키마 모드로 애매한 자연어 부분 주변에 깔끔한 경계를 만들고, LLM이 자기 출력을 유용한 형식으로 포착하는 전처리기 역할을 하게 됨 - 모델이 JSON 출력에 얼마나 미세조정되어 있는지에 달려 있음
또 모델에게 스키마를 알려줘야 함. 알려주지 않으면 이상한 토큰화 문제가 더 많이 생김
예를 들어 스키마가 JSON 키"foobarbaz"를 기대하고 표준 BPE 토큰화가["foobar", "baz"]라면, 현재 제약 출력 라이브러리들이 만드는 토큰 마스크는 모델이"f","foo","foobar"중 고르게 허용할 수 있음. 모델이"foo"를 고르면 제약이 다음 토큰으로 예컨대"bar"와"baz"를 강제함. 그러면 모델은["foobar", "baz"]가 아니라["foo", "bar", "baz"]를 보게 되어 혼란스러워짐 [0]
프롬프트에서"foobarbaz"가 스키마 키 중 하나라는 사실을 모델이 알면 일반적으로"foo"보다"foobar"를 선호함
[0] 최신 모델에서는 정규화 때문에 이런 토큰들이 서로 관련은 있지만, 같은 것은 아님 - 상황마다 다르지만 추론 측면에서는 부정적 영향이 있고, 대부분의 경우 차이가 아주 크지는 않았음
LLM과 프롬프트가 원래 JSON 응답을 만들 가능성이 있는지에 많이 좌우됨. LLM을 더 많이 억지로 몰아갈수록 정상적인 입력을 만들 가능성이 낮아짐
작은 모델에서는 의미 있는 예측력이 있는 공간의 가장자리에 더 빨리 도달해서 출력이 무작위 잡음에 가까워지기 시작함
엄밀한 측정은 아니고 여러 LLM 프로젝트에 많은 시간을 쓴 뒤 감으로 판단한 것임. 이 특정 도구들은 아직 써보지 않았지만, ollama도 예전에 비슷한 기법으로 보이는 방식으로 JSON 출력을 보장할 수 있었고, 파트너와 함께 또 다른 LLM 런타임 도구인 oobabooga용 jsonformer 비슷한 것도 작업한 적 있음 - 관련 연구를 계속 주시하고 있음. 샘플링 전반을 속도와 정확도 양쪽에서 개선하려고 살펴보는 중임
그런 변화가 들어가면 JSON에만 한정하지 않는 일반적인 구조 생성도 가능해질 수 있기를 바람 - instructor 같은 도구의 유용성에 대해 내가 완전히 틀렸다고 말할 수 있음
토큰을 많이 태우기 쉽지만, 하려는 일이 그 비용을 정당화한다면 꽤 강하게 밀어붙일 수 있음. 절대 최고 품질은 아니어도, 노력 없이 95% 수준까지 얻는 도구는 충분히 도구상자에 넣을 만함
- OpenAI 모델을 쓸 때는 추가 토큰 비용을 감당할 수 있다면 두 번 통과시키는 전략이 아주 좋았음
-
지원되는 아무 모델에나 쓸 수 있는지 궁금함
내 하드웨어에서는 1B~3B 모델만 안정적으로 돌릴 수 있어서 물어봄- 블로그 글 작성자임. 어떤 모델이든 사용할 수 있어야 함
작은 모델에서는 결과가 들쭉날쭉할 수 있지만, “x를 JSON으로 반환하라”고 요청하면 정확도에 도움이 되는 편이었음
- 블로그 글 작성자임. 어떤 모델이든 사용할 수 있어야 함
-
이 기능 관련 PR이 거의 1년 동안 열려 있었음
유지보수자들이 너무 조용했던 점은 좀 아쉬움- 글 작성자이자 유지보수자 중 한 명임. 동의함. 유지보수자들이 늦게 대응했고, 전반적으로 더 많은 기여를 장려하고 싶음
내년에는 커뮤니티 PR을 더 잘 챙기고 병합할 수 있기를 바람 - 분위기를 보면 회사 중심의 길로 가는 것 같고, 그래서 모든 것을 그 렌즈와 수익 극대화 관점에서 보는 듯함
- 나도 이걸 함께 만드는 데 협력하고 싶었지만 대화가 갑자기 끊겼음
- 글 작성자이자 유지보수자 중 한 명임. 동의함. 유지보수자들이 늦게 대응했고, 전반적으로 더 많은 기여를 장려하고 싶음
-
아직도 oobabooga를 쓰는 중임. exlv2 지원 덕분에 듀얼 3090에서 훨씬 효율적인 추론이 가능함
- ooba를 한동안 안 만져봤는데, exl2와 q3k_s 같은 비균질 양자화 방식의 상황이 어떤지 궁금함
기억으로는 exl2가 더 빠르긴 해도, 특히 낮은 비트 깊이에서는 gptq 양자화가 정확도 면에서 더 나았던 것 같음
- ooba를 한동안 안 만져봤는데, exl2와 q3k_s 같은 비균질 양자화 방식의 상황이 어떤지 궁금함
-
outlines와 비교했을 때 어떤 부가 가치가 있는지 궁금함
https://www.souzatharsis.com/tamingLLMs/notebooks/structured...- 블로그 작성자임. 현재 구현은 llama.cpp GBNF를 사용해서 빠르게 구현할 수 있었음. 지금 시점의 가장 큰 부가 가치는 기능을 출시했다는 점임
최근의 outlines/xgrammar 관련 연구를 바탕으로, 더 많은 형식을 지원하고 정확도를 높이며 성능을 개선하도록 샘플링을 업데이트할 수 있기를 기대함
- 블로그 작성자임. 현재 구현은 llama.cpp GBNF를 사용해서 빠르게 구현할 수 있었음. 지금 시점의 가장 큰 부가 가치는 기능을 출시했다는 점임
-
LLM에 구조화된 입력을 제공하는 최선의 방법이 있는지 궁금함
예를 들어 문장 100개를 넣고 각각을 여러 방식으로 분류하게 하는 경우임. 구조화된 데이터를 받는 것은 쉽지만, 줄 번호를 앞에 붙이는 내 방식은 투박하게 느껴짐- 모델들은 Markdown, JSON, 여러 프로그래밍 언어로 학습되어 있으니 그중 하나면 동작할 것임
다만 이 경우에는 모델이 헷갈리지 않게 문장을 하나씩 주는 편이 가장 좋음
프롬프트를"다음 문장을 분류하라. 규칙은 ..."+ 문장 형태로 구성하면 접두사 캐시를 활용할 수 있어, 한 번에 질의하는 것보다 성능도 더 좋아질 수 있음
물론 접두사 캐시가 있고 입력 토큰당 과금되지 않을 때 가능한 방식임. 요즘 대부분의 제공자는 접두사 캐시 사용 의사를 표시하면 더 낮은 비용으로 쓸 수 있게 해줌
- 모델들은 Markdown, JSON, 여러 프로그래밍 언어로 학습되어 있으니 그중 하나면 동작할 것임