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,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)})
|