hdsp-jupyter-extension 2.0.7__py3-none-any.whl → 2.0.8__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/embedding_service.py +67 -46
- agent_server/core/rag_manager.py +31 -17
- agent_server/core/retriever.py +13 -8
- agent_server/core/vllm_embedding_service.py +243 -0
- agent_server/langchain/agent.py +8 -0
- agent_server/langchain/custom_middleware.py +58 -31
- agent_server/langchain/hitl_config.py +6 -1
- agent_server/langchain/logging_utils.py +53 -14
- agent_server/langchain/prompts.py +47 -16
- agent_server/langchain/tools/__init__.py +13 -0
- agent_server/langchain/tools/file_tools.py +285 -7
- agent_server/langchain/tools/file_utils.py +334 -0
- agent_server/langchain/tools/lsp_tools.py +264 -0
- agent_server/main.py +7 -0
- agent_server/routers/langchain_agent.py +115 -19
- agent_server/routers/rag.py +8 -3
- hdsp_agent_core/models/rag.py +15 -1
- hdsp_agent_core/services/rag_service.py +6 -1
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js +160 -3
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js +1759 -221
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js +14 -12
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
- {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +1 -1
- {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +66 -63
- jupyter_ext/__init__.py +18 -0
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +176 -1
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +3 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.4770ec0fb2d173b6deb4.js → frontend_styles_index_js.8740a527757068814573.js} +160 -3
- jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.29cf4312af19e86f82af.js → lib_index_js.e4ff4b5779b5e049f84c.js} +1759 -221
- jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.61343eb4cf0577e74b50.js → remoteEntry.020cdb0b864cfaa4e41e.js} +14 -12
- jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
- hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
- jupyter_ext/labextension/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.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.7.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.7.data → hdsp_jupyter_extension-2.0.8.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.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.7.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -78,6 +78,20 @@ def parse_json_tool_call(text) -> Optional[Dict[str, Any]]:
|
|
|
78
78
|
return None
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def normalize_tool_name(tool_name: str) -> str:
|
|
82
|
+
"""Normalize tool name to match registered tool names.
|
|
83
|
+
|
|
84
|
+
Rules:
|
|
85
|
+
- write_todos_tool → write_todos (TodoListMiddleware exception)
|
|
86
|
+
- other tools without _tool suffix → add _tool suffix
|
|
87
|
+
"""
|
|
88
|
+
if tool_name == "write_todos_tool":
|
|
89
|
+
return "write_todos"
|
|
90
|
+
if not tool_name.endswith("_tool") and tool_name != "write_todos":
|
|
91
|
+
return f"{tool_name}_tool"
|
|
92
|
+
return tool_name
|
|
93
|
+
|
|
94
|
+
|
|
81
95
|
def create_tool_call_message(tool_name: str, arguments: Dict[str, Any]) -> AIMessage:
|
|
82
96
|
"""Create AIMessage with tool_calls from parsed JSON.
|
|
83
97
|
|
|
@@ -88,9 +102,7 @@ def create_tool_call_message(tool_name: str, arguments: Dict[str, Any]) -> AIMes
|
|
|
88
102
|
Returns:
|
|
89
103
|
AIMessage with properly formatted tool_calls
|
|
90
104
|
"""
|
|
91
|
-
|
|
92
|
-
if not tool_name.endswith("_tool"):
|
|
93
|
-
tool_name = f"{tool_name}_tool"
|
|
105
|
+
tool_name = normalize_tool_name(tool_name)
|
|
94
106
|
|
|
95
107
|
return AIMessage(
|
|
96
108
|
content="",
|
|
@@ -363,70 +375,70 @@ def create_limit_tool_calls_middleware(wrap_model_call):
|
|
|
363
375
|
|
|
364
376
|
def _get_string_params_from_tools(tools) -> Dict[str, set]:
|
|
365
377
|
"""Extract string parameter names from tool schemas.
|
|
366
|
-
|
|
378
|
+
|
|
367
379
|
Analyzes each tool's Pydantic args_schema to determine which parameters
|
|
368
380
|
should be strings (not arrays).
|
|
369
|
-
|
|
381
|
+
|
|
370
382
|
Args:
|
|
371
383
|
tools: List of LangChain tools
|
|
372
|
-
|
|
384
|
+
|
|
373
385
|
Returns:
|
|
374
386
|
Dict mapping tool names to sets of string parameter names
|
|
375
387
|
"""
|
|
376
388
|
from typing import get_args, get_origin
|
|
377
|
-
|
|
389
|
+
|
|
378
390
|
tool_string_params: Dict[str, set] = {}
|
|
379
|
-
|
|
391
|
+
|
|
380
392
|
for tool in tools:
|
|
381
|
-
tool_name = getattr(tool,
|
|
393
|
+
tool_name = getattr(tool, "name", None)
|
|
382
394
|
if not tool_name:
|
|
383
395
|
continue
|
|
384
|
-
|
|
385
|
-
args_schema = getattr(tool,
|
|
396
|
+
|
|
397
|
+
args_schema = getattr(tool, "args_schema", None)
|
|
386
398
|
if not args_schema:
|
|
387
399
|
continue
|
|
388
|
-
|
|
400
|
+
|
|
389
401
|
string_params = set()
|
|
390
|
-
|
|
402
|
+
|
|
391
403
|
# Get field annotations from Pydantic model
|
|
392
404
|
try:
|
|
393
|
-
annotations = getattr(args_schema,
|
|
405
|
+
annotations = getattr(args_schema, "__annotations__", {})
|
|
394
406
|
for field_name, field_type in annotations.items():
|
|
395
407
|
origin = get_origin(field_type)
|
|
396
|
-
|
|
408
|
+
|
|
397
409
|
# Check if it's a simple str type
|
|
398
410
|
if field_type is str:
|
|
399
411
|
string_params.add(field_name)
|
|
400
412
|
# Check if it's Optional[str] (Union[str, None])
|
|
401
|
-
elif origin is type(None) or str(origin) ==
|
|
413
|
+
elif origin is type(None) or str(origin) == "typing.Union":
|
|
402
414
|
args = get_args(field_type)
|
|
403
415
|
if str in args:
|
|
404
416
|
string_params.add(field_name)
|
|
405
417
|
except Exception as e:
|
|
406
418
|
logger.debug("Failed to analyze schema for tool %s: %s", tool_name, e)
|
|
407
|
-
|
|
419
|
+
|
|
408
420
|
if string_params:
|
|
409
421
|
tool_string_params[tool_name] = string_params
|
|
410
422
|
logger.debug("Tool %s string params: %s", tool_name, string_params)
|
|
411
|
-
|
|
423
|
+
|
|
412
424
|
return tool_string_params
|
|
413
425
|
|
|
414
426
|
|
|
415
427
|
def create_normalize_tool_args_middleware(wrap_model_call, tools=None):
|
|
416
428
|
"""Create middleware to normalize tool call arguments.
|
|
417
|
-
|
|
429
|
+
|
|
418
430
|
Gemini sometimes returns tool call arguments with list values instead of strings.
|
|
419
431
|
This middleware converts list arguments to strings ONLY for parameters that
|
|
420
432
|
are defined as str in the tool's Pydantic schema.
|
|
421
|
-
|
|
433
|
+
|
|
422
434
|
Args:
|
|
423
435
|
wrap_model_call: LangChain's wrap_model_call decorator
|
|
424
436
|
tools: Optional list of tools to analyze for type information
|
|
425
|
-
|
|
437
|
+
|
|
426
438
|
Returns:
|
|
427
439
|
Middleware function
|
|
428
440
|
"""
|
|
429
|
-
|
|
441
|
+
|
|
430
442
|
# Build tool -> string params mapping from tool schemas
|
|
431
443
|
tool_string_params: Dict[str, set] = {}
|
|
432
444
|
if tools:
|
|
@@ -436,25 +448,37 @@ def create_normalize_tool_args_middleware(wrap_model_call, tools=None):
|
|
|
436
448
|
len(tool_string_params),
|
|
437
449
|
{k: list(v) for k, v in tool_string_params.items()},
|
|
438
450
|
)
|
|
439
|
-
|
|
451
|
+
|
|
440
452
|
@wrap_model_call
|
|
441
453
|
@_with_middleware_logging("normalize_tool_args")
|
|
442
454
|
def normalize_tool_args(request, handler):
|
|
443
455
|
response = handler(request)
|
|
444
|
-
|
|
456
|
+
|
|
445
457
|
if hasattr(response, "result"):
|
|
446
458
|
result = response.result
|
|
447
459
|
messages = result if isinstance(result, list) else [result]
|
|
448
|
-
|
|
460
|
+
|
|
449
461
|
for msg in messages:
|
|
450
462
|
if isinstance(msg, AIMessage) and hasattr(msg, "tool_calls"):
|
|
451
463
|
tool_calls = msg.tool_calls
|
|
452
464
|
if tool_calls:
|
|
453
465
|
for tool_call in tool_calls:
|
|
454
466
|
tool_name = tool_call.get("name", "")
|
|
467
|
+
# Normalize tool name (e.g., write_todos_tool → write_todos)
|
|
468
|
+
normalized_name = normalize_tool_name(tool_name)
|
|
469
|
+
if normalized_name != tool_name:
|
|
470
|
+
logger.info(
|
|
471
|
+
"Normalized tool name: %s → %s",
|
|
472
|
+
tool_name,
|
|
473
|
+
normalized_name,
|
|
474
|
+
)
|
|
475
|
+
tool_call["name"] = normalized_name
|
|
476
|
+
tool_name = normalized_name
|
|
455
477
|
string_params = tool_string_params.get(tool_name, set())
|
|
456
|
-
|
|
457
|
-
if "args" in tool_call and isinstance(
|
|
478
|
+
|
|
479
|
+
if "args" in tool_call and isinstance(
|
|
480
|
+
tool_call["args"], dict
|
|
481
|
+
):
|
|
458
482
|
args = tool_call["args"]
|
|
459
483
|
# Normalize list arguments to strings for str-typed params
|
|
460
484
|
for key, value in args.items():
|
|
@@ -464,9 +488,12 @@ def create_normalize_tool_args_middleware(wrap_model_call, tools=None):
|
|
|
464
488
|
for part in value:
|
|
465
489
|
if isinstance(part, str):
|
|
466
490
|
text_parts.append(part)
|
|
467
|
-
elif
|
|
491
|
+
elif (
|
|
492
|
+
isinstance(part, dict)
|
|
493
|
+
and part.get("type") == "text"
|
|
494
|
+
):
|
|
468
495
|
text_parts.append(part.get("text", ""))
|
|
469
|
-
|
|
496
|
+
|
|
470
497
|
if text_parts:
|
|
471
498
|
normalized_value = "\n".join(text_parts)
|
|
472
499
|
logger.info(
|
|
@@ -476,9 +503,9 @@ def create_normalize_tool_args_middleware(wrap_model_call, tools=None):
|
|
|
476
503
|
tool_name,
|
|
477
504
|
)
|
|
478
505
|
args[key] = normalized_value
|
|
479
|
-
|
|
506
|
+
|
|
480
507
|
return response
|
|
481
|
-
|
|
508
|
+
|
|
482
509
|
return normalize_tool_args
|
|
483
510
|
|
|
484
511
|
|
|
@@ -59,7 +59,12 @@ def get_hitl_interrupt_config() -> Dict[str, Any]:
|
|
|
59
59
|
# File write requires approval
|
|
60
60
|
"write_file_tool": {
|
|
61
61
|
"allowed_decisions": ["approve", "edit", "reject"],
|
|
62
|
-
"description": "
|
|
62
|
+
"description": "File write requires approval",
|
|
63
|
+
},
|
|
64
|
+
# File edit requires approval (string replacement with diff preview)
|
|
65
|
+
"edit_file_tool": {
|
|
66
|
+
"allowed_decisions": ["approve", "edit", "reject"],
|
|
67
|
+
"description": "File edit requires approval",
|
|
63
68
|
},
|
|
64
69
|
# Final answer doesn't need approval
|
|
65
70
|
"final_answer_tool": False,
|
|
@@ -115,8 +115,28 @@ def _with_middleware_logging(name: str):
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
class LLMTraceLogger(BaseCallbackHandler):
|
|
118
|
-
"""Log prompts, responses, tool calls, and tool messages.
|
|
119
|
-
|
|
118
|
+
"""Log prompts, responses, tool calls, and tool messages.
|
|
119
|
+
|
|
120
|
+
Only logs newly added messages to avoid duplicate logging of conversation history.
|
|
121
|
+
Uses content hash of first message (usually system prompt) to identify conversation threads.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self):
|
|
125
|
+
super().__init__()
|
|
126
|
+
# Track last logged message count per conversation thread
|
|
127
|
+
# Key: hash of first message content, Value: message count
|
|
128
|
+
self._last_message_counts: Dict[str, int] = {}
|
|
129
|
+
|
|
130
|
+
def _get_conversation_key(self, batch) -> str:
|
|
131
|
+
"""Get a stable key for the conversation based on first message content."""
|
|
132
|
+
if not batch:
|
|
133
|
+
return "empty"
|
|
134
|
+
first_msg = batch[0]
|
|
135
|
+
content = getattr(first_msg, "content", "")
|
|
136
|
+
# Use hash of first 200 chars of first message (usually system prompt)
|
|
137
|
+
content_preview = str(content)[:200] if content else ""
|
|
138
|
+
return str(hash(content_preview))
|
|
139
|
+
|
|
120
140
|
def _normalize_batches(self, messages):
|
|
121
141
|
if not messages:
|
|
122
142
|
return []
|
|
@@ -125,19 +145,38 @@ class LLMTraceLogger(BaseCallbackHandler):
|
|
|
125
145
|
return [messages]
|
|
126
146
|
|
|
127
147
|
def _log_prompt_batches(self, title: str, messages) -> None:
|
|
148
|
+
"""Log only new messages that haven't been logged before."""
|
|
128
149
|
for batch_idx, batch in enumerate(self._normalize_batches(messages)):
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
# Get stable conversation key based on first message
|
|
151
|
+
conv_key = self._get_conversation_key(batch)
|
|
152
|
+
batch_key = f"{conv_key}_{batch_idx}"
|
|
153
|
+
last_count = self._last_message_counts.get(batch_key, 0)
|
|
154
|
+
|
|
155
|
+
# Only log new messages
|
|
156
|
+
new_messages = batch[last_count:]
|
|
157
|
+
if not new_messages:
|
|
158
|
+
logger.debug(
|
|
159
|
+
"Skipping duplicate log for batch %d (already logged %d messages)",
|
|
160
|
+
batch_idx,
|
|
161
|
+
last_count,
|
|
162
|
+
)
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
# Update count
|
|
166
|
+
self._last_message_counts[batch_key] = len(batch)
|
|
167
|
+
|
|
168
|
+
# Log with offset info
|
|
169
|
+
header = f"{title} (batch={batch_idx}, new={len(new_messages)}, total={len(batch)})"
|
|
170
|
+
|
|
171
|
+
# Format new messages with correct indices
|
|
172
|
+
lines = [LOG_SEPARATOR, header, LOG_SEPARATOR]
|
|
173
|
+
for idx, message in enumerate(new_messages, start=last_count):
|
|
174
|
+
lines.append(f"[{idx}] {message.__class__.__name__}")
|
|
175
|
+
lines.append(_pretty_json(_serialize_message(message)))
|
|
176
|
+
if idx < len(batch) - 1:
|
|
177
|
+
lines.append(LOG_SUBSECTION)
|
|
178
|
+
lines.append(LOG_SEPARATOR)
|
|
179
|
+
logger.info("%s", "\n".join(lines))
|
|
141
180
|
|
|
142
181
|
def on_chat_model_start(self, serialized, messages, **kwargs) -> None:
|
|
143
182
|
if not messages:
|
|
@@ -8,7 +8,14 @@ and middleware-specific prompts.
|
|
|
8
8
|
DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant.
|
|
9
9
|
Your role is to help users with data analysis, visualization, and Python coding tasks in Jupyter notebooks. You can use only Korean
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Core Behavior
|
|
12
|
+
Be concise and direct. Answer in fewer than 4 lines unless the user asks for detail.
|
|
13
|
+
After working on a file, just stop - don't explain what you did unless asked.
|
|
14
|
+
Avoid unnecessary introductions or conclusions.
|
|
15
|
+
|
|
16
|
+
## Task Management
|
|
17
|
+
Use write_todos for complex multi-step tasks (3+ steps). Mark tasks in_progress before starting, completed immediately after finishing.
|
|
18
|
+
For simple 1-2 step tasks, just do them directly without todos.
|
|
12
19
|
|
|
13
20
|
You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
|
|
14
21
|
1. Check your todo list - are there pending or in_progress items?
|
|
@@ -24,8 +31,6 @@ You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
|
|
|
24
31
|
}
|
|
25
32
|
4. If ALL todos are completed → call final_answer_tool with a summary
|
|
26
33
|
|
|
27
|
-
NEVER end your turn without calling a tool. NEVER produce an empty response.
|
|
28
|
-
|
|
29
34
|
## 🔴 MANDATORY: Resource Check Before Data Hanlding
|
|
30
35
|
**ALWAYS call check_resource_tool FIRST** when the task involves:
|
|
31
36
|
- Loading files: .csv, .parquet, .json, .xlsx, .pickle, .h5, .feather
|
|
@@ -45,11 +50,27 @@ NEVER end your turn without calling a tool. NEVER produce an empty response.
|
|
|
45
50
|
- Ending without calling final_answer_tool
|
|
46
51
|
- Leaving todos in "in_progress" or "pending" state without continuing
|
|
47
52
|
|
|
48
|
-
##
|
|
49
|
-
**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
## 📖 File Reading Best Practices
|
|
54
|
+
**CRITICAL**: When exploring codebases or reading files, use pagination to prevent context overflow.
|
|
55
|
+
|
|
56
|
+
**Pattern for codebase exploration:**
|
|
57
|
+
1. First scan: `read_file_tool(path, limit=100)` - See file structure and key sections
|
|
58
|
+
2. Targeted read: `read_file_tool(path, offset=100, limit=200)` - Read specific sections if needed
|
|
59
|
+
3. Full read: Only read without limit when necessary for immediate editing
|
|
60
|
+
|
|
61
|
+
**When to paginate (use offset/limit):**
|
|
62
|
+
- Reading any file >500 lines
|
|
63
|
+
- Exploring unfamiliar codebases (always start with limit=100)
|
|
64
|
+
- Reading multiple files in sequence
|
|
65
|
+
- Any research or investigation task
|
|
66
|
+
|
|
67
|
+
**When full read is OK:**
|
|
68
|
+
- Small files (<500 lines)
|
|
69
|
+
- Files you need to edit immediately after reading
|
|
70
|
+
- After confirming file size with first scan
|
|
71
|
+
|
|
72
|
+
## 🔧 Code Development
|
|
73
|
+
For code generation/refactoring, use LSP tools (diagnostics_tool, references_tool) to check errors and find symbol usages. Use multiedit_file_tool for multiple changes in one file.
|
|
53
74
|
"""
|
|
54
75
|
|
|
55
76
|
JSON_TOOL_SCHEMA = """You MUST respond with ONLY valid JSON matching this schema:
|
|
@@ -63,7 +84,7 @@ Available tools:
|
|
|
63
84
|
- markdown_tool: Add markdown cell. Arguments: {"content": "<markdown>"}
|
|
64
85
|
- final_answer_tool: Complete task. Arguments: {"answer": "<summary>"}
|
|
65
86
|
- write_todos: Update task list. Arguments: {"todos": [{"content": "...", "status": "pending|in_progress|completed"}]}
|
|
66
|
-
- read_file_tool: Read file. Arguments: {"path": "<file_path>"}
|
|
87
|
+
- read_file_tool: Read file with pagination. Arguments: {"path": "<file_path>", "offset": 0, "limit": 500}
|
|
67
88
|
- write_file_tool: Write file. Arguments: {"path": "<path>", "content": "<content>", "overwrite": false}
|
|
68
89
|
- list_files_tool: List directory. Arguments: {"path": ".", "recursive": false}
|
|
69
90
|
- search_workspace_tool: Search files. Arguments: {"pattern": "<regex>", "file_types": ["py"], "path": "."}
|
|
@@ -75,27 +96,32 @@ Output ONLY the JSON object, no markdown, no explanation."""
|
|
|
75
96
|
|
|
76
97
|
TODO_LIST_SYSTEM_PROMPT = """
|
|
77
98
|
## CRITICAL WORKFLOW RULES - MUST FOLLOW:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
99
|
+
- NEVER stop after calling write_todos - ALWAYS make another tool call immediately
|
|
100
|
+
- For simple 1-2 step tasks, just do them directly without todos.
|
|
101
|
+
|
|
102
|
+
## 🔴 NEW USER MESSAGE = FRESH START:
|
|
103
|
+
- When user sends a NEW message, treat it as a COMPLETELY NEW TASK
|
|
104
|
+
- IGNORE any previous todo completion history - start fresh
|
|
105
|
+
- Do NOT assume any work was already done based on past conversations
|
|
106
|
+
- Create a NEW todo list for the new request, even if similar items existed before
|
|
107
|
+
- "다음 단계 제시" from a previous task is NOT completed for the new task
|
|
81
108
|
|
|
82
109
|
## Todo List Management:
|
|
83
110
|
- Before complex tasks, use write_todos to create a task list
|
|
84
111
|
- Update todos as you complete each step (mark 'in_progress' → 'completed')
|
|
85
|
-
- Each todo item should be specific and descriptive
|
|
112
|
+
- Each todo item should be specific and descriptive
|
|
86
113
|
- All todo items must be written in Korean
|
|
87
114
|
- ALWAYS include "다음 단계 제시" as the LAST item
|
|
88
115
|
|
|
89
116
|
## Task Completion Flow:
|
|
90
117
|
1. When current task is done → mark it 'completed' with write_todos
|
|
91
|
-
2.
|
|
92
|
-
3. For "다음 단계 제시" → mark completed, then call final_answer_tool with suggestions
|
|
93
|
-
4. NEVER end your turn after write_todos - you MUST continue with actual work
|
|
118
|
+
2. For "다음 단계 제시" → mark completed, then call final_answer_tool with suggestions
|
|
94
119
|
|
|
95
120
|
## FORBIDDEN PATTERNS:
|
|
96
121
|
❌ Calling write_todos and then stopping
|
|
97
122
|
❌ Updating todo status without doing the actual work
|
|
98
123
|
❌ Ending turn without calling final_answer_tool when all tasks are done
|
|
124
|
+
❌ Marking a todo as 'completed' without actually executing it in THIS conversation
|
|
99
125
|
"""
|
|
100
126
|
|
|
101
127
|
TODO_LIST_TOOL_DESCRIPTION = """Update the task list for tracking progress.
|
|
@@ -116,4 +142,9 @@ NON_HITL_TOOLS = {
|
|
|
116
142
|
"search_notebook_cells_tool",
|
|
117
143
|
"search_notebook_cells",
|
|
118
144
|
"write_todos",
|
|
145
|
+
# LSP tools (read-only)
|
|
146
|
+
"diagnostics_tool",
|
|
147
|
+
"diagnostics",
|
|
148
|
+
"references_tool",
|
|
149
|
+
"references",
|
|
119
150
|
}
|
|
@@ -7,15 +7,20 @@ Tools available:
|
|
|
7
7
|
- final_answer: Complete the task
|
|
8
8
|
- read_file: Read file content
|
|
9
9
|
- write_file: Write file content
|
|
10
|
+
- edit_file: Edit file with string replacement
|
|
10
11
|
- list_files: List directory contents
|
|
11
12
|
- search_workspace: Search files in workspace
|
|
12
13
|
- search_notebook_cells: Search cells in notebooks
|
|
13
14
|
- execute_command_tool: Run shell commands (client-executed)
|
|
14
15
|
- check_resource_tool: Check resources before data processing (client-executed)
|
|
16
|
+
- diagnostics_tool: Get LSP diagnostics (errors, warnings)
|
|
17
|
+
- references_tool: Find symbol references via LSP
|
|
15
18
|
"""
|
|
16
19
|
|
|
17
20
|
from agent_server.langchain.tools.file_tools import (
|
|
21
|
+
edit_file_tool,
|
|
18
22
|
list_files_tool,
|
|
23
|
+
multiedit_file_tool,
|
|
19
24
|
read_file_tool,
|
|
20
25
|
write_file_tool,
|
|
21
26
|
)
|
|
@@ -24,6 +29,10 @@ from agent_server.langchain.tools.jupyter_tools import (
|
|
|
24
29
|
jupyter_cell_tool,
|
|
25
30
|
markdown_tool,
|
|
26
31
|
)
|
|
32
|
+
from agent_server.langchain.tools.lsp_tools import (
|
|
33
|
+
diagnostics_tool,
|
|
34
|
+
references_tool,
|
|
35
|
+
)
|
|
27
36
|
from agent_server.langchain.tools.resource_tools import check_resource_tool
|
|
28
37
|
from agent_server.langchain.tools.search_tools import (
|
|
29
38
|
search_notebook_cells_tool,
|
|
@@ -37,9 +46,13 @@ __all__ = [
|
|
|
37
46
|
"final_answer_tool",
|
|
38
47
|
"read_file_tool",
|
|
39
48
|
"write_file_tool",
|
|
49
|
+
"edit_file_tool",
|
|
50
|
+
"multiedit_file_tool",
|
|
40
51
|
"list_files_tool",
|
|
41
52
|
"search_workspace_tool",
|
|
42
53
|
"search_notebook_cells_tool",
|
|
43
54
|
"execute_command_tool",
|
|
44
55
|
"check_resource_tool",
|
|
56
|
+
"diagnostics_tool",
|
|
57
|
+
"references_tool",
|
|
45
58
|
]
|