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,298 @@
|
|
|
1
|
+
"""Tests for resource tier classification and guidance generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from ..resource_usage import (
|
|
8
|
+
_classify_cpu,
|
|
9
|
+
_classify_memory,
|
|
10
|
+
_classify_single_gpu,
|
|
11
|
+
_classify_gpu,
|
|
12
|
+
classify_resource_tiers,
|
|
13
|
+
generate_tier_guidance,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# CPU tier classification
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestClassifyCPU:
|
|
23
|
+
@pytest.mark.parametrize(
|
|
24
|
+
"cores,expected",
|
|
25
|
+
[
|
|
26
|
+
(1, "low"),
|
|
27
|
+
(2, "low"),
|
|
28
|
+
(3, "low"),
|
|
29
|
+
(4, "medium"),
|
|
30
|
+
(7, "medium"),
|
|
31
|
+
(8, "high"),
|
|
32
|
+
(16, "high"),
|
|
33
|
+
(31, "high"),
|
|
34
|
+
(32, "very_high"),
|
|
35
|
+
(56, "very_high"),
|
|
36
|
+
(128, "very_high"),
|
|
37
|
+
(2.5, "low"), # fractional (K8s pod)
|
|
38
|
+
(None, "unknown"),
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
def test_cpu_tiers(self, cores, expected):
|
|
42
|
+
assert _classify_cpu(cores) == expected
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Memory tier classification
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestClassifyMemory:
|
|
51
|
+
@pytest.mark.parametrize(
|
|
52
|
+
"total_gb,expected",
|
|
53
|
+
[
|
|
54
|
+
(2, "constrained"),
|
|
55
|
+
(3.9, "constrained"),
|
|
56
|
+
(4, "limited"),
|
|
57
|
+
(15, "limited"),
|
|
58
|
+
(16, "comfortable"),
|
|
59
|
+
(63, "comfortable"),
|
|
60
|
+
(64, "large"),
|
|
61
|
+
(128, "large"),
|
|
62
|
+
(255, "large"),
|
|
63
|
+
(256, "very_large"),
|
|
64
|
+
(900, "very_large"),
|
|
65
|
+
(None, "unknown"),
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
def test_memory_tiers(self, total_gb, expected):
|
|
69
|
+
assert _classify_memory(total_gb) == expected
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# GPU tier classification
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestClassifyGPU:
|
|
78
|
+
@pytest.mark.parametrize(
|
|
79
|
+
"name,vram_mb,expected",
|
|
80
|
+
[
|
|
81
|
+
("NVIDIA L4", 24576, "entry"),
|
|
82
|
+
("Tesla T4", 16384, "entry"),
|
|
83
|
+
("Tesla V100-SXM2-16GB", 16384, "mid"),
|
|
84
|
+
("Tesla V100-SXM2-32GB", 32768, "high"),
|
|
85
|
+
("NVIDIA A100-SXM4-40GB", 40960, "high"),
|
|
86
|
+
("NVIDIA A100-SXM4-80GB", 81920, "ultra"),
|
|
87
|
+
("NVIDIA H100 80GB HBM3", 81920, "ultra"),
|
|
88
|
+
("NVIDIA H200", 144000, "ultra"),
|
|
89
|
+
("NVIDIA RTX 3060", 12288, "entry"),
|
|
90
|
+
("NVIDIA RTX 4090", 24576, "high"),
|
|
91
|
+
("Unknown GPU", 8192, "entry"),
|
|
92
|
+
("Unknown GPU", 32768, "high"),
|
|
93
|
+
("Unknown GPU", 49152, "ultra"),
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
def test_single_gpu_tiers(self, name, vram_mb, expected):
|
|
97
|
+
assert _classify_single_gpu(name, vram_mb) == expected
|
|
98
|
+
|
|
99
|
+
def test_no_gpu(self):
|
|
100
|
+
result = _classify_gpu([])
|
|
101
|
+
assert result["tier"] == "none"
|
|
102
|
+
assert result["count"] == 0
|
|
103
|
+
|
|
104
|
+
def test_single_gpu(self):
|
|
105
|
+
gpus = [{"name": "NVIDIA A100-SXM4-80GB", "memory_total_mb": 81920}]
|
|
106
|
+
result = _classify_gpu(gpus)
|
|
107
|
+
assert result["tier"] == "ultra"
|
|
108
|
+
assert result["count"] == 1
|
|
109
|
+
assert not result["multi_gpu"]
|
|
110
|
+
|
|
111
|
+
def test_multi_gpu(self):
|
|
112
|
+
gpus = [
|
|
113
|
+
{"name": "NVIDIA H200", "memory_total_mb": 144000},
|
|
114
|
+
{"name": "NVIDIA H200", "memory_total_mb": 144000},
|
|
115
|
+
{"name": "NVIDIA H200", "memory_total_mb": 144000},
|
|
116
|
+
{"name": "NVIDIA H200", "memory_total_mb": 144000},
|
|
117
|
+
]
|
|
118
|
+
result = _classify_gpu(gpus)
|
|
119
|
+
assert result["tier"] == "ultra"
|
|
120
|
+
assert result["count"] == 4
|
|
121
|
+
assert result["multi_gpu"]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# classify_resource_tiers integration
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestClassifyResourceTiers:
|
|
130
|
+
def _make_data(self, cores=8, total_gb=32, avail_gb=24, gpus=None):
|
|
131
|
+
return {
|
|
132
|
+
"cpu": {"cores": cores, "usage_percent": 30.0},
|
|
133
|
+
"memory": {
|
|
134
|
+
"total_gb": total_gb,
|
|
135
|
+
"available_gb": avail_gb,
|
|
136
|
+
"used_gb": total_gb - avail_gb,
|
|
137
|
+
},
|
|
138
|
+
"gpus": gpus or [],
|
|
139
|
+
"gpu_status": "available" if gpus else "not_detected",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
def test_mid_workstation(self):
|
|
143
|
+
tiers = classify_resource_tiers(
|
|
144
|
+
self._make_data(cores=7, total_gb=128, avail_gb=100)
|
|
145
|
+
)
|
|
146
|
+
assert tiers["cpu_tier"] == "medium"
|
|
147
|
+
assert tiers["mem_tier"] == "large"
|
|
148
|
+
assert tiers["gpu"]["tier"] == "none"
|
|
149
|
+
|
|
150
|
+
def test_high_server(self):
|
|
151
|
+
gpus = [
|
|
152
|
+
{"name": "NVIDIA A100-SXM4-80GB", "memory_total_mb": 81920},
|
|
153
|
+
{"name": "NVIDIA A100-SXM4-80GB", "memory_total_mb": 81920},
|
|
154
|
+
]
|
|
155
|
+
tiers = classify_resource_tiers(
|
|
156
|
+
self._make_data(cores=56, total_gb=900, avail_gb=850, gpus=gpus)
|
|
157
|
+
)
|
|
158
|
+
assert tiers["cpu_tier"] == "very_high"
|
|
159
|
+
assert tiers["mem_tier"] == "very_large"
|
|
160
|
+
assert tiers["gpu"]["tier"] == "ultra"
|
|
161
|
+
assert tiers["gpu"]["multi_gpu"]
|
|
162
|
+
|
|
163
|
+
def test_low_container(self):
|
|
164
|
+
tiers = classify_resource_tiers(
|
|
165
|
+
self._make_data(cores=2, total_gb=4, avail_gb=2)
|
|
166
|
+
)
|
|
167
|
+
assert tiers["cpu_tier"] == "low"
|
|
168
|
+
assert tiers["mem_tier"] == "limited"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
# Guidance generation — differentiation tests
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class TestGenerateTierGuidance:
|
|
177
|
+
def _make_data(self, cores=8, total_gb=32, avail_gb=24, gpus=None):
|
|
178
|
+
return {
|
|
179
|
+
"cpu": {"cores": cores, "usage_percent": 30.0},
|
|
180
|
+
"memory": {
|
|
181
|
+
"total_gb": total_gb,
|
|
182
|
+
"available_gb": avail_gb,
|
|
183
|
+
"used_gb": total_gb - avail_gb,
|
|
184
|
+
},
|
|
185
|
+
"gpus": gpus or [],
|
|
186
|
+
"gpu_status": "available" if gpus else "not_detected",
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
def test_scenario_a_mid_workstation_with_l4(self):
|
|
190
|
+
"""7코어/128GB/L4 → Polars 권장, joblib, LoRA"""
|
|
191
|
+
gpus = [{"name": "NVIDIA L4", "memory_total_mb": 24576}]
|
|
192
|
+
tiers = classify_resource_tiers(
|
|
193
|
+
self._make_data(cores=7, total_gb=128, avail_gb=100, gpus=gpus)
|
|
194
|
+
)
|
|
195
|
+
guidance = generate_tier_guidance(tiers)
|
|
196
|
+
|
|
197
|
+
assert "joblib" in guidance
|
|
198
|
+
assert "n_jobs=6" in guidance or "n_jobs" in guidance
|
|
199
|
+
assert "Polars" in guidance
|
|
200
|
+
assert "LoRA" in guidance or "entry" in guidance
|
|
201
|
+
|
|
202
|
+
def test_scenario_b_high_server_with_a100(self):
|
|
203
|
+
"""56코어/900GB/A100x2 → Dask/Ray, cuDF, DDP"""
|
|
204
|
+
gpus = [
|
|
205
|
+
{"name": "NVIDIA A100-SXM4-80GB", "memory_total_mb": 81920},
|
|
206
|
+
{"name": "NVIDIA A100-SXM4-80GB", "memory_total_mb": 81920},
|
|
207
|
+
]
|
|
208
|
+
tiers = classify_resource_tiers(
|
|
209
|
+
self._make_data(cores=56, total_gb=900, avail_gb=850, gpus=gpus)
|
|
210
|
+
)
|
|
211
|
+
guidance = generate_tier_guidance(tiers)
|
|
212
|
+
|
|
213
|
+
assert "Dask" in guidance or "Ray" in guidance
|
|
214
|
+
assert "cuDF" in guidance or "cudf" in guidance.lower()
|
|
215
|
+
assert "DDP" in guidance or "FSDP" in guidance or "멀티GPU" in guidance
|
|
216
|
+
assert "서버급" in guidance
|
|
217
|
+
|
|
218
|
+
def test_scenario_c_high_server_no_gpu(self):
|
|
219
|
+
"""56코어/900GB/GPU없음 → Dask/Ray CPU, Polars 멀티스레드"""
|
|
220
|
+
tiers = classify_resource_tiers(
|
|
221
|
+
self._make_data(cores=56, total_gb=900, avail_gb=850)
|
|
222
|
+
)
|
|
223
|
+
guidance = generate_tier_guidance(tiers)
|
|
224
|
+
|
|
225
|
+
assert "Dask" in guidance or "Ray" in guidance
|
|
226
|
+
assert "Polars" in guidance
|
|
227
|
+
assert "GPU 미감지" in guidance
|
|
228
|
+
assert "device='cpu'" in guidance or "CPU" in guidance
|
|
229
|
+
|
|
230
|
+
def test_scenario_d_low_container(self):
|
|
231
|
+
"""2코어/4GB/GPU없음 → chunked, 병렬처리 비권장"""
|
|
232
|
+
tiers = classify_resource_tiers(
|
|
233
|
+
self._make_data(cores=2, total_gb=4, avail_gb=2)
|
|
234
|
+
)
|
|
235
|
+
guidance = generate_tier_guidance(tiers)
|
|
236
|
+
|
|
237
|
+
assert "저사양" in guidance
|
|
238
|
+
assert "병렬처리" in guidance
|
|
239
|
+
assert "dtypes" in guidance or "chunked" in guidance
|
|
240
|
+
|
|
241
|
+
def test_scenarios_produce_different_guidance(self):
|
|
242
|
+
"""핵심: 서로 다른 환경은 확실히 다른 가이드를 생성해야 함."""
|
|
243
|
+
gpus_l4 = [{"name": "NVIDIA L4", "memory_total_mb": 24576}]
|
|
244
|
+
gpus_a100 = [
|
|
245
|
+
{"name": "NVIDIA A100-SXM4-80GB", "memory_total_mb": 81920},
|
|
246
|
+
{"name": "NVIDIA A100-SXM4-80GB", "memory_total_mb": 81920},
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
tiers_a = classify_resource_tiers(
|
|
250
|
+
self._make_data(cores=7, total_gb=128, avail_gb=100, gpus=gpus_l4)
|
|
251
|
+
)
|
|
252
|
+
tiers_b = classify_resource_tiers(
|
|
253
|
+
self._make_data(cores=56, total_gb=900, avail_gb=850, gpus=gpus_a100)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
guidance_a = generate_tier_guidance(tiers_a)
|
|
257
|
+
guidance_b = generate_tier_guidance(tiers_b)
|
|
258
|
+
|
|
259
|
+
# Must be substantially different
|
|
260
|
+
assert guidance_a != guidance_b
|
|
261
|
+
|
|
262
|
+
# Scenario A should NOT contain server-grade terms
|
|
263
|
+
assert "서버급" not in guidance_a
|
|
264
|
+
assert "Spark" not in guidance_a
|
|
265
|
+
|
|
266
|
+
# Scenario B should NOT contain low-tier terms
|
|
267
|
+
assert "저사양" not in guidance_b
|
|
268
|
+
assert "LoRA" not in guidance_b
|
|
269
|
+
|
|
270
|
+
def test_mode_codegen_differs_from_review(self):
|
|
271
|
+
"""codegen 모드는 다른 헤더를 사용."""
|
|
272
|
+
tiers = classify_resource_tiers(self._make_data())
|
|
273
|
+
review = generate_tier_guidance(tiers, mode="review")
|
|
274
|
+
codegen = generate_tier_guidance(tiers, mode="codegen")
|
|
275
|
+
|
|
276
|
+
assert "환경 맞춤 기술 스택 권장" in review
|
|
277
|
+
assert "기술 스택 지침" in codegen
|
|
278
|
+
|
|
279
|
+
def test_memory_pressure_warning(self):
|
|
280
|
+
"""가용 메모리가 전체의 30% 미만일 때 경고 출력."""
|
|
281
|
+
tiers = classify_resource_tiers(
|
|
282
|
+
self._make_data(cores=16, total_gb=128, avail_gb=10)
|
|
283
|
+
)
|
|
284
|
+
guidance = generate_tier_guidance(tiers)
|
|
285
|
+
|
|
286
|
+
assert "메모리 압박" in guidance
|
|
287
|
+
|
|
288
|
+
def test_h200_multi_gpu_guidance(self):
|
|
289
|
+
"""4x H200 → tensor parallelism, FSDP"""
|
|
290
|
+
gpus = [{"name": "NVIDIA H200", "memory_total_mb": 144000}] * 4
|
|
291
|
+
tiers = classify_resource_tiers(
|
|
292
|
+
self._make_data(cores=56, total_gb=900, avail_gb=850, gpus=gpus)
|
|
293
|
+
)
|
|
294
|
+
guidance = generate_tier_guidance(tiers)
|
|
295
|
+
|
|
296
|
+
assert "FSDP" in guidance or "DeepSpeed" in guidance
|
|
297
|
+
assert "멀티GPU" in guidance
|
|
298
|
+
assert "tensor" in guidance.lower() or "pipeline" in guidance.lower()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Tool system for Codebuff."""
|
|
2
|
+
|
|
3
|
+
from .base import BaseTool, ToolResult
|
|
4
|
+
from .registry import ToolRegistry
|
|
5
|
+
from .context import NotebookContextManager, NotebookContext, CellInfo
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BaseTool",
|
|
9
|
+
"ToolResult",
|
|
10
|
+
"ToolRegistry",
|
|
11
|
+
# Notebook context management
|
|
12
|
+
"NotebookContextManager",
|
|
13
|
+
"NotebookContext",
|
|
14
|
+
"CellInfo",
|
|
15
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""기본 도구 인터페이스."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ToolResult:
|
|
12
|
+
"""도구 실행 결과."""
|
|
13
|
+
|
|
14
|
+
success: bool
|
|
15
|
+
data: Optional[Any] = None
|
|
16
|
+
error: Optional[str] = None
|
|
17
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
|
|
19
|
+
def to_dict(self) -> dict[str, Any]:
|
|
20
|
+
"""딕셔너리로 변환."""
|
|
21
|
+
result = {"success": self.success}
|
|
22
|
+
if self.data is not None:
|
|
23
|
+
result["data"] = self.data
|
|
24
|
+
if self.error is not None:
|
|
25
|
+
result["error"] = self.error
|
|
26
|
+
if self.metadata:
|
|
27
|
+
result["metadata"] = self.metadata
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BaseTool(ABC):
|
|
32
|
+
"""도구의 추상 기본 클래스."""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def name(self) -> str:
|
|
37
|
+
"""도구 이름."""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def description(self) -> str:
|
|
43
|
+
"""도구 설명."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def parameters(self) -> dict[str, Any]:
|
|
49
|
+
"""도구 매개변수 스키마 (JSON Schema 형식)."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def risk_level(self) -> str:
|
|
54
|
+
"""
|
|
55
|
+
도구의 위험 수준.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
다음 중 하나: "low", "medium", "high", "critical"
|
|
59
|
+
"""
|
|
60
|
+
return "low"
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def definition(self) -> dict[str, Any]:
|
|
64
|
+
"""
|
|
65
|
+
OpenAI 호환 도구 정의를 가져옵니다.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
도구 정의 딕셔너리
|
|
69
|
+
"""
|
|
70
|
+
return {
|
|
71
|
+
"type": "function",
|
|
72
|
+
"function": {
|
|
73
|
+
"name": self.name,
|
|
74
|
+
"description": self.description,
|
|
75
|
+
"parameters": self.parameters,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
async def execute(self, **kwargs: Any) -> ToolResult:
|
|
81
|
+
"""
|
|
82
|
+
도구를 실행합니다.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
**kwargs: 도구 인자
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
실행 결과가 담긴 ToolResult
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Jupyter 도구를 위한 노트북 컨텍스트 관리."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class CellInfo:
|
|
11
|
+
"""노트북 셀 정보."""
|
|
12
|
+
|
|
13
|
+
index: int
|
|
14
|
+
cell_type: str # "code" 또는 "markdown"
|
|
15
|
+
source: str
|
|
16
|
+
outputs: list[dict[str, Any]] = field(default_factory=list)
|
|
17
|
+
execution_count: Optional[int] = None
|
|
18
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class NotebookContext:
|
|
23
|
+
"""현재 노트북에 대한 컨텍스트 정보."""
|
|
24
|
+
|
|
25
|
+
path: str
|
|
26
|
+
cells: list[CellInfo] = field(default_factory=list)
|
|
27
|
+
kernel_name: Optional[str] = None
|
|
28
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def cell_count(self) -> int:
|
|
32
|
+
"""노트북의 셀 수를 가져옵니다."""
|
|
33
|
+
return len(self.cells)
|
|
34
|
+
|
|
35
|
+
def get_cell(self, index: int) -> Optional[CellInfo]:
|
|
36
|
+
"""인덱스로 셀을 가져옵니다."""
|
|
37
|
+
if 0 <= index < len(self.cells):
|
|
38
|
+
return self.cells[index]
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
def get_code_cells(self) -> list[CellInfo]:
|
|
42
|
+
"""모든 코드 셀을 가져옵니다."""
|
|
43
|
+
return [c for c in self.cells if c.cell_type == "code"]
|
|
44
|
+
|
|
45
|
+
def get_markdown_cells(self) -> list[CellInfo]:
|
|
46
|
+
"""모든 마크다운 셀을 가져옵니다."""
|
|
47
|
+
return [c for c in self.cells if c.cell_type == "markdown"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class NotebookContextManager:
|
|
51
|
+
"""
|
|
52
|
+
노트북 컨텍스트 관리자로 노트북 상태에 대한 접근을 제공합니다.
|
|
53
|
+
|
|
54
|
+
이 클래스는 Jupyter 서버의 노트북 모델과 LangChain Agent 도구 사이를
|
|
55
|
+
연결하여 도구가 노트북 상태를 읽고 수정할 수 있게 합니다.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
kernel_manager: Any = None,
|
|
61
|
+
contents_manager: Any = None,
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
컨텍스트 관리자를 초기화합니다.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
kernel_manager: 코드 실행을 위한 Jupyter 커널 관리자
|
|
68
|
+
contents_manager: 파일 작업을 위한 Jupyter 콘텐츠 관리자
|
|
69
|
+
"""
|
|
70
|
+
self._kernel_manager = kernel_manager
|
|
71
|
+
self._contents_manager = contents_manager
|
|
72
|
+
self._current_notebook: Optional[NotebookContext] = None
|
|
73
|
+
self._notebook_model: Optional[dict[str, Any]] = None
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def current_notebook(self) -> Optional[NotebookContext]:
|
|
77
|
+
"""현재 노트북 컨텍스트를 가져옵니다."""
|
|
78
|
+
return self._current_notebook
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def notebook_model(self) -> Optional[dict[str, Any]]:
|
|
82
|
+
"""원시 노트북 모델을 가져옵니다 (직접 조작용)."""
|
|
83
|
+
return self._notebook_model
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def kernel_manager(self) -> Any:
|
|
87
|
+
"""커널 관리자를 가져옵니다."""
|
|
88
|
+
return self._kernel_manager
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def contents_manager(self) -> Any:
|
|
92
|
+
"""콘텐츠 관리자를 가져옵니다."""
|
|
93
|
+
return self._contents_manager
|
|
94
|
+
|
|
95
|
+
def set_notebook(self, path: str, model: dict[str, Any]) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Jupyter 노트북 모델에서 현재 노트북 컨텍스트를 설정합니다.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
path: 노트북 경로
|
|
101
|
+
model: Jupyter 노트북 모델 딕셔너리
|
|
102
|
+
"""
|
|
103
|
+
self._notebook_model = model
|
|
104
|
+
|
|
105
|
+
# 모델에서 셀 파싱
|
|
106
|
+
content = model.get("content", {})
|
|
107
|
+
raw_cells = content.get("cells", [])
|
|
108
|
+
cells = []
|
|
109
|
+
|
|
110
|
+
for i, cell in enumerate(raw_cells):
|
|
111
|
+
cells.append(
|
|
112
|
+
CellInfo(
|
|
113
|
+
index=i,
|
|
114
|
+
cell_type=cell.get("cell_type", "code"),
|
|
115
|
+
source=cell.get("source", ""),
|
|
116
|
+
outputs=cell.get("outputs", []),
|
|
117
|
+
execution_count=cell.get("execution_count"),
|
|
118
|
+
metadata=cell.get("metadata", {}),
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# 노트북 메타데이터 추출
|
|
123
|
+
nb_metadata = content.get("metadata", {})
|
|
124
|
+
kernel_info = nb_metadata.get("kernelspec", {})
|
|
125
|
+
|
|
126
|
+
self._current_notebook = NotebookContext(
|
|
127
|
+
path=path,
|
|
128
|
+
cells=cells,
|
|
129
|
+
kernel_name=kernel_info.get("name"),
|
|
130
|
+
metadata=nb_metadata,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def clear(self) -> None:
|
|
134
|
+
"""현재 노트북 컨텍스트를 지웁니다."""
|
|
135
|
+
self._current_notebook = None
|
|
136
|
+
self._notebook_model = None
|
|
137
|
+
|
|
138
|
+
def update_cell(self, index: int, source: str) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
셀의 소스 코드를 업데이트합니다.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
index: 셀 인덱스
|
|
144
|
+
source: 새 소스 코드
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
성공하면 True
|
|
148
|
+
"""
|
|
149
|
+
if not self._notebook_model or not self._current_notebook:
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
cells = self._notebook_model.get("content", {}).get("cells", [])
|
|
153
|
+
if not 0 <= index < len(cells):
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
cells[index]["source"] = source
|
|
157
|
+
|
|
158
|
+
# 컨텍스트 업데이트
|
|
159
|
+
if 0 <= index < len(self._current_notebook.cells):
|
|
160
|
+
self._current_notebook.cells[index].source = source
|
|
161
|
+
|
|
162
|
+
return True
|
|
163
|
+
|
|
164
|
+
def insert_cell(
|
|
165
|
+
self,
|
|
166
|
+
source: str,
|
|
167
|
+
cell_type: str = "code",
|
|
168
|
+
insert_after: Optional[int] = None,
|
|
169
|
+
) -> int:
|
|
170
|
+
"""
|
|
171
|
+
새 셀을 삽입합니다.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
source: 셀 소스 내용
|
|
175
|
+
cell_type: "code" 또는 "markdown"
|
|
176
|
+
insert_after: 이 인덱스 뒤에 삽입 (None = 끝에 추가)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
새 셀의 인덱스
|
|
180
|
+
"""
|
|
181
|
+
if not self._notebook_model:
|
|
182
|
+
raise ValueError("노트북 모델이 설정되지 않음")
|
|
183
|
+
|
|
184
|
+
cells = self._notebook_model.get("content", {}).get("cells", [])
|
|
185
|
+
|
|
186
|
+
new_cell = {
|
|
187
|
+
"cell_type": cell_type,
|
|
188
|
+
"source": source,
|
|
189
|
+
"metadata": {},
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if cell_type == "code":
|
|
193
|
+
new_cell["outputs"] = []
|
|
194
|
+
new_cell["execution_count"] = None
|
|
195
|
+
|
|
196
|
+
if insert_after is not None and 0 <= insert_after < len(cells):
|
|
197
|
+
cells.insert(insert_after + 1, new_cell)
|
|
198
|
+
new_index = insert_after + 1
|
|
199
|
+
else:
|
|
200
|
+
cells.append(new_cell)
|
|
201
|
+
new_index = len(cells) - 1
|
|
202
|
+
|
|
203
|
+
# 컨텍스트 업데이트
|
|
204
|
+
if self._current_notebook:
|
|
205
|
+
new_cell_info = CellInfo(
|
|
206
|
+
index=new_index,
|
|
207
|
+
cell_type=cell_type,
|
|
208
|
+
source=source,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if insert_after is not None and 0 <= insert_after < len(
|
|
212
|
+
self._current_notebook.cells
|
|
213
|
+
):
|
|
214
|
+
self._current_notebook.cells.insert(insert_after + 1, new_cell_info)
|
|
215
|
+
# 이후 셀 인덱스 재설정
|
|
216
|
+
for i in range(insert_after + 2, len(self._current_notebook.cells)):
|
|
217
|
+
self._current_notebook.cells[i].index = i
|
|
218
|
+
else:
|
|
219
|
+
self._current_notebook.cells.append(new_cell_info)
|
|
220
|
+
|
|
221
|
+
return new_index
|
|
222
|
+
|
|
223
|
+
def delete_cell(self, index: int) -> bool:
|
|
224
|
+
"""
|
|
225
|
+
셀을 삭제합니다.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
index: 삭제할 셀 인덱스
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
성공하면 True
|
|
232
|
+
"""
|
|
233
|
+
if not self._notebook_model or not self._current_notebook:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
cells = self._notebook_model.get("content", {}).get("cells", [])
|
|
237
|
+
if not 0 <= index < len(cells):
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
del cells[index]
|
|
241
|
+
|
|
242
|
+
# 컨텍스트 업데이트
|
|
243
|
+
if 0 <= index < len(self._current_notebook.cells):
|
|
244
|
+
del self._current_notebook.cells[index]
|
|
245
|
+
# 이후 셀 인덱스 재설정
|
|
246
|
+
for i in range(index, len(self._current_notebook.cells)):
|
|
247
|
+
self._current_notebook.cells[i].index = i
|
|
248
|
+
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
def get_summary(self) -> dict[str, Any]:
|
|
252
|
+
"""
|
|
253
|
+
현재 노트북 상태의 요약을 가져옵니다.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
노트북 요약이 담긴 딕셔너리
|
|
257
|
+
"""
|
|
258
|
+
if not self._current_notebook:
|
|
259
|
+
return {"status": "no_notebook"}
|
|
260
|
+
|
|
261
|
+
nb = self._current_notebook
|
|
262
|
+
return {
|
|
263
|
+
"path": nb.path,
|
|
264
|
+
"cell_count": nb.cell_count,
|
|
265
|
+
"code_cells": len(nb.get_code_cells()),
|
|
266
|
+
"markdown_cells": len(nb.get_markdown_cells()),
|
|
267
|
+
"kernel": nb.kernel_name,
|
|
268
|
+
}
|