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
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Code Search Middleware
|
|
3
|
-
|
|
4
|
-
Automatically searches workspace and notebook cells for relevant code
|
|
5
|
-
before model calls. Helps the agent understand existing code context.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
import re
|
|
10
|
-
from typing import Any, Dict, List, Optional
|
|
11
|
-
|
|
12
|
-
from agent_server.langchain.executors.notebook_searcher import (
|
|
13
|
-
NotebookSearcher,
|
|
14
|
-
get_notebook_searcher,
|
|
15
|
-
)
|
|
16
|
-
from agent_server.langchain.state import AgentRuntime, AgentState, SearchResult
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class CodeSearchMiddleware:
|
|
22
|
-
"""
|
|
23
|
-
Middleware that searches for relevant code before model calls.
|
|
24
|
-
|
|
25
|
-
This middleware:
|
|
26
|
-
1. Extracts search terms from the user request
|
|
27
|
-
2. Searches workspace files and notebook cells
|
|
28
|
-
3. Injects relevant code context into the state
|
|
29
|
-
|
|
30
|
-
Uses @before_model hook pattern from LangChain middleware.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
notebook_searcher: Optional[NotebookSearcher] = None,
|
|
36
|
-
workspace_root: str = ".",
|
|
37
|
-
max_results: int = 10,
|
|
38
|
-
auto_search: bool = True,
|
|
39
|
-
search_patterns: Optional[List[str]] = None,
|
|
40
|
-
enabled: bool = True,
|
|
41
|
-
):
|
|
42
|
-
"""
|
|
43
|
-
Initialize code search middleware.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
notebook_searcher: NotebookSearcher instance
|
|
47
|
-
workspace_root: Root directory for searches
|
|
48
|
-
max_results: Maximum search results to include
|
|
49
|
-
auto_search: Automatically extract and search patterns
|
|
50
|
-
search_patterns: Additional patterns to always search
|
|
51
|
-
enabled: Whether the middleware is enabled
|
|
52
|
-
"""
|
|
53
|
-
self._searcher = notebook_searcher
|
|
54
|
-
self._workspace_root = workspace_root
|
|
55
|
-
self._max_results = max_results
|
|
56
|
-
self._auto_search = auto_search
|
|
57
|
-
self._search_patterns = search_patterns or []
|
|
58
|
-
self._enabled = enabled
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def name(self) -> str:
|
|
62
|
-
return "CodeSearchMiddleware"
|
|
63
|
-
|
|
64
|
-
def _get_searcher(self) -> NotebookSearcher:
|
|
65
|
-
"""Get or create notebook searcher"""
|
|
66
|
-
if self._searcher is None:
|
|
67
|
-
self._searcher = get_notebook_searcher(self._workspace_root)
|
|
68
|
-
return self._searcher
|
|
69
|
-
|
|
70
|
-
def _extract_search_terms(self, request: str) -> List[str]:
|
|
71
|
-
"""
|
|
72
|
-
Extract potential search terms from user request.
|
|
73
|
-
|
|
74
|
-
Looks for:
|
|
75
|
-
- Variable names (snake_case, camelCase)
|
|
76
|
-
- Function calls (func_name(), methodName())
|
|
77
|
-
- Class names (PascalCase)
|
|
78
|
-
- File references (*.py, *.ipynb)
|
|
79
|
-
- Quoted strings
|
|
80
|
-
"""
|
|
81
|
-
terms = set()
|
|
82
|
-
|
|
83
|
-
# Extract quoted strings
|
|
84
|
-
quoted = re.findall(r'["\']([^"\']+)["\']', request)
|
|
85
|
-
terms.update(quoted)
|
|
86
|
-
|
|
87
|
-
# Extract potential identifiers (excluding common words)
|
|
88
|
-
common_words = {
|
|
89
|
-
"the", "a", "an", "is", "are", "was", "were", "be", "been",
|
|
90
|
-
"have", "has", "had", "do", "does", "did", "will", "would",
|
|
91
|
-
"could", "should", "may", "might", "must", "shall", "can",
|
|
92
|
-
"for", "and", "or", "but", "in", "on", "at", "to", "from",
|
|
93
|
-
"with", "by", "about", "into", "through", "during", "before",
|
|
94
|
-
"after", "above", "below", "between", "under", "again",
|
|
95
|
-
"further", "then", "once", "here", "there", "when", "where",
|
|
96
|
-
"why", "how", "all", "each", "every", "both", "few", "more",
|
|
97
|
-
"most", "other", "some", "such", "no", "nor", "not", "only",
|
|
98
|
-
"own", "same", "so", "than", "too", "very", "just", "also",
|
|
99
|
-
"now", "please", "help", "want", "need", "make", "create",
|
|
100
|
-
"use", "using", "show", "display", "get", "set", "add",
|
|
101
|
-
"remove", "delete", "update", "change", "modify", "fix",
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
# Look for identifiers (snake_case, camelCase, PascalCase)
|
|
105
|
-
identifiers = re.findall(r'\b([a-zA-Z_][a-zA-Z0-9_]*)\b', request)
|
|
106
|
-
for ident in identifiers:
|
|
107
|
-
if ident.lower() not in common_words and len(ident) > 2:
|
|
108
|
-
terms.add(ident)
|
|
109
|
-
|
|
110
|
-
# Look for file patterns
|
|
111
|
-
file_patterns = re.findall(r'\b(\w+\.(?:py|ipynb|csv|json|txt))\b', request)
|
|
112
|
-
terms.update(file_patterns)
|
|
113
|
-
|
|
114
|
-
# Look for function/method calls
|
|
115
|
-
func_calls = re.findall(r'\b(\w+)\s*\(', request)
|
|
116
|
-
for func in func_calls:
|
|
117
|
-
if func.lower() not in common_words:
|
|
118
|
-
terms.add(func)
|
|
119
|
-
|
|
120
|
-
return list(terms)[:10] # Limit to top 10 terms
|
|
121
|
-
|
|
122
|
-
async def before_model(
|
|
123
|
-
self,
|
|
124
|
-
state: AgentState,
|
|
125
|
-
runtime: AgentRuntime,
|
|
126
|
-
) -> Optional[Dict[str, Any]]:
|
|
127
|
-
"""
|
|
128
|
-
Hook called before each model invocation.
|
|
129
|
-
|
|
130
|
-
Searches for relevant code and injects into state.
|
|
131
|
-
|
|
132
|
-
Args:
|
|
133
|
-
state: Current agent state
|
|
134
|
-
runtime: Agent runtime context
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
Updated state fields or None
|
|
138
|
-
"""
|
|
139
|
-
# Skip if middleware is disabled
|
|
140
|
-
if not self._enabled:
|
|
141
|
-
return None
|
|
142
|
-
|
|
143
|
-
# Skip if search results already present
|
|
144
|
-
if state.get("search_results"):
|
|
145
|
-
return None
|
|
146
|
-
|
|
147
|
-
user_request = state.get("user_request", "")
|
|
148
|
-
if not user_request:
|
|
149
|
-
return None
|
|
150
|
-
|
|
151
|
-
search_results: List[SearchResult] = []
|
|
152
|
-
searcher = self._get_searcher()
|
|
153
|
-
|
|
154
|
-
# Get current notebook path
|
|
155
|
-
notebook_context = state.get("notebook_context", {})
|
|
156
|
-
current_notebook = notebook_context.get("notebook_path", "")
|
|
157
|
-
|
|
158
|
-
# Auto-extract search terms
|
|
159
|
-
if self._auto_search:
|
|
160
|
-
terms = self._extract_search_terms(user_request)
|
|
161
|
-
terms.extend(self._search_patterns)
|
|
162
|
-
else:
|
|
163
|
-
terms = self._search_patterns
|
|
164
|
-
|
|
165
|
-
if not terms:
|
|
166
|
-
return None
|
|
167
|
-
|
|
168
|
-
logger.info(f"Searching for terms: {terms}")
|
|
169
|
-
|
|
170
|
-
# Search current notebook first
|
|
171
|
-
if current_notebook:
|
|
172
|
-
for term in terms[:5]: # Limit terms for current notebook
|
|
173
|
-
try:
|
|
174
|
-
results = searcher.search_notebook(
|
|
175
|
-
current_notebook,
|
|
176
|
-
term,
|
|
177
|
-
max_results=3,
|
|
178
|
-
)
|
|
179
|
-
for match in results.matches:
|
|
180
|
-
search_results.append(SearchResult(
|
|
181
|
-
file_path=match.file_path,
|
|
182
|
-
cell_index=match.cell_index,
|
|
183
|
-
line_number=match.line_number,
|
|
184
|
-
content=match.content,
|
|
185
|
-
match_type="cell",
|
|
186
|
-
))
|
|
187
|
-
except Exception as e:
|
|
188
|
-
logger.warning(f"Notebook search failed: {e}")
|
|
189
|
-
|
|
190
|
-
# Search workspace for remaining capacity
|
|
191
|
-
remaining = self._max_results - len(search_results)
|
|
192
|
-
if remaining > 0:
|
|
193
|
-
for term in terms[:3]: # Limit workspace searches
|
|
194
|
-
try:
|
|
195
|
-
results = searcher.search_workspace(
|
|
196
|
-
term,
|
|
197
|
-
max_results=remaining,
|
|
198
|
-
)
|
|
199
|
-
for match in results.matches:
|
|
200
|
-
# Avoid duplicates
|
|
201
|
-
if not any(
|
|
202
|
-
r["file_path"] == match.file_path and
|
|
203
|
-
r.get("line_number") == match.line_number
|
|
204
|
-
for r in search_results
|
|
205
|
-
):
|
|
206
|
-
search_results.append(SearchResult(
|
|
207
|
-
file_path=match.file_path,
|
|
208
|
-
cell_index=match.cell_index,
|
|
209
|
-
line_number=match.line_number,
|
|
210
|
-
content=match.content,
|
|
211
|
-
match_type=match.match_type,
|
|
212
|
-
))
|
|
213
|
-
except Exception as e:
|
|
214
|
-
logger.warning(f"Workspace search failed: {e}")
|
|
215
|
-
|
|
216
|
-
if search_results:
|
|
217
|
-
logger.info(f"Found {len(search_results)} relevant code snippets")
|
|
218
|
-
return {"search_results": search_results[:self._max_results]}
|
|
219
|
-
|
|
220
|
-
return None
|
|
221
|
-
|
|
222
|
-
def format_search_results_for_prompt(
|
|
223
|
-
self,
|
|
224
|
-
search_results: List[SearchResult],
|
|
225
|
-
) -> str:
|
|
226
|
-
"""
|
|
227
|
-
Format search results for inclusion in the prompt.
|
|
228
|
-
|
|
229
|
-
Args:
|
|
230
|
-
search_results: List of search results
|
|
231
|
-
|
|
232
|
-
Returns:
|
|
233
|
-
Formatted context string
|
|
234
|
-
"""
|
|
235
|
-
if not search_results:
|
|
236
|
-
return ""
|
|
237
|
-
|
|
238
|
-
lines = ["## Relevant Code from Workspace"]
|
|
239
|
-
|
|
240
|
-
for i, result in enumerate(search_results[:self._max_results], 1):
|
|
241
|
-
file_path = result.get("file_path", "unknown")
|
|
242
|
-
cell_idx = result.get("cell_index")
|
|
243
|
-
line_num = result.get("line_number")
|
|
244
|
-
content = result.get("content", "")
|
|
245
|
-
|
|
246
|
-
location = f"{file_path}"
|
|
247
|
-
if cell_idx is not None:
|
|
248
|
-
location += f" [Cell {cell_idx}]"
|
|
249
|
-
if line_num is not None:
|
|
250
|
-
location += f":L{line_num}"
|
|
251
|
-
|
|
252
|
-
lines.append(f"\n### {i}. {location}")
|
|
253
|
-
lines.append(f"```\n{content}\n```")
|
|
254
|
-
|
|
255
|
-
return "\n".join(lines)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def create_code_search_middleware(
|
|
259
|
-
workspace_root: str = ".",
|
|
260
|
-
max_results: int = 10,
|
|
261
|
-
auto_search: bool = True,
|
|
262
|
-
) -> CodeSearchMiddleware:
|
|
263
|
-
"""
|
|
264
|
-
Factory function to create code search middleware.
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
workspace_root: Root directory for searches
|
|
268
|
-
max_results: Maximum results to include
|
|
269
|
-
auto_search: Auto-extract search terms from request
|
|
270
|
-
|
|
271
|
-
Returns:
|
|
272
|
-
Configured CodeSearchMiddleware instance
|
|
273
|
-
"""
|
|
274
|
-
return CodeSearchMiddleware(
|
|
275
|
-
workspace_root=workspace_root,
|
|
276
|
-
max_results=max_results,
|
|
277
|
-
auto_search=auto_search,
|
|
278
|
-
)
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Error Handling Middleware
|
|
3
|
-
|
|
4
|
-
Classifies errors and decides on recovery strategies after tool execution.
|
|
5
|
-
Implements self-healing and adaptive replanning logic.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
from typing import Any, Dict, List, Optional
|
|
10
|
-
|
|
11
|
-
from agent_server.langchain.state import AgentRuntime, AgentState
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ErrorHandlingMiddleware:
|
|
17
|
-
"""
|
|
18
|
-
Middleware that handles errors after tool execution.
|
|
19
|
-
|
|
20
|
-
This middleware:
|
|
21
|
-
1. Classifies errors using ErrorClassifier
|
|
22
|
-
2. Decides on recovery strategy (refine, insert_steps, replan)
|
|
23
|
-
3. Updates state with recovery information
|
|
24
|
-
4. Tracks retry attempts
|
|
25
|
-
|
|
26
|
-
Uses @after_tool_call hook pattern from LangChain middleware.
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
error_classifier: Any = None,
|
|
32
|
-
max_retries: int = 3,
|
|
33
|
-
use_llm_fallback: bool = True,
|
|
34
|
-
enabled: bool = True,
|
|
35
|
-
):
|
|
36
|
-
"""
|
|
37
|
-
Initialize error handling middleware.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
error_classifier: ErrorClassifier instance
|
|
41
|
-
max_retries: Maximum retry attempts per error
|
|
42
|
-
use_llm_fallback: Use LLM for complex error analysis
|
|
43
|
-
enabled: Whether error handling is enabled
|
|
44
|
-
"""
|
|
45
|
-
self._classifier = error_classifier
|
|
46
|
-
self._max_retries = max_retries
|
|
47
|
-
self._use_llm_fallback = use_llm_fallback
|
|
48
|
-
self._enabled = enabled
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def name(self) -> str:
|
|
52
|
-
return "ErrorHandlingMiddleware"
|
|
53
|
-
|
|
54
|
-
def _get_classifier(self):
|
|
55
|
-
"""Lazy load error classifier"""
|
|
56
|
-
if self._classifier is None:
|
|
57
|
-
try:
|
|
58
|
-
from agent_server.core.error_classifier import get_error_classifier
|
|
59
|
-
self._classifier = get_error_classifier()
|
|
60
|
-
except ImportError:
|
|
61
|
-
logger.warning("ErrorClassifier not available")
|
|
62
|
-
return None
|
|
63
|
-
return self._classifier
|
|
64
|
-
|
|
65
|
-
def _classify_error(
|
|
66
|
-
self,
|
|
67
|
-
error_type: str,
|
|
68
|
-
error_message: str,
|
|
69
|
-
traceback: Optional[List[str]] = None,
|
|
70
|
-
previous_attempts: int = 0,
|
|
71
|
-
) -> Dict[str, Any]:
|
|
72
|
-
"""Classify error using ErrorClassifier"""
|
|
73
|
-
classifier = self._get_classifier()
|
|
74
|
-
|
|
75
|
-
if classifier is None:
|
|
76
|
-
# Fallback classification
|
|
77
|
-
return self._fallback_classify(error_type, error_message)
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
traceback_str = "\n".join(traceback) if traceback else ""
|
|
81
|
-
|
|
82
|
-
# Check if LLM fallback should be used
|
|
83
|
-
should_use_llm, reason = classifier.should_use_llm_fallback(
|
|
84
|
-
error_type=error_type,
|
|
85
|
-
traceback=traceback_str,
|
|
86
|
-
previous_attempts=previous_attempts,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# Classify using pattern matching
|
|
90
|
-
analysis = classifier.classify(
|
|
91
|
-
error_type=error_type,
|
|
92
|
-
error_message=error_message,
|
|
93
|
-
traceback=traceback_str,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
result = analysis.to_dict()
|
|
97
|
-
result["should_use_llm"] = should_use_llm
|
|
98
|
-
result["llm_reason"] = reason
|
|
99
|
-
|
|
100
|
-
return result
|
|
101
|
-
|
|
102
|
-
except Exception as e:
|
|
103
|
-
logger.error(f"Error classification failed: {e}")
|
|
104
|
-
return self._fallback_classify(error_type, error_message)
|
|
105
|
-
|
|
106
|
-
def _fallback_classify(
|
|
107
|
-
self,
|
|
108
|
-
error_type: str,
|
|
109
|
-
error_message: str,
|
|
110
|
-
) -> Dict[str, Any]:
|
|
111
|
-
"""Fallback error classification without ErrorClassifier"""
|
|
112
|
-
# Simple heuristic-based classification
|
|
113
|
-
|
|
114
|
-
# Module not found -> need to install
|
|
115
|
-
if error_type in ("ModuleNotFoundError", "ImportError"):
|
|
116
|
-
module_name = self._extract_module_name(error_message)
|
|
117
|
-
return {
|
|
118
|
-
"decision": "insert_steps",
|
|
119
|
-
"analysis": {
|
|
120
|
-
"root_cause": f"Missing module: {module_name}",
|
|
121
|
-
"is_approach_problem": False,
|
|
122
|
-
},
|
|
123
|
-
"reasoning": "Package installation required",
|
|
124
|
-
"changes": {
|
|
125
|
-
"new_steps": [{
|
|
126
|
-
"description": f"Install {module_name}",
|
|
127
|
-
"toolCalls": [{
|
|
128
|
-
"tool": "jupyter_cell",
|
|
129
|
-
"parameters": {
|
|
130
|
-
"code": f"!pip install {module_name}"
|
|
131
|
-
}
|
|
132
|
-
}]
|
|
133
|
-
}]
|
|
134
|
-
},
|
|
135
|
-
"confidence": 0.9,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# Syntax/Type/Value errors -> refine code
|
|
139
|
-
if error_type in ("SyntaxError", "TypeError", "ValueError", "KeyError", "IndexError"):
|
|
140
|
-
return {
|
|
141
|
-
"decision": "refine",
|
|
142
|
-
"analysis": {
|
|
143
|
-
"root_cause": f"{error_type}: {error_message}",
|
|
144
|
-
"is_approach_problem": False,
|
|
145
|
-
},
|
|
146
|
-
"reasoning": "Code can be fixed with refinement",
|
|
147
|
-
"changes": {},
|
|
148
|
-
"confidence": 0.8,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
# Name errors -> might be missing definition
|
|
152
|
-
if error_type == "NameError":
|
|
153
|
-
return {
|
|
154
|
-
"decision": "refine",
|
|
155
|
-
"analysis": {
|
|
156
|
-
"root_cause": f"Undefined variable: {error_message}",
|
|
157
|
-
"is_approach_problem": False,
|
|
158
|
-
},
|
|
159
|
-
"reasoning": "Variable not defined, need to fix code",
|
|
160
|
-
"changes": {},
|
|
161
|
-
"confidence": 0.7,
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
# Default -> try refinement first
|
|
165
|
-
return {
|
|
166
|
-
"decision": "refine",
|
|
167
|
-
"analysis": {
|
|
168
|
-
"root_cause": f"{error_type}: {error_message}",
|
|
169
|
-
"is_approach_problem": False,
|
|
170
|
-
},
|
|
171
|
-
"reasoning": "Attempting code refinement",
|
|
172
|
-
"changes": {},
|
|
173
|
-
"confidence": 0.5,
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
def _extract_module_name(self, error_message: str) -> str:
|
|
177
|
-
"""Extract module name from import error message"""
|
|
178
|
-
import re
|
|
179
|
-
|
|
180
|
-
# Match "No module named 'xxx'" or "No module named 'xxx.yyy'"
|
|
181
|
-
match = re.search(r"No module named ['\"]([^'\"]+)['\"]", error_message)
|
|
182
|
-
if match:
|
|
183
|
-
module = match.group(1).split(".")[0]
|
|
184
|
-
|
|
185
|
-
# Handle common aliases
|
|
186
|
-
aliases = {
|
|
187
|
-
"sklearn": "scikit-learn",
|
|
188
|
-
"cv2": "opencv-python",
|
|
189
|
-
"PIL": "pillow",
|
|
190
|
-
}
|
|
191
|
-
return aliases.get(module, module)
|
|
192
|
-
|
|
193
|
-
return "unknown-package"
|
|
194
|
-
|
|
195
|
-
async def after_tool_call(
|
|
196
|
-
self,
|
|
197
|
-
tool_name: str,
|
|
198
|
-
tool_input: Dict[str, Any],
|
|
199
|
-
tool_result: Dict[str, Any],
|
|
200
|
-
state: AgentState,
|
|
201
|
-
runtime: AgentRuntime,
|
|
202
|
-
) -> Optional[Dict[str, Any]]:
|
|
203
|
-
"""
|
|
204
|
-
Hook called after each tool execution.
|
|
205
|
-
|
|
206
|
-
Handles errors and updates recovery strategy.
|
|
207
|
-
|
|
208
|
-
Args:
|
|
209
|
-
tool_name: Name of the tool that was called
|
|
210
|
-
tool_input: Tool input parameters
|
|
211
|
-
tool_result: Result from tool execution
|
|
212
|
-
state: Current agent state
|
|
213
|
-
runtime: Agent runtime context
|
|
214
|
-
|
|
215
|
-
Returns:
|
|
216
|
-
Modified result or None
|
|
217
|
-
"""
|
|
218
|
-
if not self._enabled:
|
|
219
|
-
return None
|
|
220
|
-
|
|
221
|
-
# Only handle jupyter_cell errors
|
|
222
|
-
if tool_name != "jupyter_cell_tool":
|
|
223
|
-
return None
|
|
224
|
-
|
|
225
|
-
# Check if execution succeeded
|
|
226
|
-
if tool_result.get("success", True):
|
|
227
|
-
# Reset error count on success
|
|
228
|
-
state["error_count"] = 0
|
|
229
|
-
state["last_error"] = None
|
|
230
|
-
state["recovery_strategy"] = None
|
|
231
|
-
return None
|
|
232
|
-
|
|
233
|
-
# Handle execution error
|
|
234
|
-
error_type = tool_result.get("error_type", "UnknownError")
|
|
235
|
-
error_message = tool_result.get("error", "Unknown error")
|
|
236
|
-
traceback = tool_result.get("traceback", [])
|
|
237
|
-
|
|
238
|
-
error_count = state.get("error_count", 0)
|
|
239
|
-
|
|
240
|
-
logger.info(f"Handling error: {error_type} (attempt {error_count})")
|
|
241
|
-
|
|
242
|
-
# Classify error and get recovery strategy
|
|
243
|
-
classification = self._classify_error(
|
|
244
|
-
error_type=error_type,
|
|
245
|
-
error_message=error_message,
|
|
246
|
-
traceback=traceback,
|
|
247
|
-
previous_attempts=error_count,
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
# Check retry limit
|
|
251
|
-
if error_count >= self._max_retries:
|
|
252
|
-
classification["decision"] = "replan_remaining"
|
|
253
|
-
classification["reasoning"] = f"Max retries ({self._max_retries}) exceeded"
|
|
254
|
-
|
|
255
|
-
# Update state with recovery information
|
|
256
|
-
state["recovery_strategy"] = classification["decision"]
|
|
257
|
-
state["last_error"] = {
|
|
258
|
-
"error_type": error_type,
|
|
259
|
-
"error_message": error_message,
|
|
260
|
-
"traceback": traceback,
|
|
261
|
-
"classification": classification,
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
# Enrich tool result with classification
|
|
265
|
-
tool_result["error_classification"] = classification
|
|
266
|
-
tool_result["recovery_strategy"] = classification["decision"]
|
|
267
|
-
tool_result["recovery_changes"] = classification.get("changes", {})
|
|
268
|
-
|
|
269
|
-
logger.info(
|
|
270
|
-
f"Error classified: decision={classification['decision']}, "
|
|
271
|
-
f"confidence={classification.get('confidence', 0)}"
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
return tool_result
|
|
275
|
-
|
|
276
|
-
def format_error_feedback(
|
|
277
|
-
self,
|
|
278
|
-
last_error: Dict[str, Any],
|
|
279
|
-
) -> str:
|
|
280
|
-
"""
|
|
281
|
-
Format error information for feedback to the model.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
last_error: Last error information from state
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
Formatted feedback string
|
|
288
|
-
"""
|
|
289
|
-
if not last_error:
|
|
290
|
-
return ""
|
|
291
|
-
|
|
292
|
-
lines = ["## Execution Error"]
|
|
293
|
-
|
|
294
|
-
error_type = last_error.get("error_type", "UnknownError")
|
|
295
|
-
error_message = last_error.get("error_message", "")
|
|
296
|
-
|
|
297
|
-
lines.append(f"**Type**: {error_type}")
|
|
298
|
-
lines.append(f"**Message**: {error_message}")
|
|
299
|
-
|
|
300
|
-
classification = last_error.get("classification", {})
|
|
301
|
-
if classification:
|
|
302
|
-
decision = classification.get("decision", "unknown")
|
|
303
|
-
reasoning = classification.get("reasoning", "")
|
|
304
|
-
|
|
305
|
-
lines.append(f"\n**Recovery Strategy**: {decision}")
|
|
306
|
-
lines.append(f"**Reasoning**: {reasoning}")
|
|
307
|
-
|
|
308
|
-
changes = classification.get("changes", {})
|
|
309
|
-
if changes:
|
|
310
|
-
lines.append("\n**Suggested Changes**:")
|
|
311
|
-
if "new_steps" in changes:
|
|
312
|
-
for step in changes["new_steps"]:
|
|
313
|
-
lines.append(f"- {step.get('description', 'New step')}")
|
|
314
|
-
|
|
315
|
-
return "\n".join(lines)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def create_error_handling_middleware(
|
|
319
|
-
max_retries: int = 3,
|
|
320
|
-
use_llm_fallback: bool = True,
|
|
321
|
-
enabled: bool = True,
|
|
322
|
-
) -> ErrorHandlingMiddleware:
|
|
323
|
-
"""
|
|
324
|
-
Factory function to create error handling middleware.
|
|
325
|
-
|
|
326
|
-
Args:
|
|
327
|
-
max_retries: Maximum retry attempts
|
|
328
|
-
use_llm_fallback: Use LLM for complex errors
|
|
329
|
-
enabled: Whether to enable error handling
|
|
330
|
-
|
|
331
|
-
Returns:
|
|
332
|
-
Configured ErrorHandlingMiddleware instance
|
|
333
|
-
"""
|
|
334
|
-
return ErrorHandlingMiddleware(
|
|
335
|
-
max_retries=max_retries,
|
|
336
|
-
use_llm_fallback=use_llm_fallback,
|
|
337
|
-
enabled=enabled,
|
|
338
|
-
)
|