youngjin-langchain-tools 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,8 +8,8 @@ LangGraph agent responses in Streamlit applications.
8
8
  Replaces the deprecated StreamlitCallbackHandler for LangGraph-based agents.
9
9
  """
10
10
 
11
- from typing import Any, Dict, List, Optional, Union, Generator
12
- from dataclasses import dataclass, field
11
+ from typing import Any, Dict, List, Optional, Generator
12
+ from dataclasses import dataclass
13
13
  import logging
14
14
  import re
15
15
 
@@ -22,38 +22,33 @@ logger = logging.getLogger(__name__)
22
22
  # ============================================================
23
23
  ERROR_PATTERNS = {
24
24
  # OpenAI errors
25
- r"AuthenticationError.*API key": {
26
- "title": "🔑 API Key 오류",
27
- "message": "API 키가 설정되지 않았거나 유효하지 않습니다.",
25
+ r"AuthenticationError.*API key|openai.*api.*key|OPENAI_API_KEY": {
26
+ "title": "🔑 OpenAI API Key 오류",
27
+ "message": "OpenAI API 키가 설정되지 않았거나 유효하지 않습니다.",
28
28
  "solution": [
29
- "1. `.env` 파일에 `OPENAI_API_KEY=sk-...` 형태로 키를 설정하세요.",
30
- "2. 또는 코드 상단에 직접 API 키를 입력하세요.",
31
- "3. API 키는 https://platform.openai.com/api-keys 에서 발급받을 수 있습니다.",
29
+ "환경변수 `OPENAI_API_KEY`를 설정하거나 클라이언트 초기화 시 `api_key` 파라미터를 전달하세요.",
30
+ "API 발급: https://platform.openai.com/api-keys",
32
31
  ],
33
32
  },
34
33
  r"RateLimitError|rate_limit|429": {
35
34
  "title": "⏱️ Rate Limit 초과",
36
35
  "message": "API 요청 한도를 초과했습니다.",
37
36
  "solution": [
38
- "1. 잠시 후 다시 시도해주세요.",
39
- "2. API 사용량을 확인하세요: https://platform.openai.com/usage",
40
- "3. 필요시 요금제를 업그레이드하세요.",
37
+ "잠시 후 다시 시도하거나, API 사용량 및 요금제를 확인하세요.",
41
38
  ],
42
39
  },
43
40
  r"InsufficientQuotaError|insufficient_quota|billing": {
44
41
  "title": "💳 크레딧 부족",
45
42
  "message": "API 크레딧이 부족합니다.",
46
43
  "solution": [
47
- "1. 결제 정보를 확인하세요: https://platform.openai.com/account/billing",
48
- "2. 크레딧을 충전하세요.",
44
+ "API 제공자의 결제 페이지에서 크레딧을 충전하세요.",
49
45
  ],
50
46
  },
51
47
  r"InvalidRequestError|invalid_request": {
52
48
  "title": "❌ 잘못된 요청",
53
49
  "message": "API 요청 형식이 올바르지 않습니다.",
54
50
  "solution": [
55
- "1. 입력 데이터를 확인하세요.",
56
- "2. 모델명이 올바른지 확인하세요.",
51
+ "입력 데이터와 모델명이 올바른지 확인하세요.",
57
52
  ],
58
53
  },
59
54
  # Anthropic errors
@@ -61,8 +56,8 @@ ERROR_PATTERNS = {
61
56
  "title": "🔑 Anthropic API Key 오류",
62
57
  "message": "Anthropic API 키가 설정되지 않았거나 유효하지 않습니다.",
63
58
  "solution": [
64
- "1. `.env` 파일에 `ANTHROPIC_API_KEY=sk-ant-...` 형태로 키를 설정하세요.",
65
- "2. API 키는 https://console.anthropic.com/ 에서 발급받을 수 있습니다.",
59
+ "환경변수 `ANTHROPIC_API_KEY`를 설정하세요.",
60
+ "API 발급: https://console.anthropic.com/",
66
61
  ],
67
62
  },
68
63
  # Google errors
@@ -70,8 +65,8 @@ ERROR_PATTERNS = {
70
65
  "title": "🔑 Google API Key 오류",
71
66
  "message": "Google API 키가 설정되지 않았거나 유효하지 않습니다.",
72
67
  "solution": [
73
- "1. `.env` 파일에 `GOOGLE_API_KEY=...` 형태로 키를 설정하세요.",
74
- "2. API 키는 https://aistudio.google.com/apikey 에서 발급받을 수 있습니다.",
68
+ "환경변수 `GOOGLE_API_KEY`를 설정하세요.",
69
+ "API 발급: https://aistudio.google.com/apikey",
75
70
  ],
76
71
  },
77
72
  # Network errors
@@ -79,18 +74,14 @@ ERROR_PATTERNS = {
79
74
  "title": "🌐 네트워크 오류",
80
75
  "message": "API 서버에 연결할 수 없습니다.",
81
76
  "solution": [
82
- "1. 인터넷 연결을 확인하세요.",
83
- "2. 방화벽/프록시 설정을 확인하세요.",
84
- "3. API 서버 상태를 확인하세요.",
77
+ "인터넷 연결 및 방화벽/프록시 설정을 확인하세요.",
85
78
  ],
86
79
  },
87
80
  r"TimeoutError|timeout|timed out": {
88
81
  "title": "⏰ 시간 초과",
89
82
  "message": "API 요청이 시간 초과되었습니다.",
90
83
  "solution": [
91
- "1. 네트워크 연결을 확인하세요.",
92
- "2. 잠시 후 다시 시도해주세요.",
93
- "3. 요청 크기를 줄여보세요.",
84
+ "네트워크 연결을 확인하고 잠시 후 다시 시도하세요.",
94
85
  ],
95
86
  },
96
87
  # Model errors
@@ -98,9 +89,7 @@ ERROR_PATTERNS = {
98
89
  "title": "🤖 모델 오류",
99
90
  "message": "지정된 모델을 찾을 수 없습니다.",
100
91
  "solution": [
101
- "1. 모델명이 올바른지 확인하세요.",
102
- "2. 해당 모델에 대한 접근 권한이 있는지 확인하세요.",
103
- "3. 사용 가능한 모델 목록을 확인하세요.",
92
+ "모델명과 접근 권한을 확인하세요.",
104
93
  ],
105
94
  },
106
95
  }
@@ -140,6 +129,13 @@ class StreamlitLanggraphHandlerConfig:
140
129
  expand_new_thoughts: bool = True
141
130
  """Whether to expand the status container to show tool calls."""
142
131
 
132
+ collapse_completed_thoughts: bool = True
133
+ """Whether to automatically collapse completed thought containers."""
134
+
135
+ max_thought_containers: int = 4
136
+ """Maximum number of thought containers to show at once.
137
+ When exceeded, oldest thoughts are moved to a 'History' expander."""
138
+
143
139
  max_tool_content_length: int = 2000
144
140
  """Maximum length of tool output to display before truncating."""
145
141
 
@@ -213,6 +209,8 @@ class StreamlitLanggraphHandler:
213
209
  container: Any,
214
210
  *,
215
211
  expand_new_thoughts: bool = True,
212
+ collapse_completed_thoughts: bool = True,
213
+ max_thought_containers: int = 4,
216
214
  max_tool_content_length: int = 2000,
217
215
  show_tool_calls: bool = True,
218
216
  show_tool_results: bool = True,
@@ -228,6 +226,11 @@ class StreamlitLanggraphHandler:
228
226
  Usually st.container() or similar.
229
227
  expand_new_thoughts: Whether to expand status container
230
228
  to show tool calls. Defaults to True.
229
+ collapse_completed_thoughts: Whether to collapse completed
230
+ thought containers. Defaults to True.
231
+ max_thought_containers: Maximum number of thought containers
232
+ to show at once. Oldest are moved to
233
+ 'History' expander. Defaults to 4.
231
234
  max_tool_content_length: Maximum characters of tool output
232
235
  to display. Defaults to 2000.
233
236
  show_tool_calls: Whether to show tool call info. Defaults to True.
@@ -241,6 +244,8 @@ class StreamlitLanggraphHandler:
241
244
  else:
242
245
  self._config = StreamlitLanggraphHandlerConfig(
243
246
  expand_new_thoughts=expand_new_thoughts,
247
+ collapse_completed_thoughts=collapse_completed_thoughts,
248
+ max_thought_containers=max_thought_containers,
244
249
  max_tool_content_length=max_tool_content_length,
245
250
  show_tool_calls=show_tool_calls,
246
251
  show_tool_results=show_tool_results,
@@ -252,6 +257,9 @@ class StreamlitLanggraphHandler:
252
257
  self._final_response: str = ""
253
258
  self._status_container: Any = None
254
259
  self._response_placeholder: Any = None
260
+ self._history_placeholder: Any = None # Placeholder for history expander
261
+ self._thought_history: List[Dict[str, Any]] = [] # History of old thoughts
262
+ self._current_thoughts: List[Dict[str, Any]] = [] # Current visible thoughts
255
263
 
256
264
  @property
257
265
  def config(self) -> StreamlitLanggraphHandlerConfig:
@@ -343,9 +351,13 @@ class StreamlitLanggraphHandler:
343
351
 
344
352
  # Reset state
345
353
  self._final_response = ""
354
+ self._thought_history = []
355
+ self._current_thoughts = []
346
356
 
347
357
  # Create UI components
348
358
  with self._container:
359
+ # History expander placeholder (will be shown when needed)
360
+ self._history_placeholder = st.empty()
349
361
  self._status_container = st.status(
350
362
  self._config.thinking_label,
351
363
  expanded=self._config.expand_new_thoughts
@@ -416,6 +428,50 @@ class StreamlitLanggraphHandler:
416
428
 
417
429
  yield {"type": "complete", "data": {"response": self._final_response}}
418
430
 
431
+ def _add_thought(self, thought_type: str, data: Dict[str, Any]) -> None:
432
+ """Add a thought and manage history if max_thought_containers exceeded."""
433
+ import streamlit as st
434
+
435
+ thought = {"type": thought_type, "data": data}
436
+ self._current_thoughts.append(thought)
437
+
438
+ # Check if we need to move old thoughts to history
439
+ max_containers = self._config.max_thought_containers
440
+ if len(self._current_thoughts) > max_containers:
441
+ # Move oldest thoughts to history
442
+ while len(self._current_thoughts) > max_containers:
443
+ old_thought = self._current_thoughts.pop(0)
444
+ self._thought_history.append(old_thought)
445
+
446
+ # Update history expander
447
+ self._update_history_expander()
448
+
449
+ def _update_history_expander(self) -> None:
450
+ """Update the history expander with old thoughts."""
451
+ import streamlit as st
452
+
453
+ if not self._thought_history:
454
+ return
455
+
456
+ with self._history_placeholder.container():
457
+ with st.expander(f"📜 History ({len(self._thought_history)} items)", expanded=False):
458
+ for thought in self._thought_history:
459
+ if thought["type"] == "tool_call":
460
+ st.markdown(
461
+ f"{self._config.tool_call_emoji} "
462
+ f"**{thought['data']['name']}**: `{thought['data']['args']}`"
463
+ )
464
+ elif thought["type"] == "tool_result":
465
+ st.markdown(
466
+ f"{self._config.tool_complete_emoji} "
467
+ f"**{thought['data']['name']}** 완료"
468
+ )
469
+ content = thought['data']['content']
470
+ if len(content) > 200: # Shorter preview in history
471
+ st.code(content[:200] + "\n... (truncated)", language="text")
472
+ else:
473
+ st.code(content, language="text")
474
+
419
475
  def _handle_updates(
420
476
  self,
421
477
  data: Dict[str, Any]
@@ -439,6 +495,9 @@ class StreamlitLanggraphHandler:
439
495
  tool_name = tc.get('name', 'tool')
440
496
  tool_args = tc.get('args', {})
441
497
 
498
+ # Add to thought tracking
499
+ self._add_thought("tool_call", {"name": tool_name, "args": tool_args})
500
+
442
501
  with self._status_container:
443
502
  st.write(
444
503
  f"{self._config.tool_call_emoji} "
@@ -456,12 +515,17 @@ class StreamlitLanggraphHandler:
456
515
  tool_name = msg.name
457
516
  tool_content = str(msg.content) if hasattr(msg, 'content') else ""
458
517
 
518
+ # Add to thought tracking
519
+ self._add_thought("tool_result", {"name": tool_name, "content": tool_content})
520
+
459
521
  with self._status_container:
460
522
  st.write(
461
523
  f"{self._config.tool_complete_emoji} "
462
524
  f"**{tool_name}** 완료"
463
525
  )
464
- with st.expander(f"📋 {tool_name} 결과 보기", expanded=False):
526
+ # Collapse based on config
527
+ expanded = not self._config.collapse_completed_thoughts
528
+ with st.expander(f"📋 {tool_name} 결과 보기", expanded=expanded):
465
529
  if len(tool_content) > self._config.max_tool_content_length:
466
530
  st.code(
467
531
  tool_content[:self._config.max_tool_content_length]
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: youngjin-langchain-tools
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: LangGraph utilities for Streamlit - StreamlitLanggraphHandler and more
5
5
  Project-URL: Homepage, https://github.com/yourusername/youngjin-langchain-tools
6
6
  Project-URL: Documentation, https://github.com/yourusername/youngjin-langchain-tools#readme
7
7
  Project-URL: Repository, https://github.com/yourusername/youngjin-langchain-tools.git
8
8
  Project-URL: Issues, https://github.com/yourusername/youngjin-langchain-tools/issues
9
9
  Project-URL: Changelog, https://github.com/yourusername/youngjin-langchain-tools/releases
10
- Author-email: YoungJin <your-email@example.com>
11
- Maintainer-email: YoungJin <your-email@example.com>
10
+ Author-email: CoCoRoF <gkfua00@gmail.com>
11
+ Maintainer-email: CoCoRoF <gkfua00@gmail.com>
12
12
  License: Apache-2.0
13
13
  License-File: LICENSE
14
14
  Keywords: agents,ai,callback,handler,langchain,langgraph,llm,streamlit
@@ -16,12 +16,13 @@ Classifier: Development Status :: 3 - Alpha
16
16
  Classifier: Intended Audience :: Developers
17
17
  Classifier: License :: OSI Approved :: Apache Software License
18
18
  Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
19
21
  Classifier: Programming Language :: Python :: 3.12
20
22
  Classifier: Programming Language :: Python :: 3.13
21
- Classifier: Programming Language :: Python :: 3.14
22
23
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Requires-Python: >=3.12
25
+ Requires-Python: >=3.10
25
26
  Requires-Dist: langchain-core<1.2.7,>=1.0.0
26
27
  Requires-Dist: langchain<1.2.7,>=1.0.0
27
28
  Requires-Dist: pydantic>=2.0.0
@@ -1,9 +1,9 @@
1
1
  youngjin_langchain_tools/__init__.py,sha256=S5GJtbYymhDuGtTibEG61Li9UvwvoFtnKm4mxjWNgnU,1310
2
2
  youngjin_langchain_tools/handlers/__init__.py,sha256=-vGk-m1fqOipJSe02ogcScE0K3pdVwO9A_EqBovPRxo,397
3
- youngjin_langchain_tools/handlers/streamlit_langgraph_handler.py,sha256=hd509HM1SjAhPRxXqZh1we_ynDRetXrrB9TFSENJWY8,18423
3
+ youngjin_langchain_tools/handlers/streamlit_langgraph_handler.py,sha256=TDWjcgthh9eFPnufELbo4gzjUCCSYro6fUSRpwSXYuY,21540
4
4
  youngjin_langchain_tools/utils/__init__.py,sha256=LgSd7Gz2n5WgIhxaKHqpmQVUaBUDT_TTTw63hzoWGgM,272
5
5
  youngjin_langchain_tools/utils/config.py,sha256=hHvdxsn5ZFWRJvR6N7ODy94JbCkeJocsS58hmvxoK5I,1640
6
- youngjin_langchain_tools-0.1.1.dist-info/METADATA,sha256=3KyJIb5zWwslYNBXcZFRbyOT_CZP2q-0sx3DzMSAYoI,6920
7
- youngjin_langchain_tools-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
- youngjin_langchain_tools-0.1.1.dist-info/licenses/LICENSE,sha256=fENUlkDDxJEn5c0u8mhVcz5ek-rTg2L4Kby1tMNchI8,11342
9
- youngjin_langchain_tools-0.1.1.dist-info/RECORD,,
6
+ youngjin_langchain_tools-0.1.3.dist-info/METADATA,sha256=0hGrXQcNkFakDVdfvVdk14CXuciF7f5GR_RXP0QjM10,6959
7
+ youngjin_langchain_tools-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
+ youngjin_langchain_tools-0.1.3.dist-info/licenses/LICENSE,sha256=fENUlkDDxJEn5c0u8mhVcz5ek-rTg2L4Kby1tMNchI8,11342
9
+ youngjin_langchain_tools-0.1.3.dist-info/RECORD,,