codebuff-agent 2.1.74__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  2. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
  3. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  4. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
  5. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
  6. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
  7. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
  8. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
  9. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
  10. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
  11. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  12. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  13. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  14. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
  15. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
  16. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
  17. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
  18. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
  19. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  20. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  21. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
  22. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
  23. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  24. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
  25. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
  26. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
  27. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
  28. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  29. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  30. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  31. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  32. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
  33. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
  34. codebuff_agent-2.1.74.dist-info/METADATA +142 -0
  35. codebuff_agent-2.1.74.dist-info/RECORD +217 -0
  36. codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
  37. codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
  38. codebuff_core/__init__.py +153 -0
  39. codebuff_core/codebuff_config_defaults.json +64 -0
  40. codebuff_core/core/__init__.py +35 -0
  41. codebuff_core/core/code_validator.py +1238 -0
  42. codebuff_core/core/error_classifier.py +579 -0
  43. codebuff_core/core/exceptions.py +49 -0
  44. codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
  45. codebuff_core/directory_policy.py +32 -0
  46. codebuff_core/factory.py +155 -0
  47. codebuff_core/interfaces.py +197 -0
  48. codebuff_core/knowledge/__init__.py +31 -0
  49. codebuff_core/knowledge/chunking.py +375 -0
  50. codebuff_core/knowledge/loader.py +339 -0
  51. codebuff_core/knowledge/watchdog_service.py +354 -0
  52. codebuff_core/legacy_paths.py +69 -0
  53. codebuff_core/llm/__init__.py +13 -0
  54. codebuff_core/llm/service.py +407 -0
  55. codebuff_core/managers/__init__.py +22 -0
  56. codebuff_core/managers/config_manager.py +690 -0
  57. codebuff_core/managers/remote_config.py +673 -0
  58. codebuff_core/managers/remote_skills.py +539 -0
  59. codebuff_core/managers/session_manager.py +255 -0
  60. codebuff_core/middleware/__init__.py +3 -0
  61. codebuff_core/middleware/error_recovery.py +394 -0
  62. codebuff_core/middleware/logging_utils.py +33 -0
  63. codebuff_core/models/__init__.py +111 -0
  64. codebuff_core/models/agent.py +306 -0
  65. codebuff_core/models/chat.py +57 -0
  66. codebuff_core/models/common.py +82 -0
  67. codebuff_core/models/rag.py +300 -0
  68. codebuff_core/platform_rules.py +127 -0
  69. codebuff_core/prompts/__init__.py +44 -0
  70. codebuff_core/prompts/auto_agent_prompts.py +1278 -0
  71. codebuff_core/services/__init__.py +30 -0
  72. codebuff_core/services/agent_service.py +345 -0
  73. codebuff_core/services/analytics_aggregate.py +837 -0
  74. codebuff_core/services/chat_service.py +898 -0
  75. codebuff_core/services/context_condenser.py +306 -0
  76. codebuff_core/services/pii_guardrail.py +393 -0
  77. codebuff_core/services/rag_chat_agent.py +2993 -0
  78. codebuff_core/services/rag_collections.py +2740 -0
  79. codebuff_core/services/rag_manager.py +890 -0
  80. codebuff_core/services/rag_service.py +145 -0
  81. codebuff_core/services/retriever.py +260 -0
  82. codebuff_core/services/vllm_embedding_service.py +290 -0
  83. codebuff_core/tests/__init__.py +1 -0
  84. codebuff_core/tests/conftest.py +81 -0
  85. codebuff_core/tests/test_error_recovery.py +647 -0
  86. codebuff_core/tests/test_factory.py +135 -0
  87. codebuff_core/tests/test_services.py +150 -0
  88. jupyter_ext/__init__.py +266 -0
  89. jupyter_ext/_version.py +4 -0
  90. jupyter_ext/agent/__init__.py +49 -0
  91. jupyter_ext/agent/agent_factory.py +2270 -0
  92. jupyter_ext/agent/ast_tools.py +451 -0
  93. jupyter_ext/agent/autofix_middleware.py +481 -0
  94. jupyter_ext/agent/background_procs.py +372 -0
  95. jupyter_ext/agent/code_history_middleware.py +211 -0
  96. jupyter_ext/agent/codex_autofix.py +160 -0
  97. jupyter_ext/agent/codex_bridge.py +3339 -0
  98. jupyter_ext/agent/codex_hooks.py +320 -0
  99. jupyter_ext/agent/codex_slash.py +685 -0
  100. jupyter_ext/agent/codex_threads.py +114 -0
  101. jupyter_ext/agent/content_injection.py +377 -0
  102. jupyter_ext/agent/continuation_middleware.py +548 -0
  103. jupyter_ext/agent/custom_skills.py +335 -0
  104. jupyter_ext/agent/file_diffs.py +99 -0
  105. jupyter_ext/agent/git_history.py +188 -0
  106. jupyter_ext/agent/guardrail_middleware.py +421 -0
  107. jupyter_ext/agent/json_repair_utils.py +54 -0
  108. jupyter_ext/agent/langchain_tools.py +2673 -0
  109. jupyter_ext/agent/mermaid_fixer.py +289 -0
  110. jupyter_ext/agent/mermaid_middleware.py +135 -0
  111. jupyter_ext/agent/middleware.py +359 -0
  112. jupyter_ext/agent/middleware_prompts.py +53 -0
  113. jupyter_ext/agent/observability.py +334 -0
  114. jupyter_ext/agent/patch_tool_calls.py +57 -0
  115. jupyter_ext/agent/prompts.py +112 -0
  116. jupyter_ext/agent/skill_middleware.py +257 -0
  117. jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
  118. jupyter_ext/agent/skills/anomaly_detection.md +12 -0
  119. jupyter_ext/agent/skills/athena_sql.md +156 -0
  120. jupyter_ext/agent/skills/bdp_table_search.md +33 -0
  121. jupyter_ext/agent/skills/bigquery.md +65 -0
  122. jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
  123. jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
  124. jupyter_ext/agent/skills/classification_evaluation.md +11 -0
  125. jupyter_ext/agent/skills/clustering_analysis.md +12 -0
  126. jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
  127. jupyter_ext/agent/skills/data_analysis.md +236 -0
  128. jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
  129. jupyter_ext/agent/skills/data_loading.md +158 -0
  130. jupyter_ext/agent/skills/data_quality_report.md +13 -0
  131. jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
  132. jupyter_ext/agent/skills/emr_script_development.md +17 -0
  133. jupyter_ext/agent/skills/experiment_tracking.md +13 -0
  134. jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
  135. jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
  136. jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
  137. jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
  138. jupyter_ext/agent/skills/inference.md +392 -0
  139. jupyter_ext/agent/skills/lambda_function_development.md +19 -0
  140. jupyter_ext/agent/skills/model_comparison.md +11 -0
  141. jupyter_ext/agent/skills/model_explainability.md +12 -0
  142. jupyter_ext/agent/skills/model_training.md +318 -0
  143. jupyter_ext/agent/skills/pyspark.md +352 -0
  144. jupyter_ext/agent/skills/python_code_quality.md +35 -0
  145. jupyter_ext/agent/skills/regression_evaluation.md +12 -0
  146. jupyter_ext/agent/skills/spring_java_development.md +35 -0
  147. jupyter_ext/agent/skills/text_preprocessing.md +12 -0
  148. jupyter_ext/agent/skills/time_series_analysis.md +13 -0
  149. jupyter_ext/agent/skills/unit_testing.md +203 -0
  150. jupyter_ext/agent/todo_prompts.py +57 -0
  151. jupyter_ext/agent/tool_report.py +45 -0
  152. jupyter_ext/analytics_ingest.py +599 -0
  153. jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  154. jupyter_ext/handlers/__init__.py +8 -0
  155. jupyter_ext/handlers/analytics_handler.py +170 -0
  156. jupyter_ext/handlers/benchmark_handler.py +794 -0
  157. jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
  158. jupyter_ext/handlers/rag_admin_handler.py +689 -0
  159. jupyter_ext/handlers/websocket.py +497 -0
  160. jupyter_ext/http_handlers.py +8744 -0
  161. jupyter_ext/idle_monitor.py +346 -0
  162. jupyter_ext/labextension/package.json +157 -0
  163. jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  164. jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
  165. jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
  166. jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
  167. jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
  168. jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
  169. jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
  170. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
  171. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  172. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  173. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  174. jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
  175. jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
  176. jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
  177. jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
  178. jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
  179. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  180. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  181. jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
  182. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
  183. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  184. jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
  185. jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
  186. jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
  187. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
  188. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  189. jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  190. jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  191. jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  192. jupyter_ext/labextension/static/style.js +4 -0
  193. jupyter_ext/labextension/static/third-party-licenses.json +724 -0
  194. jupyter_ext/log_paths.py +113 -0
  195. jupyter_ext/resource_usage.py +519 -0
  196. jupyter_ext/secrets_bootstrap.py +116 -0
  197. jupyter_ext/team_access.py +105 -0
  198. jupyter_ext/tests/__init__.py +1 -0
  199. jupyter_ext/tests/test_context.py +234 -0
  200. jupyter_ext/tests/test_factory.py +563 -0
  201. jupyter_ext/tests/test_file_finder.py +139 -0
  202. jupyter_ext/tests/test_handlers.py +58 -0
  203. jupyter_ext/tests/test_mcp_selftest.py +204 -0
  204. jupyter_ext/tests/test_mermaid_fixer.py +272 -0
  205. jupyter_ext/tests/test_resource_tiers.py +298 -0
  206. jupyter_ext/tools/__init__.py +15 -0
  207. jupyter_ext/tools/base.py +90 -0
  208. jupyter_ext/tools/context.py +268 -0
  209. jupyter_ext/tools/dir_complete.py +86 -0
  210. jupyter_ext/tools/file_finder.py +303 -0
  211. jupyter_ext/tools/file_tools.py +244 -0
  212. jupyter_ext/tools/jupyter_tools.py +296 -0
  213. jupyter_ext/tools/registry.py +159 -0
  214. jupyter_ext/tools/search_tools.py +230 -0
  215. jupyter_ext/tools/security_tools.py +657 -0
  216. jupyter_ext/tools/shell_tools.py +109 -0
  217. jupyter_ext/tools/user_tools.py +132 -0
@@ -0,0 +1,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
+ }