hanzo-mcp 0.8.11__py3-none-any.whl → 0.9.0__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 hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +1 -3
- hanzo_mcp/analytics/posthog_analytics.py +3 -9
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +6 -15
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +1 -3
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +2 -6
- hanzo_mcp/tools/__init__.py +26 -27
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +22 -15
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +75 -74
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/path_utils.py +34 -0
- hanzo_mcp/tools/common/permissions.py +14 -13
- hanzo_mcp/tools/common/personality.py +983 -701
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +6 -18
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/compiler/__init__.py +8 -0
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/environment/__init__.py +8 -0
- hanzo_mcp/tools/environment/environment_detector.py +594 -0
- hanzo_mcp/tools/filesystem/__init__.py +28 -26
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
- hanzo_mcp/tools/filesystem/base.py +20 -12
- hanzo_mcp/tools/filesystem/content_replace.py +7 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
- hanzo_mcp/tools/filesystem/edit.py +10 -18
- hanzo_mcp/tools/filesystem/find.py +312 -179
- hanzo_mcp/tools/filesystem/git_search.py +12 -24
- hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
- hanzo_mcp/tools/filesystem/read.py +14 -30
- hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
- hanzo_mcp/tools/filesystem/search.py +1160 -0
- hanzo_mcp/tools/filesystem/watch.py +2 -4
- hanzo_mcp/tools/filesystem/write.py +7 -10
- hanzo_mcp/tools/framework/__init__.py +8 -0
- hanzo_mcp/tools/framework/framework_modes.py +714 -0
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
- hanzo_mcp/tools/mcp/mcp_add.py +1 -3
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +10 -27
- hanzo_mcp/tools/memory/conversation_memory.py +636 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +6 -18
- hanzo_mcp/tools/search/find_tool.py +12 -34
- hanzo_mcp/tools/search/unified_search.py +24 -78
- hanzo_mcp/tools/shell/__init__.py +16 -4
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/run_tool.py +56 -0
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/vector/__init__.py +97 -50
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +7 -27
- hanzo_mcp/tools/vector/mock_infinity.py +1 -3
- hanzo_mcp/tools/vector/node_tool.py +538 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/unified_vector.py +384 -0
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
- hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
- hanzo_mcp/tools/agent/swarm_tool.py +0 -718
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
- hanzo_mcp/tools/filesystem/batch_search.py +0 -900
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
- hanzo_mcp/tools/filesystem/find_files.py +0 -369
- hanzo_mcp/tools/filesystem/grep.py +0 -467
- hanzo_mcp/tools/filesystem/search_tool.py +0 -767
- hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
- hanzo_mcp/tools/filesystem/tree.py +0 -270
- hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
- hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
- hanzo_mcp/tools/todo/todo_read.py +0 -143
- hanzo_mcp/tools/todo/todo_write.py +0 -374
- hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/jupyter/base.py
CHANGED
|
@@ -127,9 +127,7 @@ class JupyterBaseTool(FilesystemBaseTool, ABC):
|
|
|
127
127
|
"""
|
|
128
128
|
tool_ctx.set_tool_info(self.name)
|
|
129
129
|
|
|
130
|
-
async def parse_notebook(
|
|
131
|
-
self, file_path: Path
|
|
132
|
-
) -> tuple[dict[str, Any], list[NotebookCellSource]]:
|
|
130
|
+
async def parse_notebook(self, file_path: Path) -> tuple[dict[str, Any], list[NotebookCellSource]]:
|
|
133
131
|
"""Parse a Jupyter notebook file.
|
|
134
132
|
|
|
135
133
|
Args:
|
|
@@ -143,9 +141,7 @@ class JupyterBaseTool(FilesystemBaseTool, ABC):
|
|
|
143
141
|
notebook = json.loads(content)
|
|
144
142
|
|
|
145
143
|
# Get notebook language
|
|
146
|
-
language = (
|
|
147
|
-
notebook.get("metadata", {}).get("language_info", {}).get("name", "python")
|
|
148
|
-
)
|
|
144
|
+
language = notebook.get("metadata", {}).get("language_info", {}).get("name", "python")
|
|
149
145
|
cells = notebook.get("cells", [])
|
|
150
146
|
processed_cells = []
|
|
151
147
|
|
|
@@ -177,9 +173,7 @@ class JupyterBaseTool(FilesystemBaseTool, ABC):
|
|
|
177
173
|
text = output.get("text", "")
|
|
178
174
|
if isinstance(text, list):
|
|
179
175
|
text = "".join(text)
|
|
180
|
-
outputs.append(
|
|
181
|
-
NotebookCellOutput(output_type="stream", text=text)
|
|
182
|
-
)
|
|
176
|
+
outputs.append(NotebookCellOutput(output_type="stream", text=text))
|
|
183
177
|
|
|
184
178
|
elif output_type in ["execute_result", "display_data"]:
|
|
185
179
|
# Process text output
|
|
@@ -205,11 +199,7 @@ class JupyterBaseTool(FilesystemBaseTool, ABC):
|
|
|
205
199
|
media_type="image/jpeg",
|
|
206
200
|
)
|
|
207
201
|
|
|
208
|
-
outputs.append(
|
|
209
|
-
NotebookCellOutput(
|
|
210
|
-
output_type=output_type, text=text, image=image
|
|
211
|
-
)
|
|
212
|
-
)
|
|
202
|
+
outputs.append(NotebookCellOutput(output_type=output_type, text=text, image=image))
|
|
213
203
|
|
|
214
204
|
elif output_type == "error":
|
|
215
205
|
# Format error traceback
|
|
@@ -220,17 +210,13 @@ class JupyterBaseTool(FilesystemBaseTool, ABC):
|
|
|
220
210
|
# Handle raw text strings and lists of strings
|
|
221
211
|
if isinstance(traceback, list):
|
|
222
212
|
# Clean ANSI escape codes and join the list but preserve the formatting
|
|
223
|
-
clean_traceback = [
|
|
224
|
-
clean_ansi_escapes(line) for line in traceback
|
|
225
|
-
]
|
|
213
|
+
clean_traceback = [clean_ansi_escapes(line) for line in traceback]
|
|
226
214
|
traceback_text = "\n".join(clean_traceback)
|
|
227
215
|
else:
|
|
228
216
|
traceback_text = clean_ansi_escapes(str(traceback))
|
|
229
217
|
|
|
230
218
|
error_text = f"{ename}: {evalue}\n{traceback_text}"
|
|
231
|
-
outputs.append(
|
|
232
|
-
NotebookCellOutput(output_type="error", text=error_text)
|
|
233
|
-
)
|
|
219
|
+
outputs.append(NotebookCellOutput(output_type="error", text=error_text))
|
|
234
220
|
|
|
235
221
|
# Create cell object
|
|
236
222
|
processed_cell = NotebookCellSource(
|
|
@@ -151,9 +151,7 @@ jupyter --action create "new.ipynb"
|
|
|
151
151
|
else:
|
|
152
152
|
return f"Error: Unknown action '{action}'. Valid actions: read, edit, create, delete, execute"
|
|
153
153
|
|
|
154
|
-
async def _handle_read(
|
|
155
|
-
self, notebook_path: str, params: Dict[str, Any], tool_ctx
|
|
156
|
-
) -> str:
|
|
154
|
+
async def _handle_read(self, notebook_path: str, params: Dict[str, Any], tool_ctx) -> str:
|
|
157
155
|
"""Read notebook or specific cell."""
|
|
158
156
|
exists, error_msg = await self.check_path_exists(notebook_path, tool_ctx)
|
|
159
157
|
if not exists:
|
|
@@ -188,9 +186,7 @@ jupyter --action create "new.ipynb"
|
|
|
188
186
|
await tool_ctx.error(f"Failed to read notebook: {str(e)}")
|
|
189
187
|
return f"Error reading notebook: {str(e)}"
|
|
190
188
|
|
|
191
|
-
async def _handle_edit(
|
|
192
|
-
self, notebook_path: str, params: Dict[str, Any], tool_ctx
|
|
193
|
-
) -> str:
|
|
189
|
+
async def _handle_edit(self, notebook_path: str, params: Dict[str, Any], tool_ctx) -> str:
|
|
194
190
|
"""Edit notebook cell."""
|
|
195
191
|
exists, error_msg = await self.check_path_exists(notebook_path, tool_ctx)
|
|
196
192
|
if not exists:
|
|
@@ -295,9 +291,7 @@ jupyter --action create "new.ipynb"
|
|
|
295
291
|
await tool_ctx.error(f"Failed to create notebook: {str(e)}")
|
|
296
292
|
return f"Error creating notebook: {str(e)}"
|
|
297
293
|
|
|
298
|
-
async def _handle_delete(
|
|
299
|
-
self, notebook_path: str, params: Dict[str, Any], tool_ctx
|
|
300
|
-
) -> str:
|
|
294
|
+
async def _handle_delete(self, notebook_path: str, params: Dict[str, Any], tool_ctx) -> str:
|
|
301
295
|
"""Delete notebook or cell."""
|
|
302
296
|
# If cell specified, delegate to edit with delete mode
|
|
303
297
|
if params.get("cell_id") or params.get("cell_index") is not None:
|
|
@@ -316,9 +310,7 @@ jupyter --action create "new.ipynb"
|
|
|
316
310
|
await tool_ctx.error(f"Failed to delete notebook: {str(e)}")
|
|
317
311
|
return f"Error deleting notebook: {str(e)}"
|
|
318
312
|
|
|
319
|
-
async def _handle_execute(
|
|
320
|
-
self, notebook_path: str, params: Dict[str, Any], tool_ctx
|
|
321
|
-
) -> str:
|
|
313
|
+
async def _handle_execute(self, notebook_path: str, params: Dict[str, Any], tool_ctx) -> str:
|
|
322
314
|
"""Execute notebook cells (placeholder for future implementation)."""
|
|
323
315
|
return "Error: Cell execution not yet implemented. Use a Jupyter kernel or server for execution."
|
|
324
316
|
|
|
@@ -211,22 +211,14 @@ The tool will:
|
|
|
211
211
|
)
|
|
212
212
|
|
|
213
213
|
# Prepare summary of results
|
|
214
|
-
successful_responses = [
|
|
215
|
-
|
|
216
|
-
]
|
|
217
|
-
failed_responses = [
|
|
218
|
-
(m, r) for m, r in results.items() if r.startswith("Error:")
|
|
219
|
-
]
|
|
214
|
+
successful_responses = [(m, r) for m, r in results.items() if not r.startswith("Error:")]
|
|
215
|
+
failed_responses = [(m, r) for m, r in results.items() if r.startswith("Error:")]
|
|
220
216
|
|
|
221
217
|
if not successful_responses:
|
|
222
|
-
return "Error: All model queries failed:\n\n" + "\n".join(
|
|
223
|
-
[f"{m}: {r}" for m, r in failed_responses]
|
|
224
|
-
)
|
|
218
|
+
return "Error: All model queries failed:\n\n" + "\n".join([f"{m}: {r}" for m, r in failed_responses])
|
|
225
219
|
|
|
226
220
|
# Use aggregation model to synthesize responses
|
|
227
|
-
consensus = await self._aggregate_responses(
|
|
228
|
-
successful_responses, prompt, aggregation_model
|
|
229
|
-
)
|
|
221
|
+
consensus = await self._aggregate_responses(successful_responses, prompt, aggregation_model)
|
|
230
222
|
|
|
231
223
|
# Format output
|
|
232
224
|
output = ["=== LLM Consensus Analysis ==="]
|
|
@@ -245,9 +237,7 @@ The tool will:
|
|
|
245
237
|
output.append("\n=== Individual Responses ===")
|
|
246
238
|
for model, response in successful_responses:
|
|
247
239
|
output.append(f"\n--- {model} ---")
|
|
248
|
-
output.append(
|
|
249
|
-
response[:500] + "..." if len(response) > 500 else response
|
|
250
|
-
)
|
|
240
|
+
output.append(response[:500] + "..." if len(response) > 500 else response)
|
|
251
241
|
|
|
252
242
|
if failed_responses:
|
|
253
243
|
output.append("\n=== Failed Queries ===")
|
|
@@ -282,9 +272,7 @@ The tool will:
|
|
|
282
272
|
# Create a mock context for the LLM tool
|
|
283
273
|
mock_ctx = type("MockContext", (), {"client": None})()
|
|
284
274
|
|
|
285
|
-
result = await asyncio.wait_for(
|
|
286
|
-
self.llm_tool.call(mock_ctx, **params), timeout=timeout
|
|
287
|
-
)
|
|
275
|
+
result = await asyncio.wait_for(self.llm_tool.call(mock_ctx, **params), timeout=timeout)
|
|
288
276
|
return (model, result)
|
|
289
277
|
except asyncio.TimeoutError:
|
|
290
278
|
return (model, f"Error: Timeout after {timeout} seconds")
|
|
@@ -305,9 +293,7 @@ The tool will:
|
|
|
305
293
|
) -> str:
|
|
306
294
|
"""Use an LLM to aggregate and analyze responses."""
|
|
307
295
|
# Prepare the aggregation prompt
|
|
308
|
-
response_summary = "\n\n".join(
|
|
309
|
-
[f"Model: {model}\nResponse: {response}" for model, response in responses]
|
|
310
|
-
)
|
|
296
|
+
response_summary = "\n\n".join([f"Model: {model}\nResponse: {response}" for model, response in responses])
|
|
311
297
|
|
|
312
298
|
aggregation_prompt = f"""You are analyzing responses from multiple AI models to the following prompt:
|
|
313
299
|
|
|
@@ -360,9 +346,7 @@ Be concise but thorough. Focus on providing actionable insights."""
|
|
|
360
346
|
for model, response in responses:
|
|
361
347
|
output.append(f"- {model}: {len(response)} characters")
|
|
362
348
|
|
|
363
|
-
output.append(
|
|
364
|
-
"\nNote: Advanced consensus analysis unavailable. Showing basic summary only."
|
|
365
|
-
)
|
|
349
|
+
output.append("\nNote: Advanced consensus analysis unavailable. Showing basic summary only.")
|
|
366
350
|
|
|
367
351
|
return "\n".join(output)
|
|
368
352
|
|
|
@@ -192,9 +192,7 @@ Providers are automatically detected based on environment variables:
|
|
|
192
192
|
for provider in available_but_disabled:
|
|
193
193
|
env_vars = available_providers.get(provider, [])
|
|
194
194
|
output.append(f" - {provider}: {', '.join(env_vars)}")
|
|
195
|
-
output.append(
|
|
196
|
-
f" Use: llm_manage --action enable --provider {provider}"
|
|
197
|
-
)
|
|
195
|
+
output.append(f" Use: llm_manage --action enable --provider {provider}")
|
|
198
196
|
output.append("")
|
|
199
197
|
|
|
200
198
|
# Show providers without API keys
|
|
@@ -356,9 +354,7 @@ Providers are automatically detected based on environment variables:
|
|
|
356
354
|
except Exception as e:
|
|
357
355
|
return f"Error listing models: {str(e)}"
|
|
358
356
|
|
|
359
|
-
async def _test_model(
|
|
360
|
-
self, ctx: MCPContext, provider: Optional[str], model: Optional[str]
|
|
361
|
-
) -> str:
|
|
357
|
+
async def _test_model(self, ctx: MCPContext, provider: Optional[str], model: Optional[str]) -> str:
|
|
362
358
|
"""Test a model to verify it works."""
|
|
363
359
|
if not model and not provider:
|
|
364
360
|
return "Error: Either model or provider is required for test action"
|
hanzo_mcp/tools/llm/llm_tool.py
CHANGED
|
@@ -294,9 +294,7 @@ Available: {", ".join(available) if available else "None"}"""
|
|
|
294
294
|
# Other actions can fall through to regular path if available
|
|
295
295
|
|
|
296
296
|
if not LITELLM_AVAILABLE:
|
|
297
|
-
return
|
|
298
|
-
"Error: LiteLLM is not installed. Install it with: pip install litellm"
|
|
299
|
-
)
|
|
297
|
+
return "Error: LiteLLM is not installed. Install it with: pip install litellm"
|
|
300
298
|
|
|
301
299
|
# Extract action
|
|
302
300
|
action = params.get("action", "query")
|
|
@@ -315,9 +313,7 @@ Available: {", ".join(available) if available else "None"}"""
|
|
|
315
313
|
elif action == "disable":
|
|
316
314
|
return self._handle_disable(params.get("provider"))
|
|
317
315
|
elif action == "test":
|
|
318
|
-
return await self._handle_test(
|
|
319
|
-
tool_ctx, params.get("model"), params.get("provider")
|
|
320
|
-
)
|
|
316
|
+
return await self._handle_test(tool_ctx, params.get("model"), params.get("provider"))
|
|
321
317
|
else:
|
|
322
318
|
return f"Error: Unknown action '{action}'. Valid actions: query, consensus, list, models, enable, disable, test"
|
|
323
319
|
|
|
@@ -407,9 +403,7 @@ Available: {", ".join(available) if available else "None"}"""
|
|
|
407
403
|
models = params.get("models")
|
|
408
404
|
if not models:
|
|
409
405
|
# Use configured or default models
|
|
410
|
-
consensus_size = params.get("consensus_size") or self.config.get(
|
|
411
|
-
"consensus_size", 3
|
|
412
|
-
)
|
|
406
|
+
consensus_size = params.get("consensus_size") or self.config.get("consensus_size", 3)
|
|
413
407
|
models = self._get_consensus_models(consensus_size)
|
|
414
408
|
|
|
415
409
|
if not models:
|
|
@@ -449,11 +443,7 @@ Available: {", ".join(available) if available else "None"}"""
|
|
|
449
443
|
if devil_model:
|
|
450
444
|
# Create devil's advocate prompt
|
|
451
445
|
responses_text = "\n\n".join(
|
|
452
|
-
[
|
|
453
|
-
f"Model {i + 1}: {resp['response']}"
|
|
454
|
-
for i, resp in enumerate(responses)
|
|
455
|
-
if resp["response"]
|
|
456
|
-
]
|
|
446
|
+
[f"Model {i + 1}: {resp['response']}" for i, resp in enumerate(responses) if resp["response"]]
|
|
457
447
|
)
|
|
458
448
|
|
|
459
449
|
devil_prompt = f"""You are a critical analyst. Review these responses to the question below and provide a devil's advocate perspective. Challenge assumptions, point out weaknesses, and suggest alternative viewpoints.
|
|
@@ -477,14 +467,10 @@ Provide your critical analysis:"""
|
|
|
477
467
|
}
|
|
478
468
|
|
|
479
469
|
# Aggregate responses
|
|
480
|
-
judge_model = params.get("judge_model") or self.config.get(
|
|
481
|
-
"default_judge_model", "gpt-4o"
|
|
482
|
-
)
|
|
470
|
+
judge_model = params.get("judge_model") or self.config.get("default_judge_model", "gpt-4o")
|
|
483
471
|
include_raw = params.get("include_raw", False)
|
|
484
472
|
|
|
485
|
-
return await self._aggregate_consensus(
|
|
486
|
-
responses, prompt, judge_model, include_raw, devil_response, tool_ctx
|
|
487
|
-
)
|
|
473
|
+
return await self._aggregate_consensus(responses, prompt, judge_model, include_raw, devil_response, tool_ctx)
|
|
488
474
|
|
|
489
475
|
def _handle_list(self) -> str:
|
|
490
476
|
"""List available providers."""
|
|
@@ -518,9 +504,7 @@ Provide your critical analysis:"""
|
|
|
518
504
|
output.append(f"{provider}: {status}")
|
|
519
505
|
output.append(f" Environment variables: {', '.join(env_vars)}")
|
|
520
506
|
|
|
521
|
-
output.append(
|
|
522
|
-
"\nUse 'llm --action enable/disable --provider <name>' to manage providers"
|
|
523
|
-
)
|
|
507
|
+
output.append("\nUse 'llm --action enable/disable --provider <name>' to manage providers")
|
|
524
508
|
|
|
525
509
|
return "\n".join(output)
|
|
526
510
|
|
|
@@ -560,16 +544,10 @@ Provide your critical analysis:"""
|
|
|
560
544
|
# Show providers with counts
|
|
561
545
|
for provider_name, models in sorted(all_models.items()):
|
|
562
546
|
if models:
|
|
563
|
-
available =
|
|
564
|
-
|
|
565
|
-
)
|
|
566
|
-
output.append(
|
|
567
|
-
f"{available} {provider_name}: {len(models)} models"
|
|
568
|
-
)
|
|
547
|
+
available = "✅" if provider_name in self.available_providers else "❌"
|
|
548
|
+
output.append(f"{available} {provider_name}: {len(models)} models")
|
|
569
549
|
|
|
570
|
-
output.append(
|
|
571
|
-
"\nUse 'llm --action models --provider <name>' to see specific models"
|
|
572
|
-
)
|
|
550
|
+
output.append("\nUse 'llm --action models --provider <name>' to see specific models")
|
|
573
551
|
|
|
574
552
|
return "\n".join(output)
|
|
575
553
|
|
|
@@ -608,9 +586,7 @@ Provide your critical analysis:"""
|
|
|
608
586
|
else:
|
|
609
587
|
return f"{provider} is already disabled"
|
|
610
588
|
|
|
611
|
-
async def _handle_test(
|
|
612
|
-
self, tool_ctx, model: Optional[str], provider: Optional[str]
|
|
613
|
-
) -> str:
|
|
589
|
+
async def _handle_test(self, tool_ctx, model: Optional[str], provider: Optional[str]) -> str:
|
|
614
590
|
"""Test a model or provider."""
|
|
615
591
|
if not model and not provider:
|
|
616
592
|
return "Error: Either model or provider is required for test action"
|
|
@@ -666,11 +642,7 @@ Provide your critical analysis:"""
|
|
|
666
642
|
break
|
|
667
643
|
|
|
668
644
|
provider = self._get_provider_for_model(model)
|
|
669
|
-
if
|
|
670
|
-
provider
|
|
671
|
-
and provider in self.available_providers
|
|
672
|
-
and provider not in disabled
|
|
673
|
-
):
|
|
645
|
+
if provider and provider in self.available_providers and provider not in disabled:
|
|
674
646
|
models.append(model)
|
|
675
647
|
|
|
676
648
|
# If still need more, add from available providers
|
|
@@ -703,9 +675,7 @@ Provide your critical analysis:"""
|
|
|
703
675
|
"""Query multiple models in parallel."""
|
|
704
676
|
|
|
705
677
|
async def query_with_info(model: str) -> Dict[str, Any]:
|
|
706
|
-
result = await self._query_single_model(
|
|
707
|
-
model, prompt, system_prompt, temperature, max_tokens
|
|
708
|
-
)
|
|
678
|
+
result = await self._query_single_model(model, prompt, system_prompt, temperature, max_tokens)
|
|
709
679
|
return {
|
|
710
680
|
"model": model,
|
|
711
681
|
"response": result.get("response"),
|
|
@@ -784,12 +754,7 @@ Provide your critical analysis:"""
|
|
|
784
754
|
return "Error: All models failed to respond"
|
|
785
755
|
|
|
786
756
|
# Format responses for aggregation
|
|
787
|
-
responses_text = "\n\n".join(
|
|
788
|
-
[
|
|
789
|
-
f"Model: {r['model']}\nResponse: {r['response']}"
|
|
790
|
-
for r in successful_responses
|
|
791
|
-
]
|
|
792
|
-
)
|
|
757
|
+
responses_text = "\n\n".join([f"Model: {r['model']}\nResponse: {r['response']}" for r in successful_responses])
|
|
793
758
|
|
|
794
759
|
if devil_response:
|
|
795
760
|
responses_text += f"\n\nDevil's Advocate ({devil_response['model']}):\n{devil_response['response']}"
|
|
@@ -818,23 +783,17 @@ Be concise and highlight the most important findings."""
|
|
|
818
783
|
if tool_ctx:
|
|
819
784
|
await tool_ctx.info(f"Aggregating responses with {judge_model}...")
|
|
820
785
|
|
|
821
|
-
judge_result = await self._query_single_model(
|
|
822
|
-
judge_model, aggregation_prompt, None, 0.3, None
|
|
823
|
-
)
|
|
786
|
+
judge_result = await self._query_single_model(judge_model, aggregation_prompt, None, 0.3, None)
|
|
824
787
|
|
|
825
788
|
if not judge_result["success"]:
|
|
826
789
|
return f"Error: Judge model failed: {judge_result.get('error', 'Unknown error')}"
|
|
827
790
|
|
|
828
791
|
# Format output
|
|
829
|
-
output = [
|
|
830
|
-
f"=== Consensus Analysis ({len(successful_responses)} models) ===\n"
|
|
831
|
-
]
|
|
792
|
+
output = [f"=== Consensus Analysis ({len(successful_responses)} models) ===\n"]
|
|
832
793
|
output.append(judge_result["response"])
|
|
833
794
|
|
|
834
795
|
# Add model list
|
|
835
|
-
output.append(
|
|
836
|
-
f"\nModels consulted: {', '.join([r['model'] for r in successful_responses])}"
|
|
837
|
-
)
|
|
796
|
+
output.append(f"\nModels consulted: {', '.join([r['model'] for r in successful_responses])}")
|
|
838
797
|
if devil_response:
|
|
839
798
|
output.append(f"Devil's Advocate: {devil_response['model']}")
|
|
840
799
|
|
|
@@ -260,7 +260,7 @@ llm "Explain this code" --model gpt-4o
|
|
|
260
260
|
llm --action consensus "Is this approach correct?" --devils-advocate
|
|
261
261
|
llm --action models --provider openai
|
|
262
262
|
|
|
263
|
-
Available: {
|
|
263
|
+
Available: {", ".join(available) if available else "None"}"""
|
|
264
264
|
|
|
265
265
|
@override
|
|
266
266
|
async def call(
|
|
@@ -281,9 +281,7 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
281
281
|
pass
|
|
282
282
|
|
|
283
283
|
if not LITELLM_AVAILABLE:
|
|
284
|
-
return
|
|
285
|
-
"Error: LiteLLM is not installed. Install it with: pip install litellm"
|
|
286
|
-
)
|
|
284
|
+
return "Error: LiteLLM is not installed. Install it with: pip install litellm"
|
|
287
285
|
|
|
288
286
|
# Extract action
|
|
289
287
|
action = params.get("action", "query")
|
|
@@ -302,9 +300,7 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
302
300
|
elif action == "disable":
|
|
303
301
|
return self._handle_disable(params.get("provider"))
|
|
304
302
|
elif action == "test":
|
|
305
|
-
return await self._handle_test(
|
|
306
|
-
tool_ctx, params.get("model"), params.get("provider")
|
|
307
|
-
)
|
|
303
|
+
return await self._handle_test(tool_ctx, params.get("model"), params.get("provider"))
|
|
308
304
|
else:
|
|
309
305
|
return f"Error: Unknown action '{action}'. Valid actions: query, consensus, list, models, enable, disable, test"
|
|
310
306
|
|
|
@@ -394,9 +390,7 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
394
390
|
models = params.get("models")
|
|
395
391
|
if not models:
|
|
396
392
|
# Use configured or default models
|
|
397
|
-
consensus_size = params.get("consensus_size") or self.config.get(
|
|
398
|
-
"consensus_size", 3
|
|
399
|
-
)
|
|
393
|
+
consensus_size = params.get("consensus_size") or self.config.get("consensus_size", 3)
|
|
400
394
|
models = self._get_consensus_models(consensus_size)
|
|
401
395
|
|
|
402
396
|
if not models:
|
|
@@ -436,11 +430,7 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
436
430
|
if devil_model:
|
|
437
431
|
# Create devil's advocate prompt
|
|
438
432
|
responses_text = "\n\n".join(
|
|
439
|
-
[
|
|
440
|
-
f"Model {i+1}: {resp['response']}"
|
|
441
|
-
for i, resp in enumerate(responses)
|
|
442
|
-
if resp["response"]
|
|
443
|
-
]
|
|
433
|
+
[f"Model {i + 1}: {resp['response']}" for i, resp in enumerate(responses) if resp["response"]]
|
|
444
434
|
)
|
|
445
435
|
|
|
446
436
|
devil_prompt = f"""You are a critical analyst. Review these responses to the question below and provide a devil's advocate perspective. Challenge assumptions, point out weaknesses, and suggest alternative viewpoints.
|
|
@@ -464,14 +454,10 @@ Provide your critical analysis:"""
|
|
|
464
454
|
}
|
|
465
455
|
|
|
466
456
|
# Aggregate responses
|
|
467
|
-
judge_model = params.get("judge_model") or self.config.get(
|
|
468
|
-
"default_judge_model", "gpt-4o"
|
|
469
|
-
)
|
|
457
|
+
judge_model = params.get("judge_model") or self.config.get("default_judge_model", "gpt-4o")
|
|
470
458
|
include_raw = params.get("include_raw", False)
|
|
471
459
|
|
|
472
|
-
return await self._aggregate_consensus(
|
|
473
|
-
responses, prompt, judge_model, include_raw, devil_response, tool_ctx
|
|
474
|
-
)
|
|
460
|
+
return await self._aggregate_consensus(responses, prompt, judge_model, include_raw, devil_response, tool_ctx)
|
|
475
461
|
|
|
476
462
|
def _handle_list(self) -> str:
|
|
477
463
|
"""List available providers."""
|
|
@@ -505,9 +491,7 @@ Provide your critical analysis:"""
|
|
|
505
491
|
output.append(f"{provider}: {status}")
|
|
506
492
|
output.append(f" Environment variables: {', '.join(env_vars)}")
|
|
507
493
|
|
|
508
|
-
output.append(
|
|
509
|
-
"\nUse 'llm --action enable/disable --provider <name>' to manage providers"
|
|
510
|
-
)
|
|
494
|
+
output.append("\nUse 'llm --action enable/disable --provider <name>' to manage providers")
|
|
511
495
|
|
|
512
496
|
return "\n".join(output)
|
|
513
497
|
|
|
@@ -547,16 +531,10 @@ Provide your critical analysis:"""
|
|
|
547
531
|
# Show providers with counts
|
|
548
532
|
for provider_name, models in sorted(all_models.items()):
|
|
549
533
|
if models:
|
|
550
|
-
available =
|
|
551
|
-
|
|
552
|
-
)
|
|
553
|
-
output.append(
|
|
554
|
-
f"{available} {provider_name}: {len(models)} models"
|
|
555
|
-
)
|
|
534
|
+
available = "✅" if provider_name in self.available_providers else "❌"
|
|
535
|
+
output.append(f"{available} {provider_name}: {len(models)} models")
|
|
556
536
|
|
|
557
|
-
output.append(
|
|
558
|
-
"\nUse 'llm --action models --provider <name>' to see specific models"
|
|
559
|
-
)
|
|
537
|
+
output.append("\nUse 'llm --action models --provider <name>' to see specific models")
|
|
560
538
|
|
|
561
539
|
return "\n".join(output)
|
|
562
540
|
|
|
@@ -595,9 +573,7 @@ Provide your critical analysis:"""
|
|
|
595
573
|
else:
|
|
596
574
|
return f"{provider} is already disabled"
|
|
597
575
|
|
|
598
|
-
async def _handle_test(
|
|
599
|
-
self, tool_ctx, model: Optional[str], provider: Optional[str]
|
|
600
|
-
) -> str:
|
|
576
|
+
async def _handle_test(self, tool_ctx, model: Optional[str], provider: Optional[str]) -> str:
|
|
601
577
|
"""Test a model or provider."""
|
|
602
578
|
if not model and not provider:
|
|
603
579
|
return "Error: Either model or provider is required for test action"
|
|
@@ -653,11 +629,7 @@ Provide your critical analysis:"""
|
|
|
653
629
|
break
|
|
654
630
|
|
|
655
631
|
provider = self._get_provider_for_model(model)
|
|
656
|
-
if
|
|
657
|
-
provider
|
|
658
|
-
and provider in self.available_providers
|
|
659
|
-
and provider not in disabled
|
|
660
|
-
):
|
|
632
|
+
if provider and provider in self.available_providers and provider not in disabled:
|
|
661
633
|
models.append(model)
|
|
662
634
|
|
|
663
635
|
# If still need more, add from available providers
|
|
@@ -690,9 +662,7 @@ Provide your critical analysis:"""
|
|
|
690
662
|
"""Query multiple models in parallel."""
|
|
691
663
|
|
|
692
664
|
async def query_with_info(model: str) -> Dict[str, Any]:
|
|
693
|
-
result = await self._query_single_model(
|
|
694
|
-
model, prompt, system_prompt, temperature, max_tokens
|
|
695
|
-
)
|
|
665
|
+
result = await self._query_single_model(model, prompt, system_prompt, temperature, max_tokens)
|
|
696
666
|
return {
|
|
697
667
|
"model": model,
|
|
698
668
|
"response": result.get("response"),
|
|
@@ -771,12 +741,7 @@ Provide your critical analysis:"""
|
|
|
771
741
|
return "Error: All models failed to respond"
|
|
772
742
|
|
|
773
743
|
# Format responses for aggregation
|
|
774
|
-
responses_text = "\n\n".join(
|
|
775
|
-
[
|
|
776
|
-
f"Model: {r['model']}\nResponse: {r['response']}"
|
|
777
|
-
for r in successful_responses
|
|
778
|
-
]
|
|
779
|
-
)
|
|
744
|
+
responses_text = "\n\n".join([f"Model: {r['model']}\nResponse: {r['response']}" for r in successful_responses])
|
|
780
745
|
|
|
781
746
|
if devil_response:
|
|
782
747
|
responses_text += f"\n\nDevil's Advocate ({devil_response['model']}):\n{devil_response['response']}"
|
|
@@ -805,23 +770,17 @@ Be concise and highlight the most important findings."""
|
|
|
805
770
|
if tool_ctx:
|
|
806
771
|
await tool_ctx.info(f"Aggregating responses with {judge_model}...")
|
|
807
772
|
|
|
808
|
-
judge_result = await self._query_single_model(
|
|
809
|
-
judge_model, aggregation_prompt, None, 0.3, None
|
|
810
|
-
)
|
|
773
|
+
judge_result = await self._query_single_model(judge_model, aggregation_prompt, None, 0.3, None)
|
|
811
774
|
|
|
812
775
|
if not judge_result["success"]:
|
|
813
776
|
return f"Error: Judge model failed: {judge_result.get('error', 'Unknown error')}"
|
|
814
777
|
|
|
815
778
|
# Format output
|
|
816
|
-
output = [
|
|
817
|
-
f"=== Consensus Analysis ({len(successful_responses)} models) ===\n"
|
|
818
|
-
]
|
|
779
|
+
output = [f"=== Consensus Analysis ({len(successful_responses)} models) ===\n"]
|
|
819
780
|
output.append(judge_result["response"])
|
|
820
781
|
|
|
821
782
|
# Add model list
|
|
822
|
-
output.append(
|
|
823
|
-
f"\nModels consulted: {', '.join([r['model'] for r in successful_responses])}"
|
|
824
|
-
)
|
|
783
|
+
output.append(f"\nModels consulted: {', '.join([r['model'] for r in successful_responses])}")
|
|
825
784
|
if devil_response:
|
|
826
785
|
output.append(f"Devil's Advocate: {devil_response['model']}")
|
|
827
786
|
|
|
@@ -71,9 +71,7 @@ class ProviderToolParams(TypedDict, total=False):
|
|
|
71
71
|
class BaseProviderTool(BaseTool):
|
|
72
72
|
"""Base class for provider-specific LLM tools."""
|
|
73
73
|
|
|
74
|
-
def __init__(
|
|
75
|
-
self, provider: str, default_model: str, model_variants: Dict[str, str]
|
|
76
|
-
):
|
|
74
|
+
def __init__(self, provider: str, default_model: str, model_variants: Dict[str, str]):
|
|
77
75
|
"""Initialize provider tool.
|
|
78
76
|
|
|
79
77
|
Args:
|