velune-cli 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) 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 +208 -0
  5. velune/cli/autocomplete.py +80 -0
  6. velune/cli/banner.py +60 -0
  7. velune/cli/commands/__init__.py +32 -0
  8. velune/cli/commands/ask.py +175 -0
  9. velune/cli/commands/base.py +16 -0
  10. velune/cli/commands/chat.py +228 -0
  11. velune/cli/commands/config.py +224 -0
  12. velune/cli/commands/daemon.py +88 -0
  13. velune/cli/commands/doctor.py +721 -0
  14. velune/cli/commands/init.py +170 -0
  15. velune/cli/commands/mcp.py +82 -0
  16. velune/cli/commands/memory.py +293 -0
  17. velune/cli/commands/models.py +683 -0
  18. velune/cli/commands/preflight.py +95 -0
  19. velune/cli/commands/run.py +270 -0
  20. velune/cli/commands/setup.py +184 -0
  21. velune/cli/commands/workspace.py +249 -0
  22. velune/cli/context.py +36 -0
  23. velune/cli/councilmodel_ui.py +199 -0
  24. velune/cli/display/council_view.py +254 -0
  25. velune/cli/display/memory_view.py +126 -0
  26. velune/cli/display/panels.py +35 -0
  27. velune/cli/display/progress.py +25 -0
  28. velune/cli/display/themes.py +25 -0
  29. velune/cli/main.py +15 -0
  30. velune/cli/model_selector.py +51 -0
  31. velune/cli/modes.py +86 -0
  32. velune/cli/pull_ui.py +123 -0
  33. velune/cli/registry.py +80 -0
  34. velune/cli/rendering/__init__.py +5 -0
  35. velune/cli/rendering/error_panel.py +79 -0
  36. velune/cli/rendering/markdown.py +63 -0
  37. velune/cli/repl.py +1855 -0
  38. velune/cli/session_manager.py +71 -0
  39. velune/cli/slash_commands.py +37 -0
  40. velune/cli/theme.py +8 -0
  41. velune/cognition/__init__.py +23 -0
  42. velune/cognition/agents/__init__.py +7 -0
  43. velune/cognition/agents/coder.py +209 -0
  44. velune/cognition/agents/planner.py +156 -0
  45. velune/cognition/agents/reviewer.py +195 -0
  46. velune/cognition/arbitrator.py +220 -0
  47. velune/cognition/architecture.py +415 -0
  48. velune/cognition/budget.py +65 -0
  49. velune/cognition/council/__init__.py +47 -0
  50. velune/cognition/council/base.py +217 -0
  51. velune/cognition/council/challenger.py +74 -0
  52. velune/cognition/council/coder.py +79 -0
  53. velune/cognition/council/critic_agent.py +43 -0
  54. velune/cognition/council/critic_configs.py +111 -0
  55. velune/cognition/council/critics.py +41 -0
  56. velune/cognition/council/debate.py +46 -0
  57. velune/cognition/council/factory.py +140 -0
  58. velune/cognition/council/messages.py +56 -0
  59. velune/cognition/council/planner.py +124 -0
  60. velune/cognition/council/reviewer.py +74 -0
  61. velune/cognition/council/synthesizer.py +67 -0
  62. velune/cognition/council/tiers.py +188 -0
  63. velune/cognition/council_orchestrator.py +282 -0
  64. velune/cognition/firewall.py +354 -0
  65. velune/cognition/module.py +46 -0
  66. velune/cognition/orchestrator.py +1205 -0
  67. velune/cognition/personality.py +238 -0
  68. velune/cognition/state.py +104 -0
  69. velune/cognition/style_resolver.py +64 -0
  70. velune/cognition/verification.py +205 -0
  71. velune/context/__init__.py +28 -0
  72. velune/context/assembler.py +240 -0
  73. velune/context/budget.py +97 -0
  74. velune/context/extractive.py +95 -0
  75. velune/context/prompt_adaptation.py +480 -0
  76. velune/context/sections.py +99 -0
  77. velune/context/token_counter.py +134 -0
  78. velune/context/utilization.py +33 -0
  79. velune/context/window.py +63 -0
  80. velune/core/__init__.py +89 -0
  81. velune/core/background.py +5 -0
  82. velune/core/config/__init__.py +37 -0
  83. velune/core/errors/__init__.py +90 -0
  84. velune/core/errors/catalog.py +188 -0
  85. velune/core/errors/execution.py +31 -0
  86. velune/core/errors/memory.py +25 -0
  87. velune/core/errors/orchestration.py +31 -0
  88. velune/core/errors/provider.py +37 -0
  89. velune/core/event_loop.py +35 -0
  90. velune/core/logging.py +83 -0
  91. velune/core/paths.py +165 -0
  92. velune/core/runtime.py +113 -0
  93. velune/core/startup_profiler.py +56 -0
  94. velune/core/task_registry.py +117 -0
  95. velune/core/trace.py +83 -0
  96. velune/core/types/__init__.py +48 -0
  97. velune/core/types/agent.py +53 -0
  98. velune/core/types/context.py +42 -0
  99. velune/core/types/inference.py +38 -0
  100. velune/core/types/memory.py +42 -0
  101. velune/core/types/model.py +70 -0
  102. velune/core/types/provider.py +62 -0
  103. velune/core/types/repository.py +38 -0
  104. velune/core/types/task.py +61 -0
  105. velune/core/types/workspace.py +28 -0
  106. velune/daemon/client.py +13 -0
  107. velune/daemon/server.py +127 -0
  108. velune/daemon/transport.py +179 -0
  109. velune/events.py +204 -0
  110. velune/execution/__init__.py +22 -0
  111. velune/execution/benchmarker.py +315 -0
  112. velune/execution/cancellation.py +53 -0
  113. velune/execution/checkpointer.py +130 -0
  114. velune/execution/command_spec.py +165 -0
  115. velune/execution/diff_preview.py +197 -0
  116. velune/execution/executor.py +181 -0
  117. velune/execution/module.py +18 -0
  118. velune/execution/multi_diff.py +67 -0
  119. velune/execution/path_guard.py +74 -0
  120. velune/execution/planner.py +91 -0
  121. velune/execution/rollback.py +89 -0
  122. velune/execution/sandbox.py +268 -0
  123. velune/execution/validator.py +115 -0
  124. velune/hardware/__init__.py +1 -0
  125. velune/hardware/detector.py +192 -0
  126. velune/kernel/__init__.py +55 -0
  127. velune/kernel/bootstrap.py +125 -0
  128. velune/kernel/config.py +426 -0
  129. velune/kernel/entrypoint.py +78 -0
  130. velune/kernel/health.py +54 -0
  131. velune/kernel/lifecycle.py +143 -0
  132. velune/kernel/module.py +17 -0
  133. velune/kernel/modules.py +23 -0
  134. velune/kernel/registry.py +96 -0
  135. velune/kernel/schemas.py +28 -0
  136. velune/main.py +9 -0
  137. velune/mcp/__init__.py +9 -0
  138. velune/mcp/client.py +115 -0
  139. velune/mcp/config.py +19 -0
  140. velune/mcp/server.py +624 -0
  141. velune/memory/__init__.py +32 -0
  142. velune/memory/compaction.py +506 -0
  143. velune/memory/embedding_pipeline.py +241 -0
  144. velune/memory/lifecycle.py +680 -0
  145. velune/memory/module.py +218 -0
  146. velune/memory/prioritizer.py +67 -0
  147. velune/memory/storage/episodic_schema.sql +53 -0
  148. velune/memory/storage/lancedb_store.py +282 -0
  149. velune/memory/storage/sqlite_manager.py +369 -0
  150. velune/memory/storage/sqlite_pool.py +149 -0
  151. velune/memory/tiers/episodic.py +588 -0
  152. velune/memory/tiers/graph.py +378 -0
  153. velune/memory/tiers/lineage.py +416 -0
  154. velune/memory/tiers/semantic.py +475 -0
  155. velune/memory/tiers/working.py +168 -0
  156. velune/memory/vitality.py +132 -0
  157. velune/models/__init__.py +15 -0
  158. velune/models/family.py +76 -0
  159. velune/models/module.py +20 -0
  160. velune/models/probes.py +192 -0
  161. velune/models/profile_cache.py +84 -0
  162. velune/models/profiler.py +108 -0
  163. velune/models/registry.py +251 -0
  164. velune/models/scorer.py +233 -0
  165. velune/models/specializations.py +205 -0
  166. velune/orchestration/__init__.py +19 -0
  167. velune/orchestration/engine.py +239 -0
  168. velune/orchestration/module.py +15 -0
  169. velune/orchestration/role_assignments.py +82 -0
  170. velune/orchestration/schemas.py +98 -0
  171. velune/plugins/__init__.py +20 -0
  172. velune/plugins/hooks.py +50 -0
  173. velune/plugins/loader.py +161 -0
  174. velune/plugins/registry.py +56 -0
  175. velune/plugins/schemas.py +21 -0
  176. velune/providers/__init__.py +23 -0
  177. velune/providers/adapters/anthropic.py +257 -0
  178. velune/providers/adapters/fireworks.py +115 -0
  179. velune/providers/adapters/google.py +234 -0
  180. velune/providers/adapters/groq.py +151 -0
  181. velune/providers/adapters/huggingface.py +210 -0
  182. velune/providers/adapters/llamacpp.py +208 -0
  183. velune/providers/adapters/lmstudio.py +175 -0
  184. velune/providers/adapters/ollama.py +233 -0
  185. velune/providers/adapters/openai.py +213 -0
  186. velune/providers/adapters/openrouter.py +81 -0
  187. velune/providers/adapters/together.py +134 -0
  188. velune/providers/adapters/xai.py +60 -0
  189. velune/providers/base.py +86 -0
  190. velune/providers/benchmarker.py +138 -0
  191. velune/providers/discovery/__init__.py +33 -0
  192. velune/providers/discovery/anthropic.py +79 -0
  193. velune/providers/discovery/benchmarks.py +44 -0
  194. velune/providers/discovery/classifier.py +69 -0
  195. velune/providers/discovery/fireworks.py +95 -0
  196. velune/providers/discovery/gguf.py +88 -0
  197. velune/providers/discovery/google.py +95 -0
  198. velune/providers/discovery/gpu.py +117 -0
  199. velune/providers/discovery/groq.py +21 -0
  200. velune/providers/discovery/huggingface.py +67 -0
  201. velune/providers/discovery/lmstudio.py +80 -0
  202. velune/providers/discovery/ollama.py +162 -0
  203. velune/providers/discovery/openai.py +96 -0
  204. velune/providers/discovery/openrouter.py +113 -0
  205. velune/providers/discovery/scanner.py +115 -0
  206. velune/providers/discovery/together.py +114 -0
  207. velune/providers/discovery/xai.py +57 -0
  208. velune/providers/health.py +67 -0
  209. velune/providers/health_monitor.py +169 -0
  210. velune/providers/keystore.py +142 -0
  211. velune/providers/local_paths.py +49 -0
  212. velune/providers/local_resolver.py +229 -0
  213. velune/providers/module.py +51 -0
  214. velune/providers/ollama_manager.py +193 -0
  215. velune/providers/registry.py +220 -0
  216. velune/providers/router.py +255 -0
  217. velune/providers/task_classifier.py +288 -0
  218. velune/py.typed +0 -0
  219. velune/repository/__init__.py +33 -0
  220. velune/repository/analyzer.py +127 -0
  221. velune/repository/ast_parser.py +822 -0
  222. velune/repository/blast_radius.py +298 -0
  223. velune/repository/boundary_classifier.py +295 -0
  224. velune/repository/cognition.py +316 -0
  225. velune/repository/grapher.py +179 -0
  226. velune/repository/import_graph.py +263 -0
  227. velune/repository/incremental_indexer.py +275 -0
  228. velune/repository/index_state.py +96 -0
  229. velune/repository/indexer.py +243 -0
  230. velune/repository/module.py +17 -0
  231. velune/repository/parser.py +474 -0
  232. velune/repository/project_type.py +300 -0
  233. velune/repository/rename_journal.py +287 -0
  234. velune/repository/scanner.py +193 -0
  235. velune/repository/schemas.py +102 -0
  236. velune/repository/symbol_registry.py +365 -0
  237. velune/repository/tracker.py +252 -0
  238. velune/retrieval/__init__.py +27 -0
  239. velune/retrieval/cache.py +110 -0
  240. velune/retrieval/fast_path.py +391 -0
  241. velune/retrieval/graph.py +124 -0
  242. velune/retrieval/hybrid.py +271 -0
  243. velune/retrieval/keyword.py +131 -0
  244. velune/retrieval/module.py +26 -0
  245. velune/retrieval/pipeline.py +303 -0
  246. velune/retrieval/reranker.py +102 -0
  247. velune/retrieval/schemas.py +59 -0
  248. velune/retrieval/slow_path.py +364 -0
  249. velune/retrieval/vector.py +203 -0
  250. velune/telemetry/__init__.py +59 -0
  251. velune/telemetry/cognition.py +267 -0
  252. velune/telemetry/cost_estimator.py +92 -0
  253. velune/telemetry/debug.py +304 -0
  254. velune/telemetry/doctor.py +244 -0
  255. velune/telemetry/logging.py +286 -0
  256. velune/telemetry/spans.py +277 -0
  257. velune/telemetry/token_tracker.py +140 -0
  258. velune/telemetry/usage_tracker.py +340 -0
  259. velune/tools/__init__.py +41 -0
  260. velune/tools/base/registry.py +87 -0
  261. velune/tools/base/tool.py +63 -0
  262. velune/tools/code/navigate.py +116 -0
  263. velune/tools/code/search.py +123 -0
  264. velune/tools/filesystem/read.py +75 -0
  265. velune/tools/filesystem/search.py +136 -0
  266. velune/tools/filesystem/write.py +163 -0
  267. velune/tools/git/history.py +177 -0
  268. velune/tools/git/operations.py +122 -0
  269. velune/tools/git/state.py +121 -0
  270. velune/tools/module.py +81 -0
  271. velune/tools/terminal/execute.py +72 -0
  272. velune/tools/terminal/history.py +47 -0
  273. velune/tools/web/fetch.py +55 -0
  274. velune/tools/web/validator.py +122 -0
  275. velune_cli-0.9.0.dist-info/METADATA +518 -0
  276. velune_cli-0.9.0.dist-info/RECORD +279 -0
  277. velune_cli-0.9.0.dist-info/WHEEL +4 -0
  278. velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
  279. velune_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,138 @@
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(
31
+ self, prompt: str = "Hello, respond with exactly 'pong'."
32
+ ) -> ModelBenchmarkMetrics:
33
+ """Probe time-to-first-token and total generation latency."""
34
+ metrics = ModelBenchmarkMetrics()
35
+ request = InferenceRequest(
36
+ model_id=self.model_id,
37
+ messages=[{"role": "user", "content": prompt}],
38
+ temperature=0.0,
39
+ max_tokens=10,
40
+ )
41
+
42
+ start_time = time.perf_counter()
43
+ first_token_time: float | None = None
44
+ chars_received = 0
45
+
46
+ try:
47
+ # We use streaming to calculate TTFT and TPS
48
+ async for chunk in self.provider.stream(request):
49
+ if first_token_time is None and chunk.content:
50
+ first_token_time = time.perf_counter()
51
+ metrics.ttft_ms = (first_token_time - start_time) * 1000.0
52
+
53
+ chars_received += len(chunk.content)
54
+ # Estimating tokens: 1 token ≈ 4 characters
55
+ metrics.tokens_generated = int(chars_received / 4.0)
56
+
57
+ end_time = time.perf_counter()
58
+ metrics.total_latency_ms = (end_time - start_time) * 1000.0
59
+
60
+ if first_token_time is not None:
61
+ gen_duration = end_time - first_token_time
62
+ if gen_duration > 0 and metrics.tokens_generated > 0:
63
+ metrics.tps = metrics.tokens_generated / gen_duration
64
+ else:
65
+ metrics.ttft_ms = metrics.total_latency_ms
66
+ metrics.tps = 0.0
67
+
68
+ except Exception:
69
+ # Handle non-streaming fallback
70
+ try:
71
+ start_time = time.perf_counter()
72
+ response = await self.provider.infer(request)
73
+ end_time = time.perf_counter()
74
+
75
+ metrics.total_latency_ms = (end_time - start_time) * 1000.0
76
+ metrics.ttft_ms = metrics.total_latency_ms # No stream TTFT estimation
77
+ metrics.tokens_generated = response.tokens_used or int(len(response.content) / 4.0)
78
+
79
+ duration = end_time - start_time
80
+ if duration > 0:
81
+ metrics.tps = metrics.tokens_generated / duration
82
+ except Exception:
83
+ # Failed probe
84
+ metrics.ttft_ms = -1.0
85
+ metrics.tps = 0.0
86
+
87
+ return metrics
88
+
89
+ async def run_structured_probe(self) -> dict[str, float]:
90
+ """Probe model capabilities in generating valid JSON format."""
91
+ prompt = (
92
+ "Return a JSON object representing a file list with keys 'files' (list of strings) and 'count' (integer). "
93
+ "Return ONLY raw valid JSON code. No markdown wrapper, no extra text."
94
+ )
95
+ request = InferenceRequest(
96
+ model_id=self.model_id,
97
+ messages=[{"role": "user", "content": prompt}],
98
+ temperature=0.0,
99
+ max_tokens=100,
100
+ )
101
+
102
+ json_valid = 0.0
103
+ structure_valid = 0.0
104
+
105
+ try:
106
+ response = await self.provider.infer(request)
107
+ content = response.content.strip()
108
+
109
+ # Clean possible markdown wrappers
110
+ if content.startswith("```"):
111
+ lines = content.splitlines()
112
+ if len(lines) > 2:
113
+ content = "\n".join(lines[1:-1])
114
+ if content.startswith("json"):
115
+ content = content[4:].strip()
116
+
117
+ import json
118
+
119
+ parsed = json.loads(content)
120
+ json_valid = 1.0
121
+
122
+ if isinstance(parsed, dict) and "files" in parsed and "count" in parsed:
123
+ if isinstance(parsed["files"], list) and isinstance(parsed["count"], int):
124
+ structure_valid = 1.0
125
+ except Exception:
126
+ pass
127
+
128
+ return {"json_validity": json_valid, "structure_validity": structure_valid}
129
+
130
+ async def evaluate(self) -> ModelBenchmarkMetrics:
131
+ """Run all benchmark probes and aggregate metrics."""
132
+ metrics = await self.run_latency_probe()
133
+ struct_res = await self.run_structured_probe()
134
+
135
+ metrics.json_validity = struct_res["json_validity"]
136
+ metrics.tool_accuracy = struct_res["structure_validity"]
137
+
138
+ 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,79 @@
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(
30
+ self, model_id: str, context_length: int, cost_per_1k: float
31
+ ) -> ModelDescriptor:
32
+ """Create a model descriptor."""
33
+ capabilities = self._classify_capabilities(model_id)
34
+
35
+ return ModelDescriptor(
36
+ model_id=model_id,
37
+ provider_id=self.provider_id,
38
+ display_name=model_id,
39
+ context_length=context_length,
40
+ capabilities=capabilities,
41
+ quantization=None,
42
+ vram_required_gb=None,
43
+ parameter_count_b=None,
44
+ speed_tier="medium",
45
+ cost_per_1k_tokens=cost_per_1k,
46
+ tags=["cloud", "anthropic"],
47
+ metadata={},
48
+ )
49
+
50
+ def _classify_capabilities(self, model_id: str) -> ModelCapabilityProfile:
51
+ """Classify capabilities for Anthropic models."""
52
+ profile = ModelCapabilityProfile()
53
+
54
+ if "opus" in model_id:
55
+ profile.coding = CapabilityLevel.EXPERT
56
+ profile.reasoning = CapabilityLevel.EXPERT
57
+ profile.planning = CapabilityLevel.EXPERT
58
+ profile.summarization = CapabilityLevel.EXPERT
59
+ profile.instruction_following = CapabilityLevel.EXPERT
60
+ profile.tool_use = CapabilityLevel.EXPERT
61
+ profile.long_context = CapabilityLevel.EXPERT
62
+ elif "sonnet" in model_id:
63
+ profile.coding = CapabilityLevel.ADVANCED
64
+ profile.reasoning = CapabilityLevel.ADVANCED
65
+ profile.planning = CapabilityLevel.ADVANCED
66
+ profile.summarization = CapabilityLevel.ADVANCED
67
+ profile.instruction_following = CapabilityLevel.ADVANCED
68
+ profile.tool_use = CapabilityLevel.EXPERT
69
+ profile.long_context = CapabilityLevel.ADVANCED
70
+ elif "haiku" in model_id:
71
+ profile.coding = CapabilityLevel.INTERMEDIATE
72
+ profile.reasoning = CapabilityLevel.INTERMEDIATE
73
+ profile.planning = CapabilityLevel.INTERMEDIATE
74
+ profile.summarization = CapabilityLevel.INTERMEDIATE
75
+ profile.instruction_following = CapabilityLevel.ADVANCED
76
+ profile.tool_use = CapabilityLevel.ADVANCED
77
+ profile.long_context = CapabilityLevel.INTERMEDIATE
78
+
79
+ 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,88 @@
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
+
21
+ resolver = LocalModelResolver()
22
+ files = resolver.scan_gguf_files()
23
+
24
+ models: list[ModelDescriptor] = []
25
+ for gguf_path in files:
26
+ descriptor = self._build_descriptor(gguf_path, resolver)
27
+ if descriptor is not None:
28
+ models.append(descriptor)
29
+ return models
30
+
31
+ def _build_descriptor(self, gguf_path: Path, resolver) -> ModelDescriptor | None:
32
+ try:
33
+ meta = resolver.get_model_metadata(gguf_path)
34
+
35
+ try:
36
+ model_id = str(gguf_path.relative_to(Path.home()))
37
+ except ValueError:
38
+ model_id = str(gguf_path)
39
+
40
+ display_name = gguf_path.stem
41
+ param_count_b = meta.get("param_count_b")
42
+ quantization = meta.get("quantization")
43
+ context_length = meta.get("context_length") or 4096
44
+ capabilities = self._classify_capabilities(display_name)
45
+ vram_gb = self._estimate_vram(param_count_b, quantization)
46
+
47
+ return ModelDescriptor(
48
+ model_id=model_id,
49
+ provider_id=self.provider_id,
50
+ display_name=display_name,
51
+ context_length=context_length,
52
+ capabilities=capabilities,
53
+ quantization=quantization,
54
+ vram_required_gb=vram_gb,
55
+ parameter_count_b=param_count_b,
56
+ speed_tier="medium",
57
+ cost_per_1k_tokens=None,
58
+ tags=["local", "gguf"],
59
+ metadata={"gguf_path": str(gguf_path), "family": meta.get("family")},
60
+ )
61
+ except Exception:
62
+ logger.debug("Failed to build descriptor for %s", gguf_path, exc_info=True)
63
+ return None
64
+
65
+ def _estimate_vram(self, param_count_b: float | None, quantization: str | None) -> float | None:
66
+ if not param_count_b:
67
+ return None
68
+ quant_lower = (quantization or "").lower()
69
+ if "q4" in quant_lower:
70
+ bpp = 0.55
71
+ elif "q8" in quant_lower:
72
+ bpp = 1.0
73
+ elif "fp16" in quant_lower or "f16" in quant_lower:
74
+ bpp = 2.0
75
+ else:
76
+ bpp = 0.55
77
+ return param_count_b * bpp + 0.5
78
+
79
+ def _classify_capabilities(self, filename: str) -> ModelCapabilityProfile:
80
+ lower = filename.lower()
81
+ profile = ModelCapabilityProfile()
82
+ if any(kw in lower for kw in ("coder", "code")):
83
+ profile.coding = CapabilityLevel.INTERMEDIATE
84
+ else:
85
+ profile.coding = CapabilityLevel.BASIC
86
+ profile.reasoning = CapabilityLevel.BASIC
87
+ profile.instruction_following = CapabilityLevel.BASIC
88
+ return profile
@@ -0,0 +1,95 @@
1
+ """Google Gemini 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 GoogleDiscovery:
10
+ """Returns the hardcoded Gemini model list when a key is configured."""
11
+
12
+ provider_id = "google"
13
+
14
+ async def discover(self) -> list[ModelDescriptor]:
15
+ if not get_key("google"):
16
+ return []
17
+
18
+ return [
19
+ ModelDescriptor(
20
+ model_id="gemini-2.0-flash",
21
+ provider_id="google",
22
+ display_name="Gemini 2.0 Flash",
23
+ context_length=1048576,
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.EXPERT,
32
+ ),
33
+ speed_tier="fast",
34
+ cost_per_1k_tokens=0.000075,
35
+ tags=["cloud", "google", "flash", "free"],
36
+ metadata={},
37
+ ),
38
+ ModelDescriptor(
39
+ model_id="gemini-1.5-pro",
40
+ provider_id="google",
41
+ display_name="Gemini 1.5 Pro",
42
+ context_length=2097152,
43
+ capabilities=ModelCapabilityProfile(
44
+ coding=CapabilityLevel.EXPERT,
45
+ reasoning=CapabilityLevel.EXPERT,
46
+ planning=CapabilityLevel.EXPERT,
47
+ summarization=CapabilityLevel.EXPERT,
48
+ instruction_following=CapabilityLevel.EXPERT,
49
+ tool_use=CapabilityLevel.EXPERT,
50
+ long_context=CapabilityLevel.EXPERT,
51
+ ),
52
+ speed_tier="medium",
53
+ cost_per_1k_tokens=0.00125,
54
+ tags=["cloud", "google", "pro"],
55
+ metadata={},
56
+ ),
57
+ ModelDescriptor(
58
+ model_id="gemini-1.5-flash",
59
+ provider_id="google",
60
+ display_name="Gemini 1.5 Flash",
61
+ context_length=1048576,
62
+ capabilities=ModelCapabilityProfile(
63
+ coding=CapabilityLevel.ADVANCED,
64
+ reasoning=CapabilityLevel.ADVANCED,
65
+ planning=CapabilityLevel.INTERMEDIATE,
66
+ summarization=CapabilityLevel.ADVANCED,
67
+ instruction_following=CapabilityLevel.EXPERT,
68
+ tool_use=CapabilityLevel.ADVANCED,
69
+ long_context=CapabilityLevel.EXPERT,
70
+ ),
71
+ speed_tier="fast",
72
+ cost_per_1k_tokens=0.000075,
73
+ tags=["cloud", "google", "flash", "free"],
74
+ metadata={},
75
+ ),
76
+ ModelDescriptor(
77
+ model_id="gemini-2.0-flash-thinking-exp",
78
+ provider_id="google",
79
+ display_name="Gemini 2.0 Flash Thinking",
80
+ context_length=32767,
81
+ capabilities=ModelCapabilityProfile(
82
+ coding=CapabilityLevel.EXPERT,
83
+ reasoning=CapabilityLevel.EXPERT,
84
+ planning=CapabilityLevel.EXPERT,
85
+ summarization=CapabilityLevel.ADVANCED,
86
+ instruction_following=CapabilityLevel.EXPERT,
87
+ tool_use=CapabilityLevel.ADVANCED,
88
+ long_context=CapabilityLevel.INTERMEDIATE,
89
+ ),
90
+ speed_tier="medium",
91
+ cost_per_1k_tokens=0.0,
92
+ tags=["cloud", "google", "thinking", "free"],
93
+ metadata={},
94
+ ),
95
+ ]