hdsp-jupyter-extension 2.0.7__py3-none-any.whl → 2.0.8__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 (78) hide show
  1. agent_server/core/embedding_service.py +67 -46
  2. agent_server/core/rag_manager.py +31 -17
  3. agent_server/core/retriever.py +13 -8
  4. agent_server/core/vllm_embedding_service.py +243 -0
  5. agent_server/langchain/agent.py +8 -0
  6. agent_server/langchain/custom_middleware.py +58 -31
  7. agent_server/langchain/hitl_config.py +6 -1
  8. agent_server/langchain/logging_utils.py +53 -14
  9. agent_server/langchain/prompts.py +47 -16
  10. agent_server/langchain/tools/__init__.py +13 -0
  11. agent_server/langchain/tools/file_tools.py +285 -7
  12. agent_server/langchain/tools/file_utils.py +334 -0
  13. agent_server/langchain/tools/lsp_tools.py +264 -0
  14. agent_server/main.py +7 -0
  15. agent_server/routers/langchain_agent.py +115 -19
  16. agent_server/routers/rag.py +8 -3
  17. hdsp_agent_core/models/rag.py +15 -1
  18. hdsp_agent_core/services/rag_service.py +6 -1
  19. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  20. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
  21. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js +160 -3
  22. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  23. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js +1759 -221
  24. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  25. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js +14 -12
  26. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  27. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  28. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  29. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  30. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  31. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
  32. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  33. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +1 -1
  34. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +66 -63
  35. jupyter_ext/__init__.py +18 -0
  36. jupyter_ext/_version.py +1 -1
  37. jupyter_ext/handlers.py +176 -1
  38. jupyter_ext/labextension/build_log.json +1 -1
  39. jupyter_ext/labextension/package.json +3 -2
  40. jupyter_ext/labextension/static/{frontend_styles_index_js.4770ec0fb2d173b6deb4.js → frontend_styles_index_js.8740a527757068814573.js} +160 -3
  41. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  42. jupyter_ext/labextension/static/{lib_index_js.29cf4312af19e86f82af.js → lib_index_js.e4ff4b5779b5e049f84c.js} +1759 -221
  43. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  44. jupyter_ext/labextension/static/{remoteEntry.61343eb4cf0577e74b50.js → remoteEntry.020cdb0b864cfaa4e41e.js} +14 -12
  45. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  46. 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 → jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  47. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  48. 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 → jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  49. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  50. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
  51. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  52. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
  53. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
  54. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
  55. 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 +0 -1
  56. 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 +0 -1
  57. 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 +0 -1
  58. jupyter_ext/labextension/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
  59. jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
  60. jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
  61. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
  62. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
  63. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
  64. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  65. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  66. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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
  67. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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
  68. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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
  69. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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
  70. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  71. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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
  72. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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
  73. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  74. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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
  75. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  76. {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  77. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
  78. {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,264 @@
1
+ """
2
+ LSP Tools for LangChain Agent
3
+
4
+ Provides tools for LSP (Language Server Protocol) integration:
5
+ - diagnostics_tool: Get code diagnostics (errors, warnings)
6
+ - references_tool: Find symbol references
7
+
8
+ Crush 패턴 적용:
9
+ - 진단 결과 포맷팅 (severity 기반 정렬)
10
+ - 출력 제한 (최대 10개 + 요약)
11
+ - Grep-then-LSP 패턴 (references)
12
+ """
13
+
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from langchain_core.tools import tool
17
+ from pydantic import BaseModel, Field
18
+
19
+
20
+ class DiagnosticsInput(BaseModel):
21
+ """Input schema for diagnostics tool"""
22
+
23
+ path: Optional[str] = Field(
24
+ default=None,
25
+ description="File path to get diagnostics for. If not provided, returns project-wide diagnostics.",
26
+ )
27
+ severity_filter: Optional[str] = Field(
28
+ default=None,
29
+ description="Filter by severity: 'error', 'warning', 'hint', or None for all",
30
+ )
31
+ execution_result: Optional[Dict[str, Any]] = Field(
32
+ default=None,
33
+ description="LSP diagnostics result from client",
34
+ )
35
+
36
+
37
+ class ReferencesInput(BaseModel):
38
+ """Input schema for references tool"""
39
+
40
+ symbol: str = Field(description="Symbol name to find references for")
41
+ path: Optional[str] = Field(
42
+ default=None,
43
+ description="File path where the symbol is located (optional)",
44
+ )
45
+ line: Optional[int] = Field(
46
+ default=None, description="Line number (1-indexed, optional)"
47
+ )
48
+ character: Optional[int] = Field(
49
+ default=None, description="Character position (optional)"
50
+ )
51
+ execution_result: Optional[Dict[str, Any]] = Field(
52
+ default=None,
53
+ description="LSP references result from client",
54
+ )
55
+
56
+
57
+ @tool(args_schema=DiagnosticsInput)
58
+ def diagnostics_tool(
59
+ path: Optional[str] = None,
60
+ severity_filter: Optional[str] = None,
61
+ execution_result: Optional[Dict[str, Any]] = None,
62
+ ) -> Dict[str, Any]:
63
+ """
64
+ Get LSP diagnostics (errors, warnings) for a file or the entire project.
65
+
66
+ Use this tool to:
67
+ - Check for syntax errors before running code
68
+ - Find type errors in Python/TypeScript files
69
+ - Identify unused imports or variables
70
+ - Verify code quality issues after editing
71
+
72
+ The diagnostics are provided by language servers (pylsp, etc.)
73
+ and are more accurate than simple linting.
74
+
75
+ **Best Practice**: Always check diagnostics after editing code:
76
+ 1. edit_file_tool(...) - make changes
77
+ 2. diagnostics_tool(path="file.py") - verify no new errors
78
+
79
+ Args:
80
+ path: Optional file path. None = project-wide diagnostics
81
+ severity_filter: Optional filter ('error', 'warning', 'hint')
82
+
83
+ Returns:
84
+ Formatted diagnostics with severity, location, and message
85
+ """
86
+ if execution_result is None:
87
+ # Client needs to execute this
88
+ return {
89
+ "tool": "diagnostics_tool",
90
+ "parameters": {
91
+ "path": path,
92
+ "severity_filter": severity_filter,
93
+ },
94
+ "status": "pending_execution",
95
+ "message": "Diagnostics request queued for LSP bridge execution",
96
+ }
97
+
98
+ # Process client result (Crush 패턴)
99
+ diagnostics = execution_result.get("diagnostics", [])
100
+ lsp_available = execution_result.get("lsp_available", False)
101
+
102
+ if not lsp_available:
103
+ return {
104
+ "tool": "diagnostics_tool",
105
+ "success": True,
106
+ "output": "LSP not available. Install jupyterlab-lsp for code diagnostics.\nUse search_workspace_tool for text-based code search instead.",
107
+ "counts": {"errors": 0, "warnings": 0, "total": 0},
108
+ }
109
+
110
+ # Severity ordering (errors first)
111
+ severity_order = {"error": 0, "warning": 1, "information": 2, "hint": 3}
112
+
113
+ # Sort diagnostics
114
+ sorted_diags = sorted(
115
+ diagnostics,
116
+ key=lambda d: (
117
+ severity_order.get(d.get("severity", "hint"), 3),
118
+ d.get("file", ""),
119
+ d.get("line", 0),
120
+ ),
121
+ )
122
+
123
+ # Filter by severity if specified
124
+ if severity_filter:
125
+ sorted_diags = [
126
+ d for d in sorted_diags if d.get("severity") == severity_filter
127
+ ]
128
+
129
+ # Format output (Crush의 formatDiagnostics 패턴)
130
+ formatted_lines = []
131
+ for d in sorted_diags[:10]: # 최대 10개
132
+ severity = d.get("severity", "hint").upper()
133
+ line = d.get("line", 0)
134
+ col = d.get("character", 0)
135
+ source = d.get("source", "")
136
+ code = d.get("code", "")
137
+ message = d.get("message", "")
138
+ file = d.get("file", path or "")
139
+
140
+ location = f"{file}:{line}:{col}" if file else f"L{line}:{col}"
141
+ source_info = f"[{source}]" if source else ""
142
+ code_info = f"[{code}]" if code else ""
143
+
144
+ formatted_lines.append(
145
+ f"{severity} {location} {source_info}{code_info} {message}"
146
+ )
147
+
148
+ # Calculate counts
149
+ total = len(diagnostics)
150
+ errors = sum(1 for d in diagnostics if d.get("severity") == "error")
151
+ warnings = sum(1 for d in diagnostics if d.get("severity") == "warning")
152
+
153
+ # Add summary
154
+ summary = f"\n--- Summary: {errors} errors, {warnings} warnings, {total} total"
155
+ if total > 10:
156
+ summary += f" (showing first 10)"
157
+
158
+ output = (
159
+ "\n".join(formatted_lines) + summary
160
+ if formatted_lines
161
+ else f"No diagnostics found.{' LSP is available.' if lsp_available else ''}"
162
+ )
163
+
164
+ return {
165
+ "tool": "diagnostics_tool",
166
+ "success": True,
167
+ "output": output,
168
+ "counts": {"errors": errors, "warnings": warnings, "total": total},
169
+ }
170
+
171
+
172
+ @tool(args_schema=ReferencesInput)
173
+ def references_tool(
174
+ symbol: str,
175
+ path: Optional[str] = None,
176
+ line: Optional[int] = None,
177
+ character: Optional[int] = None,
178
+ execution_result: Optional[Dict[str, Any]] = None,
179
+ ) -> Dict[str, Any]:
180
+ """
181
+ Find all references to a symbol across the codebase.
182
+
183
+ Use this tool to:
184
+ - Check if a function/class is used before renaming/deleting
185
+ - Understand how a variable is used throughout the code
186
+ - Find all usages before refactoring
187
+
188
+ If LSP is not available, falls back to search_workspace_tool.
189
+
190
+ Args:
191
+ symbol: Symbol name (function, class, variable)
192
+ path: Optional file path where symbol is located
193
+ line: Optional line number (1-indexed)
194
+ character: Optional character position
195
+
196
+ Returns:
197
+ List of locations where the symbol is referenced
198
+ """
199
+ if execution_result is None:
200
+ return {
201
+ "tool": "references_tool",
202
+ "parameters": {
203
+ "symbol": symbol,
204
+ "path": path,
205
+ "line": line,
206
+ "character": character,
207
+ },
208
+ "status": "pending_execution",
209
+ "message": "References search queued for execution",
210
+ }
211
+
212
+ locations = execution_result.get("locations", [])
213
+ lsp_available = execution_result.get("lsp_available", False)
214
+ used_grep = execution_result.get("used_grep", False)
215
+
216
+ if not locations:
217
+ if not lsp_available:
218
+ return {
219
+ "tool": "references_tool",
220
+ "success": True,
221
+ "output": f"LSP not available. Use search_workspace_tool with pattern='{symbol}' for text-based search.",
222
+ "count": 0,
223
+ }
224
+ return {
225
+ "tool": "references_tool",
226
+ "success": True,
227
+ "output": f"No references found for '{symbol}'",
228
+ "count": 0,
229
+ }
230
+
231
+ # Group by file (Crush 패턴)
232
+ by_file: Dict[str, List] = {}
233
+ for loc in locations:
234
+ file = loc.get("file", "unknown")
235
+ if file not in by_file:
236
+ by_file[file] = []
237
+ by_file[file].append(loc)
238
+
239
+ # Format output
240
+ method_note = " (grep-based)" if used_grep else " (LSP)"
241
+ formatted_lines = [f"Found {len(locations)} references to '{symbol}'{method_note}:\n"]
242
+
243
+ for file, locs in sorted(by_file.items()):
244
+ formatted_lines.append(f"\n📄 {file}")
245
+ for loc in sorted(locs, key=lambda x: x.get("line", 0)):
246
+ line_num = loc.get("line", 0)
247
+ col = loc.get("character", 0)
248
+ preview = (loc.get("preview", "") or "")[:60]
249
+ formatted_lines.append(f" L{line_num}:{col} {preview}")
250
+
251
+ return {
252
+ "tool": "references_tool",
253
+ "success": True,
254
+ "output": "\n".join(formatted_lines),
255
+ "count": len(locations),
256
+ "by_file": {f: len(locs) for f, locs in by_file.items()},
257
+ }
258
+
259
+
260
+ # Export all LSP tools
261
+ LSP_TOOLS = [
262
+ diagnostics_tool,
263
+ references_tool,
264
+ ]
agent_server/main.py CHANGED
@@ -32,6 +32,13 @@ logging.basicConfig(
32
32
  )
33
33
  logger = logging.getLogger(__name__)
34
34
 
35
+ # Reduce verbose logging from LangChain/LangGraph
36
+ # These libraries log entire message histories which creates excessive duplicate logs
37
+ logging.getLogger("langchain").setLevel(logging.WARNING)
38
+ logging.getLogger("langchain_core").setLevel(logging.WARNING)
39
+ logging.getLogger("langgraph").setLevel(logging.WARNING)
40
+ logging.getLogger("langsmith").setLevel(logging.WARNING)
41
+
35
42
 
36
43
  @asynccontextmanager
37
44
  async def lifespan(app: FastAPI):
@@ -561,23 +561,50 @@ async def stream_agent(request: AgentRequest):
561
561
  # Prepare config with thread_id
562
562
  config = {"configurable": {"thread_id": thread_id}}
563
563
 
564
- # Debug: Check if there's existing state for this thread
564
+ # Check existing state and reset todos if all completed
565
+ should_reset_todos = False
565
566
  try:
566
567
  existing_state = checkpointer.get(config)
567
568
  if existing_state:
568
- existing_messages = existing_state.get("channel_values", {}).get(
569
- "messages", []
570
- )
569
+ channel_values = existing_state.get("channel_values", {})
570
+ existing_messages = channel_values.get("messages", [])
571
+ existing_todos = channel_values.get("todos", [])
571
572
  logger.info(
572
- "Existing state for thread %s: %d messages found",
573
+ "Existing state for thread %s: %d messages, %d todos found",
573
574
  thread_id,
574
575
  len(existing_messages),
576
+ len(existing_todos),
575
577
  )
578
+ # Check if all todos are completed - if so, reset them
579
+ if existing_todos:
580
+ all_completed = all(
581
+ t.get("status") == "completed" for t in existing_todos
582
+ )
583
+ if all_completed:
584
+ should_reset_todos = True
585
+ logger.info(
586
+ "All %d todos are completed, will reset for new task",
587
+ len(existing_todos),
588
+ )
576
589
  else:
577
590
  logger.info("No existing state for thread %s", thread_id)
578
591
  except Exception as e:
579
592
  logger.warning("Could not check existing state: %s", e)
580
593
 
594
+ # Reset todos in agent state if all were completed
595
+ todos_reset_event = None
596
+ if should_reset_todos:
597
+ try:
598
+ agent.update_state(config, {"todos": []})
599
+ logger.info("Reset todos in agent state for thread %s", thread_id)
600
+ # Prepare event to notify frontend (will be yielded after function setup)
601
+ todos_reset_event = {
602
+ "event": "todos",
603
+ "data": json.dumps({"todos": [], "reset": True}),
604
+ }
605
+ except Exception as e:
606
+ logger.warning("Could not reset todos in agent state: %s", e)
607
+
581
608
  # Prepare input
582
609
  agent_input = {"messages": [{"role": "user", "content": request.request}]}
583
610
 
@@ -593,6 +620,11 @@ async def stream_agent(request: AgentRequest):
593
620
  emitted_contents: set = set()
594
621
  _simple_agent_emitted_contents[thread_id] = emitted_contents
595
622
 
623
+ # Emit todos reset event if needed (before starting the stream)
624
+ if todos_reset_event:
625
+ logger.info("SSE: Emitting todos reset event")
626
+ yield todos_reset_event
627
+
596
628
  # Initial status: waiting for LLM
597
629
  logger.info("SSE: Sending initial debug status '🤔 LLM 응답 대기 중'")
598
630
  yield {
@@ -695,11 +727,27 @@ async def stream_agent(request: AgentRequest):
695
727
  final_answer = tool_result.get(
696
728
  "answer"
697
729
  ) or tool_result.get("parameters", {}).get("answer")
730
+
731
+ # Check for next_items in answer field (LLM may put JSON here)
732
+ if final_answer:
733
+ try:
734
+ answer_json = json.loads(final_answer)
735
+ if "next_items" in answer_json:
736
+ next_items_block = f"\n\n```json\n{json.dumps(answer_json, ensure_ascii=False, indent=2)}\n```"
737
+ # Get summary for the main text
738
+ summary_text = tool_result.get(
739
+ "summary"
740
+ ) or tool_result.get("parameters", {}).get("summary") or ""
741
+ final_answer = summary_text + next_items_block
742
+ logger.info("Extracted next_items from answer field")
743
+ except (json.JSONDecodeError, TypeError):
744
+ pass
745
+
698
746
  # Check for next_items in summary field (Gemini puts JSON here)
699
747
  summary = tool_result.get(
700
748
  "summary"
701
749
  ) or tool_result.get("parameters", {}).get("summary")
702
- if summary:
750
+ if summary and "next_items" not in (final_answer or ""):
703
751
  try:
704
752
  summary_json = json.loads(summary)
705
753
  if "next_items" in summary_json:
@@ -1388,9 +1436,26 @@ async def resume_agent(request: ResumeRequest):
1388
1436
  )
1389
1437
  # Get or create cached agent
1390
1438
  resolved_workspace_root = _resolve_workspace_root(request.workspaceRoot)
1391
- checkpointer = _simple_agent_checkpointers.setdefault(
1392
- request.threadId, InMemorySaver()
1393
- )
1439
+
1440
+ # CRITICAL: Validate checkpoint exists before resume
1441
+ # InMemorySaver is volatile - server restart loses all checkpoints
1442
+ if request.threadId not in _simple_agent_checkpointers:
1443
+ logger.warning(
1444
+ "Resume failed: No checkpoint found for thread %s. "
1445
+ "Server may have restarted or session expired.",
1446
+ request.threadId,
1447
+ )
1448
+ yield {
1449
+ "event": "error",
1450
+ "data": json.dumps({
1451
+ "error": "Session expired or not found",
1452
+ "code": "CHECKPOINT_NOT_FOUND",
1453
+ "message": "이전 세션을 찾을 수 없습니다. 서버가 재시작되었거나 세션이 만료되었습니다. 새로운 대화를 시작해주세요.",
1454
+ }),
1455
+ }
1456
+ return
1457
+
1458
+ checkpointer = _simple_agent_checkpointers.get(request.threadId)
1394
1459
 
1395
1460
  agent_cache_key = _get_agent_cache_key(
1396
1461
  llm_config=config_dict,
@@ -1612,11 +1677,27 @@ async def resume_agent(request: ResumeRequest):
1612
1677
  final_answer = tool_result.get(
1613
1678
  "answer"
1614
1679
  ) or tool_result.get("parameters", {}).get("answer")
1680
+
1681
+ # Check for next_items in answer field (LLM may put JSON here)
1682
+ if final_answer:
1683
+ try:
1684
+ answer_json = json.loads(final_answer)
1685
+ if "next_items" in answer_json:
1686
+ next_items_block = f"\n\n```json\n{json.dumps(answer_json, ensure_ascii=False, indent=2)}\n```"
1687
+ # Get summary for the main text
1688
+ summary_text = tool_result.get(
1689
+ "summary"
1690
+ ) or tool_result.get("parameters", {}).get("summary") or ""
1691
+ final_answer = summary_text + next_items_block
1692
+ logger.info("Resume: Extracted next_items from answer field")
1693
+ except (json.JSONDecodeError, TypeError):
1694
+ pass
1695
+
1615
1696
  # Check for next_items in summary field (Gemini puts JSON here)
1616
1697
  summary = tool_result.get(
1617
1698
  "summary"
1618
1699
  ) or tool_result.get("parameters", {}).get("summary")
1619
- if summary:
1700
+ if summary and "next_items" not in (final_answer or ""):
1620
1701
  try:
1621
1702
  summary_json = json.loads(summary)
1622
1703
  if "next_items" in summary_json:
@@ -1966,16 +2047,31 @@ async def resume_agent(request: ResumeRequest):
1966
2047
  }
1967
2048
 
1968
2049
  except Exception as e:
1969
- logger.error(f"Resume error: {e}", exc_info=True)
1970
- yield {
1971
- "event": "error",
1972
- "data": json.dumps(
1973
- {
1974
- "error": str(e),
2050
+ error_msg = str(e)
2051
+ logger.error(f"Resume error: {error_msg}", exc_info=True)
2052
+
2053
+ # Detect specific Gemini error for empty contents
2054
+ if "contents is not specified" in error_msg.lower():
2055
+ logger.warning(
2056
+ "Detected 'contents is not specified' error - likely session state loss"
2057
+ )
2058
+ yield {
2059
+ "event": "error",
2060
+ "data": json.dumps({
2061
+ "error": "Session state lost",
2062
+ "code": "CONTENTS_NOT_SPECIFIED",
1975
2063
  "error_type": type(e).__name__,
1976
- }
1977
- ),
1978
- }
2064
+ "message": "세션 상태가 손실되었습니다. 서버가 재시작되었거나 세션이 만료되었습니다. 새로운 대화를 시작해주세요.",
2065
+ }),
2066
+ }
2067
+ else:
2068
+ yield {
2069
+ "event": "error",
2070
+ "data": json.dumps({
2071
+ "error": error_msg,
2072
+ "error_type": type(e).__name__,
2073
+ }),
2074
+ }
1979
2075
 
1980
2076
  return EventSourceResponse(event_generator())
1981
2077
 
@@ -105,9 +105,14 @@ async def reindex(request: ReindexRequest) -> ReindexResponse:
105
105
  )
106
106
 
107
107
  try:
108
- # For now, return a simple success response
109
- # Full reindex implementation would go here
110
- return ReindexResponse(success=True, indexed=0, skipped=0, errors=[])
108
+ # Trigger actual reindexing
109
+ result = await rag_manager._index_knowledge_base()
110
+ return ReindexResponse(
111
+ success=True,
112
+ indexed=result["indexed"],
113
+ skipped=result["skipped"],
114
+ errors=result["errors"],
115
+ )
111
116
 
112
117
  except Exception as e:
113
118
  logger.error(f"Reindex failed: {e}")
@@ -41,12 +41,26 @@ class QdrantConfig(BaseModel):
41
41
  description="Vector collection name"
42
42
  )
43
43
 
44
+ def get_mode(self) -> str:
45
+ """Get mode with environment variable override"""
46
+ env_mode = os.environ.get("QDRANT_MODE")
47
+ if env_mode:
48
+ return env_mode
49
+ return self.mode
50
+
51
+ def get_url(self) -> str:
52
+ """Get URL with environment variable override"""
53
+ env_url = os.environ.get("QDRANT_URL")
54
+ if env_url:
55
+ return env_url
56
+ return self.url or "http://localhost:6333"
57
+
44
58
  def get_local_path(self) -> str:
45
59
  """Get resolved local path with environment variable support"""
46
60
  if self.local_path:
47
61
  return os.path.expanduser(self.local_path)
48
62
  # Check environment variable
49
- env_path = os.environ.get("HDSP_QDRANT_PATH")
63
+ env_path = os.environ.get("QDRANT_PATH")
50
64
  if env_path:
51
65
  return os.path.expanduser(env_path)
52
66
  # Default path
@@ -35,8 +35,13 @@ class EmbeddedRAGService(IRAGService):
35
35
  return
36
36
 
37
37
  try:
38
- from hdsp_agent_core.managers.rag_manager import get_rag_manager
38
+ from agent_server.core.rag_manager import get_rag_manager
39
+
39
40
  self._rag_manager = get_rag_manager()
41
+
42
+ # Initialize RAG system
43
+ await self._rag_manager.initialize()
44
+
40
45
  self._initialized = True
41
46
  logger.info("EmbeddedRAGService initialized with RAGManager")
42
47
  except ImportError as e:
@@ -722,7 +722,7 @@
722
722
  "@mui/material": {},
723
723
  "react-markdown": {},
724
724
  "hdsp-agent": {
725
- "version": "2.0.7",
725
+ "version": "2.0.8",
726
726
  "singleton": true,
727
727
  "import": "/Users/a421721/Desktop/hdsp/hdsp_agent/extensions/jupyter/lib/index.js"
728
728
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hdsp-agent",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "HDSP Agent JupyterLab Extension - Thin client for Agent Server",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -70,6 +70,7 @@
70
70
  "@jupyterlab/launcher": "^4.0.0",
71
71
  "@jupyterlab/notebook": "^4.0.0",
72
72
  "@jupyterlab/services": "^7.0.0",
73
+ "@jupyterlab/terminal": "^4.0.0",
73
74
  "@lumino/widgets": "^2.0.0",
74
75
  "@mui/icons-material": "^7.3.4",
75
76
  "@mui/material": "^5.14.0",
@@ -126,7 +127,7 @@
126
127
  }
127
128
  },
128
129
  "_build": {
129
- "load": "static/remoteEntry.61343eb4cf0577e74b50.js",
130
+ "load": "static/remoteEntry.020cdb0b864cfaa4e41e.js",
130
131
  "extension": "./extension",
131
132
  "style": "./style"
132
133
  }