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,359 @@
|
|
|
1
|
+
"""DS 코딩 에이전트 커스텀 미들웨어.
|
|
2
|
+
|
|
3
|
+
데이터 사이언스 워크플로우에 특화된 미들웨어:
|
|
4
|
+
- DSMemoryMiddleware: 세션 메모리 (데이터셋, 실험 이력) 자동 추적
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
import uuid
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from langchain_core.messages import AIMessage, AnyMessage, SystemMessage
|
|
20
|
+
|
|
21
|
+
from langchain.agents.middleware.types import (
|
|
22
|
+
AgentMiddleware,
|
|
23
|
+
AgentState,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from .middleware_prompts import DS_MEMORY_INJECTION_TEMPLATE
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# MemoryStore
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
MEMORY_CATEGORIES = (
|
|
35
|
+
"data_source",
|
|
36
|
+
"user_preference",
|
|
37
|
+
"experiment",
|
|
38
|
+
"environment",
|
|
39
|
+
"decision",
|
|
40
|
+
"finding",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class MemoryEntry:
|
|
46
|
+
"""세션 메모리 단일 항목."""
|
|
47
|
+
|
|
48
|
+
id: str
|
|
49
|
+
category: str
|
|
50
|
+
content: str
|
|
51
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
52
|
+
access_count: int = 0
|
|
53
|
+
session_id: str = ""
|
|
54
|
+
tags: list[str] = field(default_factory=list)
|
|
55
|
+
persistent: bool = True # False = 세션 종료 시 삭제, 디스크 저장 안 함
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _entry_to_dict(entry: MemoryEntry) -> dict:
|
|
59
|
+
"""MemoryEntry → JSON-serializable dict. persistent 필드는 포함하지 않음."""
|
|
60
|
+
return {
|
|
61
|
+
"id": entry.id,
|
|
62
|
+
"category": entry.category,
|
|
63
|
+
"content": entry.content,
|
|
64
|
+
"created_at": entry.created_at.isoformat(),
|
|
65
|
+
"access_count": entry.access_count,
|
|
66
|
+
"session_id": entry.session_id,
|
|
67
|
+
"tags": entry.tags,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _dict_to_entry(d: dict) -> MemoryEntry:
|
|
72
|
+
"""dict → MemoryEntry. 디스크에서 복원된 항목은 항상 persistent=True."""
|
|
73
|
+
return MemoryEntry(
|
|
74
|
+
id=d["id"],
|
|
75
|
+
category=d["category"],
|
|
76
|
+
content=d["content"],
|
|
77
|
+
created_at=datetime.fromisoformat(d["created_at"]),
|
|
78
|
+
access_count=d.get("access_count", 0),
|
|
79
|
+
session_id=d.get("session_id", ""),
|
|
80
|
+
tags=d.get("tags", []),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MemoryStore:
|
|
85
|
+
"""키워드 기반 메모리 저장소 (선택적 파일 영속화).
|
|
86
|
+
|
|
87
|
+
storage_path가 주어지면 persistent=True인 항목을 JSON 파일로 저장.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
MAX_PERSISTENT_ENTRIES = 500
|
|
91
|
+
|
|
92
|
+
def __init__(self, storage_path: str | Path | None = None) -> None:
|
|
93
|
+
self._memories: dict[str, MemoryEntry] = {}
|
|
94
|
+
self._storage_path: Path | None = Path(storage_path) if storage_path else None
|
|
95
|
+
self._flush_timer: asyncio.TimerHandle | None = None
|
|
96
|
+
self._load()
|
|
97
|
+
|
|
98
|
+
# -- persistence helpers --------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def _load(self) -> None:
|
|
101
|
+
"""시작 시 디스크에서 메모리 로드."""
|
|
102
|
+
if not self._storage_path or not self._storage_path.exists():
|
|
103
|
+
return
|
|
104
|
+
try:
|
|
105
|
+
data = json.loads(self._storage_path.read_text(encoding="utf-8"))
|
|
106
|
+
for d in data:
|
|
107
|
+
entry = _dict_to_entry(d)
|
|
108
|
+
self._memories[entry.id] = entry
|
|
109
|
+
logger.info(
|
|
110
|
+
"MemoryStore: loaded %d entries from %s",
|
|
111
|
+
len(self._memories),
|
|
112
|
+
self._storage_path,
|
|
113
|
+
)
|
|
114
|
+
except (json.JSONDecodeError, KeyError, OSError) as e:
|
|
115
|
+
logger.warning("MemoryStore: failed to load memories: %s", e)
|
|
116
|
+
|
|
117
|
+
def _schedule_flush(self) -> None:
|
|
118
|
+
"""디바운스 디스크 저장 예약 (0.5초 후)."""
|
|
119
|
+
if not self._storage_path:
|
|
120
|
+
return
|
|
121
|
+
if self._flush_timer is not None:
|
|
122
|
+
self._flush_timer.cancel()
|
|
123
|
+
try:
|
|
124
|
+
loop = asyncio.get_running_loop()
|
|
125
|
+
self._flush_timer = loop.call_later(
|
|
126
|
+
0.5, lambda: loop.create_task(self._flush())
|
|
127
|
+
)
|
|
128
|
+
except RuntimeError:
|
|
129
|
+
# 이벤트 루프 밖에서 호출된 경우 동기 저장
|
|
130
|
+
self._flush_sync()
|
|
131
|
+
|
|
132
|
+
def _flush_sync(self) -> None:
|
|
133
|
+
"""동기적으로 persistent 항목을 디스크에 저장 (atomic write)."""
|
|
134
|
+
if not self._storage_path:
|
|
135
|
+
return
|
|
136
|
+
persistent = [
|
|
137
|
+
_entry_to_dict(e) for e in self._memories.values() if e.persistent
|
|
138
|
+
]
|
|
139
|
+
# 최대 개수 초과 시 오래된 것부터 제거
|
|
140
|
+
if len(persistent) > self.MAX_PERSISTENT_ENTRIES:
|
|
141
|
+
persistent.sort(key=lambda d: d["created_at"])
|
|
142
|
+
persistent = persistent[-self.MAX_PERSISTENT_ENTRIES :]
|
|
143
|
+
self._storage_path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
tmp = self._storage_path.with_suffix(".tmp")
|
|
145
|
+
tmp.write_text(
|
|
146
|
+
json.dumps(persistent, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
147
|
+
)
|
|
148
|
+
tmp.replace(self._storage_path) # atomic rename
|
|
149
|
+
|
|
150
|
+
async def _flush(self) -> None:
|
|
151
|
+
"""비동기 디스크 저장."""
|
|
152
|
+
await asyncio.to_thread(self._flush_sync)
|
|
153
|
+
|
|
154
|
+
def flush_now(self) -> None:
|
|
155
|
+
"""즉시 디스크에 저장 (세션 종료 시 호출)."""
|
|
156
|
+
if self._flush_timer is not None:
|
|
157
|
+
self._flush_timer.cancel()
|
|
158
|
+
self._flush_timer = None
|
|
159
|
+
self._flush_sync()
|
|
160
|
+
|
|
161
|
+
def clear_session(self, session_id: str) -> None:
|
|
162
|
+
"""특정 세션의 transient 메모리만 제거."""
|
|
163
|
+
to_remove = [
|
|
164
|
+
k
|
|
165
|
+
for k, v in self._memories.items()
|
|
166
|
+
if not v.persistent and v.session_id == session_id
|
|
167
|
+
]
|
|
168
|
+
for k in to_remove:
|
|
169
|
+
del self._memories[k]
|
|
170
|
+
|
|
171
|
+
# -- public API -----------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
def add(
|
|
174
|
+
self,
|
|
175
|
+
content: str,
|
|
176
|
+
category: str,
|
|
177
|
+
*,
|
|
178
|
+
session_id: str = "",
|
|
179
|
+
tags: list[str] | None = None,
|
|
180
|
+
persistent: bool = True,
|
|
181
|
+
) -> MemoryEntry:
|
|
182
|
+
"""메모리 추가."""
|
|
183
|
+
entry = MemoryEntry(
|
|
184
|
+
id=str(uuid.uuid4()),
|
|
185
|
+
category=category,
|
|
186
|
+
content=content,
|
|
187
|
+
session_id=session_id,
|
|
188
|
+
tags=tags or [],
|
|
189
|
+
persistent=persistent,
|
|
190
|
+
)
|
|
191
|
+
self._memories[entry.id] = entry
|
|
192
|
+
if persistent:
|
|
193
|
+
self._schedule_flush()
|
|
194
|
+
return entry
|
|
195
|
+
|
|
196
|
+
def search(self, query: str, *, limit: int = 10) -> list[MemoryEntry]:
|
|
197
|
+
"""키워드 기반 검색. 대소문자 무시."""
|
|
198
|
+
if not query.strip():
|
|
199
|
+
return []
|
|
200
|
+
|
|
201
|
+
keywords = query.lower().split()
|
|
202
|
+
scored: list[tuple[int, MemoryEntry]] = []
|
|
203
|
+
|
|
204
|
+
for entry in self._memories.values():
|
|
205
|
+
text = f"{entry.content} {entry.category} {' '.join(entry.tags)}".lower()
|
|
206
|
+
score = sum(1 for kw in keywords if kw in text)
|
|
207
|
+
if score > 0:
|
|
208
|
+
scored.append((score, entry))
|
|
209
|
+
|
|
210
|
+
scored.sort(key=lambda x: (-x[0], x[1].created_at))
|
|
211
|
+
results = [entry for _, entry in scored[:limit]]
|
|
212
|
+
|
|
213
|
+
for entry in results:
|
|
214
|
+
entry.access_count += 1
|
|
215
|
+
|
|
216
|
+
return results
|
|
217
|
+
|
|
218
|
+
def get_by_category(self, category: str, *, limit: int = 10) -> list[MemoryEntry]:
|
|
219
|
+
"""카테고리별 메모리 조회."""
|
|
220
|
+
entries = [e for e in self._memories.values() if e.category == category]
|
|
221
|
+
entries.sort(key=lambda e: e.created_at, reverse=True)
|
|
222
|
+
return entries[:limit]
|
|
223
|
+
|
|
224
|
+
def all(self) -> list[MemoryEntry]:
|
|
225
|
+
"""모든 메모리 조회."""
|
|
226
|
+
return list(self._memories.values())
|
|
227
|
+
|
|
228
|
+
def clear(self) -> None:
|
|
229
|
+
"""모든 메모리 삭제."""
|
|
230
|
+
self._memories.clear()
|
|
231
|
+
self._schedule_flush()
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def count(self) -> int:
|
|
235
|
+
return len(self._memories)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# 자동 추출 규칙
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
_EXTRACTION_PATTERNS: list[tuple[str, str, re.Pattern[str]]] = [
|
|
243
|
+
# (카테고리, 설명 접두어, 정규식 패턴)
|
|
244
|
+
(
|
|
245
|
+
"data_source",
|
|
246
|
+
"Loaded data",
|
|
247
|
+
re.compile(
|
|
248
|
+
r"(?:read_csv|read_parquet|read_excel|read_json|load_dataset)\(['\"]([^'\"]+)['\"]\)"
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
("experiment", "Model training", re.compile(r"\.(fit|train)\s*\(")),
|
|
252
|
+
(
|
|
253
|
+
"experiment",
|
|
254
|
+
"Model metric",
|
|
255
|
+
re.compile(
|
|
256
|
+
r"(?:accuracy|f1_score|rmse|auc|precision|recall|r2_score|mean_squared_error)\s*[:=]?\s*([\d.]+)"
|
|
257
|
+
),
|
|
258
|
+
),
|
|
259
|
+
("finding", "Shape info", re.compile(r"\.shape\s*(?:==|is)?\s*\((\d+),\s*(\d+)\)")),
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _extract_facts_from_messages(messages: list[AnyMessage]) -> list[tuple[str, str]]:
|
|
264
|
+
"""메시지에서 DS 관련 팩트를 자동 추출.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
(category, content) 튜플 리스트
|
|
268
|
+
"""
|
|
269
|
+
facts: list[tuple[str, str]] = []
|
|
270
|
+
|
|
271
|
+
for msg in messages:
|
|
272
|
+
if not hasattr(msg, "content") or not isinstance(msg.content, str):
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
text = msg.content
|
|
276
|
+
|
|
277
|
+
for category, prefix, pattern in _EXTRACTION_PATTERNS:
|
|
278
|
+
for match in pattern.finditer(text):
|
|
279
|
+
fact_content = f"{prefix}: {match.group(0)}"
|
|
280
|
+
facts.append((category, fact_content))
|
|
281
|
+
|
|
282
|
+
return facts
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# DSMemoryMiddleware
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class DSMemoryMiddleware(AgentMiddleware):
|
|
291
|
+
"""DS 세션 메모리 미들웨어.
|
|
292
|
+
|
|
293
|
+
before_model: 관련 메모리를 SystemMessage에 주입
|
|
294
|
+
after_model: AI 응답에서 팩트를 자동 추출하여 저장
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
def __init__(
|
|
298
|
+
self,
|
|
299
|
+
memory_store: MemoryStore,
|
|
300
|
+
*,
|
|
301
|
+
max_memories_per_call: int = 10,
|
|
302
|
+
auto_extract: bool = True,
|
|
303
|
+
) -> None:
|
|
304
|
+
super().__init__()
|
|
305
|
+
self.memory_store = memory_store
|
|
306
|
+
self.max_memories_per_call = max_memories_per_call
|
|
307
|
+
self.auto_extract = auto_extract
|
|
308
|
+
self.tools = []
|
|
309
|
+
|
|
310
|
+
def before_model(
|
|
311
|
+
self, state: AgentState[Any], runtime: Any
|
|
312
|
+
) -> dict[str, Any] | None:
|
|
313
|
+
"""관련 메모리를 메시지에 주입."""
|
|
314
|
+
if self.memory_store.count == 0:
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
# 마지막 사용자 메시지에서 쿼리 추출
|
|
318
|
+
query = ""
|
|
319
|
+
for msg in reversed(state["messages"]):
|
|
320
|
+
if hasattr(msg, "content") and isinstance(msg.content, str):
|
|
321
|
+
if not isinstance(msg, AIMessage):
|
|
322
|
+
query = msg.content[:200]
|
|
323
|
+
break
|
|
324
|
+
|
|
325
|
+
if not query:
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
memories = self.memory_store.search(query, limit=self.max_memories_per_call)
|
|
329
|
+
if not memories:
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
memory_lines = [f"- [{m.category}] {m.content}" for m in memories]
|
|
333
|
+
memory_text = DS_MEMORY_INJECTION_TEMPLATE.format(
|
|
334
|
+
memories="\n".join(memory_lines)
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"messages": [SystemMessage(content=memory_text)],
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
def after_model(
|
|
342
|
+
self, state: AgentState[Any], runtime: Any
|
|
343
|
+
) -> dict[str, Any] | None:
|
|
344
|
+
"""AI 응답에서 팩트를 자동 추출."""
|
|
345
|
+
if not self.auto_extract:
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
# 마지막 AI 메시지에서 팩트 추출
|
|
349
|
+
recent_messages = state["messages"][-3:]
|
|
350
|
+
facts = _extract_facts_from_messages(recent_messages)
|
|
351
|
+
|
|
352
|
+
for category, content in facts:
|
|
353
|
+
# 중복 방지: 동일 내용이 이미 있으면 스킵
|
|
354
|
+
existing = self.memory_store.search(content, limit=1)
|
|
355
|
+
if existing and existing[0].content == content:
|
|
356
|
+
continue
|
|
357
|
+
self.memory_store.add(content, category)
|
|
358
|
+
|
|
359
|
+
return None
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""DS 코딩 에이전트 미들웨어 프롬프트.
|
|
2
|
+
|
|
3
|
+
SummarizationMiddleware, DSMemoryMiddleware, HumanInTheLoopMiddleware에서
|
|
4
|
+
사용하는 프롬프트 템플릿 정의.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
DS_SUMMARIZATION_PROMPT = """<role>
|
|
8
|
+
Data Science Session Context Extractor
|
|
9
|
+
</role>
|
|
10
|
+
|
|
11
|
+
<primary_objective>
|
|
12
|
+
Extract the most important context from this data science conversation to preserve continuity.
|
|
13
|
+
</primary_objective>
|
|
14
|
+
|
|
15
|
+
<instructions>
|
|
16
|
+
You must populate each section with relevant information or state "None" if nothing to report.
|
|
17
|
+
|
|
18
|
+
## SESSION INTENT
|
|
19
|
+
What is the user's primary data science goal? What dataset(s) are involved?
|
|
20
|
+
|
|
21
|
+
## DATA CONTEXT
|
|
22
|
+
- Dataset schemas: column names, dtypes, shapes (e.g., `df: (1000, 15) with columns [id, name, price, ...]`)
|
|
23
|
+
- Data sources: file paths, URLs, database connections
|
|
24
|
+
- Variable names in use: df, X_train, X_test, model, scaler, etc.
|
|
25
|
+
- Data quality issues discovered: missing values, outliers, duplicates
|
|
26
|
+
|
|
27
|
+
## ANALYSIS PHASE
|
|
28
|
+
Where are we in the DS workflow? (EDA / Preprocessing / Feature Engineering / Modeling / Evaluation / Reporting)
|
|
29
|
+
|
|
30
|
+
## KEY FINDINGS
|
|
31
|
+
- Statistical results, correlations, distributions discovered
|
|
32
|
+
- Model metrics: accuracy, F1, RMSE, AUC, hyperparameters used
|
|
33
|
+
- Visualizations created and their insights
|
|
34
|
+
- Failed approaches and WHY they failed (important for avoiding repetition)
|
|
35
|
+
|
|
36
|
+
## CODE EXECUTION STATE
|
|
37
|
+
- Last executed code and its output summary
|
|
38
|
+
- Active variables and their current state
|
|
39
|
+
- Installed packages or environment changes
|
|
40
|
+
|
|
41
|
+
## NEXT STEPS
|
|
42
|
+
What specific tasks remain? What should be done next?
|
|
43
|
+
</instructions>
|
|
44
|
+
|
|
45
|
+
<messages>
|
|
46
|
+
Messages to summarize:
|
|
47
|
+
{messages}
|
|
48
|
+
</messages>"""
|
|
49
|
+
|
|
50
|
+
DS_MEMORY_INJECTION_TEMPLATE = """## Relevant Session Memories
|
|
51
|
+
{memories}"""
|
|
52
|
+
|
|
53
|
+
DS_HITL_DESCRIPTION_PREFIX = "Agent action requires your approval"
|