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.
- codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
- codebuff_agent-2.1.74.dist-info/METADATA +142 -0
- codebuff_agent-2.1.74.dist-info/RECORD +217 -0
- codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
- codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
- codebuff_core/__init__.py +153 -0
- codebuff_core/codebuff_config_defaults.json +64 -0
- codebuff_core/core/__init__.py +35 -0
- codebuff_core/core/code_validator.py +1238 -0
- codebuff_core/core/error_classifier.py +579 -0
- codebuff_core/core/exceptions.py +49 -0
- codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
- codebuff_core/directory_policy.py +32 -0
- codebuff_core/factory.py +155 -0
- codebuff_core/interfaces.py +197 -0
- codebuff_core/knowledge/__init__.py +31 -0
- codebuff_core/knowledge/chunking.py +375 -0
- codebuff_core/knowledge/loader.py +339 -0
- codebuff_core/knowledge/watchdog_service.py +354 -0
- codebuff_core/legacy_paths.py +69 -0
- codebuff_core/llm/__init__.py +13 -0
- codebuff_core/llm/service.py +407 -0
- codebuff_core/managers/__init__.py +22 -0
- codebuff_core/managers/config_manager.py +690 -0
- codebuff_core/managers/remote_config.py +673 -0
- codebuff_core/managers/remote_skills.py +539 -0
- codebuff_core/managers/session_manager.py +255 -0
- codebuff_core/middleware/__init__.py +3 -0
- codebuff_core/middleware/error_recovery.py +394 -0
- codebuff_core/middleware/logging_utils.py +33 -0
- codebuff_core/models/__init__.py +111 -0
- codebuff_core/models/agent.py +306 -0
- codebuff_core/models/chat.py +57 -0
- codebuff_core/models/common.py +82 -0
- codebuff_core/models/rag.py +300 -0
- codebuff_core/platform_rules.py +127 -0
- codebuff_core/prompts/__init__.py +44 -0
- codebuff_core/prompts/auto_agent_prompts.py +1278 -0
- codebuff_core/services/__init__.py +30 -0
- codebuff_core/services/agent_service.py +345 -0
- codebuff_core/services/analytics_aggregate.py +837 -0
- codebuff_core/services/chat_service.py +898 -0
- codebuff_core/services/context_condenser.py +306 -0
- codebuff_core/services/pii_guardrail.py +393 -0
- codebuff_core/services/rag_chat_agent.py +2993 -0
- codebuff_core/services/rag_collections.py +2740 -0
- codebuff_core/services/rag_manager.py +890 -0
- codebuff_core/services/rag_service.py +145 -0
- codebuff_core/services/retriever.py +260 -0
- codebuff_core/services/vllm_embedding_service.py +290 -0
- codebuff_core/tests/__init__.py +1 -0
- codebuff_core/tests/conftest.py +81 -0
- codebuff_core/tests/test_error_recovery.py +647 -0
- codebuff_core/tests/test_factory.py +135 -0
- codebuff_core/tests/test_services.py +150 -0
- jupyter_ext/__init__.py +266 -0
- jupyter_ext/_version.py +4 -0
- jupyter_ext/agent/__init__.py +49 -0
- jupyter_ext/agent/agent_factory.py +2270 -0
- jupyter_ext/agent/ast_tools.py +451 -0
- jupyter_ext/agent/autofix_middleware.py +481 -0
- jupyter_ext/agent/background_procs.py +372 -0
- jupyter_ext/agent/code_history_middleware.py +211 -0
- jupyter_ext/agent/codex_autofix.py +160 -0
- jupyter_ext/agent/codex_bridge.py +3339 -0
- jupyter_ext/agent/codex_hooks.py +320 -0
- jupyter_ext/agent/codex_slash.py +685 -0
- jupyter_ext/agent/codex_threads.py +114 -0
- jupyter_ext/agent/content_injection.py +377 -0
- jupyter_ext/agent/continuation_middleware.py +548 -0
- jupyter_ext/agent/custom_skills.py +335 -0
- jupyter_ext/agent/file_diffs.py +99 -0
- jupyter_ext/agent/git_history.py +188 -0
- jupyter_ext/agent/guardrail_middleware.py +421 -0
- jupyter_ext/agent/json_repair_utils.py +54 -0
- jupyter_ext/agent/langchain_tools.py +2673 -0
- jupyter_ext/agent/mermaid_fixer.py +289 -0
- jupyter_ext/agent/mermaid_middleware.py +135 -0
- jupyter_ext/agent/middleware.py +359 -0
- jupyter_ext/agent/middleware_prompts.py +53 -0
- jupyter_ext/agent/observability.py +334 -0
- jupyter_ext/agent/patch_tool_calls.py +57 -0
- jupyter_ext/agent/prompts.py +112 -0
- jupyter_ext/agent/skill_middleware.py +257 -0
- jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
- jupyter_ext/agent/skills/anomaly_detection.md +12 -0
- jupyter_ext/agent/skills/athena_sql.md +156 -0
- jupyter_ext/agent/skills/bdp_table_search.md +33 -0
- jupyter_ext/agent/skills/bigquery.md +65 -0
- jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
- jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
- jupyter_ext/agent/skills/classification_evaluation.md +11 -0
- jupyter_ext/agent/skills/clustering_analysis.md +12 -0
- jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
- jupyter_ext/agent/skills/data_analysis.md +236 -0
- jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
- jupyter_ext/agent/skills/data_loading.md +158 -0
- jupyter_ext/agent/skills/data_quality_report.md +13 -0
- jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
- jupyter_ext/agent/skills/emr_script_development.md +17 -0
- jupyter_ext/agent/skills/experiment_tracking.md +13 -0
- jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
- jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
- jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
- jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
- jupyter_ext/agent/skills/inference.md +392 -0
- jupyter_ext/agent/skills/lambda_function_development.md +19 -0
- jupyter_ext/agent/skills/model_comparison.md +11 -0
- jupyter_ext/agent/skills/model_explainability.md +12 -0
- jupyter_ext/agent/skills/model_training.md +318 -0
- jupyter_ext/agent/skills/pyspark.md +352 -0
- jupyter_ext/agent/skills/python_code_quality.md +35 -0
- jupyter_ext/agent/skills/regression_evaluation.md +12 -0
- jupyter_ext/agent/skills/spring_java_development.md +35 -0
- jupyter_ext/agent/skills/text_preprocessing.md +12 -0
- jupyter_ext/agent/skills/time_series_analysis.md +13 -0
- jupyter_ext/agent/skills/unit_testing.md +203 -0
- jupyter_ext/agent/todo_prompts.py +57 -0
- jupyter_ext/agent/tool_report.py +45 -0
- jupyter_ext/analytics_ingest.py +599 -0
- jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- jupyter_ext/handlers/__init__.py +8 -0
- jupyter_ext/handlers/analytics_handler.py +170 -0
- jupyter_ext/handlers/benchmark_handler.py +794 -0
- jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
- jupyter_ext/handlers/rag_admin_handler.py +689 -0
- jupyter_ext/handlers/websocket.py +497 -0
- jupyter_ext/http_handlers.py +8744 -0
- jupyter_ext/idle_monitor.py +346 -0
- jupyter_ext/labextension/package.json +157 -0
- jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
- jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
- jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
- jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
- jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
- jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
- jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
- jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
- jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
- jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
- jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
- jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- jupyter_ext/labextension/static/style.js +4 -0
- jupyter_ext/labextension/static/third-party-licenses.json +724 -0
- jupyter_ext/log_paths.py +113 -0
- jupyter_ext/resource_usage.py +519 -0
- jupyter_ext/secrets_bootstrap.py +116 -0
- jupyter_ext/team_access.py +105 -0
- jupyter_ext/tests/__init__.py +1 -0
- jupyter_ext/tests/test_context.py +234 -0
- jupyter_ext/tests/test_factory.py +563 -0
- jupyter_ext/tests/test_file_finder.py +139 -0
- jupyter_ext/tests/test_handlers.py +58 -0
- jupyter_ext/tests/test_mcp_selftest.py +204 -0
- jupyter_ext/tests/test_mermaid_fixer.py +272 -0
- jupyter_ext/tests/test_resource_tiers.py +298 -0
- jupyter_ext/tools/__init__.py +15 -0
- jupyter_ext/tools/base.py +90 -0
- jupyter_ext/tools/context.py +268 -0
- jupyter_ext/tools/dir_complete.py +86 -0
- jupyter_ext/tools/file_finder.py +303 -0
- jupyter_ext/tools/file_tools.py +244 -0
- jupyter_ext/tools/jupyter_tools.py +296 -0
- jupyter_ext/tools/registry.py +159 -0
- jupyter_ext/tools/search_tools.py +230 -0
- jupyter_ext/tools/security_tools.py +657 -0
- jupyter_ext/tools/shell_tools.py +109 -0
- jupyter_ext/tools/user_tools.py +132 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"""AutoFix Middleware - 코드 자동 수정 + 에러 복구 미들웨어.
|
|
2
|
+
|
|
3
|
+
Phase 1 (awrap_tool_call): jupyter_cell 실행 전 Ruff 자동 수정 + API 안티패턴 감지
|
|
4
|
+
Phase 2 (before_model): 실행 에러 → ErrorClassifier → 복구 프롬프트 주입
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
from collections.abc import Awaitable, Callable
|
|
13
|
+
from typing import Any, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
from langchain_core.messages import HumanMessage, ToolMessage
|
|
16
|
+
|
|
17
|
+
from langchain.agents.middleware.types import (
|
|
18
|
+
AgentMiddleware,
|
|
19
|
+
AgentState,
|
|
20
|
+
)
|
|
21
|
+
from langgraph.prebuilt.tool_node import ToolCallRequest
|
|
22
|
+
|
|
23
|
+
from codebuff_core.core.code_validator import CodeValidator, get_api_pattern_checker
|
|
24
|
+
from codebuff_core.core.error_classifier import (
|
|
25
|
+
ErrorAnalysis,
|
|
26
|
+
ReplanDecision,
|
|
27
|
+
get_error_classifier,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
from ..tools.context import NotebookContextManager
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
# HITL tools that execute code and can produce runtime errors
|
|
35
|
+
HITL_ERROR_TOOLS = {
|
|
36
|
+
"jupyter_cell_tool",
|
|
37
|
+
"jupyter_cell",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Marker prefix for error recovery messages (used for retry counting)
|
|
41
|
+
ERROR_RECOVERY_MARKER = "[ERROR_RECOVERY]"
|
|
42
|
+
|
|
43
|
+
# Error patterns in tool output
|
|
44
|
+
ERROR_PATTERNS = [
|
|
45
|
+
r"(\w+Error):\s*(.+)",
|
|
46
|
+
r"(\w+Exception):\s*(.+)",
|
|
47
|
+
r"Traceback \(most recent call last\)",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AutoFixMiddleware(AgentMiddleware):
|
|
52
|
+
"""코드 자동 수정 + 에러 복구 미들웨어.
|
|
53
|
+
|
|
54
|
+
Phase 1 (awrap_tool_call): jupyter_cell 실행 전 Ruff 자동 수정 + API 안티패턴 감지
|
|
55
|
+
Phase 2 (before_model): 실행 에러 → ErrorClassifier → 복구 프롬프트 주입
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
notebook_context: NotebookContextManager,
|
|
61
|
+
*,
|
|
62
|
+
max_retries: int = 2,
|
|
63
|
+
enable_ruff_fix: bool = True,
|
|
64
|
+
enable_api_check: bool = True,
|
|
65
|
+
session_id: str = "",
|
|
66
|
+
) -> None:
|
|
67
|
+
super().__init__()
|
|
68
|
+
self._notebook_context = notebook_context
|
|
69
|
+
self._max_retries = max_retries
|
|
70
|
+
self._enable_ruff_fix = enable_ruff_fix
|
|
71
|
+
self._enable_api_check = enable_api_check
|
|
72
|
+
self._session_id = session_id
|
|
73
|
+
self._classifier = get_error_classifier()
|
|
74
|
+
self._api_checker = get_api_pattern_checker()
|
|
75
|
+
self.tools = []
|
|
76
|
+
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
# Phase 1: Pre-execution — awrap_tool_call
|
|
79
|
+
# ------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
async def awrap_tool_call(
|
|
82
|
+
self,
|
|
83
|
+
request: ToolCallRequest,
|
|
84
|
+
handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Any]],
|
|
85
|
+
) -> ToolMessage | Any:
|
|
86
|
+
"""jupyter_cell 도구 호출 전 코드 자동 수정."""
|
|
87
|
+
tool_name = request.tool_call.get("name", "")
|
|
88
|
+
|
|
89
|
+
# jupyter_cell이 아닌 도구는 그대로 통과
|
|
90
|
+
if tool_name != "jupyter_cell":
|
|
91
|
+
return await handler(request)
|
|
92
|
+
|
|
93
|
+
args = request.tool_call.get("args", {})
|
|
94
|
+
code = args.get("code", "")
|
|
95
|
+
|
|
96
|
+
# 빈 코드는 통과
|
|
97
|
+
if not code or not code.strip():
|
|
98
|
+
return await handler(request)
|
|
99
|
+
|
|
100
|
+
fixed_code = code
|
|
101
|
+
|
|
102
|
+
# Ruff 자동 수정
|
|
103
|
+
if self._enable_ruff_fix:
|
|
104
|
+
try:
|
|
105
|
+
notebook_ctx = self._get_notebook_context_dict()
|
|
106
|
+
validator = CodeValidator(notebook_context=notebook_ctx)
|
|
107
|
+
ruff_fixed, issues = validator.check_with_ruff(code, auto_fix=True)
|
|
108
|
+
if ruff_fixed != code:
|
|
109
|
+
fixed_code = ruff_fixed
|
|
110
|
+
logger.info(
|
|
111
|
+
"AutoFix: Ruff auto-fixed code (%d issues remaining)",
|
|
112
|
+
len(issues),
|
|
113
|
+
)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.debug(
|
|
116
|
+
"AutoFix: Ruff check failed [%s], using original code",
|
|
117
|
+
type(e).__name__,
|
|
118
|
+
exc_info=True,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# API 안티패턴 감지 (경고 로깅만)
|
|
122
|
+
if self._enable_api_check:
|
|
123
|
+
try:
|
|
124
|
+
api_issues = self._api_checker.check(fixed_code)
|
|
125
|
+
for issue in api_issues:
|
|
126
|
+
logger.warning("AutoFix API pattern: %s", issue.message)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.debug(
|
|
129
|
+
"AutoFix: API pattern check failed [%s]",
|
|
130
|
+
type(e).__name__,
|
|
131
|
+
exc_info=True,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# 코드가 수정되었으면 tool_call 오버라이드
|
|
135
|
+
if fixed_code != code:
|
|
136
|
+
modified_call = {
|
|
137
|
+
**request.tool_call,
|
|
138
|
+
"args": {**args, "code": fixed_code},
|
|
139
|
+
}
|
|
140
|
+
request = request.override(tool_call=modified_call)
|
|
141
|
+
|
|
142
|
+
return await handler(request)
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
# Phase 2: Post-error — before_model
|
|
146
|
+
# ------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
def before_model(
|
|
149
|
+
self, state: AgentState[Any], runtime: Any
|
|
150
|
+
) -> dict[str, Any] | None:
|
|
151
|
+
"""실행 에러 발생 시 복구 프롬프트 주입."""
|
|
152
|
+
messages = state.get("messages", [])
|
|
153
|
+
if not messages:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
last_msg = messages[-1]
|
|
157
|
+
|
|
158
|
+
# HITL 도구 에러인지 확인
|
|
159
|
+
if not _is_hitl_tool_error(last_msg):
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
# 연속 복구 횟수 체크
|
|
163
|
+
recovery_count = _count_error_recoveries(messages)
|
|
164
|
+
if recovery_count >= self._max_retries:
|
|
165
|
+
logger.warning(
|
|
166
|
+
"AutoFix: max retries reached (%d/%d). Passing through.",
|
|
167
|
+
recovery_count,
|
|
168
|
+
self._max_retries,
|
|
169
|
+
)
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
# 에러 파싱 및 분류
|
|
173
|
+
content = getattr(last_msg, "content", "") or ""
|
|
174
|
+
error_type, error_msg, traceback_str = _extract_error_parts(content)
|
|
175
|
+
if not error_type:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
analysis = self._classifier.classify(error_type, error_msg, traceback_str)
|
|
179
|
+
|
|
180
|
+
# ErrorClassifier 결과를 structured log에 기록
|
|
181
|
+
from .observability import _write_structured_event
|
|
182
|
+
|
|
183
|
+
_write_structured_event(
|
|
184
|
+
{
|
|
185
|
+
"event": "error_classification",
|
|
186
|
+
"session_id": self._session_id,
|
|
187
|
+
"error_type": error_type,
|
|
188
|
+
"decision": analysis.decision.value,
|
|
189
|
+
"root_cause": analysis.root_cause[:200],
|
|
190
|
+
"used_llm": analysis.used_llm,
|
|
191
|
+
"confidence": analysis.confidence,
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
logger.info(
|
|
196
|
+
"AutoFix: %s -> %s (attempt %d/%d)",
|
|
197
|
+
error_type,
|
|
198
|
+
analysis.decision.value,
|
|
199
|
+
recovery_count + 1,
|
|
200
|
+
self._max_retries,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# 복구 프롬프트 생성
|
|
204
|
+
prompt = _build_replan_prompt(analysis, error_type, error_msg)
|
|
205
|
+
|
|
206
|
+
return {"messages": [HumanMessage(content=prompt)]}
|
|
207
|
+
|
|
208
|
+
# ------------------------------------------------------------------
|
|
209
|
+
# Helpers
|
|
210
|
+
# ------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
def _get_notebook_context_dict(self) -> dict:
|
|
213
|
+
"""NotebookContextManager → CodeValidator용 dict 변환."""
|
|
214
|
+
ctx = self._notebook_context.current_notebook
|
|
215
|
+
if not ctx:
|
|
216
|
+
return {}
|
|
217
|
+
|
|
218
|
+
defined_vars: list[str] = []
|
|
219
|
+
imported_libs: list[str] = []
|
|
220
|
+
|
|
221
|
+
for cell in ctx.get_code_cells():
|
|
222
|
+
source = cell.source or ""
|
|
223
|
+
for line in source.split("\n"):
|
|
224
|
+
stripped = line.strip()
|
|
225
|
+
# import 파싱
|
|
226
|
+
if stripped.startswith("import "):
|
|
227
|
+
parts = stripped.replace("import ", "").split(" as ")
|
|
228
|
+
name = parts[-1].strip().split(",")[0].strip()
|
|
229
|
+
imported_libs.append(name)
|
|
230
|
+
elif stripped.startswith("from ") and " import " in stripped:
|
|
231
|
+
after_import = stripped.split(" import ")[1]
|
|
232
|
+
for name in after_import.split(","):
|
|
233
|
+
name = name.strip().split(" as ")[-1].strip()
|
|
234
|
+
if name:
|
|
235
|
+
imported_libs.append(name)
|
|
236
|
+
# 변수 정의 파싱 (간단한 할당)
|
|
237
|
+
elif "=" in stripped and not stripped.startswith("#"):
|
|
238
|
+
lhs = stripped.split("=")[0].strip()
|
|
239
|
+
# 함수 호출이나 비교 연산자 제외
|
|
240
|
+
if (
|
|
241
|
+
lhs.isidentifier()
|
|
242
|
+
and not stripped.startswith("if ")
|
|
243
|
+
and not stripped.startswith("elif ")
|
|
244
|
+
and "==" not in stripped
|
|
245
|
+
and "!=" not in stripped
|
|
246
|
+
and "<=" not in stripped
|
|
247
|
+
and ">=" not in stripped
|
|
248
|
+
):
|
|
249
|
+
defined_vars.append(lhs)
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"definedVariables": defined_vars,
|
|
253
|
+
"importedLibraries": imported_libs,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ======================================================================
|
|
258
|
+
# Module-level helper functions (ported from error_recovery.py)
|
|
259
|
+
# ======================================================================
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _is_hitl_tool_error(msg: Any) -> bool:
|
|
263
|
+
"""Check if a message is a ToolMessage from a HITL tool containing an error."""
|
|
264
|
+
if getattr(msg, "type", "") != "tool":
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
tool_name = getattr(msg, "name", "") or ""
|
|
268
|
+
if tool_name not in HITL_ERROR_TOOLS:
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
content = getattr(msg, "content", "") or ""
|
|
272
|
+
if not isinstance(content, str):
|
|
273
|
+
return False
|
|
274
|
+
|
|
275
|
+
# Check structured execution_result first
|
|
276
|
+
try:
|
|
277
|
+
data = json.loads(content)
|
|
278
|
+
if isinstance(data, dict):
|
|
279
|
+
exec_result = data.get("execution_result")
|
|
280
|
+
if isinstance(exec_result, dict):
|
|
281
|
+
return exec_result.get("success") is False or bool(
|
|
282
|
+
exec_result.get("error")
|
|
283
|
+
)
|
|
284
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
return _has_error_indicators(content)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _has_error_indicators(content: str) -> bool:
|
|
291
|
+
"""Check if content contains Python error indicators."""
|
|
292
|
+
for pattern in ERROR_PATTERNS:
|
|
293
|
+
if re.search(pattern, content):
|
|
294
|
+
return True
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _count_error_recoveries(messages: list) -> int:
|
|
299
|
+
"""Count consecutive error recovery attempts since last successful HITL result."""
|
|
300
|
+
count = 0
|
|
301
|
+
for msg in reversed(messages):
|
|
302
|
+
content = getattr(msg, "content", "") or ""
|
|
303
|
+
msg_type = getattr(msg, "type", "")
|
|
304
|
+
|
|
305
|
+
if isinstance(content, str) and ERROR_RECOVERY_MARKER in content:
|
|
306
|
+
count += 1
|
|
307
|
+
elif msg_type == "tool":
|
|
308
|
+
tool_name = getattr(msg, "name", "") or ""
|
|
309
|
+
if tool_name in HITL_ERROR_TOOLS:
|
|
310
|
+
if isinstance(content, str) and _has_error_indicators(content):
|
|
311
|
+
continue
|
|
312
|
+
else:
|
|
313
|
+
break
|
|
314
|
+
continue
|
|
315
|
+
elif msg_type in ("ai", "human"):
|
|
316
|
+
if msg_type == "human" and isinstance(content, str):
|
|
317
|
+
if ERROR_RECOVERY_MARKER not in content:
|
|
318
|
+
break
|
|
319
|
+
continue
|
|
320
|
+
else:
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
return count
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _extract_error_parts(content: str) -> Tuple[Optional[str], str, str]:
|
|
327
|
+
"""Extract error type, message, and traceback from tool output."""
|
|
328
|
+
if not content or not isinstance(content, str):
|
|
329
|
+
return None, "", ""
|
|
330
|
+
|
|
331
|
+
# Strategy 1: Structured JSON
|
|
332
|
+
try:
|
|
333
|
+
data = json.loads(content)
|
|
334
|
+
if isinstance(data, dict):
|
|
335
|
+
exec_result = data.get("execution_result")
|
|
336
|
+
if isinstance(exec_result, dict):
|
|
337
|
+
error_type = exec_result.get("error_type")
|
|
338
|
+
if error_type:
|
|
339
|
+
error_msg = exec_result.get("error", "")
|
|
340
|
+
prefix = f"{error_type}: "
|
|
341
|
+
if error_msg.startswith(prefix):
|
|
342
|
+
error_msg = error_msg[len(prefix) :]
|
|
343
|
+
traceback_str = exec_result.get("traceback", "") or ""
|
|
344
|
+
return error_type, error_msg, traceback_str
|
|
345
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
# Strategy 2: Regex-based extraction
|
|
349
|
+
lines = content.strip().split("\n")
|
|
350
|
+
|
|
351
|
+
error_type = None
|
|
352
|
+
error_msg = ""
|
|
353
|
+
|
|
354
|
+
for line in reversed(lines):
|
|
355
|
+
line = line.strip()
|
|
356
|
+
line = re.sub(r"\x1b\[[0-9;]*m", "", line)
|
|
357
|
+
|
|
358
|
+
match = re.search(r"(\w+(?:Error|Exception)):\s*(.*)", line)
|
|
359
|
+
if match:
|
|
360
|
+
error_type = match.group(1)
|
|
361
|
+
error_msg = match.group(2)
|
|
362
|
+
break
|
|
363
|
+
|
|
364
|
+
match = re.match(r"^(\w+(?:Error|Exception))\s*$", line)
|
|
365
|
+
if match:
|
|
366
|
+
error_type = match.group(1)
|
|
367
|
+
break
|
|
368
|
+
|
|
369
|
+
traceback_str = ""
|
|
370
|
+
traceback_match = re.search(
|
|
371
|
+
r"(Traceback \(most recent call last\)[\s\S]*)", content
|
|
372
|
+
)
|
|
373
|
+
if traceback_match:
|
|
374
|
+
traceback_str = traceback_match.group(1)
|
|
375
|
+
|
|
376
|
+
return error_type, error_msg, traceback_str
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _get_error_guidance(error_type: str, error_msg: str) -> str:
|
|
380
|
+
"""Get error-specific guidance for the LLM."""
|
|
381
|
+
guidance_map = {
|
|
382
|
+
"KeyError": (
|
|
383
|
+
"Check the actual column/key names by running "
|
|
384
|
+
"`df.columns.tolist()` or `print(list(dict.keys()))` first."
|
|
385
|
+
),
|
|
386
|
+
"FileNotFoundError": (
|
|
387
|
+
"Verify the file path by running "
|
|
388
|
+
"`import os; print(os.listdir('.'))` or checking subdirectories."
|
|
389
|
+
),
|
|
390
|
+
"NameError": (
|
|
391
|
+
"Ensure all imports and variable definitions are in the same cell."
|
|
392
|
+
),
|
|
393
|
+
"ModuleNotFoundError": (
|
|
394
|
+
"Install the missing package using `!pip install <package_name>` before importing."
|
|
395
|
+
),
|
|
396
|
+
"ImportError": (
|
|
397
|
+
"Check if the package is installed and the import path is correct."
|
|
398
|
+
),
|
|
399
|
+
"TypeError": ("Check function arguments and data types."),
|
|
400
|
+
"ValueError": ("Check input values, data types, ranges, and formats."),
|
|
401
|
+
"IndexError": ("Check array/list bounds before accessing by index."),
|
|
402
|
+
"AttributeError": (
|
|
403
|
+
"Check if the object has the expected type. Use `type(obj)` and `dir(obj)`."
|
|
404
|
+
),
|
|
405
|
+
"SyntaxError": (
|
|
406
|
+
"Check for missing colons, brackets, parentheses, or indentation errors."
|
|
407
|
+
),
|
|
408
|
+
}
|
|
409
|
+
return guidance_map.get(
|
|
410
|
+
error_type,
|
|
411
|
+
f"Analyze the {error_type} carefully and fix the root cause before retrying.",
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _build_replan_prompt(
|
|
416
|
+
analysis: ErrorAnalysis,
|
|
417
|
+
error_type: str,
|
|
418
|
+
error_msg: str,
|
|
419
|
+
) -> str:
|
|
420
|
+
"""Build re-plan prompt based on ErrorClassifier analysis."""
|
|
421
|
+
decision = analysis.decision
|
|
422
|
+
guidance = _get_error_guidance(error_type, error_msg)
|
|
423
|
+
error_preview = error_msg[:200] if error_msg else "unknown error"
|
|
424
|
+
|
|
425
|
+
if decision == ReplanDecision.REFINE:
|
|
426
|
+
return (
|
|
427
|
+
f"{ERROR_RECOVERY_MARKER} 이전 코드 실행이 실패했습니다: "
|
|
428
|
+
f"{error_type}: {error_preview}\n\n"
|
|
429
|
+
f"근본 원인: {analysis.root_cause}\n\n"
|
|
430
|
+
f"지침:\n"
|
|
431
|
+
f"1. 에러를 주의 깊게 분석하세요\n"
|
|
432
|
+
f"2. {guidance}\n"
|
|
433
|
+
f"3. write_todos를 호출하여 수정 방안으로 계획을 업데이트하세요\n"
|
|
434
|
+
f"4. 수정된 코드로 jupyter_cell을 호출하세요\n"
|
|
435
|
+
f"5. 에러가 해결될 때까지 final_answer_tool을 호출하지 마세요"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
if decision == ReplanDecision.INSERT_STEPS:
|
|
439
|
+
pkg = analysis.missing_package or "unknown"
|
|
440
|
+
return (
|
|
441
|
+
f"{ERROR_RECOVERY_MARKER} 패키지 '{pkg}'가 없어서 이전 코드 실행이 "
|
|
442
|
+
f"실패했습니다.\n\n"
|
|
443
|
+
f"지침:\n"
|
|
444
|
+
f"1. jupyter_cell을 호출하여 설치하세요: !pip install {pkg}\n"
|
|
445
|
+
f"2. 그런 다음 원래 코드를 다시 실행하세요\n"
|
|
446
|
+
f"3. write_todos를 호출하여 패키지 설치 단계를 반영하세요\n"
|
|
447
|
+
f"4. 에러가 해결될 때까지 final_answer_tool을 호출하지 마세요"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
if decision == ReplanDecision.REPLACE_STEP:
|
|
451
|
+
return (
|
|
452
|
+
f"{ERROR_RECOVERY_MARKER} 이전 접근법이 완전히 실패했습니다: "
|
|
453
|
+
f"{error_type}: {error_preview}\n\n"
|
|
454
|
+
f"근본 원인: {analysis.root_cause}\n\n"
|
|
455
|
+
f"지침:\n"
|
|
456
|
+
f"1. 현재 접근법은 불가능합니다 — 완전히 다른 방법을 시도하세요\n"
|
|
457
|
+
f"2. {guidance}\n"
|
|
458
|
+
f"3. write_todos를 호출하여 현재 단계를 새로운 접근법으로 교체하세요\n"
|
|
459
|
+
f"4. 새로운 접근법으로 jupyter_cell을 호출하세요\n"
|
|
460
|
+
f"5. 에러가 해결될 때까지 final_answer_tool을 호출하지 마세요"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
if decision == ReplanDecision.REPLAN_REMAINING:
|
|
464
|
+
return (
|
|
465
|
+
f"{ERROR_RECOVERY_MARKER} 치명적 에러가 발생했습니다: "
|
|
466
|
+
f"{error_type}: {error_preview}\n\n"
|
|
467
|
+
f"근본 원인: {analysis.root_cause}\n\n"
|
|
468
|
+
f"지침:\n"
|
|
469
|
+
f"1. 근본적인 문제로 남은 모든 단계를 재계획해야 합니다\n"
|
|
470
|
+
f"2. {guidance}\n"
|
|
471
|
+
f"3. write_todos를 호출하여 남은 계획을 전면 재설계하세요\n"
|
|
472
|
+
f"4. 새로운 계획으로 jupyter_cell 실행을 시작하세요\n"
|
|
473
|
+
f"5. 에러가 해결될 때까지 final_answer_tool을 호출하지 마세요"
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
return (
|
|
477
|
+
f"{ERROR_RECOVERY_MARKER} 에러가 발생했습니다: "
|
|
478
|
+
f"{error_type}: {error_preview}\n\n"
|
|
479
|
+
f"에러를 수정하고 계속 진행하세요. "
|
|
480
|
+
f"해결될 때까지 final_answer_tool을 호출하지 마세요."
|
|
481
|
+
)
|