hdsp-jupyter-extension 2.0.25__py3-none-any.whl → 2.0.26__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 +13 -19
- agent_server/langchain/custom_middleware.py +73 -17
- 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 +198 -22
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.26.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.26.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.0fe2dcbbd176ee0efceb.js +2 -2
- hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.ffc2b4bc8e6cb300e1e1.js.map → hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.0fe2dcbbd176ee0efceb.js.map +1 -1
- {hdsp_jupyter_extension-2.0.25.dist-info → hdsp_jupyter_extension-2.0.26.dist-info}/METADATA +1 -1
- {hdsp_jupyter_extension-2.0.25.dist-info → hdsp_jupyter_extension-2.0.26.dist-info}/RECORD +43 -43
- 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.0fe2dcbbd176ee0efceb.js} +2 -2
- jupyter_ext/labextension/static/{remoteEntry.ffc2b4bc8e6cb300e1e1.js.map → remoteEntry.0fe2dcbbd176ee0efceb.js.map} +1 -1
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.26.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.26.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.25.data → hdsp_jupyter_extension-2.0.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.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.26.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.25.dist-info → hdsp_jupyter_extension-2.0.26.dist-info}/licenses/LICENSE +0 -0
|
@@ -315,24 +315,24 @@ def create_main_agent(
|
|
|
315
315
|
except Exception as e:
|
|
316
316
|
logger.warning(f"Failed to add SummarizationMiddleware: {e}")
|
|
317
317
|
|
|
318
|
-
# Build system prompt
|
|
319
|
-
# TODO: Remove this override after frontend localStorage is cleared
|
|
320
|
-
# Original priority: system_prompt_override > agent_prompts.planner > default
|
|
321
|
-
# DEBUG: Log all prompt sources to find root cause of MALFORMED_FUNCTION_CALL
|
|
318
|
+
# Build system prompt with priority: system_prompt_override > agent_prompts.planner > default
|
|
322
319
|
logger.info(
|
|
323
|
-
"
|
|
324
|
-
"agent_prompts.planner=%s
|
|
320
|
+
"Main Agent prompt sources: system_prompt_override=%s (len=%d), "
|
|
321
|
+
"agent_prompts.planner=%s",
|
|
325
322
|
bool(system_prompt_override),
|
|
323
|
+
len(system_prompt_override) if system_prompt_override else 0,
|
|
326
324
|
bool(agent_prompts.get("planner") if agent_prompts else None),
|
|
327
325
|
)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
326
|
+
|
|
327
|
+
if system_prompt_override and system_prompt_override.strip():
|
|
328
|
+
system_prompt = system_prompt_override.strip()
|
|
329
|
+
logger.info("Using system_prompt_override (length=%d)", len(system_prompt))
|
|
330
|
+
elif agent_prompts and agent_prompts.get("planner"):
|
|
331
|
+
system_prompt = agent_prompts["planner"]
|
|
332
|
+
logger.info("Using agent_prompts.planner (length=%d)", len(system_prompt))
|
|
333
|
+
else:
|
|
334
|
+
system_prompt = PLANNER_SYSTEM_PROMPT
|
|
335
|
+
logger.info("Using PLANNER_SYSTEM_PROMPT (length=%d)", len(system_prompt))
|
|
336
336
|
|
|
337
337
|
# Log provider info for debugging
|
|
338
338
|
provider = llm_config.get("provider", "")
|
|
@@ -55,25 +55,19 @@ PLANNER_SYSTEM_PROMPT = """당신은 작업을 조율하는 Main Agent입니다.
|
|
|
55
55
|
- content에 도구(tool)명 언급 금지
|
|
56
56
|
- **[필수] 마지막 todo는 반드시 "작업 요약 및 다음 단계 제시"**
|
|
57
57
|
|
|
58
|
-
# "작업 요약 및 다음 단계 제시" todo
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
**중요**: JSON은 반드시 in_progress 상태일 때 출력! completed 먼저 표시 금지!
|
|
72
|
-
- next_items 3개 이상 필수
|
|
73
|
-
- **summary JSON 없이 종료 금지**
|
|
74
|
-
- **주의**: JSON은 todo 항목이 아닌 일반 텍스트 응답으로 출력
|
|
75
|
-
|
|
76
|
-
|
|
58
|
+
# "작업 요약 및 다음 단계 제시" todo 완료 시 [필수]
|
|
59
|
+
1. "작업 요약 및 다음 단계 제시"를 **in_progress**로 변경 (write_todos 호출)
|
|
60
|
+
2. **반드시 final_summary_tool 호출**:
|
|
61
|
+
final_summary_tool(
|
|
62
|
+
summary="완료된 작업 요약",
|
|
63
|
+
next_items=[{"subject": "제목", "description": "설명"}, ...]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
3. final_summary_tool 호출 후 "작업 요약 및 다음 단계 제시"를 **completed**로 변경
|
|
67
|
+
|
|
68
|
+
- next_items 3개 이상 필수
|
|
69
|
+
- **final_summary_tool 호출 없이 종료 금지**
|
|
70
|
+
|
|
77
71
|
# 도구 사용시 주의할 점
|
|
78
72
|
|
|
79
73
|
## 서브에이전트 호출 (코드/쿼리 생성 시 필수)
|
|
@@ -165,6 +165,22 @@ def try_extract_tool_calls_from_additional_kwargs(
|
|
|
165
165
|
if not raw_tool_calls:
|
|
166
166
|
return None
|
|
167
167
|
|
|
168
|
+
# IMPORTANT: Only use the first tool_call to prevent parallel execution issues
|
|
169
|
+
# LLM sometimes generates multiple tool_calls despite prompt instructions
|
|
170
|
+
if len(raw_tool_calls) > 1:
|
|
171
|
+
first_tc = raw_tool_calls[0]
|
|
172
|
+
first_name = first_tc.get("function", {}).get("name", "unknown")
|
|
173
|
+
ignored_names = [
|
|
174
|
+
tc.get("function", {}).get("name", "unknown") for tc in raw_tool_calls[1:]
|
|
175
|
+
]
|
|
176
|
+
logger.warning(
|
|
177
|
+
"Multiple tool_calls in additional_kwargs (%d), using only first one: %s. Ignored: %s",
|
|
178
|
+
len(raw_tool_calls),
|
|
179
|
+
first_name,
|
|
180
|
+
ignored_names,
|
|
181
|
+
)
|
|
182
|
+
raw_tool_calls = raw_tool_calls[:1]
|
|
183
|
+
|
|
168
184
|
repaired_tool_calls = []
|
|
169
185
|
for tc in raw_tool_calls:
|
|
170
186
|
func = tc.get("function", {})
|
|
@@ -316,8 +332,7 @@ def create_handle_empty_response_middleware(wrap_model_call):
|
|
|
316
332
|
summary_todo_completed = all_todos_completed and last_todo_is_summary
|
|
317
333
|
|
|
318
334
|
if not summary_todo_completed and any(
|
|
319
|
-
t.get("status") == "completed"
|
|
320
|
-
and "작업 요약" in t.get("content", "")
|
|
335
|
+
t.get("status") == "completed" and "작업 요약" in t.get("content", "")
|
|
321
336
|
for t in todos
|
|
322
337
|
):
|
|
323
338
|
logger.debug(
|
|
@@ -439,15 +454,16 @@ def create_handle_empty_response_middleware(wrap_model_call):
|
|
|
439
454
|
content = " ".join(str(p) for p in content)
|
|
440
455
|
|
|
441
456
|
# Check if content contains summary JSON pattern
|
|
442
|
-
has_summary_pattern = (
|
|
443
|
-
'"
|
|
444
|
-
)
|
|
457
|
+
has_summary_pattern = (
|
|
458
|
+
'"summary"' in content or "'summary'" in content
|
|
459
|
+
) and ('"next_items"' in content or "'next_items'" in content)
|
|
445
460
|
|
|
446
461
|
if has_summary_pattern:
|
|
447
462
|
# Check if pending todos exist - if so, don't force complete
|
|
448
463
|
current_todos = request.state.get("todos", [])
|
|
449
464
|
pending_todos = [
|
|
450
|
-
t
|
|
465
|
+
t
|
|
466
|
+
for t in current_todos
|
|
451
467
|
if isinstance(t, dict) and t.get("status") == "pending"
|
|
452
468
|
]
|
|
453
469
|
if pending_todos:
|
|
@@ -463,7 +479,12 @@ def create_handle_empty_response_middleware(wrap_model_call):
|
|
|
463
479
|
try:
|
|
464
480
|
# Try to find JSON object containing summary
|
|
465
481
|
import re
|
|
466
|
-
|
|
482
|
+
|
|
483
|
+
json_match = re.search(
|
|
484
|
+
r'\{[^{}]*"summary"[^{}]*"next_items"[^{}]*\}',
|
|
485
|
+
content,
|
|
486
|
+
re.DOTALL,
|
|
487
|
+
)
|
|
467
488
|
if json_match:
|
|
468
489
|
repaired_summary = repair_json(
|
|
469
490
|
json_match.group(), return_objects=True
|
|
@@ -503,7 +524,9 @@ def create_handle_empty_response_middleware(wrap_model_call):
|
|
|
503
524
|
)
|
|
504
525
|
return response
|
|
505
526
|
except Exception as e:
|
|
506
|
-
logger.debug(
|
|
527
|
+
logger.debug(
|
|
528
|
+
f"Failed to extract summary JSON from mixed content: {e}"
|
|
529
|
+
)
|
|
507
530
|
|
|
508
531
|
# Fallback: accept as-is if repair failed but looks like summary
|
|
509
532
|
logger.info(
|
|
@@ -543,7 +566,9 @@ def create_handle_empty_response_middleware(wrap_model_call):
|
|
|
543
566
|
is_summary_todo = any(
|
|
544
567
|
kw in current_todo.get("content", "") for kw in summary_keywords
|
|
545
568
|
)
|
|
546
|
-
if is_summary_todo and (
|
|
569
|
+
if is_summary_todo and (
|
|
570
|
+
'"summary"' in content or "'summary'" in content
|
|
571
|
+
):
|
|
547
572
|
# This is a summary todo with summary content - accept it
|
|
548
573
|
logger.info(
|
|
549
574
|
"Summary todo with summary content detected - accepting"
|
|
@@ -785,7 +810,7 @@ def _create_synthetic_completion(request, response_message, has_content):
|
|
|
785
810
|
logger.warning(
|
|
786
811
|
"Force-completing %d pending todos that were never started: %s",
|
|
787
812
|
pending_count,
|
|
788
|
-
[t.get("content") for t in todos if t.get("status") == "pending"]
|
|
813
|
+
[t.get("content") for t in todos if t.get("status") == "pending"],
|
|
789
814
|
)
|
|
790
815
|
|
|
791
816
|
# Mark all todos as completed
|
|
@@ -858,6 +883,21 @@ def create_limit_tool_calls_middleware(wrap_model_call):
|
|
|
858
883
|
)
|
|
859
884
|
msg.tool_calls = [tool_calls[0]]
|
|
860
885
|
|
|
886
|
+
# Remove additional_kwargs["tool_calls"] entirely when
|
|
887
|
+
# msg.tool_calls exists. ChatOpenAI duplicates tool_calls
|
|
888
|
+
# into additional_kwargs, and leftover entries pollute the
|
|
889
|
+
# conversation context - LLM sees them and assumes all
|
|
890
|
+
# listed tool calls were executed.
|
|
891
|
+
additional_kwargs = getattr(msg, "additional_kwargs", {})
|
|
892
|
+
if msg.tool_calls and additional_kwargs.get("tool_calls"):
|
|
893
|
+
removed_count = len(additional_kwargs["tool_calls"])
|
|
894
|
+
del additional_kwargs["tool_calls"]
|
|
895
|
+
logger.info(
|
|
896
|
+
"Removed %d tool_calls from additional_kwargs "
|
|
897
|
+
"(canonical source: msg.tool_calls)",
|
|
898
|
+
removed_count,
|
|
899
|
+
)
|
|
900
|
+
|
|
861
901
|
return response
|
|
862
902
|
|
|
863
903
|
return limit_tool_calls_to_one
|
|
@@ -1026,29 +1066,45 @@ def create_normalize_tool_args_middleware(wrap_model_call, tools=None):
|
|
|
1026
1066
|
|
|
1027
1067
|
# Validate: "작업 요약 및 다음 단계 제시" cannot be in_progress if pending todos exist
|
|
1028
1068
|
# This prevents LLM from skipping pending tasks
|
|
1029
|
-
summary_keywords = [
|
|
1069
|
+
summary_keywords = [
|
|
1070
|
+
"작업 요약",
|
|
1071
|
+
"다음 단계 제시",
|
|
1072
|
+
]
|
|
1030
1073
|
for i, todo in enumerate(todos):
|
|
1031
1074
|
if not isinstance(todo, dict):
|
|
1032
1075
|
continue
|
|
1033
1076
|
content = todo.get("content", "")
|
|
1034
|
-
is_summary_todo = any(
|
|
1077
|
+
is_summary_todo = any(
|
|
1078
|
+
kw in content for kw in summary_keywords
|
|
1079
|
+
)
|
|
1035
1080
|
|
|
1036
|
-
if
|
|
1081
|
+
if (
|
|
1082
|
+
is_summary_todo
|
|
1083
|
+
and todo.get("status") == "in_progress"
|
|
1084
|
+
):
|
|
1037
1085
|
# Check if there are pending todos before this one
|
|
1038
1086
|
pending_before = [
|
|
1039
|
-
t
|
|
1040
|
-
|
|
1087
|
+
t
|
|
1088
|
+
for t in todos[:i]
|
|
1089
|
+
if isinstance(t, dict)
|
|
1090
|
+
and t.get("status") == "pending"
|
|
1041
1091
|
]
|
|
1042
1092
|
if pending_before:
|
|
1043
1093
|
# Revert summary todo to pending
|
|
1044
1094
|
todo["status"] = "pending"
|
|
1045
1095
|
# Set the first pending todo to in_progress
|
|
1046
1096
|
for t in todos:
|
|
1047
|
-
if
|
|
1097
|
+
if (
|
|
1098
|
+
isinstance(t, dict)
|
|
1099
|
+
and t.get("status")
|
|
1100
|
+
== "pending"
|
|
1101
|
+
):
|
|
1048
1102
|
t["status"] = "in_progress"
|
|
1049
1103
|
logger.warning(
|
|
1050
1104
|
"Reverted summary todo to pending, set '%s' to in_progress (pending todos exist)",
|
|
1051
|
-
t.get("content", "")[
|
|
1105
|
+
t.get("content", "")[
|
|
1106
|
+
:30
|
|
1107
|
+
],
|
|
1052
1108
|
)
|
|
1053
1109
|
break
|
|
1054
1110
|
break
|
|
@@ -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
|
]
|