hdsp-jupyter-extension 2.0.8__py3-none-any.whl → 2.0.11__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/notebook_generator.py +4 -4
- agent_server/core/rag_manager.py +12 -3
- agent_server/core/retriever.py +2 -1
- agent_server/core/vllm_embedding_service.py +8 -5
- agent_server/langchain/ARCHITECTURE.md +7 -51
- agent_server/langchain/agent.py +31 -20
- agent_server/langchain/custom_middleware.py +234 -31
- agent_server/langchain/hitl_config.py +5 -8
- agent_server/langchain/logging_utils.py +7 -7
- agent_server/langchain/prompts.py +106 -120
- agent_server/langchain/tools/__init__.py +1 -10
- agent_server/langchain/tools/file_tools.py +9 -61
- agent_server/langchain/tools/jupyter_tools.py +0 -1
- agent_server/langchain/tools/lsp_tools.py +8 -8
- agent_server/langchain/tools/resource_tools.py +12 -12
- agent_server/langchain/tools/search_tools.py +3 -158
- agent_server/prompts/file_action_prompts.py +8 -8
- agent_server/routers/langchain_agent.py +200 -125
- hdsp_agent_core/__init__.py +46 -47
- hdsp_agent_core/factory.py +6 -10
- hdsp_agent_core/interfaces.py +4 -2
- hdsp_agent_core/knowledge/__init__.py +5 -5
- hdsp_agent_core/knowledge/chunking.py +87 -61
- hdsp_agent_core/knowledge/loader.py +103 -101
- hdsp_agent_core/llm/service.py +192 -107
- hdsp_agent_core/managers/config_manager.py +16 -22
- hdsp_agent_core/managers/session_manager.py +5 -4
- hdsp_agent_core/models/__init__.py +12 -12
- hdsp_agent_core/models/agent.py +15 -8
- hdsp_agent_core/models/common.py +1 -2
- hdsp_agent_core/models/rag.py +48 -111
- hdsp_agent_core/prompts/__init__.py +12 -12
- hdsp_agent_core/prompts/cell_action_prompts.py +9 -7
- hdsp_agent_core/services/agent_service.py +10 -8
- hdsp_agent_core/services/chat_service.py +10 -6
- hdsp_agent_core/services/rag_service.py +3 -6
- hdsp_agent_core/tests/conftest.py +4 -1
- hdsp_agent_core/tests/test_factory.py +2 -2
- hdsp_agent_core/tests/test_services.py +12 -19
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js +93 -4
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js +153 -130
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js → hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js +6 -6
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/METADATA +1 -3
- hdsp_jupyter_extension-2.0.11.dist-info/RECORD +144 -0
- jupyter_ext/__init__.py +21 -11
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +69 -50
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +2 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.8740a527757068814573.js → frontend_styles_index_js.2d9fb488c82498c45c2d.js} +93 -4
- jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.e4ff4b5779b5e049f84c.js → lib_index_js.58c1e128ba0b76f41f04.js} +153 -130
- jupyter_ext/labextension/static/lib_index_js.58c1e128ba0b76f41f04.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.020cdb0b864cfaa4e41e.js → remoteEntry.9da31d1134a53b0c4af5.js} +6 -6
- jupyter_ext/labextension/static/remoteEntry.9da31d1134a53b0c4af5.js.map +1 -0
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
- hdsp_jupyter_extension-2.0.8.dist-info/RECORD +0 -144
- jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +0 -1
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.data → hdsp_jupyter_extension-2.0.11.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.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.8.dist-info → hdsp_jupyter_extension-2.0.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -702,6 +702,27 @@ async def stream_agent(request: AgentRequest):
|
|
|
702
702
|
"event": "todos",
|
|
703
703
|
"data": json.dumps({"todos": todos}),
|
|
704
704
|
}
|
|
705
|
+
# Check if all todos are completed - auto terminate
|
|
706
|
+
all_completed = all(
|
|
707
|
+
t.get("status") == "completed" for t in todos
|
|
708
|
+
)
|
|
709
|
+
if all_completed and len(todos) > 0:
|
|
710
|
+
logger.info(
|
|
711
|
+
"All %d todos completed, auto-terminating agent",
|
|
712
|
+
len(todos),
|
|
713
|
+
)
|
|
714
|
+
yield {
|
|
715
|
+
"event": "debug_clear",
|
|
716
|
+
"data": json.dumps({}),
|
|
717
|
+
}
|
|
718
|
+
yield {
|
|
719
|
+
"event": "done",
|
|
720
|
+
"data": json.dumps(
|
|
721
|
+
{"reason": "all_todos_completed"}
|
|
722
|
+
),
|
|
723
|
+
}
|
|
724
|
+
return # Exit the generator
|
|
725
|
+
|
|
705
726
|
tool_name = getattr(last_message, "name", "") or ""
|
|
706
727
|
logger.info(
|
|
707
728
|
"SimpleAgent ToolMessage name attribute: %s", tool_name
|
|
@@ -727,7 +748,7 @@ async def stream_agent(request: AgentRequest):
|
|
|
727
748
|
final_answer = tool_result.get(
|
|
728
749
|
"answer"
|
|
729
750
|
) or tool_result.get("parameters", {}).get("answer")
|
|
730
|
-
|
|
751
|
+
|
|
731
752
|
# Check for next_items in answer field (LLM may put JSON here)
|
|
732
753
|
if final_answer:
|
|
733
754
|
try:
|
|
@@ -735,25 +756,41 @@ async def stream_agent(request: AgentRequest):
|
|
|
735
756
|
if "next_items" in answer_json:
|
|
736
757
|
next_items_block = f"\n\n```json\n{json.dumps(answer_json, ensure_ascii=False, indent=2)}\n```"
|
|
737
758
|
# Get summary for the main text
|
|
738
|
-
summary_text =
|
|
739
|
-
"summary"
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
759
|
+
summary_text = (
|
|
760
|
+
tool_result.get("summary")
|
|
761
|
+
or tool_result.get(
|
|
762
|
+
"parameters", {}
|
|
763
|
+
).get("summary")
|
|
764
|
+
or ""
|
|
765
|
+
)
|
|
766
|
+
final_answer = (
|
|
767
|
+
summary_text + next_items_block
|
|
768
|
+
)
|
|
769
|
+
logger.info(
|
|
770
|
+
"Extracted next_items from answer field"
|
|
771
|
+
)
|
|
743
772
|
except (json.JSONDecodeError, TypeError):
|
|
744
773
|
pass
|
|
745
|
-
|
|
774
|
+
|
|
746
775
|
# Check for next_items in summary field (Gemini puts JSON here)
|
|
747
776
|
summary = tool_result.get(
|
|
748
777
|
"summary"
|
|
749
|
-
) or tool_result.get("parameters", {}).get(
|
|
750
|
-
|
|
778
|
+
) or tool_result.get("parameters", {}).get(
|
|
779
|
+
"summary"
|
|
780
|
+
)
|
|
781
|
+
if summary and "next_items" not in (
|
|
782
|
+
final_answer or ""
|
|
783
|
+
):
|
|
751
784
|
try:
|
|
752
785
|
summary_json = json.loads(summary)
|
|
753
786
|
if "next_items" in summary_json:
|
|
754
787
|
next_items_block = f"\n\n```json\n{json.dumps(summary_json, ensure_ascii=False, indent=2)}\n```"
|
|
755
|
-
final_answer = (
|
|
756
|
-
|
|
788
|
+
final_answer = (
|
|
789
|
+
final_answer or ""
|
|
790
|
+
) + next_items_block
|
|
791
|
+
logger.info(
|
|
792
|
+
"Extracted next_items from summary field"
|
|
793
|
+
)
|
|
757
794
|
except (json.JSONDecodeError, TypeError):
|
|
758
795
|
pass
|
|
759
796
|
if final_answer:
|
|
@@ -872,19 +909,32 @@ async def stream_agent(request: AgentRequest):
|
|
|
872
909
|
"event": "todos",
|
|
873
910
|
"data": json.dumps({"todos": todos}),
|
|
874
911
|
}
|
|
912
|
+
# Check if all todos are completed - terminate early
|
|
913
|
+
all_completed = all(
|
|
914
|
+
t.get("status") == "completed" for t in todos
|
|
915
|
+
)
|
|
916
|
+
if all_completed and len(todos) > 0:
|
|
917
|
+
logger.info(
|
|
918
|
+
"All %d todos completed in AIMessage tool_calls, auto-terminating",
|
|
919
|
+
len(todos),
|
|
920
|
+
)
|
|
921
|
+
yield {
|
|
922
|
+
"event": "debug_clear",
|
|
923
|
+
"data": json.dumps({}),
|
|
924
|
+
}
|
|
925
|
+
yield {
|
|
926
|
+
"event": "done",
|
|
927
|
+
"data": json.dumps(
|
|
928
|
+
{"reason": "all_todos_completed"}
|
|
929
|
+
),
|
|
930
|
+
}
|
|
931
|
+
return # Exit before executing more tool calls
|
|
875
932
|
for tool_call in tool_calls:
|
|
876
933
|
tool_name = tool_call.get("name", "unknown")
|
|
877
934
|
tool_args = tool_call.get("args", {})
|
|
878
935
|
|
|
879
936
|
# Create detailed status message for search tools
|
|
880
937
|
if tool_name in (
|
|
881
|
-
"search_workspace_tool",
|
|
882
|
-
"search_workspace",
|
|
883
|
-
):
|
|
884
|
-
pattern = tool_args.get("pattern", "")
|
|
885
|
-
path = tool_args.get("path", ".")
|
|
886
|
-
status_msg = f"🔍 검색 실행: grep/rg '{pattern}' in {path}"
|
|
887
|
-
elif tool_name in (
|
|
888
938
|
"search_notebook_cells_tool",
|
|
889
939
|
"search_notebook_cells",
|
|
890
940
|
):
|
|
@@ -950,34 +1000,6 @@ async def stream_agent(request: AgentRequest):
|
|
|
950
1000
|
}
|
|
951
1001
|
),
|
|
952
1002
|
}
|
|
953
|
-
elif tool_name in (
|
|
954
|
-
"search_workspace_tool",
|
|
955
|
-
"search_workspace",
|
|
956
|
-
):
|
|
957
|
-
# Search workspace - emit tool_call for client-side execution
|
|
958
|
-
produced_output = True
|
|
959
|
-
yield {
|
|
960
|
-
"event": "tool_call",
|
|
961
|
-
"data": json.dumps(
|
|
962
|
-
{
|
|
963
|
-
"tool": "search_workspace",
|
|
964
|
-
"pattern": tool_args.get(
|
|
965
|
-
"pattern", ""
|
|
966
|
-
),
|
|
967
|
-
"file_types": tool_args.get(
|
|
968
|
-
"file_types",
|
|
969
|
-
["*.py", "*.ipynb"],
|
|
970
|
-
),
|
|
971
|
-
"path": tool_args.get("path", "."),
|
|
972
|
-
"max_results": tool_args.get(
|
|
973
|
-
"max_results", 50
|
|
974
|
-
),
|
|
975
|
-
"case_sensitive": tool_args.get(
|
|
976
|
-
"case_sensitive", False
|
|
977
|
-
),
|
|
978
|
-
}
|
|
979
|
-
),
|
|
980
|
-
}
|
|
981
1003
|
elif tool_name in (
|
|
982
1004
|
"search_notebook_cells_tool",
|
|
983
1005
|
"search_notebook_cells",
|
|
@@ -1030,16 +1052,31 @@ async def stream_agent(request: AgentRequest):
|
|
|
1030
1052
|
content = "\n".join(text_parts)
|
|
1031
1053
|
|
|
1032
1054
|
# Filter out raw JSON tool responses
|
|
1055
|
+
content_stripped = content.strip() if content else ""
|
|
1056
|
+
|
|
1057
|
+
# Filter out tool call JSON (but allow summary/next_items JSON for frontend rendering)
|
|
1058
|
+
is_json_tool_response = (
|
|
1059
|
+
content_stripped.startswith('{"tool":')
|
|
1060
|
+
or content_stripped.startswith('{ "tool":')
|
|
1061
|
+
or content_stripped.startswith('{"tool" :')
|
|
1062
|
+
or content_stripped.startswith('{"status":')
|
|
1063
|
+
or '"pending_execution"' in content
|
|
1064
|
+
or '"status": "complete"' in content
|
|
1065
|
+
or (
|
|
1066
|
+
'"tool"' in content
|
|
1067
|
+
and '"write_todos"' in content
|
|
1068
|
+
)
|
|
1069
|
+
or (
|
|
1070
|
+
'"tool"' in content
|
|
1071
|
+
and '"arguments"' in content
|
|
1072
|
+
and content_stripped.startswith("{")
|
|
1073
|
+
)
|
|
1074
|
+
)
|
|
1033
1075
|
if (
|
|
1034
1076
|
content
|
|
1035
1077
|
and isinstance(content, str)
|
|
1036
1078
|
and not has_final_answer_tool
|
|
1037
|
-
and not
|
|
1038
|
-
content.strip().startswith('{"tool":')
|
|
1039
|
-
or content.strip().startswith('{"status":')
|
|
1040
|
-
or '"pending_execution"' in content
|
|
1041
|
-
or '"status": "complete"' in content
|
|
1042
|
-
)
|
|
1079
|
+
and not is_json_tool_response
|
|
1043
1080
|
):
|
|
1044
1081
|
# Check if we've already emitted this content (prevents duplicates)
|
|
1045
1082
|
content_hash = hash(content)
|
|
@@ -1161,7 +1198,7 @@ async def stream_agent(request: AgentRequest):
|
|
|
1161
1198
|
content=(
|
|
1162
1199
|
"You MUST respond with a valid tool call. "
|
|
1163
1200
|
"Available tools: jupyter_cell_tool (for Python code), markdown_tool (for text), "
|
|
1164
|
-
"
|
|
1201
|
+
"execute_command_tool (to search files with find/grep), read_file_tool (to read files). "
|
|
1165
1202
|
"Choose the most appropriate tool and provide valid JSON arguments."
|
|
1166
1203
|
)
|
|
1167
1204
|
),
|
|
@@ -1280,11 +1317,7 @@ async def stream_agent(request: AgentRequest):
|
|
|
1280
1317
|
}
|
|
1281
1318
|
),
|
|
1282
1319
|
}
|
|
1283
|
-
elif tool_name
|
|
1284
|
-
"read_file_tool",
|
|
1285
|
-
"list_files_tool",
|
|
1286
|
-
"search_workspace_tool",
|
|
1287
|
-
):
|
|
1320
|
+
elif tool_name == "read_file_tool":
|
|
1288
1321
|
# For file operations, generate code with the LLM
|
|
1289
1322
|
logger.info(
|
|
1290
1323
|
"Fallback: Generating code for %s via LLM",
|
|
@@ -1447,11 +1480,13 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1447
1480
|
)
|
|
1448
1481
|
yield {
|
|
1449
1482
|
"event": "error",
|
|
1450
|
-
"data": json.dumps(
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1483
|
+
"data": json.dumps(
|
|
1484
|
+
{
|
|
1485
|
+
"error": "Session expired or not found",
|
|
1486
|
+
"code": "CHECKPOINT_NOT_FOUND",
|
|
1487
|
+
"message": "이전 세션을 찾을 수 없습니다. 서버가 재시작되었거나 세션이 만료되었습니다. 새로운 대화를 시작해주세요.",
|
|
1488
|
+
}
|
|
1489
|
+
),
|
|
1455
1490
|
}
|
|
1456
1491
|
return
|
|
1457
1492
|
|
|
@@ -1471,7 +1506,9 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1471
1506
|
len(_simple_agent_instances),
|
|
1472
1507
|
)
|
|
1473
1508
|
else:
|
|
1474
|
-
logger.info(
|
|
1509
|
+
logger.info(
|
|
1510
|
+
"Resume: Creating new agent for key %s", agent_cache_key[:8]
|
|
1511
|
+
)
|
|
1475
1512
|
agent = create_simple_chat_agent(
|
|
1476
1513
|
llm_config=config_dict,
|
|
1477
1514
|
workspace_root=resolved_workspace_root,
|
|
@@ -1654,6 +1691,27 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1654
1691
|
"event": "todos",
|
|
1655
1692
|
"data": json.dumps({"todos": todos}),
|
|
1656
1693
|
}
|
|
1694
|
+
# Check if all todos are completed - auto terminate
|
|
1695
|
+
all_completed = all(
|
|
1696
|
+
t.get("status") == "completed" for t in todos
|
|
1697
|
+
)
|
|
1698
|
+
if all_completed and len(todos) > 0:
|
|
1699
|
+
logger.info(
|
|
1700
|
+
"Resume: All %d todos completed, auto-terminating agent",
|
|
1701
|
+
len(todos),
|
|
1702
|
+
)
|
|
1703
|
+
yield {
|
|
1704
|
+
"event": "debug_clear",
|
|
1705
|
+
"data": json.dumps({}),
|
|
1706
|
+
}
|
|
1707
|
+
yield {
|
|
1708
|
+
"event": "done",
|
|
1709
|
+
"data": json.dumps(
|
|
1710
|
+
{"reason": "all_todos_completed"}
|
|
1711
|
+
),
|
|
1712
|
+
}
|
|
1713
|
+
return # Exit the generator
|
|
1714
|
+
|
|
1657
1715
|
tool_name = getattr(last_message, "name", "") or ""
|
|
1658
1716
|
logger.info(
|
|
1659
1717
|
"Resume ToolMessage name attribute: %s", tool_name
|
|
@@ -1677,7 +1735,7 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1677
1735
|
final_answer = tool_result.get(
|
|
1678
1736
|
"answer"
|
|
1679
1737
|
) or tool_result.get("parameters", {}).get("answer")
|
|
1680
|
-
|
|
1738
|
+
|
|
1681
1739
|
# Check for next_items in answer field (LLM may put JSON here)
|
|
1682
1740
|
if final_answer:
|
|
1683
1741
|
try:
|
|
@@ -1685,25 +1743,41 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1685
1743
|
if "next_items" in answer_json:
|
|
1686
1744
|
next_items_block = f"\n\n```json\n{json.dumps(answer_json, ensure_ascii=False, indent=2)}\n```"
|
|
1687
1745
|
# Get summary for the main text
|
|
1688
|
-
summary_text =
|
|
1689
|
-
"summary"
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1746
|
+
summary_text = (
|
|
1747
|
+
tool_result.get("summary")
|
|
1748
|
+
or tool_result.get(
|
|
1749
|
+
"parameters", {}
|
|
1750
|
+
).get("summary")
|
|
1751
|
+
or ""
|
|
1752
|
+
)
|
|
1753
|
+
final_answer = (
|
|
1754
|
+
summary_text + next_items_block
|
|
1755
|
+
)
|
|
1756
|
+
logger.info(
|
|
1757
|
+
"Resume: Extracted next_items from answer field"
|
|
1758
|
+
)
|
|
1693
1759
|
except (json.JSONDecodeError, TypeError):
|
|
1694
1760
|
pass
|
|
1695
|
-
|
|
1761
|
+
|
|
1696
1762
|
# Check for next_items in summary field (Gemini puts JSON here)
|
|
1697
1763
|
summary = tool_result.get(
|
|
1698
1764
|
"summary"
|
|
1699
|
-
) or tool_result.get("parameters", {}).get(
|
|
1700
|
-
|
|
1765
|
+
) or tool_result.get("parameters", {}).get(
|
|
1766
|
+
"summary"
|
|
1767
|
+
)
|
|
1768
|
+
if summary and "next_items" not in (
|
|
1769
|
+
final_answer or ""
|
|
1770
|
+
):
|
|
1701
1771
|
try:
|
|
1702
1772
|
summary_json = json.loads(summary)
|
|
1703
1773
|
if "next_items" in summary_json:
|
|
1704
1774
|
next_items_block = f"\n\n```json\n{json.dumps(summary_json, ensure_ascii=False, indent=2)}\n```"
|
|
1705
|
-
final_answer = (
|
|
1706
|
-
|
|
1775
|
+
final_answer = (
|
|
1776
|
+
final_answer or ""
|
|
1777
|
+
) + next_items_block
|
|
1778
|
+
logger.info(
|
|
1779
|
+
"Resume: Extracted next_items from summary field"
|
|
1780
|
+
)
|
|
1707
1781
|
except (json.JSONDecodeError, TypeError):
|
|
1708
1782
|
pass
|
|
1709
1783
|
if final_answer:
|
|
@@ -1775,16 +1849,27 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1775
1849
|
content = "\n".join(text_parts)
|
|
1776
1850
|
|
|
1777
1851
|
# Filter out raw JSON tool responses
|
|
1852
|
+
content_stripped = content.strip() if content else ""
|
|
1853
|
+
# Filter out tool call JSON (but allow summary/next_items JSON for frontend rendering)
|
|
1854
|
+
is_json_tool_response = (
|
|
1855
|
+
content_stripped.startswith('{"tool":')
|
|
1856
|
+
or content_stripped.startswith('{ "tool":')
|
|
1857
|
+
or content_stripped.startswith('{"tool" :')
|
|
1858
|
+
or content_stripped.startswith('{"status":')
|
|
1859
|
+
or '"pending_execution"' in content
|
|
1860
|
+
or '"status": "complete"' in content
|
|
1861
|
+
or ('"tool"' in content and '"write_todos"' in content)
|
|
1862
|
+
or (
|
|
1863
|
+
'"tool"' in content
|
|
1864
|
+
and '"arguments"' in content
|
|
1865
|
+
and content_stripped.startswith("{")
|
|
1866
|
+
)
|
|
1867
|
+
)
|
|
1778
1868
|
if (
|
|
1779
1869
|
content
|
|
1780
1870
|
and isinstance(content, str)
|
|
1781
1871
|
and not has_final_answer_tool
|
|
1782
|
-
and not
|
|
1783
|
-
content.strip().startswith('{"tool":')
|
|
1784
|
-
or content.strip().startswith('{"status":')
|
|
1785
|
-
or '"pending_execution"' in content
|
|
1786
|
-
or '"status": "complete"' in content
|
|
1787
|
-
)
|
|
1872
|
+
and not is_json_tool_response
|
|
1788
1873
|
):
|
|
1789
1874
|
# Check if we've already emitted this content (prevents duplicates)
|
|
1790
1875
|
content_hash = hash(content)
|
|
@@ -1835,6 +1920,26 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1835
1920
|
"event": "todos",
|
|
1836
1921
|
"data": json.dumps({"todos": todos}),
|
|
1837
1922
|
}
|
|
1923
|
+
# Check if all todos are completed - terminate early
|
|
1924
|
+
all_completed = all(
|
|
1925
|
+
t.get("status") == "completed" for t in todos
|
|
1926
|
+
)
|
|
1927
|
+
if all_completed and len(todos) > 0:
|
|
1928
|
+
logger.info(
|
|
1929
|
+
"Resume: All %d todos completed in AIMessage tool_calls, auto-terminating",
|
|
1930
|
+
len(todos),
|
|
1931
|
+
)
|
|
1932
|
+
yield {
|
|
1933
|
+
"event": "debug_clear",
|
|
1934
|
+
"data": json.dumps({}),
|
|
1935
|
+
}
|
|
1936
|
+
yield {
|
|
1937
|
+
"event": "done",
|
|
1938
|
+
"data": json.dumps(
|
|
1939
|
+
{"reason": "all_todos_completed"}
|
|
1940
|
+
),
|
|
1941
|
+
}
|
|
1942
|
+
return # Exit before executing more tool calls
|
|
1838
1943
|
|
|
1839
1944
|
# Process tool calls
|
|
1840
1945
|
for tool_call in new_tool_calls:
|
|
@@ -1850,13 +1955,6 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1850
1955
|
|
|
1851
1956
|
# Create detailed status message for search tools
|
|
1852
1957
|
if tool_name in (
|
|
1853
|
-
"search_workspace_tool",
|
|
1854
|
-
"search_workspace",
|
|
1855
|
-
):
|
|
1856
|
-
pattern = tool_args.get("pattern", "")
|
|
1857
|
-
path = tool_args.get("path", ".")
|
|
1858
|
-
status_msg = f"🔍 검색 실행: grep/rg '{pattern}' in {path}"
|
|
1859
|
-
elif tool_name in (
|
|
1860
1958
|
"search_notebook_cells_tool",
|
|
1861
1959
|
"search_notebook_cells",
|
|
1862
1960
|
):
|
|
@@ -1914,33 +2012,6 @@ async def resume_agent(request: ResumeRequest):
|
|
|
1914
2012
|
}
|
|
1915
2013
|
),
|
|
1916
2014
|
}
|
|
1917
|
-
elif tool_name in (
|
|
1918
|
-
"search_workspace_tool",
|
|
1919
|
-
"search_workspace",
|
|
1920
|
-
):
|
|
1921
|
-
# Search workspace - emit tool_call for client-side execution
|
|
1922
|
-
yield {
|
|
1923
|
-
"event": "tool_call",
|
|
1924
|
-
"data": json.dumps(
|
|
1925
|
-
{
|
|
1926
|
-
"tool": "search_workspace",
|
|
1927
|
-
"pattern": tool_args.get(
|
|
1928
|
-
"pattern", ""
|
|
1929
|
-
),
|
|
1930
|
-
"file_types": tool_args.get(
|
|
1931
|
-
"file_types",
|
|
1932
|
-
["*.py", "*.ipynb"],
|
|
1933
|
-
),
|
|
1934
|
-
"path": tool_args.get("path", "."),
|
|
1935
|
-
"max_results": tool_args.get(
|
|
1936
|
-
"max_results", 50
|
|
1937
|
-
),
|
|
1938
|
-
"case_sensitive": tool_args.get(
|
|
1939
|
-
"case_sensitive", False
|
|
1940
|
-
),
|
|
1941
|
-
}
|
|
1942
|
-
),
|
|
1943
|
-
}
|
|
1944
2015
|
elif tool_name in (
|
|
1945
2016
|
"search_notebook_cells_tool",
|
|
1946
2017
|
"search_notebook_cells",
|
|
@@ -2057,20 +2128,24 @@ async def resume_agent(request: ResumeRequest):
|
|
|
2057
2128
|
)
|
|
2058
2129
|
yield {
|
|
2059
2130
|
"event": "error",
|
|
2060
|
-
"data": json.dumps(
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2131
|
+
"data": json.dumps(
|
|
2132
|
+
{
|
|
2133
|
+
"error": "Session state lost",
|
|
2134
|
+
"code": "CONTENTS_NOT_SPECIFIED",
|
|
2135
|
+
"error_type": type(e).__name__,
|
|
2136
|
+
"message": "세션 상태가 손실되었습니다. 서버가 재시작되었거나 세션이 만료되었습니다. 새로운 대화를 시작해주세요.",
|
|
2137
|
+
}
|
|
2138
|
+
),
|
|
2066
2139
|
}
|
|
2067
2140
|
else:
|
|
2068
2141
|
yield {
|
|
2069
2142
|
"event": "error",
|
|
2070
|
-
"data": json.dumps(
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2143
|
+
"data": json.dumps(
|
|
2144
|
+
{
|
|
2145
|
+
"error": error_msg,
|
|
2146
|
+
"error_type": type(e).__name__,
|
|
2147
|
+
}
|
|
2148
|
+
),
|
|
2074
2149
|
}
|
|
2075
2150
|
|
|
2076
2151
|
return EventSourceResponse(event_generator())
|
hdsp_agent_core/__init__.py
CHANGED
|
@@ -15,77 +15,76 @@ Modules:
|
|
|
15
15
|
__version__ = "1.0.0"
|
|
16
16
|
|
|
17
17
|
# Models
|
|
18
|
+
# Knowledge
|
|
19
|
+
from hdsp_agent_core.knowledge import (
|
|
20
|
+
LIBRARY_DESCRIPTIONS,
|
|
21
|
+
DocumentChunker,
|
|
22
|
+
KnowledgeBase,
|
|
23
|
+
KnowledgeLoader,
|
|
24
|
+
LibraryDetector,
|
|
25
|
+
chunk_file,
|
|
26
|
+
get_knowledge_base,
|
|
27
|
+
get_knowledge_loader,
|
|
28
|
+
get_library_detector,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# LLM
|
|
32
|
+
from hdsp_agent_core.llm import (
|
|
33
|
+
LLMService,
|
|
34
|
+
call_llm,
|
|
35
|
+
call_llm_stream,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Managers
|
|
39
|
+
from hdsp_agent_core.managers import (
|
|
40
|
+
ConfigManager,
|
|
41
|
+
SessionManager,
|
|
42
|
+
get_config_manager,
|
|
43
|
+
get_session_manager,
|
|
44
|
+
)
|
|
18
45
|
from hdsp_agent_core.models import (
|
|
19
46
|
# Common
|
|
20
47
|
APIResponse,
|
|
48
|
+
# Chat
|
|
49
|
+
ChatRequest,
|
|
50
|
+
ChatResponse,
|
|
51
|
+
# RAG
|
|
52
|
+
ChunkingConfig,
|
|
53
|
+
EmbeddingConfig,
|
|
21
54
|
ErrorInfo,
|
|
55
|
+
# Agent
|
|
56
|
+
ExecutionPlan,
|
|
22
57
|
GeminiConfig,
|
|
58
|
+
IndexStatusResponse,
|
|
23
59
|
LLMConfig,
|
|
24
60
|
NotebookContext,
|
|
25
61
|
OpenAIConfig,
|
|
26
|
-
ToolCall,
|
|
27
|
-
VLLMConfig,
|
|
28
|
-
# Agent
|
|
29
|
-
ExecutionPlan,
|
|
30
62
|
PlanRequest,
|
|
31
63
|
PlanResponse,
|
|
32
64
|
PlanStep,
|
|
33
|
-
RefineRequest,
|
|
34
|
-
RefineResponse,
|
|
35
|
-
ReplanRequest,
|
|
36
|
-
ReplanResponse,
|
|
37
|
-
ValidationIssue,
|
|
38
|
-
# Chat
|
|
39
|
-
ChatRequest,
|
|
40
|
-
ChatResponse,
|
|
41
|
-
StreamChunk,
|
|
42
|
-
# RAG
|
|
43
|
-
ChunkingConfig,
|
|
44
|
-
EmbeddingConfig,
|
|
45
|
-
IndexStatusResponse,
|
|
46
65
|
QdrantConfig,
|
|
47
66
|
RAGConfig,
|
|
67
|
+
RefineRequest,
|
|
68
|
+
RefineResponse,
|
|
48
69
|
ReindexRequest,
|
|
49
70
|
ReindexResponse,
|
|
71
|
+
ReplanRequest,
|
|
72
|
+
ReplanResponse,
|
|
50
73
|
SearchRequest,
|
|
51
74
|
SearchResponse,
|
|
75
|
+
StreamChunk,
|
|
76
|
+
ToolCall,
|
|
77
|
+
ValidationIssue,
|
|
78
|
+
VLLMConfig,
|
|
52
79
|
WatchdogConfig,
|
|
53
80
|
)
|
|
54
81
|
|
|
55
|
-
# Managers
|
|
56
|
-
from hdsp_agent_core.managers import (
|
|
57
|
-
ConfigManager,
|
|
58
|
-
SessionManager,
|
|
59
|
-
get_config_manager,
|
|
60
|
-
get_session_manager,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
# LLM
|
|
64
|
-
from hdsp_agent_core.llm import (
|
|
65
|
-
LLMService,
|
|
66
|
-
call_llm,
|
|
67
|
-
call_llm_stream,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# Knowledge
|
|
71
|
-
from hdsp_agent_core.knowledge import (
|
|
72
|
-
DocumentChunker,
|
|
73
|
-
KnowledgeBase,
|
|
74
|
-
KnowledgeLoader,
|
|
75
|
-
LibraryDetector,
|
|
76
|
-
chunk_file,
|
|
77
|
-
get_knowledge_base,
|
|
78
|
-
get_knowledge_loader,
|
|
79
|
-
get_library_detector,
|
|
80
|
-
LIBRARY_DESCRIPTIONS,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
82
|
# Prompts
|
|
84
83
|
from hdsp_agent_core.prompts import (
|
|
85
|
-
|
|
84
|
+
ADAPTIVE_REPLAN_PROMPT,
|
|
86
85
|
CODE_GENERATION_PROMPT,
|
|
87
86
|
ERROR_REFINEMENT_PROMPT,
|
|
88
|
-
|
|
87
|
+
PLAN_GENERATION_PROMPT,
|
|
89
88
|
format_plan_prompt,
|
|
90
89
|
format_refine_prompt,
|
|
91
90
|
format_replan_prompt,
|
hdsp_agent_core/factory.py
CHANGED
|
@@ -21,8 +21,9 @@ logger = logging.getLogger(__name__)
|
|
|
21
21
|
|
|
22
22
|
class AgentMode(Enum):
|
|
23
23
|
"""Agent execution mode"""
|
|
24
|
+
|
|
24
25
|
EMBEDDED = "embedded" # Direct in-process execution
|
|
25
|
-
PROXY = "proxy"
|
|
26
|
+
PROXY = "proxy" # HTTP proxy to external server
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class ServiceFactory:
|
|
@@ -84,9 +85,7 @@ class ServiceFactory:
|
|
|
84
85
|
elif mode_str == "proxy":
|
|
85
86
|
return AgentMode.PROXY
|
|
86
87
|
else:
|
|
87
|
-
logger.warning(
|
|
88
|
-
f"Unknown HDSP_AGENT_MODE '{mode_str}', defaulting to proxy"
|
|
89
|
-
)
|
|
88
|
+
logger.warning(f"Unknown HDSP_AGENT_MODE '{mode_str}', defaulting to proxy")
|
|
90
89
|
return AgentMode.PROXY
|
|
91
90
|
|
|
92
91
|
@property
|
|
@@ -164,16 +163,13 @@ class ServiceFactory:
|
|
|
164
163
|
|
|
165
164
|
# Create proxy service instances
|
|
166
165
|
self._agent_service = ProxyAgentService(
|
|
167
|
-
base_url=self._server_url,
|
|
168
|
-
timeout=self._timeout
|
|
166
|
+
base_url=self._server_url, timeout=self._timeout
|
|
169
167
|
)
|
|
170
168
|
self._chat_service = ProxyChatService(
|
|
171
|
-
base_url=self._server_url,
|
|
172
|
-
timeout=self._timeout
|
|
169
|
+
base_url=self._server_url, timeout=self._timeout
|
|
173
170
|
)
|
|
174
171
|
self._rag_service = ProxyRAGService(
|
|
175
|
-
base_url=self._server_url,
|
|
176
|
-
timeout=self._timeout
|
|
172
|
+
base_url=self._server_url, timeout=self._timeout
|
|
177
173
|
)
|
|
178
174
|
|
|
179
175
|
# Optionally validate connectivity
|