[๊ฐ์ด๋] Ollama + LangChain ์ค์ ๊ฐ์ด๋ - ChatOllama ์ฌ์ฉํ๊ธฐ
์๋ณธ ๊ฒ์๊ธ: https://velog.io/@euisuk-chung/๊ฐ์ด๋-Ollama-LangChain-์ค์ -๊ฐ์ด๋-ChatOllama-์ฌ์ฉํ๊ธฐ
์ด์ ํฌ์คํธ์์๋ Python ์ฝ๋์์ Ollama ๋ช ๋ น์ด๋ฅผ ํ์ฉํ์ฌ ๋ก์ปฌ์์ LLM์ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์์ต๋๋ค.
- [์ด์ ํฌ์คํธ
๋ณด๋ฌ๊ฐ๊ธฐ](https://velog.io/@euisuk-chung/%EA%B0%80%EC%9D%B4%EB%93%9C-Python%EC%97%90%EC%84%9C-Ollama-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-AI-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0)
์ด๋ฒ์๋ LangChain์ ChatOllama
๋ฅผ ์ค์ฌ์ผ๋ก, LLM์ ํ๋์ ๋ํํ ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ๋ ๋ฒ์ ๊ตฌ์ฒด์ ์ผ๋ก ๋ค๋ค๋ณด๊ฒ ์ต๋๋ค.
LangChain์ ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ์ ์ค์ ์ ํ๋ฆฌ์ผ์ด์
์ผ๋ก ๊ตฌํํ ์ ์๋๋ก ๋๋ Python ํ๋ ์์ํฌ์ด๋ฉฐ,
ChatOllama
๋ LangChain์ด Ollama์ ์ฐ๋๋ ์ ์๊ฒ ํด์ฃผ๋ ํต์ฌ ๋ชจ๋์
๋๋ค.
์ฐธ๊ณ ๋ฌธ์
โ ํ๊ฒฝ ๊ตฌ์ฑ
1. ํ์ํ ํจํค์ง ์ค์น
๋จผ์ ๋ค์ ํจํค์ง๋ค์ด ์ค์น๊ฐ ๋์ด์ผ ์๋ ์ฝ๋๋ค์ ์คํํ ์ ์์ต๋๋ค.
1
pip install -U ollama langchain-core langchain-ollama langchain-community
์ต์ ๋ฒ์ ์ Ollama์ LangChain์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
tool calling
,streaming
,structured output
๋ฑ์ ๊ธฐ๋ฅ์ ์ต์ API ๋ฒ์ ์์๋ง ์ง์๋ฉ๋๋ค.
2. ๋ชจ๋ธ ๋ค์ด๋ก๋
1
2
ollama pull llama3.1:8b
ollama pull llava-llama3
๋ชจ๋ธ ๋ชฉ๋ก ํ์ธ:
1
ollama list
๐ก ํต์ฌ ํด๋์ค: ChatOllama
ChatOllama
๋ LangChain์ BaseChatModel
์ ์์ํ ํด๋์ค๋ก, Ollama ์๋ฒ์์ ์คํ ์ค์ธ LLM์ ์ฌ์ฉํ์ฌ ์ฑํ
์คํ์ผ์ ์๋ต์ ์์ฑํฉ๋๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# ๋ชจ๋ธ ๋ก๋
llm = ChatOllama(model="llama3.1:8b", temperature=0.7)
# ํ๋กฌํํธ ํ
ํ๋ฆฟ ์ค์ (์ ํ ์ฌํญ์ด์ง๋ง ๊ถ์ฅ)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("user", "{input}")
])
# ์ถ๋ ฅ ํ์ ์ค์ (LLM์ ์ถ๋ ฅ์ ๋ฌธ์์ด๋ก ํ์ฑ)
output_parser = StrOutputParser()
# ์ฒด์ธ ๊ตฌ์ฑ
chain = prompt | llm | output_parser
# ๋ต๋ณ ์์ฒญ
question = "๋ํ๋ฏผ๊ตญ์ ์๋๋ ์ด๋์ธ๊ฐ์?"
response = chain.invoke({"input": question})
# ๋ต๋ณ ์ถ๋ ฅ
print(f"์ง๋ฌธ: {question}")
print(f"๋ต๋ณ: {response}")
ChatOllama ํ๋ผ๋ฏธํฐ
ํ๋ผ๋ฏธํฐ | ์ค๋ช |
---|---|
model | ์ฌ์ฉํ ๋ชจ๋ธ ์ด๋ฆ (์: llama3 , mistral , qwen2 ๋ฑ) |
temperature | ์๋ต์ ์ฐฝ์์ฑ/๋ฌด์์์ฑ ์ ์ด (0์ ๊ฐ๊น์ธ์๋ก ๊ฒฐ์ ์ , 1์ ๊ฐ๊น์ธ์๋ก ๋ค์ํจ) |
num_predict | ์์ฑํ ์ต๋ ํ ํฐ ์ (์: 256, -1 : ๋ฌด์ ํ, -2 : ์ปจํ
์คํธ ํ๋๊น์ง) |
base_url | Ollama ์๋ฒ ์ฃผ์ (๊ธฐ๋ณธ๊ฐ: http://localhost:11434 ) |
format | ์๋ต ํฌ๋งท ์ง์ (์: "json" ์ผ๋ก ์ค์ ์ JSON ํ์ ์๋ต ์ ๋) |
top_k | ๋ค์ ํ ํฐ ์์ธก ์ ํ๋ฅ ์์ K ๊ฐ ํ๋ณด ์ค ์ ํ (์: 40) |
top_p | ๋์ ํ๋ฅ P ์ด์์ด ๋๋ ํ ํฐ ์งํฉ์์ ์ํ๋ง (์: 0.9) |
num_ctx | ํ ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋ ์ต๋ ์ปจํ ์คํธ ๊ธธ์ด (ํ ํฐ ๋จ์, ์: 2048) |
repeat_penalty | ๋ฐ๋ณต๋๋ ๋จ์ด์ ๋ํ ํจ๋ํฐ ๋ถ์ฌ (์: 1.1) |
stop | ์๋ต ์์ฑ์ ์ค๋จํ ๋ฌธ์์ด ๋ฆฌ์คํธ (์: ["\n", "Question:"] ) |
keep_alive | ๋ชจ๋ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์งํ ์๊ฐ (์: "5m" , "0" , "-1" ) |
mirostat | Mirostat ์ํ๋ง ๋ชจ๋ (0: ๋นํ์ฑํ, 1: Mirostat v1, 2: Mirostat v2) |
mirostat_eta | Mirostat ํ์ต๋ฅ ์กฐ์ ๊ฐ (์: 0.1) |
mirostat_tau | Mirostat ๋ชฉํ perplexity ๊ฐ (์: 5.0) |
num_gpu | ์ฌ์ฉํ GPU ๋ ์ด์ด ์ (์: 0์ CPU๋ง, -1์ ์ ์ฒด GPU ํ์ฉ) |
seed | ๋์ ์๋ ๊ฐ ์ค์ (์: 42, ๋์ผ ์ ๋ ฅ์ ๋ํด ๋์ผ ์๋ต ์์ฑ ์ ๋) |
ChatOllama ํ๋ผ๋ฏธํฐ ์์ธ ์ค๋ช
1. model
- ์ค๋ช
: Ollama ์๋ฒ์์ ์ฌ์ฉํ LLM์ ์ด๋ฆ์ ์ง์ ํฉ๋๋ค. (์:
llama3:8b
,qwen2:7b-instruct
,mistral:latest
) -
ํน์ง:
- ๋ฐ๋์
ollama pull <๋ชจ๋ธ๋ช >
์ ํตํด ๋ชจ๋ธ์ด ๋ก์ปฌ์ ์ค์น๋์ด ์์ด์ผ ํฉ๋๋ค. - ๋ชจ๋ธ๋ง๋ค ํ์ต ํน์ฑ๊ณผ ๊ฐ์ (์: ์ฝ๋ฉ, ์ถ๋ก , ์ธ์ด)์ด ๋ค๋ฅด๋ฉฐ ์์คํ ์์ ์๊ตฌ๋(RAM/VRAM)๋ ์์ดํฉ๋๋ค.
:8b
,:instruct
๋ฑ์ ๋ฒ์ , ํ๋ ๋ฐฉ์, ํฌ๊ธฐ๋ฅผ ๋ํ๋ ๋๋ค.- ์ ํ ์๋ น: ์์ ๋ชฉ์ (๋ํ, ์์ฑ, ๋ถ์ ๋ฑ)๊ณผ ์์คํ ์ฌ์์ ๊ณ ๋ คํด ์ ํํด์ผ ํฉ๋๋ค.
- ๋ฐ๋์
2. temperature
- ์ค๋ช : ์์ฑ ์๋ต์ ๋ฌด์์์ฑ ๋๋ ์ฐฝ์์ฑ ์กฐ์ (0.0 ~ 1.0)
-
ํน์ง:
- ๋ฎ์ (0.0~0.3): ๊ฒฐ์ ๋ก ์ , ์ผ๊ด๋ ์๋ต์ ์ ๋ฆฌ (์์ฝ, ์ฝ๋ ๋ฑ)
- ์ค๊ฐ (0.5~0.7): ์์ฐ์ค๋ฌ์ด ๋ํ, ๊ท ํ ์กํ ์์ฑ
- ๋์ (0.8~1.0): ์ฐฝ์์ฑ/๋ฌด์์์ฑ ์ฆ๊ฐ, ๋ธ๋ ์ธ์คํ ๋ฐ์ ์ ๋ฆฌ
- 0์ ๊ฐ๊น์ธ์๋ก
top_k
,top_p
์ ํจ๊ณผ๊ฐ ์ค์ด๋ญ๋๋ค.
3. base_url
- ์ค๋ช
: Ollama ์๋ฒ์ ์ฃผ์๋ฅผ ์ค์ (๊ธฐ๋ณธ:
http://localhost:11434
) -
ํน์ง:
- Docker, ์๊ฒฉ ์๋ฒ์์ ์ฌ์ฉํ ๊ฒฝ์ฐ ํด๋น IP:PORT๋ก ์ค์ ํ์
4. format
- ์ค๋ช
: ์ถ๋ ฅ ํฌ๋งท์ ์ง์ (์:
json
) -
ํน์ง:
"json"
์ง์ ์ ๋ชจ๋ธ์ JSON ํ์ ์ถ๋ ฅ์ ์๋ํจ- ํ๋กฌํํธ์ JSON ํ์ ์๋ต ์์ฒญ์ ๋ช ์ํด์ผ ์ ํ๋ ํฅ์
5. top_k
- ์ค๋ช
: ๋ค์ ํ ํฐ ํ๋ณด ์ค ํ๋ฅ ์์
K
๊ฐ๋ง ๊ณ ๋ ค -
ํน์ง:
- ์์ ๊ฐ์ ๋ณด์์ , ํฐ ๊ฐ์ ๋ค์์ฑ ์ฆ๊ฐ
- ์ผ๋ฐ์ ์ผ๋ก
top_p
์ ๋ณ์ฉํ๊ฑฐ๋ ๋ ์ค ํ๋๋ง ์ฌ์ฉ
6. top_p
(Nucleus Sampling)
- ์ค๋ช
: ๋์ ํ๋ฅ ์ด
p
์ด์์ผ ๋๊น์ง ํ ํฐ์ ํ๋ณด๋ก ํฌํจ -
ํน์ง:
- ๋์ ๋ฒ์ ์ง์ (ํ๋ฅ ์ด ๋พฐ์กฑํ ์๋ก ํ๋ณด๊ตฐ์ด ์์์ง)
- ๋ณดํต 0.9 ~ 0.95 ์ฌ์ด๊ฐ ์์ ์
7. num_ctx
- ์ค๋ช : ์ปจํ ์คํธ ๊ธธ์ด ์ ํ (ํ ํฐ ๋จ์)
-
ํน์ง:
- ๋ชจ๋ธ์ ์ต๋ ์ ๋ ฅ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ ํจ (์: 2048)
- ๊ธด ๋ฌธ์ ์์ฝ, ๊ธด ๋ํ ๋ฑ์ ์ค์
8. repeat_penalty
- ์ค๋ช : ๋์ผํ ๋ฌธ๊ตฌ ๋ฐ๋ณต ๋ฐฉ์ง๋ฅผ ์ํ ํจ๋ํฐ (๊ธฐ๋ณธ: 1.1)
-
ํน์ง:
- 1.0์ ํจ๋ํฐ ์์, 1.1~1.5๋ ๋ฐ๋ณต ์ต์
- ๊ณผ๋ํ ์ค์ ์ ํํ๋ ฅ์ ์ ํํ ์ ์์
9. stop
- ์ค๋ช : ํน์ ๋ฌธ์์ด์ด ์ถ๋ ฅ๋๋ฉด ์๋ต ์์ฑ ์ค๋จ
-
ํน์ง:
- ์:
["\n", "User:"]
- LangChain์
stop
ํ ํฐ ์ดํ ํ ์คํธ๋ฅผ ์ ๊ฑฐํจ
- ์:
10. num_predict
- ์ค๋ช : ์์ฑํ ์ต๋ ํ ํฐ ์ ์ง์
-
ํน์ง:
-1
: ๋ฌด์ ํ,-2
: ์ต๋ ์ปจํ ์คํธ ์ฑ์- ์ผ๋ฐ์ ์ผ๋ก๋ 128~1024 ๋ฒ์์์ ์ค์
11. keep_alive
- ์ค๋ช
: ๋ชจ๋ธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์งํ ์๊ฐ (์:
"5m"
,"0"
,"-1"
) -
ํน์ง:
- ๋น๋ฒํ ์์ฒญ์๋
5m
์ด์์ด ์ ์ - ๋๋ฌผ๊ฒ ์ฌ์ฉํ๋ ๋ชจ๋ธ์
0
์ผ๋ก ์ค์ ํด ๋ฉ๋ชจ๋ฆฌ ํด์ ๊ฐ๋ฅ
- ๋น๋ฒํ ์์ฒญ์๋
12. mirostat
, mirostat_eta
, mirostat_tau
-
์ค๋ช :
mirostat
: ์ํ๋ง ์๊ณ ๋ฆฌ์ฆ ์ค์ (0: ๋นํ์ฑ, 1: Mirostat, 2: Mirostat 2.0)eta
: ํ์ต๋ฅ (0.1 ๊ถ์ฅ)tau
: ๋ชฉํ perplexity (5.0 ๊ถ์ฅ)
-
ํน์ง:
temperature
/top_p
๋ณด๋ค ์ ๋ฐ ์ ์ด ๊ฐ๋ฅ- ๊ณ ๊ธ ์ฌ์ฉ์์๊ฒ ์ ํฉ
13. num_gpu
- ์ค๋ช : ๋ชจ๋ธ์ GPU์ ์ฌ๋ฆด ๋ ์ด์ด ์ ์ค์
-
ํน์ง:
0
: CPU๋ง ์ฌ์ฉ,-1
: ๊ฐ๋ฅํ ์ ์ฒด ๋ ์ด์ด๋ฅผ GPU์ ํ ๋น- GPU ๊ฐ์ฉ์ฑ ๋ฐ VRAM ๊ณ ๋ ค ํ์
14. seed
- ์ค๋ช : ๋์ผํ ์ ๋ ฅ์ ๋ํด ๋์ผ ์ถ๋ ฅ์ ๋ณด์ฅํ๊ธฐ ์ํ ๋์ ์๋ ๊ฐ
-
ํน์ง:
- ํ ์คํธ, ๋๋ฒ๊น , ํ๊ท ๊ฒ์ฆ ์ ์ ์ฉ
temperature
๊ฐ ๋ฎ์์๋กseed
ํจ๊ณผ๊ฐ ์ ๋๋ฌ๋จ
๐ ์ฐธ๊ณ : system
, human
, assistant
๋ฉ์์ง๋ ํ๋ผ๋ฏธํฐ๊ฐ ์๋ ๋ฉ์์ง ์ญํ ์
๋๋ค.
1
2
3
4
5
6
messages = [
("system", "You are a helpful assistant."),
("human", "What's the weather in Seoul?")
]
llm.invoke(messages)
- ์ด๋
ChatOllama
์invoke()
๋๋stream()
๋ฉ์๋์ ์ ๋ฌํ๋ ์ ๋ ฅ ํ์์ ๋๋ค. - LangChain์์๋
ChatPromptTemplate
์ ํตํด ์ญํ ๊ธฐ๋ฐ ๋ฉ์์ง๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
(์ฐธ๊ณ ) ChatOllama vs ChatPromptTemplate ์ฐจ์ด์ ์ฉ๋ ๋น๊ต
ํญ๋ชฉ | ChatOllama |
ChatPromptTemplate |
---|---|---|
์ ์ | Ollama๋ฅผ ํตํด ์คํ๋๋ LLM ๋ชจ๋ธ ๋ํผ (LLM ํธ์ถ๊ธฐ) | ์์คํ ๋ฉ์์ง, ์ฌ์ฉ์ ๋ฉ์์ง ๋ฑ์ผ๋ก ๊ตฌ์ฑ๋ ๋ํํ ํ๋กฌํํธ ์์ฑ๊ธฐ |
์ญํ | LLM์ ์ค์ ๋ก ์คํํ์ฌ ์๋ต์ ์์ฑ | ํ๋กฌํํธ ๋ฉ์์ง๋ค์ ์ฒด๊ณ์ ์ผ๋ก ๊ตฌ์ฑํ๊ณ ํฌ๋งทํ |
์ฃผ์ ๊ธฐ๋ฅ | .invoke() , .stream() ๋ฑ์ ํตํด LLM ์คํ |
.format_messages() , .format() ๋ฑ์ ํตํด structured message ์์ฑ |
์ฌ์ฉ ์์ | ๋ชจ๋ธ์ด ์๋ต์ ์์ฑํด์ผ ํ ๋ | ํ๋กฌํํธ๋ฅผ ๋์ ์ผ๋ก ๊ตฌ์ฑํ๊ฑฐ๋, ์ญํ /๋ฌธ๋งฅ์ ๊ตฌ์กฐํํ ๋ |
๋น์ | ๋๋ตํ๋ ์ฌ๋ (๋ชจ๋ธ) | ์ง๋ฌธ์ ๋ง๋ค์ด์ฃผ๋ ์ฌ๋ (ํ๋กฌํํธ ๋น๋) |
(์ฐธ๊ณ ) ๐ฏ ์ธ์ ๊ฐ๊ฐ์ ์จ์ผ ํ๋๊ฐ?
-
ChatPromptTemplate
์ ํ๋กฌํํธ๋ฅผ ๋ช ํํ ์ค๊ณํ๊ฑฐ๋ ์์คํ ์ญํ , ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ตฌ๋ถํ๊ณ ์ถ์ ๋ ์ฌ์ฉํฉ๋๋ค.- ์: ์์คํ
์ญํ ์ด
"You are a translator"
์ด๊ณ , ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ณ์๋ก ๋ฐ๋ ๋ฒ์ญ๊ธฐ ๊ตฌ์ฑ ๋ฑ
- ์: ์์คํ
์ญํ ์ด
-
ChatOllama
๋ ๋ชจ๋ธ์ ์คํํ ๋ ๋ฐ๋์ ํ์ํฉ๋๋ค.- ์ค์ ์๋ต์ ๋ฐ๊ณ ์ถ์ ๋
.invoke()
,.stream()
๋ฑ์ ํธ์ถ
- ์ค์ ์๋ต์ ๋ฐ๊ณ ์ถ์ ๋
-
๋ณดํต์ ์ด ๋์ ์กฐํฉํด์ ์ฒด์ธ์ผ๋ก ์ฌ์ฉํฉ๋๋ค:
1 2 3
prompt = ChatPromptTemplate.from_messages([...]) # ์ ์ llm = ChatOllama(model="llama3") # ์ ์ธ chain = prompt | llm # ์ฒด์ธ
๐งช LangChain + Ollama ์ข ํฉ ์ค์ต ๊ฐ์ด๋
1. ๐ฌ ์์คํ ํ๋กฌํํธ ๊ธฐ๋ฐ ๋ฒ์ญ๊ธฐ
ChatPromptTemplate
์ ํตํด ๋ค์ํ ์ญํ (system
)์ ์ค์ ํ ํ๋กฌํํธ ํ ํ๋ฆฟ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from langchain_core.prompts import ChatPromptTemplate
# from langchain_community.chat_models import ChatOllama
from langchain_ollama import ChatOllama
# 1. ํ๋กฌํํธ ํ
ํ๋ฆฟ ์ ์
prompt = ChatPromptTemplate.from_messages([
("system", "You are a translator from English to Korean."),
("human", "{sentence}")
])
# 2. LLM ๋ชจ๋ธ ์ ์ (Ollama ๊ธฐ๋ฐ)
llm = ChatOllama(model="llama3.1:8b")
# 3. ์ฒด์ธ ๊ตฌ์ฑ
chain = prompt | llm
# 4. ์คํ
result = chain.invoke({"sentence": "I love programming."})
print(result.content) # ๋๋ ํ๋ก๊ทธ๋๋ฐ์ ์ข์ํฉ๋๋ค.
2. ๐ฅ๏ธ .invoke()
ํจ์ ์ฌ์ฉํ๊ธฐ
- LangChain์
.invoke()
๋ฉ์๋๋ฅผ ๊ฐ์ฅ ๋จ์ํ๊ณ ์ง๊ด์ ์ผ๋ก ๋ณด์ฌ์ค๋๋ค.
์ฝ๋
1
2
3
4
5
6
from langchain_ollama import ChatOllama
llm = ChatOllama(model="llama3.1:8b")
response = llm.invoke("Where is the nearest train station in Kingston, London?")
print(response.content)
๋ต๋ณ
1
2
3
4
5
6
7
8
9
10
Kingston upon Thames has several train stations. The two main ones are:
1. **Kingston Railway Station**: This is the primary railway station serving Kingston and is located on London Road, near the town centre. It's operated by Southern and Thameslink trains.
2. **Norbiton Station** (about 0.8 miles from Kingston Centre): Although not in the very heart of Kingston, this station offers a convenient alternative for those living nearby.
Both stations offer frequent services to central London, as well as other destinations in South London and beyond.
To get to either of these stations, you can use online journey planners like National Rail or Transport for London (TfL) Journey Planner.
If you're looking for the nearest train station from a specific location within Kingston, I'd be happy to help with more precise directions!
3. ๐ ๏ธ Tool Calling ๊ธฐ๋ฅ (๋๊ตฌ ์๋ ํธ์ถ)
@tool
: ํจ์๋ฅผ LangChain์์ ์ฌ์ฉ ๊ฐ๋ฅํ ํด๋ก ๋ฑ๋ก.bind_tools()
: LLM์ ๋๊ตฌ ์ฌ์ฉ ๊ถํ ๋ถ์ฌ.invoke()
: ์ ์ ์ ๋ ฅ์ ๋ฐ๋ผ LLM์ด ํด ์ฌ์ฉ ์ฌ๋ถ ํ๋จresponse.tool_calls
: LLM์ด ํธ์ถํ ๋๊ตฌ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ช ์ํ ์ง์tool.invoke(args)
: ๋๊ตฌ๋ฅผ ์๋ ์คํํ์ฌ ์ค์ ๊ฒฐ๊ณผ ์์ฑ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
@tool
def check_balance(account_id: int) -> str:
"""Check the balance of a bank account by its ID."""
return f"Account {account_id} has $5,000."
llm = ChatOllama(model="llama3.1:8b").bind_tools([check_balance])
response = llm.invoke("Check the balance for account 1234.")
print("########################")
print(response.tool_calls)
print("########################")
# tool_calls๋ง ์์ผ๋ฉด, LLM์ด ํ๋จ๋ง ํ๋ค๋ ๋ป โ ์ค์ ์คํ์ ์ฐ๋ฆฌ๊ฐ ์ง์
if response.tool_calls:
for call in response.tool_calls:
tool_name = call["name"]
args = call["args"]
# ์ฌ๊ธฐ์ ์ค์ ์คํ
if tool_name == "check_balance":
result = check_balance.invoke(args)
print(f"โ
Tool ์คํ ๊ฒฐ๊ณผ: {result}")
logic flow
1
2
3
4
5
6
7
8
9
[์ ์ ์
๋ ฅ]
โ
LLM ๋ถ์ โ "๋๊ตฌ๊ฐ ํ์ํด!"
โ
tool_calls ๋ฐํ (ํจ์ ์ด๋ฆ + ํ๋ผ๋ฏธํฐ)
โ
๊ฐ๋ฐ์๊ฐ ์ง์ tool ์คํ (invoke)
โ
์คํ ๊ฒฐ๊ณผ ์ถ๋ ฅ or ํ์ ๋ํ๋ก ์ ๋ฌ
์คํ ๊ฒฐ๊ณผ ์์:
1
2
3
4
5
6
7
8
9
10
########################
[
{'name': 'check_balance',
'args': {'account_id': 1234},
'id': 'f62b110c-9d9c-4500-a753-4b5afcf2dfd1',
'type': 'tool_call'}
]
########################
โ
Tool ์คํ ๊ฒฐ๊ณผ: Account 1234 has $5,000.
๐ธ ์ด ๊ตฌ์กฐ๋ ๋ฉํฐ์คํ Agent๋ก ํ์ฅ๋ ์ ์์ผ๋ฉฐ, LangGraph์์๋
tool ํธ์ถ
โ์คํ
โํ์ ํ๋กฌํํธ
์๋ ํ๋ฆ๋ ๊ตฌ์ฑ ๊ฐ๋ฅํฉ๋๋ค.
์ฐธ๊ณ - langraph ์ ์ฉ
(์์ฑ ๊ตฌ์กฐ)
1
2
3
4
5
[entry: llm_call]
โ
(tool_call ์์?) โโ Yes โ [tool_execute] โ [llm_continue] โ END
โ
โโโ No โ [end_or_output] โ END
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from typing import TypedDict, List, Dict
from langgraph.graph import StateGraph, END
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
@tool
def check_balance(account_id: int) -> str:
"""Check the balance of a bank account by its ID."""
return f"Account {account_id} has $5,000."
llm = ChatOllama(model="llama3.1:8b").bind_tools([check_balance])
# -----------------------
# ์ํ ์ ์
# -----------------------
class AgentState(TypedDict):
messages: List[Dict]
tool_calls: List[Dict]
tool_results: List[Dict]
# -----------------------
# ๋
ธ๋ ํจ์ ์ ์
# -----------------------
def llm_call(state: AgentState) -> AgentState:
response = llm.invoke(state["messages"])
return {
"messages": state["messages"] + [response],
"tool_calls": response.tool_calls,
"tool_results": []
}
def tool_execute(state: AgentState) -> AgentState:
results = []
for call in state["tool_calls"]:
if call["name"] == "check_balance":
result = check_balance.invoke(call["args"])
results.append({"tool": call["name"], "output": result})
return {**state, "tool_results": results}
def llm_continue(state: AgentState) -> AgentState:
tool_msgs = [
{"role": "tool", "tool_call_id": call["id"], "content": res["output"]}
for call, res in zip(state["tool_calls"], state["tool_results"])
]
response = llm.invoke(state["messages"] + tool_msgs)
return {"messages": state["messages"] + tool_msgs + [response]}
def return_as_is(state: AgentState) -> AgentState:
return state # LLM๋ง ํธ์ถ ํ ๋๋ผ ๊ฒฝ์ฐ
# -----------------------
# ๊ทธ๋ํ ๊ตฌ์ฑ
# -----------------------
builder = StateGraph(state_schema=AgentState)
builder.add_node("llm_call", llm_call)
builder.add_node("tool_execute", tool_execute)
builder.add_node("llm_continue", llm_continue)
builder.add_node("skip_tool", return_as_is)
builder.set_entry_point("llm_call")
# ์กฐ๊ฑด ๋ถ๊ธฐ ์ถ๊ฐ (tool_call์ด ์๋์ง ์ฌ๋ถ์ ๋ฐ๋ผ)
def has_tool(state: AgentState) -> str:
return "yes" if state["tool_calls"] else "no"
builder.add_conditional_edges(
"llm_call",
has_tool,
{
"yes": "tool_execute",
"no": "skip_tool"
}
)
builder.add_edge("tool_execute", "llm_continue")
builder.add_edge("llm_continue", END)
builder.add_edge("skip_tool", END)
graph = builder.compile()
graph
์ด๋ ๊ฒ ํ๋ฉด ์์ฒญํ ์ง์๋ฌธ์ ๋ฐ๋ผ์ ๋ถ๊ธฐ๋ฅผ ์ณ์ ๋ต๋ณํด์ฃผ๋ ๊ฒ์ ํ์ธํ ์ ์์:
4. ๐ง ๋ฉํฐ ๋ฉ์์ง ๋ํ (invoke(messages)
) - langchain
ํต์ฌ ์์ด๋์ด:
- ๋ํ ๋ด์ฉ์ ์ ์ฅํ ๋ฆฌ์คํธ (
chat_history
)๋ฅผ ์ ์งํฉ๋๋ค. - ๋งค๋ฒ LLM์ ํธ์ถํ ๋, ์์คํ
๋ฉ์์ง์ ํจ๊ป
chat_history
์ ์ต๊ทผ N๊ฐ ๋ฉ์์ง, ๊ทธ๋ฆฌ๊ณ ํ์ฌ ์ฌ์ฉ์์ ์ ๋ฉ์์ง๋ฅผ ํจ๊ป ์ ๋ฌํฉ๋๋ค. - LLM์ ์๋ต๋
chat_history
์ ์ถ๊ฐํ์ฌ ๋ค์ ๋ํ์ ํ์ฉํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_ollama import ChatOllama
# LLM ์ด๊ธฐํ
llm = ChatOllama(model="llama3.1:8b")
# 1. ์์คํ
๋ฉ์์ง ์ ์
system_message = SystemMessage(content="You are a helpful AI assistant who remembers the last few turns of our conversation.")
# 2. ๋ํ ๋ด์ฉ(Chat History)์ ์ ์ฅํ ๋ฆฌ์คํธ
chat_history = []
# 3. LLM์ ์ ๋ฌํ ์ต๊ทผ ๋ฉ์์ง ๊ฐ์ ์ค์ (์: ์ต๊ทผ 4๊ฐ ๋ฉ์์ง = 2ํด์ ๋ํ)
# ์ด ๊ฐ์ ์กฐ์ ํ์ฌ ์ผ๋ง๋ ๋ง์ ๊ณผ๊ฑฐ ๋ํ๋ฅผ LLM์ด ๊ธฐ์ตํ๊ฒ ํ ์ง ๊ฒฐ์ ํ ์ ์์ต๋๋ค.
# (์์คํ
๋ฉ์์ง๋ ํญ์ ํฌํจ๋๋ฏ๋ก, ์ค์ ๋ก๋ ์ด ๊ฐ์ + 1๊ฐ์ ๋ฉ์์ง๊ฐ ์ ๋ฌ๋จ)
MAX_HISTORY_MESSAGES = 4
print("AI Assistant: ์๋
ํ์ธ์! ๋ํ๋ฅผ ์์ํด ๋ณด์ธ์. (์ข
๋ฃํ๋ ค๋ฉด 'quit'์ ์
๋ ฅํ์ธ์)")
while True:
user_input = input("๋: ")
if user_input.lower() == 'quit':
print("AI Assistant: ๋ํ๋ฅผ ์ข
๋ฃํฉ๋๋ค.")
break
current_human_message = HumanMessage(content=user_input)
# 4. LLM์ ์ ๋ฌํ ๋ฉ์์ง ๋ฆฌ์คํธ ๊ตฌ์ฑ
messages_to_llm = [system_message] # ํญ์ ์์คํ
๋ฉ์์ง๋ก ์์
# ์ต๊ทผ ๋ํ ๊ธฐ๋ก ์ถ๊ฐ (๊ฐ์ฅ ์ค๋๋ ๊ฒ๋ถํฐ ์์๋๋ก)
# chat_history์์ ์ต๊ทผ MAX_HISTORY_MESSAGES ๊ฐ์๋งํผ ๊ฐ์ ธ์ด
recent_history = chat_history[-MAX_HISTORY_MESSAGES:]
messages_to_llm.extend(recent_history)
messages_to_llm.append(current_human_message) # ํ์ฌ ์ฌ์ฉ์ ๋ฉ์์ง ์ถ๊ฐ
# ๋๋ฒ๊น
: LLM์ ์ ๋ฌ๋๋ ๋ฉ์์ง ํ์ธ
# print("\n--- LLM์ ์ ๋ฌ๋๋ ๋ฉ์์ง ---")
# for msg in messages_to_llm:
# print(f"{msg.type}: {msg.content}")
# print("---------------------------\n")
# 5. LLM ํธ์ถ
ai_response = llm.invoke(messages_to_llm)
print(f"AI Assistant: {ai_response.content}")
# 6. ํ์ฌ ์ฌ์ฉ์์ ๋ฉ์์ง์ AI์ ์๋ต์ chat_history์ ์ถ๊ฐ
chat_history.append(current_human_message)
chat_history.append(ai_response) # AIMessage ๊ฐ์ฒด ๊ทธ๋๋ก ์ถ๊ฐ
# (์ ํ์ ) ๋๋ฌด ๋ง์ ํ์คํ ๋ฆฌ๊ฐ ์์ด๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ค๋๋ ํ์คํ ๋ฆฌ ์ ๊ฑฐ
# ์๋ฅผ ๋ค์ด, chat_history์ ์ต๋ ๊ธธ์ด๋ฅผ 10๊ฐ ๋ฉ์์ง (5ํด)๋ก ์ ์ง
# MAX_TOTAL_HISTORY = 10
# if len(chat_history) > MAX_TOTAL_HISTORY:
# chat_history = chat_history[-MAX_TOTAL_HISTORY:]
system_message
: LLM์ ์ญํ ์ด๋ ํ๋ ์ง์นจ์ ์ค์ ํฉ๋๋ค. ์ด ๋ฉ์์ง๋ ๋งค๋ฒ LLM ํธ์ถ ์ ๋ฉ์์ง ๋ฆฌ์คํธ์ ๊ฐ์ฅ ์์ ์์นํฉ๋๋ค.chat_history
: HumanMessage์ AIMessage ๊ฐ์ฒด๋ฅผ ์์๋๋ก ์ ์ฅํ๋ ๋ฆฌ์คํธ์ ๋๋ค. ์ด๊ฒ์ด ์ฐ๋ฆฌ์ ๊ฐ๋จํ ์ฑํ ํ์คํ ๋ฆฌ ์ ์ฅ์ ์ญํ ์ ํฉ๋๋ค.-
MAX_HISTORY_MESSAGES
: LLM์๊ฒ ์ ๋ฌํ chat_history ๋ด ์ต๊ทผ ๋ฉ์์ง์ ์ต๋ ๊ฐ์๋ฅผ ์๋ฏธํฉ๋๋ค.- ์๋ฅผ ๋ค์ด 4๋ก ์ค์ ํ๋ฉด, ๊ฐ์ฅ ์ต๊ทผ์ 4๊ฐ ๋ฉ์์ง (๋ณดํต ์ฌ์ฉ์ ์ง๋ฌธ 2๊ฐ + AI ๋ต๋ณ 2๊ฐ)๊ฐ ์์คํ ๋ฉ์์ง ๋ฐ ํ์ฌ ์ฌ์ฉ์ ์ง๋ฌธ๊ณผ ํจ๊ป ์ ๋ฌ๋ฉ๋๋ค. ์ด ๊ฐ์ ํตํด LLM์ด ์ผ๋ง๋ โ๊ธฐ์ตโํ ์ง ์กฐ์ ํ ์ ์์ต๋๋ค.
(์ฐธ๊ณ ) LangGraph์ checkpointer๋ฅผ ์ฌ์ฉํ๋ฉด ์ ์ฝ๋์์ chat_history๋ฅผ ์ง์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์งํ์ง ์์๋, ์ํ(state) ์์ ์ ์ฅ๋ ๋ฉ์์ง ์ด๋ ฅ์ ์๋์ผ๋ก ์ ์ฅํ๊ณ ๋ณต์ํ ์ ์์ต๋๋ค.
โ LangGraph Checkpointer์ ์ฅ์
๊ธฐ๋ฅ | ์ค๋ช | ์์ |
---|---|---|
โ ์ง์์ฑ | ์ธ์ ์ํ๋ฅผ ์ ์ฅํ์ฌ ์ธ์ ๋ ์ง ๋ณต์ ๊ฐ๋ฅ | ์ฌ์ฉ์๊ฐ ๋ค์ ์ ์ํ์ ๋๋ ์ด์ ๋ํ ์ด์ด๊ฐ๊ธฐ |
โ ์ธ์ ๊ด๋ฆฌ | thread_id ๋ฅผ ํตํด ์ฌ์ฉ์๋ณ ์ธ์
๋ถ๋ฆฌ |
์ ์ A, ์ ์ B์ ๋ํ ์ด๋ ฅ์ ๋์์ ๊ด๋ฆฌ ๊ฐ๋ฅ |
โ ํ์ฅ์ฑ | Redis, Postgres, MongoDB ๋ฑ๊ณผ ์ฐ๋ ๊ฐ๋ฅ | ์๋น์ค ๊ท๋ชจ ํ์ฅ ์์๋ ์์ ์ ์ผ๋ก ์ํ ๊ด๋ฆฌ |
โ ์๋ ํ๋ฆ ์ ์ฅ | ๊ฐ ๋ ธ๋ ์คํ ํ ์ํ๋ฅผ ์๋ ์ฒดํฌํฌ์ธํธ | ๋ํ ํ๋ฆ์ด ๋๊ฒจ๋ ์ด์ด์ ์คํ ๊ฐ๋ฅ (resume) |
โ ๊ธฐ์ต ๋ฒ์ ์กฐ์ | ์ํ ๋ด ๋ฉ์์ง ์ ์ ํ ๋๋ ์์ฝ ๊ฐ๋ฅ | LLM context ์ ํ์ ๋ง์ถฐ ์ต๊ทผ N๊ฐ๋ง ์ ์ง |
5. ๐ ํ ํฐ ์คํธ๋ฆฌ๋ฐ (๋๊ธฐ ๋ฒ์ )
1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain_community.chat_models import ChatOllama
# streaming=True๋ก LLM์ ์ด๊ธฐํํฉ๋๋ค.
llm = ChatOllama(model="llama3.1:8b", streaming=True)
print("AI๊ฐ ์ด์ผ๊ธฐ๋ฅผ ์์ํฉ๋๋ค...")
# llm.stream()์ ์ฌ์ฉํ์ฌ ์๋ต์ ์คํธ๋ฆฌ๋ฐ์ผ๋ก ๋ฐ์ต๋๋ค.
for chunk in llm.stream("Tell me a story about a fox and a cat."):
# chunk.content์ ํ์ฌ ์์ ๋ ํ
์คํธ ์กฐ๊ฐ์ด ๋ค์ด์์ต๋๋ค.
# end=""๋ print ํจ์๊ฐ ์๋์ผ๋ก ์ค๋ฐ๊ฟํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
# flush=True๋ ์ถ๋ ฅ ๋ฒํผ๋ฅผ ์ฆ์ ๋น์ ํ๋ฉด์ ๋ฐ๋ก ํ์๋๋๋ก ํฉ๋๋ค.
print(chunk.content, end="", flush=True)
print("\n์ด์ผ๊ธฐ ๋!")
-
streaming=True (LLM ์ด๊ธฐํ ์)
:- ์๋ ์๋ฆฌ:
ChatOllama(..., streaming=True)
์ ๊ฐ์ด LLM์ ์ด๊ธฐํํ ๋ streaming ์ต์ ์ True๋ก ์ค์ ํ๋ฉด, LLM์๊ฒ ์๋ต์ ์์ฑํ๋ ์ฆ์ ์์ ์กฐ๊ฐ(chunk ๋๋ token) ๋จ์๋ก ๋ณด๋ด๋ฌ๋ผ๊ณ ์์ฒญํ๋ ๊ฒ์ ๋๋ค. LLM์ ์ ์ฒด ์๋ต์ด ์์ฑ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ , ์์ฑ๋๋ ๋ถ๋ถ๋ถํฐ ์์ฐจ์ ์ผ๋ก ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํฉ๋๋ค. llm.stream()
๊ณผ์ ๊ด๊ณ: ์ดstreaming=True
์ค์ ์llm.stream()
๋ฉ์๋์ ํจ๊ป ์ฌ์ฉ๋ ๋ ์๋ฏธ๊ฐ ์์ต๋๋ค. llm.stream()์ ์ด๋ ๊ฒ ์คํธ๋ฆฌ๋ฐ ๋ฐฉ์์ผ๋ก ์ ๋ฌ๋๋ ์๋ต ์กฐ๊ฐ๋ค์ ์ํํ ์ ์๋ ๋ฐ๋ณต์(iterator)๋ฅผ ๋ฐํํฉ๋๋ค. for chunk in llm.stream(โฆ) ๊ตฌ๋ฌธ์ ํตํด ๊ฐ ์กฐ๊ฐ์ ์ค์๊ฐ์ผ๋ก ๋ฐ์ ์ ์์ต๋๋ค.
- ์๋ ์๋ฆฌ:
-
print(..., flush=True) (์ถ๋ ฅ ์)
:- print() ํจ์์ flush ๋งค๊ฐ๋ณ์: ์ด๊ฒ์ Python์ ๋ด์ฅ print() ํจ์์ ์๋ ์ต์ ์ ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก flush=False์ ๋๋ค.
flush=True
์ ์ญํ :print(..., flush=True)
๋ฅผ ์ฌ์ฉํ๋ฉด, print ํจ์๊ฐ ํธ์ถ๋ ๋๋ง๋ค ์ถ๋ ฅ ๋ฒํผ์ ๋ด์ฉ์ ์ฆ์ ๊ฐ์ ๋ก ๋น์ฐ๊ณ ํด๋น ๋ด์ฉ์ ํ๋ฉด(๋๋ ๋ค๋ฅธ ์ถ๋ ฅ ๋์)์ผ๋ก ๋ณด๋ด๋๋ก ํฉ๋๋ค.- ๋ฐ๋ผ์
end=""
์ ํจ๊ปflush=True
๋ฅผ ์ฌ์ฉํ๋ฉด, ์คํธ๋ฆฌ๋ฐ์ผ๋ก ์์ ๋๋ ๊ฐ ํ ์คํธ ์กฐ๊ฐ(chunk.content
)์ดprint
๋๋ ์ฆ์ ํ๋ฉด์ ๋ํ๋๊ฒ ๋์ด, ๋ง์น ํ์ ์น๋ฏ์ด ์ค์๊ฐ์ผ๋ก ํ ์คํธ๊ฐ ํ์๋๋ ํจ๊ณผ๋ฅผ ์ป์ ์ ์์ต๋๋ค.
๐งญ ๋ง๋ฌด๋ฆฌ
ChatOllama
๋ฅผ ํ์ฉํ๋ฉด ๋ก์ปฌ์์ ์คํ ์ค์ธ Ollama ๋ชจ๋ธ์ LangChain ์ฒด์ธ์ ์์ฝ๊ฒ ํตํฉํ ์ ์์ผ๋ฉฐ,
์ด๋ฅผ ํตํด ๋ํํ ์์ด์ ํธ, ํด ์ฐ๋, ์คํธ๋ฆฌ๋ฐ ์ถ๋ ฅ ๋ฑ ๋ค์ํ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์ต๋๋ค.
๋ค์ ํฌ์คํธ์์๋ LangChain์ ์์ ๊ฐ๋
์ธ LangGraph๋ฅผ ๋ณต์ตํ๊ณ ,
์ค์ ์ํฌํ๋ก์ฐ ์ค๊ณ์ ์ํ ๊ธฐ๋ฐ ์์ด์ ํธ ๊ตฌํ์ ๋ํด ์ ๋ฆฌํด๋ณผ ์์ ์
๋๋ค.
LangChain์ ๊ตฌ์กฐ์ ๊ตฌ์ฑ๊ณผ SLM ๊ธฐ๋ฐ ์์ฉ์ ๊ด์ฌ ์๋ ๋ถ๋ค์๊ฒ๋ ๋ง์ ๋์์ด ๋์ จ๊ธธ ๋ฐ๋๋๋ค. ๐ฅ
์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค!