hdsp-jupyter-extension 2.0.11__py3-none-any.whl → 2.0.13__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/MULTI_AGENT_ARCHITECTURE.md +1114 -0
- agent_server/langchain/__init__.py +2 -2
- agent_server/langchain/agent.py +72 -33
- agent_server/langchain/agent_factory.py +400 -0
- agent_server/langchain/agent_prompts/__init__.py +25 -0
- agent_server/langchain/agent_prompts/athena_query_prompt.py +71 -0
- agent_server/langchain/agent_prompts/planner_prompt.py +85 -0
- agent_server/langchain/agent_prompts/python_developer_prompt.py +123 -0
- agent_server/langchain/agent_prompts/researcher_prompt.py +38 -0
- agent_server/langchain/custom_middleware.py +652 -195
- agent_server/langchain/hitl_config.py +34 -10
- agent_server/langchain/middleware/__init__.py +24 -0
- agent_server/langchain/middleware/code_history_middleware.py +412 -0
- agent_server/langchain/middleware/description_injector.py +150 -0
- agent_server/langchain/middleware/skill_middleware.py +298 -0
- agent_server/langchain/middleware/subagent_events.py +171 -0
- agent_server/langchain/middleware/subagent_middleware.py +329 -0
- agent_server/langchain/prompts.py +96 -101
- agent_server/langchain/skills/data_analysis.md +236 -0
- agent_server/langchain/skills/data_loading.md +158 -0
- agent_server/langchain/skills/inference.md +392 -0
- agent_server/langchain/skills/model_training.md +318 -0
- agent_server/langchain/skills/pyspark.md +352 -0
- agent_server/langchain/subagents/__init__.py +20 -0
- agent_server/langchain/subagents/base.py +173 -0
- agent_server/langchain/tools/__init__.py +3 -0
- agent_server/langchain/tools/jupyter_tools.py +58 -20
- agent_server/langchain/tools/lsp_tools.py +1 -1
- agent_server/langchain/tools/shared/__init__.py +26 -0
- agent_server/langchain/tools/shared/qdrant_search.py +175 -0
- agent_server/langchain/tools/tool_registry.py +219 -0
- agent_server/langchain/tools/workspace_tools.py +197 -0
- agent_server/routers/config.py +40 -1
- agent_server/routers/langchain_agent.py +818 -337
- {hdsp_jupyter_extension-2.0.11.data → hdsp_jupyter_extension-2.0.13.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.11.data → hdsp_jupyter_extension-2.0.13.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +7 -2
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js → hdsp_jupyter_extension-2.0.13.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.037b3c8e5d6a92b63b16.js +1108 -179
- hdsp_jupyter_extension-2.0.13.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.037b3c8e5d6a92b63b16.js.map +1 -0
- jupyter_ext/labextension/static/lib_index_js.58c1e128ba0b76f41f04.js → hdsp_jupyter_extension-2.0.13.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.5449ba3c7e25177d2987.js +3916 -8128
- hdsp_jupyter_extension-2.0.13.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.5449ba3c7e25177d2987.js.map +1 -0
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js → hdsp_jupyter_extension-2.0.13.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.a8e0b064eb9b1c1ff463.js +17 -17
- hdsp_jupyter_extension-2.0.13.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.a8e0b064eb9b1c1ff463.js.map +1 -0
- {hdsp_jupyter_extension-2.0.11.dist-info → hdsp_jupyter_extension-2.0.13.dist-info}/METADATA +1 -1
- {hdsp_jupyter_extension-2.0.11.dist-info → hdsp_jupyter_extension-2.0.13.dist-info}/RECORD +75 -51
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +59 -8
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +7 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.2d9fb488c82498c45c2d.js → frontend_styles_index_js.037b3c8e5d6a92b63b16.js} +1108 -179
- jupyter_ext/labextension/static/frontend_styles_index_js.037b3c8e5d6a92b63b16.js.map +1 -0
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js → jupyter_ext/labextension/static/lib_index_js.5449ba3c7e25177d2987.js +3916 -8128
- jupyter_ext/labextension/static/lib_index_js.5449ba3c7e25177d2987.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.9da31d1134a53b0c4af5.js → remoteEntry.a8e0b064eb9b1c1ff463.js} +17 -17
- jupyter_ext/labextension/static/remoteEntry.a8e0b064eb9b1c1ff463.js.map +1 -0
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +0 -1
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.58c1e128ba0b76f41f04.js.map +0 -1
- hdsp_jupyter_extension-2.0.11.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.9da31d1134a53b0c4af5.js.map +0 -1
- jupyter_ext/labextension/static/frontend_styles_index_js.2d9fb488c82498c45c2d.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.58c1e128ba0b76f41f04.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.9da31d1134a53b0c4af5.js.map +0 -1
- {hdsp_jupyter_extension-2.0.11.data → hdsp_jupyter_extension-2.0.13.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.11.data → hdsp_jupyter_extension-2.0.13.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.data → hdsp_jupyter_extension-2.0.13.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.11.dist-info → hdsp_jupyter_extension-2.0.13.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.11.dist-info → hdsp_jupyter_extension-2.0.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SubAgentMiddleware
|
|
3
|
+
|
|
4
|
+
Middleware that enables subagent delegation via the `task` tool.
|
|
5
|
+
Based on Deep Agents library pattern (benchmarked, not installed).
|
|
6
|
+
|
|
7
|
+
Key features:
|
|
8
|
+
- Provides task(agent_name, description) tool for subagent invocation
|
|
9
|
+
- Context isolation: subagents run in clean context
|
|
10
|
+
- Synchronous execution: subagent returns result directly to caller
|
|
11
|
+
- Nested subagent support: python_developer can call athena_query
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from langchain_core.tools import tool
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Global registry for subagent factories (set by AgentFactory)
|
|
26
|
+
_subagent_factory = None
|
|
27
|
+
_current_llm_config = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def set_subagent_factory(factory_func, llm_config: Dict[str, Any]):
|
|
31
|
+
"""
|
|
32
|
+
Set the subagent factory function.
|
|
33
|
+
Called by AgentFactory during initialization.
|
|
34
|
+
"""
|
|
35
|
+
global _subagent_factory, _current_llm_config
|
|
36
|
+
_subagent_factory = factory_func
|
|
37
|
+
_current_llm_config = llm_config
|
|
38
|
+
logger.info("SubAgentMiddleware factory initialized")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_subagent_factory():
|
|
42
|
+
"""Get the current subagent factory function."""
|
|
43
|
+
return _subagent_factory, _current_llm_config
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create_task_tool(
|
|
47
|
+
caller_name: str,
|
|
48
|
+
allowed_subagents: Optional[List[str]] = None,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Create a task tool for calling subagents.
|
|
52
|
+
|
|
53
|
+
The task tool executes subagents synchronously and returns their result.
|
|
54
|
+
Subagents like python_developer return generated code/analysis,
|
|
55
|
+
which the Main Agent can then execute if needed.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
caller_name: Name of the agent creating this tool (for logging/validation)
|
|
59
|
+
allowed_subagents: Optional list of subagent names this agent can call.
|
|
60
|
+
If None, all subagents are allowed (for Main Agent).
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A tool that can be used to delegate tasks to subagents.
|
|
64
|
+
"""
|
|
65
|
+
from agent_server.langchain.subagents.base import (
|
|
66
|
+
SUBAGENT_CONFIGS,
|
|
67
|
+
get_subagent_config,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Build description based on allowed subagents
|
|
71
|
+
if allowed_subagents:
|
|
72
|
+
available = [
|
|
73
|
+
f"- {name}: {SUBAGENT_CONFIGS[name].description}"
|
|
74
|
+
for name in allowed_subagents
|
|
75
|
+
if name in SUBAGENT_CONFIGS
|
|
76
|
+
]
|
|
77
|
+
else:
|
|
78
|
+
# Main Agent (planner) can call non-restricted subagents OR those explicitly allowing "planner"
|
|
79
|
+
available = [
|
|
80
|
+
f"- {config.name}: {config.description}"
|
|
81
|
+
for config in SUBAGENT_CONFIGS.values()
|
|
82
|
+
if not config.callable_by or caller_name in config.callable_by
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
available_str = "\n".join(available)
|
|
86
|
+
|
|
87
|
+
# Create Pydantic schema for the task tool (required for Gemini compatibility)
|
|
88
|
+
class TaskInput(BaseModel):
|
|
89
|
+
"""Input schema for task tool"""
|
|
90
|
+
|
|
91
|
+
agent_name: str = Field(
|
|
92
|
+
description=f"Name of the subagent to invoke. Available: {', '.join(allowed_subagents) if allowed_subagents else 'python_developer, researcher, athena_query'}"
|
|
93
|
+
)
|
|
94
|
+
description: str = Field(
|
|
95
|
+
description="Detailed task description for the subagent (Korean preferred)"
|
|
96
|
+
)
|
|
97
|
+
context: Optional[str] = Field(
|
|
98
|
+
default=None,
|
|
99
|
+
description="Additional context for the subagent: resource info (file sizes, memory), previous code, variable state, etc."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@tool(args_schema=TaskInput)
|
|
103
|
+
def task_tool(agent_name: str, description: str, context: Optional[str] = None) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Delegate a task to a specialized subagent.
|
|
106
|
+
|
|
107
|
+
The subagent will execute the task and return its result (code, analysis, etc.).
|
|
108
|
+
Code execution tools (jupyter_cell_tool, write_file_tool) are handled by Main Agent.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
agent_name: Name of the subagent to invoke
|
|
112
|
+
description: Detailed task description for the subagent
|
|
113
|
+
context: Additional context (resource info, previous code, etc.)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Result from the subagent execution (string summary or generated code)
|
|
117
|
+
"""
|
|
118
|
+
# Validate subagent exists
|
|
119
|
+
if agent_name not in SUBAGENT_CONFIGS:
|
|
120
|
+
return f"Error: Unknown agent '{agent_name}'. Available agents:\n{available_str}"
|
|
121
|
+
|
|
122
|
+
# Validate caller is allowed to call this subagent
|
|
123
|
+
config = get_subagent_config(agent_name)
|
|
124
|
+
if allowed_subagents and agent_name not in allowed_subagents:
|
|
125
|
+
return f"Error: '{caller_name}' cannot call '{agent_name}'. Allowed: {allowed_subagents}"
|
|
126
|
+
|
|
127
|
+
if config.callable_by and caller_name not in config.callable_by:
|
|
128
|
+
return f"Error: '{agent_name}' can only be called by: {config.callable_by}"
|
|
129
|
+
|
|
130
|
+
logger.info(
|
|
131
|
+
f"[{caller_name}] Invoking subagent '{agent_name}': {description[:100]}..."
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Import subagent event emitters
|
|
135
|
+
from agent_server.langchain.middleware.subagent_events import (
|
|
136
|
+
emit_subagent_start,
|
|
137
|
+
emit_subagent_complete,
|
|
138
|
+
set_current_subagent,
|
|
139
|
+
clear_current_subagent,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Emit subagent start event for UI
|
|
143
|
+
emit_subagent_start(agent_name, description)
|
|
144
|
+
|
|
145
|
+
# Get the factory and config
|
|
146
|
+
factory_func, llm_config = get_subagent_factory()
|
|
147
|
+
if factory_func is None:
|
|
148
|
+
return "Error: SubAgentMiddleware not initialized. Call set_subagent_factory first."
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
# Set current subagent context for tool call tracking
|
|
152
|
+
set_current_subagent(agent_name)
|
|
153
|
+
|
|
154
|
+
# Create the subagent
|
|
155
|
+
subagent = factory_func(agent_name, llm_config)
|
|
156
|
+
|
|
157
|
+
# Execute subagent synchronously with clean context
|
|
158
|
+
# The subagent runs in isolation, receiving task description + optional context
|
|
159
|
+
import uuid
|
|
160
|
+
|
|
161
|
+
subagent_thread_id = f"subagent-{agent_name}-{uuid.uuid4().hex[:8]}"
|
|
162
|
+
subagent_config = {
|
|
163
|
+
"configurable": {
|
|
164
|
+
"thread_id": subagent_thread_id,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Inject code history for python_developer
|
|
169
|
+
enhanced_context = context
|
|
170
|
+
if agent_name == "python_developer":
|
|
171
|
+
try:
|
|
172
|
+
from agent_server.langchain.middleware.code_history_middleware import (
|
|
173
|
+
get_context_with_history,
|
|
174
|
+
get_code_history_tracker,
|
|
175
|
+
)
|
|
176
|
+
tracker = get_code_history_tracker()
|
|
177
|
+
if tracker.get_entry_count() > 0:
|
|
178
|
+
enhanced_context = get_context_with_history(context)
|
|
179
|
+
logger.info(
|
|
180
|
+
f"[{caller_name}] Injected code history into context "
|
|
181
|
+
f"(entries={tracker.get_entry_count()}, "
|
|
182
|
+
f"context_len={len(enhanced_context) if enhanced_context else 0})"
|
|
183
|
+
)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.warning(f"Failed to inject code history: {e}")
|
|
186
|
+
|
|
187
|
+
# Build the message content with optional context
|
|
188
|
+
if enhanced_context:
|
|
189
|
+
message_content = f"""## Task
|
|
190
|
+
{description}
|
|
191
|
+
|
|
192
|
+
## Context (provided by Main Agent)
|
|
193
|
+
{enhanced_context}"""
|
|
194
|
+
else:
|
|
195
|
+
message_content = description
|
|
196
|
+
|
|
197
|
+
logger.info(f"[{caller_name}] Subagent message length: {len(message_content)}")
|
|
198
|
+
|
|
199
|
+
# Execute the subagent
|
|
200
|
+
result = subagent.invoke(
|
|
201
|
+
{"messages": [{"role": "user", "content": message_content}]},
|
|
202
|
+
config=subagent_config,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Extract the final message from the result
|
|
206
|
+
messages = result.get("messages", [])
|
|
207
|
+
if messages:
|
|
208
|
+
final_message = messages[-1]
|
|
209
|
+
if hasattr(final_message, "content"):
|
|
210
|
+
response = final_message.content
|
|
211
|
+
else:
|
|
212
|
+
response = str(final_message)
|
|
213
|
+
else:
|
|
214
|
+
response = "Subagent completed but returned no messages."
|
|
215
|
+
|
|
216
|
+
logger.info(
|
|
217
|
+
f"[{caller_name}] Subagent '{agent_name}' returned: {str(response)[:200]}..."
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Extract description from python_developer response for auto-injection
|
|
221
|
+
if agent_name == "python_developer":
|
|
222
|
+
try:
|
|
223
|
+
from agent_server.langchain.middleware.description_injector import (
|
|
224
|
+
process_task_tool_response,
|
|
225
|
+
)
|
|
226
|
+
process_task_tool_response(agent_name, str(response))
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.warning(f"Failed to extract description: {e}")
|
|
229
|
+
|
|
230
|
+
# Emit subagent complete event for UI
|
|
231
|
+
emit_subagent_complete(agent_name, str(response)[:100])
|
|
232
|
+
|
|
233
|
+
return response
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
error_msg = f"Subagent '{agent_name}' failed: {str(e)}"
|
|
237
|
+
logger.error(error_msg, exc_info=True)
|
|
238
|
+
# Emit complete event even on error
|
|
239
|
+
emit_subagent_complete(agent_name, f"Error: {str(e)[:50]}")
|
|
240
|
+
return f"Error: {error_msg}"
|
|
241
|
+
finally:
|
|
242
|
+
# Always clear subagent context
|
|
243
|
+
clear_current_subagent()
|
|
244
|
+
|
|
245
|
+
# Update tool docstring with available agents
|
|
246
|
+
task_tool.__doc__ = f"""Delegate a task to a specialized subagent.
|
|
247
|
+
|
|
248
|
+
Available agents:
|
|
249
|
+
{available_str}
|
|
250
|
+
|
|
251
|
+
The subagent will analyze the task and return its result.
|
|
252
|
+
- python_developer: Returns generated Python code and analysis
|
|
253
|
+
- researcher: Returns search results and findings
|
|
254
|
+
- athena_query: Returns SQL query string
|
|
255
|
+
|
|
256
|
+
For code execution (running Python, writing files), use Main Agent's tools directly.
|
|
257
|
+
|
|
258
|
+
IMPORTANT: For python_developer, ALWAYS provide context with:
|
|
259
|
+
- Resource info (file sizes, memory) from check_resource_tool
|
|
260
|
+
- Previous code context if building on existing work
|
|
261
|
+
- Variable names and their current state
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
agent_name: Name of the subagent to invoke
|
|
265
|
+
description: Detailed task description for the subagent
|
|
266
|
+
context: Additional context (resource info, previous code, etc.)
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Result from the subagent execution
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
return task_tool
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class SubAgentMiddleware:
|
|
276
|
+
"""
|
|
277
|
+
Middleware that adds subagent delegation capability.
|
|
278
|
+
|
|
279
|
+
This middleware:
|
|
280
|
+
1. Adds the `task` tool to the agent's toolset
|
|
281
|
+
2. The task tool executes subagents synchronously
|
|
282
|
+
3. Subagent results are returned directly to the caller
|
|
283
|
+
|
|
284
|
+
Usage:
|
|
285
|
+
middleware = SubAgentMiddleware(
|
|
286
|
+
caller_name="main_agent",
|
|
287
|
+
allowed_subagents=["python_developer", "researcher"],
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
agent = create_agent(
|
|
291
|
+
model=llm,
|
|
292
|
+
tools=tools,
|
|
293
|
+
middleware=[middleware, ...],
|
|
294
|
+
)
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
def __init__(
|
|
298
|
+
self,
|
|
299
|
+
caller_name: str,
|
|
300
|
+
allowed_subagents: Optional[List[str]] = None,
|
|
301
|
+
):
|
|
302
|
+
"""
|
|
303
|
+
Initialize SubAgentMiddleware.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
caller_name: Name of the agent using this middleware
|
|
307
|
+
allowed_subagents: List of subagents this agent can call.
|
|
308
|
+
None means all non-restricted subagents.
|
|
309
|
+
"""
|
|
310
|
+
self.caller_name = caller_name
|
|
311
|
+
self.allowed_subagents = allowed_subagents
|
|
312
|
+
self.task_tool = create_task_tool(caller_name, allowed_subagents)
|
|
313
|
+
|
|
314
|
+
logger.info(
|
|
315
|
+
f"SubAgentMiddleware initialized for '{caller_name}' "
|
|
316
|
+
f"with allowed_subagents={allowed_subagents}"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def get_tools(self) -> List[Any]:
|
|
320
|
+
"""Get the tools provided by this middleware."""
|
|
321
|
+
return [self.task_tool]
|
|
322
|
+
|
|
323
|
+
def __call__(self, tools: List[Any]) -> List[Any]:
|
|
324
|
+
"""
|
|
325
|
+
Add task tool to the agent's toolset.
|
|
326
|
+
|
|
327
|
+
This is called during agent creation to augment the tool list.
|
|
328
|
+
"""
|
|
329
|
+
return tools + [self.task_tool]
|
|
@@ -1,67 +1,62 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Prompt templates for LangChain agent.
|
|
3
|
-
|
|
4
|
-
Contains system prompts, JSON schema for fallback tool calling,
|
|
5
|
-
and middleware-specific prompts.
|
|
6
3
|
"""
|
|
7
4
|
|
|
8
|
-
DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant.
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
1.
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
- Tool calls without Korean explanation
|
|
64
|
-
- Stopping with pending/in_progress todos
|
|
5
|
+
DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant.
|
|
6
|
+
|
|
7
|
+
# 핵심 규칙
|
|
8
|
+
1. 한국어로 응답하세요
|
|
9
|
+
2. 간결하게 (4줄 이하, 상세 요청 시 예외)
|
|
10
|
+
3. 모든 응답에 도구를 호출하세요 (텍스트만 응답 금지)
|
|
11
|
+
4. **[필수] 도구 호출 시 반드시 텍스트 설명을 함께 출력**
|
|
12
|
+
- 예: "파일 구조를 확인하겠습니다." + tool_call
|
|
13
|
+
- 설명 없이 tool_call만 하면 안 됨
|
|
14
|
+
|
|
15
|
+
# 작업 흐름
|
|
16
|
+
1. 간단한 작업 (1-2단계): 바로 실행
|
|
17
|
+
2. 복잡한 작업 (3단계+): write_todos로 계획 → 순차 실행
|
|
18
|
+
|
|
19
|
+
# write_todos 규칙 [필수]
|
|
20
|
+
- 한국어로 작성
|
|
21
|
+
- **🔴 기존 todo 절대 삭제 금지**: 전체 리스트를 항상 포함하고 status만 변경
|
|
22
|
+
- 잘못된 예: [{"content": "작업 요약", "status": "completed"}] ← 기존 todo 삭제됨!
|
|
23
|
+
- 올바른 예: [{"content": "기존 작업1", "status": "completed"}, {"content": "기존 작업2", "status": "completed"}, {"content": "작업 요약", "status": "completed"}]
|
|
24
|
+
- **일괄 업데이트**: 연속 완료된 todo는 한 번의 write_todos 호출로 처리
|
|
25
|
+
- in_progress는 **1개만** 유지
|
|
26
|
+
- **[필수] 마지막 todo는 반드시 "작업 요약 및 다음 단계 제시"로 생성**
|
|
27
|
+
- **🔴 [실행 순서 필수]**: "작업 요약 및 다음 단계 제시"는 **반드시 가장 마지막에 실행**
|
|
28
|
+
- 다른 모든 todo가 completed 상태가 된 후에만 이 todo를 in_progress로 변경
|
|
29
|
+
- 비슷한 이름의 다른 작업(보고서 검토, 결과 정리 등)과 혼동 금지
|
|
30
|
+
- **[중요] "작업 요약 및 다음 단계 제시"는 summary JSON 출력 후에만 completed 표시**
|
|
31
|
+
|
|
32
|
+
# 모든 작업 완료 후 [필수]
|
|
33
|
+
마지막 todo "작업 요약 및 다음 단계 제시"를 completed로 변경한 후,
|
|
34
|
+
**텍스트 응답으로** 아래 JSON을 출력하세요 (todo content가 아님!):
|
|
35
|
+
{"summary": "완료된 작업 요약", "next_items": [{"subject": "제목", "description": "설명"}]}
|
|
36
|
+
- next_items 3개 이상 필수
|
|
37
|
+
- **summary JSON 없이 종료 금지**
|
|
38
|
+
- **주의**: JSON은 todo 항목이 아닌 일반 텍스트 응답으로 출력
|
|
39
|
+
|
|
40
|
+
# 도구 사용
|
|
41
|
+
- check_resource_tool: 대용량 파일/데이터프레임 작업 전 필수
|
|
42
|
+
- read_file_tool: 대용량 파일은 limit=100으로 먼저 확인
|
|
43
|
+
- jupyter_cell_tool: 차트 라벨은 영어로
|
|
44
|
+
- **파일 수정 후**: diagnostics_tool로 오류 확인 필수
|
|
45
|
+
|
|
46
|
+
# 사용자 입력 요청 [중요]
|
|
47
|
+
- **ask_user_tool**: 사용자 응답이 필요할 때 사용 (파일 업로드, 선택, 정보 요청)
|
|
48
|
+
- 파일 업로드: ask_user_tool(question="kaggle.json 파일을 업로드해 주세요", input_type="file")
|
|
49
|
+
- 선택 요청: ask_user_tool(question="모델 선택?", options=["Logistic", "RandomForest"])
|
|
50
|
+
- 정보 요청: ask_user_tool(question="API 키를 입력해 주세요", input_type="text")
|
|
51
|
+
- **markdown_tool**: 정보 출력용 (사용자 응답 불필요)
|
|
52
|
+
- ⚠️ 사용자 응답이 필요하면 markdown_tool 대신 반드시 ask_user_tool 사용!
|
|
53
|
+
|
|
54
|
+
# 금지 사항
|
|
55
|
+
- 빈 응답 (도구 호출도 없고 내용도 없음)
|
|
56
|
+
- 설명 없이 도구만 호출
|
|
57
|
+
- pending/in_progress todo 남기고 종료
|
|
58
|
+
- "작업 요약 및 다음 단계 제시" todo 없이 todo 리스트 생성
|
|
59
|
+
- **🔴 다른 pending todo가 있는데 "작업 요약 및 다음 단계 제시"를 먼저 실행** (순서 위반)
|
|
65
60
|
"""
|
|
66
61
|
|
|
67
62
|
JSON_TOOL_SCHEMA = """Respond with ONLY valid JSON:
|
|
@@ -70,61 +65,61 @@ JSON_TOOL_SCHEMA = """Respond with ONLY valid JSON:
|
|
|
70
65
|
Tools:
|
|
71
66
|
- jupyter_cell_tool: {"code": "<python>"}
|
|
72
67
|
- markdown_tool: {"content": "<markdown>"}
|
|
73
|
-
-
|
|
68
|
+
- ask_user_tool: {"question": "<질문>", "options": ["선택1", "선택2"], "input_type": "text|file|selection"}
|
|
69
|
+
- write_todos: {"todos": [{"content": "내용", "status": "pending|in_progress|completed"}]}
|
|
74
70
|
- read_file_tool: {"path": "<path>", "offset": 0, "limit": 500}
|
|
75
|
-
- write_file_tool: {"path": "<path>", "content": "<content>"
|
|
76
|
-
-
|
|
71
|
+
- write_file_tool: {"path": "<path>", "content": "<content>"}
|
|
72
|
+
- multiedit_file_tool: {"path": "<path>", "edits": [{"old_string": "...", "new_string": "..."}]}
|
|
77
73
|
- execute_command_tool: {"command": "<cmd>"}
|
|
78
74
|
- check_resource_tool: {"files": ["<path>"], "dataframes": ["<var>"]}
|
|
79
75
|
|
|
80
76
|
No markdown wrapping. JSON only."""
|
|
81
77
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
## 🔴 Final Todo ("작업 요약 및 다음단계 제시") - CRITICAL
|
|
101
|
-
When executing this todo, you MUST:
|
|
102
|
-
1. Output the summary JSON in your content FIRST:
|
|
103
|
-
{"summary": "작업 내용 요약", "next_items": [{"subject": "...", "description": "..."}]}
|
|
104
|
-
2. THEN call write_todos to mark all as completed
|
|
105
|
-
3. If you don't output the JSON, the todo will NOT be marked as completed!
|
|
106
|
-
|
|
107
|
-
## Completion Check
|
|
108
|
-
- ✅ Done: Executed in THIS response
|
|
109
|
-
- ❌ Not done: Only visible in chat history
|
|
110
|
-
- ❌ "작업 요약" cannot be completed without outputting summary JSON
|
|
111
|
-
|
|
112
|
-
## Forbidden
|
|
113
|
-
- Marking complete without executing in THIS response
|
|
114
|
-
- Marking "작업 요약" complete without outputting JSON summary
|
|
115
|
-
- Todos without "작업 요약 및 다음단계 제시" as final item
|
|
78
|
+
# Merged into DEFAULT_SYSTEM_PROMPT
|
|
79
|
+
TODO_LIST_SYSTEM_PROMPT = ""
|
|
80
|
+
|
|
81
|
+
TODO_LIST_TOOL_DESCRIPTION = """Todo 리스트 관리 도구.
|
|
82
|
+
|
|
83
|
+
사용 시점:
|
|
84
|
+
- 3단계 이상의 복잡한 작업
|
|
85
|
+
- 진행 상황 추적이 필요할 때
|
|
86
|
+
|
|
87
|
+
규칙:
|
|
88
|
+
- in_progress는 1개만
|
|
89
|
+
- 완료 즉시 completed로 변경
|
|
90
|
+
- **[필수] 마지막 todo는 반드시 "작업 요약 및 다음 단계 제시"로 생성**
|
|
91
|
+
- **🔴 [실행 순서]**: todo는 반드시 리스트 순서대로 실행하고, "작업 요약 및 다음 단계 제시"는 맨 마지막에 실행
|
|
92
|
+
- 이 "작업 요약 및 다음 단계 제시" todo 에서는 전체 작업 요약과 다음 단계를 제시하는 내용을 JSON 형태로 출력:
|
|
93
|
+
{"summary": "완료 요약", "next_items": [{"subject": "...", "description": "..."}]}
|
|
94
|
+
(next_items 3개 이상 필수)
|
|
116
95
|
"""
|
|
117
96
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
97
|
+
# List of tools available to the agent
|
|
98
|
+
TOOL_LIST = [
|
|
99
|
+
"jupyter_cell_tool",
|
|
100
|
+
"markdown_tool",
|
|
101
|
+
"ask_user_tool",
|
|
102
|
+
"write_todos",
|
|
103
|
+
"read_file_tool",
|
|
104
|
+
"write_file_tool",
|
|
105
|
+
"multiedit_file_tool",
|
|
106
|
+
"search_notebook_cells_tool",
|
|
107
|
+
"execute_command_tool",
|
|
108
|
+
"check_resource_tool",
|
|
109
|
+
"list_workspace_tool",
|
|
110
|
+
"search_files_tool",
|
|
111
|
+
]
|
|
121
112
|
|
|
122
|
-
#
|
|
113
|
+
# Tools that don't require HITL (Human-in-the-Loop) approval
|
|
123
114
|
NON_HITL_TOOLS = {
|
|
124
115
|
"markdown_tool",
|
|
125
116
|
"markdown",
|
|
126
117
|
"read_file_tool",
|
|
127
118
|
"read_file",
|
|
119
|
+
"list_files_tool",
|
|
120
|
+
"list_files",
|
|
121
|
+
"search_workspace_tool",
|
|
122
|
+
"search_workspace",
|
|
128
123
|
"search_notebook_cells_tool",
|
|
129
124
|
"search_notebook_cells",
|
|
130
125
|
"write_todos",
|