superqode 0.1.5__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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"""Tool-calling capability detection and testing for local models.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for detecting and testing whether local
|
|
4
|
+
models support function/tool calling, which is critical for coding assistants.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Dict, List, Optional, Set
|
|
12
|
+
|
|
13
|
+
from superqode.providers.local.base import (
|
|
14
|
+
LocalModel,
|
|
15
|
+
ToolTestResult,
|
|
16
|
+
detect_model_family,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Models known to support tool calling well (by family and version)
|
|
21
|
+
TOOL_CAPABLE_MODELS: Dict[str, Dict[str, Any]] = {
|
|
22
|
+
# Llama family
|
|
23
|
+
"llama3.1": {
|
|
24
|
+
"supports_tools": True,
|
|
25
|
+
"parallel_tools": True,
|
|
26
|
+
"tool_choice": ["auto", "required", "none"],
|
|
27
|
+
"notes": "Native tool support in Llama 3.1+",
|
|
28
|
+
},
|
|
29
|
+
"llama3.2": {
|
|
30
|
+
"supports_tools": True,
|
|
31
|
+
"parallel_tools": True,
|
|
32
|
+
"tool_choice": ["auto", "required", "none"],
|
|
33
|
+
"notes": "Native tool support",
|
|
34
|
+
},
|
|
35
|
+
"llama3.3": {
|
|
36
|
+
"supports_tools": True,
|
|
37
|
+
"parallel_tools": True,
|
|
38
|
+
"tool_choice": ["auto", "required", "none"],
|
|
39
|
+
"notes": "Latest Llama with improved tool support",
|
|
40
|
+
},
|
|
41
|
+
# Qwen family
|
|
42
|
+
"qwen2.5": {
|
|
43
|
+
"supports_tools": True,
|
|
44
|
+
"parallel_tools": True,
|
|
45
|
+
"tool_choice": ["auto", "required", "none"],
|
|
46
|
+
"notes": "Excellent tool support in Qwen 2.5",
|
|
47
|
+
"recommended_params": {"num_ctx": 16384},
|
|
48
|
+
},
|
|
49
|
+
"qwen2.5-coder": {
|
|
50
|
+
"supports_tools": True,
|
|
51
|
+
"parallel_tools": True,
|
|
52
|
+
"tool_choice": ["auto", "required", "none"],
|
|
53
|
+
"notes": "Optimized for code, great tool support",
|
|
54
|
+
"recommended_params": {"num_ctx": 32768},
|
|
55
|
+
},
|
|
56
|
+
# Mistral family
|
|
57
|
+
"mistral": {
|
|
58
|
+
"supports_tools": True,
|
|
59
|
+
"parallel_tools": False,
|
|
60
|
+
"tool_choice": ["auto", "none"],
|
|
61
|
+
"notes": "Good tool support",
|
|
62
|
+
},
|
|
63
|
+
"mixtral": {
|
|
64
|
+
"supports_tools": True,
|
|
65
|
+
"parallel_tools": True,
|
|
66
|
+
"tool_choice": ["auto", "required", "none"],
|
|
67
|
+
"notes": "MoE with tool support",
|
|
68
|
+
},
|
|
69
|
+
# DeepSeek
|
|
70
|
+
"deepseek-coder": {
|
|
71
|
+
"supports_tools": True,
|
|
72
|
+
"parallel_tools": False,
|
|
73
|
+
"tool_choice": ["auto"],
|
|
74
|
+
"notes": "Code-focused with tool support",
|
|
75
|
+
},
|
|
76
|
+
"deepseek-coder-v2": {
|
|
77
|
+
"supports_tools": True,
|
|
78
|
+
"parallel_tools": True,
|
|
79
|
+
"tool_choice": ["auto", "required"],
|
|
80
|
+
"notes": "Improved tool support in v2",
|
|
81
|
+
},
|
|
82
|
+
# Command-R
|
|
83
|
+
"command-r": {
|
|
84
|
+
"supports_tools": True,
|
|
85
|
+
"parallel_tools": True,
|
|
86
|
+
"tool_choice": ["auto", "required", "none"],
|
|
87
|
+
"notes": "Cohere's tool-focused model",
|
|
88
|
+
},
|
|
89
|
+
# Hermes (fine-tuned for tools)
|
|
90
|
+
"hermes": {
|
|
91
|
+
"supports_tools": True,
|
|
92
|
+
"parallel_tools": True,
|
|
93
|
+
"tool_choice": ["auto", "required"],
|
|
94
|
+
"notes": "Fine-tuned specifically for function calling",
|
|
95
|
+
},
|
|
96
|
+
"nous-hermes": {
|
|
97
|
+
"supports_tools": True,
|
|
98
|
+
"parallel_tools": True,
|
|
99
|
+
"tool_choice": ["auto", "required"],
|
|
100
|
+
"notes": "NousResearch fine-tune for tools",
|
|
101
|
+
},
|
|
102
|
+
# Functionary (specialized for function calling)
|
|
103
|
+
"functionary": {
|
|
104
|
+
"supports_tools": True,
|
|
105
|
+
"parallel_tools": True,
|
|
106
|
+
"tool_choice": ["auto", "required", "none"],
|
|
107
|
+
"notes": "Specialized for OpenAI-compatible function calling",
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Models that need special handling or have quirks
|
|
112
|
+
TOOL_QUIRKS: Dict[str, Dict[str, Any]] = {
|
|
113
|
+
"qwen2.5-coder": {
|
|
114
|
+
"needs_num_ctx": 16384, # Needs larger context for tools
|
|
115
|
+
"json_mode_helps": True,
|
|
116
|
+
},
|
|
117
|
+
"llama3.1": {
|
|
118
|
+
"supports_parallel_tools": True,
|
|
119
|
+
"native_tool_format": True,
|
|
120
|
+
},
|
|
121
|
+
"mistral": {
|
|
122
|
+
"tool_use_special_tokens": True,
|
|
123
|
+
"max_tools_per_call": 1, # Single tool per response
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Models that definitely do NOT support tools
|
|
128
|
+
NO_TOOL_SUPPORT: Set[str] = {
|
|
129
|
+
"tinyllama",
|
|
130
|
+
"phi-2",
|
|
131
|
+
"gemma", # Base Gemma doesn't support tools
|
|
132
|
+
"stablelm",
|
|
133
|
+
"falcon",
|
|
134
|
+
"mpt",
|
|
135
|
+
"dolly",
|
|
136
|
+
"vicuna",
|
|
137
|
+
"alpaca",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Test tool definition for capability testing
|
|
141
|
+
TEST_TOOL = {
|
|
142
|
+
"type": "function",
|
|
143
|
+
"function": {
|
|
144
|
+
"name": "get_weather",
|
|
145
|
+
"description": "Get the current weather for a city",
|
|
146
|
+
"parameters": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"city": {"type": "string", "description": "The city name"},
|
|
150
|
+
"unit": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"enum": ["celsius", "fahrenheit"],
|
|
153
|
+
"description": "Temperature unit",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
"required": ["city"],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Test message that should trigger tool use
|
|
162
|
+
TEST_MESSAGE = {"role": "user", "content": "What's the weather like in Paris?"}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class ToolCapabilityInfo:
|
|
167
|
+
"""Detailed tool capability information for a model.
|
|
168
|
+
|
|
169
|
+
Attributes:
|
|
170
|
+
model_id: Model identifier
|
|
171
|
+
supports_tools: Whether tool calling is supported
|
|
172
|
+
parallel_tools: Whether parallel tool calls are supported
|
|
173
|
+
tool_choice_modes: Supported tool_choice modes
|
|
174
|
+
recommended_params: Recommended parameters for tool use
|
|
175
|
+
quirks: Known quirks and workarounds
|
|
176
|
+
verified: Whether capability was verified by testing
|
|
177
|
+
confidence: Confidence level (heuristic, tested, confirmed)
|
|
178
|
+
notes: Additional notes
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
model_id: str
|
|
182
|
+
supports_tools: bool = False
|
|
183
|
+
parallel_tools: bool = False
|
|
184
|
+
tool_choice_modes: List[str] = field(default_factory=list)
|
|
185
|
+
recommended_params: Dict[str, Any] = field(default_factory=dict)
|
|
186
|
+
quirks: Dict[str, Any] = field(default_factory=dict)
|
|
187
|
+
verified: bool = False
|
|
188
|
+
confidence: str = "unknown" # unknown, heuristic, tested, confirmed
|
|
189
|
+
notes: str = ""
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_tool_capability_info(model_id: str) -> ToolCapabilityInfo:
|
|
193
|
+
"""Get tool capability information for a model based on heuristics.
|
|
194
|
+
|
|
195
|
+
This provides a quick assessment without actually testing the model.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
model_id: Model identifier (e.g., "llama3.2:8b-instruct-q4_K_M")
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
ToolCapabilityInfo with heuristic-based assessment.
|
|
202
|
+
"""
|
|
203
|
+
model_lower = model_id.lower()
|
|
204
|
+
|
|
205
|
+
# Check if definitely not supported
|
|
206
|
+
for pattern in NO_TOOL_SUPPORT:
|
|
207
|
+
if pattern in model_lower:
|
|
208
|
+
return ToolCapabilityInfo(
|
|
209
|
+
model_id=model_id,
|
|
210
|
+
supports_tools=False,
|
|
211
|
+
confidence="heuristic",
|
|
212
|
+
notes=f"Model family '{pattern}' does not support tools",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Check known capable models
|
|
216
|
+
for pattern, info in TOOL_CAPABLE_MODELS.items():
|
|
217
|
+
if pattern in model_lower:
|
|
218
|
+
quirks = TOOL_QUIRKS.get(pattern, {})
|
|
219
|
+
return ToolCapabilityInfo(
|
|
220
|
+
model_id=model_id,
|
|
221
|
+
supports_tools=info.get("supports_tools", False),
|
|
222
|
+
parallel_tools=info.get("parallel_tools", False),
|
|
223
|
+
tool_choice_modes=info.get("tool_choice", []),
|
|
224
|
+
recommended_params=info.get("recommended_params", {}),
|
|
225
|
+
quirks=quirks,
|
|
226
|
+
confidence="heuristic",
|
|
227
|
+
notes=info.get("notes", ""),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Unknown - check for instruct variant which might support tools
|
|
231
|
+
if any(x in model_lower for x in ["instruct", "chat", "assistant"]):
|
|
232
|
+
return ToolCapabilityInfo(
|
|
233
|
+
model_id=model_id,
|
|
234
|
+
supports_tools=False, # Assume no until tested
|
|
235
|
+
confidence="unknown",
|
|
236
|
+
notes="Instruct model, may support tools. Test to confirm.",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return ToolCapabilityInfo(
|
|
240
|
+
model_id=model_id,
|
|
241
|
+
supports_tools=False,
|
|
242
|
+
confidence="unknown",
|
|
243
|
+
notes="Unknown model, tool support uncertain",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
async def test_tool_calling(
|
|
248
|
+
model_id: str, provider_host: str = "http://localhost:11434", timeout: float = 60.0
|
|
249
|
+
) -> ToolTestResult:
|
|
250
|
+
"""Test if a model can execute tool calls.
|
|
251
|
+
|
|
252
|
+
Performs an actual API call to test tool calling capability.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
model_id: Model identifier
|
|
256
|
+
provider_host: Provider host URL (default: Ollama)
|
|
257
|
+
timeout: Request timeout in seconds
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
ToolTestResult with actual test results.
|
|
261
|
+
"""
|
|
262
|
+
from urllib.request import Request, urlopen
|
|
263
|
+
from urllib.error import URLError, HTTPError
|
|
264
|
+
|
|
265
|
+
start_time = time.time()
|
|
266
|
+
|
|
267
|
+
# First check heuristics
|
|
268
|
+
info = get_tool_capability_info(model_id)
|
|
269
|
+
if info.confidence == "heuristic" and not info.supports_tools:
|
|
270
|
+
return ToolTestResult(model_id=model_id, supports_tools=False, notes=info.notes)
|
|
271
|
+
|
|
272
|
+
# Prepare test request
|
|
273
|
+
endpoint = f"{provider_host}/api/chat"
|
|
274
|
+
|
|
275
|
+
payload = {
|
|
276
|
+
"model": model_id,
|
|
277
|
+
"messages": [TEST_MESSAGE],
|
|
278
|
+
"tools": [TEST_TOOL],
|
|
279
|
+
"stream": False,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# Add recommended params if any
|
|
283
|
+
if info.recommended_params:
|
|
284
|
+
payload["options"] = info.recommended_params
|
|
285
|
+
|
|
286
|
+
loop = asyncio.get_event_loop()
|
|
287
|
+
|
|
288
|
+
def do_test():
|
|
289
|
+
try:
|
|
290
|
+
headers = {"Content-Type": "application/json"}
|
|
291
|
+
body = json.dumps(payload).encode("utf-8")
|
|
292
|
+
request = Request(endpoint, data=body, headers=headers, method="POST")
|
|
293
|
+
|
|
294
|
+
with urlopen(request, timeout=timeout) as response:
|
|
295
|
+
return json.loads(response.read().decode("utf-8"))
|
|
296
|
+
|
|
297
|
+
except HTTPError as e:
|
|
298
|
+
if e.code == 400:
|
|
299
|
+
# Model might not support tools parameter
|
|
300
|
+
return {"error": "tools_not_supported", "code": 400}
|
|
301
|
+
return {"error": str(e), "code": e.code}
|
|
302
|
+
except URLError as e:
|
|
303
|
+
return {"error": str(e.reason)}
|
|
304
|
+
except Exception as e:
|
|
305
|
+
return {"error": str(e)}
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
response = await loop.run_in_executor(None, do_test)
|
|
309
|
+
latency = (time.time() - start_time) * 1000
|
|
310
|
+
|
|
311
|
+
# Check for errors
|
|
312
|
+
if "error" in response:
|
|
313
|
+
error_msg = response.get("error", "")
|
|
314
|
+
if "tools_not_supported" in str(error_msg) or response.get("code") == 400:
|
|
315
|
+
return ToolTestResult(
|
|
316
|
+
model_id=model_id,
|
|
317
|
+
supports_tools=False,
|
|
318
|
+
latency_ms=latency,
|
|
319
|
+
notes="Model returned error for tools parameter",
|
|
320
|
+
)
|
|
321
|
+
return ToolTestResult(
|
|
322
|
+
model_id=model_id,
|
|
323
|
+
supports_tools=False,
|
|
324
|
+
error=str(error_msg),
|
|
325
|
+
latency_ms=latency,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Check for tool calls in response
|
|
329
|
+
message = response.get("message", {})
|
|
330
|
+
tool_calls = message.get("tool_calls", [])
|
|
331
|
+
|
|
332
|
+
if tool_calls:
|
|
333
|
+
# Verify tool call structure
|
|
334
|
+
valid_call = False
|
|
335
|
+
parallel = len(tool_calls) > 1
|
|
336
|
+
|
|
337
|
+
for call in tool_calls:
|
|
338
|
+
func = call.get("function", {})
|
|
339
|
+
if func.get("name") == "get_weather":
|
|
340
|
+
valid_call = True
|
|
341
|
+
break
|
|
342
|
+
|
|
343
|
+
return ToolTestResult(
|
|
344
|
+
model_id=model_id,
|
|
345
|
+
supports_tools=valid_call,
|
|
346
|
+
parallel_tools=parallel,
|
|
347
|
+
tool_choice=info.tool_choice_modes or ["auto"],
|
|
348
|
+
latency_ms=latency,
|
|
349
|
+
notes="Tool calling verified by test"
|
|
350
|
+
if valid_call
|
|
351
|
+
else "Response had tool_calls but wrong function",
|
|
352
|
+
)
|
|
353
|
+
else:
|
|
354
|
+
# Model responded but didn't use tools
|
|
355
|
+
content = message.get("content", "")
|
|
356
|
+
if content:
|
|
357
|
+
return ToolTestResult(
|
|
358
|
+
model_id=model_id,
|
|
359
|
+
supports_tools=False,
|
|
360
|
+
latency_ms=latency,
|
|
361
|
+
notes="Model responded with text instead of tool call",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return ToolTestResult(
|
|
365
|
+
model_id=model_id,
|
|
366
|
+
supports_tools=False,
|
|
367
|
+
latency_ms=latency,
|
|
368
|
+
notes="Empty response, tool calling may not be supported",
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
except Exception as e:
|
|
372
|
+
return ToolTestResult(
|
|
373
|
+
model_id=model_id,
|
|
374
|
+
supports_tools=False,
|
|
375
|
+
error=str(e),
|
|
376
|
+
notes="Test failed with exception",
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def get_recommended_coding_models() -> List[Dict[str, Any]]:
|
|
381
|
+
"""Get list of models recommended for coding with tool support.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
List of model recommendations with capability info.
|
|
385
|
+
"""
|
|
386
|
+
recommendations = [
|
|
387
|
+
{
|
|
388
|
+
"model": "qwen2.5-coder:32b",
|
|
389
|
+
"family": "qwen",
|
|
390
|
+
"params": "32B",
|
|
391
|
+
"tool_support": "excellent",
|
|
392
|
+
"coding_quality": "excellent",
|
|
393
|
+
"context": "32K",
|
|
394
|
+
"notes": "Best for coding with tools, requires ~20GB VRAM",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"model": "qwen2.5-coder:7b",
|
|
398
|
+
"family": "qwen",
|
|
399
|
+
"params": "7B",
|
|
400
|
+
"tool_support": "excellent",
|
|
401
|
+
"coding_quality": "very good",
|
|
402
|
+
"context": "32K",
|
|
403
|
+
"notes": "Good balance of quality and resources",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"model": "llama3.3:70b",
|
|
407
|
+
"family": "llama",
|
|
408
|
+
"params": "70B",
|
|
409
|
+
"tool_support": "excellent",
|
|
410
|
+
"coding_quality": "excellent",
|
|
411
|
+
"context": "128K",
|
|
412
|
+
"notes": "Latest Llama, excellent overall",
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"model": "llama3.2:8b",
|
|
416
|
+
"family": "llama",
|
|
417
|
+
"params": "8B",
|
|
418
|
+
"tool_support": "excellent",
|
|
419
|
+
"coding_quality": "good",
|
|
420
|
+
"context": "128K",
|
|
421
|
+
"notes": "Efficient with native tool support",
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"model": "deepseek-coder-v2:16b",
|
|
425
|
+
"family": "deepseek",
|
|
426
|
+
"params": "16B",
|
|
427
|
+
"tool_support": "good",
|
|
428
|
+
"coding_quality": "excellent",
|
|
429
|
+
"context": "128K",
|
|
430
|
+
"notes": "Specialized for code generation",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
"model": "mistral:7b",
|
|
434
|
+
"family": "mistral",
|
|
435
|
+
"params": "7B",
|
|
436
|
+
"tool_support": "good",
|
|
437
|
+
"coding_quality": "good",
|
|
438
|
+
"context": "32K",
|
|
439
|
+
"notes": "Reliable tool support, efficient",
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"model": "functionary:latest",
|
|
443
|
+
"family": "functionary",
|
|
444
|
+
"params": "varies",
|
|
445
|
+
"tool_support": "excellent",
|
|
446
|
+
"coding_quality": "good",
|
|
447
|
+
"context": "8K",
|
|
448
|
+
"notes": "Specialized for function calling",
|
|
449
|
+
},
|
|
450
|
+
]
|
|
451
|
+
|
|
452
|
+
return recommendations
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def estimate_tool_support(model: LocalModel) -> str:
|
|
456
|
+
"""Estimate tool support level for a model.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
model: LocalModel instance
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Support level: "excellent", "good", "limited", "none", "unknown"
|
|
463
|
+
"""
|
|
464
|
+
info = get_tool_capability_info(model.id)
|
|
465
|
+
|
|
466
|
+
if not info.supports_tools:
|
|
467
|
+
if info.confidence == "heuristic":
|
|
468
|
+
return "none"
|
|
469
|
+
return "unknown"
|
|
470
|
+
|
|
471
|
+
if info.parallel_tools and len(info.tool_choice_modes) >= 3:
|
|
472
|
+
return "excellent"
|
|
473
|
+
|
|
474
|
+
if info.supports_tools:
|
|
475
|
+
return "good"
|
|
476
|
+
|
|
477
|
+
return "limited"
|