diff --git a/05-Memory/01-LangGraph-Add-Memory.ipynb b/05-Memory/01-LangGraph-Add-Memory.ipynb index 8798689..3fc3267 100644 --- a/05-Memory/01-LangGraph-Add-Memory.ipynb +++ b/05-Memory/01-LangGraph-Add-Memory.ipynb @@ -2,1115 +2,812 @@ "cells": [ { "cell_type": "markdown", + "id": "cell-0", "metadata": {}, "source": [ - "# ๐Ÿง  LangGraph ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ\n", + "# LangGraph ๋ฉ”๋ชจ๋ฆฌ ์ถ”๊ฐ€\n", "\n", - "## ๐Ÿ“š ๊ฐœ์š”\n", + "LangGraph์—์„œ `๋ฉ”๋ชจ๋ฆฌ(Memory)`๋Š” ์—์ด์ „ํŠธ๊ฐ€ ์ด์ „ ๋Œ€ํ™” ๋‚ด์šฉ์„ ๊ธฐ์–ตํ•˜๊ณ  ๋งฅ๋ฝ์— ๋งž๋Š” ์‘๋‹ต์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ์—์ด์ „ํŠธ๋Š” ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ๋Œ€ํ™”๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜์—ฌ ์ผ๊ด€๋œ ๋‹ค์ค‘ ํ„ด(multi-turn) ๋Œ€ํ™”๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.\n", "\n", - "AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ง„์ •ํ•œ ๊ฐ€์น˜๋ฅผ ์ œ๊ณตํ•˜๋ ค๋ฉด **๋ฉ”๋ชจ๋ฆฌ(Memory)**๊ฐ€ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. LangGraph๋Š” ๋‘ ๊ฐ€์ง€ ๊ฐ•๋ ฅํ•œ ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:\n", + "LangGraph๋Š” **Checkpointer**๋ฅผ ํ†ตํ•ด ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜ํ”„๋ฅผ ์ปดํŒŒ์ผํ•  ๋•Œ checkpointer๋ฅผ ์ œ๊ณตํ•˜๊ณ , ๊ทธ๋ž˜ํ”„๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ `thread_id`๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๊ฐ ์‹คํ–‰ ๋‹จ๊ณ„ ํ›„ ์ƒํƒœ๊ฐ€ ์ž๋™์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ `thread_id`๋กœ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๋ฉด ์ €์žฅ๋œ ์ƒํƒœ๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์ด์ „ ๋Œ€ํ™”๋ฅผ ์ด์–ด์„œ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", "\n", - "1. **๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(Short-term Memory)**: ๋Œ€ํ™” ์„ธ์…˜ ๋‚ด์—์„œ ์ปจํ…์ŠคํŠธ ์œ ์ง€\n", - "2. **์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(Long-term Memory)**: ์„ธ์…˜์„ ๋„˜์–ด ์‚ฌ์šฉ์ž๋ณ„ ์ •๋ณด ์ €์žฅ\n", - "\n", - "## ๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ\n", - "\n", - "์ด ํŠœํ† ๋ฆฌ์–ผ์„ ์™„๋ฃŒํ•˜๋ฉด ๋‹ค์Œ์„ ๋งˆ์Šคํ„ฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค:\n", - "\n", - "1. **๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ** ๊ตฌํ˜„ - Checkpointer๋ฅผ ํ™œ์šฉํ•œ ๋Œ€ํ™” ์ง€์†์„ฑ\n", - "2. **์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ** ๊ตฌ์ถ• - Store๋ฅผ ํ™œ์šฉํ•œ ์˜๊ตฌ ๋ฐ์ดํ„ฐ ์ €์žฅ\n", - "3. **๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ** ์ „๋žต - ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ, ์š”์•ฝ, ์‚ญ์ œ\n", - "4. **์‹œ๋งจํ‹ฑ ๊ฒ€์ƒ‰** - ์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ ๋ฉ”๋ชจ๋ฆฌ ๊ฒ€์ƒ‰\n", - "5. **ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ** - PostgreSQL, Redis ๋“ฑ ์‹ค์ œ ํ™˜๊ฒฝ ์ ์šฉ\n", - "\n", - "## ๐Ÿ”‘ ํ•ต์‹ฌ ๊ฐœ๋… ๋ฏธ๋ฆฌ๋ณด๊ธฐ\n", - "\n", - "```\n", - "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n", - "โ”‚ LangGraph Memory System โ”‚\n", - "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n", - "โ”‚ Short-term Memory โ”‚ Long-term Memory โ”‚\n", - "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n", - "โ”‚ โ€ข Thread-level โ”‚ โ€ข User-level โ”‚\n", - "โ”‚ โ€ข Checkpointer โ”‚ โ€ข Store โ”‚\n", - "โ”‚ โ€ข Multi-turn chats โ”‚ โ€ข Persistent data โ”‚\n", - "โ”‚ โ€ข Session context โ”‚ โ€ข Cross-session โ”‚\n", - "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n", - "```\n", - "\n", - "## ๐Ÿ’ก ์ค‘์š” ์›์น™\n", - "\n", - "> **\"๋ฉ”๋ชจ๋ฆฌ๋Š” AI ์—์ด์ „ํŠธ๋ฅผ ๋‹จ์ˆœํ•œ ๋„๊ตฌ์—์„œ ์ง€๋Šฅ์ ์ธ ํŒŒํŠธ๋„ˆ๋กœ ๋ณ€ํ™”์‹œํ‚ต๋‹ˆ๋‹ค\"**\n", - "> \n", - "> _Memory transforms AI agents from simple tools to intelligent partners_" + "> ์ฐธ๊ณ  ๋ฌธ์„œ: [LangGraph Persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/)" ] }, { "cell_type": "markdown", + "id": "cell-1", "metadata": {}, "source": [ - "## ํ™˜๊ฒฝ ์„ค์ •" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์ž„ํฌํŠธ\n", - "import os\n", - "from dotenv import load_dotenv\n", + "## ํ•™์Šต ๋ชฉํ‘œ\n", "\n", - "# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ\n", - "load_dotenv()\n", + "์ด ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” ๋‹ค์Œ ๋‚ด์šฉ์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค:\n", "\n", - "# API ํ‚ค ์„ค์ •\n", - "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\", \"your-api-key\")\n", - "\n", - "print(\"โœ… ํ™˜๊ฒฝ ์„ค์ • ์™„๋ฃŒ!\")" + "- MemorySaver ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ ์ƒํƒœ ์ €์žฅ\n", + "- thread_id๋ฅผ ํ†ตํ•œ ๋Œ€ํ™” ์„ธ์…˜ ๊ด€๋ฆฌ\n", + "- ์ €์žฅ๋œ ์ƒํƒœ(์Šค๋ƒ…์ƒท) ์กฐํšŒ\n", + "- ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ์„ ํ†ตํ•œ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ\n", + "- ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ์˜ ์ฒดํฌํฌ์ธํ„ฐ ์„ ํƒ" ] }, { "cell_type": "markdown", + "id": "cell-2", "metadata": {}, "source": [ - "---\n", + "## ํ™˜๊ฒฝ ์„ค์ •\n", "\n", - "# Part 1: ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ (Short-term Memory) ๐ŸŽฏ\n", + "LangGraph ํŠœํ† ๋ฆฌ์–ผ์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ํ•„์š”ํ•œ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. `dotenv`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API ํ‚ค๋ฅผ ๋กœ๋“œํ•˜๊ณ , `langchain_teddynote`์˜ ๋กœ๊น… ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜์—ฌ LangSmith์—์„œ ์‹คํ–‰ ์ถ”์ ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", - "## 1.1 ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ž€?\n", - "\n", - "๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋Š” **๋Œ€ํ™” ์„ธ์…˜ ๋‚ด์—์„œ** ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค:\n", - "\n", - "- **Thread ๊ธฐ๋ฐ˜**: ๊ฐ ๋Œ€ํ™”๋Š” ๊ณ ์œ ํ•œ thread_id๋กœ ์‹๋ณ„\n", - "- **Checkpointer ์‚ฌ์šฉ**: ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ณต์›\n", - "- **Multi-turn ๋Œ€ํ™”**: ์ด์ „ ๋Œ€ํ™” ๋‚ด์šฉ์„ ๊ธฐ์–ต\n", - "\n", - "### ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ: Checkpointer\n", + "์•„๋ž˜ ์ฝ”๋“œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋กœ๋“œํ•˜๊ณ  LangSmith ํ”„๋กœ์ ํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# API ํ‚ค๋ฅผ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ • ํŒŒ์ผ\n", + "from dotenv import load_dotenv\n", "\n", - "Checkpointer๋Š” ๊ทธ๋ž˜ํ”„์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ณต์›ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” `InMemorySaver`๋ฅผ, ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ฐ˜ Checkpointer๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค." + "# API ํ‚ค ์ •๋ณด ๋กœ๋“œ\n", + "load_dotenv(override=True)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, + "id": "cell-4", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LangSmith ์ถ”์ ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.\n", + "[ํ”„๋กœ์ ํŠธ๋ช…]\n", + "LangGraph-V1-Tutorial\n" + ] + } + ], "source": [ - "from langgraph.checkpoint.memory import InMemorySaver\n", - "from langgraph.graph import StateGraph, MessagesState, START, END\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "# LLM ์ดˆ๊ธฐํ™”\n", - "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", - "\n", - "# Checkpointer ์ƒ์„ฑ - ๋ฉ”๋ชจ๋ฆฌ์— ์ƒํƒœ๋ฅผ ์ €์žฅ\n", - "checkpointer = InMemorySaver()\n", - "\n", - "\n", - "# ๊ฐ„๋‹จํ•œ ์ฑ—๋ด‡ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ\n", - "def call_model(state: MessagesState):\n", - " \"\"\"Call the LLM with the current messages\"\"\"\n", - " # ํ˜„์žฌ ๋ฉ”์‹œ์ง€๋กœ LLM ํ˜ธ์ถœ\n", - " response = llm.invoke(state[\"messages\"])\n", - " # ์‘๋‹ต์„ ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€\n", - " return {\"messages\": response}\n", + "# LangSmith ์ถ”์ ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. https://smith.langchain.com\n", + "from langchain_teddynote import logging\n", "\n", - "\n", - "# ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(\"call_model\", call_model)\n", - "builder.add_edge(START, \"call_model\")\n", - "builder.add_edge(\"call_model\", END)\n", - "\n", - "# Checkpointer์™€ ํ•จ๊ป˜ ์ปดํŒŒ์ผ - ํ•ต์‹ฌ!\n", - "graph = builder.compile(checkpointer=checkpointer)\n", - "\n", - "print(\"โœ… ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์™„๋ฃŒ!\")" + "# ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.\n", + "logging.langsmith(\"LangGraph-V1-Tutorial\")" ] }, { "cell_type": "markdown", + "id": "cell-5", "metadata": {}, "source": [ - "## 1.2 ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ์‹ค์Šต: Multi-turn ๋Œ€ํ™”\n", + "---\n", "\n", - "์ด์ œ ๊ฐ™์€ thread์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ ๋Œ€ํ™”๋ฅผ ๋‚˜๋ˆ„๋ฉฐ ๋ด‡์ด ์ด์ „ ๋Œ€ํ™”๋ฅผ ๊ธฐ์–ตํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค." + "## MemorySaver ์ฒดํฌํฌ์ธํ„ฐ\n", + "\n", + "์ฒดํฌํฌ์ธํ„ฐ(Checkpointer)๋Š” ๊ทธ๋ž˜ํ”„์˜ ๊ฐ ๋‹จ๊ณ„์—์„œ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์—ฌ, ์ดํ›„ ๋™์ผํ•œ ๋Œ€ํ™”๋ฅผ ์ด์–ด์„œ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. `MemorySaver`๋Š” ์ธ๋ฉ”๋ชจ๋ฆฌ ์ฒดํฌํฌ์ธํ„ฐ๋กœ, ๋ฉ”๋ชจ๋ฆฌ์— ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋ฏ€๋กœ ๊ฐœ๋ฐœ ๋ฐ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ์—๋„ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ `PostgresSaver`๋‚˜ `SqliteSaver` ๊ฐ™์€ ์˜๊ตฌ ์ €์žฅ์†Œ ๊ธฐ๋ฐ˜ ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” `MemorySaver` ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, + "id": "cell-6", "metadata": {}, "outputs": [], "source": [ - "# Thread ID ์„ค์ • - ๋Œ€ํ™” ์„ธ์…˜ ์‹๋ณ„์ž\n", - "config = {\"configurable\": {\"thread_id\": \"conversation_1\"}} # ๊ณ ์œ ํ•œ ๋Œ€ํ™” ์‹๋ณ„์ž\n", + "from langgraph.checkpoint.memory import MemorySaver\n", "\n", - "# ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€ - ์ž๊ธฐ์†Œ๊ฐœ\n", - "print(\"๐Ÿ‘ค User: ์•ˆ๋…•! ๋‚˜๋Š” ์ฒ ์ˆ˜์•ผ\")\n", - "result = graph.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"์•ˆ๋…•! ๋‚˜๋Š” ์ฒ ์ˆ˜์•ผ\"}]},\n", - " config, # thread_id ์ „๋‹ฌ\n", - ")\n", - "print(f\"๐Ÿค– Bot: {result['messages'][-1].content}\\n\")\n", - "\n", - "# ๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€ - ์ด๋ฆ„ ํ™•์ธ\n", - "print(\"๐Ÿ‘ค User: ๋‚ด ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ง€?\")\n", - "result = graph.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"๋‚ด ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ง€?\"}]},\n", - " config, # ๊ฐ™์€ thread_id ์‚ฌ์šฉ\n", - ")\n", - "print(f\"๐Ÿค– Bot: {result['messages'][-1].content}\\n\")\n", - "\n", - "# ๋‹ค๋ฅธ thread๋กœ ํ…Œ์ŠคํŠธ - ๋ฉ”๋ชจ๋ฆฌ ๋ถ„๋ฆฌ ํ™•์ธ\n", - "config_2 = {\"configurable\": {\"thread_id\": \"conversation_2\"}}\n", - "\n", - "print(\"--- ์ƒˆ๋กœ์šด ๋Œ€ํ™” ์„ธ์…˜ ---\")\n", - "print(\"๐Ÿ‘ค User: ๋‚ด ์ด๋ฆ„์ด ๋ญ์•ผ?\")\n", - "result = graph.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"๋‚ด ์ด๋ฆ„์ด ๋ญ์•ผ?\"}]},\n", - " config_2, # ๋‹ค๋ฅธ thread_id\n", - ")\n", - "print(f\"๐Ÿค– Bot: {result['messages'][-1].content}\")\n", - "print(\"\\n๐Ÿ’ก ๋‹ค๋ฅธ thread์—์„œ๋Š” ์ด์ „ ๋Œ€ํ™”๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค!\")" + "# ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ ์ƒ์„ฑ\n", + "memory = MemorySaver()" ] }, { "cell_type": "markdown", + "id": "cell-7", "metadata": {}, "source": [ - "## 1.3 ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ: PostgreSQL Checkpointer\n", + "---\n", + "\n", + "## ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์žˆ๋Š” ์ฑ—๋ด‡ ๊ตฌ์ถ•\n", + "\n", + "์ด์ œ ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€ํ™” ๊ธฐ๋ก์„ ์ €์žฅํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์ฑ—๋ด‡์„ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. ์ฑ—๋ด‡์€ ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•„ LLM์— ์ „๋‹ฌํ•˜๊ณ , ์‘๋‹ต์„ ์ƒํƒœ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ฒดํฌํฌ์ธํ„ฐ๊ฐ€ ๊ฐ ๋‹จ๊ณ„์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋ฏ€๋กœ ๋™์ผํ•œ `thread_id`๋กœ ํ˜ธ์ถœํ•˜๋ฉด ์ด์ „ ๋Œ€ํ™” ๋‚ด์šฉ์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.\n", + "\n", + "### State ์ •์˜\n", "\n", - "์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ฐ˜ Checkpointer๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” PostgreSQL ์˜ˆ์ œ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค." + "State๋Š” ๊ทธ๋ž˜ํ”„ ์ „์ฒด์—์„œ ๊ณต์œ ๋˜๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. `messages` ํ•„๋“œ์— `add_messages` ๋ฆฌ๋“€์„œ๋ฅผ ์ ์šฉํ•˜๋ฉด, ์ƒˆ ๋ฉ”์‹œ์ง€๊ฐ€ ๊ธฐ์กด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ณ  ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋Œ€ํ™” ์ด๋ ฅ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” State๋ฅผ ์ •์˜ํ•˜๊ณ , ์ฑ—๋ด‡ ๋…ธ๋“œ ํ•จ์ˆ˜์™€ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 4, + "id": "cell-8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์™„๋ฃŒ!\n" + ] + } + ], "source": [ - "# PostgreSQL Checkpointer ์˜ˆ์ œ (์‹ค์ œ ์‹คํ–‰ ์‹œ DB ์—ฐ๊ฒฐ ํ•„์š”)\n", - "from typing import Dict, Any\n", + "from typing import Annotated\n", + "from typing_extensions import TypedDict\n", + "from langchain_openai import ChatOpenAI\n", + "from langgraph.graph import StateGraph, START, END\n", + "from langgraph.graph.message import add_messages\n", "\n", "\n", - "def create_production_graph():\n", - " \"\"\"Create a production-ready graph with PostgreSQL checkpointer\"\"\"\n", + "# State ์ •์˜: ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌ\n", + "class State(TypedDict):\n", + " \"\"\"์ฑ—๋ด‡์˜ ์ƒํƒœ๋ฅผ ์ •์˜ํ•˜๋Š” ํƒ€์ž…\n", "\n", - " # ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ๋Š” ์•„๋ž˜ ์ฃผ์„์„ ํ•ด์ œํ•˜๊ณ  ์‚ฌ์šฉ\n", - " from langgraph.checkpoint.postgres import PostgresSaver\n", + " messages: ๋Œ€ํ™” ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ\n", + " - add_messages ๋ฆฌ๋“€์„œ๋ฅผ ํ†ตํ•ด ์ƒˆ ๋ฉ”์‹œ์ง€๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค\n", + " \"\"\"\n", "\n", - " DB_URI = \"postgresql://postgres:postgres@localhost:5432/mydb\"\n", + " messages: Annotated[list, add_messages]\n", "\n", - " with PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n", - " # ์ฒซ ์‹คํ–‰ ์‹œ ํ…Œ์ด๋ธ” ์ƒ์„ฑ\n", - " # checkpointer.setup()\n", "\n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(\"call_model\", call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " builder.add_edge(\"call_model\", END)\n", + "# LLM ์ดˆ๊ธฐํ™”\n", + "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", "\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " return graph\n", "\n", - " print(\"๐Ÿ“ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ์ฝ”๋“œ ์˜ˆ์ œ:\")\n", - " print(\n", - " \"\"\"\n", - " DB_URI = \"postgresql://user:pass@host:port/db\"\n", - " \n", - " with PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n", - " graph = builder.compile(checkpointer=checkpointer)\n", + "# ์ฑ—๋ด‡ ๋…ธ๋“œ ํ•จ์ˆ˜ ์ •์˜\n", + "def chatbot(state: State):\n", + " \"\"\"์ฑ—๋ด‡ ๋…ธ๋“œ ํ•จ์ˆ˜\n", + "\n", + " ํ˜„์žฌ ์ƒํƒœ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•„ LLM์— ์ „๋‹ฌํ•˜๊ณ ,\n", + " ์‘๋‹ต์„ ์ƒˆ ๋ฉ”์‹œ์ง€๋กœ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n", " \"\"\"\n", - " )\n", - " return None\n", + " # LLM ํ˜ธ์ถœ ๋ฐ ์‘๋‹ต ๋ฐ˜ํ™˜\n", + " response = llm.invoke(state[\"messages\"])\n", + " return {\"messages\": [response]}\n", + "\n", + "\n", + "# StateGraph ์ƒ์„ฑ\n", + "graph_builder = StateGraph(State)\n", + "\n", + "# ๋…ธ๋“œ ์ถ”๊ฐ€\n", + "graph_builder.add_node(\"chatbot\", chatbot)\n", + "\n", + "# ์—ฃ์ง€ ์ถ”๊ฐ€\n", + "graph_builder.add_edge(START, \"chatbot\")\n", + "graph_builder.add_edge(\"chatbot\", END)\n", "\n", + "# ์ฒดํฌํฌ์ธํ„ฐ์™€ ํ•จ๊ป˜ ์ปดํŒŒ์ผ\n", + "graph = graph_builder.compile(checkpointer=memory)\n", "\n", - "create_production_graph()" + "print(\"๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์™„๋ฃŒ!\")" ] }, { "cell_type": "markdown", + "id": "cell-9", "metadata": {}, "source": [ - "## 1.4 Subgraph์—์„œ์˜ ๋ฉ”๋ชจ๋ฆฌ\n", + "### ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", "\n", - "์„œ๋ธŒ๊ทธ๋ž˜ํ”„๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋ถ€๋ชจ ๊ทธ๋ž˜ํ”„์—๋งŒ Checkpointer๋ฅผ ์„ค์ •ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค." + "`langchain_teddynote.graphs` ๋ชจ๋“ˆ์˜ `visualize_graph()` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ทธ๋ž˜ํ”„ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ฒดํฌํฌ์ธํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด๋„ ๊ทธ๋ž˜ํ”„์˜ ๊ตฌ์กฐ ์ž์ฒด๋Š” ๋™์ผํ•˜๋ฉฐ, ์ฐจ์ด์ ์€ ๊ฐ ๋…ธ๋“œ ์‹คํ–‰ ์‹œ ์ƒํƒœ๊ฐ€ ์ž๋™์œผ๋กœ ์ €์žฅ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, + "id": "cell-10", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "from typing_extensions import TypedDict\n", - "\n", - "\n", - "class SubgraphState(TypedDict):\n", - " \"\"\"State for subgraph example\"\"\"\n", - "\n", - " message: str\n", - " counter: int\n", - "\n", - "\n", - "# ์„œ๋ธŒ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ\n", - "def create_subgraph():\n", - " \"\"\"Create a subgraph\"\"\"\n", - "\n", - " def subgraph_node(state: SubgraphState):\n", - " # ์นด์šดํ„ฐ ์ฆ๊ฐ€\n", - " return {\n", - " \"message\": state[\"message\"] + \" (์„œ๋ธŒ๊ทธ๋ž˜ํ”„ ์ฒ˜๋ฆฌ)\",\n", - " \"counter\": state[\"counter\"] + 1,\n", - " }\n", - "\n", - " subgraph_builder = StateGraph(SubgraphState)\n", - " subgraph_builder.add_node(\"process\", subgraph_node)\n", - " subgraph_builder.add_edge(START, \"process\")\n", - " subgraph_builder.add_edge(\"process\", END)\n", - "\n", - " # ์„œ๋ธŒ๊ทธ๋ž˜ํ”„๋Š” checkpointer ์—†์ด ์ปดํŒŒ์ผ\n", - " return subgraph_builder.compile()\n", - "\n", - "\n", - "# ๋ถ€๋ชจ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ\n", - "def main_node(state: SubgraphState):\n", - " \"\"\"Main graph node\"\"\"\n", - " return {\n", - " \"message\": state[\"message\"] + \" (๋ฉ”์ธ ์ฒ˜๋ฆฌ)\",\n", - " \"counter\": state[\"counter\"] + 10,\n", - " }\n", + "from langchain_teddynote.graphs import visualize_graph\n", "\n", - "\n", - "# ์„œ๋ธŒ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ\n", - "subgraph = create_subgraph()\n", - "\n", - "# ๋ฉ”์ธ ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ\n", - "main_builder = StateGraph(SubgraphState)\n", - "main_builder.add_node(\"main\", main_node)\n", - "main_builder.add_node(\"subgraph\", subgraph) # ์„œ๋ธŒ๊ทธ๋ž˜ํ”„๋ฅผ ๋…ธ๋“œ๋กœ ์ถ”๊ฐ€\n", - "\n", - "main_builder.add_edge(START, \"main\")\n", - "main_builder.add_edge(\"main\", \"subgraph\")\n", - "main_builder.add_edge(\"subgraph\", END)\n", - "\n", - "# ๋ถ€๋ชจ ๊ทธ๋ž˜ํ”„๋งŒ checkpointer์™€ ํ•จ๊ป˜ ์ปดํŒŒ์ผ\n", - "parent_checkpointer = InMemorySaver()\n", - "parent_graph = main_builder.compile(checkpointer=parent_checkpointer)\n", - "\n", - "# ์‹คํ–‰\n", - "result = parent_graph.invoke(\n", - " {\"message\": \"์‹œ์ž‘\", \"counter\": 0}, {\"configurable\": {\"thread_id\": \"sub_test\"}}\n", - ")\n", - "\n", - "print(f\"โœ… ์„œ๋ธŒ๊ทธ๋ž˜ํ”„ ์‹คํ–‰ ๊ฒฐ๊ณผ:\")\n", - "print(f\" ๋ฉ”์‹œ์ง€: {result['message']}\")\n", - "print(f\" ์นด์šดํ„ฐ: {result['counter']}\")\n", - "print(\"\\n๐Ÿ’ก ๋ถ€๋ชจ ๊ทธ๋ž˜ํ”„์˜ checkpointer๊ฐ€ ์„œ๋ธŒ๊ทธ๋ž˜ํ”„์—๋„ ์ž๋™ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค!\")" + "# ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", + "visualize_graph(graph)" ] }, { "cell_type": "markdown", + "id": "cell-11", "metadata": {}, "source": [ "---\n", "\n", - "# Part 2: ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ (Long-term Memory) ๐Ÿ’พ\n", - "\n", - "## 2.1 ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ž€?\n", - "\n", - "์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋Š” **์„ธ์…˜์„ ๋„˜์–ด์„œ** ์‚ฌ์šฉ์ž๋ณ„ ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค:\n", + "## ๋ฉ€ํ‹ฐํ„ด ๋Œ€ํ™” ํ…Œ์ŠคํŠธ\n", "\n", - "- **์‚ฌ์šฉ์ž ํ”„๋กœํ•„**: ์„ ํ˜ธ๋„, ์„ค์ •, ๊ฐœ์ธ ์ •๋ณด\n", - "- **๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ**: ๊ณผ๊ฑฐ ์ƒํ˜ธ์ž‘์šฉ ๊ธฐ๋ก\n", - "- **ํ•™์Šต๋œ ์ •๋ณด**: ์‹œ์Šคํ…œ์ด ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ํ•™์Šตํ•œ ๋‚ด์šฉ\n", + "์ด์ œ ๊ฐ™์€ `thread_id`๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ๋Œ€ํ™”๋ฅผ ๋‚˜๋ˆ„๋ฉฐ ์ฑ—๋ด‡์ด ์ด์ „ ๋Œ€ํ™”๋ฅผ ๊ธฐ์–ตํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. `RunnableConfig`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `thread_id`๋ฅผ ์„ค์ •ํ•˜๋ฉด, ํ•ด๋‹น ์Šค๋ ˆ๋“œ์˜ ๋Œ€ํ™” ๊ธฐ๋ก์ด ์ฒดํฌํฌ์ธํ„ฐ์— ์ €์žฅ๋˜๊ณ  ๋ถˆ๋Ÿฌ์™€์ง‘๋‹ˆ๋‹ค.\n", "\n", - "### ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ: Store\n", - "\n", - "Store๋Š” key-value ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์˜๊ตฌ ์ €์žฅํ•˜๋Š” ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค." + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์ฒซ ๋ฒˆ์งธ ๋Œ€ํ™”์—์„œ ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ๊ณ , ๋‘ ๋ฒˆ์งธ ๋Œ€ํ™”์—์„œ ์ด๋ฆ„์„ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, + "id": "cell-12", "metadata": {}, "outputs": [], "source": [ - "from langgraph.store.memory import InMemoryStore\n", "from langchain_core.runnables import RunnableConfig\n", - "from langgraph.store.base import BaseStore\n", - "import uuid\n", - "\n", - "# ๋ฉ”๋ชจ๋ฆฌ ์Šคํ† ์–ด ์ƒ์„ฑ\n", - "store = InMemoryStore()\n", - "\n", - "\n", - "# ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ๊ทธ๋ž˜ํ”„\n", - "class UserState(MessagesState):\n", - " \"\"\"State with user context\"\"\"\n", - "\n", - " user_id: str\n", - "\n", - "\n", - "def chat_with_memory(\n", - " state: UserState, config: RunnableConfig, *, store: BaseStore # store ์ฃผ์ž…\n", - "):\n", - " \"\"\"Chat function with long-term memory\"\"\"\n", - "\n", - " # ์‚ฌ์šฉ์ž ID ๊ฐ€์ ธ์˜ค๊ธฐ\n", - " user_id = config[\"configurable\"].get(\"user_id\", \"default_user\")\n", "\n", - " # ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ •์˜ - ์‚ฌ์šฉ์ž๋ณ„ ๋ฉ”๋ชจ๋ฆฌ ๋ถ„๋ฆฌ\n", - " namespace = (\"users\", user_id)\n", - "\n", - " # ๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€ ํ™•์ธ\n", - " last_message = state[\"messages\"][-1]\n", - "\n", - " # \"๊ธฐ์–ตํ•ด\" ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ์ €์žฅ\n", - " if \"๊ธฐ์–ตํ•ด\" in last_message.content:\n", - " # ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•  ๋‚ด์šฉ ์ถ”์ถœ\n", - " memory_content = last_message.content.replace(\"๊ธฐ์–ตํ•ด:\", \"\").strip()\n", - " # Store์— ์ €์žฅ\n", - " store.put(namespace, str(uuid.uuid4()), {\"memory\": memory_content})\n", - "\n", - " return {\n", - " \"messages\": [\n", - " {\n", - " \"role\": \"assistant\",\n", - " \"content\": f\"์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. '{memory_content}'๋ฅผ ๊ธฐ์–ตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.\",\n", - " }\n", - " ]\n", - " }\n", - "\n", - " # \"๋ญ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด?\" ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ์กฐํšŒ\n", - " elif \"๋ญ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด\" in last_message.content:\n", - " # ์ €์žฅ๋œ ๋ฉ”๋ชจ๋ฆฌ ๊ฒ€์ƒ‰\n", - " memories = store.search(namespace, query=\"*\") # ๋ชจ๋“  ๋ฉ”๋ชจ๋ฆฌ ์กฐํšŒ\n", - "\n", - " if memories:\n", - " memory_list = [item.value[\"memory\"] for item in memories]\n", - " response = \"์ œ๊ฐ€ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ๋Š” ๋‚ด์šฉ:\\n\" + \"\\n\".join(\n", - " f\"โ€ข {m}\" for m in memory_list\n", - " )\n", - " else:\n", - " response = \"์•„์ง ๊ธฐ์–ตํ•˜๊ณ  ์žˆ๋Š” ๋‚ด์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค.\"\n", - "\n", - " return {\"messages\": [{\"role\": \"assistant\", \"content\": response}]}\n", - "\n", - " # ์ผ๋ฐ˜ ๋Œ€ํ™”\n", - " else:\n", - " # ๊ธฐ์กด ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ปจํ…์ŠคํŠธ๋กœ ์‚ฌ์šฉ\n", - " memories = store.search(namespace, query=\"*\")\n", - " context = \"\"\n", - " if memories:\n", - " context = \"\\n\".join([item.value[\"memory\"] for item in memories])\n", - " system_prompt = f\"๋‹น์‹ ์€ ๋„์›€์ด ๋˜๋Š” ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ์•Œ๊ณ  ์žˆ๋Š” ์ •๋ณด: {context}\"\n", - " else:\n", - " system_prompt = \"๋‹น์‹ ์€ ๋„์›€์ด ๋˜๋Š” ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค.\"\n", - "\n", - " # LLM ํ˜ธ์ถœ\n", - " messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n", - " response = llm.invoke(messages)\n", - "\n", - " return {\"messages\": response}\n", - "\n", - "\n", - "# ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ\n", - "memory_builder = StateGraph(UserState)\n", - "memory_builder.add_node(\"chat\", chat_with_memory)\n", - "memory_builder.add_edge(START, \"chat\")\n", - "memory_builder.add_edge(\"chat\", END)\n", - "\n", - "# Store์™€ Checkpointer ๋ชจ๋‘ ์‚ฌ์šฉ\n", - "memory_graph = memory_builder.compile(\n", - " checkpointer=InMemorySaver(), store=store # ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ # ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ\n", - ")\n", - "\n", - "print(\"โœ… ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์™„๋ฃŒ!\")" + "# Config ์„ค์ •: thread_id๋กœ ๋Œ€ํ™” ์„ธ์…˜์„ ๊ตฌ๋ถ„\n", + "config = RunnableConfig(\n", + " recursion_limit=10, # ์ตœ๋Œ€ ๋ฐฉ๋ฌธ ๋…ธ๋“œ ์ˆ˜\n", + " configurable={\"thread_id\": \"conversation_1\"}, # ๋Œ€ํ™” ์„ธ์…˜ ID\n", + ")" ] }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2.2 ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ์‹ค์Šต: ์„ธ์…˜ ๊ฐ„ ์ •๋ณด ์œ ์ง€" + "cell_type": "code", + "execution_count": 7, + "id": "cell-13", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User: ์•ˆ๋…•ํ•˜์„ธ์š”! ์ €๋Š” ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\n", + "Bot: ์•ˆ๋…•ํ•˜์„ธ์š”, ์ฒ ์ˆ˜๋‹˜! ์–ด๋–ป๊ฒŒ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n" + ] + } + ], + "source": [ + "# ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€: ์ž๊ธฐ์†Œ๊ฐœ\n", + "print(\"User: ์•ˆ๋…•ํ•˜์„ธ์š”! ์ €๋Š” ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\")\n", + "\n", + "result = graph.invoke(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": \"์•ˆ๋…•ํ•˜์„ธ์š”! ์ €๋Š” ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\"}]},\n", + " config,\n", + ")\n", + "\n", + "print(f\"Bot: {result['messages'][-1].content}\")" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ์ฒซ ๋ฒˆ์งธ ์„ธ์…˜ - ์ •๋ณด ์ €์žฅ\n", - "config_session1 = {\"configurable\": {\"thread_id\": \"session_1\", \"user_id\": \"user_123\"}}\n", - "\n", - "print(\"=== ์„ธ์…˜ 1: ์ •๋ณด ์ €์žฅ ===\")\n", - "print(\"\\n๐Ÿ‘ค User: ๊ธฐ์–ตํ•ด: ๋‚ด ์ด๋ฆ„์€ ๊น€์ฒ ์ˆ˜์ด๊ณ  ๊ฐœ๋ฐœ์ž์•ผ\")\n", - "result = memory_graph.invoke(\n", - " {\n", - " \"messages\": [\n", - " {\"role\": \"user\", \"content\": \"๊ธฐ์–ตํ•ด: ๋‚ด ์ด๋ฆ„์€ ๊น€์ฒ ์ˆ˜์ด๊ณ  ๊ฐœ๋ฐœ์ž์•ผ\"}\n", - " ]\n", - " },\n", - " config_session1,\n", - ")\n", - "print(f\"๐Ÿค– Bot: {result['messages'][-1].content}\")\n", + "execution_count": 8, + "id": "cell-14", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User: ์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\n", + "Bot: ๋‹น์‹ ์˜ ์ด๋ฆ„์€ ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์งˆ๋ฌธ์ด๋‚˜ ๋„์›€์ด ํ•„์š”ํ•˜์‹ ๊ฐ€์š”?\n" + ] + } + ], + "source": [ + "# ๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€: ์ด๋ฆ„ ํ™•์ธ (์ด์ „ ๋Œ€ํ™” ๊ธฐ์–ต ํ™•์ธ)\n", + "print(\"User: ์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\")\n", "\n", - "print(\"\\n๐Ÿ‘ค User: ๊ธฐ์–ตํ•ด: ๋‚˜๋Š” ํŒŒ์ด์ฌ์„ ์ข‹์•„ํ•ด\")\n", - "result = memory_graph.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"๊ธฐ์–ตํ•ด: ๋‚˜๋Š” ํŒŒ์ด์ฌ์„ ์ข‹์•„ํ•ด\"}]},\n", - " config_session1,\n", - ")\n", - "print(f\"๐Ÿค– Bot: {result['messages'][-1].content}\")\n", - "\n", - "# ๋‘ ๋ฒˆ์งธ ์„ธ์…˜ - ๋‹ค๋ฅธ thread_id์ง€๋งŒ ๊ฐ™์€ user_id\n", - "config_session2 = {\n", - " \"configurable\": {\n", - " \"thread_id\": \"session_2\", # ๋‹ค๋ฅธ ์„ธ์…˜\n", - " \"user_id\": \"user_123\", # ๊ฐ™์€ ์‚ฌ์šฉ์ž\n", - " }\n", - "}\n", - "\n", - "print(\"\\n=== ์„ธ์…˜ 2: ์ƒˆ๋กœ์šด ๋Œ€ํ™” (๋‹ค๋ฅธ thread_id) ===\")\n", - "print(\"\\n๐Ÿ‘ค User: ๋ญ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด?\")\n", - "result = memory_graph.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"๋ญ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด?\"}]}, config_session2\n", + "result = graph.invoke(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": \"์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\"}]},\n", + " config, # ๊ฐ™์€ thread_id ์‚ฌ์šฉ\n", ")\n", - "print(f\"๐Ÿค– Bot: {result['messages'][-1].content}\")\n", "\n", - "print(\"\\n๐Ÿ’ก ๋‹ค๋ฅธ ์„ธ์…˜์—์„œ๋„ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ธฐ์–ตํ•ฉ๋‹ˆ๋‹ค!\")" + "print(f\"Bot: {result['messages'][-1].content}\")" ] }, { "cell_type": "markdown", + "id": "cell-15", "metadata": {}, "source": [ - "## 2.3 Tool์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ\n", + "### ๋‹ค๋ฅธ thread_id๋กœ ํ…Œ์ŠคํŠธ\n", + "\n", + "`thread_id`๊ฐ€ ๋‹ค๋ฅด๋ฉด ๋ณ„๋„์˜ ๋Œ€ํ™” ์„ธ์…˜์œผ๋กœ ์ทจ๊ธ‰๋˜๋ฏ€๋กœ, ์ด์ „ ๋Œ€ํ™” ๋‚ด์šฉ์„ ๊ธฐ์–ตํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž ๋˜๋Š” ์—ฌ๋Ÿฌ ๋Œ€ํ™” ์„ธ์…˜์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", "\n", - "์—์ด์ „ํŠธ์˜ ๋„๊ตฌ(Tool)์—์„œ๋„ ๋ฉ”๋ชจ๋ฆฌ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ๋‹ค๋ฅธ `thread_id`๋กœ ๊ฐ™์€ ์งˆ๋ฌธ์„ ํ•ด๋ด…๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Annotated\n", - "from langgraph.prebuilt import InjectedState\n", - "from langchain_core.tools import tool\n", - "\n", - "\n", - "# Tool์—์„œ State ์ ‘๊ทผ ์˜ˆ์ œ\n", - "class AgentState(MessagesState):\n", - " \"\"\"State for agent with tools\"\"\"\n", - "\n", - " user_preference: str\n", - "\n", - "\n", - "@tool\n", - "def get_user_preference(\n", - " state: Annotated[AgentState, InjectedState], # State ์ฃผ์ž…\n", - ") -> str:\n", - " \"\"\"Get user preference from state\"\"\"\n", - " preference = state.get(\"user_preference\", \"์—†์Œ\")\n", - " return f\"์‚ฌ์šฉ์ž ์„ ํ˜ธ๋„: {preference}\"\n", - "\n", - "\n", - "@tool\n", - "def update_user_preference(\n", - " new_preference: str, state: Annotated[AgentState, InjectedState]\n", - ") -> str:\n", - " \"\"\"Update user preference in state\"\"\"\n", - " # Tool์—์„œ state ์—…๋ฐ์ดํŠธ๋Š” Command๋ฅผ ํ†ตํ•ด ์ˆ˜ํ–‰\n", - " return f\"์„ ํ˜ธ๋„๋ฅผ '{new_preference}'๋กœ ์—…๋ฐ์ดํŠธํ–ˆ์Šต๋‹ˆ๋‹ค.\"\n", - "\n", - "\n", - "# ๋„๊ตฌ ์‚ฌ์šฉ ์˜ˆ์ œ\n", - "def agent_with_tools(state: AgentState):\n", - " \"\"\"Agent that can use tools\"\"\"\n", - " # ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋งŒ ๋ณด์—ฌ์คŒ\n", - " last_message = state[\"messages\"][-1].content\n", - "\n", - " if \"์„ ํ˜ธ๋„\" in last_message:\n", - " # Tool ํ˜ธ์ถœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜\n", - " tool_result = get_user_preference.invoke({\"state\": state})\n", - " return {\"messages\": [{\"role\": \"assistant\", \"content\": tool_result}]}\n", + "execution_count": 9, + "id": "cell-16", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- ์ƒˆ๋กœ์šด ๋Œ€ํ™” ์„ธ์…˜ (thread_id: conversation_2) ---\n", + "User: ์ œ ์ด๋ฆ„์ด ๋ญ์˜ˆ์š”?\n", + "Bot: ์ฃ„์†กํ•˜์ง€๋งŒ, ๋‹น์‹ ์˜ ์ด๋ฆ„์„ ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ๊ทธ์— ๋งž์ถฐ ๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!\n", + "\n", + "๋‹ค๋ฅธ thread์—์„œ๋Š” ์ด์ „ ๋Œ€ํ™”๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.\n" + ] + } + ], + "source": [ + "# ๋‹ค๋ฅธ thread_id๋กœ Config ์„ค์ •\n", + "config_2 = RunnableConfig(\n", + " recursion_limit=10,\n", + " configurable={\"thread_id\": \"conversation_2\"}, # ๋‹ค๋ฅธ ์„ธ์…˜\n", + ")\n", "\n", - " return {\"messages\": [{\"role\": \"assistant\", \"content\": \"๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\"}]}\n", + "print(\"--- ์ƒˆ๋กœ์šด ๋Œ€ํ™” ์„ธ์…˜ (thread_id: conversation_2) ---\")\n", + "print(\"User: ์ œ ์ด๋ฆ„์ด ๋ญ์˜ˆ์š”?\")\n", "\n", + "result = graph.invoke(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": \"์ œ ์ด๋ฆ„์ด ๋ญ์˜ˆ์š”?\"}]},\n", + " config_2,\n", + ")\n", "\n", - "print(\"โœ… Tool์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ํŒจํ„ด ์ •์˜ ์™„๋ฃŒ!\")\n", - "print(\"\\n๐Ÿ’ก InjectedState๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Tool์—์„œ state์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\")" + "print(f\"Bot: {result['messages'][-1].content}\")\n", + "print(\"\\n๋‹ค๋ฅธ thread์—์„œ๋Š” ์ด์ „ ๋Œ€ํ™”๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.\")" ] }, { "cell_type": "markdown", + "id": "cell-17", "metadata": {}, "source": [ "---\n", "\n", - "# Part 3: ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ์ „๋žต ๐Ÿ”ง\n", + "## ์ €์žฅ๋œ ์ƒํƒœ(์Šค๋ƒ…์ƒท) ํ™•์ธ\n", "\n", - "## 3.1 ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ (Trimming)\n", + "์ฒดํฌํฌ์ธํ„ฐ๋Š” ๊ทธ๋ž˜ํ”„ ์‹คํ–‰์˜ ๊ฐ ๋‹จ๊ณ„์—์„œ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. `get_state()` ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • `thread_id`์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šค๋ƒ…์ƒท์—๋Š” ํ˜„์žฌ ์ƒํƒœ ๊ฐ’(values), ์„ค์ • ์ •๋ณด(config), ๋‹ค์Œ ๋…ธ๋“œ(next) ๋“ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.\n", "\n", - "๊ธด ๋Œ€ํ™”์—์„œ LLM์˜ ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ ์ œํ•œ์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค." + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์ €์žฅ๋œ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•˜๊ณ  ๊ฐ ์†์„ฑ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, + "id": "cell-18", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์ €์žฅ๋œ ๋ฉ”์‹œ์ง€:\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”! ์ €๋Š” ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”, ์ฒ ์ˆ˜๋‹˜! ์–ด๋–ป๊ฒŒ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "๋‹น์‹ ์˜ ์ด๋ฆ„์€ ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์งˆ๋ฌธ์ด๋‚˜ ๋„์›€์ด ํ•„์š”ํ•˜์‹ ๊ฐ€์š”?\n" + ] + } + ], + "source": [ + "# ์ฒซ ๋ฒˆ์งธ thread์˜ ์ƒํƒœ ์กฐํšŒ\n", + "snapshot = graph.get_state(config)\n", + "\n", + "# ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ ํ™•์ธ\n", + "print(\"์ €์žฅ๋œ ๋ฉ”์‹œ์ง€:\")\n", + "for msg in snapshot.values[\"messages\"]:\n", + " msg.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cell-19", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Config ์ •๋ณด:\n", + "{'configurable': {'thread_id': 'conversation_1', 'checkpoint_ns': '', 'checkpoint_id': '1f101af6-c18b-66a8-8004-52adef3f3c97'}}\n" + ] + } + ], "source": [ - "from langchain_core.messages import trim_messages, HumanMessage, AIMessage\n", - "\n", - "\n", - "# ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ ์˜ˆ์ œ\n", - "def demonstrate_trimming():\n", - " \"\"\"Demonstrate message trimming strategies\"\"\"\n", - "\n", - " # ๊ธด ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜\n", - " messages = [\n", - " HumanMessage(content=\"์•ˆ๋…•ํ•˜์„ธ์š”\"),\n", - " AIMessage(content=\"์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\"),\n", - " HumanMessage(content=\"๋‚ ์”จ๊ฐ€ ์–ด๋•Œ์š”?\"),\n", - " AIMessage(content=\"์˜ค๋Š˜์€ ๋ง‘์€ ๋‚ ์”จ์ž…๋‹ˆ๋‹ค.\"),\n", - " HumanMessage(content=\"์ถ”์ฒœ ์Œ์‹์ด ์žˆ๋‚˜์š”?\"),\n", - " AIMessage(content=\"ํŒŒ์Šคํƒ€๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.\"),\n", - " HumanMessage(content=\"๋ ˆ์‹œํ”ผ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”\"),\n", - " AIMessage(content=\"ํ† ๋งˆํ†  ํŒŒ์Šคํƒ€ ๋ ˆ์‹œํ”ผ์ž…๋‹ˆ๋‹ค...\"),\n", - " HumanMessage(content=\"๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค\"),\n", - " AIMessage(content=\"์ฒœ๋งŒ์—์š”!\"),\n", - " ]\n", - "\n", - " print(f\"์›๋ณธ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(messages)}\\n\")\n", - "\n", - " # ์ „๋žต 1: ์ตœ๊ทผ N๊ฐœ ๋ฉ”์‹œ์ง€๋งŒ ์œ ์ง€\n", - " trimmed_last = trim_messages(\n", - " messages,\n", - " strategy=\"last\",\n", - " max_tokens=100, # ๋Œ€๋žต 100 ํ† ํฐ๋งŒ ์œ ์ง€\n", - " start_on=\"human\", # ์‚ฌ๋žŒ ๋ฉ”์‹œ์ง€๋กœ ์‹œ์ž‘\n", - " end_on=(\"human\", \"ai\"), # ์‚ฌ๋žŒ ๋˜๋Š” AI ๋ฉ”์‹œ์ง€๋กœ ๋\n", - " )\n", - "\n", - " print(\"์ „๋žต 1 - ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์œ ์ง€:\")\n", - " for msg in trimmed_last:\n", - " role = \"User\" if isinstance(msg, HumanMessage) else \"Bot\"\n", - " print(f\" {role}: {msg.content[:30]}...\")\n", - "\n", - " # ์ „๋žต 2: ์ฒซ ๋ฉ”์‹œ์ง€์™€ ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์œ ์ง€\n", - " trimmed_mixed = trim_messages(\n", - " messages,\n", - " strategy=\"first\",\n", - " max_tokens=100,\n", - " include_system=False,\n", - " )\n", - "\n", - " print(\"\\n์ „๋žต 2 - ์ฒซ ๋ฉ”์‹œ์ง€ ์œ ์ง€:\")\n", - " for msg in trimmed_mixed:\n", - " role = \"User\" if isinstance(msg, HumanMessage) else \"Bot\"\n", - " print(f\" {role}: {msg.content[:30]}...\")\n", - "\n", - " return trimmed_last\n", - "\n", - "\n", - "trimmed = demonstrate_trimming()\n", - "print(f\"\\nโœ… ํŠธ๋ฆฌ๋ฐ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(trimmed)}\")" + "# ์„ค์ • ์ •๋ณด ํ™•์ธ\n", + "print(\"Config ์ •๋ณด:\")\n", + "print(snapshot.config)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, + "id": "cell-20", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๋‹ค์Œ ๋…ธ๋“œ: ()\n" + ] + } + ], "source": [ - "# ๊ทธ๋ž˜ํ”„์—์„œ ํŠธ๋ฆฌ๋ฐ ์ ์šฉ\n", - "class TrimmedState(MessagesState):\n", - " \"\"\"State with message trimming\"\"\"\n", - "\n", - " pass\n", - "\n", - "\n", - "def call_model_with_trimming(state: TrimmedState):\n", - " \"\"\"Call model with automatic message trimming\"\"\"\n", - "\n", - " # ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ - ์ตœ๋Œ€ 500 ํ† ํฐ๋งŒ ์œ ์ง€\n", - " trimmed_messages = trim_messages(\n", - " state[\"messages\"],\n", - " strategy=\"last\",\n", - " max_tokens=500,\n", - " start_on=\"human\",\n", - " end_on=(\"human\", \"ai\"),\n", - " include_system=True, # ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€ ํฌํ•จ\n", - " )\n", - "\n", - " # ํŠธ๋ฆฌ๋ฐ๋œ ๋ฉ”์‹œ์ง€๋กœ LLM ํ˜ธ์ถœ\n", - " response = llm.invoke(trimmed_messages)\n", - "\n", - " return {\"messages\": [response]}\n", - "\n", - "\n", - "# ํŠธ๋ฆฌ๋ฐ์ด ์ ์šฉ๋œ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ\n", - "trimming_builder = StateGraph(TrimmedState)\n", - "trimming_builder.add_node(\"chat\", call_model_with_trimming)\n", - "trimming_builder.add_edge(START, \"chat\")\n", - "trimming_builder.add_edge(\"chat\", END)\n", - "\n", - "trimming_graph = trimming_builder.compile(checkpointer=InMemorySaver())\n", - "\n", - "print(\"โœ… ์ž๋™ ํŠธ๋ฆฌ๋ฐ์ด ์ ์šฉ๋œ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์™„๋ฃŒ!\")\n", - "print(\"\\n๐Ÿ’ก ๊ธด ๋Œ€ํ™”์—์„œ๋„ ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ ์ œํ•œ์„ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\")" + "# ๋‹ค์Œ ๋…ธ๋“œ ํ™•์ธ (์‹คํ–‰ ์™„๋ฃŒ ์‹œ ๋นˆ ๊ฐ’)\n", + "print(\"๋‹ค์Œ ๋…ธ๋“œ:\", snapshot.next)" ] }, { "cell_type": "markdown", + "id": "cell-21", "metadata": {}, "source": [ - "## 3.2 ๋ฉ”์‹œ์ง€ ์‚ญ์ œ (Deletion)\n", + "### ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”\n", + "\n", + "์Šค๋ƒ…์ƒท์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” ์ค‘์ฒฉ๋œ ๊ตฌ์กฐ๋กœ ๋˜์–ด ์žˆ์–ด ์ง์ ‘ ํ™•์ธํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `langchain_teddynote.messages` ๋ชจ๋“ˆ์˜ `display_message_tree()` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠธ๋ฆฌ ํ˜•ํƒœ๋กœ ๋ณด๊ธฐ ์‰ฝ๊ฒŒ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", "\n", - "ํŠน์ • ๋ฉ”์‹œ์ง€๋ฅผ ์˜๊ตฌ์ ์œผ๋กœ ์‚ญ์ œํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์Šค๋ƒ…์ƒท์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํŠธ๋ฆฌ ํ˜•ํƒœ๋กœ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, + "id": "cell-22", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " \u001b[93msource\u001b[0m: \"loop\"\n", + " \u001b[93mstep\u001b[0m: 4\n", + " \u001b[93mparents\u001b[0m: {}\n" + ] + } + ], "source": [ - "from langchain_core.messages import RemoveMessage\n", + "from langchain_teddynote.messages import display_message_tree\n", "\n", + "# ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํŠธ๋ฆฌ ํ˜•ํƒœ๋กœ ์ถœ๋ ฅ\n", + "display_message_tree(snapshot.metadata)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-23", + "metadata": {}, + "source": [ + "---\n", "\n", - "class DeletionState(MessagesState):\n", - " \"\"\"State for message deletion example\"\"\"\n", - "\n", - " pass\n", - "\n", - "\n", - "def delete_old_messages(state: DeletionState):\n", - " \"\"\"Delete messages older than threshold\"\"\"\n", - " messages = state[\"messages\"]\n", - "\n", - " # 5๊ฐœ ์ด์ƒ์˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", - " if len(messages) > 5:\n", - " # ์ฒ˜์Œ 2๊ฐœ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", - " messages_to_delete = [RemoveMessage(id=msg.id) for msg in messages[:2]]\n", - " return {\"messages\": messages_to_delete}\n", - "\n", - " return {}\n", - "\n", - "\n", - "def chat_and_cleanup(state: DeletionState):\n", - " \"\"\"Chat with automatic cleanup\"\"\"\n", - " # ๋จผ์ € ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€ ์ •๋ฆฌ\n", - " cleanup_result = delete_old_messages(state)\n", - "\n", - " # LLM ํ˜ธ์ถœ\n", - " response = llm.invoke(state[\"messages\"])\n", - "\n", - " # ์‘๋‹ต๊ณผ ์ •๋ฆฌ ๊ฒฐ๊ณผ ๋ณ‘ํ•ฉ\n", - " messages_update = [response]\n", - " if \"messages\" in cleanup_result:\n", - " messages_update = cleanup_result[\"messages\"] + messages_update\n", - "\n", - " return {\"messages\": messages_update}\n", - "\n", + "## ์ƒํƒœ ์ด๋ ฅ ์กฐํšŒ\n", "\n", - "# ์‚ญ์ œ ๋กœ์ง์ด ํฌํ•จ๋œ ๊ทธ๋ž˜ํ”„\n", - "deletion_builder = StateGraph(DeletionState)\n", - "deletion_builder.add_node(\"chat\", chat_and_cleanup)\n", - "deletion_builder.add_edge(START, \"chat\")\n", - "deletion_builder.add_edge(\"chat\", END)\n", + "์ฒดํฌํฌ์ธํ„ฐ๋Š” ๋ชจ๋“  ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๊ธฐ๋กํ•˜๋ฏ€๋กœ, `get_state_history()` ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณผ๊ฑฐ ์ƒํƒœ๋“ค์„ ์‹œ๊ฐ„ ์—ญ์ˆœ์œผ๋กœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํŠน์ • ์‹œ์ ์˜ ์ƒํƒœ๋กœ ๋กค๋ฐฑํ•˜๊ฑฐ๋‚˜ ๋””๋ฒ„๊น…์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", "\n", - "deletion_graph = deletion_builder.compile(checkpointer=InMemorySaver())\n", + "> ์ฐธ๊ณ  ๋ฌธ์„œ: [LangGraph Persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/)\n", "\n", - "print(\"โœ… ์ž๋™ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ๊ฐ€ ์ ์šฉ๋œ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์™„๋ฃŒ!\")\n", - "print(\"\\n๐Ÿ’ก ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์ž๋™์œผ๋กœ ์‚ญ์ œํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\")" + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์ƒํƒœ ์ด๋ ฅ์„ ์กฐํšŒํ•˜์—ฌ ๊ฐ ์ฒดํฌํฌ์ธํŠธ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cell-24", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์ƒํƒœ ์ด๋ ฅ:\n", + " [0] checkpoint_id: 1f101af6-c18b-66a8-8... | ๋ฉ”์‹œ์ง€ ์ˆ˜: 4\n", + " [1] checkpoint_id: 1f101af6-b968-628c-8... | ๋ฉ”์‹œ์ง€ ์ˆ˜: 3\n", + " [2] checkpoint_id: 1f101af6-b966-6bf8-8... | ๋ฉ”์‹œ์ง€ ์ˆ˜: 2\n", + " [3] checkpoint_id: 1f101af6-b958-6490-8... | ๋ฉ”์‹œ์ง€ ์ˆ˜: 2\n", + " [4] checkpoint_id: 1f101af6-af61-64c8-8... | ๋ฉ”์‹œ์ง€ ์ˆ˜: 1\n", + " [5] checkpoint_id: 1f101af6-af5f-6038-b... | ๋ฉ”์‹œ์ง€ ์ˆ˜: 0\n" + ] + } + ], + "source": [ + "# ์ƒํƒœ ์ด๋ ฅ ์กฐํšŒ\n", + "print(\"์ƒํƒœ ์ด๋ ฅ:\")\n", + "for i, state in enumerate(graph.get_state_history(config)):\n", + " checkpoint_id = state.config[\"configurable\"].get(\"checkpoint_id\", \"N/A\")\n", + " msg_count = len(state.values.get(\"messages\", []))\n", + " print(f\" [{i}] checkpoint_id: {checkpoint_id[:20]}... | ๋ฉ”์‹œ์ง€ ์ˆ˜: {msg_count}\")" ] }, { "cell_type": "markdown", + "id": "cell-25", "metadata": {}, "source": [ - "## 3.3 ๋ฉ”์‹œ์ง€ ์š”์•ฝ (Summarization)\n", + "---\n", + "\n", + "## ๋„๊ตฌ์™€ ๋ฉ”๋ชจ๋ฆฌ ๊ฒฐํ•ฉ\n", "\n", - "์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์š”์•ฝํ•˜์—ฌ ์ปจํ…์ŠคํŠธ๋ฅผ ์••์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + "์‹ค์ œ ์—์ด์ „ํŠธ๋Š” ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์™ธ๋ถ€ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ฑฐ๋‚˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์—์ด์ „ํŠธ์—๋„ ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์ ์šฉํ•˜๋ฉด ๋„๊ตฌ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๊นŒ์ง€ ํฌํ•จํ•œ ์ „์ฒด ๋Œ€ํ™” ๊ธฐ๋ก์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ๊ฒ€์ƒ‰ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์—์ด์ „ํŠธ์— ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, + "id": "cell-26", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๋„๊ตฌ์™€ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ†ตํ•ฉ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\n" + ] + } + ], "source": [ - "from langchain_core.messages import SystemMessage\n", - "\n", - "\n", - "class SummarizationState(MessagesState):\n", - " \"\"\"State with summarization support\"\"\"\n", + "from langchain_teddynote.tools.tavily import TavilySearch\n", + "from langgraph.prebuilt import ToolNode, tools_condition\n", "\n", - " summary: str = \"\" # ๋Œ€ํ™” ์š”์•ฝ ์ €์žฅ\n", + "# ์ƒˆ๋กœ์šด ์ฒดํฌํฌ์ธํ„ฐ ์ƒ์„ฑ\n", + "agent_memory = MemorySaver()\n", "\n", + "# ๊ฒ€์ƒ‰ ๋„๊ตฌ ์„ค์ •\n", + "search_tool = TavilySearch(max_results=2)\n", + "tools = [search_tool]\n", "\n", - "def summarize_conversation(messages: list) -> str:\n", - " \"\"\"Summarize a list of messages\"\"\"\n", - " # ์š”์•ฝ์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ\n", - " summary_prompt = \"\"\"\n", - " Please summarize the following conversation in 2-3 sentences,\n", - " focusing on key information and context:\n", - " \n", - " {conversation}\n", - " \"\"\"\n", + "# LLM์— ๋„๊ตฌ ๋ฐ”์ธ๋”ฉ\n", + "llm_with_tools = ChatOpenAI(model=\"gpt-4o-mini\").bind_tools(tools)\n", "\n", - " # ๋Œ€ํ™” ๋‚ด์šฉ ํฌ๋งทํŒ…\n", - " conversation = \"\\n\".join([f\"{msg.type}: {msg.content}\" for msg in messages])\n", "\n", - " # LLM์œผ๋กœ ์š”์•ฝ ์ƒ์„ฑ (์‹ค์ œ๋กœ๋Š” ๋ณ„๋„์˜ ์š”์•ฝ ๋ชจ๋ธ ์‚ฌ์šฉ ๊ถŒ์žฅ)\n", - " summary_response = llm.invoke(\n", - " [\n", - " {\n", - " \"role\": \"system\",\n", - " \"content\": summary_prompt.format(conversation=conversation),\n", - " }\n", - " ]\n", - " )\n", + "# ์—์ด์ „ํŠธ ๋…ธ๋“œ ํ•จ์ˆ˜\n", + "def agent(state: State):\n", + " \"\"\"์—์ด์ „ํŠธ ๋…ธ๋“œ ํ•จ์ˆ˜\n", "\n", - " return summary_response.content\n", - "\n", - "\n", - "def chat_with_summarization(state: SummarizationState):\n", - " \"\"\"Chat with automatic summarization\"\"\"\n", - " messages = state[\"messages\"]\n", + " ๋„๊ตฌ๊ฐ€ ๋ฐ”์ธ๋”ฉ๋œ LLM์„ ํ˜ธ์ถœํ•˜์—ฌ ์‘๋‹ต์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " response = llm_with_tools.invoke(state[\"messages\"])\n", + " return {\"messages\": [response]}\n", "\n", - " # 10๊ฐœ ์ด์ƒ์˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ์š”์•ฝ\n", - " if len(messages) > 10:\n", - " # ์ฒ˜์Œ 5๊ฐœ ๋ฉ”์‹œ์ง€ ์š”์•ฝ\n", - " messages_to_summarize = messages[:5]\n", - " summary = summarize_conversation(messages_to_summarize)\n", "\n", - " # ์š”์•ฝ์„ ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๋กœ ์ถ”๊ฐ€ํ•˜๊ณ  ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", - " new_messages = [\n", - " SystemMessage(content=f\"Previous conversation summary: {summary}\")\n", - " ] + messages[\n", - " 5:\n", - " ] # ์š”์•ฝ๋œ ๋ฉ”์‹œ์ง€๋Š” ์ œ๊ฑฐ\n", + "# ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ\n", + "agent_builder = StateGraph(State)\n", "\n", - " # ์ƒํƒœ ์—…๋ฐ์ดํŠธ\n", - " return {\"messages\": new_messages, \"summary\": summary}\n", + "# ๋…ธ๋“œ ์ถ”๊ฐ€\n", + "agent_builder.add_node(\"agent\", agent)\n", + "agent_builder.add_node(\"tools\", ToolNode(tools=tools))\n", "\n", - " # ์ผ๋ฐ˜ ์‘๋‹ต\n", - " response = llm.invoke(messages)\n", - " return {\"messages\": [response]}\n", + "# ์—ฃ์ง€ ์ถ”๊ฐ€\n", + "agent_builder.add_edge(START, \"agent\")\n", + "agent_builder.add_conditional_edges(\"agent\", tools_condition)\n", + "agent_builder.add_edge(\"tools\", \"agent\")\n", "\n", + "# ์ฒดํฌํฌ์ธํ„ฐ์™€ ํ•จ๊ป˜ ์ปดํŒŒ์ผ\n", + "agent_graph = agent_builder.compile(checkpointer=agent_memory)\n", "\n", - "print(\"โœ… ๋Œ€ํ™” ์š”์•ฝ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์™„๋ฃŒ!\")\n", - "print(\"\\n๐Ÿ’ก ๊ธด ๋Œ€ํ™”๋ฅผ ์ž๋™์œผ๋กœ ์š”์•ฝํ•˜์—ฌ ์ปจํ…์ŠคํŠธ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\")" + "print(\"๋„๊ตฌ์™€ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ†ตํ•ฉ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\")" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 16, + "id": "cell-27", "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "---\n", - "\n", - "# Part 4: ์‹œ๋งจํ‹ฑ ๊ฒ€์ƒ‰ (Semantic Search) ๐Ÿ”\n", - "\n", - "## 4.1 ์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ ๋ฉ”๋ชจ๋ฆฌ ๊ฒ€์ƒ‰\n", - "\n", - "Store์— ์‹œ๋งจํ‹ฑ ๊ฒ€์ƒ‰์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์˜๋ฏธ์ ์œผ๋กœ ์œ ์‚ฌํ•œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + "# ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", + "visualize_graph(agent_graph)" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import OpenAIEmbeddings\n", - "\n", - "# ์‹œ๋งจํ‹ฑ ๊ฒ€์ƒ‰์ด ํ™œ์„ฑํ™”๋œ Store ์ƒ์„ฑ\n", - "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n", - "\n", - "semantic_store = InMemoryStore(\n", - " index={\n", - " \"embed\": embeddings, # ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ\n", - " \"dims\": 1536, # ์ž„๋ฒ ๋”ฉ ์ฐจ์›\n", - " }\n", + "execution_count": 17, + "id": "cell-28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==================================================\n", + "๐Ÿ”„ Node: \u001b[1;36magent\u001b[0m ๐Ÿ”„\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "\n", + "==================================================\n", + "๐Ÿ”„ Node: \u001b[1;36mtools\u001b[0m ๐Ÿ”„\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "[{\"url\": \"https://pypi.org/project/langgraph/\", \"title\": \"langgraph - PyPI\", \"content\": \"langgraph 1.0.7. pip install langgraph. Copy PIP instructions. Latest version. Released: Jan 22, 2026. Building stateful, multi-actor applications with LLMs\", \"score\": 0.8170061, \"raw_content\": \"![PyPI](/static/images/logo-small.8998e9d1.svg)\\n\\n# langgraph 1.0.7\\n\\npip install langgraph\\n\\n\\nCopy PIP instructions\\n\\nReleased: \\nJan 22, 2026\\n\\nBuilding stateful, multi-actor applications with LLMs\\n\\n### Navigation\\n\\n### Verified details\\n\\n###### Project links\\n\\n###### GitHub Statistics\\n\\n###### Maintainers\\n\\n![Avatar for hwchase17 from gravatar.com](https://pypi-camo.freetls.fastly.net/1cfaf7a4a11345982a82162569a80132773223b2/68747470733a2f2f7365637572652e67726176617461722e636f6d2f6176617461722f34323334343831366538383438623232383732363861366132613264636134323f73697a653d3530 \\\"Avatar for hwchase17 from gravatar.com\\\")\\n![Avatar for langchain from gravatar.com](https://pypi-camo.freetls.fastly.net/10849e96b3129daeb6e3cea4358f26c29391a326/68747470733a2f2f7365637572652e67726176617461722e636f6d2f6176617461722f35333034373437326436306565616461383938393764356464656464653064393f73697a653d3530 \\\"Avatar for langchain from gravatar.com\\\")\\n![Avatar for nfcampos from gravatar.com](https://pypi-camo.freetls.fastly.net/c923ff105e656a4b1cfdd3485b772eacfa1909c6/68747470733a2f2f7365637572652e67726176617461722e636f6d2f6176617461722f61653636636439386133616636333930636265336464643537343330663531623f73697a653d3530 \\\"Avatar for nfcampos from gravatar.com\\\")\\n\\n### Unverified details\\n\\n###### Project links\\n\\n###### Meta\\n\\n###### Classifiers\\n\\n## Project description\\n\\n![LangGraph Logo](https://pypi-camo.freetls.fastly.net/68186a512b8a67dc7e6998fab8e1ad139f9c9570/68747470733a2f2f6c616e67636861696e2d61692e6769746875622e696f2f6c616e6767726170682f7374617469632f776f72646d61726b5f6461726b2e737667)\\n\\n[![Version](https://pypi-camo.freetls.fastly.net/830fecaa9294a9f8038a3a37718272a26d1f6498/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6c616e6767726170682e737667)](https://pypi.org/project/langgraph/)\\n[![Downloads](https://pypi-camo.freetls.fastly.net/6f026d474c4f9a1d14616600638c813075032074/68747470733a2f2f7374617469632e706570792e746563682f62616467652f6c616e6767726170682f6d6f6e7468)](https://pepy.tech/project/langgraph)\\n[![Open Issues](https://pypi-camo.freetls.fastly.net/d88db9815c1e34d9449907c2361113c4c92a23ee/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732d7261772f6c616e67636861696e2d61692f6c616e676772617068)](https://github.com/langchain-ai/langgraph/issues)\\n[![Docs](https://pypi-camo.freetls.fastly.net/9284977fc757d5cf8fb9af0452b3f6b045990371/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646f63732d6c61746573742d626c7565)](https://docs.langchain.com/oss/python/langgraph/overview)\\n\\n![Version](https://pypi-camo.freetls.fastly.net/830fecaa9294a9f8038a3a37718272a26d1f6498/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6c616e6767726170682e737667)\\n![Downloads](https://pypi-camo.freetls.fastly.net/6f026d474c4f9a1d14616600638c813075032074/68747470733a2f2f7374617469632e706570792e746563682f62616467652f6c616e6767726170682f6d6f6e7468)\\n![Open Issues](https://pypi-camo.freetls.fastly.net/d88db9815c1e34d9449907c2361113c4c92a23ee/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732d7261772f6c616e67636861696e2d61692f6c616e676772617068)\\n![Docs](https://pypi-camo.freetls.fastly.net/9284977fc757d5cf8fb9af0452b3f6b045990371/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646f63732d6c61746573742d626c7565)\\n\\nTrusted by companies shaping the future of agents โ€“ including Klarna, Replit, Elastic, and more โ€“ LangGraph is a low-level orchestration framework for building, managing, and deploying long-running, stateful agents.\\n\\n## Get started\\n\\nInstall LangGraph:\\n\\n`pip install -U langgraph`\\n\\nCreate a simple workflow:\\n\\nGet started with the [LangGraph Quickstart](https://docs.langchain.com/oss/python/langgraph/quickstart).\\n\\nTo quickly build agents with LangChain's `create_agent` (built on LangGraph), see the [LangChain Agents documentation](https://docs.langchain.com/oss/python/langchain/agents).\\n\\n`create_agent`\\n\\n## Core benefits\\n\\nLangGraph provides low-level supporting infrastructure for *any* long-running, stateful workflow or agent. LangGraph does not abstract prompts or architecture, and provides the following central benefits:\\n\\n## LangGraphโ€™s ecosystem\\n\\nWhile LangGraph can be used standalone, it also integrates seamlessly with any LangChain product, giving developers a full suite of tools for building agents. To improve your LLM application development, pair LangGraph with:\\n\\n[!NOTE]\\nLooking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://docs.langchain.com/oss/javascript/langgraph/overview).\\n\\n## Additional resources\\n\\n## Acknowledgements\\n\\nLangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.\\n\\n## Project details\\n\\n### Verified details\\n\\n###### Project links\\n\\n###### GitHub Statistics\\n\\n###### Maintainers\\n\\n![Avatar for hwchase17 from gravatar.com](https://pypi-camo.freetls.fastly.net/1cfaf7a4a11345982a82162569a80132773223b2/68747470733a2f2f7365637572652e67726176617461722e636f6d2f6176617461722f34323334343831366538383438623232383732363861366132613264636134323f73697a653d3530 \\\"Avatar for hwchase17 from gravatar.com\\\")\\n![Avatar for langchain from gravatar.com](https://pypi-camo.freetls.fastly.net/10849e96b3129daeb6e3cea4358f26c29391a326/68747470733a2f2f7365637572652e67726176617461722e636f6d2f6176617461722f35333034373437326436306565616461383938393764356464656464653064393f73697a653d3530 \\\"Avatar for langchain from gravatar.com\\\")\\n![Avatar for nfcampos from gravatar.com](https://pypi-camo.freetls.fastly.net/c923ff105e656a4b1cfdd3485b772eacfa1909c6/68747470733a2f2f7365637572652e67726176617461722e636f6d2f6176617461722f61653636636439386133616636333930636265336464643537343330663531623f73697a653d3530 \\\"Avatar for nfcampos from gravatar.com\\\")\\n\\n### Unverified details\\n\\n###### Project links\\n\\n###### Meta\\n\\n###### Classifiers\\n\\n## Release history [Release notifications](/help/#project-release-notifications) | [RSS feed](/rss/project/langgraph/releases.xml)\\n\\n![](https://pypi.org/static/images/blue-cube.572a5bfb.svg)\\n\\n1.0.7\\n\\nJan 22, 2026\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.6\\n\\nJan 12, 2026\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.5\\n\\nDec 12, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.4\\n\\nNov 25, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.3\\n\\nNov 10, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.2\\n\\nOct 29, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.1\\n\\nOct 20, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.0\\n\\nOct 17, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.0rc1\\npre-release\\n\\nOct 17, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.0a4\\npre-release\\n\\nSep 29, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.0a3\\npre-release\\n\\nSep 7, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.0a2\\npre-release\\n\\nSep 2, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n1.0.0a1\\npre-release\\n\\nAug 27, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.11\\n\\nOct 21, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.10\\n\\nOct 9, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.9\\nyanked\\n\\nOct 7, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.8\\n\\nSep 29, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.7\\n\\nSep 7, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.6\\n\\nAug 20, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.5\\n\\nAug 13, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.4\\n\\nAug 7, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.3\\n\\nAug 3, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.2\\n\\nJul 30, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.1\\n\\nJul 29, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.0\\n\\nJul 28, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.0a2\\npre-release\\n\\nJul 25, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.6.0a1\\npre-release\\n\\nJul 22, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.5.4\\n\\nJul 21, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.5.3\\n\\nJul 14, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.5.2\\n\\nJul 9, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.5.1\\n\\nJul 2, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.5.0\\n\\nJun 26, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.5.0rc1\\npre-release\\n\\nJun 17, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.5.0rc0\\npre-release\\n\\nJun 16, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.10\\n\\nJun 25, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.9\\n\\nJun 25, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.8\\n\\nJun 2, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.7\\n\\nMay 24, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.6\\n\\nMay 23, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.5\\n\\nMay 15, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.4\\nyanked\\n\\nMay 15, 2025\\n\\nReason this release was yanked:\\n\\nIncorrect dependency range\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.3\\n\\nMay 8, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.2\\n\\nMay 7, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.1\\n\\nApr 30, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.4.0\\n\\nApr 29, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.34\\n\\nApr 24, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.33\\n\\nApr 23, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.32\\n\\nApr 23, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.31\\n\\nApr 17, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.30\\n\\nApr 14, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.29\\n\\nApr 11, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.28\\n\\nApr 11, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.27\\n\\nApr 8, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.26\\n\\nApr 8, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.25\\n\\nApr 3, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.24\\n\\nApr 2, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.23\\n\\nApr 2, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.22\\n\\nApr 1, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.21\\n\\nMar 27, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.20\\n\\nMar 25, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.19\\n\\nMar 24, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.18\\n\\nMar 19, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.17\\n\\nMar 19, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.16\\n\\nMar 19, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.15\\n\\nMar 18, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.14\\n\\nMar 18, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.13\\n\\nMar 18, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.12\\n\\nMar 18, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.11\\n\\nMar 14, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.10\\n\\nMar 14, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.9\\n\\nMar 13, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.8\\n\\nMar 12, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.7\\n\\nMar 12, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.6\\n\\nMar 11, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.5\\n\\nMar 5, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.4\\n\\nMar 4, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.3\\n\\nMar 4, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.2\\n\\nFeb 28, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.1\\n\\nFeb 27, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.3.0\\nyanked\\n\\nFeb 26, 2025\\n\\nReason this release was yanked:\\n\\nMissing dependency on langgraph-prebuilt\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.76\\n\\nFeb 26, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.75\\n\\nFeb 26, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.74\\n\\nFeb 19, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.73\\n\\nFeb 15, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.72\\n\\nFeb 13, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.71\\n\\nFeb 11, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.70\\n\\nFeb 6, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.69\\n\\nJan 31, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.68\\n\\nJan 28, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.67\\n\\nJan 23, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.66\\n\\nJan 21, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.65\\n\\nJan 21, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.64\\n\\nJan 17, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.63\\n\\nJan 16, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.62\\n\\nJan 10, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.61\\n\\nJan 5, 2025\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.60\\n\\nDec 18, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.59\\n\\nDec 11, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.58\\n\\nDec 10, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.57\\n\\nDec 10, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.56\\n\\nDec 5, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.55\\n\\nDec 5, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.54\\n\\nDec 3, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.53\\n\\nNov 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.52\\n\\nNov 19, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.51\\n\\nNov 19, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.50\\n\\nNov 15, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.49\\n\\nNov 15, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.48\\n\\nNov 14, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.47\\n\\nNov 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.46\\n\\nNov 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.45\\n\\nNov 4, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.44\\n\\nNov 2, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.43\\n\\nOct 31, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.42\\n\\nOct 31, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.41\\n\\nOct 31, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.40\\n\\nOct 30, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.39\\n\\nOct 18, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.38\\n\\nOct 15, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.37\\n\\nOct 15, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.36\\n\\nOct 14, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.35\\n\\nOct 9, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.34\\n\\nOct 2, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.33\\n\\nOct 2, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.32\\n\\nOct 1, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.31\\nyanked\\n\\nSep 30, 2024\\n\\nReason this release was yanked:\\n\\nUses internal APIs of upstream package.\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.30\\nyanked\\n\\nSep 30, 2024\\n\\nReason this release was yanked:\\n\\nUses internal APIs of upstream package.\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.29\\nyanked\\n\\nSep 30, 2024\\n\\nReason this release was yanked:\\n\\nUses internal APIs of upstream package.\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.28\\n\\nSep 25, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.27\\n\\nSep 24, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.26\\n\\nSep 24, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.25\\n\\nSep 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.24\\n\\nSep 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.23\\n\\nSep 20, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.22\\n\\nSep 16, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.21\\n\\nSep 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.20\\n\\nSep 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.19\\n\\nSep 6, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.18\\n\\nSep 6, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.17\\n\\nSep 5, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.16\\n\\nSep 1, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.15\\n\\nAug 30, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.14\\n\\nAug 24, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.13\\n\\nAug 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.12\\n\\nAug 22, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.11\\n\\nAug 22, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.10\\n\\nAug 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.9\\n\\nAug 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.8\\n\\nAug 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.7\\n\\nAug 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.7a0\\npre-release\\n\\nAug 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.6\\n\\nAug 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.5\\n\\nAug 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.5a0\\npre-release\\n\\nAug 20, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.4\\n\\nAug 15, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.3\\n\\nAug 8, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.2\\n\\nAug 7, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.1\\n\\nAug 7, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.2.0\\n\\nAug 7, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.19\\n\\nAug 1, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.18\\nyanked\\n\\nJul 31, 2024\\n\\nReason this release was yanked:\\n\\nbroken\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.17\\n\\nJul 31, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.16\\n\\nJul 29, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.15\\n\\nJul 26, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.14\\n\\nJul 24, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.13\\n\\nJul 24, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.12\\n\\nJul 24, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.11\\n\\nJul 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.10\\n\\nJul 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.9\\n\\nJul 18, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.8\\n\\nJul 12, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.7\\n\\nJul 10, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.6\\n\\nJul 9, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.5\\n\\nJul 1, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.4\\n\\nJun 28, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.3\\n\\nJun 28, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.2\\n\\nJun 26, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.1.1\\n\\nJun 22, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.69\\n\\nJun 14, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.68\\n\\nJun 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.67\\n\\nJun 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.66\\n\\nJun 10, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.65\\n\\nJun 7, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.64\\n\\nJun 6, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.63\\n\\nJun 5, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.62\\n\\nJun 3, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.61\\n\\nJun 2, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.60\\n\\nMay 31, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.59\\n\\nMay 30, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.58\\n\\nMay 30, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.57\\n\\nMay 30, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.56\\n\\nMay 29, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.55\\n\\nMay 22, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.54\\n\\nMay 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.53\\n\\nMay 20, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.52\\n\\nMay 20, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.51\\n\\nMay 20, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.50\\n\\nMay 17, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.49\\n\\nMay 14, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.48\\n\\nMay 8, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.47\\n\\nMay 8, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.46\\n\\nMay 7, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.45\\n\\nMay 7, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.44\\n\\nMay 3, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.43\\n\\nMay 3, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.42\\n\\nMay 3, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.41\\n\\nMay 3, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.40\\n\\nApr 30, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.39\\n\\nApr 25, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.38\\n\\nApr 17, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.37\\n\\nApr 12, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.36\\n\\nApr 11, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.35\\n\\nApr 11, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.34\\n\\nApr 10, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.33\\n\\nApr 10, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.32\\n\\nApr 4, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.31\\n\\nApr 2, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.30\\n\\nMar 22, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.29\\n\\nMar 20, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.28\\n\\nMar 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.27\\n\\nMar 13, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.26\\n\\nFeb 22, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.25\\n\\nFeb 22, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.24\\n\\nFeb 8, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.23\\n\\nFeb 4, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.22\\n\\nFeb 4, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.21\\n\\nJan 31, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.20\\n\\nJan 27, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.19\\n\\nJan 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.18\\n\\nJan 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.17\\n\\nJan 23, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.16\\n\\nJan 21, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.15\\n\\nJan 19, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.14\\n\\nJan 18, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.13\\n\\nJan 17, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.12\\n\\nJan 17, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.11\\n\\nJan 16, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.10\\n\\nJan 9, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.9\\n\\nJan 8, 2024\\n\\n![](https://pypi.org/static/images/white-cube.2351a86c.svg)\\n\\n0.0.8\\nyanked\\n\\nJan 8, 2024\\n\\n## Download files\\n\\nDownload the file for your platform. If you're not sure which to choose, learn more about [installing packages](https://packaging.python.org/tutorials/installing-packages/ \\\"External link\\\").\\n\\n### Source Distribution\\n\\nUploaded \\nJan 22, 2026\\n`Source`\\n\\n`Source`\\n\\n### Built Distribution\\n\\nFilter files by name, interpreter, ABI, and platform.\\n\\nIf you're not sure about the file name format, learn more about [wheel file names](https://packaging.python.org/en/latest/specifications/binary-distribution-format/ \\\"External link\\\").\\n\\nThe dropdown lists show the available interpreters, ABIs, and platforms.\\n\\nEnable javascript to be able to filter the list of wheel files.\\n\\nCopy a direct link to the current filters \\n\\nCopy\\n\\nUploaded \\nJan 22, 2026\\n`Python 3`\\n\\n`Python 3`\\n\\n## File details\\n\\nDetails for the file `langgraph-1.0.7.tar.gz`.\\n\\n`langgraph-1.0.7.tar.gz`\\n\\n### File metadata\\n\\n### File hashes\\n\\nHashes for langgraph-1.0.7.tar.gz\\n\\n| Algorithm | Hash digest | |\\n| --- | --- | --- |\\n| SHA256 | `0cfdfee51e6e8cfe503ecc7367c73933437c505b03fa10a85c710975c8182d9a` | Copy |\\n| MD5 | `a350531ec3eddfd0dc07d068b94feb71` | Copy |\\n| BLAKE2b-256 | `725bf72655717c04e33d3b62f21b166dc063d192b53980e9e3be0e2a117f1c9f` | Copy |\\n\\n`0cfdfee51e6e8cfe503ecc7367c73933437c505b03fa10a85c710975c8182d9a`\\n`a350531ec3eddfd0dc07d068b94feb71`\\n`725bf72655717c04e33d3b62f21b166dc063d192b53980e9e3be0e2a117f1c9f`\\n\\n[See more details on using hashes here.](https://pip.pypa.io/en/stable/topics/secure-installs/#hash-checking-mode \\\"External link\\\")\\n\\n## File details\\n\\nDetails for the file `langgraph-1.0.7-py3-none-any.whl`.\\n\\n`langgraph-1.0.7-py3-none-any.whl`\\n\\n### File metadata\\n\\n### File hashes\\n\\nHashes for langgraph-1.0.7-py3-none-any.whl\\n\\n| Algorithm | Hash digest | |\\n| --- | --- | --- |\\n| SHA256 | `9d68e8f8dd8f3de2fec45f9a06de05766d9b075b78fb03171779893b7a52c4d2` | Copy |\\n| MD5 | `5f2e0a25b6e7788ac150e51f4292de21` | Copy |\\n| BLAKE2b-256 | `7e0efe80144e3e4048e5d19ccdb91ac547c1a7dc3da8dbd1443e210048194c14` | Copy |\\n\\n`9d68e8f8dd8f3de2fec45f9a06de05766d9b075b78fb03171779893b7a52c4d2`\\n`5f2e0a25b6e7788ac150e51f4292de21`\\n`7e0efe80144e3e4048e5d19ccdb91ac547c1a7dc3da8dbd1443e210048194c14`\\n\\n[See more details on using hashes here.](https://pip.pypa.io/en/stable/topics/secure-installs/#hash-checking-mode \\\"External link\\\")\\n\\n![](/static/images/white-cube.2351a86c.svg)\\n\\n## Help\\n\\n## About PyPI\\n\\n## Contributing to PyPI\\n\\n## Using PyPI\\n\\nStatus:[all systems operational](https://status.python.org/ \\\"External link\\\")\\n\\nDeveloped and maintained by the Python community, for the Python community. \\n[Donate today!](https://donate.pypi.org)\\n\\n\\\"PyPI\\\", \\\"Python Package Index\\\", and the blocks logos are registered [trademarks](/trademarks/) of the [Python Software Foundation](https://www.python.org/psf-landing).\\n\\nยฉ 2026 [Python Software Foundation](https://www.python.org/psf-landing/ \\\"External link\\\")\\n \\n[Site map](/sitemap/)\\n\\nSupported by\\n\\n![](https://pypi-camo.freetls.fastly.net/ed7074cadad1a06f56bc520ad9bd3e00d0704c5b/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f6177732d77686974652d6c6f676f2d7443615473387a432e706e67)\\n![](https://pypi-camo.freetls.fastly.net/8855f7c063a3bdb5b0ce8d91bfc50cf851cc5c51/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f64617461646f672d77686974652d6c6f676f2d6668644c4e666c6f2e706e67)\\n![](https://pypi-camo.freetls.fastly.net/60f709d24f3e4d469f9adc77c65e2f5291a3d165/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f6465706f742d77686974652d6c6f676f2d7038506f476831302e706e67)\\n![](https://pypi-camo.freetls.fastly.net/df6fe8829cbff2d7f668d98571df1fd011f36192/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f666173746c792d77686974652d6c6f676f2d65684d3077735f6f2e706e67)\\n![](https://pypi-camo.freetls.fastly.net/420cc8cf360bac879e24c923b2f50ba7d1314fb0/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f676f6f676c652d77686974652d6c6f676f2d616734424e3774332e706e67)\\n![](https://pypi-camo.freetls.fastly.net/d01053c02f3a626b73ffcb06b96367fdbbf9e230/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f70696e67646f6d2d77686974652d6c6f676f2d67355831547546362e706e67)\\n![](https://pypi-camo.freetls.fastly.net/67af7117035e2345bacb5a82e9aa8b5b3e70701d/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f73656e7472792d77686974652d6c6f676f2d4a2d6b64742d706e2e706e67)\\n![](https://pypi-camo.freetls.fastly.net/b611884ff90435a0575dbab7d9b0d3e60f136466/68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f707970692d6173736574732f73706f6e736f726c6f676f732f737461747573706167652d77686974652d6c6f676f2d5467476c6a4a2d502e706e67)\"}, {\"url\": \"https://wikidocs.net/261587\", \"title\": \"1-2-1. LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๊ตฌ์„ฑ - ์œ„ํ‚ค๋…์Šค\", \"content\": \"**LangGraph ๊ฐ€์ด๋“œ๋ถ - ์—์ด์ „ํŠธ RAG with ๋žญ๊ทธ๋ž˜ํ”„**. ๋žญ๊ทธ๋ž˜ํ”„ LangGraph ๊ธฐ์ดˆ) 1-1. ๋žญ๊ทธ๋ž˜ํ”„ LangGraph ์†Œ๊ฐœ) 1-1-1. LangGraph ์ฒซ๊ฑธ์Œ) 1-1-2. LangGraph ํ™˜๊ฒฝ ์„ค์ • ๋ฐ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•) 1-2-1. LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๊ตฌ์„ฑ) 1-2-2. ์ƒํƒœ (State)) 1-3-2-1. ์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ์ดˆ) 1-3-2-2. ๋…ธ๋“œ ๊ตฌ์„ฑ ๊ณ ๊ธ‰ ํŒจํ„ด ์œ ํ˜•) 1-3-4. ๊ทธ๋ž˜ํ”„ ์—ฐ๊ฒฐ(์ปดํŒŒ์ผ) ๋ฐ ์‹คํ–‰) 1-3-5-1. ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ ์š”์†Œ ์—ฐ๊ฒฐ) 1-3-5-2. 1. **LangGraph ๊ฐ€์ด๋“œ๋ถ - ์—์ด์ „ํŠธ RAโ€ฆ**. LangGraph ํ™˜๊ฒฝ ์„ค์ • ๋ฐ ๊ธฐโ€ฆ. LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๊ตฌ์„ฑ. ## LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๊ตฌ์„ฑ. ### Python ์„ค์น˜. * Python ๋‹ค์šด๋กœ๋“œ ๋ฐ ์„ค์น˜: python.org. ์„ค์น˜ ํ›„ ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ Python ๋ฒ„์ „์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค:. conda create --name langgraph_env python=3.11. ์ด ๋ช…๋ น์–ด๋Š” 'langgraph\\\\_env'๋ผ๋Š” ์ด๋ฆ„์˜ ์ƒˆ๋กœ์šด conda ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•˜๋ฉฐ, Python 3.11 ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™œ์„ฑํ™”: - Windows, macOS ๋ฐ Linux ๋ชจ๋‘ ๊ฐ™์€ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ### LangGraph ์„ค์น˜. pip install -U langgraph # ์ตœ์‹  ๋ฒ„์ „ pip install langgraph==0.0.16 # ํŠน์ • ๋ฒ„์ „ (์˜ˆ: 0.0.16). from langgraph.graph import StateGraph print(\\\"LangGraph successfully installed!\\\"). ### ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”๋ฅผ ์œ„ํ•œ Graphviz ์„ค์น˜ (์„ ํƒ์‚ฌํ•ญ). LangGraph ํ™˜๊ฒฝ ์„ค์ • ๋ฐ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•).\", \"score\": 0.7942736, \"raw_content\": \"[**LangGraph ๊ฐ€์ด๋“œ๋ถ - ์—์ด์ „ํŠธ RAG with ๋žญ๊ทธ๋ž˜ํ”„**](/book/16723) \\n\\n[Part 0. ๊ธ€์“ด์ด ์†Œ๊ฐœ](javascript:page(261572)) [Part 1. ๋žญ๊ทธ๋ž˜ํ”„ LangGraph ๊ธฐ์ดˆ](javascript:page(261576)) [1-1. ๋žญ๊ทธ๋ž˜ํ”„ LangGraph ์†Œ๊ฐœ](javascript:page(261577)) [1-1-1. LangGraph ์ฒซ๊ฑธ์Œ](javascript:page(261584)) [1-1-2. LangGraph์™€ LangChain์˜ ์ฐจ์ด์ ](javascript:page(261585)) [1-1-3. LangGraph๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ด์œ ](javascript:page(261586)) [1-2. LangGraph ํ™˜๊ฒฝ ์„ค์ • ๋ฐ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•](javascript:page(261578)) [1-2-1. LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๊ตฌ์„ฑ](javascript:page(261587)) [1-2-2. ๊ธฐ๋ณธ์ ์ธ LLM ๋ชจ๋ธ ์„ค์ • (GPT)](javascript:page(261588)) [1-2-3. ๋‹ค์–‘ํ•œ LLM ๋ชจ๋ธ ํ™œ์šฉ ๋ฐฉ๋ฒ• (Anthropic Claude, Google Gemini)](javascript:page(261589)) [1-3. StateGraph ์ดํ•ดํ•˜๊ธฐ](javascript:page(261579)) [1-3-1. ๊ธฐ๋ณธ ๊ตฌ์„ฑ์š”์†Œ ์ดํ•ด](javascript:page(261590)) [1-3-2. ์ƒํƒœ (State)](javascript:page(261591)) [1-3-2-1. ์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ์ดˆ](javascript:page(293297)) [1-3-2-2. ์ƒํƒœ ์Šคํ‚ค๋งˆ ์„ค๊ณ„](javascript:page(293353)) [1-3-2-3. ๋ฆฌ๋“€์„œ(Reducer) - ์ƒํƒœ ์—…๋ฐ์ดํŠธ](javascript:page(293355)) [1-3-2-4. MessagesState - ๋Œ€ํ™”ํ˜• ์•ฑ์˜ ์ƒํƒœ ๊ด€๋ฆฌ](javascript:page(319006)) [1-3-2-5. Private State - ๋…ธ๋“œ ๊ฐ„ ๋น„๊ณต๊ฐœ ๋ฐ์ดํ„ฐ](javascript:page(319067)) [1-3-3. ๋…ธ๋“œ (Node)](javascript:page(261580)) [1-3-3-1. ๋…ธ๋“œ์˜ ๊ธฐ๋ณธ ๊ฐœ๋…](javascript:page(261593)) [1-3-3-2. ๋…ธ๋“œ์˜ ์—ญํ• ๊ณผ ์ฑ…์ž„](javascript:page(293519)) [1-3-3-3. ๋…ธ๋“œ ํƒ€์ž…๊ณผ ํŒจํ„ด](javascript:page(293520)) [1-3-3-4. ๋…ธ๋“œ์™€ ๊ตฌ์„ฑ (Configuration)](javascript:page(293528)) [1-3-3-5. ๋…ธ๋“œ ๊ตฌ์„ฑ ๊ณ ๊ธ‰ ํŒจํ„ด ์œ ํ˜•](javascript:page(293529)) [1-3-4. ์—ฃ์ง€ (Edge)](javascript:page(262302)) [1-3-4-1. ์—ฃ์ง€์˜ ๊ฐœ๋…๊ณผ ์ข…๋ฅ˜](javascript:page(261594)) [1-3-4-2. ์—ฃ์ง€์˜ ์—ญํ• ๊ณผ ๊ธฐ๋Šฅ](javascript:page(293533)) [1-3-4-3. ์กฐ๊ฑด๋ถ€ ์—ฃ์ง€ (Conditional Edges)](javascript:page(293535)) [1-3-4-4. Command๋ฅผ ํ™œ์šฉํ•œ ๊ณ ๊ธ‰ ํ๋ฆ„ ์ œ์–ด](javascript:page(293558)) [1-3-4-5. Send API - ๋™์  ๋ณ‘๋ ฌ ์‹คํ–‰](javascript:page(319069)) [1-3-5. ๊ทธ๋ž˜ํ”„ ์—ฐ๊ฒฐ(์ปดํŒŒ์ผ) ๋ฐ ์‹คํ–‰](javascript:page(262304)) [1-3-5-1. ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ ์š”์†Œ ์—ฐ๊ฒฐ](javascript:page(293393)) [1-3-5-2. ๊ทธ๋ž˜ํ”„ ์‹คํ–‰ ๋ฐฉ๋ฒ• (invoke, stream, async)](javascript:page(293826)) [1-4. ๋ฉ”๋ชจ๋ฆฌ (Memory)](javascript:page(261582)) [1-4-1. ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋Šฅ๊ณผ ๊ธฐ๋ณธ ์˜ˆ์ œ](javascript:page(261599)) [1-4-2. ์ฒดํฌํฌ์ธํ„ฐ ์ข…๋ฅ˜๋ณ„ ํ™œ์šฉ๋ฒ•](javascript:page(261600)) [1-4-2-1. InMemorySaver](javascript:page(321315)) [1-4-2-2. SqliteSaver](javascript:page(321316))\\n\\n1. [**LangGraph ๊ฐ€์ด๋“œ๋ถ - ์—์ด์ „ํŠธ RAโ€ฆ**](/book/16723)\\n2. [Part 1. ๋žญ๊ทธ๋ž˜ํ”„ LangGraph ๊ธฐ์ดˆ](/261576)\\n3. [1-2. LangGraph ํ™˜๊ฒฝ ์„ค์ • ๋ฐ ๊ธฐโ€ฆ](/261578)\\n4. [1-2-1. LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝโ€ฆ](/261587)\\n\\n1. [์œ„ํ‚ค๋…์Šค](/)\\n\\n# 1-2-1. LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๊ตฌ์„ฑ\\n\\n๊ด‘๊ณ ๊ฐ€ ์ถœ๋ ฅ๋  ์œ„์น˜์ž…๋‹ˆ๋‹ค.\\n\\n๊ด‘๊ณ ๊ฐ€ ์ถœ๋ ฅ๋  ์œ„์น˜์ž…๋‹ˆ๋‹ค.\\n\\n## LangGraph ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๊ตฌ์„ฑ\\n\\nLangGraph๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•˜๊ณ  ์„ค์น˜ํ•˜๋Š” ๊ณผ์ •์„ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.\\n\\n### Python ์„ค์น˜\\n\\nLangGraph๋Š” Python ๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋ฏ€๋กœ, ๋จผ์ € Python์ด ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Python 3.8 ์ด์ƒ์˜ ๋ฒ„์ „์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.\\n\\n* Python ๋‹ค์šด๋กœ๋“œ ๋ฐ ์„ค์น˜: [python.org](https://www.python.org/downloads/)\\n\\n์„ค์น˜ ํ›„ ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ Python ๋ฒ„์ „์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค:\\n\\n```\\npython --version \\n```\\n\\n### ๊ฐ€์ƒ ํ™˜๊ฒฝ ์ƒ์„ฑ (์„ ํƒ์‚ฌํ•ญ, ๊ถŒ์žฅ)\\n\\nํ”„๋กœ์ ํŠธ๋ณ„๋กœ ๋…๋ฆฝ๋œ ํ™˜๊ฒฝ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.\\n\\n```\\nconda create --name langgraph_env python=3.11 \\n```\\n\\n์ด ๋ช…๋ น์–ด๋Š” 'langgraph\\\\_env'๋ผ๋Š” ์ด๋ฆ„์˜ ์ƒˆ๋กœ์šด conda ํ™˜๊ฒฝ์„ ์ƒ์„ฑํ•˜๋ฉฐ, Python 3.11 ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Python ๋ฒ„์ „์€ ํ•„์š”์— ๋”ฐ๋ผ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n๊ฐ€์ƒ ํ™˜๊ฒฝ ํ™œ์„ฑํ™”: - Windows, macOS ๋ฐ Linux ๋ชจ๋‘ ๊ฐ™์€ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\\n\\n```\\nconda activate langgraph_env \\n```\\n\\nconda๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์šด์˜ ์ฒด์ œ์— ๊ด€๊ณ„์—†์ด ๋™์ผํ•œ ๋ช…๋ น์–ด๋กœ ๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n๊ฐ€์ƒ ํ™˜๊ฒฝ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\\n\\n```\\nconda deactivate \\n```\\n\\n### LangGraph ์„ค์น˜\\n\\npip๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ LangGraph๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.\\n\\n```\\npip install langgraph \\n```\\n\\n์ตœ์‹  ๋ฒ„์ „ ๋˜๋Š” ํŠน์ • ๋ฒ„์ „์„ ์„ค์น˜ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n```\\npip install -U langgraph # ์ตœ์‹  ๋ฒ„์ „ pip install langgraph==0.0.16 # ํŠน์ • ๋ฒ„์ „ (์˜ˆ: 0.0.16) \\n```\\n\\n### ์˜์กด์„ฑ ํŒจํ‚ค์ง€ ์„ค์น˜\\n\\nLangGraph๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ์˜์กด์„ฑ ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.\\n\\n```\\npip install langchain langchain-openai \\n```\\n\\n### ์„ค์น˜ ํ™•์ธ\\n\\n๋‹ค์Œ Python ์ฝ”๋“œ๋กœ LangGraph๊ฐ€ ์ œ๋Œ€๋กœ ์„ค์น˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.\\n\\n```\\nfrom langgraph.graph import StateGraph print(\\\"LangGraph successfully installed!\\\") \\n```\\n\\n### ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”๋ฅผ ์œ„ํ•œ Graphviz ์„ค์น˜ (์„ ํƒ์‚ฌํ•ญ)\\n\\nLangGraph์˜ ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Graphviz๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.\\n\\n* Windows: [Graphviz ๋‹ค์šด๋กœ๋“œ](https://graphviz.org/download/)\\n* macOS: `brew install graphviz`\\n* Linux: `sudo apt-get install graphviz`\\n\\n---\\n\\n๋งˆ์ง€๋ง‰ ํŽธ์ง‘์ผ์‹œ : 2024๋…„ 10์›” 6์ผ 4:04 ์˜คํ›„\\n\\n[๋Œ“๊ธ€ 0](javascript:show_comments();) [ํ”ผ๋“œ๋ฐฑ](#myModal \\\"ํ”ผ๋“œ๋ฐฑ์„ ๋‚จ๊ฒจ์ฃผ์„ธ์š”\\\")\\n\\n[โ€ป ๋Œ“๊ธ€ ์ž‘์„ฑ์€ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.](/loginForm) [(๋˜๋Š” ํ”ผ๋“œ๋ฐฑ์„ ์ด์šฉํ•ด ์ฃผ์„ธ์š”.)](#myModal)\\n\\n* **์ด์ „๊ธ€** : [1-2. LangGraph ํ™˜๊ฒฝ ์„ค์ • ๋ฐ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•](javascript:page(261578))\\n* **๋‹ค์Œ๊ธ€** : [1-2-2. ๊ธฐ๋ณธ์ ์ธ LLM ๋ชจ๋ธ ์„ค์ • (GPT)](javascript:page(261588))\\n\\n \\n\\n#### ์ฑ…๊ฐˆํ”ผ\\n\\n### ์ด ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ํ”ผ๋“œ๋ฐฑ์„ ๋‚จ๊ฒจ์ฃผ์„ธ์š”\\n\\n### ๋Œ“๊ธ€์„ ์‹ ๊ณ ํ•ฉ๋‹ˆ๋‹ค.\"}]\n", + "==================================================\n", + "๐Ÿ”„ Node: \u001b[1;36magent\u001b[0m ๐Ÿ”„\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "LangGraph์˜ ์ตœ์‹  ๋ฒ„์ „์€ **1.0.7**, 2026๋…„ 1์›” 22์ผ์— ์ถœ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. LangGraph๋Š” LLM(Long Language Model)์„ ์ด์šฉํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๊ฐ–๋Š” ๋ฉ€ํ‹ฐ ์•กํ„ฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๊ตฌ์ถ•ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์ค‘์ ์„ ๋‘” ์ €์ˆ˜์ค€ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.\n", + "\n", + "### ์ฃผ์š” ํŠน์ง•\n", + "- LangGraph๋Š” ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋Š” ์ž‘์—… ํ๋ฆ„์ด๋‚˜ ์—์ด์ „ํŠธ๋ฅผ ์ง€์›ํ•˜๋Š” ์ธํ”„๋ผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.\n", + "- LangChain ์ œํ’ˆ๊ณผ ํ†ตํ•ฉํ•˜์—ฌ ์—์ด์ „ํŠธ ๊ตฌ์ถ•์„ ์œ„ํ•œ ์™„๋ฒฝํ•œ ๋„๊ตฌ ์„ธํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.\n", + "- GitHub ๋ฐ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ์˜ˆ์ œ์™€ ๋ฆฌ์†Œ์Šค๊ฐ€ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.\n", + "\n", + "### ์„ค์น˜ ๋ฐฉ๋ฒ•\n", + "LangGraph๋ฅผ ์„ค์น˜ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ช…๋ น์–ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:\n", + "```bash\n", + "pip install -U langgraph\n", + "```\n", + "\n", + "์ž์„ธํ•œ ๋‚ด์šฉ์€ [PyPI ํŽ˜์ด์ง€](https://pypi.org/project/langgraph/)์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + ] + } + ], + "source": [ + "from langchain_teddynote.messages import stream_graph\n", + "\n", + "# ์—์ด์ „ํŠธ config ์„ค์ •\n", + "agent_config = RunnableConfig(\n", + " recursion_limit=20,\n", + " configurable={\"thread_id\": \"agent_session_1\"},\n", ")\n", "\n", - "# ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ\n", - "user_namespace = (\"user_456\", \"memories\")\n", - "\n", - "# ๋‹ค์–‘ํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ\n", - "memories_to_store = [\n", - " {\"id\": \"1\", \"text\": \"๋‚˜๋Š” ํ”ผ์ž๋ฅผ ์ข‹์•„ํ•ด\"},\n", - " {\"id\": \"2\", \"text\": \"๋‚ด ์ง์—…์€ ๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธํ‹ฐ์ŠคํŠธ์•ผ\"},\n", - " {\"id\": \"3\", \"text\": \"ํŒŒ์ด์ฌ๊ณผ ๋จธ์‹ ๋Ÿฌ๋‹์„ ์ฃผ๋กœ ๋‹ค๋ค„\"},\n", - " {\"id\": \"4\", \"text\": \"์ฃผ๋ง์—๋Š” ๋“ฑ์‚ฐ์„ ์ฆ๊ฒจํ•ด\"},\n", - " {\"id\": \"5\", \"text\": \"์ปคํ”ผ๋ณด๋‹ค ์ฐจ๋ฅผ ์„ ํ˜ธํ•ด\"},\n", - "]\n", - "\n", - "for memory in memories_to_store:\n", - " semantic_store.put(user_namespace, memory[\"id\"], {\"text\": memory[\"text\"]})\n", - "\n", - "print(\"โœ… ์‹œ๋งจํ‹ฑ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ ์™„๋ฃŒ!\\n\")\n", - "\n", - "# ์‹œ๋งจํ‹ฑ ๊ฒ€์ƒ‰ ํ…Œ์ŠคํŠธ\n", - "queries = [\"์Œ์‹ ์ทจํ–ฅ\", \"ํ”„๋กœ๊ทธ๋ž˜๋ฐ\", \"์—ฌ๊ฐ€ ํ™œ๋™\"]\n", - "\n", - "print(\"๐Ÿ” ์‹œ๋งจํ‹ฑ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\\n\")\n", - "for query in queries:\n", - " # ์ฟผ๋ฆฌ์™€ ์œ ์‚ฌํ•œ ๋ฉ”๋ชจ๋ฆฌ ๊ฒ€์ƒ‰\n", - " results = semantic_store.search(\n", - " user_namespace, query=query, limit=2 # ์ƒ์œ„ 2๊ฐœ ๊ฒฐ๊ณผ\n", - " )\n", - "\n", - " print(f\"Query: '{query}'\")\n", - " for item in results:\n", - " print(f\" โ†’ {item.value['text']}\")\n", - " print()" + "# ๊ฒ€์ƒ‰ ์š”์ฒญ\n", + "inputs = {\"messages\": [{\"role\": \"user\", \"content\": \"LangGraph์˜ ์ตœ์‹  ๋ฒ„์ „์— ๋Œ€ํ•ด ์•Œ๋ ค์ฃผ์„ธ์š”.\"}]}\n", + "\n", + "# ์ŠคํŠธ๋ฆฌ๋ฐ ์‹คํ–‰\n", + "stream_graph(agent_graph, inputs=inputs, config=agent_config)" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 18, + "id": "cell-29", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==================================================\n", + "๐Ÿ”„ Node: \u001b[1;36magent\u001b[0m ๐Ÿ”„\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "LangGraph์˜ ์ตœ์‹  ๋ฒ„์ „์ธ 1.0.7์€ 2026๋…„ 1์›” 22์ผ์— ์ถœ์‹œ๋˜์—ˆ์œผ๋ฉฐ, LLM์„ ์ด์šฉํ•œ ์ƒํƒœ ์œ ์ง€ ๋ฉ€ํ‹ฐ ์•กํ„ฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์ถ•์„ ์œ„ํ•œ ์ €์ˆ˜์ค€ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค." + ] + } + ], "source": [ - "## 4.2 ์‹œ๋งจํ‹ฑ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ™œ์šฉํ•œ ๋Œ€ํ™” ์‹œ์Šคํ…œ" + "# ํ›„์† ์งˆ๋ฌธ (์ด์ „ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜)\n", + "inputs = {\"messages\": [{\"role\": \"user\", \"content\": \"๋ฐฉ๊ธˆ ์•Œ๋ ค์ค€ ๋‚ด์šฉ์„ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•ด์ฃผ์„ธ์š”.\"}]}\n", + "\n", + "stream_graph(agent_graph, inputs=inputs, config=agent_config)" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", + "id": "cell-30", "metadata": {}, - "outputs": [], "source": [ - "def chat_with_semantic_memory(state: MessagesState, *, store: BaseStore):\n", - " \"\"\"Chat function with semantic memory search\"\"\"\n", - "\n", - " # ์‚ฌ์šฉ์ž์˜ ๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€\n", - " user_message = state[\"messages\"][-1].content\n", - "\n", - " # ๊ด€๋ จ ๋ฉ”๋ชจ๋ฆฌ ๊ฒ€์ƒ‰\n", - " namespace = (\"user_456\", \"memories\")\n", - " relevant_memories = store.search(namespace, query=user_message, limit=3)\n", - "\n", - " # ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ\n", - " context = \"\"\n", - " if relevant_memories:\n", - " context = \"์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ด€๋ จ ์ •๋ณด:\\n\"\n", - " context += \"\\n\".join([f\"โ€ข {item.value['text']}\" for item in relevant_memories])\n", - "\n", - " # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ\n", - " system_prompt = f\"\"\"\n", - " You are a helpful assistant with access to user's personal information.\n", - " Use the following context to provide personalized responses:\n", - " \n", - " {context}\n", - " \n", - " Respond naturally and incorporate relevant information when appropriate.\n", - " \"\"\"\n", - "\n", - " # LLM ํ˜ธ์ถœ\n", - " messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n", - "\n", - " response = llm.invoke(messages)\n", - "\n", - " return {\"messages\": [response]}\n", - "\n", - "\n", - "# ์‹œ๋งจํ‹ฑ ๋ฉ”๋ชจ๋ฆฌ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ\n", - "semantic_builder = StateGraph(MessagesState)\n", - "semantic_builder.add_node(\"chat\", chat_with_semantic_memory)\n", - "semantic_builder.add_edge(START, \"chat\")\n", - "semantic_builder.add_edge(\"chat\", END)\n", + "---\n", "\n", - "semantic_graph = semantic_builder.compile(store=semantic_store)\n", + "## ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ: ์˜๊ตฌ ์ €์žฅ์†Œ ์‚ฌ์šฉ\n", "\n", - "# ํ…Œ์ŠคํŠธ\n", - "print(\"๐Ÿ’ฌ ์‹œ๋งจํ‹ฑ ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ๋Œ€ํ™” ํ…Œ์ŠคํŠธ:\\n\")\n", + "`MemorySaver`๋Š” ์ธ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ์ด๋ฏ€๋กœ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” PostgreSQL, SQLite, Redis ๋“ฑ์˜ ์˜๊ตฌ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", - "test_messages = [\n", - " \"์˜ค๋Š˜ ์ ์‹ฌ ๋ญ ๋จน์„๊นŒ?\",\n", - " \"์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๊ณ  ํ•˜๋Š”๋ฐ ์กฐ์–ธ ์ข€ ํ•ด์ค˜\",\n", - " \"์ฃผ๋ง ๊ณ„ํš์ด ์žˆ์–ด?\",\n", - "]\n", + "### ์ง€์›๋˜๋Š” ์ฒดํฌํฌ์ธํ„ฐ\n", "\n", - "for msg in test_messages:\n", - " print(f\"๐Ÿ‘ค User: {msg}\")\n", - " result = semantic_graph.invoke({\"messages\": [{\"role\": \"user\", \"content\": msg}]})\n", - " print(f\"๐Ÿค– Bot: {result['messages'][-1].content}\\n\")" + "| ์ฒดํฌํฌ์ธํ„ฐ | ํŒจํ‚ค์ง€ | ํŠน์ง• |\n", + "|-----------|--------|------|\n", + "| PostgresSaver | langgraph-checkpoint-postgres | ACID ์ค€์ˆ˜, ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ํ‘œ์ค€ |\n", + "| SqliteSaver | langgraph-checkpoint-sqlite | ๊ฐ€๋ฒผ์šด ๋กœ์ปฌ ์ €์žฅ์†Œ |\n", + "| RedisSaver | langgraph-checkpoint-redis | ์ดˆ๊ณ ์† ๋ฉ”๋ชจ๋ฆฌ DB, ์บ์‹ฑ ์ตœ์ ํ™” |" ] }, { "cell_type": "markdown", + "id": "cell-31", "metadata": {}, "source": [ - "---\n", + "### PostgreSQL ์‚ฌ์šฉ ์˜ˆ์‹œ\n", "\n", - "# Part 5: ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ๐Ÿš€\n", + "PostgreSQL ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. `from_conn_string()` ๋ฉ”์„œ๋“œ๋กœ ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด์„ ์ „๋‹ฌํ•˜๊ณ , `setup()` ๋ฉ”์„œ๋“œ๋กœ ํ•„์š”ํ•œ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", - "## 5.1 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค Checkpointer ๋น„๊ต" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ํ”„๋กœ๋•์…˜ Checkpointer ์˜ต์…˜\n", - "production_options = {\n", - " \"PostgreSQL\": {\n", - " \"package\": \"langgraph-checkpoint-postgres\",\n", - " \"class\": \"PostgresSaver\",\n", - " \"connection\": \"postgresql://user:pass@host:port/db\",\n", - " \"features\": [\"ACID ์ค€์ˆ˜\", \"๋ณต์žกํ•œ ์ฟผ๋ฆฌ ์ง€์›\", \"์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ํ‘œ์ค€\"],\n", - " \"setup\": \"checkpointer.setup() # ์ฒซ ์‹คํ–‰ ์‹œ\",\n", - " },\n", - " \"MongoDB\": {\n", - " \"package\": \"langgraph-checkpoint-mongodb\",\n", - " \"class\": \"MongoDBSaver\",\n", - " \"connection\": \"mongodb://localhost:27017\",\n", - " \"features\": [\"NoSQL ์œ ์—ฐ์„ฑ\", \"์ˆ˜ํ‰ ํ™•์žฅ์„ฑ\", \"JSON ๋„ค์ดํ‹ฐ๋ธŒ\"],\n", - " \"setup\": \"ํด๋Ÿฌ์Šคํ„ฐ ์ƒ์„ฑ ํ•„์š”\",\n", - " },\n", - " \"Redis\": {\n", - " \"package\": \"langgraph-checkpoint-redis\",\n", - " \"class\": \"RedisSaver\",\n", - " \"connection\": \"redis://localhost:6379\",\n", - " \"features\": [\"์ดˆ๊ณ ์† ๋ฉ”๋ชจ๋ฆฌ DB\", \"์บ์‹ฑ ์ตœ์ ํ™”\", \"Pub/Sub ์ง€์›\"],\n", - " \"setup\": \"checkpointer.setup() # ์ฒซ ์‹คํ–‰ ์‹œ\",\n", - " },\n", - "}\n", - "\n", - "print(\"๐Ÿ“Š ํ”„๋กœ๋•์…˜ Checkpointer ๋น„๊ต:\\n\")\n", - "for db, info in production_options.items():\n", - " print(f\"### {db}\")\n", - " print(f\" ํŒจํ‚ค์ง€: {info['package']}\")\n", - " print(f\" ํด๋ž˜์Šค: {info['class']}\")\n", - " print(f\" ์—ฐ๊ฒฐ: {info['connection']}\")\n", - " print(f\" ํŠน์ง•: {', '.join(info['features'])}\")\n", - " print(f\" ์„ค์ •: {info['setup']}\\n\")" + "์•„๋ž˜๋Š” PostgreSQL ์ฒดํฌํฌ์ธํ„ฐ ์‚ฌ์šฉ ์˜ˆ์‹œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. (์‹ค์ œ ์‹คํ–‰ํ•˜๋ ค๋ฉด PostgreSQL ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค)" ] }, { "cell_type": "markdown", + "id": "cell-33", "metadata": {}, "source": [ - "## 5.2 ํ”„๋กœ๋•์…˜ Store ๊ตฌํ˜„ ์˜ˆ์ œ" + "#### Docker๋ฅผ ์‚ฌ์šฉํ•œ PostgreSQL ์„ค์ •\n", + "\n", + "๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ PostgreSQL์„ ๋น ๋ฅด๊ฒŒ ์„ค์ •ํ•˜๋ ค๋ฉด Docker๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "```bash\n", + "# PostgreSQL ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰\n", + "docker run --name langgraph_db \\\n", + " -e POSTGRES_PASSWORD=postgres \\\n", + " -e POSTGRES_DB=langgraph_db \\\n", + " -p 5432:5432 \\\n", + " -d postgres:15\n", + "\n", + "# ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด\n", + "DB_URI = \"postgresql://postgres:postgres@localhost:5432/langgraph_db\"\n", + "```" ] }, { "cell_type": "code", "execution_count": null, + "id": "369e7456", "metadata": {}, "outputs": [], "source": [ - "def create_production_setup():\n", - " \"\"\"Production setup example with both checkpointer and store\"\"\"\n", - "\n", - " print(\"๐Ÿญ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ์„ค์ • ์˜ˆ์ œ:\\n\")\n", - "\n", - " # PostgreSQL ์˜ˆ์ œ\n", - " postgres_example = \"\"\"\n", "from langgraph.checkpoint.postgres import PostgresSaver\n", - "from langgraph.store.postgres import PostgresStore\n", "\n", - "DB_URI = \"postgresql://user:password@localhost:5432/langgraph_db\"\n", + "DB_URI = \"postgresql://postgres:postgres@localhost:5432/langgraph_db\"\n", "\n", - "# Context manager๋กœ ์ž๋™ ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ\n", - "with (\n", - " PostgresStore.from_conn_string(DB_URI) as store,\n", - " PostgresSaver.from_conn_string(DB_URI) as checkpointer,\n", - "):\n", + "# Context manager๋กœ ์—ฐ๊ฒฐ ๊ด€๋ฆฌ\n", + "with PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n", " # ์ฒซ ์‹คํ–‰ ์‹œ ํ…Œ์ด๋ธ” ์ƒ์„ฑ\n", - " store.setup()\n", " checkpointer.setup()\n", " \n", " # ๊ทธ๋ž˜ํ”„ ์ปดํŒŒ์ผ\n", - " graph = builder.compile(\n", - " checkpointer=checkpointer, # ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ\n", - " store=store # ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ\n", - " )\n", + " graph = graph_builder.compile(checkpointer=checkpointer)\n", " \n", " # ๊ทธ๋ž˜ํ”„ ์‹คํ–‰\n", - " result = graph.invoke(input_data, config)\n", - " \"\"\"\n", - "\n", - " print(\"### PostgreSQL ์„ค์ •:\")\n", - " print(postgres_example)\n", - "\n", - " # Redis ์˜ˆ์ œ\n", - " redis_example = \"\"\"\n", - "from langgraph.checkpoint.redis import RedisSaver\n", - "from langgraph.store.redis import RedisStore\n", - "\n", - "REDIS_URI = \"redis://localhost:6379\"\n", - "\n", - "# Redis ์—ฐ๊ฒฐ\n", - "with (\n", - " RedisStore.from_conn_string(REDIS_URI) as store,\n", - " RedisSaver.from_conn_string(REDIS_URI) as checkpointer,\n", - "):\n", - " store.setup()\n", - " checkpointer.setup()\n", - " \n", - " graph = builder.compile(\n", - " checkpointer=checkpointer,\n", - " store=store\n", - " )\n", - " \"\"\"\n", - "\n", - " print(\"\\n### Redis ์„ค์ •:\")\n", - " print(redis_example)\n", - "\n", - " return \"ํ”„๋กœ๋•์…˜ ์„ค์ • ์˜ˆ์ œ ์™„๋ฃŒ\"\n", - "\n", - "\n", - "create_production_setup()" + " result = graph.invoke(inputs, config)" ] }, { "cell_type": "markdown", + "id": "cell-34", "metadata": {}, "source": [ - "## 5.3 ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค\n", - "best_practices = {\n", - " \"์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„\": [\n", - " \"๋‹จ๊ธฐ/์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„\",\n", - " \"์ ์ ˆํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ „๋žต ์ˆ˜๋ฆฝ\",\n", - " \"๋ฉ”๋ชจ๋ฆฌ ๊ณ„์ธต ๊ตฌ์กฐ ์„ค๊ณ„\",\n", - " ],\n", - " \"์„ฑ๋Šฅ ์ตœ์ ํ™”\": [\n", - " \"๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ์œผ๋กœ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ\",\n", - " \"์บ์‹ฑ ์ ๊ทน ํ™œ์šฉ\",\n", - " \"์ธ๋ฑ์‹ฑ์œผ๋กœ ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ ํ–ฅ์ƒ\",\n", - " ],\n", - " \"๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ\": [\"์ •๊ธฐ์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ\", \"๋ฐฑ์—… ์ „๋žต ์ˆ˜๋ฆฝ\", \"๋ฏผ๊ฐ ์ •๋ณด ์•”ํ˜ธํ™”\"],\n", - " \"๋ชจ๋‹ˆํ„ฐ๋ง\": [\"๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์ \", \"์‘๋‹ต ์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง\", \"์—๋Ÿฌ ๋กœ๊น… ๋ฐ ์•Œ๋ฆผ\"],\n", - " \"ํ™•์žฅ์„ฑ\": [\"์ˆ˜ํ‰ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์•„ํ‚คํ…์ฒ˜\", \"๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ ๊ณ ๋ ค\", \"์ƒค๋”ฉ ์ „๋žต ์ˆ˜๋ฆฝ\"],\n", - "}\n", - "\n", - "print(\"๐Ÿ“‹ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค:\\n\")\n", - "for category, practices in best_practices.items():\n", - " print(f\"### {category}\")\n", - " for practice in practices:\n", - " print(f\" โœ“ {practice}\")\n", - " print()" + "---\n", + "\n", + "## ์ •๋ฆฌ\n", + "\n", + "์ด๋ฒˆ ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” LangGraph์—์„œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ–ˆ์Šต๋‹ˆ๋‹ค.\n", + "\n", + "### ํ•ต์‹ฌ ๋‚ด์šฉ\n", + "\n", + "- **์ฒดํฌํฌ์ธํ„ฐ**: ๊ทธ๋ž˜ํ”„์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ณต์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ\n", + "- **thread_id**: ๋Œ€ํ™” ์„ธ์…˜์„ ๊ตฌ๋ถ„ํ•˜๋Š” ์‹๋ณ„์ž๋กœ, ๊ฐ™์€ thread_id์—์„œ๋Š” ๋Œ€ํ™”๊ฐ€ ์ด์–ด์ง\n", + "- **MemorySaver**: ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ์šฉ ์ธ๋ฉ”๋ชจ๋ฆฌ ์ฒดํฌํฌ์ธํ„ฐ\n", + "- **get_state()**: ํ˜„์žฌ ์ƒํƒœ ์Šค๋ƒ…์ƒท ์กฐํšŒ\n", + "- **get_state_history()**: ์ƒํƒœ ๋ณ€๊ฒฝ ์ด๋ ฅ ์กฐํšŒ" ] } ], @@ -1121,18 +818,10 @@ "name": "python3" }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", "version": "3.11.0" } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/05-Memory/03-LangGraph-Short-Term-Memory.ipynb b/05-Memory/03-LangGraph-Short-Term-Memory.ipynb index 45dda10..c7730e8 100644 --- a/05-Memory/03-LangGraph-Short-Term-Memory.ipynb +++ b/05-Memory/03-LangGraph-Short-Term-Memory.ipynb @@ -1,628 +1,1143 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# LangChain ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ\n", - "\n", - "๋ฉ”๋ชจ๋ฆฌ๋Š” ์ด์ „ ์ƒํ˜ธ์ž‘์šฉ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ธฐ์–ตํ•˜๋Š” ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. AI ์—์ด์ „ํŠธ์˜ ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ๋Š” ์ด์ „ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ธฐ์–ตํ•˜๊ณ , ํ”ผ๋“œ๋ฐฑ์œผ๋กœ๋ถ€ํ„ฐ ํ•™์Šตํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž ์„ ํ˜ธ๋„์— ์ ์‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋ฏ€๋กœ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.\n", - "\n", - "๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹จ์ผ ์Šค๋ ˆ๋“œ ๋˜๋Š” ๋Œ€ํ™” ๋‚ด์—์„œ ์ด์ „ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ธฐ์–ตํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.\n", - "\n", - "**์Šค๋ ˆ๋“œ**๋Š” ์ด๋ฉ”์ผ์ด ๋‹จ์ผ ๋Œ€ํ™”์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜๋Š” ๋ฐฉ์‹๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์„ธ์…˜์—์„œ ์—ฌ๋Ÿฌ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", - "\n", - "๋Œ€ํ™” ๊ธฐ๋ก์€ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ํ˜•ํƒœ์˜ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ์ž…๋‹ˆ๋‹ค. ๊ธด ๋Œ€ํ™”๋Š” ์˜ค๋Š˜๋‚ ์˜ LLM์— ๋„์ „ ๊ณผ์ œ๋ฅผ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ๊ธฐ๋ก์ด LLM์˜ ์ปจํ…์ŠคํŠธ ์ฐฝ์— ๋งž์ง€ ์•Š์•„ ์ปจํ…์ŠคํŠธ ์†์‹ค์ด๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ์‚ฌ์ „ ์ค€๋น„\n", - "\n", - "ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "\n", - "load_dotenv(override=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•\n", - "\n", - "์—์ด์ „ํŠธ์— ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(์Šค๋ ˆ๋“œ ์ˆ˜์ค€ ์ง€์†์„ฑ)๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ์—์ด์ „ํŠธ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ `checkpointer`๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", - "\n", - "LangChain์˜ ์—์ด์ „ํŠธ๋Š” ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์—์ด์ „ํŠธ ์ƒํƒœ์˜ ์ผ๋ถ€๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜ํ”„์˜ ์ƒํƒœ์— ์ €์žฅํ•จ์œผ๋กœ์จ ์—์ด์ „ํŠธ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ ๊ฐ„์˜ ๋ถ„๋ฆฌ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ํŠน์ • ๋Œ€ํ™”์— ๋Œ€ํ•œ ์ „์ฒด ์ปจํ…์ŠคํŠธ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent\n", - "from langchain_openai import ChatOpenAI\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "from langchain.tools import tool\n", - "\n", - "# ๊ฐ„๋‹จํ•œ ๋„๊ตฌ ์ •์˜\n", - "@tool\n", - "def get_user_info(user_id: str) -> str:\n", - " \"\"\"Get user information.\"\"\"\n", - " return f\"User info for {user_id}\"\n", - "\n", - "# ๋ชจ๋ธ ๋ฐ ์—์ด์ „ํŠธ ์ƒ์„ฑ (์ฒดํฌํฌ์ธํ„ฐ ํฌํ•จ)\n", - "model = ChatOpenAI(model=\"gpt-4.1-mini\")\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[get_user_info],\n", - " checkpointer=InMemorySaver(), # ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ\n", - ")\n", - "\n", - "# thread_id๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€ํ™” ์ถ”์ \n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "# ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€\n", - "result1 = agent.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"Hi! My name is Bob.\"}]},\n", - " config\n", - ")\n", - "print(\"Response 1:\", result1[\"messages\"][-1].content)\n", - "\n", - "# ๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€ (์ด์ „ ๋Œ€ํ™” ๊ธฐ์–ต)\n", - "result2 = agent.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"What's my name?\"}]},\n", - " config\n", - ")\n", - "print(\"Response 2:\", result2[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ\n", - "\n", - "ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# PostgreSQL ์ฒดํฌํฌ์ธํ„ฐ ์˜ˆ์ œ (์„ค์น˜ ํ•„์š”: pip install langgraph-checkpoint-postgres)\n", - "# from langgraph.checkpoint.postgres import PostgresSaver\n", - "\n", - "# DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\"\n", - "# with PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n", - "# checkpointer.setup() # PostgreSQL์— ํ…Œ์ด๋ธ” ์ž๋™ ์ƒ์„ฑ\n", - "# agent = create_agent(\n", - "# model=model,\n", - "# tools=[get_user_info],\n", - "# checkpointer=checkpointer,\n", - "# )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ์—์ด์ „ํŠธ ๋ฉ”๋ชจ๋ฆฌ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•\n", - "\n", - "๊ธฐ๋ณธ์ ์œผ๋กœ ์—์ด์ „ํŠธ๋Š” `AgentState`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ `messages` ํ‚ค๋ฅผ ํ†ตํ•œ ๋Œ€ํ™” ๊ธฐ๋ก์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\n", - "\n", - "`AgentState`๋ฅผ ํ™•์žฅํ•˜์—ฌ ์ถ”๊ฐ€ ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent, AgentState\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "\n", - "# ์ปค์Šคํ…€ ์ƒํƒœ ์ •์˜\n", - "class CustomAgentState(AgentState):\n", - " user_id: str\n", - " preferences: dict\n", - "\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[get_user_info],\n", - " state_schema=CustomAgentState, # ์ปค์Šคํ…€ ์ƒํƒœ ์Šคํ‚ค๋งˆ\n", - " checkpointer=InMemorySaver(),\n", - ")\n", - "\n", - "# ์ปค์Šคํ…€ ์ƒํƒœ๋ฅผ invoke์— ์ „๋‹ฌ\n", - "result = agent.invoke(\n", - " {\n", - " \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}],\n", - " \"user_id\": \"user_123\",\n", - " \"preferences\": {\"theme\": \"dark\"}\n", - " },\n", - " {\"configurable\": {\"thread_id\": \"1\"}}\n", - ")\n", - "\n", - "print(result[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด\n", - "\n", - "๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ ๊ธด ๋Œ€ํ™”๋Š” LLM์˜ ์ปจํ…์ŠคํŠธ ์ฐฝ์„ ์ดˆ๊ณผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ ํ•ด๊ฒฐ์ฑ…์€:\n", - "\n", - "1. **๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ** - ์ฒ˜์Œ ๋˜๋Š” ๋งˆ์ง€๋ง‰ N๊ฐœ์˜ ๋ฉ”์‹œ์ง€ ์ œ๊ฑฐ (LLM ํ˜ธ์ถœ ์ „)\n", - "2. **๋ฉ”์‹œ์ง€ ์‚ญ์ œ** - LangGraph ์ƒํƒœ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์˜๊ตฌ์ ์œผ๋กœ ์‚ญ์ œ\n", - "3. **๋ฉ”์‹œ์ง€ ์š”์•ฝ** - ๊ธฐ๋ก์˜ ์ด์ „ ๋ฉ”์‹œ์ง€๋ฅผ ์š”์•ฝํ•˜๊ณ  ์š”์•ฝ์œผ๋กœ ๋Œ€์ฒด\n", - "4. **์ปค์Šคํ…€ ์ „๋žต** - ๋ฉ”์‹œ์ง€ ํ•„ํ„ฐ๋ง ๋“ฑ์˜ ์ปค์Šคํ…€ ์ „๋žต" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ\n", - "\n", - "๋Œ€๋ถ€๋ถ„์˜ LLM์—๋Š” ์ตœ๋Œ€ ์ง€์› ์ปจํ…์ŠคํŠธ ์ฐฝ(ํ† ํฐ ๋‹จ์œ„)์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•˜๋Š” ์‹œ๊ธฐ๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ํ•œ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์€ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก์˜ ํ† ํฐ์„ ์„ธ๊ณ  ํ•œ๊ณ„์— ์ ‘๊ทผํ•  ๋•Œ๋งˆ๋‹ค ํŠธ๋ฆฌ๋ฐํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.\n", - "\n", - "์—์ด์ „ํŠธ์—์„œ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก์„ ํŠธ๋ฆฌ๋ฐํ•˜๋ ค๋ฉด `@before_model` ๋ฏธ๋“ค์›จ์–ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.messages import RemoveMessage\n", - "from langgraph.graph.message import REMOVE_ALL_MESSAGES\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "from langchain.agents import create_agent, AgentState\n", - "from langchain.agents.middleware import before_model\n", - "from langgraph.runtime import Runtime\n", - "from typing import Any\n", - "\n", - "@before_model\n", - "def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:\n", - " \"\"\"์ปจํ…์ŠคํŠธ ์ฐฝ์— ๋งž๋„๋ก ์ตœ๊ทผ ๋ช‡ ๊ฐœ์˜ ๋ฉ”์‹œ์ง€๋งŒ ์œ ์ง€\"\"\"\n", - " messages = state[\"messages\"]\n", - "\n", - " if len(messages) <= 3:\n", - " return None # ๋ณ€๊ฒฝ ํ•„์š” ์—†์Œ\n", - "\n", - " # ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€์™€ ์ตœ๊ทผ ๋ฉ”์‹œ์ง€๋งŒ ์œ ์ง€\n", - " first_msg = messages[0]\n", - " recent_messages = messages[-3:] if len(messages) % 2 == 0 else messages[-4:]\n", - " new_messages = [first_msg] + recent_messages\n", - "\n", - " return {\n", - " \"messages\": [\n", - " RemoveMessage(id=REMOVE_ALL_MESSAGES),\n", - " *new_messages\n", - " ]\n", - " }\n", - "\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[],\n", - " middleware=[trim_messages],\n", - " checkpointer=InMemorySaver(),\n", - ")\n", - "\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"hi, my name is bob\"}]}, config)\n", - "agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"write a short poem about cats\"}]}, config)\n", - "agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"now do the same but for dogs\"}]}, config)\n", - "final_response = agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]}, config)\n", - "\n", - "print(final_response[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", - "\n", - "๊ทธ๋ž˜ํ”„ ์ƒํƒœ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•˜์—ฌ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ • ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ ์ „์ฒด ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก์„ ์ง€์šฐ๋ ค๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.\n", - "\n", - "๊ทธ๋ž˜ํ”„ ์ƒํƒœ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•˜๋ ค๋ฉด `RemoveMessage`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.messages import RemoveMessage\n", - "from langchain.agents import create_agent, AgentState\n", - "from langchain.agents.middleware import after_model\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "from langgraph.runtime import Runtime\n", - "\n", - "@after_model\n", - "def delete_old_messages(state: AgentState, runtime: Runtime) -> dict | None:\n", - " \"\"\"๋Œ€ํ™”๋ฅผ ๊ด€๋ฆฌ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€ ์ œ๊ฑฐ\"\"\"\n", - " messages = state[\"messages\"]\n", - " if len(messages) > 2:\n", - " # ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ๋‘ ๊ฐœ์˜ ๋ฉ”์‹œ์ง€ ์ œ๊ฑฐ\n", - " return {\"messages\": [RemoveMessage(id=m.id) for m in messages[:2]]}\n", - " return None\n", - "\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[],\n", - " system_prompt=\"Please be concise and to the point.\",\n", - " middleware=[delete_old_messages],\n", - " checkpointer=InMemorySaver(),\n", - ")\n", - "\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "# ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€\n", - "result1 = agent.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " config\n", - ")\n", - "print(\"Messages after first invoke:\", len(result1[\"messages\"]))\n", - "\n", - "# ๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€\n", - "result2 = agent.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " config\n", - ")\n", - "print(\"Messages after second invoke:\", len(result2[\"messages\"]))\n", - "print(\"Last message:\", result2[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ๋ฉ”์‹œ์ง€ ์š”์•ฝ\n", - "\n", - "๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๋Š” ๋ฌธ์ œ๋Š” ๋ฉ”์‹œ์ง€ ํ๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ์ •๋ณด๋ฅผ ์žƒ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋•Œ๋ฌธ์— ์ผ๋ถ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ฑ„ํŒ… ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก์„ ์š”์•ฝํ•˜๋Š” ๋” ์ •๊ตํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์˜ ์ด์ ์„ ์–ป์Šต๋‹ˆ๋‹ค.\n", - "\n", - "์—์ด์ „ํŠธ์—์„œ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก์„ ์š”์•ฝํ•˜๋ ค๋ฉด ๋‚ด์žฅ๋œ `SummarizationMiddleware`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent\n", - "from langchain.agents.middleware import SummarizationMiddleware\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "\n", - "checkpointer = InMemorySaver()\n", - "\n", - "agent = create_agent(\n", - " model=\"openai:gpt-4.1-mini\",\n", - " tools=[],\n", - " middleware=[\n", - " SummarizationMiddleware(\n", - " model=\"openai:gpt-4.1-mini\",\n", - " max_tokens_before_summary=4000, # 4000 ํ† ํฐ์—์„œ ์š”์•ฝ ํŠธ๋ฆฌ๊ฑฐ\n", - " messages_to_keep=20, # ์š”์•ฝ ํ›„ ์ตœ๊ทผ 20๊ฐœ ๋ฉ”์‹œ์ง€ ์œ ์ง€\n", - " )\n", - " ],\n", - " checkpointer=checkpointer,\n", - ")\n", - "\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"hi, my name is bob\"}]}, config)\n", - "agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"write a short poem about cats\"}]}, config)\n", - "agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"now do the same but for dogs\"}]}, config)\n", - "final_response = agent.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]}, config)\n", - "\n", - "print(final_response[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ๋ฉ”๋ชจ๋ฆฌ ์•ก์„ธ์Šค\n", - "\n", - "์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์—์ด์ „ํŠธ์˜ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(์ƒํƒœ)์— ์•ก์„ธ์Šคํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ๋„๊ตฌ์—์„œ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ์ฝ๊ธฐ\n", - "\n", - "`ToolRuntime` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋„๊ตฌ์—์„œ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(์ƒํƒœ)์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", - "\n", - "`tool_runtime` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋„๊ตฌ ์‹œ๊ทธ๋‹ˆ์ฒ˜์—์„œ ์ˆจ๊ฒจ์ ธ ์žˆ์ง€๋งŒ(๋ชจ๋ธ์ด ๋ณผ ์ˆ˜ ์—†์Œ) ๋„๊ตฌ๋Š” ์ด๋ฅผ ํ†ตํ•ด ์ƒํƒœ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent, AgentState\n", - "from langchain.tools import tool, ToolRuntime\n", - "\n", - "class CustomState(AgentState):\n", - " user_id: str\n", - "\n", - "@tool\n", - "def get_user_info(\n", - " runtime: ToolRuntime\n", - ") -> str:\n", - " \"\"\"Look up user info.\"\"\"\n", - " user_id = runtime.state[\"user_id\"]\n", - " return \"User is John Smith\" if user_id == \"user_123\" else \"Unknown user\"\n", - "\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[get_user_info],\n", - " state_schema=CustomState,\n", - ")\n", - "\n", - "result = agent.invoke({\n", - " \"messages\": [{\"role\": \"user\", \"content\": \"look up user information\"}],\n", - " \"user_id\": \"user_123\"\n", - "})\n", - "\n", - "print(result[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ๋„๊ตฌ์—์„œ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ์“ฐ๊ธฐ\n", - "\n", - "์‹คํ–‰ ์ค‘์— ์—์ด์ „ํŠธ์˜ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(์ƒํƒœ)๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๋ฉด ๋„๊ตฌ์—์„œ ์ง์ ‘ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.tools import tool, ToolRuntime\n", - "from langchain.messages import ToolMessage\n", - "from langchain.agents import create_agent, AgentState\n", - "from langgraph.types import Command\n", - "from pydantic import BaseModel\n", - "\n", - "class CustomState(AgentState):\n", - " user_name: str\n", - "\n", - "class CustomContext(BaseModel):\n", - " user_id: str\n", - "\n", - "@tool\n", - "def update_user_info(\n", - " runtime: ToolRuntime[CustomContext, CustomState],\n", - ") -> Command:\n", - " \"\"\"Look up and update user info.\"\"\"\n", - " user_id = runtime.context.user_id\n", - " name = \"John Smith\" if user_id == \"user_123\" else \"Unknown user\"\n", - " return Command(update={\n", - " \"user_name\": name,\n", - " \"messages\": [\n", - " ToolMessage(\n", - " \"Successfully looked up user information\",\n", - " tool_call_id=runtime.tool_call_id\n", - " )\n", - " ]\n", - " })\n", - "\n", - "@tool\n", - "def greet(\n", - " runtime: ToolRuntime[CustomContext, CustomState]\n", - ") -> str:\n", - " \"\"\"Use this to greet the user once you found their info.\"\"\"\n", - " user_name = runtime.state[\"user_name\"]\n", - " return f\"Hello {user_name}!\"\n", - "\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[update_user_info, greet],\n", - " state_schema=CustomState,\n", - " context_schema=CustomContext,\n", - ")\n", - "\n", - "result = agent.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"greet the user\"}]},\n", - " context=CustomContext(user_id=\"user_123\"),\n", - ")\n", - "\n", - "print(result[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ํ”„๋กฌํ”„ํŠธ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์•ก์„ธ์Šค\n", - "\n", - "๋Œ€ํ™” ๊ธฐ๋ก์ด๋‚˜ ์ปค์Šคํ…€ ์ƒํƒœ ํ•„๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์  ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋ฏธ๋“ค์›จ์–ด์—์„œ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(์ƒํƒœ)์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent\n", - "from typing import TypedDict\n", - "from langchain.agents.middleware import dynamic_prompt, ModelRequest\n", - "from langchain.tools import tool\n", - "\n", - "class CustomContext(TypedDict):\n", - " user_name: str\n", - "\n", - "@tool\n", - "def get_weather(city: str) -> str:\n", - " \"\"\"Get the weather in a city.\"\"\"\n", - " return f\"The weather in {city} is always sunny!\"\n", - "\n", - "@dynamic_prompt\n", - "def dynamic_system_prompt(request: ModelRequest) -> str:\n", - " user_name = request.runtime.context[\"user_name\"]\n", - " system_prompt = f\"You are a helpful assistant. Address the user as {user_name}.\"\n", - " return system_prompt\n", - "\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[get_weather],\n", - " middleware=[dynamic_system_prompt],\n", - " context_schema=CustomContext,\n", - ")\n", - "\n", - "result = agent.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in SF?\"}]},\n", - " context=CustomContext(user_name=\"John Smith\"),\n", - ")\n", - "\n", - "print(result[\"messages\"][-1].content)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ์ข…ํ•ฉ ์˜ˆ์ œ\n", - "\n", - "๋‹ค์–‘ํ•œ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ธฐ๋ฒ•์„ ๊ฒฐํ•ฉํ•œ ์‹ค์šฉ์ ์ธ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent, AgentState\n", - "from langchain.agents.middleware import SummarizationMiddleware, before_model\n", - "from langchain.tools import tool, ToolRuntime\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "from langgraph.runtime import Runtime\n", - "from typing import Any\n", - "\n", - "# ์ปค์Šคํ…€ ์ƒํƒœ\n", - "class ConversationState(AgentState):\n", - " user_preferences: dict\n", - " conversation_count: int\n", - "\n", - "# ๋„๊ตฌ ์ •์˜\n", - "@tool\n", - "def save_preference(\n", - " preference_key: str,\n", - " preference_value: str,\n", - " runtime: ToolRuntime\n", - ") -> str:\n", - " \"\"\"Save user preference.\"\"\"\n", - " return f\"Saved preference: {preference_key} = {preference_value}\"\n", - "\n", - "# ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ ๋ฏธ๋“ค์›จ์–ด\n", - "@before_model\n", - "def count_and_trim(state: ConversationState, runtime: Runtime) -> dict[str, Any] | None:\n", - " messages = state[\"messages\"]\n", - " count = state.get(\"conversation_count\", 0) + 1\n", - " \n", - " updates = {\"conversation_count\": count}\n", - " \n", - " if len(messages) > 10:\n", - " print(f\"Trimming messages (conversation #{count})\")\n", - " from langchain.messages import RemoveMessage\n", - " from langgraph.graph.message import REMOVE_ALL_MESSAGES\n", - " updates[\"messages\"] = [\n", - " RemoveMessage(id=REMOVE_ALL_MESSAGES),\n", - " messages[0],\n", - " *messages[-5:]\n", - " ]\n", - " \n", - " return updates\n", - "\n", - "# ์—์ด์ „ํŠธ ์ƒ์„ฑ\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[save_preference],\n", - " state_schema=ConversationState,\n", - " middleware=[\n", - " count_and_trim,\n", - " SummarizationMiddleware(\n", - " model=\"openai:gpt-4.1-mini\",\n", - " max_tokens_before_summary=5000,\n", - " messages_to_keep=10,\n", - " )\n", - " ],\n", - " checkpointer=InMemorySaver(),\n", - ")\n", - "\n", - "# ํ…Œ์ŠคํŠธ\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "result = agent.invoke(\n", - " {\n", - " \"messages\": [{\"role\": \"user\", \"content\": \"Hi! I prefer dark mode.\"}],\n", - " \"user_preferences\": {},\n", - " \"conversation_count\": 0\n", - " },\n", - " config\n", - ")\n", - "\n", - "print(\"\\nFinal state:\")\n", - "print(f\"Conversation count: {result.get('conversation_count', 0)}\")\n", - "print(f\"Message count: {len(result['messages'])}\")\n", - "print(f\"Last message: {result['messages'][-1].content}\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0" - } - }, - "nbformat": 4, - "nbformat_minor": 4 + "cells": [ + { + "cell_type": "markdown", + "id": "cell-0", + "metadata": {}, + "source": [ + "# LangGraph ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ\n", + "\n", + "๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ(Short-term Memory)๋Š” ํ•˜๋‚˜์˜ ๋Œ€ํ™” ์„ธ์…˜(thread) ๋‚ด์—์„œ ์ด์ „ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ธฐ์–ตํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. LangGraph์—์„œ๋Š” ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ํ†ตํ•ด ๋Œ€ํ™” ๊ธฐ๋ก์„ ์ €์žฅํ•˜๊ณ , ๋™์ผํ•œ `thread_id`๋กœ ํ˜ธ์ถœํ•˜๋ฉด ์ด์ „ ๋Œ€ํ™”๋ฅผ ์ด์–ด์„œ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", + "\n", + "๊ทธ๋Ÿฌ๋‚˜ ๋Œ€ํ™”๊ฐ€ ๊ธธ์–ด์ง€๋ฉด LLM์˜ ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ ์ œํ•œ์— ๋„๋‹ฌํ•˜๊ฑฐ๋‚˜ ํ† ํฐ ๋น„์šฉ์ด ์ฆ๊ฐ€ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ(trimming), ์‚ญ์ œ(deletion), ์š”์•ฝ(summarization) ๋“ฑ์˜ ์ „๋žต์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "> ์ฐธ๊ณ  ๋ฌธ์„œ: [LangGraph Persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-1", + "metadata": {}, + "source": [ + "## ํ•™์Šต ๋ชฉํ‘œ\n", + "\n", + "์ด ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” ๋‹ค์Œ ๋‚ด์šฉ์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค:\n", + "\n", + "- ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ(trim_messages)์„ ํ†ตํ•œ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ\n", + "- RemoveMessage๋ฅผ ์‚ฌ์šฉํ•œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", + "- ๊ทธ๋ž˜ํ”„ ๋‚ด์—์„œ ๋™์ ์œผ๋กœ ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ\n", + "- ๋Œ€ํ™” ์š”์•ฝ์„ ํ†ตํ•œ ์ปจํ…์ŠคํŠธ ์••์ถ•" + ] + }, + { + "cell_type": "markdown", + "id": "cell-2", + "metadata": {}, + "source": [ + "## ํ™˜๊ฒฝ ์„ค์ •\n", + "\n", + "LangGraph ํŠœํ† ๋ฆฌ์–ผ์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ํ•„์š”ํ•œ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. `dotenv`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API ํ‚ค๋ฅผ ๋กœ๋“œํ•˜๊ณ , `langchain_teddynote`์˜ ๋กœ๊น… ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜์—ฌ LangSmith์—์„œ ์‹คํ–‰ ์ถ”์ ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋กœ๋“œํ•˜๊ณ  LangSmith ํ”„๋กœ์ ํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# API ํ‚ค๋ฅผ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ • ํŒŒ์ผ\n", + "from dotenv import load_dotenv\n", + "\n", + "# API ํ‚ค ์ •๋ณด ๋กœ๋“œ\n", + "load_dotenv(override=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LangSmith ์ถ”์ ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.\n", + "[ํ”„๋กœ์ ํŠธ๋ช…]\n", + "LangGraph-V1-Tutorial\n" + ] + } + ], + "source": [ + "# LangSmith ์ถ”์ ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. https://smith.langchain.com\n", + "from langchain_teddynote import logging\n", + "\n", + "# ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.\n", + "logging.langsmith(\"LangGraph-V1-Tutorial\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-5", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## ๊ธฐ๋ณธ ์—์ด์ „ํŠธ ๊ตฌ์ถ•\n", + "\n", + "๋จผ์ € ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ ๊ธฐ๋ฒ•์„ ํ…Œ์ŠคํŠธํ•  ๊ธฐ๋ณธ ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. ์ด ์—์ด์ „ํŠธ๋Š” ๊ฒ€์ƒ‰ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, `MemorySaver` ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ํ†ตํ•ด ๋Œ€ํ™” ๊ธฐ๋ก์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€๊ฐ€ ๋ˆ„์ ๋˜๋ฉด์„œ ๋‹ค์–‘ํ•œ ๊ด€๋ฆฌ ๊ธฐ๋ฒ•์„ ์ ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ๊ฒ€์ƒ‰ ๋„๊ตฌ, LLM ๋ชจ๋ธ, ๊ทธ๋ฆฌ๊ณ  StateGraph๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๊ธฐ๋ณธ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\n" + ] + } + ], + "source": [ + "from typing import Literal\n", + "\n", + "from langchain_core.tools import tool\n", + "from langchain_openai import ChatOpenAI\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.graph import MessagesState, StateGraph, START, END\n", + "from langgraph.prebuilt import ToolNode, tools_condition\n", + "\n", + "# ์ฒดํฌํฌ์ธํŠธ ์ €์žฅ์„ ์œ„ํ•œ ๋ฉ”๋ชจ๋ฆฌ ๊ฐ์ฒด ์ดˆ๊ธฐํ™”\n", + "memory = MemorySaver()\n", + "\n", + "\n", + "# ์›น ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ๋ชจ๋ฐฉํ•˜๋Š” ๋„๊ตฌ ํ•จ์ˆ˜ ์ •์˜\n", + "@tool\n", + "def search(query: str):\n", + " \"\"\"์›น ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\"\"\"\n", + " return f\"๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ: '{query}'์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. LangGraph๋Š” ์ƒํƒœ ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.\"\n", + "\n", + "\n", + "# ๋„๊ตฌ ๋ชฉ๋ก ์ƒ์„ฑ ๋ฐ ๋„๊ตฌ ๋…ธ๋“œ ์ดˆ๊ธฐํ™”\n", + "tools = [search]\n", + "tool_node = ToolNode(tools)\n", + "\n", + "# ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ๋ฐ ๋„๊ตฌ ๋ฐ”์ธ๋”ฉ\n", + "model = ChatOpenAI(model=\"gpt-4o-mini\")\n", + "model_with_tools = model.bind_tools(tools)\n", + "\n", + "\n", + "# LLM ๋ชจ๋ธ ํ˜ธ์ถœ ๋ฐ ์‘๋‹ต ์ฒ˜๋ฆฌ ํ•จ์ˆ˜\n", + "def call_model(state: MessagesState):\n", + " \"\"\"์—์ด์ „ํŠธ ๋…ธ๋“œ ํ•จ์ˆ˜\n", + " \n", + " ํ˜„์žฌ ๋ฉ”์‹œ์ง€๋ฅผ LLM์— ์ „๋‹ฌํ•˜๊ณ  ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " response = model_with_tools.invoke(state[\"messages\"])\n", + " return {\"messages\": [response]}\n", + "\n", + "\n", + "# ์ƒํƒœ ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ ๊ทธ๋ž˜ํ”„ ์ดˆ๊ธฐํ™”\n", + "workflow = StateGraph(MessagesState)\n", + "\n", + "# ์—์ด์ „ํŠธ์™€ ๋„๊ตฌ ๋…ธ๋“œ ์ถ”๊ฐ€\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"tools\", tool_node)\n", + "\n", + "# ์‹œ์ž‘์ ์„ ์—์ด์ „ํŠธ ๋…ธ๋“œ๋กœ ์„ค์ •\n", + "workflow.add_edge(START, \"agent\")\n", + "\n", + "# ์กฐ๊ฑด๋ถ€ ์—ฃ์ง€ ์„ค์ •: ๋„๊ตฌ ํ˜ธ์ถœ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋ถ„๊ธฐ\n", + "workflow.add_conditional_edges(\"agent\", tools_condition)\n", + "\n", + "# ๋„๊ตฌ ์‹คํ–‰ ํ›„ ์—์ด์ „ํŠธ๋กœ ๋Œ์•„๊ฐ€๋Š” ์—ฃ์ง€ ์ถ”๊ฐ€\n", + "workflow.add_edge(\"tools\", \"agent\")\n", + "\n", + "# ์ฒดํฌํฌ์ธํ„ฐ๊ฐ€ ํฌํ•จ๋œ ์ตœ์ข… ์›Œํฌํ”Œ๋กœ์šฐ ์ปดํŒŒ์ผ\n", + "app = workflow.compile(checkpointer=memory)\n", + "\n", + "print(\"๊ธฐ๋ณธ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-7", + "metadata": {}, + "source": [ + "### ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", + "\n", + "์ปดํŒŒ์ผ๋œ ๊ทธ๋ž˜ํ”„์˜ ๊ตฌ์กฐ๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค. `agent` ๋…ธ๋“œ์—์„œ ๋„๊ตฌ ํ˜ธ์ถœ ์—ฌ๋ถ€์— ๋”ฐ๋ผ `tools` ๋…ธ๋“œ๋กœ ๋ถ„๊ธฐํ•˜๊ฑฐ๋‚˜ ์ข…๋ฃŒ๋˜๋Š” ํ๋ฆ„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ๋Š” ๊ทธ๋ž˜ํ”„๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-8", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAANwAAAD5CAIAAADDWcxTAAAQAElEQVR4nOydB2AU1dbH72wv2fRGCukJJQmhhF7yqCJIsVJFEFs+UbAhKshDQFARUB8gYAGJCqhIla50ktBbGoSQ3kjfvrPzndlJQoAkEMnuzuze3+ONd+/cmc3O/vfce85tAoqiEAbDJgQIg2EZWJQY1oFFiWEdWJQY1oFFiWEdWJQY1sGfP38+wtxNiU79U07GH/nXk8qLNudmJOSm8xFqp3B978qJH7NTBQSvncJl3rXT629d0xjIaCe3j6+eXp99rUqv6+zsMffaqe9upRgpY0dHt8VpZ9ZkXYGgWwdH1/cvn/whJ4VJf3D15PfZKU4CYbDc6e1LxzbmpMGbdlC4zr166rvsFBlfEObgPOvy0Z9y0pgyH5nyEaKgzDuXj2/ISXUSCILlzrMuHYMyEh4/3MF5QWrSlrzrl6vLVEbSV+7IaWODLeUdstTKJWnJJVpVjUEv4QscBMIAuYKAExRSkeRtnQaZ0mpSB2kjpCiko+h8kqDzDZSRzqfotMZIp0kjXUprpMtQhNF0raH+WiZNma7VMPmmtFqvp/NN91EZ9KbyRlOZO/dXGug0oqgG94Q/Up9RXX6lsvTLjHNCHr+fh897IZ0RByFw8BwgEXr9wj+Zykp3sfRZ39C+bj6I42zITkuvLr+lrmorc/w2Jg5xCixK9MG1U+fKi3u5+bweHIVsC53ROOfqySKt+s3QmMc8/RFHsHdRxl/4p1CjWtdlILJdrikr1mVe9pbKP+vQG3EBuxblvJREgkBvBHdCdsBbl0+AT/Z2aAxiPTxkrzybtFdDknaiSODLqD5XqkrfuXICsR47FWX8xSP+MsXs8C7Invgisi+46qtuXkbsxh5Fua0gE76bD8K7IvtjQfseuwpuXqopQyzGHkW5LuvqKzbnaD88I9oEL05JRizG7kT5xuVjbkJJmMwR2SvjfEMVAsGOoizEVuxOlLnK6lnhHPBAzUoHhfumW6mIrdiXKLfm31AIRQESBbIsSxfO/nH9V6jljB8z4NKFM6i1eb5tRJVee6W6HLES+xLltrwbziIxsixqtWrP9i1hER1QCzmbdOJmZvq/uPBhCHJw2l14E7ES+xJltUEb5+GLzMO5M6c+W/T+0yN794jyHtyn3ZaE9ZB57Mj+uO7BGo36rfhJH737CuSkpV6GYs880ad/t8CXp4w6eewwczn0YsT1CPl101rI7NXJZ/nSefEvPkVndg/etmUjam0iHd1zVNWIldjRKCENQnyC19/VLIMtdHrd7JlTBwwcvmDJ//zbBp1JOv7+rOnBIRH9Bgyd9vLMnX9u3nXoPBQzGo3z3ot3dHZ+/6OlSlXN0b/3zYqfsGP/Wa82voX5uWqV8uDe7WOenrx81c9yuUPqtYue3j6fLF2NzEAbmXx/0S3ESuxIlFcrS4SEuWqGspLiqsqKfnFDO0TSo8X+M3jkr9uPtg0IgfT1jNT6KpjH461Y/bNEKnVxdYeXQcERO7f9kpFxDUSZnkbHtIcMHzNyzDimcFrK5b4DhiDzEK1w0xlJxErsSJSpVbe1lBGZB1BV9179P1s0p7S4qGff//j6BQQFhzOnrmdcG/LYaCYN7cvtv2+6eD4p6+b1stslTKa7hxddLD1VKpWBmWQyC/JyoHBYRCQyDy4CkZjga5BBwj4N2FGbkkfwCHNpEhEEsWT5d0MfG/PLprXPjOy94rN5ICnIV6mU+bnZoeEdkanufnXKGKigxz47ZceBc4mXC6e9MgsuDAwKg7M3MlIiO3UTiyXMDTPSr8KxQ6QZo1dQbRjM9kAeBTsSZZjCRSY0o1WQyxVvvjt/684Ts+d+tn/v9oVz34TMjFRaW+HtaFGeSzqRmnLpgwXLhw4fIxQK6bNp14JCIiQSKaTTU6+EhLarvxtU+m7uno5Ozsg8VBtJDUk68NhYVdqRKIPlLpV6LTID4CP/fXCXTkffHCzf6KcmDhw8IudWFrzMvJ4qEAiYxmVhYR4cvbxrPa2igrzEk/8wegU/KSf7Zmh4+/p7ZmakBIVGILNxqaKEQARiJXYkSleBgKSoxIoS1NqolNXz58z4cunclGsXy8tK9+3ZtnvHlthe/eFUWVkpj8cHG1lSXNg2MBRyDu7dAUcwmQvmzVQ4Onl4toGX6Sm0lxPaICRZXlGmVirPJh7XajXIDNxSV7mIRYiV2Fec0lEgOl6ag1obuYPjl6sSINb9wnPDnhrRe9uWDW+88/Frb8yBU4MfG+3nH/jma+Ozb2VGx3SbNXvBhu++hkDmV1/8d96CFd169P3p+29Wfv5xRvo1MLEhYXcs5bPjp+bnZc955yWSNIuPnFFTCZ2NiJXY18jzDTlpp24XLOrQE9k9E5P3vRvRdZC7H2If9jXFdop/xM/ZqdWkXsEXNlVmySfv6TT31pgarUZS5xffg1AsnjPvc2Qesm5mbFz/daOnSkoLPdy9Gz0FzYAJz7+KmmZzXgYc2alIZIdzdCae2c8niOVR/ZAd887l473dfV4KMEuv+qNjd0PXEroNLdKokB1zvLywVKdhrSKRfY48H+MTvCA1Cdkra25cmuhvxmDTo2OPonwtKKpYq16VdRXZHwvTkyMd3cb5hSEWY6ezGX+JHXa2rPDA7dYPD7GZrzIvK/WGzyP7IHZj14sRTD13MMbFe5JvKLID5qcl64zkt53iEOux92Vbxibu8ZLIFrW38cjlwtTkMp1mQzdzDYRrXfACV2jq2YOles2TPmGjvAOQzfHljYvny4uGeQW+GRKNOAIWJc2xsoI1mZfLdZr+7n4T24bLWDl2pkWkqar25GderiwV8nifRfcPljog7oBFeYcfc1KhE/JmTaWYz3cQiEMVTp5iqSNf5CGSKEl9rkrpJha3kciLtaoSrcZVKHEXSyr0uhKtykMsdRaKmbS7SOoiEpfptLd1aib/tlZbpq9Nl+s1pVqNp0jmJBKV63SlutryzLVuYgnctkKvLdGq3cRSV6G40qAr1tSWqSH1BWqli+l9laQhX13jLBB7SKQq0pCnrqky6NUUKSB4ZVp1jrrGQBmDZY5TAzt0cfJAXAOLshESctOTywuNFEFSRimf7ygQa0h9pqoa1Bkkd8xV1xTQQpF4iGVKgy5brWwrk8v5oiq9Nk+j8pJIaWEZtAVqladE6laX9pc6OAiEVQZdnlrpShGOQrFeyMtTq3wlMkgz1/pIZE5CsVKvy9Yo/aQyhUCsIvW3VDW+UqmjQKIlDfA3eEskLkKpwUhmKKu8QMQiqZGi0moqEDJSiAiQKXwl8gC542jvIMRZsCitwCeffBIdHT169GiEaQy85rkVMBgMAgF+8k2CH40VwKJsHvxorAAWZfPgR2MFQJTMxDFMo2BRWgFsKZsHPxorgEXZPPjRWAEQJZ/PR5gmwKK0Anq9HrcpmwGL0grg6rt58KOxAliUzYMfjRXAomwe/GisABZl8+BHYwWwo9M8WJRWAFvK5sGPxgpgUTYPfjRWAIuyefCjsQK4Tdk8WJRWAFvK5sGPxgqQJIlF2Qz40VgaUCQejdE8WJSWBtfdDwQ/HUsDXg4WZfPgp2NpsKV8IPjpWBqKonx9zbWTrm2ARWlpwMvJybGvdTFbChalpYG6G2pwhGkaLEpLg0X5QLAoLQ0W5QOx0zXPrQiPRz9zo5GVmxqzAyxKK4CNZfNgUVoBLMrmwW1KK4BF2TxYlFYAi7J5sCitABZl82BRWgEsyubBorQCWJTNg0VpBbAomweL0gpgUTYPFqUVwKJsHixKK4BF2TxYlFYAi7J58I5jliMmJoaogzIBmT179ly9ejXCNAD3fVuOfv368UyAKOHI5/NdXFwmT56MMHeDRWk5pk2b5ubm1jAnIiKid+/eCHM3WJSWo3PnzlCD17+UyWTjxo1DmPvAorQoU6dO9fT0ZNJBQUEDBgxAmPvAorQo7du379atGyTEYvH48eMRpjGw930v6brKnTlZVTotZTQS8HwQ/XwIAjHPCZwUoynFI+hd3+kEIoyIggLwP2Pdw6RMV5gu5FFU7cwHxulWqVQXLlwUCgWxsbFMfv2tGoMyXXfna6r/S+ogTGXuIOALXCTSVwI7ihBXwaK8i4ln9lfodRI+T2M0UkaqXgL1uqFFSpgS9Ak6RdSlQDlG5lyd/ugLKWSszaNrJUaepMEAV/H4tUHie2XV8DVF/y7qL0T3ifJ+QYv4PD4i1EZDW7njmug4xEGwKO/wTOJfHlLZ834RyCb4KuuSp0iyIrI/4hq4TVnL+LP7XGS2o0jgjcDoXLVqxqVjiGtgUdIcKSus1uun+dqOIhleDemQWVOBuAYWJc2B4iw53wYXIXegW5jE1sJMxCnwgAyaSq3OYKOrAxgoqlhVjTgFFiUNSRlIyjZFCVGq+pgAV8CixLAOLEobh+Cg34BFaeNQDQLvXAGLEsM6sCgZCI75AjYNFiWDzXa28nCbEsM2jLhNyVF4BA93bbEHLEoTFLLZsVIU10LnWJQMxmZG2XIdDrpwWJQMtux8c+73hkXJgEc6swjcvucMaxd9+NLgWGQHYEvJDQx6/ZkjB5F9gEX5L1Epa7Z9982Fk0dKCvJ9A4J7Dn58xKQXmY2b4NTaT+ZcPXPa09d/4JjnSNKwcdknsXFD3/z0KzhbU1mR8NXStItnqisqImN7jZryclC7SMjPzcx4f+ITUpnDF1v2bl278uyxQzK5w7BnJw99ZvLZo4eWz/4/5n0n9Wo3fPwLE994H9kuuPr+l2xctuCvX36UyGQjJ71YnJ+7efWy/Vt/Yk59v/TjM0cOUBQZGhm955fv923eAJl8AR+ZNvv+5NWJx/Zs8/YP6DX08ctJJ/77ysTMa1fglEAkhqNWo1rx/uukXu/h7VuUm73xy0U5N9LbtA3q9/hYOCsUisZMi4+M7fPwfyfBQScOi5KG38LguU6jLisuatc5dvqchU+/9Mbw8VMg89yxw3CsKi9LOvQXJKZ/sHjqu/MX/vAbGM76C68kncjLuqFwcZ312app7/03fv7nBp12x8ZvEb09Hi0eo9HYpd+gV+YtmfttgnsbX9MlJ30Cgwc88SQyCRferlOvfg/9l9IeHPa+OQlJtWwyhEgi/eCbDfUvXdzplViqym7DMfdGOgiLIIgufePgpVSu6D1k5L4tG5mSUGvDMTCsPdg8SIRG0ksLpZxPanjz3sNGIJNRbBsaUVqQV1VRhuwMLEoaXgtDzBRF/fbtin1bf9KoVPecqqooRyaTJhJLmByJVHrnbDl9FmptaBrWZyqrKtXKO9NoxFI5kxCJ6Qr9EbcWJSjuxc+xKGmMLRwllPzP/u0bvhWKJZNmzmkbEnHuxN97f601nFK5Axz1Wo1WrRJLZZBW1lTVXyhXOMIxLDLmmVdmNrwh06A0B3QnI9eGQOE25b+hIDsLjv7BYY89N6VDt54VpcWozqQFtevIlDl/8igcwQReOnW8/sKQjtFwLC0sCGofBRd6BwRm30gjjSRTmzcD7NHr8AAAEABJREFUYbJ2er2upSuacNHRwZaShkC8Fn1zvoHBcLyZeiXhqyV6nVatrAHRFOVl/7buK3BEuvYffPbowfWLP7x29tTV5FPqBjNcu/Yb5OXXFtzqudOeio0bcv7Y37k3rw979vmo7g9wqF08vOAIXtG6xR+1i+naf8ST6OHgoqODLSUN1cIBGZ37Duw5ZISbt0/SoX18gXDGwpVPTp8hEkmO7fkTzr44Z0F4p64alfKf7VvDomL6Dh+DaMeFrqAFQuG7y9Z2GzAEPJidG9ep1apxr7877vV3HviOnj7+TFTo6K7fr1+9iGwavMAVTfyFw/ka9fvhXVFrkHI+mSJJv9BwR2dXeLl05ouXE088/crMMS+8iizO/NSkkV6Br4dEI+6Aq+/WZ9emdRdPHvXw8es97ImS/FxQJPgx3foPQtaAi8OfcPVNw2/VkecQEu8x6DFIbP9hdeKhv2L6xEEI3S84DFkDHDznKi0NnjePXOE0Y+EKxBqw981JTN63zY7zxZaSk5i8bxueEMGxj4ZFScNrYZySW+BV1ziJDU8cAz3yEcfAorRx8AJXGEwrgEVJw7dd7xsPyOAqpO163zh4jsG0AliUGNaB+75pZHyRWGCbj0LME0hFHNsiCIuSxs9BobfRSCVJkR0dXRGnwKKkmRnUSUuStw06ZFscLssX8vi9nLwQp8CirOUZv7BVNy7Z2JDnYyV5H7fribgGHnl+h7mHd58TkwGOzpEOrjwecc9wtvoN6e/Ko7mTyWxI3/B0wwsabtXd8NRddzZt8H3nGsp0+k5hoi7IQzS4Se1/mf/wCV41ZUipKMvX1HwZ3b+d3AlxDex913L69GnekaQ3Z0z/KTttb9EtLWm4T4DEI8Yy75FsK965IXyCEPL5Up1xrmd4iEiGOAi2lGjXrl0jR44sKiry8rJQ22vRokUdOnQYO3YsMg8vvvji+fPnXVxcpFKpm5tbeHh4ly5dgoODIyK4sXe0vVvKFStWMPO1LaZIwMkEMhuvvfbahx9+ePv27crKyvz8/CtXrmzbts3R0VEkEu3duxexHvu1lMnJybGxsenp6WBIkM0BukxMTOQ1mHoEX/TZs2cRF7BH7xtM4wsvvFBVRa+mYhVFgg1T3bcIUesyYcIEZ2fnhjk+Pj6II9idKAsLC2tqat55551Bg6wz5xX49NNPk5KSkDnp169fQEBA/eJYBEGsX78ecQQ7EiU0sMaNG0eSJLSuIiMjkfUAG6ZQKJCZgQ8rl9MLuAkEgt27d4P3c+DAAcQF7KhN+ddff0FlHRISguyGiRMnXr169cKFC8zLOXPmeHh4vPXWW4jd2L6lzMzMBCMBieHDh7NEkSUlJVqtFpmfhIQET0/P+pfQbPD29ob2tNHI6ikSti9K+GIWL16M2ARYrJSUFGQR9u/f3/AlOEDQnu7Zs+fFi+xdJctmRQnhj3Xr1kFi7ty5loxBPgwQ0GZae1YB2tPgZn399dc//fQTYiW22aYEn+a9995bvny5TMbJfjbLsHLlyry8vM8++wyxDFsT5YkTJ9zd3f39/dksRwhLwR8JTjGyNocPHwZR/vDDD23atEGswaaq7+PHj2/ZsiUsLIzlBjI+Ph56/xALGDhw4KZNm15++eV7mp7WxUZEefLkSTjCzx2qJB7rN5QHj1gsNtfK+y0FbPbOnTuPHDnyxRdfIHZgC9X3smXLQIizZs1CmEfg119/hVDu999/z+dbeaEXbouSGU4BvmT37t0Rd4C6G+KFLLToEGmfNm3amjVrOnfujKwHV6tvCP/OmDGDaZlxS5HApEmToP8dsY+OHTsmJiauWrVq48aNyHpwUpTV1dXZ2dkQB46Li0McBNq+QiF7p71CfBdiahBjR1aCY9W3UqmcOXPmkiVLIP6MMObkn3/+gW5JaGL6+voiy8IxUUK1EhUVZd0Wz6OTm5vr5+eHWM/t27ehifnaa6899thjyIJwQ5TQ8bB69eqFCxci7kOSZO/evaHphjjCRx995OTk9O677yJLwY02JciRGeljA4Aog4KCEHeAhx8QEDB58mSDwYAsAqstZZqJUaNGIYy1SUlJmTp1KjjmXbp0QWaGvZayqKhowYIFAwYMQLYFBLOysrIQ12jfvv3p06chhLl582ZkZtgrSogtJyQkmHUqqlXQ6/XQFwpRLcRB1q5dm5ycbO5ZkSwV5bZt26C3Btki0Ou9fPnyXbt2cbQvrbS01Nwd9ywV5c2bN2/duoVsl/Hjx4PfABUi4hrXr18PDQ1F5oSlK2SMHj1aJBIhmwY6dTZt2gS9O+DbIo4AEVZ3d3eJRILMCUstZUhIiL+/P7J1vvnmGwjBajQaxBEsYCYRa0W5Z88eVg07NR8QSIfIJTgQiAtkZGSEhZl9j2iWijInJ8e225QNYSaRXb16FbEesJT2K8rhw4cPHToU2Q0vv/wySLO4uBixGxClBebOs1SUbdu25VDzv1UIDAyUSqUzZ85EbEWn0xUUFFjge2GpKA8fPgyRPGRnKBSKp59+mrXLBFimQYlYK8r8/HyoKZD90bdv3+DgYIutn9Eibty4YQHXG7E2Tjlw4EAOBUpaF7CXoMtBgwYdOnQIsQmwlJYRJUstpY+PD3wxyF6Bfrw//vgD6nGIFiHWYO/V98mTJ7du3YrsGCcnp06dOiUnJ0N0HbEDy0TOEWtFWVJSkpqaiuyenj17xsfHg9uLrE1paalAILhnyWozwdI2JXwZ7du3RxiEtm/fDm6fSCSCTmdkPSxWdyPWWkovLy+b3LTh3wEtbPB8rdvvarG6G7FWlOfPn7fufHi20aNHjyNHjlgxImGZDkYGloqyvLycE33BlmTRokXgjF+5cqU+p2vXritWrEAWwWLxIMRaUYLjOWXKFIS5G+gfpyiKWR6tV69ecATziSwCrr7pBZg7dOiAMPcRFRXl5+fXp08fvV5PEERFRcWxY8eQmYEWbVBQkMVW5GKpKNPS0r799luEaYzVq1fXby5RWVm5c+dOZGYs1sHIwFJRwrNm8/YFViQuLk6pVNa/BOsFP2Bzj3mzZIMSsVaUERERr776KsLczezZsz09PR0cHBrOhCwqKjp69CgyJ5YMUiLWBs+hky06Ohph7mbp0qVw3LFjR1JSUmZmZpm3c4VSaTRSm69dcCrpQRAItMpDlBERiN7bnt7ZnqAQQVBGguBRiNnRicmnt743Ioq4k2O6muARyFgneLgWCsAxVWQs9HI6WJJT/1PgUYSRoGpvxRQ2XY/q7tPo3y8Tivo4P3j7GHYt2zJ9+vSamhqj0ahSqaqrq11cXCAIolarDx48iDANeP7cwVKNCuRjIGplQTRdGL5icInuzbzvElpKBGqos2YKN5ZVf4Yimjgn4vHhOn+5w5roONQ07LKU0LWYkJBQ7+UxjSfrdq+xkNGJu9pIFW+16+6AuEeeTvVb/o0Xzh36sUuTuwizq005adKke5boBKvJBOQwDGMT98S6+bzgF8FFRQK+ItmbgVECHn/C2Sa31GWXKKHLe8iQIQ1zPDw8xo8fjzAmFqef5RPEYFfObCffFNPbtqvRa3eVZDV6lnXe9/PPP99wGQLo2sEjM+q5Wn3by1Y29lMIxIeLGh8qyjpROjs7P/7448xWLtCvM3nyZISpQ20wSCgrb3LTWsDHKNM1Pr6EjXHKCRMmMMYSehqhVw1h6tAaST27t+p+eOCDaMjGlwZ+JO9baSQPFuccK80r1CjhDeieL9L4XkQXGV8wPyUZogKzI7pC1GxpxjkIac0Ki4HYxJfXL0Kr6IPwLrf12tWZl3kE7+3QGB5BLM04CyGJGcFRzkIxtJx0b05y/GVX+LgxT5zexUd0GY3R8M2NSxBQ+L/gaG+JbH5KosFIvRUWI+ULFqedgfjEm6GdRARvWcZ5KDM9qIOzULIs4xxJUTNDOxVq1X/k34C/KtrRrbOz5yAPPwJh2Mu/jFP+7+blA0XZ8MMV8vmauqWwIejKoyjSFIClY2MQsgK5QYKio1qQhrguBHYpunOMYArT0a4GaQiUEXTwlr6kNgBsyofy9H9MNzUVgTJ07Ja+I2KivXALU5SNNiQEvBd9FZMm7gTpCDpUxtOSpINQ9GJAhxHegYhTjDi1M0TmNMHfFhrZK69fhG/z19hh959qsaX8NP1sYlmBGvRhqkfIBouzgxZBMkyThxGC6f+0VIjaUCstI6JB4fvTppcNgq91+cS9ZWpvRNRdX3eCx2QSDdL1UHQNCP0dRI1BD3Z3/a1rY31CnvePQBxBYPrB2wZi+HaIxj9My0Q55vRuNUlSyBa2CCcRpTToE7JTT5UVrO4Uh7iAARpDtrI9O1gHqokf2MM6Ouk1FaMSd6tIg20osh74MNmq6qcT/0IY1vBQoizUqWdcPKKx1DYqFgbcwCqDbvjJHQjDDh4syjNVpVOTD9iUeWwM8NNHnDT7aNlHhE+7fMg2EPEICdF4zPXBolxw7TRpW1V2U+gp47jkfYjF1AYybAKdkdJQjS9K8wBRPpP0l85WorUPQ6Ve+8qFvxFbMdIhNdunOVGuzLxYpQdB24WZZIBK/JayOkVVhVgJ0ey4SZuhOVEeLMq2Iz3WAdH9FWnm3VHrX0Mh22lIQeO4KfE1Kco/C28aWB8T+2fk8zd/+g21NjeVlSV6Vq6OSRCElUxlSX7upF7t4J+yuhK1BlTTzeMmRbnhVgrL/Rt1YbG+osohJBC1OgRvYUoSYiFUi3uFj+75E5R0M411y400Y/Wb7NHRsGm5zkapTsuEoyI0ELU+VI66BrEPouVNyqRD3OsXaFyUieXF5m69FOw/UrDvSOWVVFlbX9+Rg/1G13bMX/xgCV8uc+8ek7piHUUaXbpEtnvzJYkXPU2nKu3G9bUJldfSHQL9/Z8crsotELu5SDzNMoOnqVFV1qVFASGj0fh8n9pVRua+8FRQu46f/PA7pNMvnft11bLCW5kajdqzjV+vYSNHT7kzm/nw9s2Hfv+1KO8WXyD08m375PTXY3oPuP/mep1239ZNiQf35GXdcPHwiurep89jo8IiY1Br0Hj1famqlB7xYDaufvpN2sr13kP69936rdd/eqd8vrr0VK1vUZOVU3k1TZVf1HvTN7GrFldcSs35k/6tQ2V97u3/8kTCHms/a//ea1m//Fl46Jgi3FxLUENz+kRZIeIy0PwcMy2eSQ8c/WzcqGeRSZELX5uUfvGsX2i7XkNGFuXnbl2zYvPqZUyxXQnffb/k45wbad0GDAmP7pKZcvmLt1+5cPLI/TfftPLTX7/5XKtWDxo7Liyy08Hff172zqsalQo9NAIeT9zEOjCNW0oQpYEyV/VdfDQxf/fBzss+du/RGV4GThhbeSUtf89h915dSa1WnVcIhjN4yjNwSuTiJA/w1ZaWQzrn9z1wjJw7UyCTQiJi5vQz8R949OmOzMbFytI+rt6ITRDMRO6HLEwQT7/0xo4f14DJ/M/Y54IiOkLmb2tXwu1oW/wAAAgdSURBVMvew56In/85vIzu2ffrD2f+9cuPIydN5/OFf37/P8icNvu/caPo57/hi08O/J7w+/qv7zeW6ZfOw3H6h4vCo+gvMbpHP5KEG7egejE0PSCjcVGSJAURO555gmKFB4+ChWMUySByc665cQsScKRI0nfUnb3GNEWlztH0kr5gSkGCjCIBsasLHB3M0qCkMfXmsS5QTdDjU//9lwKWLOUc7cD1HDScyenafzCPzzfo9SnnEgVCMWPqeg4ewZyN/c9QEOXNlCs1Vfd63N5+ATnX09YunNO13yB3b98+w0fJ5K02v7JxUbqLpek1Zcg84Yeq9Ex1bsGBvmMaZnr0iUUmURJ8vkNwWyZTX1WjKSyWB/jpq6qVWTkBz42qL6/Ko+tWRWgQMg/w7SsE5t1A2PIoqysY793JzYPJEQgEcken6vKy6ooKvpDey1oslUrq5qY5uroxCRAl/+6qdvyMd2uqKkDiuxO+g5dbVn8Z1aPP6wuXt8rKbI2LUsrnC3gCo3n6tEilyv+pxz3jejfMhJoajtU3shRhQTyhkMmszmD86yBNcSkkxB5u9eXLz13miUTytuaabGqkR7mzzlIaH209EwdHZ4KeCkCpamq7rAwGg6aGjjM4ubrDdw4JnUaj02pEYvoHWVNZzhRzdHFVVlY0vJWnj/+H/9t4M/VK9vW0c8f+Pnv0YNLf+1LPJXXo1hM9Mo3rmv7kZutdFLm5CuRy186RzD9QobytLzjUyGQpG8YdazJvgfJow2nyuow6PZNParRFR04pIoIJvrmm9oEeA6ROyAYwVXd609KBYqmsfVdaNImH9jInz/yzX6/XgWls3yU2IqabVK4AySYdrh2VcurAbji279L9nqpZp1Hv3bzh+yXzgtpFDhj51Kyl3wx6chzk3y4uQg+NkCBEqCWOTh+3NkdLcpF58B0xKOuXbeDNiNxcys9fufXLnw5hQRGvT4VT1emZHv161JesycwGyRI8HtTgEPrJ23VAoJBDozNv5wFSpVZ064TMBrQpB7i1QSzjX0yHgHjN7cL839Z93S6m65Mvvv7sqzM/eTX5nx1by0uLHJ1cTx2kZffU9DdAjpB4cvr/Jaxcsv7Tj66dS6ooLbp0+jhUx8/Fv33PPYViydHd27IzUstvlwSEtweDemo/7YYGd4hED42eYqZiNfoxG2OAm8/nBE9nnurb/+kR+uqaxJffhf4YeaC/a9eo8PgpkA9xR4NSpQgJqC9Ji9LkyvAE/MiP30r98tuzMz4SKByi5r9ddvaSzN+MC0W4CNnYoPwX0yHGTotfv/ija2dOVZQUgShDO3aauyZhwxcLLp6kVw9082zz5Euvg7VjCg8f94KDo9PWNSuP7qIjmhDanPLOPLjknntCG2Dmkq9/+frzM0cOnD/+t0Ao7NJ34LDnJvsGts6uy03OZnzlwt83lSwdLGNu+ATRy63NvIhYxDKeOL0rWOo43m5nMw708v8h82ozplKVk5/18zbUEhyCA9o+MxK1HteW/q+pUwaVuj5+dA8h0ydAVxBqGgiHzWWfItEjOzpcoUlRPtcm9OesVLWxyRA61J4dZv8fsirm+AMgGNRe4cLOYYs8qw0San1Ed82kvovmokrxodEiS20IwB7gWS2O7IvYCUXZzDBf03SIxmvi5jQ3zKOtMwRUbefH+WBAkVEKNylbPzLPdtYiaI4HGMJN3YaxdFV0MwDhDyehaGlkb8RWDPSyJLbfqnxw7fxH71ECu/h9IgVfsDn2McRibKlN2QwPFiXE63b1GmXzz0LKF2ztPhyxGzvxvh/Kj4FCu/uMtlWXh4cIOV+4vecIxHp4hO3MZoTqV0S03Pu+6xYI7e0zOtTByZb8cYJeBA5BnHxbz8cRFzBS7Npi5lEwGCkd1ZJuxqZY1Slua8H1jVmpFKK4vkgBtM+kfOH8jt07ObghzkDZQzCkxb71M21C4d+f+ZkHS3KuKyvhh8sneAZTwMk0aZLi0QnTCClUu68QvQcWweOZVnfgmQbg8Oo2B4KfPo+gmGNtDjKtrVq7DRa9SCo0pEBAzFUkIvimKX3wBky67iw9JLk2TSB6d62G+cyal/BpCcJgWs01xtlznF94ZycOyZGGRxC22ohqyL8M+IzxCYZ/kNhXmpNVU1WkUaVXl/fx8IlycNucl64xGrq7eMc4u/+UnaYhDV2dPWNdvX7IugbGtZuLp79UsbvwJjzcSCd3H4l8b9EtEhk7O3lGObklZKdpKTLW2audwmVLbgZpNHaG8jKH3QVZIMROzh7Bckf4PRiNVIyLR4jMcVtBJigsqu4+IONoJ48gB8fteZkkZezs7Bnh4Lw173qVQQ8tmCCpU4BcEa5w6eHsibiJnSzbYjttFHsALy+NYR0SHl/EFyKbQCQQ8B/R+8awAZlAWEPqkE2gJ0kXgbjRU1iUXKKri1exVolsgipSN9q/8Y3tsSi5xMzgaAhH/Jx/A3Gcr7Kuuotl/ZvY+xs7Otxj3Jm9fEI42Mu3vcwZcY3EiuJjpXmBcqcvOjY58AWLkpPEXz6Sraw2Go337IpHoPsWoCZM0d76V8Td01SJxteMuiu77poGmbXJO29Xf455u/q3uXv8p5DHF/J4HZ3dF7frgZoGi5LDlCGkvmd1ONPebA2/U7rXwmhkpnubXhN1fRtM59AdDfN4PKNp56s7BeryKeae9eo0naVHLEGC+VXUiZI+xRxN+fUlmbt5Sx0eZk40FiWGdeA4JYZ1YFFiWAcWJYZ1YFFiWAcWJYZ1YFFiWMf/AwAA//96o3jvAAAABklEQVQDACUOqoSVLQoyAAAAAElFTkSuQmCC", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from langchain_teddynote.graphs import visualize_graph\n", + "\n", + "# ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", + "visualize_graph(app)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-9", + "metadata": {}, + "source": [ + "### ์—์ด์ „ํŠธ ํ…Œ์ŠคํŠธ\n", + "\n", + "๊ธฐ๋ณธ ์—์ด์ „ํŠธ๊ฐ€ ๋Œ€ํ™”๋ฅผ ๊ธฐ์–ตํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€์—์„œ ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ๊ณ , ๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€์—์„œ ์ด๋ฆ„์„ ๋ฌผ์–ด๋ณด๋ฉด ์ฒดํฌํฌ์ธํ„ฐ์— ์ €์žฅ๋œ ๋Œ€ํ™” ๊ธฐ๋ก์„ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์—ฐ์†์œผ๋กœ ๋‘ ๋ฒˆ ๋Œ€ํ™”๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-10", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”! ์ œ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”, Teddy! ๋งŒ๋‚˜์„œ ๋ฐ˜๊ฐ€์›Œ์š”. ์–ด๋–ป๊ฒŒ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n" + ] + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "\n", + "# ์Šค๋ ˆ๋“œ ID๊ฐ€ 1์ธ ์„ค์ • ๊ฐ์ฒด ์ดˆ๊ธฐํ™”\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "# ์ฒซ ๋ฒˆ์งธ ์งˆ๋ฌธ\n", + "input_message = HumanMessage(content=\"์•ˆ๋…•ํ•˜์„ธ์š”! ์ œ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค.\")\n", + "\n", + "# ์ŠคํŠธ๋ฆผ ๋ชจ๋“œ๋กœ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ๋ฐ ์‘๋‹ต ์ถœ๋ ฅ\n", + "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-11", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "๋‹น์‹ ์˜ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค. ๋งž๋‚˜์š”?\n" + ] + } + ], + "source": [ + "# ํ›„์† ์งˆ๋ฌธ: ์ด๋ฆ„ ๊ธฐ์–ต ํ™•์ธ\n", + "input_message = HumanMessage(content=\"์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\")\n", + "\n", + "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-12", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ํ˜„์žฌ ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ ์ˆ˜: 4๊ฐœ\n", + "\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”! ์ œ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”, Teddy! ๋งŒ๋‚˜์„œ ๋ฐ˜๊ฐ€์›Œ์š”. ์–ด๋–ป๊ฒŒ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "๋‹น์‹ ์˜ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค. ๋งž๋‚˜์š”?\n" + ] + } + ], + "source": [ + "# ํ˜„์žฌ ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ ํ™•์ธ\n", + "messages = app.get_state(config).values[\"messages\"]\n", + "print(f\"ํ˜„์žฌ ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(messages)}๊ฐœ\\n\")\n", + "\n", + "for message in messages:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ(Trimming)\n", + "\n", + "๋Œ€๋ถ€๋ถ„์˜ LLM์—๋Š” ์ตœ๋Œ€ ์ง€์› ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ(ํ† ํฐ ๋‹จ์œ„)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€ํ™”๊ฐ€ ๊ธธ์–ด์ง€๋ฉด ์ด ํ•œ๊ณ„์— ๋„๋‹ฌํ•˜์—ฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ์ปจํ…์ŠคํŠธ๊ฐ€ ์†์‹ค๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `trim_messages` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก์˜ ํ† ํฐ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ , ํ•œ๊ณ„์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", + "\n", + "### trim_messages ํ•จ์ˆ˜\n", + "\n", + "`langchain_core.messages`์˜ `trim_messages` ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ต์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:\n", + "\n", + "- `strategy`: \"last\"(์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์œ ์ง€) ๋˜๋Š” \"first\"(์ฒซ ๋ฉ”์‹œ์ง€ ์œ ์ง€)\n", + "- `max_tokens`: ์œ ์ง€ํ•  ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜\n", + "- `token_counter`: ํ† ํฐ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ํ•จ์ˆ˜ ๋˜๋Š” LLM ๋ชจ๋ธ\n", + "- `start_on`: ์‹œ์ž‘ํ•  ๋ฉ”์‹œ์ง€ ํƒ€์ž… (\"human\", \"ai\" ๋“ฑ)\n", + "- `include_system`: ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€ ํฌํ•จ ์—ฌ๋ถ€ (strategy๊ฐ€ \"last\"์ผ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ)\n", + "\n", + "### token_counter ์ฃผ์˜์‚ฌํ•ญ\n", + "\n", + "`token_counter` ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” **LLM ๋ชจ๋ธ ๊ฐ์ฒด**๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. `token_counter=len`์ฒ˜๋Ÿผ `len` ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋ฉ”์‹œ์ง€ **๊ฐœ์ˆ˜**๋ฅผ ํ† ํฐ ์ˆ˜๋กœ ์ž˜๋ชป ๊ณ„์‚ฐํ•˜์—ฌ ํŠธ๋ฆฌ๋ฐ์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n", + "\n", + "```python\n", + "# ์ž˜๋ชป๋œ ์‚ฌ์šฉ (๋ฉ”์‹œ์ง€ ๊ฐœ์ˆ˜๋ฅผ ํ† ํฐ์œผ๋กœ ๊ณ„์‚ฐ)\n", + "trim_messages(messages, max_tokens=100, token_counter=len)\n", + "\n", + "# ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ (LLM ๋ชจ๋ธ๋กœ ์ •ํ™•ํ•œ ํ† ํฐ ๊ณ„์‚ฐ)\n", + "trim_messages(messages, max_tokens=100, token_counter=model)\n", + "```\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” LLM ๋ชจ๋ธ์„ token_counter๋กœ ์‚ฌ์šฉํ•˜์—ฌ ํŠธ๋ฆฌ๋ฐ ์ „๋žต์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-14", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์›๋ณธ ๋ฉ”์‹œ์ง€ ์ˆ˜: 11๊ฐœ\n" + ] + } + ], + "source": [ + "from langchain_core.messages import trim_messages, HumanMessage, AIMessage, SystemMessage\n", + "\n", + "# ๊ธด ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜\n", + "sample_messages = [\n", + " SystemMessage(content=\"๋‹น์‹ ์€ ์นœ์ ˆํ•œ ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค.\"),\n", + " HumanMessage(content=\"์•ˆ๋…•ํ•˜์„ธ์š”\"),\n", + " AIMessage(content=\"์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\"),\n", + " HumanMessage(content=\"๋‚ ์”จ๊ฐ€ ์–ด๋•Œ์š”?\"),\n", + " AIMessage(content=\"์˜ค๋Š˜์€ ๋ง‘์€ ๋‚ ์”จ์ž…๋‹ˆ๋‹ค.\"),\n", + " HumanMessage(content=\"์ถ”์ฒœ ์Œ์‹์ด ์žˆ๋‚˜์š”?\"),\n", + " AIMessage(content=\"ํŒŒ์Šคํƒ€๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.\"),\n", + " HumanMessage(content=\"๋ ˆ์‹œํ”ผ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”\"),\n", + " AIMessage(content=\"ํ† ๋งˆํ†  ํŒŒ์Šคํƒ€ ๋ ˆ์‹œํ”ผ์ž…๋‹ˆ๋‹ค. ๋จผ์ € ํ† ๋งˆํ† ๋ฅผ ์ค€๋น„ํ•˜๊ณ ...\"),\n", + " HumanMessage(content=\"๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค\"),\n", + " AIMessage(content=\"์ฒœ๋งŒ์—์š”! ๋‹ค๋ฅธ ์งˆ๋ฌธ์ด ์žˆ์œผ์‹œ๋ฉด ๋ง์”€ํ•ด์ฃผ์„ธ์š”.\"),\n", + "]\n", + "\n", + "print(f\"์›๋ณธ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(sample_messages)}๊ฐœ\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell-15", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์ „๋žต 1 - ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์œ ์ง€ (strategy='last'):\n", + "ํŠธ๋ฆฌ๋ฐ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜: 7๊ฐœ\n", + "\n", + " [system] ๋‹น์‹ ์€ ์นœ์ ˆํ•œ ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค.\n", + " [human] ์ถ”์ฒœ ์Œ์‹์ด ์žˆ๋‚˜์š”?\n", + " [ai] ํŒŒ์Šคํƒ€๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.\n", + " [human] ๋ ˆ์‹œํ”ผ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”\n", + " [ai] ํ† ๋งˆํ†  ํŒŒ์Šคํƒ€ ๋ ˆ์‹œํ”ผ์ž…๋‹ˆ๋‹ค. ๋จผ์ € ํ† ๋งˆํ† ๋ฅผ ์ค€๋น„ํ•˜๊ณ ...\n", + " [human] ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค\n", + " [ai] ์ฒœ๋งŒ์—์š”! ๋‹ค๋ฅธ ์งˆ๋ฌธ์ด ์žˆ์œผ์‹œ๋ฉด ๋ง์”€ํ•ด์ฃผ์„ธ์š”.\n" + ] + } + ], + "source": [ + "# ์ „๋žต 1: ์ตœ๊ทผ ๋ฉ”์‹œ์ง€๋งŒ ์œ ์ง€ (strategy=\"last\")\n", + "# token_counter์— LLM ๋ชจ๋ธ์„ ์ „๋‹ฌํ•˜๋ฉด ์ •ํ™•ํ•œ ํ† ํฐ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.\n", + "trimmed_last = trim_messages(\n", + " sample_messages,\n", + " strategy=\"last\",\n", + " max_tokens=100, # ์ตœ๋Œ€ 100 ํ† ํฐ๋งŒ ์œ ์ง€\n", + " token_counter=model, # LLM ๋ชจ๋ธ์„ ์ „๋‹ฌํ•˜์—ฌ ์ •ํ™•ํ•œ ํ† ํฐ ๊ณ„์‚ฐ\n", + " start_on=\"human\", # ์‚ฌ๋žŒ ๋ฉ”์‹œ์ง€๋กœ ์‹œ์ž‘\n", + " include_system=True, # ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€ ํฌํ•จ\n", + ")\n", + "\n", + "print(\"์ „๋žต 1 - ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์œ ์ง€ (strategy='last'):\")\n", + "print(f\"ํŠธ๋ฆฌ๋ฐ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(trimmed_last)}๊ฐœ\\n\")\n", + "for msg in trimmed_last:\n", + " print(f\" [{msg.type}] {msg.content[:50]}...\" if len(msg.content) > 50 else f\" [{msg.type}] {msg.content}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cell-16", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์ „๋žต 2 - ์ฒซ ๋ฉ”์‹œ์ง€ ์œ ์ง€ (strategy='first'):\n", + "ํŠธ๋ฆฌ๋ฐ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜: 8๊ฐœ\n", + "\n", + " [system] ๋‹น์‹ ์€ ์นœ์ ˆํ•œ ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค.\n", + " [human] ์•ˆ๋…•ํ•˜์„ธ์š”\n", + " [ai] ์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n", + " [human] ๋‚ ์”จ๊ฐ€ ์–ด๋•Œ์š”?\n", + " [ai] ์˜ค๋Š˜์€ ๋ง‘์€ ๋‚ ์”จ์ž…๋‹ˆ๋‹ค.\n", + " [human] ์ถ”์ฒœ ์Œ์‹์ด ์žˆ๋‚˜์š”?\n", + " [ai] ํŒŒ์Šคํƒ€๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.\n", + " [human] ๋ ˆ์‹œํ”ผ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”\n" + ] + } + ], + "source": [ + "# ์ „๋žต 2: ์ฒซ ๋ฉ”์‹œ์ง€ ์œ ์ง€ (strategy=\"first\")\n", + "trimmed_first = trim_messages(\n", + " sample_messages,\n", + " strategy=\"first\",\n", + " max_tokens=100,\n", + " token_counter=model, # LLM ๋ชจ๋ธ์„ ์ „๋‹ฌํ•˜์—ฌ ์ •ํ™•ํ•œ ํ† ํฐ ๊ณ„์‚ฐ\n", + ")\n", + "\n", + "print(\"์ „๋žต 2 - ์ฒซ ๋ฉ”์‹œ์ง€ ์œ ์ง€ (strategy='first'):\")\n", + "print(f\"ํŠธ๋ฆฌ๋ฐ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(trimmed_first)}๊ฐœ\\n\")\n", + "for msg in trimmed_first:\n", + " print(f\" [{msg.type}] {msg.content[:50]}...\" if len(msg.content) > 50 else f\" [{msg.type}] {msg.content}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "### ๊ทธ๋ž˜ํ”„์—์„œ ํŠธ๋ฆฌ๋ฐ ์ ์šฉ\n", + "\n", + "LLM์„ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•˜์—ฌ ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ ์ œํ•œ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ์—์„œ๋Š” ์—์ด์ „ํŠธ ๋…ธ๋“œ์—์„œ LLM์„ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ์ž๋™์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ํŠธ๋ฆฌ๋ฐ์ด ์ ์šฉ๋œ ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cell-18", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ํŠธ๋ฆฌ๋ฐ์ด ์ ์šฉ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\n" + ] + } + ], + "source": [ + "# ์ƒˆ๋กœ์šด ์ฒดํฌํฌ์ธํ„ฐ ์ƒ์„ฑ\n", + "memory_trimmed = MemorySaver()\n", + "\n", + "\n", + "def call_model_with_trimming(state: MessagesState):\n", + " \"\"\"ํŠธ๋ฆฌ๋ฐ์ด ์ ์šฉ๋œ ์—์ด์ „ํŠธ ๋…ธ๋“œ\n", + " \n", + " LLM ํ˜ธ์ถœ ์ „์— ๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•˜์—ฌ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " # ๋ฉ”์‹œ์ง€ ํŠธ๋ฆฌ๋ฐ - ์ตœ๋Œ€ 500 ํ† ํฐ๋งŒ ์œ ์ง€\n", + " # token_counter์— LLM ๋ชจ๋ธ์„ ์ „๋‹ฌํ•˜์—ฌ ์ •ํ™•ํ•œ ํ† ํฐ ๊ณ„์‚ฐ\n", + " trimmed_messages = trim_messages(\n", + " state[\"messages\"],\n", + " strategy=\"last\",\n", + " max_tokens=500,\n", + " token_counter=model, # LLM ๋ชจ๋ธ ์ „๋‹ฌ\n", + " start_on=\"human\",\n", + " include_system=True,\n", + " )\n", + " \n", + " # ํŠธ๋ฆฌ๋ฐ๋œ ๋ฉ”์‹œ์ง€๋กœ LLM ํ˜ธ์ถœ\n", + " response = model_with_tools.invoke(trimmed_messages)\n", + " return {\"messages\": [response]}\n", + "\n", + "\n", + "# ํŠธ๋ฆฌ๋ฐ์ด ์ ์šฉ๋œ ์›Œํฌํ”Œ๋กœ์šฐ ์ƒ์„ฑ\n", + "workflow_trimmed = StateGraph(MessagesState)\n", + "workflow_trimmed.add_node(\"agent\", call_model_with_trimming)\n", + "workflow_trimmed.add_node(\"tools\", tool_node)\n", + "workflow_trimmed.add_edge(START, \"agent\")\n", + "workflow_trimmed.add_conditional_edges(\"agent\", tools_condition)\n", + "workflow_trimmed.add_edge(\"tools\", \"agent\")\n", + "\n", + "app_trimmed = workflow_trimmed.compile(checkpointer=memory_trimmed)\n", + "\n", + "print(\"ํŠธ๋ฆฌ๋ฐ์ด ์ ์šฉ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## RemoveMessage๋ฅผ ์‚ฌ์šฉํ•œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", + "\n", + "`RemoveMessage`๋Š” LangGraph์—์„œ ์ œ๊ณตํ•˜๋Š” ํŠน์ˆ˜ ์ˆ˜์ •์ž๋กœ, ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก์—์„œ ํŠน์ • ๋ฉ”์‹œ์ง€๋ฅผ ์˜๊ตฌ์ ์œผ๋กœ ์‚ญ์ œํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํŠธ๋ฆฌ๋ฐ๊ณผ ๋‹ฌ๋ฆฌ ๋ฉ”์‹œ์ง€๊ฐ€ ์‹ค์ œ๋กœ ์ƒํƒœ์—์„œ ์ œ๊ฑฐ๋˜๋ฏ€๋กœ, ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ ์–‘์„ ์ค„์ด๊ฑฐ๋‚˜ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "### ์ˆ˜๋™์œผ๋กœ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", + "\n", + "`update_state()` ๋ฉ”์„œ๋“œ์™€ `RemoveMessage`๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ๋ž˜ํ”„ ์ƒํƒœ์—์„œ ์›ํ•˜๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์„ ํƒ์ ์œผ๋กœ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”์‹œ์ง€๋Š” ๊ณ ์œ ํ•œ `id` ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ์ •ํ™•ํ•˜๊ฒŒ ์‚ญ์ œ ๋Œ€์ƒ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ ์ค‘ ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cell-20", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์‚ญ์ œ ์ „ ๋ฉ”์‹œ์ง€ ์ˆ˜: 4๊ฐœ\n", + "\n", + " [0] human: ์•ˆ๋…•ํ•˜์„ธ์š”! ์ œ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค.\n", + " [1] ai: ์•ˆ๋…•ํ•˜์„ธ์š”, Teddy! ๋งŒ๋‚˜์„œ ๋ฐ˜๊ฐ€์›Œ์š”. ์–ด๋–ป๊ฒŒ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n", + " [2] human: ์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\n", + " [3] ai: ๋‹น์‹ ์˜ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค. ๋งž๋‚˜์š”?\n" + ] + } + ], + "source": [ + "from langchain_core.messages import RemoveMessage\n", + "\n", + "# ํ˜„์žฌ ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ ํ™•์ธ\n", + "messages = app.get_state(config).values[\"messages\"]\n", + "print(f\"์‚ญ์ œ ์ „ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(messages)}๊ฐœ\\n\")\n", + "\n", + "for i, msg in enumerate(messages):\n", + " print(f\" [{i}] {msg.type}: {msg.content[:40]}...\" if len(msg.content) > 40 else f\" [{i}] {msg.type}: {msg.content}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cell-21", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์‚ญ์ œ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜: 3๊ฐœ\n", + "\n", + " [0] ai: ์•ˆ๋…•ํ•˜์„ธ์š”, Teddy! ๋งŒ๋‚˜์„œ ๋ฐ˜๊ฐ€์›Œ์š”. ์–ด๋–ป๊ฒŒ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n", + " [1] human: ์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\n", + " [2] ai: ๋‹น์‹ ์˜ ์ด๋ฆ„์€ Teddy์ž…๋‹ˆ๋‹ค. ๋งž๋‚˜์š”?\n" + ] + } + ], + "source": [ + "# ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", + "app.update_state(config, {\"messages\": RemoveMessage(id=messages[0].id)})\n", + "\n", + "# ์‚ญ์ œ ๊ฒฐ๊ณผ ํ™•์ธ\n", + "messages_after = app.get_state(config).values[\"messages\"]\n", + "print(f\"์‚ญ์ œ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(messages_after)}๊ฐœ\\n\")\n", + "\n", + "for i, msg in enumerate(messages_after):\n", + " print(f\" [{i}] {msg.type}: {msg.content[:40]}...\" if len(msg.content) > 40 else f\" [{i}] {msg.type}: {msg.content}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-22", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## ๊ทธ๋ž˜ํ”„ ๋‚ด๋ถ€์—์„œ ๋™์ ์œผ๋กœ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", + "\n", + "์ˆ˜๋™ ์‚ญ์ œ ์™ธ์—๋„ ๊ทธ๋ž˜ํ”„ ์‹คํ–‰ ์ค‘์— ์ž๋™์œผ๋กœ ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋Œ€ํ™”๊ฐ€ ๊ธธ์–ด์งˆ ๋•Œ ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ๋ฅผ ๊ด€๋ฆฌํ•˜๊ฑฐ๋‚˜ ํ† ํฐ ๋น„์šฉ์„ ์ ˆ์•ฝํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์˜ˆ์‹œ์—์„œ๋Š” ๊ทธ๋ž˜ํ”„ ์‹คํ–‰์ด ์ข…๋ฃŒ๋  ๋•Œ ์ตœ๊ทผ 3๊ฐœ ๋ฉ”์‹œ์ง€๋งŒ ์œ ์ง€ํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ž๋™์œผ๋กœ ์‚ญ์ œํ•˜๋Š” `delete_messages` ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cell-23", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๋™์  ์‚ญ์ œ๊ฐ€ ์ ์šฉ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\n" + ] + } + ], + "source": [ + "from typing import Literal\n", + "from langchain_core.messages import RemoveMessage\n", + "\n", + "# ์ƒˆ๋กœ์šด ์ฒดํฌํฌ์ธํ„ฐ ์ƒ์„ฑ\n", + "memory_auto_delete = MemorySaver()\n", + "\n", + "\n", + "def delete_messages(state: MessagesState):\n", + " \"\"\"์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ ๋…ธ๋“œ\n", + " \n", + " ๋ฉ”์‹œ์ง€๊ฐ€ 3๊ฐœ๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์ตœ์‹  3๊ฐœ๋งŒ ์œ ์ง€ํ•˜๊ณ  ๋‚˜๋จธ์ง€๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " messages = state[\"messages\"]\n", + " if len(messages) > 3:\n", + " # ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ (์ตœ์‹  3๊ฐœ๋งŒ ์œ ์ง€)\n", + " return {\"messages\": [RemoveMessage(id=m.id) for m in messages[:-3]]}\n", + " return {}\n", + "\n", + "\n", + "def should_continue(state: MessagesState) -> Literal[\"tools\", \"delete_messages\"]:\n", + " \"\"\"์กฐ๊ฑด๋ถ€ ๋ผ์šฐํŒ… ํ•จ์ˆ˜\n", + " \n", + " ๋„๊ตฌ ํ˜ธ์ถœ์ด ์žˆ์œผ๋ฉด tools ๋…ธ๋“œ๋กœ, ์—†์œผ๋ฉด delete_messages ๋…ธ๋“œ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " last_message = state[\"messages\"][-1]\n", + " # ๋„๊ตฌ ํ˜ธ์ถœ์ด ์—†์œผ๋ฉด ๋ฉ”์‹œ์ง€ ์‚ญ์ œ ๋…ธ๋“œ๋กœ ์ด๋™\n", + " if not last_message.tool_calls:\n", + " return \"delete_messages\"\n", + " return \"tools\"\n", + "\n", + "\n", + "# ๋™์  ์‚ญ์ œ๊ฐ€ ํฌํ•จ๋œ ์›Œํฌํ”Œ๋กœ์šฐ ์ƒ์„ฑ\n", + "workflow_auto = StateGraph(MessagesState)\n", + "\n", + "# ๋…ธ๋“œ ์ถ”๊ฐ€\n", + "workflow_auto.add_node(\"agent\", call_model)\n", + "workflow_auto.add_node(\"tools\", tool_node)\n", + "workflow_auto.add_node(\"delete_messages\", delete_messages)\n", + "\n", + "# ์—ฃ์ง€ ์ถ”๊ฐ€\n", + "workflow_auto.add_edge(START, \"agent\")\n", + "workflow_auto.add_conditional_edges(\"agent\", should_continue)\n", + "workflow_auto.add_edge(\"tools\", \"agent\")\n", + "workflow_auto.add_edge(\"delete_messages\", END)\n", + "\n", + "# ์ปดํŒŒ์ผ\n", + "app_auto = workflow_auto.compile(checkpointer=memory_auto_delete)\n", + "\n", + "print(\"๋™์  ์‚ญ์ œ๊ฐ€ ์ ์šฉ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-24", + "metadata": {}, + "source": [ + "### ์ˆ˜์ •๋œ ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", + "\n", + "์ˆ˜์ •๋œ ๊ทธ๋ž˜ํ”„์—๋Š” `delete_messages` ๋…ธ๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ๊ฐ€ ๋„๊ตฌ ํ˜ธ์ถœ ์—†์ด ์‘๋‹ต์„ ์™„๋ฃŒํ•˜๋ฉด `delete_messages` ๋…ธ๋“œ๋ฅผ ๊ฑฐ์ณ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ๋Š” ์ˆ˜์ •๋œ ๊ทธ๋ž˜ํ”„๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "cell-25", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", + "visualize_graph(app_auto)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-26", + "metadata": {}, + "source": [ + "### ๋™์  ์‚ญ์ œ ํ…Œ์ŠคํŠธ\n", + "\n", + "์ด์ œ ๊ทธ๋ž˜ํ”„๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜์—ฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ˆ„์ ๋˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๊ฐ€ ์ž๋™์œผ๋กœ ์‚ญ์ œ๋˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ ํ˜ธ์ถœ ํ›„ ์ƒํƒœ์—๋Š” ์ตœ์‹  3๊ฐœ์˜ ๋ฉ”์‹œ์ง€๋งŒ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์—ฐ์†์œผ๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ๋Œ€ํ™”๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "cell-27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”! ์ €๋Š” ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "์•ˆ๋…•ํ•˜์„ธ์š”, ์ฒ ์ˆ˜๋‹˜! ์–ด๋–ป๊ฒŒ ๋„์™€๋“œ๋ฆด๊นŒ์š”?\n", + "\n", + "ํ˜„์žฌ ๋ฉ”์‹œ์ง€ ์ˆ˜: 2๊ฐœ\n" + ] + } + ], + "source": [ + "# ์ƒˆ๋กœ์šด ์Šค๋ ˆ๋“œ ID๋กœ ์„ค์ •\n", + "config_auto = {\"configurable\": {\"thread_id\": \"auto_delete_test\"}}\n", + "\n", + "# ์ฒซ ๋ฒˆ์งธ ๋Œ€ํ™”\n", + "input1 = HumanMessage(content=\"์•ˆ๋…•ํ•˜์„ธ์š”! ์ €๋Š” ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\")\n", + "for event in app_auto.stream({\"messages\": [input1]}, config_auto, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()\n", + "\n", + "print(f\"\\nํ˜„์žฌ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(app_auto.get_state(config_auto).values['messages'])}๊ฐœ\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "cell-28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "๋‹น์‹ ์˜ ์ด๋ฆ„์€ ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "๋‹น์‹ ์˜ ์ด๋ฆ„์€ ์ฒ ์ˆ˜์ž…๋‹ˆ๋‹ค.\n", + "\n", + "ํ˜„์žฌ ๋ฉ”์‹œ์ง€ ์ˆ˜: 3๊ฐœ\n" + ] + } + ], + "source": [ + "# ๋‘ ๋ฒˆ์งธ ๋Œ€ํ™”\n", + "input2 = HumanMessage(content=\"์ œ ์ด๋ฆ„์ด ๋ญ๋ผ๊ณ  ํ–ˆ์ฃ ?\")\n", + "for event in app_auto.stream({\"messages\": [input2]}, config_auto, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()\n", + "\n", + "print(f\"\\nํ˜„์žฌ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(app_auto.get_state(config_auto).values['messages'])}๊ฐœ\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "cell-29", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "์˜ค๋Š˜ ๋‚ ์”จ๋Š” ์–ด๋•Œ์š”?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " search (call_vdi67oi2oSSLOoQUQ946BHPW)\n", + " Call ID: call_vdi67oi2oSSLOoQUQ946BHPW\n", + " Args:\n", + " query: ์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: search\n", + "\n", + "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ: '์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ'์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. LangGraph๋Š” ์ƒํƒœ ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "์˜ค๋Š˜ ์„œ์šธ์˜ ๋‚ ์”จ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ํ™•์ธํ•ด ๋ณด์‹œ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์งˆ๋ฌธ์ด ์žˆ์œผ์‹œ๋ฉด ๋ง์”€ํ•ด ์ฃผ์„ธ์š”!\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "์˜ค๋Š˜ ์„œ์šธ์˜ ๋‚ ์”จ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ํ™•์ธํ•ด ๋ณด์‹œ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์งˆ๋ฌธ์ด ์žˆ์œผ์‹œ๋ฉด ๋ง์”€ํ•ด ์ฃผ์„ธ์š”!\n", + "\n", + "ํ˜„์žฌ ๋ฉ”์‹œ์ง€ ์ˆ˜: 3๊ฐœ\n" + ] + } + ], + "source": [ + "# ์„ธ ๋ฒˆ์งธ ๋Œ€ํ™” (์ด ์‹œ์ ์—์„œ ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์˜ˆ์ƒ)\n", + "input3 = HumanMessage(content=\"์˜ค๋Š˜ ๋‚ ์”จ๋Š” ์–ด๋•Œ์š”?\")\n", + "for event in app_auto.stream({\"messages\": [input3]}, config_auto, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()\n", + "\n", + "print(f\"\\nํ˜„์žฌ ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(app_auto.get_state(config_auto).values['messages'])}๊ฐœ\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "cell-30", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์ตœ์ข… ๋ฉ”์‹œ์ง€ ์ˆ˜: 3๊ฐœ\n", + "\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " search (call_vdi67oi2oSSLOoQUQ946BHPW)\n", + " Call ID: call_vdi67oi2oSSLOoQUQ946BHPW\n", + " Args:\n", + " query: ์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: search\n", + "\n", + "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ: '์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ'์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. LangGraph๋Š” ์ƒํƒœ ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "์˜ค๋Š˜ ์„œ์šธ์˜ ๋‚ ์”จ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ํ™•์ธํ•ด ๋ณด์‹œ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์งˆ๋ฌธ์ด ์žˆ์œผ์‹œ๋ฉด ๋ง์”€ํ•ด ์ฃผ์„ธ์š”!\n" + ] + } + ], + "source": [ + "# ์ตœ์ข… ์ƒํƒœ ํ™•์ธ\n", + "final_messages = app_auto.get_state(config_auto).values[\"messages\"]\n", + "print(f\"์ตœ์ข… ๋ฉ”์‹œ์ง€ ์ˆ˜: {len(final_messages)}๊ฐœ\\n\")\n", + "\n", + "for msg in final_messages:\n", + " msg.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-31", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## ๋Œ€ํ™” ์š”์•ฝ์„ ํ†ตํ•œ ์ปจํ…์ŠคํŠธ ์••์ถ•\n", + "\n", + "๋ฉ”์‹œ์ง€๋ฅผ ํŠธ๋ฆฌ๋ฐํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋ฉด ์ •๋ณด๊ฐ€ ์†์‹ค๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€ํ™” ์š”์•ฝ(summarization)์€ ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋“ค์˜ ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์••์ถ•ํ•˜์—ฌ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ด๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.\n", + "\n", + "### ์š”์•ฝ ์ „๋žต\n", + "\n", + "1. ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋“ค์„ LLM์„ ์‚ฌ์šฉํ•˜์—ฌ ์š”์•ฝ\n", + "2. ์š”์•ฝ ๋‚ด์šฉ์„ ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๋กœ ์ถ”๊ฐ€\n", + "3. ์›๋ณธ ๋ฉ”์‹œ์ง€๋Š” ์‚ญ์ œ\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ๋Œ€ํ™” ์š”์•ฝ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "cell-32", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๋Œ€ํ™” ์š”์•ฝ ํ•จ์ˆ˜ ์ •์˜ ์™„๋ฃŒ!\n" + ] + } + ], + "source": [ + "from typing import Annotated\n", + "from typing_extensions import TypedDict\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "\n", + "# ์š”์•ฝ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ํ™•์žฅ๋œ State\n", + "class SummaryState(TypedDict):\n", + " \"\"\"์š”์•ฝ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ State\n", + " \n", + " messages: ๋Œ€ํ™” ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ\n", + " summary: ์ด์ „ ๋Œ€ํ™”์˜ ์š”์•ฝ (์„ ํƒ์ )\n", + " \"\"\"\n", + " messages: Annotated[list, add_messages]\n", + " summary: str\n", + "\n", + "\n", + "def summarize_conversation(messages: list) -> str:\n", + " \"\"\"๋Œ€ํ™” ๋‚ด์šฉ์„ ์š”์•ฝํ•˜๋Š” ํ•จ์ˆ˜\n", + " \n", + " ์ฃผ์–ด์ง„ ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ๋ฅผ LLM์„ ์‚ฌ์šฉํ•˜์—ฌ 2-3๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " # ์š”์•ฝ์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ\n", + " summary_prompt = f\"\"\"\n", + " ๋‹ค์Œ ๋Œ€ํ™” ๋‚ด์šฉ์„ 2-3๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•ด์ฃผ์„ธ์š”.\n", + " ํ•ต์‹ฌ ์ •๋ณด์™€ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.\n", + " \n", + " ๋Œ€ํ™” ๋‚ด์šฉ:\n", + " {chr(10).join([f\"{msg.type}: {msg.content}\" for msg in messages])}\n", + " \"\"\"\n", + " \n", + " # LLM์œผ๋กœ ์š”์•ฝ ์ƒ์„ฑ\n", + " summary_response = model.invoke([HumanMessage(content=summary_prompt)])\n", + " return summary_response.content\n", + "\n", + "\n", + "print(\"๋Œ€ํ™” ์š”์•ฝ ํ•จ์ˆ˜ ์ •์˜ ์™„๋ฃŒ!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "cell-33", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๋Œ€ํ™” ์š”์•ฝ ๊ฒฐ๊ณผ:\n", + "์‚ฌ๋žŒ์ด AI์™€์˜ ๋Œ€ํ™”์—์„œ ๋‚ ์”จ์™€ ์Œ์‹ ์ถ”์ฒœ์„ ๋ฌผ์–ด๋ณด์•˜๊ณ , AI๋Š” ์˜ค๋Š˜์˜ ๋‚ ์”จ๊ฐ€ ๋ง‘์Œ์„ ์•Œ๋ฆฌ๊ณ  ํŒŒ์Šคํƒ€๋ฅผ ์ถ”์ฒœํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ํ›„, AI๋Š” ํ† ๋งˆํ†  ํŒŒ์Šคํƒ€ ๋ ˆ์‹œํ”ผ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ ๋Œ€ํ™”๊ฐ€ ๋๋‚ฌ์Šต๋‹ˆ๋‹ค.\n" + ] + } + ], + "source": [ + "# ์š”์•ฝ ํ…Œ์ŠคํŠธ\n", + "test_summary = summarize_conversation(sample_messages[1:]) # ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€ ์ œ์™ธ\n", + "print(\"๋Œ€ํ™” ์š”์•ฝ ๊ฒฐ๊ณผ:\")\n", + "print(test_summary)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-34", + "metadata": {}, + "source": [ + "### ์š”์•ฝ์ด ํฌํ•จ๋œ ์—์ด์ „ํŠธ ๊ตฌ์ถ•\n", + "\n", + "์ด์ œ ๋Œ€ํ™”๊ฐ€ ๊ธธ์–ด์ง€๋ฉด ์ž๋™์œผ๋กœ ์š”์•ฝ์„ ์ƒ์„ฑํ•˜๊ณ , ์š”์•ฝ๋œ ๋‚ด์šฉ์„ ์ปจํ…์ŠคํŠธ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ์ •๋ณด ์†์‹ค์„ ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ ํ† ํฐ ํšจ์œจ์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.\n", + "\n", + "์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ์š”์•ฝ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "cell-35", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "์š”์•ฝ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\n" + ] + } + ], + "source": [ + "# ์ƒˆ๋กœ์šด ์ฒดํฌํฌ์ธํ„ฐ ์ƒ์„ฑ\n", + "memory_summary = MemorySaver()\n", + "\n", + "\n", + "def call_model_with_summary(state: SummaryState):\n", + " \"\"\"์š”์•ฝ์„ ํ™œ์šฉํ•˜๋Š” ์—์ด์ „ํŠธ ๋…ธ๋“œ\n", + " \n", + " ์ด์ „ ๋Œ€ํ™” ์š”์•ฝ์ด ์žˆ์œผ๋ฉด ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๋กœ ์ถ”๊ฐ€ํ•˜์—ฌ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " # ์š”์•ฝ์ด ์žˆ์œผ๋ฉด ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๋กœ ์ถ”๊ฐ€\n", + " messages = state[\"messages\"]\n", + " if state.get(\"summary\"):\n", + " system_msg = SystemMessage(content=f\"์ด์ „ ๋Œ€ํ™” ์š”์•ฝ: {state['summary']}\")\n", + " messages = [system_msg] + list(messages)\n", + " \n", + " response = model_with_tools.invoke(messages)\n", + " return {\"messages\": [response]}\n", + "\n", + "\n", + "def maybe_summarize(state: SummaryState):\n", + " \"\"\"์กฐ๊ฑด๋ถ€ ์š”์•ฝ ๋…ธ๋“œ\n", + " \n", + " ๋ฉ”์‹œ์ง€๊ฐ€ 6๊ฐœ๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์ฒ˜์Œ 4๊ฐœ๋ฅผ ์š”์•ฝํ•˜๊ณ  ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.\n", + " \"\"\"\n", + " messages = state[\"messages\"]\n", + " \n", + " if len(messages) > 6:\n", + " # ์ฒ˜์Œ 4๊ฐœ ๋ฉ”์‹œ์ง€ ์š”์•ฝ\n", + " messages_to_summarize = messages[:4]\n", + " summary = summarize_conversation(messages_to_summarize)\n", + " \n", + " # ์š”์•ฝ๋œ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ\n", + " delete_messages = [RemoveMessage(id=m.id) for m in messages_to_summarize]\n", + " \n", + " return {\n", + " \"messages\": delete_messages,\n", + " \"summary\": summary\n", + " }\n", + " \n", + " return {}\n", + "\n", + "\n", + "def should_continue_summary(state: SummaryState) -> Literal[\"tools\", \"summarize\"]:\n", + " \"\"\"์กฐ๊ฑด๋ถ€ ๋ผ์šฐํŒ… ํ•จ์ˆ˜\"\"\"\n", + " last_message = state[\"messages\"][-1]\n", + " if not last_message.tool_calls:\n", + " return \"summarize\"\n", + " return \"tools\"\n", + "\n", + "\n", + "# ์š”์•ฝ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ ์›Œํฌํ”Œ๋กœ์šฐ ์ƒ์„ฑ\n", + "workflow_summary = StateGraph(SummaryState)\n", + "\n", + "workflow_summary.add_node(\"agent\", call_model_with_summary)\n", + "workflow_summary.add_node(\"tools\", tool_node)\n", + "workflow_summary.add_node(\"summarize\", maybe_summarize)\n", + "\n", + "workflow_summary.add_edge(START, \"agent\")\n", + "workflow_summary.add_conditional_edges(\"agent\", should_continue_summary)\n", + "workflow_summary.add_edge(\"tools\", \"agent\")\n", + "workflow_summary.add_edge(\"summarize\", END)\n", + "\n", + "app_summary = workflow_summary.compile(checkpointer=memory_summary)\n", + "\n", + "print(\"์š”์•ฝ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ ์—์ด์ „ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "cell-36", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”\n", + "visualize_graph(app_summary)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-37", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## ์ •๋ฆฌ\n", + "\n", + "์ด๋ฒˆ ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” LangGraph์˜ ๋‹จ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ–ˆ์Šต๋‹ˆ๋‹ค.\n", + "\n", + "### ํ•ต์‹ฌ ๋‚ด์šฉ\n", + "\n", + "| ๊ธฐ๋ฒ• | ํŠน์ง• | ์‚ฌ์šฉ ์‚ฌ๋ก€ |\n", + "|------|------|----------|\n", + "| ํŠธ๋ฆฌ๋ฐ | LLM ํ˜ธ์ถœ ์ „ ๋ฉ”์‹œ์ง€ ํ•„ํ„ฐ๋ง | ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ ์ œํ•œ ๊ด€๋ฆฌ |\n", + "| ์‚ญ์ œ | ์ƒํƒœ์—์„œ ๋ฉ”์‹œ์ง€ ์˜๊ตฌ ์ œ๊ฑฐ | ๋ฏผ๊ฐ ์ •๋ณด ์‚ญ์ œ, ์ €์žฅ ๊ณต๊ฐ„ ๊ด€๋ฆฌ |\n", + "| ์š”์•ฝ | ์˜ค๋ž˜๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์••์ถ• | ์ •๋ณด ์†์‹ค ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ ํ† ํฐ ์ ˆ์•ฝ |\n", + "\n", + "### ์ „๋žต ์„ ํƒ ๊ฐ€์ด๋“œ\n", + "\n", + "- **ํŠธ๋ฆฌ๋ฐ**: ๊ฐ„๋‹จํ•˜๊ณ  ๋น ๋ฆ„, ์ •๋ณด ์†์‹ค ๊ฐ€๋Šฅ\n", + "- **์‚ญ์ œ**: ์ €์žฅ ๊ณต๊ฐ„ ์ ˆ์•ฝ, ์ •๋ณด ์†์‹ค ๊ฐ€๋Šฅ\n", + "- **์š”์•ฝ**: ์ •๋ณด ๋ณด์กด, ์ถ”๊ฐ€ LLM ํ˜ธ์ถœ ๋น„์šฉ ๋ฐœ์ƒ" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 }