๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

SKN ai Family camp 13๊ธฐ/์ฃผ๊ฐ„ ํšŒ๊ณ 

[ํ”Œ๋ ˆ์ด๋ฐ์ดํ„ฐ SK๋„คํŠธ์›์Šค Family AI ์บ ํ”„ 13๊ธฐ] - Monthly ํšŒ๊ณ  (7์›” 1์ฃผ์ฐจ)

1. ํ•œ ๋‹ฌ ํšŒ๊ณ  

ํ•œ ๋‹ฌ์ด ์–ด๋–ป๊ฒŒ ํ˜๋Ÿฌ๊ฐ”๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค. 

๋น…๋ฐ์ดํ„ฐ ์‡ผ๋ฅผ ๊ฐ”๋‹ค์™€์„œ ๊ทธ๋Ÿฐ์ง€

์š”์ฆ˜ Langchain - RAG ์ด ๋ถ€๋ถ„์ด ๋”์šฑ ํฅ๋ฏธ๊ฐ€ ์ƒ๊ฒผ๋‹ค. ๋‹จ์ˆœํžˆ ๊ธฐ์ˆ ์ ์œผ๋กœ ํฅ๋ฏธ๋กญ๋‹ค๋Š” ๊ฑธ ๋„˜์–ด์„œ ์š”์ฆ˜ ํšŒ์‚ฌ๋“ค์ด ์ด ๋ถ€๋ถ„์„ ๋งŽ์ด ๊ฐœ๋ฐœํ•˜๋ ค๊ณ  ํ•œ๋‹ค๋Š” ์ ์—์„œ ๊ทธ๋ฆฌ๊ณ  ๋‚ด๊ฐ€ ๋ญ”๊ฐ€ ๊ธฐ์—ฌํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ ๊ฐ™์•„์„œ ์ด ์ชฝ ๋ถ„์•ผ๋กœ ์ผ์„ ์ฐพ๊ณ ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค. 

ํ˜„์‹ค์ ์œผ๋กœ ์‹ ์ž…์„ ๋งŽ์ด ๋ฝ‘๋Š” ๋ถ„์•ผ๋Š” ์•„๋‹ˆ์˜€๊ธฐ์— ๊ณ ๋ฏผํ•˜๋˜ ์™€์ค‘์—, ์•„์นจ์— ๊ฐ•์‚ฌ๋‹˜์ด ์–ด๋–ป๊ฒŒ ์ง€๋‚ด๋ƒ๊ณ  ๋ฌผ์–ด๋ด์ฃผ์…จ๊ณ  ์ง„๋กœ์— ๋Œ€ํ•œ ์ƒ๋‹ด์„ ์ž ๊น์ด๋‚˜๋งˆ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๊ผญ ์ด ์ชฝ ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด๋ณด๊ณ ์‹ถ๋‹ค. 

 

2. ํŒ€ํ”„๋กœ์ ํŠธ

์›”์š”์ผ ๋ฐœํ‘œ! 

์—„์ฒญ๋‚œ ๋ฐ์ดํ„ฐ ์‚ฌ์ด์—์„œ ํ—ˆ๋•์ด๋‹ค๊ฐ€ ์ŠคํŠธ๋ฆผ๋ฆฟ๊นŒ์ง€ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ ๋‹น์ผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋‹ต๋ณ€ํ•œ๋‹ค๋Š” ๊ฑธ ๊นจ๋‹ฌ์•˜๋‹ค.. ๊ทธ๋ฆฌ๊ณ  ์ด๊ฑธ ๊ณ ์ณค๋˜ ํ•œ ํŒ€์›์ด ๊ณ ์ƒ์„ ํ–ˆ๋‹ค... ๊ทธ ๋…ธ๋ ฅ๊ณผ ์ง‘์ค‘๋ ฅ์„ ์กด๊ฒฝํ•œ๋‹ค ์ •๋ง.. 

์‚ฌ์‹ค ๋‚˜๋„ ๊ทธใ…ก ๊ณผ์ •์„ ์˜†์—์„œ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ์œผ๋ฉด ์ข‹์•˜๊ฒ ์ง€๋งŒ, ์‹œ๊ฐ„์ด ๋„ˆ๋ฌด ์—†์—ˆ๋‹ค.

๋‹ค์Œ๋ฒˆ์— ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ๋ฌผ์–ด๋ณด๊ณ  ๋‚ด ์†์œผ๋กœ ๋‹ค์‹œ ์žฌํ˜„์„ ํ•ด๋ด์•ผ๊ฒ ๋‹ค.

 

์ด๋ฒˆ์—๋Š” ๋‚˜๋„ ๋ฐœํ‘œ์— ์ฐธ์—ฌํ–ˆ๋‹ค. ํฌ๊ฒŒ ์–ด๋ ค์šด ๊ฑด ์•„๋‹ˆ์˜€๋Š”๋ฐ ์˜ค๋žœ๋งŒ์— ๋ฐœํ‘œ๋ผ ๋งŽ์ด ๋–จ๋ ธ๋‹ค. 

๋‚ด๊ฐ€ ๋งก์€ ๋ถ€๋ถ„์„ ์„ค๋ช…ํ•˜๊ณ  ์ „๋‹ฌํ–ˆ๋‹ค๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์ด ํŒ€ํ”„๋กœ์ ํŠธ๋ฅผ ์ž˜ ์ดํ•ดํ•˜๊ณ ์žˆ๋‹ค๋Š” ์ ์—์„œ ๋‚ด ์Šค์Šค๋กœ ์˜๋ฏธ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ „์ฒ˜๋ฆฌ๋ถ€๋ถ„๊ณผ ํŒŒ์ธํŠœ๋‹ ๋ถ€๋ถ„์€ ์ „๋ฌธ๊ฐ€(?)์ธ ์žฌ๋ฒ”์ด๊ฐ€ ๋ฐœํ‘œ๋ฅผ ํ–ˆ๋‹ค. ๋„˜ ๋ฉ‹์ง„ ๋™๊ธฐ๋‹ค. ๋งŽ์ด ๋ฐฐ์›Œ๊ฐ‘๋‹ˆ๋‹ค. 

 

 

 

https://github.com/SKNETWORKS-FAMILY-AICAMP/SKN13-3rd-3Team

 

GitHub - SKNETWORKS-FAMILY-AICAMP/SKN13-3rd-3Team: [SK Networks Family AI Camp 13th] 3rd mini project

[SK Networks Family AI Camp 13th] 3rd mini project - SKNETWORKS-FAMILY-AICAMP/SKN13-3rd-3Team

github.com

์šฐ๋ฆฌ์˜ ์ž๋ž‘์Šค๋Ÿฌ์šด ์‚ฐ์ถœ๋ฌผ๋“ค! ์—ฌ๊ธฐ๋‹ค๊ฐ€๋„ ์˜ฌ๋ ค๋ณธ๋‹ค ใ…‹ใ…‹ใ…‹ใ…‹ 

 

์•„๋ฌด๋ž˜๋„ ์šฐ๋ฆฌํŒ€์ด ์ œ์ผ ์ž˜ํ•œ๊ฑฐ๊ฐ™๋‹ค(ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹)

์ข‹์€ ํŒ€์›๋“ค ๋งŒ๋‚˜์„œ ์œผ์Œฐ์œผ์Œฐํ•˜๊ณ  ๊ฐ™์ด ์–˜๊ธฐํ•˜๋ฉด์„œ ๋” ๋งŽ์ด ๊ณต๋ถ€๋ฅผ ํ•˜๊ฒŒ๋˜๊ณ ..

์ •๋ง ๋ฉ‹์ง„ ํŒ€์›๋“ค๊ณผ ํ•จ๊ป˜ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ๋งŒ์œผ๋กœ๋„ ๊ฐ’์กŒ๋˜ ์‹œ๊ฐ„์ด์˜€๋‹ค.

 

์•ž์œผ๋กœ ๋ณด์™„ํ•  ์ ์„ ๋ณด์™„ํ•˜๊ณ  ์žฅ๊ณ ๋กœ ํ™”๋ฉด๊ตฌํ˜„๊นŒ์ง€ ์ž˜ ์ด์–ด๋‚˜๊ฐ€๋ณด๊ฒ ๋‹ค. 

 

 

2.1 ๋ณด์™„ํ•˜๊ณ  ์‹ถ์€ ์  - langchain์„ langgraph๋กœ ๋ฐ”๊ฟ”์„œ ์จ๋ณด๊ณ ์‹ถ๋‹ค. 

from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

from typing_extensions import TypedDict
from typing import Annotated

from dotenv import load_dotenv
load_dotenv()

class ProductInfoState(TypedDict):
	product: str
    price: int
    ingredient:str
    review: str
    

# ๋…ธ๋“œ ์ •์˜ -> ํ•จ์ˆ˜(callable) -> ์ž…๋ ฅ: State, ์ถœ๋ ฅ: State์— ์ €์žฅํ•  ์†์„ฑ(state)
def add_Product(state:ProductInfoState):
    print("add_product:", state)
    return {"product": "cream"}
    
def add_price(state:ProductInfoState):
    print("add_price:", state)
    return {"price":10000} 

def add_ingredient(state:ProductInfoState):
    print("add_ingredient:", state)
    return {"ingredient": "water"}

def add_review(state:ProductInfoState):
    print("add_review:", state)
    return {"review": "perfect"}
    
    
# StateGraph๋ฅผ ๊ตฌ์„ฑ.
workflow = StateGraph(UserInfoState)
# ๋…ธ๋“œ ์ถ”๊ฐ€
workflow.add_node("add_product", add_product)
workflow.add_node("add_price", add_price)
workflow.add_node("add_ingredient", add_ingredient)
workflow.add_node("add_review", add_review)

# ์—ฃ์ง€ ์ถ”๊ฐ€ (๋…ธ๋“œ๊ฐ„์˜ ์—ฐ๊ฒฐ) -> ์‹œ์ž‘: START ๋…ธ๋“œ
workflow.add_edge(START, "add_product")
workflow.add_edge("add_product", "add_ingredient")
workflow.add_edge("add_price", "add_ingredient")
workflow.add_edge("add_ingredient", "add_review")
workflow.add_edge("add_review", END)

# state graph๋ฅผ ์ปดํŒŒ์ผ - ์‹คํ–‰๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋งŒ๋“ ๋‹ค. (๊ทธ๋ž˜ํ”„ ๊ฒ€์ฆ, ์ตœ์ ํ™”)
graph = workflow.compile()


# ์‹œ๊ฐํ™”
from IPython.display import Image, display
display(Image(graph.get_graph().draw_mermaid_png()))


######################
# Chatbot 
######################
# ์ƒํƒœ(State) ํด๋ž˜์Šค ์ •์˜
#  ๋…ธ๋“œ๋“ค์ด ๊ณต์œ ํ•  state๊ฐ’๋“ค์„ ์ •์˜ํ•˜๋Š” ํด๋ž˜์Šค(ํƒ€์ž…)
class State(TypedDict):
    # state์— ์ €์žฅํ•  ๊ฐ’(์†์„ฑ, state) ๋“ค์„ ์ •์˜
    messages: Annotated[list, add_messages]  # ๋ณ€์ˆ˜: Annotated[๋ณ€์ˆ˜ํƒ€์ž…, ์„ค๋ช…]

    # add_messages(left:list, right:list) : return left + right
    
model = ChatOpenAI(model='gpt-4.1-mini')

#######################
# ๋…ธ๋“œ ์ •์˜ 
# ๋…ธ๋“œ == ๊ธฐ๋Šฅ == ํ•จ์ˆ˜(callable): ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ State ํƒ€์ž…์„ ์„ ์–ธ. 
def chatbot(state:State):
    # state: Dictionary - key: Stateํด๋ž˜์Šค์— ์ •์˜ํ•œ ๋ณ€์ˆ˜๋“ค.
    print("chatbot: state type:", type(state), state)
    # messages๋ฅผ llm์— query๋กœ ์ „๋‹ฌ.
    response = model.invoke(state['messages'])  #invoke(str ๋˜๋Š” Message List)
    # response๋ฅผ state์— ์ €์žฅ. -> dictionary๋กœ ๊ตฌ์„ฑ {์ €์žฅํ•  state์˜ ์ด๋ฆ„: ์ €์žฅํ•  ๊ฐ’}
    return {"messages": [response]}  # ๋ฆฌํ„ด -> State์— ์ €์žฅ.
    
# ๊ทธ๋ž˜ํ”„๋ฅผ ๊ตฌ์„ฑ (StateGraph)
## ๋…ธ๋“œ์™€ ์—ฃ์ง€ + state
workflow = StateGraph(State)

# graph(workflow)์— ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€. ์ด๋ฆ„-ํ•จ์ˆ˜
workflow.add_node("chatbot", chatbot)

# graph์— ์—ฃ์ง€๋ฅผ ๊ตฌ์„ฑ. ์—ฃ์ง€: ๋…ธ๋“œ์™€ ๋…ธ๋“œ๋ฅผ ์—ฐ๊ฒฐ
## START ๋…ธ๋“œ - ์—ฃ์ง€ ๊ตฌ์„ฑ - (END ๋…ธ๋“œ:์ƒ๋žต๊ฐ€๋Šฅ)
workflow.add_edge(START, "chatbot")  # ์‹œ์ž‘๋…ธ๋“œ(์ด๋ฆ„) -> ๋„์ฐฉ๋…ธ๋“œ(์ด๋ฆ„)
workflow.add_edge("chatbot", END)

# ๊ตฌ์„ฑ์„ ์™„๋ฃŒํ•˜๋ฉด compile -> ๊ทธ๋ž˜ํ”„ ์ตœ์ ํ™”๋ฅผ ํ•˜๊ณ  ์‹คํ–‰ํ•  ์ˆ˜์žˆ๋„๋กœ ๋งŒ๋“œ๋Š” ์ž‘์—….
graph = workflow.compile()


# ๊ทธ๋ž˜ํ”„ ๊ตฌ์กฐ ์‹œ๊ฐํ™” -> ์‹คํ–‰ ํ๋ฆ„์„ ํ™•์ธ
from IPython.display import Image
Image(graph.get_graph().draw_mermaid_png())


########################
# ์‹คํ–‰ - graph.invoke()
########################
from langchain_core.messages import HumanMessage
query = "Langgraph์— ๋Œ€ํ•ด์„œ ์•Œ๋ ค์ค˜."

init_state = {"messages":[HumanMessage(content=query)]}

resp = graph.invoke(init_state)

 

2.2 ๋ณด์™„ํ•˜๊ณ  ์‹ถ์€ ์  - tool ์“ฐ๊ธฐ 

from dotenv import load_dotenv
from langchain_tavily import TavilySearch

load_dotenv()


avily_search = TavilySearch(
    max_results=1,
    include_images=True, # ๊ฒ€์ƒ‰ํ•œ ํŽ˜์ด์ง€์˜ ์ด๋ฏธ์ง€๋“ค์˜ URL๋„ ๋ฐ˜ํ™˜.
    time_range="month", 
)
query = "๋…๋„ ํ† ๋„ˆ ๊ฐ€๊ฒฉ์— ๋Œ€ํ•ด ์•Œ๋ ค์ค˜."
resp = tavily_search.invoke(query)

print(type(resp))
resp


##### LLM MODEL์— Tool binding

from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4.1-mini")
# model์— tool(๋“ค)์„ binding(๋ถ™์—ฌ์ฃผ๊ธฐ.)
tool_model = model.bind_tools(tools=[tavily_search])
type(model), type(tool_model)

query = "๋…๋„ ํ† ๋„ˆ ๊ฐ€๊ฒฉ์— ๋Œ€ํ•ด ์•Œ๋ ค์ค˜."
resp1 = tool_model.invoke(query)

print(resp2.content)
print("------------------")
resp1.tool_calls


##### Tool calls ์ด์šฉํ•ด Tool ํ˜ธ์ถœ

# tool๊ฐ์ฒด.invoke(args)
search_result = tavily_search.invoke(resp1.tool_calls[0]['args'])

type(search_result)
search_result

##### Tool call์ด ์—ฌ๋Ÿฌ๊ฐœ์ธ ๊ฒฝ์šฐ
# Runnable์„ ํ•œ๋ฒˆ์— ์—ฌ๋Ÿฌ๋ฒˆ ํ˜ธ์ถœํ•  ๋•Œ
## Runnable.batch([์ „๋‹ฌํ• ๊ฐ’1, ์ „๋‹ฌํ• ๊ฐ’2, .....]) : [๊ฒฐ๊ณผ๊ฐ’1, ๊ฒฐ๊ณผ๊ฐ’2, ..]
resp = model.batch(["์•ˆ๋…•ํ•˜์„ธ์š”", 
                    "๋…๋„ ํ† ๋„ˆ์— ๊ฐ€๊ฒฉ์— ๋Œ€ํ•ด ์•Œ๋ ค์ค˜.", 
                    "์ด๋‹ˆ์Šคํ”„๋ฆฌ ํฌ๋ฆผ ์„ฑ๋ถ„์„ ์•Œ๋ ค์ค˜."])
                    

##### Tool ์˜ ์ฒ˜๋ฆฌ(์‘๋‹ต) ๊ฒฐ๊ณผ๋ฅผ LLM ์š”์ฒญ์‹œ ์‚ฌ์šฉ
                    
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate(
    [
        ("system", "๋‹น์‹ ์€ AI ์ •๋ณด์ œ๊ณต์ž์ž…๋‹ˆ๋‹ค. ์ œ๊ณต๋œ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”."),
        ("human", "{user_input}"),
        MessagesPlaceholder(variable_name="messages", optional=True)
    ]
)
input_dict = {"user_input":query, "messages":[resp2, *search_result3]} # messages: [AIMessage, ToolMessage, ToolMessage, ..]

final_chain = prompt | tool_model
final_response = final_chain.invoke(input_dict)
                    
print(final_response.content)