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.
Files changed (63) hide show
  1. agent_server/core/rag_manager.py +12 -3
  2. agent_server/core/retriever.py +2 -1
  3. agent_server/core/vllm_embedding_service.py +8 -5
  4. agent_server/langchain/ARCHITECTURE.md +7 -51
  5. agent_server/langchain/agent.py +31 -20
  6. agent_server/langchain/custom_middleware.py +148 -31
  7. agent_server/langchain/hitl_config.py +0 -8
  8. agent_server/langchain/llm_factory.py +85 -1
  9. agent_server/langchain/logging_utils.py +7 -7
  10. agent_server/langchain/prompts.py +45 -36
  11. agent_server/langchain/tools/__init__.py +1 -10
  12. agent_server/langchain/tools/file_tools.py +9 -61
  13. agent_server/langchain/tools/jupyter_tools.py +0 -1
  14. agent_server/langchain/tools/lsp_tools.py +8 -8
  15. agent_server/langchain/tools/resource_tools.py +12 -12
  16. agent_server/langchain/tools/search_tools.py +3 -158
  17. agent_server/routers/langchain_agent.py +122 -113
  18. {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
  19. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  20. 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
  21. hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  22. 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
  23. hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.dc6434bee96ab03a0539.js.map +1 -0
  24. 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
  25. hdsp_jupyter_extension-2.0.10.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.4a252df3ade74efee8d6.js.map +1 -0
  26. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/METADATA +1 -3
  27. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/RECORD +57 -57
  28. jupyter_ext/_version.py +1 -1
  29. jupyter_ext/labextension/build_log.json +1 -1
  30. jupyter_ext/labextension/package.json +2 -2
  31. jupyter_ext/labextension/static/{frontend_styles_index_js.8740a527757068814573.js → frontend_styles_index_js.2d9fb488c82498c45c2d.js} +93 -4
  32. jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
  33. jupyter_ext/labextension/static/{lib_index_js.e4ff4b5779b5e049f84c.js → lib_index_js.dc6434bee96ab03a0539.js} +90 -71
  34. jupyter_ext/labextension/static/lib_index_js.dc6434bee96ab03a0539.js.map +1 -0
  35. jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.4a252df3ade74efee8d6.js} +6 -6
  36. jupyter_ext/labextension/static/remoteEntry.4a252df3ade74efee8d6.js.map +1 -0
  37. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  38. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  39. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  40. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
  41. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
  42. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
  43. {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
  44. {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.10.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  45. {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
  46. {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
  47. {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
  48. {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
  49. {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
  50. {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
  51. {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
  52. {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
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {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
  60. {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
  61. {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
  62. {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.10.dist-info}/WHEEL +0 -0
  63. {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
- return ChatOpenAI(
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 you suggest next steps for todo item '다음 단계 제시', you MUST create next steps in json format matching this schema:
24
- {
25
- "next_items": [
26
- {
27
- "subject": "<subject for next step>",
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. ONLY THEN call final_answer_tool to summarize
44
- 4. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell
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
- - IGNORE any previous todo completion history - start fresh
105
- - Do NOT assume any work was already done based on past conversations
106
- - Create a NEW todo list for the new request, even if similar items existed before
107
- - "다음 단계 제시" from a previous task is NOT completed for the new task
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. When current task is donemark it 'completed' with write_todos
118
- 2. For "다음 단계 제시" mark completed, then call final_answer_tool with suggestions
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
- Ending turn without calling final_answer_tool when all tasks are done
124
- Marking a todo as 'completed' without actually executing it in THIS conversation
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
- After calling this tool, you MUST IMMEDIATELY call another tool (jupyter_cell_tool, markdown_tool, or final_answer_tool).
130
- NEVER end your response after calling write_todos - always continue with the next action tool."""
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
- "index": i,
461
- "old_preview": old_preview,
462
- "new_preview": new_preview,
463
- "replace_all": replace_all_val
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
  ]
@@ -139,5 +139,4 @@ def final_answer_tool(answer: str, summary: Optional[str] = None) -> Dict[str, A
139
139
  JUPYTER_TOOLS = [
140
140
  jupyter_cell_tool,
141
141
  markdown_tool,
142
- final_answer_tool,
143
142
  ]
@@ -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 search_workspace_tool for text-based code search instead.",
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 += f" (showing first 10)"
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 search_workspace_tool.
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 search_workspace_tool with pattern='{symbol}' for text-based search.",
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 = [f"Found {len(locations)} references to '{symbol}'{method_note}:\n"]
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