이 글은 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 ENDgraph_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: strdef 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})

