massgen 0.1.0a3__py3-none-any.whl → 0.1.2__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/agent_config.py +17 -0
- massgen/api_params_handler/_api_params_handler_base.py +1 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +15 -2
- massgen/api_params_handler/_claude_api_params_handler.py +8 -1
- massgen/api_params_handler/_gemini_api_params_handler.py +73 -0
- massgen/api_params_handler/_response_api_params_handler.py +8 -1
- massgen/backend/base.py +83 -0
- massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +286 -15
- massgen/backend/capabilities.py +6 -6
- massgen/backend/chat_completions.py +200 -103
- massgen/backend/claude.py +115 -18
- massgen/backend/claude_code.py +378 -14
- massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
- massgen/backend/gemini.py +1333 -1629
- massgen/backend/gemini_mcp_manager.py +545 -0
- massgen/backend/gemini_trackers.py +344 -0
- massgen/backend/gemini_utils.py +43 -0
- massgen/backend/grok.py +39 -6
- massgen/backend/response.py +147 -81
- massgen/cli.py +605 -110
- massgen/config_builder.py +376 -27
- massgen/configs/README.md +123 -80
- massgen/configs/basic/multi/three_agents_default.yaml +3 -3
- massgen/configs/basic/single/single_agent.yaml +1 -1
- massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +7 -29
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +5 -6
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +4 -4
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +4 -4
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +2 -2
- massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
- massgen/formatter/_chat_completions_formatter.py +104 -0
- massgen/formatter/_claude_formatter.py +120 -0
- massgen/formatter/_gemini_formatter.py +448 -0
- massgen/formatter/_response_formatter.py +88 -0
- massgen/frontend/coordination_ui.py +4 -2
- massgen/logger_config.py +35 -3
- massgen/message_templates.py +56 -6
- massgen/orchestrator.py +512 -16
- massgen/stream_chunk/base.py +3 -0
- massgen/tests/custom_tools_example.py +392 -0
- massgen/tests/mcp_test_server.py +17 -7
- massgen/tests/test_config_builder.py +423 -0
- massgen/tests/test_custom_tools.py +401 -0
- massgen/tests/test_intelligent_planning_mode.py +643 -0
- massgen/tests/test_tools.py +127 -0
- massgen/token_manager/token_manager.py +13 -4
- massgen/tool/README.md +935 -0
- massgen/tool/__init__.py +39 -0
- massgen/tool/_async_helpers.py +70 -0
- massgen/tool/_basic/__init__.py +8 -0
- massgen/tool/_basic/_two_num_tool.py +24 -0
- massgen/tool/_code_executors/__init__.py +10 -0
- massgen/tool/_code_executors/_python_executor.py +74 -0
- massgen/tool/_code_executors/_shell_executor.py +61 -0
- massgen/tool/_exceptions.py +39 -0
- massgen/tool/_file_handlers/__init__.py +10 -0
- massgen/tool/_file_handlers/_file_operations.py +218 -0
- massgen/tool/_manager.py +634 -0
- massgen/tool/_registered_tool.py +88 -0
- massgen/tool/_result.py +66 -0
- massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
- massgen/tool/docs/builtin_tools.md +681 -0
- massgen/tool/docs/exceptions.md +794 -0
- massgen/tool/docs/execution_results.md +691 -0
- massgen/tool/docs/manager.md +887 -0
- massgen/tool/docs/workflow_toolkits.md +529 -0
- massgen/tool/workflow_toolkits/__init__.py +57 -0
- massgen/tool/workflow_toolkits/base.py +55 -0
- massgen/tool/workflow_toolkits/new_answer.py +126 -0
- massgen/tool/workflow_toolkits/vote.py +167 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/METADATA +87 -129
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/RECORD +120 -44
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/WHEEL +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -28,11 +28,9 @@ agents:
|
|
|
28
28
|
DISCORD_TOKEN: "${DISCORD_TOKEN}"
|
|
29
29
|
security:
|
|
30
30
|
level: "high"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Message reading, sending, and management
|
|
35
|
-
- Channel and server information access
|
|
31
|
+
exclude_tools:
|
|
32
|
+
- mcp__discord__discord_send_webhook_message
|
|
33
|
+
- mcp__discord__discord_edit_webhook_message
|
|
36
34
|
|
|
37
35
|
- id: "openai_discord_agent"
|
|
38
36
|
backend:
|
|
@@ -50,11 +48,6 @@ agents:
|
|
|
50
48
|
exclude_tools:
|
|
51
49
|
- mcp__discord__discord_send_webhook_message
|
|
52
50
|
- mcp__discord__discord_edit_webhook_message
|
|
53
|
-
system_message: |
|
|
54
|
-
Available Discord Tools:
|
|
55
|
-
- Discord server interaction via MCP integration
|
|
56
|
-
- Message reading, sending, and management
|
|
57
|
-
- Channel and server information access
|
|
58
51
|
|
|
59
52
|
- id: "claude_code_discord_agent"
|
|
60
53
|
backend:
|
|
@@ -68,11 +61,6 @@ agents:
|
|
|
68
61
|
args: ["-y", "mcp-discord", "--config", "${DISCORD_TOKEN}"]
|
|
69
62
|
env:
|
|
70
63
|
DISCORD_TOKEN: "${DISCORD_TOKEN}"
|
|
71
|
-
system_message: |
|
|
72
|
-
Available Discord Tools:
|
|
73
|
-
- Discord server interaction via MCP integration
|
|
74
|
-
- Message reading, sending, and management
|
|
75
|
-
- Channel and server information access
|
|
76
64
|
|
|
77
65
|
- id: "claude_discord_agent"
|
|
78
66
|
backend:
|
|
@@ -90,11 +78,6 @@ agents:
|
|
|
90
78
|
exclude_tools:
|
|
91
79
|
- mcp__discord__discord_send_webhook_message
|
|
92
80
|
- mcp__discord__discord_edit_webhook_message
|
|
93
|
-
system_message: |
|
|
94
|
-
Available Discord Tools:
|
|
95
|
-
- Discord server interaction via MCP integration
|
|
96
|
-
- Message reading, sending, and management
|
|
97
|
-
- Channel and server information access
|
|
98
81
|
|
|
99
82
|
- id: "grok_discord_agent"
|
|
100
83
|
backend:
|
|
@@ -112,11 +95,6 @@ agents:
|
|
|
112
95
|
exclude_tools:
|
|
113
96
|
- mcp__discord__discord_send_webhook_message
|
|
114
97
|
- mcp__discord__discord_edit_webhook_message
|
|
115
|
-
system_message: |
|
|
116
|
-
Available Discord Tools:
|
|
117
|
-
- Discord server interaction via MCP integration
|
|
118
|
-
- Message reading, sending, and management
|
|
119
|
-
- Channel and server information access
|
|
120
98
|
|
|
121
99
|
ui:
|
|
122
100
|
display_type: "rich_terminal"
|
|
@@ -124,8 +102,8 @@ ui:
|
|
|
124
102
|
|
|
125
103
|
# Orchestrator Settings with Coordination Configuration
|
|
126
104
|
orchestrator:
|
|
127
|
-
snapshot_storage: "
|
|
128
|
-
agent_temporary_workspace: "
|
|
105
|
+
snapshot_storage: "snapshots" # Directory for workspace snapshots
|
|
106
|
+
agent_temporary_workspace: "temp_workspaces" # Directory for temporary agent workspaces
|
|
129
107
|
coordination:
|
|
130
108
|
enable_planning_mode: true
|
|
131
109
|
planning_mode_instruction: |
|
|
@@ -134,7 +112,7 @@ orchestrator:
|
|
|
134
112
|
1. Describe your intended actions and reasoning
|
|
135
113
|
2. Analyze other agents' proposals
|
|
136
114
|
3. Use only the 'vote' or 'new_answer' tools for coordination
|
|
137
|
-
4. DO NOT execute any
|
|
138
|
-
5. Save
|
|
115
|
+
4. Execute read-only actions - DO NOT execute any actions that have side effects (e.g., sending messages, modifying data)
|
|
116
|
+
5. Save actions that have side effects for the final presentation phase when the winning agent will implement the plan
|
|
139
117
|
|
|
140
118
|
Focus on planning, analysis, and coordination rather than execution.
|
|
@@ -134,8 +134,8 @@ ui:
|
|
|
134
134
|
|
|
135
135
|
# Orchestrator Settings with Coordination Configuration
|
|
136
136
|
orchestrator:
|
|
137
|
-
snapshot_storage: "
|
|
138
|
-
agent_temporary_workspace: "
|
|
137
|
+
snapshot_storage: "snapshots" # Directory for workspace snapshots
|
|
138
|
+
agent_temporary_workspace: "temp_workspaces" # Directory for temporary agent workspaces
|
|
139
139
|
coordination:
|
|
140
140
|
enable_planning_mode: true
|
|
141
141
|
planning_mode_instruction: |
|
|
@@ -144,8 +144,7 @@ orchestrator:
|
|
|
144
144
|
1. Describe your intended file operations and reasoning
|
|
145
145
|
2. Analyze other agents' proposals for the filesystem tasks
|
|
146
146
|
3. Use only the 'vote' or 'new_answer' tools for coordination
|
|
147
|
-
4. DO NOT execute any
|
|
148
|
-
5. Save
|
|
147
|
+
4. Execute read-only actions - DO NOT execute any actions that have side effects (e.g., sending messages, modifying data)
|
|
148
|
+
5. Save actions that have side effects for the final presentation phase when the winning agent will implement the plan
|
|
149
149
|
|
|
150
|
-
Focus on planning, analysis, and coordination rather than execution.
|
|
151
|
-
Example: "I would create a 'src' directory and write a main.py file..." rather than actually creating them.
|
|
150
|
+
Focus on planning, analysis, and coordination rather than execution.
|
|
@@ -135,8 +135,8 @@ ui:
|
|
|
135
135
|
|
|
136
136
|
# Orchestrator Settings with Coordination Configuration
|
|
137
137
|
orchestrator:
|
|
138
|
-
snapshot_storage: "
|
|
139
|
-
agent_temporary_workspace: "
|
|
138
|
+
snapshot_storage: "snapshots" # Directory for workspace snapshots
|
|
139
|
+
agent_temporary_workspace: "temp_workspaces" # Directory for temporary agent workspaces
|
|
140
140
|
coordination:
|
|
141
141
|
enable_planning_mode: true
|
|
142
142
|
planning_mode_instruction: |
|
|
@@ -145,7 +145,7 @@ orchestrator:
|
|
|
145
145
|
1. Describe your intended actions and reasoning
|
|
146
146
|
2. Analyze other agents' proposals
|
|
147
147
|
3. Use only the 'vote' or 'new_answer' tools for coordination
|
|
148
|
-
4. DO NOT execute any
|
|
149
|
-
5. Save
|
|
148
|
+
4. Execute read-only actions - DO NOT execute any actions that have side effects (e.g., sending messages, modifying data)
|
|
149
|
+
5. Save actions that have side effects for the final presentation phase when the winning agent will implement the plan
|
|
150
150
|
|
|
151
151
|
Focus on planning, analysis, and coordination rather than execution.
|
|
@@ -139,8 +139,8 @@ ui:
|
|
|
139
139
|
|
|
140
140
|
# Orchestrator Settings with Coordination Configuration
|
|
141
141
|
orchestrator:
|
|
142
|
-
snapshot_storage: "
|
|
143
|
-
agent_temporary_workspace: "
|
|
142
|
+
snapshot_storage: "snapshots" # Directory for workspace snapshots
|
|
143
|
+
agent_temporary_workspace: "temp_workspaces" # Directory for temporary agent workspaces
|
|
144
144
|
coordination:
|
|
145
145
|
enable_planning_mode: true
|
|
146
146
|
planning_mode_instruction: |
|
|
@@ -149,7 +149,7 @@ orchestrator:
|
|
|
149
149
|
1. Describe your intended actions and reasoning
|
|
150
150
|
2. Analyze other agents' proposals
|
|
151
151
|
3. Use only the 'vote' or 'new_answer' tools for coordination
|
|
152
|
-
4. DO NOT execute any
|
|
153
|
-
5. Save
|
|
152
|
+
4. Execute read-only actions - DO NOT execute any actions that have side effects (e.g., sending messages, modifying data)
|
|
153
|
+
5. Save actions that have side effects for the final presentation phase when the winning agent will implement the plan
|
|
154
154
|
|
|
155
155
|
Focus on planning, analysis, and coordination rather than execution.
|
|
@@ -67,7 +67,7 @@ orchestrator:
|
|
|
67
67
|
2. Analyze other agents' proposals
|
|
68
68
|
3. Use only the 'vote' or 'new_answer' tools for coordination
|
|
69
69
|
4. You CAN use web search for information gathering
|
|
70
|
-
5. DO NOT execute
|
|
71
|
-
6. Save
|
|
70
|
+
5. DO NOT execute any actions that have side effects (e.g., sending messages, modifying data)
|
|
71
|
+
6. Save actions that have side effects for the final presentation phase when the winning agent will implement the plan
|
|
72
72
|
|
|
73
73
|
Focus on planning, analysis, and coordination rather than execution.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# MassGen Voting Sensitivity Configuration Example
|
|
2
|
+
#
|
|
3
|
+
# How to run:
|
|
4
|
+
# massgen --config configs/voting/gemini_gpt_voting_sensitivity.yaml "Your question here"
|
|
5
|
+
#
|
|
6
|
+
# Or in interactive mode:
|
|
7
|
+
# massgen --config configs/voting/gemini_gpt_voting_sensitivity.yaml
|
|
8
|
+
#
|
|
9
|
+
# Try different sensitivity levels by changing the voting_sensitivity value below
|
|
10
|
+
|
|
11
|
+
agents:
|
|
12
|
+
- id: "gemini2.5flash"
|
|
13
|
+
backend:
|
|
14
|
+
type: "gemini"
|
|
15
|
+
model: "gemini-2.5-flash"
|
|
16
|
+
enable_web_search: true
|
|
17
|
+
|
|
18
|
+
- id: "gpt5nano"
|
|
19
|
+
backend:
|
|
20
|
+
type: "openai"
|
|
21
|
+
model: "gpt-5-nano"
|
|
22
|
+
text:
|
|
23
|
+
verbosity: "medium"
|
|
24
|
+
reasoning:
|
|
25
|
+
effort: "medium"
|
|
26
|
+
summary: "auto"
|
|
27
|
+
enable_web_search: true
|
|
28
|
+
enable_code_interpreter: true
|
|
29
|
+
|
|
30
|
+
orchestrator:
|
|
31
|
+
# voting_sensitivity controls how critical agents are when evaluating answers
|
|
32
|
+
#
|
|
33
|
+
# Options:
|
|
34
|
+
# - "lenient" (default): Agents vote for existing answers more readily, fewer new answers
|
|
35
|
+
# Use when you want faster convergence
|
|
36
|
+
#
|
|
37
|
+
# - "balanced": Agents apply detailed criteria (comprehensive, accurate, complete?) before voting,
|
|
38
|
+
# more new answers. Use when you want thorough evaluation
|
|
39
|
+
#
|
|
40
|
+
# - "strict": Agents apply high standards of excellence (all aspects, edge cases, reference-quality) before voting,
|
|
41
|
+
# most new answers. Use when you need maximum quality
|
|
42
|
+
#
|
|
43
|
+
voting_sensitivity: "balanced"
|
|
44
|
+
|
|
45
|
+
# max_new_answers_per_agent controls how many new answers each agent can provide
|
|
46
|
+
# Once an agent reaches this limit, they can only vote (not provide new answers)
|
|
47
|
+
#
|
|
48
|
+
# Options:
|
|
49
|
+
# - null (default): No limit - agents can provide unlimited new answers
|
|
50
|
+
# - 1, 2, 3, etc.: Cap the number of new answers per agent
|
|
51
|
+
#
|
|
52
|
+
# Example: Set to 2 for thorough evaluation but bounded duration
|
|
53
|
+
max_new_answers_per_agent: 2
|
|
54
|
+
|
|
55
|
+
# answer_novelty_requirement controls how different new answers must be from existing ones
|
|
56
|
+
#
|
|
57
|
+
# Options:
|
|
58
|
+
# - "lenient" (default): No additional checks - current behavior (fastest)
|
|
59
|
+
# - "balanced": Reject if >70% token overlap - requires meaningful differences
|
|
60
|
+
# - "strict": Reject if >50% token overlap - requires substantially different solutions
|
|
61
|
+
#
|
|
62
|
+
# Example: Use "balanced" to prevent agents from just rephrasing the same answer
|
|
63
|
+
answer_novelty_requirement: "balanced"
|
|
64
|
+
|
|
65
|
+
ui:
|
|
66
|
+
display_type: "rich_terminal"
|
|
67
|
+
logging_enabled: true
|
|
@@ -258,6 +258,110 @@ class ChatCompletionsFormatter(FormatterBase):
|
|
|
258
258
|
|
|
259
259
|
return converted_tools
|
|
260
260
|
|
|
261
|
+
def format_custom_tools(self, custom_tools: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
262
|
+
"""
|
|
263
|
+
Convert custom tools from RegisteredToolEntry format to Chat Completions API format.
|
|
264
|
+
|
|
265
|
+
Custom tools are provided as a dictionary where:
|
|
266
|
+
- Keys are tool names (str)
|
|
267
|
+
- Values are RegisteredToolEntry objects with:
|
|
268
|
+
- tool_name: str
|
|
269
|
+
- schema_def: dict with structure {"type": "function", "function": {...}}
|
|
270
|
+
- get_extended_schema: property that returns the schema with extensions
|
|
271
|
+
|
|
272
|
+
Chat Completions API expects: {"type": "function", "function": {"name": ..., "description": ..., "parameters": ...}}
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
custom_tools: Dictionary of tool_name -> RegisteredToolEntry objects
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
List of tools in Chat Completions API format
|
|
279
|
+
"""
|
|
280
|
+
if not custom_tools:
|
|
281
|
+
return []
|
|
282
|
+
|
|
283
|
+
converted_tools = []
|
|
284
|
+
|
|
285
|
+
# Handle dictionary format: {tool_name: RegisteredToolEntry, ...}
|
|
286
|
+
if isinstance(custom_tools, dict):
|
|
287
|
+
for tool_name, tool_entry in custom_tools.items():
|
|
288
|
+
# Check if it's a RegisteredToolEntry object with schema_def
|
|
289
|
+
if hasattr(tool_entry, "schema_def"):
|
|
290
|
+
tool_schema = tool_entry.schema_def
|
|
291
|
+
|
|
292
|
+
# Schema may already be in Chat Completions format
|
|
293
|
+
if tool_schema.get("type") == "function" and "function" in tool_schema:
|
|
294
|
+
# Already in correct format, just append
|
|
295
|
+
converted_tools.append(tool_schema)
|
|
296
|
+
elif tool_schema.get("type") == "function":
|
|
297
|
+
# Response API format, need to wrap in function object
|
|
298
|
+
converted_tools.append(
|
|
299
|
+
{
|
|
300
|
+
"type": "function",
|
|
301
|
+
"function": {
|
|
302
|
+
"name": tool_schema.get("name", tool_entry.tool_name if hasattr(tool_entry, "tool_name") else tool_name),
|
|
303
|
+
"description": tool_schema.get("description", ""),
|
|
304
|
+
"parameters": tool_schema.get("parameters", {}),
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
)
|
|
308
|
+
# Check if it has get_extended_schema property
|
|
309
|
+
elif hasattr(tool_entry, "get_extended_schema"):
|
|
310
|
+
tool_schema = tool_entry.get_extended_schema
|
|
311
|
+
|
|
312
|
+
if tool_schema.get("type") == "function" and "function" in tool_schema:
|
|
313
|
+
# Already in correct format
|
|
314
|
+
converted_tools.append(tool_schema)
|
|
315
|
+
elif tool_schema.get("type") == "function":
|
|
316
|
+
# Response API format, need to wrap
|
|
317
|
+
converted_tools.append(
|
|
318
|
+
{
|
|
319
|
+
"type": "function",
|
|
320
|
+
"function": {
|
|
321
|
+
"name": tool_schema.get("name", tool_entry.tool_name if hasattr(tool_entry, "tool_name") else tool_name),
|
|
322
|
+
"description": tool_schema.get("description", ""),
|
|
323
|
+
"parameters": tool_schema.get("parameters", {}),
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
)
|
|
327
|
+
# Handle list format for backward compatibility
|
|
328
|
+
elif isinstance(custom_tools, list):
|
|
329
|
+
for tool in custom_tools:
|
|
330
|
+
if hasattr(tool, "schema_def"):
|
|
331
|
+
tool_schema = tool.schema_def
|
|
332
|
+
|
|
333
|
+
if tool_schema.get("type") == "function" and "function" in tool_schema:
|
|
334
|
+
converted_tools.append(tool_schema)
|
|
335
|
+
elif tool_schema.get("type") == "function":
|
|
336
|
+
converted_tools.append(
|
|
337
|
+
{
|
|
338
|
+
"type": "function",
|
|
339
|
+
"function": {
|
|
340
|
+
"name": tool_schema.get("name", tool.tool_name),
|
|
341
|
+
"description": tool_schema.get("description", ""),
|
|
342
|
+
"parameters": tool_schema.get("parameters", {}),
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
)
|
|
346
|
+
elif hasattr(tool, "get_extended_schema"):
|
|
347
|
+
tool_schema = tool.get_extended_schema
|
|
348
|
+
|
|
349
|
+
if tool_schema.get("type") == "function" and "function" in tool_schema:
|
|
350
|
+
converted_tools.append(tool_schema)
|
|
351
|
+
elif tool_schema.get("type") == "function":
|
|
352
|
+
converted_tools.append(
|
|
353
|
+
{
|
|
354
|
+
"type": "function",
|
|
355
|
+
"function": {
|
|
356
|
+
"name": tool_schema.get("name", tool.tool_name),
|
|
357
|
+
"description": tool_schema.get("description", ""),
|
|
358
|
+
"parameters": tool_schema.get("parameters", {}),
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
return converted_tools
|
|
364
|
+
|
|
261
365
|
def format_mcp_tools(self, mcp_functions: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
262
366
|
"""Convert MCP tools to Chat Completions format."""
|
|
263
367
|
if not mcp_functions:
|
|
@@ -233,3 +233,123 @@ class ClaudeFormatter(FormatterBase):
|
|
|
233
233
|
converted_tools.append(tool)
|
|
234
234
|
|
|
235
235
|
return converted_tools
|
|
236
|
+
|
|
237
|
+
def format_custom_tools(self, custom_tools: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
238
|
+
"""
|
|
239
|
+
Convert custom tools from RegisteredToolEntry format to Claude's custom tool format.
|
|
240
|
+
|
|
241
|
+
Custom tools are provided as a dictionary where:
|
|
242
|
+
- Keys are tool names (str)
|
|
243
|
+
- Values are RegisteredToolEntry objects with:
|
|
244
|
+
- tool_name: str
|
|
245
|
+
- schema_def: dict with structure {"type": "function", "function": {...}}
|
|
246
|
+
- get_extended_schema: property that returns the schema with extensions
|
|
247
|
+
|
|
248
|
+
Claude expects: {"type": "custom", "name": ..., "description": ..., "input_schema": ...}
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
custom_tools: Dictionary of tool_name -> RegisteredToolEntry objects
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
List of tools in Claude's custom tool format
|
|
255
|
+
"""
|
|
256
|
+
if not custom_tools:
|
|
257
|
+
return []
|
|
258
|
+
|
|
259
|
+
converted_tools = []
|
|
260
|
+
|
|
261
|
+
# Handle dictionary format: {tool_name: RegisteredToolEntry, ...}
|
|
262
|
+
if isinstance(custom_tools, dict):
|
|
263
|
+
for tool_name, tool_entry in custom_tools.items():
|
|
264
|
+
# Check if it's a RegisteredToolEntry object with schema_def
|
|
265
|
+
if hasattr(tool_entry, "schema_def"):
|
|
266
|
+
tool_schema = tool_entry.schema_def
|
|
267
|
+
|
|
268
|
+
# Extract function details from Chat Completions format
|
|
269
|
+
if tool_schema.get("type") == "function" and "function" in tool_schema:
|
|
270
|
+
func = tool_schema["function"]
|
|
271
|
+
converted_tools.append(
|
|
272
|
+
{
|
|
273
|
+
"type": "custom",
|
|
274
|
+
"name": func.get("name", tool_entry.tool_name if hasattr(tool_entry, "tool_name") else tool_name),
|
|
275
|
+
"description": func.get("description", ""),
|
|
276
|
+
"input_schema": func.get("parameters", {}),
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
elif tool_schema.get("type") == "function":
|
|
280
|
+
# Response API format - already has name, description, parameters at top level
|
|
281
|
+
converted_tools.append(
|
|
282
|
+
{
|
|
283
|
+
"type": "custom",
|
|
284
|
+
"name": tool_schema.get("name", tool_entry.tool_name if hasattr(tool_entry, "tool_name") else tool_name),
|
|
285
|
+
"description": tool_schema.get("description", ""),
|
|
286
|
+
"input_schema": tool_schema.get("parameters", {}),
|
|
287
|
+
},
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
# Unknown format, try to extract what we can
|
|
291
|
+
converted_tools.append(
|
|
292
|
+
{
|
|
293
|
+
"type": "custom",
|
|
294
|
+
"name": tool_entry.tool_name if hasattr(tool_entry, "tool_name") else tool_name,
|
|
295
|
+
"description": tool_schema.get("description", ""),
|
|
296
|
+
"input_schema": tool_schema.get("parameters", {}),
|
|
297
|
+
},
|
|
298
|
+
)
|
|
299
|
+
# Handle direct schema format (for backward compatibility)
|
|
300
|
+
elif isinstance(tool_entry, dict):
|
|
301
|
+
if tool_entry.get("type") == "function" and "function" in tool_entry:
|
|
302
|
+
# Chat Completions format
|
|
303
|
+
func = tool_entry["function"]
|
|
304
|
+
converted_tools.append(
|
|
305
|
+
{
|
|
306
|
+
"type": "custom",
|
|
307
|
+
"name": func.get("name", tool_name),
|
|
308
|
+
"description": func.get("description", ""),
|
|
309
|
+
"input_schema": func.get("parameters", {}),
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
elif tool_entry.get("type") == "function":
|
|
313
|
+
# Response API format
|
|
314
|
+
converted_tools.append(
|
|
315
|
+
{
|
|
316
|
+
"type": "custom",
|
|
317
|
+
"name": tool_entry.get("name", tool_name),
|
|
318
|
+
"description": tool_entry.get("description", ""),
|
|
319
|
+
"input_schema": tool_entry.get("parameters", {}),
|
|
320
|
+
},
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
# Already in Claude format or unknown
|
|
324
|
+
converted_tools.append(tool_entry)
|
|
325
|
+
|
|
326
|
+
# Handle list format (if custom_tools is already a list)
|
|
327
|
+
elif isinstance(custom_tools, list):
|
|
328
|
+
for tool in custom_tools:
|
|
329
|
+
if isinstance(tool, dict):
|
|
330
|
+
if tool.get("type") == "function" and "function" in tool:
|
|
331
|
+
# Chat Completions format
|
|
332
|
+
func = tool["function"]
|
|
333
|
+
converted_tools.append(
|
|
334
|
+
{
|
|
335
|
+
"type": "custom",
|
|
336
|
+
"name": func.get("name", ""),
|
|
337
|
+
"description": func.get("description", ""),
|
|
338
|
+
"input_schema": func.get("parameters", {}),
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
elif tool.get("type") == "function":
|
|
342
|
+
# Response API format
|
|
343
|
+
converted_tools.append(
|
|
344
|
+
{
|
|
345
|
+
"type": "custom",
|
|
346
|
+
"name": tool.get("name", ""),
|
|
347
|
+
"description": tool.get("description", ""),
|
|
348
|
+
"input_schema": tool.get("parameters", {}),
|
|
349
|
+
},
|
|
350
|
+
)
|
|
351
|
+
else:
|
|
352
|
+
# Already in Claude format or unknown
|
|
353
|
+
converted_tools.append(tool)
|
|
354
|
+
|
|
355
|
+
return converted_tools
|