diff --git a/03-Agent/01-LangGraph-Agents.ipynb b/03-Agent/01-LangGraph-Agents.ipynb index d1ca9cc..a69433c 100644 --- a/03-Agent/01-LangGraph-Agents.ipynb +++ b/03-Agent/01-LangGraph-Agents.ipynb @@ -3,18 +3,46 @@ { "cell_type": "markdown", "metadata": {}, - "source": "# 에이전트 (Agent)\n\n에이전트는 언어 모델(LLM)과 도구(Tool)를 결합하여 복잡한 작업을 수행하는 시스템입니다. 에이전트는 주어진 작업에 대해 추론하고, 필요한 도구를 선택하며, 목표를 향해 반복적으로 작업을 수행합니다.\n\nLangChain의 `create_agent` 함수는 프로덕션 수준의 에이전트 구현을 제공합니다. 이 함수를 사용하면 모델 선택, 도구 연동, 미들웨어 설정 등을 손쉽게 구성할 수 있습니다.\n\n> 참고 문서: [LangGraph Agents](https://docs.langchain.com/oss/python/langgraph/agents.md)" + "source": [ + "# 에이전트 (Agent)\n", + "\n", + "에이전트는 언어 모델(LLM)과 도구(Tool)를 결합하여 복잡한 작업을 수행하는 시스템입니다. 에이전트는 주어진 작업에 대해 추론하고, 필요한 도구를 선택하며, 목표를 향해 반복적으로 작업을 수행합니다.\n", + "\n", + "![](./assets/langgraph-agent.png)\n", + "\n", + "LangChain의 `create_agent` 함수는 프로덕션 수준의 에이전트 구현을 제공합니다. 이 함수를 사용하면 모델 선택, 도구 연동, 미들웨어 설정 등을 손쉽게 구성할 수 있습니다.\n", + "\n", + "> 참고 문서: [LangChain Agents](https://docs.langchain.com/oss/python/langchain/agents)" + ] }, { "cell_type": "markdown", "metadata": {}, - "source": "## 환경 설정\n\n에이전트 튜토리얼을 시작하기 전에 필요한 환경을 설정합니다. `dotenv`를 사용하여 API 키를 로드하고, `langchain_teddynote`의 로깅 기능을 활성화하여 LangSmith에서 실행 추적을 확인할 수 있도록 합니다.\n\nLangSmith 추적을 활성화하면 에이전트의 추론 과정, 도구 호출, 응답 생성 등을 시각적으로 디버깅할 수 있어 개발에 큰 도움이 됩니다.\n\n아래 코드는 환경 변수를 로드하고 LangSmith 프로젝트를 설정합니다." + "source": [ + "## 환경 설정\n", + "\n", + "에이전트 튜토리얼을 시작하기 전에 필요한 환경을 설정합니다. `dotenv`를 사용하여 API 키를 로드하고, `langchain_teddynote`의 로깅 기능을 활성화하여 LangSmith에서 실행 추적을 확인할 수 있도록 합니다.\n", + "\n", + "LangSmith 추적을 활성화하면 에이전트의 추론 과정, 도구 호출, 응답 생성 등을 시각적으로 디버깅할 수 있어 개발에 큰 도움이 됩니다.\n", + "\n", + "아래 코드는 환경 변수를 로드하고 LangSmith 프로젝트를 설정합니다." + ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LangSmith 추적을 시작합니다.\n", + "[프로젝트명]\n", + "LangGraph-V1-Tutorial\n" + ] + } + ], "source": [ "from dotenv import load_dotenv\n", "from langchain_teddynote import logging\n", @@ -22,43 +50,83 @@ "# 환경 변수 로드\n", "load_dotenv(override=True)\n", "# 추적을 위한 프로젝트 이름 설정\n", - "logging.langsmith(\"LangChain-V1-Tutorial\")" + "logging.langsmith(\"LangGraph-V1-Tutorial\")" ] }, { "cell_type": "markdown", "metadata": {}, - "source": "## 모델 (Model)\n\n에이전트의 추론 엔진인 LLM은 `create_agent` 함수의 첫 번째 인자로 전달합니다. 가장 간단한 방법은 `provider:model` 형식의 문자열을 사용하는 것입니다. 이 방식은 빠른 프로토타이핑에 적합합니다.\n\n아래 코드는 모델 식별자 문자열을 사용하여 기본 에이전트를 생성합니다." + "source": [ + "## 모델 (Model)\n", + "\n", + "에이전트의 추론 엔진인 LLM은 `create_agent` 함수의 첫 번째 인자로 전달합니다. 모델을 지정하는 방법은 크게 두 가지가 있습니다.\n", + "\n", + "### 방법 1: 문자열 식별자 (provider:model)\n", + "\n", + "가장 간단한 방법은 `provider:model` 형식의 문자열을 직접 `create_agent`에 전달하는 것입니다. 이 방식은 빠른 프로토타이핑에 적합합니다.\n", + "\n", + "### 방법 2: init_chat_model 함수\n", + "\n", + "`init_chat_model` 함수를 사용하면 모델 인스턴스를 먼저 생성한 후 전달할 수 있습니다. 이 방식은 모델 옵션을 세밀하게 제어할 때 유용합니다.\n", + "\n", + "아래 코드는 두 가지 방법을 모두 보여주는 예시입니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from langchain.agents import create_agent\n", + "from langchain.chat_models import init_chat_model\n", + "\n", + "# 방법 1: 문자열 식별자를 직접 전달 (빠른 프로토타이핑에 적합)\n", + "# agent = create_agent(\"anthropic:claude-sonnet-4-5\", tools=[])\n", + "\n", + "# 방법 2: init_chat_model로 모델 인스턴스 생성 후 전달\n", + "# OpenAI 키를 사용하는 경우 gpt-4.1-mini, gpt-4.1 등으로 변경하세요\n", + "model = init_chat_model(\"claude-sonnet-4-5\")\n", "\n", - "# 모델 식별자 문자열을 사용한 간단한 방법\n", - "agent = create_agent(\"openai:gpt-4.1-mini\", tools=[])" + "# 모델 인스턴스를 사용하여 에이전트 생성\n", + "agent = create_agent(model, tools=[])" ] }, { "cell_type": "markdown", "metadata": {}, - "source": "### 모델 세부 설정\n\n더 세밀한 제어가 필요한 경우, 모델 클래스를 직접 인스턴스화하여 다양한 옵션을 설정할 수 있습니다. `temperature`는 응답의 무작위성을, `max_tokens`는 생성할 최대 토큰 수를, `timeout`은 요청 타임아웃을 제어합니다.\n\n아래 코드는 ChatOpenAI 클래스를 사용하여 세부 옵션이 설정된 에이전트를 생성합니다." + "source": [ + "### 모델 세부 설정\n", + "\n", + "더 세밀한 제어가 필요한 경우, 모델 생성 시 추가 옵션을 전달하여 다양한 설정을 적용할 수 있습니다.\n", + "\n", + "- `temperature`: 응답의 무작위성을 제어합니다 (0에 가까울수록 일관된 응답, 1에 가까울수록 다양한 응답)\n", + "- `max_tokens`: 생성할 최대 토큰 수를 제한합니다\n", + "- `timeout`: 요청 타임아웃(초)을 설정합니다\n", + "\n", + "모델 세부 설정을 적용하는 방법은 두 가지가 있습니다.\n", + "\n", + "**방법 1: init_chat_model 사용**\n", + "\n", + "`init_chat_model` 함수에 추가 옵션을 전달하는 방식입니다. Provider에 관계없이 동일한 인터페이스로 사용할 수 있어 편리합니다.\n", + "\n", + "**방법 2: Provider별 클래스 직접 사용**\n", + "\n", + "`ChatAnthropic`, `ChatOpenAI` 등 Provider별 클래스를 직접 인스턴스화하는 방식입니다. Provider 고유의 옵션을 세밀하게 제어할 때 유용합니다.\n", + "\n", + "아래 코드는 두 가지 방법을 모두 보여주는 예시입니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ - "from langchain.agents import create_agent\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "# 모델 인스턴스를 직접 초기화하여 더 세밀한 제어\n", - "model = ChatOpenAI(\n", - " model=\"gpt-4.1-mini\",\n", + "# 방법 1: init_chat_model에 추가 옵션 전달\n", + "# Provider에 관계없이 동일한 인터페이스로 사용 가능\n", + "model = init_chat_model(\n", + " \"claude-sonnet-4-5\", # OpenAI 키를 사용하는 경우 gpt-4.1-mini, gpt-4.1 등으로 변경하세요\n", " temperature=0.1, # 응답의 무작위성 제어\n", " max_tokens=1000, # 최대 생성 토큰 수\n", " timeout=30, # 요청 타임아웃(초)\n", @@ -67,29 +135,64 @@ "agent = create_agent(model, tools=[])" ] }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# 방법 2: Provider별 클래스 직접 사용\n", + "# Provider 고유의 옵션을 세밀하게 제어할 때 유용\n", + "from langchain_anthropic import ChatAnthropic\n", + "# from langchain_openai import ChatOpenAI # OpenAI 사용 시\n", + "\n", + "model = ChatAnthropic(\n", + " model=\"claude-sonnet-4-5\",\n", + " temperature=0.1,\n", + " max_tokens=1000,\n", + " timeout=30,\n", + ")\n", + "\n", + "agent = create_agent(model, tools=[])" + ] + }, { "cell_type": "markdown", "metadata": {}, - "source": "### 동적 모델 선택\n\n동적 모델 선택은 런타임에 현재 상태와 컨텍스트를 기반으로 사용할 모델을 결정하는 패턴입니다. 이를 통해 정교한 라우팅 로직과 비용 최적화가 가능합니다. 예를 들어, 간단한 질문에는 경량 모델을, 복잡한 대화에는 고급 모델을 사용할 수 있습니다.\n\n`wrap_model_call` 데코레이터를 사용하면 모델 호출 전에 요청을 검사하고 수정할 수 있는 미들웨어를 생성할 수 있습니다.\n\n![](assets/wrap_model_call.png)\n\n아래 코드는 대화 길이에 따라 모델을 동적으로 선택하는 예시입니다." + "source": [ + "### 동적 모델 선택\n", + "\n", + "동적 모델 선택은 런타임에 현재 상태와 컨텍스트를 기반으로 사용할 모델을 결정하는 패턴입니다. 이를 통해 정교한 라우팅 로직과 비용 최적화가 가능합니다. 예를 들어, 간단한 질문에는 경량 모델을, 복잡한 대화에는 고급 모델을 사용할 수 있습니다.\n", + "\n", + "`wrap_model_call` 데코레이터를 사용하면 모델 호출 전에 요청을 검사하고 수정할 수 있는 미들웨어를 생성할 수 있습니다.\n", + "\n", + "![](../assets/wrap_model_call.png)\n", + "\n", + "아래 코드는 대화 길이에 따라 모델을 동적으로 선택하는 예시입니다." + ] }, { "cell_type": "markdown", "metadata": {}, - "source": "### ModelRequest 속성\n\n`ModelRequest`는 에이전트의 모델 호출 정보를 담는 데이터 클래스로, 미들웨어에서 요청을 검사하고 수정할 때 사용됩니다. `override()` 메서드를 통해 여러 속성을 동시에 변경할 수 있습니다.\n\n아래 코드는 ModelRequest를 사용하여 동적으로 모델과 시스템 프롬프트를 변경하는 예시입니다." + "source": [ + "### ModelRequest 속성\n", + "\n", + "`ModelRequest`는 에이전트의 모델 호출 정보를 담는 데이터 클래스로, 미들웨어에서 요청을 검사하고 수정할 때 사용됩니다. `override()` 메서드를 통해 여러 속성을 동시에 변경할 수 있습니다.\n", + "\n", + "아래 코드는 ModelRequest를 사용하여 동적으로 모델과 시스템 프롬프트를 변경하는 예시입니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "from langchain_openai import ChatOpenAI\n", - "from langchain.agents import create_agent\n", "from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse\n", "\n", "# 기본 모델과 고급 모델 정의\n", - "basic_model = ChatOpenAI(model=\"gpt-4.1-mini\")\n", - "advanced_model = ChatOpenAI(model=\"gpt-4.1\")\n", + "basic_model = init_chat_model(\"claude-haiku-4-5\")\n", + "advanced_model = init_chat_model(\"claude-sonnet-4-5\")\n", "\n", "\n", "@wrap_model_call\n", @@ -104,6 +207,7 @@ " model = basic_model\n", "\n", " request.model = model\n", + " print(f\"모델 선택: {request.model.model}\")\n", " return handler(request)\n", "\n", "\n", @@ -114,9 +218,86 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/v7/jvdq74bd213fzs8k9cmpl_7w0000gn/T/ipykernel_63447/2629723978.py:19: DeprecationWarning: Direct attribute assignment to ModelRequest.model is deprecated. Use request.override(model=...) instead to create a new request with the modified attribute.\n", + " request.model = model\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "모델 선택: claude-haiku-4-5\n", + "\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mmodel\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "# 머신러닝의 동작 원리\n", + "\n", + "머신러닝은 **데이터로부터 패턴을 학습하여 예측이나 판단을 하는 기술**입니다. 기본 동작 과정을 설명하겠습니다.\n", + "\n", + "## 🎯 기본 개념\n", + "\n", + "```\n", + "데이터 입력 → 패턴 학습 → 모델 생성 → 예측/판단\n", + "```\n", + "\n", + "## 1️⃣ **학습 데이터 수집**\n", + "- 충분한 양의 데이터가 필요합니다\n", + "- 예: 고양이 사진 10,000장\n", + "\n", + "## 2️⃣ **특징(Feature) 추출**\n", + "- 데이터에서 중요한 정보를 뽑아냅니다\n", + "- 예: 사진의 색상, 모양, 질감 등\n", + "\n", + "## 3️⃣ **모델 학습 (가중치 조정)**\n", + "\n", + "```\n", + "초기값 → 계산 → 오류 측정 → 가중치 조정 \n", + " ↑\n", + " 반복\n", + "```\n", + "\n", + "- **손실함수**: 예측값과 실제값의 차이를 계산\n", + "- **경사하강법**: 오류를 줄이는 방향으로 계속 조정\n", + "\n", + "## 4️⃣ **성능 평가**\n", + "- 학습하지 않은 테스트 데이터로 검증\n", + "- 정확도, 재현율 등으로 평가\n", + "\n", + "## 5️⃣ **예측**\n", + "- 학습된 모델이 새로운 데이터를 분류/예측\n", + "\n", + "---\n", + "\n", + "## 📊 머신러닝의 주요 유형\n", + "\n", + "| 유형 | 설명 | 예시 |\n", + "|------|------|------|\n", + "| **지도학습** | 정답을 알려주며 학습 | 스팸 메일 분류 |\n", + "| **비지도학습** | 정답 없이 패턴을 찾음 | 고객 분류 |\n", + "| **강화학습** | 보상으로 학습 | 게임 AI |\n", + "\n", + "---\n", + "\n", + "## 💡 쉬운 비유\n", + "\n", + "**아이가 동물을 배우는 과정:**\n", + "1. 많은 고양이 사진을 본다 (데이터)\n", + "2. \"귀가 있고, 수염이 있고...\" 특징을 인식 (특징 추출)\n", + "3. 규칙을 만들어간다 (모델 학습)\n", + "4. 새로운 고양이 사진을 보고 \"고양이다!\" 판단 (예측)\n", + "\n", + "궁금한 부분이 있으면 더 자세히 설명해드릴 수 있습니다! 😊" + ] + } + ], "source": [ "from langchain_teddynote.messages import stream_graph\n", "from langchain_core.messages import HumanMessage\n", @@ -132,11 +313,27 @@ { "cell_type": "markdown", "metadata": {}, - "source": "**ModelRequest 주요 속성:**\n\n| 속성 | 설명 |\n|:---|:---|\n| `model` | 사용할 `BaseChatModel` 인스턴스 |\n| `system_prompt` | 시스템 프롬프트 (선택적) |\n| `messages` | 대화 메시지 리스트 (시스템 프롬프트 제외) |\n| `tool_choice` | 도구 선택 설정 |\n| `tools` | 사용 가능한 도구 리스트 |\n| `response_format` | 응답 형식 지정 |\n| `state` | 현재 에이전트 상태 (`AgentState`) |\n| `runtime` | 에이전트 런타임 정보 |\n| `model_settings` | 추가 모델 설정 (dict) |\n\n아래 코드는 `override()` 메서드를 사용하여 여러 속성을 동시에 변경하는 예시입니다." + "source": [ + "**ModelRequest 주요 속성:**\n", + "\n", + "| 속성 | 설명 |\n", + "|:---|:---|\n", + "| `model` | 사용할 `BaseChatModel` 인스턴스 |\n", + "| `system_prompt` | 시스템 프롬프트 (선택적) |\n", + "| `messages` | 대화 메시지 리스트 (시스템 프롬프트 제외) |\n", + "| `tool_choice` | 도구 선택 설정 |\n", + "| `tools` | 사용 가능한 도구 리스트 |\n", + "| `response_format` | 응답 형식 지정 |\n", + "| `state` | 현재 에이전트 상태 (`AgentState`) |\n", + "| `runtime` | 에이전트 런타임 정보 |\n", + "| `model_settings` | 추가 모델 설정 (dict) |\n", + "\n", + "아래 코드는 `override()` 메서드를 사용하여 여러 속성을 동시에 변경하는 예시입니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -154,16 +351,16 @@ " system_prompt=\"emoji 를 사용해서 답변해줘\",\n", " tool_choice=\"auto\",\n", " )\n", - " return handler(new_request)\n", " else:\n", " new_request = request.override(\n", + " model=basic_model,\n", " system_prompt=\"한 문장으로 간결하게 답변해줘. emoji 는 사용하지 말아줘.\",\n", " tool_choice=\"auto\",\n", - " model=basic_model,\n", " )\n", - " return handler(new_request)\n", - "\n", + " print(f\"모델 선택: {new_request.model.model}\")\n", + " return handler(new_request)\n", "\n", + " \n", "agent = create_agent(\n", " model=basic_model, tools=[], middleware=[dynamic_model_selection] # 기본 모델\n", ")" @@ -172,13 +369,33 @@ { "cell_type": "markdown", "metadata": {}, - "source": "### 글자수 기반 모델 선택 테스트\n\n아래는 글자수 10자 미만일 때의 응답입니다. 간결한 답변을 생성하도록 설정되어 있습니다." + "source": [ + "### 글자수 기반 모델 선택 테스트\n", + "\n", + "아래는 글자수 10자 미만일 때의 응답입니다. 간결한 답변을 생성하도록 설정되어 있습니다." + ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "글자수: 9\n", + "모델 선택: claude-haiku-4-5\n", + "\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mmodel\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "# 머신러닝 동작원리\n", + "\n", + "머신러닝은 프로그래밍된 규칙 대신 데이터로부터 패턴을 학습하여 예측이나 분류를 수행하는 기술로, 크게 데이터 수집 → 특성 추출 → 모델 선택 → 학습(가중치 조정) → 예측 단계를 거쳐 작동합니다." + ] + } + ], "source": [ "stream_graph(agent, inputs={\"messages\": [HumanMessage(content=\"머신러닝 동작원리\")]})" ] @@ -186,13 +403,66 @@ { "cell_type": "markdown", "metadata": {}, - "source": "아래는 글자수 10자 이상일 때의 응답입니다. 이모지를 사용하여 친근한 답변을 생성하도록 설정되어 있습니다." + "source": [ + "아래는 글자수 10자 이상일 때의 응답입니다. 이모지를 사용하여 친근한 답변을 생성하도록 설정되어 있습니다." + ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "글자수: 25\n", + "모델 선택: claude-sonnet-4-5\n", + "\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mmodel\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "# 머신러닝의 동작 원리 🤖\n", + "\n", + "머신러닝은 컴퓨터가 데이터로부터 스스로 학습하는 기술이에요! 🎯\n", + "\n", + "## 기본 동작 과정 🔄\n", + "\n", + "### 1️⃣ 데이터 수집 📊\n", + "- 학습에 필요한 데이터를 모아요\n", + "- 예: 고양이 사진 1000장 🐱\n", + "\n", + "### 2️⃣ 모델 선택 🏗️\n", + "- 문제에 맞는 알고리즘을 선택해요\n", + "- 신경망, 결정트리, SVM 등\n", + "\n", + "### 3️⃣ 학습 과정 📚\n", + "```\n", + "입력 데이터 → 모델 → 예측 결과\n", + " ↓\n", + " 실제 답과 비교\n", + " ↓\n", + " 오차 계산 📉\n", + " ↓\n", + " 모델 파라미터 조정 🔧\n", + "```\n", + "\n", + "### 4️⃣ 반복 학습 🔁\n", + "- 오차가 줄어들 때까지 반복해요\n", + "- 점점 정확도가 높아져요! ⬆️\n", + "\n", + "### 5️⃣ 평가 및 예측 ✅\n", + "- 새로운 데이터로 성능 테스트\n", + "- 실전에 적용! 🚀\n", + "\n", + "## 핵심 원리 💡\n", + "\n", + "**\"경험(데이터)을 통해 성능을 개선하는 것\"**\n", + "\n", + "마치 아기가 넘어지면서 걷기를 배우는 것처럼, 컴퓨터도 실수를 반복하며 배워요! 👶➡️🚶" + ] + } + ], "source": [ "stream_graph(\n", " agent,\n", @@ -207,21 +477,36 @@ { "cell_type": "markdown", "metadata": {}, - "source": "---\n\n## 프롬프트\n\n에이전트의 동작을 제어하는 핵심 요소 중 하나는 시스템 프롬프트입니다. 시스템 프롬프트를 통해 에이전트의 역할, 응답 스타일, 제약 조건 등을 정의할 수 있습니다." + "source": [ + "---\n", + "\n", + "## 프롬프트\n", + "\n", + "에이전트의 동작을 제어하는 핵심 요소 중 하나는 시스템 프롬프트입니다. 시스템 프롬프트를 통해 에이전트의 역할, 응답 스타일, 제약 조건 등을 정의할 수 있습니다." + ] }, { "cell_type": "markdown", "metadata": {}, - "source": "### 시스템 프롬프트\n\n`system_prompt` 매개변수를 사용하여 에이전트의 기본 동작을 정의할 수 있습니다. 시스템 프롬프트는 모든 대화에서 일관되게 적용되며, 에이전트의 페르소나와 응답 가이드라인을 설정하는 데 사용됩니다.\n\n아래 코드는 간결하고 정확한 응답을 생성하도록 시스템 프롬프트를 설정한 에이전트를 생성합니다." + "source": [ + "### 시스템 프롬프트\n", + "\n", + "`system_prompt` 매개변수를 사용하여 에이전트의 기본 동작을 정의할 수 있습니다. 시스템 프롬프트는 모든 대화에서 일관되게 적용되며, 에이전트의 페르소나와 응답 가이드라인을 설정하는 데 사용됩니다.\n", + "\n", + "아래 코드는 간결하고 정확한 응답을 생성하도록 시스템 프롬프트를 설정한 에이전트를 생성합니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ + "# OpenAI 키를 사용하는 경우 gpt-4.1-mini, gpt-4.1 등으로 변경하세요\n", + "model = init_chat_model(\"claude-sonnet-4-5\")\n", + "\n", "agent = create_agent(\n", - " \"openai:gpt-4.1-mini\",\n", + " model,\n", " system_prompt=\"You are a helpful assistant. Be concise and accurate.\",\n", ")" ] @@ -229,13 +514,27 @@ { "cell_type": "markdown", "metadata": {}, - "source": "아래는 설정된 시스템 프롬프트를 사용한 에이전트의 응답 예시입니다." + "source": [ + "아래는 설정된 시스템 프롬프트를 사용한 에이전트의 응답 예시입니다." + ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mmodel\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "대한민국의 수도는 서울입니다." + ] + } + ], "source": [ "stream_graph(\n", " agent,\n", @@ -246,16 +545,23 @@ { "cell_type": "markdown", "metadata": {}, - "source": "### 동적 시스템 프롬프트 (Dynamic Prompting)\n\n런타임 컨텍스트나 에이전트 상태를 기반으로 시스템 프롬프트를 동적으로 생성해야 하는 경우가 있습니다. `dynamic_prompt` 데코레이터를 사용하면 요청마다 다른 시스템 프롬프트를 적용할 수 있습니다.\n\n이 기능은 사용자 역할, 언어 설정, 응답 형식 등을 런타임에 결정해야 할 때 유용합니다. `context_schema`를 정의하여 에이전트 호출 시 필요한 컨텍스트 정보를 전달할 수 있습니다.\n\n아래 코드는 답변 형식과 길이를 동적으로 설정하는 에이전트를 생성합니다." + "source": [ + "### 동적 시스템 프롬프트 (Dynamic Prompting)\n", + "\n", + "런타임 컨텍스트나 에이전트 상태를 기반으로 시스템 프롬프트를 동적으로 생성해야 하는 경우가 있습니다. `dynamic_prompt` 데코레이터를 사용하면 요청마다 다른 시스템 프롬프트를 적용할 수 있습니다.\n", + "\n", + "이 기능은 사용자 역할, 언어 설정, 응답 형식 등을 런타임에 결정해야 할 때 유용합니다. `context_schema`를 정의하여 에이전트 호출 시 필요한 컨텍스트 정보를 전달할 수 있습니다.\n", + "\n", + "아래 코드는 답변 형식과 길이를 동적으로 설정하는 에이전트를 생성합니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "from typing import TypedDict\n", - "from langchain.agents import create_agent\n", "from langchain.agents.middleware import dynamic_prompt, ModelRequest\n", "\n", "\n", @@ -292,7 +598,7 @@ "\n", "# 컨텍스트 스키마와 user_role_prompt 미들웨어를 사용하여 에이전트 생성\n", "agent = create_agent(\n", - " model=\"openai:gpt-4.1-mini\",\n", + " model=model,\n", " middleware=[user_role_prompt],\n", " context_schema=Context,\n", ")" @@ -300,9 +606,39 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mmodel\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "# 머신러닝, 컴퓨터가 스스로 학습하는 원리 파헤치기\n", + "\n", + "**[IT/기술]** 인공지능 시대의 핵심 기술로 자리잡은 머신러닝의 작동 메커니즘이 주목받고 있다.\n", + "\n", + "머신러닝(Machine Learning)은 컴퓨터가 명시적인 프로그래밍 없이 데이터로부터 패턴을 학습하여 스스로 판단하고 예측하는 인공지능 기술이다. 전통적인 프로그래밍이 규칙을 직접 입력하는 방식이라면, 머신러닝은 데이터에서 규칙을 찾아내는 방식이다.\n", + "\n", + "## 3단계 학습 과정\n", + "\n", + "머신러닝의 동작 원리는 크게 세 단계로 나뉜다. 첫째, **데이터 수집 및 전처리** 단계에서는 학습에 필요한 대량의 데이터를 모으고 정제한다. 둘째, **모델 학습** 단계에서는 알고리즘이 데이터의 패턴을 분석하고 수학적 모델을 구축한다. 셋째, **예측 및 평가** 단계에서는 학습된 모델로 새로운 데이터를 판단하고 정확도를 측정한다.\n", + "\n", + "## 핵심은 최적화\n", + "\n", + "학습 과정의 핵심은 '오차 최소화'다. 모델은 예측값과 실제값의 차이(오차)를 계산하고, 이를 줄이는 방향으로 내부 파라미터를 반복적으로 조정한다. 이 과정을 '경사하강법'이라 부르며, 수천에서 수만 번 반복하면서 최적의 예측 모델을 만들어낸다.\n", + "\n", + "## 세 가지 학습 방식\n", + "\n", + "머신러닝은 학습 방법에 따라 구분된다. **지도학습**은 정답이 있는 데이터로 학습하며, 이메일 스팸 분류나 질병 진단에 활용된다. **비지도학습**은 정답 없이 데이터의 구조를 파악하며, 고객 세분화에 사용된다. **강화학습**은 시행착오를 통해 보상을 최대화하는 방법을 학습하며, 게임 AI나 자율주행에 적용된다.\n", + "\n", + "업계 전문가들은 \"머신러닝은 데이터가 많을수록, 학습 시간이 길수록 성능이 향상되는 특징이 있다\"며 \"양질의 데이터 확보가 성공의 열쇠\"라고 강조했다." + ] + } + ], "source": [ "# 컨텍스트에 따라 시스템 프롬프트가 동적으로 설정됩니다\n", "stream_graph(\n", @@ -316,9 +652,27 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mmodel\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "🤖 머신러닝은 데이터로 학습해요!\n", + "\n", + "📊 데이터 → 패턴 발견 → 예측 모델 생성\n", + "\n", + "반복 학습으로 정확도 향상! 🎯\n", + "\n", + "#AI #머신러닝" + ] + } + ], "source": [ "stream_graph(\n", " agent,\n", @@ -332,158 +686,24 @@ { "cell_type": "markdown", "metadata": {}, - "source": "---\n\n## 구조화된 답변 출력 (Response Format)\n\n특정 형식으로 에이전트의 출력을 반환하고 싶을 때가 있습니다. LangChain은 `response_format` 매개변수를 통해 구조화된 출력 전략을 제공합니다. 이를 통해 자연어 응답 대신 JSON 객체, Pydantic 모델 등의 형태로 구조화된 데이터를 얻을 수 있습니다.\n\n**지원 타입:**\n- **Pydantic model class**: Pydantic 모델 클래스\n- **`ToolStrategy`**: 도구 기반 구조화 전략 (대부분의 모델에서 작동)\n- **`ProviderStrategy`**: Provider 기반 구조화 전략 (OpenAI 등 지원 모델에서만 작동)\n\n구조화된 응답은 `response_format` 설정에 따라 에이전트 상태의 `structured_response` 키에 반환됩니다." - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "### Pydantic 모델 기반 처리\n\nPydantic 모델을 사용하면 스키마 검증이 자동으로 이루어지며, 필드 설명을 통해 모델이 각 필드의 의미를 이해할 수 있습니다. `Field`의 `description` 매개변수는 모델이 올바른 값을 추출하는 데 도움이 됩니다.\n\n아래 코드는 연락처 정보를 추출하는 Pydantic 모델을 정의합니다." - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pydantic import BaseModel, Field\n", - "\n", - "\n", - "class ContactInfo(BaseModel):\n", - " \"\"\"Response schema for the agent.\"\"\"\n", - "\n", - " name: str = Field(description=\"The name of the person\")\n", - " email: str = Field(description=\"The email of the person\")\n", - " phone: str = Field(description=\"The phone number of the person\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "agent = create_agent(model=\"openai:gpt-4.1-mini\", tools=[], response_format=ContactInfo)\n", - "\n", - "result = agent.invoke(\n", - " {\n", - " \"messages\": [\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": \"Extract contact info from: 테디는 AI 엔지니어 입니다. 그의 이메일은 teddy@example.com 이고, 전화번호는 010-1234-5678 입니다.\",\n", - " }\n", - " ]\n", - " }\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "아래에서 정형화된 출력 결과를 확인할 수 있습니다. `structured_response` 키에 Pydantic 모델 인스턴스가 반환됩니다." - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result[\"structured_response\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "### ToolStrategy\n\n`ToolStrategy`는 도구 호출(Tool Calling)을 사용하여 구조화된 출력을 생성합니다. 이 방식은 도구 호출을 지원하는 대부분의 모델에서 작동하므로 호환성이 높습니다.\n\n아래 코드는 ToolStrategy를 사용하여 연락처 정보를 추출하는 예시입니다." - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], "source": [ - "from pydantic import BaseModel\n", - "from langchain.agents import create_agent\n", - "from langchain.agents.structured_output import ToolStrategy\n", - "\n", - "\n", - "# 응답 스키마 정의\n", - "class ContactInfo(BaseModel):\n", - " name: str\n", - " email: str\n", - " phone: str\n", + "---\n", "\n", + "## 미들웨어\n", "\n", - "agent = create_agent(\n", - " model=\"openai:gpt-4.1-mini\", tools=[], response_format=ToolStrategy(ContactInfo)\n", - ")\n", - "\n", - "result = agent.invoke(\n", - " {\n", - " \"messages\": [\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": \"Extract contact info from: 테디는 AI 엔지니어 입니다. 그의 이메일은 teddy@example.com 이고, 전화번호는 010-1234-5678 입니다.\",\n", - " }\n", - " ]\n", - " }\n", - ")\n", + "미들웨어를 사용하면 모델 호출 전후에 커스텀 로직을 실행할 수 있습니다. `@before_model` 및 `@after_model` 데코레이터를 사용하여 모델 호출을 감싸는 훅을 정의할 수 있습니다.\n", "\n", - "result[\"structured_response\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "### ProviderStrategy\n\n`ProviderStrategy`는 모델 제공자의 네이티브 구조화된 출력 기능을 사용합니다. OpenAI와 같이 네이티브 구조화된 출력을 지원하는 제공자에서만 작동하지만, 더 안정적인 결과를 제공합니다.\n\n아래 코드는 ProviderStrategy를 사용하는 예시입니다." - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents.structured_output import ProviderStrategy\n", + "**미들웨어 활용 사례:**\n", + "- 모델 호출 전 메시지 전처리 (예: 쿼리 재작성)\n", + "- 모델 호출 후 응답 후처리 (예: 필터링, 로깅)\n", + "- 상태 기반 동적 라우팅\n", "\n", - "agent = create_agent(\n", - " model=\"openai:gpt-4.1\", response_format=ProviderStrategy(ContactInfo)\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result = agent.invoke(\n", - " {\n", - " \"messages\": [\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": \"Extract contact info from: 테디는 AI 엔지니어 입니다. 그의 이메일은 teddy@example.com 이고, 전화번호는 010-1234-5678 입니다.\",\n", - " }\n", - " ]\n", - " }\n", - ")" + "아래 코드는 모델 호출 전후에 로깅을 수행하는 미들웨어 예시입니다." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result[\"structured_response\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "---\n\n## 미들웨어\n\n미들웨어를 사용하면 모델 호출 전후에 커스텀 로직을 실행할 수 있습니다. `@before_model` 및 `@after_model` 데코레이터를 사용하여 모델 호출을 감싸는 훅을 정의할 수 있습니다.\n\n**미들웨어 활용 사례:**\n- 모델 호출 전 메시지 전처리 (예: 쿼리 재작성)\n- 모델 호출 후 응답 후처리 (예: 필터링, 로깅)\n- 상태 기반 동적 라우팅\n\n아래 코드는 모델 호출 전후에 로깅을 수행하는 미들웨어 예시입니다." - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -495,11 +715,7 @@ " AgentState,\n", " ModelRequest,\n", " ModelResponse,\n", - " dynamic_prompt,\n", ")\n", - "from langchain.chat_models import init_chat_model\n", - "from langchain.messages import AIMessage, AnyMessage\n", - "from langchain_teddynote.messages import invoke_graph\n", "from langchain_core.prompts import PromptTemplate\n", "from langgraph.runtime import Runtime\n", "from typing import Any, Callable\n", @@ -512,7 +728,8 @@ " f\"\\033[95m\\n\\n모델 호출 전 메시지 {len(state['messages'])}개가 있습니다\\033[0m\"\n", " )\n", " last_message = state[\"messages\"][-1].content\n", - " llm = init_chat_model(\"openai:gpt-4.1-mini\")\n", + " # OpenAI 키를 사용하는 경우 gpt-4.1-mini, gpt-4.1 등으로 변경하세요\n", + " llm = init_chat_model(\"claude-sonnet-4-5\")\n", "\n", " query_rewrite = (\n", " PromptTemplate.from_template(\n", @@ -538,12 +755,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "agent = create_agent(\n", - " \"openai:gpt-4.1-mini\",\n", + " model,\n", " middleware=[\n", " log_before_model,\n", " log_after_model,\n", @@ -553,9 +770,53 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[95m\n", + "\n", + "모델 호출 전 메시지 1개가 있습니다\u001b[0m\n", + "\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mlog_before_model.before_model\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "What is the capital city of South Korea?\n", + "==================================================\n", + "🔄 Node: \u001b[1;36mmodel\u001b[0m 🔄\n", + "- - - - - - - - - - - - - - - - - - - - - - - - - \n", + "# Capital of South Korea\n", + "\n", + "The capital city of South Korea is **Seoul** (서울).\n", + "\n", + "## Key Facts about Seoul:\n", + "- **Population**: Approximately 9.7 million in the city proper, with over 25 million in the greater metropolitan area\n", + "- **Location**: Northwestern South Korea, along the Han River\n", + "- **Status**: Largest city in South Korea and the political, economic, and cultural center\n", + "- **History**: Has served as Korea's capital for over 600 years, since the Joseon Dynasty (1394)\n", + "\n", + "Seoul is a major global city known for its modern technology, K-pop culture, historical palaces, and vibrant urban life.\u001b[95m\n", + "\n", + "모델 호출 후 메시지 3개가 있습니다\u001b[0m\n", + "[0] 대한민국 수도\n", + "[1] What is the capital city of South Korea?\n", + "[2] # Capital of South Korea\n", + "\n", + "The capital city of South Korea is **Seoul** (서울).\n", + "\n", + "## Key Facts about Seoul:\n", + "- **Population**: Approximately 9.7 million in the city proper, with over 25 million in the greater metropolitan area\n", + "- **Location**: Northwestern South Korea, along the Han River\n", + "- **Status**: Largest city in South Korea and the political, economic, and cultural center\n", + "- **History**: Has served as Korea's capital for over 600 years, since the Joseon Dynasty (1394)\n", + "\n", + "Seoul is a major global city known for its modern technology, K-pop culture, historical palaces, and vibrant urban life.\n" + ] + } + ], "source": [ "stream_graph(\n", " agent,\n", @@ -566,11 +827,19 @@ { "cell_type": "markdown", "metadata": {}, - "source": "### 클래스 기반 미들웨어\n\n데코레이터 대신 클래스 기반 미들웨어를 사용할 수 있습니다. `AgentMiddleware` 클래스를 상속하고 `before_model` 및 `after_model` 메서드를 오버라이드하여 커스텀 로직을 구현합니다.\n\n클래스 기반 미들웨어는 커스텀 상태 스키마를 정의하거나 복잡한 미들웨어 로직을 구조화할 때 유용합니다.\n\n아래 코드는 클래스 기반 미들웨어를 사용하여 커스텀 상태를 관리하는 예시입니다." + "source": [ + "### 클래스 기반 미들웨어\n", + "\n", + "데코레이터 대신 클래스 기반 미들웨어를 사용할 수 있습니다. `AgentMiddleware` 클래스를 상속하고 `before_model` 및 `after_model` 메서드를 오버라이드하여 커스텀 로직을 구현합니다.\n", + "\n", + "클래스 기반 미들웨어는 커스텀 상태 스키마를 정의하거나 복잡한 미들웨어 로직을 구조화할 때 유용합니다.\n", + "\n", + "아래 코드는 클래스 기반 미들웨어를 사용하여 커스텀 상태를 관리하는 예시입니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -593,7 +862,7 @@ " pass\n", "\n", "\n", - "agent = create_agent(\"openai:gpt-4.1-mini\", tools=[], middleware=[CustomMiddleware()])\n", + "agent = create_agent(model, tools=[], middleware=[CustomMiddleware()])\n", "\n", "# 에이전트는 이제 메시지 외에 추가 상태를 추적할 수 있습니다\n", "result = agent.invoke(\n", @@ -607,11 +876,17 @@ { "cell_type": "markdown", "metadata": {}, - "source": "### 모델 오류 시 재시도 로직\n\n`wrap_model_call` 데코레이터를 사용하면 모델 호출 실패 시 자동으로 재시도하는 로직을 구현할 수 있습니다. 이는 네트워크 오류나 일시적인 API 장애에 대응하는 데 유용합니다.\n\n아래 코드는 최대 3회까지 재시도하는 미들웨어 예시입니다." + "source": [ + "### 모델 오류 시 재시도 로직\n", + "\n", + "`wrap_model_call` 데코레이터를 사용하면 모델 호출 실패 시 자동으로 재시도하는 로직을 구현할 수 있습니다. 이는 네트워크 오류나 일시적인 API 장애에 대응하는 데 유용합니다.\n", + "\n", + "아래 코드는 최대 3회까지 재시도하는 미들웨어 예시입니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -631,21 +906,73 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ + "# OpenAI 키를 사용하는 경우 gpt-4.1-mini, gpt-4.1 등으로 변경하세요\n", + "# 일부러 존재하지 않는 모델명을 사용하여 재시도 로직을 테스트합니다\n", + "model = init_chat_model(\"claude-sonnet-4-5-invalid\")\n", + "\n", "agent = create_agent(\n", - " \"openai:gpt-4.1-minis\", # 일부러 모델 호출 실패하도록 설정(모델명 오류)\n", + " model,\n", " middleware=[retry_model],\n", ")" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "오류 발생으로 1/3 번째 재시도합니다: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-sonnet-4-5-invalid'}, 'request_id': 'req_011CX8LweDEK9NgLt9dhexUc'}\n", + "오류 발생으로 2/3 번째 재시도합니다: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-sonnet-4-5-invalid'}, 'request_id': 'req_011CX8LwfW7bVThHqr49DugM'}\n" + ] + }, + { + "ename": "NotFoundError", + "evalue": "Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-sonnet-4-5-invalid'}, 'request_id': 'req_011CX8LwhFYHFhGVh4wmrHXi'}", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[21]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mstream_graph\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2\u001b[39m \u001b[43m \u001b[49m\u001b[43magent\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 3\u001b[39m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m=\u001b[49m\u001b[43m{\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmessages\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43mHumanMessage\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcontent\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m대한민국의 수도는?\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 4\u001b[39m \u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_teddynote/messages.py:369\u001b[39m, in \u001b[36mstream_graph\u001b[39m\u001b[34m(graph, inputs, config, context, node_names, callback)\u001b[39m\n\u001b[32m 366\u001b[39m prev_node = \u001b[33m\"\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 368\u001b[39m \u001b[38;5;66;03m# LangGraph v1.0에서 stream_mode=\"messages\"는 2-튜플 (message, metadata)를 반환\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m369\u001b[39m \u001b[43m\u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk_msg\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmetadata\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mgraph\u001b[49m\u001b[43m.\u001b[49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 370\u001b[39m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcontext\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcontext\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmessages\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\n\u001b[32m 371\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 372\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# metadata에서 노드 정보 추출\u001b[39;49;00m\n\u001b[32m 373\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# v1.0에서는 'langgraph_node' 키를 통해 노드 이름에 접근\u001b[39;49;00m\n\u001b[32m 374\u001b[39m \u001b[43m \u001b[49m\u001b[43mcurr_node\u001b[49m\u001b[43m \u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43mmetadata\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mlanggraph_node\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 376\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# node_names가 비어있거나 현재 노드가 node_names에 있는 경우에만 처리\u001b[39;49;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langgraph/pregel/main.py:2646\u001b[39m, in \u001b[36mPregel.stream\u001b[39m\u001b[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, subgraphs, debug, **kwargs)\u001b[39m\n\u001b[32m 2644\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m task \u001b[38;5;129;01min\u001b[39;00m loop.match_cached_writes():\n\u001b[32m 2645\u001b[39m loop.output_writes(task.id, task.writes, cached=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m-> \u001b[39m\u001b[32m2646\u001b[39m \u001b[43m\u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m_\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrunner\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtick\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2647\u001b[39m \u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mloop\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtasks\u001b[49m\u001b[43m.\u001b[49m\u001b[43mvalues\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m.\u001b[49m\u001b[43mwrites\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2648\u001b[39m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mstep_timeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2649\u001b[39m \u001b[43m \u001b[49m\u001b[43mget_waiter\u001b[49m\u001b[43m=\u001b[49m\u001b[43mget_waiter\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2650\u001b[39m \u001b[43m \u001b[49m\u001b[43mschedule_task\u001b[49m\u001b[43m=\u001b[49m\u001b[43mloop\u001b[49m\u001b[43m.\u001b[49m\u001b[43maccept_push\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2651\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 2652\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# emit output\u001b[39;49;00m\n\u001b[32m 2653\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield from\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m_output\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2654\u001b[39m \u001b[43m \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprint_mode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msubgraphs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mqueue\u001b[49m\u001b[43m.\u001b[49m\u001b[43mEmpty\u001b[49m\n\u001b[32m 2655\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2656\u001b[39m loop.after_tick()\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langgraph/pregel/_runner.py:258\u001b[39m, in \u001b[36mPregelRunner.tick\u001b[39m\u001b[34m(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task)\u001b[39m\n\u001b[32m 256\u001b[39m \u001b[38;5;66;03m# panic on failure or timeout\u001b[39;00m\n\u001b[32m 257\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m258\u001b[39m \u001b[43m_panic_or_proceed\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 259\u001b[39m \u001b[43m \u001b[49m\u001b[43mfutures\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdone\u001b[49m\u001b[43m.\u001b[49m\u001b[43munion\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mfutures\u001b[49m\u001b[43m.\u001b[49m\u001b[43mitems\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 260\u001b[39m \u001b[43m \u001b[49m\u001b[43mpanic\u001b[49m\u001b[43m=\u001b[49m\u001b[43mreraise\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 261\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 262\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 263\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m tb := exc.__traceback__:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langgraph/pregel/_runner.py:520\u001b[39m, in \u001b[36m_panic_or_proceed\u001b[39m\u001b[34m(futs, timeout_exc_cls, panic)\u001b[39m\n\u001b[32m 518\u001b[39m interrupts.append(exc)\n\u001b[32m 519\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m fut \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m SKIP_RERAISE_SET:\n\u001b[32m--> \u001b[39m\u001b[32m520\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m exc\n\u001b[32m 521\u001b[39m \u001b[38;5;66;03m# raise combined interrupts\u001b[39;00m\n\u001b[32m 522\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m interrupts:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langgraph/pregel/_executor.py:80\u001b[39m, in \u001b[36mBackgroundExecutor.done\u001b[39m\u001b[34m(self, task)\u001b[39m\n\u001b[32m 78\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Remove the task from the tasks dict when it's done.\"\"\"\u001b[39;00m\n\u001b[32m 79\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m80\u001b[39m \u001b[43mtask\u001b[49m\u001b[43m.\u001b[49m\u001b[43mresult\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 81\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m GraphBubbleUp:\n\u001b[32m 82\u001b[39m \u001b[38;5;66;03m# This exception is an interruption signal, not an error\u001b[39;00m\n\u001b[32m 83\u001b[39m \u001b[38;5;66;03m# so we don't want to re-raise it on exit\u001b[39;00m\n\u001b[32m 84\u001b[39m \u001b[38;5;28mself\u001b[39m.tasks.pop(task)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.11.14/lib/python3.11/concurrent/futures/_base.py:449\u001b[39m, in \u001b[36mFuture.result\u001b[39m\u001b[34m(self, timeout)\u001b[39m\n\u001b[32m 447\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m CancelledError()\n\u001b[32m 448\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._state == FINISHED:\n\u001b[32m--> \u001b[39m\u001b[32m449\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m__get_result\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 451\u001b[39m \u001b[38;5;28mself\u001b[39m._condition.wait(timeout)\n\u001b[32m 453\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._state \u001b[38;5;129;01min\u001b[39;00m [CANCELLED, CANCELLED_AND_NOTIFIED]:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.11.14/lib/python3.11/concurrent/futures/_base.py:401\u001b[39m, in \u001b[36mFuture.__get_result\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 399\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._exception:\n\u001b[32m 400\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m401\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m._exception\n\u001b[32m 402\u001b[39m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[32m 403\u001b[39m \u001b[38;5;66;03m# Break a reference cycle with the exception in self._exception\u001b[39;00m\n\u001b[32m 404\u001b[39m \u001b[38;5;28mself\u001b[39m = \u001b[38;5;28;01mNone\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.11.14/lib/python3.11/concurrent/futures/thread.py:58\u001b[39m, in \u001b[36m_WorkItem.run\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 55\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[32m 57\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m58\u001b[39m result = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 59\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 60\u001b[39m \u001b[38;5;28mself\u001b[39m.future.set_exception(exc)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langgraph/pregel/_retry.py:42\u001b[39m, in \u001b[36mrun_with_retry\u001b[39m\u001b[34m(task, retry_policy, configurable)\u001b[39m\n\u001b[32m 40\u001b[39m task.writes.clear()\n\u001b[32m 41\u001b[39m \u001b[38;5;66;03m# run the task\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m42\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mtask\u001b[49m\u001b[43m.\u001b[49m\u001b[43mproc\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtask\u001b[49m\u001b[43m.\u001b[49m\u001b[43minput\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 43\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ParentCommand \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 44\u001b[39m ns: \u001b[38;5;28mstr\u001b[39m = config[CONF][CONFIG_KEY_CHECKPOINT_NS]\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langgraph/_internal/_runnable.py:656\u001b[39m, in \u001b[36mRunnableSeq.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 654\u001b[39m \u001b[38;5;66;03m# run in context\u001b[39;00m\n\u001b[32m 655\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m set_config_context(config, run) \u001b[38;5;28;01mas\u001b[39;00m context:\n\u001b[32m--> \u001b[39m\u001b[32m656\u001b[39m \u001b[38;5;28minput\u001b[39m = \u001b[43mcontext\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 657\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 658\u001b[39m \u001b[38;5;28minput\u001b[39m = step.invoke(\u001b[38;5;28minput\u001b[39m, config)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langgraph/_internal/_runnable.py:400\u001b[39m, in \u001b[36mRunnableCallable.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 398\u001b[39m run_manager.on_chain_end(ret)\n\u001b[32m 399\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m400\u001b[39m ret = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 401\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.recurse \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(ret, Runnable):\n\u001b[32m 402\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m ret.invoke(\u001b[38;5;28minput\u001b[39m, config)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain/agents/factory.py:1133\u001b[39m, in \u001b[36mcreate_agent..model_node\u001b[39m\u001b[34m(state, runtime)\u001b[39m\n\u001b[32m 1130\u001b[39m response = _execute_model_sync(request)\n\u001b[32m 1131\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1132\u001b[39m \u001b[38;5;66;03m# Call composed handler with base handler\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1133\u001b[39m response = \u001b[43mwrap_model_call_handler\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_execute_model_sync\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1135\u001b[39m \u001b[38;5;66;03m# Extract state updates from ModelResponse\u001b[39;00m\n\u001b[32m 1136\u001b[39m state_updates = {\u001b[33m\"\u001b[39m\u001b[33mmessages\u001b[39m\u001b[33m\"\u001b[39m: response.result}\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain/agents/factory.py:146\u001b[39m, in \u001b[36m_chain_model_call_handlers..normalized_single\u001b[39m\u001b[34m(request, handler)\u001b[39m\n\u001b[32m 142\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mnormalized_single\u001b[39m(\n\u001b[32m 143\u001b[39m request: ModelRequest,\n\u001b[32m 144\u001b[39m handler: Callable[[ModelRequest], ModelResponse],\n\u001b[32m 145\u001b[39m ) -> ModelResponse:\n\u001b[32m--> \u001b[39m\u001b[32m146\u001b[39m result = \u001b[43msingle_handler\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhandler\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 147\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m _normalize_to_model_response(result)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain/agents/middleware/types.py:1767\u001b[39m, in \u001b[36mwrap_model_call..decorator..wrapped\u001b[39m\u001b[34m(_self, request, handler)\u001b[39m\n\u001b[32m 1762\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mwrapped\u001b[39m(\n\u001b[32m 1763\u001b[39m _self: AgentMiddleware[StateT, ContextT],\n\u001b[32m 1764\u001b[39m request: ModelRequest,\n\u001b[32m 1765\u001b[39m handler: Callable[[ModelRequest], ModelResponse],\n\u001b[32m 1766\u001b[39m ) -> ModelCallResult:\n\u001b[32m-> \u001b[39m\u001b[32m1767\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhandler\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[19]\u001b[39m\u001b[32m, line 8\u001b[39m, in \u001b[36mretry_model\u001b[39m\u001b[34m(request, handler)\u001b[39m\n\u001b[32m 6\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m attempt \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[32m3\u001b[39m):\n\u001b[32m 7\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mhandler\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 9\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 10\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m attempt == \u001b[32m2\u001b[39m:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain/agents/factory.py:1101\u001b[39m, in \u001b[36mcreate_agent.._execute_model_sync\u001b[39m\u001b[34m(request)\u001b[39m\n\u001b[32m 1098\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m request.system_message:\n\u001b[32m 1099\u001b[39m messages = [request.system_message, *messages]\n\u001b[32m-> \u001b[39m\u001b[32m1101\u001b[39m output = \u001b[43mmodel_\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmessages\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1102\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m name:\n\u001b[32m 1103\u001b[39m output.name = name\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py:5557\u001b[39m, in \u001b[36mRunnableBindingBase.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 5550\u001b[39m \u001b[38;5;129m@override\u001b[39m\n\u001b[32m 5551\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34minvoke\u001b[39m(\n\u001b[32m 5552\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 5555\u001b[39m **kwargs: Any | \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 5556\u001b[39m ) -> Output:\n\u001b[32m-> \u001b[39m\u001b[32m5557\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mbound\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 5558\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 5559\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_merge_configs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 5560\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43m{\u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 5561\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py:402\u001b[39m, in \u001b[36mBaseChatModel.invoke\u001b[39m\u001b[34m(self, input, config, stop, **kwargs)\u001b[39m\n\u001b[32m 388\u001b[39m \u001b[38;5;129m@override\u001b[39m\n\u001b[32m 389\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34minvoke\u001b[39m(\n\u001b[32m 390\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 395\u001b[39m **kwargs: Any,\n\u001b[32m 396\u001b[39m ) -> AIMessage:\n\u001b[32m 397\u001b[39m config = ensure_config(config)\n\u001b[32m 398\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m cast(\n\u001b[32m 399\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mAIMessage\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 400\u001b[39m cast(\n\u001b[32m 401\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mChatGeneration\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m--> \u001b[39m\u001b[32m402\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mgenerate_prompt\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 403\u001b[39m \u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_convert_input\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 404\u001b[39m \u001b[43m \u001b[49m\u001b[43mstop\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstop\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 405\u001b[39m \u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mcallbacks\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 406\u001b[39m \u001b[43m \u001b[49m\u001b[43mtags\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtags\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 407\u001b[39m \u001b[43m \u001b[49m\u001b[43mmetadata\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmetadata\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 408\u001b[39m \u001b[43m \u001b[49m\u001b[43mrun_name\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mrun_name\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 409\u001b[39m \u001b[43m \u001b[49m\u001b[43mrun_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpop\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mrun_id\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 410\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 411\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m.generations[\u001b[32m0\u001b[39m][\u001b[32m0\u001b[39m],\n\u001b[32m 412\u001b[39m ).message,\n\u001b[32m 413\u001b[39m )\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py:1121\u001b[39m, in \u001b[36mBaseChatModel.generate_prompt\u001b[39m\u001b[34m(self, prompts, stop, callbacks, **kwargs)\u001b[39m\n\u001b[32m 1112\u001b[39m \u001b[38;5;129m@override\u001b[39m\n\u001b[32m 1113\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mgenerate_prompt\u001b[39m(\n\u001b[32m 1114\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 1118\u001b[39m **kwargs: Any,\n\u001b[32m 1119\u001b[39m ) -> LLMResult:\n\u001b[32m 1120\u001b[39m prompt_messages = [p.to_messages() \u001b[38;5;28;01mfor\u001b[39;00m p \u001b[38;5;129;01min\u001b[39;00m prompts]\n\u001b[32m-> \u001b[39m\u001b[32m1121\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mgenerate\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprompt_messages\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstop\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstop\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py:931\u001b[39m, in \u001b[36mBaseChatModel.generate\u001b[39m\u001b[34m(self, messages, stop, callbacks, tags, metadata, run_name, run_id, **kwargs)\u001b[39m\n\u001b[32m 928\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m i, m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(input_messages):\n\u001b[32m 929\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 930\u001b[39m results.append(\n\u001b[32m--> \u001b[39m\u001b[32m931\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_generate_with_cache\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 932\u001b[39m \u001b[43m \u001b[49m\u001b[43mm\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 933\u001b[39m \u001b[43m \u001b[49m\u001b[43mstop\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstop\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 934\u001b[39m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrun_managers\u001b[49m\u001b[43m[\u001b[49m\u001b[43mi\u001b[49m\u001b[43m]\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrun_managers\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 935\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 936\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 937\u001b[39m )\n\u001b[32m 938\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 939\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m run_managers:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py:1190\u001b[39m, in \u001b[36mBaseChatModel._generate_with_cache\u001b[39m\u001b[34m(self, messages, stop, run_manager, **kwargs)\u001b[39m\n\u001b[32m 1188\u001b[39m index = -\u001b[32m1\u001b[39m\n\u001b[32m 1189\u001b[39m index_type = \u001b[33m\"\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m-> \u001b[39m\u001b[32m1190\u001b[39m \u001b[43m\u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_stream\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmessages\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstop\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstop\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 1191\u001b[39m \u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmessage\u001b[49m\u001b[43m.\u001b[49m\u001b[43mresponse_metadata\u001b[49m\u001b[43m \u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43m_gen_info_and_msg_metadata\u001b[49m\u001b[43m(\u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1192\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43moutput_version\u001b[49m\u001b[43m \u001b[49m\u001b[43m==\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mv1\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\n\u001b[32m 1193\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Overwrite .content with .content_blocks\u001b[39;49;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_anthropic/chat_models.py:1271\u001b[39m, in \u001b[36mChatAnthropic._stream\u001b[39m\u001b[34m(self, messages, stop, run_manager, stream_usage, **kwargs)\u001b[39m\n\u001b[32m 1269\u001b[39m payload = \u001b[38;5;28mself\u001b[39m._get_request_payload(messages, stop=stop, **kwargs)\n\u001b[32m 1270\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1271\u001b[39m stream = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_create\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpayload\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1272\u001b[39m coerce_content_to_string = (\n\u001b[32m 1273\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m _tools_in_params(payload)\n\u001b[32m 1274\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _documents_in_params(payload)\n\u001b[32m 1275\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _thinking_in_params(payload)\n\u001b[32m 1276\u001b[39m )\n\u001b[32m 1277\u001b[39m block_start_event = \u001b[38;5;28;01mNone\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/langchain_anthropic/chat_models.py:1250\u001b[39m, in \u001b[36mChatAnthropic._create\u001b[39m\u001b[34m(self, payload)\u001b[39m\n\u001b[32m 1248\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mbetas\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m payload:\n\u001b[32m 1249\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._client.beta.messages.create(**payload)\n\u001b[32m-> \u001b[39m\u001b[32m1250\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_client\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmessages\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcreate\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mpayload\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/anthropic/_utils/_utils.py:282\u001b[39m, in \u001b[36mrequired_args..inner..wrapper\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 280\u001b[39m msg = \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mMissing required argument: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mquote(missing[\u001b[32m0\u001b[39m])\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 281\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(msg)\n\u001b[32m--> \u001b[39m\u001b[32m282\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/anthropic/resources/messages/messages.py:932\u001b[39m, in \u001b[36mMessages.create\u001b[39m\u001b[34m(self, max_tokens, messages, model, metadata, service_tier, stop_sequences, stream, system, temperature, thinking, tool_choice, tools, top_k, top_p, extra_headers, extra_query, extra_body, timeout)\u001b[39m\n\u001b[32m 925\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m model \u001b[38;5;129;01min\u001b[39;00m DEPRECATED_MODELS:\n\u001b[32m 926\u001b[39m warnings.warn(\n\u001b[32m 927\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mThe model \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmodel\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m is deprecated and will reach end-of-life on \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mDEPRECATED_MODELS[model]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m.\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33mPlease migrate to a newer model. Visit https://docs.anthropic.com/en/docs/resources/model-deprecations for more information.\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 928\u001b[39m \u001b[38;5;167;01mDeprecationWarning\u001b[39;00m,\n\u001b[32m 929\u001b[39m stacklevel=\u001b[32m3\u001b[39m,\n\u001b[32m 930\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m932\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_post\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 933\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m/v1/messages\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 934\u001b[39m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmaybe_transform\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 935\u001b[39m \u001b[43m \u001b[49m\u001b[43m{\u001b[49m\n\u001b[32m 936\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmax_tokens\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mmax_tokens\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 937\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmessages\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mmessages\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 938\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmodel\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 939\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmetadata\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mmetadata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 940\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mservice_tier\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mservice_tier\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 941\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mstop_sequences\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mstop_sequences\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 942\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mstream\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 943\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msystem\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43msystem\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 944\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtemperature\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mtemperature\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 945\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mthinking\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mthinking\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 946\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtool_choice\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mtool_choice\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 947\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtools\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mtools\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 948\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtop_k\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mtop_k\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 949\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtop_p\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mtop_p\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 950\u001b[39m \u001b[43m \u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 951\u001b[39m \u001b[43m \u001b[49m\u001b[43mmessage_create_params\u001b[49m\u001b[43m.\u001b[49m\u001b[43mMessageCreateParamsStreaming\u001b[49m\n\u001b[32m 952\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\n\u001b[32m 953\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mmessage_create_params\u001b[49m\u001b[43m.\u001b[49m\u001b[43mMessageCreateParamsNonStreaming\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 954\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 955\u001b[39m \u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmake_request_options\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 956\u001b[39m \u001b[43m \u001b[49m\u001b[43mextra_headers\u001b[49m\u001b[43m=\u001b[49m\u001b[43mextra_headers\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mextra_query\u001b[49m\u001b[43m=\u001b[49m\u001b[43mextra_query\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mextra_body\u001b[49m\u001b[43m=\u001b[49m\u001b[43mextra_body\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout\u001b[49m\n\u001b[32m 957\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 958\u001b[39m \u001b[43m \u001b[49m\u001b[43mcast_to\u001b[49m\u001b[43m=\u001b[49m\u001b[43mMessage\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 959\u001b[39m \u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstream\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 960\u001b[39m \u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[43m=\u001b[49m\u001b[43mStream\u001b[49m\u001b[43m[\u001b[49m\u001b[43mRawMessageStreamEvent\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 961\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/anthropic/_base_client.py:1361\u001b[39m, in \u001b[36mSyncAPIClient.post\u001b[39m\u001b[34m(self, path, cast_to, body, content, options, files, stream, stream_cls)\u001b[39m\n\u001b[32m 1352\u001b[39m warnings.warn(\n\u001b[32m 1353\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mPassing raw bytes as `body` is deprecated and will be removed in a future version. \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 1354\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mPlease pass raw bytes via the `content` parameter instead.\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 1355\u001b[39m \u001b[38;5;167;01mDeprecationWarning\u001b[39;00m,\n\u001b[32m 1356\u001b[39m stacklevel=\u001b[32m2\u001b[39m,\n\u001b[32m 1357\u001b[39m )\n\u001b[32m 1358\u001b[39m opts = FinalRequestOptions.construct(\n\u001b[32m 1359\u001b[39m method=\u001b[33m\"\u001b[39m\u001b[33mpost\u001b[39m\u001b[33m\"\u001b[39m, url=path, json_data=body, content=content, files=to_httpx_files(files), **options\n\u001b[32m 1360\u001b[39m )\n\u001b[32m-> \u001b[39m\u001b[32m1361\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m cast(ResponseT, \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcast_to\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mopts\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[43m)\u001b[49m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/braincrew/langgraph-v1-tutorial/.venv/lib/python3.11/site-packages/anthropic/_base_client.py:1134\u001b[39m, in \u001b[36mSyncAPIClient.request\u001b[39m\u001b[34m(self, cast_to, options, stream, stream_cls)\u001b[39m\n\u001b[32m 1131\u001b[39m err.response.read()\n\u001b[32m 1133\u001b[39m log.debug(\u001b[33m\"\u001b[39m\u001b[33mRe-raising status error\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m1134\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m._make_status_error_from_response(err.response) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 1136\u001b[39m \u001b[38;5;28;01mbreak\u001b[39;00m\n\u001b[32m 1138\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m response \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m, \u001b[33m\"\u001b[39m\u001b[33mcould not resolve response (should never happen)\u001b[39m\u001b[33m\"\u001b[39m\n", + "\u001b[31mNotFoundError\u001b[39m: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-sonnet-4-5-invalid'}, 'request_id': 'req_011CX8LwhFYHFhGVh4wmrHXi'}", + "During task with name 'model' and id 'c98c9117-44be-713d-10f4-8125285b9ea0'" + ] + } + ], "source": [ "stream_graph(\n", " agent,\n", @@ -656,7 +983,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "feat-agent (3.11.14)", "language": "python", "name": "python3" }, @@ -670,9 +997,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.11.14" } }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/03-Agent/assets/langgraph-agent.excalidraw b/03-Agent/assets/langgraph-agent.excalidraw new file mode 100644 index 0000000..3abd80b --- /dev/null +++ b/03-Agent/assets/langgraph-agent.excalidraw @@ -0,0 +1,527 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "mermaid-to-excalidraw", + "elements": [ + { + "id": "QUERY", + "type": "rectangle", + "groupIds": [], + "x": 112.6484375, + "y": 0, + "width": 84.296875, + "height": 44, + "strokeWidth": 2, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "link": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "QUERY-text" + } + ] + }, + { + "id": "QUERY-text", + "type": "text", + "x": 124.796875, + "y": 9.5, + "width": 60, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": null, + "roundness": null, + "seed": 435946922, + "version": 1, + "versionNonce": 1499418233, + "isDeleted": false, + "boundElements": null, + "updated": 1770733281448, + "link": null, + "locked": false, + "text": "input", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "QUERY", + "originalText": "input", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "LLM", + "type": "diamond", + "groupIds": [], + "x": 90.7421875, + "y": 94, + "width": 128.109375, + "height": 128.109375, + "strokeWidth": 2, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "link": null, + "boundElements": [ + { + "type": "text", + "id": "LLM-text" + } + ] + }, + { + "id": "LLM-text", + "type": "text", + "x": 124.796875, + "y": 145.5546875, + "width": 60, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": null, + "roundness": null, + "seed": 789595348, + "version": 1, + "versionNonce": 1591726080, + "isDeleted": false, + "boundElements": null, + "updated": 1770733281448, + "link": null, + "locked": false, + "text": "model", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LLM", + "originalText": "model", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "TOOL", + "type": "rectangle", + "groupIds": [], + "x": 60.18359375, + "y": 301.109375, + "width": 69.25, + "height": 44, + "strokeWidth": 2, + "strokeColor": "#0d9488", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "link": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "TOOL-text" + } + ] + }, + { + "id": "TOOL-text", + "type": "text", + "x": 64.80859375, + "y": 310.609375, + "width": 60, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": null, + "roundness": null, + "seed": 1734077799, + "version": 1, + "versionNonce": 1144502286, + "isDeleted": false, + "boundElements": null, + "updated": 1770733281448, + "link": null, + "locked": false, + "text": "tools", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TOOL", + "originalText": "tools", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "ANSWER", + "type": "rectangle", + "groupIds": [], + "x": 229.796875, + "y": 301.109375, + "width": 100.5, + "height": 44, + "strokeWidth": 2, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "link": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ANSWER-text" + } + ] + }, + { + "id": "ANSWER-text", + "type": "text", + "x": 244.046875, + "y": 310.609375, + "width": 72, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": null, + "roundness": null, + "seed": 160711134, + "version": 1, + "versionNonce": 1008445031, + "isDeleted": false, + "boundElements": null, + "updated": 1770733281448, + "link": null, + "locked": false, + "text": "output", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ANSWER", + "originalText": "output", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "QUERY_LLM", + "type": "arrow", + "groupIds": [], + "x": 154.797, + "y": 44, + "strokeWidth": 2, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 0.396000000000015, + 45.20099999999999 + ] + ], + "roundness": { + "type": 2 + }, + "start": { + "id": "QUERY" + }, + "end": { + "id": "LLM" + } + }, + { + "id": "LLM_TOOL", + "type": "arrow", + "groupIds": [], + "x": 120.917, + "y": 188.229, + "strokeWidth": 2, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + -86.09700000000001, + 73.37999999999997 + ], + [ + -51.268, + 109.08599999999998 + ] + ], + "roundness": { + "type": 2 + }, + "start": { + "id": "LLM" + }, + "end": { + "id": "TOOL" + }, + "boundElements": [ + { + "type": "text", + "id": "LLM_TOOL-text" + } + ] + }, + { + "id": "LLM_TOOL-text", + "type": "text", + "x": -5, + "y": 235, + "width": 72, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": null, + "roundness": null, + "seed": 942062837, + "version": 1, + "versionNonce": 1083494293, + "isDeleted": false, + "boundElements": null, + "updated": 1770733281448, + "link": null, + "locked": false, + "text": "action", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LLM_TOOL", + "originalText": "action", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "TOOL_LLM", + "type": "arrow", + "groupIds": [], + "x": 116.268, + "y": 301.109, + "strokeWidth": 2, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 38.528999999999996, + -39.5 + ], + [ + 38.96100000000001, + -73.19999999999999 + ] + ], + "roundness": { + "type": 2 + }, + "start": { + "id": "TOOL" + }, + "end": { + "id": "LLM" + }, + "boundElements": [ + { + "type": "text", + "id": "TOOL_LLM-text" + } + ] + }, + { + "id": "TOOL_LLM-text", + "type": "text", + "x": 130, + "y": 255, + "width": 132, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": null, + "roundness": null, + "seed": 1390254560, + "version": 1, + "versionNonce": 1523828737, + "isDeleted": false, + "boundElements": null, + "updated": 1770733281448, + "link": null, + "locked": false, + "text": "observation", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TOOL_LLM", + "originalText": "observation", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "LLM_ANSWER", + "type": "arrow", + "groupIds": [], + "x": 190.361, + "y": 187.545, + "strokeWidth": 2, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 89.68600000000004, + 74.064 + ], + [ + 89.68600000000004, + 108.26400000000004 + ] + ], + "roundness": { + "type": 2 + }, + "start": { + "id": "LLM" + }, + "end": { + "id": "ANSWER" + }, + "boundElements": [ + { + "type": "text", + "id": "LLM_ANSWER-text" + } + ] + }, + { + "id": "LLM_ANSWER-text", + "type": "text", + "x": 275, + "y": 235, + "width": 72, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": null, + "roundness": null, + "seed": 430140322, + "version": 1, + "versionNonce": 1005965341, + "isDeleted": false, + "boundElements": null, + "updated": 1770733281448, + "link": null, + "locked": false, + "text": "finish", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LLM_ANSWER", + "originalText": "finish", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "theme": "light", + "viewBackgroundColor": "#ffffff", + "currentItemFontFamily": 1 + }, + "files": {} +} diff --git a/03-Agent/assets/langgraph-agent.png b/03-Agent/assets/langgraph-agent.png new file mode 100644 index 0000000..e9ce89c Binary files /dev/null and b/03-Agent/assets/langgraph-agent.png differ