이 글은 LangChain, LangGraph, 프롬프트 엔지니어링와 RAG에 대한 개인 학습 내용을 꾸준히 정리하여 올립니다.
다음은 테디노트님의 랭체인 한국어 튜토리얼을 기반으로 개인적으로 학습한 내용을 요약 및 정리한 글입니다.
create_sql_query_chain
을 사용하여 SQL 쿼리 생성 체인 구축QuerySQLDataBaseTool
로 실행PromptTemplate
, RunnablePassthrough
, itemgetter
등을 활용한 체인 구성create_sql_agent
로 SQL Agent 생성Quiz
클래스를 정의하여 4지선다형 퀴즈 구조화ChatOpenAI
(gpt-4o 모델)과 ChatPromptTemplate
을 사용하여 퀴즈 생성with_structured_output
메서드로 구조화된 출력 생성TypedDict와 타입 시스템
TypedDict의 특징
엄격한 타입 체크
class Person(TypedDict):
name: str
age: int
job: str
일반 dict와의 차이점
# TypedDict
typed_dict: Person = {"name": "셜리", "age": 25, "job": "디자이너"}
typed_dict["age"] = "35" # 타입 체커가 오류 감지
# 일반 dict
sample_dict: Dict[str, str] = {
"name": "테디",
"age": "30", # 문자열 허용
"job": "개발자"
}
Annotated와 메타데이터
기본 구문
from typing import Annotated
name: Annotated[str, "사용자 이름"]
age: Annotated[int, "사용자 나이 (0-150)"]
Pydantic과의 통합
class Employee(BaseModel):
id: Annotated[int, Field(..., description="직원 ID")]
name: Annotated[str, Field(..., min_length=3, max_length=50)]
salary: Annotated[float, Field(gt=0, lt=10000)]
LangGraph 상태 관리
class State(TypedDict):
messages: Annotated[list, add_messages]
add_messages
리듀서로 메시지 누적def add_messages(left: list, right: list) -> list:
# 기존 메시지와 새 메시지 병합
# 동일 ID의 메시지는 새 메시지로 대체
return merged_messages
노드 시스템
기본 노드 구현
def chatbot(state: State):
answer = llm_with_tools.invoke(state["messages"])
return {"messages": [answer]}
도구 노드 구현
class BasicToolNode:
def __init__(self, tools: list) -> None:
self.tools_list = {tool.name: tool for tool in tools}
def __call__(self, inputs: dict):
message = inputs.get("messages", [])[-1]
outputs = []
for tool_call in message.tool_calls:
tool_result = self.tools_list[tool_call["name"]].invoke(
tool_call["args"]
)
outputs.append(
ToolMessage(
content=json.dumps(tool_result),
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
)
return {"messages": outputs}
조건부 라우팅 시스템
def route_tools(state: State):
if messages := state.get("messages", []):
ai_message = messages[-1]
if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
return "tools"
return END
graph_builder.add_conditional_edges(
source="chatbot",
path=route_tools,
path_map={"tools": "tools", END: END},
)
그래프 구성 및 실행
그래프 빌더 설정
graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", tool_node)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("tools", "chatbot")
실행 및 스트리밍
graph = graph_builder.compile()
for event in graph.stream({"messages": [("user", question)]}):
for key, value in event.items():
print(f"\nSTEP: {key}\n")
display_message_tree(value["messages"][-1])
도구 통합
도구 설정
from langchain_teddynote.tools.tavily import TavilySearch
tool = TavilySearch(max_results=3)
tools = [tool]
llm_with_tools = llm.bind_tools(tools)
도구 실행 결과 처리
tool_result = self.tools_list[tool_call["name"]].invoke(tool_call["args"])
tool_message = ToolMessage(
content=json.dumps(tool_result),
name=tool_call["name"],
tool_call_id=tool_call["id"]
)
builder = StateGraph(AgentState)
builder.add_node("router", router_node)
builder.add_edge('router', END)
graph = builder.compile(checkpointer=memory)
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], operator.add]
question: str
answer: str
feedback: str
def llm_node(state: AgentState):
messages = state['messages']
response = model.invoke(messages)
return {'messages': [response]}
def tool_node(state: AgentState):
tool_calls = state['messages'][-1].tool_calls
results = [tool.invoke(call.args) for call in tool_calls]
return {'messages': results}
def route_node(state: AgentState):
return state['question_type']
builder.add_conditional_edges(
"router",
route_question,
{
'DATABASE': 'database_expert',
'LANGCHAIN': 'langchain_expert',
'GENERAL': 'general_assistant'
}
)
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string(":memory:")
graph = builder.compile(checkpointer=memory)
for event in graph.stream({
'question': question,
}, thread):
for v in event.values():
v['messages'][-1].pretty_print()
라우터 에이전트
def router_node(state: MultiAgentState):
messages = [
SystemMessage(content=question_category_prompt),
HumanMessage(content=state['question'])
]
response = model.invoke(messages)
return {"question_type": response.content}
전문가 에이전트 구현
# SQL 전문가
def sql_expert_node(state: MultiAgentState):
sql_agent = create_react_agent(model, [execute_sql],
state_modifier = sql_expert_system_prompt)
messages = [HumanMessage(content=state['question'])]
result = sql_agent.invoke({"messages": messages})
return {'answer': result['messages'][-1].content}
# 검색 전문가
def search_expert_node(state: MultiAgentState):
search_agent = create_react_agent(model, [tavily_tool],
state_modifier = search_expert_system_prompt)
return {'answer': result['messages'][-1].content}
기본 구현
def human_feedback_node(state: MultiAgentState):
pass
builder.compile(checkpointer=memory, interrupt_before=['human'])
고급 구현 (에이전트 기반)
from langchain_community.tools import HumanInputRun
human_tool = HumanInputRun()
editor_agent = create_react_agent(model, [human_tool])
try:
result = tool.invoke(args)
except Exception as e:
return {'error': str(e)}
LangGraph의 핵심 구성 요소
State (상태) 관리 특징
Node (노드) 구현 세부사항
Edge (엣지) 특성
실행 메커니즘
기술적 특징
구현 예시 코드 구조
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class MyState(TypedDict):
counter: int
graph = StateGraph(MyState)
def increment(state):
return {"counter": state["counter"] + 1}
graph.add_node("increment", increment)
graph.add_edge(START, "increment")
graph.add_edge("increment", END)
app = graph.compile()
result = app.invoke({"counter": 0})