hdsp-jupyter-extension 2.0.8__py3-none-any.whl → 2.0.10__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/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 +148 -31
- agent_server/langchain/hitl_config.py +0 -8
- agent_server/langchain/llm_factory.py +85 -1
- agent_server/langchain/logging_utils.py +7 -7
- agent_server/langchain/prompts.py +45 -36
- 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/routers/langchain_agent.py +122 -113
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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.10.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js +93 -4
- hdsp_jupyter_extension-2.0.10.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.10.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.dc6434bee96ab03a0539.js +90 -71
- hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.dc6434bee96ab03a0539.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.10.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.4a252df3ade74efee8d6.js +6 -6
- hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.4a252df3ade74efee8d6.js.map +1 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/METADATA +1 -3
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/RECORD +57 -57
- jupyter_ext/_version.py +1 -1
- 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.dc6434bee96ab03a0539.js} +90 -71
- jupyter_ext/labextension/static/lib_index_js.dc6434bee96ab03a0539.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.4a252df3ade74efee8d6.js} +6 -6
- jupyter_ext/labextension/static/remoteEntry.4a252df3ade74efee8d6.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
- 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.10.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.10.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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.10.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.10.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.10.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.10.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.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.10.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.10.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.10.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.10.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.10.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.10.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.10.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.10.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.10.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.10.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.10.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.10.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,6 +4,7 @@ LLM Factory for LangChain agent.
|
|
|
4
4
|
Provides functions to create LangChain LLM instances from configuration.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import json
|
|
7
8
|
import logging
|
|
8
9
|
from typing import Any, Dict
|
|
9
10
|
|
|
@@ -12,6 +13,74 @@ from agent_server.langchain.logging_utils import LLMTraceLogger
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
def _stringify_content(content: Any) -> str:
|
|
17
|
+
if content is None:
|
|
18
|
+
return ""
|
|
19
|
+
if isinstance(content, list):
|
|
20
|
+
parts = []
|
|
21
|
+
for part in content:
|
|
22
|
+
if isinstance(part, str):
|
|
23
|
+
parts.append(part)
|
|
24
|
+
elif isinstance(part, dict):
|
|
25
|
+
if part.get("type") == "text":
|
|
26
|
+
parts.append(part.get("text", ""))
|
|
27
|
+
elif "text" in part:
|
|
28
|
+
parts.append(part.get("text", ""))
|
|
29
|
+
return "\n".join(p for p in parts if p)
|
|
30
|
+
return str(content)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _summarize_payload(
|
|
34
|
+
payload: Dict[str, Any],
|
|
35
|
+
max_preview: int = 200,
|
|
36
|
+
max_items: int = 12,
|
|
37
|
+
) -> Dict[str, Any]:
|
|
38
|
+
summary: Dict[str, Any] = {}
|
|
39
|
+
|
|
40
|
+
def summarize_messages(key: str) -> None:
|
|
41
|
+
items = payload.get(key)
|
|
42
|
+
if not isinstance(items, list):
|
|
43
|
+
return
|
|
44
|
+
summarized = []
|
|
45
|
+
for item in items[:max_items]:
|
|
46
|
+
if not isinstance(item, dict):
|
|
47
|
+
summarized.append({"type": type(item).__name__})
|
|
48
|
+
continue
|
|
49
|
+
content_text = _stringify_content(item.get("content", ""))
|
|
50
|
+
entry = {
|
|
51
|
+
"role": item.get("role"),
|
|
52
|
+
"content_len": len(content_text),
|
|
53
|
+
"content_preview": content_text[:max_preview],
|
|
54
|
+
}
|
|
55
|
+
tool_calls = item.get("tool_calls")
|
|
56
|
+
if isinstance(tool_calls, list):
|
|
57
|
+
entry["tool_calls"] = [
|
|
58
|
+
tc.get("function", {}).get("name") or tc.get("name")
|
|
59
|
+
for tc in tool_calls
|
|
60
|
+
if isinstance(tc, dict)
|
|
61
|
+
]
|
|
62
|
+
summarized.append(entry)
|
|
63
|
+
if len(items) > max_items:
|
|
64
|
+
summarized.append({"truncated": len(items) - max_items})
|
|
65
|
+
summary[key] = summarized
|
|
66
|
+
|
|
67
|
+
summarize_messages("messages")
|
|
68
|
+
summarize_messages("input")
|
|
69
|
+
|
|
70
|
+
tools = payload.get("tools")
|
|
71
|
+
if isinstance(tools, list):
|
|
72
|
+
summary["tools"] = [
|
|
73
|
+
tool.get("function", {}).get("name") or tool.get("name")
|
|
74
|
+
for tool in tools
|
|
75
|
+
if isinstance(tool, dict)
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
if "tool_choice" in payload:
|
|
79
|
+
summary["tool_choice"] = payload.get("tool_choice")
|
|
80
|
+
|
|
81
|
+
return summary
|
|
82
|
+
|
|
83
|
+
|
|
15
84
|
def create_llm(llm_config: Dict[str, Any]):
|
|
16
85
|
"""Create LangChain LLM from config.
|
|
17
86
|
|
|
@@ -99,7 +168,22 @@ def _create_vllm_llm(llm_config: Dict[str, Any], callbacks):
|
|
|
99
168
|
|
|
100
169
|
logger.info(f"Creating vLLM LLM with model: {model}, endpoint: {endpoint}")
|
|
101
170
|
|
|
102
|
-
|
|
171
|
+
llm_class = ChatOpenAI
|
|
172
|
+
if "gpt-oss" in model.lower():
|
|
173
|
+
|
|
174
|
+
class ChatOpenAIGptOss(ChatOpenAI):
|
|
175
|
+
def _get_request_payload(self, input_, *, stop=None, **kwargs):
|
|
176
|
+
payload = super()._get_request_payload(input_, stop=stop, **kwargs)
|
|
177
|
+
summary = _summarize_payload(payload)
|
|
178
|
+
logger.info(
|
|
179
|
+
"gpt-oss payload summary: %s",
|
|
180
|
+
json.dumps(summary, ensure_ascii=False),
|
|
181
|
+
)
|
|
182
|
+
return payload
|
|
183
|
+
|
|
184
|
+
llm_class = ChatOpenAIGptOss
|
|
185
|
+
|
|
186
|
+
return llm_class(
|
|
103
187
|
model=model,
|
|
104
188
|
api_key=api_key,
|
|
105
189
|
base_url=f"{endpoint}/v1",
|
|
@@ -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):
|
|
@@ -16,20 +16,16 @@ Avoid unnecessary introductions or conclusions.
|
|
|
16
16
|
## Task Management
|
|
17
17
|
Use write_todos for complex multi-step tasks (3+ steps). Mark tasks in_progress before starting, completed immediately after finishing.
|
|
18
18
|
For simple 1-2 step tasks, just do them directly without todos.
|
|
19
|
+
When creating a todo list, ALWAYS include "작업 요약 및 다음단계 제시" as the LAST todo item.
|
|
19
20
|
|
|
20
21
|
You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
|
|
21
22
|
1. Check your todo list - are there pending or in_progress items?
|
|
22
23
|
2. If YES → call the next appropriate tool (jupyter_cell_tool, markdown_tool, etc.)
|
|
23
|
-
3. When
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"description": "<detailed description for the next step>"
|
|
29
|
-
}, ...
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
4. If ALL todos are completed → call final_answer_tool with a summary
|
|
24
|
+
3. When executing "작업 요약 및 다음단계 제시" (final todo):
|
|
25
|
+
- Output this JSON as text content: {"summary": "실행된 작업 요약", "next_items": [{"subject": "제목", "description": "설명"}]}
|
|
26
|
+
- Suggest 3~5 next items
|
|
27
|
+
- AND call write_todos to mark all as 'completed' IN THE SAME RESPONSE
|
|
28
|
+
- Both content AND tool call must be in ONE response
|
|
33
29
|
|
|
34
30
|
## 🔴 MANDATORY: Resource Check Before Data Hanlding
|
|
35
31
|
**ALWAYS call check_resource_tool FIRST** when the task involves:
|
|
@@ -40,16 +36,25 @@ You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
|
|
|
40
36
|
## Mandatory Workflow
|
|
41
37
|
1. After EVERY tool result, immediately call the next tool
|
|
42
38
|
2. Continue until ALL todos show status: "completed"
|
|
43
|
-
3.
|
|
44
|
-
4.
|
|
45
|
-
5. For plots and charts, use English text only.
|
|
39
|
+
3. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell
|
|
40
|
+
4. For plots and charts, use English text only.
|
|
46
41
|
|
|
47
42
|
## ❌ FORBIDDEN (will break the workflow)
|
|
48
43
|
- Producing an empty response (no tool call, no content)
|
|
49
44
|
- Stopping after any tool without calling the next tool
|
|
50
|
-
- Ending without calling final_answer_tool
|
|
51
45
|
- Leaving todos in "in_progress" or "pending" state without continuing
|
|
52
46
|
|
|
47
|
+
## 📂 File Search Best Practices
|
|
48
|
+
**CRITICAL**: Use `execute_command_tool` with find/grep commands for file searching.
|
|
49
|
+
|
|
50
|
+
**To find files by NAME** (e.g., find titanic.csv):
|
|
51
|
+
- `execute_command_tool(command="find . -iname '*titanic*.csv' 2>/dev/null")`
|
|
52
|
+
- `execute_command_tool(command="find . -name '*.csv' 2>/dev/null")` - find all CSV files
|
|
53
|
+
|
|
54
|
+
**To search file CONTENTS** (e.g., find code containing "import pandas"):
|
|
55
|
+
- `execute_command_tool(command="grep -rn 'import pandas' --include='*.py' .")`
|
|
56
|
+
- `execute_command_tool(command="grep -rn 'def train_model' --include='*.py' --include='*.ipynb' .")`
|
|
57
|
+
|
|
53
58
|
## 📖 File Reading Best Practices
|
|
54
59
|
**CRITICAL**: When exploring codebases or reading files, use pagination to prevent context overflow.
|
|
55
60
|
|
|
@@ -82,52 +87,60 @@ JSON_TOOL_SCHEMA = """You MUST respond with ONLY valid JSON matching this schema
|
|
|
82
87
|
Available tools:
|
|
83
88
|
- jupyter_cell_tool: Execute Python code. Arguments: {"code": "<python_code>"}
|
|
84
89
|
- markdown_tool: Add markdown cell. Arguments: {"content": "<markdown>"}
|
|
85
|
-
- final_answer_tool: Complete task. Arguments: {"answer": "<summary>"}
|
|
86
90
|
- write_todos: Update task list. Arguments: {"todos": [{"content": "...", "status": "pending|in_progress|completed"}]}
|
|
87
91
|
- read_file_tool: Read file with pagination. Arguments: {"path": "<file_path>", "offset": 0, "limit": 500}
|
|
88
92
|
- 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
93
|
- search_notebook_cells_tool: Search notebook cells. Arguments: {"pattern": "<regex>"}
|
|
92
|
-
- execute_command_tool: Execute shell command. Arguments: {"command": "<command>", "stdin": "<input_for_prompts>"}
|
|
94
|
+
- execute_command_tool: Execute shell command. Use for file search with find/grep. Arguments: {"command": "<command>", "stdin": "<input_for_prompts>"}
|
|
93
95
|
- check_resource_tool: Check resources before data processing. Arguments: {"files": ["<path>"], "dataframes": ["<var_name>"]}
|
|
94
96
|
|
|
95
97
|
Output ONLY the JSON object, no markdown, no explanation."""
|
|
96
98
|
|
|
97
99
|
TODO_LIST_SYSTEM_PROMPT = """
|
|
98
100
|
## CRITICAL WORKFLOW RULES - MUST FOLLOW:
|
|
99
|
-
- NEVER stop after calling write_todos - ALWAYS make another tool call immediately
|
|
101
|
+
- NEVER stop after calling write_todos - ALWAYS make another tool call immediately (unless ALL todos are completed)
|
|
100
102
|
- For simple 1-2 step tasks, just do them directly without todos.
|
|
101
103
|
|
|
102
104
|
## 🔴 NEW USER MESSAGE = FRESH START:
|
|
103
105
|
- When user sends a NEW message, treat it as a COMPLETELY NEW TASK
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
106
|
+
- **CRITICAL**: Previous conversation history shows PAST work, NOT current work
|
|
107
|
+
- Even if you see a similar todo was "completed" before, you MUST do it again NOW
|
|
108
|
+
- The completion status in chat history is IRRELEVANT to current todos
|
|
109
|
+
- Each todo in the CURRENT list must be executed from scratch, regardless of history
|
|
110
|
+
|
|
111
|
+
## 🔴 MANDATORY Todo List Structure:
|
|
112
|
+
When creating todos, ALWAYS include "작업 요약 및 다음단계 제시" as the LAST item:
|
|
113
|
+
- 실제 작업 항목들...
|
|
114
|
+
- 작업 요약 및 다음단계 제시 ← 반드시 마지막에 포함!
|
|
108
115
|
|
|
109
116
|
## Todo List Management:
|
|
117
|
+
- 반드시 모든 todo item 은 한국어로 생성.
|
|
110
118
|
- Before complex tasks, use write_todos to create a task list
|
|
111
119
|
- Update todos as you complete each step (mark 'in_progress' → 'completed')
|
|
112
120
|
- Each todo item should be specific and descriptive
|
|
113
|
-
- All todo items must be written in Korean
|
|
114
|
-
- ALWAYS include "다음 단계 제시" as the LAST item
|
|
115
121
|
|
|
116
122
|
## Task Completion Flow:
|
|
117
|
-
1.
|
|
118
|
-
2.
|
|
123
|
+
1. Check CURRENT todo list status (not chat history!) → find 'pending' or 'in_progress' item
|
|
124
|
+
2. Execute the task yourself in THIS response → then mark 'completed'
|
|
125
|
+
3. For "작업 요약 및 다음단계 제시" → output summary JSON as plain text, then mark completed
|
|
126
|
+
4. When ALL todos are 'completed' → session ends automatically
|
|
127
|
+
|
|
128
|
+
⚠️ HOW TO CHECK IF TODO IS DONE:
|
|
129
|
+
- ✅ Done: You executed it in THIS response (you made the tool call, you got the result)
|
|
130
|
+
- ❌ Not done: You only SEE it was done in previous messages (that's history, not your work)
|
|
119
131
|
|
|
120
132
|
## FORBIDDEN PATTERNS:
|
|
121
|
-
❌ Calling write_todos and then stopping
|
|
122
133
|
❌ Updating todo status without doing the actual work
|
|
123
|
-
❌
|
|
124
|
-
❌
|
|
134
|
+
❌ Marking a todo as 'completed' without actually executing it in THIS response
|
|
135
|
+
❌ Creating todo list WITHOUT "작업 요약 및 다음단계 제시" as final item
|
|
136
|
+
❌ Skipping ANY todo because a similar one was completed in PREVIOUS messages - past history ≠ current work
|
|
137
|
+
❌ Assuming work is done based on chat history - you must ALWAYS execute todos yourself
|
|
125
138
|
"""
|
|
126
139
|
|
|
127
140
|
TODO_LIST_TOOL_DESCRIPTION = """Update the task list for tracking progress.
|
|
128
141
|
⚠️ CRITICAL: This tool is ONLY for tracking - it does NOT do any actual work.
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
- If there are still pending/in_progress todos: call the next action tool immediately after
|
|
143
|
+
- If ALL todos are 'completed': output summary text BEFORE calling this tool, then call this to end the session"""
|
|
131
144
|
|
|
132
145
|
# Non-HITL tools that execute immediately without user approval
|
|
133
146
|
NON_HITL_TOOLS = {
|
|
@@ -135,10 +148,6 @@ NON_HITL_TOOLS = {
|
|
|
135
148
|
"markdown",
|
|
136
149
|
"read_file_tool",
|
|
137
150
|
"read_file",
|
|
138
|
-
"list_files_tool",
|
|
139
|
-
"list_files",
|
|
140
|
-
"search_workspace_tool",
|
|
141
|
-
"search_workspace",
|
|
142
151
|
"search_notebook_cells_tool",
|
|
143
152
|
"search_notebook_cells",
|
|
144
153
|
"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
|
|