codebuff-agent 2.1.74__py3-none-any.whl

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