hdsp-jupyter-extension 2.0.6__py3-none-any.whl → 2.0.8__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.
- agent_server/core/embedding_service.py +67 -46
- agent_server/core/rag_manager.py +31 -17
- agent_server/core/reflection_engine.py +0 -1
- agent_server/core/retriever.py +13 -8
- agent_server/core/vllm_embedding_service.py +243 -0
- agent_server/knowledge/watchdog_service.py +1 -1
- agent_server/langchain/ARCHITECTURE.md +1193 -0
- agent_server/langchain/agent.py +82 -588
- agent_server/langchain/custom_middleware.py +663 -0
- agent_server/langchain/executors/__init__.py +2 -7
- agent_server/langchain/executors/notebook_searcher.py +46 -38
- agent_server/langchain/hitl_config.py +71 -0
- agent_server/langchain/llm_factory.py +166 -0
- agent_server/langchain/logging_utils.py +223 -0
- agent_server/langchain/prompts.py +150 -0
- agent_server/langchain/state.py +16 -6
- agent_server/langchain/tools/__init__.py +19 -0
- agent_server/langchain/tools/file_tools.py +354 -114
- agent_server/langchain/tools/file_utils.py +334 -0
- agent_server/langchain/tools/jupyter_tools.py +18 -18
- agent_server/langchain/tools/lsp_tools.py +264 -0
- agent_server/langchain/tools/resource_tools.py +161 -0
- agent_server/langchain/tools/search_tools.py +198 -216
- agent_server/langchain/tools/shell_tools.py +54 -0
- agent_server/main.py +11 -1
- agent_server/routers/health.py +1 -1
- agent_server/routers/langchain_agent.py +1040 -289
- agent_server/routers/rag.py +8 -3
- hdsp_agent_core/models/rag.py +15 -1
- hdsp_agent_core/prompts/auto_agent_prompts.py +3 -3
- hdsp_agent_core/services/rag_service.py +6 -1
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.02d346171474a0fb2dc1.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js +470 -7
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js +3196 -441
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.addf2fa038fa60304aa2.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js +9 -7
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +2 -1
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +75 -69
- jupyter_ext/__init__.py +18 -0
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +1351 -58
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +3 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.02d346171474a0fb2dc1.js → frontend_styles_index_js.8740a527757068814573.js} +470 -7
- jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.a223ea20056954479ae9.js → lib_index_js.e4ff4b5779b5e049f84c.js} +3196 -441
- jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.addf2fa038fa60304aa2.js → remoteEntry.020cdb0b864cfaa4e41e.js} +9 -7
- jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- jupyter_ext/resource_usage.py +180 -0
- jupyter_ext/tests/test_handlers.py +58 -0
- agent_server/langchain/executors/jupyter_executor.py +0 -429
- agent_server/langchain/middleware/__init__.py +0 -36
- agent_server/langchain/middleware/code_search_middleware.py +0 -278
- agent_server/langchain/middleware/error_handling_middleware.py +0 -338
- agent_server/langchain/middleware/jupyter_execution_middleware.py +0 -301
- agent_server/langchain/middleware/rag_middleware.py +0 -227
- agent_server/langchain/middleware/validation_middleware.py +0 -240
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.02d346171474a0fb2dc1.js.map +0 -1
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.addf2fa038fa60304aa2.js.map +0 -1
- jupyter_ext/labextension/static/frontend_styles_index_js.02d346171474a0fb2dc1.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.addf2fa038fa60304aa2.js.map +0 -1
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LSP Tools for LangChain Agent
|
|
3
|
+
|
|
4
|
+
Provides tools for LSP (Language Server Protocol) integration:
|
|
5
|
+
- diagnostics_tool: Get code diagnostics (errors, warnings)
|
|
6
|
+
- references_tool: Find symbol references
|
|
7
|
+
|
|
8
|
+
Crush 패턴 적용:
|
|
9
|
+
- 진단 결과 포맷팅 (severity 기반 정렬)
|
|
10
|
+
- 출력 제한 (최대 10개 + 요약)
|
|
11
|
+
- Grep-then-LSP 패턴 (references)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from langchain_core.tools import tool
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DiagnosticsInput(BaseModel):
|
|
21
|
+
"""Input schema for diagnostics tool"""
|
|
22
|
+
|
|
23
|
+
path: Optional[str] = Field(
|
|
24
|
+
default=None,
|
|
25
|
+
description="File path to get diagnostics for. If not provided, returns project-wide diagnostics.",
|
|
26
|
+
)
|
|
27
|
+
severity_filter: Optional[str] = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Filter by severity: 'error', 'warning', 'hint', or None for all",
|
|
30
|
+
)
|
|
31
|
+
execution_result: Optional[Dict[str, Any]] = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description="LSP diagnostics result from client",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ReferencesInput(BaseModel):
|
|
38
|
+
"""Input schema for references tool"""
|
|
39
|
+
|
|
40
|
+
symbol: str = Field(description="Symbol name to find references for")
|
|
41
|
+
path: Optional[str] = Field(
|
|
42
|
+
default=None,
|
|
43
|
+
description="File path where the symbol is located (optional)",
|
|
44
|
+
)
|
|
45
|
+
line: Optional[int] = Field(
|
|
46
|
+
default=None, description="Line number (1-indexed, optional)"
|
|
47
|
+
)
|
|
48
|
+
character: Optional[int] = Field(
|
|
49
|
+
default=None, description="Character position (optional)"
|
|
50
|
+
)
|
|
51
|
+
execution_result: Optional[Dict[str, Any]] = Field(
|
|
52
|
+
default=None,
|
|
53
|
+
description="LSP references result from client",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@tool(args_schema=DiagnosticsInput)
|
|
58
|
+
def diagnostics_tool(
|
|
59
|
+
path: Optional[str] = None,
|
|
60
|
+
severity_filter: Optional[str] = None,
|
|
61
|
+
execution_result: Optional[Dict[str, Any]] = None,
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
"""
|
|
64
|
+
Get LSP diagnostics (errors, warnings) for a file or the entire project.
|
|
65
|
+
|
|
66
|
+
Use this tool to:
|
|
67
|
+
- Check for syntax errors before running code
|
|
68
|
+
- Find type errors in Python/TypeScript files
|
|
69
|
+
- Identify unused imports or variables
|
|
70
|
+
- Verify code quality issues after editing
|
|
71
|
+
|
|
72
|
+
The diagnostics are provided by language servers (pylsp, etc.)
|
|
73
|
+
and are more accurate than simple linting.
|
|
74
|
+
|
|
75
|
+
**Best Practice**: Always check diagnostics after editing code:
|
|
76
|
+
1. edit_file_tool(...) - make changes
|
|
77
|
+
2. diagnostics_tool(path="file.py") - verify no new errors
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
path: Optional file path. None = project-wide diagnostics
|
|
81
|
+
severity_filter: Optional filter ('error', 'warning', 'hint')
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Formatted diagnostics with severity, location, and message
|
|
85
|
+
"""
|
|
86
|
+
if execution_result is None:
|
|
87
|
+
# Client needs to execute this
|
|
88
|
+
return {
|
|
89
|
+
"tool": "diagnostics_tool",
|
|
90
|
+
"parameters": {
|
|
91
|
+
"path": path,
|
|
92
|
+
"severity_filter": severity_filter,
|
|
93
|
+
},
|
|
94
|
+
"status": "pending_execution",
|
|
95
|
+
"message": "Diagnostics request queued for LSP bridge execution",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Process client result (Crush 패턴)
|
|
99
|
+
diagnostics = execution_result.get("diagnostics", [])
|
|
100
|
+
lsp_available = execution_result.get("lsp_available", False)
|
|
101
|
+
|
|
102
|
+
if not lsp_available:
|
|
103
|
+
return {
|
|
104
|
+
"tool": "diagnostics_tool",
|
|
105
|
+
"success": True,
|
|
106
|
+
"output": "LSP not available. Install jupyterlab-lsp for code diagnostics.\nUse search_workspace_tool for text-based code search instead.",
|
|
107
|
+
"counts": {"errors": 0, "warnings": 0, "total": 0},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Severity ordering (errors first)
|
|
111
|
+
severity_order = {"error": 0, "warning": 1, "information": 2, "hint": 3}
|
|
112
|
+
|
|
113
|
+
# Sort diagnostics
|
|
114
|
+
sorted_diags = sorted(
|
|
115
|
+
diagnostics,
|
|
116
|
+
key=lambda d: (
|
|
117
|
+
severity_order.get(d.get("severity", "hint"), 3),
|
|
118
|
+
d.get("file", ""),
|
|
119
|
+
d.get("line", 0),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Filter by severity if specified
|
|
124
|
+
if severity_filter:
|
|
125
|
+
sorted_diags = [
|
|
126
|
+
d for d in sorted_diags if d.get("severity") == severity_filter
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
# Format output (Crush의 formatDiagnostics 패턴)
|
|
130
|
+
formatted_lines = []
|
|
131
|
+
for d in sorted_diags[:10]: # 최대 10개
|
|
132
|
+
severity = d.get("severity", "hint").upper()
|
|
133
|
+
line = d.get("line", 0)
|
|
134
|
+
col = d.get("character", 0)
|
|
135
|
+
source = d.get("source", "")
|
|
136
|
+
code = d.get("code", "")
|
|
137
|
+
message = d.get("message", "")
|
|
138
|
+
file = d.get("file", path or "")
|
|
139
|
+
|
|
140
|
+
location = f"{file}:{line}:{col}" if file else f"L{line}:{col}"
|
|
141
|
+
source_info = f"[{source}]" if source else ""
|
|
142
|
+
code_info = f"[{code}]" if code else ""
|
|
143
|
+
|
|
144
|
+
formatted_lines.append(
|
|
145
|
+
f"{severity} {location} {source_info}{code_info} {message}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Calculate counts
|
|
149
|
+
total = len(diagnostics)
|
|
150
|
+
errors = sum(1 for d in diagnostics if d.get("severity") == "error")
|
|
151
|
+
warnings = sum(1 for d in diagnostics if d.get("severity") == "warning")
|
|
152
|
+
|
|
153
|
+
# Add summary
|
|
154
|
+
summary = f"\n--- Summary: {errors} errors, {warnings} warnings, {total} total"
|
|
155
|
+
if total > 10:
|
|
156
|
+
summary += f" (showing first 10)"
|
|
157
|
+
|
|
158
|
+
output = (
|
|
159
|
+
"\n".join(formatted_lines) + summary
|
|
160
|
+
if formatted_lines
|
|
161
|
+
else f"No diagnostics found.{' LSP is available.' if lsp_available else ''}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
"tool": "diagnostics_tool",
|
|
166
|
+
"success": True,
|
|
167
|
+
"output": output,
|
|
168
|
+
"counts": {"errors": errors, "warnings": warnings, "total": total},
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@tool(args_schema=ReferencesInput)
|
|
173
|
+
def references_tool(
|
|
174
|
+
symbol: str,
|
|
175
|
+
path: Optional[str] = None,
|
|
176
|
+
line: Optional[int] = None,
|
|
177
|
+
character: Optional[int] = None,
|
|
178
|
+
execution_result: Optional[Dict[str, Any]] = None,
|
|
179
|
+
) -> Dict[str, Any]:
|
|
180
|
+
"""
|
|
181
|
+
Find all references to a symbol across the codebase.
|
|
182
|
+
|
|
183
|
+
Use this tool to:
|
|
184
|
+
- Check if a function/class is used before renaming/deleting
|
|
185
|
+
- Understand how a variable is used throughout the code
|
|
186
|
+
- Find all usages before refactoring
|
|
187
|
+
|
|
188
|
+
If LSP is not available, falls back to search_workspace_tool.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
symbol: Symbol name (function, class, variable)
|
|
192
|
+
path: Optional file path where symbol is located
|
|
193
|
+
line: Optional line number (1-indexed)
|
|
194
|
+
character: Optional character position
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
List of locations where the symbol is referenced
|
|
198
|
+
"""
|
|
199
|
+
if execution_result is None:
|
|
200
|
+
return {
|
|
201
|
+
"tool": "references_tool",
|
|
202
|
+
"parameters": {
|
|
203
|
+
"symbol": symbol,
|
|
204
|
+
"path": path,
|
|
205
|
+
"line": line,
|
|
206
|
+
"character": character,
|
|
207
|
+
},
|
|
208
|
+
"status": "pending_execution",
|
|
209
|
+
"message": "References search queued for execution",
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
locations = execution_result.get("locations", [])
|
|
213
|
+
lsp_available = execution_result.get("lsp_available", False)
|
|
214
|
+
used_grep = execution_result.get("used_grep", False)
|
|
215
|
+
|
|
216
|
+
if not locations:
|
|
217
|
+
if not lsp_available:
|
|
218
|
+
return {
|
|
219
|
+
"tool": "references_tool",
|
|
220
|
+
"success": True,
|
|
221
|
+
"output": f"LSP not available. Use search_workspace_tool with pattern='{symbol}' for text-based search.",
|
|
222
|
+
"count": 0,
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
"tool": "references_tool",
|
|
226
|
+
"success": True,
|
|
227
|
+
"output": f"No references found for '{symbol}'",
|
|
228
|
+
"count": 0,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Group by file (Crush 패턴)
|
|
232
|
+
by_file: Dict[str, List] = {}
|
|
233
|
+
for loc in locations:
|
|
234
|
+
file = loc.get("file", "unknown")
|
|
235
|
+
if file not in by_file:
|
|
236
|
+
by_file[file] = []
|
|
237
|
+
by_file[file].append(loc)
|
|
238
|
+
|
|
239
|
+
# Format output
|
|
240
|
+
method_note = " (grep-based)" if used_grep else " (LSP)"
|
|
241
|
+
formatted_lines = [f"Found {len(locations)} references to '{symbol}'{method_note}:\n"]
|
|
242
|
+
|
|
243
|
+
for file, locs in sorted(by_file.items()):
|
|
244
|
+
formatted_lines.append(f"\n📄 {file}")
|
|
245
|
+
for loc in sorted(locs, key=lambda x: x.get("line", 0)):
|
|
246
|
+
line_num = loc.get("line", 0)
|
|
247
|
+
col = loc.get("character", 0)
|
|
248
|
+
preview = (loc.get("preview", "") or "")[:60]
|
|
249
|
+
formatted_lines.append(f" L{line_num}:{col} {preview}")
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"tool": "references_tool",
|
|
253
|
+
"success": True,
|
|
254
|
+
"output": "\n".join(formatted_lines),
|
|
255
|
+
"count": len(locations),
|
|
256
|
+
"by_file": {f: len(locs) for f, locs in by_file.items()},
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# Export all LSP tools
|
|
261
|
+
LSP_TOOLS = [
|
|
262
|
+
diagnostics_tool,
|
|
263
|
+
references_tool,
|
|
264
|
+
]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resource Check Tools for LangChain Agent
|
|
3
|
+
|
|
4
|
+
Provides a tool for checking resource availability before data processing.
|
|
5
|
+
This tool is executed on the client (Jupyter) side to accurately measure:
|
|
6
|
+
- System resources (RAM, CPU)
|
|
7
|
+
- File sizes for target files
|
|
8
|
+
- In-memory DataFrame shapes
|
|
9
|
+
|
|
10
|
+
Key features:
|
|
11
|
+
- On-demand resource checking (only when LLM needs it)
|
|
12
|
+
- Returns actionable recommendations (in-memory vs DASK/Chunking)
|
|
13
|
+
- Supports both file paths and DataFrame variable names
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from langchain_core.tools import tool
|
|
20
|
+
from pydantic import BaseModel, Field
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CheckResourceInput(BaseModel):
|
|
26
|
+
"""Input schema for check_resource tool"""
|
|
27
|
+
|
|
28
|
+
files: List[str] = Field(
|
|
29
|
+
default=[],
|
|
30
|
+
description="List of file paths to check sizes for (e.g., ['data.csv', 'train.parquet'])",
|
|
31
|
+
)
|
|
32
|
+
dataframes: List[str] = Field(
|
|
33
|
+
default=[],
|
|
34
|
+
description="List of DataFrame variable names to check in memory (e.g., ['df', 'train_df'])",
|
|
35
|
+
)
|
|
36
|
+
execution_result: Optional[Dict[str, Any]] = Field(
|
|
37
|
+
default=None,
|
|
38
|
+
description="Execution result payload from the client",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _build_file_size_command(files: List[str]) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Build a shell command to get file sizes.
|
|
45
|
+
Uses stat for cross-platform compatibility.
|
|
46
|
+
"""
|
|
47
|
+
if not files:
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
# Use stat with format that works on both macOS and Linux
|
|
51
|
+
# macOS: stat -f "%z %N"
|
|
52
|
+
# Linux: stat -c "%s %n"
|
|
53
|
+
# We use a portable approach with ls -l
|
|
54
|
+
file_list = " ".join(f"'{f}'" for f in files)
|
|
55
|
+
return f"ls -l {file_list} 2>/dev/null | awk '{{print $5, $NF}}'"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _build_dataframe_check_code(dataframes: List[str]) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Build Python code to check DataFrame shapes and memory usage.
|
|
61
|
+
Returns a JSON-serializable result.
|
|
62
|
+
"""
|
|
63
|
+
if not dataframes:
|
|
64
|
+
return ""
|
|
65
|
+
|
|
66
|
+
df_checks = []
|
|
67
|
+
for df_name in dataframes:
|
|
68
|
+
df_checks.append(f'''
|
|
69
|
+
try:
|
|
70
|
+
_df = {df_name}
|
|
71
|
+
_info = {{
|
|
72
|
+
"name": "{df_name}",
|
|
73
|
+
"exists": True,
|
|
74
|
+
"rows": len(_df) if hasattr(_df, '__len__') else None,
|
|
75
|
+
"cols": len(_df.columns) if hasattr(_df, 'columns') else None,
|
|
76
|
+
"memory_mb": round(_df.memory_usage(deep=True).sum() / 1024 / 1024, 2) if hasattr(_df, 'memory_usage') else None,
|
|
77
|
+
"type": type(_df).__name__
|
|
78
|
+
}}
|
|
79
|
+
except NameError:
|
|
80
|
+
_info = {{"name": "{df_name}", "exists": False}}
|
|
81
|
+
_results.append(_info)
|
|
82
|
+
''')
|
|
83
|
+
|
|
84
|
+
code = f'''
|
|
85
|
+
import json
|
|
86
|
+
_results = []
|
|
87
|
+
{chr(10).join(df_checks)}
|
|
88
|
+
print(json.dumps(_results))
|
|
89
|
+
'''
|
|
90
|
+
return code.strip()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@tool(args_schema=CheckResourceInput)
|
|
94
|
+
def check_resource_tool(
|
|
95
|
+
files: List[str] = None,
|
|
96
|
+
dataframes: List[str] = None,
|
|
97
|
+
execution_result: Optional[Dict[str, Any]] = None,
|
|
98
|
+
) -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
Check system resources, file sizes, and DataFrame shapes before data processing.
|
|
101
|
+
|
|
102
|
+
IMPORTANT: Call this tool BEFORE writing any data analysis or ML code to ensure
|
|
103
|
+
the generated code uses appropriate memory strategies (ex. in-memory vs DASK/Chunking).
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
files: List of file paths to check sizes for (e.g., ['data.csv', 'train.parquet'])
|
|
107
|
+
dataframes: List of DataFrame variable names in memory (e.g., ['df', 'train_df'])
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Dict with:
|
|
111
|
+
- system: Current RAM/CPU availability (ram_available_mb, ram_total_mb, cpu_cores)
|
|
112
|
+
- files: File sizes in MB for each requested file
|
|
113
|
+
- dataframes: DataFrame shapes and memory usage for each requested variable
|
|
114
|
+
"""
|
|
115
|
+
if files is None:
|
|
116
|
+
files = []
|
|
117
|
+
if dataframes is None:
|
|
118
|
+
dataframes = []
|
|
119
|
+
|
|
120
|
+
# Build commands for client-side execution
|
|
121
|
+
file_size_command = _build_file_size_command(files)
|
|
122
|
+
dataframe_check_code = _build_dataframe_check_code(dataframes)
|
|
123
|
+
|
|
124
|
+
response: Dict[str, Any] = {
|
|
125
|
+
"tool": "check_resource_tool",
|
|
126
|
+
"parameters": {
|
|
127
|
+
"files": files,
|
|
128
|
+
"dataframes": dataframes,
|
|
129
|
+
},
|
|
130
|
+
"file_size_command": file_size_command,
|
|
131
|
+
"dataframe_check_code": dataframe_check_code,
|
|
132
|
+
"status": "pending_execution",
|
|
133
|
+
"message": "Resource check queued for execution by client",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if execution_result is not None:
|
|
137
|
+
response["execution_result"] = execution_result
|
|
138
|
+
response["status"] = "complete"
|
|
139
|
+
response["message"] = "Resource check completed"
|
|
140
|
+
|
|
141
|
+
# Parse the execution result
|
|
142
|
+
if isinstance(execution_result, dict):
|
|
143
|
+
response["success"] = execution_result.get("success", False)
|
|
144
|
+
|
|
145
|
+
# System resources
|
|
146
|
+
response["system"] = execution_result.get("system", {})
|
|
147
|
+
|
|
148
|
+
# File sizes
|
|
149
|
+
response["files"] = execution_result.get("files", [])
|
|
150
|
+
|
|
151
|
+
# DataFrame info
|
|
152
|
+
response["dataframes"] = execution_result.get("dataframes", [])
|
|
153
|
+
|
|
154
|
+
if "error" in execution_result:
|
|
155
|
+
response["error"] = execution_result["error"]
|
|
156
|
+
|
|
157
|
+
return response
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Export
|
|
161
|
+
RESOURCE_TOOLS = [check_resource_tool]
|