hdsp-jupyter-extension 2.0.8__py3-none-any.whl → 2.0.11__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/notebook_generator.py +4 -4
- agent_server/core/rag_manager.py +12 -3
- agent_server/core/retriever.py +2 -1
- agent_server/core/vllm_embedding_service.py +8 -5
- agent_server/langchain/ARCHITECTURE.md +7 -51
- agent_server/langchain/agent.py +31 -20
- agent_server/langchain/custom_middleware.py +234 -31
- agent_server/langchain/hitl_config.py +5 -8
- agent_server/langchain/logging_utils.py +7 -7
- agent_server/langchain/prompts.py +106 -120
- agent_server/langchain/tools/__init__.py +1 -10
- agent_server/langchain/tools/file_tools.py +9 -61
- agent_server/langchain/tools/jupyter_tools.py +0 -1
- agent_server/langchain/tools/lsp_tools.py +8 -8
- agent_server/langchain/tools/resource_tools.py +12 -12
- agent_server/langchain/tools/search_tools.py +3 -158
- agent_server/prompts/file_action_prompts.py +8 -8
- agent_server/routers/langchain_agent.py +200 -125
- hdsp_agent_core/__init__.py +46 -47
- hdsp_agent_core/factory.py +6 -10
- hdsp_agent_core/interfaces.py +4 -2
- hdsp_agent_core/knowledge/__init__.py +5 -5
- hdsp_agent_core/knowledge/chunking.py +87 -61
- hdsp_agent_core/knowledge/loader.py +103 -101
- hdsp_agent_core/llm/service.py +192 -107
- hdsp_agent_core/managers/config_manager.py +16 -22
- hdsp_agent_core/managers/session_manager.py +5 -4
- hdsp_agent_core/models/__init__.py +12 -12
- hdsp_agent_core/models/agent.py +15 -8
- hdsp_agent_core/models/common.py +1 -2
- hdsp_agent_core/models/rag.py +48 -111
- hdsp_agent_core/prompts/__init__.py +12 -12
- hdsp_agent_core/prompts/cell_action_prompts.py +9 -7
- hdsp_agent_core/services/agent_service.py +10 -8
- hdsp_agent_core/services/chat_service.py +10 -6
- hdsp_agent_core/services/rag_service.py +3 -6
- hdsp_agent_core/tests/conftest.py +4 -1
- hdsp_agent_core/tests/test_factory.py +2 -2
- hdsp_agent_core/tests/test_services.py +12 -19
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js +93 -4
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js +153 -130
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js +6 -6
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/METADATA +1 -3
- hdsp_jupyter_extension-2.0.11.dist-info/RECORD +144 -0
- jupyter_ext/__init__.py +21 -11
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +69 -50
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +2 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.8740a527757068814573.js → frontend_styles_index_js.2d9fb488c82498c45c2d.js} +93 -4
- jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.e4ff4b5779b5e049f84c.js → lib_index_js.58c1e128ba0b76f41f04.js} +153 -130
- jupyter_ext/labextension/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.9da31d1134a53b0c4af5.js} +6 -6
- jupyter_ext/labextension/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.dist-info/RECORD +0 -144
- jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -32,17 +32,9 @@ def get_hitl_interrupt_config() -> Dict[str, Any]:
|
|
|
32
32
|
"allowed_decisions": ["approve", "edit"],
|
|
33
33
|
"description": "📄 파일 읽기 실행 중",
|
|
34
34
|
},
|
|
35
|
-
"list_files_tool": {
|
|
36
|
-
"allowed_decisions": ["approve", "edit"],
|
|
37
|
-
"description": "📂 파일 목록 조회 중",
|
|
38
|
-
},
|
|
39
35
|
"write_todos": False, # Todo updates don't need approval
|
|
40
36
|
# Search tools need HITL for client-side execution (auto-approved by frontend)
|
|
41
37
|
# Uses 'edit' decision to pass execution_result back
|
|
42
|
-
"search_workspace_tool": {
|
|
43
|
-
"allowed_decisions": ["approve", "edit"],
|
|
44
|
-
"description": "🔍 Searching workspace files",
|
|
45
|
-
},
|
|
46
38
|
"search_notebook_cells_tool": {
|
|
47
39
|
"allowed_decisions": ["approve", "edit"],
|
|
48
40
|
"description": "🔍 Searching notebook cells",
|
|
@@ -66,6 +58,11 @@ def get_hitl_interrupt_config() -> Dict[str, Any]:
|
|
|
66
58
|
"allowed_decisions": ["approve", "edit", "reject"],
|
|
67
59
|
"description": "File edit requires approval",
|
|
68
60
|
},
|
|
61
|
+
# Multi-edit requires approval (multiple string replacements atomically)
|
|
62
|
+
"multiedit_file_tool": {
|
|
63
|
+
"allowed_decisions": ["approve", "edit", "reject"],
|
|
64
|
+
"description": "Multi-edit requires approval",
|
|
65
|
+
},
|
|
69
66
|
# Final answer doesn't need approval
|
|
70
67
|
"final_answer_tool": False,
|
|
71
68
|
}
|
|
@@ -116,7 +116,7 @@ def _with_middleware_logging(name: str):
|
|
|
116
116
|
|
|
117
117
|
class LLMTraceLogger(BaseCallbackHandler):
|
|
118
118
|
"""Log prompts, responses, tool calls, and tool messages.
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
Only logs newly added messages to avoid duplicate logging of conversation history.
|
|
121
121
|
Uses content hash of first message (usually system prompt) to identify conversation threads.
|
|
122
122
|
"""
|
|
@@ -126,7 +126,7 @@ class LLMTraceLogger(BaseCallbackHandler):
|
|
|
126
126
|
# Track last logged message count per conversation thread
|
|
127
127
|
# Key: hash of first message content, Value: message count
|
|
128
128
|
self._last_message_counts: Dict[str, int] = {}
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
def _get_conversation_key(self, batch) -> str:
|
|
131
131
|
"""Get a stable key for the conversation based on first message content."""
|
|
132
132
|
if not batch:
|
|
@@ -136,7 +136,7 @@ class LLMTraceLogger(BaseCallbackHandler):
|
|
|
136
136
|
# Use hash of first 200 chars of first message (usually system prompt)
|
|
137
137
|
content_preview = str(content)[:200] if content else ""
|
|
138
138
|
return str(hash(content_preview))
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
def _normalize_batches(self, messages):
|
|
141
141
|
if not messages:
|
|
142
142
|
return []
|
|
@@ -151,7 +151,7 @@ class LLMTraceLogger(BaseCallbackHandler):
|
|
|
151
151
|
conv_key = self._get_conversation_key(batch)
|
|
152
152
|
batch_key = f"{conv_key}_{batch_idx}"
|
|
153
153
|
last_count = self._last_message_counts.get(batch_key, 0)
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
# Only log new messages
|
|
156
156
|
new_messages = batch[last_count:]
|
|
157
157
|
if not new_messages:
|
|
@@ -161,13 +161,13 @@ class LLMTraceLogger(BaseCallbackHandler):
|
|
|
161
161
|
last_count,
|
|
162
162
|
)
|
|
163
163
|
continue
|
|
164
|
-
|
|
164
|
+
|
|
165
165
|
# Update count
|
|
166
166
|
self._last_message_counts[batch_key] = len(batch)
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
# Log with offset info
|
|
169
169
|
header = f"{title} (batch={batch_idx}, new={len(new_messages)}, total={len(batch)})"
|
|
170
|
-
|
|
170
|
+
|
|
171
171
|
# Format new messages with correct indices
|
|
172
172
|
lines = [LOG_SEPARATOR, header, LOG_SEPARATOR]
|
|
173
173
|
for idx, message in enumerate(new_messages, start=last_count):
|
|
@@ -5,129 +5,119 @@ Contains system prompts, JSON schema for fallback tool calling,
|
|
|
5
5
|
and middleware-specific prompts.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
1.
|
|
22
|
-
2.
|
|
23
|
-
3.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
- Any research or investigation task
|
|
66
|
-
|
|
67
|
-
**When full read is OK:**
|
|
68
|
-
- Small files (<500 lines)
|
|
69
|
-
- Files you need to edit immediately after reading
|
|
70
|
-
- After confirming file size with first scan
|
|
71
|
-
|
|
72
|
-
## 🔧 Code Development
|
|
73
|
-
For code generation/refactoring, use LSP tools (diagnostics_tool, references_tool) to check errors and find symbol usages. Use multiedit_file_tool for multiple changes in one file.
|
|
8
|
+
DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant. Respond in Korean only.
|
|
9
|
+
|
|
10
|
+
# Core Rules
|
|
11
|
+
1. Be concise (≤4 lines unless detail requested)
|
|
12
|
+
2. ALWAYS call a tool in every response - never respond with text only
|
|
13
|
+
3. ALWAYS include a brief Korean explanation before tool calls
|
|
14
|
+
|
|
15
|
+
# Task Workflow
|
|
16
|
+
|
|
17
|
+
## Simple Tasks (1-2 steps)
|
|
18
|
+
Execute directly without todos.
|
|
19
|
+
|
|
20
|
+
## Complex Tasks (3+ steps)
|
|
21
|
+
1. Create todos with write_todos (all items in Korean)
|
|
22
|
+
2. ALWAYS include "작업 요약 및 다음단계 제시" as the LAST item
|
|
23
|
+
3. After each tool result: check todos → call next tool → repeat
|
|
24
|
+
4. **Final todo ("작업 요약 및 다음단계 제시")**:
|
|
25
|
+
- FIRST: Output summary JSON in your content (REQUIRED!)
|
|
26
|
+
- THEN: Call write_todos to mark all as completed
|
|
27
|
+
- Both must be in the SAME response
|
|
28
|
+
|
|
29
|
+
### Summary JSON Format (MUST output before marking complete)
|
|
30
|
+
```json
|
|
31
|
+
{"summary": "실행된 작업 요약", "next_items": [{"subject": "제목", "description": "설명"}]}
|
|
32
|
+
```
|
|
33
|
+
Suggest 3-5 next items. **You CANNOT mark "작업 요약" as completed without outputting this JSON first.**
|
|
34
|
+
|
|
35
|
+
# Mandatory Checks
|
|
36
|
+
|
|
37
|
+
## Resource Check (BEFORE data operations)
|
|
38
|
+
Call `check_resource_tool` FIRST when:
|
|
39
|
+
- Loading files (.csv, .parquet, .json, .xlsx, .pickle, .h5, .feather)
|
|
40
|
+
- Using pandas/polars/dask for dataframes
|
|
41
|
+
- Training ML models
|
|
42
|
+
|
|
43
|
+
# Tool Usage
|
|
44
|
+
|
|
45
|
+
## File Search (execute_command_tool)
|
|
46
|
+
```bash
|
|
47
|
+
find . -iname '*filename*.csv' 2>/dev/null # Find by name
|
|
48
|
+
grep -rn 'pattern' --include='*.py' . # Search contents
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## File Reading (read_file_tool)
|
|
52
|
+
- Large files: `read_file_tool(path, limit=100)` first
|
|
53
|
+
- Use `offset` for pagination
|
|
54
|
+
- Small files (<500 lines): Read without limit
|
|
55
|
+
|
|
56
|
+
## Code Output
|
|
57
|
+
- For plots/charts: Use English labels only
|
|
58
|
+
- Use LSP tools for error checking and symbol lookup
|
|
59
|
+
- Use multiedit_file_tool for multiple changes
|
|
60
|
+
|
|
61
|
+
# Forbidden
|
|
62
|
+
- Empty responses (no tool call AND no content)
|
|
63
|
+
- Tool calls without Korean explanation
|
|
64
|
+
- Stopping with pending/in_progress todos
|
|
74
65
|
"""
|
|
75
66
|
|
|
76
|
-
JSON_TOOL_SCHEMA = """
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
67
|
+
JSON_TOOL_SCHEMA = """Respond with ONLY valid JSON:
|
|
68
|
+
{"tool": "<name>", "arguments": {...}}
|
|
69
|
+
|
|
70
|
+
Tools:
|
|
71
|
+
- jupyter_cell_tool: {"code": "<python>"}
|
|
72
|
+
- markdown_tool: {"content": "<markdown>"}
|
|
73
|
+
- write_todos: {"todos": [{"content": "한국어 내용", "status": "pending|in_progress|completed"}]}
|
|
74
|
+
- read_file_tool: {"path": "<path>", "offset": 0, "limit": 500}
|
|
75
|
+
- write_file_tool: {"path": "<path>", "content": "<content>", "overwrite": false}
|
|
76
|
+
- search_notebook_cells_tool: {"pattern": "<regex>"}
|
|
77
|
+
- execute_command_tool: {"command": "<cmd>"}
|
|
78
|
+
- check_resource_tool: {"files": ["<path>"], "dataframes": ["<var>"]}
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
- jupyter_cell_tool: Execute Python code. Arguments: {"code": "<python_code>"}
|
|
84
|
-
- markdown_tool: Add markdown cell. Arguments: {"content": "<markdown>"}
|
|
85
|
-
- final_answer_tool: Complete task. Arguments: {"answer": "<summary>"}
|
|
86
|
-
- write_todos: Update task list. Arguments: {"todos": [{"content": "...", "status": "pending|in_progress|completed"}]}
|
|
87
|
-
- read_file_tool: Read file with pagination. Arguments: {"path": "<file_path>", "offset": 0, "limit": 500}
|
|
88
|
-
- write_file_tool: Write file. Arguments: {"path": "<path>", "content": "<content>", "overwrite": false}
|
|
89
|
-
- list_files_tool: List directory. Arguments: {"path": ".", "recursive": false}
|
|
90
|
-
- search_workspace_tool: Search files. Arguments: {"pattern": "<regex>", "file_types": ["py"], "path": "."}
|
|
91
|
-
- search_notebook_cells_tool: Search notebook cells. Arguments: {"pattern": "<regex>"}
|
|
92
|
-
- execute_command_tool: Execute shell command. Arguments: {"command": "<command>", "stdin": "<input_for_prompts>"}
|
|
93
|
-
- check_resource_tool: Check resources before data processing. Arguments: {"files": ["<path>"], "dataframes": ["<var_name>"]}
|
|
94
|
-
|
|
95
|
-
Output ONLY the JSON object, no markdown, no explanation."""
|
|
80
|
+
No markdown wrapping. JSON only."""
|
|
96
81
|
|
|
97
82
|
TODO_LIST_SYSTEM_PROMPT = """
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
##
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
1.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
❌
|
|
83
|
+
# Todo Rules
|
|
84
|
+
|
|
85
|
+
## New Message = Fresh Start
|
|
86
|
+
- Each user message is a NEW task
|
|
87
|
+
- Ignore completion status from chat history
|
|
88
|
+
- Execute ALL current todos from scratch
|
|
89
|
+
|
|
90
|
+
## Structure
|
|
91
|
+
All todo items must be in Korean. Always end with:
|
|
92
|
+
- 작업 요약 및 다음단계 제시 ← 필수 마지막 항목!
|
|
93
|
+
|
|
94
|
+
## Workflow
|
|
95
|
+
1. Find pending/in_progress todo
|
|
96
|
+
2. Execute it NOW in THIS response
|
|
97
|
+
3. Mark completed
|
|
98
|
+
4. Repeat until all done
|
|
99
|
+
|
|
100
|
+
## 🔴 Final Todo ("작업 요약 및 다음단계 제시") - CRITICAL
|
|
101
|
+
When executing this todo, you MUST:
|
|
102
|
+
1. Output the summary JSON in your content FIRST:
|
|
103
|
+
{"summary": "작업 내용 요약", "next_items": [{"subject": "...", "description": "..."}]}
|
|
104
|
+
2. THEN call write_todos to mark all as completed
|
|
105
|
+
3. If you don't output the JSON, the todo will NOT be marked as completed!
|
|
106
|
+
|
|
107
|
+
## Completion Check
|
|
108
|
+
- ✅ Done: Executed in THIS response
|
|
109
|
+
- ❌ Not done: Only visible in chat history
|
|
110
|
+
- ❌ "작업 요약" cannot be completed without outputting summary JSON
|
|
111
|
+
|
|
112
|
+
## Forbidden
|
|
113
|
+
- Marking complete without executing in THIS response
|
|
114
|
+
- Marking "작업 요약" complete without outputting JSON summary
|
|
115
|
+
- Todos without "작업 요약 및 다음단계 제시" as final item
|
|
125
116
|
"""
|
|
126
117
|
|
|
127
|
-
TODO_LIST_TOOL_DESCRIPTION = """Update
|
|
128
|
-
|
|
129
|
-
After calling
|
|
130
|
-
NEVER end your response after calling write_todos - always continue with the next action tool."""
|
|
118
|
+
TODO_LIST_TOOL_DESCRIPTION = """Update task list for tracking progress.
|
|
119
|
+
This tool ONLY tracks status - does NOT execute tasks.
|
|
120
|
+
After calling: immediately call next action tool (unless ALL completed)."""
|
|
131
121
|
|
|
132
122
|
# Non-HITL tools that execute immediately without user approval
|
|
133
123
|
NON_HITL_TOOLS = {
|
|
@@ -135,10 +125,6 @@ NON_HITL_TOOLS = {
|
|
|
135
125
|
"markdown",
|
|
136
126
|
"read_file_tool",
|
|
137
127
|
"read_file",
|
|
138
|
-
"list_files_tool",
|
|
139
|
-
"list_files",
|
|
140
|
-
"search_workspace_tool",
|
|
141
|
-
"search_workspace",
|
|
142
128
|
"search_notebook_cells_tool",
|
|
143
129
|
"search_notebook_cells",
|
|
144
130
|
"write_todos",
|
|
@@ -4,14 +4,11 @@ LangChain Tools for Jupyter Agent
|
|
|
4
4
|
Tools available:
|
|
5
5
|
- jupyter_cell: Execute Python code in notebook
|
|
6
6
|
- markdown: Add markdown cell
|
|
7
|
-
- final_answer: Complete the task
|
|
8
7
|
- read_file: Read file content
|
|
9
8
|
- write_file: Write file content
|
|
10
9
|
- edit_file: Edit file with string replacement
|
|
11
|
-
- list_files: List directory contents
|
|
12
|
-
- search_workspace: Search files in workspace
|
|
13
10
|
- search_notebook_cells: Search cells in notebooks
|
|
14
|
-
- execute_command_tool: Run shell commands (client-executed)
|
|
11
|
+
- execute_command_tool: Run shell commands (client-executed, also for file search)
|
|
15
12
|
- check_resource_tool: Check resources before data processing (client-executed)
|
|
16
13
|
- diagnostics_tool: Get LSP diagnostics (errors, warnings)
|
|
17
14
|
- references_tool: Find symbol references via LSP
|
|
@@ -19,13 +16,11 @@ Tools available:
|
|
|
19
16
|
|
|
20
17
|
from agent_server.langchain.tools.file_tools import (
|
|
21
18
|
edit_file_tool,
|
|
22
|
-
list_files_tool,
|
|
23
19
|
multiedit_file_tool,
|
|
24
20
|
read_file_tool,
|
|
25
21
|
write_file_tool,
|
|
26
22
|
)
|
|
27
23
|
from agent_server.langchain.tools.jupyter_tools import (
|
|
28
|
-
final_answer_tool,
|
|
29
24
|
jupyter_cell_tool,
|
|
30
25
|
markdown_tool,
|
|
31
26
|
)
|
|
@@ -36,20 +31,16 @@ from agent_server.langchain.tools.lsp_tools import (
|
|
|
36
31
|
from agent_server.langchain.tools.resource_tools import check_resource_tool
|
|
37
32
|
from agent_server.langchain.tools.search_tools import (
|
|
38
33
|
search_notebook_cells_tool,
|
|
39
|
-
search_workspace_tool,
|
|
40
34
|
)
|
|
41
35
|
from agent_server.langchain.tools.shell_tools import execute_command_tool
|
|
42
36
|
|
|
43
37
|
__all__ = [
|
|
44
38
|
"jupyter_cell_tool",
|
|
45
39
|
"markdown_tool",
|
|
46
|
-
"final_answer_tool",
|
|
47
40
|
"read_file_tool",
|
|
48
41
|
"write_file_tool",
|
|
49
42
|
"edit_file_tool",
|
|
50
43
|
"multiedit_file_tool",
|
|
51
|
-
"list_files_tool",
|
|
52
|
-
"search_workspace_tool",
|
|
53
44
|
"search_notebook_cells_tool",
|
|
54
45
|
"execute_command_tool",
|
|
55
46
|
"check_resource_tool",
|
|
@@ -5,7 +5,6 @@ Provides tools for file system operations:
|
|
|
5
5
|
- read_file: Read file content
|
|
6
6
|
- write_file: Write content to file (requires approval)
|
|
7
7
|
- edit_file: Edit file with string replacement (requires approval)
|
|
8
|
-
- list_files: List directory contents
|
|
9
8
|
"""
|
|
10
9
|
|
|
11
10
|
import os
|
|
@@ -70,21 +69,6 @@ class EditFileInput(BaseModel):
|
|
|
70
69
|
)
|
|
71
70
|
|
|
72
71
|
|
|
73
|
-
class ListFilesInput(BaseModel):
|
|
74
|
-
"""Input schema for list_files tool"""
|
|
75
|
-
|
|
76
|
-
path: str = Field(default=".", description="Directory path to list")
|
|
77
|
-
recursive: bool = Field(default=False, description="Whether to list recursively")
|
|
78
|
-
pattern: Optional[str] = Field(
|
|
79
|
-
default=None,
|
|
80
|
-
description="Glob pattern to filter files (e.g., '*.py', '*.ipynb')",
|
|
81
|
-
)
|
|
82
|
-
execution_result: Optional[Dict[str, Any]] = Field(
|
|
83
|
-
default=None,
|
|
84
|
-
description="Optional execution result payload from the client",
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
72
|
def _validate_path(path: str, workspace_root: str = ".") -> str:
|
|
89
73
|
"""
|
|
90
74
|
Validate and resolve file path.
|
|
@@ -315,50 +299,13 @@ def edit_file_tool(
|
|
|
315
299
|
}
|
|
316
300
|
|
|
317
301
|
|
|
318
|
-
@tool(args_schema=ListFilesInput)
|
|
319
|
-
def list_files_tool(
|
|
320
|
-
path: str = ".",
|
|
321
|
-
recursive: bool = False,
|
|
322
|
-
pattern: Optional[str] = None,
|
|
323
|
-
execution_result: Optional[Dict[str, Any]] = None,
|
|
324
|
-
workspace_root: str = ".",
|
|
325
|
-
) -> Dict[str, Any]:
|
|
326
|
-
"""
|
|
327
|
-
List files and directories.
|
|
328
|
-
|
|
329
|
-
Args:
|
|
330
|
-
path: Directory path to list (default: current directory)
|
|
331
|
-
recursive: Whether to list recursively
|
|
332
|
-
pattern: Optional glob pattern to filter (e.g., '*.py')
|
|
333
|
-
|
|
334
|
-
Returns:
|
|
335
|
-
Dict with list of files and directories
|
|
336
|
-
"""
|
|
337
|
-
response: Dict[str, Any] = {
|
|
338
|
-
"tool": "list_files_tool",
|
|
339
|
-
"parameters": {
|
|
340
|
-
"path": path,
|
|
341
|
-
"recursive": recursive,
|
|
342
|
-
"pattern": pattern,
|
|
343
|
-
},
|
|
344
|
-
"status": "pending_execution",
|
|
345
|
-
"message": "File listing queued for execution by client",
|
|
346
|
-
}
|
|
347
|
-
if execution_result is not None:
|
|
348
|
-
response["execution_result"] = execution_result
|
|
349
|
-
response["status"] = "complete"
|
|
350
|
-
response["message"] = "File listing executed with client-reported results"
|
|
351
|
-
return response
|
|
352
|
-
|
|
353
|
-
|
|
354
302
|
class EditOperation(BaseModel):
|
|
355
303
|
"""Single edit operation for multiedit_file tool"""
|
|
356
304
|
|
|
357
305
|
old_string: str = Field(description="The exact string to find and replace")
|
|
358
306
|
new_string: str = Field(description="The replacement string")
|
|
359
307
|
replace_all: bool = Field(
|
|
360
|
-
default=False,
|
|
361
|
-
description="Whether to replace all occurrences (default: false)"
|
|
308
|
+
default=False, description="Whether to replace all occurrences (default: false)"
|
|
362
309
|
)
|
|
363
310
|
|
|
364
311
|
|
|
@@ -456,12 +403,14 @@ def multiedit_file_tool(
|
|
|
456
403
|
|
|
457
404
|
old_preview = (old_str[:50] + "...") if len(old_str) > 50 else old_str
|
|
458
405
|
new_preview = (new_str[:50] + "...") if len(new_str) > 50 else new_str
|
|
459
|
-
edits_preview.append(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
406
|
+
edits_preview.append(
|
|
407
|
+
{
|
|
408
|
+
"index": i,
|
|
409
|
+
"old_preview": old_preview,
|
|
410
|
+
"new_preview": new_preview,
|
|
411
|
+
"replace_all": replace_all_val,
|
|
412
|
+
}
|
|
413
|
+
)
|
|
465
414
|
|
|
466
415
|
# Convert all edits to dicts for serialization
|
|
467
416
|
for edit in edits:
|
|
@@ -515,5 +464,4 @@ FILE_TOOLS = [
|
|
|
515
464
|
write_file_tool,
|
|
516
465
|
edit_file_tool,
|
|
517
466
|
multiedit_file_tool,
|
|
518
|
-
list_files_tool,
|
|
519
467
|
]
|
|
@@ -103,7 +103,7 @@ def diagnostics_tool(
|
|
|
103
103
|
return {
|
|
104
104
|
"tool": "diagnostics_tool",
|
|
105
105
|
"success": True,
|
|
106
|
-
"output": "LSP not available. Install jupyterlab-lsp for code diagnostics.\nUse
|
|
106
|
+
"output": "LSP not available. Install jupyterlab-lsp for code diagnostics.\nUse execute_command_tool with grep for text-based code search instead.",
|
|
107
107
|
"counts": {"errors": 0, "warnings": 0, "total": 0},
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -122,9 +122,7 @@ def diagnostics_tool(
|
|
|
122
122
|
|
|
123
123
|
# Filter by severity if specified
|
|
124
124
|
if severity_filter:
|
|
125
|
-
sorted_diags = [
|
|
126
|
-
d for d in sorted_diags if d.get("severity") == severity_filter
|
|
127
|
-
]
|
|
125
|
+
sorted_diags = [d for d in sorted_diags if d.get("severity") == severity_filter]
|
|
128
126
|
|
|
129
127
|
# Format output (Crush의 formatDiagnostics 패턴)
|
|
130
128
|
formatted_lines = []
|
|
@@ -153,7 +151,7 @@ def diagnostics_tool(
|
|
|
153
151
|
# Add summary
|
|
154
152
|
summary = f"\n--- Summary: {errors} errors, {warnings} warnings, {total} total"
|
|
155
153
|
if total > 10:
|
|
156
|
-
summary +=
|
|
154
|
+
summary += " (showing first 10)"
|
|
157
155
|
|
|
158
156
|
output = (
|
|
159
157
|
"\n".join(formatted_lines) + summary
|
|
@@ -185,7 +183,7 @@ def references_tool(
|
|
|
185
183
|
- Understand how a variable is used throughout the code
|
|
186
184
|
- Find all usages before refactoring
|
|
187
185
|
|
|
188
|
-
If LSP is not available, falls back to
|
|
186
|
+
If LSP is not available, falls back to execute_command_tool with grep.
|
|
189
187
|
|
|
190
188
|
Args:
|
|
191
189
|
symbol: Symbol name (function, class, variable)
|
|
@@ -218,7 +216,7 @@ def references_tool(
|
|
|
218
216
|
return {
|
|
219
217
|
"tool": "references_tool",
|
|
220
218
|
"success": True,
|
|
221
|
-
"output": f"LSP not available. Use
|
|
219
|
+
"output": f"LSP not available. Use execute_command_tool with grep with pattern='{symbol}' for text-based search.",
|
|
222
220
|
"count": 0,
|
|
223
221
|
}
|
|
224
222
|
return {
|
|
@@ -238,7 +236,9 @@ def references_tool(
|
|
|
238
236
|
|
|
239
237
|
# Format output
|
|
240
238
|
method_note = " (grep-based)" if used_grep else " (LSP)"
|
|
241
|
-
formatted_lines = [
|
|
239
|
+
formatted_lines = [
|
|
240
|
+
f"Found {len(locations)} references to '{symbol}'{method_note}:\n"
|
|
241
|
+
]
|
|
242
242
|
|
|
243
243
|
for file, locs in sorted(by_file.items()):
|
|
244
244
|
formatted_lines.append(f"\n📄 {file}")
|
|
@@ -46,7 +46,7 @@ def _build_file_size_command(files: List[str]) -> str:
|
|
|
46
46
|
"""
|
|
47
47
|
if not files:
|
|
48
48
|
return ""
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
# Use stat with format that works on both macOS and Linux
|
|
51
51
|
# macOS: stat -f "%z %N"
|
|
52
52
|
# Linux: stat -c "%s %n"
|
|
@@ -62,10 +62,10 @@ def _build_dataframe_check_code(dataframes: List[str]) -> str:
|
|
|
62
62
|
"""
|
|
63
63
|
if not dataframes:
|
|
64
64
|
return ""
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
df_checks = []
|
|
67
67
|
for df_name in dataframes:
|
|
68
|
-
df_checks.append(f
|
|
68
|
+
df_checks.append(f"""
|
|
69
69
|
try:
|
|
70
70
|
_df = {df_name}
|
|
71
71
|
_info = {{
|
|
@@ -79,14 +79,14 @@ try:
|
|
|
79
79
|
except NameError:
|
|
80
80
|
_info = {{"name": "{df_name}", "exists": False}}
|
|
81
81
|
_results.append(_info)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
code = f
|
|
82
|
+
""")
|
|
83
|
+
|
|
84
|
+
code = f"""
|
|
85
85
|
import json
|
|
86
86
|
_results = []
|
|
87
87
|
{chr(10).join(df_checks)}
|
|
88
88
|
print(json.dumps(_results))
|
|
89
|
-
|
|
89
|
+
"""
|
|
90
90
|
return code.strip()
|
|
91
91
|
|
|
92
92
|
|
|
@@ -137,20 +137,20 @@ def check_resource_tool(
|
|
|
137
137
|
response["execution_result"] = execution_result
|
|
138
138
|
response["status"] = "complete"
|
|
139
139
|
response["message"] = "Resource check completed"
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
# Parse the execution result
|
|
142
142
|
if isinstance(execution_result, dict):
|
|
143
143
|
response["success"] = execution_result.get("success", False)
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
# System resources
|
|
146
146
|
response["system"] = execution_result.get("system", {})
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
# File sizes
|
|
149
149
|
response["files"] = execution_result.get("files", [])
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
# DataFrame info
|
|
152
152
|
response["dataframes"] = execution_result.get("dataframes", [])
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
if "error" in execution_result:
|
|
155
155
|
response["error"] = execution_result["error"]
|
|
156
156
|
|