Files
Source/ollama/langgraph1.ipynb
T
cooney 13b756911f 랭체인 체인 마무리
- 병렬 처리, 비동기 병렬 처리
랭그래프
- 워크플로 프리임워크 state, Node, Edge 구조
- 조건부 엣지, 루프, 자가 검토
2026-06-08 20:10:28 +09:00

1196 lines
40 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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
}