hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.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 +6 -0
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/llm/llm_tool.py
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
"""Unified LLM tool with multiple actions including consensus mode."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated, TypedDict, Unpack, final, override, Optional, List, Dict, Any
|
|
4
|
-
import asyncio
|
|
5
3
|
import os
|
|
6
4
|
import json
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Dict,
|
|
9
|
+
List,
|
|
10
|
+
Unpack,
|
|
11
|
+
Optional,
|
|
12
|
+
Annotated,
|
|
13
|
+
TypedDict,
|
|
14
|
+
final,
|
|
15
|
+
override,
|
|
16
|
+
)
|
|
7
17
|
from pathlib import Path
|
|
8
18
|
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
19
|
from pydantic import Field
|
|
20
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
21
|
|
|
12
22
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
13
23
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
@@ -15,6 +25,7 @@ from hanzo_mcp.tools.common.context import create_tool_context
|
|
|
15
25
|
# Check if litellm is available
|
|
16
26
|
try:
|
|
17
27
|
import litellm
|
|
28
|
+
|
|
18
29
|
LITELLM_AVAILABLE = True
|
|
19
30
|
except ImportError:
|
|
20
31
|
LITELLM_AVAILABLE = False
|
|
@@ -136,6 +147,7 @@ ConsensusSize = Annotated[
|
|
|
136
147
|
|
|
137
148
|
class LLMParams(TypedDict, total=False):
|
|
138
149
|
"""Parameters for LLM tool."""
|
|
150
|
+
|
|
139
151
|
action: str
|
|
140
152
|
model: Optional[str]
|
|
141
153
|
models: Optional[List[str]]
|
|
@@ -155,10 +167,10 @@ class LLMParams(TypedDict, total=False):
|
|
|
155
167
|
@final
|
|
156
168
|
class LLMTool(BaseTool):
|
|
157
169
|
"""Unified LLM tool with multiple actions."""
|
|
158
|
-
|
|
170
|
+
|
|
159
171
|
# Config file for settings
|
|
160
172
|
CONFIG_FILE = Path.home() / ".hanzo" / "mcp" / "llm_config.json"
|
|
161
|
-
|
|
173
|
+
|
|
162
174
|
# Default consensus models in order of preference
|
|
163
175
|
DEFAULT_CONSENSUS_MODELS = [
|
|
164
176
|
"gpt-4o", # OpenAI's latest
|
|
@@ -168,7 +180,7 @@ class LLMTool(BaseTool):
|
|
|
168
180
|
"mistral/mistral-large-latest", # Mistral's best
|
|
169
181
|
"perplexity/llama-3.1-sonar-large-128k-chat", # Perplexity with search
|
|
170
182
|
]
|
|
171
|
-
|
|
183
|
+
|
|
172
184
|
# API key environment variables
|
|
173
185
|
API_KEY_ENV_VARS = {
|
|
174
186
|
"openai": ["OPENAI_API_KEY"],
|
|
@@ -187,33 +199,33 @@ class LLMTool(BaseTool):
|
|
|
187
199
|
"voyage": ["VOYAGE_API_KEY"],
|
|
188
200
|
"deepseek": ["DEEPSEEK_API_KEY"],
|
|
189
201
|
}
|
|
190
|
-
|
|
202
|
+
|
|
191
203
|
def __init__(self):
|
|
192
204
|
"""Initialize the unified LLM tool."""
|
|
193
205
|
self.available_providers = self._detect_available_providers()
|
|
194
206
|
self.config = self._load_config()
|
|
195
|
-
|
|
207
|
+
|
|
196
208
|
def _detect_available_providers(self) -> Dict[str, List[str]]:
|
|
197
209
|
"""Detect which providers have API keys configured."""
|
|
198
210
|
available = {}
|
|
199
|
-
|
|
211
|
+
|
|
200
212
|
for provider, env_vars in self.API_KEY_ENV_VARS.items():
|
|
201
213
|
for var in env_vars:
|
|
202
214
|
if os.getenv(var):
|
|
203
215
|
available[provider] = env_vars
|
|
204
216
|
break
|
|
205
|
-
|
|
217
|
+
|
|
206
218
|
return available
|
|
207
|
-
|
|
219
|
+
|
|
208
220
|
def _load_config(self) -> Dict[str, Any]:
|
|
209
221
|
"""Load configuration from file."""
|
|
210
222
|
if self.CONFIG_FILE.exists():
|
|
211
223
|
try:
|
|
212
|
-
with open(self.CONFIG_FILE,
|
|
224
|
+
with open(self.CONFIG_FILE, "r") as f:
|
|
213
225
|
return json.load(f)
|
|
214
|
-
except:
|
|
226
|
+
except Exception:
|
|
215
227
|
pass
|
|
216
|
-
|
|
228
|
+
|
|
217
229
|
# Default config
|
|
218
230
|
return {
|
|
219
231
|
"disabled_providers": [],
|
|
@@ -221,11 +233,11 @@ class LLMTool(BaseTool):
|
|
|
221
233
|
"default_judge_model": "gpt-4o",
|
|
222
234
|
"consensus_size": 3,
|
|
223
235
|
}
|
|
224
|
-
|
|
236
|
+
|
|
225
237
|
def _save_config(self):
|
|
226
238
|
"""Save configuration to file."""
|
|
227
239
|
self.CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
228
|
-
with open(self.CONFIG_FILE,
|
|
240
|
+
with open(self.CONFIG_FILE, "w") as f:
|
|
229
241
|
json.dump(self.config, f, indent=2)
|
|
230
242
|
|
|
231
243
|
@property
|
|
@@ -239,7 +251,7 @@ class LLMTool(BaseTool):
|
|
|
239
251
|
def description(self) -> str:
|
|
240
252
|
"""Get the tool description."""
|
|
241
253
|
available = list(self.available_providers.keys())
|
|
242
|
-
|
|
254
|
+
|
|
243
255
|
return f"""Query LLMs. Default: single query. Actions: consensus, list, models, test.
|
|
244
256
|
|
|
245
257
|
Usage:
|
|
@@ -248,7 +260,7 @@ llm "Explain this code" --model gpt-4o
|
|
|
248
260
|
llm --action consensus "Is this approach correct?" --devils-advocate
|
|
249
261
|
llm --action models --provider openai
|
|
250
262
|
|
|
251
|
-
Available: {
|
|
263
|
+
Available: {", ".join(available) if available else "None"}"""
|
|
252
264
|
|
|
253
265
|
@override
|
|
254
266
|
async def call(
|
|
@@ -260,20 +272,22 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
260
272
|
# Create tool context only if we have a proper MCP context
|
|
261
273
|
tool_ctx = None
|
|
262
274
|
try:
|
|
263
|
-
if hasattr(ctx,
|
|
275
|
+
if hasattr(ctx, "client") and ctx.client and hasattr(ctx.client, "server"):
|
|
264
276
|
tool_ctx = create_tool_context(ctx)
|
|
265
277
|
if tool_ctx:
|
|
266
278
|
await tool_ctx.set_tool_info(self.name)
|
|
267
|
-
except:
|
|
279
|
+
except Exception:
|
|
268
280
|
# Running in test mode without MCP context
|
|
269
281
|
pass
|
|
270
282
|
|
|
271
283
|
if not LITELLM_AVAILABLE:
|
|
272
|
-
return
|
|
284
|
+
return (
|
|
285
|
+
"Error: LiteLLM is not installed. Install it with: pip install litellm"
|
|
286
|
+
)
|
|
273
287
|
|
|
274
288
|
# Extract action
|
|
275
289
|
action = params.get("action", "query")
|
|
276
|
-
|
|
290
|
+
|
|
277
291
|
# Route to appropriate handler
|
|
278
292
|
if action == "query":
|
|
279
293
|
return await self._handle_query(tool_ctx, params)
|
|
@@ -288,7 +302,9 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
288
302
|
elif action == "disable":
|
|
289
303
|
return self._handle_disable(params.get("provider"))
|
|
290
304
|
elif action == "test":
|
|
291
|
-
return await self._handle_test(
|
|
305
|
+
return await self._handle_test(
|
|
306
|
+
tool_ctx, params.get("model"), params.get("provider")
|
|
307
|
+
)
|
|
292
308
|
else:
|
|
293
309
|
return f"Error: Unknown action '{action}'. Valid actions: query, consensus, list, models, enable, disable, test"
|
|
294
310
|
|
|
@@ -296,10 +312,10 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
296
312
|
"""Handle single model query."""
|
|
297
313
|
model = params.get("model")
|
|
298
314
|
prompt = params.get("prompt")
|
|
299
|
-
|
|
315
|
+
|
|
300
316
|
if not prompt:
|
|
301
317
|
return "Error: prompt is required for query action"
|
|
302
|
-
|
|
318
|
+
|
|
303
319
|
# Auto-select model if not specified
|
|
304
320
|
if not model:
|
|
305
321
|
if self.available_providers:
|
|
@@ -316,39 +332,39 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
316
332
|
model = f"{provider}/default"
|
|
317
333
|
else:
|
|
318
334
|
return "Error: No model specified and no API keys found"
|
|
319
|
-
|
|
335
|
+
|
|
320
336
|
# Check if we have API key for this model
|
|
321
337
|
provider = self._get_provider_for_model(model)
|
|
322
338
|
if provider and provider not in self.available_providers:
|
|
323
339
|
env_vars = self.API_KEY_ENV_VARS.get(provider, [])
|
|
324
340
|
return f"Error: No API key found for {provider}. Set one of: {', '.join(env_vars)}"
|
|
325
|
-
|
|
341
|
+
|
|
326
342
|
# Build messages
|
|
327
343
|
messages = []
|
|
328
344
|
if params.get("system_prompt"):
|
|
329
345
|
messages.append({"role": "system", "content": params["system_prompt"]})
|
|
330
346
|
messages.append({"role": "user", "content": prompt})
|
|
331
|
-
|
|
347
|
+
|
|
332
348
|
# Build kwargs
|
|
333
349
|
kwargs = {
|
|
334
350
|
"model": model,
|
|
335
351
|
"messages": messages,
|
|
336
352
|
"temperature": params.get("temperature", 0.7),
|
|
337
353
|
}
|
|
338
|
-
|
|
354
|
+
|
|
339
355
|
if params.get("max_tokens"):
|
|
340
356
|
kwargs["max_tokens"] = params["max_tokens"]
|
|
341
|
-
|
|
357
|
+
|
|
342
358
|
if params.get("json_mode"):
|
|
343
359
|
kwargs["response_format"] = {"type": "json_object"}
|
|
344
|
-
|
|
360
|
+
|
|
345
361
|
if params.get("stream"):
|
|
346
362
|
kwargs["stream"] = True
|
|
347
|
-
|
|
363
|
+
|
|
348
364
|
try:
|
|
349
365
|
if tool_ctx:
|
|
350
366
|
await tool_ctx.info(f"Querying {model}...")
|
|
351
|
-
|
|
367
|
+
|
|
352
368
|
if kwargs.get("stream"):
|
|
353
369
|
# Handle streaming response
|
|
354
370
|
response_text = ""
|
|
@@ -360,7 +376,7 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
360
376
|
# Regular response
|
|
361
377
|
response = await litellm.acompletion(**kwargs)
|
|
362
378
|
return response.choices[0].message.content
|
|
363
|
-
|
|
379
|
+
|
|
364
380
|
except Exception as e:
|
|
365
381
|
error_msg = str(e)
|
|
366
382
|
if "model_not_found" in error_msg or "does not exist" in error_msg:
|
|
@@ -373,33 +389,35 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
373
389
|
prompt = params.get("prompt")
|
|
374
390
|
if not prompt:
|
|
375
391
|
return "Error: prompt is required for consensus action"
|
|
376
|
-
|
|
392
|
+
|
|
377
393
|
# Determine models to use
|
|
378
394
|
models = params.get("models")
|
|
379
395
|
if not models:
|
|
380
396
|
# Use configured or default models
|
|
381
|
-
consensus_size = params.get("consensus_size") or self.config.get(
|
|
397
|
+
consensus_size = params.get("consensus_size") or self.config.get(
|
|
398
|
+
"consensus_size", 3
|
|
399
|
+
)
|
|
382
400
|
models = self._get_consensus_models(consensus_size)
|
|
383
|
-
|
|
401
|
+
|
|
384
402
|
if not models:
|
|
385
403
|
return "Error: No models available for consensus. Set API keys for at least 2 providers."
|
|
386
|
-
|
|
404
|
+
|
|
387
405
|
if len(models) < 2:
|
|
388
406
|
return "Error: Consensus requires at least 2 models"
|
|
389
|
-
|
|
407
|
+
|
|
390
408
|
# Check for devil's advocate mode
|
|
391
409
|
devils_advocate = params.get("devils_advocate", False)
|
|
392
410
|
if devils_advocate and len(models) < 3:
|
|
393
411
|
return "Error: Devil's advocate mode requires at least 3 models"
|
|
394
|
-
|
|
412
|
+
|
|
395
413
|
if tool_ctx:
|
|
396
414
|
await tool_ctx.info(f"Running consensus with {len(models)} models...")
|
|
397
|
-
|
|
415
|
+
|
|
398
416
|
# Query models in parallel
|
|
399
417
|
system_prompt = params.get("system_prompt")
|
|
400
418
|
temperature = params.get("temperature", 0.7)
|
|
401
419
|
max_tokens = params.get("max_tokens")
|
|
402
|
-
|
|
420
|
+
|
|
403
421
|
# Split models if using devil's advocate
|
|
404
422
|
if devils_advocate:
|
|
405
423
|
consensus_models = models[:-1]
|
|
@@ -407,21 +425,24 @@ Available: {', '.join(available) if available else 'None'}"""
|
|
|
407
425
|
else:
|
|
408
426
|
consensus_models = models
|
|
409
427
|
devil_model = None
|
|
410
|
-
|
|
428
|
+
|
|
411
429
|
# Query consensus models
|
|
412
430
|
responses = await self._query_models_parallel(
|
|
413
431
|
consensus_models, prompt, system_prompt, temperature, max_tokens, tool_ctx
|
|
414
432
|
)
|
|
415
|
-
|
|
433
|
+
|
|
416
434
|
# Get devil's advocate response if enabled
|
|
417
435
|
devil_response = None
|
|
418
436
|
if devil_model:
|
|
419
437
|
# Create devil's advocate prompt
|
|
420
|
-
responses_text = "\n\n".join(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
438
|
+
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
|
+
]
|
|
444
|
+
)
|
|
445
|
+
|
|
425
446
|
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.
|
|
426
447
|
|
|
427
448
|
Original Question: {prompt}
|
|
@@ -430,22 +451,24 @@ Responses from other models:
|
|
|
430
451
|
{responses_text}
|
|
431
452
|
|
|
432
453
|
Provide your critical analysis:"""
|
|
433
|
-
|
|
454
|
+
|
|
434
455
|
devil_result = await self._query_single_model(
|
|
435
456
|
devil_model, devil_prompt, system_prompt, temperature, max_tokens
|
|
436
457
|
)
|
|
437
|
-
|
|
438
|
-
if devil_result[
|
|
458
|
+
|
|
459
|
+
if devil_result["success"]:
|
|
439
460
|
devil_response = {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
461
|
+
"model": devil_model,
|
|
462
|
+
"response": devil_result["response"],
|
|
463
|
+
"time_ms": devil_result["time_ms"],
|
|
443
464
|
}
|
|
444
|
-
|
|
465
|
+
|
|
445
466
|
# Aggregate responses
|
|
446
|
-
judge_model = params.get("judge_model") or self.config.get(
|
|
467
|
+
judge_model = params.get("judge_model") or self.config.get(
|
|
468
|
+
"default_judge_model", "gpt-4o"
|
|
469
|
+
)
|
|
447
470
|
include_raw = params.get("include_raw", False)
|
|
448
|
-
|
|
471
|
+
|
|
449
472
|
return await self._aggregate_consensus(
|
|
450
473
|
responses, prompt, judge_model, include_raw, devil_response, tool_ctx
|
|
451
474
|
)
|
|
@@ -453,64 +476,66 @@ Provide your critical analysis:"""
|
|
|
453
476
|
def _handle_list(self) -> str:
|
|
454
477
|
"""List available providers."""
|
|
455
478
|
output = ["=== LLM Providers ==="]
|
|
456
|
-
|
|
479
|
+
|
|
457
480
|
# Get all possible providers
|
|
458
481
|
all_providers = sorted(self.API_KEY_ENV_VARS.keys())
|
|
459
482
|
disabled = self.config.get("disabled_providers", [])
|
|
460
|
-
|
|
483
|
+
|
|
461
484
|
output.append(f"Total providers: {len(all_providers)}")
|
|
462
485
|
output.append(f"Available: {len(self.available_providers)}")
|
|
463
486
|
output.append(f"Disabled: {len(disabled)}\n")
|
|
464
|
-
|
|
487
|
+
|
|
465
488
|
for provider in all_providers:
|
|
466
489
|
status_parts = []
|
|
467
|
-
|
|
490
|
+
|
|
468
491
|
# Check if API key exists
|
|
469
492
|
if provider in self.available_providers:
|
|
470
493
|
status_parts.append("✅ API key found")
|
|
471
494
|
else:
|
|
472
495
|
status_parts.append("❌ No API key")
|
|
473
|
-
|
|
496
|
+
|
|
474
497
|
# Check if disabled
|
|
475
498
|
if provider in disabled:
|
|
476
499
|
status_parts.append("🚫 Disabled")
|
|
477
|
-
|
|
500
|
+
|
|
478
501
|
# Show environment variables
|
|
479
502
|
env_vars = self.API_KEY_ENV_VARS.get(provider, [])
|
|
480
503
|
status = " | ".join(status_parts)
|
|
481
|
-
|
|
504
|
+
|
|
482
505
|
output.append(f"{provider}: {status}")
|
|
483
506
|
output.append(f" Environment variables: {', '.join(env_vars)}")
|
|
484
|
-
|
|
485
|
-
output.append(
|
|
486
|
-
|
|
507
|
+
|
|
508
|
+
output.append(
|
|
509
|
+
"\nUse 'llm --action enable/disable --provider <name>' to manage providers"
|
|
510
|
+
)
|
|
511
|
+
|
|
487
512
|
return "\n".join(output)
|
|
488
513
|
|
|
489
514
|
def _handle_models(self, provider: Optional[str] = None) -> str:
|
|
490
515
|
"""List available models."""
|
|
491
516
|
try:
|
|
492
517
|
all_models = self._get_all_models()
|
|
493
|
-
|
|
518
|
+
|
|
494
519
|
if not all_models:
|
|
495
520
|
return "No models available or LiteLLM not properly initialized"
|
|
496
|
-
|
|
521
|
+
|
|
497
522
|
output = ["=== Available LLM Models ==="]
|
|
498
|
-
|
|
523
|
+
|
|
499
524
|
if provider:
|
|
500
525
|
# Show models for specific provider
|
|
501
526
|
provider_lower = provider.lower()
|
|
502
527
|
models = all_models.get(provider_lower, [])
|
|
503
|
-
|
|
528
|
+
|
|
504
529
|
if not models:
|
|
505
530
|
return f"No models found for provider '{provider}'"
|
|
506
|
-
|
|
531
|
+
|
|
507
532
|
output.append(f"\n{provider.upper()} ({len(models)} models):")
|
|
508
533
|
output.append("-" * 40)
|
|
509
|
-
|
|
534
|
+
|
|
510
535
|
# Show first 50 models
|
|
511
536
|
for model in models[:50]:
|
|
512
537
|
output.append(f" {model}")
|
|
513
|
-
|
|
538
|
+
|
|
514
539
|
if len(models) > 50:
|
|
515
540
|
output.append(f" ... and {len(models) - 50} more")
|
|
516
541
|
else:
|
|
@@ -518,17 +543,23 @@ Provide your critical analysis:"""
|
|
|
518
543
|
total_models = sum(len(models) for models in all_models.values())
|
|
519
544
|
output.append(f"Total models available: {total_models}")
|
|
520
545
|
output.append("")
|
|
521
|
-
|
|
546
|
+
|
|
522
547
|
# Show providers with counts
|
|
523
548
|
for provider_name, models in sorted(all_models.items()):
|
|
524
549
|
if models:
|
|
525
|
-
available =
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
550
|
+
available = (
|
|
551
|
+
"✅" if provider_name in self.available_providers else "❌"
|
|
552
|
+
)
|
|
553
|
+
output.append(
|
|
554
|
+
f"{available} {provider_name}: {len(models)} models"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
output.append(
|
|
558
|
+
"\nUse 'llm --action models --provider <name>' to see specific models"
|
|
559
|
+
)
|
|
560
|
+
|
|
530
561
|
return "\n".join(output)
|
|
531
|
-
|
|
562
|
+
|
|
532
563
|
except Exception as e:
|
|
533
564
|
return f"Error listing models: {str(e)}"
|
|
534
565
|
|
|
@@ -536,10 +567,10 @@ Provide your critical analysis:"""
|
|
|
536
567
|
"""Enable a provider."""
|
|
537
568
|
if not provider:
|
|
538
569
|
return "Error: provider is required for enable action"
|
|
539
|
-
|
|
570
|
+
|
|
540
571
|
provider = provider.lower()
|
|
541
572
|
disabled = self.config.get("disabled_providers", [])
|
|
542
|
-
|
|
573
|
+
|
|
543
574
|
if provider in disabled:
|
|
544
575
|
disabled.remove(provider)
|
|
545
576
|
self.config["disabled_providers"] = disabled
|
|
@@ -552,10 +583,10 @@ Provide your critical analysis:"""
|
|
|
552
583
|
"""Disable a provider."""
|
|
553
584
|
if not provider:
|
|
554
585
|
return "Error: provider is required for disable action"
|
|
555
|
-
|
|
586
|
+
|
|
556
587
|
provider = provider.lower()
|
|
557
588
|
disabled = self.config.get("disabled_providers", [])
|
|
558
|
-
|
|
589
|
+
|
|
559
590
|
if provider not in disabled:
|
|
560
591
|
disabled.append(provider)
|
|
561
592
|
self.config["disabled_providers"] = disabled
|
|
@@ -564,11 +595,13 @@ Provide your critical analysis:"""
|
|
|
564
595
|
else:
|
|
565
596
|
return f"{provider} is already disabled"
|
|
566
597
|
|
|
567
|
-
async def _handle_test(
|
|
598
|
+
async def _handle_test(
|
|
599
|
+
self, tool_ctx, model: Optional[str], provider: Optional[str]
|
|
600
|
+
) -> str:
|
|
568
601
|
"""Test a model or provider."""
|
|
569
602
|
if not model and not provider:
|
|
570
603
|
return "Error: Either model or provider is required for test action"
|
|
571
|
-
|
|
604
|
+
|
|
572
605
|
# If provider specified, test its default model
|
|
573
606
|
if provider and not model:
|
|
574
607
|
provider = provider.lower()
|
|
@@ -582,24 +615,24 @@ Provide your critical analysis:"""
|
|
|
582
615
|
model = "groq/llama3-8b-8192"
|
|
583
616
|
else:
|
|
584
617
|
model = f"{provider}/default"
|
|
585
|
-
|
|
618
|
+
|
|
586
619
|
# Test the model
|
|
587
620
|
test_prompt = "Say 'Hello from Hanzo AI!' in exactly 5 words."
|
|
588
|
-
|
|
621
|
+
|
|
589
622
|
try:
|
|
590
623
|
if tool_ctx:
|
|
591
624
|
await tool_ctx.info(f"Testing {model}...")
|
|
592
|
-
|
|
625
|
+
|
|
593
626
|
response = await litellm.acompletion(
|
|
594
627
|
model=model,
|
|
595
628
|
messages=[{"role": "user", "content": test_prompt}],
|
|
596
629
|
temperature=0,
|
|
597
|
-
max_tokens=20
|
|
630
|
+
max_tokens=20,
|
|
598
631
|
)
|
|
599
|
-
|
|
632
|
+
|
|
600
633
|
result = response.choices[0].message.content
|
|
601
634
|
return f"✅ {model} is working!\nResponse: {result}"
|
|
602
|
-
|
|
635
|
+
|
|
603
636
|
except Exception as e:
|
|
604
637
|
return f"❌ {model} failed: {str(e)}"
|
|
605
638
|
|
|
@@ -609,78 +642,95 @@ Provide your critical analysis:"""
|
|
|
609
642
|
configured = self.config.get("consensus_models")
|
|
610
643
|
if configured:
|
|
611
644
|
return configured[:size]
|
|
612
|
-
|
|
645
|
+
|
|
613
646
|
# Otherwise, build list from available providers
|
|
614
647
|
models = []
|
|
615
648
|
disabled = self.config.get("disabled_providers", [])
|
|
616
|
-
|
|
649
|
+
|
|
617
650
|
# Try default models first
|
|
618
651
|
for model in self.DEFAULT_CONSENSUS_MODELS:
|
|
619
652
|
if len(models) >= size:
|
|
620
653
|
break
|
|
621
|
-
|
|
654
|
+
|
|
622
655
|
provider = self._get_provider_for_model(model)
|
|
623
|
-
if
|
|
656
|
+
if (
|
|
657
|
+
provider
|
|
658
|
+
and provider in self.available_providers
|
|
659
|
+
and provider not in disabled
|
|
660
|
+
):
|
|
624
661
|
models.append(model)
|
|
625
|
-
|
|
662
|
+
|
|
626
663
|
# If still need more, add from available providers
|
|
627
664
|
if len(models) < size:
|
|
628
665
|
for provider in self.available_providers:
|
|
629
666
|
if provider in disabled:
|
|
630
667
|
continue
|
|
631
|
-
|
|
668
|
+
|
|
632
669
|
if provider == "openai" and "gpt-4o" not in models:
|
|
633
670
|
models.append("gpt-4o")
|
|
634
671
|
elif provider == "anthropic" and "claude-3-opus-20240229" not in models:
|
|
635
672
|
models.append("claude-3-opus-20240229")
|
|
636
673
|
elif provider == "google" and "gemini/gemini-1.5-pro" not in models:
|
|
637
674
|
models.append("gemini/gemini-1.5-pro")
|
|
638
|
-
|
|
675
|
+
|
|
639
676
|
if len(models) >= size:
|
|
640
677
|
break
|
|
641
|
-
|
|
678
|
+
|
|
642
679
|
return models
|
|
643
680
|
|
|
644
681
|
async def _query_models_parallel(
|
|
645
|
-
self,
|
|
646
|
-
|
|
682
|
+
self,
|
|
683
|
+
models: List[str],
|
|
684
|
+
prompt: str,
|
|
685
|
+
system_prompt: Optional[str],
|
|
686
|
+
temperature: float,
|
|
687
|
+
max_tokens: Optional[int],
|
|
688
|
+
tool_ctx,
|
|
647
689
|
) -> List[Dict[str, Any]]:
|
|
648
690
|
"""Query multiple models in parallel."""
|
|
691
|
+
|
|
649
692
|
async def query_with_info(model: str) -> Dict[str, Any]:
|
|
650
|
-
result = await self._query_single_model(
|
|
693
|
+
result = await self._query_single_model(
|
|
694
|
+
model, prompt, system_prompt, temperature, max_tokens
|
|
695
|
+
)
|
|
651
696
|
return {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
697
|
+
"model": model,
|
|
698
|
+
"response": result.get("response"),
|
|
699
|
+
"success": result.get("success", False),
|
|
700
|
+
"error": result.get("error"),
|
|
701
|
+
"time_ms": result.get("time_ms", 0),
|
|
657
702
|
}
|
|
658
|
-
|
|
703
|
+
|
|
659
704
|
# Run all queries in parallel
|
|
660
705
|
tasks = [query_with_info(model) for model in models]
|
|
661
706
|
results = await asyncio.gather(*tasks)
|
|
662
|
-
|
|
707
|
+
|
|
663
708
|
# Report results
|
|
664
|
-
successful = sum(1 for r in results if r[
|
|
709
|
+
successful = sum(1 for r in results if r["success"])
|
|
665
710
|
if tool_ctx:
|
|
666
711
|
await tool_ctx.info(f"Completed {successful}/{len(models)} model queries")
|
|
667
|
-
|
|
712
|
+
|
|
668
713
|
return results
|
|
669
714
|
|
|
670
715
|
async def _query_single_model(
|
|
671
|
-
self,
|
|
672
|
-
|
|
716
|
+
self,
|
|
717
|
+
model: str,
|
|
718
|
+
prompt: str,
|
|
719
|
+
system_prompt: Optional[str],
|
|
720
|
+
temperature: float,
|
|
721
|
+
max_tokens: Optional[int],
|
|
673
722
|
) -> Dict[str, Any]:
|
|
674
723
|
"""Query a single model and return result with metadata."""
|
|
675
724
|
import time
|
|
725
|
+
|
|
676
726
|
start_time = time.time()
|
|
677
|
-
|
|
727
|
+
|
|
678
728
|
try:
|
|
679
729
|
messages = []
|
|
680
730
|
if system_prompt:
|
|
681
731
|
messages.append({"role": "system", "content": system_prompt})
|
|
682
732
|
messages.append({"role": "user", "content": prompt})
|
|
683
|
-
|
|
733
|
+
|
|
684
734
|
kwargs = {
|
|
685
735
|
"model": model,
|
|
686
736
|
"messages": messages,
|
|
@@ -688,43 +738,49 @@ Provide your critical analysis:"""
|
|
|
688
738
|
}
|
|
689
739
|
if max_tokens:
|
|
690
740
|
kwargs["max_tokens"] = max_tokens
|
|
691
|
-
|
|
741
|
+
|
|
692
742
|
response = await litellm.acompletion(**kwargs)
|
|
693
|
-
|
|
743
|
+
|
|
694
744
|
return {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
745
|
+
"success": True,
|
|
746
|
+
"response": response.choices[0].message.content,
|
|
747
|
+
"time_ms": int((time.time() - start_time) * 1000),
|
|
698
748
|
}
|
|
699
|
-
|
|
749
|
+
|
|
700
750
|
except Exception as e:
|
|
701
751
|
return {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
752
|
+
"success": False,
|
|
753
|
+
"error": str(e),
|
|
754
|
+
"time_ms": int((time.time() - start_time) * 1000),
|
|
705
755
|
}
|
|
706
756
|
|
|
707
757
|
async def _aggregate_consensus(
|
|
708
|
-
self,
|
|
709
|
-
|
|
710
|
-
|
|
758
|
+
self,
|
|
759
|
+
responses: List[Dict[str, Any]],
|
|
760
|
+
original_prompt: str,
|
|
761
|
+
judge_model: str,
|
|
762
|
+
include_raw: bool,
|
|
763
|
+
devil_response: Optional[Dict[str, Any]],
|
|
764
|
+
tool_ctx,
|
|
711
765
|
) -> str:
|
|
712
766
|
"""Aggregate consensus responses using a judge model."""
|
|
713
767
|
# Prepare response data
|
|
714
|
-
successful_responses = [r for r in responses if r[
|
|
715
|
-
|
|
768
|
+
successful_responses = [r for r in responses if r["success"]]
|
|
769
|
+
|
|
716
770
|
if not successful_responses:
|
|
717
771
|
return "Error: All models failed to respond"
|
|
718
|
-
|
|
772
|
+
|
|
719
773
|
# Format responses for aggregation
|
|
720
|
-
responses_text = "\n\n".join(
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
774
|
+
responses_text = "\n\n".join(
|
|
775
|
+
[
|
|
776
|
+
f"Model: {r['model']}\nResponse: {r['response']}"
|
|
777
|
+
for r in successful_responses
|
|
778
|
+
]
|
|
779
|
+
)
|
|
780
|
+
|
|
725
781
|
if devil_response:
|
|
726
782
|
responses_text += f"\n\nDevil's Advocate ({devil_response['model']}):\n{devil_response['response']}"
|
|
727
|
-
|
|
783
|
+
|
|
728
784
|
# Create aggregation prompt
|
|
729
785
|
aggregation_prompt = f"""Analyze the following responses from multiple AI models to this question:
|
|
730
786
|
|
|
@@ -743,58 +799,62 @@ Please provide:
|
|
|
743
799
|
{f"4. Evaluation of the devil's advocate critique" if devil_response else ""}
|
|
744
800
|
|
|
745
801
|
Be concise and highlight the most important findings."""
|
|
746
|
-
|
|
802
|
+
|
|
747
803
|
# Get aggregation
|
|
748
804
|
try:
|
|
749
805
|
if tool_ctx:
|
|
750
806
|
await tool_ctx.info(f"Aggregating responses with {judge_model}...")
|
|
751
|
-
|
|
807
|
+
|
|
752
808
|
judge_result = await self._query_single_model(
|
|
753
809
|
judge_model, aggregation_prompt, None, 0.3, None
|
|
754
810
|
)
|
|
755
|
-
|
|
756
|
-
if not judge_result[
|
|
811
|
+
|
|
812
|
+
if not judge_result["success"]:
|
|
757
813
|
return f"Error: Judge model failed: {judge_result.get('error', 'Unknown error')}"
|
|
758
|
-
|
|
814
|
+
|
|
759
815
|
# Format output
|
|
760
|
-
output = [
|
|
761
|
-
|
|
762
|
-
|
|
816
|
+
output = [
|
|
817
|
+
f"=== Consensus Analysis ({len(successful_responses)} models) ===\n"
|
|
818
|
+
]
|
|
819
|
+
output.append(judge_result["response"])
|
|
820
|
+
|
|
763
821
|
# Add model list
|
|
764
|
-
output.append(
|
|
822
|
+
output.append(
|
|
823
|
+
f"\nModels consulted: {', '.join([r['model'] for r in successful_responses])}"
|
|
824
|
+
)
|
|
765
825
|
if devil_response:
|
|
766
826
|
output.append(f"Devil's Advocate: {devil_response['model']}")
|
|
767
|
-
|
|
827
|
+
|
|
768
828
|
# Add timing info
|
|
769
|
-
avg_time = sum(r[
|
|
829
|
+
avg_time = sum(r["time_ms"] for r in responses) / len(responses)
|
|
770
830
|
output.append(f"\nAverage response time: {avg_time:.0f}ms")
|
|
771
|
-
|
|
831
|
+
|
|
772
832
|
# Include raw responses if requested
|
|
773
833
|
if include_raw:
|
|
774
834
|
output.append("\n\n=== Raw Responses ===")
|
|
775
835
|
for r in successful_responses:
|
|
776
836
|
output.append(f"\n{r['model']}:")
|
|
777
837
|
output.append("-" * 40)
|
|
778
|
-
output.append(r[
|
|
779
|
-
|
|
838
|
+
output.append(r["response"])
|
|
839
|
+
|
|
780
840
|
if devil_response:
|
|
781
841
|
output.append(f"\nDevil's Advocate ({devil_response['model']}):")
|
|
782
842
|
output.append("-" * 40)
|
|
783
|
-
output.append(devil_response[
|
|
784
|
-
|
|
843
|
+
output.append(devil_response["response"])
|
|
844
|
+
|
|
785
845
|
return "\n".join(output)
|
|
786
|
-
|
|
846
|
+
|
|
787
847
|
except Exception as e:
|
|
788
848
|
return f"Error during aggregation: {str(e)}"
|
|
789
849
|
|
|
790
850
|
def _get_provider_for_model(self, model: str) -> Optional[str]:
|
|
791
851
|
"""Determine the provider for a given model."""
|
|
792
852
|
model_lower = model.lower()
|
|
793
|
-
|
|
853
|
+
|
|
794
854
|
# Check explicit provider prefix
|
|
795
855
|
if "/" in model:
|
|
796
856
|
return model.split("/")[0]
|
|
797
|
-
|
|
857
|
+
|
|
798
858
|
# Check model prefixes
|
|
799
859
|
if model_lower.startswith("gpt"):
|
|
800
860
|
return "openai"
|
|
@@ -804,7 +864,7 @@ Be concise and highlight the most important findings."""
|
|
|
804
864
|
return "google"
|
|
805
865
|
elif model_lower.startswith("command"):
|
|
806
866
|
return "cohere"
|
|
807
|
-
|
|
867
|
+
|
|
808
868
|
# Default to OpenAI
|
|
809
869
|
return "openai"
|
|
810
870
|
|
|
@@ -812,13 +872,13 @@ Be concise and highlight the most important findings."""
|
|
|
812
872
|
"""Get all available models from LiteLLM."""
|
|
813
873
|
try:
|
|
814
874
|
import litellm
|
|
815
|
-
|
|
875
|
+
|
|
816
876
|
# Get all models
|
|
817
877
|
all_models = litellm.model_list
|
|
818
|
-
|
|
878
|
+
|
|
819
879
|
# Organize by provider
|
|
820
880
|
providers = {}
|
|
821
|
-
|
|
881
|
+
|
|
822
882
|
for model in all_models:
|
|
823
883
|
# Extract provider
|
|
824
884
|
if "/" in model:
|
|
@@ -833,19 +893,19 @@ Be concise and highlight the most important findings."""
|
|
|
833
893
|
provider = "cohere"
|
|
834
894
|
else:
|
|
835
895
|
provider = "other"
|
|
836
|
-
|
|
896
|
+
|
|
837
897
|
if provider not in providers:
|
|
838
898
|
providers[provider] = []
|
|
839
899
|
providers[provider].append(model)
|
|
840
|
-
|
|
900
|
+
|
|
841
901
|
# Sort models within each provider
|
|
842
902
|
for provider in providers:
|
|
843
903
|
providers[provider] = sorted(providers[provider])
|
|
844
|
-
|
|
904
|
+
|
|
845
905
|
return providers
|
|
846
906
|
except Exception:
|
|
847
907
|
return {}
|
|
848
908
|
|
|
849
909
|
def register(self, mcp_server) -> None:
|
|
850
910
|
"""Register this tool with the MCP server."""
|
|
851
|
-
pass
|
|
911
|
+
pass
|