massgen 0.1.4__py3-none-any.whl → 0.1.6__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.
Potentially problematic release.
This version of massgen might be problematic. Click here for more details.
- massgen/__init__.py +1 -1
- massgen/backend/base_with_custom_tool_and_mcp.py +453 -23
- massgen/backend/capabilities.py +39 -0
- massgen/backend/chat_completions.py +111 -197
- massgen/backend/claude.py +210 -181
- massgen/backend/gemini.py +1015 -1559
- massgen/backend/grok.py +3 -2
- massgen/backend/response.py +160 -220
- massgen/chat_agent.py +340 -20
- massgen/cli.py +399 -25
- massgen/config_builder.py +20 -54
- massgen/config_validator.py +931 -0
- massgen/configs/README.md +95 -10
- massgen/configs/memory/gpt5mini_gemini_baseline_research_to_implementation.yaml +94 -0
- massgen/configs/memory/gpt5mini_gemini_context_window_management.yaml +187 -0
- massgen/configs/memory/gpt5mini_gemini_research_to_implementation.yaml +127 -0
- massgen/configs/memory/gpt5mini_high_reasoning_gemini.yaml +107 -0
- massgen/configs/memory/single_agent_compression_test.yaml +64 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +1 -1
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +1 -1
- massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +1 -1
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_langgraph_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_openai_assistant_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/agentscope_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/langgraph_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/interop/openai_assistant_lesson_planner_example.yaml +50 -0
- massgen/configs/tools/custom_tools/interop/smolagent_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/two_models_with_tools_example.yaml +44 -0
- massgen/formatter/_gemini_formatter.py +61 -15
- massgen/memory/README.md +277 -0
- massgen/memory/__init__.py +26 -0
- massgen/memory/_base.py +193 -0
- massgen/memory/_compression.py +237 -0
- massgen/memory/_context_monitor.py +211 -0
- massgen/memory/_conversation.py +255 -0
- massgen/memory/_fact_extraction_prompts.py +333 -0
- massgen/memory/_mem0_adapters.py +257 -0
- massgen/memory/_persistent.py +687 -0
- massgen/memory/docker-compose.qdrant.yml +36 -0
- massgen/memory/docs/DESIGN.md +388 -0
- massgen/memory/docs/QUICKSTART.md +409 -0
- massgen/memory/docs/SUMMARY.md +319 -0
- massgen/memory/docs/agent_use_memory.md +408 -0
- massgen/memory/docs/orchestrator_use_memory.md +586 -0
- massgen/memory/examples.py +237 -0
- massgen/orchestrator.py +207 -7
- massgen/tests/memory/test_agent_compression.py +174 -0
- massgen/tests/memory/test_context_window_management.py +286 -0
- massgen/tests/memory/test_force_compression.py +154 -0
- massgen/tests/memory/test_simple_compression.py +147 -0
- massgen/tests/test_ag2_lesson_planner.py +223 -0
- massgen/tests/test_agent_memory.py +534 -0
- massgen/tests/test_config_validator.py +1156 -0
- massgen/tests/test_conversation_memory.py +382 -0
- massgen/tests/test_langgraph_lesson_planner.py +223 -0
- massgen/tests/test_orchestrator_memory.py +620 -0
- massgen/tests/test_persistent_memory.py +435 -0
- massgen/token_manager/token_manager.py +6 -0
- massgen/tool/__init__.py +2 -9
- massgen/tool/_decorators.py +52 -0
- massgen/tool/_extraframework_agents/ag2_lesson_planner_tool.py +251 -0
- massgen/tool/_extraframework_agents/agentscope_lesson_planner_tool.py +303 -0
- massgen/tool/_extraframework_agents/langgraph_lesson_planner_tool.py +275 -0
- massgen/tool/_extraframework_agents/openai_assistant_lesson_planner_tool.py +247 -0
- massgen/tool/_extraframework_agents/smolagent_lesson_planner_tool.py +180 -0
- massgen/tool/_manager.py +102 -16
- massgen/tool/_registered_tool.py +3 -0
- massgen/tool/_result.py +3 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/METADATA +138 -77
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/RECORD +82 -37
- massgen/backend/gemini_mcp_manager.py +0 -545
- massgen/backend/gemini_trackers.py +0 -344
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/top_level.txt +0 -0
massgen/backend/claude.py
CHANGED
|
@@ -37,7 +37,12 @@ from ..formatter import ClaudeFormatter
|
|
|
37
37
|
from ..logger_config import log_backend_agent_message, log_stream_chunk, logger
|
|
38
38
|
from ..mcp_tools.backend_utils import MCPErrorHandler
|
|
39
39
|
from .base import FilesystemSupport, StreamChunk
|
|
40
|
-
from .base_with_custom_tool_and_mcp import
|
|
40
|
+
from .base_with_custom_tool_and_mcp import (
|
|
41
|
+
CustomToolAndMCPBackend,
|
|
42
|
+
CustomToolChunk,
|
|
43
|
+
ToolExecutionConfig,
|
|
44
|
+
UploadFileError,
|
|
45
|
+
)
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
class ClaudeBackend(CustomToolAndMCPBackend):
|
|
@@ -521,6 +526,87 @@ class ClaudeBackend(CustomToolAndMCPBackend):
|
|
|
521
526
|
async for chunk in self._process_stream(stream, all_params, agent_id):
|
|
522
527
|
yield chunk
|
|
523
528
|
|
|
529
|
+
def _append_tool_result_message(
|
|
530
|
+
self,
|
|
531
|
+
updated_messages: List[Dict[str, Any]],
|
|
532
|
+
call: Dict[str, Any],
|
|
533
|
+
result: Any,
|
|
534
|
+
tool_type: str,
|
|
535
|
+
) -> None:
|
|
536
|
+
"""Append tool result to messages in Claude format.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
updated_messages: Message list to append to
|
|
540
|
+
call: Tool call dictionary with call_id, name, arguments
|
|
541
|
+
result: Tool execution result
|
|
542
|
+
tool_type: "custom" or "mcp"
|
|
543
|
+
|
|
544
|
+
Note:
|
|
545
|
+
Claude uses tool_result format with tool_use_id.
|
|
546
|
+
"""
|
|
547
|
+
tool_result_msg = {
|
|
548
|
+
"role": "user",
|
|
549
|
+
"content": [
|
|
550
|
+
{
|
|
551
|
+
"type": "tool_result",
|
|
552
|
+
"tool_use_id": call.get("call_id", "") or call.get("id", ""),
|
|
553
|
+
"content": str(result),
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
}
|
|
557
|
+
updated_messages.append(tool_result_msg)
|
|
558
|
+
|
|
559
|
+
def _append_tool_error_message(
|
|
560
|
+
self,
|
|
561
|
+
updated_messages: List[Dict[str, Any]],
|
|
562
|
+
call: Dict[str, Any],
|
|
563
|
+
error_msg: str,
|
|
564
|
+
tool_type: str,
|
|
565
|
+
) -> None:
|
|
566
|
+
"""Append tool error to messages in Claude format.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
updated_messages: Message list to append to
|
|
570
|
+
call: Tool call dictionary with call_id, name, arguments
|
|
571
|
+
error_msg: Error message string
|
|
572
|
+
tool_type: "custom" or "mcp"
|
|
573
|
+
|
|
574
|
+
Note:
|
|
575
|
+
Claude uses tool_result format with tool_use_id for errors too.
|
|
576
|
+
"""
|
|
577
|
+
error_result_msg = {
|
|
578
|
+
"role": "user",
|
|
579
|
+
"content": [
|
|
580
|
+
{
|
|
581
|
+
"type": "tool_result",
|
|
582
|
+
"tool_use_id": call.get("call_id", "") or call.get("id", ""),
|
|
583
|
+
"content": error_msg,
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
}
|
|
587
|
+
updated_messages.append(error_result_msg)
|
|
588
|
+
|
|
589
|
+
async def _execute_custom_tool(self, call: Dict[str, Any]) -> AsyncGenerator[CustomToolChunk, None]:
|
|
590
|
+
"""Execute custom tool with streaming support - async generator for base class.
|
|
591
|
+
|
|
592
|
+
This method is called by _execute_tool_with_logging and yields CustomToolChunk
|
|
593
|
+
objects for intermediate streaming output. The base class detects the async
|
|
594
|
+
generator and streams intermediate results to users in real-time.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
call: Tool call dictionary with name and arguments
|
|
598
|
+
|
|
599
|
+
Yields:
|
|
600
|
+
CustomToolChunk objects with streaming data
|
|
601
|
+
|
|
602
|
+
Note:
|
|
603
|
+
- Intermediate chunks (completed=False) are streamed to users in real-time
|
|
604
|
+
- Final chunk (completed=True) contains the accumulated result for message history
|
|
605
|
+
- The base class automatically handles extracting and displaying intermediate chunks
|
|
606
|
+
"""
|
|
607
|
+
async for chunk in self.stream_custom_tool_execution(call):
|
|
608
|
+
yield chunk
|
|
609
|
+
|
|
524
610
|
async def _stream_with_custom_and_mcp_tools(
|
|
525
611
|
self,
|
|
526
612
|
current_messages: List[Dict[str, Any]],
|
|
@@ -660,8 +746,9 @@ class ClaudeBackend(CustomToolAndMCPBackend):
|
|
|
660
746
|
elif event.type == "message_delta":
|
|
661
747
|
pass
|
|
662
748
|
elif event.type == "message_stop":
|
|
663
|
-
|
|
664
|
-
|
|
749
|
+
captured_calls = []
|
|
750
|
+
tool_use_by_name: Dict[str, Dict[str, Any]] = {}
|
|
751
|
+
|
|
665
752
|
if current_tool_uses:
|
|
666
753
|
for tool_use in current_tool_uses.values():
|
|
667
754
|
tool_name = tool_use.get("name", "")
|
|
@@ -675,39 +762,68 @@ class ClaudeBackend(CustomToolAndMCPBackend):
|
|
|
675
762
|
except json.JSONDecodeError:
|
|
676
763
|
parsed_input = {"raw_input": tool_input}
|
|
677
764
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
765
|
+
captured_calls.append(
|
|
766
|
+
{
|
|
767
|
+
"name": tool_name,
|
|
768
|
+
"arguments": json.dumps(parsed_input) if isinstance(parsed_input, dict) else str(parsed_input),
|
|
769
|
+
"call_id": tool_use["id"],
|
|
770
|
+
},
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
# Store tool_use info for reconstruction
|
|
774
|
+
tool_use_by_name[tool_use["id"]] = {
|
|
775
|
+
"id": tool_use["id"],
|
|
776
|
+
"parsed_input": parsed_input,
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
# Use helper to categorize tool calls
|
|
780
|
+
if captured_calls:
|
|
781
|
+
categorized_mcp, categorized_custom, categorized_provider = self._categorize_tool_calls(captured_calls)
|
|
782
|
+
|
|
783
|
+
# Reconstruct Claude-specific format for each category
|
|
784
|
+
for call in categorized_mcp:
|
|
785
|
+
tool_info = tool_use_by_name[call["call_id"]]
|
|
786
|
+
mcp_tool_calls.append(
|
|
787
|
+
{
|
|
788
|
+
"id": tool_info["id"],
|
|
789
|
+
"type": "function",
|
|
790
|
+
"function": {
|
|
791
|
+
"name": call["name"],
|
|
792
|
+
"arguments": tool_info["parsed_input"],
|
|
687
793
|
},
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
794
|
+
},
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
for call in categorized_custom:
|
|
798
|
+
tool_info = tool_use_by_name[call["call_id"]]
|
|
799
|
+
custom_tool_calls.append(
|
|
800
|
+
{
|
|
801
|
+
"id": tool_info["id"],
|
|
802
|
+
"type": "function",
|
|
803
|
+
"function": {
|
|
804
|
+
"name": call["name"],
|
|
805
|
+
"arguments": tool_info["parsed_input"],
|
|
698
806
|
},
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
807
|
+
},
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
# Build non-MCP/non-custom tool calls (including workflow tools)
|
|
811
|
+
non_mcp_non_custom_tool_calls = []
|
|
812
|
+
for call in categorized_provider:
|
|
813
|
+
tool_info = tool_use_by_name[call["call_id"]]
|
|
814
|
+
non_mcp_non_custom_tool_calls.append(
|
|
815
|
+
{
|
|
816
|
+
"id": tool_info["id"],
|
|
817
|
+
"type": "function",
|
|
818
|
+
"function": {
|
|
819
|
+
"name": call["name"],
|
|
820
|
+
"arguments": tool_info["parsed_input"],
|
|
709
821
|
},
|
|
710
|
-
|
|
822
|
+
},
|
|
823
|
+
)
|
|
824
|
+
else:
|
|
825
|
+
non_mcp_non_custom_tool_calls = []
|
|
826
|
+
|
|
711
827
|
# Emit non-MCP/non-custom tool calls for the caller to execute
|
|
712
828
|
if non_mcp_non_custom_tool_calls:
|
|
713
829
|
log_stream_chunk("backend.claude", "tool_calls", non_mcp_non_custom_tool_calls, agent_id)
|
|
@@ -773,156 +889,72 @@ class ClaudeBackend(CustomToolAndMCPBackend):
|
|
|
773
889
|
# Append the assistant message with tool uses
|
|
774
890
|
updated_messages.append({"role": "assistant", "content": assistant_content})
|
|
775
891
|
|
|
776
|
-
#
|
|
777
|
-
|
|
778
|
-
|
|
892
|
+
# Configuration for custom tool execution
|
|
893
|
+
CUSTOM_TOOL_CONFIG = ToolExecutionConfig(
|
|
894
|
+
tool_type="custom",
|
|
895
|
+
chunk_type="custom_tool_status",
|
|
896
|
+
emoji_prefix="🔧 [Custom Tool]",
|
|
897
|
+
success_emoji="✅ [Custom Tool]",
|
|
898
|
+
error_emoji="❌ [Custom Tool Error]",
|
|
899
|
+
source_prefix="custom_",
|
|
900
|
+
status_called="custom_tool_called",
|
|
901
|
+
status_response="custom_tool_response",
|
|
902
|
+
status_error="custom_tool_error",
|
|
903
|
+
execution_callback=self._execute_custom_tool,
|
|
904
|
+
)
|
|
779
905
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
906
|
+
# Configuration for MCP tool execution
|
|
907
|
+
MCP_TOOL_CONFIG = ToolExecutionConfig(
|
|
908
|
+
tool_type="mcp",
|
|
909
|
+
chunk_type="mcp_status",
|
|
910
|
+
emoji_prefix="🔧 [MCP Tool]",
|
|
911
|
+
success_emoji="✅ [MCP Tool]",
|
|
912
|
+
error_emoji="❌ [MCP Tool Error]",
|
|
913
|
+
source_prefix="mcp_",
|
|
914
|
+
status_called="mcp_tool_called",
|
|
915
|
+
status_response="mcp_tool_response",
|
|
916
|
+
status_error="mcp_tool_error",
|
|
917
|
+
execution_callback=self._execute_mcp_function_with_retry,
|
|
918
|
+
)
|
|
787
919
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
if isinstance(tool_call["function"].get("arguments"), (dict, list))
|
|
795
|
-
else tool_call["function"].get("arguments", "{}"),
|
|
796
|
-
"call_id": tool_call["id"],
|
|
797
|
-
},
|
|
798
|
-
)
|
|
799
|
-
if not result_str or result_str.startswith("Error:"):
|
|
800
|
-
logger.warning(f"Custom function {function_name} failed: {result_str or 'unknown error'}")
|
|
801
|
-
result_str = result_str or "Tool execution failed"
|
|
802
|
-
except Exception as e:
|
|
803
|
-
logger.error(f"Unexpected error in custom function execution: {e}")
|
|
804
|
-
result_str = f"Error executing custom tool: {str(e)}"
|
|
805
|
-
|
|
806
|
-
# Build tool result message
|
|
807
|
-
tool_result_msg = {
|
|
808
|
-
"role": "user",
|
|
809
|
-
"content": [
|
|
810
|
-
{
|
|
811
|
-
"type": "tool_result",
|
|
812
|
-
"tool_use_id": tool_call["id"],
|
|
813
|
-
"content": result_str,
|
|
814
|
-
},
|
|
815
|
-
],
|
|
920
|
+
def normalize_tool_call(tool_call: Dict[str, Any]) -> Dict[str, Any]:
|
|
921
|
+
"""Convert Claude tool call format to unified format."""
|
|
922
|
+
return {
|
|
923
|
+
"name": tool_call["function"]["name"],
|
|
924
|
+
"arguments": json.dumps(tool_call["function"]["arguments"]) if isinstance(tool_call["function"].get("arguments"), (dict, list)) else tool_call["function"].get("arguments", "{}"),
|
|
925
|
+
"call_id": tool_call["id"], # Normalize "id" to "call_id"
|
|
816
926
|
}
|
|
817
927
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
)
|
|
834
|
-
|
|
835
|
-
logger.info(f"Executed custom function {function_name}")
|
|
836
|
-
yield StreamChunk(
|
|
837
|
-
type="custom_tool_status",
|
|
838
|
-
status="custom_tool_response",
|
|
839
|
-
content=f"✅ [Custom Tool] {function_name} completed",
|
|
840
|
-
source=f"custom_{function_name}",
|
|
841
|
-
)
|
|
842
|
-
|
|
843
|
-
# Then execute MCP tool calls and append results
|
|
928
|
+
# Execute custom tools using unified method
|
|
929
|
+
for tool_call in custom_tool_calls:
|
|
930
|
+
# Normalize Claude tool call format to unified format
|
|
931
|
+
normalized_call = normalize_tool_call(tool_call)
|
|
932
|
+
|
|
933
|
+
# Use unified execution method
|
|
934
|
+
async for chunk in self._execute_tool_with_logging(
|
|
935
|
+
normalized_call,
|
|
936
|
+
CUSTOM_TOOL_CONFIG,
|
|
937
|
+
updated_messages,
|
|
938
|
+
set(),
|
|
939
|
+
):
|
|
940
|
+
yield chunk
|
|
941
|
+
|
|
942
|
+
# Execute MCP tools using unified method
|
|
844
943
|
for tool_call in mcp_tool_calls:
|
|
845
|
-
|
|
944
|
+
# Normalize Claude tool call format to unified format
|
|
945
|
+
normalized_call = normalize_tool_call(tool_call)
|
|
946
|
+
|
|
947
|
+
# Use unified execution method
|
|
948
|
+
async for chunk in self._execute_tool_with_logging(
|
|
949
|
+
normalized_call,
|
|
950
|
+
MCP_TOOL_CONFIG,
|
|
951
|
+
updated_messages,
|
|
952
|
+
set(),
|
|
953
|
+
):
|
|
954
|
+
yield chunk
|
|
846
955
|
|
|
847
|
-
# Yield MCP tool call status
|
|
848
|
-
yield StreamChunk(
|
|
849
|
-
type="mcp_status",
|
|
850
|
-
status="mcp_tool_called",
|
|
851
|
-
content=f"🔧 [MCP Tool] Calling {function_name}...",
|
|
852
|
-
source=f"mcp_{function_name}",
|
|
853
|
-
)
|
|
854
|
-
|
|
855
|
-
try:
|
|
856
|
-
# Execute MCP function
|
|
857
|
-
args_json = json.dumps(tool_call["function"]["arguments"]) if isinstance(tool_call["function"].get("arguments"), (dict, list)) else tool_call["function"].get("arguments", "{}")
|
|
858
|
-
result_list = await self._execute_mcp_function_with_retry(function_name, args_json)
|
|
859
|
-
if not result_list or (isinstance(result_list[0], str) and result_list[0].startswith("Error:")):
|
|
860
|
-
logger.warning(f"MCP function {function_name} failed after retries: {result_list[0] if result_list else 'unknown error'}")
|
|
861
|
-
continue
|
|
862
|
-
result_str = result_list[0]
|
|
863
|
-
result_obj = result_list[1] if len(result_list) > 1 else None
|
|
864
|
-
except Exception as e:
|
|
865
|
-
logger.error(f"Unexpected error in MCP function execution: {e}")
|
|
866
|
-
continue
|
|
867
|
-
|
|
868
|
-
# Build tool result message: { "role":"user", "content":[{ "type":"tool_result", "tool_use_id": tool_call["id"], "content": result_str }] }
|
|
869
|
-
tool_result_msg = {
|
|
870
|
-
"role": "user",
|
|
871
|
-
"content": [
|
|
872
|
-
{
|
|
873
|
-
"type": "tool_result",
|
|
874
|
-
"tool_use_id": tool_call["id"],
|
|
875
|
-
"content": result_str,
|
|
876
|
-
},
|
|
877
|
-
],
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
# Append to updated_messages
|
|
881
|
-
updated_messages.append(tool_result_msg)
|
|
882
|
-
|
|
883
|
-
yield StreamChunk(
|
|
884
|
-
type="mcp_status",
|
|
885
|
-
status="function_call",
|
|
886
|
-
content=f"Arguments for Calling {function_name}: {json.dumps(tool_call['function'].get('arguments', {}))}",
|
|
887
|
-
source=f"mcp_{function_name}",
|
|
888
|
-
)
|
|
889
|
-
|
|
890
|
-
# If result_obj might be structured, try to display summary
|
|
891
|
-
result_display = None
|
|
892
|
-
try:
|
|
893
|
-
if hasattr(result_obj, "content") and result_obj.content:
|
|
894
|
-
part = result_obj.content[0]
|
|
895
|
-
if hasattr(part, "text"):
|
|
896
|
-
result_display = str(part.text)
|
|
897
|
-
except Exception:
|
|
898
|
-
result_display = None
|
|
899
|
-
if result_display:
|
|
900
|
-
yield StreamChunk(
|
|
901
|
-
type="mcp_status",
|
|
902
|
-
status="function_call_output",
|
|
903
|
-
content=f"Results for Calling {function_name}: {result_display}",
|
|
904
|
-
source=f"mcp_{function_name}",
|
|
905
|
-
)
|
|
906
|
-
else:
|
|
907
|
-
yield StreamChunk(
|
|
908
|
-
type="mcp_status",
|
|
909
|
-
status="function_call_output",
|
|
910
|
-
content=f"Results for Calling {function_name}: {result_str}",
|
|
911
|
-
source=f"mcp_{function_name}",
|
|
912
|
-
)
|
|
913
|
-
|
|
914
|
-
logger.info(f"Executed MCP function {function_name} (stdio/streamable-http)")
|
|
915
|
-
yield StreamChunk(
|
|
916
|
-
type="mcp_status",
|
|
917
|
-
status="mcp_tool_response",
|
|
918
|
-
content=f"✅ [MCP Tool] {function_name} completed",
|
|
919
|
-
source=f"mcp_{function_name}",
|
|
920
|
-
)
|
|
921
|
-
|
|
922
|
-
# Trim updated_messages using base class method
|
|
923
956
|
updated_messages = self._trim_message_history(updated_messages)
|
|
924
957
|
|
|
925
|
-
# After processing all tool calls, recurse
|
|
926
958
|
async for chunk in self._stream_with_custom_and_mcp_tools(updated_messages, tools, client, **kwargs):
|
|
927
959
|
yield chunk
|
|
928
960
|
return
|
|
@@ -1167,10 +1199,8 @@ class ClaudeBackend(CustomToolAndMCPBackend):
|
|
|
1167
1199
|
content=f"\n⚠️ {user_message} ({error}); continuing without MCP tools\n",
|
|
1168
1200
|
)
|
|
1169
1201
|
|
|
1170
|
-
# Build non-MCP configuration and stream fallback
|
|
1171
1202
|
fallback_params = dict(api_params)
|
|
1172
1203
|
|
|
1173
|
-
# Remove any MCP tools from the tools list
|
|
1174
1204
|
if "tools" in fallback_params and self._mcp_functions:
|
|
1175
1205
|
mcp_names = set(self._mcp_functions.keys())
|
|
1176
1206
|
non_mcp_tools = []
|
|
@@ -1195,7 +1225,7 @@ class ClaudeBackend(CustomToolAndMCPBackend):
|
|
|
1195
1225
|
function_name: str,
|
|
1196
1226
|
arguments_json: str,
|
|
1197
1227
|
max_retries: int = 3,
|
|
1198
|
-
) ->
|
|
1228
|
+
) -> Tuple[str, Any]:
|
|
1199
1229
|
"""Execute MCP function with Claude-specific formatting."""
|
|
1200
1230
|
# Use parent class method which returns tuple
|
|
1201
1231
|
result_str, result_obj = await super()._execute_mcp_function_with_retry(
|
|
@@ -1204,10 +1234,9 @@ class ClaudeBackend(CustomToolAndMCPBackend):
|
|
|
1204
1234
|
max_retries,
|
|
1205
1235
|
)
|
|
1206
1236
|
|
|
1207
|
-
# Convert to list format expected by Claude streaming
|
|
1208
1237
|
if result_str.startswith("Error:"):
|
|
1209
|
-
return
|
|
1210
|
-
return
|
|
1238
|
+
return (result_str, {"error": result_str})
|
|
1239
|
+
return (result_str, result_obj)
|
|
1211
1240
|
|
|
1212
1241
|
def create_tool_result_message(self, tool_call: Dict[str, Any], result_content: str) -> Dict[str, Any]:
|
|
1213
1242
|
"""Create tool result message in Claude's expected format."""
|