hdsp-jupyter-extension 2.0.20__py3-none-any.whl → 2.0.22__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/llm_factory.py +12 -4
- agent_server/langchain/logging_utils.py +75 -22
- agent_server/langchain/middleware/subagent_middleware.py +80 -11
- agent_server/routers/langchain_agent.py +100 -341
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.20.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.586bf5521d043cdd37b8.js → hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.8496e8475f1bd164669b.js +2 -2
- jupyter_ext/labextension/static/remoteEntry.586bf5521d043cdd37b8.js.map → hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.8496e8475f1bd164669b.js.map +1 -1
- {hdsp_jupyter_extension-2.0.20.dist-info → hdsp_jupyter_extension-2.0.22.dist-info}/METADATA +1 -1
- {hdsp_jupyter_extension-2.0.20.dist-info → hdsp_jupyter_extension-2.0.22.dist-info}/RECORD +40 -40
- 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.586bf5521d043cdd37b8.js → remoteEntry.8496e8475f1bd164669b.js} +2 -2
- hdsp_jupyter_extension-2.0.20.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.586bf5521d043cdd37b8.js.map → jupyter_ext/labextension/static/remoteEntry.8496e8475f1bd164669b.js.map +1 -1
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.96745acc14125453fba8.js +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.96745acc14125453fba8.js.map +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.90f80cb80187de8c5ae5.js +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.90f80cb80187de8c5ae5.js.map +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.data → hdsp_jupyter_extension-2.0.22.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.20.dist-info → hdsp_jupyter_extension-2.0.22.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.20.dist-info → hdsp_jupyter_extension-2.0.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -106,10 +106,12 @@ def _create_vllm_llm(llm_config: Dict[str, Any], callbacks):
|
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
# Use ChatGPTOSS for gpt-oss models (Harmony format with developer role)
|
|
109
|
-
|
|
109
|
+
# NOTE: OpenRouter doesn't support 'developer' role - only use for direct gpt-oss endpoints
|
|
110
|
+
is_openrouter = "openrouter" in endpoint.lower()
|
|
111
|
+
if "gpt-oss" in model.lower() and not is_openrouter:
|
|
110
112
|
from agent_server.langchain.models import ChatGPTOSS
|
|
111
113
|
|
|
112
|
-
logger.info(
|
|
114
|
+
logger.info("Using ChatGPTOSS for gpt-oss model (developer role support)")
|
|
113
115
|
return ChatGPTOSS(
|
|
114
116
|
model=model,
|
|
115
117
|
base_url=endpoint,
|
|
@@ -119,6 +121,11 @@ def _create_vllm_llm(llm_config: Dict[str, Any], callbacks):
|
|
|
119
121
|
streaming=False,
|
|
120
122
|
callbacks=callbacks,
|
|
121
123
|
)
|
|
124
|
+
elif "gpt-oss" in model.lower() and is_openrouter:
|
|
125
|
+
logger.warning(
|
|
126
|
+
"gpt-oss model via OpenRouter - using standard ChatOpenAI "
|
|
127
|
+
"(developer role not supported by OpenRouter)"
|
|
128
|
+
)
|
|
122
129
|
|
|
123
130
|
return ChatOpenAI(
|
|
124
131
|
model=model,
|
|
@@ -175,8 +182,9 @@ def create_summarization_llm(llm_config: Dict[str, Any]):
|
|
|
175
182
|
model = vllm_config.get("model", "default")
|
|
176
183
|
api_key = vllm_config.get("apiKey", "dummy")
|
|
177
184
|
|
|
178
|
-
# Use ChatGPTOSS for gpt-oss models
|
|
179
|
-
|
|
185
|
+
# Use ChatGPTOSS for gpt-oss models (but not via OpenRouter)
|
|
186
|
+
is_openrouter = "openrouter" in endpoint.lower()
|
|
187
|
+
if "gpt-oss" in model.lower() and not is_openrouter:
|
|
180
188
|
from agent_server.langchain.models import ChatGPTOSS
|
|
181
189
|
|
|
182
190
|
return ChatGPTOSS(
|
|
@@ -22,7 +22,7 @@ llm_response_logger.propagate = True # Propagate to root logger
|
|
|
22
22
|
# Ensure it has a handler if running standalone
|
|
23
23
|
if not llm_response_logger.handlers and not logging.getLogger().handlers:
|
|
24
24
|
_handler = logging.StreamHandler()
|
|
25
|
-
_handler.setFormatter(logging.Formatter(
|
|
25
|
+
_handler.setFormatter(logging.Formatter("%(message)s"))
|
|
26
26
|
llm_response_logger.addHandler(_handler)
|
|
27
27
|
|
|
28
28
|
|
|
@@ -42,7 +42,11 @@ disable_langchain_logging()
|
|
|
42
42
|
LOG_SEPARATOR = "=" * 96
|
|
43
43
|
LOG_SUBSECTION = "-" * 96
|
|
44
44
|
LOG_EMOJI_LINE = "🔵" * 48
|
|
45
|
-
|
|
45
|
+
LOG_REQUEST_START = f"\n\n{'🟢' * 48}\n{'=' * 96}\n 📤 LLM REQUEST START\n{'=' * 96}"
|
|
46
|
+
LOG_REQUEST_END = f"{'=' * 96}\n 📤 LLM REQUEST END\n{'=' * 96}\n{'🟢' * 48}\n"
|
|
47
|
+
LOG_RESPONSE_START = (
|
|
48
|
+
f"\n\n{LOG_EMOJI_LINE}\n{'=' * 96}\n ✨ LLM RESPONSE START\n{'=' * 96}"
|
|
49
|
+
)
|
|
46
50
|
LOG_RESPONSE_END = f"{'=' * 96}\n ✅ LLM RESPONSE END\n{'=' * 96}\n{LOG_EMOJI_LINE}\n"
|
|
47
51
|
|
|
48
52
|
|
|
@@ -207,42 +211,91 @@ class LLMTraceLogger(BaseCallbackHandler):
|
|
|
207
211
|
logger.info("%s", "\n".join(lines))
|
|
208
212
|
|
|
209
213
|
def on_chat_model_start(self, serialized, messages, **kwargs) -> None:
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
"""Log LLM request messages as raw structured JSON."""
|
|
215
|
+
print(LOG_REQUEST_START, flush=True)
|
|
216
|
+
|
|
217
|
+
# Build raw structured request data
|
|
218
|
+
request_data = {
|
|
219
|
+
"model": serialized.get("name", "unknown") if serialized else "unknown",
|
|
220
|
+
"kwargs": {k: str(v)[:200] for k, v in kwargs.items() if k != "messages"},
|
|
221
|
+
"messages": [],
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for batch in self._normalize_batches(messages):
|
|
225
|
+
batch_messages = []
|
|
226
|
+
for msg in batch:
|
|
227
|
+
batch_messages.append(_serialize_message(msg))
|
|
228
|
+
request_data["messages"].append(batch_messages)
|
|
229
|
+
|
|
230
|
+
# Output beautified JSON
|
|
231
|
+
print(_pretty_json(request_data), flush=True)
|
|
232
|
+
|
|
233
|
+
print(LOG_REQUEST_END, flush=True)
|
|
234
|
+
|
|
235
|
+
# --- OLD TEXT-PARSED LOGGING (commented out) ---
|
|
236
|
+
# for batch_idx, batch in enumerate(self._normalize_batches(messages)):
|
|
237
|
+
# msg_types = {}
|
|
238
|
+
# for msg in batch:
|
|
239
|
+
# msg_type = msg.__class__.__name__
|
|
240
|
+
# msg_types[msg_type] = msg_types.get(msg_type, 0) + 1
|
|
241
|
+
# print(f"\nBatch {batch_idx}: {len(batch)} messages - {msg_types}", flush=True)
|
|
242
|
+
# recent_count = min(5, len(batch))
|
|
243
|
+
# if len(batch) > recent_count:
|
|
244
|
+
# print(f"... ({len(batch) - recent_count} earlier messages omitted)", flush=True)
|
|
245
|
+
# for idx, message in enumerate(batch[-recent_count:], start=len(batch) - recent_count):
|
|
246
|
+
# lines = [LOG_SUBSECTION]
|
|
247
|
+
# lines.append(f"[{idx}] {message.__class__.__name__}")
|
|
248
|
+
# lines.append(_pretty_json(_serialize_message(message)))
|
|
249
|
+
# print("\n".join(lines), flush=True)
|
|
212
250
|
|
|
213
251
|
def on_chat_model_end(self, response, **kwargs) -> None:
|
|
214
|
-
|
|
215
|
-
print("[DEBUG] on_chat_model_end CALLED!", flush=True)
|
|
216
|
-
# Use print for guaranteed visibility
|
|
252
|
+
"""Log LLM response as raw structured JSON."""
|
|
217
253
|
print(LOG_RESPONSE_START, flush=True)
|
|
218
254
|
|
|
255
|
+
# Build raw structured response data
|
|
256
|
+
response_data = {
|
|
257
|
+
"llm_output": getattr(response, "llm_output", None),
|
|
258
|
+
"generations": [],
|
|
259
|
+
}
|
|
260
|
+
|
|
219
261
|
generations = getattr(response, "generations", None) or []
|
|
220
262
|
if generations and isinstance(generations[0], list):
|
|
221
263
|
batches = generations
|
|
222
264
|
else:
|
|
223
265
|
batches = [generations]
|
|
224
266
|
|
|
225
|
-
for
|
|
226
|
-
|
|
267
|
+
for batch in batches:
|
|
268
|
+
batch_data = []
|
|
269
|
+
for generation in batch:
|
|
270
|
+
gen_data = {}
|
|
227
271
|
message = getattr(generation, "message", None)
|
|
228
|
-
if
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
272
|
+
if message:
|
|
273
|
+
gen_data["message"] = _serialize_message(message)
|
|
274
|
+
gen_data["text"] = getattr(generation, "text", None)
|
|
275
|
+
gen_data["generation_info"] = getattr(
|
|
276
|
+
generation, "generation_info", None
|
|
233
277
|
)
|
|
234
|
-
|
|
278
|
+
batch_data.append(gen_data)
|
|
279
|
+
response_data["generations"].append(batch_data)
|
|
235
280
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
tool_title = (
|
|
239
|
-
"LLM -> AGENT TOOL CALLS "
|
|
240
|
-
f"(batch={batch_idx}, generation={gen_idx})"
|
|
241
|
-
)
|
|
242
|
-
print(_format_json_block(tool_title, tool_calls), flush=True)
|
|
281
|
+
# Output beautified JSON
|
|
282
|
+
print(_pretty_json(response_data), flush=True)
|
|
243
283
|
|
|
244
284
|
print(LOG_RESPONSE_END, flush=True)
|
|
245
285
|
|
|
286
|
+
# --- OLD TEXT-PARSED LOGGING (commented out) ---
|
|
287
|
+
# for batch_idx, batch in enumerate(batches):
|
|
288
|
+
# for gen_idx, generation in enumerate(batch):
|
|
289
|
+
# message = getattr(generation, "message", None)
|
|
290
|
+
# if not message:
|
|
291
|
+
# continue
|
|
292
|
+
# title = f"LLM -> AGENT RESPONSE (batch={batch_idx}, generation={gen_idx})"
|
|
293
|
+
# print(_format_messages_block(title, [message]), flush=True)
|
|
294
|
+
# tool_calls = getattr(message, "tool_calls", None)
|
|
295
|
+
# if tool_calls:
|
|
296
|
+
# tool_title = f"LLM -> AGENT TOOL CALLS (batch={batch_idx}, generation={gen_idx})"
|
|
297
|
+
# print(_format_json_block(tool_title, tool_calls), flush=True)
|
|
298
|
+
|
|
246
299
|
def on_llm_start(self, serialized, prompts, **kwargs) -> None:
|
|
247
300
|
# Request logging disabled - only log responses
|
|
248
301
|
pass
|
|
@@ -9,8 +9,11 @@ Key features:
|
|
|
9
9
|
- Context isolation: subagents run in clean context
|
|
10
10
|
- Synchronous execution: subagent returns result directly to caller
|
|
11
11
|
- Nested subagent support: python_developer can call athena_query
|
|
12
|
+
- Subagent caching: compiled agents are cached to avoid recompilation overhead
|
|
12
13
|
"""
|
|
13
14
|
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
14
17
|
import logging
|
|
15
18
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
16
19
|
|
|
@@ -25,6 +28,8 @@ logger = logging.getLogger(__name__)
|
|
|
25
28
|
# Global registry for subagent factories (set by AgentFactory)
|
|
26
29
|
_subagent_factory = None
|
|
27
30
|
_current_llm_config = None
|
|
31
|
+
# Subagent cache: key = "{agent_name}_{config_hash}" -> compiled agent
|
|
32
|
+
_subagent_cache: Dict[str, Any] = {}
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
def set_subagent_factory(factory_func, llm_config: Dict[str, Any]):
|
|
@@ -32,10 +37,12 @@ def set_subagent_factory(factory_func, llm_config: Dict[str, Any]):
|
|
|
32
37
|
Set the subagent factory function.
|
|
33
38
|
Called by AgentFactory during initialization.
|
|
34
39
|
"""
|
|
35
|
-
global _subagent_factory, _current_llm_config
|
|
40
|
+
global _subagent_factory, _current_llm_config, _subagent_cache
|
|
36
41
|
_subagent_factory = factory_func
|
|
37
42
|
_current_llm_config = llm_config
|
|
38
|
-
|
|
43
|
+
# Clear cache when factory changes (new LLM config)
|
|
44
|
+
_subagent_cache.clear()
|
|
45
|
+
logger.info("SubAgentMiddleware factory initialized (cache cleared)")
|
|
39
46
|
|
|
40
47
|
|
|
41
48
|
def get_subagent_factory():
|
|
@@ -43,6 +50,48 @@ def get_subagent_factory():
|
|
|
43
50
|
return _subagent_factory, _current_llm_config
|
|
44
51
|
|
|
45
52
|
|
|
53
|
+
def _get_config_hash(llm_config: Dict[str, Any]) -> str:
|
|
54
|
+
"""Generate a hash of llm_config for caching."""
|
|
55
|
+
config_str = json.dumps(llm_config, sort_keys=True, default=str)
|
|
56
|
+
return hashlib.md5(config_str.encode()).hexdigest()[:12]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_or_create_subagent(
|
|
60
|
+
agent_name: str, factory_func, llm_config: Dict[str, Any]
|
|
61
|
+
) -> Any:
|
|
62
|
+
"""
|
|
63
|
+
Get cached subagent or create new one.
|
|
64
|
+
|
|
65
|
+
Caching avoids expensive recompilation of LangGraph agents.
|
|
66
|
+
Cache key = "{agent_name}_{config_hash}" to handle different LLM configs.
|
|
67
|
+
"""
|
|
68
|
+
global _subagent_cache
|
|
69
|
+
|
|
70
|
+
config_hash = _get_config_hash(llm_config)
|
|
71
|
+
cache_key = f"{agent_name}_{config_hash}"
|
|
72
|
+
|
|
73
|
+
if cache_key in _subagent_cache:
|
|
74
|
+
logger.info(f"Using cached subagent '{agent_name}' (key={cache_key})")
|
|
75
|
+
return _subagent_cache[cache_key]
|
|
76
|
+
|
|
77
|
+
logger.info(f"Creating new subagent '{agent_name}' (key={cache_key})...")
|
|
78
|
+
subagent = factory_func(agent_name, llm_config)
|
|
79
|
+
_subagent_cache[cache_key] = subagent
|
|
80
|
+
logger.info(
|
|
81
|
+
f"Cached subagent '{agent_name}' (total cached: {len(_subagent_cache)})"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return subagent
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def clear_subagent_cache():
|
|
88
|
+
"""Clear the subagent cache. Useful for testing or config changes."""
|
|
89
|
+
global _subagent_cache
|
|
90
|
+
count = len(_subagent_cache)
|
|
91
|
+
_subagent_cache.clear()
|
|
92
|
+
logger.info(f"Subagent cache cleared ({count} entries removed)")
|
|
93
|
+
|
|
94
|
+
|
|
46
95
|
def create_task_tool(
|
|
47
96
|
caller_name: str,
|
|
48
97
|
allowed_subagents: Optional[List[str]] = None,
|
|
@@ -96,11 +145,13 @@ def create_task_tool(
|
|
|
96
145
|
)
|
|
97
146
|
context: Optional[str] = Field(
|
|
98
147
|
default=None,
|
|
99
|
-
description="Additional context for the subagent: resource info (file sizes, memory), previous code, variable state, etc."
|
|
148
|
+
description="Additional context for the subagent: resource info (file sizes, memory), previous code, variable state, etc.",
|
|
100
149
|
)
|
|
101
150
|
|
|
102
151
|
@tool(args_schema=TaskInput)
|
|
103
|
-
def task_tool(
|
|
152
|
+
def task_tool(
|
|
153
|
+
agent_name: str, description: str, context: Optional[str] = None
|
|
154
|
+
) -> str:
|
|
104
155
|
"""
|
|
105
156
|
Delegate a task to a specialized subagent.
|
|
106
157
|
|
|
@@ -133,10 +184,10 @@ def create_task_tool(
|
|
|
133
184
|
|
|
134
185
|
# Import subagent event emitters
|
|
135
186
|
from agent_server.langchain.middleware.subagent_events import (
|
|
136
|
-
|
|
187
|
+
clear_current_subagent,
|
|
137
188
|
emit_subagent_complete,
|
|
189
|
+
emit_subagent_start,
|
|
138
190
|
set_current_subagent,
|
|
139
|
-
clear_current_subagent,
|
|
140
191
|
)
|
|
141
192
|
|
|
142
193
|
# Emit subagent start event for UI
|
|
@@ -148,11 +199,17 @@ def create_task_tool(
|
|
|
148
199
|
return "Error: SubAgentMiddleware not initialized. Call set_subagent_factory first."
|
|
149
200
|
|
|
150
201
|
try:
|
|
202
|
+
import time
|
|
203
|
+
|
|
151
204
|
# Set current subagent context for tool call tracking
|
|
152
205
|
set_current_subagent(agent_name)
|
|
153
206
|
|
|
154
|
-
#
|
|
155
|
-
|
|
207
|
+
# Get or create the subagent (cached for performance)
|
|
208
|
+
# Avoids expensive LangGraph recompilation on each call
|
|
209
|
+
t0 = time.time()
|
|
210
|
+
subagent = get_or_create_subagent(agent_name, factory_func, llm_config)
|
|
211
|
+
t1 = time.time()
|
|
212
|
+
logger.info(f"[TIMING] get_or_create_subagent took {t1-t0:.2f}s")
|
|
156
213
|
|
|
157
214
|
# Execute subagent synchronously with clean context
|
|
158
215
|
# The subagent runs in isolation, receiving task description + optional context
|
|
@@ -169,15 +226,18 @@ def create_task_tool(
|
|
|
169
226
|
enhanced_context = context
|
|
170
227
|
if agent_name == "python_developer":
|
|
171
228
|
try:
|
|
229
|
+
t2 = time.time()
|
|
172
230
|
from agent_server.langchain.middleware.code_history_middleware import (
|
|
173
|
-
get_context_with_history,
|
|
174
231
|
get_code_history_tracker,
|
|
232
|
+
get_context_with_history,
|
|
175
233
|
)
|
|
234
|
+
|
|
176
235
|
tracker = get_code_history_tracker()
|
|
177
236
|
if tracker.get_entry_count() > 0:
|
|
178
237
|
enhanced_context = get_context_with_history(context)
|
|
238
|
+
t3 = time.time()
|
|
179
239
|
logger.info(
|
|
180
|
-
f"[
|
|
240
|
+
f"[TIMING] code history injection took {t3-t2:.2f}s "
|
|
181
241
|
f"(entries={tracker.get_entry_count()}, "
|
|
182
242
|
f"context_len={len(enhanced_context) if enhanced_context else 0})"
|
|
183
243
|
)
|
|
@@ -194,13 +254,21 @@ def create_task_tool(
|
|
|
194
254
|
else:
|
|
195
255
|
message_content = description
|
|
196
256
|
|
|
197
|
-
logger.info(
|
|
257
|
+
logger.info(
|
|
258
|
+
f"[{caller_name}] Subagent message length: {len(message_content)}"
|
|
259
|
+
)
|
|
198
260
|
|
|
199
261
|
# Execute the subagent
|
|
262
|
+
t_invoke_start = time.time()
|
|
263
|
+
logger.info(f"[TIMING] About to invoke subagent '{agent_name}'...")
|
|
200
264
|
result = subagent.invoke(
|
|
201
265
|
{"messages": [{"role": "user", "content": message_content}]},
|
|
202
266
|
config=subagent_config,
|
|
203
267
|
)
|
|
268
|
+
t_invoke_end = time.time()
|
|
269
|
+
logger.info(
|
|
270
|
+
f"[TIMING] subagent.invoke() took {t_invoke_end-t_invoke_start:.2f}s"
|
|
271
|
+
)
|
|
204
272
|
|
|
205
273
|
# Extract the final message from the result
|
|
206
274
|
messages = result.get("messages", [])
|
|
@@ -223,6 +291,7 @@ def create_task_tool(
|
|
|
223
291
|
from agent_server.langchain.middleware.description_injector import (
|
|
224
292
|
process_task_tool_response,
|
|
225
293
|
)
|
|
294
|
+
|
|
226
295
|
process_task_tool_response(agent_name, str(response))
|
|
227
296
|
except Exception as e:
|
|
228
297
|
logger.warning(f"Failed to extract description: {e}")
|