diff --git a/08-Core-Features/05-LangGraph-Streaming-Outputs.ipynb b/08-Core-Features/05-LangGraph-Streaming-Outputs.ipynb index 45f8c69..f26e89f 100644 --- a/08-Core-Features/05-LangGraph-Streaming-Outputs.ipynb +++ b/08-Core-Features/05-LangGraph-Streaming-Outputs.ipynb @@ -7,24 +7,40 @@ "source": [ "# LangGraph 단계별 스트리밍 출력\n", "\n", - "이번에는 LangGrpah 의 `stream()` 출력 함수에 대한 조금 더 자세한 설명을 진행합니다.\n", + "LangGraph의 `stream()` 메서드는 그래프의 각 단계를 스트리밍하는 기능을 제공합니다. 이 튜토리얼에서는 다양한 스트리밍 모드와 옵션을 살펴봅니다.\n", "\n", - "LangGraph 의 스트리밍 출력 함수는 그래프의 각 단계를 스트리밍하는 기능을 제공합니다.\n", - "\n" + "> 참고 문서: [LangGraph Streaming](https://langchain-ai.github.io/langgraph/concepts/streaming/)" ] }, { "cell_type": "markdown", "id": "1b2184f4", "metadata": {}, - "source": "## 튜토리얼 그래프 설정\n\n아래의 LangGraph 예제는 이전 섹션의 Agent 예제와 동일합니다. Google News 검색 도구를 사용하는 챗봇 에이전트를 구축하고, 이를 통해 다양한 스트리밍 모드를 테스트합니다.\n\n추가로 `dummy_data` 상태 필드를 추가하여 여러 상태 키가 있을 때 `output_keys` 옵션이 어떻게 동작하는지 확인합니다." + "source": [ + "## 튜토리얼 그래프 설정\n", + "\n", + "아래의 LangGraph 예제는 이전 섹션의 Agent 예제와 동일합니다. Google News 검색 도구를 사용하는 챗봇 에이전트를 구축하고, 이를 통해 다양한 스트리밍 모드를 테스트합니다.\n", + "\n", + "추가로 `dummy_data` 상태 필드를 추가하여 여러 상태 키가 있을 때 `output_keys` 옵션이 어떻게 동작하는지 확인합니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "de9d9d8d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# API 키를 환경변수로 관리하기 위한 설정 파일\n", "from dotenv import load_dotenv\n", @@ -35,10 +51,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "6b5c6228", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LangSmith 추적을 시작합니다.\n", + "[프로젝트명]\n", + "LangGraph-V1-Tutorial\n" + ] + } + ], "source": [ "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n", "# !pip install -qU langchain-teddynote\n", @@ -50,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "51549b1d", "metadata": {}, "outputs": [], @@ -71,9 +97,10 @@ "########## 1. 상태 정의 ##########\n", "# 상태 정의\n", "class State(TypedDict):\n", - " # 메시지 목록 주석 추가\n", + " # 메시지 목록: add_messages reducer를 사용하여 메시지 누적\n", " messages: Annotated[list, add_messages]\n", - " dummy_data: Annotated[str, \"dummy\"]\n", + " # 더미 데이터: output_keys 테스트용 (reducer 없이 단순 덮어쓰기)\n", + " dummy_data: str\n", "\n", "\n", "########## 2. 도구 정의 및 바인딩 ##########\n", @@ -101,7 +128,10 @@ "########## 3. 노드 추가 ##########\n", "# 챗봇 함수 정의\n", "def chatbot(state: State):\n", - " # 메시지 호출 및 반환\n", + " \"\"\"챗봇 노드 함수\n", + " \n", + " 메시지를 받아 LLM에 전달하고 응답을 반환합니다.\n", + " \"\"\"\n", " return {\n", " \"messages\": [llm_with_tools.invoke(state[\"messages\"])],\n", " \"dummy_data\": \"[chatbot] 호출, dummy data\", # 테스트를 위하여 더미 데이터를 추가합니다.\n", @@ -135,17 +165,23 @@ "# START > chatbot\n", "graph_builder.add_edge(START, \"chatbot\")\n", "\n", - "# chatbot > END\n", - "graph_builder.add_edge(\"chatbot\", END)\n", - "\n", "########## 5. 그래프 컴파일 ##########\n", "\n", + "# 메모리 체크포인터 생성 (interrupt 기능 사용을 위해 필요)\n", + "memory = MemorySaver()\n", + "\n", "# 그래프 빌더 컴파일\n", - "graph = graph_builder.compile()\n", + "graph = graph_builder.compile(checkpointer=memory)" + ] + }, + { + "cell_type": "markdown", + "id": "g5vjvx0kwya", + "metadata": {}, + "source": [ + "위 코드에서 생성한 그래프의 구조는 다음과 같습니다. `chatbot` 노드에서 `tools_condition`에 따라 조건부 분기가 이루어지며, 도구 호출이 필요한 경우 `tools` 노드로, 그렇지 않으면 `__end__`로 이동합니다.\n", "\n", - "########## 6. 그래프 시각화 ##########\n", - "# 그래프 시각화\n", - "visualize_graph(graph)" + "![chatbot-graph](./assets/chatbot-graph.png)" ] }, { @@ -155,31 +191,34 @@ "source": [ "## StateGraph의 `stream` 메서드\n", "\n", - "`stream` 메서드는 단일 입력에 대한 그래프 단계를 스트리밍하는 기능을 제공합니다.\n", + "`stream` 메서드는 단일 입력에 대한 그래프 단계를 스트리밍하는 기능을 제공합니다. 각 노드의 실행 결과를 순차적으로 받아볼 수 있어, 실시간으로 진행 상황을 모니터링하거나 점진적으로 결과를 표시할 때 유용합니다.\n", "\n", "**매개변수**\n", "- `input` (Union[dict[str, Any], Any]): 그래프에 대한 입력\n", - "- `config` (Optional[RunnableConfig]): 실행 구성\n", + "- `config` (Optional[RunnableConfig]): 실행 구성 (thread_id, recursion_limit 등)\n", "- `stream_mode` (Optional[Union[StreamMode, list[StreamMode]]]): 출력 스트리밍 모드\n", "- `output_keys` (Optional[Union[str, Sequence[str]]]): 스트리밍할 키\n", - "- `interrupt_before` (Optional[Union[All, Sequence[str]]]): 실행 전에 중단할 노드\n", - "- `interrupt_after` (Optional[Union[All, Sequence[str]]]): 실행 후에 중단할 노드\n", - "- `debug` (Optional[bool]): 디버그 정보 출력 여부\n", "- `subgraphs` (bool): 하위 그래프 스트리밍 여부\n", "\n", "**반환값**\n", "- Iterator[Union[dict[str, Any], Any]]: 그래프의 각 단계 출력. 출력 형태는 `stream_mode`에 따라 다름\n", "\n", - "**주요 기능**\n", - "1. 입력된 설정에 따라 그래프 실행을 스트리밍 방식으로 처리\n", - "2. 다양한 스트리밍 모드 지원 (`values`, `updates`, `debug`)\n", - "3. 콜백 관리 및 오류 처리\n", - "4. 재귀 제한 및 중단 조건 처리\n", - "\n", "**스트리밍 모드**\n", "- `values`: 각 단계의 현재 상태 값 출력\n", - "- `updates`: 각 단계의 상태 업데이트만 출력\n", - "- `debug`: 각 단계의 디버그 이벤트 출력" + "- `updates`: 각 단계의 상태 업데이트만 출력 (기본값)\n", + "- `debug`: 각 단계의 디버그 이벤트 출력\n", + "\n", + "**참고: interrupt 옵션**\n", + "\n", + "`interrupt_before`와 `interrupt_after` 옵션은 **`compile()` 메서드**에서 설정해야 합니다. `stream()` 메서드가 아닌 그래프 컴파일 시점에 지정합니다.\n", + "\n", + "```python\n", + "# 올바른 사용법: compile()에서 interrupt 설정\n", + "graph = graph_builder.compile(\n", + " checkpointer=memory,\n", + " interrupt_before=[\"tools\"], # tools 노드 실행 전 중단\n", + ")\n", + "```" ] }, { @@ -209,14 +248,66 @@ "cell_type": "markdown", "id": "e931823b", "metadata": {}, - "source": "## 그래프 실행 및 스트리밍 테스트\n\n`config` 설정과 함께 스트리밍 출력을 진행합니다. 기본 스트리밍 모드는 `updates`이며, 각 노드의 실행 결과를 순차적으로 출력합니다.\n\n아래 코드에서는 질문을 입력하고 그래프를 스트리밍 방식으로 실행합니다." + "source": [ + "## 그래프 실행 및 스트리밍 테스트\n", + "\n", + "`config` 설정과 함께 스트리밍 출력을 진행합니다. 기본 스트리밍 모드는 `updates`이며, 각 노드의 실행 결과를 순차적으로 출력합니다.\n", + "\n", + "아래 코드에서는 질문을 입력하고 그래프를 스트리밍 방식으로 실행합니다." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "ab5c0bad", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[ chatbot ]\n", + "\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " search_keyword (call_96Sw1QepxIyZSArSPTaDz8sZ)\n", + " Call ID: call_96Sw1QepxIyZSArSPTaDz8sZ\n", + " Args:\n", + " query: 2024 노벨 문학상\n", + "\n", + "[ tools ]\n", + "\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: search_keyword\n", + "\n", + "[{\"url\": \"https://news.google.com/rss/articles/CBMibkFVX3lxTFA4YVpqdTZvWURMMWxtbFFzVVBDZkc3ZmMtOEpEV0hZQ3JoUEtyWmFqN21uZkthdW1EM3lwOW1vUlBnU2R3SkpQWDVaa2FQLTBUY1gwWnlhWVFkMDcwdENrNHpEcVhaTXZLX2JFSlBR?oc=5\", \"content\": \"[1년전 오늘] 한강 작가, 2024 노벨문학상 시상식 참석 - 전국매일신문\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiUkFVX3lxTE9ZSHIzUEQxcFlXSE1GUDR0MWJPZHdWTVFrcm9qaVQ3MTBtU2tsUVZrTjlxMFJTeWctYlIxUElVRmJNTjl1aE1nQVJLNUVSRk82cUE?oc=5\", \"content\": \"속보노벨문학상 한강 \\\"2024년에 계엄 상황 충격받았다\\\" - 서울경제\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiWkFVX3lxTE0zaHd3RVowVldBdW1COEtkS29Lc2NXVkUxb0d6OW1NVzhtUEJuNVZrenRxX0pYMFV2TGlhQkRaQkpBS2pLYkRMS2dIck4xcktIcE1sVDBZaFZoQdIBVEFVX3lxTE5fYXBPVE9PalBqcThfSGRIa2R0REJsSVBzNlRWZzktQzlzTDJTdFpqUHo4NHg2aHdJU2dMMUhFRnZ0My1KejRIVVdhNlhNS2FJWE11bA?oc=5\", \"content\": \"2025 노벨문학상 D-1…수상자는 누가 정하나? - 한국경제\"}, {\"url\": \"https://news.google.com/rss/articles/CBMia0FVX3lxTFBTOE1zQXVRS2FiSzlDaGVyUDhrVXBHbFF0OEFpVzVTeWZITEZOVTBfdkpxdmFJYmlGRnU2VFAyYzUtZXRGX1M4YmhzWWpPSnhjU09SYnYtZ0pMa0kxZU1IbWQzR09fVWV4Ykdr?oc=5\", \"content\": \"지난해 도서관 최다 대출 도서는 한강 '소년이 온다' - 뉴스저널리즘\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiVkFVX3lxTE9mZ0pEUmFGWUkwWmNQbTgwQTlKeWpEOFc4Yjc5U0tOdTgxUmpXME9wdlBFX3BUWkNNWXhqN1lnNklNWjhMVHg4N2tiLVpKOHowYTU3dVZB?oc=5\", \"content\": \"[테마스페셜] 한강 작가, 2024 노벨문학상 수상 “세계 사로잡은 독창성” - knn.co.kr\"}]\n", + "\n", + "[ chatbot ]\n", + "\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "2024년 노벨 문학상에 관련된 최근 뉴스는 다음과 같습니다:\n", + "\n", + "1. **한강 작가, 2024 노벨문학상 시상식 참석** \n", + " - [전국매일신문 기사](https://news.google.com/rss/articles/CBMibkFVX3lxTFA4YVpqdTZvWURMMWxtbFFzVVBDZkc3ZmMtOEpEV0hZQ3JoUEtyWmFqN21uZkthdW1EM3lwOW1vUlBnU2R3SkpQWDVaa2FQLTBUY1gwWnlhWVFkMDcwdENrNHpEcVhaTXZLX2JFSlBR?oc=5)\n", + "\n", + "2. **속보: 노벨문학상 한강 \"2024년에 계엄 상황 충격받았다\"** \n", + " - [서울경제 기사](https://news.google.com/rss/articles/CBMiUkFVX3lxTE9ZSHIzUEQxcFlXSE1GUDR0MWJPZHdWTVFrcm9qaVQ3MTBtU2tsUVZrTjlxMFJTeWctYlIxUElVRmJNTjl1aE1nQVJLNUVSRk82cUE?oc=5)\n", + "\n", + "3. **2025 노벨문학상 D-1…수상자는 누가 정하나?** \n", + " - [한국경제 기사](https://news.google.com/rss/articles/CBMiWkFVX3lxTE0zaHd3RVowVldBdW1COEtkS29Lc2NXVkUxb0d6OW1NVzhtUEJuNVZrenRxX0pYMFV2TGlhQkRaQkpBS2pLYkRMS2dIck4xcktIcE1sVDBZaFZoQdIBVEFVX3lxTE5fYXBPVE9PalBqcThfSGRIa2R0REJsSVBzNlRWZzktQzlzTDJTdFpqUHo4NHg2aHdJU2dMMUhFRnZ0My1KejRIVVdhNlhNS2FJWE11bA?oc=5)\n", + "\n", + "4. **지난해 도서관 최다 대출 도서는 한강 '소년이 온다'** \n", + " - [뉴스저널리즘 기사](https://news.google.com/rss/articles/CBMia0FVX3lxTFBTOE1zQXVRS2FiSzlDaGVyUDhrVXBHbFF0OEFpVzVTeWZITEZOVTBfdkpxdmFJYmlGRnU2VFAyYzUtZXRGX1M4YmhzWWpPSnhjU09SYnYtZ0pMa0kxZU1IbWQzR09fVWV4Ykdr?oc=5)\n", + "\n", + "5. **[테마스페셜] 한강 작가, 2024 노벨문학상 수상 “세계 사로잡은 독창성”** \n", + " - [knn.co.kr 기사](https://news.google.com/rss/articles/CBMiVkFVX3lxTE9mZ0pEUmFGWUkwWmNQbTgwQTlKeWpEOFc4Yjc5U0tOdTgxUmpXME9wdlBFX3BUWkNNWXhqN1lnNklNWjhMVHg4N2tiLVpKOHowYTU3dVZB?oc=5)\n", + "\n", + "이 뉴스들은 한강 작가와 관련된 내용과 2024년 노벨 문학상의 주요 이슈들에 대해 다루고 있습니다.\n" + ] + } + ], "source": [ "for event in graph.stream(input=input, config=config):\n", " for key, value in event.items():\n", @@ -246,10 +337,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "efe63155", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['messages', 'dummy_data', '__start__', '__pregel_tasks', 'branch:to:chatbot', 'branch:to:tools']\n" + ] + } + ], "source": [ "# channels 에 정의된 키 목록을 출력합니다.\n", "print(list(graph.channels.keys()))" @@ -257,10 +356,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "87efc73e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[ chatbot ]\n", + "\n", + "dict_keys(['dummy_data'])\n", + "[chatbot] 호출, dummy data\n", + "\n", + "[ tools ]\n", + "\n", + "\n", + "[ chatbot ]\n", + "\n", + "dict_keys(['dummy_data'])\n", + "[chatbot] 호출, dummy data\n" + ] + } + ], "source": [ "# 질문\n", "question = \"2024년 노벨 문학상 관련 뉴스를 알려주세요.\"\n", @@ -295,10 +414,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "9cecf357", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[ chatbot ]\n", + "\n", + "\n", + "\n", + "[ tools ]\n", + "\n", + "[{\"url\": \"https://news.google.com/rss/articles/CBMiQkFVX3lxTE9QemstQnJSeXZvc2lQWk56dHlFZXEzNGNaMXhmbm5GT2tobHRDcnp2RTROdVlrOUpjdGY2bTloeEJVQQ?oc=5\", \"content\": \"부천시립도서관, ‘2025년 베스트 대출 도서’ 발표 - 브레이크뉴스\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiXkFVX3lxTE8zbzljanBYZmpabGxRRzU4ZlcxaXlCRWxESWFzQ1NfYmptYXU1cXUydzRlVG41NXA2dkpxWWNDeWhXVkFnZDYwb1p2dmdoRWF2WEdROEttbVZTLVNYQVE?oc=5\", \"content\": \"Han Kang’s poetic prose embraces wounds of martial law - 경향신문\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiU0FVX3lxTE84bnZBVzYtTGdJWFNnVG5VeFl5b3B1RVJGUkZvZ3dETjFBU3R6Q0VWTXd2M3UwSXltOHR0WVRhSjBkbTljQ3AwYUdjdFdpd3NvSDk4?oc=5\", \"content\": \"Han Kang Wins Nobel Prize in Literature: A Moment of Honor - 미주한국일보\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiR0FVX3lxTE5xZkFDTzNsZ05QTUNpbUFiQlRZeFZMaDVDTGNKNkt3TXdLeU9qc2xwWld2TXZTZlVDMXpOdUxkWkZUV1FnNlB3?oc=5\", \"content\": \"월간 '리더피플', ‘2025년 대한민국 리더 100인’ 선정 - 브레이크뉴스\"}, {\"url\": \"https://news.google.com/rss/articles/CBMihwNBVV95cUxNSE9FdzRYelhleXFyMFpsb2hFTEZxMkF4eDcxX18zQTh2NWl0cVUzOUdSNXlHOEJ1TFdOeENTZHZFbHFzNVFPTkpYenYya2dJQ3dGUk1fVXNDREhzNnpuN3drWDZzcVYzQjNBSGQ0NnYteDNwNFltZGVUSy0yMWpGN1B1WUQ0TTJhNnlyZktka3YxSWdyZDhOY1NsZkNTMzlNd1VjUldTRlRNeEhjdTJkN2lOX1NkUjctS3VHcUhSOWtvalJtWGY2eHFKUEhXZTFLWGRscGFxb2hrdEdPck41ZmRENlVXNno1NXpCbWY5ZXBvck5hTUJ4OWtudzRFMy1lX0Zrc0owb2c0azV3MVc1aFRNNGpFX19ZQVd5WmVvWk5MTXhOZ2w2Mm9DWmxVVC1BeWRyZE40M1hQVnh5U3BDM0s4X1NhSkZ0ZHVlbE5DYlUyTGVBV1FlbWZUV1U0RE4zbmFFVHNBcWdCUGdNNUhsREp0S2FMRVNhTVpFMjI5akkwbEx3UG5v?oc=5\", \"content\": \"한강의 노벨상 수상, 정치적 혼란 속 맛본 승리의 순간 - Korea JoongAng Daily\"}]\n", + "\n", + "[ chatbot ]\n", + "\n", + "2024년 노벨 문학상에 대한 최근 뉴스는 다음과 같습니다:\n", + "\n", + "1. **한강의 시적 프로즈, 계엄의 상처를 포용하다** \n", + " - [경향신문 기사](https://news.google.com/rss/articles/CBMiXkFVX3lxTE8zbzljanBYZmpabGxRRzU4ZlcxaXlCRWxESWFzQ1NfYmptYXU1cXUydzRlVG41NXA2dkpxWWNDeWhXVkFnZDYwb1p2dmdoRWF2WEdROEttbVZTLVNYQVE?oc=5)\n", + "\n", + "2. **한강, 노벨 문학상 수상: 영예의 순간** \n", + " - [미주한국일보 기사](https://news.google.com/rss/articles/CBMiU0FVX3lxTE84bnZBVzYtTGdJWFNnVG5VeFl5b3B1RVJGUkZvZ3dETjFBU3R6Q0VWTXd2M3UwSXltOHR0WVRhSjBkbTljQ3AwYUdjdFdpd3NvSDk4?oc=5)\n", + "\n", + "3. **한강의 노벨상 수상, 정치적 혼란 속 맛본 승리의 순간** \n", + " - [Korea JoongAng Daily 기사](https://news.google.com/rss/articles/CBMihwNBVV95cUxNSE9FdzRYelhleXFyMFpsb2hFTEZxMkF4eDcxX18zQTh2NWl0cVUzOUdSNXlHOEJ1TFdOeENTZHZFbHFzNVFPTkpYenYya2dJQ3dGUk1fVXNDREhzNnpuN3drWDZzcVYzQjNBSGQ0NnYteDNwNFltZGVUSy0yMWpGN1B1WUQ0TTJhNnlyZktka3YxSWdyZDhOY1NsZkNTMzlNd1VjUldTRlRNeEhjdTJkN2lOX1NkUjctS3VHcUhSOWtvalJtWGY2eHFKUEhXZTFLWGRscGFxb2hrdEdPck41ZmRENlVXNno1NXpCbWY5ZXBvck5hTUJ4OWtudzRFMy1lX0Zrc0owb2c0azV3MVc1aFRNNGpFX19ZQVd5WmVvWk5MTXhOZ2w2Mm9DWmxVVC1BeWRyZE40M1hQVnh5U3BDM0s4X1NhSkZ0ZHVlbE5DYlUyTGVBV1FlbWZUV1U0RE4zbmFFVHNBcWdCUGdNNUhsREp0S2FMRVNhTVpFMjI5akkwbEx3UG5v?oc=5)\n", + "\n", + "이 기사들은 한강 작가의 노벨 문학상 수상과 관련된 다양한 측면을 다루고 있습니다.\n" + ] + } + ], "source": [ "# 질문\n", "question = \"2024년 노벨 문학상 관련 뉴스를 알려주세요.\"\n", @@ -337,7 +486,11 @@ "`stream_mode` 옵션은 스트리밍 출력 모드를 지정하는 데 사용됩니다.\n", "\n", "- `values`: 각 단계의 현재 상태 값 출력 \n", - "- `updates`: 각 단계의 상태 업데이트만 출력 (기본값)" + "- `updates`: 각 단계의 상태 업데이트만 출력 (기본값)\n", + "\n", + "아래 다이어그램은 두 모드의 차이를 보여줍니다.\n", + "\n", + "![stream_mode 비교](./assets/stream-mode-comparison.png)" ] }, { @@ -359,10 +512,49 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "89eff21c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[ messages ]\n", + "\n", + "메시지 개수: 1\n", + "\n", + "[ dummy_data ]\n", + "\n", + "============================== 단계 ==============================\n", + "\n", + "[ messages ]\n", + "\n", + "메시지 개수: 2\n", + "\n", + "[ dummy_data ]\n", + "\n", + "============================== 단계 ==============================\n", + "\n", + "[ messages ]\n", + "\n", + "메시지 개수: 3\n", + "\n", + "[ dummy_data ]\n", + "\n", + "============================== 단계 ==============================\n", + "\n", + "[ messages ]\n", + "\n", + "메시지 개수: 4\n", + "\n", + "[ dummy_data ]\n", + "\n", + "============================== 단계 ==============================\n" + ] + } + ], "source": [ "# 질문\n", "question = \"2024년 노벨 문학상 관련 뉴스를 알려주세요.\"\n", @@ -370,17 +562,18 @@ "# 초기 입력 State 를 정의\n", "input = State(dummy_data=\"테스트 문자열\", messages=[(\"user\", question)])\n", "\n", - "# config 설정\n", + "# config 설정 (새로운 thread_id 사용)\n", "config = RunnableConfig(\n", - " recursion_limit=10, # 최대 10개의 노드까지 방문. 그 이상은 RecursionError 발생\n", - " configurable={\"thread_id\": \"1\"}, # 스레드 ID 설정\n", - " tags=[\"my-rag\"], # Tag\n", + " recursion_limit=10,\n", + " configurable={\"thread_id\": \"values-mode-test\"}, # thread_id 필수\n", + " tags=[\"my-rag\"],\n", ")\n", "\n", "# values 모드로 스트리밍 출력\n", "for event in graph.stream(\n", " input=input,\n", - " stream_mode=\"values\", # 기본값\n", + " config=config, # checkpointer 사용 시 config 필수\n", + " stream_mode=\"values\",\n", "):\n", " for key, value in event.items():\n", " # key 는 state 의 key 값\n", @@ -412,10 +605,35 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "2124b916", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[ chatbot ]\n", + "\n", + "dict_keys(['messages', 'dummy_data'])\n", + "메시지 개수: 1\n", + "============================== 단계 ==============================\n", + "\n", + "[ tools ]\n", + "\n", + "dict_keys(['messages'])\n", + "메시지 개수: 1\n", + "============================== 단계 ==============================\n", + "\n", + "[ chatbot ]\n", + "\n", + "dict_keys(['messages', 'dummy_data'])\n", + "메시지 개수: 1\n", + "============================== 단계 ==============================\n" + ] + } + ], "source": [ "# 질문\n", "question = \"2024년 노벨 문학상 관련 뉴스를 알려주세요.\"\n", @@ -423,17 +641,18 @@ "# 초기 입력 State 를 정의\n", "input = State(dummy_data=\"테스트 문자열\", messages=[(\"user\", question)])\n", "\n", - "# config 설정\n", + "# config 설정 (새로운 thread_id 사용)\n", "config = RunnableConfig(\n", - " recursion_limit=10, # 최대 10개의 노드까지 방문. 그 이상은 RecursionError 발생\n", - " configurable={\"thread_id\": \"1\"}, # 스레드 ID 설정\n", - " tags=[\"my-rag\"], # Tag\n", + " recursion_limit=10,\n", + " configurable={\"thread_id\": \"updates-mode-test\"}, # thread_id 필수\n", + " tags=[\"my-rag\"],\n", ")\n", "\n", "# updates 모드로 스트리밍 출력\n", "for event in graph.stream(\n", " input=input,\n", - " stream_mode=\"updates\", # 기본값\n", + " config=config, # checkpointer 사용 시 config 필수\n", + " stream_mode=\"updates\",\n", "):\n", " for key, value in event.items():\n", " # key 는 노드 이름\n", @@ -454,101 +673,193 @@ "id": "d98b21a8", "metadata": {}, "source": [ - "### `interrupt_before` 와 `interrupt_after` 옵션\n", + "## `interrupt_before`와 `interrupt_after` 옵션\n", + "\n", + "LangGraph v1에서 `interrupt_before`와 `interrupt_after` 옵션은 **`compile()` 메서드**에서 설정합니다. 이 옵션들은 Human-in-the-Loop(HITL) 패턴을 구현할 때 유용하며, 특정 노드 실행 전후에 그래프 실행을 일시 중단합니다.\n", + "\n", + "- `interrupt_before`: 지정된 노드 **실행 전**에 중단\n", + "- `interrupt_after`: 지정된 노드 **실행 후**에 중단\n", + "\n", + "**중요**: interrupt 기능을 사용하려면 반드시 `checkpointer`가 필요합니다. 체크포인터가 있어야 중단된 지점의 상태를 저장하고, 이후 `invoke(None, config)`로 재개할 수 있습니다.\n", "\n", - "`interrupt_before` 와 `interrupt_after` 옵션은 스트리밍 중단 시점을 지정하는 데 사용됩니다.\n", + "아래 다이어그램은 두 옵션의 중단 시점 차이를 보여줍니다.\n", "\n", - "- `interrupt_before`: 지정된 노드 이전에 스트리밍 중단\n", - "- `interrupt_after`: 지정된 노드 이후에 스트리밍 중단\n" + "![interrupt_before vs interrupt_after](./assets/interrupt-flow.png)\n", + "\n", + "아래 코드에서는 `interrupt_before`를 사용하여 `tools` 노드 실행 전에 중단되는 그래프를 컴파일합니다." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "6f583a38", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== interrupt_before=['tools'] 테스트 ===\n", + "\n", + "tools 노드 실행 전에 중단됩니다.\n", + "\n", + "\n", + "[chatbot]\n", + "\n", + "출력 키: dict_keys(['messages', 'dummy_data'])\n", + "메시지 개수: 1\n", + "도구 호출 요청: search_keyword\n", + "========================================\n", + "\n", + "[__interrupt__]\n", + "\n", + "========================================\n", + "\n", + "현재 상태 - 다음 실행 노드: ('tools',)\n" + ] + } + ], "source": [ + "# interrupt_before를 사용하는 그래프 컴파일\n", + "graph_with_interrupt_before = graph_builder.compile(\n", + " checkpointer=MemorySaver(),\n", + " interrupt_before=[\"tools\"], # tools 노드 실행 전에 중단\n", + ")\n", + "\n", "# 질문\n", "question = \"2024년 노벨 문학상 관련 뉴스를 알려주세요.\"\n", "\n", - "# 초기 입력 State 를 정의\n", + "# 초기 입력 State를 정의\n", "input = State(dummy_data=\"테스트 문자열\", messages=[(\"user\", question)])\n", "\n", - "# config 설정\n", + "# config 설정 (새로운 thread_id 사용)\n", "config = RunnableConfig(\n", - " recursion_limit=10, # 최대 10개의 노드까지 방문. 그 이상은 RecursionError 발생\n", - " configurable={\"thread_id\": \"1\"}, # 스레드 ID 설정\n", - " tags=[\"my-rag\"], # Tag\n", + " recursion_limit=10,\n", + " configurable={\"thread_id\": \"interrupt-before-test\"},\n", ")\n", "\n", - "for event in graph.stream(\n", + "print(\"=== interrupt_before=['tools'] 테스트 ===\\n\")\n", + "print(\"tools 노드 실행 전에 중단됩니다.\\n\")\n", + "\n", + "for event in graph_with_interrupt_before.stream(\n", " input=input,\n", " config=config,\n", - " stream_mode=\"updates\", # 기본값\n", - " interrupt_before=[\"tools\"], # tools 노드 이전에 스트리밍 중단\n", + " stream_mode=\"updates\",\n", "):\n", " for key, value in event.items():\n", - " # key 는 노드 이름\n", + " # key는 노드 이름\n", " print(f\"\\n[{key}]\\n\")\n", "\n", - " # value 는 노드의 출력값\n", + " # value는 노드의 출력값\n", " if isinstance(value, dict):\n", - " print(value.keys())\n", + " print(f\"출력 키: {value.keys()}\")\n", " if \"messages\" in value:\n", - " print(value[\"messages\"])\n", + " print(f\"메시지 개수: {len(value['messages'])}\")\n", + " # 마지막 메시지 내용 출력\n", + " last_msg = value[\"messages\"][-1]\n", + " if hasattr(last_msg, \"tool_calls\") and last_msg.tool_calls:\n", + " print(f\"도구 호출 요청: {last_msg.tool_calls[0]['name']}\")\n", + " print(\"=\" * 40)\n", + "\n", + "# 중단 후 상태 확인\n", + "state = graph_with_interrupt_before.get_state(config)\n", + "print(f\"\\n현재 상태 - 다음 실행 노드: {state.next}\")" + ] + }, + { + "cell_type": "markdown", + "id": "bgcjoa94cjr", + "metadata": {}, + "source": [ + "### `interrupt_after` 예제\n", "\n", - " # value 에는 state 가 dict 형태로 저장(values 의 key 값)\n", - " if \"messages\" in value:\n", - " print(f\"메시지 개수: {len(value['messages'])}\")\n", - " print(\"===\" * 10, \" 단계 \", \"===\" * 10)" + "`interrupt_after`는 지정된 노드가 **실행된 후**에 그래프를 중단합니다. 도구 실행 결과를 확인한 후 다음 단계로 진행할지 결정하는 시나리오에 유용합니다.\n", + "\n", + "아래 코드에서는 `interrupt_after`를 사용하여 `tools` 노드 실행 후에 중단되는 그래프를 컴파일합니다." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "33dc67a2", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== interrupt_after=['tools'] 테스트 ===\n", + "\n", + "tools 노드 실행 후에 중단됩니다.\n", + "\n", + "\n", + "[chatbot]\n", + "\n", + "출력 키: dict_keys(['messages', 'dummy_data'])\n", + "메시지 개수: 1\n", + "========================================\n", + "\n", + "[tools]\n", + "\n", + "출력 키: dict_keys(['messages'])\n", + "메시지 개수: 1\n", + "========================================\n", + "\n", + "[__interrupt__]\n", + "\n", + "========================================\n", + "\n", + "현재 상태 - 다음 실행 노드: ('chatbot',)\n" + ] + } + ], "source": [ + "# interrupt_after를 사용하는 그래프 컴파일\n", + "graph_with_interrupt_after = graph_builder.compile(\n", + " checkpointer=MemorySaver(),\n", + " interrupt_after=[\"tools\"], # tools 노드 실행 후에 중단\n", + ")\n", + "\n", "# 질문\n", "question = \"2024년 노벨 문학상 관련 뉴스를 알려주세요.\"\n", "\n", - "# 초기 입력 State 를 정의\n", + "# 초기 입력 State를 정의\n", "input = State(dummy_data=\"테스트 문자열\", messages=[(\"user\", question)])\n", "\n", - "# config 설정\n", + "# config 설정 (새로운 thread_id 사용)\n", "config = RunnableConfig(\n", - " recursion_limit=10, # 최대 10개의 노드까지 방문. 그 이상은 RecursionError 발생\n", - " configurable={\"thread_id\": \"1\"}, # 스레드 ID 설정\n", - " tags=[\"my-rag\"], # Tag\n", + " recursion_limit=10,\n", + " configurable={\"thread_id\": \"interrupt-after-test\"},\n", ")\n", "\n", - "for event in graph.stream(\n", + "print(\"=== interrupt_after=['tools'] 테스트 ===\\n\")\n", + "print(\"tools 노드 실행 후에 중단됩니다.\\n\")\n", + "\n", + "for event in graph_with_interrupt_after.stream(\n", " input=input,\n", " config=config,\n", " stream_mode=\"updates\",\n", - " interrupt_after=[\"tools\"], # tools 실행 후 interrupt\n", "):\n", - " for value in event.values():\n", - " # key 는 노드 이름\n", + " for key, value in event.items():\n", + " # key는 노드 이름\n", " print(f\"\\n[{key}]\\n\")\n", "\n", + " # value는 노드의 출력값\n", " if isinstance(value, dict):\n", - " # value 는 노드의 출력값\n", - " print(value.keys())\n", + " print(f\"출력 키: {value.keys()}\")\n", " if \"messages\" in value:\n", - " print(value[\"messages\"])\n", + " print(f\"메시지 개수: {len(value['messages'])}\")\n", + " print(\"=\" * 40)\n", "\n", - " # value 에는 state 가 dict 형태로 저장(values 의 key 값)\n", - " if \"messages\" in value:\n", - " print(f\"메시지 개수: {len(value['messages'])}\")" + "# 중단 후 상태 확인\n", + "state = graph_with_interrupt_after.get_state(config)\n", + "print(f\"\\n현재 상태 - 다음 실행 노드: {state.next}\")" ] } ], "metadata": { "kernelspec": { - "display_name": "langchain-kr-lwwSZlnu-py3.11", + "display_name": "langgraph-v1-tutorial", "language": "python", "name": "python3" }, @@ -562,9 +873,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.11.13" } }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/08-Core-Features/assets/chatbot-graph.excalidraw b/08-Core-Features/assets/chatbot-graph.excalidraw new file mode 100644 index 0000000..ad816b0 --- /dev/null +++ b/08-Core-Features/assets/chatbot-graph.excalidraw @@ -0,0 +1,449 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "langgraph-tutorial", + "elements": [ + { + "id": "__start__", + "type": "ellipse", + "x": 200, + "y": 50, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 2 }, + "seed": 1001, + "version": 1, + "versionNonce": 1001, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "__start__-text" }, + { "type": "arrow", "id": "arrow-start-chatbot" } + ], + "updated": 1700000000000, + "link": null, + "locked": false + }, + { + "id": "__start__-text", + "type": "text", + "x": 215, + "y": 62, + "width": 90, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1002, + "version": 1, + "versionNonce": 1002, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "text": "__start__", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "__start__", + "originalText": "__start__", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "chatbot", + "type": "rectangle", + "x": 200, + "y": 160, + "width": 120, + "height": 55, + "angle": 0, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 1003, + "version": 1, + "versionNonce": 1003, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "chatbot-text" }, + { "type": "arrow", "id": "arrow-start-chatbot" }, + { "type": "arrow", "id": "arrow-chatbot-tools" }, + { "type": "arrow", "id": "arrow-chatbot-end" }, + { "type": "arrow", "id": "arrow-tools-chatbot" } + ], + "updated": 1700000000000, + "link": null, + "locked": false + }, + { + "id": "chatbot-text", + "type": "text", + "x": 220, + "y": 175, + "width": 80, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1004, + "version": 1, + "versionNonce": 1004, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "text": "chatbot", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "chatbot", + "originalText": "chatbot", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "tools", + "type": "rectangle", + "x": 80, + "y": 290, + "width": 120, + "height": 55, + "angle": 0, + "strokeColor": "#0d9488", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 1005, + "version": 1, + "versionNonce": 1005, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "tools-text" }, + { "type": "arrow", "id": "arrow-chatbot-tools" }, + { "type": "arrow", "id": "arrow-tools-chatbot" } + ], + "updated": 1700000000000, + "link": null, + "locked": false + }, + { + "id": "tools-text", + "type": "text", + "x": 108, + "y": 305, + "width": 64, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1006, + "version": 1, + "versionNonce": 1006, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "text": "tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "tools", + "originalText": "tools", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "__end__", + "type": "ellipse", + "x": 320, + "y": 290, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 2 }, + "seed": 1007, + "version": 1, + "versionNonce": 1007, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "__end__-text" }, + { "type": "arrow", "id": "arrow-chatbot-end" } + ], + "updated": 1700000000000, + "link": null, + "locked": false + }, + { + "id": "__end__-text", + "type": "text", + "x": 345, + "y": 302, + "width": 70, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1008, + "version": 1, + "versionNonce": 1008, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "text": "__end__", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "__end__", + "originalText": "__end__", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "arrow-start-chatbot", + "type": "arrow", + "x": 260, + "y": 100, + "width": 0, + "height": 60, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1009, + "version": 1, + "versionNonce": 1009, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "points": [[0, 0], [0, 60]], + "startBinding": { "elementId": "__start__", "focus": 0, "gap": 1 }, + "endBinding": { "elementId": "chatbot", "focus": 0, "gap": 1 }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "arrow-chatbot-tools", + "type": "arrow", + "x": 215, + "y": 215, + "width": 75, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1010, + "version": 1, + "versionNonce": 1010, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "points": [[0, 0], [-75, 75]], + "startBinding": { "elementId": "chatbot", "focus": 0, "gap": 1 }, + "endBinding": { "elementId": "tools", "focus": 0, "gap": 1 }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "arrow-chatbot-end", + "type": "arrow", + "x": 305, + "y": 215, + "width": 75, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1011, + "version": 1, + "versionNonce": 1011, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "points": [[0, 0], [75, 75]], + "startBinding": { "elementId": "chatbot", "focus": 0, "gap": 1 }, + "endBinding": { "elementId": "__end__", "focus": 0, "gap": 1 }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "arrow-tools-chatbot", + "type": "arrow", + "x": 185, + "y": 290, + "width": 30, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1012, + "version": 1, + "versionNonce": 1012, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "points": [[0, 0], [30, -75]], + "startBinding": { "elementId": "tools", "focus": 0, "gap": 1 }, + "endBinding": { "elementId": "chatbot", "focus": 0, "gap": 1 }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "condition-label", + "type": "text", + "x": 195, + "y": 250, + "width": 130, + "height": 20, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1013, + "version": 1, + "versionNonce": 1013, + "isDeleted": false, + "boundElements": null, + "updated": 1700000000000, + "link": null, + "locked": false, + "text": "tools_condition", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": null, + "originalText": "tools_condition", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "theme": "light", + "viewBackgroundColor": "#ffffff", + "currentItemFontFamily": 1 + }, + "files": {} +} diff --git a/08-Core-Features/assets/chatbot-graph.png b/08-Core-Features/assets/chatbot-graph.png new file mode 100644 index 0000000..b02ba3b Binary files /dev/null and b/08-Core-Features/assets/chatbot-graph.png differ diff --git a/08-Core-Features/assets/interrupt-flow.excalidraw b/08-Core-Features/assets/interrupt-flow.excalidraw new file mode 100644 index 0000000..b6187e2 --- /dev/null +++ b/08-Core-Features/assets/interrupt-flow.excalidraw @@ -0,0 +1,1163 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "claude-code-excalidraw-skill", + "elements": [ + { + "id": "title", + "type": "text", + "x": 150, + "y": 30, + "width": 500, + "height": 40, + "text": "interrupt_before vs interrupt_after", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "interrupt_before vs interrupt_after", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-section-title", + "type": "text", + "x": 60, + "y": 90, + "width": 450, + "height": 30, + "text": "interrupt_before=[\"tools\"]", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "interrupt_before=[\"tools\"]", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#dc2626", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2, + "version": 1, + "versionNonce": 2, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-chatbot", + "type": "rectangle", + "x": 60, + "y": 135, + "width": 110, + "height": 55, + "angle": 0, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 3, + "version": 1, + "versionNonce": 3, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "before-chatbot-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-chatbot-text", + "type": "text", + "x": 70, + "y": 152, + "width": 90, + "height": 20, + "text": "chatbot", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "before-chatbot", + "originalText": "chatbot", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 4, + "version": 1, + "versionNonce": 4, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-pause", + "type": "rectangle", + "x": 210, + "y": 135, + "width": 90, + "height": 55, + "angle": 0, + "strokeColor": "#dc2626", + "backgroundColor": "#fecaca", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 5, + "version": 1, + "versionNonce": 5, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "before-pause-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-pause-text", + "type": "text", + "x": 220, + "y": 152, + "width": 70, + "height": 20, + "text": "PAUSE", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "before-pause", + "originalText": "PAUSE", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#dc2626", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 6, + "version": 1, + "versionNonce": 6, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-tools", + "type": "rectangle", + "x": 340, + "y": 135, + "width": 90, + "height": 55, + "angle": 0, + "strokeColor": "#0d9488", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 7, + "version": 1, + "versionNonce": 7, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "before-tools-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-tools-text", + "type": "text", + "x": 350, + "y": 152, + "width": 70, + "height": 20, + "text": "tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "before-tools", + "originalText": "tools", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 8, + "version": 1, + "versionNonce": 8, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-chatbot2", + "type": "rectangle", + "x": 470, + "y": 135, + "width": 110, + "height": 55, + "angle": 0, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 9, + "version": 1, + "versionNonce": 9, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "before-chatbot2-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-chatbot2-text", + "type": "text", + "x": 480, + "y": 152, + "width": 90, + "height": 20, + "text": "chatbot", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "before-chatbot2", + "originalText": "chatbot", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 10, + "version": 1, + "versionNonce": 10, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-end", + "type": "ellipse", + "x": 620, + "y": 137, + "width": 70, + "height": 50, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 2 }, + "seed": 11, + "version": 1, + "versionNonce": 11, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "before-end-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-end-text", + "type": "text", + "x": 635, + "y": 152, + "width": 40, + "height": 20, + "text": "END", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "before-end", + "originalText": "END", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 12, + "version": 1, + "versionNonce": 12, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "before-arrow1", + "type": "arrow", + "x": 170, + "y": 163, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 13, + "version": 1, + "versionNonce": 13, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "before-arrow2", + "type": "arrow", + "x": 300, + "y": 163, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#dc2626", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 14, + "version": 1, + "versionNonce": 14, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "before-arrow3", + "type": "arrow", + "x": 430, + "y": 163, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 15, + "version": 1, + "versionNonce": 15, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "before-arrow4", + "type": "arrow", + "x": 580, + "y": 163, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 16, + "version": 1, + "versionNonce": 16, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "before-desc", + "type": "text", + "x": 60, + "y": 205, + "width": 550, + "height": 50, + "text": "Pauses BEFORE tools execution\nUse case: Approve tool execution before running", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Pauses BEFORE tools execution\nUse case: Approve tool execution before running", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 17, + "version": 1, + "versionNonce": 17, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-section-title", + "type": "text", + "x": 60, + "y": 280, + "width": 450, + "height": 30, + "text": "interrupt_after=[\"tools\"]", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "interrupt_after=[\"tools\"]", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#9333ea", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 18, + "version": 1, + "versionNonce": 18, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-chatbot", + "type": "rectangle", + "x": 60, + "y": 325, + "width": 110, + "height": 55, + "angle": 0, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 19, + "version": 1, + "versionNonce": 19, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "after-chatbot-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-chatbot-text", + "type": "text", + "x": 70, + "y": 342, + "width": 90, + "height": 20, + "text": "chatbot", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "after-chatbot", + "originalText": "chatbot", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 20, + "version": 1, + "versionNonce": 20, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-tools", + "type": "rectangle", + "x": 210, + "y": 325, + "width": 90, + "height": 55, + "angle": 0, + "strokeColor": "#0d9488", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 21, + "version": 1, + "versionNonce": 21, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "after-tools-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-tools-text", + "type": "text", + "x": 220, + "y": 342, + "width": 70, + "height": 20, + "text": "tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "after-tools", + "originalText": "tools", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 22, + "version": 1, + "versionNonce": 22, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-pause", + "type": "rectangle", + "x": 340, + "y": 325, + "width": 90, + "height": 55, + "angle": 0, + "strokeColor": "#9333ea", + "backgroundColor": "#e9d5ff", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 23, + "version": 1, + "versionNonce": 23, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "after-pause-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-pause-text", + "type": "text", + "x": 350, + "y": 342, + "width": 70, + "height": 20, + "text": "PAUSE", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "after-pause", + "originalText": "PAUSE", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#9333ea", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 24, + "version": 1, + "versionNonce": 24, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-chatbot2", + "type": "rectangle", + "x": 470, + "y": 325, + "width": 110, + "height": 55, + "angle": 0, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 25, + "version": 1, + "versionNonce": 25, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "after-chatbot2-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-chatbot2-text", + "type": "text", + "x": 480, + "y": 342, + "width": 90, + "height": 20, + "text": "chatbot", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "after-chatbot2", + "originalText": "chatbot", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 26, + "version": 1, + "versionNonce": 26, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-end", + "type": "ellipse", + "x": 620, + "y": 327, + "width": 70, + "height": 50, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 2 }, + "seed": 27, + "version": 1, + "versionNonce": 27, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "after-end-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-end-text", + "type": "text", + "x": 635, + "y": 342, + "width": 40, + "height": 20, + "text": "END", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "after-end", + "originalText": "END", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 28, + "version": 1, + "versionNonce": 28, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "after-arrow1", + "type": "arrow", + "x": 170, + "y": 353, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 29, + "version": 1, + "versionNonce": 29, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "after-arrow2", + "type": "arrow", + "x": 300, + "y": 353, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 30, + "version": 1, + "versionNonce": 30, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "after-arrow3", + "type": "arrow", + "x": 430, + "y": 353, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#9333ea", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 31, + "version": 1, + "versionNonce": 31, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "after-arrow4", + "type": "arrow", + "x": 580, + "y": 353, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 32, + "version": 1, + "versionNonce": 32, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "after-desc", + "type": "text", + "x": 60, + "y": 395, + "width": 550, + "height": 50, + "text": "Pauses AFTER tools execution\nUse case: Review tool results before continuing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Pauses AFTER tools execution\nUse case: Review tool results before continuing", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 33, + "version": 1, + "versionNonce": 33, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "resume-note", + "type": "rectangle", + "x": 60, + "y": 470, + "width": 630, + "height": 55, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 34, + "version": 1, + "versionNonce": 34, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "resume-note-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "resume-note-text", + "type": "text", + "x": 70, + "y": 485, + "width": 610, + "height": 25, + "text": "Resume: graph.invoke(None, config) to continue from paused state", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "resume-note", + "originalText": "Resume: graph.invoke(None, config) to continue from paused state", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 35, + "version": 1, + "versionNonce": 35, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/08-Core-Features/assets/interrupt-flow.png b/08-Core-Features/assets/interrupt-flow.png new file mode 100644 index 0000000..d30e5d5 Binary files /dev/null and b/08-Core-Features/assets/interrupt-flow.png differ diff --git a/08-Core-Features/assets/stream-mode-comparison.excalidraw b/08-Core-Features/assets/stream-mode-comparison.excalidraw new file mode 100644 index 0000000..4fd127c --- /dev/null +++ b/08-Core-Features/assets/stream-mode-comparison.excalidraw @@ -0,0 +1,1010 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "claude-code-excalidraw-skill", + "elements": [ + { + "id": "title", + "type": "text", + "x": 180, + "y": 30, + "width": 350, + "height": 35, + "text": "stream_mode Comparison", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "stream_mode Comparison", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "flow-title", + "type": "text", + "x": 60, + "y": 90, + "width": 200, + "height": 24, + "text": "Graph Execution Flow:", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Graph Execution Flow:", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2, + "version": 1, + "versionNonce": 2, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "start-node", + "type": "ellipse", + "x": 60, + "y": 130, + "width": 80, + "height": 50, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 2 }, + "seed": 3, + "version": 1, + "versionNonce": 3, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "start-node-text" }, + { "type": "arrow", "id": "arrow-start-chatbot1" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "start-node-text", + "type": "text", + "x": 70, + "y": 145, + "width": 60, + "height": 20, + "text": "START", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "start-node", + "originalText": "START", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 4, + "version": 1, + "versionNonce": 4, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "chatbot1-node", + "type": "rectangle", + "x": 180, + "y": 127, + "width": 100, + "height": 55, + "angle": 0, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 5, + "version": 1, + "versionNonce": 5, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "chatbot1-node-text" }, + { "type": "arrow", "id": "arrow-start-chatbot1" }, + { "type": "arrow", "id": "arrow-chatbot1-tools" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "chatbot1-node-text", + "type": "text", + "x": 190, + "y": 145, + "width": 80, + "height": 20, + "text": "chatbot", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "chatbot1-node", + "originalText": "chatbot", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 6, + "version": 1, + "versionNonce": 6, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "tools-node", + "type": "rectangle", + "x": 320, + "y": 127, + "width": 80, + "height": 55, + "angle": 0, + "strokeColor": "#0d9488", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 7, + "version": 1, + "versionNonce": 7, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "tools-node-text" }, + { "type": "arrow", "id": "arrow-chatbot1-tools" }, + { "type": "arrow", "id": "arrow-tools-chatbot2" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "tools-node-text", + "type": "text", + "x": 335, + "y": 145, + "width": 50, + "height": 20, + "text": "tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "tools-node", + "originalText": "tools", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 8, + "version": 1, + "versionNonce": 8, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "chatbot2-node", + "type": "rectangle", + "x": 440, + "y": 127, + "width": 100, + "height": 55, + "angle": 0, + "strokeColor": "#ca8a04", + "backgroundColor": "#fef08a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 9, + "version": 1, + "versionNonce": 9, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "chatbot2-node-text" }, + { "type": "arrow", "id": "arrow-tools-chatbot2" }, + { "type": "arrow", "id": "arrow-chatbot2-end" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "chatbot2-node-text", + "type": "text", + "x": 450, + "y": 145, + "width": 80, + "height": 20, + "text": "chatbot", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "chatbot2-node", + "originalText": "chatbot", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 10, + "version": 1, + "versionNonce": 10, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "end-node", + "type": "ellipse", + "x": 580, + "y": 130, + "width": 70, + "height": 50, + "angle": 0, + "strokeColor": "#6b7280", + "backgroundColor": "#f3f4f6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 2 }, + "seed": 11, + "version": 1, + "versionNonce": 11, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "end-node-text" }, + { "type": "arrow", "id": "arrow-chatbot2-end" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "end-node-text", + "type": "text", + "x": 595, + "y": 145, + "width": 40, + "height": 20, + "text": "END", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "end-node", + "originalText": "END", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 12, + "version": 1, + "versionNonce": 12, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "arrow-start-chatbot1", + "type": "arrow", + "x": 140, + "y": 155, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 13, + "version": 1, + "versionNonce": 13, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "arrow-chatbot1-tools", + "type": "arrow", + "x": 280, + "y": 155, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 14, + "version": 1, + "versionNonce": 14, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "arrow-tools-chatbot2", + "type": "arrow", + "x": 400, + "y": 155, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 15, + "version": 1, + "versionNonce": 15, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "arrow-chatbot2-end", + "type": "arrow", + "x": 540, + "y": 155, + "width": 40, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 16, + "version": 1, + "versionNonce": 16, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [40, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "values-section-title", + "type": "text", + "x": 60, + "y": 220, + "width": 350, + "height": 28, + "text": "stream_mode=\"values\"", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "stream_mode=\"values\"", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#2563eb", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 17, + "version": 1, + "versionNonce": 17, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "values-desc", + "type": "text", + "x": 60, + "y": 250, + "width": 350, + "height": 22, + "text": "Each step outputs FULL State", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Each step outputs FULL State", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 18, + "version": 1, + "versionNonce": 18, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "values-step1-box", + "type": "rectangle", + "x": 60, + "y": 285, + "width": 260, + "height": 100, + "angle": 0, + "strokeColor": "#2563eb", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 19, + "version": 1, + "versionNonce": 19, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "values-step1-box-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "values-step1-box-text", + "type": "text", + "x": 70, + "y": 300, + "width": 240, + "height": 70, + "text": "Step 1: chatbot\nmessages: [user, ai]\ndummy_data: \"...\"", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "values-step1-box", + "originalText": "Step 1: chatbot\nmessages: [user, ai]\ndummy_data: \"...\"", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 20, + "version": 1, + "versionNonce": 20, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "values-step2-box", + "type": "rectangle", + "x": 380, + "y": 285, + "width": 280, + "height": 100, + "angle": 0, + "strokeColor": "#2563eb", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 21, + "version": 1, + "versionNonce": 21, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "values-step2-box-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "values-step2-box-text", + "type": "text", + "x": 390, + "y": 300, + "width": 260, + "height": 70, + "text": "Step 2: tools\nmessages: [user, ai, tool]\ndummy_data: \"...\"", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "values-step2-box", + "originalText": "Step 2: tools\nmessages: [user, ai, tool]\ndummy_data: \"...\"", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 22, + "version": 1, + "versionNonce": 22, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "values-arrow", + "type": "arrow", + "x": 320, + "y": 335, + "width": 60, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 23, + "version": 1, + "versionNonce": 23, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [60, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "updates-section-title", + "type": "text", + "x": 60, + "y": 420, + "width": 450, + "height": 28, + "text": "stream_mode=\"updates\" (default)", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "stream_mode=\"updates\" (default)", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#0d9488", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 24, + "version": 1, + "versionNonce": 24, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "updates-desc", + "type": "text", + "x": 60, + "y": 450, + "width": 450, + "height": 22, + "text": "Each step outputs ONLY changes (node name as key)", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Each step outputs ONLY changes (node name as key)", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 25, + "version": 1, + "versionNonce": 25, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "updates-step1-box", + "type": "rectangle", + "x": 60, + "y": 485, + "width": 260, + "height": 110, + "angle": 0, + "strokeColor": "#0d9488", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 26, + "version": 1, + "versionNonce": 26, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "updates-step1-box-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "updates-step1-box-text", + "type": "text", + "x": 70, + "y": 500, + "width": 240, + "height": 80, + "text": "Step 1:\n{\"chatbot\": {\n messages: [ai_msg]\n}}", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "updates-step1-box", + "originalText": "Step 1:\n{\"chatbot\": {\n messages: [ai_msg]\n}}", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 27, + "version": 1, + "versionNonce": 27, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "updates-step2-box", + "type": "rectangle", + "x": 380, + "y": 485, + "width": 260, + "height": 110, + "angle": 0, + "strokeColor": "#0d9488", + "backgroundColor": "#ccfbf1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { "type": 3 }, + "seed": 28, + "version": 1, + "versionNonce": 28, + "isDeleted": false, + "boundElements": [ + { "type": "text", "id": "updates-step2-box-text" } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "updates-step2-box-text", + "type": "text", + "x": 390, + "y": 500, + "width": 240, + "height": 80, + "text": "Step 2:\n{\"tools\": {\n messages: [tool_msg]\n}}", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "updates-step2-box", + "originalText": "Step 2:\n{\"tools\": {\n messages: [tool_msg]\n}}", + "lineHeight": 1.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 29, + "version": 1, + "versionNonce": 29, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "updates-arrow", + "type": "arrow", + "x": 320, + "y": 540, + "width": 60, + "height": 0, + "angle": 0, + "strokeColor": "#4b5563", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 30, + "version": 1, + "versionNonce": 30, + "isDeleted": false, + "boundElements": null, + "updated": 1, + "link": null, + "locked": false, + "points": [[0, 0], [60, 0]], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/08-Core-Features/assets/stream-mode-comparison.png b/08-Core-Features/assets/stream-mode-comparison.png new file mode 100644 index 0000000..64790ba Binary files /dev/null and b/08-Core-Features/assets/stream-mode-comparison.png differ