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.
- agent_server/core/embedding_service.py +67 -46
- agent_server/core/rag_manager.py +31 -17
- agent_server/core/retriever.py +13 -8
- agent_server/core/vllm_embedding_service.py +243 -0
- agent_server/langchain/agent.py +8 -0
- agent_server/langchain/custom_middleware.py +58 -31
- agent_server/langchain/hitl_config.py +6 -1
- agent_server/langchain/logging_utils.py +53 -14
- agent_server/langchain/prompts.py +47 -16
- agent_server/langchain/tools/__init__.py +13 -0
- agent_server/langchain/tools/file_tools.py +285 -7
- agent_server/langchain/tools/file_utils.py +334 -0
- agent_server/langchain/tools/lsp_tools.py +264 -0
- agent_server/main.py +7 -0
- agent_server/routers/langchain_agent.py +115 -19
- agent_server/routers/rag.py +8 -3
- hdsp_agent_core/models/rag.py +15 -1
- hdsp_agent_core/services/rag_service.py +6 -1
- {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
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
- 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
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- 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
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- 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
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- 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
- 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
- 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
- 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
- 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
- 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
- {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +1 -1
- {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +66 -63
- jupyter_ext/__init__.py +18 -0
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +176 -1
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +3 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.4770ec0fb2d173b6deb4.js → frontend_styles_index_js.8740a527757068814573.js} +160 -3
- jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.29cf4312af19e86f82af.js → lib_index_js.e4ff4b5779b5e049f84c.js} +1759 -221
- jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.61343eb4cf0577e74b50.js → remoteEntry.020cdb0b864cfaa4e41e.js} +14 -12
- jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- 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
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
- 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
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
- 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
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
- 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
- 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
- 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
- jupyter_ext/labextension/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
- {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
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
- {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
|
-
#
|
|
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
|
-
|
|
569
|
-
|
|
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
|
-
|
|
1392
|
-
|
|
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
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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
|
|
agent_server/routers/rag.py
CHANGED
|
@@ -105,9 +105,14 @@ async def reindex(request: ReindexRequest) -> ReindexResponse:
|
|
|
105
105
|
)
|
|
106
106
|
|
|
107
107
|
try:
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
return ReindexResponse(
|
|
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}")
|
hdsp_agent_core/models/rag.py
CHANGED
|
@@ -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("
|
|
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
|
|
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:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hdsp-agent",
|
|
3
|
-
"version": "2.0.
|
|
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.
|
|
130
|
+
"load": "static/remoteEntry.020cdb0b864cfaa4e41e.js",
|
|
130
131
|
"extension": "./extension",
|
|
131
132
|
"style": "./style"
|
|
132
133
|
}
|