Skip to content

Commit 1fba8a4

Browse files
committed
chatbot_2: post added
1 parent cf286b6 commit 1fba8a4

File tree

2 files changed

+206
-3
lines changed

2 files changed

+206
-3
lines changed

_posts/2025-06-07-chatbot_1_api_key.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
title: "Langchain 기반 챗봇 만들기 #1 - OpenAI API Key 발급"
33
description: OpenAI API를 사용하기 위해 계정 생성부터 API Key 발급, 모델 등록, 테스트 코드 실행까지의 과정을 단계별로
44
정리했습니다. 다음 장에서는 Langchain을 활용해 기억하는 챗봇을 만들어봅니다.
5-
categories:
6-
- Flutter
7-
- Programming
5+
categories: Programming
86
date: 2025-06-07 13:32 +0900
97
---
108
# OpenAI API Key 발급 기록
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
---
2+
title: 'Langchain 기반 챗봇 만들기 #2 - Runnable과 체이닝 기초 익히기'
3+
description: 'Runnable 인터페이스·LCEL 파이프 연산자부터 Prompt-LLM-OutputParser 체인 구성, 그리고 RunnableLambda로
4+
커스텀 단계를 삽입하는 방법까지: LangChain 0.2 기준으로 챗봇 파이프라인의 핵심 개념과 예제 코드를 한눈에 정리합니다.'
5+
categories: Programming
6+
mermaid: true
7+
date: 2025-06-07 21:15 +0900
8+
---
9+
[Langchain 기반 챗봇 만들기 #1](/posts/chatbot_1_api_key/) 에서는 OpenAI API 키를 발급받아 기본 질문·응답 예제를 실행해 보았다. 그런데 단순한 요청-응답만으로는 이전 대화 내용을 참고하지 못해, 대화가 이어진다는 느낌이 들지 않는다.
10+
11+
이럴 때 필요한 도구가 바로 Langchain이다. Langchain은 단일 프롬프트 호출을 넘어, 대화 맥락을 유지하거나 외부 도구를 활용하는 파이프라인을 쉽게 구성할 수 있게 돕는다.
12+
13+
Langchain은 기능이 많아 처음 접할 때 개념이 다소 복잡하게 느껴졌다. 여기에 최대한 정리해놓도록 하려 한다.
14+
15+
16+
17+
## Langchain이란
18+
> Langchain은 대형 언어 모델(LLM) 애플리케이션 개발을 돕는 파이썬 라이브러리다. 프롬프트 관리, 컨텍스트 유지, 외부 도구 연동 등 반복 작업을 추상화해 개발 생산성을 높여준다...
19+
20+
라고 적혀있다. 모르겠고 코드를 통해 보자.
21+
22+
23+
## Runnable
24+
LangChain은 말 그대로 ‘체인(chain)’ 방식으로 동작을 구성한다. 마치 파이프처럼 각 구성 요소를 연결하여, 한 요소의 출력(output)이 다음 요소의 입력(input)이 되도록 한다.
25+
26+
이를 위해 Chain, Agent, Tool, Memory 등 주요 실행 단위는 Runnable 인터페이스를 구현하거나 래핑된 형태로 제공된다.
27+
28+
다음 예시코드에서는 직접 Runnable을 상속받아서 두 클래스를 제작했다.
29+
```python
30+
from langchain_core.runnables import Runnable
31+
32+
class AddOne(Runnable):
33+
def invoke(self, input: int, config=None, **kwargs):
34+
return input + 1 # (+1)
35+
36+
class MultiplyByTwo(Runnable):
37+
def invoke(self, input: int, config=None, **kwargs):
38+
return input * 2 # (×2)
39+
40+
chain = AddOne() | MultiplyByTwo() # 체이닝 – LCEL 파이프
41+
42+
print(chain.invoke(3)) # (3 + 1) * 2 → 8
43+
```
44+
45+
`AddOne`: 입력값에 1을 더하는 연산
46+
47+
`MultiplyByTwo`: 입력값을 2배로 곱하는 연산
48+
49+
이 두 클래스를 파이프 연산자 `|`를 통해 연결했다. 이 방식은 앞에서 나온 출력이 자동으로 다음 입력으로 넘어가는 체이닝 구조를 만든다. 이러한 구조 덕분에 복잡한 LLM 파이프라인도 매우 직관적으로 표현할 수 있다.
50+
51+
```python
52+
chain = AddOne() | MultiplyByTwo()
53+
```
54+
`invoke()` 메서드를 통해 전체 체인을 실행할 수 있으며, 이는 내부적으로 각 `Runnable``invoke()`를 순차적으로 호출한다.
55+
```mermaid
56+
flowchart LR
57+
A["입력값: 3"] -- 3 --> B["AddOne(+1)"]
58+
B -- 4 --> C["MultiplyByTwo(×2)"]
59+
C -- 8 --> D["출력값: 8"]
60+
```
61+
62+
63+
여기서 주의할 점은 `AddOne`이 input으로 integer형 변수 1개를 받기 때문에 `invoke()`에도 integer형 변수인 `3`을 넘겨주었다는 것이다.
64+
65+
66+
67+
## LLM, Prompt, Chain
68+
그러면 위의 Runnable을 이용해서 각종 LLM을 어떻게 구동하는걸까?
69+
LLM을 구동시키는 데는 크게 세 가지 구성 요소가 필요하다:
70+
* **LLM**: 어떤 언어모델을 사용할 것인지
71+
* **Prompt**: 언어모델에 어떤 입력을 줄 것인지 (예: "너는 LLM 전문가야. 아래 질문에 답해줘. 질문: {input}")
72+
* **OutputParser**: 어떤 출력값을 원하는지 (예: 전체 응답에서 텍스트만 추출)
73+
74+
이 세 가지는 모두 Runnable로 동작하기 때문에, 앞서 봤던 파이프 연산자 `|`를 이용해 다음과 같이 쉽게 연결할 수 있다.
75+
76+
```python
77+
from langchain_core.prompts import ChatPromptTemplate
78+
from langchain_core.output_parsers import StrOutputParser
79+
from langchain_openai import ChatOpenAI
80+
81+
# 프롬프트 템플릿 정의
82+
prompt = ChatPromptTemplate.from_template(
83+
"너는 전문가야. 아래 질문에 답해줘.\n\n질문: {input}"
84+
)
85+
86+
# 사용할 언어모델 지정
87+
llm = ChatOpenAI(model="gpt-4.1-nano", openai_api_key="YOUR_API_KEY")
88+
89+
# 출력 결과를 문자열로 정리
90+
output_parser = StrOutputParser()
91+
92+
# 체이닝: Prompt → LLM → OutputParser
93+
chain = prompt | llm | output_parser
94+
95+
# 실행
96+
print(chain.invoke({"input": "지구의 자전 주기는?"}))
97+
```
98+
99+
```mermaid
100+
graph LR
101+
%% 노드 정의 (줄바꿈에 <br/> 사용)
102+
A["Input<br/>(dict)"]
103+
B["PromptTemplate<br/>(ChatPromptValue)"]
104+
C["ChatOpenAI<br/>(AIMessage)"]
105+
D["StrOutputParser<br/>(str)"]
106+
E["Final Output<br/>(str)"]
107+
108+
%% 데이터 흐름
109+
A -- "{'input': '지구의 자전 주기는?'}" --> B
110+
B -- "list[HumanMessage]<br/>(프롬프트 메시지)" --> C
111+
C -- "AIMessage<br/>(모델 응답 전체)" --> D
112+
D -- "str<br/>(가공된 텍스트)" --> E
113+
```
114+
115+
실행해보면 다음과 같은 결과가 나온다.
116+
```
117+
지구의 자전 주기(즉, 지구가 자신을 한 바퀴 도는 시간)는 약 23시간 56분 4초입니다. 이를 "항성일(sidereal day)"이라고 하며, 태양을 기준으로 한 태양일(약 24시간)과는 약 4분 정도 차이가 있습니다. 태양일은 태양이 하늘에서 같은 위치에 다시 도달하는 데 걸리는 시간을 의미하며, 일상적으로 우리가 사용하는 하루는 이 태양일에 해당합니다.
118+
```
119+
120+
prompt가 `{"input": "지구의 자전 주기는?"}` 같은 dict를 받으면, 그 안의 `"input"` 값을 템플릿의 `{input}` 자리에 자동으로 치환해서 LLM 호출용 메시지를 완성해준다.
121+
122+
---
123+
124+
125+
## PromptTemplate 주요 종류
126+
127+
| 분류 | 대표 클래스 | 한 줄 설명 |
128+
|------------------|-----------------------------|-------------------------------------------|
129+
| 문자열 프롬프트 | `PromptTemplate` | f-string·Jinja2 등으로 LLM 입력 문자열 생성 |
130+
| 대화형 프롬프트 | `ChatPromptTemplate` | 시스템·사용자·AI 메시지를 묶어 대화 맥락 구성 |
131+
| 메시지 자리표시 | `MessagesPlaceholder` | 기존 메시지 리스트를 그대로 삽입(메모리 연동) |
132+
| Few-Shot 프롬프트 | `FewShotPromptTemplate`| I/O 예시를 접두·접미로 배치해 few-shot 학습 |
133+
134+
> 이외에도 `PipelinePromptTemplate`, `StructuredChatPromptTemplate` 등 다양한 고급 템플릿을 제공한다.
135+
136+
137+
## LLM 래퍼 주요 종류
138+
139+
| 클래스명 | 비고(필요 패키지·API) |
140+
|---------------|-------------------|
141+
| `ChatOpenAI` | `openai` 패키지, GPT 계열 |
142+
| `ChatAnthropic` | Claude 계열 |
143+
| `ChatGoogleVertexAI` (`ChatGooglePalm` 구 버전) | Vertex AI, PaLM-2/Gecko 등 |
144+
| `ChatCohere` | Cohere Command-R 등 |
145+
146+
> LiteLLM, Ollama 등 로컬·프록시형 래퍼도 다수 존재한다.
147+
148+
149+
## OutputParser 주요 종류
150+
151+
| 클래스명 | 용도 |
152+
|----------------------|-------------------------------------|
153+
| `StrOutputParser` | 전체 응답을 문자열로 반환(가장 기본) |
154+
| `JSONOutputParser` | JSON 형태 응답을 딕셔너리로 파싱 |
155+
| `PydanticOutputParser` | 결과를 Pydantic 모델 인스턴스로 변환 |
156+
157+
> 이외에 `RegexOutputParser`, `CommaSeparatedListOutputParser` 등 특수 목적 파서도 제공된다.
158+
159+
---
160+
161+
## RunnableLambda로 커스텀 처리 단계 추가하기
162+
만약 나만의 Custom 로직을 추가하고 싶다면?
163+
164+
[`Runnable`](#runnable)을 직접 상속하여 클래스를 구현하는 방법도 있지만, `RunnableLambda`를 이용하면 더 간단하게 사용자 정의 단계를 체인에 삽입할 수 있다.
165+
166+
```python
167+
from langchain_core.prompts import ChatPromptTemplate
168+
from langchain_core.prompt_values import ChatPromptValue
169+
from langchain_core.runnables import RunnableLambda
170+
from langchain.schema import HumanMessage, BaseMessage
171+
172+
# 1) Prompt와 LLM 정의
173+
prompt = ChatPromptTemplate.from_template(
174+
"질문에 친절히 답할게요:\n\n{input}"
175+
)
176+
177+
# 2) ChatPromptValue를 받아 HumanMessage에 '?' 추가하는 단순 함수
178+
def emphasize_question_fn(pv: ChatPromptValue) -> ChatPromptValue:
179+
msgs: list[BaseMessage] = pv.to_messages()
180+
new_msgs: list[BaseMessage] = []
181+
for m in msgs:
182+
if isinstance(m, HumanMessage):
183+
# 사용자 메시지 앞뒤에 물음표 이모지 추가
184+
content = f"? {m.content.strip()} ?"
185+
new_msgs.append(HumanMessage(content=content))
186+
else:
187+
new_msgs.append(m)
188+
return ChatPromptValue(messages=new_msgs)
189+
190+
# 3) RunnableLambda로 래핑
191+
normalize_input = RunnableLambda(emphasize_question_fn)
192+
193+
# 4) 체인: prompt → normalize_input → llm
194+
chain = prompt | normalize_input
195+
196+
# 5) 실행 예시
197+
print(chain.invoke({"input": "지구의 자전 주기는?"}))
198+
```
199+
200+
```
201+
# 실행결과
202+
messages=[HumanMessage(content='? 질문에 친절히 답할게요:\n\n지구의 자전 주기는? ?', additional_kwargs={}, response_metadata={})]
203+
```
204+
205+
> `RunnableLambda`는 주로 데이터 전처리, 형 변환, 로깅 등의 목적에 활용되며 체인의 유연성을 크게 높여준다.

0 commit comments

Comments
 (0)