[๊ฐ์๋ ธํธ] LangChain Academy : Introduction to LangGraph (Module 4)
์๋ณธ ๊ฒ์๊ธ: https://velog.io/@euisuk-chung/LangChain-Academy-Introduction-to-LangGraph-Module-4
๋ญ์ฒด์ธ(LangChain)๊ณผ ๋ญ๊ทธ๋ํ(LangGraph)๋ ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ(LLM)์ ํ์ฉํ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ํ ๋๊ตฌ๋ค์ ๋๋ค. ์ ๊ฐ์๋ LangChain์์ ์ด์ํ๋ LangChain Academy์์ ์ ์ํ โIntroduction to LangGraphโ ๊ฐ์์ ๋ด์ฉ์ ์ ๋ฆฌ ๋ฐ ์ถ๊ฐ ์ค๋ช ํ ๋ด์ฉ์ ๋๋ค.
- ๊ฐ์ ๋งํฌ : https://youtu.be/29XE10U6ooc
- ๋ญ์ฒด์ธ : https://www.langchain.com/
์ด๋ฒ ํฌ์คํธ๋ โModule4โ๋ด์ฉ์ ๋ค๋ฃน๋๋ค:
- Lesson 1: Parallelization
- Lesson 2: Sub-graphs
- Lesson 3: Map-reduce
- Lesson 4: Research Assistant
Lesson 1: Parallelization
๊ฐ์
- ๋ณ๋ ฌํ(Parallelization)๋ ์์
์ ์ฌ๋ฌ ๊ฐ์ ๋
ธ๋๋ก ๋๋์ด ๋์์ ์ฒ๋ฆฌํ์ฌ ์ฑ๋ฅ์ ๋์ด๋ ๋ฐฉ๋ฒ์
๋๋ค.
- ๊ทธ๋ฌ๋ ๋ณ๋ ฌ๋ก ์คํ๋ ๋ ธ๋๋ค์ด ๋์ผํ ์ํ ํค๋ฅผ ์ ๋ฐ์ดํธํ ๋ ์ํ ์ถฉ๋์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก, ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฆฌ๋์(Reducer)๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ์์ ํ๊ฒ ๋ณํฉํ ์ ์์ต๋๋ค.
์ฃผ์ ๊ฐ๋
- ๋ณ๋ ฌํ ๊ฐ๋
ํฌ์์(fan-out)
: ํ ๋ ธ๋์์ ์ฌ๋ฌ ๋ ธ๋๋ก ์์ ์ด ํ์ฅ๋๋ ๊ณผ์ ์ ๋๋ค.- ์๋ฅผ ๋ค์ด, ๋
ธ๋ A์์ B์ C๋ฅผ ๋์์ ์คํํ๋ ๊ฒ์
ํฌ์์
์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
- ์๋ฅผ ๋ค์ด, ๋
ธ๋ A์์ B์ C๋ฅผ ๋์์ ์คํํ๋ ๊ฒ์
ํฌ์ธ(fan-in)
: ์ฌ๋ฌ ๋ณ๋ ฌ ์์ ์ด ๋ชจ๋ ์๋ฃ๋ ํ ํ๋์ ๋ ธ๋๋ก ๋ค์ ํฉ์ณ์ง๋ ๊ณผ์ ์ ๋๋ค.- ์๋ฅผ ๋ค์ด, B์ C์ ์์
์ด ์๋ฃ๋๋ฉด D ๋
ธ๋๋ก ํฉ์ณ์ง๋ ๊ฒ์ด
ํฌ์ธ
์ ๋๋ค.
- ์๋ฅผ ๋ค์ด, B์ C์ ์์
์ด ์๋ฃ๋๋ฉด D ๋
ธ๋๋ก ํฉ์ณ์ง๋ ๊ฒ์ด
-
๋ณ๋ ฌ ๋ ธ๋ ์คํ:
- ์์ ์ ์ฌ๋ฌ ๋ ธ๋๋ก ๋ถ๋ฐฐํ์ฌ ๋์์ ์คํํ๋ ์์ ์ ์๋ฏธํฉ๋๋ค.
- ์ฌ๋ฌ ๋ ธ๋๊ฐ ๋์์ ์คํ๋ ๋ ์ํ๋ฅผ ์์ ํ๊ฒ ์ ๋ฐ์ดํธํ๊ธฐ ์ํ ์กฐ์น๊ฐ ํ์ํฉ๋๋ค.
-
๋ฆฌ๋์(Reducer):
- ๋ณ๋ ฌ๋ก ์คํ๋ ๋ ธ๋๊ฐ ๋์ผํ ์ํ ํค๋ฅผ ์ ๋ฐ์ดํธํ ๋ ์ด๋ฅผ ๋ณํฉํ๋ ์ญํ ์ ์๋ฏธํฉ๋๋ค.
- ๋ณ๋ ฌ ๋ ธ๋๊ฐ ์ํ ํค์ ์ถ๊ฐํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฆฌ์คํธ๋ก ๋ณํฉํ๋ ๋ฑ์ ์์ ์ ์ํํฉ๋๋ค.
์ฝ๋ ์์
- ๊ธฐ๋ณธ์ ์ธ ๋ณ๋ ฌํ ์ฒ๋ฆฌ ์์:
- ๊ธฐ๋ณธ์ ์ผ๋ก ์ง๋ ฌ ์คํ๋๋ ๊ทธ๋ํ์์ A, B, C, D ์์๋๋ก ๋ ธ๋๊ฐ ์คํ๋ฉ๋๋ค.
- ์๋ ๊ทธ๋ํ๋ ๊ฐ ๋จ๊ณ์์ ์ํ๋ฅผ ๋ฎ์ด์ฐ๋ ๊ฐ๋จํ ์ ํ ๊ทธ๋ํ์ ๋๋ค.
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
from IPython.display import Image, display
from typing import Any
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
# operator.add ๋ฆฌ๋์ ํจ์๋ ์ด๊ฒ์ ์ถ๊ฐ ์ ์ฉ์ผ๋ก ๋ง๋ญ๋๋ค
state: str
class ReturnNodeValue:
def __init__(self, node_secret: str):
self._value = node_secret
def __call__(self, state: State) -> Any:
print(f"{self._value}๋ฅผ {state['state']}์ ์ถ๊ฐํฉ๋๋ค")
return {"state": [self._value]}
# ๋
ธ๋ ์ถ๊ฐ
builder = StateGraph(State)
# ๊ฐ ๋
ธ๋๋ฅผ node_secret์ผ๋ก ์ด๊ธฐํ
builder.add_node("a", ReturnNodeValue("๋๋ A์
๋๋ค"))
builder.add_node("b", ReturnNodeValue("๋๋ B์
๋๋ค"))
builder.add_node("c", ReturnNodeValue("๋๋ C์
๋๋ค"))
builder.add_node("d", ReturnNodeValue("๋๋ D์
๋๋ค"))
# ํ๋ฆ
builder.add_edge(START, "a")
builder.add_edge("a", "b")
builder.add_edge("b", "c")
builder.add_edge("c", "d")
builder.add_edge("d", END)
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
- ๋ณ๋ ฌํ ์ฒ๋ฆฌ:
- ์ด์ A์์ B์ C๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋๋๋ก ํฌ์์ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
builder = StateGraph(State)
# ๊ฐ ๋
ธ๋๋ฅผ node_secret์ผ๋ก ์ด๊ธฐํ
builder.add_node("a", ReturnNodeValue("๋๋ A์
๋๋ค"))
builder.add_node("b", ReturnNodeValue("๋๋ B์
๋๋ค"))
builder.add_node("c", ReturnNodeValue("๋๋ C์
๋๋ค"))
builder.add_node("d", ReturnNodeValue("๋๋ D์
๋๋ค"))
# ํ๋ฆ
builder.add_edge(START, "a")
builder.add_edge("a", "b")
builder.add_edge("a", "c")
builder.add_edge("b", "d")
builder.add_edge("c", "d")
builder.add_edge("d", END)
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
- ์๋ฌ ๋ฐ์
- ๊ตฌ์กฐ๋ ์์ ๊ฐ์ด ์ฝ๊ฒ ์ ์ํ ์ ์์ง๋ง, ๋ง์ ์คํํด๋ณด๋ฉด, B์ C๊ฐ
๋์์ ๋์ผํ ์ํ ํค๋ฅผ ์ ๋ฐ์ดํธ
ํ๋ ค ํ๋ฏ๋ก ์ถฉ๋์ด ๋ฐ์ํ์ฌ ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
- ๋ฆฌ๋์๋ฅผ ํตํ ์ถฉ๋ ํด๊ฒฐ:
- ๋ณ๋ ฌ ๋ ธ๋ B์ C๊ฐ ๋์์ ๋ฆฌ์คํธ์ ๊ฐ์ ์ถ๊ฐํ๋๋ก ๋ฆฌ๋์๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ์ถฉ๋์ ํด๊ฒฐํฉ๋๋ค.
- ํฌ ์์์ ์ฌ์ฉํ ๋๋ ๋จ๊ณ๊ฐ ๋์ผํ ์ฑ๋/ํค์ ์ฐ๋ ๊ฒฝ์ฐ ๋ฆฌ๋์๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
- ๋ชจ๋ 2์์ ์ธ๊ธํ๋ฏ์ด
operator.add
๋ Python์ ๋ด์ฅ operator ๋ชจ๋์ ํจ์์ ๋๋ค. operator.add
๊ฐ ๋ฆฌ์คํธ์ ์ ์ฉ๋๋ฉด ๋ฆฌ์คํธ ์ฐ๊ฒฐ์ ์ํํฉ๋๋ค.
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
import operator
from typing import Annotated
class State(TypedDict):
# operator.add ๋ฆฌ๋์ ํจ์๋ ์ด๊ฒ์ ์ถ๊ฐ ์ ์ฉ์ผ๋ก ๋ง๋ญ๋๋ค
state: Annotated[list, operator.add]
# ๋
ธ๋ ์ถ๊ฐ
builder = StateGraph(State)
# ๊ฐ ๋
ธ๋๋ฅผ node_secret์ผ๋ก ์ด๊ธฐํ
builder.add_node("a", ReturnNodeValue("๋๋ A์
๋๋ค"))
builder.add_node("b", ReturnNodeValue("๋๋ B์
๋๋ค"))
builder.add_node("c", ReturnNodeValue("๋๋ C์
๋๋ค"))
builder.add_node("d", ReturnNodeValue("๋๋ D์
๋๋ค"))
# ํ๋ฆ
builder.add_edge(START, "a")
builder.add_edge("a", "b")
builder.add_edge("a", "c")
builder.add_edge("b", "d")
builder.add_edge("c", "d")
builder.add_edge("d", END)
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
1
graph.invoke({"state": []})
- ์์ ๊ฐ์ด ๊ทธ๋ํ๋ฅผ ์ํํ๋ฉด ์๋ํ ๊ฒ์ฒ๋ผ ๋๊น์ง ์คํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
1
2
3
4
5
๋๋ A์
๋๋ค๋ฅผ []์ ์ถ๊ฐํฉ๋๋ค
๋๋ B์
๋๋ค๋ฅผ ['๋๋ A์
๋๋ค']์ ์ถ๊ฐํฉ๋๋ค
๋๋ C์
๋๋ค๋ฅผ ['๋๋ A์
๋๋ค']์ ์ถ๊ฐํฉ๋๋ค
๋๋ D์
๋๋ค๋ฅผ ['๋๋ A์
๋๋ค', '๋๋ B์
๋๋ค', '๋๋ C์
๋๋ค']์ ์ถ๊ฐํฉ๋๋ค
{'state': ['๋๋ A์
๋๋ค', '๋๋ B์
๋๋ค', '๋๋ C์
๋๋ค', '๋๋ D์
๋๋ค']}
- ์ด์
b
์c
๊ฐ ๋ณ๋ ฌ๋ก ์ํํ ์ ๋ฐ์ดํธ๊ฐ ์ํ์ ์ถ๊ฐ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
- ์ถ๊ฐ ์ค์ต
- ์ข ๋ ๋ณต์กํ ๋ณ๋ ฌ ๊ทธ๋ํ์์๋ ์ ๋๋ก ์๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
- ์ด ๊ฒฝ์ฐ
b
,b2
,c
๋ ๋ชจ๋ ๋์ผํ ๋จ๊ณ์ ์ผ๋ถ์ ๋๋ค. - ๊ทธ๋ํ๋
d
๋จ๊ณ๋ก ์งํํ๊ธฐ ์ ์ ์ด ๋ชจ๋ ๊ฒ๋ค์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class State(TypedDict):
# operator.add ๋ฆฌ๋์ ํจ์๋ ์ด๊ฒ์ ์ถ๊ฐ ์ ์ฉ์ผ๋ก ๋ง๋ญ๋๋ค
state: Annotated[list, operator.add]
# Add nodes
builder = StateGraph(State)
# Initialize each node with node_secret
builder.add_node("a", ReturnNodeValue("I'm A"))
builder.add_node("b", ReturnNodeValue("I'm B"))
builder.add_node("b2", ReturnNodeValue("I'm B2"))
builder.add_node("c", ReturnNodeValue("I'm C"))
builder.add_node("d", ReturnNodeValue("I'm D"))
# Flow
builder.add_edge(START, "a")
builder.add_edge("a", "b")
builder.add_edge("a", "c")
builder.add_edge("b", "b2")
builder.add_edge(["b2", "c"], "d")
builder.add_edge("d", END)
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
b
์c
๋ ๋ณ๋ ฌ๋ก ์คํ์ด ๋๊ธฐ๋๋ฌธ์ ๋จผ์ ์คํ์ด ๋๊ณ ,.add
operator๋ก ์ธํด์ append๋ ์์๋๋ก ์ถ๋ ฅ์ด ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
1
{'state': ["I'm A", "I'm B", "I'm C", "I'm B2", "I'm D"]}
- ๊ทธ๋ํ ๋ณ๋ ฌํ ์์ ์ ์ด
- ๊ธฐ๋ณธ์ ์ผ๋ก
LangGraph
๋ ๋ณ๋ ฌ๋ก ์คํ๋๋ ๋ ธ๋๋ค์ ์ ๋ฐ์ดํธ ์์๋ฅผ์๋์ผ๋ก ๊ฒฐ์
ํฉ๋๋ค. -
์์ 4๋ฒ ์์ ์์๋ ์ด๋ ์ฐ๋ฆฌ๊ฐ ์ ์ดํ์ง ์๋ ๊ทธ๋ํ ํ ํด๋ก์ง์ ๊ธฐ๋ฐํ์ฌ LangGraph๊ฐ ๊ฒฐ์ ๋ก ์ ์์๋ฅผ ์ ํด์ค ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
- ์์์ ์ฐ๋ฆฌ๋
c
๊ฐb2
์ ์ ์ถ๊ฐ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. - ํ์ง๋ง, ๋ง์ฝ ์ฌ์ฉ์๊ฐ
ํน์ ์์๋ก ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌ
ํ๊ณ ์ถ๋ค๋ฉด, ์ปค์คํ ๋ฆฌ๋์(Custom Reducer)๋ฅผ ์ ์ํ์ฌ ์ด ์์๋ฅผ ์ง์ ์ ์ดํ ์ ์์ต๋๋ค.
- ์์์ ์ฐ๋ฆฌ๋
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
def sorting_reducer(left, right):
""" ๋ฆฌ์คํธ์ ๊ฐ๋ค์ ๊ฒฐํฉํ๊ณ ์ ๋ ฌํฉ๋๋ค"""
if not isinstance(left, list):
left = [left]
if not isinstance(right, list):
right = [right]
return sorted(left + right, reverse=False)
class State(TypedDict):
# sorting_reducer๋ ์ํ์ ๊ฐ๋ค์ ์ ๋ ฌํฉ๋๋ค
state: Annotated[list, sorting_reducer]
# ๋
ธ๋ ์ถ๊ฐ
builder = StateGraph(State)
# ๊ฐ ๋
ธ๋๋ฅผ node_secret์ผ๋ก ์ด๊ธฐํ
builder.add_node("a", ReturnNodeValue("๋๋ A์
๋๋ค"))
builder.add_node("b", ReturnNodeValue("๋๋ B์
๋๋ค"))
builder.add_node("b2", ReturnNodeValue("๋๋ B2์
๋๋ค"))
builder.add_node("c", ReturnNodeValue("๋๋ C์
๋๋ค"))
builder.add_node("d", ReturnNodeValue("๋๋ D์
๋๋ค"))
# ํ๋ฆ
builder.add_edge(START, "a")
builder.add_edge("a", "b")
builder.add_edge("a", "c")
builder.add_edge("b", "b2")
builder.add_edge(["b2", "c"], "d")
builder.add_edge("d", END)
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
- ์ด์ ๋ฆฌ๋์๊ฐ ์ ๋ฐ์ดํธ๋ ์ํ ๊ฐ์ ์ ๋ ฌํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
1
{'state': ["I'm A", "I'm B", "I'm B2", "I'm C", "I'm D"]}
- (์์ฉ) ๊ทธ๋ํ ๋ณ๋ ฌํ ์์ ์ ์ด
- ์ข ๋ ์ด๋ ค์ด(?) ๋ชจ์์ผ๋ก ๊ทธ๋ํ๋ฅผ ๋ง๋ค๊ณ ํ๋ฒ ๋ ์ค์ต์ ํด๋ณด๊ฒ ์ต๋๋ค.
1) ๊ธฐ์กด operator.add
๋ฆฌ๋์ ์ฌ์ฉํ ๊ฒฝ์ฐ:
1
2
3
class State(TypedDict):
# operator.add ๋ฆฌ๋์ ํจ์๋ ์ด๊ฒ์ ์ถ๊ฐ ์ ์ฉ์ผ๋ก ๋ง๋ญ๋๋ค
state: Annotated[list, operator.add]
- ์ด ๋ฐฉ์์์๋ ๋ ธ๋๊ฐ ์์ฐจ์ ์ผ๋ก ๊ฐ์ ์ถ๊ฐํ๋ ๋ฐฉ์์ ๋๋ค.
- ๊ฐ ๋ ธ๋์์ ์์ฑ๋ ๊ฐ์ ์์๋๋ก ์ํ ๋ฆฌ์คํธ์ ์ถ๊ฐ๋ฉ๋๋ค.
"b1"
๊ณผ"c1"
์ด ๋ณ๋ ฌ๋ก ์คํ๋๊ณ , ๊ทธ ํ์"b2"
์"c2"
๊ฐ ์คํ๋์ด ๊ทธ ๊ฒฐ๊ณผ๋ค์ด ํฉ์ณ์ง ๋ค์"d"
๋ก ์ด๋ํฉ๋๋ค.- ์ด๋๋ ์ฝ๊ฐ์ DFS์ฒ๋ผ ๋์ํฉ๋๋ค. ์ฆ, ํ ๊ฒฝ๋ก๋ฅผ ๋ฐ๋ผ ๋๊น์ง ์คํํ ๋ค์, ๋ค๋ฅธ ๊ฒฝ๋ก๋ฅผ ๋ฐ๋ผ๊ฐ๋๋ค.
- ๋ฐ๋ผ์ ์ถ๋ ฅ์
"I'm A", "I'm B1", "I'm C1", "I'm B2", "I'm C2", "I'm D"
์ ๊ฐ์ด ๋ํ๋ฉ๋๋ค. -
์๋ ํ๋ฆ์ ์ดํด๋ณด๋ฉด:
a
์คํ ํb1
๊ณผc1
๋ณ๋ ฌ๋ก ์คํ.b1
๊ฐ ์คํ๋๊ณ ๋์b2
๋ก ์ด๋.c1
์ด ์คํ๋๊ณ ๋์c2
๋ก ์ด๋.b2
์c2
๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋๊ณ ๊ทธ ํd
๋ก ์ด๋.
-
๊ฒฐ๊ณผ:
1
{'state': ["I'm A", "I'm B1", "I'm C1", "I'm B2", "I'm C2", "I'm D"]}
- ์ด๊ฒ์ ๋ง์น DFS์์ ํ ๊ฒฝ๋ก๋ฅผ ์ญ ๋ฐ๋ผ๊ฐ๋ฉด์ ์คํํ๊ณ , ๊ทธ ํ ๋ค๋ฅธ ๊ฒฝ๋ก๋ก ์งํํ๋ ํ๋ฆ๊ณผ ์ ์ฌํฉ๋๋ค.
2) sorting_reducer
๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ:
1
2
3
4
5
6
7
8
9
10
11
12
13
def sorting_reducer(left, right):
"""Combines and sorts the values in a list"""
if not isinstance(left, list):
left = [left]
if not isinstance(right, list):
right = [right]
return sorted(left + right, reverse=False)
class State(TypedDict):
# sorting_reducer will sort the values in state
state: Annotated[list, sorting_reducer]
sorting_reducer
๋ฅผ ์ฌ์ฉํ๋ฉด, ๋ณ๋ ฌ๋ก ์คํ๋ ๋ ธ๋๋ค์ ๊ฐ์ด ์ ๋ ฌ๋์ด ์ํ ๋ฆฌ์คํธ์ ์ถ๊ฐ๋ฉ๋๋ค.- ์ด ๋ฐฉ์์ BFS์ฒ๋ผ ์๋ํฉ๋๋ค. ์ฆ, ๋์ผํ ๋ ๋ฒจ์ ์๋ ๋ ธ๋๋ค์ด ๋ณ๋ ฌ๋ก ์คํ๋๊ณ , ๊ทธ ํ์ ๋ค์ ๋ ๋ฒจ๋ก ์งํ๋ฉ๋๋ค.
"b1"
๊ณผ"c1"
์ด ๋ณ๋ ฌ๋ก ์คํ๋ ํ,"b2"
์"c2"
๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋ฉ๋๋ค.- ์ํ์ ์ถ๊ฐ๋ ๋๋ ์ํ๋ฒณ ์์๋๋ก ์ ๋ ฌ๋๊ธฐ ๋๋ฌธ์
"I'm B1"
,"I'm B2"
,"I'm C1"
,"I'm C2"
์ ์์๋ก ์ ๋ ฌ๋ฉ๋๋ค. -
์๋ ํ๋ฆ์ ์ดํด๋ณด๋ฉด:
a
์คํ ํb1
๊ณผc1
์ด ๋ณ๋ ฌ๋ก ์คํ.b1
๊ณผc1
์ ๊ฒฐ๊ณผ๊ฐ ๋ณํฉ๋๊ณ ,b2
์c2
๊ฐ ๋ณ๋ ฌ๋ก ์คํ.b2
์c2
์ ๊ฒฐ๊ณผ๊ฐ ๋ณํฉ๋๊ณ ์ ๋ ฌ๋จ.- ๊ทธ ํ
d
๊ฐ ์คํ๋จ.
-
๊ฒฐ๊ณผ:
1
{'state': ["I'm A", "I'm B1", "I'm B2", "I'm C1", "I'm C2", "I'm D"]}
- ์ด๋ ๋ง์น BFS์์ ํ ๋ ๋ฒจ์ ๋ ธ๋๋ฅผ ๋ชจ๋ ์คํํ ํ ๋ค์ ๋ ๋ฒจ๋ก ๋์ด๊ฐ๋ ๋ฐฉ์๊ณผ ์ ์ฌํฉ๋๋ค. ๋ณ๋ ฌ๋ก ์คํ๋ ๋ ธ๋๋ค์ด ํญ์ ์ ๋ ฌ๋ ํํ๋ก ์ํ์ ์ ์ฅ๋ฉ๋๋ค.
- (์ฌํ) ์ ๋ณด ๊ฒ์ ๊ทธ๋ํ ๊ตฌํ
-
์ฌ๊ธฐ์ ๋ ๊ฐ์ ๊ฒ์ ์์ ์ ๋ณ๋ ฌ๋ก ์คํํ๋ ๊ทธ๋ํ๋ฅผ ์ ์ํฉ๋๋ค:
- ์น ๊ฒ์(Web Search): ์ง๋ฌธ์ ๊ธฐ๋ฐ์ผ๋ก ์น์์ ๊ด๋ จ ๋ฌธ์๋ฅผ ๊ฒ์ํฉ๋๋ค.
- ์ํคํผ๋์ ๊ฒ์(Wikipedia Search): ์ง๋ฌธ์ ๊ธฐ๋ฐ์ผ๋ก ์ํคํผ๋์์์ ์ ๋ณด๋ฅผ ๊ฒ์ํฉ๋๋ค.
- ๋ ๊ฒ์ ๊ฒฐ๊ณผ๋ ๋ชจ๋ context๋ผ๋ ์ํ ํค์ ์ ์ฅ๋ฉ๋๋ค.
- ์๋ ์ฝ๋๋
์น ๊ฒ์
๊ณผ์ํคํผ๋์ ๊ฒ์
์ ๋ณ๋ ฌ๋ก ์คํํ ํ, ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ํตํฉํ์ฌ LLM์ ํตํด ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ์์ฑํ๋ ๊ณผ์ ์ ๋ณด์ฌ์ค๋๋ค.
0) Import Libraries
1
2
3
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.document_loaders import WikipediaLoader
from langchain_community.tools.tavily_search import TavilySearchResults
1) search_web
ํจ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def search_web(state):
""" ์น ๊ฒ์์์ ๋ฌธ์ ๊ฒ์ """
# ๊ฒ์
tavily_search = TavilySearchResults(max_results=3)
search_docs = tavily_search.invoke(state['question'])
# ํ์ ์ง์
formatted_search_docs = "\n\n---\n\n".join(
[
f'<Document href="{doc["url"]}"/>\n{doc["content"]}\n</Document>'
for doc in search_docs
]
)
return {"context": [formatted_search_docs]}
-
์ญํ :
search_web
ํจ์๋ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ฐ์์ ์น์์ ๋ฌธ์๋ฅผ ๊ฒ์ํฉ๋๋ค.- ์ฌ๊ธฐ์๋ TavilySearchResults๋ฅผ ์ฌ์ฉํ์ฌ ์ต๋ 3๊ฐ์ ๋ฌธ์๋ฅผ ๊ฒ์ํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํฌ๋งทํ ํ์ฌ ๋ฐํํฉ๋๋ค.
tavily_search.invoke
: ์ง๋ฌธ์ ์ ๋ ฅ๋ฐ์ ์น ๊ฒ์์ ์ํํ๊ณ ๊ฒฐ๊ณผ ๋ฌธ์๋ค์ ๋ฐํํฉ๋๋ค.-
๋ฆฌ์คํธ ์ปดํ๋ฆฌํจ์ : ๊ฒ์ ๊ฒฐ๊ณผ๋ HTML ํ์์ ํ๊ทธ๋ก ์ง์ ๋๋ฉฐ, ๊ฐ ๋ฌธ์๋
<Document>
ํ๊ทธ๋ก ๊ฐ์ธ์ ธ ์์ต๋๋ค.search_docs
๋ฆฌ์คํธ์ ๊ฐ ์์doc
์ ๋ํด ๋ฐ๋ณตํฉ๋๋ค.- ๊ฐ
doc
๋ ๋์ ๋๋ฆฌ๋ก,url
๊ณผcontent
ํค๋ฅผ ๊ฐ์ง๋๋ค. - ๊ฐ
doc
๋ฅผ<Document>
ํ๊ทธ๋ก ๊ฐ์ธ๊ณ ,href
์์ฑ์url
๊ฐ์, ํ๊ทธ ์์content
๊ฐ์ ๋ฃ์ต๋๋ค. - ์ด ๊ณผ์ ์ ํตํด ๊ฐ doc๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํฉ๋๋ค.
-
๋ฌธ์์ด ๊ฒฐํฉ: ๋ฆฌ์คํธ ์ปดํ๋ฆฌํจ์ ์ ํตํด ์์ฑ๋ ๋ฌธ์์ด ๋ฆฌ์คํธ๋ฅผ
"\n\n---\n\n"
๋ฌธ์์ด๋ก ๊ฒฐํฉํฉ๋๋ค.- ์ด ๊ฒฐํฉ ๋ฌธ์์ด์ ๊ฐ ๋ฌธ์ ์ฌ์ด์ ๊ตฌ๋ถ์ ์ ์ถ๊ฐํ๋ ์ญํ ์ ํฉ๋๋ค.
- ์ถ๋ ฅ: ํฌ๋งทํ
๋ ๋ฌธ์ ๋ฆฌ์คํธ(
formatted_search_docs
)๋context
์ ์ ์ฅ๋์ด ๋ฐํ๋ฉ๋๋ค.
2) search_wikipedia
ํจ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def search_wikipedia(state):
""" ์ํคํผ๋์์์ ๋ฌธ์ ๊ฒ์ """
# ๊ฒ์
search_docs = WikipediaLoader(query=state['question'],
load_max_docs=2).load()
# ํ์ ์ง์
formatted_search_docs = "\n\n---\n\n".join(
[
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
for doc in search_docs
]
)
return {"context": [formatted_search_docs]}
- ์ญํ :
search_wikipedia
ํจ์๋ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ฐ์์ ์ํคํผ๋์์์ ๊ด๋ จ ๋ฌธ์๋ฅผ ๊ฒ์ํฉ๋๋ค. WikipediaLoader
: ์ฃผ์ด์ง ์ง๋ฌธ์ ์ฌ์ฉํ์ฌ ์ํคํผ๋์์์ ์ต๋ 2๊ฐ์ ๋ฌธ์๋ฅผ ๋ก๋ํฉ๋๋ค.-
ํ์ ์ง์ : ๊ฒ์ ๊ฒฐ๊ณผ๋
<Document>
ํ๊ทธ๋ก ํฌ๋งทํ ๋์ด ๋ฌธ์์ ์ถ์ฒ์ ํ์ด์ง ์ ๋ณด๋ฅผ ํฌํจํฉ๋๋ค.formatted_search_docs
๋ณ์๋ ๋ฆฌ์คํธ ์ปดํ๋ฆฌํจ์ ์ ์ฌ์ฉํ์ฌ ๊ฐ ๋ฌธ์๋ฅผ ํน์ XML ํ์์ผ๋ก ๋ณํํ ๋ฌธ์์ด๋ค์ ๊ฒฐํฉํ ๊ฒฐ๊ณผ์ ๋๋ค.- ๊ฐ ๋ฌธ์๋
<Document>
ํ๊ทธ๋ก ๊ฐ์ธ์ ธ ์์ผ๋ฉฐ, ๋ฌธ์์ ์ถ์ฒ์ ํ์ด์ง ์ ๋ณด๊ฐ ํฌํจ๋ฉ๋๋ค. ์ด ์ ๋ณด๋doc.metadata
๋์ ๋๋ฆฌ์์ ๊ฐ์ ธ์ต๋๋ค. - ๋ฌธ์์ ์ค์ ๋ด์ฉ์
doc.page_content
์์ ๊ฐ์ ธ์ต๋๋ค.
- ์ถ๋ ฅ: ํฌ๋งทํ
๋ ์ํคํผ๋์ ๋ฌธ์ ๋ฆฌ์คํธ๊ฐ
context
์ ์ ์ฅ๋์ด ๋ฐํ๋ฉ๋๋ค.
3) generate_answer
ํจ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def generate_answer(state):
""" ์ง๋ฌธ์ ๋ต๋ณํ๋ ๋
ธ๋ """
# ์ํ ๊ฐ์ ธ์ค๊ธฐ
context = state["context"]
question = state["question"]
# ํ
ํ๋ฆฟ
answer_template = """์ด ๋งฅ๋ฝ์ ์ฌ์ฉํ์ฌ {question} ์ง๋ฌธ์ ๋ตํ์ธ์: {context}"""
answer_instructions = answer_template.format(question=question,
context=context)
# ๋ต๋ณ
answer = llm.invoke([SystemMessage(content=answer_instructions)]+[HumanMessage(content=f"์ง๋ฌธ์ ๋ตํ์ธ์.")])
# ์ํ์ ์ถ๊ฐ
return {"answer": answer}
- ์ญํ :
generate_answer
ํจ์๋์น ๊ฒ์
๋ฐ์ํคํผ๋์ ๊ฒ์
๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ต๋ณ์ ์์ฑํฉ๋๋ค. - ํ ํ๋ฆฟ: ๊ฒ์๋ ์ปจํ ์คํธ์ ์ง๋ฌธ์ ๊ฒฐํฉํ์ฌ LLM(Large Language Model)์ ์ ๋ฌํ ํ ์คํธ๋ฅผ ์์ฑํฉ๋๋ค.
- LLM ์ฌ์ฉ: ์์ฑ๋ ํ
ํ๋ฆฟ์
SystemMessage
์HumanMessage
๋ก LLM์ ์ ๋ฌํ์ฌ ๋ต๋ณ์ ์์ฑํฉ๋๋ค. - ์ถ๋ ฅ: ์์ฑ๋ ๋ต๋ณ์ ์ํ์ ์ถ๊ฐ๋์ด ๋ฐํ๋ฉ๋๋ค.
4) ๊ทธ๋ํ ๋ ธ๋ ์ถ๊ฐ ๋ฐ ํ๋ฆ ์ค์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ๋
ธ๋ ์ถ๊ฐ
builder = StateGraph(State)
# ๊ฐ ๋
ธ๋๋ฅผ node_secret์ผ๋ก ์ด๊ธฐํ
builder.add_node("search_web",search_web)
builder.add_node("search_wikipedia", search_wikipedia)
builder.add_node("generate_answer", generate_answer)
# ํ๋ฆ
builder.add_edge(START, "search_wikipedia")
builder.add_edge(START, "search_web")
builder.add_edge("search_wikipedia", "generate_answer")
builder.add_edge("search_web", "generate_answer")
builder.add_edge("generate_answer", END)
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
- ํ๋ฆ: ์ด ๊ทธ๋ํ๋ ๋ณ๋ ฌ๋ก ๊ฒ์ ์์ ์ ์ฒ๋ฆฌํ๊ณ , ๊ทธ ํ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ด์ฉํ์ฌ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
-
์คํ ํ๋ฆ ์ค๋ช :
- ์ฌ์ฉ์๊ฐ ์ง๋ฌธ์ ์ ๋ ฅํ๋ฉด, ์ํคํผ๋์์ ์น ๊ฒ์์ด ๋์์ ์คํ๋ฉ๋๋ค.
- ๊ฒ์๋ ๋ฌธ์๋ค์ด ๊ฐ๊ฐ ํฌ๋งทํ
๋์ด
context
์ํ์ ์ ์ฅ๋ฉ๋๋ค. - ๊ทธ ํ, ๋ ๊ฒ์์ ๊ฒฐ๊ณผ๊ฐ ๋ณํฉ๋์ด
generate_answer
๋ ธ๋์์ LLM์ ํตํด ๋ต๋ณ์ด ์์ฑ๋ฉ๋๋ค.
- ๊ฒฐ๊ณผ์ ์ผ๋ก, ๊ฒ์๋ ๋ฌธ์๋ค์ ๋ฐํ์ผ๋ก ์ง๋ฌธ์ ๋ํ ์ต์ข ๋ต๋ณ์ ์์ฑํ๋ ์์คํ ์ ๋๋ค.
5) ์ง๋ฌธ ์ ๋ ฅ
- ์๋์ ๊ฐ์ด ์ง๋ฌธ์ ์ ๋ ฅํ๋ฉด TAVILY์ WIKIPEDIA SEARCH๋ฅผ ํตํด์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ์ํํฉ๋๋ค.
1
2
result = graph.invoke({"question": "LG์ ์์ 2024๋
3๋ถ๊ธฐ ์ค์ ์ ์ด๋ ๋์?"})
print(result['answer'].content)
- ์๋๋ ์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ ์์์ ๋๋ค:
1
2
3
4
5
6
LG์ ์์ 2024๋
3๋ถ๊ธฐ ์ค์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋งค์ถ: 22์กฐ1769์ต์, ์ ๋
๋๊ธฐ ๋๋น 10.7% ์ฆ๊ฐ
- ์์
์ด์ต: 7511์ต์, ์ ๋
๋๊ธฐ ๋๋น 20.9% ๊ฐ์
๋งค์ถ์ 3๋ถ๊ธฐ ์ต๋์น๋ฅผ ๊ธฐ๋กํ์ผ๋ฉฐ, ์ด๋ ๊ฐ์ ์ ํ๊ณผ ์ ์ฅ ๋ถ๋ฌธ์ ์ง์์ ์ธ ์ฑ์ฅ ๋๋ถ์
๋๋ค. ๊ทธ๋ฌ๋ ์์
์ด์ต์ ๋ฌผ๋ฅ๋น ๊ธ๋ฑ ๋ฐ ๋ง์ผํ
๋น ์ฆ๊ฐ๋ก ์ธํด ๊ฐ์ํ์ต๋๋ค.
Lesson 2: Sub-graphs
๊ฐ์
์๋ธ๊ทธ๋ํ(Sub-graph)
๋ ํฐ ๊ทธ๋ํ์ ์ผ๋ถ๋ถ์ผ๋ก, ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ ์์ ๋จ์์ ๋๋ค.
- ์๋ธ๊ทธ๋ํ๋ ์ํ ํค๋ฅผ ๊ณต์ ํ๊ฑฐ๋ ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ผ๋ฉฐ, ๋ฉํฐ ์์ด์ ํธ ์์คํ ์์ ๊ฐ ์์ด์ ํธ๊ฐ ๋ ๋ฆฝ์ ์ธ ์์ ์ ์ํํ๋ฉด์๋ ๋ถ๋ชจ ๊ทธ๋ํ์ ์ํธ์์ฉํ ์ ์์ต๋๋ค.
-
๊ฐ๋จํ ์๋ฅผ ์ดํด๋ด ์๋ค:
- ๋ก๊ทธ๋ฅผ ๋ฐ๋ ์์คํ ์ด ์์ต๋๋ค
- ์ด ์์คํ ์ ์๋ก ๋ค๋ฅธ ๊ฐ๊ฐ์ ์์ด์ ํธ(Summary Agent, Failure Analysis Agent)์ ์ํด ๋ ๊ฐ์ง ๋ณ๋์ ํ์ ์์ ์ ์ํํฉ๋๋ค(๋ฌธ์์์ฝ, ์ค๋ฅ๋ถ์)
- ์ด ๋ ๊ฐ์ง ์์ ์ ๋ ๊ฐ์ ๋ค๋ฅธ ์๋ธ ๊ทธ๋ํ์์ ์ํํ๊ณ ์ถ์ต๋๋ค.
- ์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์ ๊ทธ๋ํ๋ค์ด โ์ด๋ป๊ฒ ํต์ ์ ํ ๊นโ๋ผ๋ ๊ฒ์ ๋๋ค.
-
๊ฐ๋จํ ๋งํด, ํต์ ์ ๊ฒน์น๋ ํค๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋๋ค:
- ์๋ธ ๊ทธ๋ํ๋ ๋ถ๋ชจ๋ก๋ถํฐ docs์ ์ ๊ทผํ ์ ์์ต๋๋ค
- ๋ถ๋ชจ๋ ์๋ธ ๊ทธ๋ํ๋ก๋ถํฐ Summary Agent ๋๋ Failure Analysis Agent์ ์ ๊ทผํ ์ ์์ต๋๋ค
์ฃผ์ ๊ฐ๋
-
์๋ธ ๊ทธ๋ํ์ ๋ถ๋ชจ ๊ทธ๋ํ ๊ฐ์ ์ํ ๊ณต์ :
์๋ธ ๊ทธ๋ํ
์๋ถ๋ชจ ๊ทธ๋ํ
๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ตํํ๊ธฐ ์ํด ์ํ ํค๊ฐ ์ค๋ณต๋ ์ ์์ต๋๋ค.- ํน์ ์ํ๋ ์๋ธ๊ทธ๋ํ ๋ด์์๋ง ๊ด๋ฆฌ๋๋ฉฐ ๋ถ๋ชจ ๊ทธ๋ํ์๋ ์ํฅ์ ์ฃผ์ง ์์ต๋๋ค.
-
์ถ๋ ฅ ์คํค๋ง:
- ์๋ธ ๊ทธ๋ํ์ ์ถ๋ ฅ ์ํ๋ฅผ ์ ํํ์ฌ ๋ถํ์ํ ์ํ ํค๋ฅผ ๋ถ๋ชจ ๊ทธ๋ํ์ ๋ฐํํ์ง ์๋๋ก ํฉ๋๋ค.
์ฝ๋ ์์
- ์ด๋ฒ ์ฝ๋ ์ค์ต์ ๋ ๊ฐ์ ์๋ธ-๊ทธ๋ํ(
Failure Analysis ์๋ธ-๊ทธ๋ํ
์Question Summarization ์๋ธ-๊ทธ๋ํ
)๋ฅผ ๋ถ๋ชจ ๊ทธ๋ํ(EntryGraph
)๋ก ํตํฉํ์ฌ, ์ฃผ์ด์ง ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๊ณ ๋ถ์ํ๋ ์์คํ ์ ๊ตฌํํ ์์ ์ ๋๋ค.- ์ด ์์คํ
์ ๋ณ๋ ฌ๋ก ๋ ๊ฐ์ ์๋ธ๊ทธ๋ํ๋ฅผ ์คํํ ํ, ๊ฐ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ข
ํฉํ์ฌ
์ต์ข ๋ณด๊ณ ์
์์์ฝ์ ์์ฑ
ํฉ๋๋ค.
- ์ด ์์คํ
์ ๋ณ๋ ฌ๋ก ๋ ๊ฐ์ ์๋ธ๊ทธ๋ํ๋ฅผ ์คํํ ํ, ๊ฐ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ข
ํฉํ์ฌ
1
2
3
4
# ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ํฌํธ
from operator import add
from typing_extensions import TypedDict
from typing import List, Optional, Annotated
1. ๋ก๊ทธ ๊ตฌ์กฐ ์ ์:
- ๋จผ์ ,
Log
ํด๋์ค๋ ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๊ฐ ๋ก๊ทธ๋ ์ง๋ฌธ๊ณผ ๋ต๋ณ, ํ๊ฐ ์ ๋ณด ๋ฐ ํผ๋๋ฐฑ์ ํฌํจํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
class Log(TypedDict):
id: str
question: str
docs: Optional[List]
answer: str
grade: Optional[int]
grader: Optional[str]
feedback: Optional[str]
- id: ๋ก๊ทธ์ ๊ณ ์ ์๋ณ์.
- question: ์ฌ์ฉ์์ ์ง๋ฌธ.
- docs: ์ง๋ฌธ์ ๋ํด ๊ฒ์๋ ๋ฌธ์๋ค.
- answer: ์ ๊ณต๋ ๋ต๋ณ.
- grade: ๋ก๊ทธ์ ํ๊ฐ (์: ํ์ง ์ ์).
- grader: ํ๊ฐ์ ์ ๋ณด.
- feedback: ํ๊ฐ์ ๊ด๋ จ๋ ํผ๋๋ฐฑ.
2. Failure Analysis ์๋ธ๊ทธ๋ํ:
Failure Analysis
์๋ธ๊ทธ๋ํ์ ๋ชฉํ๋ ์คํจํ ๋ก๊ทธ๋ฅผ ์๋ณํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์์ฝํ๋ ๊ฒ์ ๋๋ค.
FailureAnalysisState
๋ ์คํจ ๋ถ์ ์๋ธ๊ทธ๋ํ์์ ๊ด๋ฆฌํ ์ํ๋ฅผ ์ ์ํ๋ฉฐ, ์ฃผ๋ก ์คํจํ ๋ก๊ทธ์ ๋ํ ์์ฝ๊ณผ ์ฒ๋ฆฌ๋ ๋ก๊ทธ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
1
2
3
4
5
6
class FailureAnalysisState(TypedDict):
cleaned_logs: List[Log] # ์ฒ๋ฆฌ๋ ๋ก๊ทธ
failures: List[Log] # ์คํจํ ๋ก๊ทธ ๋ชฉ๋ก
fa_summary: str # ์คํจ ๋ถ์ ์์ฝ
processed_logs: List[str] # ์ฒ๋ฆฌ๋ ๋ก๊ทธ ์ ๋ณด
FailureAnalysisOutputState
๋ ์คํจ ๋ถ์ ์๋ธ๊ทธ๋ํ์ ์ถ๋ ฅ ์คํค๋ง๋ฅผ ์ ์ํฉ๋๋ค. ํด๋น ์ถ๋ ฅ ์คํค๋ง๋ ์์์๋ ๋ค๋ฅด๊ฒ ์ ์ ์๊ฒ ์ ํ๊ณ ์ถ์ ์ ๋ณด๋ง ๊ฐ์ง๊ณ ์์ต๋๋ค.
1
2
3
4
class FailureAnalysisOutputState(TypedDict):
fa_summary: str
processed_logs: List[str]
- get_failures: ์คํจํ ๋ก๊ทธ(grade๊ฐ ํฌํจ๋ ๋ก๊ทธ)๋ฅผ ํํฐ๋งํ์ฌ ๋ฐํํฉ๋๋ค.
1
2
3
4
5
6
def get_failures(state):
""" ์คํจ๋ฅผ ํฌํจํ๋ ๋ก๊ทธ๋ฅผ ๊ฐ์ ธ์ต๋๋ค """
cleaned_logs = state["cleaned_logs"]
failures = [log for log in cleaned_logs if "grade" in log]
return {"failures": failures}
- generate_summary: ํํฐ๋ง๋ ์คํจ ๋ก๊ทธ๋ค์ ๊ธฐ๋ฐ์ผ๋ก ์์ฝ์ ์์ฑํ๊ณ , ์ด๋ฅผ
processed_logs
๋ก ๊ธฐ๋กํฉ๋๋ค.
1
2
3
4
5
6
7
8
def generate_summary(state):
""" ์คํจ์ ์์ฝ์ ์์ฑํฉ๋๋ค """
failures = state["failures"]
# ํจ์ ์ถ๊ฐ: fa_summary = summarize(failures)
fa_summary = "Chroma ๋ฌธ์์ ๊ฒ์ ํ์ง์ด ๋ฎ์."
return {"fa_summary": fa_summary,
"processed_logs": [f"failure-analysis-on-log-{failure['id']}" for failure in failures]}
- sub-graph ์ ์: ์๋ ์ฝ๋๋ฅผ ํตํด์ sub-graph๋ฅผ ์ ์ํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
fa_builder = StateGraph(input=FailureAnalysisState,output=FailureAnalysisOutputState)
fa_builder.add_node("get_failures", get_failures)
fa_builder.add_node("generate_summary", generate_summary)
fa_builder.add_edge(START, "get_failures")
fa_builder.add_edge("get_failures", "generate_summary")
fa_builder.add_edge("generate_summary", END)
graph = fa_builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
3. Question Summarization ์๋ธ๊ทธ๋ํ
Question Summarization
์๋ธ๊ทธ๋ํ๋ ๋ก๊ทธ๋ฅผ ์์ฝํ๊ณ , ์ต์ข ๋ณด๊ณ ์๋ฅผ ์์ฑํ๋ ์ญํ ์ ํฉ๋๋ค.
QuestionSummarizationState
๋ ์ง๋ฌธ ์์ฝ ์๋ธ๊ทธ๋ํ์์ ์ฌ์ฉํ ์ํ๋ฅผ ์ ์ํ๋ฉฐ, ์ฃผ๋ก ์ง๋ฌธ์ ๋ํ ์์ฝ๊ณผ ๋ณด๊ณ ์๋ฅผ ์์ฑํฉ๋๋ค.
1
2
3
4
5
6
class QuestionSummarizationState(TypedDict):
cleaned_logs: List[Log] # ์ฒ๋ฆฌ๋ ๋ก๊ทธ
qs_summary: str # ์ง๋ฌธ ์์ฝ
report: str # ๋ณด๊ณ ์
processed_logs: List[str] # ์ฒ๋ฆฌ๋ ๋ก๊ทธ ์ ๋ณด
QuestionSummarizationOutputState
๋ ์ง๋ฌธ ์์ฝ ์๋ธ๊ทธ๋ํ์ ์ถ๋ ฅ ์คํค๋ง๋ฅผ ์ ์ํฉ๋๋ค. ํด๋น ์ถ๋ ฅ ์คํค๋ง๋ ์์์๋ ๋ค๋ฅด๊ฒ ์ ์ ์๊ฒ ์ ํ๊ณ ์ถ์ ์ ๋ณด๋ง ๊ฐ์ง๊ณ ์์ต๋๋ค.
1
2
3
4
class QuestionSummarizationOutputState(TypedDict):
report: str
processed_logs: List[str]
- generate_summary: ์ง๋ฌธ ๋ก๊ทธ๋ฅผ ์์ฝํ๊ณ , ๊ฐ ๋ก๊ทธ์ ์์ฝ ๊ฒฐ๊ณผ๋ฅผ
processed_logs
์ ๊ธฐ๋กํฉ๋๋ค.
1
2
3
4
5
6
def generate_summary(state):
cleaned_logs = state["cleaned_logs"]
# ํจ์ ์ถ๊ฐ: summary = summarize(generate_summary)
summary = "์ง๋ฌธ์ ChatOllama์ Chroma ๋ฒกํฐ ์ ์ฅ์์ ์ฌ์ฉ์ ์ด์ ์ ๋ง์ท์ต๋๋ค."
return {"qs_summary": summary,
"processed_logs": [f"summary-on-log-{log['id']}" for log in cleaned_logs]}
- send_to_slack: ์์ฑ๋ ์์ฝ์ ๋ณด๊ณ ์๋ก ๋ณํํฉ๋๋ค(๋จ์ํ ๋๋ฏธ ์์์์ โfoo bar bazโ๋ผ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ).
1
2
3
4
5
6
def send_to_slack(state):
qs_summary = state["qs_summary"]
# ํจ์ ์ถ๊ฐ: report = report_generation(qs_summary)
report = "foo bar baz"
return {"report": report}
- sub-graph ์ ์: ์๋ ์ฝ๋๋ฅผ ํตํด์ sub-graph๋ฅผ ์ ์ํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
qs_builder = StateGraph(input=QuestionSummarizationState,output=QuestionSummarizationOutputState)
qs_builder.add_node("generate_summary", generate_summary)
qs_builder.add_node("send_to_slack", send_to_slack)
qs_builder.add_edge(START, "generate_summary")
qs_builder.add_edge("generate_summary", "send_to_slack")
qs_builder.add_edge("send_to_slack", END)
graph = qs_builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
๐ (์ฐธ๊ณ ) EntryGraphStates
- ์ฐ๋ฆฌ๋
EntryGraphState
ํด๋์ค๋ก ๋ถ๋ชจ ๊ทธ๋ํ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.- ์๋์ฒ๋ผ ์ ์ํด์ ์๋ธ ๋ ธ๋๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
entry_builder.add_node("question_summarization", qs_builder.compile())
entry_builder.add_node("failure_analysis", fa_builder.compile())
EntryGraphState
ํด๋์ค๋ฅผ ์ดํด๋ณด๋ฉด,cleaned_logs
์processed_logs
์Annotated[List[Log], add]
๊ฐ ์ฌ์ฉ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
1 2 3 4 5 6 7 # ์ง์ ๊ทธ๋ํ class EntryGraphState(TypedDict): raw_logs: List[Log] cleaned_logs: Annotated[List[Log], add] # ์ด๊ฒ์ ๋ ์๋ธ ๊ทธ๋ํ์์ ๋ชจ๋ ์ฌ์ฉ๋ฉ๋๋ค fa_summary: str # ์ด๊ฒ์ FA ์๋ธ ๊ทธ๋ํ์์๋ง ์์ฑ๋ฉ๋๋ค report: str # ์ด๊ฒ์ QS ์๋ธ ๊ทธ๋ํ์์๋ง ์์ฑ๋ฉ๋๋ค processed_logs: Annotated[List[int], add] # ์ด๊ฒ์ ๋ ์๋ธ ๊ทธ๋ํ์์ ๋ชจ๋ ์์ฑ๋ฉ๋๋ค
- ์์
EntryGraphState
์์ ๊ฐ๊ฐ cleaned_logs์ processed_logs์Annotated[List[Log], add]
๋ฆฌ๋์๊ฐ ์ฌ์ฉ๋๋ ์ด์ ๋, ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋๋ฉด์ ๋์ผํ ์ํ ํค๋ฅผ ์ฐธ์กฐํ๊ฑฐ๋ ์ ๋ฐ์ดํธํ๊ธฐ ๋๋ฌธ์ ๋๋ค.- ๊ฐ๊ฐ์ ๋ฆฌ๋์๋ฅผ ์ ์ํด์ค์ผ๋ก์จ
Annotated[List[int], add]
๋ณ๋ ฌ ์คํ ์ ๋ฐ์ํ ์ ์๋ ์ํ ์ถฉ๋์ ๋ฐฉ์งํ๊ณ , ์ฌ๋ฌ ๊ฐ์ ์์ ํ๊ฒ ๋ณํฉํ ์ ์๋๋ก ๋์์ค๋๋ค.
๐ (์ฌํ)
cleaned_logs: Annotated[List[Log], add]
1 cleaned_logs: Annotated[List[Log], add]
- ๋ ์๋ธ๊ทธ๋ํ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ:
cleaned_logs
๋ Failure Analysis ์๋ธ๊ทธ๋ํ์ Question Summarization ์๋ธ๊ทธ๋ํ๊ฐ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ์ํ์ ๋๋ค. ํ์ง๋ง ์ด ์ํ๋ ๋ ์๋ธ๊ทธ๋ํ์์ ์์ ๋์ง ์๊ณ ์ฐธ์กฐ๋ง ๋ฉ๋๋ค.- ๋ณ๋ ฌ ์คํ ์์ ์ํ ์ถฉ๋ ๋ฐฉ์ง: ๋ณ๋ ฌ๋ก ์คํ๋๋ ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๊ฐ์ ์ํ ํค(
cleaned_logs
)๋ฅผ ์ฐธ์กฐํ์ง๋ง, ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ ๋ ๋์ผํ ํค๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์, ์ด ํค์ ๋ํ ์ํ ๋ณํฉ์ด ํ์ํฉ๋๋ค. ๋ฆฌ๋์๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๋ฐํํ๋ ์ํ๊ฐ ์ถฉ๋ํ ์ ์์ต๋๋ค.add
๋ฆฌ๋์์ ์ญํ :add
๋ฆฌ๋์๋ ๊ฐ ์๋ธ๊ทธ๋ํ์์ ๋ฐํ๋cleaned_logs
๋ฆฌ์คํธ๋ฅผ ๋ณํฉํด ํ๋์ ๋ฆฌ์คํธ๋ก ๋ง๋ค๋ฉฐ, ์ด๋ ์๋ณธ ๋ฆฌ์คํธ๋ ์์ ๋์ง ์๊ณ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค. ์ฆ, ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๊ฐ์cleaned_logs
๋ฅผ ๋ฐํํ๋๋ผ๋,add
๋ฆฌ๋์๋ฅผ ํตํด ์์ ํ๊ฒ ๋ณํฉํ ์ ์์ต๋๋ค.
๐ (์ฌํ)
processed_logs: Annotated[List[int], add]
1 processed_logs: Annotated[List[int], add]
- ๋ ์๋ธ๊ทธ๋ํ์์ ์์ฑ๋๋ ๊ฐ:
processed_logs
๋ Failure Analysis ์๋ธ๊ทธ๋ํ์ Question Summarization ์๋ธ๊ทธ๋ํ๊ฐ ๊ฐ๊ธฐ ๋ค๋ฅธ ๊ฐ์ ์์ฑํ๋ ์ํ์ ๋๋ค. ๊ฐ ์๋ธ๊ทธ๋ํ๋ ์์ฒด์ ์ผ๋ก ์ฒ๋ฆฌ๋ ๋ก๊ทธ์ ID ๋๋ ๊ด๋ จ ์ ๋ณด๋ฅผprocessed_logs
์ ๊ธฐ๋กํฉ๋๋ค.- ๋ณ๋ ฌ ์คํ ์ ๊ฐ ์๋ธ๊ทธ๋ํ์์ ๊ฐ ์ถ๊ฐ: ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋๋ฉด์, ๊ฐ๊ฐ์
processed_logs
๊ฐ์ ๋ฐํํฉ๋๋ค. ๋ง์ฝ ๋ฆฌ๋์๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด, ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๋ฐํํ ๊ฐ์ด ์ถฉ๋ํ์ฌ ํ ์๋ธ๊ทธ๋ํ์ ๊ฐ๋ง ๋ฐ์๋๊ฑฐ๋ ์ํ๊ฐ ๋ฎ์ด์์์ง ์ ์์ต๋๋ค.add
๋ฆฌ๋์์ ์ญํ :add
๋ฆฌ๋์๋ ๋ ์๋ธ๊ทธ๋ํ์์ ๋ฐํ๋ ๋ฆฌ์คํธ๋ฅผ ๋ณํฉํ์ฌ, ๋ ์๋ธ๊ทธ๋ํ์ ๋ชจ๋ ๋ก๊ทธ๊ฐ ์์ ํ๊ฒ ๊ธฐ๋ก๋๋๋ก ํฉ๋๋ค. ์ฆ, ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๊ฐ๊ธฐ ๋ค๋ฅธ ๊ฐ์ ์์ฑํด๋,add
๋ฆฌ๋์๊ฐ ๊ฐ ๊ฐ๋ค์ ๊ฒฐํฉํ์ฌprocessed_logs
์ ์ ์ฅํฉ๋๋ค.
๐ค (์ฌํ) ๊ฐ๊ฐ ๋๋ค ๊ณตํต์ ์ธ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ณต์ ํ๋ค๋ณด๋ ๊ทธ๋ฐ๊ฑฐ๋ผ๊ณ ์ดํดํ ์ ์๋๋ฐ ๋ค๋ฅธ ํด๋์ค๋ผ์ ์๊ด์์ง ์๋?
- ์ข์ ์ง๋ฌธ์ ๋๋ค! ๊ฒ๋ณด๊ธฐ์๋
FailureAnalysisState
์QuestionSummarizationState
๊ฐ ์๋ก ๋ค๋ฅธ ํด๋์ค์ด๊ณ , ๊ฐ ์๋ธ๊ทธ๋ํ ๋ด์์ ๋ ๋ฆฝ์ ์ผ๋ก ์ ์๋ ์ํ์ด๊ธฐ ๋๋ฌธ์cleaned_logs
์processed_logs
๊ฐ ๊ฒน์น๋๋ผ๋ ๋ฌธ์ ๊ฐ ์์ด ๋ณด์ผ ์ ์์ต๋๋ค. ํ์ง๋ง ์ด๋ค์ด ๋์ผํ ์์ ๊ทธ๋ํ(Entry Graph)์์ ๋ณ๋ ฌ๋ก ์คํ๋ ๋๋ ์ํฉ์ด ๋ค๋ฆ ๋๋ค.cleaned_logs
์processed_logs
๊ฐ ๋ ๋ฆฝ๋ ํด๋์ค์์ ์ ์๋์์ง๋ง, Entry Graph(์์ ๊ทธ๋ํ)์์๋ ์ด๋ค ์๋ธ๊ทธ๋ํ๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋๊ธฐ ๋๋ฌธ์ ๋ ์๋ธ๊ทธ๋ํ์ ์ํ๊ฐ ์์ ๊ทธ๋ํ์ ์ํ๋ก ๋ณํฉ๋ฉ๋๋ค. ์ฆ, ๋ ์๋ธ๊ทธ๋ํ๋EntryGraphState
์ ์ ์๋cleaned_logs
์processed_logs
๋ฅผ ๊ณต์ ํ๊ฒ ๋ฉ๋๋ค.- ์๋ธ๊ทธ๋ํ๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋ ๋, ๋์ผํ
cleaned_logs
์processed_logs
์ํ๋ฅผ ์ฐธ์กฐํ๊ฑฐ๋ ๋ฐํํ๊ฒ ๋๋ฏ๋ก, ์ด ์ํ๋ฅผ ์์ ํ๊ฒ ๋ณํฉํ์ง ์์ผ๋ฉด ์ํ ์ถฉ๋์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
cleaned_logs
: ๋ ์๋ธ๊ทธ๋ํ์์ ์ฐธ์กฐ๋ง ํ๋ฏ๋ก ์ค์ ๋ก ์์ ๋์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋ ์๋ธ๊ทธ๋ํ๊ฐ ๋ณ๋ ฌ๋ก ์คํ๋ ํ, ๊ฐ๊ฐ cleaned_logs๋ฅผ ๋์ผํ ํค๋ก ๋ฐํํ๋ฏ๋ก, ์์ ๊ทธ๋ํ์์๋ ์ด๋ฅผ ๋ณํฉํด์ผ ํฉ๋๋ค. ์ด๋add
๋ฆฌ๋์๊ฐ ๋ณํฉ ์ถฉ๋์ ๋ฐฉ์งํฉ๋๋ค.processed_logs
: ๊ฐ ์๋ธ๊ทธ๋ํ์์ ์์ฑํ ์ฒ๋ฆฌ ๋ก๊ทธ ์ ๋ณด๋ฅผ ์ถ๊ฐํ๋ฏ๋ก, ๋ณ๋ ฌ ์คํ ํ ๊ฐ๊ฐ์ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๊ฐ ์์ ๊ทธ๋ํ์ processed_logs์ ๋์ ๋์ด์ผ ํฉ๋๋ค. ์ด๋๋add
๋ฆฌ๋์๊ฐ ์์ด์ผ ๋ณํฉ ์ถฉ๋ ์์ด ๊ฐ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ํ ๋ฆฌ์คํธ๋ก ๋ชจ์์ง๋๋ค.
4. ๋ถ๋ชจ ๊ทธ๋ํ (EntryGraph)
- ์ด์
๋ถ๋ชจ ๊ทธ๋ํ
์์๋ ๋ ์๋ธ๊ทธ๋ํ๋ฅผ๋ณ๋ ฌ๋ก ์คํ
ํ๊ณ ๊ทธ๊ฒฐ๊ณผ๋ฅผ ์ข ํฉ
ํฉ๋๋ค.
- (์ฐธ๊ณ )
xray=0
๊ฐ์ผ๋ก ์ฃผ์์ ๋์ ์๊ฐํ ๊ฒฐ๊ณผ
EntryGraphState
๋ ๋ถ๋ชจ ๊ทธ๋ํ์์ ๊ด๋ฆฌํ ์ ์ฒด ์ํ๋ฅผ ์ ์ํฉ๋๋ค.
1
2
3
4
5
6
7
class EntryGraphState(TypedDict):
raw_logs: List[Log] # ์์ ๋ก๊ทธ ๋ฐ์ดํฐ
cleaned_logs: List[Log] # ์ ์ ๋ ๋ก๊ทธ (๋ ์๋ธ๊ทธ๋ํ์์ ์ฌ์ฉ)
fa_summary: str # ์คํจ ๋ถ์ ์์ฝ (FA ์๋ธ๊ทธ๋ํ์์ ์์ฑ)
report: str # ์ง๋ฌธ ์์ฝ ๋ณด๊ณ ์ (QS ์๋ธ๊ทธ๋ํ์์ ์์ฑ)
processed_logs: Annotated[List[int], add] # ์ฒ๋ฆฌ๋ ๋ก๊ทธ ๋ชฉ๋ก (๋ ์๋ธ๊ทธ๋ํ์์ ์์ฑ)
- fa_summary: Failure Analysis ์๋ธ๊ทธ๋ํ์์ ์์ฑ๋ ์คํจ ๋ถ์ ์์ฝ.
- report: Question Summarization ์๋ธ๊ทธ๋ํ์์ ์์ฑ๋ ๋ณด๊ณ ์.
- processed_logs: ๋ ์๋ธ๊ทธ๋ํ์์ ๊ณตํต์ผ๋ก ์์ฑ๋ ์ฒ๋ฆฌ๋ ๋ก๊ทธ ๋ชฉ๋ก์ ๋ณํฉํฉ๋๋ค.
-
cleaned_logs: ์์ ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ ์ ํ์ฌ
cleaned_logs
์ํ๋ก ๋ณํํฉ๋๋ค. ์ด ์ํ๋ ๋ ์๋ธ๊ทธ๋ํ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.1 2 3 4 5 6 7
def cleaned_logs(state): # ๋ก๊ทธ ๊ฐ์ ธ์ค๊ธฐ raw_logs = state["raw_logs"] # raw_logs -> docs ๋ฐ์ดํฐ ์ ์ cleaned_logs = raw_logs return {"cleaned_logs": cleaned_logs}
๐คฆโโ๏ธ ์ ๊น!! ์ด!? ์ฌ๊ธฐ cleaned_logs์๋ ๋ฆฌ๋์๊ฐ ์๋๋ฐ์?
1 2 3 4 5 6 7 # ์ง์ ๊ทธ๋ํ class EntryGraphState(TypedDict): raw_logs: List[Log] cleaned_logs: List[Log] fa_summary: str # ์ด๊ฒ์ FA ์๋ธ ๊ทธ๋ํ์์๋ง ์์ฑ๋ฉ๋๋ค report: str # ์ด๊ฒ์ QS ์๋ธ ๊ทธ๋ํ์์๋ง ์์ฑ๋ฉ๋๋ค processed_logs: Annotated[List[int], add] # ์ด๊ฒ์ ๋ ์๋ธ ๊ทธ๋ํ์์ ๋ชจ๋ ์์ฑ๋ฉ๋๋ค
์ด๊ฒ์ ํน์ด ์ผ์ด์ค๋ก, ์์์ ๊ฐ๊ฐ FailureAnalysis์ QuestionSummarization์
OutputState
(FailureAnalysisOutputState
,QuestionSummarizationOutputState
)๋ฅผ ์ ์ํ์ฌ, clean_logs๋ฅผ output์ผ๋ก ๋ณด๋ด์ง ์๊ณ processed_logs๋ง ๋ณด๋ด์ฃผ๊ณ ์์ต๋๋ค.
์ด์
processed_logs: Annotated[List[int], add]
๋ง ๋ณ๋๋ก ๋ฆฌ๋์ ์ฒ๋ฆฌํด์ค ๊ฒ์ด๋ผ๊ณ ํฉ๋๋ค.
5. ์๋ธ๊ทธ๋ํ์ ํตํฉ ๋ฐ ๋ณ๋ ฌ ์คํ
๋ ์๋ธ๊ทธ๋ํ๋ clean_logs
๊ฐ ์๋ฃ๋ ํ ๋ณ๋ ฌ๋ก ์คํ๋ฉ๋๋ค. ์ด ๋ ๋ ์๋ธ๊ทธ๋ํ๋ ๊ฐ์ cleaned_logs ์ํ๋ฅผ ์ฐธ์กฐํ์ง๋ง ์์ ํ์ง๋ ์์ต๋๋ค.
1
2
3
4
5
6
7
8
9
10
entry_builder.add_node("cleaned_logs", cleaned_logs)
entry_builder.add_node("question_summarization", qs_builder.compile())
entry_builder.add_node("failure_analysis", fa_builder.compile())
entry_builder.add_edge("cleaned_logs", "failure_analysis")
entry_builder.add_edge("cleaned_logs", "question_summarization")
entry_builder.add_edge("failure_analysis", END)
entry_builder.add_edge("question_summarization", END)
graph = entry_builder.compile()
cleaned_logs
: ๋ถ๋ชจ ๊ทธ๋ํ์์ ์์ ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ ์ ํ๋ ์ญํ ์ ํฉ๋๋ค.question_summarization
: Question Summarization ์๋ธ๊ทธ๋ํ๋ฅผ ๋ถ๋ชจ ๊ทธ๋ํ์ ์ถ๊ฐํฉ๋๋ค.failure_analysis
: Failure Analysis ์๋ธ๊ทธ๋ํ๋ฅผ ๋ถ๋ชจ ๊ทธ๋ํ์ ์ถ๊ฐํฉ๋๋ค.
6. ์คํ ์์
- ๋๋ฏธ ๋ก๊ทธ ์์:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ๋๋ฏธ ๋ก๊ทธ
question_answer = Log(
id="1",
question="ChatOllama๋ฅผ ์ด๋ป๊ฒ ์ํฌํธํ ์ ์๋์?",
answer="ChatOllama๋ฅผ ์ํฌํธํ๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ์ธ์: 'from langchain_community.chat_models import ChatOllama.'",
)
question_answer_feedback = Log(
id="2",
question="Chroma ๋ฒกํฐ ์ ์ฅ์๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์๋์?",
answer="Chroma๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ์ธ์: rag_chain = create_retrieval_chain(retriever, question_answer_chain).",
grade=0,
grader="๋ฌธ์ ๊ด๋ จ์ฑ ํ์",
feedback="๊ฒ์๋ ๋ฌธ์๋ค์ ๋ฒกํฐ ์ ์ฅ์์ ๋ํด ์ผ๋ฐ์ ์ผ๋ก ๋
ผ์ํ์ง๋ง, Chroma์ ๋ํด ๊ตฌ์ฒด์ ์ผ๋ก ๋ค๋ฃจ์ง ์์ต๋๋ค",
)
- ์์-1:
1
2
raw_logs = [question_answer, question_answer_feedback]
graph.invoke({"raw_logs": [question_answer]})
- ์ถ๋ ฅ ๊ฒฐ๊ณผ-1:
1
2
3
4
5
6
7
8
9
10
11
{'raw_logs': [{'id': '1',
'question': 'ChatOllama๋ฅผ ์ด๋ป๊ฒ ์ํฌํธํ ์ ์๋์?',
'answer': "ChatOllama๋ฅผ ์ํฌํธํ๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ์ธ์: 'from langchain_community.chat_models import ChatOllama.'"}],
'cleaned_logs': [{'id': '1',
'question': 'ChatOllama๋ฅผ ์ด๋ป๊ฒ ์ํฌํธํ ์ ์๋์?',
'answer': "ChatOllama๋ฅผ ์ํฌํธํ๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ์ธ์: 'from langchain_community.chat_models import ChatOllama.'"}],
'fa_summary': 'Chroma ๋ฌธ์์ ๊ฒ์ ํ์ง์ด ๋ฎ์.',
'report': 'foo bar baz',
'processed_logs': ['summary-on-log-1']}
- ์์-2:
1
2
ans = graph.invoke({"raw_logs": [question_answer_feedback]})
ans
- ์ถ๋ ฅ ๊ฒฐ๊ณผ-2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{'raw_logs': [{'id': '2',
'question': 'Chroma ๋ฒกํฐ ์ ์ฅ์๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์๋์?',
'answer': 'Chroma๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ์ธ์: rag_chain = create_retrieval_chain(retriever, question_answer_chain).',
'grade': 0,
'grader': '๋ฌธ์ ๊ด๋ จ์ฑ ํ์',
'feedback': '๊ฒ์๋ ๋ฌธ์๋ค์ ๋ฒกํฐ ์ ์ฅ์์ ๋ํด ์ผ๋ฐ์ ์ผ๋ก ๋
ผ์ํ์ง๋ง, Chroma์ ๋ํด ๊ตฌ์ฒด์ ์ผ๋ก ๋ค๋ฃจ์ง ์์ต๋๋ค'}],
'cleaned_logs': [{'id': '2',
'question': 'Chroma ๋ฒกํฐ ์ ์ฅ์๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์๋์?',
'answer': 'Chroma๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ์ธ์: rag_chain = create_retrieval_chain(retriever, question_answer_chain).',
'grade': 0,
'grader': '๋ฌธ์ ๊ด๋ จ์ฑ ํ์',
'feedback': '๊ฒ์๋ ๋ฌธ์๋ค์ ๋ฒกํฐ ์ ์ฅ์์ ๋ํด ์ผ๋ฐ์ ์ผ๋ก ๋
ผ์ํ์ง๋ง, Chroma์ ๋ํด ๊ตฌ์ฒด์ ์ผ๋ก ๋ค๋ฃจ์ง ์์ต๋๋ค'}],
'fa_summary': 'Chroma ๋ฌธ์์ ๊ฒ์ ํ์ง์ด ๋ฎ์.',
'report': 'foo bar baz',
'processed_logs': ['summary-on-log-2', 'failure-analysis-on-log-2']}
๐ ์์ ํ๋ฆ ์ ๋ฆฌ
- ๋ถ๋ชจ ๊ทธ๋ํ์์
์๋ณธ ๋ก๊ทธ ๋ฐ์ดํฐ
๋ฅผ ์ ์ ํ์ฌcleaned_logs
๋ก ๋ณํํฉ๋๋ค.
- ์์ ์์๋ ์ด๋ฅผ ์ํด โ๋๋ฏธ ๋ก๊ทธโ๋ฅผ ์์ฑ ํ ๋ฃ์ด์ฃผ๊ณ ์์ต๋๋ค
cleaned_logs
๋ Failure Analysis ์๋ธ๊ทธ๋ํ์ Question Summarization ์๋ธ๊ทธ๋ํ์ ์ ๋ฌ๋ฉ๋๋ค.
- Failure Analysis ์๋ธ๊ทธ๋ํ๋ ์คํจํ ๋ก๊ทธ(question_answer_feedback)๋ฅผ ์๋ณํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ก ์คํจ ์์ฝ(fa_summary)์ ์์ฑํฉ๋๋ค.
- Question Summarization ์๋ธ๊ทธ๋ํ๋ ์ง๋ฌธ ๋ก๊ทธ(question_answer)๋ฅผ ์์ฝํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ก ๋ณด๊ณ ์(report)๋ฅผ ์์ฑํฉ๋๋ค.
- ๋ ์๋ธ๊ทธ๋ํ์์ ์ฒ๋ฆฌ๋ ๋ก๊ทธ ์ ๋ณด๋
processed_logs
์ ๊ธฐ๋ก๋ฉ๋๋ค.
- ์ต์ข ๊ฒฐ๊ณผ๋ก, ๊ฐ ์๋ธ๊ทธ๋ํ์ ๊ฒฐ๊ณผ(
fa_summary
์report
)์ ์ฒ๋ฆฌ๋ ๋ก๊ทธ ๋ชฉ๋ก์ด ์ถ๋ ฅ๋ฉ๋๋ค.
Lesson 3: Map-reduce
๊ฐ์
MapReduce๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ ๊ฐ์ง ๊ธฐ๋ณธ ๋จ๊ณ์ธ Map ๋จ๊ณ์ Reduce ๋จ๊ณ๋ก ๋๋๋ ํ๋ ์์ํฌ์ ๋๋ค. ์ฌ๊ธฐ์ ๊ฐ ๋จ๊ณ์ ํต์ฌ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- Map ๋จ๊ณ: ์ฃผ์ด์ง ์์ ์ ์ฌ๋ฌ ๊ฐ์ ์์ ์์ ์ผ๋ก ๋ถํ ํ๊ณ ์ด๋ฅผ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ์ฌ ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํฉ๋๋ค.
- Reduce ๋จ๊ณ: Map ๋จ๊ณ์ ์ค๊ฐ ๊ฒฐ๊ณผ๋ค์ ํ๋๋ก ํตํฉํ๋ ์์ ์ ์ํํ์ฌ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ์ป์ต๋๋ค.
์ฃผ์ ๊ฐ๋
LangGraph์์์ Map ๋จ๊ณ ๊ตฌํ
-
์์ ์์ ๋ค์๊ณผ ๊ฐ์ ์์๋ก Map ๋จ๊ณ๋ฅผ ๊ตฌํํฉ๋๋ค:
-
์ฃผ์ ์์ฑ:
- ์ฒซ ๋ฒ์งธ ๋จ๊ณ์์๋ ์ฌ์ฉ์๊ฐ ์ ๊ณตํ ์ฃผ์ ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก LangGraph๊ฐ ์ฌ๋ฌ ๊ฐ์ ๊ด๋ จ ์ฃผ์ (subject)๋ฅผ ์์ฑํฉ๋๋ค.
- ์ด ๊ณผ์ ์ LangGraph์
send
API๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ๊ฐ์generate joke
๋ ธ๋๋ก ํ์ฅ๋ฉ๋๋ค. - ์ด๋ ๊ฐ ์ฃผ์ ๋ณ๋ก LangGraph๊ฐ ๋ณ๋์ ๋ ธ๋๋ฅผ ์๋์ผ๋ก ์์ฑํ์ฌ ๋ณ๋ ฌ์ ์ผ๋ก joke์ ์์ฑํ๋๋ก ํฉ๋๋ค.
-
joke ์์ฑ:
- ๊ฐ ์ฃผ์ ์ ๋ํด LangGraph๋
generate joke
๋ ธ๋์์ ํด๋น ์ฃผ์ ์ ๋ง๋ joke์ ์์ฑํฉ๋๋ค. - ์ด ๋ ธ๋๋ ์ ๋ ฅ์ผ๋ก ์ฃผ์ ๋ฅผ ๋ฐ๊ณ , ํด๋น ์ฃผ์ ์ ๋ง๋ joke์ ์์ฑํ์ฌ ๊ฒฐ๊ณผ๋ฅผ jokes ๋ฆฌ์คํธ์ ์ถ๊ฐํฉ๋๋ค.
- LangGraph์ add reducer ๊ธฐ๋ฅ์ ํตํด ์์ฑ๋ joke๋ค์ด ํ๋์ jokes ๋ฆฌ์คํธ๋ก ํตํฉ๋ฉ๋๋ค.
- ๊ฐ ์ฃผ์ ์ ๋ํด LangGraph๋
-
LangGraph์์์ Reduce ๋จ๊ณ ๊ตฌํ
-
Reduce ๋จ๊ณ์์๋ ์์ฑ๋ ๋ชจ๋ joke ์ค์์ ๊ฐ์ฅ ์ฐ์ํ ๊ฒ์ ์ ํํฉ๋๋ค. ์ฃผ์ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ต๊ณ ์ joke ์ ํ:
- ๋ชจ๋ joke์ด jokes ๋ฆฌ์คํธ์ ์ถ๊ฐ๋๋ฉด, LangGraph๋ ํด๋น ๋ฆฌ์คํธ๋ฅผ ํ ๋ฌธ์์ด๋ก ํฉ์ณ best joke prompt๋ก ์ ๋ฌํฉ๋๋ค.
- ๋ชจ๋ธ์ ์ ์ฒด joke ๋ฆฌ์คํธ๋ฅผ ํ๊ฐํ๊ณ , ๊ฐ์ฅ ์ฌ๋ฏธ์๋ joke์ ์ธ๋ฑ์ค๋ฅผ ๋ฐํํฉ๋๋ค.
- ๋ฐํ๋ ์ธ๋ฑ์ค๋ best joke๋ก์ ์ต์ข ๊ฒฐ๊ณผ์ ํฌํจ๋ฉ๋๋ค.
- ์ต๊ณ ์ joke ์ ํ:
์ฝ๋ ์์
- LLM ๋ฐ State ์ ์
- ๋๋ด ์์ฑ ๋ฐ ์ ํ์ ์ํ
LLM์ ์ ์
ํฉ๋๋ค. - ๊ทธ๋ํ์
์ง์ ์ ์ ์ ์
ํฉ๋๋ค. ์ด ์ง์ ์ ์:- ์ฌ์ฉ์ ์ ๋ ฅ ์ฃผ์ (topic)๋ฅผ ๋ฐ์ต๋๋ค
- ๊ทธ๋ก๋ถํฐ ๋๋ด ์ฃผ์ ๋ชฉ๋ก(subject)์ ์์ฑํฉ๋๋ค
- ๊ฐ ๋๋ด ์ฃผ์ ๋ฅผ ์์ ๋๋ด ์์ฑ ๋ ธ๋๋ก ๋ณด๋ ๋๋ค
- ์ด๋ ์์ฑ๋ ๋๋ด์ ๋ฆฌ๋์๋ก ์ ์ฅํด๋
jokes
ํค ์ ์
- ๋๋ด ์์ฑ ๋ฐ ์ ํ์ ์ํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from langchain_openai import ChatOpenAI
# ์ฌ์ฉํ ํ๋กฌํํธ
# subjects_prompt = """๋ค์ ์ ์ฒด ์ฃผ์ ์ ๊ด๋ จ๋ 3๊ฐ์ sub-topics ๋ชฉ๋ก์ ์์ฑํ์ธ์: {topic}."""
# joke_prompt = """{subject}์ ๋ํ ๋๋ด์ ์์ฑํ์ธ์"""
# best_joke_prompt = """์๋๋ {topic}์ ๋ํ ์ฌ๋ฌ ๋๋ด์
๋๋ค. ๊ฐ์ฅ ์ข์ ๊ฒ์ ์ ํํ์ธ์! ๊ฐ์ฅ ์ข์ ๋๋ด์ ID๋ฅผ ๋ฐํํ์ธ์, ์ฒซ ๋ฒ์งธ ๋๋ด์ ID๋ 0๋ถํฐ ์์ํฉ๋๋ค. ๋๋ด๋ค: \n\n {jokes}"""
subjects_prompt = """Generate a list of 3 sub-topics that are all related to this overall topic: {topic}."""
joke_prompt = """Generate a joke about {subject}"""
best_joke_prompt = """Below are a bunch of jokes about {topic}. Select the best one! Return the ID of the best one, starting 0 as the ID for the first joke. Jokes: \n\n {jokes}"""
# LLM
model = ChatOpenAI(model="gpt-4", temperature=0)
joke_model = ChatOpenAI(model="gpt-4", temperature=1.0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import operator
from typing import Annotated
from typing_extensions import TypedDict
from pydantic import BaseModel
class Subjects(BaseModel):
subjects: list[str]
class BestJoke(BaseModel):
id: int
class OverallState(TypedDict):
topic: str
subjects: list
jokes: Annotated[list, operator.add]
best_selected_joke: str
-
์ฝ๋ ์ค๋ช
- Subjects: ํ์ ์ฃผ์ ๋ค์ ํฌํจํ๋ ๊ตฌ์กฐ์ฒด๋ก, ๊ฐ ์ฃผ์ ๋ณ๋ก
joke
๋ฅผ ์์ฑํ๊ธฐ ์ํดMap
๋จ๊ณ์์ ์ฌ์ฉ๋ฉ๋๋ค. - BestJoke: ๊ฐ์ฅ ์ฌ๋ฏธ์๋
joke
์ ์ธ๋ฑ์ค๋ฅผ ์ ์ฅํ์ฌ,Reduce
๋จ๊ณ์์ ์ต์์ joke๋ฅผ ์ ํํ ๋ ์ฌ์ฉ๋ฉ๋๋ค. - OverallState: ์ ์ฒด
MapReduce
๊ณผ์ ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๊ตฌ์กฐ์ฒด๋ก, ์ฃผ์ด์ง ์ฃผ์ ์์ํ์ ์ฃผ์
,์์ฑ๋ joke ๋ฆฌ์คํธ
,์ต์ข ์ ํ๋ joke
๊น์ง์ ๋ชจ๋ ์ํ๋ฅผ ์ ์ฅํฉ๋๋ค.
- Subjects: ํ์ ์ฃผ์ ๋ค์ ํฌํจํ๋ ๊ตฌ์กฐ์ฒด๋ก, ๊ฐ ์ฃผ์ ๋ณ๋ก
- ์ฃผ์ ์ ๋ํ ํ์ ์์
์์ฑ (Map ์ด์ ๋จ๊ณ) -
generate_topics
ํจ์
-
์ญํ :
generate_topics
ํจ์๋ ์ฃผ์ด์ง ์ฃผ์ (topic
)์ ๋ํด ๊ด๋ จ ์๋ ํ์ ์ฃผ์ ๋ชฉ๋ก์ ์์ฑํ๋ ์ญํ ์ ํฉ๋๋ค.1 2 3 4 5
def generate_topics(state: OverallState): prompt = subjects_prompt.format(topic=state["topic"]) response = model.with_structured_output(Subjects).invoke(prompt) return {"subjects": response.subjects}
-
๋์:
subjects_prompt
๋ฅผ ํตํด ์ฌ์ฉ์๊ฐ ์ ๊ณตํ ์ฃผ์ ์ฃผ์ (topic
)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก LangChain ๋ชจ๋ธ์ ์ ๋ฌํ ํ๋กฌํํธ๋ฅผ ์์ฑํฉ๋๋ค.model.with_structured_output(Subjects).invoke(prompt)
๋ฅผ ์ฌ์ฉํ์ฌ LangChain ๋ชจ๋ธ์์Subjects
์คํค๋ง์ ๋ง๋ ์๋ต(๋ฆฌ์คํธ, ์๋ ์ฐธ๊ณ )์ ์์ฑํ๋๋ก ์ง์ ํฉ๋๋ค. ```
class Subjects(BaseModel): subjects: list[str] ```
- ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ๋ชจ๋ธ์ ์๋ต์
Subjects
์คํค๋ง์ ์ ์๋ ํํ(subjects: list[str]
)๋ฅผ ๊ฐ์ง๋ฉฐ, ์ ํํ ๋ฆฌ์คํธ ํ์์ผ๋ก ์ฃผ์ ๋ค์ด ๋ฐํ๋ฉ๋๋ค. - ๋ฐํ๋ ์ฃผ์ ๋ชฉ๋ก์
subjects
๋ก ์ ์ฅ๋์ด ์ดํMap
๋จ๊ณ์์ ๊ฐ ์ฃผ์ ์ ๋ํ joke ์์ฑ ์์ ์ ์ฌ์ฉ๋ฉ๋๋ค.
-
์ํ: ํ์ฌ ์ํ(state)๋
subjects: list[str]
์ ๋ฆฌ์คํธ ํํ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. langgraph์ Send API๋ฅผ ์ฌ์ฉํ์ฌ continue_to_jokes ํจ์์ ๊ฐ๊ฐ์ subjects ๋ฆฌ์คํธ ์์๋ค์ ๋๊ฒจ์ค ์ ์์ต๋๋ค.
-
-
Send
์continue_to_jokes
ํจ์1 2 3 4 5
from langgraph.constants import Send def continue_to_jokes(state: OverallState): return [Send("generate_joke", {"subject": s}) for s in state["subjects"]]
Send
๋ ๊ฐ ์ฃผ์ ๋ฅผ ๊ธฐ๋ฐ์ผ๋กgenerate_joke
๋ ธ๋๋ก ์์ ์ ์ ๋ฌํ์ฌ ๋ณ๋ ฌ์ ์ผ๋ก joke๋ฅผ ์์ฑํ๋๋ก ํฉ๋๋ค.continue_to_jokes
๋generate_joke
๋ ธ๋๋ก์ ์ ์ก์ ์ค์ ํ๋ ์ญํ ์ ํฉ๋๋ค.-
๋์:
state["subjects"]
๋ฆฌ์คํธ์์ ๊ฐ ์ฃผ์ s
์ ๋ํดSend
๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค.Send("generate_joke", {"subject": s})
๋generate_joke
๋ ธ๋์{ "subject": s }
ํํ๋ก ์ํ๋ฅผ ์ ๋ฌํฉ๋๋ค.- ์ด ์ ๋ฌ๋๋ ์ํ๊ฐ
OverallState
์ ์ผ์นํ ํ์๋ ์์ผ๋ฉฐ,generate_joke
๋ ธ๋๋ ์์ ์ ์ํ์ ๋ง๊ฒ ์ด ์ ๋ณด๋ฅผ ํ์ฉํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค.
- ์ด ์ ๋ฌ๋๋ ์ํ๊ฐ
- ๊ฒฐ๊ณผ์ ์ผ๋ก
Send
๋ฅผ ํตํด ๊ฐ ์ฃผ์ ๋ฅผ ๋ณ๋ ฌ๋กgenerate_joke
์ ์ ๋ฌํ์ฌ ๊ฐ ์ฃผ์ ์ ๋ํ joke๋ฅผ ์์ฑํ๋๋ก ํฉ๋๋ค.
-
ํ์ ์ฃผ์ ์ ๋ํ ๋๋ด ์์ฑ (Map ๋จ๊ณ) -
generate_joke
ํจ์-
์ญํ :
generate_joke
๋ ๊ฐ ์ฃผ์ ์ ๋ํด joke๋ฅผ ์์ฑํ๊ณ , ์ด๋ฅผOverallState
์jokes
๋ฆฌ์คํธ์ ์ถ๊ฐํ๋ ์ญํ ์ ํฉ๋๋ค.1 2 3 4 5 6 7 8 9 10 11
class JokeState(TypedDict): subject: str class Joke(BaseModel): joke: str def generate_joke(state: JokeState): prompt = joke_prompt.format(subject=state["subject"]) response = model.with_structured_output(Joke).invoke(prompt) return {"jokes": [response.joke]}
-
๋์:
state["subject"]
๊ฐ์ ์ด์ฉํดjoke_prompt
๋ฅผ ์์ฑํ๊ณ , ์ด๋ฅผ ํ๋กฌํํธ๋ก ์ฌ์ฉํ์ฌ LangChain ๋ชจ๋ธ์ ํธ์ถํฉ๋๋ค.-
model.with_structured_output(Joke).invoke(prompt)
๋ฅผ ์ฌ์ฉํ์ฌJoke
์คํค๋ง์ ๋ง๋ ์๋ต์ ์์ฑํ๋๋ก ์ง์ ํฉ๋๋ค.- ๋ชจ๋ธ์ ์๋ต์
Joke
์คํค๋ง์ ์ ์๋ ํํ(joke: str
)๋ก ๋ฐํ๋๋ฉฐ, ์ ํํ ๋ฌธ์์ด ํ์์ joke๊ฐ ์์ฑ๋ฉ๋๋ค.
- ๋ชจ๋ธ์ ์๋ต์
-
๋ฐํ๋ joke๋ ๋ฆฌ์คํธ๋ก ๊ฐ์ธ์ ธ
{ "jokes": [response.joke] }
ํ์์ผ๋ก ๋ฐํ๋ฉ๋๋ค.- ์ด๋
OverallState
์jokes
ํค์ ์ถ๊ฐ๋๋ฉฐ, ์ฌ๊ธฐ์jokes
ํค๋add reducer
๋ฅผ ํตํด ๋ฆฌ์คํธ๋ก ์๋ ์ง๊ณ๋ฉ๋๋ค.
- ์ด๋
-
-
์ต๊ณ ์ ๋๋ด ์ ํ (Reduce ๋จ๊ณ) -
best_joke
ํจ์-
์ญํ :
best_joke
ํจ์๋OverallState
์ ์๋ ์ฌ๋ฌ ๊ฐ์ joke ์ค์์ ๊ฐ์ฅ ์ฌ๋ฏธ์๋ joke๋ฅผ ์ ํํ๋ ๊ธฐ๋ฅ์ ํฉ๋๋ค.1 2 3 4 5 6
# Reduce ๋จ๊ณ: ๊ฐ์ฅ ์ฌ๋ฏธ์๋ ๋๋ด ์ ํ def best_joke(state: OverallState): jokes = "\n\n".join(state["jokes"]) prompt = best_joke_prompt.format(topic=state["topic"], jokes=jokes) response = model.with_structured_output(BestJoke).invoke(prompt) return {"best_selected_joke": state["jokes"][response.id]}
-
๋์:
state["jokes"]
๋ฆฌ์คํธ์ ์ ์ฅ๋ ๋ชจ๋ joke๋ฅผ\n\n
๋ก ๊ตฌ๋ถํ์ฌ ํ๋์ ๋ฌธ์์ด๋ก ๊ฒฐํฉํฉ๋๋ค.best_joke_prompt
๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์ด์ง topic๊ณผ ๊ฒฐํฉ๋ joke ๋ฆฌ์คํธ๋ฅผ LangChain ๋ชจ๋ธ์ ํ๋กฌํํธ๋ก ์์ฑํฉ๋๋ค.with_structured_output(BestJoke).invoke(prompt)
๋ฅผ ํธ์ถํ์ฌ LangChain ๋ชจ๋ธ์ดBestJoke ์คํค๋ง
์ ๋ง๋ ์๋ต์ ์์ฑํ๋๋ก ํฉ๋๋ค.- ์ด ๋ชจ๋ธ์ ๊ฐ์ฅ ์ฌ๋ฏธ์๋
joke์ ์ธ๋ฑ์ค๋ฅผ ๋ฐํ
ํ๋ฉฐ,response.id์ ํด๋น ์ธ๋ฑ์ค๊ฐ ์ ์ฅ
๋ฉ๋๋ค.
- ์ด ๋ชจ๋ธ์ ๊ฐ์ฅ ์ฌ๋ฏธ์๋
- ์ต์ข
์ ์ผ๋ก
state["jokes"][response.id]
์ ํด๋นํ๋ joke๋ฅผbest_selected_joke
ํค์ ์ ์ฅํ์ฌ ๋ฐํํฉ๋๋ค.
-
-
StateGraph ๊ตฌ์ฑ ๋ฐ ์ปดํ์ผ ๋จ๊ณ
-
์ญํ :
StateGraph
๋ ์ ์ฒด MapReduce ํ๋ฆ์ ํ ๊ทธ๋ํ ๋ด์์ ๊ตฌ์ฑํ๋ ์ญํ ์ ํฉ๋๋ค.1 2 3 4 5 6 7 8 9 10 11 12
from IPython.display import Image from langgraph.graph import END, StateGraph, START # ๊ทธ๋ํ ๊ตฌ์ฑ graph = StateGraph(OverallState) graph.add_node("generate_topics", generate_topics) graph.add_node("generate_joke", generate_joke) graph.add_node("best_joke", best_joke) graph.add_edge(START, "generate_topics") graph.add_conditional_edges("generate_topics", continue_to_jokes, ["generate_joke"]) graph.add_edge("generate_joke", "best_joke") graph.add_edge("best_joke", END)
-
๋์:
OverallState
๋ฅผ ์ํ๋ก ์ฌ์ฉํ๋StateGraph ๊ฐ์ฒด๋ฅผ ์์ฑ
ํฉ๋๋ค.generate_topics
,generate_joke
,best_joke
๋ ธ๋๋ฅผ ๊ฐ๊ฐ ๊ทธ๋ํ์ ์ถ๊ฐํ์ฌ ๊ฐ ์์ ๋จ๊ณ๋ฅผ ์ค์ ํฉ๋๋ค.- ์์์ (START)์์
generate_topics
๋ ธ๋๋ก ์ด๋ํ๋๋กadd_edge
๋ฉ์๋๋ก ์ฃ์ง๋ฅผ ์ถ๊ฐํฉ๋๋ค. generate_topics
๋จ๊ณ ์ดํ ๊ฐ ์ฃผ์ ์ ๋ํด์กฐ๊ฑด๋ถ ์ฃ์ง(add_conditional_edges)
๋ฅผ ์ถ๊ฐํ์ฌgenerate_joke๋ก ์ฐ๊ฒฐ
๋๋๋ก ํฉ๋๋ค.- ๊ฐ
generate_joke
์์ ์ดํbest_joke ๋จ๊ณ๋ก ์ฐ๊ฒฐ
๋๋ฉฐ,best_joke์์ ์ต์ข ๊ฒฐ๊ณผ๋ฅผ ์์ฑ
ํ์ฌ ์ข ๋ฃ์ (END)์ผ๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
-
์คํ ์์
- ์๋์ ๊ฐ์ด
science
๋ผ๋ topic์ ์ฃผ๋ฉด, LLM์ด ์ ์ฌ ์ฃผ์ 3๊ฐ๋ฅผ ๋ง๋ค๊ณ , ์ด์ ๊ด๋ จ๋ joke ์์ฑ ํ best joke๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
1
2
3
# ๊ทธ๋ํ ํธ์ถ: ์ฌ๊ธฐ์ ๋๋ด ๋ชฉ๋ก์ ์์ฑํ๊ธฐ ์ํด ํธ์ถํฉ๋๋ค
for s in app.stream({"topic": "science"}):
print(s)
1
2
3
4
5
6
7
8
9
10
11
12
Generated Subjects: ['Physics', 'Biology', 'Chemistry']
{'generate_topics': {'subjects': ['Physics', 'Biology', 'Chemistry']}}
{'generate_joke': {
'jokes': ["Why did the biologist look forward to casual Fridays? Because they're allowed to wear genes to work!"]}}
{'generate_joke': {
'jokes': ["Why can't you trust an atom? Because they make up everything!"]}}
{'generate_joke': {
'jokes': ["Why do chemists like nitrates so much? Because they're cheaper than day rates!"]}}
{'best_joke': {'best_selected_joke': "Why can't you trust an atom? Because they make up everything!"}}
Lesson 4: Research Assistant
๊ฐ์
Research Assistant ์์คํ ์ ์ฌ๋ฌ AI ์์ด์ ํธ๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๊ตฌ ์์ ์ ์๋ํํ๋ ๊ตฌ์กฐ์ ๋๋ค. ์ฃผ์ ์ ๋ฐ๋ผ ๊ฐ ์์ด์ ํธ๊ฐ ํ์ ์ฃผ์ ๋ฅผ ๋ด๋นํ๊ณ , ์ธ๋ถ ์์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฐ๊ตฌ๋ฅผ ์ํํ ํ ๊ฒฐ๊ณผ๋ฅผ ๋ณํฉํ์ฌ ์ต์ข ๋ณด๊ณ ์๋ฅผ ์์ฑํฉ๋๋ค.
์ฃผ์ ๊ฐ๋
์ด๋ฒ ์ฑํฐ์ ๋ชฉํ๋ ์ฑํ ๋ชจ๋ธ์ ์ค์ฌ์ผ๋ก ๊ฒฝ๋ ๋ฉํฐ ์์ด์ ํธ ์์คํ ์ ๊ตฌ์ถํ์ฌ ์ฐ๊ตฌ ํ๋ก์ธ์ค๋ฅผ ์ต์ ํํ๋ ๊ฒ์ ๋๋ค.
ํด๋น ๋ฉํฐ ์์ด์ ํธ ์์คํ ์ ์๋ ๋ชจ๋๋ค๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
-
์์ค ์ ํ
- ์ฌ์ฉ์๋ ์ฐ๊ตฌ์ ์ฌ์ฉํ ์ ๋ ฅ ์์ค ์ธํธ๋ฅผ ์ ํํ ์ ์์ต๋๋ค.
-
๊ณํ
- ์ฌ์ฉ์๊ฐ ์ฃผ์ ๋ฅผ ์ ๊ณตํ๋ฉด ์์คํ ์ AI ๋ถ์๊ฐ ํ์ ์์ฑํ๊ณ , ๊ฐ ๋ถ์๊ฐ๋ ํ๋์ ํ์ ์ฃผ์ ์ ์ง์คํฉ๋๋ค.
- ์ฐ๊ตฌ ์์ ์ ์ ์ด๋ฌํ ํ์ ์ฃผ์ ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด
Human-in-the-loop
๊ฐ ์ฌ์ฉ๋ฉ๋๋ค.
-
LLM ํ์ฉ
- ๊ฐ ๋ถ์๊ฐ๋ ์ ํ๋ ์์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ฌธ๊ฐ AI์ ์ฌ์ธต ์ธํฐ๋ทฐ๋ฅผ ์ํํฉ๋๋ค.
- ์ธํฐ๋ทฐ๋ STORM ๋ ผ๋ฌธ์์ ๋ณด์ฌ์ง ๊ฒ์ฒ๋ผ ์์ธํ ํต์ฐฐ์ ์ถ์ถํ๊ธฐ ์ํ ๋ค์ค ํด ๋ํ๊ฐ ๋ ๊ฒ์ ๋๋ค.
- ์ด๋ฌํ ์ธํฐ๋ทฐ๋ ๋ด๋ถ ์ํ๋ฅผ ๊ฐ์ง
sub-graphs
๋ฅผ ์ฌ์ฉํ์ฌ ์บก์ฒ๋ ๊ฒ์ ๋๋ค.
-
์ฐ๊ตฌ ํ๋ก์ธ์ค
- ์ ๋ฌธ๊ฐ๋ค์ ๋ถ์๊ฐ์ ์ง๋ฌธ์ ๋ตํ๊ธฐ ์ํด
parallel
๋ก ์ ๋ณด๋ฅผ ์์งํฉ๋๋ค. - ๋ชจ๋ ์ธํฐ๋ทฐ๋
map-reduce
๋ฅผ ํตํด ๋์์ ์ํ๋ฉ๋๋ค.
- ์ ๋ฌธ๊ฐ๋ค์ ๋ถ์๊ฐ์ ์ง๋ฌธ์ ๋ตํ๊ธฐ ์ํด
-
์ถ๋ ฅ ํ์
- ๊ฐ ์ธํฐ๋ทฐ์์ ์์ง๋ ํต์ฐฐ์ ์ต์ข ๋ณด๊ณ ์๋ก ์ข ํฉ๋ฉ๋๋ค.
- ์ฐ๋ฆฌ๋ ๋ณด๊ณ ์์ ๋ํด ๋ง์ถคํ ๊ฐ๋ฅํ ํ๋กฌํํธ๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฐํ ์ถ๋ ฅ ํ์์ ํ์ฉํฉ๋๋ค.
๐ช๏ธ โ์ํคํผ๋์์ ์ ์ฌํ ๊ธ ์์ฑ์ ์ํ ๋ํ ์ธ์ด ๋ชจ๋ธ ์ง์ ์์คํ , STORMโ
- ๋ ผ๋ฌธ ๋งํฌ : https://arxiv.org/pdf/2402.14207
ํด๋น ๋ ผ๋ฌธ์ ๋ํ ์ธ์ด ๋ชจ๋ธ(LLM)์ ํ์ฉํ์ฌ ์ํคํผ๋์ ์์ค์ ์ฅ๋ฌธ ๊ธ ์์ฑ์ ์ง์ํ๋
STORM ์์คํ
์ ์๊ฐํฉ๋๋ค.
- STORM์ ์ฌ์ฉ์๊ฐ ์ ๊ณตํ ์ฃผ์ ๋ฅผ ๋ฐํ์ผ๋ก ์ํคํผ๋์ ์คํ์ผ์ ์ํฐํด์ ์์ฑํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ์ด ์์คํ ์ ๋ณด๋ค ์กฐ์ง์ ์ด๊ณ ํฌ๊ด์ ์ธ ์ํฐํด์ ๋ง๋ค๊ธฐ ์ํด ๋ค์ ๋ ๊ฐ์ง ์ฃผ์ ์ธ์ฌ์ดํธ๋ฅผ ํ์ฉํฉ๋๋ค:
- ์ ์ฌํ ์ฃผ์ ์ ๋ํ ์ฟผ๋ฆฌ๋ฅผ ํตํด ๊ฐ์๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์ฃผ์ ์ ํฌ๊ด์ฑ์ ํฅ์์ํต๋๋ค.
- ๋ค์ํ ๊ด์ ์ ๋ฐ์ํ๊ณ ๊ฒ์์ ๊ธฐ๋ฐํ ๋ํ ์๋ฎฌ๋ ์ด์ ์ ํตํด ์ฐธ์กฐ ์๋ฃ์ ์์ ์ ๋ณด ๋ฐ๋๋ฅผ ๋์ ๋๋ค.
- ์ ๊ทธ๋ฆผ์ ๋ ผ๋ฌธ์์ ์ ์ํ flow diagram์ด๋ฉฐ, ์๋ ๊ทธ๋ฆผ์ langchain ์ธก์์ ์ข ๋ ๋ํ ์ผํ๊ฒ ๊ทธ๋ฆฐ flow diagram์ ๋๋ค.
์๋ฃ ๋งํฌ : https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/storm/storm.ipynb
๐ช๏ธ STORM ๋ ผ๋ฌธ์์ ์ํํ๋ ๋ค์ค ํด ๋ํ(multi-turn conversation)๋ ์ํคํผ๋์ ์์ฑ์์ ์ฃผ์ ์ ๋ฌธ๊ฐ ๊ฐ์ ๊ฐ์ ๋ํ๋ฅผ ์๋ฎฌ๋ ์ด์ ํ์ฌ ์ ๋ณด๋ฅผ ์ป๋ ๊ณผ์ ์ ๋๋ค.
- ์ด ๊ณผ์ ์์ STORM์ ๋ค์๊ณผ ๊ฐ์ ๋จ๊ณ๋ฅผ ๊ฑฐ์นฉ๋๋ค:
- ์ง๋ฌธ ์์ฑ: ํน์ ๊ด์ ์ ๊ฐ์ง ์ํคํผ๋์ ์์ฑ์๊ฐ ์ฃผ์ ์ ๋ํด ์ง๋ฌธ์ ๋์ง๋๋ค. ์๋ฅผ ๋ค์ด, โ2022๋ ๋๊ณ ์ฌ๋ฆผํฝ ๊ฐ๋ง์์ ๊ตํต ์ค๋น๋ ์ด๋ป๊ฒ ๋์๋๊ฐ?โ์ ๊ฐ์ ์ธ๋ถ์ ์ธ ์ง๋ฌธ์ด ์์ฑ๋ ์ ์์ต๋๋ค. ์ด๋ ๋จ์ํ โ๋ฌด์โ์ด๋ โ์ธ์ โ์ ๊ฐ์ ํ๋ฉด์ ์ธ ์ง๋ฌธ์์ ๋ฒ์ด๋, ์ฃผ์ ์ ๋ํ ์ฌ์ธต์ ์ธ ํ๊ตฌ๋ฅผ ์ ๋ํฉ๋๋ค.
- ์ง๋ฌธ ์ธ๋ถํ ๋ฐ ๊ฒ์: ๊ฐ ์ง๋ฌธ์ ์ธ๋ถ ๊ฒ์ ์ฟผ๋ฆฌ๋ก ๋ถํ ๋๊ณ , ์ ๋ขฐํ ์ ์๋ ์ธํฐ๋ท ์ถ์ฒ๋ฅผ ์ฌ์ฉํด ํ์ํ ์ ๋ณด๋ฅผ ์์งํฉ๋๋ค. ์ด ๋จ๊ณ์์๋ ์ํคํผ๋์์ ์ ๋ขฐ๋ ๊ธฐ์ค์ ๋ง๋ ์ถ์ฒ๋ง ์ ํํ์ฌ, ์ ํํ ์ ๋ณด๋ง์ด ๋ต๋ณ์ ๋ฐ์๋๋๋ก ํฉ๋๋ค.
- ๋๋ต ์์ฑ ๋ฐ ๋ํ ๋ฐ๋ณต: ์์ง๋ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ๊ฐ์์ ์ ๋ฌธ๊ฐ๊ฐ ์ง๋ฌธ์ ๋ต๋ณํ๊ณ , ์ด ๋ต๋ณ์ ๋ฐํ์ผ๋ก ์์ฑ์๊ฐ ์๋ก์ด ํ์ ์ง๋ฌธ์ ์์ฑํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฒซ ๋ฒ์งธ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์์ ์ถ๊ฐ์ ์ธ ๊ตํต ํธ์์ ๊ดํ ์ธ๊ธ์ด ์๋ค๋ฉด, ์์ฑ์๋ ์ด์ ๊ด๋ จ๋ ์๋ก์ด ์ง๋ฌธ์ ํ ์ ์์ต๋๋ค.
- ์๋ฃ ์ถ์ ๋ฐ ๊ตฌ์กฐ ํ์ฑ: ์ฌ๋ฌ ๋ผ์ด๋์ ๊ฑธ์น ๋ํ๊ฐ ์งํ๋๋ฉด์, STORM์ ์ฃผ์ ์ ๊ด๋ จ๋ ๊น์ด ์๋ ์ ๋ณด๋ฅผ ์์งํ๊ฒ ๋ฉ๋๋ค. ์ด๋ ๊ฒ ์ป์ด์ง ์ ๋ณด๋ ๋์ค์ ์ ์ฒด ๋ฌธ์์ ๊ฐ์๋ฅผ ์ ๋ฆฌํ๋ ๋ฐ ํ์ฉ๋ฉ๋๋ค.
์ฝ๋ ์์
โ๏ธ Generate Analysts: Human-In-The-Loop
Generate Analysts
์น์
์์๋ LangGraph์ Human-in-the-loop
๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๊ตฌ ์ฃผ์ ์ ๋ง๋ AI ๋ถ์๊ฐ๋ฅผ ์์ฑํ๊ณ ์ธ๊ฐ์ ํผ๋๋ฐฑ์ ๋ฐ์ํ์ฌ ๋ถ์๊ฐ๋ฅผ ๊ฒํ ํ๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃน๋๋ค.
- ์ด๋ฅผ ํตํด ์ฐ๊ตฌ ์ฃผ์ ๋ฅผ ๋ค๋ฃจ๊ธฐ ์ํ ์ต์ ์ ๋ถ์๊ฐ ํ์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
1. ๊ธฐ๋ณธ ์ฝ๋ ๊ตฌ์ฑ
๋จผ์ ํ์ํ ๋ชจ๋๊ณผ ๊ธฐ๋ณธ ํด๋์ค ๊ตฌ์กฐ๋ฅผ ์ ์ํฉ๋๋ค. ๋ถ์๊ฐ๋ ์ฐ๊ตฌ ์ฃผ์ ์ ์ญํ ์ ๋ฐ๋ผ ๊ฐ๊ธฐ ๋ค๋ฅธ ํ๋ฅด์๋๋ฅผ ๊ฐ์ง๋ฉฐ, ์ด ํ๋ฅด์๋๋ ์ฃผ์ ์ ๋ง๋ ์ง๋ฌธ๊ณผ ๋ถ์์ ์ํํ๋ ๋ฐ ํ์ฉ๋ฉ๋๋ค.
1) Analyst ํด๋์ค ์คํค๋ง ์ ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from typing import List
from pydantic import BaseModel, Field
class Analyst(BaseModel):
affiliation: str = Field(
description="๋ถ์๊ฐ์ ์ฃผ์ ์์.",
)
name: str = Field(
description="๋ถ์๊ฐ์ ์ด๋ฆ."
)
role: str = Field(
description="์ฃผ์ ์ ๊ด๋ จ๋ ๋ถ์๊ฐ์ ์ญํ .",
)
description: str = Field(
description="๋ถ์๊ฐ์ ์ด์ , ๊ด์ฌ์ฌ, ๋๊ธฐ์ ๋ํ ์ค๋ช
.",
)
# ํ๋ฅด์๋ ์์ฑ: ๋ถ์๊ฐ์ ๋ฐฐ๊ฒฝ ๋ฐ ์ญํ ์ ์์ฝํ ํ
์คํธ
@property
def persona(self) -> str:
return f"์ด๋ฆ: {self.name}\n์ญํ : {self.role}\n์์: {self.affiliation}\n์ค๋ช
: {self.description}\n"
- Analyst ํด๋์ค: ๋ถ์๊ฐ์ ๋ํ ์ฃผ์ ์ ๋ณด๋ฅผ ๋ด๊ณ ์์ผ๋ฉฐ,
affiliation
,name
,role
,description
์์ฑ์ ํฌํจํฉ๋๋ค.- affiliation: ๋ถ์๊ฐ์ ์ฃผ์ ์์์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- name: ๋ถ์๊ฐ์ ์ด๋ฆ์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- role: ์ฃผ์ ์ ๊ด๋ จ๋ ๋ถ์๊ฐ์ ์ญํ ์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- description: ๋ถ์๊ฐ์ ์ด์ , ๊ด์ฌ์ฌ, ๋๊ธฐ์ ๋ํ ์ค๋ช ์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- persona ์์ฑ: ๋ถ์๊ฐ์ ๋ฐฐ๊ฒฝ์ ์์ฝํ์ฌ ํ๋ฅด์๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ด๋ฅผ ํตํด ๋ถ์๊ฐ๊ฐ ์ํํ ์ญํ ์ ๋ง๋ ์บ๋ฆญํฐ๋ฅผ ๊ฐ์ถ ์ ์์ต๋๋ค.
- persona๋
@property
๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ ๋ฉ์๋์ ๋๋ค. ์ด ๋ฉ์๋๋ ๋ถ์๊ฐ์ ์ ๋ณด๋ฅผ ์์ฝ๋ ๋ฌธ์์ด ํํ๋ก ๋ฐํํฉ๋๋ค. ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ, ์ด ๋ฉ์๋๋ฅผ ์์ฑ์ฒ๋ผ ์ ๊ทผํ ์ ์๊ฒ ๋ฉ๋๋ค (์:analyst.persona
).
2) Perspectives ํด๋์ค ์ ์
1
2
3
4
class Perspectives(BaseModel):
analysts: List[Analyst] = Field(
description="๋ถ์๊ฐ๋ค์ ์ญํ ๊ณผ ์์์ ํฌํจํ ์ข
ํฉ์ ์ธ ๋ชฉ๋ก.",
)
- Perspectives ํด๋์ค: ๋ถ์๊ฐ ์งํฉ ๋ฆฌ์คํธ (
List[Analyst]
)
3) GenerateAnalystsState ํด๋์ค ์ ์
1
2
3
4
5
class GenerateAnalystsState(TypedDict):
topic: str # ์ฐ๊ตฌ ์ฃผ์
max_analysts: int # ์์ฑํ ๋ถ์๊ฐ ์
human_analyst_feedback: str # ์ธ๊ฐ ํผ๋๋ฐฑ
analysts: List[Analyst] # ๋ถ์๊ฐ ๋ชฉ๋ก
- GenerateAnalystsState ํด๋์ค: ์ฃผ์ ์ ๋ถ์๊ฐ ์, ์ธ๊ฐ ํผ๋๋ฐฑ ๋ฑ์ ํฌํจํ์ฌ ์ฐ๊ตฌ ์ฃผ์ ์ ๋ง๋ ๋ถ์๊ฐ ํ์ ์ค์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
GenerateAnalystsState
ํด๋์ค๋ ์ฃผ์ ์ ๋ถ์๊ฐ ํ์ ์์ฑํ๊ณ ์ธ๊ฐ์ ํผ๋๋ฐฑ์ ๋ฐ์ํ์ฌ ์ฃผ์ด์ง ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
4) ๋ถ์๊ฐ ์์ฑ ์ง์นจ (Analyst Instructions) ์ ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
analyst_instructions = """๋น์ ์ AI ๋ถ์๊ฐ ํ๋ฅด์๋ ์ธํธ๋ฅผ ๋ง๋๋ ์๋ฌด๋ฅผ ๋งก์์ต๋๋ค. ๋ค์ ์ง์นจ์ ์ฃผ์ ๊น๊ฒ ๋ฐ๋ฅด์ธ์:
1. ๋จผ์ ์ฐ๊ตฌ ์ฃผ์ ๋ฅผ ๊ฒํ ํ์ธ์:
{topic}
2. ๋ถ์๊ฐ ์์ฑ์ ์๋ดํ๊ธฐ ์ํด ์ ํ์ ์ผ๋ก ์ ๊ณต๋ ํธ์ง ํผ๋๋ฐฑ์ ๊ฒํ ํ์ธ์:
{human_analyst_feedback}
3. ์์ ๋ฌธ์ ๋ฐ/๋๋ ํผ๋๋ฐฑ์ ๋ฐํ์ผ๋ก ๊ฐ์ฅ ํฅ๋ฏธ๋ก์ด ์ฃผ์ ๋ฅผ ๊ฒฐ์ ํ์ธ์.
4. ์์ {max_analysts}๊ฐ์ ์ฃผ์ ๋ฅผ ์ ํํ์ธ์.
5. ๊ฐ ์ฃผ์ ์ ํ ๋ช
์ ๋ถ์๊ฐ๋ฅผ ํ ๋นํ์ธ์.
"""
- analyst_instructions ์ง์นจ: ์ฐ๊ตฌ ์ฃผ์ ๋ฅผ ๋ฐํ์ผ๋ก ๋ถ์๊ฐ์ ํ๋ฅด์๋๋ฅผ ์์ฑํ๊ธฐ ์ํ ์ผ๋ จ์ ๋จ๊ณ๋ค์ ์ ๊ณตํฉ๋๋ค.
- ๋ถ์๊ฐ๋ฅผ ์์ฑํ๊ธฐ ์ํ ์์คํ
๋ฉ์์ง์ธ
analyst_instructions
๋ ์ฐ๊ตฌ ์ฃผ์ ์ ํผ๋๋ฐฑ์ ๊ธฐ๋ฐ์ผ๋ก ํน์ ํ ๋ถ์๊ฐ๋ฅผ ์์ฑํ๋ ์ง์นจ์ ํฌํจํฉ๋๋ค. - ํผ๋๋ฐฑ์ ๋ฐํ์ผ๋ก ํฅ๋ฏธ๋ก์ด ์ฃผ์ ๋ฅผ ๊ฒฐ์ ํ๊ณ , ๊ฐ๊ฐ์ ์ฃผ์ ์ ๋ง๋ ๋ถ์๊ฐ๋ฅผ ์์ฑํฉ๋๋ค.
- ๋ถ์๊ฐ๋ฅผ ์์ฑํ๊ธฐ ์ํ ์์คํ
๋ฉ์์ง์ธ
5) create_analysts ํจ์ ์ ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def create_analysts(state: GenerateAnalystsState):
topic = state['topic']
max_analysts = state['max_analysts']
human_analyst_feedback = state.get('human_analyst_feedback', '')
# ๊ตฌ์กฐํ๋ ์ถ๋ ฅ ๊ฐ์
structured_llm = llm.with_structured_output(Perspectives)
# ์์คํ
๋ฉ์์ง
system_message = analyst_instructions.format(topic=topic,
human_analyst_feedback=human_analyst_feedback,
max_analysts=max_analysts)
# ์ง๋ฌธ ์์ฑ
analysts = structured_llm.invoke(
[SystemMessage(content=system_message)]
+
[HumanMessage(content="๋ถ์๊ฐ ์ง๋จ(set of analysts)์ ๋ง๋ค์ด์ฃผ์ธ์.")]
)
# ๋ถ์ ๋ชฉ๋ก์ ์ํ์ ๊ธฐ๋ก
return {"analysts": analysts.analysts}
- create_analysts ํจ์: ์ฃผ์ด์ง ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ถ์๊ฐ๋ฅผ ์์ฑํ๋ ์์
์ ์ํํฉ๋๋ค.
llm.invoke
๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๊ตฌ์กฐํ๋ ์ถ๋ ฅ์ผ๋ก ๋ถ์๊ฐ ๋ชฉ๋ก์ ์์ฑํฉ๋๋ค.- structured_llm:
llm.with_structured_output(Perspectives)
์ ํตํด ๋ถ์๊ฐ์ ์ถ๋ ฅ ํ์์ ๊ตฌ์กฐํํ์ฌ ๋ฐํํฉ๋๋ค. (2 Perspective ํด๋์ค ์ฐธ๊ณ ) - system_message: ์ฃผ์ ์ ํผ๋๋ฐฑ์ ๊ธฐ๋ฐ์ผ๋ก ๋ถ์๊ฐ์ ํ๋ฅด์๋๋ฅผ ์์ฑํ๋ ์์คํ ๋ฉ์์ง์ ๋๋ค.
- invoke: ๊ตฌ์กฐํ๋ ์ถ๋ ฅ์ผ๋ก ๋ถ์๊ฐ ๋ฆฌ์คํธ๋ฅผ ์์ฑํ๋ฉฐ, ์ด ๋ฆฌ์คํธ๋ ์ํ์ ๊ธฐ๋ก๋ฉ๋๋ค.
- structured_llm:
6-1) Human Feedback ํจ์ ์ ์
1
2
3
def human_feedback(state: GenerateAnalystsState):
""" ์ค๋จ๋์ด์ผ ํ๋ no-op ๋
ธ๋ """
pass
- human_feedback: ์ฌ์ฉ์๊ฐ ์ ๊ณตํ๋ ํผ๋๋ฐฑ์ ๋ฐ์ํ ์ ์๋๋ก ํ๋ ํจ์์
๋๋ค. ํผ๋๋ฐฑ์ ์ฃผ๊ณ ๋ฐ๋ ๊ณผ์ ์ ์ํด ์์ ์ค๋จ ์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์์ฑ๋ ๋ถ์๊ฐ์ ๋ํ ํผ๋๋ฐฑ์ ๋ฐ์ํ๊ธฐ ์ํด
human_feedback
ํจ์์should_continue
ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค. - ํผ๋๋ฐฑ์ด ์์ ๊ฒฝ์ฐ,
create_analysts
ํจ์๊ฐ ๋ค์ ํธ์ถ๋์ด ํผ๋๋ฐฑ์ ๋ง๋ ์๋ก์ด ๋ถ์๊ฐ๊ฐ ์์ฑ๋ฉ๋๋ค. should_continue
ํจ์๋ ์๋๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
- ์์ฑ๋ ๋ถ์๊ฐ์ ๋ํ ํผ๋๋ฐฑ์ ๋ฐ์ํ๊ธฐ ์ํด
6-2) should_continue ํจ์ ์ ์
1
2
3
4
5
6
def should_continue(state: GenerateAnalystsState):
# ์ธ๊ฐ ํผ๋๋ฐฑ ํ์ธ
human_analyst_feedback = state.get('human_analyst_feedback', None)
if human_analyst_feedback:
return "create_analysts"
return END
- should_continue: ํผ๋๋ฐฑ์ด ์๋ค๋ฉด
create_analysts
๋ก ๋์๊ฐ ๋ถ์๊ฐ๋ฅผ ๋ค์ ์์ฑํ๋ฉฐ, ์๋ค๋ฉด ์ข ๋ฃํฉ๋๋ค.
(์ถ๊ฐ) ์๋๋ฐฉ์ ๋ถ์ฐ ์ค๋ช
- LangGraph์์ ๋ถ์๊ฐ๋ฅผ ์์ฑํ๋ ๊ณผ์ ์ค, human_feedback ๋ ธ๋๊ฐ ์ฌ์ฉ์์ ํผ๋๋ฐฑ์ ๋ฐ์ ์ ์๋ ์ค๋จ ์ง์ ์ ์ ๊ณตํฉ๋๋ค. ์ด ํจ์๊ฐ ํธ์ถ๋๋ ๋์ ์ฌ์ฉ์๋ ์์ฑ๋ ๋ถ์๊ฐ ๋ชฉ๋ก์ ๋ณด๊ณ , ํ์ํ ๊ฒฝ์ฐ ํผ๋๋ฐฑ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
- ์ดํ, ์ฌ์ฉ์์ ํผ๋๋ฐฑ์ด should_continue ํจ์๋ฅผ ํตํด ํ์ธ๋๊ณ , ํผ๋๋ฐฑ์ด ์๋ ๊ฒฝ์ฐ ๋ถ์๊ฐ ์์ฑ ๋จ๊ณ(create_analysts)๋ก ๋ค์ ๋์๊ฐ ํผ๋๋ฐฑ์ ๋ฐ์ํ ์๋ก์ด ๋ถ์๊ฐ๊ฐ ์์ฑ๋ฉ๋๋ค.
6-3) Graph ์ ์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from langgraph.graph import START, END, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
builder = StateGraph(GenerateAnalystsState)
builder.add_node("create_analysts", create_analysts)
builder.add_node("human_feedback", human_feedback)
builder.add_edge(START, "create_analysts")
builder.add_edge("create_analysts", "human_feedback")
builder.add_conditional_edges("human_feedback", should_continue, ["create_analysts", END])
# ์ปดํ์ผ
memory = MemorySaver()
graph = builder.compile(interrupt_before=['human_feedback'], checkpointer=memory)
# ๋ณด๊ธฐ
display(Image(graph.get_graph(xray=1).draw_mermaid_png()))
- ์ด์
StateGraph
ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋ ธ๋์ ์ฃ์ง๋ฅผ ์ถ๊ฐํ๊ณ ์ ์ฒด์ ์ธ ํ๋ฆ์ ์ค์ ํฉ๋๋ค.- builder: ๊ทธ๋ํ ๋น๋๋ฅผ ์ค์ ํ๊ณ ๊ฐ ๋ ธ๋์ ์ฃ์ง๋ฅผ ์ถ๊ฐํฉ๋๋ค.
- add_node:
create_analysts
๋ฐhuman_feedback
๋ ธ๋๋ฅผ ์ถ๊ฐํ์ฌ ๋ถ์๊ฐ ์์ฑ๊ณผ ํผ๋๋ฐฑ์ ์ฒ๋ฆฌํฉ๋๋ค. - add_edge: ์์๊ณผ ์ข ๋ฃ ๋ ธ๋๋ฅผ ์ฐ๊ฒฐํ๋ฉฐ, ํผ๋๋ฐฑ์ด ์์ ๊ฒฝ์ฐ ๋ถ์๊ฐ๋ฅผ ์์ฑํ๋ ๋ ธ๋๋ก ๋์๊ฐ๋๋ก ํฉ๋๋ค.
- MemorySaver: ์คํ ์ํ๋ฅผ ์ ์ฅํ์ฌ ํ์ํ ๋ ๋ณต์ํ ์ ์๋๋ก ํฉ๋๋ค.
- display: ๊ทธ๋ํ๋ฅผ ์๊ฐํํ์ฌ ๊ตฌ์กฐ๋ฅผ ํ์ธํฉ๋๋ค.
(์ค๊ฐ ์ ๊ฒ) ๊ทธ๋ผ ์ด์ ์ ๋ง๋ค์ด์ก๋ ํ์ธํด๋ด์ผ๊ฒ ์ฃ ?
- ์๋์ ๊ฐ์ด
LLMOps ์๋ฃจ์ ์ ์
์ด๋ผ๋ ์ฃผ์ ๋ก ์ ๋ฌธ๊ฐ๋ค์ 3๋ช ๊ณ ์ฉํด๋ณด๊ฒ ์ต๋๋ค.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # ์ ๋ ฅ max_analysts = 3 topic = "LLMOps ์๋ฃจ์ ์ ์" thread = {"configurable": {"thread_id": "1"}} # ์ฒซ ๋ฒ์งธ ์ค๋จ๊น์ง ๊ทธ๋ํ ์คํ for event in graph.stream({"topic":topic,"max_analysts":max_analysts,}, thread, stream_mode="values"): # ๊ฒํ analysts = event.get('analysts', '') if analysts: for analyst in analysts: print(f"์ด๋ฆ: {analyst.name}") print(f"์์: {analyst.affiliation}") print(f"์ญํ : {analyst.role}") print(f"์ค๋ช : {analyst.description}") print("-" * 50)
(๊ฒฐ๊ณผ ํ์ธ) ์ค์ ๋ก ์ ๋ฌธ๊ฐ๋ค์ด ์ ๋๋ก ์์ฑ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค
Lead Research Scientist
,Data Engineer
,AI Ethics Consultant
์ง์ฑ ์ผ๋ก ๊ณ์ ๋ถ๋ค์ ๋ง๋ค์ด๋๊ตฐ์.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ์ด๋ฆ: Dr. Emily Carter ์์: Tech Innovators Inc. ์ญํ : Lead Research Scientist ์ค๋ช : Dr. Carter focuses on the development and optimization of LLMOps solutions, with a particular interest in scalability and efficiency. -------------------------------------------------- ์ด๋ฆ: Michael Thompson ์์: AI Solutions Ltd. ์ญํ : Data Engineer ์ค๋ช : Michael specializes in data pipeline architecture and is passionate about integrating LLMOps solutions to streamline data processing workflows. -------------------------------------------------- ์ด๋ฆ: Sophia Martinez ์์: FutureTech Analytics ์ญํ : AI Ethics Consultant ์ค๋ช : Sophia is dedicated to ensuring that LLMOps solutions are developed and deployed ethically, with a focus on transparency and fairness. --------------------------------------------------
์๋ ์ฝ๋๋ฅผ ์คํํด๋ณด๋ฉด ํ์ฌ ('human_feedback',)
์ด์ ์์ ๋๊ธฐ ์ค์ด๋ผ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
1
2
3
# ์ํ ๊ฐ์ ธ์ค๊ณ ๋ค์ ๋
ธ๋ ํ์ธ
state = graph.get_state(thread)
state.next
๊ทธ๋ฆฌ๊ณ ๋์ ์๋์ ๊ฐ์ ์ฝ๋๋ก human_feedback
์ ์ค ์ ์์ต๋๋ค.
1
2
3
# ์ด์ human_feedback ๋
ธ๋์ธ ๊ฒ์ฒ๋ผ ์ํ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค
graph.update_state(thread, {"human_analyst_feedback":
"๊ธฐ์
๊ฐ์ ๊ด์ ์ ์ถ๊ฐํ๊ธฐ ์ํด gen ai ๋ค์ดํฐ๋ธ ์คํํธ์
์ CEO๋ฅผ ์ถ๊ฐํ์ธ์"}, as_node="human_feedback")
๊ทธ๋ผ human_feedback ๋ ธ๋๋ฅผ ํตํด ์ด์ ๋ค์ create_analyst ๋ ธ๋๋ก ๋์ด๊ฐ์ ์ถ๊ฐ์ ์ผ๋ก analyst๋ฅผ ๋ง๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ ๊ทธ๋ฆผ์ ๊ธ๋ก ์ ๋ฆฌํ์๋ฉด,
-
์ฒซ ๋ฒ์งธ
create_analysts
ํธ์ถ:- ์ฃผ์ ์ ๋ง๋ ๋ถ์๊ฐ 3๋ช
์ ์์ฑํ์ฌ
analysts
๋ฆฌ์คํธ์ ์ถ๊ฐํฉ๋๋ค. - ์ด ๋ฆฌ์คํธ๋ state์ ์ ์ฅ๋ฉ๋๋ค.
- ์ฃผ์ ์ ๋ง๋ ๋ถ์๊ฐ 3๋ช
์ ์์ฑํ์ฌ
-
human_feedback
๋ฅผ ํตํ ํผ๋๋ฐฑ ๋ฐ์:human_feedback
๋ ธ๋์์ ํผ๋๋ฐฑ์ ๋ฐ๊ณ ,create_analysts
๊ฐ ์ฌํธ์ถ๋ฉ๋๋ค.- ์ด๋,
state['analysts']
์ ๊ธฐ์กด 3๋ช ์ ๋ถ์๊ฐ๊ฐ ์ด๋ฏธ ํฌํจ๋์ด ์์ต๋๋ค. - ์๋ก์ด 3๋ช ์ด ์ถ๊ฐ๋๋ฉด์ ์ต์ข ์ ์ผ๋ก 6๋ช ์ ๋ถ์๊ฐ ๋ฆฌ์คํธ๊ฐ ์์ฑ๋ฉ๋๋ค.
ํ์ฌ ์ถ๋ ฅ์ ์ง๊ธ 6๋ช ์ผ๋ก ๋ฌ ์ด์ ๋ MemorySaver()๊ฐ checkpointer๋ก ์ฌ์ฉ๋์๊ธฐ ๋๋ฌธ์ ์ด์ ์คํ ์ํ๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ๋์๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก create_analysts๊ฐ ์ฌํธ์ถ๋๋ฉด์ ๊ธฐ์กด ๋ถ์๊ฐ ๋ชฉ๋ก(์ด์ ์ ํธ์ถ๋ 3๋ช ์ ๋ถ์๊ฐ)์ ์๋ก์ด ๋ถ์๊ฐ๋ค(์๋กญ๊ฒ ์ด๋ฒ์ ์ถ๊ฐ๋ 3๋ช ์ ์ถ๊ฐ ๋ถ์๊ฐ)์ด ๊ณ์ ์ถ๊ฐ๋ ๊ฒ์ ๋๋ค(์ด 6๋ช ์ ๋ถ์๊ฐ).
ํ์ฌ state ์ ๋ณด๋ง ๋ณด๋ ค๋ฉด ์๋์ ๊ฐ์ด ์ฝ๋๋ฅผ ์ถ๋ ฅํด๋ณผ ์ ์์ต๋๋ค:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
state = graph.get_state(thread)
# state.values
# ํ์ฌ ์ํ ์ถ๋ ฅ
print(f"์ฃผ์ : {state.values['topic']}")
print(f"์ต๋ ๋ถ์๊ฐ ์: {state.values['max_analysts']}")
print(f"ํผ๋๋ฐฑ: {state.values['human_analyst_feedback']}")
print('======================')
print(' PRINT INFO ')
print('======================')
# analysts ๋ชฉ๋ก ์ถ๋ ฅ
analysts = state.values['analysts']
if analysts:
for analyst in analysts:
print(f"\n์ด๋ฆ: {analyst.name}")
print(f"์์: {analyst.affiliation}")
print(f"์ญํ : {analyst.role}")
print(f"์ค๋ช
: {analyst.description}")
print("-" * 50)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
์ฃผ์ : LLMOps ์๋ฃจ์
์ ์
์ต๋ ๋ถ์๊ฐ ์: 3
ํผ๋๋ฐฑ: ๊ธฐ์
๊ฐ์ ๊ด์ ์ ์ถ๊ฐํ๊ธฐ ์ํด gen ai ๋ค์ดํฐ๋ธ ์คํํธ์
์ CEO๋ฅผ ์ถ๊ฐํ์ธ์
======================
PRINT INFO
======================
์ด๋ฆ: Dr. Emily Zhang
์์: Tech Innovators Inc.
์ญํ : AI Research Scientist
์ค๋ช
: Dr. Zhang focuses on the development and optimization of large language models (LLMs). She is particularly interested in the technical challenges and innovations in LLMOps.
--------------------------------------------------
์ด๋ฆ: Alex Kim
์์: GenAI Startups
์ญํ : CEO
์ค๋ช
: Alex is the CEO of a GenAI-native startup. He brings an entrepreneurial perspective to the table, focusing on the business applications and market potential of LLMOps solutions.
--------------------------------------------------
์ด๋ฆ: Priya Natarajan
์์: Data Security Solutions
์ญํ : Data Privacy Analyst
์ค๋ช
: Priya specializes in data privacy and security. Her interest lies in ensuring that LLMOps solutions comply with data protection regulations and maintain user privacy.
--------------------------------------------------
๋์ด์ human-in-the-loop
(ํด๋จผ-ํผ๋๋ฐฑ)์ด ํ์์๋ค๊ณ ํ๋จ์ด ๋๋ฉด ์๋์ ๊ฐ์ด None
์ ํผ๋๋ฐฑ์ผ๋ก ์ ๊ณตํ์ฌ ๊ทธ๋ํ๊ฐ ์ข
๋ฃ๋ ์ ์๋๋ก ํฉ๋๋ค.
1
2
3
4
# ๋ง์กฑํ๋ค๋ฉด ํผ๋๋ฐฑ ์์์ ์ ๊ณต
further_feedack = None
graph.update_state(thread, {"human_analyst_feedback":
further_feedack}, as_node="human_feedback")
์ด๋ ๊ฒ ๊ทธ๋ํ๋ฅผ ๋๊น์ง ์คํ์ด ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. (์ถ๋ ฅ ๊ฒฐ๊ณผ: ()
)
1
2
3
4
5
6
7
8
9
10
# ๊ทธ๋ํ ์คํ์ ๋๊น์ง ๊ณ์ํฉ๋๋ค
for event in graph.stream(None, thread, stream_mode="updates"):
print("--๋
ธ๋--")
node_name = next(iter(event.keys()))
print(node_name)
final_state = graph.get_state(thread)
analysts = final_state.values.get('analysts')
final_state.next
์ต์ข ์ ์ Analyst๋ ์์์ 1๋ฒ์ ํผ๋๋ฐฑ์ ๊ฑฐ์น ๋ถ์๊ฐ๋ค์์ ํ์ธํ ์ ์์ต๋๋ค:
โ๏ธ Conduct Interview: ์ง๋ฌธ ์์ฑ ๋ฐ ์ธํฐ๋ทฐ ์ํ
Conduct Interview ๋จ๊ณ
์์๋ ์์ฑ๋ ๋ถ์๊ฐ๊ฐ ์ ๋ฌธ๊ฐ AI์ ๋ํํ๋ฉฐ ์ฃผ์ ์ ๋ํ ํต์ฐฐ์ ์ป๊ธฐ ์ํด ์ง๋ฌธ์ ๋์ง๊ณ ๋ต๋ณ์ ์์งํ๋ ๊ณผ์ ์ ์งํํฉ๋๋ค.
- ์ด ๊ณผ์ ์ ๋ถ์๊ฐ์ ์ญํ ์ ๋ฐ๋ผ ์ค๊ณ๋ ์ง๋ฌธ์ ์์ฑํ๊ณ , ๋ค์ํ ์์ค์์ ์ ๋ณด๋ฅผ ๋ณ๋ ฌ๋ก ์์งํ ๋ค ๋ต๋ณ์ ์์ฑํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
1
2
3
import operator
from typing import Annotated
from langgraph.graph import MessagesState
-
InterviewState
ํด๋์ค:-
MessagesState
๋ฅผ ์์๋ฐ์ ์ธํฐ๋ทฐ ๋ํ์์ ํ์ํ ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.1 2 3 4 5 6
class InterviewState(MessagesState): max_num_turns: int # ๋ํ ํด ์ context: Annotated[list, operator.add] # ์์ค ๋ฌธ์ analyst: Analyst # ์ง๋ฌธํ๋ ๋ถ์๊ฐ interview: str # ์ธํฐ๋ทฐ ๊ธฐ๋ก sections: list # Send() API๋ฅผ ์ํด ์ธ๋ถ ์ํ์์ ๋ณต์ ํ๋ ์ต์ข ํค
-
์ด ํด๋์ค๋ ๋ถ์๊ฐ(Analyst)์ ์ธํฐ๋ทฐ ์งํ ๊ณผ์ ์์ ์ฌ์ฉ๋๋ ์ฌ๋ฌ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค.
max_num_turns
: ์ธํฐ๋ทฐ์ ์ต๋ ํด ์๋ฅผ ์ค์ ํ์ฌ, ์ธํฐ๋ทฐ๊ฐ ๋ช ์ฐจ๋ก์ ์ง๋ฌธ-์๋ต ์ ์ดํ ์ข ๋ฃ๋ ์ง ๊ฒฐ์ ํฉ๋๋ค.context
: Annotated ํ์ ์ ์ฌ์ฉํ์ฌ ์์ค ๋ฌธ์์ ๋ชฉ๋ก์ ์ค์ ํ๊ณ ,operator.add
๋ฅผ ์ ์ฉํ์ฌ ๊ฐ ์ธํฐ๋ทฐ์์ ์์ง๋ ์์ค ๋ฌธ์๋ฅผ ์ ์ง์ ์ผ๋ก ๋์ ํฉ๋๋ค.analyst
: ํ์ฌ ์ง๋ฌธ์ ํ๋ ๋ถ์๊ฐ๋ฅผ ๋ํ๋ด๋ฉฐ,Analyst
๊ฐ์ฒด๋ก ๊ด๋ฆฌ๋ฉ๋๋ค.interview
: ์ธํฐ๋ทฐ ๋ด์ฉ์ ๋ฌธ์์ด๋ก ์ ์ฅํ์ฌ, ๋ํ ์ ์ฒด์ ๊ธฐ๋ก์ ๋ณด์กดํฉ๋๋ค.sections
:Send()
API์์ ์ธ๋ถ ์ํ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํด ์ฌ์ฉ๋๋ ํค์ ๋๋ค.
-
-
SearchQuery
ํด๋์ค:-
search_query
์์ฑ๋ง์ ๊ฐ์ง๋ ๊ฐ๋จํ ๋ฐ์ดํฐ ๋ชจ๋ธ๋ก, ๊ฒ์ ์ฟผ๋ฆฌ๋ฅผ ๋ฌธ์์ด๋ก ์ ์ฅํฉ๋๋ค.1 2
class SearchQuery(BaseModel): search_query: str = Field(None, description="๊ฒ์์ ์ํ ์ฟผ๋ฆฌ.")
-
search_query
๋Field
ํจ์์ ํจ๊ป ์ค์ ๋์ด, ๊ฒ์์ด์ ์ฉ๋๋ฅผ ๋ช ์์ ์ผ๋ก ์ค๋ช ํฉ๋๋ค.- Field ํจ์๋ฅผ ํตํด ๊ฒ์ ์ฟผ๋ฆฌ์ ์ฉ๋๋ฅผ ์ค๋ช
ํ์ฌ
search_query
๋ฅผ ๋ช ํํ๊ฒ ์ง์ ํฉ๋๋ค. - ๊ฒ์ ๊ณผ์ ์์ ๋ถ์๊ฐ์ ์ง๋ฌธ์ ๊ธฐ๋ฐ์ผ๋ก ๊ฒ์์ด๋ฅผ ์์ฑํ์ฌ ์ ์ฅํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- Field ํจ์๋ฅผ ํตํด ๊ฒ์ ์ฟผ๋ฆฌ์ ์ฉ๋๋ฅผ ์ค๋ช
ํ์ฌ
-
-
question_instructions
์ฟผ๋ฆฌ:
question_instructions
๋ ์ธํฐ๋ทฐ๋ฅผ ์งํํ๋ ๋ถ์๊ฐ๊ฐ ์ ๋ฌธ๊ฐ์๊ฒ ํฅ๋ฏธ๋กญ๊ณ ๊ตฌ์ฒด์ ์ธ ์ง๋ฌธ์ ๋์ง๋๋ก ๋๋ ๊ฐ์ด๋๋ผ์ธ์ ๋๋ค.- ์ด ํ๋กฌํํธ๋ ๋ถ์๊ฐ๊ฐ ์ธํฐ๋ทฐ์ ์ด์ ๊ณผ ๋ชฉํ์ ๋ง์ถฐ ๋ํ๋ฅผ ์ด๋์ด๋๊ฐ๋๋ก ์ ๋ํ๋ฉฐ, ์ ์ฒด ์ธํฐ๋ทฐ ๋ํ์ ์ง๊ณผ ๋ฐฉํฅ์ฑ์ ์ ์งํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ๋ถ์๊ฐ๊ฐ ์ ๋ฌธ๊ฐ์๊ฒ ํฅ๋ฏธ๋กญ๊ณ ๊ตฌ์ฒด์ ์ธ ์ง๋ฌธ์ ๋์ง๋๋ก ๋๋ ๊ฐ์ด๋๋ผ์ธ
question_instructions = """
๋น์ ์ ํน์ ์ฃผ์ ์ ๋ํด ์ ๋ฌธ๊ฐ๋ฅผ ์ธํฐ๋ทฐํ๋ ๋ถ์๊ฐ์
๋๋ค.
๋น์ ์ ๋ชฉํ๋ ์ฃผ์ ์ ๊ด๋ จ๋ ํฅ๋ฏธ๋กญ๊ณ ๊ตฌ์ฒด์ ์ธ ํต์ฐฐ์ ์ป๋ ๊ฒ์
๋๋ค.
1. Interesting: ์ฌ๋๋ค์ด ๋๋๊ฑฐ๋ ๋ป๋ฐ์ด๋ผ๊ณ ์๊ฐํ ํต์ฐฐ.
2. Specific: ์ผ๋ฐ๋ก ์ ํผํ๊ณ ์ ๋ฌธ๊ฐ์ ๊ตฌ์ฒด์ ์ธ ์์๋ฅผ ํฌํจํ๋ ํต์ฐฐ.
๋ค์์ ๋น์ ์ topic of focus์ set of goals์
๋๋ค: {goals}
๋น์ ์ ํ๋ฅด์๋์ ๋ง๋ ์ด๋ฆ์ผ๋ก ์์ ์ ์๊ฐํ๊ณ ์ง๋ฌธ์ ์์ํ์ธ์.
์ฃผ์ ์ ๋ํ ์ดํด๋ฅผ ์ฌํํ๊ณ ๊ตฌ์ฒดํํ๊ธฐ ์ํด ๊ณ์ํด์ ์ง๋ฌธํ์ธ์.
์ดํด๊ฐ ์ถฉ๋ถํ๋ค๊ณ ํ๋จ๋๋ฉด "๋์ ์ฃผ์
์ ์ ๋ง ๊ฐ์ฌํฉ๋๋ค!"๋ผ๊ณ ๋งํ๋ฉฐ ์ธํฐ๋ทฐ๋ฅผ ๋ง๋ฌด๋ฆฌํ์ธ์.
์ ๊ณต๋ ํ๋ฅด์๋์ ๋ชฉํ๋ฅผ ๋ฐ์ํ๋ฉฐ ์๋ต ์ ์ฒด์ ๊ฑธ์ณ ์บ๋ฆญํฐ๋ฅผ ์ ์งํ์ธ์.
"""
1) ์ง๋ฌธ ์์ฑ (Generate Question)
์ด ๋จ๊ณ๋ ๋ถ์๊ฐ๊ฐ ์ ๋ฌธ๊ฐ์๊ฒ ์ฃผ์ ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์ฒด์ ์ด๊ณ ํฅ๋ฏธ๋ก์ด ์ง๋ฌธ์ ๋์ง ์ ์๋๋ก ๋์ต๋๋ค.
- ์ง๋ฌธ ์์ฑ์
generate_question
ํจ์์์ ์ด๋ฃจ์ด์ง๋ฉฐ, ์ด ํจ์๋ ๋ถ์๊ฐ์ ํ๋ฅด์๋์ ๋ชฉํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ์ ํ ์ง๋ฌธ์ ๋ง๋ญ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
def generate_question(state: InterviewState):
# ๋ถ์๊ฐ์ ์ด์ ๋ฉ์์ง ๊ฐ์ ธ์ค๊ธฐ
analyst = state["analyst"]
messages = state["messages"]
# ๋ถ์๊ฐ์ ๋ชฉํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ง๋ฌธ ์์ฑ
system_message = question_instructions.format(goals=analyst.persona)
question = llm.invoke([SystemMessage(content=system_message)] + messages)
# ์์ฑ๋ ์ง๋ฌธ์ ์ํ์ ๊ธฐ๋กํ์ฌ ๋ค์ ํ๋ก์ธ์ค๋ก ์ ๋ฌ
return {"messages": [question]}
- analyst: ๋ถ์๊ฐ์ ํ๋ฅด์๋ ๋ฐ ๋ชฉํ๋ฅผ ํฌํจํ ์ธ์คํด์ค๋ก, ์ฐ๊ตฌ ์ฃผ์ ์ ๊ด๋ จ๋ ๊ตฌ์ฒด์ ์ธ ์ง๋ฌธ์ ์์ฑํ๋ ๋ฐ ํ์ํ ์ ๋ณด๊ฐ ๋ด๊น๋๋ค.
-
question_instructions: ๋ถ์๊ฐ๊ฐ ์ง๋ฌธ์ ํ ๋ ๋ฐ๋ผ์ผ ํ ์ง์นจ์ผ๋ก, ์ง๋ฌธ์ด ํฅ๋ฏธ๋กญ๊ณ ๊ตฌ์ฒด์ ์ธ ํต์ฐฐ์ ์ ๋ํ๋๋ก ์ค๊ณ๋์์ต๋๋ค.
- (์ฐธ๊ณ )
question_instructions
์ ๋ฐ๋ก ์3. question_instructions ์ฟผ๋ฆฌ
์์ ์ ์๋์ด ์์ต๋๋ค.
- (์ฐธ๊ณ )
- question =
llm.invoke
๋ฉ์๋๋ ์์คํ ๋ฉ์์ง์ ์ด์ ๋ํ ๋ฉ์์ง๋ฅผ ์ ๋ ฅ์ผ๋ก ๋ฐ์ ์๋ก์ด ์ง๋ฌธ์ ์์ฑํฉ๋๋ค.- llm.invoke์ ๋ค์ด๊ฐ๋ ์ธ์๋
[SystemMessage(content=system_message)] + messages
๋ก, ์์์ ์ ์ํ question_instructions๊ณผ messages์ ๋๋ค. - ์ฐธ๊ณ ๋ก, messages๋ ํ์ฌ ์งํ ์ค์ธ ์ธํฐ๋ทฐ ๋ํ์ ์ ์ฒด ๋ฉ์์ง ๊ธฐ๋ก์ ๋ํ๋ ๋๋ค. ์ด๋ ๋ถ์๊ฐ์ ์ ๋ฌธ๊ฐ ๊ฐ์ ์ด์ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ด ์์๋๋ก ํฌํจ๋ ์ํ์ ๋๋ค.
- llm.invoke์ ๋ค์ด๊ฐ๋ ์ธ์๋
2) ๋ณ๋ ฌ ๋ต๋ณ ์์ง (Parallelized Answer Collection)
LangGraph์ ๋ณ๋ ฌํ ๊ธฐ๋ฅ์ ํ์ฉํด ์ ๋ฌธ๊ฐ๊ฐ ์ง๋ฌธ์ ๋ต๋ณํ๊ธฐ ์ํด ๋ค์ํ ์์ค์์ ์ ๋ณด๋ฅผ ์์งํ๋ ๋ฐฉ์์ผ๋ก ์งํ๋ฉ๋๋ค.
- ๋ณธ ์์ ์์๋
tavily_search
์search_wikipedia
ํจ์๋ฅผ ํตํด ๊ฐ ์์ค๋ฅผ ๊ฒ์ํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํฉ๋๋ค.
1
2
3
4
5
6
# ์น ๊ฒ์ ๋๊ตฌ
from langchain_community.tools.tavily_search import TavilySearchResults
tavily_search = TavilySearchResults(max_results=3)
# ์ํคํผ๋์ ๊ฒ์ ๋๊ตฌ
from langchain_community.document_loaders import WikipediaLoader
search_instructions
-
search_instructions
๋ ๋ํ ๋งฅ๋ฝ์ ๋ฐํ์ผ๋ก ์ต์ ์ ๊ฒ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐ ํ์ํ ์ง์นจ์ ์ ๊ณตํฉ๋๋ค.- ์ด ํ๋กฌํํธ๋ ํน์ ํ ์ง๋ฌธ์ ๋ํ ์ ๋ณด๋ฅผ ๊ฒ์ํ ๋ ๊ตฌ์ฒด์ ์ด๊ณ ์ ์ฉํ ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ ์ ์๋๋ก ์ ๋ํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
# ๊ฒ์ ์ฟผ๋ฆฌ ์์ฑ
search_instructions = SystemMessage(content=f"""
๋ถ์๊ฐ์ ์ ๋ฌธ๊ฐ ์ฌ์ด์ ๋ํ๊ฐ ์ฃผ์ด์ง ๊ฒ์
๋๋ค.
๋น์ ์ ๋ชฉํ๋ ๋ํ์ ๊ด๋ จ๋ ๊ฒ์ ๋ฐ/๋๋ ์น ๊ฒ์์ ์ฌ์ฉํ ์ ๊ตฌ์กฐํ๋ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ฒ์
๋๋ค.
๋จผ์ ์ ์ฒด ๋ํ๋ฅผ ๋ถ์ํ์ธ์.
๋ถ์๊ฐ๊ฐ ๋ง์ง๋ง์ผ๋ก ์ ๊ธฐํ ์ง๋ฌธ์ ํนํ ์ฃผ์๋ฅผ ๊ธฐ์ธ์ด์ธ์.
์ด ๋ง์ง๋ง ์ง๋ฌธ์ ์ ๊ตฌ์กฐํ๋ ์น ๊ฒ์ ์ฟผ๋ฆฌ๋ก ๋ณํํ์ธ์
""")
์น ๊ฒ์ (search_web
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def search_web(state: InterviewState):
""" ์น ๊ฒ์์์ ๋ฌธ์ ๊ฒ์ """
# ๊ฒ์ ์ฟผ๋ฆฌ ์์ฑ
structured_llm = llm.with_structured_output(SearchQuery)
search_query = structured_llm.invoke([search_instructions] + state["messages"])
# Tavily ๊ฒ์ API๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ด
search_docs = tavily_search.invoke(search_query.search_query)
# ํ์ ์ง์ ๋ ๊ฒฐ๊ณผ ๋ฐํ
formatted_search_docs = "\n\n---\n\n".join(
[f'<Document href="{doc["url"]}"/>\n{doc["content"]}\n</Document>' for doc in search_docs]
)
return {"context": [formatted_search_docs]}
- search_query: ๋ถ์๊ฐ์ ์ง๋ฌธ์ ๋ฐํ์ผ๋ก ์น ๊ฒ์์ ์ ํฉํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค.
-
tavily_search: Tavily API๋ฅผ ์ฌ์ฉํ์ฌ ์น ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- ์ด ๊ฒฐ๊ณผ๋ ์ถํ ๋ต๋ณ์ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- formatted_search_docs: ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ HTML ํ์์ผ๋ก ๋ณํํ์ฌ ๋ณด๊ณ ์ ์์ฑ ์ ์ถ์ฒ๋ก ํ์ฉํ ์ ์๋๋ก ๊ตฌ์ฑํฉ๋๋ค.
์ํคํผ๋์ ๊ฒ์ (search_wikipedia
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def search_wikipedia(state: InterviewState):
""" ์ํคํผ๋์์์ ๋ฌธ์ ๊ฒ์ """
# ๊ฒ์ ์ฟผ๋ฆฌ ์์ฑ
structured_llm = llm.with_structured_output(SearchQuery)
search_query = structured_llm.invoke([search_instructions] + state["messages"])
# WikipediaLoader๋ก ๊ฒ์ ๊ฒฐ๊ณผ ๊ฐ์ ธ์ค๊ธฐ
search_docs = WikipediaLoader(query=search_query.search_query, load_max_docs=2).load()
# ํ์ ์ง์ ๋ ๊ฒฐ๊ณผ ๋ฐํ
formatted_search_docs = "\n\n---\n\n".join(
[f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>' for doc in search_docs]
)
return {"context": [formatted_search_docs]}
- WikipediaLoader: ์ํคํผ๋์์์ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ค๋ ์ญํ ์ ํ๋ฉฐ, ๊ฒ์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด ๊ด๋ จ ๋ฌธ์๋ฅผ ๋ฐํํฉ๋๋ค.
- formatted_search_docs: ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ HTML ํ์์ผ๋ก ๋ณํํ์ฌ ๋ณด๊ณ ์ ์์ฑ ์ ์ถ์ฒ๋ก ํ์ฉํ ์ ์๋๋ก ๊ตฌ์ฑํฉ๋๋ค.
answer_instructions
-
answer_instructions
๋ ๋ถ์๊ฐ๊ฐ ์ ์ํ ์ง๋ฌธ์ ๋ํด ์ ๋ฌธ๊ฐ๊ฐ ๊ด๋ จ ์ ๋ณด๋ง์ ์ฌ์ฉํด ์ ํํ๊ณ ๊ตฌ์ฒด์ ์ธ ๋ต๋ณ์ ์์ฑํ ์ ์๋๋ก ๋์ต๋๋ค.- ์ด ๊ฐ์ด๋๋ผ์ธ์ ๋ต๋ณ์ ์ผ๊ด์ฑ๊ณผ ์ ํ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ์ค๊ณ๋์์ต๋๋ค.
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
answer_instructions = """
๋น์ ์ ๋ถ์๊ฐ์ ์ํด ์ธํฐ๋ทฐ๋ฅผ ๋ฐ๊ณ ์๋ ์ ๋ฌธ๊ฐ์
๋๋ค.
๋ค์์ ๋ถ์๊ฐ์ ๊ด์ฌ ์์ญ์
๋๋ค:
{goals}.
๋น์ ์ ๋ชฉํ๋ ์ธํฐ๋ทฐ์ด๊ฐ ์ ๊ธฐํ ์ง๋ฌธ์ ๋ตํ๋ ๊ฒ์
๋๋ค.
์ง๋ฌธ์ ๋ตํ๊ธฐ ์ํด ์ด ์ปจํ
์คํธ๋ฅผ ์ฌ์ฉํ์ธ์:
{context}.
์ง๋ฌธ์ ๋๋ตํ ๋ ์๋ ์ง์นจ์ ๋ฐ๋ฅด์ธ์:
1. ์ ๊ณต๋ ์ปจํ
์คํธ์ ์ ๋ณด๋ง ์ฌ์ฉํ์ธ์.
2. ์ปจํ
์คํธ์ ๋ช
์์ ์ผ๋ก ์ธ๊ธ๋์ง ์์ ์ธ๋ถ ์ ๋ณด๋ฅผ ๋์
ํ๊ฑฐ๋ ๊ฐ์ ํ์ง ๋ง์ธ์.
3. ์ปจํ
์คํธ์๋ ๊ฐ ๊ฐ๋ณ ๋ฌธ์ ์๋จ์ ์์ค๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
4. ๊ด๋ จ ์ง์ ์์ ์ด๋ฌํ ์์ค๋ฅผ ๋ต๋ณ์ ํฌํจํ์ธ์. ์๋ฅผ ๋ค์ด, ์์ค #1์ ๊ฒฝ์ฐ [1]์ ์ฌ์ฉํ์ธ์.
5. ๋ต๋ณ ํ๋จ์ ์์ค๋ฅผ ์์๋๋ก ๋์ดํ์ธ์. [1] ์์ค 1, [2] ์์ค 2 ๋ฑ
6. ์์ค๊ฐ '<Document source="assistant/docs/llama3_1.pdf" page="7"/>'์ธ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ๋์ดํ์ธ์:
[1] assistant/docs/llama3_1.pdf, 7ํ์ด์ง
์ธ์ฉ ์ ๊ดํธ์ Document source ์ ๋ฌธ์ ์๋ตํ์ธ์.
"""
- ๋ต๋ณ ์์ฑ (Generate Answer)
๋ถ์๊ฐ์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ์น ๋ฐ ์ํคํผ๋์ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํด ์์ฑ๋ฉ๋๋ค.
generate_answer
ํจ์๋ ๋ฌธ๋งฅ(context)๊ณผ ์ง๋ฌธ์ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ฌธ๊ฐ์ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
def generate_answer(state: InterviewState):
# ๋ถ์๊ฐ์ ์ปจํ
์คํธ ๊ฐ์ ธ์ค๊ธฐ
analyst = state["analyst"]
context = state["context"]
# ์ง๋ฌธ์ ๋ฐํ์ผ๋ก ๋ต๋ณ ์์ฑ
system_message = answer_instructions.format(goals=analyst.persona, context=context)
answer = llm.invoke([SystemMessage(content=system_message)] + state["messages"])
answer.name = "expert" # ์ ๋ฌธ๊ฐ์ ์ด๋ฆ ์ง์
return {"messages": [answer]}
- context: ์ด์ ์ ์์ง๋ ๊ฒ์ ๊ฒฐ๊ณผ๋ค์ด ํฌํจ๋ ๋ฌธ๋งฅ ๋ฐ์ดํฐ๋ก, ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
-
answer_instructions: ์ ๋ฌธ๊ฐ๊ฐ ์ปจํ ์คํธ์ ๋ถ์๊ฐ์ ๋ชฉํ๋ฅผ ๋ฐ์ํ์ฌ ์ ํํ๊ณ ๊ด๋ จ ์๋ ๋ต๋ณ์ ์์ฑํ ์ ์๋๋ก ์ง์นจ์ ์ ๊ณตํฉ๋๋ค.
- (์ฐธ๊ณ )
answer_instructions
์ ์์์ ์ ์๋์ด ์์ต๋๋ค.
- (์ฐธ๊ณ )
- answer.name: ๋ต๋ณ์ ์ ๋ฌธ๊ฐ๋ก๋ถํฐ ์จ ๋ฉ์์ง๋ก ์ง์ ํ์ฌ ์ธํฐ๋ทฐ์ ์ผ๊ด์ฑ์ ์ ์งํฉ๋๋ค.
- ์ธํฐ๋ทฐ ์ ์ฅ ๋ฐ ์์ฝ (Save Interview)
์ธํฐ๋ทฐ๊ฐ ๋๋๋ฉด ๋ชจ๋ ๋ฉ์์ง๋ฅผ ํ๋์ ํ
์คํธ๋ก ๋ณํํ์ฌ ์ ์ฅํ๋ save_interview
ํจ์๊ฐ ํธ์ถ๋ฉ๋๋ค. ์ด ์ธํฐ๋ทฐ ๊ธฐ๋ก์ ์ดํ ์ต์ข
๋ณด๊ณ ์ ์์ฑ์ ์ฌ์ฉ๋ฉ๋๋ค.
1
2
3
4
5
6
7
8
9
def save_interview(state: InterviewState):
# ๋ฉ์์ง ๊ฐ์ ธ์ค๊ธฐ
messages = state["messages"]
# ์ธํฐ๋ทฐ ๋ด์ฉ์ ํ๋์ ๋ฌธ์์ด๋ก ๋ณํ
interview = get_buffer_string(messages)
# ์ธํฐ๋ทฐ ๋ด์ฉ์ ์ํ์ ์ ์ฅ
return {"interview": interview}
- get_buffer_string: ์ธํฐ๋ทฐ ๋ํ๋ฅผ ํ๋์ ํ ์คํธ ํ์์ผ๋ก ๋ณํํ์ฌ ์ดํ ๋จ๊ณ์์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
๐ค get_buffer_string
get_buffer_string
ํจ์๋ LangChain ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ผ๋ถ์ ๋๋ค. ๊ตฌ์ฒด์ ์ผ๋ก, ์ด ํจ์๋ langchain_core.messages.utils ๋ชจ๋์ ์ ์๋์ด ์์ต๋๋ค.- ๋ชฉ์ : ์ผ๋ จ์ ๋ฉ์์ง(Messages)๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํ๊ณ ํ๋์ ๋ฌธ์์ด๋ก ์ฐ๊ฒฐํฉ๋๋ค
- ์์ ๋ฅผ ํตํด ์ด๋ป๊ฒ ์๋ํ๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
1 2 3 4 5 6 7 from langchain_core import AIMessage, HumanMessage messages = [ HumanMessage(content="Hi, how are you?"), AIMessage(content="Good, how are you?"), ] result = get_buffer_string(messages) # ๊ฒฐ๊ณผ: "Human: Hi, how are you?\nAI: Good, how are you?"- ์์ ์์ ์ ๊ฐ์ด
get_buffer_string
ํจ์๋ ์ฃผ์ด์ง ๋ฉ์์ง ๋ฆฌ์คํธ๋ฅผ ํ๋์ ๋ฌธ์์ด๋ก ๋ณํํ๋ฉฐ, ๊ฐ ๋ฉ์์ง์ ๋ํดprefix
์content
๋ฅผ ๊ฒฐํฉํ์ฌ ์ถ๋ ฅํฉ๋๋ค.
- ๋ผ์ฐํ ํจ์ (Route Messages)
์ง๋ฌธ๊ณผ ๋ต๋ณ์ ์ฃผ๊ณ ๋ฐ๋ ํ๋ก์ธ์ค๋ฅผ ์กฐ์จํ๊ธฐ ์ํด route_messages
ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- ์ด ํจ์๋ ์ ๋ฌธ๊ฐ๊ฐ ํน์ ํ ์๋ต ์์ ๋๋ฌํ๊ฑฐ๋ ๋ถ์๊ฐ๊ฐ ๋ํ๋ฅผ ๋ง์น๊ณ ์ ํ๋ ์ ํธ(โ๋์ ์ฃผ์ ์ ์ ๋ง ๊ฐ์ฌํฉ๋๋คโ)๋ฅผ ๋ณด๋์ ๋ ์ธํฐ๋ทฐ๋ฅผ ์ข ๋ฃํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def route_messages(state: InterviewState, name: str = "expert"):
# ๋ฉ์์ง ๊ฐ์ ธ์ค๊ธฐ
messages = state["messages"]
max_num_turns = state.get('max_num_turns', 2)
# ์ ๋ฌธ๊ฐ ์๋ต ์ ํ์ธ
num_responses = len(
[m for m in messages if isinstance(m, AIMessage) and m.name == name]
)
# ์๋ต์ด ์ต๋ ํด ์๋ฅผ ์ด๊ณผํ๊ฑฐ๋ ๊ฐ์ฌ ๋ฉ์์ง๊ฐ ์์ ๊ฒฝ์ฐ ์ธํฐ๋ทฐ ์ข
๋ฃ
if num_responses >= max_num_turns:
return 'save_interview'
last_question = messages[-2] # ๋ง์ง๋ง ์ง๋ฌธ ํ์ธ
if "๋์ ์ฃผ์
์ ์ ๋ง ๊ฐ์ฌํฉ๋๋ค" in last_question.content:
return 'save_interview'
return "ask_question"
-
์ข ๋ฃ ์กฐ๊ฑด
:- num_responses: ์ ๋ฌธ๊ฐ์ ์๋ต ํ์๋ฅผ ํ์ธํ์ฌ ์ต๋ ์๋ต ์์ ๋๋ฌํ๋ฉด ์ธํฐ๋ทฐ๋ฅผ ์ข ๋ฃํ๋๋ก ์ค์ ํฉ๋๋ค.
- last_question: ๋ถ์๊ฐ์ ๋ง์ง๋ง ์ง๋ฌธ์ ํ์ธํ์ฌ ๋ํ ์ข ๋ฃ๋ฅผ ์์ฒญํ๋ ๋ฉ์์ง๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ ์ธํฐ๋ทฐ๋ฅผ ์ข ๋ฃํฉ๋๋ค.
- write_section ํจ์
section_writer_instructions
-
section_writer_instructions
๋ ์ ๋ฌธ ๊ธฐ์ ์๊ฐ๊ฐ ๋ณด๊ณ ์ ์น์ ์ ์์ฑํ๋ ๋ฐ ํ์ํ ์ธ๋ถ ์ง์นจ์ ์ ๊ณตํ์ฌ, ์ธํฐ๋ทฐ์์ ์์งํ ํต์ฐฐ์ ๋ช ํํ๊ณ ์ผ๊ด๋ ํ์์ผ๋ก ์ ๋ฆฌํ ์ ์๋๋ก ๋์ต๋๋ค.- ์ด ๊ณผ์ ์
Conduct Interview
๋จ๊ณ์์ ์์ง๋ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก, ๋ณด๊ณ ์์ ๊ฐ ์น์ ์ ์์ฑํ๋ ๋ฐ ์ค์ ์ ๋ก๋๋ค. ์ฃผ์ ์ง์นจ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ด ๊ณผ์ ์
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
section_writer_instructions = """
๋น์ ์ ์ ๋ฌธ ๊ธฐ์ ์๊ฐ์
๋๋ค.
๋น์ ์ ์๋ฌด๋ ์์ค ๋ฌธ์ ์ธํธ๋ฅผ ๋ฐํ์ผ๋ก ๋ณด๊ณ ์์ ์งง๊ณ ์ดํดํ๊ธฐ ์ฌ์ด ์น์
์ ์์ฑํ๋ ๊ฒ์
๋๋ค.
1. ์์ค ๋ฌธ์์ ๋ด์ฉ ๋ถ์:
- ๊ฐ ์์ค ๋ฌธ์์ ์ด๋ฆ์ <Document ํ๊ทธ์ ํจ๊ป ๋ฌธ์ ์์ ๋ถ๋ถ์ ์์ต๋๋ค.
2. ๋งํฌ๋ค์ด ํ์์ ์ฌ์ฉํ์ฌ ๋ณด๊ณ ์ ๊ตฌ์กฐ ์์ฑ:
- ์น์
์ ๋ชฉ์๋ ## ์ฌ์ฉ
- ํ์ ์น์
ํค๋์๋ ### ์ฌ์ฉ
3. ๋ค์ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ผ ๋ณด๊ณ ์ ์์ฑ:
a. ์ ๋ชฉ (## ํค๋)
b. ์์ฝ (### ํค๋)
c. ์ถ์ฒ (### ํค๋)
4. ๋ถ์๊ฐ์ ๊ด์ฌ ์์ญ์ ๋ฐํ์ผ๋ก ํฅ๋ฏธ๋ก์ด ์ ๋ชฉ ๋ง๋ค๊ธฐ:
{focus}
5. ์์ฝ ์น์
:
- ๋ถ์๊ฐ์ ๊ด์ฌ ์์ญ๊ณผ ๊ด๋ จ๋ ์ผ๋ฐ์ ์ธ ๋ฐฐ๊ฒฝ/๋งฅ๋ฝ์ผ๋ก ์์ฝ ์์
- ์ธํฐ๋ทฐ์์ ์ป์ ํต์ฐฐ ์ค ์๋กญ๊ฑฐ๋ ํฅ๋ฏธ๋กญ๊ฑฐ๋ ๋๋ผ์ด ์ ๊ฐ์กฐ
- ์ฌ์ฉํ ์์ค ๋ฌธ์์ ๋ฒํธ ๋ชฉ๋ก ์์ฑ
- ์ธํฐ๋ทฐ์ด๋ ์ ๋ฌธ๊ฐ์ ์ด๋ฆ ์ธ๊ธํ์ง ์๊ธฐ
- ์ต๋ 400๋จ์ด๋ฅผ ๋ชฉํ๋ก ํจ
- ์์ค ๋ฌธ์์ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ๋ณด๊ณ ์์ ๋ฒํธ๊ฐ ๋งค๊ฒจ์ง ์ถ์ฒ ์ฌ์ฉ (์: [1], [2])
6. ์ถ์ฒ ์น์
:
- ๋ณด๊ณ ์์ ์ฌ์ฉ๋ ๋ชจ๋ ์ถ์ฒ ํฌํจ
- ๊ด๋ จ ์น์ฌ์ดํธ๋ ํน์ ๋ฌธ์ ๊ฒฝ๋ก์ ์ ์ฒด ๋งํฌ ์ ๊ณต
- ๊ฐ ์ถ์ฒ๋ฅผ ์ค๋ฐ๊ฟ์ผ๋ก ๊ตฌ๋ถ. ๊ฐ ์ค ๋์ ๋ ๊ฐ์ ๊ณต๋ฐฑ์ ์ฌ์ฉํ์ฌ ๋งํฌ๋ค์ด์์ ์ค๋ฐ๊ฟ ์์ฑ.
- ๋ค์๊ณผ ๊ฐ์ด ๋ณด์ผ ๊ฒ์
๋๋ค:
### ์ถ์ฒ
[1] ๋งํฌ ๋๋ ๋ฌธ์ ์ด๋ฆ
[2] ๋งํฌ ๋๋ ๋ฌธ์ ์ด๋ฆ
7. ์ถ์ฒ๋ฅผ ๋ฐ๋์ ๊ฒฐํฉํ์ธ์. ์๋ฅผ ๋ค์ด ๋ค์์ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค:
[3] https://ai.meta.com/blog/meta-llama-3-1/
[4] https://ai.meta.com/blog/meta-llama-3-1/
์ค๋ณต๋ ์ถ์ฒ๊ฐ ์์ด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ๊ฐ๋จํ ํ์ํด์ผ ํฉ๋๋ค:
[3] https://ai.meta.com/blog/meta-llama-3-1/
8. ์ต์ข
๊ฒํ :
- ๋ณด๊ณ ์๊ฐ ํ์ํ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฅด๋์ง ํ์ธ
- ๋ณด๊ณ ์ ์ ๋ชฉ ์ ์ ์๋ฌธ์ ํฌํจํ์ง ์์
- ๋ชจ๋ ์ง์นจ์ ๋ฐ๋๋์ง ํ์ธ
"""
write_section
ํจ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def write_section(state: InterviewState):
""" ์ง๋ฌธ์ ๋ต๋ณํ๋ ๋
ธ๋ """
# ์ํ ๊ฐ์ ธ์ค๊ธฐ
interview = state["interview"]
context = state["context"]
analyst = state["analyst"]
# ์ธํฐ๋ทฐ์์ ์์งํ ์์ค ๋ฌธ์(context) ๋๋ ์ธํฐ๋ทฐ ์์ฒด(interview)๋ฅผ ์ฌ์ฉํ์ฌ ์น์
์์ฑ
system_message = section_writer_instructions.format(focus=analyst.description)
section = llm.invoke(
[SystemMessage(content=system_message)]
+
[HumanMessage(content=f"์ด ์์ค๋ฅผ ์ฌ์ฉํ์ฌ ์น์
์ ์์ฑํ์ธ์: {context}")])
# ์ํ์ ์ถ๊ฐ
return {"sections": [section.content]}
- ๋ชฉ์ : ์ด ํจ์๋
InterviewState
์์ ์์งํ ์ธํฐ๋ทฐ ์ ๋ณด๋ฅผ ์ฌ์ฉํ์ฌ ์ต์ข ๋ณด๊ณ ์์ ๊ฐ ์น์ ์ ์์ฑํฉ๋๋ค. -
์ํ ์์ ๊ฐ์ ธ์ค๊ธฐ:
interview
: ์ธํฐ๋ทฐ ๊ธฐ๋ก์ ๊ฐ์ ธ์ต๋๋ค.context
: ์ธํฐ๋ทฐ์์ ์์ฑ๋ ์์ค ๋ฌธ์๋ ์ฐธ๊ณ ์๋ฃ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.analyst
: ๋ถ์๊ฐ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ ํด๋น ๋ถ์๊ฐ๊ฐ ๊ฐ์ง ํน์ ๋ชฉํ(analyst.description
)๋ฅผ ์น์ ์์ฑ์ ๋ฐ์ํฉ๋๋ค.
-
์น์ ์์ฑ:
section_writer_instructions
์focus
๋ฅผ ๋ถ์๊ฐ์ ์ค๋ช (analyst.description
)์ผ๋ก ์ค์ ํฉ๋๋ค.section_writer_instructions
๋ ๋ณด๊ณ ์๊ฐ ์ผ๊ด์ฑ ์๊ฒ ์์ฑ๋๋๋ก ์ง์นจ์ ํฌํจํ๊ณ ์์ต๋๋ค.SystemMessage
๋ ์น์ ์์ฑ์ ์ํ ์ง์นจ์ ์ ๋ฌํ๊ณ ,HumanMessage
๋ ์์ฑ๋context
๋ฅผ ํฌํจํ์ฌ AI ๋ชจ๋ธ์ ์ ๋ฌํฉ๋๋ค.- ์ด๋ฅผ ํตํด ์ธํฐ๋ทฐ ๋ด์ฉ์ ๋ฐ์ํ ์น์ ์ด ์์ฑ๋ฉ๋๋ค.
-
์น์ ์ถ๊ฐ:
sections
๋ผ๋ ํค์ ํด๋น ์น์ ์ ์ถ๊ฐํ์ฌ ์ํ์ ๋ฐํํฉ๋๋ค.- ์ด ์น์ ์ ๋ณด๊ณ ์ ์์ฑ์ ์ต์ข ๋จ๊ณ๋ก ์ด์ด์ง๋๋ค.
StateGraph
์ ์ ๋ฐ ๋ ธ๋ ์ถ๊ฐ
1
2
3
4
5
6
7
interview_builder = StateGraph(InterviewState)
interview_builder.add_node("ask_question", generate_question)
interview_builder.add_node("search_web", search_web)
interview_builder.add_node("search_wikipedia", search_wikipedia)
interview_builder.add_node("answer_question", generate_answer)
interview_builder.add_node("save_interview", save_interview)
interview_builder.add_node("write_section", write_section)
StateGraph
์ ์:InterviewState
๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ๊ทธ๋ํ๋ฅผ ๋ง๋ญ๋๋ค. ์ด ๊ทธ๋ํ๋write_section
๊น์ง ์ด์ด์ง๋ ์ธํฐ๋ทฐ ํ๋ก์ธ์ค์ ๋ชจ๋ ๋จ๊ณ๋ณ ๋ ธ๋๋ฅผ ํฌํจํฉ๋๋ค.- ๋
ธ๋ ์ถ๊ฐ: ๊ฐ ๋จ๊ณ์ ์ฃผ์ ๊ธฐ๋ฅ์ ๋ด๋นํ๋ ๋
ธ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
ask_question
: ์ธํฐ๋ทฐ ์ง๋ฌธ์ ์์ฑํ๋ ๋จ๊ณ์ ๋๋ค.search_web
๋ฐsearch_wikipedia
: ํ์ํ ์๋ฃ๋ฅผ ๊ฒ์ํ๋ ๋จ๊ณ์ ๋๋ค.answer_question
: ์ ๋ฌธ๊ฐ์ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.save_interview
: ์ธํฐ๋ทฐ ๋ด์ฉ์ ์ ์ฅํฉ๋๋ค.write_section
: ์ธํฐ๋ทฐ์ ์์ ์ ์น์ ์ผ๋ก ์์ฑํฉ๋๋ค.
- ์ธํฐ๋ทฐ ๋จ๊ณ์ ํ๋ฆ ์ ์
1
2
3
4
5
6
7
8
9
# ํ๋ฆ
interview_builder.add_edge(START, "ask_question")
interview_builder.add_edge("ask_question", "search_web")
interview_builder.add_edge("ask_question", "search_wikipedia")
interview_builder.add_edge("search_web", "answer_question")
interview_builder.add_edge("search_wikipedia", "answer_question")
interview_builder.add_conditional_edges("answer_question", route_messages, ['ask_question','save_interview'])
interview_builder.add_edge("save_interview", "write_section")
interview_builder.add_edge("write_section", END)
- ํ๋ฆ ์ ์:
START
์์ask_question
๋ก ์ฐ๊ฒฐํ์ฌ ์ธํฐ๋ทฐ ์ง๋ฌธ์ ์์ํฉ๋๋ค.- ์ง๋ฌธ ํ
search_web
๋๋search_wikipedia
๋ก ์งํํด ๊ด๋ จ ์ ๋ณด๋ฅผ ๊ฒ์ํฉ๋๋ค. - ๊ฐ ๊ฒ์ ๊ฒฐ๊ณผ๋
answer_question
๋ ธ๋๋ก ์ ๋ฌ๋์ด ์ธํฐ๋ทฐ์ ๋ต๋ณ์ผ๋ก ์ด์ด์ง๋๋ค.
- ์กฐ๊ฑด๋ถ ์ฃ์ง:
answer_question
์์route_messages
๋ฅผ ํตํด ๋ํ๋ฅผ ๊ณ์ ์งํํ ์ง(ask_question
์ผ๋ก ๋์๊ฐ) ์๋๋ฉด ์ธํฐ๋ทฐ ์ ์ฅ ๋จ๊ณ(save_interview
)๋ก ๋์ด๊ฐ์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
- ๋ง์ง๋ง ๋จ๊ณ:
- ์ธํฐ๋ทฐ๊ฐ ์ ์ฅ๋๋ฉด
write_section
์ผ๋ก ๋์ด๊ฐ ๋ณด๊ณ ์์ ์น์ ์ ์์ฑํ ํ ์ข ๋ฃ(END
)๋ฉ๋๋ค.
- ์ธํฐ๋ทฐ๊ฐ ์ ์ฅ๋๋ฉด
- ๊ทธ๋ํ ์ปดํ์ผ ๋ฐ ์ธํฐ๋ทฐ ์คํ
1
2
3
# ์ธํฐ๋ทฐ
memory = MemorySaver()
interview_graph = interview_builder.compile(checkpointer=memory).with_config(run_name="์ธํฐ๋ทฐ ์ํ")
MemorySaver
: ์ธํฐ๋ทฐ ์ค๊ฐ ์ํ๋ฅผ ์ ์ฅํ์ฌ, ์ธํฐ๋ทฐ ์งํ ์ค์ ๋ฐ์ํ๋ ์ํ ๋ณํ๋ฅผ ์ฒดํฌํฌ์ธํธ๋ก ๊ธฐ๋กํฉ๋๋ค.- ๊ทธ๋ํ ์ปดํ์ผ: ์ธํฐ๋ทฐ ์ํ์ ์ํด ์ ์ฒด ๊ทธ๋ํ๋ฅผ ์ปดํ์ผํ๋ฉฐ, ์ด๋ฅผ
interview_graph
๋ก ์ ์ฅํฉ๋๋ค.
- ๊ทธ๋ํ ์๊ฐํ
1
2
# ๋ณด๊ธฐ
display(Image(interview_graph.get_graph().draw_mermaid_png()))
- ๊ทธ๋ํ ์๊ฐํ:
draw_mermaid_png()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ๊ทธ๋ํ๋ฅผ ์๊ฐํํ์ฌ ํ๋ฆ์ ํ์ธํ ์ ์์ต๋๋ค.
(์ค๊ฐ์ ๊ฒ) ๋ถ์๊ฐ ํ๋ช ์ ์ ํํด์ ๊ธ ์์ฑ์ ์์ฒญํ ์ ์์ต๋๋ค.
- ํ ๋ช ์ ์ ํํด์ ํด๋น ์ฃผ์ ์ ๋ํ ๊ธ์ ์์ฒญํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
1 2 # ๋ถ์๊ฐ ํ ๋ช ์ ํ analysts[0]
1 2 3 4 5 6 Analyst( affiliation='Tech Innovators Inc.', name='Dr. Emily Zhang', role='AI Research Scientist', description='Dr. Zhang focuses on the development and optimization of large language models (LLMs). Her interest lies in improving model efficiency and scalability.' )
- ๊ธ ์์ฑ ์์ฒญ ํ ๊ฒฐ๊ณผ๋ฅผ ํ๋ฒ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
1 2 3 4 5 from IPython.display import Markdown messages = [HumanMessage(f"๋น์ ์ด {topic}์ ๋ํ ๊ธ์ ์ฐ๊ณ ์๋ค๊ณ ํ๋์?")] thread = {"configurable": {"thread_id": "1"}} interview = interview_graph.invoke({"analyst": analysts[0], "messages": messages, "max_num_turns": 3}, thread) Markdown(interview['sections'][0])
์์ฒญ ๊ทธ๋ด ๋ฏํ๊ฒ LLMOps์ ๋ํ ๊ธ์ ์์ฑํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋ํ ์์ ์ธ์ด ๋ผ์ฐํ ๊ท์น์ ์ํด
max_num_turns
3ํ๊น์ง ๋๊ณ , ๋ต๋ณ์ ์์ฑํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
(๊ฒฐ๊ณผํด์) ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ข ๋ ์ธ๋ถํํด์ ํด์ํด๋ณผ๊น์?
์ถ๋ ฅ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด, ๋ํ ์๋๋ฆฌ์ค์ ๋ฌธ์ ๊ธฐ๋ฐ ์น์ ์์ฑ์ด ์ ์์ ์ผ๋ก ์๋ํ ๊ฒ ๊ฐ์ต๋๋ค.
- ๊ตฌ์ฒด์ ์ผ๋ก ํ์ธํด๋ณด๋ฉด, ์ฃผ์ ์ ์ฐจ๊ฐ ๋ค์๊ณผ ๊ฐ์ด ์์ฐจ์ ์ผ๋ก ์คํ๋์์ต๋๋ค.
- ๋ํ ์งํ: ๋ํ ํ๋ฆ์ด
HumanMessage
์AIMessage
์ ๋ฐ๋ณต์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ์ด์ด์ก์ต๋๋ค. ๊ฐHumanMessage
์ ๋ํดAIMessage
๊ฐ ๋ ผ๋ฆฌ์ ์ธ ๋ต๋ณ์ ์ ๊ณตํ๊ณ ์์ผ๋ฉฐ, LLMOps ๊ด๋ จ ์ฃผ์ ๊ฐ๋ ๊ณผ ๊ตฌํ ๋์ ๊ณผ์ ๋ค์ด ์ค๋ช ๋์์ต๋๋ค.- ๋ฌธ์ ์ปจํ ์คํธ ํ์ฉ:
context
๋ก ์ง์ ๋ ๋ฌธ์๋ค์ดsection_writer_instructions
์ ๋ฐ๋ผ ์์ฝ ๋ฐ ๋ฌธ์ ๊ตฌ์ฑ์ ์ํด ์ฌ์ฉ๋์์ต๋๋ค.write_section
ํจ์์์ ๋ฌธ์ ๋ด์ฉ์ด ์น์ ์ ์ ์ ํ ๋ฐ์๋์ด,LLMOps
์ ์ฃผ์ ๊ฐ๋ ๊ณผ ์ ์ฉ ์ฌ๋ก ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก๊ฐ ํฌํจ๋ ์์ฝ์ด ์์ฑ๋์์ต๋๋ค.- ์น์ ์์ฑ:
sections
ํ๋์ ์ ์ฅ๋ ๋ณด๊ณ ์๋##
์###
ํ์์ ์ฌ์ฉํด ๊ตฌ์กฐ์ ์ผ๋ก ์์ฑ๋์๊ณ , ์ถ์ฒ ๋ชฉ๋ก์ด ํฌํจ๋์ด ์์ต๋๋ค. ์ฌ๊ธฐ์ ๊ฐ ์ถ์ฒ๊ฐ[1]
,[2]
๋ฑ์ผ๋ก ์ ๋๋ก ๋งํฌ๋์์ผ๋ฉฐ, ๋ชจ๋ฒ ์ฌ๋ก์ LLMOps์ ์ฃผ์ ๊ตฌ์ฑ ์์๋ ์์ฝ์ ํฌํจ๋์ด ์์ต๋๋ค.
์ด์ ๊ฐ๊ฐ์ Analyst๋ค์ ํํํ๊ฒ ๊ทธ๋ํ ์์์ ์คํํ๊ธฐ ์ํด ๋ค์์ ์ํํ ์ ์์ต๋๋ค.
โ๏ธ Parallelize ์ธํฐ๋ทฐ: Map-Reduce ํจํด ์ ์ฉ
์ด ๊ตฌ์กฐ์์๋ ์ธํฐ๋ทฐ๋ฅผ ๋ณ๋ ฌ๋ก ์ํํ์ฌ ์๊ฐ์ ์ ์ฝํ๊ณ , ์ต์ข ์ ์ผ๋ก ๋ฆฌํฌํธ๋ฅผ ์์ฑํ๊ธฐ ์ํด ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐํฉํ๋ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.
-
๋งต(Map) ๋จ๊ณ:
initiate_all_interviews
ํจ์๊ฐSend()
API๋ฅผ ์ฌ์ฉํ์ฌ ๋ณ๋ ฌ๋ก ์ธํฐ๋ทฐ๋ฅผ ์คํํ๋ ์ญํ ์ ํฉ๋๋ค.Send()
๋ ๊ฐ ๋ถ์๊ฐ์ ์ ๋ฌธ๊ฐ ๊ฐ ์ธํฐ๋ทฐ ์ธ์ ์ ๋ ๋ฆฝ์ ์ผ๋ก ์คํํ๋ฉฐ, ๊ฐ ์ธํฐ๋ทฐ๊ฐ ๋ณ๋๋ก ์งํ๋๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ ์ธํฐ๋ทฐ๋ฅผ ๋ณ๋ ฌ๋ก ์คํํ์ฌ ์๊ฐ ํจ์จ์ฑ์ ๋์ ๋๋ค.- ์ด ๋จ๊ณ์์๋ ๊ฐ ์ธํฐ๋ทฐ์ ๊ฒฐ๊ณผ๊ฐ
ResearchGraphState
์sections
๋ฆฌ์คํธ์ ์ ์ฅ๋ฉ๋๋ค.
-
๋ฆฌ๋์ค(Reduce) ๋จ๊ณ:
write_report
ํจ์๋sections
๋ฆฌ์คํธ์ ์ ์ฅ๋ ๋ชจ๋ ์ธํฐ๋ทฐ ์ธ์ ์ ๊ฒฐํฉํ์ฌ ์ต์ข ๋ณด๊ณ ์ ๋ด์ฉ์ ์์ฑํฉ๋๋ค.- ์ดํ
write_introduction
๊ณผwrite_conclusion
ํจ์๊ฐ ๊ฐ๊ฐ ์๋ก ๊ณผ ๊ฒฐ๋ก ์ ์์ฑํ์ฌ ์ต์ข ๋ณด๊ณ ์์ ํฌํจ๋ฉ๋๋ค. - ์ต์ข
๋จ๊ณ์ธ
finalize_report
์์๋ ์๋ก , ๋ณธ๋ฌธ, ๊ฒฐ๋ก ์ ํ๋์ ์์ฑ๋ ๋ณด๊ณ ์๋ก ํฉ์ณ ๋ฆฌํฌํธ๋ฅผ ์์ฑํฉ๋๋ค.
- ResearchGraphState ํด๋์ค ์ ์
- ์ญํ : ์ฐ๊ตฌ ์ฃผ์ , ๋ถ์๊ฐ ์, ํผ๋๋ฐฑ, ๋ณด๊ณ ์ ์๋ก ๋ฐ ๊ฒฐ๋ก ๋ฑ ์ํ ์ ๋ณด๋ฅผ ์ถ์ ํ์ฌ, ์ ์ฒด ์คํ ํ๋ฆ ์ค ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ๊ด๋ฆฌํ๊ณ ์ฐธ์กฐํ ์ ์๊ฒ ํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import operator
from typing import List, Annotated
from typing_extensions import TypedDict
class ResearchGraphState(TypedDict):
topic: str # ์ฐ๊ตฌ ์ฃผ์
max_analysts: int # ๋ถ์๊ฐ ์
human_analyst_feedback: str # ์ธ๊ฐ ํผ๋๋ฐฑ
analysts: List[Analyst] # ์ง๋ฌธํ๋ ๋ถ์๊ฐ
sections: Annotated[list, operator.add] # Send() API ํค
introduction: str # ์ต์ข
๋ณด๊ณ ์์ ์๋ก
content: str # ์ต์ข
๋ณด๊ณ ์์ ๋ด์ฉ
conclusion: str # ์ต์ข
๋ณด๊ณ ์์ ๊ฒฐ๋ก
final_report: str # ์ต์ข
๋ณด๊ณ ์
- TypedDict๋ก ์ง์ ํ์ฌ ์ํ๊ฐ ํน์ ํ ํค์ ๊ฐ์ ๊ฐ๋๋ก ๊ฐ์ ํฉ๋๋ค.
- Annotated๋ก ์ฌ๋ฌ sections๋ฅผ ์ถ๊ฐํ ๋ ๋ฆฌ์คํธ ํํ๋ก ๋ณํฉํ์ฌ ์ ์ฅํ๋๋ก ํฉ๋๋ค.
- initiate_all_interviews ํจ์: ์ธํฐ๋ทฐ ๋ณ๋ ฌํ ์คํ
- ์ญํ : ์ธํฐ๋ทฐ๋ฅผ ๋ณ๋ ฌ๋ก ์ํํ๋ โ๋งตโ ๋จ๊ณ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def initiate_all_interviews(state: ResearchGraphState):
""" ์ด๋ Send API๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ์ธํฐ๋ทฐ ์๋ธ๊ทธ๋ํ๋ฅผ ์คํํ๋ "๋งต" ๋จ๊ณ์
๋๋ค """
# ์ธ๊ฐ ํผ๋๋ฐฑ ํ์ธ
human_analyst_feedback=state.get('human_analyst_feedback')
if human_analyst_feedback:
# create_analysts๋ก ๋์๊ฐ๊ธฐ
return "create_analysts"
# ๊ทธ๋ ์ง ์์ผ๋ฉด Send() API๋ฅผ ํตํด ์ธํฐ๋ทฐ๋ฅผ ๋ณ๋ ฌ๋ก ์์
else:
topic = state["topic"]
return [Send("conduct_interview", {"analyst": analyst,
"messages": [HumanMessage(
content=f"๋น์ ์ด {topic}์ ๋ํ ๊ธ์ ์ฐ๊ณ ์๋ค๊ณ ํ๋์?"
)
]}) for analyst in state["analysts"]]
human_analyst_feedback
์ด ์์ ๊ฒฝ์ฐ, ํผ๋๋ฐฑ์ ๋ฐ์ํดcreate_analysts
๋ก ๋์๊ฐ ๋ถ์๊ฐ๋ฅผ ์๋ก ์์ฑํฉ๋๋ค.- ํผ๋๋ฐฑ์ด ์์ผ๋ฉด
Send()
API๋ฅผ ์ฌ์ฉํด ์ธํฐ๋ทฐ๋ฅผ ๊ฐ ๋ถ์๊ฐ์ ๋ ๋ฆฝ์ ์ผ๋ก ๋ณ๋ ฌ๋ก ์์ํฉ๋๋ค.
report_writer_instructions
- ํด๋น ์ง์นจ์ ๋ถ์๊ฐ ํ์ด ์ ๊ณตํ ๋ฉ๋ชจ๋ฅผ ๋ฐํ์ผ๋ก ๋ณด๊ณ ์ ๋ณธ๋ฌธ์ ์์ฑํ๋ ๋ฐ ํ์ํ ์ธ๋ถ์ฌํญ์ ์๋ดํฉ๋๋ค.
- ์ด ์ง์นจ์ ๋ณด๊ณ ์ ์์ฑ์๊ฐ ์ผ๊ด์ฑ ์๊ณ ์ถ์ฒ๊ฐ ๋ช ํํ ํ์๋ ๋ณด๊ณ ์๋ฅผ ์์ฑํ ์ ์๋๋ก ํ๋ฉฐ, ์ ๊ณต๋ ์๋ฃ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํต์ฐฐ๋ ฅ ์๋ ์์ฝ์ ๋ง๋ค๋๋ก ํฉ๋๋ค.
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
report_writer_instructions = """
๋น์ ์ ๋ค์ ์ ์ฒด ์ฃผ์ ์ ๋ํ ๋ณด๊ณ ์๋ฅผ ์์ฑํ๋ ๊ธฐ์ ์๊ฐ์
๋๋ค:
{topic}
๋น์ ์๊ฒ๋ ๋ถ์๊ฐ ํ์ด ์์ต๋๋ค. ๊ฐ ๋ถ์๊ฐ๋ ๋ ๊ฐ์ง ์ผ์ ํ์ต๋๋ค:
1. ํน์ ํ์ ์ฃผ์ ์ ๋ํด ์ ๋ฌธ๊ฐ์ ์ธํฐ๋ทฐ๋ฅผ ์งํํ์ต๋๋ค.
2. ๊ทธ๋ค์ ๋ฐ๊ฒฌ์ ๋ฉ๋ชจ๋ก ์์ฑํ์ต๋๋ค.
๋น์ ์ ์๋ฌด:
1. ๋ถ์๊ฐ๋ค์ ๋ฉ๋ชจ ๋ชจ์์ด ์ฃผ์ด์ง ๊ฒ์
๋๋ค.
2. ๊ฐ ๋ฉ๋ชจ์ ํต์ฐฐ์ ์ ์คํ ์๊ฐํด๋ณด์ธ์.
3. ๋ชจ๋ ๋ฉ๋ชจ์ ์ค์ฌ ์์ด๋์ด๋ฅผ ํ๋์ ๊ฐ๊ฒฐํ ์ ์ฒด ์์ฝ์ผ๋ก ํตํฉํ์ธ์.
4. ๊ฐ ๋ฉ๋ชจ์ ์ค์ฌ์ ์ ํ๋์ ์ผ๊ด๋ ๋ด๋ฌํฐ๋ธ๋ก ์์ฝํ์ธ์.
๋ณด๊ณ ์ ํ์:
1. ๋งํฌ๋ค์ด ํ์์ ์ฌ์ฉํ์ธ์.
2. ๋ณด๊ณ ์์ ์๋ฌธ์ ํฌํจํ์ง ๋ง์ธ์.
3. ์์ ๋ชฉ์ ์ฌ์ฉํ์ง ๋ง์ธ์.
4. ๋จ์ผ ์ ๋ชฉ ํค๋๋ก ๋ณด๊ณ ์๋ฅผ ์์ํ์ธ์: ## ํต์ฐฐ
5. ๋ณด๊ณ ์์ ๋ถ์๊ฐ ์ด๋ฆ์ ์ธ๊ธํ์ง ๋ง์ธ์.
6. ๋ฉ๋ชจ์ ์๋ ์ธ์ฉ์ ๋ณด์กดํ์ธ์. ์ธ์ฉ์ [1] ๋๋ [2]์ ๊ฐ์ด ๋๊ดํธ ์์ ํ์๋ฉ๋๋ค.
7. ์ต์ข
ํตํฉ ์ถ์ฒ ๋ชฉ๋ก์ ๋ง๋ค๊ณ '## ์ถ์ฒ' ํค๋๊ฐ ์๋ ์ถ์ฒ ์น์
์ ์ถ๊ฐํ์ธ์.
8. ์ถ์ฒ๋ฅผ ์์๋๋ก ๋์ดํ๊ณ ๋ฐ๋ณตํ์ง ๋ง์ธ์.
[1] ์ถ์ฒ 1
[2] ์ถ์ฒ 2
๋ค์์ ๋ณด๊ณ ์ ์์ฑ์ ์ฌ์ฉํ ๋ถ์๊ฐ๋ค์ ๋ฉ๋ชจ์
๋๋ค:
{context}
"""
- write_report ํจ์: ๋ณด๊ณ ์ ์์ฑ
- ์ญํ :
sections
์ ์๋ ์ธํฐ๋ทฐ ๋ด์ฉ์ ๊ฒฐํฉํ์ฌ ์ต์ข ๋ณด๊ณ ์๋ฅผ ์์ฑ.
1
2
3
4
5
6
7
8
9
10
11
12
def write_report(state: ResearchGraphState):
# ์ ์ฒด ์น์
์ธํธ
sections = state["sections"]
topic = state["topic"]
# ๋ชจ๋ ์น์
์ ํ๋๋ก ์ฐ๊ฒฐ
formatted_str_sections = "\n\n".join([f"{section}" for section in sections])
# ์น์
์ ์ต์ข
๋ณด๊ณ ์๋ก ์์ฝ
system_message = report_writer_instructions.format(topic=topic, context=formatted_str_sections)
report = llm.invoke([SystemMessage(content=system_message)]+[HumanMessage(content=f"์ด ๋ฉ๋ชจ๋ฅผ ๋ฐํ์ผ๋ก ๋ณด๊ณ ์๋ฅผ ์์ฑํ์ธ์.")])
return {"content": report.content}
report_writer_instructions
์์ ์ฃผ์ด์ง ์์์ ๋ฐ๋ผllm.invoke()
๋ฅผ ํตํด ๋ณด๊ณ ์ ๋ณธ๋ฌธ์ ์์ฑ.- ์ต์ข
๋ณด๊ณ ์ ๋ณธ๋ฌธ์
"content"
ํ๋์ ์ ์ฅ๋ฉ๋๋ค.
intro_conclusion_instructions
- ํด๋น ์ง์นจ์ ๋ณด๊ณ ์์ ์๋ก ๋๋ ๊ฒฐ๋ก ์ ์์ฑํ๋ ๋ฐ ํ์ํ ์ธ๋ถ ์ง์นจ์ ์ ๊ณตํฉ๋๋ค. ๋ณด๊ณ ์ ์ ์ฒด๋ฅผ ์์ฝํ๊ณ ๋ฐฉํฅ์ฑ์ ์ ๊ณตํ๊ฑฐ๋, ์ ์ฒด ๋ณด๊ณ ์ ๋ด์ฉ์ ์ ๋ฆฌํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- ์ด ์ง์นจ์ ํตํด ์์ฑ์๋ ๋ณด๊ณ ์ ์ ์ฒด ๋ด์ฉ์ ๊ฐ๊ฒฐํ๊ณ ์ค๋๋ ฅ ์๊ฒ ์ ๋ฆฌํ์ฌ, ๋ ์๋ค์ด ๋ณด๊ณ ์์ ์ฃผ์ ๋ด์ฉ์ ๋น ๋ฅด๊ฒ ํ์ ํ๊ฑฐ๋ ๊ฒฐ๋ก ์ ํตํด ์ ๋ฆฌ๋ ํต์ฐฐ์ ์ป๊ฒ ํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
intro_conclusion_instructions = """
๋น์ ์ {topic}์ ๋ํ ๋ณด๊ณ ์๋ฅผ ๋ง๋ฌด๋ฆฌํ๋ ๊ธฐ์ ์๊ฐ์
๋๋ค.
๋ณด๊ณ ์์ ๋ชจ๋ ์น์
์ด ์ฃผ์ด์ง ๊ฒ์
๋๋ค.
๋น์ ์ ์๋ฌด๋ ๊ฐ๊ฒฐํ๊ณ ์ค๋๋ ฅ ์๋ ์๋ก ๋๋ ๊ฒฐ๋ก ์น์
์ ์์ฑํ๋ ๊ฒ์
๋๋ค.
์ฌ์ฉ์๊ฐ ์๋ก ๋๋ ๊ฒฐ๋ก ์ค ์ด๋ ๊ฒ์ ์์ฑํ ์ง ์ง์ํ ๊ฒ์
๋๋ค.
๋ ์น์
๋ชจ๋ ์๋ฌธ์ ํฌํจํ์ง ๋ง์ธ์.
์ฝ 100๋จ์ด๋ฅผ ๋ชฉํ๋ก ํ์ฌ ๋ณด๊ณ ์์ ๋ชจ๋ ์น์
์ ๊ฐ๊ฒฐํ๊ฒ ๋ฏธ๋ฆฌ ๋ณด์ฌ์ฃผ๊ฑฐ๋(์๋ก ์ ๊ฒฝ์ฐ) ์์ฝํ์ธ์(๊ฒฐ๋ก ์ ๊ฒฝ์ฐ).
๋งํฌ๋ค์ด ํ์์ ์ฌ์ฉํ์ธ์.
์๋ก ์ ๊ฒฝ์ฐ, ๋งค๋ ฅ์ ์ธ ์ ๋ชฉ์ ๋ง๋ค๊ณ # ํค๋๋ฅผ ์ ๋ชฉ์ ์ฌ์ฉํ์ธ์.
์๋ก ์ ๊ฒฝ์ฐ, ## ์๋ก ์ ์น์
ํค๋๋ก ์ฌ์ฉํ์ธ์.
๊ฒฐ๋ก ์ ๊ฒฝ์ฐ, ## ๊ฒฐ๋ก ์ ์น์
ํค๋๋ก ์ฌ์ฉํ์ธ์.
๋ค์์ ์์ฑ ์ ์ฐธ๊ณ ํ ์น์
๋ค์
๋๋ค: {formatted_str_sections}
"""
- write_introduction ๋ฐ write_conclusion ํจ์: ์๋ก ๋ฐ ๊ฒฐ๋ก ์์ฑ
- ์ญํ : ๋ณด๊ณ ์์ ์๋ก ๋ฐ ๊ฒฐ๋ก ์ ์ถ๊ฐํด ๊ตฌ์กฐํ.
1
2
3
4
5
6
7
8
9
10
11
12
def write_introduction(state: ResearchGraphState):
# ์ ์ฒด ์น์
์ธํธ
sections = state["sections"]
topic = state["topic"]
# ๋ชจ๋ ์น์
์ ํ๋๋ก ์ฐ๊ฒฐ
formatted_str_sections = "\n\n".join([f"{section}" for section in sections])
# ์น์
์ ์ต์ข
๋ณด๊ณ ์๋ก ์์ฝ
instructions = intro_conclusion_instructions.format(topic=topic, formatted_str_sections=formatted_str_sections)
intro = llm.invoke([instructions]+[HumanMessage(content=f"๋ณด๊ณ ์ ์๋ก ์ ์์ฑํ์ธ์")])
return {"introduction": intro.content}
1
2
3
4
5
6
7
8
9
10
11
12
def write_conclusion(state: ResearchGraphState):
# ์ ์ฒด ์น์
์ธํธ
sections = state["sections"]
topic = state["topic"]
# ๋ชจ๋ ์น์
์ ํ๋๋ก ์ฐ๊ฒฐ
formatted_str_sections = "\n\n".join([f"{section}" for section in sections])
# ์น์
์ ์ต์ข
๋ณด๊ณ ์๋ก ์์ฝ
instructions = intro_conclusion_instructions.format(topic=topic, formatted_str_sections=formatted_str_sections)
conclusion = llm.invoke([instructions]+[HumanMessage(content=f"๋ณด๊ณ ์ ๊ฒฐ๋ก ์ ์์ฑํ์ธ์")])
return {"conclusion": conclusion.content}
intro_conclusion_instructions
์ ๋ฐ๋ผ, ์๋ก ์ ๋ณด๊ณ ์์ ๊ฐ๊ฒฐํ ์์ฝ์, ๊ฒฐ๋ก ์ ์์ฝ ๋ฐ ๋ง๋ฌด๋ฆฌ ๋ด์ฉ์ ๋ด์ต๋๋ค.write_introduction
๋ฐwrite_conclusion
์ ๊ฐ๊ฐintroduction
,conclusion
ํ๋์ ์ ์ฅ.
- finalize_report ํจ์: ์ต์ข ๋ณด๊ณ ์ ๊ฒฐํฉ ๋ฐ ์๋ฃ
- ์ญํ : ์๋ก , ๋ณธ๋ฌธ, ๊ฒฐ๋ก ์ ํ๋์ ์์ฑ๋ ๋ณด๊ณ ์๋ก ๊ฒฐํฉํ๋ โ๋ฆฌ๋์คโ ๋จ๊ณ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def finalize_report(state: ResearchGraphState):
""" ์ด๋ ๋ชจ๋ ์น์
์ ์์งํ๊ณ ๊ฒฐํฉํ ๋ค์ ์๋ก /๊ฒฐ๋ก ์ ์์ฑํ๊ธฐ ์ํด ๊ทธ๋ค์ ๋ฐ์ํ๋ "๋ฆฌ๋์ค" ๋จ๊ณ์
๋๋ค """
# ์ ์ฒด ์ต์ข
๋ณด๊ณ ์ ์ ์ฅ
content = state["content"]
if content.startswith("## ํต์ฐฐ"):
content = content.strip("## ํต์ฐฐ")
if "## ์ถ์ฒ" in content:
try:
content, sources = content.split("\n## ์ถ์ฒ\n")
except:
sources = None
else:
sources = None
final_report = state["introduction"] + "\n\n---\n\n" + content + "\n\n---\n\n" + state["conclusion"]
if sources is not None:
final_report += "\n\n## ์ถ์ฒ\n" + sources
return {"final_report": final_report}
content
,introduction
,conclusion
,sources
๋ฅผ ํฉ์ณ ์ต์ข ๋ณด๊ณ ์(final_report
)์ ์ ์ฅํฉ๋๋ค.
- StateGraph ๋ ธ๋ ๋ฐ ์ฃ์ง ์ ์
- ์ญํ : ์ธํฐ๋ทฐ ์์ฑ, ํผ๋๋ฐฑ ์ฒ๋ฆฌ, ์ธํฐ๋ทฐ ์งํ, ๋ณด๊ณ ์ ์์ฑ, ์๋ก ๋ฐ ๊ฒฐ๋ก ์์ฑ, ์ต์ข ๋ณด๊ณ ์ ์์ฑ์ ์ํ ๋ ธ๋์ ์ฃ์ง ์ ์.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ๋
ธ๋์ ์ฃ์ง ์ถ๊ฐ
builder = StateGraph(ResearchGraphState)
builder.add_node("create_analysts", create_analysts)
builder.add_node("human_feedback", human_feedback)
builder.add_node("conduct_interview", interview_builder.compile())
builder.add_node("write_report",write_report)
builder.add_node("write_introduction",write_introduction)
builder.add_node("write_conclusion",write_conclusion)
builder.add_node("finalize_report",finalize_report)
# ๋ก์ง
builder.add_edge(START, "create_analysts")
builder.add_edge("create_analysts", "human_feedback")
builder.add_conditional_edges("human_feedback", initiate_all_interviews, ["create_analysts", "conduct_interview"])
builder.add_edge("conduct_interview", "write_report")
builder.add_edge("conduct_interview", "write_introduction")
builder.add_edge("conduct_interview", "write_conclusion")
builder.add_edge(["write_conclusion", "write_report", "write_introduction"], "finalize_report")
builder.add_edge("finalize_report", END)
add_conditional_edges
๋กhuman_feedback
์กฐ๊ฑด์ ์ถฉ์กฑํ ๊ฒฝ์ฐ ์๋ก์ด ๋ถ์๊ฐ๋ฅผ ์์ฑํ๊ฑฐ๋ ์ธํฐ๋ทฐ๋ฅผ ์์ํ๋๋ก ๊ตฌ์ฑ.- ์ฌ๊ธฐ์
interview_builder
๋ฅผ ์๋ธ๊ทธ๋ํ๋ก ์ฒ๋ฆฌํ ๋ฐฉ์์ ์ธํฐ๋ทฐ ์ํ ๊ณผ์ ์ ๋ ๋ฆฝ์ ์ธ ๊ทธ๋ํ๋ก ๊ตฌ์ฑํ์ฌ ๋ชจ๋ํํ๊ณ , ์ด๋ฅผ ์ฃผ ๊ทธ๋ํ์ธ StateGraph์ ํน์ ๋ ธ๋๋ก ์ปดํ์ผํ์ฌ ์ฌ์ฌ์ฉํ ๊ฒ์ ๋๋ค.- ์ด ๊ณผ์ ์ ๋ณต์กํ ์ํฌํ๋ก์ฐ๋ฅผ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ ์ ์๊ฒ ํ๊ณ , StateGraph์ ๋ ธ๋ ๊ฐ ๋จ๊ณ๋ณ ํ๋ฆ์ ๋ ์ฝ๊ฒ ์ค์ ํ ์ ์๋๋ก ๋์์ค๋๋ค.
interview_builder
๋ ์ธํฐ๋ทฐ ์ํ์ ๊ฐ ๋จ๊ณ๋ฅผ ํฌํจํ๋ ์์ ๊ทธ๋ํ๋ก, ๋ ๋ฆฝ์ ์ธ StateGraph ์ธ์คํด์ค๋ก ์์ฑ๋ฉ๋๋ค.interview_builder
์๋ธ๊ทธ๋ํ๋ ์ด์ ๋จ๊ณ์์ ๋ง๋ask_question
,search_web
,search_wikipedia
,answer_question
,save_interview
,write_section
๋ ธ๋์ ์ด๋ค์ ์๋ ์ฃ์ง๋ก ๊ตฌ์ฑ๋์ด, ์ธํฐ๋ทฐ์ ๊ด๋ จ๋ ๋ชจ๋ ๋ก์ง์ ๊ด๋ฆฌํฉ๋๋ค.
- ๊ทธ๋ํ ์คํ ๋ฐ ํ์ธ
- ์ญํ : ๊ทธ๋ํ๋ฅผ ์ปดํ์ผํ๊ณ ๊ฐ ๋จ๊ณ์ ์ํ๋ฅผ ์๊ฐํํ์ฌ ๊ฒ์ฌํฉ๋๋ค.
1
2
3
4
# ์ปดํ์ผ
memory = MemorySaver()
graph = builder.compile(interrupt_before=['human_feedback'], checkpointer=memory)
display(Image(graph.get_graph(xray=1).draw_mermaid_png()))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ์
๋ ฅ
max_analysts = 3
topic = "LLMOps ํ๋ซํผ ๊ตฌ์ถ"
thread = {"configurable": {"thread_id": "1"}}
# ์ฒซ ๋ฒ์งธ ์ค๋จ๊น์ง ๊ทธ๋ํ ์คํ
for event in graph.stream({"topic":topic,
"max_analysts":max_analysts},
thread,
stream_mode="values"):
analysts = event.get('analysts', '')
if analysts:
for analyst in analysts:
print(f"์ด๋ฆ: {analyst.name}")
print(f"์์: {analyst.affiliation}")
print(f"์ญํ : {analyst.role}")
print(f"์ค๋ช
: {analyst.description}")
print("-" * 50)
graph.stream
์ผ๋ก ๊ทธ๋ํ๊ฐ ํ๋ฆ์ ๋ฐ๋ผ ๋จ๊ณ๋ณ๋ก ์งํ๋๋ฉฐ, ์ํ์ ์ ์ฅ๋ ๋ถ์๊ฐ ์ ๋ณด๋ ์ถ๋ ฅ๋ฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
# ๋๋จธ์ง๋ ๋๊น์ง ์คํํด์ค๋๋ค
graph.update_state(thread, {"human_analyst_feedback":
None}, as_node="human_feedback")
# ๊ณ์
for event in graph.stream(None, thread, stream_mode="updates"):
print("--๋
ธ๋--")
node_name = next(iter(event.keys()))
print(node_name)
- ์๋ํ ๊ฒ์ฒ๋ผ
conduct_interview
๋ค์ด ๋ณ๋ ฌ์ ์ผ๋ก ์ํ๋๊ณ ,write ์์ ๋ค
์ด ์ํ๋ ๋ค์,finalize_report
์ด ์ํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
--๋
ธ๋--
conduct_interview
--๋
ธ๋--
conduct_interview
--๋
ธ๋--
conduct_interview
--๋
ธ๋--
write_introduction
--๋
ธ๋--
write_conclusion
--๋
ธ๋--
write_report
--๋
ธ๋--
finalize_report
- ์ต์ข ๋ณด๊ณ ์ ๊ฒฐ๊ณผ ์ถ๋ ฅ
- ์ญํ : ์ต์ข ๋ณด๊ณ ์๋ฅผ Markdown ํ์์ผ๋ก ์ถ๋ ฅ.
1
2
3
4
from IPython.display import Markdown
final_state = graph.get_state(thread)
report = final_state.values.get('final_report')
Markdown(report)
final_report
์ ์ ์ฅ๋ ์ต์ข ๋ณด๊ณ ์๋ฅผ ์๊ฐํํ์ฌ, ๊ตฌ์กฐ๊ฐ ์์ฑ๋ ๋ณด๊ณ ์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.