codebuff-agent 2.1.74__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.
Files changed (217) hide show
  1. codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  2. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
  3. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  4. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
  5. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
  6. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
  7. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
  8. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
  9. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
  10. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
  11. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  12. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  13. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  14. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
  15. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
  16. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
  17. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
  18. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
  19. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  20. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  21. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
  22. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
  23. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  24. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
  25. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
  26. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
  27. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
  28. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  29. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  30. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  31. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  32. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
  33. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
  34. codebuff_agent-2.1.74.dist-info/METADATA +142 -0
  35. codebuff_agent-2.1.74.dist-info/RECORD +217 -0
  36. codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
  37. codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
  38. codebuff_core/__init__.py +153 -0
  39. codebuff_core/codebuff_config_defaults.json +64 -0
  40. codebuff_core/core/__init__.py +35 -0
  41. codebuff_core/core/code_validator.py +1238 -0
  42. codebuff_core/core/error_classifier.py +579 -0
  43. codebuff_core/core/exceptions.py +49 -0
  44. codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
  45. codebuff_core/directory_policy.py +32 -0
  46. codebuff_core/factory.py +155 -0
  47. codebuff_core/interfaces.py +197 -0
  48. codebuff_core/knowledge/__init__.py +31 -0
  49. codebuff_core/knowledge/chunking.py +375 -0
  50. codebuff_core/knowledge/loader.py +339 -0
  51. codebuff_core/knowledge/watchdog_service.py +354 -0
  52. codebuff_core/legacy_paths.py +69 -0
  53. codebuff_core/llm/__init__.py +13 -0
  54. codebuff_core/llm/service.py +407 -0
  55. codebuff_core/managers/__init__.py +22 -0
  56. codebuff_core/managers/config_manager.py +690 -0
  57. codebuff_core/managers/remote_config.py +673 -0
  58. codebuff_core/managers/remote_skills.py +539 -0
  59. codebuff_core/managers/session_manager.py +255 -0
  60. codebuff_core/middleware/__init__.py +3 -0
  61. codebuff_core/middleware/error_recovery.py +394 -0
  62. codebuff_core/middleware/logging_utils.py +33 -0
  63. codebuff_core/models/__init__.py +111 -0
  64. codebuff_core/models/agent.py +306 -0
  65. codebuff_core/models/chat.py +57 -0
  66. codebuff_core/models/common.py +82 -0
  67. codebuff_core/models/rag.py +300 -0
  68. codebuff_core/platform_rules.py +127 -0
  69. codebuff_core/prompts/__init__.py +44 -0
  70. codebuff_core/prompts/auto_agent_prompts.py +1278 -0
  71. codebuff_core/services/__init__.py +30 -0
  72. codebuff_core/services/agent_service.py +345 -0
  73. codebuff_core/services/analytics_aggregate.py +837 -0
  74. codebuff_core/services/chat_service.py +898 -0
  75. codebuff_core/services/context_condenser.py +306 -0
  76. codebuff_core/services/pii_guardrail.py +393 -0
  77. codebuff_core/services/rag_chat_agent.py +2993 -0
  78. codebuff_core/services/rag_collections.py +2740 -0
  79. codebuff_core/services/rag_manager.py +890 -0
  80. codebuff_core/services/rag_service.py +145 -0
  81. codebuff_core/services/retriever.py +260 -0
  82. codebuff_core/services/vllm_embedding_service.py +290 -0
  83. codebuff_core/tests/__init__.py +1 -0
  84. codebuff_core/tests/conftest.py +81 -0
  85. codebuff_core/tests/test_error_recovery.py +647 -0
  86. codebuff_core/tests/test_factory.py +135 -0
  87. codebuff_core/tests/test_services.py +150 -0
  88. jupyter_ext/__init__.py +266 -0
  89. jupyter_ext/_version.py +4 -0
  90. jupyter_ext/agent/__init__.py +49 -0
  91. jupyter_ext/agent/agent_factory.py +2270 -0
  92. jupyter_ext/agent/ast_tools.py +451 -0
  93. jupyter_ext/agent/autofix_middleware.py +481 -0
  94. jupyter_ext/agent/background_procs.py +372 -0
  95. jupyter_ext/agent/code_history_middleware.py +211 -0
  96. jupyter_ext/agent/codex_autofix.py +160 -0
  97. jupyter_ext/agent/codex_bridge.py +3339 -0
  98. jupyter_ext/agent/codex_hooks.py +320 -0
  99. jupyter_ext/agent/codex_slash.py +685 -0
  100. jupyter_ext/agent/codex_threads.py +114 -0
  101. jupyter_ext/agent/content_injection.py +377 -0
  102. jupyter_ext/agent/continuation_middleware.py +548 -0
  103. jupyter_ext/agent/custom_skills.py +335 -0
  104. jupyter_ext/agent/file_diffs.py +99 -0
  105. jupyter_ext/agent/git_history.py +188 -0
  106. jupyter_ext/agent/guardrail_middleware.py +421 -0
  107. jupyter_ext/agent/json_repair_utils.py +54 -0
  108. jupyter_ext/agent/langchain_tools.py +2673 -0
  109. jupyter_ext/agent/mermaid_fixer.py +289 -0
  110. jupyter_ext/agent/mermaid_middleware.py +135 -0
  111. jupyter_ext/agent/middleware.py +359 -0
  112. jupyter_ext/agent/middleware_prompts.py +53 -0
  113. jupyter_ext/agent/observability.py +334 -0
  114. jupyter_ext/agent/patch_tool_calls.py +57 -0
  115. jupyter_ext/agent/prompts.py +112 -0
  116. jupyter_ext/agent/skill_middleware.py +257 -0
  117. jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
  118. jupyter_ext/agent/skills/anomaly_detection.md +12 -0
  119. jupyter_ext/agent/skills/athena_sql.md +156 -0
  120. jupyter_ext/agent/skills/bdp_table_search.md +33 -0
  121. jupyter_ext/agent/skills/bigquery.md +65 -0
  122. jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
  123. jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
  124. jupyter_ext/agent/skills/classification_evaluation.md +11 -0
  125. jupyter_ext/agent/skills/clustering_analysis.md +12 -0
  126. jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
  127. jupyter_ext/agent/skills/data_analysis.md +236 -0
  128. jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
  129. jupyter_ext/agent/skills/data_loading.md +158 -0
  130. jupyter_ext/agent/skills/data_quality_report.md +13 -0
  131. jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
  132. jupyter_ext/agent/skills/emr_script_development.md +17 -0
  133. jupyter_ext/agent/skills/experiment_tracking.md +13 -0
  134. jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
  135. jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
  136. jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
  137. jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
  138. jupyter_ext/agent/skills/inference.md +392 -0
  139. jupyter_ext/agent/skills/lambda_function_development.md +19 -0
  140. jupyter_ext/agent/skills/model_comparison.md +11 -0
  141. jupyter_ext/agent/skills/model_explainability.md +12 -0
  142. jupyter_ext/agent/skills/model_training.md +318 -0
  143. jupyter_ext/agent/skills/pyspark.md +352 -0
  144. jupyter_ext/agent/skills/python_code_quality.md +35 -0
  145. jupyter_ext/agent/skills/regression_evaluation.md +12 -0
  146. jupyter_ext/agent/skills/spring_java_development.md +35 -0
  147. jupyter_ext/agent/skills/text_preprocessing.md +12 -0
  148. jupyter_ext/agent/skills/time_series_analysis.md +13 -0
  149. jupyter_ext/agent/skills/unit_testing.md +203 -0
  150. jupyter_ext/agent/todo_prompts.py +57 -0
  151. jupyter_ext/agent/tool_report.py +45 -0
  152. jupyter_ext/analytics_ingest.py +599 -0
  153. jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  154. jupyter_ext/handlers/__init__.py +8 -0
  155. jupyter_ext/handlers/analytics_handler.py +170 -0
  156. jupyter_ext/handlers/benchmark_handler.py +794 -0
  157. jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
  158. jupyter_ext/handlers/rag_admin_handler.py +689 -0
  159. jupyter_ext/handlers/websocket.py +497 -0
  160. jupyter_ext/http_handlers.py +8744 -0
  161. jupyter_ext/idle_monitor.py +346 -0
  162. jupyter_ext/labextension/package.json +157 -0
  163. jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  164. jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
  165. jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
  166. jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
  167. jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
  168. jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
  169. jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
  170. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
  171. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  172. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  173. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  174. jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
  175. jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
  176. jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
  177. jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
  178. jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
  179. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  180. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  181. jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
  182. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
  183. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  184. jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
  185. jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
  186. jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
  187. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
  188. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  189. jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  190. jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  191. jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  192. jupyter_ext/labextension/static/style.js +4 -0
  193. jupyter_ext/labextension/static/third-party-licenses.json +724 -0
  194. jupyter_ext/log_paths.py +113 -0
  195. jupyter_ext/resource_usage.py +519 -0
  196. jupyter_ext/secrets_bootstrap.py +116 -0
  197. jupyter_ext/team_access.py +105 -0
  198. jupyter_ext/tests/__init__.py +1 -0
  199. jupyter_ext/tests/test_context.py +234 -0
  200. jupyter_ext/tests/test_factory.py +563 -0
  201. jupyter_ext/tests/test_file_finder.py +139 -0
  202. jupyter_ext/tests/test_handlers.py +58 -0
  203. jupyter_ext/tests/test_mcp_selftest.py +204 -0
  204. jupyter_ext/tests/test_mermaid_fixer.py +272 -0
  205. jupyter_ext/tests/test_resource_tiers.py +298 -0
  206. jupyter_ext/tools/__init__.py +15 -0
  207. jupyter_ext/tools/base.py +90 -0
  208. jupyter_ext/tools/context.py +268 -0
  209. jupyter_ext/tools/dir_complete.py +86 -0
  210. jupyter_ext/tools/file_finder.py +303 -0
  211. jupyter_ext/tools/file_tools.py +244 -0
  212. jupyter_ext/tools/jupyter_tools.py +296 -0
  213. jupyter_ext/tools/registry.py +159 -0
  214. jupyter_ext/tools/search_tools.py +230 -0
  215. jupyter_ext/tools/security_tools.py +657 -0
  216. jupyter_ext/tools/shell_tools.py +109 -0
  217. jupyter_ext/tools/user_tools.py +132 -0
@@ -0,0 +1,579 @@
1
+ """
2
+ Error Classifier - 결정론적 에러 분류 및 Replan 결정
3
+ LLM 호출 없이 에러 타입 기반으로 refine/insert_steps/replace_step/replan_remaining 결정
4
+ 토큰 절약: ~1,000-2,000 토큰/세션
5
+
6
+ LLM Fallback 조건:
7
+ 1. 동일 에러로 REFINE 2회 이상 실패
8
+ 2. 패턴 매핑에 없는 미지의 에러 타입
9
+ 3. 복잡한 에러 (트레이스백에 2개 이상 Exception)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import re
16
+ from dataclasses import dataclass, field
17
+ from enum import Enum
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ from codebuff_core.prompts.auto_agent_prompts import PIP_INDEX_OPTION
21
+
22
+
23
+ class ReplanDecision(Enum):
24
+ """Replan 결정 타입"""
25
+
26
+ REFINE = "refine" # 같은 접근법으로 코드만 수정
27
+ INSERT_STEPS = "insert_steps" # 선행 작업 추가 (패키지 설치 등)
28
+ REPLACE_STEP = "replace_step" # 완전히 다른 접근법으로 교체
29
+ REPLAN_REMAINING = "replan_remaining" # 남은 단계 모두 재계획
30
+
31
+
32
+ @dataclass
33
+ class ErrorAnalysis:
34
+ """에러 분석 결과"""
35
+
36
+ decision: ReplanDecision
37
+ root_cause: str
38
+ reasoning: str
39
+ missing_package: Optional[str] = None
40
+ changes: Dict[str, Any] = field(default_factory=dict)
41
+ used_llm: bool = False # LLM Fallback 사용 여부
42
+ confidence: float = 1.0 # 분석 신뢰도 (패턴 매칭=1.0, LLM=0.0~1.0)
43
+
44
+ def to_dict(self) -> Dict[str, Any]:
45
+ """API 응답용 딕셔너리 변환"""
46
+ return {
47
+ "analysis": {
48
+ "root_cause": self.root_cause,
49
+ "is_approach_problem": self.decision
50
+ in (ReplanDecision.REPLACE_STEP, ReplanDecision.REPLAN_REMAINING),
51
+ "missing_prerequisites": [self.missing_package]
52
+ if self.missing_package
53
+ else [],
54
+ },
55
+ "decision": self.decision.value,
56
+ "reasoning": self.reasoning,
57
+ "changes": self.changes,
58
+ "usedLlm": self.used_llm,
59
+ "confidence": self.confidence,
60
+ }
61
+
62
+
63
+ class ErrorClassifier:
64
+ """
65
+ 결정론적 에러 분류기 (LLM 호출 없음)
66
+ 에러 타입 기반으로 replan 결정을 자동으로 수행
67
+
68
+ 규칙:
69
+ - ModuleNotFoundError/ImportError → 무조건 INSERT_STEPS (pip install)
70
+ - SyntaxError/TypeError/ValueError 등 → REFINE (코드 수정)
71
+ - FileNotFoundError → REFINE (경로 수정 시도)
72
+ """
73
+
74
+ # 패키지명 별칭 매핑 (import명 → pip 패키지명)
75
+ PACKAGE_ALIASES: Dict[str, str] = {
76
+ "sklearn": "scikit-learn",
77
+ "cv2": "opencv-python",
78
+ "PIL": "pillow",
79
+ "yaml": "pyyaml",
80
+ "bs4": "beautifulsoup4",
81
+ "skimage": "scikit-image",
82
+ "dotenv": "python-dotenv",
83
+ "dateutil": "python-dateutil",
84
+ }
85
+
86
+ # 에러 타입별 결정 매핑
87
+ ERROR_DECISION_MAP: Dict[str, ReplanDecision] = {
88
+ # INSERT_STEPS: 패키지 설치 필요
89
+ "ModuleNotFoundError": ReplanDecision.INSERT_STEPS,
90
+ "ImportError": ReplanDecision.INSERT_STEPS,
91
+ # REFINE: 코드 수정으로 해결 가능
92
+ "SyntaxError": ReplanDecision.REFINE,
93
+ "TypeError": ReplanDecision.REFINE,
94
+ "ValueError": ReplanDecision.REFINE,
95
+ "KeyError": ReplanDecision.REFINE,
96
+ "IndexError": ReplanDecision.REFINE,
97
+ "AttributeError": ReplanDecision.REFINE,
98
+ "NameError": ReplanDecision.REFINE,
99
+ "ZeroDivisionError": ReplanDecision.REFINE,
100
+ "FileNotFoundError": ReplanDecision.REFINE,
101
+ "PermissionError": ReplanDecision.REFINE,
102
+ "RuntimeError": ReplanDecision.REFINE,
103
+ "AssertionError": ReplanDecision.REFINE,
104
+ "StopIteration": ReplanDecision.REFINE,
105
+ "RecursionError": ReplanDecision.REFINE,
106
+ "MemoryError": ReplanDecision.REFINE,
107
+ "OverflowError": ReplanDecision.REFINE,
108
+ "FloatingPointError": ReplanDecision.REFINE,
109
+ "UnicodeError": ReplanDecision.REFINE,
110
+ "UnicodeDecodeError": ReplanDecision.REFINE,
111
+ "UnicodeEncodeError": ReplanDecision.REFINE,
112
+ "OSError": ReplanDecision.REFINE, # 기본값, dlopen은 별도 처리
113
+ }
114
+
115
+ # dlopen 에러 패턴 (시스템 라이브러리 누락)
116
+ DLOPEN_ERROR_PATTERNS = [
117
+ r"dlopen\([^)]+\).*Library not loaded.*?(\w+\.dylib)", # macOS
118
+ r"cannot open shared object file.*?lib(\w+)\.so", # Linux
119
+ r"DLL load failed.*?(\w+\.dll)", # Windows
120
+ ]
121
+
122
+ # ModuleNotFoundError 추출 패턴
123
+ MODULE_ERROR_PATTERNS = [
124
+ r"ModuleNotFoundError: No module named ['\"]([^'\"]+)['\"]",
125
+ r"ImportError: No module named ['\"]([^'\"]+)['\"]",
126
+ r"ImportError: cannot import name ['\"]([^'\"]+)['\"]",
127
+ r"No module named ['\"]([^'\"]+)['\"]",
128
+ ]
129
+
130
+ def __init__(self, pip_index_option: str = None):
131
+ """
132
+ Args:
133
+ pip_index_option: pip install 시 사용할 인덱스 옵션 (환경별)
134
+ """
135
+ self.pip_index_option = pip_index_option or PIP_INDEX_OPTION
136
+
137
+ def classify(
138
+ self,
139
+ error_type: str,
140
+ error_message: str,
141
+ traceback: str = "",
142
+ installed_packages: List[str] = None,
143
+ ) -> ErrorAnalysis:
144
+ """
145
+ 에러를 분류하고 replan 결정 반환
146
+
147
+ Args:
148
+ error_type: 에러 타입 (예: 'ModuleNotFoundError')
149
+ error_message: 에러 메시지
150
+ traceback: 스택 트레이스
151
+ installed_packages: 설치된 패키지 목록
152
+
153
+ Returns:
154
+ ErrorAnalysis: 에러 분석 결과 및 replan 결정
155
+ """
156
+ installed_packages = installed_packages or []
157
+ installed_lower = {pkg.lower() for pkg in installed_packages}
158
+
159
+ # Step 0: 일반 타입('runtime' 등)일 경우 traceback에서 실제 에러 추출
160
+ if error_type in ("runtime", "timeout", "safety", "validation", "environment"):
161
+ actual_error_type = self._extract_error_type_from_traceback(
162
+ traceback, error_message
163
+ )
164
+ if actual_error_type:
165
+ error_type = actual_error_type
166
+
167
+ # Step 1: 에러 타입 정규화
168
+ error_type_normalized = self._normalize_error_type(error_type)
169
+
170
+ # Step 2: ModuleNotFoundError/ImportError 특별 처리
171
+ if error_type_normalized in ("ModuleNotFoundError", "ImportError"):
172
+ return self._handle_module_error(error_message, traceback, installed_lower)
173
+
174
+ # Step 2.5: OSError 중 dlopen 에러 특별 처리
175
+ if error_type_normalized == "OSError":
176
+ return self._handle_os_error(error_message, traceback)
177
+
178
+ # Step 3: 에러 타입 기반 결정
179
+ decision = self.ERROR_DECISION_MAP.get(
180
+ error_type_normalized,
181
+ ReplanDecision.REFINE, # 기본값: REFINE
182
+ )
183
+
184
+ return ErrorAnalysis(
185
+ decision=decision,
186
+ root_cause=self._get_error_description(
187
+ error_type_normalized, error_message
188
+ ),
189
+ reasoning=f"{error_type_normalized}는 코드 수정으로 해결 가능합니다.",
190
+ changes={"refined_code": None}, # LLM이 코드 생성
191
+ )
192
+
193
+ def _normalize_error_type(self, error_type: str) -> str:
194
+ """에러 타입 정규화"""
195
+ if not error_type:
196
+ return "RuntimeError"
197
+
198
+ # 'ModuleNotFoundError: ...' 형태에서 타입만 추출
199
+ if ":" in error_type:
200
+ error_type = error_type.split(":")[0].strip()
201
+
202
+ # 전체 경로에서 클래스명만 추출 (예: 'builtins.ValueError' → 'ValueError')
203
+ if "." in error_type:
204
+ error_type = error_type.split(".")[-1]
205
+
206
+ return error_type
207
+
208
+ def _extract_error_type_from_traceback(
209
+ self, traceback: str, error_message: str
210
+ ) -> Optional[str]:
211
+ """
212
+ traceback에서 실제 Python 에러 타입 추출
213
+
214
+ 프론트엔드가 error.type을 'runtime'으로 보낼 때,
215
+ traceback에서 실제 에러 타입(ModuleNotFoundError, ImportError 등)을 찾음
216
+
217
+ Args:
218
+ traceback: 스택 트레이스 문자열
219
+ error_message: 에러 메시지
220
+
221
+ Returns:
222
+ 추출된 에러 타입 (예: 'ModuleNotFoundError') 또는 None
223
+ """
224
+ if not traceback:
225
+ return None
226
+
227
+ # traceback의 마지막 줄에서 에러 타입 추출
228
+ # 형식: "ModuleNotFoundError: No module named 'matplotlib'"
229
+ lines = traceback.strip().split("\n")
230
+
231
+ # 뒤에서부터 에러 타입 라인 찾기
232
+ for line in reversed(lines):
233
+ line = line.strip()
234
+
235
+ # ANSI 색상 코드 제거
236
+ line = re.sub(r"\x1b\[[0-9;]*m", "", line)
237
+
238
+ # Python 에러 타입 패턴 매칭
239
+ # 예: "ModuleNotFoundError: ..." 또는 "ModuleNotFoundError Traceback..."
240
+ error_pattern = r"^([A-Z][a-zA-Z0-9]*Error|[A-Z][a-zA-Z0-9]*Exception)[\s:]"
241
+ match = re.match(error_pattern, line)
242
+
243
+ if match:
244
+ return match.group(1)
245
+
246
+ return None
247
+
248
+ def _handle_module_error(
249
+ self, error_message: str, traceback: str, installed_packages: set
250
+ ) -> ErrorAnalysis:
251
+ """
252
+ ModuleNotFoundError/ImportError 처리
253
+
254
+ CRITICAL: 에러 메시지에서 패키지명 추출 (사용자 코드 아님!)
255
+ """
256
+ full_text = f"{error_message}\n{traceback}"
257
+
258
+ # 패키지명 추출
259
+ missing_pkg = self._extract_missing_package(full_text)
260
+
261
+ if not missing_pkg:
262
+ # 패키지명을 찾지 못한 경우 REFINE으로 폴백
263
+ return ErrorAnalysis(
264
+ decision=ReplanDecision.REFINE,
265
+ root_cause="Import 에러 발생, 패키지명 추출 실패",
266
+ reasoning="패키지명을 특정할 수 없어 코드 수정 시도",
267
+ changes={"refined_code": None},
268
+ )
269
+
270
+ # pip 패키지명으로 변환
271
+ pip_pkg = self._get_pip_package_name(missing_pkg)
272
+
273
+ # 이미 설치된 패키지인지 확인
274
+ if pip_pkg.lower() in installed_packages:
275
+ # 패키지는 설치되어 있지만 import 실패 → 코드 문제
276
+ return ErrorAnalysis(
277
+ decision=ReplanDecision.REFINE,
278
+ root_cause=f"'{missing_pkg}' import 실패 (패키지는 이미 설치됨)",
279
+ reasoning="패키지가 설치되어 있으므로 import 구문 또는 코드 수정 필요",
280
+ changes={"refined_code": None},
281
+ )
282
+
283
+ # pip install 코드 생성
284
+ pip_command = self._generate_pip_install(pip_pkg)
285
+
286
+ return ErrorAnalysis(
287
+ decision=ReplanDecision.INSERT_STEPS,
288
+ root_cause=f"'{missing_pkg}' 모듈이 설치되지 않음",
289
+ reasoning="ModuleNotFoundError는 항상 패키지 설치로 해결합니다.",
290
+ missing_package=pip_pkg,
291
+ changes={
292
+ "new_steps": [
293
+ {
294
+ "description": f"{pip_pkg} 패키지 설치",
295
+ "toolCalls": [
296
+ {
297
+ "tool": "jupyter_cell",
298
+ "parameters": {"code": pip_command},
299
+ }
300
+ ],
301
+ }
302
+ ]
303
+ },
304
+ )
305
+
306
+ def _handle_os_error(
307
+ self,
308
+ error_message: str,
309
+ traceback: str,
310
+ ) -> ErrorAnalysis:
311
+ """
312
+ OSError 처리 - dlopen 에러 감지
313
+ """
314
+ full_text = f"{error_message}\n{traceback}"
315
+
316
+ # dlopen 에러 패턴 확인
317
+ for pattern in self.DLOPEN_ERROR_PATTERNS:
318
+ match = re.search(pattern, full_text, re.IGNORECASE | re.DOTALL)
319
+ if match:
320
+ missing_lib = match.group(1) if match.groups() else "unknown"
321
+ return ErrorAnalysis(
322
+ decision=ReplanDecision.REPLAN_REMAINING,
323
+ root_cause=f"시스템 라이브러리 누락: {missing_lib}",
324
+ reasoning="dlopen 에러는 시스템 라이브러리 문제입니다. pip으로 해결할 수 없으며, 시스템 패키지 관리자(brew/apt)로 설치가 필요합니다.",
325
+ changes={"system_dependency": missing_lib},
326
+ )
327
+
328
+ # 일반 OSError는 REFINE
329
+ return ErrorAnalysis(
330
+ decision=ReplanDecision.REFINE,
331
+ root_cause=f"OSError: {error_message[:150]}",
332
+ reasoning="일반 OSError는 코드 수정으로 해결을 시도합니다.",
333
+ changes={"refined_code": None},
334
+ )
335
+
336
+ def _extract_missing_package(self, text: str) -> Optional[str]:
337
+ """에러 메시지에서 누락된 패키지명 추출"""
338
+ for pattern in self.MODULE_ERROR_PATTERNS:
339
+ match = re.search(pattern, text, re.IGNORECASE)
340
+ if match:
341
+ pkg = match.group(1)
342
+ # 최상위 패키지만 반환 (예: 'pyarrow.lib' → 'pyarrow')
343
+ return pkg.split(".")[0]
344
+ return None
345
+
346
+ def _get_pip_package_name(self, import_name: str) -> str:
347
+ """import 이름을 pip 패키지명으로 변환"""
348
+ return self.PACKAGE_ALIASES.get(import_name, import_name)
349
+
350
+ def _generate_pip_install(self, package: str) -> str:
351
+ """pip install 명령 생성"""
352
+ if self.pip_index_option:
353
+ return f"!pip install {self.pip_index_option} --timeout 180 {package}"
354
+ return f"!pip install --timeout 180 {package}"
355
+
356
+ def _get_error_description(self, error_type: str, error_msg: str) -> str:
357
+ """에러 타입별 설명 생성"""
358
+ descriptions = {
359
+ "SyntaxError": "문법 오류",
360
+ "TypeError": "타입 불일치",
361
+ "ValueError": "값 오류",
362
+ "KeyError": "딕셔너리/데이터프레임 키 없음",
363
+ "IndexError": "인덱스 범위 초과",
364
+ "AttributeError": "속성/메서드 없음",
365
+ "NameError": "변수 미정의",
366
+ "FileNotFoundError": "파일을 찾을 수 없음",
367
+ "ZeroDivisionError": "0으로 나누기",
368
+ "PermissionError": "권한 없음",
369
+ "RuntimeError": "런타임 에러",
370
+ "MemoryError": "메모리 부족",
371
+ }
372
+ base = descriptions.get(error_type, error_type)
373
+ # 에러 메시지에서 핵심 부분만 추출 (150자 제한)
374
+ msg_preview = error_msg[:150] if error_msg else ""
375
+ return f"{base}: {msg_preview}"
376
+
377
+ # =========================================================================
378
+ # LLM Fallback 관련 메서드
379
+ # =========================================================================
380
+
381
+ def _count_exceptions_in_traceback(self, traceback: str) -> int:
382
+ """트레이스백에서 Exception 개수 카운트"""
383
+ if not traceback:
384
+ return 0
385
+ # 다양한 Exception 패턴 매칭
386
+ patterns = [
387
+ r"\b\w+Error\b", # ValueError, TypeError 등
388
+ r"\b\w+Exception\b", # CustomException 등
389
+ r"During handling of the above exception", # 연쇄 예외
390
+ ]
391
+ count = 0
392
+ for pattern in patterns:
393
+ count += len(re.findall(pattern, traceback))
394
+ return count
395
+
396
+ def should_use_llm_fallback(
397
+ self,
398
+ error_type: str,
399
+ traceback: str = "",
400
+ previous_attempts: int = 0,
401
+ ) -> tuple[bool, str]:
402
+ """
403
+ LLM Fallback 사용 여부 결정
404
+
405
+ Returns:
406
+ (should_use: bool, reason: str)
407
+ """
408
+ # 조건 1: 동일 에러로 REFINE 2회 이상 실패
409
+ if previous_attempts >= 2:
410
+ return True, f"동일 에러 {previous_attempts}회 실패 후 LLM 분석 필요"
411
+
412
+ # 조건 2: 패턴 매핑에 없는 미지의 에러 타입
413
+ error_type_normalized = self._normalize_error_type(error_type)
414
+ if error_type_normalized not in self.ERROR_DECISION_MAP:
415
+ return True, f"미지의 에러 타입: {error_type_normalized}"
416
+
417
+ # 조건 3: 복잡한 에러 (트레이스백에 2개 이상 Exception)
418
+ exception_count = self._count_exceptions_in_traceback(traceback)
419
+ if exception_count >= 2:
420
+ return True, f"복잡한 에러 (트레이스백에 {exception_count}개 Exception)"
421
+
422
+ return False, ""
423
+
424
+ async def classify_with_fallback(
425
+ self,
426
+ error_type: str,
427
+ error_message: str,
428
+ traceback: str = "",
429
+ installed_packages: List[str] = None,
430
+ previous_attempts: int = 0,
431
+ previous_codes: List[str] = None,
432
+ llm_client=None,
433
+ model: str = "gpt-4o-mini",
434
+ ) -> ErrorAnalysis:
435
+ """
436
+ 패턴 매칭 우선, 조건 충족 시 LLM Fallback
437
+
438
+ Args:
439
+ error_type: 에러 타입
440
+ error_message: 에러 메시지
441
+ traceback: 스택 트레이스
442
+ installed_packages: 설치된 패키지 목록
443
+ previous_attempts: 이전 시도 횟수
444
+ previous_codes: 이전에 시도한 코드들
445
+ llm_client: LLM 클라이언트 (AsyncOpenAI 호환)
446
+ model: 사용할 모델명
447
+
448
+ Returns:
449
+ ErrorAnalysis: 에러 분석 결과
450
+ """
451
+ # Step 1: LLM Fallback 필요 여부 확인
452
+ should_use_llm, fallback_reason = self.should_use_llm_fallback(
453
+ error_type, traceback, previous_attempts
454
+ )
455
+
456
+ # Step 2: 패턴 매칭 우선 시도
457
+ if not should_use_llm:
458
+ return self.classify(
459
+ error_type, error_message, traceback, installed_packages
460
+ )
461
+
462
+ # Step 3: LLM Fallback
463
+ if llm_client is None:
464
+ # LLM 클라이언트 없으면 패턴 매칭으로 폴백
465
+ print(
466
+ f"[ErrorClassifier] LLM 클라이언트 없음, 패턴 매칭 사용: {fallback_reason}"
467
+ )
468
+ return self.classify(
469
+ error_type, error_message, traceback, installed_packages
470
+ )
471
+
472
+ print(f"[ErrorClassifier] LLM Fallback 사용: {fallback_reason}")
473
+ return await self._classify_with_llm(
474
+ error_type=error_type,
475
+ error_message=error_message,
476
+ traceback=traceback,
477
+ previous_attempts=previous_attempts,
478
+ previous_codes=previous_codes or [],
479
+ llm_client=llm_client,
480
+ model=model,
481
+ )
482
+
483
+ async def _classify_with_llm(
484
+ self,
485
+ error_type: str,
486
+ error_message: str,
487
+ traceback: str,
488
+ previous_attempts: int,
489
+ previous_codes: List[str],
490
+ llm_client,
491
+ model: str,
492
+ ) -> ErrorAnalysis:
493
+ """LLM을 사용한 에러 분석"""
494
+ from codebuff_core.prompts.auto_agent_prompts import (
495
+ format_error_analysis_prompt,
496
+ )
497
+
498
+ prompt = format_error_analysis_prompt(
499
+ error_type=error_type,
500
+ error_message=error_message,
501
+ traceback=traceback,
502
+ previous_attempts=previous_attempts,
503
+ previous_codes=previous_codes,
504
+ )
505
+
506
+ try:
507
+ response = await llm_client.chat.completions.create(
508
+ model=model,
509
+ messages=[{"role": "user", "content": prompt}],
510
+ temperature=0.3,
511
+ max_tokens=2000,
512
+ )
513
+
514
+ content = response.choices[0].message.content
515
+ return self._parse_llm_response(content)
516
+
517
+ except Exception as e:
518
+ print(f"[ErrorClassifier] LLM 호출 실패: {e}")
519
+ # LLM 실패 시 패턴 매칭으로 폴백
520
+ result = self.classify(error_type, error_message, traceback, [])
521
+ result.reasoning += f" (LLM 실패로 패턴 매칭 사용: {str(e)[:50]})"
522
+ return result
523
+
524
+ def _parse_llm_response(self, content: str) -> ErrorAnalysis:
525
+ """LLM 응답 파싱"""
526
+ try:
527
+ # JSON 블록 추출
528
+ json_match = re.search(r"```json\s*([\s\S]*?)\s*```", content)
529
+ if json_match:
530
+ json_str = json_match.group(1)
531
+ else:
532
+ json_str = content
533
+
534
+ data = json.loads(json_str)
535
+
536
+ # decision 파싱
537
+ decision_str = data.get("decision", "refine")
538
+ decision_map = {
539
+ "refine": ReplanDecision.REFINE,
540
+ "insert_steps": ReplanDecision.INSERT_STEPS,
541
+ "replace_step": ReplanDecision.REPLACE_STEP,
542
+ "replan_remaining": ReplanDecision.REPLAN_REMAINING,
543
+ }
544
+ decision = decision_map.get(decision_str, ReplanDecision.REFINE)
545
+
546
+ # confidence 추출
547
+ confidence = float(data.get("confidence", 0.8))
548
+
549
+ return ErrorAnalysis(
550
+ decision=decision,
551
+ root_cause=data.get("analysis", {}).get("root_cause", "LLM 분석 결과"),
552
+ reasoning=data.get("reasoning", "LLM 분석 기반 결정"),
553
+ changes=data.get("changes", {}),
554
+ used_llm=True,
555
+ confidence=confidence,
556
+ )
557
+
558
+ except (json.JSONDecodeError, KeyError, ValueError) as e:
559
+ print(f"[ErrorClassifier] LLM 응답 파싱 실패: {e}")
560
+ return ErrorAnalysis(
561
+ decision=ReplanDecision.REFINE,
562
+ root_cause="LLM 응답 파싱 실패",
563
+ reasoning=f"파싱 오류로 기본값(refine) 사용: {str(e)[:50]}",
564
+ changes={"refined_code": None},
565
+ used_llm=True,
566
+ confidence=0.3,
567
+ )
568
+
569
+
570
+ # 싱글톤 인스턴스
571
+ _error_classifier_instance: Optional[ErrorClassifier] = None
572
+
573
+
574
+ def get_error_classifier() -> ErrorClassifier:
575
+ """싱글톤 ErrorClassifier 반환"""
576
+ global _error_classifier_instance
577
+ if _error_classifier_instance is None:
578
+ _error_classifier_instance = ErrorClassifier()
579
+ return _error_classifier_instance
@@ -0,0 +1,49 @@
1
+ """AgentError 계층 구조 — 에이전트 에러의 구조화된 분류."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class AgentError(Exception):
7
+ """에이전트 에러의 기본 클래스."""
8
+
9
+ error_type: str = "agent_error"
10
+
11
+ def __init__(self, message: str, *, cause: Exception | None = None):
12
+ super().__init__(message)
13
+ self.cause = cause
14
+
15
+
16
+ class LLMError(AgentError):
17
+ """LLM 호출 관련 에러."""
18
+
19
+ error_type: str = "llm_error"
20
+
21
+ def __init__(
22
+ self,
23
+ message: str,
24
+ *,
25
+ provider: str = "",
26
+ model: str = "",
27
+ cause: Exception | None = None,
28
+ ):
29
+ super().__init__(message, cause=cause)
30
+ self.provider = provider
31
+ self.model = model
32
+
33
+
34
+ class ToolError(AgentError):
35
+ """도구 실행 관련 에러."""
36
+
37
+ error_type: str = "tool_error"
38
+
39
+ def __init__(
40
+ self, message: str, *, tool_name: str = "", cause: Exception | None = None
41
+ ):
42
+ super().__init__(message, cause=cause)
43
+ self.tool_name = tool_name
44
+
45
+
46
+ class ValidationError(AgentError):
47
+ """입력/출력 검증 에러."""
48
+
49
+ error_type: str = "validation_error"