{ "cells": [ { "metadata": {}, "cell_type": "markdown", "source": [ "#### LangGraph\n", "\n", "- 워크플로 프레임워크\n", "- LCEL 선형 (A -> B -> C) / LangGraph 순환 ( A-> B -> A -> C -> B) 흐름 지원\n", "- 개념\n", " - Node : 실행할 함수\n", " - Edge : Node 간의 연결\n", " - State : Node 간의 전체 상태를 담는 딕셔너리\n", "- 조건부 엣지, Agent 루프, 자기 수정, 멀티 에이전트 등 복잡한 팬턴 구현" ], "id": "4f173862ffa5000b" }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T10:59:55.228433274Z", "start_time": "2026-06-08T10:59:55.202226030Z" } }, "cell_type": "code", "source": "# !pip install langgraph grandalf", "id": "1ecece0e830ff1b5", "outputs": [], "execution_count": 1 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.004421946Z", "start_time": "2026-06-08T10:59:55.236638820Z" } }, "cell_type": "code", "source": [ "from langgraph.graph import StateGraph, START, END\n", "from typing import TypedDict\n", "\n", "import os\n", "\n", "from cohere.types import summarize_request_extractiveness\n", "from google.protobuf.timestamp import from_current_time\n", "from langchain_classic.chains.sequential import SequentialChain\n", "from langchain_openai import ChatOpenAI\n", "from langchain_ibm import WatsonxEmbeddings\n", "from langchain_ibm import ChatWatsonx\n", "from langchain.agents import create_agent\n", "from langchain_ollama import OllamaEmbeddings\n", "from langchain_ollama import ChatOllama\n", "from langchain_ollama import OllamaEmbeddings, ChatOllama\n", "from dotenv import load_dotenv\n", "\n", "from langchain_community.utilities import GoogleSerperAPIWrapper\n", "from langchain_core.tools import Tool\n", "from langchain_core.tools import tool\n", "\n", "from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder\n", "from langchain_core.output_parsers import StrOutputParser, JsonOutputParser, PydanticOutputParser\n", "from langchain_core.runnables import RunnablePassthrough, RunnableParallel, RunnableLambda\n", "from langgraph import graph\n", "from pygments.unistring import combine\n", "from sklearn import pipeline" ], "id": "424747aac7874d9", "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/cooney/Source/.venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n", "/tmp/ipykernel_239115/245101980.py:18: DeprecationWarning: `langchain-community` is being sunset and is no longer actively maintained. See https://github.com/langchain-ai/langchain-community/issues/674 for details and migration guidance toward standalone integration packages.\n", " from langchain_community.utilities import GoogleSerperAPIWrapper\n" ] } ], "execution_count": 2 }, { "metadata": {}, "cell_type": "markdown", "source": [ "#### TypedDIct / Pydantic\n", "- TypedDict : 딕셔너리 키 , 값 타입을 정의할 수 있게 도와줌, 단순한 State 구현 시 사용, 기본값 줄 수 없음\n", "- Pydantic : validation 검사(입력값), 검증이 필요한 State 인 경우 사용" ], "id": "ab7dfbff952ba846" }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.372424934Z", "start_time": "2026-06-08T11:00:03.059989505Z" } }, "cell_type": "code", "source": [ "# 데이터 저장소 생성\n", "# message 상태관리 할거야 => 공유\n", "class MyState(TypedDict):\n", " message:str\n", "\n", "# 작업 함수 생성(노드)\n", "def say_hello(state):\n", " # state 값의 변화\n", " return {\"message\": \"Hello, LangGraph!\"}\n", "\n", "# 그래프 생성\n", "graph = StateGraph(MyState)\n", "graph.add_node(\"hello\", say_hello)\n", "\n", "graph.add_edge(START, \"hello\")\n", "graph.add_edge(\"hello\", END)\n", "\n", "# 실행\n", "app = graph.compile()\n", "result = app.invoke({\"message\": \"\"})\n", "print(result)\n", "\n", "# 그래프 시각화(참고)\n", "app.get_graph().print_ascii()" ], "id": "977a81b9df795373", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'message': 'Hello, LangGraph!'}\n", "+-----------+ \n", "| __start__ | \n", "+-----------+ \n", " * \n", " * \n", " * \n", " +-------+ \n", " | hello | \n", " +-------+ \n", " * \n", " * \n", " * \n", " +---------+ \n", " | __end__ | \n", " +---------+ \n" ] } ], "execution_count": 3 }, { "metadata": {}, "cell_type": "markdown", "source": [ "#### State\n", "- 상태를 중심으로 동작\n", "- TypeDict, Pydantic Base Model 을 사용하여 정의\n", "- 그래프 실행 중 지속적으로 업데이트 됨\n", "- 노드 간의 전환은 조건부 엣지를 통해 제어 가능하며 복잡한 의사결정 프로세스 모델링 간으\n", "- 재귀적 실행 지원\n", "\n", "#### Node\n", "- 실제 작업을 수행하는 기본단위\n", "- 함수기반\n", "- 상태중심 : 현재 상태를 입력으로 받아 처리\n", "- 독립적 실행 : 각 노드는 독립적으로 실행\n", "- 조합 가능 : 여러 노드를 연결하여 복잡한 워크플로 가능\n", "\n", "### Edge\n", "- 노드간의 연결과 실행 흐름을 정의\n" ], "id": "65888b5803542435" }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.485999045Z", "start_time": "2026-06-08T11:00:03.389852542Z" } }, "cell_type": "code", "source": [ "# 카운터 공유\n", "\n", "class CounterState(TypedDict):\n", " count: int\n", "\n", "# 증가 함수\n", "def increment(state):\n", " print(f\"현재 카운트 : {state['count']}\")\n", " new_count = state['count'] + 1\n", " print(f\"새로운 카운트 : {new_count}\")\n", "\n", " # state 값 변경(return)\n", " return {\"count\": new_count}\n", "\n", "# 그래프 생성\n", "graph = StateGraph(CounterState)\n", "graph.add_node(\"increment\", increment)\n", "\n", "# 그래프 연결(그래프가 실행될 것인가?)\n", "graph.add_edge(START, \"increment\")\n", "graph.add_edge(\"increment\", END)\n", "\n", "# 그래프를 실행 가능한 형태로 변경\n", "app = graph.compile()\n", "result = app.invoke({\"count\": 0})\n", "print(f\"최종 결과 {result}\")\n", "\n", "# 그래프 시각화(참고)\n", "app.get_graph().print_ascii()\n", "\n" ], "id": "3acb2357ac1825d7", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "현재 카운트 : 0\n", "새로운 카운트 : 1\n", "최종 결과 {'count': 1}\n", "+-----------+ \n", "| __start__ | \n", "+-----------+ \n", " * \n", " * \n", " * \n", "+-----------+ \n", "| increment | \n", "+-----------+ \n", " * \n", " * \n", " * \n", " +---------+ \n", " | __end__ | \n", " +---------+ \n" ] } ], "execution_count": 4 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.537019587Z", "start_time": "2026-06-08T11:00:03.491230609Z" } }, "cell_type": "code", "source": [ "# 2개의 노드\n", "\n", "def first_increment(state):\n", " return {\"count\":state['count']+1}\n", "\n", "def second_increment(state):\n", " return {\"count\":state['count']+10}\n", "\n", "class CounterState(TypedDict):\n", " count: int\n", "\n", "# START => first => second => END\n", "# 그래프 생성\n", "graph = StateGraph(CounterState)\n", "graph.add_node(\"first\",first_increment)\n", "graph.add_node(\"second\", second_increment)\n", "\n", "# 그래프 연결(그래프가 실행될 것인가?)\n", "graph.add_edge(START, \"first\")\n", "graph.add_edge(\"first\", 'second')\n", "graph.add_edge('second', END)\n", "\n", "# 그래프를 실행 가능한 형태로 변경\n", "app = graph.compile()\n", "result = app.invoke({\"count\": 0})\n", "print(f\"최종 결과 {result}\")\n", "\n", "# 그래프 시각화(참고)\n", "app.get_graph().print_ascii()" ], "id": "d0b60e65d488a596", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "최종 결과 {'count': 11}\n", "+-----------+ \n", "| __start__ | \n", "+-----------+ \n", " * \n", " * \n", " * \n", " +-------+ \n", " | first | \n", " +-------+ \n", " * \n", " * \n", " * \n", " +--------+ \n", " | second | \n", " +--------+ \n", " * \n", " * \n", " * \n", " +---------+ \n", " | __end__ | \n", " +---------+ \n" ] } ], "execution_count": 5 }, { "metadata": {}, "cell_type": "markdown", "source": [ "- 조건부 엣지\n", " - 런타임 상태에 따라 동적으로 실행 경로 결정\n", " - add_conditional_edges()" ], "id": "c516f3c13d3ab6e7" }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.599429285Z", "start_time": "2026-06-08T11:00:03.548093598Z" } }, "cell_type": "code", "source": [ "# 입력숫자 > 10 => big\n", "# 입력숫자 > 10 => small\n", "\n", "# State : 숫자, 결과\n", "class NumberState(TypedDict):\n", " number : int\n", " result : str\n", "\n", "# 노드(result 값 변경)\n", "def handle_big_number(state):\n", " return {'result':f\"{state['number']}는 큰 숫자입니다.\"}\n", "\n", "def handle_small_number(state):\n", " return {'result':f\"{state['number']}는 작은 숫자입니다.\"}\n", "\n", "# 라우터(조건함수)\n", "def check_size(state):\n", " if state['number'] > 10:\n", " return \"big\"\n", " else:\n", " return \"small\"\n", "\n", "# 그래프 생성\n", "graph = StateGraph(NumberState)\n", "graph.add_node(\"big_handler\", handle_big_number)\n", "graph.add_node(\"small_handler\", handle_small_number)\n", "\n", "# 엣지\n", "graph.add_edge(\"big_handler\", END)\n", "graph.add_edge(\"small_handler\", END)\n", "\n", "# 조건부 엣지(number 값에 따라 big? small?)\n", "graph.add_conditional_edges(START, check_size, {'big':'big_handler', \"small\":\"small_handler\"})\n", "\n", "app = graph.compile()\n", "result = app.invoke({\"number\": 15, \"result\" : \"\"})\n", "print(f\"큰 숫자 {result}\")\n", "\n", "result = app.invoke({\"number\": 5, \"result\" : \"\"})\n", "print(f\"작은 숫자 {result}\")\n", "\n", "# 그래프 시각화(참고)\n", "app.get_graph().print_ascii()\n" ], "id": "acf3f40b4d866477", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "큰 숫자 {'number': 15, 'result': '15는 큰 숫자입니다.'}\n", "작은 숫자 {'number': 5, 'result': '5는 작은 숫자입니다.'}\n", " +-----------+ \n", " | __start__ | \n", " +-----------+ \n", " .. .. \n", " .. .. \n", " .. .. \n", "+-------------+ +---------------+ \n", "| big_handler | | small_handler | \n", "+-------------+ +---------------+ \n", " ** ** \n", " ** ** \n", " ** ** \n", " +---------+ \n", " | __end__ | \n", " +---------+ \n" ] } ], "execution_count": 6 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.650967440Z", "start_time": "2026-06-08T11:00:03.606842460Z" } }, "cell_type": "code", "source": [ "# 홀, 짝\n", "\n", "# State : 숫자, 결과\n", "class NumberState(TypedDict):\n", " number : int\n", " result : str\n", "\n", "# 노드(result 값 변경)\n", "def even_node(state):\n", " return {'result':f\"{state['number']}는 짝수입니다.\"}\n", "\n", "def odd_node(state):\n", " return {'result':f\"{state['number']}는 홀수입니다.\"}\n", "\n", "# 라우터(조건함수)\n", "def check_number(state):\n", " if state['number'] % 2 == 0:\n", " return \"even\"\n", " else:\n", " return \"odd\"\n", "\n", "# 그래프 생성\n", "graph = StateGraph(NumberState)\n", "graph.add_node(\"even_handler\", even_node)\n", "graph.add_node(\"odd_handler\", odd_node)\n", "\n", "# 엣지\n", "graph.add_edge(\"even_handler\", END)\n", "graph.add_edge(\"odd_handler\", END)\n", "\n", "# 조건부 엣지(number 값에 따라 even? odd)\n", "graph.add_conditional_edges(START, check_number, {'even':'even_handler', \"odd\":\"odd_handler\"})\n", "\n", "app = graph.compile()\n", "result = app.invoke({\"number\": 15, \"result\" : \"\"})\n", "print(f\"홀수 {result}\")\n", "\n", "result = app.invoke({\"number\": 6, \"result\" : \"\"})\n", "print(f\"짝수 {result}\")\n", "\n", "# 그래프 시각화(참고)\n", "app.get_graph().print_ascii()\n" ], "id": "6900d17fc36539a3", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "홀수 {'number': 15, 'result': '15는 홀수입니다.'}\n", "짝수 {'number': 6, 'result': '6는 짝수입니다.'}\n", " +-----------+ \n", " | __start__ | \n", " +-----------+ \n", " .. .. \n", " .. .. \n", " .. .. \n", "+--------------+ +-------------+ \n", "| even_handler | | odd_handler | \n", "+--------------+ +-------------+ \n", " ** ** \n", " ** ** \n", " ** ** \n", " +---------+ \n", " | __end__ | \n", " +---------+ \n" ] } ], "execution_count": 7 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.705048993Z", "start_time": "2026-06-08T11:00:03.656007893Z" } }, "cell_type": "code", "source": [ "class ScoreState(TypedDict):\n", " score:int\n", "\n", "# 노드 return => 상태값 변경\n", "# return 을 안하면 None\n", "def grade_a(state):\n", " print(\"A 학점\")\n", " # score = None\n", " return {}\n", "\n", "def grade_b(state):\n", " print(\"B 학점\")\n", " return {}\n", "\n", "def grade_c(state):\n", " print(\"C 학점\")\n", " return {}\n", "\n", "# >=90 A, >=80 B, 나머지C,\n", "# 라우터(조건함수)\n", "def route_grade(state):\n", " if state['score'] >= 90:\n", " return \"A\"\n", " elif state['score'] >= 80:\n", " return \"B\"\n", " else:\n", " return \"C\"\n", "\n", "graph = StateGraph(ScoreState)\n", "graph.add_node(\"grade_a\", grade_a)\n", "graph.add_node(\"grade_b\", grade_b)\n", "graph.add_node(\"grade_c\", grade_c)\n", "\n", "graph.add_edge(\"grade_a\", END)\n", "graph.add_edge(\"grade_b\", END)\n", "graph.add_edge(\"grade_c\", END)\n", "\n", "graph.add_conditional_edges(START, route_grade, {'A':'grade_a', \"B\":\"grade_b\", \"C\":\"grade_c\"})\n", "\n", "app = graph.compile()\n", "result = app.invoke({\"score\": 95, \"result\" : \"\"})\n", "print(f\"학점 {result}\")\n", "\n", "result = app.invoke({\"score\": 88, \"result\" : \"\"})\n", "print(f\"학점 {result}\")\n", "\n", "# app.get_graph().print_ascii()" ], "id": "3ec626fba58ea942", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A 학점\n", "학점 {'score': 95}\n", "B 학점\n", "학점 {'score': 88}\n" ] } ], "execution_count": 8 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.767196417Z", "start_time": "2026-06-08T11:00:03.711901025Z" } }, "cell_type": "code", "source": [ "# 상태관리 : text, sentiment, result\n", "# text : 오늘 기분이 너무 좋아\n", "# analyze_sentiment() : 감정평가 text 좋 positive / 싫 negative / x neutral\n", "# positive_node() : 긍정 의견 / negative_node() : 부정 의견 / neutral_node() : 중립 의견 => result 업데이트\n", "# route_sentiment : return state['sentiment']\n", "\n", "# START => analyze => positive / negative / neutral => route_sentiment => positive_node / negative_node / neutral_node => END\n", "\n", "class SentimentState(TypedDict):\n", " text : str # 입력 문장\n", " sentiment : str # 감정 결과 (positive / negative / neutral)\n", " result : str # 최종 출력 메시지\n", "\n", "def analyze_sentiment(state):\n", " text = state[\"text\"]\n", "\n", " if \"좋\" in text:\n", " sentiment = \"positive\"\n", " elif \"싫\" in text:\n", " sentiment = \"negative\"\n", " else:\n", " sentiment = \"neutral\"\n", "\n", " return {\"sentiment\": sentiment}\n", "\n", "def positive_node(state):\n", " return {\"result\": \"긍정 의견\"}\n", "\n", "def negative_node(state):\n", " return {\"result\": \"부정 의견\"}\n", "\n", "def neutral_node(state):\n", " return {\"result\": \"중립 의견\"}\n", "\n", "# 라우터(조건함수)\n", "def route_sentiment(state):\n", " return state['sentiment']\n", "\n", "graph = StateGraph(SentimentState)\n", "graph.add_node(\"analyze\", analyze_sentiment)\n", "graph.add_node(\"positive\", positive_node)\n", "graph.add_node(\"negative\", negative_node)\n", "graph.add_node(\"neutral\", neutral_node)\n", "\n", "graph.add_edge(START, \"analyze\")\n", "graph.add_edge(\"positive\", END)\n", "graph.add_edge(\"negative\", END)\n", "graph.add_edge(\"neutral\", END)\n", "\n", "graph.add_conditional_edges(\"analyze\", route_sentiment, {'positive':'positive', \"negative\":\"negative\", \"neutral\":\"neutral\"}) # 라우터 결과가 'positive'면 ->️ 'positive' 노드로 가라!\n", "\n", "app = graph.compile()\n", "result = app.invoke({\"text\": \"좋\"})\n", "print(f\"{result}\")\n", "\n", "result = app.invoke({\"text\": \"싫\"})\n", "print(f\"{result}\")\n", "\n", "result = app.invoke({\"text\": \"밥\"})\n", "print(f\"{result}\")\n", "\n", "app.get_graph().print_ascii()" ], "id": "94202953b8b8be5f", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'text': '좋', 'sentiment': 'positive', 'result': '긍정 의견'}\n", "{'text': '싫', 'sentiment': 'negative', 'result': '부정 의견'}\n", "{'text': '밥', 'sentiment': 'neutral', 'result': '중립 의견'}\n", " +-----------+ \n", " | __start__ | \n", " +-----------+ \n", " * \n", " * \n", " * \n", " +---------+ \n", " | analyze | \n", " ...+---------+.... \n", " .... . .... \n", " .... . .... \n", " .. . .. \n", "+----------+ +---------+ +----------+ \n", "| negative | | neutral | | positive | \n", "+----------+**** +---------+ ***+----------+ \n", " **** * **** \n", " **** * **** \n", " ** * ** \n", " +---------+ \n", " | __end__ | \n", " +---------+ \n" ] } ], "execution_count": 9 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:03.794644656Z", "start_time": "2026-06-08T11:00:03.778761416Z" } }, "cell_type": "code", "source": [ "# .env 내용 가져오기\n", "load_dotenv()\n", "\n", "apikey = os.getenv(\"WATSONX_API_KEY\")\n", "project_id = os.getenv(\"WATSONX_PROJECT_ID\")\n", "watsonx_ai_url = os.getenv(\"WATSONX_URL\")\n", "hf_token = os.getenv(\"HF_TOKEN\")\n", "COHERE_API_KEY = os.getenv(\"COHERE_API_KEY\")\n", "SERPER_API_KEY = os.getenv(\"SERPER_API_KEY\")\n", "GEMINI_API_KEY = os.getenv(\"GEMINI_API_KEY\")\n", "LANGSMITH_API_KEY = os.getenv(\"LANGSMITH_API_KEY\")" ], "id": "c2b2990c27f33682", "outputs": [], "execution_count": 10 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:00:11.384010221Z", "start_time": "2026-06-08T11:00:03.798592437Z" } }, "cell_type": "code", "source": [ "watson_llm = ChatWatsonx(\n", " model_id=\"ibm/granite-4-h-small\",\n", " url=f\"{watsonx_ai_url}\",\n", " api_key=f\"{apikey}\",\n", " project_id=f\"{project_id}\",\n", " max_tokens=2000,\n", " params={\n", " \"temperature\": 0\n", " }\n", ")\n", "\n", "watson_embedding = WatsonxEmbeddings(\n", " model_id=\"ibm/granite-embedding-278m-multilingual\",\n", " url=f\"{watsonx_ai_url}\",\n", " api_key=f\"{apikey}\",\n", " project_id=f\"{project_id}\"\n", ")\n", "\n", "hugging_llm = ChatOpenAI(\n", " model=\"Qwen/Qwen2.5-7B-Instruct:together\",\n", " api_key=hf_token,\n", " base_url=\"https://router.huggingface.co/v1\",\n", " temperature=0,\n", ")\n", "\n", "ollama_embedding = OllamaEmbeddings(model=\"nomic-embed-text-v2-moe\")\n", "\n", "# 로컬 LLM\n", "qwen_llm = ChatOllama(model=\"qwen3.5:4b\", temperature=0)\n", "exaone_llm = ChatOllama(model=\"exaone3.5:2.4b\", temperature=0)\n", "gemma_llm = ChatOllama(model=\"gemma4:e2b\")" ], "id": "f5fdb0bea4930484", "outputs": [], "execution_count": 11 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:03:06.386132558Z", "start_time": "2026-06-08T11:03:04.111106960Z" } }, "cell_type": "code", "source": [ "parser = StrOutputParser()\n", "\n", "translate_chain = ChatPromptTemplate.from_messages([\n", " (\"system\", \"다음 텍스트를 한국어로 번역하세요. 번역문만 출력:\\n{text}.\"),\n", "]) | watson_llm | parser\n", "\n", "summarize_chain = ChatPromptTemplate.from_messages([\n", " (\"system\", \"다음 텍스트를 3문장으로 요약하세요.:\\n{text}.\"),\n", "]) | watson_llm | parser\n", "\n", "sentiment_chain = ChatPromptTemplate.from_messages([\n", " (\"system\", \"다음 텍스트를 의 감정을 긍정/부정/중립 중 하나로만 답하세요.:\\n{summary}.\"),\n", "]) | watson_llm | parser\n", "\n", "# State 정의\n", "class AnalysisState(TypedDict):\n", " text: str\n", " translated: str\n", " summary: str\n", " sentiment: str\n", " done: bool\n", "\n", "# node 정의\n", "def translated_node(state):\n", " result = translate_chain.invoke({\"text\":state['text']})\n", " return {\"translated\":result}\n", "\n", "def summary_node(state):\n", " result = summarize_chain.invoke({'text':state['translated']})\n", " return {\"summary\":result}\n", "\n", "def sentiment_node(state):\n", " result = sentiment_chain.invoke({'summary':state['summary']})\n", " return {\"sentiment\":result}\n", "\n", "# 그래프 생성\n", "graph = StateGraph(AnalysisState)\n", "graph.add_node(\"translate\", translated_node)\n", "graph.add_node(\"summarize\", summary_node)\n", "graph.add_node(\"sentiment\", sentiment_node)\n", "\n", "graph.add_edge(START, \"translate\")\n", "graph.add_edge(\"translate\", \"summarize\")\n", "graph.add_edge(\"summarize\", \"sentiment\")\n", "graph.add_edge(\"sentiment\", END)\n", "\n", "# 그래프 실행\n", "app = graph.compile()\n", "result = app.invoke({'text': 'Python is grate!!', \"done\":False})\n", "print(\"번역 : \", result['translated'])\n", "print(\"요약 : \", result['summary'])\n", "print(\"감정 : \", result['sentiment'])\n", "\n", "app.get_graph().print_ascii()" ], "id": "75b0102055a3b951", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "번역 : 파이썬은 대단해요!!\n", "요약 : 파이썬은 대단한 프로그래밍 언어입니다. 파이썬은 간결하고 읽기 쉬운 문법을 가지고 있어 초보자에게도 적합합니다. 또한 다양한 라이브러리와 프레임워크를 제공하여 다양한 분야에서 활용될 수 있습니다.\n", "감정 : 긍정\n", "+-----------+ \n", "| __start__ | \n", "+-----------+ \n", " * \n", " * \n", " * \n", "+-----------+ \n", "| translate | \n", "+-----------+ \n", " * \n", " * \n", " * \n", "+-----------+ \n", "| summarize | \n", "+-----------+ \n", " * \n", " * \n", " * \n", "+-----------+ \n", "| sentiment | \n", "+-----------+ \n", " * \n", " * \n", " * \n", " +---------+ \n", " | __end__ | \n", " +---------+ \n" ] } ], "execution_count": 14 }, { "metadata": { "ExecuteTime": { "end_time": "2026-06-08T11:07:53.195678876Z", "start_time": "2026-06-08T11:07:51.827957635Z" } }, "cell_type": "code", "source": [ "# 조건부엣지 : summary >= 100자 이상 good / 100자 미만 poor\n", "\n", "class SummaryState(TypedDict):\n", " text:str\n", " summary:str\n", " quality:str\n", " retries:int\n", "\n", "def summary_node(state):\n", " result = summarize_chain.invoke({'text':state['text']})\n", " return {\"summary\":result}\n", "\n", "# quality\n", "def check_quality_node(state):\n", " # summary >= 100 자 이상 good / 100자 미만 poor\n", " quality = \"good\" if len(state['summary']) >= 100 else \"poor\"\n", " return {\"quality\":quality}\n", "\n", "def retry_node(state):\n", " return {\"text\":state['text']+\"\\n 더 자세히 요약하세요\", \"retries\":state['retries'] + 1}\n", "\n", "def route_by_quality(state):\n", " if state['quality'] == 'poor' and state['retries'] < 3:\n", " return \"retry\"\n", " return \"done\"\n", "\n", "graph = StateGraph(SummaryState)\n", "graph.add_node(\"summary\", summary_node)\n", "graph.add_node(\"check_quality\", check_quality_node)\n", "graph.add_node(\"retry\", retry_node)\n", "\n", "# START -> summary -> check_quality\n", "graph.add_edge(START, \"summary\")\n", "graph.add_edge(\"summary\", \"check_quality\")\n", "graph.add_conditional_edges(\"check_quality\", route_by_quality, {\"retry\": \"retry\", \"done\" : END})\n", "graph.add_edge(\"retry\", \"summary\")\n", "\n", "# 그래프 실행\n", "app = graph.compile()\n", "result = app.invoke({'text': '잛은 글'})\n", "print(f\"최종요약({len(result['summary'])}자): {result['summary'][:100]}\")\n", "\n", "app.get_graph().print_ascii()\n" ], "id": "b5a849ba6c73ae41", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "최종요약(150자): 제공된 텍스트는 질문이 없는 한 문장으로, 요약할 내용이 없습니다. 그러나 이 문장을 3문장으로 요약하면 다음과 같습니다:\n", "\n", "1. 이 프롬프트는 요약을 요청하는 것입니다.\n", "2. 하\n", " +-----------+ \n", " | __start__ | \n", " +-----------+ \n", " * \n", " * \n", " * \n", " +---------+ \n", " | summary | \n", " +---------+ \n", " *** *** \n", " * * \n", " ** *** \n", "+---------------+ * \n", "| check_quality | * \n", "+---------------+. * \n", " . ..... * \n", " . ... * \n", " . ... * \n", " +---------+ +-------+ \n", " | __end__ | | retry | \n", " +---------+ +-------+ \n" ] } ], "execution_count": 17 }, { "metadata": {}, "cell_type": "code", "source": [ "class VerifyState(TypedDict):\n", " question:str\n", " answer:str\n", " feedback:str\n", " is_verified:bool\n", " attempt:int\n", "\n", "def generate(state):\n", " \"\"\"답변을 생성합니다. 이전 피드백이 있으면 반영합니다.\"\"\"\n", " prompt = f\"질문 : {state['question']}\"\n", "\n", " if state['feedback']:\n", " prompt += f\"\\n\\n이전 답변의 피드백 : {state['feedback']}\\n위 피드백을 반영하여 개선된 답변을 작성하세요\"\n", "\n", " result = watson_llm.invoke(prompt)\n", " return {'answer':result.content, \"attempt\":state['attempt'] + 1}\n", "\n", "def verify(state):\n", " \"\"\"답변의 정확성과 완전성을 검증합니다.\"\"\"\n", " verification = watson_llm.invoke(f\"\"\"\n", "다음 답변의 정확성과 완정성을 검증하세요\n", "\n", "질문 :\n", "{state['question']}\n", "\n", "답변 :\n", "{state['answer']}\n", "\n", "정확하고 완전하면 첫 줄에 'PASS' 를, 수정이 필요하면 첫줄에 'FAIL' 을 쓰고 구체적인 개선사항을 설명하세요.\n", "\"\"\")\n", "\n", " content = verification.content\n", " is_pass = content.strip().startswith('PASS')\n", "\n", " return {'is_verified':is_pass, 'feedback':content}\n", "\n", "# router 개념\n", "def should_retry(state):\n", " \"\"\"검증 통과 또는 최대 횟수 도달시 종료\"\"\"\n", " if state['is_verified'] or state['attempt'] >= 3:\n", " return END\n", " return 'generate'\n", "\n", "# 질문 => LLM 답변 생성 => LLM 답변 검증 => 검증통과\n", "# 검증 미통과 => LLM 답변 생성\n", "\n", "graph = StateGraph(VerifyState)\n", "graph.add_node(\"generate\", generate)\n", "graph.add_node(\"verify\", verify)\n", "\n", "graph.add_edge(START, \"generate\")\n", "graph.add_edge(\"generate\", \"verify\")\n", "graph.add_conditional_edges(\"verify\", should_retry, {\"generate\": \"generate\", END: END})\n", "\n", "# 그래프 실행\n", "app = graph.compile()\n", "result = app.invoke({'question': '파이썬에서 GIL이 무엇이며 멀티쓰레딩에 어떤 영향을 주는지 설명해줘', \"answer\":\"\",\"feedback\":\"\", \"is_verified\":False, \"attempt\":0})\n", "\n", "print(\"시도 횟수\", result['attempt'])\n", "print(\"검증통과\", result['is_verified'])\n", "print(\"최종답변\", result['answer'][:300])\n", "\n", "app.get_graph().print_ascii()" ], "id": "686634fe1625b815", "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "import time\n", "from langchain_core.runnables import RunnableParallel\n", "\n", "class AnalysisState(TypedDict):\n", " text:str\n", " translated:str\n", " summary:str\n", " keywords:list[str]\n", "\n", "translate_chain = ChatPromptTemplate.from_messages([\n", " (\"system\", \"다음 텍스트를 한국어로 번역하세요. 번역문만 출력:\\n{text}.\"),\n", "]) | watson_llm | parser\n", "\n", "summarize_chain = ChatPromptTemplate.from_messages([\n", " (\"system\", \"다음 텍스트를 3문장으로 번역하세요.:\\n{text}.\"),\n", "]) | watson_llm | parser\n", "\n", "keyword_chain = ChatPromptTemplate.from_messages([\n", " (\"system\", \"키워드 5개 추출:\\n{text}.\"),\n", "]) | watson_llm | parser\n", "\n", "def translate_node(state):\n", " print(\"번역 시작\")\n", " time.sleep(3)\n", " print(\"번역 종료\")\n", " return {\"translated\": translate_chain.invoke({\"text\":state['text']})}\n", "\n", "def summarize_node(state):\n", " print(\"요약 시작\")\n", " time.sleep(3)\n", " print(\"요약 종료\")\n", " return {\"summary\": summarize_chain.invoke({\"text\":state['text']})}\n", "\n", "def keyword_node(state):\n", " print(\"키워드 추출 시작\")\n", " time.sleep(3)\n", " print(\"키워드 추출 종료\")\n", " return {\"keywords\": keyword_chain.invoke({\"text\":state['text']})}\n", "\n", "graph = StateGraph(AnalysisState)\n", "graph.add_node('translated', translated_node)\n", "graph.add_node('summary', summarize_node)\n", "graph.add_node('keywords', keyword_node)\n", "\n", "graph.add_edge(START, \"translated\")\n", "graph.add_edge(START, \"summary\")\n", "graph.add_edge(START, \"keywords\")\n", "\n", "graph.add_edge(\"translated\", END)\n", "graph.add_edge(\"summary\", END)\n", "graph.add_edge(\"keywords\", END)\n", "\n", "app = graph.compile()\n", "text = {'text' : 'Python is a versatile language used in AI and web development'}\n", "result = app.invoke(text)\n", "\n", "print(\"번역\", result['translated'][:100])\n", "print(\"요약\", result['summary'][:100])\n", "print(\"키워드\", result['keywords'][:100])\n", "\n", "app.get_graph().print_ascii()" ], "id": "956e5a8f89675559", "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "markdown", "source": [ "- rag LangGraph\n", " - 사용자 질문 => retrieve => generate => END" ], "id": "e9270ebbd217d878" }, { "metadata": {}, "cell_type": "code", "source": [ "# !pip install langchain_community\n", "from langchain_community.document_loaders import PyPDFLoader, CSVLoader, WebBaseLoader, DirectoryLoader\n", "from youtube_transcript_api import YouTubeTranscriptApi\n", "from langchain_core.documents import Document\n", "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", "from langchain_ollama import OllamaEmbeddings\n", "from langchain_ibm import WatsonxEmbeddings\n", "from langchain_chroma import Chroma\n", "from langchain_community.vectorstores import FAISS" ], "id": "d03e2b9989d53cb2", "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "# STEP 1 : 문서로드\n", "loader = PyPDFLoader(\"./data/Summary of ChatGPTGPT-4 Research.pdf\")\n", "# STEP 2 : 문서분할\n", "splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)\n", "chunks = splitter.split_documents(loader.load())\n", "print(f\"chunks 수 {chunks}\")\n", "# STEP 3 : 인덱싱 - 임베딩\n", "# STEP 4 : 벡터스토어(Chroma or FAISS)\n", "vectorstore = Chroma.from_documents(chunks, watson_embedding, persist_directory=\"./db/chroma_db\", collection_name=\"research\")\n", "# STEP 5 : as_retriever() : Vector Store 를 Retriever 형태로 변환하여 LangChain 에 연결\n", "retriever = vectorstore.as_retriever(search_type=\"similarity\", search_kwargs={\"k\":3})" ], "id": "1f0fda34f40177ea", "outputs": [], "execution_count": null }, { "metadata": {}, "cell_type": "code", "source": [ "from typing import List\n", "from langchain_core.documents import Document\n", "\n", "class RAGState(TypedDict):\n", " query:str\n", " retriever_docs:list[Document]\n", " answer:str\n", "\n", "def retrieve(state):\n", " # 기존의 벡터스토어에 질의\n", " vectorstore = Chroma(collection_name=\"research\", embedding_function=watson_embedding, persist_directory=\"./db/chroma_db\")\n", "\n", " docs = vectorstore.similarity_search(state['query'], k=3)\n", "\n", " return {\"retriever_docs\" : docs}\n", "\n", "def generate(state):\n", " context = \"\\n\\n\".join(doc.page_content for doc in state['retriever_docs'])\n", "\n", " prompt = \"\"\"\\\n", "다음 컨텍스트를 참고하여 질문에 답하세요.\n", "컨텍스트에 없는 내용은 모른다고 답하세요.\n", "\n", "컨텍스트:\n", "{context}\n", "질문:\n", "{query}\n", "\"\"\"\n", " response = watson_llm.invoke(prompt.format(context=context, query=state['query']))\n", " return {\"answer\":response.content}\n", "\n", "graph = StateGraph(RAGState)\n", "graph.add_node(\"retrieve\", retrieve)\n", "graph.add_node(\"generate\", generate)\n", "\n", "graph.add_edge(START, \"retrieve\")\n", "graph.add_edge(\"retrieve\", \"generate\")\n", "graph.add_edge(\"generate\", END)\n", "\n", "app = graph.compile()\n", "result = app.invoke({\"query\":\"where can i use chatGPT?\"})\n", "print(result['answer'])" ], "id": "bcf8f539b91ab2b7", "outputs": [], "execution_count": null } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 }