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.
Files changed (229) hide show
  1. velune/__init__.py +5 -0
  2. velune/__main__.py +6 -0
  3. velune/cli/__init__.py +5 -0
  4. velune/cli/app.py +212 -0
  5. velune/cli/autocomplete.py +76 -0
  6. velune/cli/banner.py +98 -0
  7. velune/cli/commands/__init__.py +32 -0
  8. velune/cli/commands/ask.py +149 -0
  9. velune/cli/commands/base.py +16 -0
  10. velune/cli/commands/chat.py +188 -0
  11. velune/cli/commands/config.py +182 -0
  12. velune/cli/commands/daemon.py +85 -0
  13. velune/cli/commands/doctor.py +373 -0
  14. velune/cli/commands/init.py +160 -0
  15. velune/cli/commands/mcp.py +80 -0
  16. velune/cli/commands/memory.py +269 -0
  17. velune/cli/commands/models.py +462 -0
  18. velune/cli/commands/preflight.py +95 -0
  19. velune/cli/commands/run.py +171 -0
  20. velune/cli/commands/setup.py +182 -0
  21. velune/cli/commands/workspace.py +217 -0
  22. velune/cli/context.py +37 -0
  23. velune/cli/councilmodel_ui.py +171 -0
  24. velune/cli/display/council_view.py +240 -0
  25. velune/cli/display/memory_view.py +93 -0
  26. velune/cli/display/panels.py +35 -0
  27. velune/cli/display/progress.py +25 -0
  28. velune/cli/display/themes.py +21 -0
  29. velune/cli/main.py +15 -0
  30. velune/cli/model_selector.py +44 -0
  31. velune/cli/modes.py +86 -0
  32. velune/cli/pull_ui.py +118 -0
  33. velune/cli/registry.py +81 -0
  34. velune/cli/repl.py +1178 -0
  35. velune/cli/session_manager.py +69 -0
  36. velune/cli/slash_commands.py +37 -0
  37. velune/cognition/__init__.py +19 -0
  38. velune/cognition/arbitrator.py +216 -0
  39. velune/cognition/architecture.py +398 -0
  40. velune/cognition/council/__init__.py +47 -0
  41. velune/cognition/council/base.py +216 -0
  42. velune/cognition/council/challenger.py +70 -0
  43. velune/cognition/council/coder.py +79 -0
  44. velune/cognition/council/critic_agent.py +39 -0
  45. velune/cognition/council/critic_configs.py +111 -0
  46. velune/cognition/council/critics.py +41 -0
  47. velune/cognition/council/debate.py +44 -0
  48. velune/cognition/council/factory.py +140 -0
  49. velune/cognition/council/messages.py +53 -0
  50. velune/cognition/council/planner.py +119 -0
  51. velune/cognition/council/reviewer.py +72 -0
  52. velune/cognition/council/synthesizer.py +67 -0
  53. velune/cognition/council/tiers.py +181 -0
  54. velune/cognition/firewall.py +256 -0
  55. velune/cognition/module.py +38 -0
  56. velune/cognition/orchestrator.py +886 -0
  57. velune/cognition/personality.py +236 -0
  58. velune/cognition/style_resolver.py +62 -0
  59. velune/cognition/verification.py +201 -0
  60. velune/context/__init__.py +11 -0
  61. velune/context/extractive.py +94 -0
  62. velune/context/window.py +62 -0
  63. velune/core/__init__.py +89 -0
  64. velune/core/background.py +5 -0
  65. velune/core/config/__init__.py +37 -0
  66. velune/core/errors/__init__.py +53 -0
  67. velune/core/errors/execution.py +26 -0
  68. velune/core/errors/memory.py +21 -0
  69. velune/core/errors/orchestration.py +26 -0
  70. velune/core/errors/provider.py +31 -0
  71. velune/core/event_loop.py +30 -0
  72. velune/core/logging.py +83 -0
  73. velune/core/runtime.py +106 -0
  74. velune/core/task_registry.py +120 -0
  75. velune/core/trace.py +80 -0
  76. velune/core/types/__init__.py +48 -0
  77. velune/core/types/agent.py +49 -0
  78. velune/core/types/context.py +39 -0
  79. velune/core/types/inference.py +35 -0
  80. velune/core/types/memory.py +39 -0
  81. velune/core/types/model.py +64 -0
  82. velune/core/types/provider.py +35 -0
  83. velune/core/types/repository.py +35 -0
  84. velune/core/types/task.py +56 -0
  85. velune/core/types/workspace.py +28 -0
  86. velune/daemon/client.py +13 -0
  87. velune/daemon/server.py +115 -0
  88. velune/daemon/transport.py +169 -0
  89. velune/events.py +194 -0
  90. velune/execution/__init__.py +22 -0
  91. velune/execution/benchmarker.py +311 -0
  92. velune/execution/cancellation.py +53 -0
  93. velune/execution/checkpointer.py +128 -0
  94. velune/execution/command_spec.py +140 -0
  95. velune/execution/diff_preview.py +172 -0
  96. velune/execution/executor.py +173 -0
  97. velune/execution/module.py +16 -0
  98. velune/execution/multi_diff.py +70 -0
  99. velune/execution/path_guard.py +19 -0
  100. velune/execution/planner.py +91 -0
  101. velune/execution/rollback.py +80 -0
  102. velune/execution/sandbox.py +257 -0
  103. velune/execution/validator.py +113 -0
  104. velune/hardware/__init__.py +1 -0
  105. velune/hardware/detector.py +162 -0
  106. velune/kernel/__init__.py +58 -0
  107. velune/kernel/bootstrap.py +107 -0
  108. velune/kernel/config.py +252 -0
  109. velune/kernel/health.py +54 -0
  110. velune/kernel/lifecycle.py +102 -0
  111. velune/kernel/module.py +15 -0
  112. velune/kernel/modules.py +23 -0
  113. velune/kernel/registry.py +93 -0
  114. velune/kernel/schemas.py +28 -0
  115. velune/main.py +9 -0
  116. velune/mcp/__init__.py +9 -0
  117. velune/mcp/client.py +113 -0
  118. velune/mcp/config.py +19 -0
  119. velune/mcp/server.py +90 -0
  120. velune/memory/__init__.py +28 -0
  121. velune/memory/lifecycle.py +154 -0
  122. velune/memory/module.py +94 -0
  123. velune/memory/prioritizer.py +65 -0
  124. velune/memory/storage/sqlite_manager.py +368 -0
  125. velune/memory/tiers/episodic.py +156 -0
  126. velune/memory/tiers/graph.py +282 -0
  127. velune/memory/tiers/lineage.py +367 -0
  128. velune/memory/tiers/semantic.py +198 -0
  129. velune/memory/tiers/working.py +165 -0
  130. velune/models/__init__.py +16 -0
  131. velune/models/module.py +18 -0
  132. velune/models/probes.py +182 -0
  133. velune/models/profile_cache.py +82 -0
  134. velune/models/profiler.py +105 -0
  135. velune/models/registry.py +201 -0
  136. velune/models/scorer.py +225 -0
  137. velune/models/specializations.py +196 -0
  138. velune/orchestration/__init__.py +15 -0
  139. velune/orchestration/module.py +14 -0
  140. velune/orchestration/role_assignments.py +78 -0
  141. velune/orchestration/schemas.py +99 -0
  142. velune/plugins/__init__.py +13 -0
  143. velune/plugins/hooks.py +49 -0
  144. velune/plugins/loader.py +95 -0
  145. velune/plugins/registry.py +54 -0
  146. velune/plugins/schemas.py +19 -0
  147. velune/providers/__init__.py +18 -0
  148. velune/providers/adapters/anthropic.py +231 -0
  149. velune/providers/adapters/fireworks.py +115 -0
  150. velune/providers/adapters/google.py +234 -0
  151. velune/providers/adapters/groq.py +151 -0
  152. velune/providers/adapters/huggingface.py +203 -0
  153. velune/providers/adapters/llamacpp.py +202 -0
  154. velune/providers/adapters/lmstudio.py +173 -0
  155. velune/providers/adapters/ollama.py +186 -0
  156. velune/providers/adapters/openai.py +207 -0
  157. velune/providers/adapters/openrouter.py +81 -0
  158. velune/providers/adapters/together.py +134 -0
  159. velune/providers/adapters/xai.py +60 -0
  160. velune/providers/base.py +86 -0
  161. velune/providers/benchmarker.py +135 -0
  162. velune/providers/discovery/__init__.py +33 -0
  163. velune/providers/discovery/anthropic.py +77 -0
  164. velune/providers/discovery/benchmarks.py +44 -0
  165. velune/providers/discovery/classifier.py +69 -0
  166. velune/providers/discovery/fireworks.py +95 -0
  167. velune/providers/discovery/gguf.py +87 -0
  168. velune/providers/discovery/google.py +95 -0
  169. velune/providers/discovery/gpu.py +109 -0
  170. velune/providers/discovery/groq.py +20 -0
  171. velune/providers/discovery/huggingface.py +67 -0
  172. velune/providers/discovery/lmstudio.py +80 -0
  173. velune/providers/discovery/ollama.py +165 -0
  174. velune/providers/discovery/openai.py +96 -0
  175. velune/providers/discovery/openrouter.py +113 -0
  176. velune/providers/discovery/scanner.py +114 -0
  177. velune/providers/discovery/together.py +114 -0
  178. velune/providers/discovery/xai.py +57 -0
  179. velune/providers/keystore.py +83 -0
  180. velune/providers/local_paths.py +49 -0
  181. velune/providers/local_resolver.py +208 -0
  182. velune/providers/module.py +15 -0
  183. velune/providers/ollama_manager.py +193 -0
  184. velune/providers/registry.py +173 -0
  185. velune/providers/router.py +82 -0
  186. velune/py.typed +0 -0
  187. velune/repository/__init__.py +21 -0
  188. velune/repository/analyzer.py +123 -0
  189. velune/repository/cognition.py +172 -0
  190. velune/repository/grapher.py +182 -0
  191. velune/repository/indexer.py +229 -0
  192. velune/repository/module.py +15 -0
  193. velune/repository/parser.py +378 -0
  194. velune/repository/project_type.py +293 -0
  195. velune/repository/scanner.py +177 -0
  196. velune/repository/schemas.py +102 -0
  197. velune/repository/tracker.py +233 -0
  198. velune/retrieval/__init__.py +27 -0
  199. velune/retrieval/graph.py +117 -0
  200. velune/retrieval/hybrid.py +250 -0
  201. velune/retrieval/keyword.py +113 -0
  202. velune/retrieval/module.py +19 -0
  203. velune/retrieval/reranker.py +68 -0
  204. velune/retrieval/schemas.py +59 -0
  205. velune/retrieval/vector.py +163 -0
  206. velune/telemetry/__init__.py +7 -0
  207. velune/telemetry/cognition.py +260 -0
  208. velune/telemetry/token_tracker.py +133 -0
  209. velune/tools/__init__.py +41 -0
  210. velune/tools/base/registry.py +84 -0
  211. velune/tools/base/tool.py +63 -0
  212. velune/tools/code/navigate.py +107 -0
  213. velune/tools/code/search.py +112 -0
  214. velune/tools/filesystem/read.py +75 -0
  215. velune/tools/filesystem/search.py +123 -0
  216. velune/tools/filesystem/write.py +160 -0
  217. velune/tools/git/history.py +185 -0
  218. velune/tools/git/operations.py +132 -0
  219. velune/tools/git/state.py +134 -0
  220. velune/tools/module.py +65 -0
  221. velune/tools/terminal/execute.py +72 -0
  222. velune/tools/terminal/history.py +47 -0
  223. velune/tools/web/fetch.py +55 -0
  224. velune/tools/web/validator.py +96 -0
  225. velune_cli-1.0.0.dist-info/METADATA +497 -0
  226. velune_cli-1.0.0.dist-info/RECORD +229 -0
  227. velune_cli-1.0.0.dist-info/WHEEL +4 -0
  228. velune_cli-1.0.0.dist-info/entry_points.txt +2 -0
  229. velune_cli-1.0.0.dist-info/licenses/LICENSE +201 -0
@@ -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