hdsp-jupyter-extension 2.0.25__py3-none-any.whl → 2.0.27__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/langchain/agent_factory.py +14 -14
- agent_server/langchain/agent_prompts/planner_prompt.py +17 -27
- agent_server/langchain/custom_middleware.py +83 -17
- agent_server/langchain/middleware/code_history_middleware.py +126 -37
- agent_server/langchain/middleware/subagent_middleware.py +24 -2
- agent_server/langchain/models/gpt_oss_chat.py +26 -13
- agent_server/langchain/prompts.py +11 -8
- agent_server/langchain/tools/jupyter_tools.py +43 -0
- agent_server/routers/langchain_agent.py +235 -23
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.ffc2b4bc8e6cb300e1e1.js → hdsp_jupyter_extension-2.0.27.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.4ab73bb5068405670214.js +2 -2
- jupyter_ext/labextension/static/remoteEntry.ffc2b4bc8e6cb300e1e1.js.map → hdsp_jupyter_extension-2.0.27.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.4ab73bb5068405670214.js.map +1 -1
- {hdsp_jupyter_extension-2.0.25.dist-info → hdsp_jupyter_extension-2.0.27.dist-info}/METADATA +1 -1
- {hdsp_jupyter_extension-2.0.25.dist-info → hdsp_jupyter_extension-2.0.27.dist-info}/RECORD +45 -45
- jupyter_ext/_version.py +1 -1
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +2 -2
- jupyter_ext/labextension/static/{remoteEntry.ffc2b4bc8e6cb300e1e1.js → remoteEntry.4ab73bb5068405670214.js} +2 -2
- hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.ffc2b4bc8e6cb300e1e1.js.map → jupyter_ext/labextension/static/remoteEntry.4ab73bb5068405670214.js.map +1 -1
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js.map +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js.map +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.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.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.27.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
- {hdsp_jupyter_extension-2.0.25.dist-info → hdsp_jupyter_extension-2.0.27.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.25.dist-info → hdsp_jupyter_extension-2.0.27.dist-info}/licenses/LICENSE +0 -0
|
@@ -200,19 +200,20 @@ class ChatGPTOSS(BaseChatModel):
|
|
|
200
200
|
tool_calls_list = []
|
|
201
201
|
|
|
202
202
|
if message.tool_calls:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
#
|
|
215
|
-
|
|
203
|
+
# IMPORTANT: Only use the first tool_call to prevent parallel execution issues
|
|
204
|
+
# LLM sometimes generates multiple tool_calls despite prompt instructions
|
|
205
|
+
tool_calls_to_use = message.tool_calls[:1]
|
|
206
|
+
if len(message.tool_calls) > 1:
|
|
207
|
+
logger.warning(
|
|
208
|
+
"Multiple tool_calls detected (%d), using only first one: %s. Ignored: %s",
|
|
209
|
+
len(message.tool_calls),
|
|
210
|
+
message.tool_calls[0].function.name,
|
|
211
|
+
[tc.function.name for tc in message.tool_calls[1:]],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Convert to LangChain tool_calls format only
|
|
215
|
+
# Do NOT put tool_calls in additional_kwargs to avoid duplicate/conflicting data
|
|
216
|
+
for tc in tool_calls_to_use:
|
|
216
217
|
try:
|
|
217
218
|
args = json.loads(tc.function.arguments)
|
|
218
219
|
except json.JSONDecodeError:
|
|
@@ -327,9 +328,21 @@ class ChatGPTOSS(BaseChatModel):
|
|
|
327
328
|
tool_call_chunks = []
|
|
328
329
|
|
|
329
330
|
# Handle tool calls in streaming
|
|
331
|
+
# IMPORTANT: Only process the first tool_call (index 0) to prevent parallel execution
|
|
330
332
|
if delta.tool_calls:
|
|
331
333
|
for tc in delta.tool_calls:
|
|
332
334
|
idx = tc.index
|
|
335
|
+
# Only accumulate the first tool_call (index 0)
|
|
336
|
+
if idx > 0:
|
|
337
|
+
if idx == 1 and tc.function and tc.function.name:
|
|
338
|
+
# Log warning only once when we first see index 1
|
|
339
|
+
logger.warning(
|
|
340
|
+
"Multiple tool_calls in stream, ignoring index %d: %s",
|
|
341
|
+
idx,
|
|
342
|
+
tc.function.name,
|
|
343
|
+
)
|
|
344
|
+
continue
|
|
345
|
+
|
|
333
346
|
if idx not in tool_calls_accum:
|
|
334
347
|
tool_calls_accum[idx] = {
|
|
335
348
|
"id": tc.id or "",
|
|
@@ -31,12 +31,12 @@ DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter n
|
|
|
31
31
|
- **[중요] "작업 요약 및 다음 단계 제시"는 summary JSON 출력 후에만 completed 표시**
|
|
32
32
|
|
|
33
33
|
# 모든 작업 완료 후 [필수]
|
|
34
|
-
마지막 todo "작업 요약 및 다음 단계 제시"를
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
마지막 todo "작업 요약 및 다음 단계 제시"를 완료할 때:
|
|
35
|
+
**반드시 final_summary_tool을 호출**하여 요약과 다음 단계를 제시하세요.
|
|
36
|
+
- final_summary_tool(summary="완료된 작업 요약", next_items=[{"subject": "제목", "description": "설명"}, ...])
|
|
37
37
|
- next_items 3개 이상 필수
|
|
38
|
-
- **
|
|
39
|
-
- **주의**: JSON
|
|
38
|
+
- **final_summary_tool 호출 없이 종료 금지**
|
|
39
|
+
- **주의**: 텍스트로 JSON 출력하지 말고, 반드시 도구 호출로!
|
|
40
40
|
|
|
41
41
|
# 도구 사용
|
|
42
42
|
- check_resource_tool: 대용량 파일/데이터프레임 작업 전 필수
|
|
@@ -94,9 +94,9 @@ TODO_LIST_TOOL_DESCRIPTION = """Todo 리스트 관리 도구.
|
|
|
94
94
|
- in_progress 상태는 **동시에 1개만** 허용
|
|
95
95
|
- **[필수] 마지막 todo는 반드시 "작업 요약 및 다음 단계 제시"로 생성**
|
|
96
96
|
- **🔴 [실행 순서]**: todo는 반드시 리스트 순서대로 실행하고, "작업 요약 및 다음 단계 제시"는 맨 마지막에 실행
|
|
97
|
-
- 이 "작업 요약 및 다음 단계 제시" todo
|
|
98
|
-
|
|
99
|
-
(next_items 3개 이상
|
|
97
|
+
- 이 "작업 요약 및 다음 단계 제시" todo 완료 시 **반드시 final_summary_tool 호출**:
|
|
98
|
+
final_summary_tool(summary="완료 요약", next_items=[{"subject": "...", "description": "..."}])
|
|
99
|
+
(next_items 3개 이상 필수, 텍스트 JSON 출력 금지!)
|
|
100
100
|
"""
|
|
101
101
|
|
|
102
102
|
# List of tools available to the agent
|
|
@@ -104,6 +104,7 @@ TOOL_LIST = [
|
|
|
104
104
|
"jupyter_cell_tool",
|
|
105
105
|
"markdown_tool",
|
|
106
106
|
"ask_user_tool",
|
|
107
|
+
"final_summary_tool",
|
|
107
108
|
"write_todos",
|
|
108
109
|
"read_file_tool",
|
|
109
110
|
"write_file_tool",
|
|
@@ -128,6 +129,8 @@ NON_HITL_TOOLS = {
|
|
|
128
129
|
"search_notebook_cells_tool",
|
|
129
130
|
"search_notebook_cells",
|
|
130
131
|
"write_todos",
|
|
132
|
+
"final_summary_tool",
|
|
133
|
+
"final_summary",
|
|
131
134
|
# LSP tools (read-only)
|
|
132
135
|
"diagnostics_tool",
|
|
133
136
|
"diagnostics",
|
|
@@ -172,9 +172,52 @@ def ask_user_tool(
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
|
|
175
|
+
class NextItem(BaseModel):
|
|
176
|
+
"""Schema for a next step item"""
|
|
177
|
+
|
|
178
|
+
subject: str = Field(description="다음 단계 제목")
|
|
179
|
+
description: str = Field(description="다음 단계 설명")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class FinalSummaryInput(BaseModel):
|
|
183
|
+
"""Input schema for final_summary tool"""
|
|
184
|
+
|
|
185
|
+
summary: str = Field(description="완료된 작업에 대한 요약 (한국어)")
|
|
186
|
+
next_items: List[NextItem] = Field(
|
|
187
|
+
description="다음 단계 제안 목록 (3개 이상)", min_length=3
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@tool(args_schema=FinalSummaryInput)
|
|
192
|
+
def final_summary_tool(
|
|
193
|
+
summary: str,
|
|
194
|
+
next_items: List[Dict[str, str]],
|
|
195
|
+
) -> Dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
모든 작업이 완료된 후 최종 요약과 다음 단계를 제시하는 도구.
|
|
198
|
+
|
|
199
|
+
이 도구는 반드시 모든 todo가 완료된 후, 마지막 "작업 요약 및 다음 단계 제시" todo를 처리할 때만 호출하세요.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
summary: 완료된 작업에 대한 요약 (한국어로 작성)
|
|
203
|
+
next_items: 다음 단계 제안 목록 (각각 subject와 description 포함, 3개 이상)
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Dict containing the summary and next items for frontend display
|
|
207
|
+
"""
|
|
208
|
+
return {
|
|
209
|
+
"tool": "final_summary",
|
|
210
|
+
"summary": summary,
|
|
211
|
+
"next_items": next_items,
|
|
212
|
+
"status": "completed",
|
|
213
|
+
"message": "작업 요약이 완료되었습니다.",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
175
217
|
# Export all tools
|
|
176
218
|
JUPYTER_TOOLS = [
|
|
177
219
|
jupyter_cell_tool,
|
|
178
220
|
markdown_tool,
|
|
179
221
|
ask_user_tool,
|
|
222
|
+
final_summary_tool,
|
|
180
223
|
]
|
|
@@ -695,6 +695,19 @@ async def stream_agent(request: AgentRequest):
|
|
|
695
695
|
thread_id,
|
|
696
696
|
)
|
|
697
697
|
|
|
698
|
+
# Handle @reset command
|
|
699
|
+
if "@reset" in request.request:
|
|
700
|
+
logger.info(f"@reset command detected for thread: {thread_id}")
|
|
701
|
+
from agent_server.langchain.middleware.code_history_middleware import (
|
|
702
|
+
clear_code_history,
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
# Clear code history and checkpointer for this thread
|
|
706
|
+
clear_code_history(thread_id)
|
|
707
|
+
if thread_id in _simple_agent_checkpointers:
|
|
708
|
+
del _simple_agent_checkpointers[thread_id]
|
|
709
|
+
logger.info(f"Session reset complete for thread: {thread_id}")
|
|
710
|
+
|
|
698
711
|
async def event_generator():
|
|
699
712
|
try:
|
|
700
713
|
# Use simple agent with HITL
|
|
@@ -739,6 +752,15 @@ async def stream_agent(request: AgentRequest):
|
|
|
739
752
|
len(_simple_agent_checkpointers),
|
|
740
753
|
)
|
|
741
754
|
|
|
755
|
+
# Clear code history for new threads
|
|
756
|
+
if not is_existing_thread:
|
|
757
|
+
from agent_server.langchain.middleware.code_history_middleware import (
|
|
758
|
+
clear_code_history,
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
clear_code_history(thread_id)
|
|
762
|
+
logger.info(f"Code history cleared for new thread: {thread_id}")
|
|
763
|
+
|
|
742
764
|
resolved_workspace_root = _resolve_workspace_root(request.workspaceRoot)
|
|
743
765
|
|
|
744
766
|
# Get agent mode (single or multi)
|
|
@@ -746,20 +768,31 @@ async def stream_agent(request: AgentRequest):
|
|
|
746
768
|
logger.info("Agent mode: %s", agent_mode)
|
|
747
769
|
|
|
748
770
|
# Get agent prompts (for multi-agent mode)
|
|
749
|
-
# ROOT CAUSE FOUND: Frontend sends both systemPrompt AND agentPrompts
|
|
750
|
-
# which creates different cache keys and causes MALFORMED_FUNCTION_CALL
|
|
751
|
-
# FIX: Ignore all custom prompts for multi-agent mode until frontend is fixed
|
|
752
771
|
agent_prompts = None
|
|
753
772
|
if agent_mode == "multi":
|
|
754
|
-
#
|
|
773
|
+
# Multi-agent mode: Use agentPrompts for per-agent customization
|
|
774
|
+
# systemPrompt is for single-agent mode only (DEFAULT_SYSTEM_PROMPT)
|
|
755
775
|
if request.llmConfig and request.llmConfig.agent_prompts:
|
|
756
|
-
|
|
757
|
-
"
|
|
776
|
+
agent_prompts = {
|
|
777
|
+
"planner": request.llmConfig.agent_prompts.planner,
|
|
778
|
+
"python_developer": (
|
|
779
|
+
request.llmConfig.agent_prompts.python_developer
|
|
780
|
+
),
|
|
781
|
+
"researcher": request.llmConfig.agent_prompts.researcher,
|
|
782
|
+
"athena_query": request.llmConfig.agent_prompts.athena_query,
|
|
783
|
+
}
|
|
784
|
+
agent_prompts = {k: v for k, v in agent_prompts.items() if v}
|
|
785
|
+
logger.info(
|
|
786
|
+
"Multi-agent mode: Using agentPrompts (%s)",
|
|
787
|
+
list(agent_prompts.keys()),
|
|
758
788
|
)
|
|
759
|
-
#
|
|
789
|
+
# In multi-agent mode, DON'T use systemPrompt as override
|
|
790
|
+
# (systemPrompt = single-agent prompt, not planner prompt)
|
|
791
|
+
# Use agentPrompts.planner instead (handled by agent_factory)
|
|
760
792
|
if system_prompt_override:
|
|
761
|
-
logger.
|
|
762
|
-
"Multi-agent mode: Ignoring systemPrompt override (len=%d)"
|
|
793
|
+
logger.info(
|
|
794
|
+
"Multi-agent mode: Ignoring systemPrompt override (len=%d) - "
|
|
795
|
+
"use agentPrompts.planner instead",
|
|
763
796
|
len(system_prompt_override),
|
|
764
797
|
)
|
|
765
798
|
system_prompt_override = None
|
|
@@ -827,6 +860,13 @@ async def stream_agent(request: AgentRequest):
|
|
|
827
860
|
# Prepare config with thread_id
|
|
828
861
|
config = {"configurable": {"thread_id": thread_id}}
|
|
829
862
|
|
|
863
|
+
# Set current thread_id for code history tracking
|
|
864
|
+
from agent_server.langchain.middleware.subagent_middleware import (
|
|
865
|
+
set_current_thread_id,
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
set_current_thread_id(thread_id)
|
|
869
|
+
|
|
830
870
|
# Check existing state and ALWAYS reset todos for new request
|
|
831
871
|
# Each new user request starts a fresh todo list
|
|
832
872
|
should_reset_todos = False
|
|
@@ -903,7 +943,6 @@ async def stream_agent(request: AgentRequest):
|
|
|
903
943
|
produced_output = False
|
|
904
944
|
last_finish_reason = None
|
|
905
945
|
last_signature = None
|
|
906
|
-
latest_todos: Optional[List[Dict[str, Any]]] = None
|
|
907
946
|
# Initialize emitted contents set for this thread (clear any stale data)
|
|
908
947
|
emitted_contents: set = set()
|
|
909
948
|
_simple_agent_emitted_contents[thread_id] = emitted_contents
|
|
@@ -957,12 +996,10 @@ async def stream_agent(request: AgentRequest):
|
|
|
957
996
|
if isinstance(step, dict) and "todos" in step:
|
|
958
997
|
todos = step["todos"]
|
|
959
998
|
if todos:
|
|
960
|
-
latest_todos = todos
|
|
961
999
|
yield {"event": "todos", "data": json.dumps({"todos": todos})}
|
|
962
1000
|
elif isinstance(step, dict):
|
|
963
1001
|
todos = _extract_todos(step)
|
|
964
1002
|
if todos:
|
|
965
|
-
latest_todos = todos
|
|
966
1003
|
yield {"event": "todos", "data": json.dumps({"todos": todos})}
|
|
967
1004
|
|
|
968
1005
|
# Process messages (no continue statements to ensure interrupt check always runs)
|
|
@@ -1012,7 +1049,6 @@ async def stream_agent(request: AgentRequest):
|
|
|
1012
1049
|
)
|
|
1013
1050
|
todos = _extract_todos(last_message.content)
|
|
1014
1051
|
if todos:
|
|
1015
|
-
latest_todos = todos
|
|
1016
1052
|
yield {
|
|
1017
1053
|
"event": "todos",
|
|
1018
1054
|
"data": json.dumps({"todos": todos}),
|
|
@@ -1270,7 +1306,6 @@ async def stream_agent(request: AgentRequest):
|
|
|
1270
1306
|
"SSE: Emitting todos event from AIMessage tool_calls: %d items",
|
|
1271
1307
|
len(todos),
|
|
1272
1308
|
)
|
|
1273
|
-
latest_todos = todos
|
|
1274
1309
|
yield {
|
|
1275
1310
|
"event": "todos",
|
|
1276
1311
|
"data": json.dumps({"todos": todos}),
|
|
@@ -1286,7 +1321,7 @@ async def stream_agent(request: AgentRequest):
|
|
|
1286
1321
|
"다음단계",
|
|
1287
1322
|
"다음 단계",
|
|
1288
1323
|
]
|
|
1289
|
-
|
|
1324
|
+
any(
|
|
1290
1325
|
any(
|
|
1291
1326
|
kw in t.get("content", "")
|
|
1292
1327
|
for kw in summary_keywords
|
|
@@ -1461,6 +1496,22 @@ async def stream_agent(request: AgentRequest):
|
|
|
1461
1496
|
}
|
|
1462
1497
|
),
|
|
1463
1498
|
}
|
|
1499
|
+
elif tool_name in (
|
|
1500
|
+
"final_summary_tool",
|
|
1501
|
+
"final_summary",
|
|
1502
|
+
):
|
|
1503
|
+
# Final summary - emit summary event for frontend
|
|
1504
|
+
produced_output = True
|
|
1505
|
+
summary_data = {
|
|
1506
|
+
"summary": tool_args.get("summary", ""),
|
|
1507
|
+
"next_items": tool_args.get(
|
|
1508
|
+
"next_items", []
|
|
1509
|
+
),
|
|
1510
|
+
}
|
|
1511
|
+
yield {
|
|
1512
|
+
"event": "summary",
|
|
1513
|
+
"data": json.dumps(summary_data),
|
|
1514
|
+
}
|
|
1464
1515
|
|
|
1465
1516
|
# Only display content if it's not empty and not a JSON tool response
|
|
1466
1517
|
if (
|
|
@@ -1504,6 +1555,31 @@ async def stream_agent(request: AgentRequest):
|
|
|
1504
1555
|
and content_stripped.startswith("{")
|
|
1505
1556
|
)
|
|
1506
1557
|
)
|
|
1558
|
+
|
|
1559
|
+
# Check if this is summary/next_items with tool_calls (premature summary)
|
|
1560
|
+
has_summary_pattern = (
|
|
1561
|
+
'"summary"' in content or "'summary'" in content
|
|
1562
|
+
) and (
|
|
1563
|
+
'"next_items"' in content
|
|
1564
|
+
or "'next_items'" in content
|
|
1565
|
+
)
|
|
1566
|
+
tool_calls = (
|
|
1567
|
+
getattr(last_message, "tool_calls", []) or []
|
|
1568
|
+
)
|
|
1569
|
+
# Check if any tool_call is NOT write_todos (work still in progress)
|
|
1570
|
+
has_non_todo_tool_calls = any(
|
|
1571
|
+
tc.get("name")
|
|
1572
|
+
not in ("write_todos", "write_todos_tool")
|
|
1573
|
+
for tc in tool_calls
|
|
1574
|
+
)
|
|
1575
|
+
# Skip summary if non-write_todos tool_calls exist (work still in progress)
|
|
1576
|
+
if has_summary_pattern and has_non_todo_tool_calls:
|
|
1577
|
+
logger.info(
|
|
1578
|
+
"Initial: SKIPPING premature summary (has non-todo tool_calls): %s",
|
|
1579
|
+
content[:100],
|
|
1580
|
+
)
|
|
1581
|
+
content = None # Skip this content
|
|
1582
|
+
|
|
1507
1583
|
if (
|
|
1508
1584
|
content
|
|
1509
1585
|
and isinstance(content, str)
|
|
@@ -1709,6 +1785,39 @@ async def stream_agent(request: AgentRequest):
|
|
|
1709
1785
|
}
|
|
1710
1786
|
produced_output = True
|
|
1711
1787
|
fallback_response = None
|
|
1788
|
+
if isinstance(fallback_response, AIMessage):
|
|
1789
|
+
# LLM Response - structured JSON format
|
|
1790
|
+
print(LOG_RESPONSE_START, flush=True)
|
|
1791
|
+
response_data = {
|
|
1792
|
+
"type": "AIMessage",
|
|
1793
|
+
"content": fallback_response.content or "",
|
|
1794
|
+
"tool_calls": fallback_response.tool_calls
|
|
1795
|
+
if hasattr(fallback_response, "tool_calls")
|
|
1796
|
+
else [],
|
|
1797
|
+
"additional_kwargs": getattr(
|
|
1798
|
+
fallback_response, "additional_kwargs", {}
|
|
1799
|
+
)
|
|
1800
|
+
or {},
|
|
1801
|
+
"response_metadata": getattr(
|
|
1802
|
+
fallback_response, "response_metadata", {}
|
|
1803
|
+
)
|
|
1804
|
+
or {},
|
|
1805
|
+
"usage_metadata": getattr(
|
|
1806
|
+
fallback_response, "usage_metadata", {}
|
|
1807
|
+
)
|
|
1808
|
+
or {},
|
|
1809
|
+
}
|
|
1810
|
+
print(
|
|
1811
|
+
json.dumps(
|
|
1812
|
+
response_data,
|
|
1813
|
+
indent=2,
|
|
1814
|
+
ensure_ascii=False,
|
|
1815
|
+
default=str,
|
|
1816
|
+
),
|
|
1817
|
+
flush=True,
|
|
1818
|
+
)
|
|
1819
|
+
print(LOG_RESPONSE_END, flush=True)
|
|
1820
|
+
|
|
1712
1821
|
if isinstance(fallback_response, AIMessage) and getattr(
|
|
1713
1822
|
fallback_response, "tool_calls", None
|
|
1714
1823
|
):
|
|
@@ -1771,6 +1880,17 @@ async def stream_agent(request: AgentRequest):
|
|
|
1771
1880
|
}
|
|
1772
1881
|
),
|
|
1773
1882
|
}
|
|
1883
|
+
elif tool_name in ("final_summary_tool", "final_summary"):
|
|
1884
|
+
# Final summary - emit summary event for frontend
|
|
1885
|
+
produced_output = True
|
|
1886
|
+
summary_data = {
|
|
1887
|
+
"summary": tool_args.get("summary", ""),
|
|
1888
|
+
"next_items": tool_args.get("next_items", []),
|
|
1889
|
+
}
|
|
1890
|
+
yield {
|
|
1891
|
+
"event": "summary",
|
|
1892
|
+
"data": json.dumps(summary_data),
|
|
1893
|
+
}
|
|
1774
1894
|
elif tool_name == "read_file_tool":
|
|
1775
1895
|
# For file operations, generate code with the LLM
|
|
1776
1896
|
logger.info(
|
|
@@ -1959,14 +2079,28 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1959
2079
|
logger.info("Resume: Agent mode: %s", agent_mode)
|
|
1960
2080
|
|
|
1961
2081
|
# Get agent prompts (for multi-agent mode)
|
|
1962
|
-
# ROOT CAUSE: Frontend sends both systemPrompt AND agentPrompts
|
|
1963
|
-
# FIX: Ignore all custom prompts for multi-agent mode
|
|
1964
2082
|
agent_prompts = None
|
|
1965
2083
|
if agent_mode == "multi":
|
|
1966
2084
|
if request.llmConfig and request.llmConfig.agent_prompts:
|
|
1967
|
-
|
|
2085
|
+
agent_prompts = {
|
|
2086
|
+
"planner": request.llmConfig.agent_prompts.planner,
|
|
2087
|
+
"python_developer": (
|
|
2088
|
+
request.llmConfig.agent_prompts.python_developer
|
|
2089
|
+
),
|
|
2090
|
+
"researcher": request.llmConfig.agent_prompts.researcher,
|
|
2091
|
+
"athena_query": request.llmConfig.agent_prompts.athena_query,
|
|
2092
|
+
}
|
|
2093
|
+
agent_prompts = {k: v for k, v in agent_prompts.items() if v}
|
|
2094
|
+
logger.info(
|
|
2095
|
+
"Resume: Multi-agent mode - using agentPrompts (%s)",
|
|
2096
|
+
list(agent_prompts.keys()),
|
|
2097
|
+
)
|
|
2098
|
+
# In multi-agent mode, DON'T use systemPrompt as override
|
|
1968
2099
|
if system_prompt_override:
|
|
1969
|
-
logger.
|
|
2100
|
+
logger.info(
|
|
2101
|
+
"Resume: Multi-agent mode - ignoring systemPrompt (len=%d)",
|
|
2102
|
+
len(system_prompt_override),
|
|
2103
|
+
)
|
|
1970
2104
|
system_prompt_override = None
|
|
1971
2105
|
|
|
1972
2106
|
agent_cache_key = _get_agent_cache_key(
|
|
@@ -2011,6 +2145,13 @@ async def resume_agent(request: ResumeRequest):
|
|
|
2011
2145
|
# Prepare config with thread_id
|
|
2012
2146
|
config = {"configurable": {"thread_id": request.threadId}}
|
|
2013
2147
|
|
|
2148
|
+
# Set current thread_id for code history tracking
|
|
2149
|
+
from agent_server.langchain.middleware.subagent_middleware import (
|
|
2150
|
+
set_current_thread_id,
|
|
2151
|
+
)
|
|
2152
|
+
|
|
2153
|
+
set_current_thread_id(request.threadId)
|
|
2154
|
+
|
|
2014
2155
|
pending_actions = _simple_agent_pending_actions.get(request.threadId, [])
|
|
2015
2156
|
|
|
2016
2157
|
# Convert decisions to LangChain format
|
|
@@ -2047,7 +2188,7 @@ async def resume_agent(request: ResumeRequest):
|
|
|
2047
2188
|
"edit_file_tool",
|
|
2048
2189
|
"multiedit_file_tool",
|
|
2049
2190
|
):
|
|
2050
|
-
track_tool_execution(tool_name, args)
|
|
2191
|
+
track_tool_execution(tool_name, args, request.threadId)
|
|
2051
2192
|
langgraph_decisions.append(
|
|
2052
2193
|
{
|
|
2053
2194
|
"type": "edit",
|
|
@@ -2341,7 +2482,39 @@ async def resume_agent(request: ResumeRequest):
|
|
|
2341
2482
|
# ToolMessage processing continues (no final_answer_tool)
|
|
2342
2483
|
|
|
2343
2484
|
# Handle AIMessage (use elif to avoid processing after ToolMessage)
|
|
2344
|
-
elif
|
|
2485
|
+
elif isinstance(last_message, AIMessage):
|
|
2486
|
+
# LLM Response - structured JSON format
|
|
2487
|
+
print(LOG_RESPONSE_START, flush=True)
|
|
2488
|
+
response_data = {
|
|
2489
|
+
"type": "AIMessage",
|
|
2490
|
+
"content": last_message.content or "",
|
|
2491
|
+
"tool_calls": last_message.tool_calls
|
|
2492
|
+
if hasattr(last_message, "tool_calls")
|
|
2493
|
+
else [],
|
|
2494
|
+
"additional_kwargs": getattr(
|
|
2495
|
+
last_message, "additional_kwargs", {}
|
|
2496
|
+
)
|
|
2497
|
+
or {},
|
|
2498
|
+
"response_metadata": getattr(
|
|
2499
|
+
last_message, "response_metadata", {}
|
|
2500
|
+
)
|
|
2501
|
+
or {},
|
|
2502
|
+
"usage_metadata": getattr(
|
|
2503
|
+
last_message, "usage_metadata", {}
|
|
2504
|
+
)
|
|
2505
|
+
or {},
|
|
2506
|
+
}
|
|
2507
|
+
print(
|
|
2508
|
+
json.dumps(
|
|
2509
|
+
response_data,
|
|
2510
|
+
indent=2,
|
|
2511
|
+
ensure_ascii=False,
|
|
2512
|
+
default=str,
|
|
2513
|
+
),
|
|
2514
|
+
flush=True,
|
|
2515
|
+
)
|
|
2516
|
+
print(LOG_RESPONSE_END, flush=True)
|
|
2517
|
+
|
|
2345
2518
|
content = last_message.content
|
|
2346
2519
|
|
|
2347
2520
|
# Handle list content (e.g., multimodal responses)
|
|
@@ -2375,6 +2548,28 @@ async def resume_agent(request: ResumeRequest):
|
|
|
2375
2548
|
and content_stripped.startswith("{")
|
|
2376
2549
|
)
|
|
2377
2550
|
)
|
|
2551
|
+
|
|
2552
|
+
# Check if this is summary/next_items with tool_calls (premature summary)
|
|
2553
|
+
has_summary_pattern = (
|
|
2554
|
+
'"summary"' in content or "'summary'" in content
|
|
2555
|
+
) and (
|
|
2556
|
+
'"next_items"' in content or "'next_items'" in content
|
|
2557
|
+
)
|
|
2558
|
+
tool_calls = getattr(last_message, "tool_calls", []) or []
|
|
2559
|
+
# Check if any tool_call is NOT write_todos (work still in progress)
|
|
2560
|
+
has_non_todo_tool_calls = any(
|
|
2561
|
+
tc.get("name")
|
|
2562
|
+
not in ("write_todos", "write_todos_tool")
|
|
2563
|
+
for tc in tool_calls
|
|
2564
|
+
)
|
|
2565
|
+
# Skip summary if non-write_todos tool_calls exist (work still in progress)
|
|
2566
|
+
if has_summary_pattern and has_non_todo_tool_calls:
|
|
2567
|
+
logger.info(
|
|
2568
|
+
"Resume: SKIPPING premature summary (has non-todo tool_calls): %s",
|
|
2569
|
+
content[:100],
|
|
2570
|
+
)
|
|
2571
|
+
content = None # Skip this content
|
|
2572
|
+
|
|
2378
2573
|
if (
|
|
2379
2574
|
content
|
|
2380
2575
|
and isinstance(content, str)
|
|
@@ -2452,7 +2647,7 @@ async def resume_agent(request: ResumeRequest):
|
|
|
2452
2647
|
"다음단계",
|
|
2453
2648
|
"다음 단계",
|
|
2454
2649
|
]
|
|
2455
|
-
|
|
2650
|
+
any(
|
|
2456
2651
|
any(
|
|
2457
2652
|
kw in t.get("content", "")
|
|
2458
2653
|
for kw in summary_keywords
|
|
@@ -2626,6 +2821,21 @@ async def resume_agent(request: ResumeRequest):
|
|
|
2626
2821
|
}
|
|
2627
2822
|
),
|
|
2628
2823
|
}
|
|
2824
|
+
elif tool_name in (
|
|
2825
|
+
"final_summary_tool",
|
|
2826
|
+
"final_summary",
|
|
2827
|
+
):
|
|
2828
|
+
# Final summary - emit summary event for frontend
|
|
2829
|
+
summary_data = {
|
|
2830
|
+
"summary": tool_args.get("summary", ""),
|
|
2831
|
+
"next_items": tool_args.get(
|
|
2832
|
+
"next_items", []
|
|
2833
|
+
),
|
|
2834
|
+
}
|
|
2835
|
+
yield {
|
|
2836
|
+
"event": "summary",
|
|
2837
|
+
"data": json.dumps(summary_data),
|
|
2838
|
+
}
|
|
2629
2839
|
|
|
2630
2840
|
# Drain and emit any subagent events (tool calls from subagents)
|
|
2631
2841
|
for subagent_event in get_subagent_debug_events():
|
|
@@ -2888,5 +3098,7 @@ async def reset_agent_thread(request: ResetRequest) -> Dict[str, Any]:
|
|
|
2888
3098
|
"status": "ok",
|
|
2889
3099
|
"thread_id": thread_id,
|
|
2890
3100
|
"cleared": cleared,
|
|
2891
|
-
"message": f"Thread {thread_id} has been reset"
|
|
3101
|
+
"message": f"Thread {thread_id} has been reset"
|
|
3102
|
+
if cleared
|
|
3103
|
+
else f"Thread {thread_id} had no state to clear",
|
|
2892
3104
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hdsp-agent",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.27",
|
|
4
4
|
"description": "HDSP Agent JupyterLab Extension - Thin client for Agent Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
}
|
|
133
133
|
},
|
|
134
134
|
"_build": {
|
|
135
|
-
"load": "static/remoteEntry.
|
|
135
|
+
"load": "static/remoteEntry.4ab73bb5068405670214.js",
|
|
136
136
|
"extension": "./extension",
|
|
137
137
|
"style": "./style"
|
|
138
138
|
}
|
|
@@ -281,7 +281,7 @@ __webpack_require__.d(exports, {
|
|
|
281
281
|
/******/ register("@emotion/react", "11.14.0", () => (Promise.all([__webpack_require__.e("vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js"), __webpack_require__.e("vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195"), __webpack_require__.e("vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js"), __webpack_require__.e("webpack_sharing_consume_default_react"), __webpack_require__.e("node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80")]).then(() => (() => (__webpack_require__(/*! ./node_modules/@emotion/react/dist/emotion-react.browser.development.esm.js */ "./node_modules/@emotion/react/dist/emotion-react.browser.development.esm.js"))))));
|
|
282
282
|
/******/ register("@emotion/styled", "11.14.1", () => (Promise.all([__webpack_require__.e("vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195"), __webpack_require__.e("vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js"), __webpack_require__.e("webpack_sharing_consume_default_react"), __webpack_require__.e("webpack_sharing_consume_default_emotion_react_emotion_react")]).then(() => (() => (__webpack_require__(/*! ./node_modules/@emotion/styled/dist/emotion-styled.browser.development.esm.js */ "./node_modules/@emotion/styled/dist/emotion-styled.browser.development.esm.js"))))));
|
|
283
283
|
/******/ register("@mui/material", "5.18.0", () => (Promise.all([__webpack_require__.e("vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js"), __webpack_require__.e("vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195"), __webpack_require__.e("vendors-node_modules_mui_material_index_js"), __webpack_require__.e("vendors-node_modules_mui_material_utils_createSvgIcon_js"), __webpack_require__.e("webpack_sharing_consume_default_react"), __webpack_require__.e("webpack_sharing_consume_default_emotion_react_emotion_react-webpack_sharing_consume_default_e-5f41bf")]).then(() => (() => (__webpack_require__(/*! ./node_modules/@mui/material/index.js */ "./node_modules/@mui/material/index.js"))))));
|
|
284
|
-
/******/ register("hdsp-agent", "2.0.
|
|
284
|
+
/******/ register("hdsp-agent", "2.0.27", () => (Promise.all([__webpack_require__.e("vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js"), __webpack_require__.e("vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195"), __webpack_require__.e("vendors-node_modules_mui_material_utils_createSvgIcon_js"), __webpack_require__.e("webpack_sharing_consume_default_react"), __webpack_require__.e("webpack_sharing_consume_default_emotion_react_emotion_react-webpack_sharing_consume_default_e-5f41bf"), __webpack_require__.e("lib_index_js")]).then(() => (() => (__webpack_require__(/*! ./lib/index.js */ "./lib/index.js"))))));
|
|
285
285
|
/******/ }
|
|
286
286
|
/******/ break;
|
|
287
287
|
/******/ }
|
|
@@ -622,4 +622,4 @@ __webpack_require__.d(exports, {
|
|
|
622
622
|
/******/
|
|
623
623
|
/******/ })()
|
|
624
624
|
;
|
|
625
|
-
//# sourceMappingURL=remoteEntry.
|
|
625
|
+
//# sourceMappingURL=remoteEntry.4ab73bb5068405670214.js.map
|