hdsp-jupyter-extension 2.0.0__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 (121) hide show
  1. agent_server/__init__.py +8 -0
  2. agent_server/core/__init__.py +92 -0
  3. agent_server/core/api_key_manager.py +427 -0
  4. agent_server/core/code_validator.py +1238 -0
  5. agent_server/core/context_condenser.py +308 -0
  6. agent_server/core/embedding_service.py +254 -0
  7. agent_server/core/error_classifier.py +577 -0
  8. agent_server/core/llm_client.py +95 -0
  9. agent_server/core/llm_service.py +649 -0
  10. agent_server/core/notebook_generator.py +274 -0
  11. agent_server/core/prompt_builder.py +35 -0
  12. agent_server/core/rag_manager.py +742 -0
  13. agent_server/core/reflection_engine.py +489 -0
  14. agent_server/core/retriever.py +248 -0
  15. agent_server/core/state_verifier.py +452 -0
  16. agent_server/core/summary_generator.py +484 -0
  17. agent_server/core/task_manager.py +198 -0
  18. agent_server/knowledge/__init__.py +9 -0
  19. agent_server/knowledge/watchdog_service.py +352 -0
  20. agent_server/main.py +160 -0
  21. agent_server/prompts/__init__.py +60 -0
  22. agent_server/prompts/file_action_prompts.py +113 -0
  23. agent_server/routers/__init__.py +9 -0
  24. agent_server/routers/agent.py +591 -0
  25. agent_server/routers/chat.py +188 -0
  26. agent_server/routers/config.py +100 -0
  27. agent_server/routers/file_resolver.py +293 -0
  28. agent_server/routers/health.py +42 -0
  29. agent_server/routers/rag.py +163 -0
  30. agent_server/schemas/__init__.py +60 -0
  31. hdsp_agent_core/__init__.py +158 -0
  32. hdsp_agent_core/factory.py +252 -0
  33. hdsp_agent_core/interfaces.py +203 -0
  34. hdsp_agent_core/knowledge/__init__.py +31 -0
  35. hdsp_agent_core/knowledge/chunking.py +356 -0
  36. hdsp_agent_core/knowledge/libraries/dask.md +188 -0
  37. hdsp_agent_core/knowledge/libraries/matplotlib.md +164 -0
  38. hdsp_agent_core/knowledge/libraries/polars.md +68 -0
  39. hdsp_agent_core/knowledge/loader.py +337 -0
  40. hdsp_agent_core/llm/__init__.py +13 -0
  41. hdsp_agent_core/llm/service.py +556 -0
  42. hdsp_agent_core/managers/__init__.py +22 -0
  43. hdsp_agent_core/managers/config_manager.py +133 -0
  44. hdsp_agent_core/managers/session_manager.py +251 -0
  45. hdsp_agent_core/models/__init__.py +115 -0
  46. hdsp_agent_core/models/agent.py +316 -0
  47. hdsp_agent_core/models/chat.py +41 -0
  48. hdsp_agent_core/models/common.py +95 -0
  49. hdsp_agent_core/models/rag.py +368 -0
  50. hdsp_agent_core/prompts/__init__.py +63 -0
  51. hdsp_agent_core/prompts/auto_agent_prompts.py +1260 -0
  52. hdsp_agent_core/prompts/cell_action_prompts.py +98 -0
  53. hdsp_agent_core/services/__init__.py +18 -0
  54. hdsp_agent_core/services/agent_service.py +438 -0
  55. hdsp_agent_core/services/chat_service.py +205 -0
  56. hdsp_agent_core/services/rag_service.py +262 -0
  57. hdsp_agent_core/tests/__init__.py +1 -0
  58. hdsp_agent_core/tests/conftest.py +102 -0
  59. hdsp_agent_core/tests/test_factory.py +251 -0
  60. hdsp_agent_core/tests/test_services.py +326 -0
  61. hdsp_jupyter_extension-2.0.0.data/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  62. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/build_log.json +738 -0
  63. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/install.json +5 -0
  64. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/package.json +134 -0
  65. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  66. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  67. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  68. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  69. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
  70. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
  71. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
  72. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
  73. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  74. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  75. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/style.js +4 -0
  76. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
  77. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
  78. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
  79. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  80. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
  81. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  82. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
  83. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
  84. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
  85. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
  86. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
  87. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
  88. hdsp_jupyter_extension-2.0.0.dist-info/METADATA +152 -0
  89. hdsp_jupyter_extension-2.0.0.dist-info/RECORD +121 -0
  90. hdsp_jupyter_extension-2.0.0.dist-info/WHEEL +4 -0
  91. hdsp_jupyter_extension-2.0.0.dist-info/licenses/LICENSE +21 -0
  92. jupyter_ext/__init__.py +233 -0
  93. jupyter_ext/_version.py +4 -0
  94. jupyter_ext/config.py +111 -0
  95. jupyter_ext/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  96. jupyter_ext/handlers.py +632 -0
  97. jupyter_ext/labextension/build_log.json +738 -0
  98. jupyter_ext/labextension/package.json +134 -0
  99. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  100. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  101. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  102. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  103. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
  104. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
  105. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
  106. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
  107. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  108. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  109. jupyter_ext/labextension/static/style.js +4 -0
  110. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
  111. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
  112. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
  113. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  114. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
  115. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  116. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
  117. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
  118. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
  119. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
  120. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
  121. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
@@ -0,0 +1,1260 @@
1
+ """
2
+ Auto-Agent Prompts
3
+ HuggingFace Jupyter Agent 패턴 기반 프롬프트 템플릿
4
+
5
+ Tool Calling 구조:
6
+ - jupyter_cell: 코드 셀 생성/수정/실행
7
+ - markdown: 마크다운 셀 생성/수정
8
+ - final_answer: 작업 완료 신호
9
+ - read_file: 파일 읽기 (상대 경로만)
10
+ - write_file: 파일 쓰기 (승인 필요)
11
+ - list_files: 디렉토리 조회
12
+ - execute_command: 셸 명령 실행 (위험 명령만 승인)
13
+ - search_files: 파일 내용 검색
14
+ """
15
+
16
+ import os
17
+
18
+ # ═══════════════════════════════════════════════════════════════════════════
19
+ # Nexus URL 설정 (보안을 위해 외부 파일에서 읽기)
20
+ # ═══════════════════════════════════════════════════════════════════════════
21
+
22
+
23
+ def _get_pip_index_option() -> str:
24
+ """
25
+ pip install 시 사용할 index-url 옵션 반환
26
+ - Sagemaker 환경: nexus-url.txt에서 읽어서 --index-url <url> 반환
27
+ - 로컬 환경: 빈 문자열 반환 (일반 pip install)
28
+ """
29
+ nexus_url_path = "/home/sagemaker-user/nexus-url.txt"
30
+
31
+ try:
32
+ if os.path.exists(nexus_url_path):
33
+ with open(nexus_url_path, "r") as f:
34
+ url = f.read().strip()
35
+ if url:
36
+ return f"--index-url {url}"
37
+ except Exception as e:
38
+ print(
39
+ f"[AutoAgent] Warning: Failed to load nexus URL from {nexus_url_path}: {e}"
40
+ )
41
+
42
+ # 파일이 없거나 읽기 실패 시: 일반 pip install (로컬 환경)
43
+ return ""
44
+
45
+
46
+ PIP_INDEX_OPTION = _get_pip_index_option()
47
+
48
+ # ═══════════════════════════════════════════════════════════════════════════
49
+ # 실행 계획 생성 프롬프트
50
+ # ═══════════════════════════════════════════════════════════════════════════
51
+
52
+ PLAN_GENERATION_PROMPT = """Jupyter 노트북 Python 전문가. 단계별 실행 계획을 JSON으로 생성.
53
+
54
+ ## 도구
55
+ ### 기본 도구 (셀 작업)
56
+ 1. **jupyter_cell**: {{"code": "Python코드"}} - 노트북 끝에 새 셀 추가
57
+ 2. **markdown**: {{"content": "마크다운"}} - 설명 셀 추가
58
+ 3. **final_answer**: {{"answer": "완료메시지"}} - 작업 완료
59
+
60
+ ### 확장 도구 (파일/터미널)
61
+ 4. **read_file**: {{"path": "상대경로"}} - 파일 읽기 (절대경로/.. 금지)
62
+ 5. **write_file**: {{"path": "상대경로", "content": "내용"}} - 파일 쓰기 (승인 필요)
63
+ 6. **list_files**: {{"path": ".", "recursive": false, "pattern": "*.py"}} - 디렉토리 조회
64
+ 7. **execute_command**: {{"command": "pip list"}} - 셸 명령 (위험 명령만 승인)
65
+ 8. **search_files**: {{"pattern": "def func", "path": "src"}} - 파일 내용 검색
66
+
67
+ ## 🚨 핵심 원칙 (CRITICAL!)
68
+ 1. ⛔ **기존 셀 수정 금지! 항상 새 셀을 노트북 끝에 추가**
69
+ 2. ⛔ **기존 변수(df 등)에 의존 금지! 새 셀은 독립적으로 실행 가능해야 함**
70
+ 3. ✅ **데이터 로딩/정의를 포함한 완전한 코드 작성** (기존 코드는 참고용)
71
+
72
+ ## 노트북 현황 (참고용 - 기존 변수 사용 금지!)
73
+ - 셀: {cell_count}개 | 라이브러리: {imported_libraries} | 변수: {defined_variables}
74
+ - 최근 셀 (참고용):
75
+ {recent_cells}
76
+
77
+ ## 환경: {available_libraries}
78
+
79
+ ## 요청: {request}
80
+
81
+ ## 규칙
82
+ 1. 최대 10단계, 마지막은 final_answer
83
+ 2. 한글 설명, 한자 금지
84
+ 3. 미설치 패키지: `!pip install {PIP_INDEX_OPTION} --timeout 180 패키지`
85
+ 4. 시각화 전 데이터 검증 필수
86
+ 5. 첫 셀에 warnings 필터링 + 필요한 import + 데이터 로딩 포함
87
+ 6. 기존 노트북 코드를 분석/개선할 때도 새 셀에서 처음부터 구현
88
+ 7. **시각화 코드에는 반드시 한글 폰트 설정 포함**:
89
+ ```python
90
+ import matplotlib.pyplot as plt
91
+ plt.rcParams['font.family'] = 'AppleGothic'
92
+ plt.rcParams['axes.unicode_minus'] = False
93
+ ```
94
+
95
+ ## JSON 출력
96
+ ```json
97
+ {{"reasoning":"이유","plan":{{"totalSteps":N,"steps":[{{"stepNumber":1,"description":"설명","toolCalls":[{{"tool":"jupyter_cell","parameters":{{"code":"코드"}}}}],"dependencies":[]}}]}}}}
98
+ ```
99
+ JSON만 출력."""
100
+
101
+
102
+ # ═══════════════════════════════════════════════════════════════════════════
103
+ # 코드 생성 프롬프트 (단일 셀)
104
+ # ═══════════════════════════════════════════════════════════════════════════
105
+
106
+ CODE_GENERATION_PROMPT = """당신은 Jupyter 노트북을 위한 Python 코드 전문가입니다.
107
+
108
+ ## 요청
109
+
110
+ {request}
111
+
112
+ ## 컨텍스트
113
+
114
+ - 사용 가능한 라이브러리: {available_libraries}
115
+ - 정의된 변수: {defined_variables}
116
+ - 이전 셀 출력: {previous_output}
117
+
118
+ ## 지침
119
+
120
+ 1. 실행 가능한 Python 코드만 생성하세요
121
+ 2. 필요한 import 문을 포함하세요
122
+ 3. 마지막 줄에 결과를 반환/출력하세요
123
+ 4. 주석은 간결하게 작성하세요
124
+ 5. **코드 내 주석과 문자열은 한글 또는 영어로만 작성하세요 (한자 사용 절대 금지)**
125
+ 6. **함수 docstring은 작은따옴표(') 3개만 사용하세요. 절대 백틱(`)을 사용하지 마세요.**
126
+ 7. **변수 값 출력**:
127
+ - f-string을 사용하여 변수 값을 출력하세요
128
+ - Markdown cell에 변수를 쓰면 변수명이 그대로 텍스트로 출력됩니다
129
+ - 변수 값을 출력하려면 code cell에서 print 또는 display 사용하세요
130
+ - 마크다운 포맷으로 예쁘게 출력하려면 display(Markdown(...)) 사용 권장
131
+ 8. 에러 처리를 적절히 포함하세요
132
+ 9. **시각화 시 한글 폰트 설정 필수**:
133
+ ```python
134
+ import matplotlib.pyplot as plt
135
+ plt.rcParams['font.family'] = 'AppleGothic'
136
+ plt.rcParams['axes.unicode_minus'] = False
137
+ ```
138
+
139
+ ## 출력
140
+
141
+ Python 코드만 출력하세요. 마크다운이나 설명 없이."""
142
+
143
+
144
+ # ═══════════════════════════════════════════════════════════════════════════
145
+ # 에러 수정 프롬프트 (Self-Healing)
146
+ # ═══════════════════════════════════════════════════════════════════════════
147
+
148
+ ERROR_REFINEMENT_PROMPT = """다음 코드가 오류로 실패했습니다. 수정된 코드를 제공하세요.
149
+
150
+ ## 원래 코드
151
+
152
+ ```python
153
+ {original_code}
154
+ ```
155
+
156
+ ## 오류 정보
157
+
158
+ - 오류 유형: {error_type}
159
+ - 오류 메시지: {error_message}
160
+ - 트레이스백:
161
+ ```
162
+ {traceback}
163
+ ```
164
+
165
+ ## 시도 횟수
166
+
167
+ {attempt}/{max_attempts}
168
+
169
+ ## 컨텍스트
170
+
171
+ - 사용 가능한 라이브러리: {available_libraries}
172
+ - 정의된 변수: {defined_variables}
173
+
174
+ ## 지침
175
+
176
+ 1. 오류의 근본 원인을 분석하세요
177
+ 2. 수정된 코드를 제공하세요
178
+ 3. 같은 오류가 반복되지 않도록 하세요
179
+ 4. **코드 내 주석과 문자열은 한글 또는 영어로만 작성하세요 (한자 사용 절대 금지)**
180
+ 5. **함수 docstring은 작은따옴표(') 3개만 사용하세요. 절대 백틱(`)을 사용하지 마세요.**
181
+ 6. **변수 값 출력**:
182
+ - f-string을 사용하여 변수 값을 출력하세요
183
+ - Markdown cell에 변수를 쓰면 변수명이 그대로 텍스트로 출력됩니다
184
+ - 변수 값을 출력하려면 code cell에서 print 또는 display 사용하세요
185
+ - 마크다운 포맷으로 예쁘게 출력하려면 display(Markdown(...)) 사용 권장
186
+
187
+ ## ⚠️ 중요 규칙 (절대 위반 금지)
188
+
189
+ **ModuleNotFoundError/ImportError 처리**:
190
+ - 모듈이 없는 에러의 경우, **절대로 다른 라이브러리로 대체하지 마세요**
191
+ - 예: `import dask` 실패 시 → `import pandas`로 대체 ❌ 금지!
192
+ - 이런 에러는 시스템이 자동으로 패키지 설치로 해결합니다
193
+ - Self-Healing에서는 **코드 문법/로직 수정만** 수행하세요
194
+
195
+ **수정 가능한 에러 유형**:
196
+ - SyntaxError (문법 오류)
197
+ - TypeError (타입 불일치)
198
+ - ValueError (값 오류)
199
+ - KeyError (잘못된 키)
200
+ - IndexError (인덱스 범위)
201
+ - AttributeError (잘못된 속성)
202
+ - NameError (변수명 오타)
203
+
204
+ **수정 불가 - 원래 코드 그대로 반환해야 하는 에러 유형**:
205
+ - ModuleNotFoundError
206
+ - ImportError
207
+ - FileNotFoundError (경로 문제는 시스템이 처리)
208
+
209
+ ## 출력 형식 (JSON)
210
+
211
+ ```json
212
+ {{
213
+ "reasoning": "오류 분석 및 수정 방법 설명",
214
+ "toolCalls": [
215
+ {{
216
+ "tool": "jupyter_cell",
217
+ "parameters": {{
218
+ "code": "수정된 Python 코드"
219
+ }}
220
+ }}
221
+ ]
222
+ }}
223
+ ```
224
+
225
+ JSON만 출력하세요."""
226
+
227
+
228
+ # ═══════════════════════════════════════════════════════════════════════════
229
+ # Adaptive Replanning 프롬프트 (계획 수정)
230
+ # ═══════════════════════════════════════════════════════════════════════════
231
+
232
+ ADAPTIVE_REPLAN_PROMPT = """에러가 발생했습니다. 출력과 에러를 분석하여 계획을 수정하거나 새로운 접근법을 제시하세요.
233
+
234
+ ## 원래 요청
235
+
236
+ {original_request}
237
+
238
+ ## 현재까지 실행된 단계
239
+
240
+ {executed_steps}
241
+
242
+ ## 실패한 단계
243
+
244
+ - 단계 번호: {failed_step_number}
245
+ - 설명: {failed_step_description}
246
+ - 실행된 코드:
247
+ ```python
248
+ {failed_code}
249
+ ```
250
+
251
+ ## 에러 정보
252
+
253
+ - 오류 유형: {error_type}
254
+ - 오류 메시지: {error_message}
255
+ - 트레이스백:
256
+ ```
257
+ {traceback}
258
+ ```
259
+
260
+ ## 실행 출력 (stdout/stderr)
261
+
262
+ ```
263
+ {execution_output}
264
+ ```
265
+
266
+ ## 현재 환경 정보
267
+
268
+ - **설치된 패키지**: {available_libraries}
269
+
270
+ ## ⚠️ 필수 규칙 (MANDATORY RULES - 반드시 따를 것!)
271
+
272
+ ### 🚨🚨🚨 ModuleNotFoundError / ImportError → 무조건 `insert_steps` 사용! 🚨🚨🚨
273
+
274
+ **⛔ 절대적 금지 사항 (이 규칙은 어떤 경우에도 위반 불가)**:
275
+ - `ModuleNotFoundError`나 `ImportError` 발생 시:
276
+ - ❌ `refine` 사용 금지!
277
+ - ❌ `replace_step` 사용 금지!
278
+ - ❌ `replan_remaining` 사용 금지!
279
+ - ✅ 오직 `insert_steps`만 허용!
280
+
281
+ **🔍 간접 의존성 오류 (CRITICAL - 매우 중요!)**:
282
+ - 실행한 코드와 오류 메시지의 패키지가 **달라도** `insert_steps` 사용!
283
+ - 예시 1: `import dask.dataframe as dd` 실행 → `No module named 'pyarrow'` 오류
284
+ → pyarrow는 dask의 **내부 의존성**
285
+ → `insert_steps`로 `!pip install {PIP_INDEX_OPTION} --timeout 180 pyarrow` 추가!
286
+ → ❌ "dask 대신 pandas 사용" 같은 접근법 변경 금지!
287
+ - 예시 2: `import tensorflow` 실행 → `No module named 'keras'` 오류
288
+ → `insert_steps`로 `!pip install {PIP_INDEX_OPTION} --timeout 180 keras` 추가!
289
+ - 예시 3: `from transformers import AutoModel` 실행 → `No module named 'accelerate'` 오류
290
+ → `insert_steps`로 `!pip install {PIP_INDEX_OPTION} --timeout 180 accelerate` 추가!
291
+
292
+ **📋 판단 기준**: 에러 메시지에 `No module named` 또는 `ImportError`가 있으면:
293
+ 1. **⚠️ 에러 메시지에서 패키지명 추출 (코드가 아님!)** ⚠️
294
+ 2. 무조건 `insert_steps` 선택
295
+ 3. `!pip install {PIP_INDEX_OPTION} --timeout 180 에러메시지의_패키지명` 단계 추가
296
+ 4. **사용자가 요청한 원래 라이브러리(dask 등)는 그대로 유지!**
297
+
298
+ **🚨 URL 축약 절대 금지!**:
299
+ - pip install 명령어에서 URL이 포함된 경우, **반드시 전체 URL을 그대로 사용**해야 합니다
300
+ - ❌ 금지: `https://repository.example.../simple` (... 로 축약)
301
+ - ✅ 필수: `https://repository.example.com/pypi/simple` (전체 URL)
302
+ - 긴 URL이라도 절대 축약하지 마세요! 실행되지 않습니다!
303
+
304
+ **🚨 패키지 설치 전 필수 확인!**:
305
+ - **설치된 패키지** 목록을 반드시 확인하세요
306
+ - 에러 메시지의 패키지가 **이미 설치되어 있다면** 설치 단계를 추가하지 마세요!
307
+ - 예: 에러가 `No module named 'pyarrow'`인데 설치된 패키지에 `pyarrow`가 있으면 → 설치 불필요
308
+ - 예: 에러가 `No module named 'dask'`인데 설치된 패키지에 `dask`가 있으면 → 설치 불필요
309
+ - ⚠️ **주의**: 패키지가 이미 있는데도 설치를 반복하면 무한 루프에 빠집니다!
310
+ - ✅ 패키지가 없을 때만 `insert_steps`로 설치 추가하세요
311
+
312
+ ### 🚨🚨🚨 패키지명 추출 - 매우 중요!!! 🚨🚨🚨
313
+
314
+ **반드시 에러 메시지에서 추출하세요! 사용자 코드에서 추출하면 안 됩니다!**
315
+
316
+ **예시 상황**:
317
+ - 사용자 코드: `import dask.dataframe as dd`
318
+ - 에러 메시지: `ModuleNotFoundError: No module named 'pyarrow'`
319
+
320
+ | 추출 방법 | 결과 | 판정 |
321
+ |----------|------|------|
322
+ | 사용자 코드에서 추출 | `!pip install {PIP_INDEX_OPTION} --timeout 180 dask` | ❌ **완전히 틀림!** |
323
+ | 에러 메시지에서 추출 | `!pip install {PIP_INDEX_OPTION} --timeout 180 pyarrow` | ✅ **정답!** |
324
+
325
+ **왜 중요한가?**:
326
+ - dask는 이미 설치되어 있음 (그래서 import dask가 시작됨)
327
+ - 하지만 dask 내부에서 pyarrow를 로드하려다 실패
328
+ - 따라서 설치해야 할 패키지는 pyarrow!
329
+
330
+ ### 패키지명 추출 규칙
331
+ - "No module named 'xxx'" → `!pip install {PIP_INDEX_OPTION} --timeout 180 xxx` (에러 메시지의 xxx!)
332
+ - "No module named 'xxx.yyy'" → `!pip install {PIP_INDEX_OPTION} --timeout 180 xxx` (최상위 패키지만)
333
+ - 예외: `sklearn` → `!pip install {PIP_INDEX_OPTION} --timeout 180 scikit-learn`
334
+ - 예외: `cv2` → `!pip install {PIP_INDEX_OPTION} --timeout 180 opencv-python`
335
+ - 예외: `PIL` → `!pip install {PIP_INDEX_OPTION} --timeout 180 pillow`
336
+
337
+ ## 분석 지침
338
+
339
+ 1. **근본 원인 분석**: 단순 코드 버그인가, 접근법 자체의 문제인가?
340
+ 2. **필요한 선행 작업**: 누락된 import, 데이터 변환, 환경 설정이 있는가?
341
+ 3. **대안적 접근법**: 다른 라이브러리나 방법을 사용해야 하는가?
342
+ 4. **⚠️ 이전 실행된 코드 참고**: 위의 "현재까지 실행된 단계"에 표시된 코드를 반드시 확인하세요!
343
+ - 예: 이전 단계에서 데이터프레임 컬럼명을 소문자로 변환했다면, 현재 단계에서도 소문자로 접근해야 합니다
344
+ - 예: 이전 단계에서 특정 변수를 정의했다면, 그 변수명을 그대로 사용해야 합니다
345
+ - 데이터 전처리, 변수 변환 등 이전 컨텍스트를 유지하세요
346
+ 5. **코드 내 주석과 문자열은 한글 또는 영어로만 작성하세요 (한자 사용 절대 금지)**
347
+ 6. **함수 docstring은 작은따옴표(') 3개만 사용하세요. 절대 백틱(`)을 사용하지 마세요.**
348
+ 7. **변수 값 출력**:
349
+ - f-string을 사용하여 변수 값을 출력하세요
350
+ - Markdown cell에 변수를 쓰면 변수명이 그대로 텍스트로 출력됩니다
351
+ - 변수 값을 출력하려면 code cell에서 print 또는 display 사용하세요
352
+ - 마크다운 포맷으로 예쁘게 출력하려면 display(Markdown(...)) 사용 권장
353
+
354
+ ## 에러 유형별 해결 전략
355
+
356
+ ### FileNotFoundError
357
+ - 파일 경로 확인 또는 파일 존재 여부 체크 단계 추가
358
+ - 가능하면 `os.path.exists()` 검증 후 적절한 에러 메시지
359
+
360
+ ### NameError (변수 미정의)
361
+ **원인을 먼저 파악하세요:**
362
+ 1. **이전 MODIFY 단계에서 원본 코드가 손실된 경우**
363
+ - 이전 단계에서 셀을 MODIFY할 때 관련 없는 코드를 삭제했을 가능성
364
+ - **해결책**: `refine`으로 해당 코드에 누락된 변수 정의를 복원
365
+
366
+ 2. **단순 오타인 경우**
367
+ - `refine`으로 수정
368
+
369
+ 3. **원래 계획에서 변수 정의가 누락된 경우**
370
+ - 필요한 변수 정의를 추가하는 것이 적절
371
+
372
+ ### TypeError / ValueError
373
+ - 대부분 `refine`으로 코드 수정
374
+ - 데이터 타입 변환이 필요하면 변환 로직 추가
375
+
376
+ ## 결정 옵션
377
+
378
+ 1. **refine**: 같은 접근법으로 코드만 수정
379
+ - ✅ 사용 가능: SyntaxError, TypeError, ValueError, KeyError, IndexError, AttributeError, NameError
380
+ - ❌ 사용 금지: ModuleNotFoundError, ImportError
381
+
382
+ 2. **insert_steps**: 현재 단계 전에 필요한 단계 추가 (선행 작업 필요)
383
+ - ✅ **ModuleNotFoundError, ImportError 발생 시 유일하게 허용되는 옵션!**
384
+ - 패키지 설치: `!pip install {PIP_INDEX_OPTION} --timeout 180 패키지명` 단계 추가
385
+ - 에러 메시지의 패키지명을 정확히 추출하여 설치
386
+
387
+ 3. **replace_step**: 현재 단계를 완전히 다른 접근법으로 교체
388
+ - ❌ ModuleNotFoundError, ImportError 시 사용 금지! (라이브러리 대체 금지)
389
+
390
+ 4. **replan_remaining**: 남은 모든 단계를 새로 계획 (final_answer도 새로 작성!)
391
+ - ❌ ModuleNotFoundError, ImportError 시 사용 금지! (접근법 변경 금지)
392
+
393
+ ## 중요 규칙
394
+
395
+ - **replan_remaining 또는 replace_step 선택 시**: 접근법이 변경되면 final_answer 메시지도 반드시 실제 사용된 방법을 반영해야 합니다.
396
+ - 예: dask → pandas로 변경 시, final_answer는 "pandas를 사용하여..."로 작성
397
+ - **final_answer는 실제 실행된 코드를 정확히 반영**해야 합니다.
398
+
399
+ ## 🚨 import 문 보존 규칙 (CRITICAL!)
400
+
401
+ **코드를 수정할 때 import 문은 절대로 주석 처리하지 마세요!**
402
+
403
+ ```python
404
+ # ❌ 잘못된 예시 - import까지 주석 처리 → 후속 Step에서 NameError 발생
405
+ # import matplotlib.pyplot as plt ← 이렇게 하면 안 됨!
406
+ # import matplotlib.font_manager as fm
407
+
408
+ # ✅ 올바른 예시 - import는 유지하고 문제 코드만 수정
409
+ import matplotlib.pyplot as plt # 반드시 유지!
410
+ import matplotlib.font_manager as fm # 반드시 유지!
411
+
412
+ # 문제가 있는 부분만 try-except로 감싸거나 제거
413
+ try:
414
+ # 한글 폰트 설정 등 문제 코드
415
+ pass
416
+ except Exception:
417
+ pass
418
+ ```
419
+
420
+ **규칙**: matplotlib, pandas, numpy, seaborn 등의 import 문은 항상 유지하세요. 문제가 생기면 import 이후의 코드만 수정하세요.
421
+
422
+ ## 🚨 Matplotlib API 금지 규칙 (CRITICAL!)
423
+
424
+ **⛔ tick_params()에서 절대 사용 금지:**
425
+ - ❌ `ax.tick_params(ha='right')` - ValueError 발생!
426
+ - ❌ `ax.tick_params(horizontalalignment='right')` - ValueError 발생!
427
+ - ❌ `ax.tick_params(va='center')` - ValueError 발생!
428
+
429
+ **✅ 레이블 정렬이 필요하면 반드시 이 방법 사용:**
430
+ ```python
431
+ # 올바른 방법: plt.setp() 사용
432
+ plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
433
+
434
+ # 또는 plt.xticks() 사용
435
+ plt.xticks(rotation=45, ha='right')
436
+ ```
437
+
438
+ ## 🚨 Dask DataFrame 금지 규칙 (CRITICAL!)
439
+
440
+ **⛔ .head() 결과에 .compute() 절대 사용 금지:**
441
+ - ❌ `df.head().compute()` - AttributeError 발생! head()는 이미 pandas!
442
+ - ❌ `df.head(1000).compute()` - AttributeError 발생!
443
+ - ❌ `df[['col1', 'col2']].head(5000).compute()` - 컬럼 선택 후에도 금지!
444
+ - ❌ `sample_df = df.head(100); sample_df.compute()` - head() 결과는 이미 pandas!
445
+
446
+ **✅ head()는 직접 사용 (compute 불필요):**
447
+ ```python
448
+ # 올바른 방법: head()는 이미 pandas DataFrame 반환
449
+ sample_df = df.head(1000) # 이미 pandas!
450
+ sample_df = df[['col1', 'col2']].head(5000) # 이미 pandas!
451
+ # 바로 시각화나 분석에 사용하면 됨
452
+ ```
453
+
454
+ **⛔ corr() 사용 시 문자열 컬럼 포함 금지:**
455
+ - ❌ `df.corr().compute()` - 문자열 컬럼이 있으면 ValueError 발생!
456
+
457
+ **✅ 반드시 숫자형 컬럼만 선택 후 사용:**
458
+ ```python
459
+ # 올바른 방법: 숫자형 컬럼만 선택
460
+ numeric_cols = df.select_dtypes(include=['number']).columns.tolist()
461
+ correlation_matrix = df[numeric_cols].corr().compute()
462
+ ```
463
+
464
+ **⛔ value_counts().unstack() 사용 금지:**
465
+ - ❌ `df.groupby('Sex')['Survived'].value_counts().unstack().compute()` - Dask Series에는 unstack() 메서드 없음! AttributeError 발생!
466
+
467
+ **✅ 대체 방법: compute 후 unstack 또는 crosstab 사용:**
468
+ ```python
469
+ # 방법 1: groupby + size + compute 후 unstack
470
+ cross_tab = df.groupby(['Sex', 'Survived']).size().compute().unstack(fill_value=0)
471
+
472
+ # 방법 2: pandas crosstab (compute 후 적용)
473
+ sample = df[['Sex', 'Survived']].compute()
474
+ cross_tab = pd.crosstab(sample['Sex'], sample['Survived'])
475
+ ```
476
+
477
+ ## 출력 형식 (JSON)
478
+
479
+ **⚠️ 중요: 응답은 간결하게!**
480
+ - `root_cause`: 1-2문장으로 간결하게 작성
481
+ - `reasoning`: 1-2문장으로 간결하게 작성
482
+ - 장황한 설명 금지!
483
+
484
+ ```json
485
+ {{
486
+ "analysis": {{
487
+ "root_cause": "근본 원인을 1-2문장으로 간결하게 (한국어)",
488
+ "is_approach_problem": true/false,
489
+ "missing_prerequisites": ["누락된 선행 작업들"]
490
+ }},
491
+ "decision": "refine | insert_steps | replace_step | replan_remaining",
492
+ "reasoning": "결정 이유를 1-2문장으로 간결하게 (한국어)",
493
+ "changes": {{
494
+ // decision이 "refine"인 경우:
495
+ "refined_code": "수정된 코드",
496
+
497
+ // decision이 "insert_steps"인 경우 (예: 패키지 설치):
498
+ // ⚠️ 중요: 에러메시지의 패키지명 사용! (예: pyarrow, 사용자코드의 dask 아님!)
499
+ "new_steps": [
500
+ {{
501
+ "description": "에러메시지에서 확인된 패키지(예: pyarrow) 설치",
502
+ "toolCalls": [{{"tool": "jupyter_cell", "parameters": {{"code": "!pip install {PIP_INDEX_OPTION} --timeout 180 에러메시지의_패키지명"}}}}]
503
+ }}
504
+ ],
505
+
506
+ // decision이 "replace_step"인 경우:
507
+ "replacement": {{
508
+ "description": "새 단계 설명",
509
+ "toolCalls": [{{"tool": "jupyter_cell", "parameters": {{"code": "코드"}}}}]
510
+ }},
511
+
512
+ // decision이 "replan_remaining"인 경우 (final_answer 필수 포함!):
513
+ "new_plan": [
514
+ {{
515
+ "description": "단계 설명",
516
+ "toolCalls": [{{"tool": "jupyter_cell", "parameters": {{"code": "코드"}}}}]
517
+ }},
518
+ {{
519
+ "description": "최종 결과 제시",
520
+ "toolCalls": [{{"tool": "final_answer", "parameters": {{"answer": "실제 사용된 방법을 반영한 완료 메시지"}}}}]
521
+ }}
522
+ ]
523
+ }}
524
+ }}
525
+ ```
526
+
527
+ ## 🚨 출력 형식 필수 규칙 (CRITICAL!)
528
+
529
+ **⛔ 절대 금지:**
530
+ - ❌ 마크다운 형식 (## 분석, **굵은 글씨** 등) 출력 금지!
531
+ - ❌ 설명, 해설, 주석 출력 금지!
532
+ - ❌ "다음은...", "분석 결과..." 같은 서두 금지!
533
+
534
+ **✅ 필수:**
535
+ - JSON 코드 블록만 출력하세요!
536
+ - ```json 으로 시작하고 ``` 으로 끝나야 합니다!
537
+
538
+ **올바른 응답 예시:**
539
+ ```json
540
+ {{
541
+ "analysis": {{...}},
542
+ "decision": "refine",
543
+ "reasoning": "...",
544
+ "changes": {{...}}
545
+ }}
546
+ ```
547
+
548
+ 위 형식으로만 응답하세요. 다른 텍스트는 절대 포함하지 마세요!"""
549
+
550
+
551
+ # ═══════════════════════════════════════════════════════════════════════════
552
+ # 구조화된 계획 생성 프롬프트 (Enhanced Planning with Checkpoints)
553
+ # ═══════════════════════════════════════════════════════════════════════════
554
+
555
+ STRUCTURED_PLAN_PROMPT = """당신은 Jupyter 노트북을 위한 Python 코드 전문가입니다.
556
+ 사용자의 요청을 체계적으로 분석하고, 검증 가능한 단계별 실행 계획을 생성하세요.
557
+
558
+ ## 분석 프레임워크
559
+
560
+ ### 1. 문제 분해 (Problem Decomposition)
561
+ - 핵심 목표는 무엇인가?
562
+ - 필수 단계와 선택적 단계는 무엇인가?
563
+ - 각 단계의 입력과 출력은 무엇인가?
564
+
565
+ ### 2. 의존성 분석 (Dependency Analysis)
566
+ - 어떤 라이브러리가 필요한가?
567
+ - 단계 간 데이터 흐름은 어떠한가?
568
+ - 어떤 변수/객체가 단계 간에 공유되는가?
569
+
570
+ ### 3. 위험도 평가 (Risk Assessment)
571
+ - 실패 가능성이 높은 단계는?
572
+ - 외부 의존성(API, 파일, 네트워크)이 있는 단계는?
573
+ - 실행 시간이 오래 걸릴 수 있는 단계는?
574
+
575
+ ### 4. 검증 전략 (Validation Strategy)
576
+ - 각 단계의 성공을 어떻게 확인할 수 있는가?
577
+ - 예상 출력 형태는 무엇인가?
578
+ - 체크포인트 기준은 무엇인가?
579
+
580
+ ## 사용 가능한 도구
581
+
582
+ ### 기본 도구 (셀 작업)
583
+ 1. **jupyter_cell**: Python 코드 셀 생성 (노트북 끝에 추가)
584
+ - parameters: {{"code": "Python 코드"}}
585
+ - **항상 새 셀을 노트북 끝에 추가합니다**
586
+
587
+ 2. **markdown**: 마크다운 설명 셀 생성 (노트북 끝에 추가)
588
+ - parameters: {{"content": "마크다운 텍스트"}}
589
+
590
+ 3. **final_answer**: 작업 완료 및 최종 답변
591
+ - parameters: {{"answer": "최종 답변 텍스트", "summary": "작업 요약(선택)"}}
592
+
593
+ ### 확장 도구 (파일/터미널)
594
+ 4. **read_file**: 파일 읽기 (절대경로/.. 금지)
595
+ - parameters: {{"path": "상대경로"}}
596
+
597
+ 5. **write_file**: 파일 쓰기 (승인 필요)
598
+ - parameters: {{"path": "상대경로", "content": "내용"}}
599
+
600
+ 6. **list_files**: 디렉토리 조회
601
+ - parameters: {{"path": ".", "recursive": false, "pattern": "*.py"}}
602
+
603
+ 7. **execute_command**: 셸 명령 (위험 명령만 승인)
604
+ - parameters: {{"command": "pip list"}}
605
+
606
+ 8. **search_files**: 파일 내용 검색
607
+ - parameters: {{"pattern": "def func", "path": "src"}}
608
+
609
+ ## 🔴 핵심 원칙: 항상 새 셀을 아래에 추가!
610
+
611
+ **⛔ 기존 셀을 수정하지 마세요! 항상 새 셀을 노트북 끝에 추가합니다.**
612
+
613
+ 이 방식의 장점:
614
+ - 기존 코드 히스토리가 보존됨
615
+ - 사용자가 이전/이후 코드를 비교할 수 있음
616
+ - 실행 순서가 명확해짐
617
+ - 롤백이 쉬움 (불필요한 셀만 삭제하면 됨)
618
+
619
+ ## 노트북 컨텍스트 (참고용 - 기존 코드를 수정하지 마세요!)
620
+
621
+ - 셀 개수: {cell_count}
622
+ - 임포트된 라이브러리: {imported_libraries}
623
+ - 정의된 변수: {defined_variables}
624
+ - 최근 셀 내용 (참고용):
625
+ {recent_cells}
626
+
627
+ **참고**: 위 기존 셀들은 수정하지 않습니다. 필요한 코드는 새 셀로 추가하세요.
628
+
629
+ ## 사용자 요청
630
+
631
+ {request}
632
+
633
+ ## ⚠️ 초기 설정 (첫 번째 코드 셀에 포함)
634
+
635
+ **먼저 "설치된 패키지" 목록을 확인하세요!**
636
+ - 필요한 라이브러리가 없으면 `!pip install {PIP_INDEX_OPTION} --timeout 180 패키지명` 형식으로 설치 단계를 먼저 추가하세요.
637
+
638
+ 첫 번째 코드 셀 예시 (설치된 패키지에 따라 조정):
639
+ ```python
640
+ # === 경고 필터링 ===
641
+ import warnings
642
+ warnings.filterwarnings('ignore', category=RuntimeWarning)
643
+ warnings.filterwarnings('ignore', category=FutureWarning)
644
+
645
+ # === 기본 라이브러리 import (pandas, numpy는 대부분 설치되어 있음) ===
646
+ import pandas as pd
647
+ import numpy as np
648
+
649
+ # === 시각화 라이브러리 (설치 확인 후 import) ===
650
+ # matplotlib, seaborn이 설치된 패키지 목록에 있는 경우에만 import
651
+ import matplotlib.pyplot as plt
652
+ import seaborn as sns
653
+
654
+ # === 한글 폰트 설정 (선택적 - matplotlib 설치된 경우) ===
655
+ try:
656
+ import matplotlib.font_manager as fm
657
+ korean_fonts = ['Apple SD Gothic Neo', 'Malgun Gothic', 'NanumGothic', 'Noto Sans CJK KR']
658
+ available = set(f.name for f in fm.fontManager.ttflist)
659
+ for font in korean_fonts:
660
+ if font in available:
661
+ plt.rcParams['font.family'] = font
662
+ break
663
+ plt.rcParams['axes.unicode_minus'] = False
664
+ except Exception:
665
+ pass # 폰트 설정 실패해도 계속 진행
666
+ ```
667
+
668
+ **🔴 중요**:
669
+ - **설치되지 않은 라이브러리는 import하지 마세요!** 먼저 `!pip install {PIP_INDEX_OPTION} --timeout 180 패키지명` 단계를 추가하세요.
670
+ - import 문은 **절대로** 주석 처리하지 마세요! 문제가 생기면 한글 폰트 설정 블록(try 블록)만 수정하세요.
671
+
672
+ ## 🔍 파일 탐색 규칙 (중요!)
673
+
674
+ 사용자 요청에 **파일명이 언급된 경우**, 반드시 다음 순서로 처리하세요:
675
+
676
+ 1. **로컬 파일 탐색 우선**: 먼저 `os.listdir()`, `glob.glob()` 등으로 현재 디렉토리 및 하위 디렉토리에서 해당 파일을 탐색합니다
677
+ 2. **파일 존재 확인**: `os.path.exists()` 또는 유사한 방법으로 파일 존재 여부를 확인합니다
678
+ 3. **경로 출력**: 발견된 파일의 전체 경로를 출력하여 사용자에게 알립니다
679
+ 4. **파일이 없는 경우**: 파일을 찾을 수 없으면 명확한 에러 메시지를 제공합니다
680
+
681
+ 예시:
682
+ - "train.csv 파일을 로드해줘" → 먼저 `glob.glob('**/train.csv', recursive=True)`로 파일 탐색
683
+ - "data.xlsx를 읽어줘" → 먼저 로컬에서 해당 파일 검색 후 로드
684
+
685
+ ## 📊 시각화 전 데이터 검증 (중요!)
686
+
687
+ **시각화하기 전에 항상 데이터가 비어있는지 확인하세요!**
688
+
689
+ 빈 데이터로 `.plot()` 호출 시 `IndexError`가 발생합니다. 다음 패턴을 사용하세요:
690
+
691
+ ```python
692
+ # ❌ 잘못된 예시 - 빈 데이터일 때 에러 발생
693
+ missing_pct[missing_pct > 0].head(20).plot(kind='bar')
694
+
695
+ # ✅ 올바른 예시 - 데이터 존재 여부 확인
696
+ data_to_plot = missing_pct[missing_pct > 0].head(20)
697
+ if len(data_to_plot) > 0:
698
+ data_to_plot.plot(kind='bar')
699
+ plt.title('결측치 비율')
700
+ plt.show()
701
+ else:
702
+ print("시각화할 데이터가 없습니다 (결측치 없음)")
703
+ ```
704
+
705
+ ## 출력 형식 (JSON)
706
+
707
+ ```json
708
+ {{
709
+ "analysis": {{
710
+ "problem_decomposition": {{
711
+ "core_goal": "핵심 목표",
712
+ "essential_steps": ["필수 단계 목록"],
713
+ "optional_steps": ["선택적 단계 목록"]
714
+ }},
715
+ "dependency_analysis": {{
716
+ "required_libraries": ["필요한 라이브러리"],
717
+ "data_flow": "데이터 흐름 설명",
718
+ "shared_variables": ["공유 변수"]
719
+ }},
720
+ "risk_assessment": {{
721
+ "high_risk_steps": [1, 2],
722
+ "external_dependencies": ["외부 의존성"],
723
+ "estimated_complexity": "low | medium | high"
724
+ }}
725
+ }},
726
+ "reasoning": "계획 수립 이유에 대한 설명",
727
+ "plan": {{
728
+ "totalSteps": 단계_수,
729
+ "steps": [
730
+ {{
731
+ "stepNumber": 1,
732
+ "description": "단계 설명 (한국어)",
733
+ "toolCalls": [
734
+ {{
735
+ "tool": "jupyter_cell",
736
+ "parameters": {{
737
+ "code": "Python 코드"
738
+ }}
739
+ }}
740
+ ],
741
+ "dependencies": [],
742
+ "checkpoint": {{
743
+ "expectedOutcome": "예상 결과",
744
+ "validationCriteria": ["검증 기준 1", "검증 기준 2"],
745
+ "successIndicators": ["성공 지표"]
746
+ }},
747
+ "riskLevel": "low | medium | high"
748
+ }}
749
+ ]
750
+ }}
751
+ }}
752
+ ```
753
+
754
+ JSON만 출력하세요. 다른 텍스트 없이."""
755
+
756
+
757
+ # ═══════════════════════════════════════════════════════════════════════════
758
+ # Reflection 프롬프트 (실행 결과 분석 및 적응적 조정)
759
+ # ═══════════════════════════════════════════════════════════════════════════
760
+
761
+ REFLECTION_PROMPT = """실행 결과를 분석하고 다음 단계에 대한 조정을 제안하세요.
762
+
763
+ ## 실행된 단계
764
+
765
+ - 단계 번호: {step_number}
766
+ - 설명: {step_description}
767
+ - 실행된 코드:
768
+ ```python
769
+ {executed_code}
770
+ ```
771
+
772
+ ## 실행 결과
773
+
774
+ - 상태: {execution_status}
775
+ - 출력:
776
+ ```
777
+ {execution_output}
778
+ ```
779
+ - 오류 (있는 경우):
780
+ ```
781
+ {error_message}
782
+ ```
783
+
784
+ ## 체크포인트 기준
785
+
786
+ - 예상 결과: {expected_outcome}
787
+ - 검증 기준: {validation_criteria}
788
+
789
+ ## 남은 단계
790
+
791
+ {remaining_steps}
792
+
793
+ ## 분석 요청
794
+
795
+ 1. **결과 평가**: 실행 결과가 예상과 일치하는가?
796
+ 2. **성공/실패 요인**: 무엇이 잘 되었고 무엇이 문제인가?
797
+ 3. **다음 단계 영향**: 이 결과가 남은 단계에 어떤 영향을 미치는가?
798
+ 4. **조정 제안**: 계획을 수정해야 하는가?
799
+
800
+ ## 출력 형식 (JSON)
801
+
802
+ ```json
803
+ {{
804
+ "evaluation": {{
805
+ "checkpoint_passed": true/false,
806
+ "output_matches_expected": true/false,
807
+ "confidence_score": 0.0-1.0
808
+ }},
809
+ "analysis": {{
810
+ "success_factors": ["성공 요인들"],
811
+ "failure_factors": ["실패 요인들"],
812
+ "unexpected_outcomes": ["예상치 못한 결과들"]
813
+ }},
814
+ "impact_on_remaining": {{
815
+ "affected_steps": [단계_번호들],
816
+ "severity": "none | minor | major | critical",
817
+ "description": "영향 설명"
818
+ }},
819
+ "recommendations": {{
820
+ "action": "continue | adjust | retry | replan",
821
+ "adjustments": [
822
+ {{
823
+ "step_number": 단계_번호,
824
+ "change_type": "modify_code | add_step | remove_step | change_approach",
825
+ "description": "변경 설명",
826
+ "new_content": "새 코드 또는 내용 (필요한 경우)"
827
+ }}
828
+ ],
829
+ "reasoning": "조정 이유"
830
+ }}
831
+ }}
832
+ ```
833
+
834
+ JSON만 출력하세요."""
835
+
836
+
837
+ # ═══════════════════════════════════════════════════════════════════════════
838
+ # 최종 답변 생성 프롬프트
839
+ # ═══════════════════════════════════════════════════════════════════════════
840
+
841
+ FINAL_ANSWER_PROMPT = """작업이 완료되었습니다. 결과를 요약해주세요.
842
+
843
+ ## 원래 요청
844
+
845
+ {original_request}
846
+
847
+ ## 실행된 단계
848
+
849
+ {executed_steps}
850
+
851
+ ## 생성된 출력
852
+
853
+ {outputs}
854
+
855
+ ## 지침
856
+
857
+ 1. 작업 결과를 간결하게 요약하세요
858
+ 2. 주요 발견사항이나 결과를 강조하세요
859
+ 3. 다음 단계에 대한 제안이 있으면 포함하세요
860
+ 4. 한국어로 작성하세요
861
+ 5. **변수명이 아닌 실제 계산된 값을 사용하세요** (예: "n_rows 행" 대신 "891 행")
862
+
863
+ ## 출력
864
+
865
+ 간결한 요약 텍스트 (200자 이내)"""
866
+
867
+
868
+ # ═══════════════════════════════════════════════════════════════════════════
869
+ # 유틸리티 함수
870
+ # ═══════════════════════════════════════════════════════════════════════════
871
+
872
+
873
+ def format_plan_prompt(
874
+ request: str,
875
+ cell_count: int,
876
+ imported_libraries: list,
877
+ defined_variables: list,
878
+ recent_cells: list,
879
+ available_libraries: list = None,
880
+ detected_libraries: list = None, # LibraryDetector로 감지된 라이브러리
881
+ rag_context: str = None, # RAG 검색 결과 컨텍스트 (primary)
882
+ ) -> str:
883
+ """
884
+ 실행 계획 생성 프롬프트 포맷팅
885
+
886
+ 지식 주입 우선순위:
887
+ 1. RAG 컨텍스트가 있으면 RAG 결과 사용 (시맨틱 검색)
888
+ 2. RAG가 없으면 KnowledgeBase fallback (전체 API 가이드 로드)
889
+ """
890
+ # 최근 셀 내용 포맷팅 (참고용으로만 표시) - 최대 5개 셀, 각 150자
891
+ recent_cells_text = ""
892
+ max_cells = min(5, len(recent_cells)) # 최대 5개 셀만
893
+ for i, cell in enumerate(recent_cells[-max_cells:]): # 마지막 5개만
894
+ source = cell.get("source", "")[:150] # 최대 150자
895
+ cell_index = cell.get("index", i)
896
+ recent_cells_text += (
897
+ f"\n[셀 {cell_index}]: {source[:100]}...\n"
898
+ if len(source) > 100
899
+ else f"\n[셀 {cell_index}]: {source}\n"
900
+ )
901
+
902
+ # 기본 프롬프트 생성
903
+ base_prompt = PLAN_GENERATION_PROMPT.format(
904
+ request=request,
905
+ cell_count=cell_count,
906
+ imported_libraries=", ".join(imported_libraries)
907
+ if imported_libraries
908
+ else "없음",
909
+ defined_variables=", ".join(defined_variables) if defined_variables else "없음",
910
+ recent_cells=recent_cells_text if recent_cells_text else "없음",
911
+ available_libraries=", ".join(available_libraries)
912
+ if available_libraries
913
+ else "정보 없음",
914
+ )
915
+
916
+ # 지식 주입: RAG primary, KnowledgeBase fallback
917
+ if rag_context:
918
+ # RAG 결과가 있으면 RAG 사용 (시맨틱 검색 기반)
919
+ print(f"[RAG] 컨텍스트 주입됨: {len(rag_context)} chars")
920
+ base_prompt = base_prompt.replace(
921
+ "## JSON 출력", f"{rag_context}\n\n## JSON 출력"
922
+ )
923
+ elif detected_libraries:
924
+ # RAG가 없으면 KnowledgeBase fallback (전체 API 가이드)
925
+ from hdsp_agent_core.knowledge.loader import get_knowledge_loader
926
+
927
+ knowledge_loader = get_knowledge_loader()
928
+ library_knowledge = knowledge_loader.format_knowledge_section(
929
+ detected_libraries
930
+ )
931
+
932
+ if library_knowledge:
933
+ if len(library_knowledge) > 2000:
934
+ library_knowledge = library_knowledge[:2000] + "\n... (생략)"
935
+ print(
936
+ f"[KnowledgeBase Fallback] 라이브러리 지식 주입됨: {detected_libraries} ({len(library_knowledge)} chars)"
937
+ )
938
+ base_prompt = base_prompt.replace(
939
+ "## JSON 출력", f"{library_knowledge}\n\n## JSON 출력"
940
+ )
941
+ else:
942
+ print(
943
+ f"[KnowledgeBase] 주입할 라이브러리 지식 없음. detected={detected_libraries}"
944
+ )
945
+ else:
946
+ print("[Knowledge] RAG 컨텍스트 없음, 감지된 라이브러리 없음")
947
+
948
+ return base_prompt
949
+
950
+
951
+ def format_refine_prompt(
952
+ original_code: str,
953
+ error_type: str,
954
+ error_message: str,
955
+ traceback: str,
956
+ attempt: int,
957
+ max_attempts: int,
958
+ available_libraries: list,
959
+ defined_variables: list,
960
+ ) -> str:
961
+ """에러 수정 프롬프트 포맷팅"""
962
+ return ERROR_REFINEMENT_PROMPT.format(
963
+ original_code=original_code,
964
+ error_type=error_type,
965
+ error_message=error_message,
966
+ traceback=traceback,
967
+ attempt=attempt,
968
+ max_attempts=max_attempts,
969
+ available_libraries=", ".join(available_libraries)
970
+ if available_libraries
971
+ else "pandas, numpy, matplotlib",
972
+ defined_variables=", ".join(defined_variables) if defined_variables else "없음",
973
+ )
974
+
975
+
976
+ def format_final_answer_prompt(
977
+ original_request: str, executed_steps: list, outputs: list
978
+ ) -> str:
979
+ """최종 답변 프롬프트 포맷팅"""
980
+ steps_text = "\n".join(
981
+ [
982
+ f"- Step {s.get('stepNumber', i + 1)}: {s.get('description', '완료')}"
983
+ for i, s in enumerate(executed_steps)
984
+ ]
985
+ )
986
+
987
+ outputs_text = "\n".join(
988
+ [f"[출력 {i + 1}]: {str(o)[:200]}" for i, o in enumerate(outputs)]
989
+ )
990
+
991
+ return FINAL_ANSWER_PROMPT.format(
992
+ original_request=original_request,
993
+ executed_steps=steps_text if steps_text else "없음",
994
+ outputs=outputs_text if outputs_text else "없음",
995
+ )
996
+
997
+
998
+ def format_replan_prompt(
999
+ original_request: str,
1000
+ executed_steps: list,
1001
+ failed_step: dict,
1002
+ error_info: dict,
1003
+ execution_output: str = "",
1004
+ available_libraries: list = None,
1005
+ ) -> str:
1006
+ """Adaptive Replanning 프롬프트 포맷팅"""
1007
+ # 실행된 단계 텍스트 (코드 포함)
1008
+ executed_text_parts = []
1009
+ if executed_steps:
1010
+ for i, s in enumerate(executed_steps):
1011
+ step_num = s.get("stepNumber", i + 1)
1012
+ step_desc = s.get("description", "완료")
1013
+ executed_text_parts.append(f"- Step {step_num}: {step_desc} ✅")
1014
+
1015
+ # 이 스텝에서 실행한 코드 추가
1016
+ tool_calls = s.get("toolCalls", [])
1017
+ for tc in tool_calls:
1018
+ if tc.get("tool") == "jupyter_cell":
1019
+ code = tc.get("parameters", {}).get("code", "")
1020
+ if code:
1021
+ # 코드를 간략하게 표시 (처음 3줄 또는 전체)
1022
+ code_lines = code.split("\n")
1023
+ if len(code_lines) > 5:
1024
+ code_preview = "\n".join(code_lines[:5]) + "\n ...(생략)"
1025
+ else:
1026
+ code_preview = code
1027
+ executed_text_parts.append(
1028
+ f" 코드:\n {code_preview.replace(chr(10), chr(10) + ' ')}"
1029
+ )
1030
+
1031
+ executed_text = "\n".join(executed_text_parts) if executed_text_parts else "없음"
1032
+
1033
+ # 실패한 코드 추출
1034
+ failed_code = ""
1035
+ if failed_step.get("toolCalls"):
1036
+ for tc in failed_step["toolCalls"]:
1037
+ if tc.get("tool") == "jupyter_cell":
1038
+ failed_code = tc.get("parameters", {}).get("code", "")
1039
+ break
1040
+
1041
+ # traceback 처리
1042
+ traceback_data = error_info.get("traceback", [])
1043
+ if isinstance(traceback_data, list):
1044
+ traceback_str = "\n".join(traceback_data)
1045
+ else:
1046
+ traceback_str = str(traceback_data) if traceback_data else ""
1047
+
1048
+ # errorName (Python 예외 이름)이 있으면 우선 사용, 없으면 type 필드 사용
1049
+ # 예: "ModuleNotFoundError", "ImportError", "TypeError" 등
1050
+ error_type = error_info.get("errorName") or error_info.get("type", "runtime")
1051
+
1052
+ return ADAPTIVE_REPLAN_PROMPT.format(
1053
+ original_request=original_request,
1054
+ executed_steps=executed_text,
1055
+ failed_step_number=failed_step.get("stepNumber", "?"),
1056
+ failed_step_description=failed_step.get("description", ""),
1057
+ failed_code=failed_code,
1058
+ error_type=error_type, # Python 예외 이름 (ModuleNotFoundError 등)
1059
+ error_message=error_info.get("message", "Unknown error"),
1060
+ traceback=traceback_str,
1061
+ execution_output=execution_output if execution_output else "없음",
1062
+ available_libraries=", ".join(available_libraries)
1063
+ if available_libraries
1064
+ else "정보 없음",
1065
+ )
1066
+
1067
+
1068
+ def format_structured_plan_prompt(
1069
+ request: str,
1070
+ cell_count: int,
1071
+ imported_libraries: list,
1072
+ defined_variables: list,
1073
+ recent_cells: list,
1074
+ ) -> str:
1075
+ """구조화된 계획 생성 프롬프트 포맷팅 (Enhanced Planning)"""
1076
+ recent_cells_text = ""
1077
+ for i, cell in enumerate(recent_cells):
1078
+ cell_type = cell.get("type", "code")
1079
+ source = cell.get("source", "")[:300]
1080
+ recent_cells_text += (
1081
+ f"\n[셀 {cell.get('index', i)}] ({cell_type}):\n```\n{source}\n```\n"
1082
+ )
1083
+
1084
+ return STRUCTURED_PLAN_PROMPT.format(
1085
+ request=request,
1086
+ cell_count=cell_count,
1087
+ imported_libraries=", ".join(imported_libraries)
1088
+ if imported_libraries
1089
+ else "없음",
1090
+ defined_variables=", ".join(defined_variables) if defined_variables else "없음",
1091
+ recent_cells=recent_cells_text if recent_cells_text else "없음",
1092
+ )
1093
+
1094
+
1095
+ def format_reflection_prompt(
1096
+ step_number: int,
1097
+ step_description: str,
1098
+ executed_code: str,
1099
+ execution_status: str,
1100
+ execution_output: str,
1101
+ error_message: str,
1102
+ expected_outcome: str,
1103
+ validation_criteria: list,
1104
+ remaining_steps: list,
1105
+ ) -> str:
1106
+ """Reflection 프롬프트 포맷팅 (실행 결과 분석)"""
1107
+ # 검증 기준 텍스트
1108
+ criteria_text = (
1109
+ "\n".join([f"- {c}" for c in validation_criteria])
1110
+ if validation_criteria
1111
+ else "없음"
1112
+ )
1113
+
1114
+ # 남은 단계 텍스트
1115
+ remaining_text = (
1116
+ "\n".join(
1117
+ [
1118
+ f"- Step {s.get('stepNumber', i + 1)}: {s.get('description', '')}"
1119
+ for i, s in enumerate(remaining_steps)
1120
+ ]
1121
+ )
1122
+ if remaining_steps
1123
+ else "없음"
1124
+ )
1125
+
1126
+ return REFLECTION_PROMPT.format(
1127
+ step_number=step_number,
1128
+ step_description=step_description,
1129
+ executed_code=executed_code,
1130
+ execution_status=execution_status,
1131
+ execution_output=execution_output if execution_output else "없음",
1132
+ error_message=error_message if error_message else "없음",
1133
+ expected_outcome=expected_outcome if expected_outcome else "성공적 실행",
1134
+ validation_criteria=criteria_text,
1135
+ remaining_steps=remaining_text,
1136
+ )
1137
+
1138
+
1139
+ # ═══════════════════════════════════════════════════════════════════════════
1140
+ # LLM Fallback 에러 분석 프롬프트 (패턴 매칭 실패 시 사용)
1141
+ # ═══════════════════════════════════════════════════════════════════════════
1142
+
1143
+ ERROR_ANALYSIS_PROMPT = """에러를 분석하고 복구 전략을 결정하세요.
1144
+
1145
+ ## 에러 정보
1146
+
1147
+ - 오류 유형: {error_type}
1148
+ - 오류 메시지: {error_message}
1149
+ - 트레이스백:
1150
+ ```
1151
+ {traceback}
1152
+ ```
1153
+
1154
+ ## 이전 시도 횟수: {previous_attempts}
1155
+
1156
+ ## 이전 코드 (있는 경우)
1157
+ {previous_codes}
1158
+
1159
+ ## 복구 전략 선택지
1160
+
1161
+ 1. **refine**: 코드 수정으로 해결 가능한 에러
1162
+ - SyntaxError, TypeError, ValueError, KeyError 등 단순 코드 버그
1163
+
1164
+ 2. **insert_steps**: 선행 작업이 필요한 경우
1165
+ - 패키지 설치가 필요한 경우 (ModuleNotFoundError)
1166
+ - 데이터 전처리가 필요한 경우
1167
+
1168
+ 3. **replace_step**: 완전히 다른 접근법이 필요한 경우
1169
+ - 현재 방법이 근본적으로 작동하지 않는 경우
1170
+ - 대안적 라이브러리/알고리즘이 필요한 경우
1171
+
1172
+ 4. **replan_remaining**: 남은 모든 단계를 재계획해야 하는 경우
1173
+ - 시스템 레벨 문제 (dlopen 에러 등)
1174
+ - 전체 접근법 변경이 필요한 경우
1175
+
1176
+ ## 분석 지침
1177
+
1178
+ 1. 에러의 근본 원인을 파악하세요
1179
+ 2. 이전 시도 횟수를 고려하세요 (2회 이상 실패 시 다른 전략 고려)
1180
+ 3. 에러 메시지와 트레이스백을 면밀히 분석하세요
1181
+ 4. 가장 효율적인 복구 전략을 선택하세요
1182
+
1183
+ ## 출력 형식 (JSON)
1184
+
1185
+ ```json
1186
+ {{
1187
+ "analysis": {{
1188
+ "root_cause": "에러의 근본 원인 (1-2문장)",
1189
+ "is_approach_problem": true/false,
1190
+ "missing_prerequisites": ["누락된 선행 작업들"],
1191
+ "complexity": "simple | moderate | complex"
1192
+ }},
1193
+ "decision": "refine | insert_steps | replace_step | replan_remaining",
1194
+ "reasoning": "결정 이유 (1-2문장)",
1195
+ "confidence": 0.0-1.0,
1196
+ "changes": {{
1197
+ // decision이 "refine"인 경우:
1198
+ "refined_code": null,
1199
+
1200
+ // decision이 "insert_steps"인 경우:
1201
+ "new_steps": [
1202
+ {{
1203
+ "description": "단계 설명",
1204
+ "toolCalls": [{{"tool": "jupyter_cell", "parameters": {{"code": "코드"}}}}]
1205
+ }}
1206
+ ],
1207
+
1208
+ // decision이 "replace_step"인 경우:
1209
+ "replacement": {{
1210
+ "description": "새 단계 설명",
1211
+ "toolCalls": [{{"tool": "jupyter_cell", "parameters": {{"code": "코드"}}}}]
1212
+ }},
1213
+
1214
+ // decision이 "replan_remaining"인 경우:
1215
+ "new_plan": []
1216
+ }}
1217
+ }}
1218
+ ```
1219
+
1220
+ JSON만 출력하세요."""
1221
+
1222
+
1223
+ def format_error_analysis_prompt(
1224
+ error_type: str,
1225
+ error_message: str,
1226
+ traceback: str,
1227
+ previous_attempts: int = 0,
1228
+ previous_codes: list = None,
1229
+ ) -> str:
1230
+ """LLM Fallback 에러 분석 프롬프트 포맷팅"""
1231
+ previous_codes = previous_codes or []
1232
+ codes_text = ""
1233
+ if previous_codes:
1234
+ for i, code in enumerate(previous_codes[-3:], 1): # 최근 3개만
1235
+ codes_text += f"\n### 시도 {i}:\n```python\n{code[:500]}\n```\n"
1236
+ else:
1237
+ codes_text = "없음"
1238
+
1239
+ return ERROR_ANALYSIS_PROMPT.format(
1240
+ error_type=error_type,
1241
+ error_message=error_message[:500] if error_message else "없음",
1242
+ traceback=traceback[:1000] if traceback else "없음",
1243
+ previous_attempts=previous_attempts,
1244
+ previous_codes=codes_text,
1245
+ )
1246
+
1247
+
1248
+ # ═══════════════════════════════════════════════════════════════════════════
1249
+ # 프롬프트 치환: {PIP_INDEX_OPTION} placeholder를 실제 값으로 교체
1250
+ # ═══════════════════════════════════════════════════════════════════════════
1251
+
1252
+ # 모든 프롬프트에서 {PIP_INDEX_OPTION}을 실제 값으로 치환
1253
+ # - 로컬 환경: 빈 문자열 → `!pip install --timeout 180 패키지명`
1254
+ # - 내부망: "--index-url <url>" → `!pip install --index-url <url> --timeout 180 패키지명`
1255
+ PLAN_GENERATION_PROMPT = PLAN_GENERATION_PROMPT.replace(
1256
+ "{PIP_INDEX_OPTION}", PIP_INDEX_OPTION
1257
+ )
1258
+ ADAPTIVE_REPLAN_PROMPT = ADAPTIVE_REPLAN_PROMPT.replace(
1259
+ "{PIP_INDEX_OPTION}", PIP_INDEX_OPTION
1260
+ )