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,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RAG 서비스 구현
|
|
3
|
+
|
|
4
|
+
IRAGService의 Embedded 구현.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from codebuff_core.interfaces import IRAGService
|
|
11
|
+
from codebuff_core.models.rag import SearchRequest, SearchResponse
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmbeddedRAGService(IRAGService):
|
|
17
|
+
"""
|
|
18
|
+
RAG 서비스 구현.
|
|
19
|
+
|
|
20
|
+
인프로세스에서 직접 RAGManager를 사용합니다.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""Embedded RAG 서비스 초기화"""
|
|
25
|
+
self._rag_manager = None
|
|
26
|
+
self._initialized = False
|
|
27
|
+
logger.info("EmbeddedRAGService 생성됨 (아직 초기화되지 않음)")
|
|
28
|
+
|
|
29
|
+
async def initialize(self) -> None:
|
|
30
|
+
"""RAG 관리자 초기화."""
|
|
31
|
+
if self._initialized:
|
|
32
|
+
return
|
|
33
|
+
try:
|
|
34
|
+
from codebuff_core.services.rag_manager import get_rag_manager
|
|
35
|
+
|
|
36
|
+
self._rag_manager = get_rag_manager()
|
|
37
|
+
success = await self._rag_manager.initialize()
|
|
38
|
+
self._initialized = success
|
|
39
|
+
if success:
|
|
40
|
+
logger.info("EmbeddedRAGService: RAG 초기화 완료")
|
|
41
|
+
else:
|
|
42
|
+
logger.info("EmbeddedRAGService: RAG 비활성화 (설정)")
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.warning(f"EmbeddedRAGService: RAG 초기화 실패: {e}")
|
|
45
|
+
self._initialized = False
|
|
46
|
+
|
|
47
|
+
async def shutdown(self) -> None:
|
|
48
|
+
"""RAG 서비스 종료"""
|
|
49
|
+
self._rag_manager = None
|
|
50
|
+
self._initialized = False
|
|
51
|
+
logger.info("EmbeddedRAGService 종료됨")
|
|
52
|
+
|
|
53
|
+
def is_ready(self) -> bool:
|
|
54
|
+
"""RAG 서비스 준비 상태 확인"""
|
|
55
|
+
if not self._initialized or not self._rag_manager:
|
|
56
|
+
return False
|
|
57
|
+
return self._rag_manager.is_ready
|
|
58
|
+
|
|
59
|
+
async def search(self, request: SearchRequest) -> SearchResponse:
|
|
60
|
+
"""지식 베이스 검색"""
|
|
61
|
+
logger.info(f"[Embedded] RAG 검색: {request.query[:100]}...")
|
|
62
|
+
|
|
63
|
+
if not self.is_ready():
|
|
64
|
+
return SearchResponse(
|
|
65
|
+
results=[],
|
|
66
|
+
total=0,
|
|
67
|
+
query=request.query,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
results = await self._rag_manager.search(
|
|
71
|
+
query=request.query,
|
|
72
|
+
collection=request.collection,
|
|
73
|
+
limit=request.limit,
|
|
74
|
+
score_threshold=request.scoreThreshold,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return SearchResponse(
|
|
78
|
+
results=results.get("results", []),
|
|
79
|
+
total=results.get("total", 0),
|
|
80
|
+
query=request.query,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def get_context_for_query(
|
|
84
|
+
self,
|
|
85
|
+
query: str,
|
|
86
|
+
detected_libraries: Optional[List[str]] = None,
|
|
87
|
+
max_results: int = 5,
|
|
88
|
+
) -> Optional[str]:
|
|
89
|
+
"""쿼리에 대한 포맷된 컨텍스트 가져오기"""
|
|
90
|
+
logger.info(f"[Embedded] 쿼리 컨텍스트 조회: {query[:100]}...")
|
|
91
|
+
|
|
92
|
+
if not self.is_ready():
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
return await self._rag_manager.get_context_for_query(
|
|
97
|
+
query=query,
|
|
98
|
+
detected_libraries=detected_libraries,
|
|
99
|
+
max_results=max_results,
|
|
100
|
+
)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.error(f"RAG 컨텍스트 조회 실패: {e}")
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
async def get_index_status(self) -> Dict[str, Any]:
|
|
106
|
+
"""현재 인덱스 상태 가져오기"""
|
|
107
|
+
logger.info("[Embedded] 인덱스 상태 조회")
|
|
108
|
+
|
|
109
|
+
if not self.is_ready():
|
|
110
|
+
return {
|
|
111
|
+
"ready": False,
|
|
112
|
+
"documentCount": 0,
|
|
113
|
+
"collections": [],
|
|
114
|
+
"message": "RAG 서비스가 초기화되지 않음",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
return await self._rag_manager.get_index_status()
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"인덱스 상태 조회 실패: {e}")
|
|
121
|
+
return {
|
|
122
|
+
"ready": False,
|
|
123
|
+
"documentCount": 0,
|
|
124
|
+
"collections": [],
|
|
125
|
+
"message": str(e),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async def trigger_reindex(self, force: bool = False) -> Dict[str, Any]:
|
|
129
|
+
"""재인덱싱 작업 트리거"""
|
|
130
|
+
logger.info(f"[Embedded] 재인덱싱 트리거 (force={force})")
|
|
131
|
+
|
|
132
|
+
if not self.is_ready():
|
|
133
|
+
return {
|
|
134
|
+
"success": False,
|
|
135
|
+
"message": "RAG 서비스가 초기화되지 않음",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
return await self._rag_manager.trigger_reindex(force=force)
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"재인덱싱 트리거 실패: {e}")
|
|
142
|
+
return {
|
|
143
|
+
"success": False,
|
|
144
|
+
"message": str(e),
|
|
145
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Retriever - Dense vector search implementation.
|
|
3
|
+
|
|
4
|
+
Provides semantic similarity search via Qdrant vector database.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from codebuff_core.models.rag import RAGConfig
|
|
14
|
+
|
|
15
|
+
from codebuff_core.services.vllm_embedding_service import VLLMEmbeddingService
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ChunkScoreDetails:
|
|
22
|
+
"""청크별 점수 상세 정보"""
|
|
23
|
+
|
|
24
|
+
chunk_id: str
|
|
25
|
+
content: str
|
|
26
|
+
score: float # Vector similarity score (0-1)
|
|
27
|
+
rank: int
|
|
28
|
+
metadata: Dict[str, Any]
|
|
29
|
+
passed_threshold: bool
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DebugSearchResult(NamedTuple):
|
|
33
|
+
"""디버그 검색 결과"""
|
|
34
|
+
|
|
35
|
+
chunks: List[ChunkScoreDetails]
|
|
36
|
+
search_ms: float
|
|
37
|
+
total_candidates: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Retriever:
|
|
41
|
+
"""
|
|
42
|
+
Dense vector retrieval using Qdrant.
|
|
43
|
+
|
|
44
|
+
Features:
|
|
45
|
+
- Dense vector search via Qdrant
|
|
46
|
+
- Metadata filtering
|
|
47
|
+
- Score thresholding
|
|
48
|
+
|
|
49
|
+
Usage:
|
|
50
|
+
retriever = Retriever(client, embedding_service, config)
|
|
51
|
+
results = await retriever.search("query", top_k=5)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
client, # Qdrant client
|
|
57
|
+
embedding_service: "VLLMEmbeddingService",
|
|
58
|
+
config: "RAGConfig",
|
|
59
|
+
):
|
|
60
|
+
self._client = client
|
|
61
|
+
self._embedding_service = embedding_service
|
|
62
|
+
self._config = config
|
|
63
|
+
|
|
64
|
+
async def search(
|
|
65
|
+
self,
|
|
66
|
+
query: str,
|
|
67
|
+
collection_name: Optional[str] = None,
|
|
68
|
+
top_k: Optional[int] = None,
|
|
69
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
70
|
+
score_threshold: Optional[float] = None,
|
|
71
|
+
) -> List[Dict[str, Any]]:
|
|
72
|
+
"""
|
|
73
|
+
Perform dense vector search.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
query: Search query
|
|
77
|
+
collection_name: Target collection (default from config)
|
|
78
|
+
top_k: Number of results (default from config)
|
|
79
|
+
filters: Metadata filters
|
|
80
|
+
score_threshold: Minimum score (default from config)
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List of results with content, score, metadata
|
|
84
|
+
"""
|
|
85
|
+
effective_top_k = top_k or self._config.top_k
|
|
86
|
+
target_collection = collection_name or self._config.qdrant.collection_name
|
|
87
|
+
effective_threshold = score_threshold or self._config.score_threshold
|
|
88
|
+
|
|
89
|
+
# Generate query embedding
|
|
90
|
+
query_embedding = await self._embedding_service.embed_query(query)
|
|
91
|
+
|
|
92
|
+
# Build filter condition
|
|
93
|
+
qdrant_filter = self._build_filter(filters) if filters else None
|
|
94
|
+
|
|
95
|
+
# Dense vector search
|
|
96
|
+
try:
|
|
97
|
+
response = self._client.query_points(
|
|
98
|
+
collection_name=target_collection,
|
|
99
|
+
query=query_embedding,
|
|
100
|
+
query_filter=qdrant_filter,
|
|
101
|
+
limit=effective_top_k,
|
|
102
|
+
score_threshold=effective_threshold
|
|
103
|
+
* 0.5, # Lower for initial retrieval
|
|
104
|
+
with_payload=True,
|
|
105
|
+
with_vectors=False,
|
|
106
|
+
)
|
|
107
|
+
results = response.points
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Search failed: {e}")
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
if not results:
|
|
113
|
+
logger.debug(f"No results for query: {query[:50]}...")
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
return self._format_results(results, effective_threshold)
|
|
117
|
+
|
|
118
|
+
def _build_filter(self, filters: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
119
|
+
"""Convert filter dict to Qdrant filter format"""
|
|
120
|
+
if not filters:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
conditions = []
|
|
124
|
+
for key, value in filters.items():
|
|
125
|
+
if isinstance(value, list):
|
|
126
|
+
# Multiple values - any match
|
|
127
|
+
conditions.append(
|
|
128
|
+
{"should": [{"key": key, "match": {"value": v}} for v in value]}
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
conditions.append({"key": key, "match": {"value": value}})
|
|
132
|
+
|
|
133
|
+
return {"must": conditions} if conditions else None
|
|
134
|
+
|
|
135
|
+
def _format_results(
|
|
136
|
+
self, results: List, score_threshold: float
|
|
137
|
+
) -> List[Dict[str, Any]]:
|
|
138
|
+
"""Format Qdrant results to standard format"""
|
|
139
|
+
formatted = []
|
|
140
|
+
for r in results:
|
|
141
|
+
if r.score < score_threshold:
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
formatted.append(
|
|
145
|
+
{
|
|
146
|
+
"content": r.payload.get("content", ""),
|
|
147
|
+
"score": round(r.score, 4),
|
|
148
|
+
"metadata": {k: v for k, v in r.payload.items() if k != "content"},
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
return formatted
|
|
152
|
+
|
|
153
|
+
def search_sync(
|
|
154
|
+
self,
|
|
155
|
+
query: str,
|
|
156
|
+
top_k: Optional[int] = None,
|
|
157
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
158
|
+
) -> List[Dict[str, Any]]:
|
|
159
|
+
"""
|
|
160
|
+
Synchronous search wrapper for non-async contexts.
|
|
161
|
+
|
|
162
|
+
Note: Qdrant client operations are synchronous,
|
|
163
|
+
so this is just a convenience method.
|
|
164
|
+
"""
|
|
165
|
+
import asyncio
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
asyncio.get_running_loop()
|
|
169
|
+
logger.warning(
|
|
170
|
+
"search_sync called from async context, use search() instead"
|
|
171
|
+
)
|
|
172
|
+
except RuntimeError:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
return asyncio.run(self.search(query, top_k, filters))
|
|
176
|
+
|
|
177
|
+
async def search_with_debug(
|
|
178
|
+
self,
|
|
179
|
+
query: str,
|
|
180
|
+
collection_name: Optional[str] = None,
|
|
181
|
+
top_k: Optional[int] = None,
|
|
182
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
183
|
+
score_threshold: Optional[float] = None,
|
|
184
|
+
) -> DebugSearchResult:
|
|
185
|
+
"""
|
|
186
|
+
전체 점수 정보를 포함한 디버그 검색 수행.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
query: Search query
|
|
190
|
+
collection_name: Target collection (default from config)
|
|
191
|
+
top_k: Number of results (default from config)
|
|
192
|
+
filters: Metadata filters
|
|
193
|
+
score_threshold: Minimum score (default from config)
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
DebugSearchResult with detailed scoring information
|
|
197
|
+
"""
|
|
198
|
+
start_time = time.perf_counter()
|
|
199
|
+
|
|
200
|
+
effective_top_k = top_k or self._config.top_k
|
|
201
|
+
target_collection = collection_name or self._config.qdrant.collection_name
|
|
202
|
+
effective_threshold = score_threshold or self._config.score_threshold
|
|
203
|
+
|
|
204
|
+
# Generate query embedding
|
|
205
|
+
query_embedding = await self._embedding_service.embed_query(query)
|
|
206
|
+
|
|
207
|
+
# Build filter condition
|
|
208
|
+
qdrant_filter = self._build_filter(filters) if filters else None
|
|
209
|
+
|
|
210
|
+
# Vector search with timing
|
|
211
|
+
try:
|
|
212
|
+
# 디버그용으로 더 많은 결과 (3배)를 낮은 threshold로 가져옴
|
|
213
|
+
response = self._client.query_points(
|
|
214
|
+
collection_name=target_collection,
|
|
215
|
+
query=query_embedding,
|
|
216
|
+
query_filter=qdrant_filter,
|
|
217
|
+
limit=effective_top_k * 3,
|
|
218
|
+
score_threshold=effective_threshold * 0.3,
|
|
219
|
+
with_payload=True,
|
|
220
|
+
with_vectors=False,
|
|
221
|
+
)
|
|
222
|
+
results = response.points
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(f"Search failed: {e}")
|
|
225
|
+
return DebugSearchResult(
|
|
226
|
+
chunks=[],
|
|
227
|
+
search_ms=0.0,
|
|
228
|
+
total_candidates=0,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
search_ms = (time.perf_counter() - start_time) * 1000
|
|
232
|
+
|
|
233
|
+
if not results:
|
|
234
|
+
return DebugSearchResult(
|
|
235
|
+
chunks=[],
|
|
236
|
+
search_ms=round(search_ms, 2),
|
|
237
|
+
total_candidates=0,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Build detailed results
|
|
241
|
+
chunks = []
|
|
242
|
+
for rank, result in enumerate(results, start=1):
|
|
243
|
+
chunks.append(
|
|
244
|
+
ChunkScoreDetails(
|
|
245
|
+
chunk_id=str(result.id),
|
|
246
|
+
content=result.payload.get("content", ""),
|
|
247
|
+
score=round(result.score, 4),
|
|
248
|
+
rank=rank,
|
|
249
|
+
metadata={
|
|
250
|
+
k: v for k, v in result.payload.items() if k != "content"
|
|
251
|
+
},
|
|
252
|
+
passed_threshold=result.score >= effective_threshold,
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return DebugSearchResult(
|
|
257
|
+
chunks=chunks,
|
|
258
|
+
search_ms=round(search_ms, 2),
|
|
259
|
+
total_candidates=len(results),
|
|
260
|
+
)
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
vLLM Embedding Service - Remote embedding generation using vLLM server.
|
|
3
|
+
|
|
4
|
+
Features:
|
|
5
|
+
- GPU-accelerated embeddings via vLLM server
|
|
6
|
+
- OpenAI-compatible API interface
|
|
7
|
+
- Retry logic for reliability
|
|
8
|
+
- Support for large models (qwen3-embedding-8b, gte-Qwen2-7B, etc.)
|
|
9
|
+
|
|
10
|
+
Prerequisites:
|
|
11
|
+
- vLLM embedding server running (e.g., http://10.222.52.31:8000)
|
|
12
|
+
- Model loaded on vLLM server
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from codebuff_core.models.rag import EmbeddingConfig
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VLLMEmbeddingService:
|
|
27
|
+
"""
|
|
28
|
+
Remote embedding generation using vLLM server.
|
|
29
|
+
|
|
30
|
+
Design Principles:
|
|
31
|
+
- Stateless client (vLLM server holds the model)
|
|
32
|
+
- Retry logic for network resilience
|
|
33
|
+
- OpenAI-compatible API interface
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
service = get_vllm_embedding_service()
|
|
37
|
+
embeddings = service.embed_texts(["text1", "text2"])
|
|
38
|
+
query_embedding = service.embed_query("search query")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_instance: Optional["VLLMEmbeddingService"] = None
|
|
42
|
+
_initialized: bool = False
|
|
43
|
+
|
|
44
|
+
def __new__(cls, *args, **kwargs):
|
|
45
|
+
if cls._instance is None:
|
|
46
|
+
cls._instance = super().__new__(cls)
|
|
47
|
+
return cls._instance
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: Optional["EmbeddingConfig"] = None):
|
|
50
|
+
if self._initialized:
|
|
51
|
+
return
|
|
52
|
+
self._initialized = True
|
|
53
|
+
|
|
54
|
+
from codebuff_core.models.rag import EmbeddingConfig
|
|
55
|
+
|
|
56
|
+
self._config = config or EmbeddingConfig()
|
|
57
|
+
|
|
58
|
+
# 환경변수 우선 → config 파일 폴백
|
|
59
|
+
from codebuff_core.managers.config_manager import get_config_manager
|
|
60
|
+
|
|
61
|
+
cfg = get_config_manager().get_config()
|
|
62
|
+
rag = cfg.get("rag", {})
|
|
63
|
+
|
|
64
|
+
# get_config() 가 centri > env > 파일로 해석해 둔 값이다 (이슈 #113)
|
|
65
|
+
self._endpoint = rag.get("vllmEndpoint", "") or "http://localhost:8000"
|
|
66
|
+
self._model = rag.get("vllmModel", "") or "qwen3-embedding-8b"
|
|
67
|
+
self._dimension = int(rag.get("vllmDimension", 4096) or 4096)
|
|
68
|
+
rag_api_key = rag.get("vllmApiKey", "")
|
|
69
|
+
if rag_api_key in ("***", ""):
|
|
70
|
+
rag_api_key = ""
|
|
71
|
+
self._api_key = rag_api_key or cfg.get("vllm", {}).get("apiKey", "")
|
|
72
|
+
|
|
73
|
+
# HTTP client with retry (include auth header if API key provided)
|
|
74
|
+
headers = {}
|
|
75
|
+
if self._api_key:
|
|
76
|
+
headers["Authorization"] = f"Bearer {self._api_key}"
|
|
77
|
+
self._client = httpx.AsyncClient(
|
|
78
|
+
base_url=self._endpoint,
|
|
79
|
+
timeout=httpx.Timeout(None),
|
|
80
|
+
limits=httpx.Limits(max_keepalive_connections=5, max_connections=10),
|
|
81
|
+
headers=headers,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
logger.info(
|
|
85
|
+
f"vLLM Embedding Service initialized: "
|
|
86
|
+
f"endpoint={self._endpoint}, model={self._model}, dim={self._dimension}, "
|
|
87
|
+
f"auth={'enabled' if self._api_key else 'disabled'}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def dimension(self) -> int:
|
|
92
|
+
"""Get embedding dimension"""
|
|
93
|
+
return self._dimension
|
|
94
|
+
|
|
95
|
+
async def _call_vllm_api(
|
|
96
|
+
self, texts: List[str], max_retries: int = 3, timeout: Optional[float] = None
|
|
97
|
+
) -> List[List[float]]:
|
|
98
|
+
"""
|
|
99
|
+
Call vLLM embedding API with retry logic.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
texts: List of text strings to embed
|
|
103
|
+
max_retries: Maximum number of retry attempts
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of embedding vectors
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
Exception if all retries fail
|
|
110
|
+
"""
|
|
111
|
+
payload = {
|
|
112
|
+
"model": self._model,
|
|
113
|
+
"input": texts,
|
|
114
|
+
"dimensions": self._dimension,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
last_error = None
|
|
118
|
+
for attempt in range(max_retries):
|
|
119
|
+
try:
|
|
120
|
+
# base_url에 이미 /v1이 포함될 수 있으므로 상대 경로 사용
|
|
121
|
+
base = self._endpoint.rstrip("/")
|
|
122
|
+
if base.endswith("/v1"):
|
|
123
|
+
path = "/embeddings"
|
|
124
|
+
else:
|
|
125
|
+
path = "/v1/embeddings"
|
|
126
|
+
response = await self._client.post(path, json=payload, timeout=timeout)
|
|
127
|
+
response.raise_for_status()
|
|
128
|
+
|
|
129
|
+
data = response.json()
|
|
130
|
+
# OpenRouter 등 일부 서비스는 HTTP 200으로 오면서 error body를 반환
|
|
131
|
+
if "error" in data and "data" not in data:
|
|
132
|
+
err_msg = data["error"].get("message", str(data["error"]))
|
|
133
|
+
err_code = data["error"].get("code", "")
|
|
134
|
+
raise Exception(f"API error {err_code}: {err_msg}")
|
|
135
|
+
# Sort by index to ensure correct order
|
|
136
|
+
sorted_items = sorted(data["data"], key=lambda x: x["index"])
|
|
137
|
+
embeddings = [item["embedding"] for item in sorted_items]
|
|
138
|
+
return embeddings
|
|
139
|
+
|
|
140
|
+
except httpx.HTTPStatusError as e:
|
|
141
|
+
last_error = e
|
|
142
|
+
logger.warning(
|
|
143
|
+
f"vLLM API HTTP error (attempt {attempt + 1}/{max_retries}): "
|
|
144
|
+
f"{e.response.status_code} - {e.response.text}"
|
|
145
|
+
)
|
|
146
|
+
except httpx.RequestError as e:
|
|
147
|
+
last_error = e
|
|
148
|
+
logger.warning(
|
|
149
|
+
f"vLLM API connection error (attempt {attempt + 1}/{max_retries}): {e}"
|
|
150
|
+
)
|
|
151
|
+
# connection pool 오염 시 client 재생성
|
|
152
|
+
try:
|
|
153
|
+
await self._client.aclose()
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
headers = {}
|
|
157
|
+
if self._api_key:
|
|
158
|
+
headers["Authorization"] = f"Bearer {self._api_key}"
|
|
159
|
+
self._client = httpx.AsyncClient(
|
|
160
|
+
base_url=self._endpoint,
|
|
161
|
+
timeout=httpx.Timeout(None),
|
|
162
|
+
limits=httpx.Limits(
|
|
163
|
+
max_keepalive_connections=5, max_connections=10
|
|
164
|
+
),
|
|
165
|
+
headers=headers,
|
|
166
|
+
)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
last_error = e
|
|
169
|
+
logger.error(f"Unexpected error calling vLLM API: {e}")
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
# 에러 메시지에 응답 본문 포함 (토큰 초과 등 상세 메시지 전달)
|
|
173
|
+
detail = str(last_error)
|
|
174
|
+
if hasattr(last_error, "response") and hasattr(last_error.response, "text"):
|
|
175
|
+
detail = f"{last_error} | body={last_error.response.text[:300]}"
|
|
176
|
+
raise Exception(f"vLLM embed failed after {max_retries} attempts: {detail}")
|
|
177
|
+
|
|
178
|
+
async def embed_texts(self, texts: List[str]) -> List[List[float]]:
|
|
179
|
+
"""
|
|
180
|
+
Generate embeddings for a list of texts (documents/passages).
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
texts: List of text strings to embed
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
List of embedding vectors (as lists of floats)
|
|
187
|
+
"""
|
|
188
|
+
if not texts:
|
|
189
|
+
return []
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
return await self._call_vllm_api(texts)
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error(f"Failed to generate embeddings via vLLM: {e}")
|
|
195
|
+
raise
|
|
196
|
+
|
|
197
|
+
async def embed_query(self, query: str) -> List[float]:
|
|
198
|
+
"""
|
|
199
|
+
Generate embedding for a single query.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
query: Query string
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Embedding vector as list of floats
|
|
206
|
+
"""
|
|
207
|
+
if not query:
|
|
208
|
+
raise ValueError("Query cannot be empty")
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
embeddings = await self._call_vllm_api([query], timeout=10.0)
|
|
212
|
+
return embeddings[0]
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.error(f"Failed to generate query embedding via vLLM: {e}")
|
|
215
|
+
raise
|
|
216
|
+
|
|
217
|
+
async def embed_batch(
|
|
218
|
+
self, texts: List[str], batch_size: Optional[int] = None
|
|
219
|
+
) -> List[List[float]]:
|
|
220
|
+
"""
|
|
221
|
+
Generate embeddings with batching for large document sets.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
texts: List of text strings to embed
|
|
225
|
+
batch_size: Override default batch size (for vLLM, can handle large batches)
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
List of embedding vectors
|
|
229
|
+
"""
|
|
230
|
+
if not texts:
|
|
231
|
+
return []
|
|
232
|
+
|
|
233
|
+
# vLLM can handle large batches efficiently
|
|
234
|
+
effective_batch_size = batch_size or 100
|
|
235
|
+
all_embeddings = []
|
|
236
|
+
|
|
237
|
+
for i in range(0, len(texts), effective_batch_size):
|
|
238
|
+
batch = texts[i : i + effective_batch_size]
|
|
239
|
+
embeddings = await self._call_vllm_api(batch)
|
|
240
|
+
all_embeddings.extend(embeddings)
|
|
241
|
+
|
|
242
|
+
return all_embeddings
|
|
243
|
+
|
|
244
|
+
def get_model_info(self) -> dict:
|
|
245
|
+
"""Get information about the vLLM embedding service"""
|
|
246
|
+
return {
|
|
247
|
+
"backend": "vllm",
|
|
248
|
+
"endpoint": self._endpoint,
|
|
249
|
+
"model_name": self._model,
|
|
250
|
+
"dimension": self._dimension,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async def close(self):
|
|
254
|
+
"""Close HTTP client connection"""
|
|
255
|
+
await self._client.aclose()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ============ Singleton Accessor ============
|
|
259
|
+
|
|
260
|
+
_vllm_embedding_service: Optional[VLLMEmbeddingService] = None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_vllm_embedding_service(
|
|
264
|
+
config: Optional["EmbeddingConfig"] = None,
|
|
265
|
+
) -> VLLMEmbeddingService:
|
|
266
|
+
"""
|
|
267
|
+
Get the singleton VLLMEmbeddingService instance.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
config: Optional EmbeddingConfig (only used on first call)
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
VLLMEmbeddingService singleton instance
|
|
274
|
+
"""
|
|
275
|
+
global _vllm_embedding_service
|
|
276
|
+
if _vllm_embedding_service is None:
|
|
277
|
+
_vllm_embedding_service = VLLMEmbeddingService(config)
|
|
278
|
+
return _vllm_embedding_service
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def reset_vllm_embedding_service() -> None:
|
|
282
|
+
"""
|
|
283
|
+
Reset the singleton instance (for testing purposes).
|
|
284
|
+
"""
|
|
285
|
+
global _vllm_embedding_service
|
|
286
|
+
if _vllm_embedding_service is not None:
|
|
287
|
+
_vllm_embedding_service._initialized = False
|
|
288
|
+
_vllm_embedding_service = None
|
|
289
|
+
VLLMEmbeddingService._instance = None
|
|
290
|
+
VLLMEmbeddingService._initialized = False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Codebuff Core - Tests"""
|