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.
Files changed (90) hide show
  1. agent_server/core/reflection_engine.py +0 -1
  2. agent_server/knowledge/watchdog_service.py +1 -1
  3. agent_server/langchain/ARCHITECTURE.md +1193 -0
  4. agent_server/langchain/agent.py +74 -551
  5. agent_server/langchain/custom_middleware.py +636 -0
  6. agent_server/langchain/executors/__init__.py +2 -7
  7. agent_server/langchain/executors/notebook_searcher.py +46 -38
  8. agent_server/langchain/hitl_config.py +66 -0
  9. agent_server/langchain/llm_factory.py +166 -0
  10. agent_server/langchain/logging_utils.py +184 -0
  11. agent_server/langchain/prompts.py +119 -0
  12. agent_server/langchain/state.py +16 -6
  13. agent_server/langchain/tools/__init__.py +6 -0
  14. agent_server/langchain/tools/file_tools.py +91 -129
  15. agent_server/langchain/tools/jupyter_tools.py +18 -18
  16. agent_server/langchain/tools/resource_tools.py +161 -0
  17. agent_server/langchain/tools/search_tools.py +198 -216
  18. agent_server/langchain/tools/shell_tools.py +54 -0
  19. agent_server/main.py +4 -1
  20. agent_server/routers/health.py +1 -1
  21. agent_server/routers/langchain_agent.py +941 -305
  22. hdsp_agent_core/prompts/auto_agent_prompts.py +3 -3
  23. {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
  24. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  25. 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
  26. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +1 -0
  27. 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
  28. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +1 -0
  29. 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
  30. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +1 -0
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/METADATA +2 -1
  38. {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/RECORD +71 -68
  39. jupyter_ext/_version.py +1 -1
  40. jupyter_ext/handlers.py +1176 -58
  41. jupyter_ext/labextension/build_log.json +1 -1
  42. jupyter_ext/labextension/package.json +2 -2
  43. jupyter_ext/labextension/static/{frontend_styles_index_js.8cc4873c413ed56ff485.js → frontend_styles_index_js.4770ec0fb2d173b6deb4.js} +314 -8
  44. jupyter_ext/labextension/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +1 -0
  45. jupyter_ext/labextension/static/{lib_index_js.a223ea20056954479ae9.js → lib_index_js.29cf4312af19e86f82af.js} +1547 -330
  46. jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +1 -0
  47. jupyter_ext/labextension/static/{remoteEntry.37299706f55c6d46099d.js → remoteEntry.61343eb4cf0577e74b50.js} +8 -8
  48. jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.js.map +1 -0
  49. 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
  50. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  51. 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
  52. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  53. 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
  54. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
  55. jupyter_ext/resource_usage.py +180 -0
  56. jupyter_ext/tests/test_handlers.py +58 -0
  57. agent_server/langchain/executors/jupyter_executor.py +0 -429
  58. agent_server/langchain/middleware/__init__.py +0 -36
  59. agent_server/langchain/middleware/code_search_middleware.py +0 -278
  60. agent_server/langchain/middleware/error_handling_middleware.py +0 -338
  61. agent_server/langchain/middleware/jupyter_execution_middleware.py +0 -301
  62. agent_server/langchain/middleware/rag_middleware.py +0 -227
  63. agent_server/langchain/middleware/validation_middleware.py +0 -240
  64. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8cc4873c413ed56ff485.js.map +0 -1
  65. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
  66. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.37299706f55c6d46099d.js.map +0 -1
  67. 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
  68. 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
  69. 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
  70. jupyter_ext/labextension/static/frontend_styles_index_js.8cc4873c413ed56ff485.js.map +0 -1
  71. jupyter_ext/labextension/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
  72. jupyter_ext/labextension/static/remoteEntry.37299706f55c6d46099d.js.map +0 -1
  73. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -1
  74. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -1
  75. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -1
  76. {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
  77. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/WHEEL +0 -0
  90. {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
- )