# Concept
- LLM의 response을 변형할 때 사용된다. (ex, response를 str에서 list, dictionary로 바꾸기)
- schema에 다양한 Output Parser가 존재한다.'
# Parser Shape
### BaseOutputParser
- `Output Parser`가 constructor인 Parser class로 만든 뒤, parse functoin을 만들어 원하는 값으로 데이터를 가공하는 것이 `BaseOutputParser` 기본적인 형태이다.
- `from langchain.schema import BaseOutputParser`로 불러 올 수 있다.
- 해당 parser의 올바른 parameter가 들어오도록 **template를 설정**해야 한다.
- predict_messages의 response 값은 **content component** 에 있음을 주의하자.
```python
from langchain.schema import BaseOutputParser
class CommaOutputParser(BaseOutputParser):
def parse(self, text):
itmes = text.strip().split(",")
return list(map(str.strip, itmes))
template = ChatPromptTemplate.from_messages(
[
("system","You are a list generating machine. Everything you are asked will be answered with a comma seperated list of max {max_items} in lowercase. Do NOT reply with anything else"),
("human", "{question}"),
]
)
prompt = template.format_messages(max_items = 10, question = "What are the colors?")
result = chat.predict_messages(prompt)
p = CommaOutputParser()
p.parse(result.content)
```
### StrOutputParser
- `Chain`의 결과값을 `String`으로 바꿔 출력해주는 `Output Parser`이다.
- `from langchain.schema.output_parser import StrOutputParser`로 불러 올 수 있다.
- `StrOutputParser`를 사용하면 `invoke()` 시 `.content` component를 따로 구하지 않아도 된다.
```python
from langchain.schema.output_parser import StrOutputParser
chain = prompt | llm | StrOutputParser()
response = refine_chain.invoke({"content" : content, "question" : question})
print(response)
```
# StructuredOutputParser
- **Structured Outputs**은 2024년 8월 6일에 공식 발표되었으며, JSON Schema를 사용하는 개발자 지정 구조를 모델이 완벽하게 따르도록 보장한다.
- 이는 이전의 JSON mode보다 발전된 기능으로, 단순히 유효한 JSON을 출력하도록 하는 것을 넘어 **스키마 완전 준수**까지 보장한다.
- 이를 사용하기 위해선 Python에서는 `Pydantic`, JavaScript는 `Zod`을 통해 **타입을 반드시 강제해야 한다.**
## Python 예제
```python
# pydantic 정의
from pydantic import BaseModel
from typing import Literal
class MovieReview(BaseModel):
sentiment: Literal["positive", "neutral", "negative"]
reason: str
# `ChatOpenAI`에 구조화된 출력 설정
from langchain_openai import ChatOpenAI
from langchain.output_parsers.openai_tools import PydanticToolsParser
from langchain_core.output_parsers import JsonOutputKeyToolsParser
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(
model="gpt-4o-2024-08-06",
temperature=0,
tool_choice="auto", # tool 사용 허용
tools=[MovieReview], # 구조화 출력용 스키마 전달
)
# 프롬프트 + 파서 조합
prompt = ChatPromptTemplate.from_template(
"다음 문장을 감정 분석해줘: {text}"
)
chain = prompt | llm | PydanticToolsParser(tools=[MovieReview])
# 결과
result = chain.invoke({"text": "영화는 좋았지만 결말이 별로였어."})
print(result)
```
## JavaScript 예제
### None Pipe
```javaScript
import { z } from 'zod';
import { ChatOpenAI } from '@langchain/openai';
import { StructuredOutputParser } from 'langchain/output_parsers';
const schema = z.object({
title: z.string(),
tags: z.array(z.string())
});
const parser = StructuredOutputParser.fromZodSchema(schema);
const llm = new ChatOpenAI({
modelName: 'gpt-4o-2024-08-06',
temperature: 0
});
const prompt = `제목: "인공지능의 미래"
이 제목에 대한 구조화된 정보:
${parser.getFormatInstructions()}` // 해당 Input을 통해 zod 형태의 결과를 출력하도록 유도
const result = await llm.invoke(prompt);
const parsed = await parser.parse(result.content); // par
console.log(parsed);
```
### Pipe
```javaScript
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StructuredOutputParser } from '@langchain/core/output_parsers';
import { z } from 'zod';
import { RunnableSequence } from '@langchain/core/runnables';
const openaiApiKey = import.meta.env.VITE_OPENAI_API_KEY;
const schema = z.object({
tags: z.array(z.string()),
});
const parser = StructuredOutputParser.fromZodSchema(schema);
const model = new ChatOpenAI({
modelName: 'gpt-4o-mini',
temperature: 0.7,
apiKey: openaiApiKey,
});
export async function getSuggestionTag(title: string) {
const prompt = ChatPromptTemplate.fromTemplate(
`
Your task is to infer and suggest 3 to 5 relevant tags based solely on the given article title.
Do not just extract keywords. Instead, **analyze the title carefully**, and **infer the implied topic, context, and author’s intent**.
Use your knowledge of common article patterns, technical domains, and writing purposes to make an educated guess.
**Guidelines:**
- Tags must be relevant, specific, and helpful for categorizing or searching the article.
- Tags must be in lowercase and formatted as a list of strings in valid JSON.
- Avoid vague or generic terms like "article" or "information".
- Use your reasoning to go beyond what is explicitly written.
- Output should be in Korean.
Input Title:
"{title}"
{format_instructions}
`.trim(),
);
const chain = RunnableSequence.from([prompt, model, parser]);
const response = await chain.invoke({
title,
format_instructions: parser.getFormatInstructions(),
});
return response;
}
```
## `JsonOutputParser` vs `StructeredOutputParser`
| 비교 항목 | `JsonOutputParser` | `StructuredOutputParser` |
| ---------------- | ------------------------- | ---------------------------------- |
| **출력 구조 강제력** | ❌ 낮음 (LLM이 틀린 JSON 출력 가능) | ✅ 높음 (스키마를 따르도록 명시적으로 유도함) |
| **스키마 기반 검증** | ❌ 수동 처리 필요 | ✅ `Zod` 또는 `Pydantic`으로 자동 검증 |
| **JSON 오류 내성** | 중간 (단순 JSON 오류에 취약) | 높음 (스키마로 파싱 실패 시 에러 발생) |
| **형 변환/검증 내장** | ❌ 없음 | ✅ 있음 |
| **최신 모델 활용 최적화** | ❌ 일반 텍스트 출력 기반 | ✅ GPT-4o의 structured output 기능을 전제 |
## WithStructuredOutput
- 모델이 처음부터 올바른 형식으로 출력하도록 지시하므로, 파싱 실패 가능성이 훨씬 낮은 방식이다.
- `StructeredOutputParser`과 `JsonOutputParser`은 **모델**이 값을 생성하면 이를 **Parsing하는** 방식이라면, **withStructuredOutput**은 직접 모델이 구조화된 데이터를 생성하도록 한다.
- **withStructuredOutput**은 **모델별**로 가장 적합한 방법을 자동으로 선택한다.
- OpenAI 모델: JSON mode 또는 tool calling 사용
- Anthropic 모델: tool calling 사용
- 기타 모델: 각 모델의 최적화된 구조화 출력 방식 사용
### JavaScript
```JavaScript
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { z } from 'zod';
const openaiApiKey = import.meta.env.VITE_OPENAI_API_KEY;
const tagSchema = z.object({
tags: z.array(z.string()).describe('Array of suggested tags based on title analysis'),
});
const model = new ChatOpenAI({
modelName: 'gpt-4o-mini',
temperature: 0.7,
apiKey: openaiApiKey,
});
export async function getSuggestionTag(title: string) {
const prompt = ChatPromptTemplate.fromTemplate(
`
Your task is to infer and suggest 3 to 5 relevant tags based solely on the given article title.
Do not just extract keywords. Instead, **analyze the title carefully**, and **infer the implied topic, context, and author’s intent**.
Use your knowledge of common article patterns, technical domains, and writing purposes to make an educated guess.
**Guidelines:**
- Tags must be relevant, specific, and helpful for categorizing or searching the article.
- Tags must be in lowercase and formatted as a list of strings in valid JSON.
- Avoid vague or generic terms like "article" or "information".
- Use your reasoning to go beyond what is explicitly written.
- Output should be in Korean.
Input Title:
"{title}"
`,
);
const structuredModel = model.withStructuredOutput(tagSchema, {
name: 'tag_suggestion',
});
const response = await structuredModel.invoke(await prompt.format({ title }));
return response;
}
```
### Python
```python
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
# 환경변수 로드
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
# Pydantic 모델 정의
class TagRequest(BaseModel):
tags: list[str] = Field(description="Array of suggested tags based on title analysis")
# 프롬프트 정의
prompt = ChatPromptTemplate.from_template(
"""
Your task is to infer and suggest 3 to 5 relevant tags based solely on the given article title.
Do not just extract keywords. Instead, **analyze the title carefully**, and **infer the implied topic, context, and author’s intent**.
Use your knowledge of common article patterns, technical domains, and writing purposes to make an educated guess.
**Guidelines:**
- Tags must be relevant, specific, and helpful for categorizing or searching the article.
- Tags must be in lowercase and formatted as a list of strings in valid JSON.
- Avoid vague or generic terms like "article" or "information".
- Use your reasoning to go beyond what is explicitly written.
- Output should be in Korean.
Input Title:
"{title}"
""",
)
# 체인 정의
chain = prompt | llm.with_structured_output(TagRequest)
# 태그 생성 함수
def generate_tag(text):
return chain.invoke({"title": text})
```