hdsp-jupyter-extension 2.0.5__py3-none-any.whl → 2.0.7__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/reflection_engine.py +0 -1
- agent_server/knowledge/watchdog_service.py +1 -1
- agent_server/langchain/ARCHITECTURE.md +1193 -0
- agent_server/langchain/agent.py +74 -551
- agent_server/langchain/custom_middleware.py +636 -0
- agent_server/langchain/executors/__init__.py +2 -7
- agent_server/langchain/executors/notebook_searcher.py +46 -38
- agent_server/langchain/hitl_config.py +66 -0
- agent_server/langchain/llm_factory.py +166 -0
- agent_server/langchain/logging_utils.py +184 -0
- agent_server/langchain/prompts.py +119 -0
- agent_server/langchain/state.py +16 -6
- agent_server/langchain/tools/__init__.py +6 -0
- agent_server/langchain/tools/file_tools.py +91 -129
- agent_server/langchain/tools/jupyter_tools.py +18 -18
- agent_server/langchain/tools/resource_tools.py +161 -0
- agent_server/langchain/tools/search_tools.py +198 -216
- agent_server/langchain/tools/shell_tools.py +54 -0
- agent_server/main.py +4 -1
- agent_server/routers/health.py +1 -1
- agent_server/routers/langchain_agent.py +941 -305
- hdsp_agent_core/prompts/auto_agent_prompts.py +3 -3
- {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8cc4873c413ed56ff485.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js +314 -8
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +1 -0
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js +1547 -330
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +1 -0
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.37299706f55c6d46099d.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js +8 -8
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +1 -0
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +209 -2
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +2 -209
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +3 -212
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
- {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/METADATA +2 -1
- {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/RECORD +71 -68
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +1176 -58
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +2 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.8cc4873c413ed56ff485.js → frontend_styles_index_js.4770ec0fb2d173b6deb4.js} +314 -8
- jupyter_ext/labextension/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.a223ea20056954479ae9.js → lib_index_js.29cf4312af19e86f82af.js} +1547 -330
- jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.37299706f55c6d46099d.js → remoteEntry.61343eb4cf0577e74b50.js} +8 -8
- jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.js.map +1 -0
- jupyter_ext/labextension/static/{vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js → vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js} +209 -2
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +2 -209
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
- jupyter_ext/labextension/static/{vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js → vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js} +3 -212
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
- jupyter_ext/resource_usage.py +180 -0
- jupyter_ext/tests/test_handlers.py +58 -0
- agent_server/langchain/executors/jupyter_executor.py +0 -429
- agent_server/langchain/middleware/__init__.py +0 -36
- agent_server/langchain/middleware/code_search_middleware.py +0 -278
- agent_server/langchain/middleware/error_handling_middleware.py +0 -338
- agent_server/langchain/middleware/jupyter_execution_middleware.py +0 -301
- agent_server/langchain/middleware/rag_middleware.py +0 -227
- agent_server/langchain/middleware/validation_middleware.py +0 -240
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8cc4873c413ed56ff485.js.map +0 -1
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.37299706f55c6d46099d.js.map +0 -1
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -1
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -1
- hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -1
- jupyter_ext/labextension/static/frontend_styles_index_js.8cc4873c413ed56ff485.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.37299706f55c6d46099d.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -1
- {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.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.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prompt templates for LangChain agent.
|
|
3
|
+
|
|
4
|
+
Contains system prompts, JSON schema for fallback tool calling,
|
|
5
|
+
and middleware-specific prompts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant.
|
|
9
|
+
Your role is to help users with data analysis, visualization, and Python coding tasks in Jupyter notebooks. You can use only Korean
|
|
10
|
+
|
|
11
|
+
## ⚠️ CRITICAL RULE: NEVER produce an empty response
|
|
12
|
+
|
|
13
|
+
You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
|
|
14
|
+
1. Check your todo list - are there pending or in_progress items?
|
|
15
|
+
2. If YES → call the next appropriate tool (jupyter_cell_tool, markdown_tool, etc.)
|
|
16
|
+
3. When you suggest next steps for todo item '다음 단계 제시', you MUST create next steps in json format matching this schema:
|
|
17
|
+
{
|
|
18
|
+
"next_items": [
|
|
19
|
+
{
|
|
20
|
+
"subject": "<subject for next step>",
|
|
21
|
+
"description": "<detailed description for the next step>"
|
|
22
|
+
}, ...
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
4. If ALL todos are completed → call final_answer_tool with a summary
|
|
26
|
+
|
|
27
|
+
NEVER end your turn without calling a tool. NEVER produce an empty response.
|
|
28
|
+
|
|
29
|
+
## 🔴 MANDATORY: Resource Check Before Data Hanlding
|
|
30
|
+
**ALWAYS call check_resource_tool FIRST** when the task involves:
|
|
31
|
+
- Loading files: .csv, .parquet, .json, .xlsx, .pickle, .h5, .feather
|
|
32
|
+
- Handling datasets(dataframe) with pandas, polars, dask, or similar libraries
|
|
33
|
+
- Training ML models on data files
|
|
34
|
+
|
|
35
|
+
## Mandatory Workflow
|
|
36
|
+
1. After EVERY tool result, immediately call the next tool
|
|
37
|
+
2. Continue until ALL todos show status: "completed"
|
|
38
|
+
3. ONLY THEN call final_answer_tool to summarize
|
|
39
|
+
4. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell
|
|
40
|
+
5. For plots and charts, use English text only.
|
|
41
|
+
|
|
42
|
+
## ❌ FORBIDDEN (will break the workflow)
|
|
43
|
+
- Producing an empty response (no tool call, no content)
|
|
44
|
+
- Stopping after any tool without calling the next tool
|
|
45
|
+
- Ending without calling final_answer_tool
|
|
46
|
+
- Leaving todos in "in_progress" or "pending" state without continuing
|
|
47
|
+
|
|
48
|
+
## 🚫 execute_command_tool Rules
|
|
49
|
+
**NEVER run long-running commands** with execute_command_tool (e.g., servers, daemons, watch processes).
|
|
50
|
+
- ✅ Allowed: Quick commands like `ls`, `cat`, `grep`, `git status`
|
|
51
|
+
- ❌ Forbidden: `jupyter lab`, `npm start`, `python app.py`, `watch`, background processes
|
|
52
|
+
- For long tasks: Use jupyter_cell_tool instead or inform the user to run manually
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
JSON_TOOL_SCHEMA = """You MUST respond with ONLY valid JSON matching this schema:
|
|
56
|
+
{
|
|
57
|
+
"tool": "<tool_name>",
|
|
58
|
+
"arguments": {"arg1": "value1", ...}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Available tools:
|
|
62
|
+
- jupyter_cell_tool: Execute Python code. Arguments: {"code": "<python_code>"}
|
|
63
|
+
- markdown_tool: Add markdown cell. Arguments: {"content": "<markdown>"}
|
|
64
|
+
- final_answer_tool: Complete task. Arguments: {"answer": "<summary>"}
|
|
65
|
+
- write_todos: Update task list. Arguments: {"todos": [{"content": "...", "status": "pending|in_progress|completed"}]}
|
|
66
|
+
- read_file_tool: Read file. Arguments: {"path": "<file_path>"}
|
|
67
|
+
- write_file_tool: Write file. Arguments: {"path": "<path>", "content": "<content>", "overwrite": false}
|
|
68
|
+
- list_files_tool: List directory. Arguments: {"path": ".", "recursive": false}
|
|
69
|
+
- search_workspace_tool: Search files. Arguments: {"pattern": "<regex>", "file_types": ["py"], "path": "."}
|
|
70
|
+
- search_notebook_cells_tool: Search notebook cells. Arguments: {"pattern": "<regex>"}
|
|
71
|
+
- execute_command_tool: Execute shell command. Arguments: {"command": "<command>", "stdin": "<input_for_prompts>"}
|
|
72
|
+
- check_resource_tool: Check resources before data processing. Arguments: {"files": ["<path>"], "dataframes": ["<var_name>"]}
|
|
73
|
+
|
|
74
|
+
Output ONLY the JSON object, no markdown, no explanation."""
|
|
75
|
+
|
|
76
|
+
TODO_LIST_SYSTEM_PROMPT = """
|
|
77
|
+
## CRITICAL WORKFLOW RULES - MUST FOLLOW:
|
|
78
|
+
1. NEVER stop after calling write_todos - ALWAYS make another tool call immediately
|
|
79
|
+
2. write_todos is ONLY for tracking progress - it does NOT complete any work
|
|
80
|
+
3. After EVERY write_todos call, you MUST call another tool (jupyter_cell_tool, markdown_tool, or final_answer_tool)
|
|
81
|
+
|
|
82
|
+
## Todo List Management:
|
|
83
|
+
- Before complex tasks, use write_todos to create a task list
|
|
84
|
+
- Update todos as you complete each step (mark 'in_progress' → 'completed')
|
|
85
|
+
- Each todo item should be specific and descriptive (30-60 characters)
|
|
86
|
+
- All todo items must be written in Korean
|
|
87
|
+
- ALWAYS include "다음 단계 제시" as the LAST item
|
|
88
|
+
|
|
89
|
+
## Task Completion Flow:
|
|
90
|
+
1. When current task is done → mark it 'completed' with write_todos
|
|
91
|
+
2. IMMEDIATELY call the next tool (jupyter_cell_tool for code, markdown_tool for text)
|
|
92
|
+
3. For "다음 단계 제시" → mark completed, then call final_answer_tool with suggestions
|
|
93
|
+
4. NEVER end your turn after write_todos - you MUST continue with actual work
|
|
94
|
+
|
|
95
|
+
## FORBIDDEN PATTERNS:
|
|
96
|
+
❌ Calling write_todos and then stopping
|
|
97
|
+
❌ Updating todo status without doing the actual work
|
|
98
|
+
❌ Ending turn without calling final_answer_tool when all tasks are done
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
TODO_LIST_TOOL_DESCRIPTION = """Update the task list for tracking progress.
|
|
102
|
+
⚠️ CRITICAL: This tool is ONLY for tracking - it does NOT do any actual work.
|
|
103
|
+
After calling this tool, you MUST IMMEDIATELY call another tool (jupyter_cell_tool, markdown_tool, or final_answer_tool).
|
|
104
|
+
NEVER end your response after calling write_todos - always continue with the next action tool."""
|
|
105
|
+
|
|
106
|
+
# Non-HITL tools that execute immediately without user approval
|
|
107
|
+
NON_HITL_TOOLS = {
|
|
108
|
+
"markdown_tool",
|
|
109
|
+
"markdown",
|
|
110
|
+
"read_file_tool",
|
|
111
|
+
"read_file",
|
|
112
|
+
"list_files_tool",
|
|
113
|
+
"list_files",
|
|
114
|
+
"search_workspace_tool",
|
|
115
|
+
"search_workspace",
|
|
116
|
+
"search_notebook_cells_tool",
|
|
117
|
+
"search_notebook_cells",
|
|
118
|
+
"write_todos",
|
|
119
|
+
}
|
agent_server/langchain/state.py
CHANGED
|
@@ -6,7 +6,7 @@ This state is passed through the agent execution and updated by middleware.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
-
from typing import Annotated, Any, Dict, List, Optional, TypedDict
|
|
9
|
+
from typing import Annotated, Any, Dict, List, Optional, TypedDict, Union
|
|
10
10
|
|
|
11
11
|
from langchain_core.messages import BaseMessage
|
|
12
12
|
from langgraph.graph.message import add_messages
|
|
@@ -14,6 +14,7 @@ from langgraph.graph.message import add_messages
|
|
|
14
14
|
|
|
15
15
|
class NotebookContext(TypedDict, total=False):
|
|
16
16
|
"""Current notebook context"""
|
|
17
|
+
|
|
17
18
|
notebook_path: str
|
|
18
19
|
cell_count: int
|
|
19
20
|
imported_libraries: List[str]
|
|
@@ -24,6 +25,7 @@ class NotebookContext(TypedDict, total=False):
|
|
|
24
25
|
|
|
25
26
|
class ExecutionResult(TypedDict, total=False):
|
|
26
27
|
"""Result of code execution"""
|
|
28
|
+
|
|
27
29
|
success: bool
|
|
28
30
|
output: str
|
|
29
31
|
error_type: Optional[str]
|
|
@@ -35,6 +37,7 @@ class ExecutionResult(TypedDict, total=False):
|
|
|
35
37
|
|
|
36
38
|
class SearchResult(TypedDict, total=False):
|
|
37
39
|
"""Result of code search"""
|
|
40
|
+
|
|
38
41
|
file_path: str
|
|
39
42
|
cell_index: Optional[int]
|
|
40
43
|
line_number: Optional[int]
|
|
@@ -51,6 +54,7 @@ class AgentState(TypedDict, total=False):
|
|
|
51
54
|
- Tool executions (execution results, search results)
|
|
52
55
|
- Agent decisions (current step, plan updates)
|
|
53
56
|
"""
|
|
57
|
+
|
|
54
58
|
# Message history - uses add_messages reducer to accumulate messages
|
|
55
59
|
messages: Annotated[List[BaseMessage], add_messages]
|
|
56
60
|
|
|
@@ -87,6 +91,9 @@ class AgentState(TypedDict, total=False):
|
|
|
87
91
|
# Detected libraries for knowledge injection
|
|
88
92
|
detected_libraries: List[str]
|
|
89
93
|
|
|
94
|
+
# Resource usage context (legacy, use check_resource_tool instead)
|
|
95
|
+
resource_context: Optional[Union[Dict[str, Any], str]]
|
|
96
|
+
|
|
90
97
|
# Final answer
|
|
91
98
|
final_answer: Optional[str]
|
|
92
99
|
is_complete: bool
|
|
@@ -96,9 +103,10 @@ class AgentState(TypedDict, total=False):
|
|
|
96
103
|
class AgentRuntime:
|
|
97
104
|
"""
|
|
98
105
|
Runtime context passed to middleware.
|
|
99
|
-
|
|
106
|
+
|
|
100
107
|
Contains references to executors and services needed by middleware.
|
|
101
108
|
"""
|
|
109
|
+
|
|
102
110
|
jupyter_executor: Any = None
|
|
103
111
|
notebook_searcher: Any = None
|
|
104
112
|
rag_manager: Any = None
|
|
@@ -107,7 +115,7 @@ class AgentRuntime:
|
|
|
107
115
|
workspace_root: str = "."
|
|
108
116
|
|
|
109
117
|
# Execution mode
|
|
110
|
-
embedded_mode: bool =
|
|
118
|
+
embedded_mode: bool = False
|
|
111
119
|
|
|
112
120
|
# Configuration
|
|
113
121
|
max_retries: int = 3
|
|
@@ -123,19 +131,20 @@ def create_initial_state(
|
|
|
123
131
|
) -> AgentState:
|
|
124
132
|
"""
|
|
125
133
|
Create initial agent state from user request.
|
|
126
|
-
|
|
134
|
+
|
|
127
135
|
Args:
|
|
128
136
|
user_request: Natural language request from user
|
|
129
137
|
notebook_context: Current notebook state
|
|
130
138
|
llm_config: LLM configuration
|
|
131
|
-
|
|
139
|
+
|
|
132
140
|
Returns:
|
|
133
141
|
Initialized AgentState
|
|
134
142
|
"""
|
|
135
143
|
return AgentState(
|
|
136
144
|
messages=[],
|
|
137
145
|
user_request=user_request,
|
|
138
|
-
notebook_context=notebook_context
|
|
146
|
+
notebook_context=notebook_context
|
|
147
|
+
or NotebookContext(
|
|
139
148
|
notebook_path="",
|
|
140
149
|
cell_count=0,
|
|
141
150
|
imported_libraries=[],
|
|
@@ -154,6 +163,7 @@ def create_initial_state(
|
|
|
154
163
|
recovery_strategy=None,
|
|
155
164
|
llm_config=llm_config or {},
|
|
156
165
|
detected_libraries=[],
|
|
166
|
+
resource_context=None,
|
|
157
167
|
final_answer=None,
|
|
158
168
|
is_complete=False,
|
|
159
169
|
)
|
|
@@ -10,6 +10,8 @@ Tools available:
|
|
|
10
10
|
- list_files: List directory contents
|
|
11
11
|
- search_workspace: Search files in workspace
|
|
12
12
|
- search_notebook_cells: Search cells in notebooks
|
|
13
|
+
- execute_command_tool: Run shell commands (client-executed)
|
|
14
|
+
- check_resource_tool: Check resources before data processing (client-executed)
|
|
13
15
|
"""
|
|
14
16
|
|
|
15
17
|
from agent_server.langchain.tools.file_tools import (
|
|
@@ -22,10 +24,12 @@ from agent_server.langchain.tools.jupyter_tools import (
|
|
|
22
24
|
jupyter_cell_tool,
|
|
23
25
|
markdown_tool,
|
|
24
26
|
)
|
|
27
|
+
from agent_server.langchain.tools.resource_tools import check_resource_tool
|
|
25
28
|
from agent_server.langchain.tools.search_tools import (
|
|
26
29
|
search_notebook_cells_tool,
|
|
27
30
|
search_workspace_tool,
|
|
28
31
|
)
|
|
32
|
+
from agent_server.langchain.tools.shell_tools import execute_command_tool
|
|
29
33
|
|
|
30
34
|
__all__ = [
|
|
31
35
|
"jupyter_cell_tool",
|
|
@@ -36,4 +40,6 @@ __all__ = [
|
|
|
36
40
|
"list_files_tool",
|
|
37
41
|
"search_workspace_tool",
|
|
38
42
|
"search_notebook_cells_tool",
|
|
43
|
+
"execute_command_tool",
|
|
44
|
+
"check_resource_tool",
|
|
39
45
|
]
|
|
@@ -8,7 +8,7 @@ Provides tools for file system operations:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import os
|
|
11
|
-
from typing import Any, Dict,
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
12
|
|
|
13
13
|
from langchain_core.tools import tool
|
|
14
14
|
from pydantic import BaseModel, Field
|
|
@@ -16,31 +16,54 @@ from pydantic import BaseModel, Field
|
|
|
16
16
|
|
|
17
17
|
class ReadFileInput(BaseModel):
|
|
18
18
|
"""Input schema for read_file tool"""
|
|
19
|
+
|
|
19
20
|
path: str = Field(description="Relative path to the file to read")
|
|
20
21
|
encoding: str = Field(default="utf-8", description="File encoding")
|
|
22
|
+
max_lines: Optional[int] = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="Maximum number of lines to read",
|
|
25
|
+
)
|
|
26
|
+
execution_result: Optional[Dict[str, Any]] = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Optional execution result payload from the client",
|
|
29
|
+
)
|
|
21
30
|
|
|
22
31
|
|
|
23
32
|
class WriteFileInput(BaseModel):
|
|
24
33
|
"""Input schema for write_file tool"""
|
|
34
|
+
|
|
25
35
|
path: str = Field(description="Relative path to the file to write")
|
|
26
36
|
content: str = Field(description="Content to write to the file")
|
|
27
37
|
encoding: str = Field(default="utf-8", description="File encoding")
|
|
38
|
+
overwrite: bool = Field(
|
|
39
|
+
default=False,
|
|
40
|
+
description="Whether to overwrite an existing file (default: false)",
|
|
41
|
+
)
|
|
42
|
+
execution_result: Optional[Dict[str, Any]] = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="Optional execution result payload from the client",
|
|
45
|
+
)
|
|
28
46
|
|
|
29
47
|
|
|
30
48
|
class ListFilesInput(BaseModel):
|
|
31
49
|
"""Input schema for list_files tool"""
|
|
50
|
+
|
|
32
51
|
path: str = Field(default=".", description="Directory path to list")
|
|
33
52
|
recursive: bool = Field(default=False, description="Whether to list recursively")
|
|
34
53
|
pattern: Optional[str] = Field(
|
|
35
54
|
default=None,
|
|
36
|
-
description="Glob pattern to filter files (e.g., '*.py', '*.ipynb')"
|
|
55
|
+
description="Glob pattern to filter files (e.g., '*.py', '*.ipynb')",
|
|
56
|
+
)
|
|
57
|
+
execution_result: Optional[Dict[str, Any]] = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="Optional execution result payload from the client",
|
|
37
60
|
)
|
|
38
61
|
|
|
39
62
|
|
|
40
63
|
def _validate_path(path: str, workspace_root: str = ".") -> str:
|
|
41
64
|
"""
|
|
42
65
|
Validate and resolve file path.
|
|
43
|
-
|
|
66
|
+
|
|
44
67
|
Security checks:
|
|
45
68
|
- No absolute paths allowed
|
|
46
69
|
- No parent directory traversal (..)
|
|
@@ -70,93 +93,88 @@ def _validate_path(path: str, workspace_root: str = ".") -> str:
|
|
|
70
93
|
def read_file_tool(
|
|
71
94
|
path: str,
|
|
72
95
|
encoding: str = "utf-8",
|
|
73
|
-
|
|
96
|
+
max_lines: Optional[int] = None,
|
|
97
|
+
execution_result: Optional[Dict[str, Any]] = None,
|
|
98
|
+
workspace_root: str = ".",
|
|
74
99
|
) -> Dict[str, Any]:
|
|
75
100
|
"""
|
|
76
101
|
Read content from a file.
|
|
77
|
-
|
|
102
|
+
|
|
78
103
|
Only relative paths within the workspace are allowed.
|
|
79
104
|
Absolute paths and parent directory traversal (..) are blocked.
|
|
80
|
-
|
|
105
|
+
|
|
81
106
|
Args:
|
|
82
107
|
path: Relative path to the file
|
|
83
108
|
encoding: File encoding (default: utf-8)
|
|
84
|
-
|
|
109
|
+
|
|
85
110
|
Returns:
|
|
86
111
|
Dict with file content or error
|
|
87
112
|
"""
|
|
88
|
-
|
|
89
|
-
resolved_path = _validate_path(path, workspace_root)
|
|
90
|
-
|
|
91
|
-
if not os.path.exists(resolved_path):
|
|
92
|
-
return {
|
|
93
|
-
"tool": "read_file",
|
|
94
|
-
"success": False,
|
|
95
|
-
"error": f"File not found: {path}",
|
|
96
|
-
"path": path,
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if not os.path.isfile(resolved_path):
|
|
100
|
-
return {
|
|
101
|
-
"tool": "read_file",
|
|
102
|
-
"success": False,
|
|
103
|
-
"error": f"Not a file: {path}",
|
|
104
|
-
"path": path,
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
with open(resolved_path, "r", encoding=encoding) as f:
|
|
108
|
-
content = f.read()
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
"tool": "read_file",
|
|
112
|
-
"success": True,
|
|
113
|
-
"path": path,
|
|
114
|
-
"content": content,
|
|
115
|
-
"size": len(content),
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
except ValueError as e:
|
|
113
|
+
if os.path.isabs(path):
|
|
119
114
|
return {
|
|
120
|
-
"tool": "
|
|
115
|
+
"tool": "read_file_tool",
|
|
121
116
|
"success": False,
|
|
122
|
-
"error":
|
|
117
|
+
"error": f"Absolute paths not allowed: {path}",
|
|
123
118
|
"path": path,
|
|
124
119
|
}
|
|
125
|
-
|
|
120
|
+
if ".." in path:
|
|
126
121
|
return {
|
|
127
|
-
"tool": "
|
|
122
|
+
"tool": "read_file_tool",
|
|
128
123
|
"success": False,
|
|
129
|
-
"error": f"
|
|
124
|
+
"error": f"Parent directory traversal not allowed: {path}",
|
|
130
125
|
"path": path,
|
|
131
126
|
}
|
|
132
127
|
|
|
128
|
+
response: Dict[str, Any] = {
|
|
129
|
+
"tool": "read_file_tool",
|
|
130
|
+
"parameters": {
|
|
131
|
+
"path": path,
|
|
132
|
+
"encoding": encoding,
|
|
133
|
+
"max_lines": max_lines,
|
|
134
|
+
},
|
|
135
|
+
"status": "pending_execution",
|
|
136
|
+
"message": "File read queued for execution by client",
|
|
137
|
+
}
|
|
138
|
+
if execution_result is not None:
|
|
139
|
+
response["execution_result"] = execution_result
|
|
140
|
+
response["status"] = "complete"
|
|
141
|
+
response["message"] = "File read executed with client-reported results"
|
|
142
|
+
return response
|
|
143
|
+
|
|
133
144
|
|
|
134
145
|
@tool(args_schema=WriteFileInput)
|
|
135
146
|
def write_file_tool(
|
|
136
147
|
path: str,
|
|
137
148
|
content: str,
|
|
138
149
|
encoding: str = "utf-8",
|
|
139
|
-
|
|
150
|
+
overwrite: bool = False,
|
|
151
|
+
execution_result: Optional[Dict[str, Any]] = None,
|
|
152
|
+
workspace_root: str = ".",
|
|
140
153
|
) -> Dict[str, Any]:
|
|
141
154
|
"""
|
|
142
155
|
Write content to a file.
|
|
143
|
-
|
|
156
|
+
|
|
144
157
|
This operation requires user approval before execution.
|
|
145
158
|
Only relative paths within the workspace are allowed.
|
|
146
|
-
|
|
159
|
+
|
|
147
160
|
Args:
|
|
148
161
|
path: Relative path to the file
|
|
149
162
|
content: Content to write
|
|
150
163
|
encoding: File encoding (default: utf-8)
|
|
151
|
-
|
|
164
|
+
|
|
152
165
|
Returns:
|
|
153
166
|
Dict with operation status (pending approval)
|
|
154
167
|
"""
|
|
155
168
|
try:
|
|
156
169
|
resolved_path = _validate_path(path, workspace_root)
|
|
157
170
|
|
|
158
|
-
|
|
159
|
-
"tool": "
|
|
171
|
+
response: Dict[str, Any] = {
|
|
172
|
+
"tool": "write_file_tool",
|
|
173
|
+
"parameters": {
|
|
174
|
+
"path": path,
|
|
175
|
+
"encoding": encoding,
|
|
176
|
+
"overwrite": overwrite,
|
|
177
|
+
},
|
|
160
178
|
"status": "pending_approval",
|
|
161
179
|
"path": path,
|
|
162
180
|
"resolved_path": resolved_path,
|
|
@@ -164,10 +182,15 @@ def write_file_tool(
|
|
|
164
182
|
"content_length": len(content),
|
|
165
183
|
"message": "File write operation requires user approval",
|
|
166
184
|
}
|
|
185
|
+
if execution_result is not None:
|
|
186
|
+
response["execution_result"] = execution_result
|
|
187
|
+
response["status"] = "complete"
|
|
188
|
+
response["message"] = "File write executed with client-reported results"
|
|
189
|
+
return response
|
|
167
190
|
|
|
168
191
|
except ValueError as e:
|
|
169
192
|
return {
|
|
170
|
-
"tool": "
|
|
193
|
+
"tool": "write_file_tool",
|
|
171
194
|
"success": False,
|
|
172
195
|
"error": str(e),
|
|
173
196
|
"path": path,
|
|
@@ -179,96 +202,35 @@ def list_files_tool(
|
|
|
179
202
|
path: str = ".",
|
|
180
203
|
recursive: bool = False,
|
|
181
204
|
pattern: Optional[str] = None,
|
|
182
|
-
|
|
205
|
+
execution_result: Optional[Dict[str, Any]] = None,
|
|
206
|
+
workspace_root: str = ".",
|
|
183
207
|
) -> Dict[str, Any]:
|
|
184
208
|
"""
|
|
185
209
|
List files and directories.
|
|
186
|
-
|
|
210
|
+
|
|
187
211
|
Args:
|
|
188
212
|
path: Directory path to list (default: current directory)
|
|
189
213
|
recursive: Whether to list recursively
|
|
190
214
|
pattern: Optional glob pattern to filter (e.g., '*.py')
|
|
191
|
-
|
|
215
|
+
|
|
192
216
|
Returns:
|
|
193
217
|
Dict with list of files and directories
|
|
194
218
|
"""
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
resolved_path = _validate_path(path, workspace_root)
|
|
199
|
-
|
|
200
|
-
if not os.path.exists(resolved_path):
|
|
201
|
-
return {
|
|
202
|
-
"tool": "list_files",
|
|
203
|
-
"success": False,
|
|
204
|
-
"error": f"Directory not found: {path}",
|
|
205
|
-
"path": path,
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if not os.path.isdir(resolved_path):
|
|
209
|
-
return {
|
|
210
|
-
"tool": "list_files",
|
|
211
|
-
"success": False,
|
|
212
|
-
"error": f"Not a directory: {path}",
|
|
213
|
-
"path": path,
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
files: List[Dict[str, Any]] = []
|
|
217
|
-
dirs: List[str] = []
|
|
218
|
-
|
|
219
|
-
if recursive:
|
|
220
|
-
for root, dirnames, filenames in os.walk(resolved_path):
|
|
221
|
-
rel_root = os.path.relpath(root, resolved_path)
|
|
222
|
-
for dirname in dirnames:
|
|
223
|
-
dir_path = os.path.join(rel_root, dirname) if rel_root != "." else dirname
|
|
224
|
-
dirs.append(dir_path)
|
|
225
|
-
for filename in filenames:
|
|
226
|
-
if pattern and not fnmatch.fnmatch(filename, pattern):
|
|
227
|
-
continue
|
|
228
|
-
file_path = os.path.join(rel_root, filename) if rel_root != "." else filename
|
|
229
|
-
full_path = os.path.join(root, filename)
|
|
230
|
-
files.append({
|
|
231
|
-
"name": filename,
|
|
232
|
-
"path": file_path,
|
|
233
|
-
"size": os.path.getsize(full_path),
|
|
234
|
-
})
|
|
235
|
-
else:
|
|
236
|
-
for entry in os.scandir(resolved_path):
|
|
237
|
-
if entry.is_dir():
|
|
238
|
-
dirs.append(entry.name)
|
|
239
|
-
elif entry.is_file():
|
|
240
|
-
if pattern and not fnmatch.fnmatch(entry.name, pattern):
|
|
241
|
-
continue
|
|
242
|
-
files.append({
|
|
243
|
-
"name": entry.name,
|
|
244
|
-
"path": entry.name,
|
|
245
|
-
"size": entry.stat().st_size,
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
"tool": "list_files",
|
|
250
|
-
"success": True,
|
|
219
|
+
response: Dict[str, Any] = {
|
|
220
|
+
"tool": "list_files_tool",
|
|
221
|
+
"parameters": {
|
|
251
222
|
"path": path,
|
|
252
|
-
"
|
|
253
|
-
"
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
"path": path,
|
|
264
|
-
}
|
|
265
|
-
except Exception as e:
|
|
266
|
-
return {
|
|
267
|
-
"tool": "list_files",
|
|
268
|
-
"success": False,
|
|
269
|
-
"error": f"Failed to list directory: {str(e)}",
|
|
270
|
-
"path": path,
|
|
271
|
-
}
|
|
223
|
+
"recursive": recursive,
|
|
224
|
+
"pattern": pattern,
|
|
225
|
+
},
|
|
226
|
+
"status": "pending_execution",
|
|
227
|
+
"message": "File listing queued for execution by client",
|
|
228
|
+
}
|
|
229
|
+
if execution_result is not None:
|
|
230
|
+
response["execution_result"] = execution_result
|
|
231
|
+
response["status"] = "complete"
|
|
232
|
+
response["message"] = "File listing executed with client-reported results"
|
|
233
|
+
return response
|
|
272
234
|
|
|
273
235
|
|
|
274
236
|
# Export all tools
|