velune-cli 1.0.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.
- velune/__init__.py +5 -0
- velune/__main__.py +6 -0
- velune/cli/__init__.py +5 -0
- velune/cli/app.py +212 -0
- velune/cli/autocomplete.py +76 -0
- velune/cli/banner.py +98 -0
- velune/cli/commands/__init__.py +32 -0
- velune/cli/commands/ask.py +149 -0
- velune/cli/commands/base.py +16 -0
- velune/cli/commands/chat.py +188 -0
- velune/cli/commands/config.py +182 -0
- velune/cli/commands/daemon.py +85 -0
- velune/cli/commands/doctor.py +373 -0
- velune/cli/commands/init.py +160 -0
- velune/cli/commands/mcp.py +80 -0
- velune/cli/commands/memory.py +269 -0
- velune/cli/commands/models.py +462 -0
- velune/cli/commands/preflight.py +95 -0
- velune/cli/commands/run.py +171 -0
- velune/cli/commands/setup.py +182 -0
- velune/cli/commands/workspace.py +217 -0
- velune/cli/context.py +37 -0
- velune/cli/councilmodel_ui.py +171 -0
- velune/cli/display/council_view.py +240 -0
- velune/cli/display/memory_view.py +93 -0
- velune/cli/display/panels.py +35 -0
- velune/cli/display/progress.py +25 -0
- velune/cli/display/themes.py +21 -0
- velune/cli/main.py +15 -0
- velune/cli/model_selector.py +44 -0
- velune/cli/modes.py +86 -0
- velune/cli/pull_ui.py +118 -0
- velune/cli/registry.py +81 -0
- velune/cli/repl.py +1178 -0
- velune/cli/session_manager.py +69 -0
- velune/cli/slash_commands.py +37 -0
- velune/cognition/__init__.py +19 -0
- velune/cognition/arbitrator.py +216 -0
- velune/cognition/architecture.py +398 -0
- velune/cognition/council/__init__.py +47 -0
- velune/cognition/council/base.py +216 -0
- velune/cognition/council/challenger.py +70 -0
- velune/cognition/council/coder.py +79 -0
- velune/cognition/council/critic_agent.py +39 -0
- velune/cognition/council/critic_configs.py +111 -0
- velune/cognition/council/critics.py +41 -0
- velune/cognition/council/debate.py +44 -0
- velune/cognition/council/factory.py +140 -0
- velune/cognition/council/messages.py +53 -0
- velune/cognition/council/planner.py +119 -0
- velune/cognition/council/reviewer.py +72 -0
- velune/cognition/council/synthesizer.py +67 -0
- velune/cognition/council/tiers.py +181 -0
- velune/cognition/firewall.py +256 -0
- velune/cognition/module.py +38 -0
- velune/cognition/orchestrator.py +886 -0
- velune/cognition/personality.py +236 -0
- velune/cognition/style_resolver.py +62 -0
- velune/cognition/verification.py +201 -0
- velune/context/__init__.py +11 -0
- velune/context/extractive.py +94 -0
- velune/context/window.py +62 -0
- velune/core/__init__.py +89 -0
- velune/core/background.py +5 -0
- velune/core/config/__init__.py +37 -0
- velune/core/errors/__init__.py +53 -0
- velune/core/errors/execution.py +26 -0
- velune/core/errors/memory.py +21 -0
- velune/core/errors/orchestration.py +26 -0
- velune/core/errors/provider.py +31 -0
- velune/core/event_loop.py +30 -0
- velune/core/logging.py +83 -0
- velune/core/runtime.py +106 -0
- velune/core/task_registry.py +120 -0
- velune/core/trace.py +80 -0
- velune/core/types/__init__.py +48 -0
- velune/core/types/agent.py +49 -0
- velune/core/types/context.py +39 -0
- velune/core/types/inference.py +35 -0
- velune/core/types/memory.py +39 -0
- velune/core/types/model.py +64 -0
- velune/core/types/provider.py +35 -0
- velune/core/types/repository.py +35 -0
- velune/core/types/task.py +56 -0
- velune/core/types/workspace.py +28 -0
- velune/daemon/client.py +13 -0
- velune/daemon/server.py +115 -0
- velune/daemon/transport.py +169 -0
- velune/events.py +194 -0
- velune/execution/__init__.py +22 -0
- velune/execution/benchmarker.py +311 -0
- velune/execution/cancellation.py +53 -0
- velune/execution/checkpointer.py +128 -0
- velune/execution/command_spec.py +140 -0
- velune/execution/diff_preview.py +172 -0
- velune/execution/executor.py +173 -0
- velune/execution/module.py +16 -0
- velune/execution/multi_diff.py +70 -0
- velune/execution/path_guard.py +19 -0
- velune/execution/planner.py +91 -0
- velune/execution/rollback.py +80 -0
- velune/execution/sandbox.py +257 -0
- velune/execution/validator.py +113 -0
- velune/hardware/__init__.py +1 -0
- velune/hardware/detector.py +162 -0
- velune/kernel/__init__.py +58 -0
- velune/kernel/bootstrap.py +107 -0
- velune/kernel/config.py +252 -0
- velune/kernel/health.py +54 -0
- velune/kernel/lifecycle.py +102 -0
- velune/kernel/module.py +15 -0
- velune/kernel/modules.py +23 -0
- velune/kernel/registry.py +93 -0
- velune/kernel/schemas.py +28 -0
- velune/main.py +9 -0
- velune/mcp/__init__.py +9 -0
- velune/mcp/client.py +113 -0
- velune/mcp/config.py +19 -0
- velune/mcp/server.py +90 -0
- velune/memory/__init__.py +28 -0
- velune/memory/lifecycle.py +154 -0
- velune/memory/module.py +94 -0
- velune/memory/prioritizer.py +65 -0
- velune/memory/storage/sqlite_manager.py +368 -0
- velune/memory/tiers/episodic.py +156 -0
- velune/memory/tiers/graph.py +282 -0
- velune/memory/tiers/lineage.py +367 -0
- velune/memory/tiers/semantic.py +198 -0
- velune/memory/tiers/working.py +165 -0
- velune/models/__init__.py +16 -0
- velune/models/module.py +18 -0
- velune/models/probes.py +182 -0
- velune/models/profile_cache.py +82 -0
- velune/models/profiler.py +105 -0
- velune/models/registry.py +201 -0
- velune/models/scorer.py +225 -0
- velune/models/specializations.py +196 -0
- velune/orchestration/__init__.py +15 -0
- velune/orchestration/module.py +14 -0
- velune/orchestration/role_assignments.py +78 -0
- velune/orchestration/schemas.py +99 -0
- velune/plugins/__init__.py +13 -0
- velune/plugins/hooks.py +49 -0
- velune/plugins/loader.py +95 -0
- velune/plugins/registry.py +54 -0
- velune/plugins/schemas.py +19 -0
- velune/providers/__init__.py +18 -0
- velune/providers/adapters/anthropic.py +231 -0
- velune/providers/adapters/fireworks.py +115 -0
- velune/providers/adapters/google.py +234 -0
- velune/providers/adapters/groq.py +151 -0
- velune/providers/adapters/huggingface.py +203 -0
- velune/providers/adapters/llamacpp.py +202 -0
- velune/providers/adapters/lmstudio.py +173 -0
- velune/providers/adapters/ollama.py +186 -0
- velune/providers/adapters/openai.py +207 -0
- velune/providers/adapters/openrouter.py +81 -0
- velune/providers/adapters/together.py +134 -0
- velune/providers/adapters/xai.py +60 -0
- velune/providers/base.py +86 -0
- velune/providers/benchmarker.py +135 -0
- velune/providers/discovery/__init__.py +33 -0
- velune/providers/discovery/anthropic.py +77 -0
- velune/providers/discovery/benchmarks.py +44 -0
- velune/providers/discovery/classifier.py +69 -0
- velune/providers/discovery/fireworks.py +95 -0
- velune/providers/discovery/gguf.py +87 -0
- velune/providers/discovery/google.py +95 -0
- velune/providers/discovery/gpu.py +109 -0
- velune/providers/discovery/groq.py +20 -0
- velune/providers/discovery/huggingface.py +67 -0
- velune/providers/discovery/lmstudio.py +80 -0
- velune/providers/discovery/ollama.py +165 -0
- velune/providers/discovery/openai.py +96 -0
- velune/providers/discovery/openrouter.py +113 -0
- velune/providers/discovery/scanner.py +114 -0
- velune/providers/discovery/together.py +114 -0
- velune/providers/discovery/xai.py +57 -0
- velune/providers/keystore.py +83 -0
- velune/providers/local_paths.py +49 -0
- velune/providers/local_resolver.py +208 -0
- velune/providers/module.py +15 -0
- velune/providers/ollama_manager.py +193 -0
- velune/providers/registry.py +173 -0
- velune/providers/router.py +82 -0
- velune/py.typed +0 -0
- velune/repository/__init__.py +21 -0
- velune/repository/analyzer.py +123 -0
- velune/repository/cognition.py +172 -0
- velune/repository/grapher.py +182 -0
- velune/repository/indexer.py +229 -0
- velune/repository/module.py +15 -0
- velune/repository/parser.py +378 -0
- velune/repository/project_type.py +293 -0
- velune/repository/scanner.py +177 -0
- velune/repository/schemas.py +102 -0
- velune/repository/tracker.py +233 -0
- velune/retrieval/__init__.py +27 -0
- velune/retrieval/graph.py +117 -0
- velune/retrieval/hybrid.py +250 -0
- velune/retrieval/keyword.py +113 -0
- velune/retrieval/module.py +19 -0
- velune/retrieval/reranker.py +68 -0
- velune/retrieval/schemas.py +59 -0
- velune/retrieval/vector.py +163 -0
- velune/telemetry/__init__.py +7 -0
- velune/telemetry/cognition.py +260 -0
- velune/telemetry/token_tracker.py +133 -0
- velune/tools/__init__.py +41 -0
- velune/tools/base/registry.py +84 -0
- velune/tools/base/tool.py +63 -0
- velune/tools/code/navigate.py +107 -0
- velune/tools/code/search.py +112 -0
- velune/tools/filesystem/read.py +75 -0
- velune/tools/filesystem/search.py +123 -0
- velune/tools/filesystem/write.py +160 -0
- velune/tools/git/history.py +185 -0
- velune/tools/git/operations.py +132 -0
- velune/tools/git/state.py +134 -0
- velune/tools/module.py +65 -0
- velune/tools/terminal/execute.py +72 -0
- velune/tools/terminal/history.py +47 -0
- velune/tools/web/fetch.py +55 -0
- velune/tools/web/validator.py +96 -0
- velune_cli-1.0.0.dist-info/METADATA +497 -0
- velune_cli-1.0.0.dist-info/RECORD +229 -0
- velune_cli-1.0.0.dist-info/WHEEL +4 -0
- velune_cli-1.0.0.dist-info/entry_points.txt +2 -0
- velune_cli-1.0.0.dist-info/licenses/LICENSE +201 -0
velune/providers/base.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Provider abstraction base interfaces and capabilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
from typing import Protocol, runtime_checkable
|
|
8
|
+
|
|
9
|
+
from velune.core.types.inference import InferenceRequest, InferenceResponse, StreamChunk
|
|
10
|
+
from velune.core.types.model import ModelDescriptor
|
|
11
|
+
from velune.core.types.provider import ProviderCapabilities, ProviderHealth
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@runtime_checkable
|
|
15
|
+
class ModelProvider(Protocol):
|
|
16
|
+
"""Core provider contract. All LLM and Embedding adapters implement this."""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def provider_id(self) -> str:
|
|
20
|
+
"""The distinct ID slug of this provider (e.g., 'ollama', 'openai')."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
async def list_models(self) -> list[ModelDescriptor]:
|
|
24
|
+
"""Query and list all active/available models for this provider."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
async def infer(self, request: InferenceRequest) -> InferenceResponse:
|
|
28
|
+
"""Single-turn, non-streaming model completion."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
async def stream(self, request: InferenceRequest) -> AsyncIterator[StreamChunk]:
|
|
32
|
+
"""Multi-turn, token-streaming model completion."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
async def embed(self, texts: list[str], model_id: str) -> list[list[float]]:
|
|
36
|
+
"""Generate vector embeddings. Raises NotImplementedError if unsupported."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
async def health_check(self) -> ProviderHealth:
|
|
40
|
+
"""Query host or ping API to verify provider state."""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
def get_capabilities(self) -> ProviderCapabilities:
|
|
44
|
+
"""Query static/dynamic capabilities of the provider."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
async def initialize(self) -> None:
|
|
48
|
+
"""Perform provider connection setup and warmups."""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
async def shutdown(self) -> None:
|
|
52
|
+
"""Gracefully release provider connection resource pools."""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InferenceEngine(ABC):
|
|
57
|
+
"""Abstract inference engine for unified task executions."""
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
async def infer(self, request: InferenceRequest) -> InferenceResponse:
|
|
61
|
+
"""Perform non-streaming inference."""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
async def infer_stream(self, request: InferenceRequest) -> AsyncIterator[StreamChunk]:
|
|
66
|
+
"""Perform streaming inference."""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class EmbeddingProvider(ABC):
|
|
71
|
+
"""Abstract embedding provider interface."""
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
async def embed(self, text: str) -> list[float]:
|
|
75
|
+
"""Generate embedding for a single text."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
80
|
+
"""Generate embeddings for a batch of texts."""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def get_dimension(self) -> int:
|
|
85
|
+
"""Get the embedding dimension vector width."""
|
|
86
|
+
pass
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Provider and model benchmarking engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from velune.core.types.inference import InferenceRequest
|
|
8
|
+
from velune.providers.base import ModelProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ModelBenchmarkMetrics:
|
|
12
|
+
"""Live performance metrics recorded during model benchmarks."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
self.ttft_ms: float = 0.0 # Time to first token
|
|
16
|
+
self.tps: float = 0.0 # Tokens per second
|
|
17
|
+
self.total_latency_ms: float = 0.0 # Total latency in ms
|
|
18
|
+
self.tokens_generated: int = 0
|
|
19
|
+
self.tool_accuracy: float = 0.0 # Percentage correctness for tools
|
|
20
|
+
self.json_validity: float = 0.0 # Percentage valid JSON formats
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ProviderBenchmarker:
|
|
24
|
+
"""Evaluates provider latency, generation throughput, and structured accuracy."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, provider: ModelProvider, model_id: str) -> None:
|
|
27
|
+
self.provider = provider
|
|
28
|
+
self.model_id = model_id
|
|
29
|
+
|
|
30
|
+
async def run_latency_probe(self, prompt: str = "Hello, respond with exactly 'pong'.") -> ModelBenchmarkMetrics:
|
|
31
|
+
"""Probe time-to-first-token and total generation latency."""
|
|
32
|
+
metrics = ModelBenchmarkMetrics()
|
|
33
|
+
request = InferenceRequest(
|
|
34
|
+
model_id=self.model_id,
|
|
35
|
+
messages=[{"role": "user", "content": prompt}],
|
|
36
|
+
temperature=0.0,
|
|
37
|
+
max_tokens=10,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
start_time = time.perf_counter()
|
|
41
|
+
first_token_time: float | None = None
|
|
42
|
+
chars_received = 0
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# We use streaming to calculate TTFT and TPS
|
|
46
|
+
async for chunk in self.provider.stream(request):
|
|
47
|
+
if first_token_time is None and chunk.content:
|
|
48
|
+
first_token_time = time.perf_counter()
|
|
49
|
+
metrics.ttft_ms = (first_token_time - start_time) * 1000.0
|
|
50
|
+
|
|
51
|
+
chars_received += len(chunk.content)
|
|
52
|
+
# Estimating tokens: 1 token ≈ 4 characters
|
|
53
|
+
metrics.tokens_generated = int(chars_received / 4.0)
|
|
54
|
+
|
|
55
|
+
end_time = time.perf_counter()
|
|
56
|
+
metrics.total_latency_ms = (end_time - start_time) * 1000.0
|
|
57
|
+
|
|
58
|
+
if first_token_time is not None:
|
|
59
|
+
gen_duration = end_time - first_token_time
|
|
60
|
+
if gen_duration > 0 and metrics.tokens_generated > 0:
|
|
61
|
+
metrics.tps = metrics.tokens_generated / gen_duration
|
|
62
|
+
else:
|
|
63
|
+
metrics.ttft_ms = metrics.total_latency_ms
|
|
64
|
+
metrics.tps = 0.0
|
|
65
|
+
|
|
66
|
+
except Exception:
|
|
67
|
+
# Handle non-streaming fallback
|
|
68
|
+
try:
|
|
69
|
+
start_time = time.perf_counter()
|
|
70
|
+
response = await self.provider.infer(request)
|
|
71
|
+
end_time = time.perf_counter()
|
|
72
|
+
|
|
73
|
+
metrics.total_latency_ms = (end_time - start_time) * 1000.0
|
|
74
|
+
metrics.ttft_ms = metrics.total_latency_ms # No stream TTFT estimation
|
|
75
|
+
metrics.tokens_generated = response.tokens_used or int(len(response.content) / 4.0)
|
|
76
|
+
|
|
77
|
+
duration = (end_time - start_time)
|
|
78
|
+
if duration > 0:
|
|
79
|
+
metrics.tps = metrics.tokens_generated / duration
|
|
80
|
+
except Exception:
|
|
81
|
+
# Failed probe
|
|
82
|
+
metrics.ttft_ms = -1.0
|
|
83
|
+
metrics.tps = 0.0
|
|
84
|
+
|
|
85
|
+
return metrics
|
|
86
|
+
|
|
87
|
+
async def run_structured_probe(self) -> dict[str, float]:
|
|
88
|
+
"""Probe model capabilities in generating valid JSON format."""
|
|
89
|
+
prompt = (
|
|
90
|
+
"Return a JSON object representing a file list with keys 'files' (list of strings) and 'count' (integer). "
|
|
91
|
+
"Return ONLY raw valid JSON code. No markdown wrapper, no extra text."
|
|
92
|
+
)
|
|
93
|
+
request = InferenceRequest(
|
|
94
|
+
model_id=self.model_id,
|
|
95
|
+
messages=[{"role": "user", "content": prompt}],
|
|
96
|
+
temperature=0.0,
|
|
97
|
+
max_tokens=100,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
json_valid = 0.0
|
|
101
|
+
structure_valid = 0.0
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
response = await self.provider.infer(request)
|
|
105
|
+
content = response.content.strip()
|
|
106
|
+
|
|
107
|
+
# Clean possible markdown wrappers
|
|
108
|
+
if content.startswith("```"):
|
|
109
|
+
lines = content.splitlines()
|
|
110
|
+
if len(lines) > 2:
|
|
111
|
+
content = "\n".join(lines[1:-1])
|
|
112
|
+
if content.startswith("json"):
|
|
113
|
+
content = content[4:].strip()
|
|
114
|
+
|
|
115
|
+
import json
|
|
116
|
+
parsed = json.loads(content)
|
|
117
|
+
json_valid = 1.0
|
|
118
|
+
|
|
119
|
+
if isinstance(parsed, dict) and "files" in parsed and "count" in parsed:
|
|
120
|
+
if isinstance(parsed["files"], list) and isinstance(parsed["count"], int):
|
|
121
|
+
structure_valid = 1.0
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
return {"json_validity": json_valid, "structure_validity": structure_valid}
|
|
126
|
+
|
|
127
|
+
async def evaluate(self) -> ModelBenchmarkMetrics:
|
|
128
|
+
"""Run all benchmark probes and aggregate metrics."""
|
|
129
|
+
metrics = await self.run_latency_probe()
|
|
130
|
+
struct_res = await self.run_structured_probe()
|
|
131
|
+
|
|
132
|
+
metrics.json_validity = struct_res["json_validity"]
|
|
133
|
+
metrics.tool_accuracy = struct_res["structure_validity"]
|
|
134
|
+
|
|
135
|
+
return metrics
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Model discovery components."""
|
|
2
|
+
|
|
3
|
+
from velune.providers.discovery.anthropic import AnthropicDiscovery
|
|
4
|
+
from velune.providers.discovery.benchmarks import CapabilityBenchmark
|
|
5
|
+
from velune.providers.discovery.classifier import CapabilityClassifier
|
|
6
|
+
from velune.providers.discovery.gguf import GGUFDiscovery
|
|
7
|
+
from velune.providers.discovery.google import GoogleDiscovery
|
|
8
|
+
from velune.providers.discovery.gpu import GPUDetector
|
|
9
|
+
from velune.providers.discovery.groq import GroqDiscovery
|
|
10
|
+
from velune.providers.discovery.huggingface import HuggingFaceDiscovery
|
|
11
|
+
from velune.providers.discovery.lmstudio import LMStudioDiscovery
|
|
12
|
+
from velune.providers.discovery.ollama import OllamaDiscovery
|
|
13
|
+
from velune.providers.discovery.openai import OpenAIDiscovery
|
|
14
|
+
from velune.providers.discovery.openrouter import OpenRouterDiscovery
|
|
15
|
+
from velune.providers.discovery.scanner import ModelDiscoveryScanner
|
|
16
|
+
from velune.providers.discovery.xai import XAIDiscovery
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ModelDiscoveryScanner",
|
|
20
|
+
"OllamaDiscovery",
|
|
21
|
+
"LMStudioDiscovery",
|
|
22
|
+
"GGUFDiscovery",
|
|
23
|
+
"HuggingFaceDiscovery",
|
|
24
|
+
"OpenAIDiscovery",
|
|
25
|
+
"AnthropicDiscovery",
|
|
26
|
+
"XAIDiscovery",
|
|
27
|
+
"GoogleDiscovery",
|
|
28
|
+
"GroqDiscovery",
|
|
29
|
+
"OpenRouterDiscovery",
|
|
30
|
+
"GPUDetector",
|
|
31
|
+
"CapabilityClassifier",
|
|
32
|
+
"CapabilityBenchmark",
|
|
33
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from velune.core.types.model import CapabilityLevel, ModelCapabilityProfile, ModelDescriptor
|
|
4
|
+
from velune.providers.keystore import get_key
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AnthropicDiscovery:
|
|
8
|
+
"""Discovers models from Anthropic."""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.provider_id = "anthropic"
|
|
12
|
+
self.api_key = get_key("anthropic")
|
|
13
|
+
self.base_url = "https://api.anthropic.com"
|
|
14
|
+
|
|
15
|
+
async def discover(self) -> list[ModelDescriptor]:
|
|
16
|
+
"""Discover models from Anthropic."""
|
|
17
|
+
if not self.api_key:
|
|
18
|
+
return []
|
|
19
|
+
|
|
20
|
+
# Anthropic has a fixed set of models
|
|
21
|
+
models = [
|
|
22
|
+
self._create_model_descriptor("claude-opus-4-5", 200000, 0.015),
|
|
23
|
+
self._create_model_descriptor("claude-sonnet-4-5", 200000, 0.003),
|
|
24
|
+
self._create_model_descriptor("claude-haiku-4-5", 200000, 0.00025),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
return models
|
|
28
|
+
|
|
29
|
+
def _create_model_descriptor(self, model_id: str, context_length: int, cost_per_1k: float) -> ModelDescriptor:
|
|
30
|
+
"""Create a model descriptor."""
|
|
31
|
+
capabilities = self._classify_capabilities(model_id)
|
|
32
|
+
|
|
33
|
+
return ModelDescriptor(
|
|
34
|
+
model_id=model_id,
|
|
35
|
+
provider_id=self.provider_id,
|
|
36
|
+
display_name=model_id,
|
|
37
|
+
context_length=context_length,
|
|
38
|
+
capabilities=capabilities,
|
|
39
|
+
quantization=None,
|
|
40
|
+
vram_required_gb=None,
|
|
41
|
+
parameter_count_b=None,
|
|
42
|
+
speed_tier="medium",
|
|
43
|
+
cost_per_1k_tokens=cost_per_1k,
|
|
44
|
+
tags=["cloud", "anthropic"],
|
|
45
|
+
metadata={},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def _classify_capabilities(self, model_id: str) -> ModelCapabilityProfile:
|
|
49
|
+
"""Classify capabilities for Anthropic models."""
|
|
50
|
+
profile = ModelCapabilityProfile()
|
|
51
|
+
|
|
52
|
+
if "opus" in model_id:
|
|
53
|
+
profile.coding = CapabilityLevel.EXPERT
|
|
54
|
+
profile.reasoning = CapabilityLevel.EXPERT
|
|
55
|
+
profile.planning = CapabilityLevel.EXPERT
|
|
56
|
+
profile.summarization = CapabilityLevel.EXPERT
|
|
57
|
+
profile.instruction_following = CapabilityLevel.EXPERT
|
|
58
|
+
profile.tool_use = CapabilityLevel.EXPERT
|
|
59
|
+
profile.long_context = CapabilityLevel.EXPERT
|
|
60
|
+
elif "sonnet" in model_id:
|
|
61
|
+
profile.coding = CapabilityLevel.ADVANCED
|
|
62
|
+
profile.reasoning = CapabilityLevel.ADVANCED
|
|
63
|
+
profile.planning = CapabilityLevel.ADVANCED
|
|
64
|
+
profile.summarization = CapabilityLevel.ADVANCED
|
|
65
|
+
profile.instruction_following = CapabilityLevel.ADVANCED
|
|
66
|
+
profile.tool_use = CapabilityLevel.EXPERT
|
|
67
|
+
profile.long_context = CapabilityLevel.ADVANCED
|
|
68
|
+
elif "haiku" in model_id:
|
|
69
|
+
profile.coding = CapabilityLevel.INTERMEDIATE
|
|
70
|
+
profile.reasoning = CapabilityLevel.INTERMEDIATE
|
|
71
|
+
profile.planning = CapabilityLevel.INTERMEDIATE
|
|
72
|
+
profile.summarization = CapabilityLevel.INTERMEDIATE
|
|
73
|
+
profile.instruction_following = CapabilityLevel.ADVANCED
|
|
74
|
+
profile.tool_use = CapabilityLevel.ADVANCED
|
|
75
|
+
profile.long_context = CapabilityLevel.INTERMEDIATE
|
|
76
|
+
|
|
77
|
+
return profile
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Lightweight capability benchmarking."""
|
|
2
|
+
|
|
3
|
+
from velune.core.types.model import CapabilityLevel, ModelCapabilityProfile
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CapabilityBenchmark:
|
|
7
|
+
"""Lightweight benchmark probes for capability detection."""
|
|
8
|
+
|
|
9
|
+
async def benchmark_coding(self, model_id: str) -> CapabilityLevel:
|
|
10
|
+
"""Benchmark coding capability."""
|
|
11
|
+
# In production, this would run actual coding tasks
|
|
12
|
+
# For now, return based on heuristics
|
|
13
|
+
model_lower = model_id.lower()
|
|
14
|
+
|
|
15
|
+
if any(name in model_lower for name in ["coder", "deepseek-coder"]):
|
|
16
|
+
return CapabilityLevel.ADVANCED
|
|
17
|
+
elif any(name in model_lower for name in ["llama", "mistral"]):
|
|
18
|
+
return CapabilityLevel.INTERMEDIATE
|
|
19
|
+
|
|
20
|
+
return CapabilityLevel.BASIC
|
|
21
|
+
|
|
22
|
+
async def benchmark_reasoning(self, model_id: str) -> CapabilityLevel:
|
|
23
|
+
"""Benchmark reasoning capability."""
|
|
24
|
+
model_lower = model_id.lower()
|
|
25
|
+
|
|
26
|
+
if any(name in model_lower for name in ["r1", "qwq", "deepseek-r1"]):
|
|
27
|
+
return CapabilityLevel.EXPERT
|
|
28
|
+
elif any(name in model_lower for name in ["qwen"]):
|
|
29
|
+
return CapabilityLevel.INTERMEDIATE
|
|
30
|
+
|
|
31
|
+
return CapabilityLevel.BASIC
|
|
32
|
+
|
|
33
|
+
async def run_full_benchmark(self, model_id: str) -> ModelCapabilityProfile:
|
|
34
|
+
"""Run full capability benchmark."""
|
|
35
|
+
profile = ModelCapabilityProfile()
|
|
36
|
+
|
|
37
|
+
profile.coding = await self.benchmark_coding(model_id)
|
|
38
|
+
profile.reasoning = await self.benchmark_reasoning(model_id)
|
|
39
|
+
|
|
40
|
+
# Infer other capabilities
|
|
41
|
+
if profile.reasoning >= CapabilityLevel.INTERMEDIATE:
|
|
42
|
+
profile.planning = CapabilityLevel.INTERMEDIATE
|
|
43
|
+
|
|
44
|
+
return profile
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Capability classification engine."""
|
|
2
|
+
|
|
3
|
+
from velune.core.types.model import CapabilityLevel, ModelCapabilityProfile, ModelDescriptor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CapabilityClassifier:
|
|
7
|
+
"""Classifies model capabilities using multiple strategies."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.name_patterns = {
|
|
11
|
+
"coding": ["coder", "code", "deepseek-coder", "qwen-coder", "starcoder"],
|
|
12
|
+
"reasoning": ["r1", "reason", "deepseek-r1", "qwq", "qwen"],
|
|
13
|
+
"planning": ["qwq", "qwen", "r1"],
|
|
14
|
+
"summarization": ["llama", "mistral"],
|
|
15
|
+
"instruction_following": ["instruct", "chat"],
|
|
16
|
+
"long_context": ["long", "32k", "128k", "200k"],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def classify(self, model: ModelDescriptor) -> ModelCapabilityProfile:
|
|
20
|
+
"""Classify capabilities for a model."""
|
|
21
|
+
profile = ModelCapabilityProfile()
|
|
22
|
+
model_lower = model.model_id.lower()
|
|
23
|
+
|
|
24
|
+
# Name-based classification
|
|
25
|
+
for capability, patterns in self.name_patterns.items():
|
|
26
|
+
if any(pattern in model_lower for pattern in patterns):
|
|
27
|
+
setattr(profile, capability, self._infer_level(model_lower, capability))
|
|
28
|
+
|
|
29
|
+
# Architecture-based inference
|
|
30
|
+
if "moe" in model_lower or "mixture-of-experts" in model_lower:
|
|
31
|
+
profile.reasoning = max(profile.reasoning, CapabilityLevel.INTERMEDIATE)
|
|
32
|
+
|
|
33
|
+
# GGUF metadata parsing
|
|
34
|
+
if model.metadata.get("gguf_metadata"):
|
|
35
|
+
self._parse_gguf_metadata(model.metadata["gguf_metadata"], profile)
|
|
36
|
+
|
|
37
|
+
return profile
|
|
38
|
+
|
|
39
|
+
def _infer_level(self, model_id: str, capability: str) -> CapabilityLevel:
|
|
40
|
+
"""Infer capability level from model name."""
|
|
41
|
+
model_lower = model_id.lower()
|
|
42
|
+
|
|
43
|
+
# Strong indicators
|
|
44
|
+
if any(indicator in model_lower for indicator in ["v2", "latest", "pro"]):
|
|
45
|
+
return CapabilityLevel.ADVANCED
|
|
46
|
+
|
|
47
|
+
# Capable indicators
|
|
48
|
+
if any(indicator in model_lower for indicator in ["coder", "instruct"]):
|
|
49
|
+
return CapabilityLevel.INTERMEDIATE
|
|
50
|
+
|
|
51
|
+
# Basic indicators
|
|
52
|
+
if capability == "reasoning" and "r1" in model_lower:
|
|
53
|
+
return CapabilityLevel.EXPERT
|
|
54
|
+
|
|
55
|
+
return CapabilityLevel.BASIC
|
|
56
|
+
|
|
57
|
+
def _parse_gguf_metadata(self, metadata: dict, profile: ModelCapabilityProfile) -> None:
|
|
58
|
+
"""Parse GGUF metadata for capability hints."""
|
|
59
|
+
# Check for long context
|
|
60
|
+
context_length = metadata.get("context_length", 0)
|
|
61
|
+
if context_length >= 32000:
|
|
62
|
+
profile.long_context = CapabilityLevel.INTERMEDIATE
|
|
63
|
+
elif context_length >= 8000:
|
|
64
|
+
profile.long_context = CapabilityLevel.BASIC
|
|
65
|
+
|
|
66
|
+
# Check parameter count for capability inference
|
|
67
|
+
param_count = metadata.get("parameter_count", 0)
|
|
68
|
+
if param_count >= 70e9: # 70B+
|
|
69
|
+
profile.reasoning = max(profile.reasoning, CapabilityLevel.INTERMEDIATE)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Fireworks.AI model discovery."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from velune.core.types.model import CapabilityLevel, ModelCapabilityProfile, ModelDescriptor
|
|
6
|
+
from velune.providers.keystore import get_key
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FireworksDiscovery:
|
|
10
|
+
"""Returns the hardcoded Fireworks.AI model list when a key is configured."""
|
|
11
|
+
|
|
12
|
+
provider_id = "fireworks"
|
|
13
|
+
|
|
14
|
+
async def discover(self) -> list[ModelDescriptor]:
|
|
15
|
+
if not get_key("fireworks"):
|
|
16
|
+
return []
|
|
17
|
+
|
|
18
|
+
return [
|
|
19
|
+
ModelDescriptor(
|
|
20
|
+
model_id="accounts/fireworks/models/llama-v3p3-70b-instruct",
|
|
21
|
+
display_name="Llama 3.3 70B Instruct",
|
|
22
|
+
provider_id="fireworks",
|
|
23
|
+
context_length=131072,
|
|
24
|
+
capabilities=ModelCapabilityProfile(
|
|
25
|
+
coding=CapabilityLevel.ADVANCED,
|
|
26
|
+
reasoning=CapabilityLevel.ADVANCED,
|
|
27
|
+
planning=CapabilityLevel.ADVANCED,
|
|
28
|
+
summarization=CapabilityLevel.EXPERT,
|
|
29
|
+
instruction_following=CapabilityLevel.EXPERT,
|
|
30
|
+
tool_use=CapabilityLevel.ADVANCED,
|
|
31
|
+
long_context=CapabilityLevel.ADVANCED,
|
|
32
|
+
),
|
|
33
|
+
speed_tier="fast",
|
|
34
|
+
cost_per_1k_tokens=0.0009,
|
|
35
|
+
tags=["cloud", "fireworks", "llama"],
|
|
36
|
+
metadata={},
|
|
37
|
+
),
|
|
38
|
+
ModelDescriptor(
|
|
39
|
+
model_id="accounts/fireworks/models/deepseek-r1",
|
|
40
|
+
display_name="DeepSeek R1",
|
|
41
|
+
provider_id="fireworks",
|
|
42
|
+
context_length=163840,
|
|
43
|
+
capabilities=ModelCapabilityProfile(
|
|
44
|
+
coding=CapabilityLevel.EXPERT,
|
|
45
|
+
reasoning=CapabilityLevel.EXPERT,
|
|
46
|
+
planning=CapabilityLevel.EXPERT,
|
|
47
|
+
summarization=CapabilityLevel.ADVANCED,
|
|
48
|
+
instruction_following=CapabilityLevel.EXPERT,
|
|
49
|
+
tool_use=CapabilityLevel.ADVANCED,
|
|
50
|
+
long_context=CapabilityLevel.ADVANCED,
|
|
51
|
+
),
|
|
52
|
+
speed_tier="slow",
|
|
53
|
+
cost_per_1k_tokens=0.003,
|
|
54
|
+
tags=["cloud", "fireworks", "deepseek", "reasoning"],
|
|
55
|
+
metadata={},
|
|
56
|
+
),
|
|
57
|
+
ModelDescriptor(
|
|
58
|
+
model_id="accounts/fireworks/models/qwen2p5-coder-32b-instruct",
|
|
59
|
+
display_name="Qwen 2.5 Coder 32B Instruct",
|
|
60
|
+
provider_id="fireworks",
|
|
61
|
+
context_length=131072,
|
|
62
|
+
capabilities=ModelCapabilityProfile(
|
|
63
|
+
coding=CapabilityLevel.EXPERT,
|
|
64
|
+
reasoning=CapabilityLevel.ADVANCED,
|
|
65
|
+
planning=CapabilityLevel.ADVANCED,
|
|
66
|
+
summarization=CapabilityLevel.ADVANCED,
|
|
67
|
+
instruction_following=CapabilityLevel.EXPERT,
|
|
68
|
+
tool_use=CapabilityLevel.ADVANCED,
|
|
69
|
+
long_context=CapabilityLevel.ADVANCED,
|
|
70
|
+
),
|
|
71
|
+
speed_tier="medium",
|
|
72
|
+
cost_per_1k_tokens=0.0009,
|
|
73
|
+
tags=["cloud", "fireworks", "qwen", "coding"],
|
|
74
|
+
metadata={},
|
|
75
|
+
),
|
|
76
|
+
ModelDescriptor(
|
|
77
|
+
model_id="accounts/fireworks/models/mixtral-8x22b-instruct",
|
|
78
|
+
display_name="Mixtral 8x22B Instruct",
|
|
79
|
+
provider_id="fireworks",
|
|
80
|
+
context_length=65536,
|
|
81
|
+
capabilities=ModelCapabilityProfile(
|
|
82
|
+
coding=CapabilityLevel.ADVANCED,
|
|
83
|
+
reasoning=CapabilityLevel.ADVANCED,
|
|
84
|
+
planning=CapabilityLevel.ADVANCED,
|
|
85
|
+
summarization=CapabilityLevel.EXPERT,
|
|
86
|
+
instruction_following=CapabilityLevel.ADVANCED,
|
|
87
|
+
tool_use=CapabilityLevel.ADVANCED,
|
|
88
|
+
long_context=CapabilityLevel.ADVANCED,
|
|
89
|
+
),
|
|
90
|
+
speed_tier="medium",
|
|
91
|
+
cost_per_1k_tokens=0.0009,
|
|
92
|
+
tags=["cloud", "fireworks", "mixtral", "moe"],
|
|
93
|
+
metadata={},
|
|
94
|
+
),
|
|
95
|
+
]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from velune.core.types.model import CapabilityLevel, ModelCapabilityProfile, ModelDescriptor
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("velune.providers.discovery.gguf")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GGUFDiscovery:
|
|
12
|
+
"""Discovers GGUF models from filesystem using LocalModelResolver."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.provider_id = "gguf"
|
|
16
|
+
|
|
17
|
+
async def discover(self) -> list[ModelDescriptor]:
|
|
18
|
+
"""Discover GGUF models across all well-known paths."""
|
|
19
|
+
from velune.providers.local_resolver import LocalModelResolver
|
|
20
|
+
resolver = LocalModelResolver()
|
|
21
|
+
files = resolver.scan_gguf_files()
|
|
22
|
+
|
|
23
|
+
models: list[ModelDescriptor] = []
|
|
24
|
+
for gguf_path in files:
|
|
25
|
+
descriptor = self._build_descriptor(gguf_path, resolver)
|
|
26
|
+
if descriptor is not None:
|
|
27
|
+
models.append(descriptor)
|
|
28
|
+
return models
|
|
29
|
+
|
|
30
|
+
def _build_descriptor(self, gguf_path: Path, resolver) -> ModelDescriptor | None:
|
|
31
|
+
try:
|
|
32
|
+
meta = resolver.get_model_metadata(gguf_path)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
model_id = str(gguf_path.relative_to(Path.home()))
|
|
36
|
+
except ValueError:
|
|
37
|
+
model_id = str(gguf_path)
|
|
38
|
+
|
|
39
|
+
display_name = gguf_path.stem
|
|
40
|
+
param_count_b = meta.get("param_count_b")
|
|
41
|
+
quantization = meta.get("quantization")
|
|
42
|
+
context_length = meta.get("context_length") or 4096
|
|
43
|
+
capabilities = self._classify_capabilities(display_name)
|
|
44
|
+
vram_gb = self._estimate_vram(param_count_b, quantization)
|
|
45
|
+
|
|
46
|
+
return ModelDescriptor(
|
|
47
|
+
model_id=model_id,
|
|
48
|
+
provider_id=self.provider_id,
|
|
49
|
+
display_name=display_name,
|
|
50
|
+
context_length=context_length,
|
|
51
|
+
capabilities=capabilities,
|
|
52
|
+
quantization=quantization,
|
|
53
|
+
vram_required_gb=vram_gb,
|
|
54
|
+
parameter_count_b=param_count_b,
|
|
55
|
+
speed_tier="medium",
|
|
56
|
+
cost_per_1k_tokens=None,
|
|
57
|
+
tags=["local", "gguf"],
|
|
58
|
+
metadata={"gguf_path": str(gguf_path), "family": meta.get("family")},
|
|
59
|
+
)
|
|
60
|
+
except Exception:
|
|
61
|
+
logger.debug("Failed to build descriptor for %s", gguf_path, exc_info=True)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def _estimate_vram(self, param_count_b: float | None, quantization: str | None) -> float | None:
|
|
65
|
+
if not param_count_b:
|
|
66
|
+
return None
|
|
67
|
+
quant_lower = (quantization or "").lower()
|
|
68
|
+
if "q4" in quant_lower:
|
|
69
|
+
bpp = 0.55
|
|
70
|
+
elif "q8" in quant_lower:
|
|
71
|
+
bpp = 1.0
|
|
72
|
+
elif "fp16" in quant_lower or "f16" in quant_lower:
|
|
73
|
+
bpp = 2.0
|
|
74
|
+
else:
|
|
75
|
+
bpp = 0.55
|
|
76
|
+
return param_count_b * bpp + 0.5
|
|
77
|
+
|
|
78
|
+
def _classify_capabilities(self, filename: str) -> ModelCapabilityProfile:
|
|
79
|
+
lower = filename.lower()
|
|
80
|
+
profile = ModelCapabilityProfile()
|
|
81
|
+
if any(kw in lower for kw in ("coder", "code")):
|
|
82
|
+
profile.coding = CapabilityLevel.INTERMEDIATE
|
|
83
|
+
else:
|
|
84
|
+
profile.coding = CapabilityLevel.BASIC
|
|
85
|
+
profile.reasoning = CapabilityLevel.BASIC
|
|
86
|
+
profile.instruction_following = CapabilityLevel.BASIC
|
|
87
|
+
return profile
|