diff --git a/.idea/Source.iml b/.idea/Source.iml
index 8b5941e..0adc258 100644
--- a/.idea/Source.iml
+++ b/.idea/Source.iml
@@ -6,7 +6,7 @@
-
+
diff --git a/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/data_level0.bin b/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/data_level0.bin
new file mode 100644
index 0000000..337c499
Binary files /dev/null and b/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/data_level0.bin differ
diff --git a/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/header.bin b/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/header.bin
new file mode 100644
index 0000000..dff34e7
Binary files /dev/null and b/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/header.bin differ
diff --git a/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/length.bin b/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/length.bin
new file mode 100644
index 0000000..a673f74
Binary files /dev/null and b/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/length.bin differ
diff --git a/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/link_lists.bin b/ollama/db/chroma_db/942717e5-bb1d-4ff9-939c-721384e4631f/link_lists.bin
new file mode 100644
index 0000000..e69de29
diff --git a/ollama/db/chroma_db/chroma.sqlite3 b/ollama/db/chroma_db/chroma.sqlite3
new file mode 100644
index 0000000..52f3bee
Binary files /dev/null and b/ollama/db/chroma_db/chroma.sqlite3 differ
diff --git a/ollama/langchain-agent2.ipynb b/ollama/langchain-agent2.ipynb
index a0e19dc..46ced0e 100644
--- a/ollama/langchain-agent2.ipynb
+++ b/ollama/langchain-agent2.ipynb
@@ -6,13 +6,14 @@
"metadata": {
"collapsed": true,
"ExecuteTime": {
- "end_time": "2026-06-05T06:07:18.527729458Z",
- "start_time": "2026-06-05T06:07:18.518264605Z"
+ "end_time": "2026-06-08T01:08:35.194596809Z",
+ "start_time": "2026-06-08T01:08:35.179927221Z"
}
},
"source": [
"import os\n",
"\n",
+ "from cohere.types import summarize_request_extractiveness\n",
"from google.protobuf.timestamp import from_current_time\n",
"from langchain_classic.chains.sequential import SequentialChain\n",
"from langchain_openai import ChatOpenAI\n",
@@ -31,16 +32,18 @@
"from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder\n",
"from langchain_core.output_parsers import StrOutputParser, JsonOutputParser, PydanticOutputParser\n",
"from langchain_core.runnables import RunnablePassthrough, RunnableParallel, RunnableLambda\n",
+ "from langgraph import graph\n",
+ "from pygments.unistring import combine\n",
"from sklearn import pipeline"
],
"outputs": [],
- "execution_count": 6
+ "execution_count": 8
},
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T06:07:18.540816647Z",
- "start_time": "2026-06-05T06:07:18.529482429Z"
+ "end_time": "2026-06-08T01:08:35.209192873Z",
+ "start_time": "2026-06-08T01:08:35.196033782Z"
}
},
"cell_type": "code",
@@ -59,13 +62,13 @@
],
"id": "c2c64c7967231118",
"outputs": [],
- "execution_count": 7
+ "execution_count": 9
},
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T06:07:22.476243454Z",
- "start_time": "2026-06-05T06:07:18.541903550Z"
+ "end_time": "2026-06-08T01:08:38.814068577Z",
+ "start_time": "2026-06-08T01:08:35.210314784Z"
}
},
"cell_type": "code",
@@ -104,13 +107,13 @@
],
"id": "560df17d1a798650",
"outputs": [],
- "execution_count": 8
+ "execution_count": 10
},
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T06:07:22.498652432Z",
- "start_time": "2026-06-05T06:07:22.487631249Z"
+ "end_time": "2026-06-08T01:08:38.836015704Z",
+ "start_time": "2026-06-08T01:08:38.823821493Z"
}
},
"cell_type": "code",
@@ -128,7 +131,7 @@
],
"id": "abfd826c1b3e2703",
"outputs": [],
- "execution_count": 9
+ "execution_count": 11
},
{
"metadata": {},
@@ -147,8 +150,8 @@
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T06:46:44.553396205Z",
- "start_time": "2026-06-05T06:46:44.544122014Z"
+ "end_time": "2026-06-08T01:08:38.851746574Z",
+ "start_time": "2026-06-08T01:08:38.839267469Z"
}
},
"cell_type": "code",
@@ -192,13 +195,13 @@
],
"id": "1e18698dde2e75b5",
"outputs": [],
- "execution_count": 13
+ "execution_count": 12
},
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T06:53:39.704503264Z",
- "start_time": "2026-06-05T06:52:54.753636702Z"
+ "end_time": "2026-06-08T01:09:39.818869100Z",
+ "start_time": "2026-06-08T01:08:38.852667199Z"
}
},
"cell_type": "code",
@@ -225,23 +228,67 @@
"\n",
"a, b, c의 변을 가진 직각삼각형이 있다고 가정합니다. 삼각형 안에 변의 길이가 a, b, c인 정사각형을 그립니다. 다음은 그 증명입니다:\n",
"\n",
- "1. 삼각형 안에 변의 길이가 a, b, c인 정사각형의 넓이는 a^2 + b^2입니다.\n",
+ "1. 삼각형 안에 변의 길이가 a, b, c인 정사각형의 넓이는 a^2 + b^2 + 2ab입니다.\n",
"\n",
- "2. 삼각형을 두 개의 작은 직각삼각형으로 나누기 위해 빗변(c)에 수직선을 그립니다.\n",
+ "2. 삼각형의 넓이는 (1/2)ab입니다.\n",
"\n",
- "3. 수직선이 전에 있었던 빗변의 길이가 c인 두 개의 작은 직각삼각형의 넓이는 각각 (a^2)와 (b^2)입니다.\n",
+ "3. 삼각형의 넓이가 정사각형의 넓이의 절반과 같다는 것은 다음과 같습니다:\n",
"\n",
- "4. 전체 삼각형의 넓이는 작은 직각삼각형의 넓이의 합이어야 합니다. 따라서, 삼각형의 넓이는 (a^2) + (b^2)입니다.\n",
+ "a^2 + b^2 + 2ab = (1/2)(a^2 + b^2)\n",
"\n",
- "5. 삼각형이 직각삼각형이므로, 빗변의 길이는 c입니다. 따라서, 삼각형의 넓이는 (1/2)c^2입니다.\n",
+ "4. 양변에 2를 곱하면 다음과 같습니다:\n",
"\n",
- "6. 넓이가 같다는 것을 결론지을 수 있으므로, (a^2) + (b^2) = (1/2)c^2입니다.\n",
+ "2a^2 + 2b^2 + 4ab = a^2 + b^2\n",
"\n",
- "7. 양변에 2를 곱하면 a^2 + b^2 = c^2가 됩니다.\n",
+ "5. 양변에서 a^2 + b^2를 빼면 다음과 같습니다:\n",
"\n",
- "8. 이것이 피타고라스의 정리입니다.\n",
+ "a^2 + b^2 = 2ab\n",
"\n",
- "따라서, 피타고라스의 정리는 직각삼각형의 빗변의 길이의 제곱이 다른 두 변의 길이의 제곱의 합과 같다는 것을 보여줍니다.\n",
+ "6. 양변에 c를 곱하면 다음과 같습니다:\n",
+ "\n",
+ "c(a^2 + b^2) = 2abc\n",
+ "\n",
+ "7. 왼쪽 항의 c를 분배하면 다음과 같습니다:\n",
+ "\n",
+ "ca^2 + cb^2 = 2abc\n",
+ "\n",
+ "8. 양변에서 2abc를 빼면 다음과 같습니다:\n",
+ "\n",
+ "ca^2 + cb^2 - 2abc = 0\n",
+ "\n",
+ "9. 왼쪽 항에서 c를 공약으로 빼면 다음과 같습니다:\n",
+ "\n",
+ "c(a^2 + b^2 - 2ab) = 0\n",
+ "\n",
+ "10. 양변을 c로 나누면 다음과 같습니다:\n",
+ "\n",
+ "a^2 + b^2 - 2ab = 0\n",
+ "\n",
+ "11. 양변에 2ab를 더하면 다음과 같습니다:\n",
+ "\n",
+ "a^2 + b^2 = 2ab\n",
+ "\n",
+ "12. 양변에 c를 곱하면 다음과 같습니다:\n",
+ "\n",
+ "c(a^2 + b^2) = 2abc\n",
+ "\n",
+ "13. 왼쪽 항의 c를 분배하면 다음과 같습니다:\n",
+ "\n",
+ "ca^2 + cb^2 = 2abc\n",
+ "\n",
+ "14. 양변에서 2abc를 빼면 다음과 같습니다:\n",
+ "\n",
+ "ca^2 + cb^2 - 2abc = 0\n",
+ "\n",
+ "15. 왼쪽 항에서 c를 공약으로 빼면 다음과 같습니다:\n",
+ "\n",
+ "c(a^2 + b^2 - 2ab) = 0\n",
+ "\n",
+ "16. 양변을 c로 나누면 다음과 같습니다:\n",
+ "\n",
+ "a^2 + b^2 = 2ab\n",
+ "\n",
+ "17. 이것은 증명이 완료되었습니다.\n",
"A: 피타고라스 정리는 직각삼각형에서 빗변의 길이의 제곱이 나머지 두 변의 길이의 제곱의 합과 같다는 정리입니다. 이 정리를 증명해 드리겠습니다.\n",
"\n",
"가정: 직각삼각형 ABC에서 각 A가...\n",
@@ -249,7 +296,7 @@
"Q: 오늘 저녁 메뉴 추천해줘\n",
" => 븐류결과 질문 유형: general\n",
"\n",
- "오늘 저녁 메뉴로는 간단하면서도 맛있는 \"치킨과 샐러드\"를 추천해드릴게요. 치킨은 구운 닭고기를 사용하고, 샐러드는 신선한 채소와 드레싱을 곁들여서 영양가 있고 가벼운 식사를 즐길 수 있습니다. 또한, 필요에 따라 사이드 디시로 감자 샐러드나 빵을 추가할 수도 있습니다. 이 메뉴는 준비하기도 쉽고, 가족이나 친구들과 함께 즐기기에 좋은 선택이 될 것 같아요.\n",
+ "오늘 저녁 메뉴로는 간단하면서도 맛있는 \"치킨과 샐러드\"를 추천해드릴게요. 치킨은 구운 닭고기를 사용하고, 샐러드는 신선한 채소와 드레싱을 곁들여서 영양가 있고 가벼운 식사를 즐길 수 있습니다. 또한, 사이드로 감자튀김이나 빵을 추가하면 더욱 만족스러운 식사가 될 거예요.\n",
"A: 저녁 메뉴로는 다양한 선택지가 있습니다. 건강하고 맛있는 한국 요리를 추천해 드리겠습니다.\n",
"\n",
"1. 비빔밥: 밥에 다양한 채소와 고기를 넣고 고추장 소스를 섞어 먹는 한국의 대표적인...\n",
@@ -277,50 +324,43 @@
"\n",
"3. 외부 루프 `for i in range(n - 1)`는 배열을 `n - 1`번 반복하며, 각 반복에서 가장 큰 원소를 배열의 끝쪽으로 이동시킵니다.\n",
"\n",
- "4. 내부 루프 `for j in range(n - i - 1)`는 현재 루프에서 비교해야 할 인접한 원소 쌍을 탐색합니다. `n - i - 1`은 이미 정렬된 원소를 제외한 나머지 원소들을 비교하기 위해 사용됩니다.\n",
+ "4. 내부 루프 `for j in range(n - i - 1)`는 현재 루프에서 비교해야 할 원소의 개수만큼 반복합니다. `i`가 증가함에 따라 이미 정렬된 원소의 개수만큼 내부 루프의 범위가 줄어듭니다.\n",
"\n",
- "5. `if arr[j] > arr[j + 1]` 조건문은 현재 원소와 다음 원소를 비교하여, 현재 원소가 다음 원소보다 크다면 두 원소를 교환합니다. 이 과정을 통해 큰 원소가 뒤로 이동하게 됩니다.\n",
+ "5. 내부 루프 내에서 현재 원소 `arr[j]`와 다음 원소 `arr[j + 1]`를 비교합니다. 만약 현재 원소가 다음 원소보다 크다면, 두 원소를 서로 교환합니다.\n",
"\n",
- "6. 모든 루프가 완료되면 정렬된 배열을 반환합니다.\n",
+ "6. 외부 루프가 모두 완료되면 정렬된 배열을 반환합니다.\n",
"\n",
"이 함수를 사용하는 예제는 다음과 같습니다.\n",
"\n",
"```python\n",
- "# 예제 사용\n",
"arr = [64, 34, 25, 12, 22, 11, 90]\n",
- "print(\"정렬 전:\", arr)\n",
- "\n",
"sorted_arr = bubble_sort(arr)\n",
- "print(\"정렬 후:\", sorted_arr)\n",
+ "print(sorted_arr)\n",
"```\n",
"\n",
- "출력 결과는 다음과 같습니다.\n",
- "\n",
+ "출력 결과:\n",
"```\n",
- "정렬 전: [64, 34, 25, 12, 22, 11, 90]\n",
- "정렬 후: [11, 12, 22, 25, 34, 64, 90]\n",
+ "[11, 12, 22, 25, 34, 64, 90]\n",
"```\n",
"\n",
- "버블 정렬은 간단하고 직관적인 정렬 알고리즘이지만, 시간 복잡도가 o(n^2)로 비효율적인 편입니다. 대규모 데이터에 대해서는 다른 정렬 알고리즘을 고려하는 것이 좋습니다.\n",
- "A: 물론입니다! 파이썬으로 버블 정렬을 구현해드리겠습니다. 버블 정렬은 인접한 두 원소를 비교하며 정렬하는 간단한 정렬 알고리즘입니다. 다음은 파이썬 코드입니다:\n",
- "```python\n",
- "d...\n",
+ "버블 정렬은 인접한 두 원소를 비교하며 정렬하는 간단한 알고리즘입니다. 시간 복잡도는 o(n^2)로, 큰 규모의 데이터에는 적합하지 않습니다. 그러나 구현이 간단하고 이해하기 쉬워 교육용으로 자주 사용됩니다.\n",
+ "A: 물론입니다! 파이썬으로 버블 정렬을 구현해드리겠습니다. 버블 정렬은 인접한 두 원소를 비교하고, 순서가 바르지 않으면 서로 교환하는 정렬 알고리즘입니다. 다음은 파이썬 코드입니다:...\n",
"\n",
"Q: 피타고라스 정리를 코드로 짜줘\n",
" => 븐류결과 피타고라스 정리를 코드로 구현하려면, 먼저 피타고라스 정리가 무엇인지 이해해야 합니다. 피타고라스 정리는 직각삼각형에서 빗변의 길이의 제곱이 나머지 두 변의 길이의 제곱의 합과 같다는 정리입니다. 수식으로 표현하면 다음과 같습니다.\n",
"\n",
- "```\n",
- "c² = a² + b²\n",
- "```\n",
+ "c^2 = a^2 + b^2\n",
"\n",
"여기서 c는 빗변의 길이이고, a와 b는 나머지 두 변의 길이입니다.\n",
"\n",
- "이제 이 정리를 코드로 구현해 보겠습니다. 여기서는 python을 사용하여 구현하겠습니다.\n",
+ "이제 이 정리를 코드로 구현해 보겠습니다. 여기서는 python을 사용하겠습니다.\n",
"\n",
"```python\n",
+ "import math\n",
+ "\n",
"def pythagorean_theorem(a, b):\n",
" c_squared = a**2 + b**2\n",
- " c = c_squared ** 0.5\n",
+ " c = math.sqrt(c_squared)\n",
" return c\n",
"\n",
"# 예시 사용\n",
@@ -330,16 +370,17 @@
"print(f\"빗변의 길이는: {c}\")\n",
"```\n",
"\n",
- "이 코드에서는 `pythagorean_theorem`이라는 함수를 정의하고, 두 개의 인자 `a`와 `b`를 받습니다. 이 함수는 피타고라스 정리를 사용하여 빗변의 길이 `c`를 계산하고 반환합니다. 예시 사용에서는 `a`와 `b`에 각각 3과 4를 할당하고, `pythagorean_theorem` 함수를 호출하여 빗변의 길이를 계산한 후 출력합니다.\n",
- "A: 피타고라스 정리를 코드로 작성하려면, 파이썬을 사용하여 다음과 같이 작성할 수 있습니다.\n",
- "```python\n",
- "def pythagorean_theorem(a, b):\n",
- " c = (...\n",
+ "이 코드에서는 `pythagorean_theorem`이라는 함수를 정의하고, 두 개의 인자 `a`와 `b`를 받습니다. 이 함수는 피타고라스 정리를 사용하여 빗변의 길이 `c`를 계산하고 반환합니다. 마지막으로, 예시 사용 부분에서 `a`와 `b`의 값을 설정하고, `pythagorean_theorem` 함수를 호출하여 빗변의 길이를 계산하고 출력합니다.\n",
+ "A: 피타고라스 정리는 직각삼각형에서 빗변의 길이의 제곱이 나머지 두 변의 길이의 제곱의 합과 같다는 정리입니다. 수식으로 표현하면 다음과 같습니다:\n",
+ "\n",
+ "a^2 + b^2 = c^2\n",
+ "\n",
+ "여...\n",
"\n"
]
}
],
- "execution_count": 19
+ "execution_count": 13
},
{
"metadata": {},
@@ -350,8 +391,8 @@
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T07:11:06.032073974Z",
- "start_time": "2026-06-05T07:10:36.542912312Z"
+ "end_time": "2026-06-08T01:10:01.596120962Z",
+ "start_time": "2026-06-08T01:09:39.839322931Z"
}
},
"cell_type": "code",
@@ -373,7 +414,7 @@
"print(branch.invoke({'topic':'math', 'question':'미분이란?'}))\n",
"print(branch.invoke({'topic':'code', 'question':'미적분 코드 구현'}))\n",
"print(branch.invoke({'topic':'cooking', 'question':'김치찌개 레시피?'}))\n",
- "print(branch.invoke({'topic':'other', 'question':'안녕하세요?'}))\n"
+ "print(branch.invoke({'topic':'other', 'question':'안녕하세요?'}))"
],
"id": "7e5d342621ece0b0",
"outputs": [
@@ -385,21 +426,25 @@
"\n",
"미분의 과정을 단계별로 설명하겠습니다.\n",
"\n",
- "1. 함수 f(x)를 주어진다고 가정합니다.\n",
- "2. 미분을 구하기 위해 독립변수 x에 대한 미소변화량을 나타내는 기호 'dx'를 사용합니다. 이때, 함수 값의 미소변화량을 'dy'로 나타냅니다.\n",
- "3. 함수 f(x)의 미분은 다음과 같이 정의됩니다: dy/dx = f'(x) = lim(h→0) (f(x+h) - f(x))/h\n",
- "4. 이 식에서 lim(h→0)은 h가 0에 가까워질 때의 극한을 의미합니다. 이 극한을 구하면 함수 f(x)의 미분, 즉 도함수 f'(x)를 얻을 수 있습니다.\n",
- "5. 도함수 f'(x)는 함수 f(x)의 그래프에서 접선의 기울기를 나타냅니다. 이를 통해 함수의 변화율을 파악할 수 있습니다.\n",
+ "1. 함수 정의: 먼저 미분하고자 하는 함수 f(x)를 정의합니다. 예를 들어, f(x) = x^2 + 3x + 1과 같은 함수를 생각해보겠습니다.\n",
+ "2. 미분 계수 정의: 미분 계수는 함수 f(x)의 변화율을 나타내는 값으로, 기호로는 f'(x) 또는 df/dx로 표기합니다. 미분 계수는 다음과 같이 정의됩니다.\n",
"\n",
- "예를 들어, 함수 f(x) = x^2를 미분해보겠습니다.\n",
+ "f'(x) = lim(h→0) [(f(x + h) - f(x)) / h]\n",
"\n",
- "1. f(x) = x^2\n",
- "2. dy/dx = f'(x) = lim(h→0) (f(x+h) - f(x))/h\n",
- "3. f(x+h) = (x+h)^2 = x^2 + 2xh + h^2\n",
- "4. f'(x) = lim(h→0) ((x^2 + 2xh + h^2) - x^2)/h = lim(h→0) (2xh + h^2)/h\n",
- "5. h를 제외한 항으로 나누면 f'(x) = lim(h→0) (2x + h) = 2x\n",
+ "여기서 h는 아주 작은 양수 값입니다.\n",
+ "3. 미분 계수 계산: 미분 계수를 구하기 위해 위 식에 함수 f(x)를 대입하고, h가 0에 가까워질 때의 극한 값을 계산합니다. 위의 예시에서 f(x) = x^2 + 3x + 1이므로, 미분 계수를 구하는 과정은 다음과 같습니다.\n",
"\n",
- "따라서, 함수 f(x) = x^2의 미분은 f'(x) = 2x입니다. 이 도함수는 함수의 그래프에서 접선의 기울기를 나타내며, 함수의 변화율을 파악하는 데 사용됩니다.\n",
+ "f'(x) = lim(h→0) [((x + h)^2 + 3(x + h) + 1 - (x^2 + 3x + 1)) / h]\n",
+ "= lim(h→0) [(x^2 + 2xh + h^2 + 3x + 3h + 1 - x^2 - 3x - 1) / h]\n",
+ "= lim(h→0) [(2xh + h^2 + 3h) / h]\n",
+ "= lim(h→0) [2x + h + 3]\n",
+ "\n",
+ "h가 0에 가까워지면, h^2와 h 항은 무시할 수 있으므로, 미분 계수는 다음과 같이 구해집니다.\n",
+ "\n",
+ "f'(x) = 2x + 3\n",
+ "4. 해석: 미분 계수 f'(x) = 2x + 3은 함수 f(x) = x^2 + 3x + 1의 변화율을 나타냅니다. 이를 통해 함수의 그래프에서 특정 지점에서의 접선의 기울기를 알 수 있습니다. 예를 들어, x = 1일 때의 미분 계수는 f'(1) = 2(1) + 3 = 5이므로, 그 지점에서의 접선의 기울기는 5입니다.\n",
+ "\n",
+ "이와 같이 미분은 함수의 변화율을 계산하는 과정으로, 함수의 그래프에서 접선의 기울기를 구하는 것이 주요 목적입니다. 미분을 통해 함수의 특성을 분석하고, 최적화 문제를 해결하는 데 활용할 수 있습니다.\n",
"미적분 코드를 구현하려면, 파이썬의 `sympy` 라이브러리를 사용할 수 있습니다. 이 라이브러리는 기호 수학을 다루는 데 사용되며, 미적분 계산에도 적합합니다. 아래는 파이썬 코드와 주석을 함께 제공한 미적분 코드 구현 예시입니다.\n",
"```python\n",
"# 필요한 라이브러리를 임포트합니다.\n",
@@ -409,7 +454,7 @@
"x = sp.symbols('x')\n",
"\n",
"# 함수를 정의합니다.\n",
- "f = x**3 - 3*x**2 + 2*x\n",
+ "f = x**3 + 2*x**2 - 5*x + 1\n",
"\n",
"# 함수의 도함수를 계산합니다.\n",
"f_prime = sp.diff(f, x)\n",
@@ -426,54 +471,56 @@
"\n",
"1. `sympy` 라이브러리를 임포트합니다.\n",
"2. 기호 변수 `x`를 정의합니다.\n",
- "3. 함수 `f`를 정의합니다. 이 예시에서는 `f(x) = x^3 - 3x^2 + 2x`입니다.\n",
+ "3. 함수 `f`를 정의합니다. 이 예시에서는 `f(x) = x^3 + 2x^2 - 5x + 1`입니다.\n",
"4. `sp.diff()` 함수를 사용하여 함수 `f`의 도함수 `f_prime`를 계산합니다.\n",
"5. `sp.integrate()` 함수를 사용하여 함수 `f`의 적분 `f_integral`을 계산합니다.\n",
"6. 결과를 출력합니다.\n",
"\n",
- "이 코드를 실행하면 다음과 같은 결과가 나옵니다.\n",
+ "이 코드를 실행하면 다음과 같은 결과가 출력됩니다.\n",
"```yaml\n",
- "함수: x**3 - 3*x**2 + 2*x\n",
- "도함수: 3*x**2 - 6*x + 2\n",
- "적분: x**4/4 - x**3 + x**2\n",
+ "함수: x**3 + 2*x**2 - 5*x + 1\n",
+ "도함수: 3*x**2 + 4*x - 5\n",
+ "적분: x**4/4 + 2*x**3/3 - 5*x**2/2 + x\n",
"```\n",
- "이 예시는 파이썬과 `sympy` 라이브러리를 사용하여 미적분 계산을 수행하는 기본적인 방법을 보여줍니다. 더 복잡한 미적분 문제를 해결하려면, `sympy` 라이브러리의 다양한 기능을 활용할 수 있습니다.\n",
+ "이 예시는 파이썬과 `sympy` 라이브러리를 사용하여 미적분 계산을 수행하는 기본적인 방법을 보여줍니다. 실제로는 더 복잡한 함수와 계산을 다루어야 할 수도 있습니다.\n",
"김치찌개 레시피를 알려드리겠습니다. 기본적인 재료와 조리법을 소개하겠습니다.\n",
"\n",
"재료:\n",
"\n",
"* 김치 2컵\n",
- "* 돼지고기 목살 200g (또는 소고기 대신 사용 가능)\n",
+ "* 돼지고기 목살 200g (선택 사항)\n",
"* 두부 1/2모\n",
- "* 양파 1개\n",
+ "* 양파 1/2개\n",
"* 대파 1대\n",
"* 마늘 3쪽\n",
"* 고추장 1큰술\n",
"* 고춧가루 1큰술 (선택 사항)\n",
- "* 된장 1작은술\n",
"* 물 2컵\n",
- "* 참기름 약간\n",
+ "* 참기름 1큰술\n",
"* 소금 약간\n",
- "* 찹쌀떡 (선택 사항)\n",
+ "* 찹쌀가루 또는 전분 1큰술 (선택 사항)\n",
"\n",
"조리법:\n",
"\n",
- "1. 돼지고기를 적당한 크기로 썰어 물에 헹궈 물기를 제거한 후, 소금 약간을 뿌려 두세요.\n",
- "2. 양파와 대파를 적당한 크기로 썰어 두세요. 마늘은 다진 후 준비합니다.\n",
- "3. 냄비에 물을 넣고 끓인 후, 고기를 넣어 익을 때까지 끓입니다.\n",
- "4. 고기가 익으면 김치를 넣고 함께 끓입니다. 김치가 익으면 두부를 넣고 끓입니다.\n",
- "5. 고추장, 고춧가루, 된장을 넣어 간을 맞춥니다. 고추가루를 넣지 않고 맵지 않은 김치찌개를 원한다면 생략할 수 있습니다.\n",
- "6. 양파, 대파, 마늘을 넣고 끓여줍니다. 소금으로 간을 맞춥니다.\n",
- "7. 끓는 김치찌개에 참기름을 넣어 향을 더해줍니다.\n",
- "8. 찹쌀떡을 넣고 싶다면 이 단계에서 넣어 끓여주세요.\n",
- "9. 김치찌개가 끓으면 불을 끄고 대파를 다시 잘게 썰어 뿌려 준비가 완료됩니다.\n",
+ "1. 돼지고기를 적당한 크기로 썰어 물에 헹궈 놓습니다. (선택 사항)\n",
+ "2. 양파와 대파를 적당한 크기로 썰어 놓습니다.\n",
+ "3. 마늘을 다져 놓습니다.\n",
+ "4. 냄비에 물을 넣고 끓입니다.\n",
+ "5. 끓는 물에 김치를 넣고 함께 끓입니다.\n",
+ "6. 김치가 물에 잘 녹아들면 돼지고기를 넣고 익을 때까지 끓입니다. (선택 사항)\n",
+ "7. 두부를 넣고 끓입니다.\n",
+ "8. 고추장, 고춧가루, 참기름, 소금을 넣고 간을 맞춥니다.\n",
+ "9. 양파와 대파를 넣고 함께 끓입니다.\n",
+ "10. 마늘을 넣고 5분 정도 더 끓입니다.\n",
+ "11. (선택 사항) 찹쌀가루 또는 전분을 물에 타서 김치찌개에 넣고 끓여 끈적한 진하게 만듭니다.\n",
+ "12. 김치찌개가 완성되면 불을 끄고 대파를 다시 썰어 장식합니다.\n",
"\n",
- "이제 맛있는 김치찌개가 완성되었습니다. 밥과 함께 드시면 더욱 맛있습니다. 맛있게 드세요!\n",
+ "이제 김치찌개가 완성되었습니다. 밥과 함께 드시면 맛있습니다. 맛있게 드세요!\n",
"안녕하세요! 어떻게 도와드릴까요?\n"
]
}
],
- "execution_count": 20
+ "execution_count": 14
},
{
"metadata": {},
@@ -491,8 +538,8 @@
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T07:53:53.154055958Z",
- "start_time": "2026-06-05T07:53:48.801194749Z"
+ "end_time": "2026-06-08T01:10:04.773900667Z",
+ "start_time": "2026-06-08T01:10:01.609764299Z"
}
},
"cell_type": "code",
@@ -545,13 +592,13 @@
]
}
],
- "execution_count": 25
+ "execution_count": 15
},
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T08:09:12.487615584Z",
- "start_time": "2026-06-05T08:09:07.590760841Z"
+ "end_time": "2026-06-08T01:10:21.181364706Z",
+ "start_time": "2026-06-08T01:10:04.790041636Z"
}
},
"cell_type": "code",
@@ -594,7 +641,7 @@
]
}
],
- "execution_count": 35
+ "execution_count": 16
},
{
"metadata": {},
@@ -609,8 +656,8 @@
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T08:41:41.441802415Z",
- "start_time": "2026-06-05T08:41:21.259025964Z"
+ "end_time": "2026-06-08T01:10:42.023077203Z",
+ "start_time": "2026-06-08T01:10:21.201615707Z"
}
},
"cell_type": "code",
@@ -656,24 +703,220 @@
"text": [
"총 청크 수 41\n",
"총 정크 수 41 개 요약 생성\n",
- "삼성E&A는 2026년 대학생 인턴 모집에서 기술직(설계/조달)과 기술직(사업관리/시공관리/품질) 두 가지 직군을 운영하며, 주요 전공 분야로는 화학/화공, 기계, 재료/금속, 전기전자(HW), 토목, 건축을 포함한다. 기술직(설계/조달)은 프로젝트의 최적화 설계와 기자재 구매, 공정 관리 등을 담당하고, 기술직(사업관리/시공관리/품질)은 프로젝트 종료 단계의 수행 관리와 품질 보증, 안전 관리를 수행한다. 삼성E&A는 석유화학, 산업·환경, 바이오 의약품, 재생에너지와 수전해 기술 등 다양한 사업 분야에서 환경 기술과 지속 가능한 솔루션을 제공하며, 신입사원들은 각 분야에서 전문성을 쌓아 사업 관리, 기술 전문가, 프로젝트 관리자 등으로 성장할 수 있는 기회를 제공한다.\n"
+ "삼성E&A는 2026년 대학생 인턴 모집에서 기술직(설계/조달)과 기술직(사업관리/시공관리/품질) 두 가지 직군을 운영하며, 주요 전공 분야로는 화학/화공, 기계, 재료/금속, 전기전자(HW), 토목, 건축을 포함합니다. 기술직(설계/조달)은 프로젝트의 최적화 설계와 기자재 구매, 공정 관리 등을 담당하고, 기술직(사업관리/시공관리/품질)은 프로젝트 종료 단계의 수행 관리와 품질 보증/관리를 수행합니다. 삼성E&A는 석유화학, 산업·환경, 바이오 의약품, 재생에너지와 수전해 기술 등 다양한 사업 분야에서 글로벌 수준의 엔지니어링 솔루션을 제공하며, 안전보건, 경영지원, 인사관리 등 다양한 분야에서도 전문적인 역할을 수행합니다.\n"
]
}
],
- "execution_count": 44
+ "execution_count": 17
},
{
"metadata": {
"ExecuteTime": {
- "end_time": "2026-06-05T08:21:18.906741845Z",
- "start_time": "2026-06-05T08:21:18.888749584Z"
+ "end_time": "2026-06-08T01:39:06.093531606Z",
+ "start_time": "2026-06-08T01:38:16.022293710Z"
}
},
"cell_type": "code",
- "source": "",
- "id": "a82774f4d34a1306",
- "outputs": [],
- "execution_count": 41
+ "source": [
+ "# map_reduce + QA\n",
+ "\n",
+ "parser = StrOutputParser()\n",
+ "\n",
+ "# 질문과 관련된 정보 추출\n",
+ "extract_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트에서 질문과 관련된 정보만 추출하세요. 없으면 '없음'으로 답하세요\"),\n",
+ " (\"user\", \"질문 : {question}\\n\\n텍스트:\\n{chunk}\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "answer_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 추출된 정보를 바탕으로 질문에 답하세요.\"),\n",
+ " (\"user\", \"질문 : {question}\\n\\n추출 정보:\\n{extracts}\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "chunks = None\n",
+ "\n",
+ "def extract_chunks(file_path):\n",
+ " global chunks\n",
+ "\n",
+ " loader = PyPDFLoader(file_path)\n",
+ " splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)\n",
+ " chunks = splitter.split_documents(loader.load())\n",
+ " print(f\"총 청크 수 {len(chunks)}\")\n",
+ "\n",
+ "def map_reduce_qa(chunks, question):\n",
+ " # Map\n",
+ " inputs = [{'question':question, 'chunk':c.page_content} for c in chunks]\n",
+ " extracts = extract_chain.batch(inputs, config={\"max_concurrency\":5})\n",
+ "\n",
+ " # '없음' 으로 넘어온 청크 필터링\n",
+ " relevant = [e for e in extracts if '없음' not in e]\n",
+ " print(f\"관련 청크 : {len(relevant)} / {len(chunks)}\")\n",
+ "\n",
+ " # Reduce\n",
+ " combined = \"\\n\\n\".join(relevant)\n",
+ " return answer_chain.invoke({'question': question, 'extracts': combined})\n",
+ "\n",
+ "extract_chunks('./data/직무기술서/2026 상 삼성E&A 직무기술서.pdf')\n",
+ "result = map_reduce_qa(chunks, '삼성 E&A Career Visition은 뭐야?')\n",
+ "print(result)"
+ ],
+ "id": "1ff7884243daec14",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "총 청크 수 41\n",
+ "관련 청크 : 18 / 41\n",
+ "삼성 E&A Career Visition은 다양한 분야에서 전문가로 성장할 수 있는 기회를 제공하는 프로그램입니다. 이 프로그램은 기술직으로 설계, 조달, 사업관리, 시공관리, 품질 분야 등 다양한 분야를 포함하고 있으며, 주요 전공으로는 화학/화공, 기계, 재료/금속, 전기전자(HW), 토목, 건축 등이 있습니다. 각 분야에서는 다음과 같은 역할과 성장 기회를 제공합니다:\n",
+ "\n",
+ "1. **설계 분야**: 기본설계부터 상세설계까지 프로젝트 목적과 규모, 사양에 따른 공종별 최적화 설계를 수행합니다.\n",
+ "2. **조달 분야**: 주요 기자재 구매, 공정관리, 검사, 물류ㆍ통관업무를 수행합니다.\n",
+ "3. **사업관리 분야**: 계약·설계·조달·시공 등 전 분야에 걸쳐 프로젝트의 모든 과정을 계획하고 최종 목표를 달성할 수 있도록 관리합니다.\n",
+ "4. **공사 분야**: 시공관리와 프로젝트 종료단계(Mechanical) 업무를 수행합니다.\n",
+ "\n",
+ "이 프로그램은 설계/공사/조달/사업 간 직무 순환을 통한 사업 관리 등 전문가로 성장할 수 있는 기회도 제공합니다. 또한, 고도의 직무 전문성을 키워 해당 분야의 기술전문가(Expert)로 성장하거나, 다양한 프로젝트 수행 경험을 축적하여 EM(Engineering Manager) 및 PM(Project Manager)으로 성장할 수 있는 경로를 제공합니다.\n",
+ "\n",
+ "각 분야의 구체적인 역할과 성장 기회는 다음과 같습니다:\n",
+ "\n",
+ "- **설계 분야**: Piping Design/Material/Stress 관련 기술 전문가, Layout 전문가, Lead Engineer 및 기술 전문가(Expert)로 성장.\n",
+ "- **전기 설계**: 전기설계 Lead Engineer로 성장, Plant 전기설계 전문가로 발돋움.\n",
+ "- **토목 분야**: 건축설계, 배관, 기계, 전기, 제어 장비를 지지하기 위한 구조물 설계 및 빌딩 설계를 수행.\n",
+ "- **조달 분야**: 조달, 플랜트, 기자재의 가격, 납기, 품질 등 다양한 요소를 고려하여 최적의 가격으로 구매하고, 적기에 자재를 확보, 납품하기 위한 공정관리, 검사, 운송 등의 업무를 수행.\n",
+ "- **품질 관리 분야**: 프로젝트 품질 관리 Specialist로 성장, 고객 및 관련 부서와 원활한 소통으로 협업 가능, 신기술과 새로운 혁신 과제를 적극적으로 개발하고 적용하고자 하는 탐구 정신을 가진 분.\n",
+ "- **안전보건 분야**: 안전관리와 안전보건경영시스템 구축 및 운영, 현장안전점검/HSE Audit/지도지원, 사고예방관리, 리더십 교육, 안전체험교육 및 법정교육 등 안전전보건 교육, 중장비/전기/구조 등 기술지원, 프로젝트의 사고 예방 계획 및 재발방지대책 수립/운영, 중대재해 처벌법 및 산업안전보건법 등 관련 법규에 따른 Risk 관리 및 대응체계 운영 등의 업무를 수행.\n",
+ "\n",
+ "이 프로그램은 각 분야에서 전문가로 성장할 수 있는 기회를 제공하며, 다양한 경험을 통해 사업관리 등 전문가로 성장할 수 있는 기회도 제공합니다.\n"
+ ]
+ }
+ ],
+ "execution_count": 23
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### ParallelChain (비동기 병렬 처리)\n",
+ "- RunnableParallel : 여러 체인을 동시에 실행하고 결과를 dict 결홥\n",
+ "- 서로 독립립적인 작업을 동시에 처리해 시간을 절약\n",
+ "- batch()\n",
+ "- 독립적인 LLM 호출이 3개였고 순차 호출이 30초가 걸렸다면 병렬 10초로 단축\n"
+ ],
+ "id": "a766990e45af368d"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T02:01:26.851581073Z",
+ "start_time": "2026-06-08T02:01:22.279822071Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "import time\n",
+ "from langchain_core.runnables import RunnableParallel\n",
+ "\n",
+ "translate_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트를 한국어로 번역하세요. 번역문만 출력:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "summarize_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트를 3문장으로 번역하세요.:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "keyword_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"키워드 5개 추출:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "text = {'text' : 'Python is a versatile language used in AI and web development'}\n",
+ "\n",
+ "start = time.time()\n",
+ "t = translate_chain.invoke(text)\n",
+ "s = summarize_chain.invoke(text)\n",
+ "k = keyword_chain.invoke(text)\n",
+ "print(f\"순차 실행 시간 : {time.time() - start:.1f}초\")\n",
+ "\n",
+ "# 병렬 실행\n",
+ "parallel = RunnableParallel(translated = translate_chain, summary = summarize_chain, keyword = keyword_chain)\n",
+ "\n",
+ "start = time.time()\n",
+ "result = parallel.invoke(text)\n",
+ "print(f\"병렬 실행 시간 : {time.time() - start:.1f}초\")\n",
+ "\n",
+ "print(\"번역\", result['translated'][:100])\n",
+ "print(\"요약\", result['summary'][:100])\n",
+ "print(\"키워드\", result['keyword'][:100])"
+ ],
+ "id": "a965d9ac34b2388d",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "순차 실행 시간 : 2.9초\n",
+ "병렬 실행 시간 : 1.7초\n",
+ "번역 파이썬은 AI와 웹 개발에 사용되는 다용도의 언어입니다.\n",
+ "요약 파이썬은 AI와 웹 개발에 사용되는 다용도 언어입니다.\n",
+ "키워드 1. Python\n",
+ "2. versatile\n",
+ "3. language\n",
+ "4. AI\n",
+ "5. web development\n"
+ ]
+ }
+ ],
+ "execution_count": 27
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T02:01:56.678134975Z",
+ "start_time": "2026-06-08T02:01:49.773633233Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 병렬로 만들어진 체인을 비동기식으로 처리\n",
+ "\n",
+ "import asyncio\n",
+ "\n",
+ "async def analyze_multiple(texts):\n",
+ " \"\"\"\n",
+ " 각 문장마다 번역, 요약, 키워드 추출을 한꺼번에 실행\n",
+ " \"\"\"\n",
+ " tasks = [parallel.ainvoke({'text':t}) for t in texts]\n",
+ " results = await asyncio.gather(*tasks)\n",
+ " return results\n",
+ "\n",
+ "texts = [\n",
+ " 'Python is grate for data science.',\n",
+ " 'Javascript dominated frontend development',\n",
+ " 'Rust i known for memory safety'\n",
+ "]\n",
+ "\n",
+ "start = time.time()\n",
+ "result = await analyze_multiple(texts)\n",
+ "print(f\"비동기 병렬 {len(texts)}개 : {time.time() - start:.1f}초\")\n",
+ "\n",
+ "for i, r in enumerate(result):\n",
+ " print(f\"[{i+1}] 번역 : {r['translated'][:40]}....\")\n",
+ "\n",
+ "result = summarize_chain.batch([{'text':t} for t in texts * 3], config={'max_concurrency': 3})"
+ ],
+ "id": "d0a16cd661d47ea5",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "비동기 병렬 3개 : 1.9초\n",
+ "[1] 번역 : 파이썬은 데이터 사이언스에 훌륭합니다.....\n",
+ "[2] 번역 : 자바스크립트가 프론트엔드 개발을 지배했습니다.....\n",
+ "[3] 번역 : 러스트는 메모리 안전성으로 유명합니다.....\n"
+ ]
+ }
+ ],
+ "execution_count": 28
}
],
"metadata": {
diff --git a/ollama/langgraph1.ipynb b/ollama/langgraph1.ipynb
new file mode 100644
index 0000000..1e61384
--- /dev/null
+++ b/ollama/langgraph1.ipynb
@@ -0,0 +1,1195 @@
+{
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### LangGraph\n",
+ "\n",
+ "- 워크플로 프레임워크\n",
+ "- LCEL 선형 (A -> B -> C) / LangGraph 순환 ( A-> B -> A -> C -> B) 흐름 지원\n",
+ "- 개념\n",
+ " - Node : 실행할 함수\n",
+ " - Edge : Node 간의 연결\n",
+ " - State : Node 간의 전체 상태를 담는 딕셔너리\n",
+ "- 조건부 엣지, Agent 루프, 자기 수정, 멀티 에이전트 등 복잡한 팬턴 구현"
+ ],
+ "id": "4f173862ffa5000b"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T10:59:55.228433274Z",
+ "start_time": "2026-06-08T10:59:55.202226030Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "# !pip install langgraph grandalf",
+ "id": "1ecece0e830ff1b5",
+ "outputs": [],
+ "execution_count": 1
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.004421946Z",
+ "start_time": "2026-06-08T10:59:55.236638820Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from langgraph.graph import StateGraph, START, END\n",
+ "from typing import TypedDict\n",
+ "\n",
+ "import os\n",
+ "\n",
+ "from cohere.types import summarize_request_extractiveness\n",
+ "from google.protobuf.timestamp import from_current_time\n",
+ "from langchain_classic.chains.sequential import SequentialChain\n",
+ "from langchain_openai import ChatOpenAI\n",
+ "from langchain_ibm import WatsonxEmbeddings\n",
+ "from langchain_ibm import ChatWatsonx\n",
+ "from langchain.agents import create_agent\n",
+ "from langchain_ollama import OllamaEmbeddings\n",
+ "from langchain_ollama import ChatOllama\n",
+ "from langchain_ollama import OllamaEmbeddings, ChatOllama\n",
+ "from dotenv import load_dotenv\n",
+ "\n",
+ "from langchain_community.utilities import GoogleSerperAPIWrapper\n",
+ "from langchain_core.tools import Tool\n",
+ "from langchain_core.tools import tool\n",
+ "\n",
+ "from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder\n",
+ "from langchain_core.output_parsers import StrOutputParser, JsonOutputParser, PydanticOutputParser\n",
+ "from langchain_core.runnables import RunnablePassthrough, RunnableParallel, RunnableLambda\n",
+ "from langgraph import graph\n",
+ "from pygments.unistring import combine\n",
+ "from sklearn import pipeline"
+ ],
+ "id": "424747aac7874d9",
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/cooney/Source/.venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
+ " from .autonotebook import tqdm as notebook_tqdm\n",
+ "/tmp/ipykernel_239115/245101980.py:18: DeprecationWarning: `langchain-community` is being sunset and is no longer actively maintained. See https://github.com/langchain-ai/langchain-community/issues/674 for details and migration guidance toward standalone integration packages.\n",
+ " from langchain_community.utilities import GoogleSerperAPIWrapper\n"
+ ]
+ }
+ ],
+ "execution_count": 2
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### TypedDIct / Pydantic\n",
+ "- TypedDict : 딕셔너리 키 , 값 타입을 정의할 수 있게 도와줌, 단순한 State 구현 시 사용, 기본값 줄 수 없음\n",
+ "- Pydantic : validation 검사(입력값), 검증이 필요한 State 인 경우 사용"
+ ],
+ "id": "ab7dfbff952ba846"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.372424934Z",
+ "start_time": "2026-06-08T11:00:03.059989505Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 데이터 저장소 생성\n",
+ "# message 상태관리 할거야 => 공유\n",
+ "class MyState(TypedDict):\n",
+ " message:str\n",
+ "\n",
+ "# 작업 함수 생성(노드)\n",
+ "def say_hello(state):\n",
+ " # state 값의 변화\n",
+ " return {\"message\": \"Hello, LangGraph!\"}\n",
+ "\n",
+ "# 그래프 생성\n",
+ "graph = StateGraph(MyState)\n",
+ "graph.add_node(\"hello\", say_hello)\n",
+ "\n",
+ "graph.add_edge(START, \"hello\")\n",
+ "graph.add_edge(\"hello\", END)\n",
+ "\n",
+ "# 실행\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"message\": \"\"})\n",
+ "print(result)\n",
+ "\n",
+ "# 그래프 시각화(참고)\n",
+ "app.get_graph().print_ascii()"
+ ],
+ "id": "977a81b9df795373",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{'message': 'Hello, LangGraph!'}\n",
+ "+-----------+ \n",
+ "| __start__ | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +-------+ \n",
+ " | hello | \n",
+ " +-------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +---------+ \n",
+ " | __end__ | \n",
+ " +---------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 3
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### State\n",
+ "- 상태를 중심으로 동작\n",
+ "- TypeDict, Pydantic Base Model 을 사용하여 정의\n",
+ "- 그래프 실행 중 지속적으로 업데이트 됨\n",
+ "- 노드 간의 전환은 조건부 엣지를 통해 제어 가능하며 복잡한 의사결정 프로세스 모델링 간으\n",
+ "- 재귀적 실행 지원\n",
+ "\n",
+ "#### Node\n",
+ "- 실제 작업을 수행하는 기본단위\n",
+ "- 함수기반\n",
+ "- 상태중심 : 현재 상태를 입력으로 받아 처리\n",
+ "- 독립적 실행 : 각 노드는 독립적으로 실행\n",
+ "- 조합 가능 : 여러 노드를 연결하여 복잡한 워크플로 가능\n",
+ "\n",
+ "### Edge\n",
+ "- 노드간의 연결과 실행 흐름을 정의\n"
+ ],
+ "id": "65888b5803542435"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.485999045Z",
+ "start_time": "2026-06-08T11:00:03.389852542Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 카운터 공유\n",
+ "\n",
+ "class CounterState(TypedDict):\n",
+ " count: int\n",
+ "\n",
+ "# 증가 함수\n",
+ "def increment(state):\n",
+ " print(f\"현재 카운트 : {state['count']}\")\n",
+ " new_count = state['count'] + 1\n",
+ " print(f\"새로운 카운트 : {new_count}\")\n",
+ "\n",
+ " # state 값 변경(return)\n",
+ " return {\"count\": new_count}\n",
+ "\n",
+ "# 그래프 생성\n",
+ "graph = StateGraph(CounterState)\n",
+ "graph.add_node(\"increment\", increment)\n",
+ "\n",
+ "# 그래프 연결(그래프가 실행될 것인가?)\n",
+ "graph.add_edge(START, \"increment\")\n",
+ "graph.add_edge(\"increment\", END)\n",
+ "\n",
+ "# 그래프를 실행 가능한 형태로 변경\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"count\": 0})\n",
+ "print(f\"최종 결과 {result}\")\n",
+ "\n",
+ "# 그래프 시각화(참고)\n",
+ "app.get_graph().print_ascii()\n",
+ "\n"
+ ],
+ "id": "3acb2357ac1825d7",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "현재 카운트 : 0\n",
+ "새로운 카운트 : 1\n",
+ "최종 결과 {'count': 1}\n",
+ "+-----------+ \n",
+ "| __start__ | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ "+-----------+ \n",
+ "| increment | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +---------+ \n",
+ " | __end__ | \n",
+ " +---------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 4
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.537019587Z",
+ "start_time": "2026-06-08T11:00:03.491230609Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 2개의 노드\n",
+ "\n",
+ "def first_increment(state):\n",
+ " return {\"count\":state['count']+1}\n",
+ "\n",
+ "def second_increment(state):\n",
+ " return {\"count\":state['count']+10}\n",
+ "\n",
+ "class CounterState(TypedDict):\n",
+ " count: int\n",
+ "\n",
+ "# START => first => second => END\n",
+ "# 그래프 생성\n",
+ "graph = StateGraph(CounterState)\n",
+ "graph.add_node(\"first\",first_increment)\n",
+ "graph.add_node(\"second\", second_increment)\n",
+ "\n",
+ "# 그래프 연결(그래프가 실행될 것인가?)\n",
+ "graph.add_edge(START, \"first\")\n",
+ "graph.add_edge(\"first\", 'second')\n",
+ "graph.add_edge('second', END)\n",
+ "\n",
+ "# 그래프를 실행 가능한 형태로 변경\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"count\": 0})\n",
+ "print(f\"최종 결과 {result}\")\n",
+ "\n",
+ "# 그래프 시각화(참고)\n",
+ "app.get_graph().print_ascii()"
+ ],
+ "id": "d0b60e65d488a596",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "최종 결과 {'count': 11}\n",
+ "+-----------+ \n",
+ "| __start__ | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +-------+ \n",
+ " | first | \n",
+ " +-------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +--------+ \n",
+ " | second | \n",
+ " +--------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +---------+ \n",
+ " | __end__ | \n",
+ " +---------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 5
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "- 조건부 엣지\n",
+ " - 런타임 상태에 따라 동적으로 실행 경로 결정\n",
+ " - add_conditional_edges()"
+ ],
+ "id": "c516f3c13d3ab6e7"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.599429285Z",
+ "start_time": "2026-06-08T11:00:03.548093598Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 입력숫자 > 10 => big\n",
+ "# 입력숫자 > 10 => small\n",
+ "\n",
+ "# State : 숫자, 결과\n",
+ "class NumberState(TypedDict):\n",
+ " number : int\n",
+ " result : str\n",
+ "\n",
+ "# 노드(result 값 변경)\n",
+ "def handle_big_number(state):\n",
+ " return {'result':f\"{state['number']}는 큰 숫자입니다.\"}\n",
+ "\n",
+ "def handle_small_number(state):\n",
+ " return {'result':f\"{state['number']}는 작은 숫자입니다.\"}\n",
+ "\n",
+ "# 라우터(조건함수)\n",
+ "def check_size(state):\n",
+ " if state['number'] > 10:\n",
+ " return \"big\"\n",
+ " else:\n",
+ " return \"small\"\n",
+ "\n",
+ "# 그래프 생성\n",
+ "graph = StateGraph(NumberState)\n",
+ "graph.add_node(\"big_handler\", handle_big_number)\n",
+ "graph.add_node(\"small_handler\", handle_small_number)\n",
+ "\n",
+ "# 엣지\n",
+ "graph.add_edge(\"big_handler\", END)\n",
+ "graph.add_edge(\"small_handler\", END)\n",
+ "\n",
+ "# 조건부 엣지(number 값에 따라 big? small?)\n",
+ "graph.add_conditional_edges(START, check_size, {'big':'big_handler', \"small\":\"small_handler\"})\n",
+ "\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"number\": 15, \"result\" : \"\"})\n",
+ "print(f\"큰 숫자 {result}\")\n",
+ "\n",
+ "result = app.invoke({\"number\": 5, \"result\" : \"\"})\n",
+ "print(f\"작은 숫자 {result}\")\n",
+ "\n",
+ "# 그래프 시각화(참고)\n",
+ "app.get_graph().print_ascii()\n"
+ ],
+ "id": "acf3f40b4d866477",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "큰 숫자 {'number': 15, 'result': '15는 큰 숫자입니다.'}\n",
+ "작은 숫자 {'number': 5, 'result': '5는 작은 숫자입니다.'}\n",
+ " +-----------+ \n",
+ " | __start__ | \n",
+ " +-----------+ \n",
+ " .. .. \n",
+ " .. .. \n",
+ " .. .. \n",
+ "+-------------+ +---------------+ \n",
+ "| big_handler | | small_handler | \n",
+ "+-------------+ +---------------+ \n",
+ " ** ** \n",
+ " ** ** \n",
+ " ** ** \n",
+ " +---------+ \n",
+ " | __end__ | \n",
+ " +---------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 6
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.650967440Z",
+ "start_time": "2026-06-08T11:00:03.606842460Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 홀, 짝\n",
+ "\n",
+ "# State : 숫자, 결과\n",
+ "class NumberState(TypedDict):\n",
+ " number : int\n",
+ " result : str\n",
+ "\n",
+ "# 노드(result 값 변경)\n",
+ "def even_node(state):\n",
+ " return {'result':f\"{state['number']}는 짝수입니다.\"}\n",
+ "\n",
+ "def odd_node(state):\n",
+ " return {'result':f\"{state['number']}는 홀수입니다.\"}\n",
+ "\n",
+ "# 라우터(조건함수)\n",
+ "def check_number(state):\n",
+ " if state['number'] % 2 == 0:\n",
+ " return \"even\"\n",
+ " else:\n",
+ " return \"odd\"\n",
+ "\n",
+ "# 그래프 생성\n",
+ "graph = StateGraph(NumberState)\n",
+ "graph.add_node(\"even_handler\", even_node)\n",
+ "graph.add_node(\"odd_handler\", odd_node)\n",
+ "\n",
+ "# 엣지\n",
+ "graph.add_edge(\"even_handler\", END)\n",
+ "graph.add_edge(\"odd_handler\", END)\n",
+ "\n",
+ "# 조건부 엣지(number 값에 따라 even? odd)\n",
+ "graph.add_conditional_edges(START, check_number, {'even':'even_handler', \"odd\":\"odd_handler\"})\n",
+ "\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"number\": 15, \"result\" : \"\"})\n",
+ "print(f\"홀수 {result}\")\n",
+ "\n",
+ "result = app.invoke({\"number\": 6, \"result\" : \"\"})\n",
+ "print(f\"짝수 {result}\")\n",
+ "\n",
+ "# 그래프 시각화(참고)\n",
+ "app.get_graph().print_ascii()\n"
+ ],
+ "id": "6900d17fc36539a3",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "홀수 {'number': 15, 'result': '15는 홀수입니다.'}\n",
+ "짝수 {'number': 6, 'result': '6는 짝수입니다.'}\n",
+ " +-----------+ \n",
+ " | __start__ | \n",
+ " +-----------+ \n",
+ " .. .. \n",
+ " .. .. \n",
+ " .. .. \n",
+ "+--------------+ +-------------+ \n",
+ "| even_handler | | odd_handler | \n",
+ "+--------------+ +-------------+ \n",
+ " ** ** \n",
+ " ** ** \n",
+ " ** ** \n",
+ " +---------+ \n",
+ " | __end__ | \n",
+ " +---------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 7
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.705048993Z",
+ "start_time": "2026-06-08T11:00:03.656007893Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "class ScoreState(TypedDict):\n",
+ " score:int\n",
+ "\n",
+ "# 노드 return => 상태값 변경\n",
+ "# return 을 안하면 None\n",
+ "def grade_a(state):\n",
+ " print(\"A 학점\")\n",
+ " # score = None\n",
+ " return {}\n",
+ "\n",
+ "def grade_b(state):\n",
+ " print(\"B 학점\")\n",
+ " return {}\n",
+ "\n",
+ "def grade_c(state):\n",
+ " print(\"C 학점\")\n",
+ " return {}\n",
+ "\n",
+ "# >=90 A, >=80 B, 나머지C,\n",
+ "# 라우터(조건함수)\n",
+ "def route_grade(state):\n",
+ " if state['score'] >= 90:\n",
+ " return \"A\"\n",
+ " elif state['score'] >= 80:\n",
+ " return \"B\"\n",
+ " else:\n",
+ " return \"C\"\n",
+ "\n",
+ "graph = StateGraph(ScoreState)\n",
+ "graph.add_node(\"grade_a\", grade_a)\n",
+ "graph.add_node(\"grade_b\", grade_b)\n",
+ "graph.add_node(\"grade_c\", grade_c)\n",
+ "\n",
+ "graph.add_edge(\"grade_a\", END)\n",
+ "graph.add_edge(\"grade_b\", END)\n",
+ "graph.add_edge(\"grade_c\", END)\n",
+ "\n",
+ "graph.add_conditional_edges(START, route_grade, {'A':'grade_a', \"B\":\"grade_b\", \"C\":\"grade_c\"})\n",
+ "\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"score\": 95, \"result\" : \"\"})\n",
+ "print(f\"학점 {result}\")\n",
+ "\n",
+ "result = app.invoke({\"score\": 88, \"result\" : \"\"})\n",
+ "print(f\"학점 {result}\")\n",
+ "\n",
+ "# app.get_graph().print_ascii()"
+ ],
+ "id": "3ec626fba58ea942",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "A 학점\n",
+ "학점 {'score': 95}\n",
+ "B 학점\n",
+ "학점 {'score': 88}\n"
+ ]
+ }
+ ],
+ "execution_count": 8
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.767196417Z",
+ "start_time": "2026-06-08T11:00:03.711901025Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 상태관리 : text, sentiment, result\n",
+ "# text : 오늘 기분이 너무 좋아\n",
+ "# analyze_sentiment() : 감정평가 text 좋 positive / 싫 negative / x neutral\n",
+ "# positive_node() : 긍정 의견 / negative_node() : 부정 의견 / neutral_node() : 중립 의견 => result 업데이트\n",
+ "# route_sentiment : return state['sentiment']\n",
+ "\n",
+ "# START => analyze => positive / negative / neutral => route_sentiment => positive_node / negative_node / neutral_node => END\n",
+ "\n",
+ "class SentimentState(TypedDict):\n",
+ " text : str # 입력 문장\n",
+ " sentiment : str # 감정 결과 (positive / negative / neutral)\n",
+ " result : str # 최종 출력 메시지\n",
+ "\n",
+ "def analyze_sentiment(state):\n",
+ " text = state[\"text\"]\n",
+ "\n",
+ " if \"좋\" in text:\n",
+ " sentiment = \"positive\"\n",
+ " elif \"싫\" in text:\n",
+ " sentiment = \"negative\"\n",
+ " else:\n",
+ " sentiment = \"neutral\"\n",
+ "\n",
+ " return {\"sentiment\": sentiment}\n",
+ "\n",
+ "def positive_node(state):\n",
+ " return {\"result\": \"긍정 의견\"}\n",
+ "\n",
+ "def negative_node(state):\n",
+ " return {\"result\": \"부정 의견\"}\n",
+ "\n",
+ "def neutral_node(state):\n",
+ " return {\"result\": \"중립 의견\"}\n",
+ "\n",
+ "# 라우터(조건함수)\n",
+ "def route_sentiment(state):\n",
+ " return state['sentiment']\n",
+ "\n",
+ "graph = StateGraph(SentimentState)\n",
+ "graph.add_node(\"analyze\", analyze_sentiment)\n",
+ "graph.add_node(\"positive\", positive_node)\n",
+ "graph.add_node(\"negative\", negative_node)\n",
+ "graph.add_node(\"neutral\", neutral_node)\n",
+ "\n",
+ "graph.add_edge(START, \"analyze\")\n",
+ "graph.add_edge(\"positive\", END)\n",
+ "graph.add_edge(\"negative\", END)\n",
+ "graph.add_edge(\"neutral\", END)\n",
+ "\n",
+ "graph.add_conditional_edges(\"analyze\", route_sentiment, {'positive':'positive', \"negative\":\"negative\", \"neutral\":\"neutral\"}) # 라우터 결과가 'positive'면 ->️ 'positive' 노드로 가라!\n",
+ "\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"text\": \"좋\"})\n",
+ "print(f\"{result}\")\n",
+ "\n",
+ "result = app.invoke({\"text\": \"싫\"})\n",
+ "print(f\"{result}\")\n",
+ "\n",
+ "result = app.invoke({\"text\": \"밥\"})\n",
+ "print(f\"{result}\")\n",
+ "\n",
+ "app.get_graph().print_ascii()"
+ ],
+ "id": "94202953b8b8be5f",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{'text': '좋', 'sentiment': 'positive', 'result': '긍정 의견'}\n",
+ "{'text': '싫', 'sentiment': 'negative', 'result': '부정 의견'}\n",
+ "{'text': '밥', 'sentiment': 'neutral', 'result': '중립 의견'}\n",
+ " +-----------+ \n",
+ " | __start__ | \n",
+ " +-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +---------+ \n",
+ " | analyze | \n",
+ " ...+---------+.... \n",
+ " .... . .... \n",
+ " .... . .... \n",
+ " .. . .. \n",
+ "+----------+ +---------+ +----------+ \n",
+ "| negative | | neutral | | positive | \n",
+ "+----------+**** +---------+ ***+----------+ \n",
+ " **** * **** \n",
+ " **** * **** \n",
+ " ** * ** \n",
+ " +---------+ \n",
+ " | __end__ | \n",
+ " +---------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 9
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:03.794644656Z",
+ "start_time": "2026-06-08T11:00:03.778761416Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# .env 내용 가져오기\n",
+ "load_dotenv()\n",
+ "\n",
+ "apikey = os.getenv(\"WATSONX_API_KEY\")\n",
+ "project_id = os.getenv(\"WATSONX_PROJECT_ID\")\n",
+ "watsonx_ai_url = os.getenv(\"WATSONX_URL\")\n",
+ "hf_token = os.getenv(\"HF_TOKEN\")\n",
+ "COHERE_API_KEY = os.getenv(\"COHERE_API_KEY\")\n",
+ "SERPER_API_KEY = os.getenv(\"SERPER_API_KEY\")\n",
+ "GEMINI_API_KEY = os.getenv(\"GEMINI_API_KEY\")\n",
+ "LANGSMITH_API_KEY = os.getenv(\"LANGSMITH_API_KEY\")"
+ ],
+ "id": "c2b2990c27f33682",
+ "outputs": [],
+ "execution_count": 10
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:00:11.384010221Z",
+ "start_time": "2026-06-08T11:00:03.798592437Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "watson_llm = ChatWatsonx(\n",
+ " model_id=\"ibm/granite-4-h-small\",\n",
+ " url=f\"{watsonx_ai_url}\",\n",
+ " api_key=f\"{apikey}\",\n",
+ " project_id=f\"{project_id}\",\n",
+ " max_tokens=2000,\n",
+ " params={\n",
+ " \"temperature\": 0\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "watson_embedding = WatsonxEmbeddings(\n",
+ " model_id=\"ibm/granite-embedding-278m-multilingual\",\n",
+ " url=f\"{watsonx_ai_url}\",\n",
+ " api_key=f\"{apikey}\",\n",
+ " project_id=f\"{project_id}\"\n",
+ ")\n",
+ "\n",
+ "hugging_llm = ChatOpenAI(\n",
+ " model=\"Qwen/Qwen2.5-7B-Instruct:together\",\n",
+ " api_key=hf_token,\n",
+ " base_url=\"https://router.huggingface.co/v1\",\n",
+ " temperature=0,\n",
+ ")\n",
+ "\n",
+ "ollama_embedding = OllamaEmbeddings(model=\"nomic-embed-text-v2-moe\")\n",
+ "\n",
+ "# 로컬 LLM\n",
+ "qwen_llm = ChatOllama(model=\"qwen3.5:4b\", temperature=0)\n",
+ "exaone_llm = ChatOllama(model=\"exaone3.5:2.4b\", temperature=0)\n",
+ "gemma_llm = ChatOllama(model=\"gemma4:e2b\")"
+ ],
+ "id": "f5fdb0bea4930484",
+ "outputs": [],
+ "execution_count": 11
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:03:06.386132558Z",
+ "start_time": "2026-06-08T11:03:04.111106960Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "parser = StrOutputParser()\n",
+ "\n",
+ "translate_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트를 한국어로 번역하세요. 번역문만 출력:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "summarize_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트를 3문장으로 요약하세요.:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "sentiment_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트를 의 감정을 긍정/부정/중립 중 하나로만 답하세요.:\\n{summary}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "# State 정의\n",
+ "class AnalysisState(TypedDict):\n",
+ " text: str\n",
+ " translated: str\n",
+ " summary: str\n",
+ " sentiment: str\n",
+ " done: bool\n",
+ "\n",
+ "# node 정의\n",
+ "def translated_node(state):\n",
+ " result = translate_chain.invoke({\"text\":state['text']})\n",
+ " return {\"translated\":result}\n",
+ "\n",
+ "def summary_node(state):\n",
+ " result = summarize_chain.invoke({'text':state['translated']})\n",
+ " return {\"summary\":result}\n",
+ "\n",
+ "def sentiment_node(state):\n",
+ " result = sentiment_chain.invoke({'summary':state['summary']})\n",
+ " return {\"sentiment\":result}\n",
+ "\n",
+ "# 그래프 생성\n",
+ "graph = StateGraph(AnalysisState)\n",
+ "graph.add_node(\"translate\", translated_node)\n",
+ "graph.add_node(\"summarize\", summary_node)\n",
+ "graph.add_node(\"sentiment\", sentiment_node)\n",
+ "\n",
+ "graph.add_edge(START, \"translate\")\n",
+ "graph.add_edge(\"translate\", \"summarize\")\n",
+ "graph.add_edge(\"summarize\", \"sentiment\")\n",
+ "graph.add_edge(\"sentiment\", END)\n",
+ "\n",
+ "# 그래프 실행\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({'text': 'Python is grate!!', \"done\":False})\n",
+ "print(\"번역 : \", result['translated'])\n",
+ "print(\"요약 : \", result['summary'])\n",
+ "print(\"감정 : \", result['sentiment'])\n",
+ "\n",
+ "app.get_graph().print_ascii()"
+ ],
+ "id": "75b0102055a3b951",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "번역 : 파이썬은 대단해요!!\n",
+ "요약 : 파이썬은 대단한 프로그래밍 언어입니다. 파이썬은 간결하고 읽기 쉬운 문법을 가지고 있어 초보자에게도 적합합니다. 또한 다양한 라이브러리와 프레임워크를 제공하여 다양한 분야에서 활용될 수 있습니다.\n",
+ "감정 : 긍정\n",
+ "+-----------+ \n",
+ "| __start__ | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ "+-----------+ \n",
+ "| translate | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ "+-----------+ \n",
+ "| summarize | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ "+-----------+ \n",
+ "| sentiment | \n",
+ "+-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +---------+ \n",
+ " | __end__ | \n",
+ " +---------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 14
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-06-08T11:07:53.195678876Z",
+ "start_time": "2026-06-08T11:07:51.827957635Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# 조건부엣지 : summary >= 100자 이상 good / 100자 미만 poor\n",
+ "\n",
+ "class SummaryState(TypedDict):\n",
+ " text:str\n",
+ " summary:str\n",
+ " quality:str\n",
+ " retries:int\n",
+ "\n",
+ "def summary_node(state):\n",
+ " result = summarize_chain.invoke({'text':state['text']})\n",
+ " return {\"summary\":result}\n",
+ "\n",
+ "# quality\n",
+ "def check_quality_node(state):\n",
+ " # summary >= 100 자 이상 good / 100자 미만 poor\n",
+ " quality = \"good\" if len(state['summary']) >= 100 else \"poor\"\n",
+ " return {\"quality\":quality}\n",
+ "\n",
+ "def retry_node(state):\n",
+ " return {\"text\":state['text']+\"\\n 더 자세히 요약하세요\", \"retries\":state['retries'] + 1}\n",
+ "\n",
+ "def route_by_quality(state):\n",
+ " if state['quality'] == 'poor' and state['retries'] < 3:\n",
+ " return \"retry\"\n",
+ " return \"done\"\n",
+ "\n",
+ "graph = StateGraph(SummaryState)\n",
+ "graph.add_node(\"summary\", summary_node)\n",
+ "graph.add_node(\"check_quality\", check_quality_node)\n",
+ "graph.add_node(\"retry\", retry_node)\n",
+ "\n",
+ "# START -> summary -> check_quality\n",
+ "graph.add_edge(START, \"summary\")\n",
+ "graph.add_edge(\"summary\", \"check_quality\")\n",
+ "graph.add_conditional_edges(\"check_quality\", route_by_quality, {\"retry\": \"retry\", \"done\" : END})\n",
+ "graph.add_edge(\"retry\", \"summary\")\n",
+ "\n",
+ "# 그래프 실행\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({'text': '잛은 글'})\n",
+ "print(f\"최종요약({len(result['summary'])}자): {result['summary'][:100]}\")\n",
+ "\n",
+ "app.get_graph().print_ascii()\n"
+ ],
+ "id": "b5a849ba6c73ae41",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "최종요약(150자): 제공된 텍스트는 질문이 없는 한 문장으로, 요약할 내용이 없습니다. 그러나 이 문장을 3문장으로 요약하면 다음과 같습니다:\n",
+ "\n",
+ "1. 이 프롬프트는 요약을 요청하는 것입니다.\n",
+ "2. 하\n",
+ " +-----------+ \n",
+ " | __start__ | \n",
+ " +-----------+ \n",
+ " * \n",
+ " * \n",
+ " * \n",
+ " +---------+ \n",
+ " | summary | \n",
+ " +---------+ \n",
+ " *** *** \n",
+ " * * \n",
+ " ** *** \n",
+ "+---------------+ * \n",
+ "| check_quality | * \n",
+ "+---------------+. * \n",
+ " . ..... * \n",
+ " . ... * \n",
+ " . ... * \n",
+ " +---------+ +-------+ \n",
+ " | __end__ | | retry | \n",
+ " +---------+ +-------+ \n"
+ ]
+ }
+ ],
+ "execution_count": 17
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "source": [
+ "class VerifyState(TypedDict):\n",
+ " question:str\n",
+ " answer:str\n",
+ " feedback:str\n",
+ " is_verified:bool\n",
+ " attempt:int\n",
+ "\n",
+ "def generate(state):\n",
+ " \"\"\"답변을 생성합니다. 이전 피드백이 있으면 반영합니다.\"\"\"\n",
+ " prompt = f\"질문 : {state['question']}\"\n",
+ "\n",
+ " if state['feedback']:\n",
+ " prompt += f\"\\n\\n이전 답변의 피드백 : {state['feedback']}\\n위 피드백을 반영하여 개선된 답변을 작성하세요\"\n",
+ "\n",
+ " result = watson_llm.invoke(prompt)\n",
+ " return {'answer':result.content, \"attempt\":state['attempt'] + 1}\n",
+ "\n",
+ "def verify(state):\n",
+ " \"\"\"답변의 정확성과 완전성을 검증합니다.\"\"\"\n",
+ " verification = watson_llm.invoke(f\"\"\"\n",
+ "다음 답변의 정확성과 완정성을 검증하세요\n",
+ "\n",
+ "질문 :\n",
+ "{state['question']}\n",
+ "\n",
+ "답변 :\n",
+ "{state['answer']}\n",
+ "\n",
+ "정확하고 완전하면 첫 줄에 'PASS' 를, 수정이 필요하면 첫줄에 'FAIL' 을 쓰고 구체적인 개선사항을 설명하세요.\n",
+ "\"\"\")\n",
+ "\n",
+ " content = verification.content\n",
+ " is_pass = content.strip().startswith('PASS')\n",
+ "\n",
+ " return {'is_verified':is_pass, 'feedback':content}\n",
+ "\n",
+ "# router 개념\n",
+ "def should_retry(state):\n",
+ " \"\"\"검증 통과 또는 최대 횟수 도달시 종료\"\"\"\n",
+ " if state['is_verified'] or state['attempt'] >= 3:\n",
+ " return END\n",
+ " return 'generate'\n",
+ "\n",
+ "# 질문 => LLM 답변 생성 => LLM 답변 검증 => 검증통과\n",
+ "# 검증 미통과 => LLM 답변 생성\n",
+ "\n",
+ "graph = StateGraph(VerifyState)\n",
+ "graph.add_node(\"generate\", generate)\n",
+ "graph.add_node(\"verify\", verify)\n",
+ "\n",
+ "graph.add_edge(START, \"generate\")\n",
+ "graph.add_edge(\"generate\", \"verify\")\n",
+ "graph.add_conditional_edges(\"verify\", should_retry, {\"generate\": \"generate\", END: END})\n",
+ "\n",
+ "# 그래프 실행\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({'question': '파이썬에서 GIL이 무엇이며 멀티쓰레딩에 어떤 영향을 주는지 설명해줘', \"answer\":\"\",\"feedback\":\"\", \"is_verified\":False, \"attempt\":0})\n",
+ "\n",
+ "print(\"시도 횟수\", result['attempt'])\n",
+ "print(\"검증통과\", result['is_verified'])\n",
+ "print(\"최종답변\", result['answer'][:300])\n",
+ "\n",
+ "app.get_graph().print_ascii()"
+ ],
+ "id": "686634fe1625b815",
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "source": [
+ "import time\n",
+ "from langchain_core.runnables import RunnableParallel\n",
+ "\n",
+ "class AnalysisState(TypedDict):\n",
+ " text:str\n",
+ " translated:str\n",
+ " summary:str\n",
+ " keywords:list[str]\n",
+ "\n",
+ "translate_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트를 한국어로 번역하세요. 번역문만 출력:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "summarize_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"다음 텍스트를 3문장으로 번역하세요.:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "keyword_chain = ChatPromptTemplate.from_messages([\n",
+ " (\"system\", \"키워드 5개 추출:\\n{text}.\"),\n",
+ "]) | watson_llm | parser\n",
+ "\n",
+ "def translate_node(state):\n",
+ " print(\"번역 시작\")\n",
+ " time.sleep(3)\n",
+ " print(\"번역 종료\")\n",
+ " return {\"translated\": translate_chain.invoke({\"text\":state['text']})}\n",
+ "\n",
+ "def summarize_node(state):\n",
+ " print(\"요약 시작\")\n",
+ " time.sleep(3)\n",
+ " print(\"요약 종료\")\n",
+ " return {\"summary\": summarize_chain.invoke({\"text\":state['text']})}\n",
+ "\n",
+ "def keyword_node(state):\n",
+ " print(\"키워드 추출 시작\")\n",
+ " time.sleep(3)\n",
+ " print(\"키워드 추출 종료\")\n",
+ " return {\"keywords\": keyword_chain.invoke({\"text\":state['text']})}\n",
+ "\n",
+ "graph = StateGraph(AnalysisState)\n",
+ "graph.add_node('translated', translated_node)\n",
+ "graph.add_node('summary', summarize_node)\n",
+ "graph.add_node('keywords', keyword_node)\n",
+ "\n",
+ "graph.add_edge(START, \"translated\")\n",
+ "graph.add_edge(START, \"summary\")\n",
+ "graph.add_edge(START, \"keywords\")\n",
+ "\n",
+ "graph.add_edge(\"translated\", END)\n",
+ "graph.add_edge(\"summary\", END)\n",
+ "graph.add_edge(\"keywords\", END)\n",
+ "\n",
+ "app = graph.compile()\n",
+ "text = {'text' : 'Python is a versatile language used in AI and web development'}\n",
+ "result = app.invoke(text)\n",
+ "\n",
+ "print(\"번역\", result['translated'][:100])\n",
+ "print(\"요약\", result['summary'][:100])\n",
+ "print(\"키워드\", result['keywords'][:100])\n",
+ "\n",
+ "app.get_graph().print_ascii()"
+ ],
+ "id": "956e5a8f89675559",
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "- rag LangGraph\n",
+ " - 사용자 질문 => retrieve => generate => END"
+ ],
+ "id": "e9270ebbd217d878"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "source": [
+ "# !pip install langchain_community\n",
+ "from langchain_community.document_loaders import PyPDFLoader, CSVLoader, WebBaseLoader, DirectoryLoader\n",
+ "from youtube_transcript_api import YouTubeTranscriptApi\n",
+ "from langchain_core.documents import Document\n",
+ "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
+ "from langchain_ollama import OllamaEmbeddings\n",
+ "from langchain_ibm import WatsonxEmbeddings\n",
+ "from langchain_chroma import Chroma\n",
+ "from langchain_community.vectorstores import FAISS"
+ ],
+ "id": "d03e2b9989d53cb2",
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "source": [
+ "# STEP 1 : 문서로드\n",
+ "loader = PyPDFLoader(\"./data/Summary of ChatGPTGPT-4 Research.pdf\")\n",
+ "# STEP 2 : 문서분할\n",
+ "splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)\n",
+ "chunks = splitter.split_documents(loader.load())\n",
+ "print(f\"chunks 수 {chunks}\")\n",
+ "# STEP 3 : 인덱싱 - 임베딩\n",
+ "# STEP 4 : 벡터스토어(Chroma or FAISS)\n",
+ "vectorstore = Chroma.from_documents(chunks, watson_embedding, persist_directory=\"./db/chroma_db\", collection_name=\"research\")\n",
+ "# STEP 5 : as_retriever() : Vector Store 를 Retriever 형태로 변환하여 LangChain 에 연결\n",
+ "retriever = vectorstore.as_retriever(search_type=\"similarity\", search_kwargs={\"k\":3})"
+ ],
+ "id": "1f0fda34f40177ea",
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "source": [
+ "from typing import List\n",
+ "from langchain_core.documents import Document\n",
+ "\n",
+ "class RAGState(TypedDict):\n",
+ " query:str\n",
+ " retriever_docs:list[Document]\n",
+ " answer:str\n",
+ "\n",
+ "def retrieve(state):\n",
+ " # 기존의 벡터스토어에 질의\n",
+ " vectorstore = Chroma(collection_name=\"research\", embedding_function=watson_embedding, persist_directory=\"./db/chroma_db\")\n",
+ "\n",
+ " docs = vectorstore.similarity_search(state['query'], k=3)\n",
+ "\n",
+ " return {\"retriever_docs\" : docs}\n",
+ "\n",
+ "def generate(state):\n",
+ " context = \"\\n\\n\".join(doc.page_content for doc in state['retriever_docs'])\n",
+ "\n",
+ " prompt = \"\"\"\\\n",
+ "다음 컨텍스트를 참고하여 질문에 답하세요.\n",
+ "컨텍스트에 없는 내용은 모른다고 답하세요.\n",
+ "\n",
+ "컨텍스트:\n",
+ "{context}\n",
+ "질문:\n",
+ "{query}\n",
+ "\"\"\"\n",
+ " response = watson_llm.invoke(prompt.format(context=context, query=state['query']))\n",
+ " return {\"answer\":response.content}\n",
+ "\n",
+ "graph = StateGraph(RAGState)\n",
+ "graph.add_node(\"retrieve\", retrieve)\n",
+ "graph.add_node(\"generate\", generate)\n",
+ "\n",
+ "graph.add_edge(START, \"retrieve\")\n",
+ "graph.add_edge(\"retrieve\", \"generate\")\n",
+ "graph.add_edge(\"generate\", END)\n",
+ "\n",
+ "app = graph.compile()\n",
+ "result = app.invoke({\"query\":\"where can i use chatGPT?\"})\n",
+ "print(result['answer'])"
+ ],
+ "id": "bcf8f539b91ab2b7",
+ "outputs": [],
+ "execution_count": null
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}