과거의 딥러닝은 주로 뇌의 신경망 구조를 흉내 내는 데서 시작되었지만, 오늘날의 트랜스포머(Transformer) 기반 생성형 AI는 인간의 언어 습득 과정과 멀티모달 감각 처리 방식을 모방하며 눈부시게 진화하고 있습니다. 머신러닝으로 학생의 취약 단원을 분석하고, 그 결과를 바탕으로 GPT가 맞춤형 문제를 자동 생성하는 ‘교육 개인화 프로젝트’처럼, AI는 이미 완결된 형태의 솔루션으로 우리 곁에 다가왔습니다.
하지만 생성형 AI 역시 환각(Hallucination)이라는 치명적인 도전 과제를 안고 있습니다. 지난 1편에서 머신러닝의 기초와 생성형 AI의 한계 극복 개념을 다루었다면, 이번 2편에서는 오픈AI(OpenAI) API의 고급 활용법부터 랭체인(LangChain)을 이용한 RAG(검색 증강 생성) 파이프라인 구축, 멀티모달(음성/이미지), 그리고 스스로 도구를 사용하는 랭그래프(LangGraph) 기반 AI 에이전트까지!! 일단 말이 너무 어려운 것 같아요! 단어들을 풀어 봅시다.
오픈AI(OpenAI) API는, 챗GPT 사이트는 OpenAI가 자기들 입맛대로 껍데기(화면)를 씌워놓은 완성된 자동차를 우리가 타는 거고요.
자동차로 예를 들면, 그냥 AI: 완성차 타기
| 구분 | 진짜 정체 | 기계적 성격 |
| OpenAI API | 엔진 알맹이 | 유일하게 힘(사고력)을 만드는 곳 |
| 랭체인 | 쇠 파이프 / 연결 볼트 | 엔진 힘을 바퀴로 넘겨주는 단순 배관 |
| RAG | 서랍 속 종이 지도책 | 엔진이 보고 달릴 참고 자료 선반 |
| 멀티모달 | 앞유리창 / 스피커 | 밖을 비추고 소리 통하는 껍데기 통로 |
| 랭그래프 | 기어 레버 / 톱니 와이어 | 조건 맞으면 척척 물리적으로 걸리는 조작계 |
Ai 에이전트 해부: 똑똑하게 부려먹는 방법
[엔진 알맹이]
기존에 챗봇을 넘어선 AI 애플리케이션을 개발하려면 단순히 프롬프트를 던지는 것을 넘어 API의 세부 파라미터를 제어할 줄 알아야 합니다. 일관된 성격과 온도로 제어하고(seed, temperature), 요금을 아끼며(tiktoken), 원하는 데이터 구조로 오차 없이 뽑아내는(Pydantic) 실전 API 활용법을 알아봅시다.
하나의 채팅 메시지는 챗봇의 행동 방식을 지정하는 system, 사용자의 입력인 user, 모델의 출력인 assistant 역할(Role)로 명확히 구분됩니다.
성격과 말투(파라미터 제어) 일관성 (Temperature & Seed)
AI의 답변은 무작위성을 띠기 때문에 실행할 때마다 결과가 달라질 수 있습니다. 이때 답변의 자유도를 조절하는 temperature(0에 가까울수록 정형화된 답변, 높아질수록 창의적 답변)와 난수 생성기 초기화 값인 seed 파라미터를 고정하면 일관된 출력을 얻을 수 있습니다.
import openai
import os
# 1. API 키 설정 (환경변수 방식 권장)
os.environ['OPENAI_API_KEY'] = "YOUR_API_KEY"
client = openai.OpenAI()
# 2. 질문과 역할 정의
messages = [
{"role": "system", "content": "너는 어려운 IT 개념을 초등학생도 알기 쉽게 설명해주는 친절한 선생님이야."},
{"role": "user", "content": "RAG(검색 증강 생성)가 뭐야?"}
]
# 3. 모델 호출 및 결과 출력
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0.3, # 0에 가까울수록 차분하고 정확한 답변
max_tokens=1024, # 출력 최대 토큰 수
seed=3379 # 결과의 일관성을 유지하기 위한 난수 고정
)
print(response.choices[0].message.content)비용 아끼기 (tiktoken 토큰 비용 계산)
API 사용 시 텍스트 길이는 곧 속도와 요금으로 직결됩니다. OpenAI의 tiktoken 라이브러리를 사용하면 모델별 토크나이저를 통해 정확한 토큰 개수를 사전에 계산할 수 있습니다. 최신 gpt-4o 모델은 o200k_base 인코딩을 사용하여 한글 처리 시 기존보다 토큰 수를 크게 절약합니다.
import tiktoken
tokenizer_4o = tiktoken.encoding_for_model("gpt-4o")
prompt = "GPT 모델별 토크나이저를 확인하고, 프롬프트에 사용된 토큰의 개수를 구할 수 있다."
tokens = tokenizer_4o.encode(prompt)
print("총 글자 수:", len(prompt))
print("총 토큰 수:", len(tokens)) # gpt-4o (o200k_base) 기준 24개 토큰표 형태로 구조화된 출력 (Pydantic & 구조화된 출력)
데이터를 추출할 때, 프로그래밍에서 바로 쓸 수 있는 정형화된 JSON 형태로 답변하도록 강제할 수 있습니다. Pydantic으로 규격을 정의해두고, 긴글로 수다 떨지 못하게 막고, 엑셀 표나 체크리스트처럼 정해진 칸에만 답을 채워 넣게 강제하는 것입니다
import os
from typing import List
import openai
from pydantic import BaseModel, Field
client = openai.OpenAI()
# 1. 데이터 규격 정의
class OrderItem(BaseModel):
메뉴명: str = Field(description="주문 메뉴 이름")
수량: int = Field(description="주문 수량")
요청사항: str = Field(description="세부 요청사항 (없으면 '없음')")
class Order(BaseModel):
주문목록: List[OrderItem]
# 2. 자연어 주문 메시지
user_message = "치즈버거 2개 갖다주시고요, 피클은 빼주세요. 감자튀김은 1개인데 케첩 많이 부탁드려요!"
# 3. 구조화 파싱 호출 (.parse)
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "손님의 주문 텍스트에서 주문 내역을 품목별로 정확하게 추출하세요."},
{"role": "user", "content": user_message}
],
response_format=Order
)
order_result = completion.choices[0].message.parsed
# 4. 구조화된 데이터 확인
for item in order_result.주문목록:
print(f"메뉴: {item.메뉴명} | 수량: {item.수량} | 요청: {item.요청사항}")
# 실행결과
# 메뉴: 치즈버거 | 수량: 2 | 요청: 피클은 빼주세요
# 메뉴: 감자튀김 | 수량: 1 | 요청: 케첩 많이 부탁드려요
랭체인(LangChain) [쇠 파이프 / 연결 볼트]
OpenAI API를 직접 다루는 것을 넘어, LLM 애플리케이션을 블록 조립하듯 효율적으로 개발하게 해주는 프레임워크가 바로 랭체인(LangChain)입니다.
실제 서비스 환경에서는 매번 프롬프트를 처음부터 작성하는 대신 ChatPromptTemplate을 구축해 두고, 사용자의 입력 변수만 교체하여 LLM에 전달(invoke)하는 방식을 사용합니다.
스팸 문자 자동 분류기
알쏭달쏭한 광고 문자나 보이스피싱 문자를 AI가 문맥을 보고 “스팸”인지 “일반”인지 딱 가려내는 코드입니다. Chain of Thought (CoT, 연쇄적 사고) 기법을 프롬프트에 녹여 문맥을 파악하게 지시하면 정확도를 극대화할 수 있습니다.
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 0. API 키 설정 (환경 변수 또는 getpass 활용)
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "YOUR-API-KEY")
# 1. 시스템 프롬프트 (CoT 구조: 판단 이유를 먼저 적고 최종 분류를 내리도록 유도)
rule_prompt = """
당신은 스마트폰의 스팸 문자 판별기입니다.
다음 판별 기준에 따라 문맥을 면밀히 분석하세요:
- 단순히 '돈', '선물' 등의 단어가 포함되었다고 무조건 스팸으로 간주하지 않습니다.
- 지인이나 가족 간의 일상적 안부/대화라면 '일반'입니다.
- 출처가 불분명한 URL 링크 클릭을 유도하거나, 무료 혜택·대출을 미끼로 개인정보를 요구하면 '스팸'입니다.
반드시 아래 형식에 맞춰 답변하세요:
이유: (왜 스팸인지 혹은 일반인지 문맥을 분석한 핵심 근거 1줄)
분류: (스팸 또는 일반 중 택1)
"""
# 2. 질문 템플릿 정의
prompt = ChatPromptTemplate.from_messages([
("system", rule_prompt),
("user", "{message}")
])
# 3. LLM 정의 (분류/판단 작업이므로 일관성을 위해 temperature=0 설정)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 4. LCEL(쇠 파이프 |) 체인 조립
spam_filter_chain = prompt | llm | StrOutputParser()
# 5. 테스트 실행
test_msg = "[Web발신] 고객님! 신청하신 해외결제 85만원이 승인되었습니다. 본인이 아니면 즉시 클릭: http://bit.ly/fake"
result = spam_filter_chain.invoke({"message": test_msg})
print(result)
# 실행 결과
# 이유: 승인 확인을 빌미로 출처가 불분명한 단축 URL(http://bit.ly/fake) 클릭을 유도하고 있습니다.
# 분류: 스팸랭체인 객체들은 .invoke() 메서드를 통해 실행되며, 분류 작업의 경우 창의성을 배제하기 위해 temperature=0 (차분한 로봇 모드)으로 설정하는 것이 핵심입니다.
“랭체인(LangChain)의 연결 방식” : [질문 완성 -> AI 두뇌 전달 -> 결과 출력]이 자동으로 끝납니다.
LCEL과 벡터 DB를 결합한 진정한 RAG 구축
LLM의 지식 한계를 극복하는 궁극의 기술인 RAG(검색 증강 생성)를 랭체인 표기법인 LCEL(LangChain Expression Language)을 통해 직관적으로 구현할 수 있습니다.
사내 복지 규정 Q&A 봇 만들기
PDF 파일에서 텍스트를 추출(PyMuPDFLoader)하고, LLM이 소화할 수 있는 크기로 문서를 쪼갠 뒤(RecursiveCharacterTextSplitter), 이를 임베딩하여 FAISS 벡터 데이터베이스에 저장합니다.
예시 파일 : 사내 복지 규정
pip install langchain langchain-openai langchain-community pymupdf faiss-cpuimport os
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 0. API 키 설정
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "YOUR-API-KEY")
# 1단계: 사내 규정 PDF 문서 로드 (PyMuPDFLoader)
# 실행 폴더에 위치한 규정 문서를 불러옵니다.
loader = PyMuPDFLoader("company_welfare_rules.pdf")
docs = loader.load()
# 2단계: 문서 분할 (RecursiveCharacterTextSplitter)
# 500자 단위로 자르고 맥락 단절을 막기 위해 50자씩 겹치도록 설정합니다.
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", " ", ""]
)
splits = text_splitter.split_documents(docs)
# 3단계: 임베딩 생성 및 FAISS 벡터 데이터베이스 적재
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_db = FAISS.from_documents(splits, embeddings)
# 질문과 가장 관련된 규정 문서 2개를 찾아오는 검색기(Retriever) 생성
retriever = vector_db.as_retriever(search_kwargs={"k": 2})
# 4단계: AI에게 건넬 프롬프트 템플릿 정의
prompt = ChatPromptTemplate.from_template("""
당신은 사내 복지 규정을 안내하는 친절한 인사팀 비서입니다.
반드시 아래 [사내 규정]에 적힌 내용만을 근거로 정확하게 답변하세요.
문서에 명시되지 않은 내용은 임의로 추측하지 말고 솔직하게 "해당 규정은 문서에 명시되어 있지 않습니다."라고 답하세요.
[사내 규정]:
{context}
질문: {question}
""")
# 검색된 다중 문서 객체(Document)의 텍스트를 하나로 묶어주는 포맷팅 함수
def format_docs(retrieved_docs):
return "\n\n".join(doc.page_content for doc in retrieved_docs)
# 5단계: LCEL(쇠 파이프 |)로 공정 조립
# [문서 검색 + 원본 질문 유지] -> [프롬프트 완성] -> [LLM 추론] -> [텍스트 추출]
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| ChatOpenAI(model="gpt-4o-mini", temperature=0)
| StrOutputParser()
)
# 6단계: 질문 던져보기
question = "신입사원 첫해에 연차 며칠 쓸 수 있나요?"
answer = rag_chain.invoke(question)
print("질문:", question)
print("답변:", answer)
# 실행결과
# 질문: 신입사원 첫해에 연차 며칠 쓸 수 있나요?
# 답변: 사내 규정에 따르면, 신입사원의 연차 휴가는 입사 첫해 1년 미만 기간 동안 1개월 개근 시 1일씩 부여되어 최대 총 11일까지 사용할 수 있습니다.이 짧은 코드로 모델은 PDF 내부의 지식을 실시간으로 검색하여 병렬 처리의 이점과 장거리 의존성 해결이라는 정확한 논문 기반 답변을 생성해 냅니다.
멀티모달 시대: 이미지 생성과 음성 AI

트랜스포머 구조는 텍스트를 넘어 이미지, 음성 등 다양한 모달리티(Modality)를 통합 처리하며 실제 인간의 감각 처리 흐름을 모방하고 있습니다. OpenAI API를 통해 멀티모달 기능을 쉽게 호출할 수 있습니다.
DALL-E 3로 상상력을 시각화하기
client.images.generate 엔드포인트를 호출하면 텍스트 묘사만으로 초고화질 이미지를 얻을 수 있습니다. DALL-E 3가 프롬프트를 스스로 훨씬 정교하게 재작성(revised_prompt)하여 퀄리티를 끌어올린다는 것입니다.
import openai
client = openai.OpenAI()
# 신제품 홍보용 배너 이미지 생성 요청
response = client.images.generate(
model="dall-e-3",
prompt="모던한 대리석 테이블 위에 놓인 프리미엄 무선 헤드폰, 은은한 스튜디오 간접 조명, 고급 광고 화보 사진 스타일",
size="1024x1024",
quality="standard",
n=1
)
# 1. 생성된 이미지 확인
image_url = response.data[0].url
print("생성된 이미지 URL:", image_url)
# 2. DALL-E 3가 전문가처럼 보강한 세부 프롬프트 확인
print("\n[AI가 보강한 프롬프트]:\n", response.data[0].revised_prompt)TTS(텍스트 음성 변환)와 Whisper(음성 인식)
오디오 영역에서도 텍스트를 자연스러운 성우 목소리로 변환(TTS)하거나, 고객의 녹음 파일 및 음성 리뷰를 텍스트로 풀어내는 작업(STT)을 손쉽게 구현할 수 있습니다.
- TTS (Text-to-Speech):
client.audio.speech.create를 사용해 텍스트를 지정한 목소리(alloy, nova 등)의 mp3 파일로 변환합니다. - STT (Speech-to-Text):
client.audio.transcriptions.create와whisper-1모델을 사용하여 음성 파일을 다시 텍스트(Transcript)로 전사합니다.
스스로 생각하고 도구를 쓰는 AI: 랭그래프(LangGraph) 에이전트
지금까지의 AI는 사용자가 질문하면 답변만 반환하는 수동적인 형태였습니다. 하지만 AI 에이전트(Agent)는 사전에 정의된 도구(Tools)들을 쥐어주면, LLM 스스로 어떤 도구를 어떤 순서로 쓸지 판단하여 복합적인 임무를 완수합니다.
아래는 사칙연산, 네이버 웹 크롤링, PDF 임베딩이라는 세 가지 무기를 가진 강력한 LangGraph 에이전트 구축 예시입니다.
import os
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
# 0. API 키 설정
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "YOUR-API-KEY")
# 1. 에이전트가 사용할 도구(Tool) 정의
@tool
def calculate(a: int, b: int, operator: str) -> str:
"""간단한 사칙연산을 수행합니다.
operator는 '+', '-', '*', '/' 중 하나여야 합니다."""
if operator == '+': return f"계산 결과: {a + b}"
elif operator == '-': return f"계산 결과: {a - b}"
elif operator == '*': return f"계산 결과: {a * b}"
elif operator == '/': return f"계산 결과: {a / b}" if b != 0 else "0으로 나눌 수 없습니다."
return "지원하지 않는 연산자입니다."
@tool
def embed_pdf_to_faiss(file_path: str) -> str:
"""PDF 파일의 내용을 파싱하여 벡터 데이터베이스(FAISS)에 저장합니다."""
# (내부 로직 생략: PyMuPDFLoader 및 FAISS 적재 파이프라인)
return f"'{file_path}' 문서의 벡터 데이터베이스 저장이 완료되었습니다."
@tool
def crawl_naver_news_and_embed(search_term: str) -> str:
"""지정한 검색어로 네이버 뉴스를 크롤링하여 벡터 DB에 실시간 저장합니다."""
# (내부 로직 생략: 네이버 뉴스 API 호출 및 FAISS 저장)
return f"'{search_term}' 관련 최신 뉴스 수집 및 임베딩 저장이 완료되었습니다."
tools = [calculate, embed_pdf_to_faiss, crawl_naver_news_and_embed]
# 2. 두뇌(LLM) 준비 및 LangGraph ReAct 에이전트 초기화
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent_executor = create_react_agent(llm, tools)
# 3. 자연어로 복합 명령 내리기
# query = "사내 규정 PDF 문서 좀 디비에 저장해줘"
# query = "25 곱하기 14가 뭐야?"
query = "GPU라는 키워드로 네이버 뉴스를 검색해서 트렌드를 저장해줘."
inputs = {"messages": [("user", query)]}
# 4. 에이전트 실행 및 단계별 판단 흐름 확인
for step in agent_executor.stream(inputs, stream_mode="values"):
latest_message = step["messages"][-1]
latest_message.pretty_print()사용자가 자연어로 요청하면 에이전트는 도구들의 설명을 읽고 스스로 crawl_naver_news_and_embed가 필요하다고 판단한 뒤, 매개변수로 {'search_term': 'GPU'}를 채워 도구를 실행(Tool Call)합니다.
이러한 에이전트 아키텍처는 단순한 검색을 넘어 기업 내부망 검색, 외부 API 연동, 데이터베이스 쿼리를 스스로 조합하여 해결하는 궁극적인 자동화 솔루션으로 자리매김하고 있습니다. 언어와 코드를 넘나들며 진화하는 생성형 AI, 오늘 실습한 코드들을 바탕으로 AI 파트너를 설계해 보시기 바랍니다.
자주 묻는 질문 (FAQ)
Q1. RAG에서 chunk_size와 chunk_overlap은 어떤 기준으로 정하는 건가요?
- 권장 기준: 문맥의 성격에 따라 다릅니다. 사내 복지 규정이나 FAQ 같은 단문 규정은
chunk_size=300~500,chunk_overlap=50내외가 적당하며, 복잡한 기술 보고서나 계약서는chunk_size=800~1000,chunk_overlap=100~200정도로 설정합니다. - 쪼개는 것이기 때문에, 청크가 너무 작으면 문맥 단절로 검색 누락이 생기고 너무 크면 불필요한 노이즈가 프롬프트에 들어가 LLM이 엉뚱한 답을 낼 수 있습니다.
Q2. 단순 LangChain 체인(LCEL)과 LangGraph 에이전트의 결정적 차이는 무엇인가요?
- LangChain LCEL: A ➔ B ➔ C (단방향 DAG) 파이프라인입니다. 질문을 받으면 무조건 정해진 레일을 따라 순서대로 [검색 ➔ 프롬프트 ➔ 모델]로 흘러갑니다.
- LangGraph 에이전트: 원형 순환 루프(Cycle)와 자율 분기를 지원합니다. LLM이 도구(계산기, 크롤러 등)를 실행한 뒤 그 결과가 만족스럽지 않거나 추가 계산이 필요하면, 스스로 생각하고 되돌아봐 오류를 수정합니다.
Q3. seed 파라미터를 고정하고 temperature=0으로 두면? 매번 100% 동일한 답변이 나오나요?
- 대부분의 경우 거의 동일한 결과를 얻을 수 있지만, 다만 GPU 클러스터의 병렬 연산 특성(부동소수점 비결정론)과 모델의 백그라운드 업데이트로 인해 아주 드물게 미세한 어휘 차이가 발생할 수 있습니다. 완벽히 통제된 데이터 구조가 필요하다면 Seed 고정과 함께 Pydantic 구조화 출력을 반드시 병행해야 합니다.
References
RAG 원저 논문 Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (NeurIPS 2020)
[트랜스포머 원저 논문] Attention Is All You Need (NeurIPS 2017)
순환 신경망(RNN/LSTM)의 장기 의존성 문제를 극복하고 Self-Attention을 도입한 현대 LLM과 멀티모달의 핵심 아키텍처
[AI 에이전트 원저 논문] ReAct: Synergizing Reasoning and Acting in Language Models (ICLR 2023)
본문 LangGraph 에이전트의 기반이 되는 ‘추론(Reasoning)’과 ‘행동(Acting/도구 호출)’을 유기적으로 반복하는 자율 문제 해결 구조
[생각의 사슬 원저 논문] Chain-of-Thought Prompting Elicits Reasoning in Large Language Models (NeurIPS 2022)
단계별 사고 과정을 유도하여 복합 추론 및 텍스트 분류 정확도를 극대화하는 CoT 프롬프트 기법 원저
[벡터 검색 논문] Billion-scale similarity search with GPUs (IEEE / HAL)
대규모 고차원 임베딩 벡터 간 코사인/유클리드 유사도를 초고속으로 계산해 내는 FAISS 벡터 인덱싱 원리
“랭체인(LangChain)과 RAG, AI 에이전트 구축 완벽 가이드 (2편)”에 대한 1개의 생각