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,689 @@
1
+ """
2
+ RAG Admin REST API Handlers.
3
+
4
+ Admin 모드에서만 접근 가능한 RAG 관리 엔드포인트.
5
+ RAGCollectionManager를 통해 3개 컬렉션 관리.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import os
13
+ from typing import Any, Dict, Optional
14
+
15
+ from jupyter_server.base.handlers import APIHandler
16
+
17
+ from codebuff_core.legacy_paths import resolve_home
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def _is_admin() -> bool:
23
+ """Admin 모드 판별."""
24
+ sagemaker_space = os.environ.get("SAGEMAKER_SPACE_NAME")
25
+ hdsp_prj_id = os.environ.get("HDSP_PRJ_ID")
26
+ if sagemaker_space is None and hdsp_prj_id is None:
27
+ return True # dev
28
+ if hdsp_prj_id == "local_dev":
29
+ return True # local dev (jl.sh sets this)
30
+ if sagemaker_space and "pl2wadmprj" in sagemaker_space:
31
+ return True
32
+ if hdsp_prj_id == "pl2wadmprj":
33
+ return True
34
+ return False
35
+
36
+
37
+ def _parse_body(handler: APIHandler) -> Dict[str, Any]:
38
+ """요청 body JSON 파싱."""
39
+ if not handler.request.body:
40
+ return {}
41
+ return json.loads(handler.request.body.decode("utf-8"))
42
+
43
+
44
+ def _get_manager():
45
+ """RAGCollectionManager 싱글턴 획득."""
46
+
47
+ # 싱글턴 패턴: settings에 저장
48
+ # 초기화는 RAGAdminConfigHandler.post 에서 수행
49
+ return _rag_collection_manager
50
+
51
+
52
+ _rag_collection_manager: Optional[Any] = None
53
+
54
+
55
+ def _ensure_manager(config: Optional[Dict[str, Any]] = None):
56
+ """RAGCollectionManager 초기화 (lazy)."""
57
+ global _rag_collection_manager
58
+ if _rag_collection_manager is not None:
59
+ return _rag_collection_manager
60
+
61
+ from codebuff_core.services.rag_collections import RAGCollectionManager
62
+
63
+ # RAGManager에 이미 Qdrant 클라이언트가 있으면 재사용 (로컬 모드 잠금 충돌 방지)
64
+ from codebuff_core.services.rag_manager import get_rag_manager
65
+
66
+ rag_mgr = get_rag_manager()
67
+ if rag_mgr._client is not None:
68
+ client = rag_mgr._client
69
+ else:
70
+ try:
71
+ from qdrant_client import QdrantClient
72
+ except ImportError:
73
+ raise ImportError(
74
+ "qdrant-client가 설치되지 않았습니다. pip install qdrant-client"
75
+ )
76
+
77
+ from codebuff_core.managers.config_manager import get_config_manager
78
+
79
+ cfg = get_config_manager().get_config()
80
+ rag_cfg = cfg.get("rag", {})
81
+
82
+ # 환경변수 우선 → config 파일 폴백
83
+ mode = os.environ.get("QDRANT_MODE", "") or rag_cfg.get("qdrantMode", "local")
84
+ if mode == "server":
85
+ url = os.environ.get("QDRANT_URL", "") or rag_cfg.get(
86
+ "qdrantUrl", "http://localhost:6333"
87
+ )
88
+ api_key = os.environ.get("QDRANT_API_KEY", "") or rag_cfg.get(
89
+ "qdrantApiKey"
90
+ )
91
+ client = (
92
+ QdrantClient(url=url, api_key=api_key)
93
+ if api_key
94
+ else QdrantClient(url=url)
95
+ )
96
+ else:
97
+ configured = os.environ.get("QDRANT_PATH", "") or rag_cfg.get(
98
+ "qdrantPath", ""
99
+ )
100
+ local_path = (
101
+ os.path.expanduser(configured)
102
+ if configured
103
+ else str(resolve_home("qdrant"))
104
+ )
105
+ os.makedirs(local_path, exist_ok=True)
106
+ client = QdrantClient(path=local_path)
107
+
108
+ # 임베딩 서비스 (리모트 전용)
109
+ from codebuff_core.services.vllm_embedding_service import (
110
+ get_vllm_embedding_service,
111
+ )
112
+
113
+ embedding = get_vllm_embedding_service()
114
+
115
+ from codebuff_core.services.rag_collections import register_manager
116
+
117
+ _rag_collection_manager = RAGCollectionManager(
118
+ qdrant_client=client,
119
+ embedding_service=embedding,
120
+ llm_config=config,
121
+ )
122
+ register_manager(_rag_collection_manager)
123
+ return _rag_collection_manager
124
+
125
+
126
+ def reset_manager():
127
+ """매니저 리셋 (설정 변경 시). 임베딩 서비스 싱글톤도 함께 리셋."""
128
+ global _rag_collection_manager
129
+ _rag_collection_manager = None
130
+ # 임베딩 서비스 싱글톤 리셋 (dimension 등 설정 변경 반영)
131
+ from codebuff_core.services.vllm_embedding_service import (
132
+ reset_vllm_embedding_service,
133
+ )
134
+
135
+ reset_vllm_embedding_service()
136
+
137
+
138
+ # ============ Config Handler ============
139
+
140
+
141
+ class RAGAdminConfigHandler(APIHandler):
142
+ """GET/POST /codebuff/rag/admin/config
143
+
144
+ Qdrant 연결 + 임베딩 + 컬렉션 매핑 설정 조회/저장.
145
+ """
146
+
147
+ async def get(self):
148
+ try:
149
+ if not _is_admin():
150
+ self.set_status(403)
151
+ self.write({"error": "Admin only"})
152
+ return
153
+ from codebuff_core.managers.config_manager import get_config_manager
154
+
155
+ cfg = get_config_manager().get_config()
156
+ rag = cfg.get("rag", {})
157
+
158
+ # 임베딩 접속 설정은 읽기 전용이다 — get_config() 가 centri > 시크릿 env >
159
+ # pod 파일로 해석한 값을 보여 줄 뿐, 여기서 고치지 않는다 (이슈 #113)
160
+ self.write(
161
+ {
162
+ "qdrantMode": rag.get("qdrantMode", "local"),
163
+ "qdrantUrl": rag.get("qdrantUrl", "http://localhost:6333"),
164
+ "qdrantApiKey": "***" if rag.get("qdrantApiKey") else "",
165
+ "qdrantPath": rag.get("qdrantPath") or str(resolve_home("qdrant")),
166
+ "embeddingEndpoint": rag.get("vllmEndpoint", ""),
167
+ "embeddingApiKey": "***" if rag.get("vllmApiKey") else "",
168
+ "embeddingModel": rag.get("vllmModel", ""),
169
+ "embeddingDimension": rag.get("vllmDimension", 4096),
170
+ "collections": rag.get("collections", {}),
171
+ "chunking": rag.get("chunking", {}),
172
+ }
173
+ )
174
+ except Exception as e:
175
+ logger.error("RAGAdminConfig GET error: %s", e, exc_info=True)
176
+ self.set_status(500)
177
+ self.write({"error": str(e)})
178
+
179
+ async def post(self):
180
+ try:
181
+ if not _is_admin():
182
+ self.set_status(403)
183
+ self.write({"error": "Admin only"})
184
+ return
185
+ body = _parse_body(self)
186
+ from codebuff_core.managers.config_manager import get_config_manager
187
+
188
+ cm = get_config_manager()
189
+ cfg = cm.get_config()
190
+ rag = cfg.get("rag", {})
191
+
192
+ # 업데이트 가능한 필드. 임베딩 접속 설정(주소·모델·차원·키)은 받지 않는다 —
193
+ # centri 가 정본이고 키는 시크릿 env 다 (이슈 #113)
194
+ for key in (
195
+ "qdrantMode",
196
+ "qdrantUrl",
197
+ "qdrantPath",
198
+ "collections",
199
+ "chunking",
200
+ ):
201
+ if key in body:
202
+ rag[key] = body[key]
203
+
204
+ # API 키 필드: 마스킹 값(***) 이면 기존 값 유지
205
+ if body.get("qdrantApiKey") not in (None, "", "***"):
206
+ rag["qdrantApiKey"] = body["qdrantApiKey"]
207
+
208
+ cfg["rag"] = rag
209
+ cm.save_config(cfg)
210
+ reset_manager() # 설정 변경 시 매니저 리셋
211
+ self.write({"success": True})
212
+ except Exception as e:
213
+ logger.error("RAGAdminConfig POST error: %s", e, exc_info=True)
214
+ self.set_status(500)
215
+ self.write({"error": str(e)})
216
+
217
+
218
+ # ============ Connection Test ============
219
+
220
+
221
+ class RAGAdminTestConnectionHandler(APIHandler):
222
+ """POST /codebuff/rag/admin/test-connection
223
+
224
+ Qdrant 연결 테스트. Body: { "type": "qdrant" }
225
+ 임베딩 연결 확인은 없앴다 — 접속 설정은 centri·env 가 정하고 화면에서 고치지 않는다 (이슈 #113).
226
+ """
227
+
228
+ async def post(self):
229
+ try:
230
+ if not _is_admin():
231
+ self.set_status(403)
232
+ self.write({"error": "Admin only"})
233
+ return
234
+ body = _parse_body(self)
235
+ test_type = body.get("type", "qdrant")
236
+
237
+ if test_type == "qdrant":
238
+ mgr = _ensure_manager()
239
+ stats = await mgr.get_collection_stats()
240
+ self.write({"success": True, "stats": stats})
241
+ else:
242
+ self.set_status(400)
243
+ self.write({"error": f"Unknown type: {test_type}"})
244
+ except Exception as e:
245
+ logger.error("Connection test error: %s", e, exc_info=True)
246
+ self.set_status(500)
247
+ self.write({"success": False, "error": str(e)})
248
+
249
+
250
+ # ============ Stats Handler ============
251
+
252
+
253
+ class RAGAdminStatsHandler(APIHandler):
254
+ """GET /codebuff/rag/admin/stats"""
255
+
256
+ async def get(self):
257
+ try:
258
+ if not _is_admin():
259
+ self.set_status(403)
260
+ self.write({"error": "Admin only"})
261
+ return
262
+ mgr = _ensure_manager()
263
+ stats = await mgr.get_collection_stats()
264
+ self.write({"stats": stats})
265
+ except Exception as e:
266
+ logger.error("RAGAdminStats error: %s", e, exc_info=True)
267
+ self.set_status(500)
268
+ self.write({"error": str(e)})
269
+
270
+
271
+ # ============ Upsert Handler ============
272
+
273
+
274
+ class RAGAdminUpsertHandler(APIHandler):
275
+ """POST /codebuff/rag/admin/upsert
276
+
277
+ Body:
278
+ - collection: "enterprise_tables" | "bdp_tables" | "guide_docs" | "ds_guide_docs"
279
+ - filePath: CSV 또는 디렉토리 경로
280
+ - joinFilePath: (bdp_tables만)
281
+ - joinKey: (bdp_tables만)
282
+ - mapping: 컬럼 매핑 (테이블 컬렉션만)
283
+ - chunking: 청킹 설정 (guide 컬렉션만)
284
+ """
285
+
286
+ async def post(self):
287
+ try:
288
+ if not _is_admin():
289
+ self.set_status(403)
290
+ self.write({"error": "Admin only"})
291
+ return
292
+ body = _parse_body(self)
293
+ collection = body.get("collection", "")
294
+ mgr = _ensure_manager()
295
+
296
+ # 컬렉션 ensure
297
+ await mgr.ensure_collections(mgr._embedding.dimension)
298
+
299
+ if collection in ("enterprise_tables", "bdp_tables"):
300
+ result = await mgr.upsert_table_collection(
301
+ collection_name=collection,
302
+ file_path=body["filePath"],
303
+ mapping=body.get("mapping", {}),
304
+ join_file_path=body.get("joinFilePath"),
305
+ join_key=body.get("joinKey"),
306
+ )
307
+ elif collection in ("guide_docs", "ds_guide_docs"):
308
+ result = await mgr.upsert_guide_directory(
309
+ dir_path=body["filePath"],
310
+ chunking_config=body.get("chunking"),
311
+ collection_name=collection,
312
+ )
313
+ elif collection == "faq_docs":
314
+ result = await mgr.upsert_faq_collection(
315
+ file_path=body["filePath"],
316
+ )
317
+ else:
318
+ self.set_status(400)
319
+ self.write({"error": f"Unknown collection: {collection}"})
320
+ return
321
+
322
+ self.write(result)
323
+ except Exception as e:
324
+ logger.error("RAGAdminUpsert error: %s", e, exc_info=True)
325
+ self.set_status(500)
326
+ self.write({"error": str(e)})
327
+
328
+
329
+ # ============ Delete Collection ============
330
+
331
+
332
+ class RAGAdminDeleteHandler(APIHandler):
333
+ """DELETE /codebuff/rag/admin/collection/{name}"""
334
+
335
+ async def delete(self, collection_name: str):
336
+ try:
337
+ if not _is_admin():
338
+ self.set_status(403)
339
+ self.write({"error": "Admin only"})
340
+ return
341
+ mgr = _ensure_manager()
342
+ await mgr.delete_collection(collection_name)
343
+ self.write({"success": True, "collection": collection_name})
344
+ except Exception as e:
345
+ logger.error("RAGAdminDelete error: %s", e, exc_info=True)
346
+ self.set_status(500)
347
+ self.write({"error": str(e)})
348
+
349
+
350
+ # ============ Preview Handler ============
351
+
352
+
353
+ class RAGAdminPreviewHandler(APIHandler):
354
+ """POST /codebuff/rag/admin/preview
355
+
356
+ CSV 헤더 파싱 / 디렉토리 파일 목록 반환.
357
+ Body: { "filePath": "...", "type": "csv_headers" | "directory_files" }
358
+ """
359
+
360
+ async def post(self):
361
+ try:
362
+ if not _is_admin():
363
+ self.set_status(403)
364
+ self.write({"error": "Admin only"})
365
+ return
366
+ body = _parse_body(self)
367
+ file_path = body.get("filePath", "")
368
+ preview_type = body.get("type", "csv_headers")
369
+ mgr = _ensure_manager()
370
+
371
+ if preview_type == "csv_headers":
372
+ headers = mgr.parse_csv_headers(file_path)
373
+ self.write({"headers": headers})
374
+ elif preview_type == "directory_files":
375
+ files = mgr.list_directory_files(file_path)
376
+ self.write({"files": files})
377
+ else:
378
+ self.set_status(400)
379
+ self.write({"error": f"Unknown type: {preview_type}"})
380
+ except Exception as e:
381
+ logger.error("RAGAdminPreview error: %s", e, exc_info=True)
382
+ self.set_status(500)
383
+ self.write({"error": str(e)})
384
+
385
+
386
+ # ============ Dry Run ============
387
+
388
+
389
+ class RAGAdminDryRunHandler(APIHandler):
390
+ """POST /codebuff/rag/admin/dry-run
391
+
392
+ 적재 전 프리뷰.
393
+ Body: { "collection": "...", "filePath": "...", "mapping": {...}, ... }
394
+ """
395
+
396
+ async def post(self):
397
+ try:
398
+ if not _is_admin():
399
+ self.set_status(403)
400
+ self.write({"error": "Admin only"})
401
+ return
402
+ body = _parse_body(self)
403
+ collection = body.get("collection", "")
404
+ mgr = _ensure_manager()
405
+
406
+ if collection in ("enterprise_tables", "bdp_tables"):
407
+ result = await mgr.dry_run_table(
408
+ file_path=body["filePath"],
409
+ mapping=body.get("mapping", {}),
410
+ join_file_path=body.get("joinFilePath"),
411
+ join_key=body.get("joinKey"),
412
+ )
413
+ elif collection in ("guide_docs", "ds_guide_docs"):
414
+ result = await mgr.dry_run_guide(
415
+ dir_path=body["filePath"],
416
+ chunking_config=body.get("chunking"),
417
+ )
418
+ else:
419
+ self.set_status(400)
420
+ self.write({"error": f"Unknown collection: {collection}"})
421
+ return
422
+
423
+ self.write(result)
424
+ except Exception as e:
425
+ logger.error("RAGAdminDryRun error: %s", e, exc_info=True)
426
+ self.set_status(500)
427
+ self.write({"error": str(e)})
428
+
429
+
430
+ # ============ Validate CSV ============
431
+
432
+
433
+ class RAGAdminValidateHandler(APIHandler):
434
+ """POST /codebuff/rag/admin/validate
435
+
436
+ CSV 유효성 검증.
437
+ Body: { "filePath": "...", "mapping": {...} }
438
+ """
439
+
440
+ async def post(self):
441
+ try:
442
+ if not _is_admin():
443
+ self.set_status(403)
444
+ self.write({"error": "Admin only"})
445
+ return
446
+ body = _parse_body(self)
447
+ mgr = _ensure_manager()
448
+ result = mgr.validate_csv_mapping(
449
+ file_path=body["filePath"],
450
+ mapping=body.get("mapping", {}),
451
+ )
452
+ self.write(result)
453
+ except Exception as e:
454
+ logger.error("RAGAdminValidate error: %s", e, exc_info=True)
455
+ self.set_status(500)
456
+ self.write({"error": str(e)})
457
+
458
+
459
+ # ============ Browse Collection ============
460
+
461
+
462
+ class RAGAdminBrowseHandler(APIHandler):
463
+ """GET /codebuff/rag/admin/browse/{collection}
464
+
465
+ 포인트 목록 페이징.
466
+ Query params: offset, limit, filter_field, filter_value
467
+ """
468
+
469
+ async def get(self, collection: str):
470
+ try:
471
+ if not _is_admin():
472
+ self.set_status(403)
473
+ self.write({"error": "Admin only"})
474
+ return
475
+
476
+ offset_str = self.get_argument("offset", "")
477
+ offset = offset_str if offset_str else None
478
+ limit = int(self.get_argument("limit", "10"))
479
+
480
+ filters_json = self.get_argument("filters", "")
481
+ filters = None
482
+ if filters_json:
483
+ import json as _json
484
+
485
+ try:
486
+ filters = _json.loads(filters_json)
487
+ except Exception:
488
+ pass
489
+
490
+ mgr = _ensure_manager()
491
+ result = await mgr.browse_collection(
492
+ collection=collection,
493
+ offset=offset,
494
+ limit=limit,
495
+ filters=filters,
496
+ )
497
+ self.write(result)
498
+ except Exception as e:
499
+ logger.error("RAGAdminBrowse error: %s", e, exc_info=True)
500
+ self.set_status(500)
501
+ self.write({"error": str(e)})
502
+
503
+ async def delete(self, collection: str):
504
+ """개별 포인트 삭제."""
505
+ try:
506
+ if not _is_admin():
507
+ self.set_status(403)
508
+ self.write({"error": "Admin only"})
509
+ return
510
+ body = _parse_body(self)
511
+ point_id = body.get("pointId", "")
512
+ if not point_id:
513
+ self.set_status(400)
514
+ self.write({"error": "pointId required"})
515
+ return
516
+ mgr = _ensure_manager()
517
+ await mgr.delete_point(collection, point_id)
518
+ self.write({"success": True})
519
+ except Exception as e:
520
+ logger.error("RAGAdminBrowse DELETE error: %s", e, exc_info=True)
521
+ self.set_status(500)
522
+ self.write({"error": str(e)})
523
+
524
+
525
+ # ============ Search Test ============
526
+
527
+
528
+ class RAGAdminSearchTestHandler(APIHandler):
529
+ """POST /codebuff/rag/admin/search-test
530
+
531
+ Body: { "query": "...", "collection": null | "...", "topK": 5 }
532
+ """
533
+
534
+ async def post(self):
535
+ try:
536
+ if not _is_admin():
537
+ self.set_status(403)
538
+ self.write({"error": "Admin only"})
539
+ return
540
+ body = _parse_body(self)
541
+ collection = body.get("collection")
542
+ query = body.get("query", "")
543
+ top_k = body.get("topK", 5)
544
+
545
+ mgr = _ensure_manager()
546
+ results = await mgr.search_test(
547
+ query=query,
548
+ collection=collection,
549
+ top_k=top_k,
550
+ )
551
+ self.write({"results": results})
552
+ except Exception as e:
553
+ logger.error("RAGAdminSearchTest error: %s", e, exc_info=True)
554
+ self.set_status(500)
555
+ self.write({"error": str(e)})
556
+
557
+
558
+ # ============ History ============
559
+
560
+
561
+ class RAGAdminHistoryHandler(APIHandler):
562
+ """GET /codebuff/rag/admin/history"""
563
+
564
+ async def get(self):
565
+ try:
566
+ if not _is_admin():
567
+ self.set_status(403)
568
+ self.write({"error": "Admin only"})
569
+ return
570
+ mgr = _ensure_manager()
571
+ history = mgr.get_history()
572
+ self.write({"history": history})
573
+ except Exception as e:
574
+ logger.error("RAGAdminHistory error: %s", e, exc_info=True)
575
+ self.set_status(500)
576
+ self.write({"error": str(e)})
577
+
578
+
579
+ # ============ Progress Handler ============
580
+
581
+
582
+ class RAGAdminProgressHandler(APIHandler):
583
+ """GET /codebuff/rag/admin/progress
584
+
585
+ 적재 진행 상황 조회 (폴링).
586
+ """
587
+
588
+ async def get(self):
589
+ try:
590
+ if not _is_admin():
591
+ self.set_status(403)
592
+ self.write({"error": "Admin only"})
593
+ return
594
+ mgr = _get_manager()
595
+ if mgr is None:
596
+ self.write({"status": "idle"})
597
+ return
598
+ self.write(mgr.get_progress() or {"status": "idle"})
599
+ except Exception as e:
600
+ logger.error("RAGAdminProgress error: %s", e, exc_info=True)
601
+ self.set_status(500)
602
+ self.write({"error": str(e)})
603
+
604
+
605
+ class RAGAdminCancelHandler(APIHandler):
606
+ """POST /codebuff/rag/admin/cancel — 적재 중지."""
607
+
608
+ async def post(self):
609
+ try:
610
+ if not _is_admin():
611
+ self.set_status(403)
612
+ self.write({"error": "Admin only"})
613
+ return
614
+ mgr = _get_manager()
615
+ if mgr is None:
616
+ self.write({"success": False, "error": "Manager not initialized"})
617
+ return
618
+ cancelled = mgr.cancel_upsert()
619
+ self.write({"success": cancelled})
620
+ except Exception as e:
621
+ self.set_status(500)
622
+ self.write({"error": str(e)})
623
+
624
+
625
+ # ============ Guide Pipeline (3-step) ============
626
+
627
+
628
+ class RAGAdminGuidePipelineHandler(APIHandler):
629
+ """POST /codebuff/rag/admin/guide-pipeline — 3단계 파이프라인 실행 (SSE).
630
+
631
+ Body: {"sourceDir": "...", "collection": "guide_docs", "step": 1|2|3}
632
+ """
633
+
634
+ async def post(self):
635
+ try:
636
+ if not _is_admin():
637
+ self.set_status(403)
638
+ self.write({"error": "Admin only"})
639
+ return
640
+
641
+ body = _parse_body(self)
642
+ source_dir = body.get("sourceDir", "")
643
+ collection = body.get("collection", "guide_docs")
644
+ step = body.get("step", 1)
645
+ mgr = _ensure_manager()
646
+
647
+ self.set_header("Content-Type", "text/event-stream")
648
+ self.set_header("Cache-Control", "no-cache")
649
+
650
+ if step == 1:
651
+ gen = mgr.guide_pipeline_step1(source_dir)
652
+ elif step == 2:
653
+ gen = mgr.guide_pipeline_step2(source_dir)
654
+ elif step == 3:
655
+ gen = mgr.guide_pipeline_step3(source_dir, collection)
656
+ else:
657
+ self.set_status(400)
658
+ self.write({"error": f"Invalid step: {step}"})
659
+ return
660
+
661
+ async for event in gen:
662
+ self.write(f"data: {json.dumps(event, ensure_ascii=False)}\n\n")
663
+ self.flush()
664
+
665
+ except Exception as e:
666
+ logger.error("GuidePipeline error: %s", e, exc_info=True)
667
+ self.write(f"data: {json.dumps({'status': 'error', 'error': str(e)})}\n\n")
668
+ self.flush()
669
+
670
+
671
+ class RAGAdminGuidePipelineStatusHandler(APIHandler):
672
+ """GET /codebuff/rag/admin/guide-pipeline/status"""
673
+
674
+ async def get(self):
675
+ try:
676
+ if not _is_admin():
677
+ self.set_status(403)
678
+ self.write({"error": "Admin only"})
679
+ return
680
+ source_dir = self.get_argument("sourceDir", "")
681
+ if not source_dir:
682
+ self.set_status(400)
683
+ self.write({"error": "sourceDir required"})
684
+ return
685
+ mgr = _ensure_manager()
686
+ self.write(mgr.guide_pipeline_status(source_dir))
687
+ except Exception as e:
688
+ self.set_status(500)
689
+ self.write({"error": str(e)})