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.
- velune/__init__.py +5 -0
- velune/__main__.py +6 -0
- velune/cli/__init__.py +5 -0
- velune/cli/app.py +208 -0
- velune/cli/autocomplete.py +80 -0
- velune/cli/banner.py +60 -0
- velune/cli/commands/__init__.py +32 -0
- velune/cli/commands/ask.py +175 -0
- velune/cli/commands/base.py +16 -0
- velune/cli/commands/chat.py +228 -0
- velune/cli/commands/config.py +224 -0
- velune/cli/commands/daemon.py +88 -0
- velune/cli/commands/doctor.py +721 -0
- velune/cli/commands/init.py +170 -0
- velune/cli/commands/mcp.py +82 -0
- velune/cli/commands/memory.py +293 -0
- velune/cli/commands/models.py +683 -0
- velune/cli/commands/preflight.py +95 -0
- velune/cli/commands/run.py +270 -0
- velune/cli/commands/setup.py +184 -0
- velune/cli/commands/workspace.py +249 -0
- velune/cli/context.py +36 -0
- velune/cli/councilmodel_ui.py +199 -0
- velune/cli/display/council_view.py +254 -0
- velune/cli/display/memory_view.py +126 -0
- velune/cli/display/panels.py +35 -0
- velune/cli/display/progress.py +25 -0
- velune/cli/display/themes.py +25 -0
- velune/cli/main.py +15 -0
- velune/cli/model_selector.py +51 -0
- velune/cli/modes.py +86 -0
- velune/cli/pull_ui.py +123 -0
- velune/cli/registry.py +80 -0
- velune/cli/rendering/__init__.py +5 -0
- velune/cli/rendering/error_panel.py +79 -0
- velune/cli/rendering/markdown.py +63 -0
- velune/cli/repl.py +1855 -0
- velune/cli/session_manager.py +71 -0
- velune/cli/slash_commands.py +37 -0
- velune/cli/theme.py +8 -0
- velune/cognition/__init__.py +23 -0
- velune/cognition/agents/__init__.py +7 -0
- velune/cognition/agents/coder.py +209 -0
- velune/cognition/agents/planner.py +156 -0
- velune/cognition/agents/reviewer.py +195 -0
- velune/cognition/arbitrator.py +220 -0
- velune/cognition/architecture.py +415 -0
- velune/cognition/budget.py +65 -0
- velune/cognition/council/__init__.py +47 -0
- velune/cognition/council/base.py +217 -0
- velune/cognition/council/challenger.py +74 -0
- velune/cognition/council/coder.py +79 -0
- velune/cognition/council/critic_agent.py +43 -0
- velune/cognition/council/critic_configs.py +111 -0
- velune/cognition/council/critics.py +41 -0
- velune/cognition/council/debate.py +46 -0
- velune/cognition/council/factory.py +140 -0
- velune/cognition/council/messages.py +56 -0
- velune/cognition/council/planner.py +124 -0
- velune/cognition/council/reviewer.py +74 -0
- velune/cognition/council/synthesizer.py +67 -0
- velune/cognition/council/tiers.py +188 -0
- velune/cognition/council_orchestrator.py +282 -0
- velune/cognition/firewall.py +354 -0
- velune/cognition/module.py +46 -0
- velune/cognition/orchestrator.py +1205 -0
- velune/cognition/personality.py +238 -0
- velune/cognition/state.py +104 -0
- velune/cognition/style_resolver.py +64 -0
- velune/cognition/verification.py +205 -0
- velune/context/__init__.py +28 -0
- velune/context/assembler.py +240 -0
- velune/context/budget.py +97 -0
- velune/context/extractive.py +95 -0
- velune/context/prompt_adaptation.py +480 -0
- velune/context/sections.py +99 -0
- velune/context/token_counter.py +134 -0
- velune/context/utilization.py +33 -0
- velune/context/window.py +63 -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 +90 -0
- velune/core/errors/catalog.py +188 -0
- velune/core/errors/execution.py +31 -0
- velune/core/errors/memory.py +25 -0
- velune/core/errors/orchestration.py +31 -0
- velune/core/errors/provider.py +37 -0
- velune/core/event_loop.py +35 -0
- velune/core/logging.py +83 -0
- velune/core/paths.py +165 -0
- velune/core/runtime.py +113 -0
- velune/core/startup_profiler.py +56 -0
- velune/core/task_registry.py +117 -0
- velune/core/trace.py +83 -0
- velune/core/types/__init__.py +48 -0
- velune/core/types/agent.py +53 -0
- velune/core/types/context.py +42 -0
- velune/core/types/inference.py +38 -0
- velune/core/types/memory.py +42 -0
- velune/core/types/model.py +70 -0
- velune/core/types/provider.py +62 -0
- velune/core/types/repository.py +38 -0
- velune/core/types/task.py +61 -0
- velune/core/types/workspace.py +28 -0
- velune/daemon/client.py +13 -0
- velune/daemon/server.py +127 -0
- velune/daemon/transport.py +179 -0
- velune/events.py +204 -0
- velune/execution/__init__.py +22 -0
- velune/execution/benchmarker.py +315 -0
- velune/execution/cancellation.py +53 -0
- velune/execution/checkpointer.py +130 -0
- velune/execution/command_spec.py +165 -0
- velune/execution/diff_preview.py +197 -0
- velune/execution/executor.py +181 -0
- velune/execution/module.py +18 -0
- velune/execution/multi_diff.py +67 -0
- velune/execution/path_guard.py +74 -0
- velune/execution/planner.py +91 -0
- velune/execution/rollback.py +89 -0
- velune/execution/sandbox.py +268 -0
- velune/execution/validator.py +115 -0
- velune/hardware/__init__.py +1 -0
- velune/hardware/detector.py +192 -0
- velune/kernel/__init__.py +55 -0
- velune/kernel/bootstrap.py +125 -0
- velune/kernel/config.py +426 -0
- velune/kernel/entrypoint.py +78 -0
- velune/kernel/health.py +54 -0
- velune/kernel/lifecycle.py +143 -0
- velune/kernel/module.py +17 -0
- velune/kernel/modules.py +23 -0
- velune/kernel/registry.py +96 -0
- velune/kernel/schemas.py +28 -0
- velune/main.py +9 -0
- velune/mcp/__init__.py +9 -0
- velune/mcp/client.py +115 -0
- velune/mcp/config.py +19 -0
- velune/mcp/server.py +624 -0
- velune/memory/__init__.py +32 -0
- velune/memory/compaction.py +506 -0
- velune/memory/embedding_pipeline.py +241 -0
- velune/memory/lifecycle.py +680 -0
- velune/memory/module.py +218 -0
- velune/memory/prioritizer.py +67 -0
- velune/memory/storage/episodic_schema.sql +53 -0
- velune/memory/storage/lancedb_store.py +282 -0
- velune/memory/storage/sqlite_manager.py +369 -0
- velune/memory/storage/sqlite_pool.py +149 -0
- velune/memory/tiers/episodic.py +588 -0
- velune/memory/tiers/graph.py +378 -0
- velune/memory/tiers/lineage.py +416 -0
- velune/memory/tiers/semantic.py +475 -0
- velune/memory/tiers/working.py +168 -0
- velune/memory/vitality.py +132 -0
- velune/models/__init__.py +15 -0
- velune/models/family.py +76 -0
- velune/models/module.py +20 -0
- velune/models/probes.py +192 -0
- velune/models/profile_cache.py +84 -0
- velune/models/profiler.py +108 -0
- velune/models/registry.py +251 -0
- velune/models/scorer.py +233 -0
- velune/models/specializations.py +205 -0
- velune/orchestration/__init__.py +19 -0
- velune/orchestration/engine.py +239 -0
- velune/orchestration/module.py +15 -0
- velune/orchestration/role_assignments.py +82 -0
- velune/orchestration/schemas.py +98 -0
- velune/plugins/__init__.py +20 -0
- velune/plugins/hooks.py +50 -0
- velune/plugins/loader.py +161 -0
- velune/plugins/registry.py +56 -0
- velune/plugins/schemas.py +21 -0
- velune/providers/__init__.py +23 -0
- velune/providers/adapters/anthropic.py +257 -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 +210 -0
- velune/providers/adapters/llamacpp.py +208 -0
- velune/providers/adapters/lmstudio.py +175 -0
- velune/providers/adapters/ollama.py +233 -0
- velune/providers/adapters/openai.py +213 -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 +138 -0
- velune/providers/discovery/__init__.py +33 -0
- velune/providers/discovery/anthropic.py +79 -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 +88 -0
- velune/providers/discovery/google.py +95 -0
- velune/providers/discovery/gpu.py +117 -0
- velune/providers/discovery/groq.py +21 -0
- velune/providers/discovery/huggingface.py +67 -0
- velune/providers/discovery/lmstudio.py +80 -0
- velune/providers/discovery/ollama.py +162 -0
- velune/providers/discovery/openai.py +96 -0
- velune/providers/discovery/openrouter.py +113 -0
- velune/providers/discovery/scanner.py +115 -0
- velune/providers/discovery/together.py +114 -0
- velune/providers/discovery/xai.py +57 -0
- velune/providers/health.py +67 -0
- velune/providers/health_monitor.py +169 -0
- velune/providers/keystore.py +142 -0
- velune/providers/local_paths.py +49 -0
- velune/providers/local_resolver.py +229 -0
- velune/providers/module.py +51 -0
- velune/providers/ollama_manager.py +193 -0
- velune/providers/registry.py +220 -0
- velune/providers/router.py +255 -0
- velune/providers/task_classifier.py +288 -0
- velune/py.typed +0 -0
- velune/repository/__init__.py +33 -0
- velune/repository/analyzer.py +127 -0
- velune/repository/ast_parser.py +822 -0
- velune/repository/blast_radius.py +298 -0
- velune/repository/boundary_classifier.py +295 -0
- velune/repository/cognition.py +316 -0
- velune/repository/grapher.py +179 -0
- velune/repository/import_graph.py +263 -0
- velune/repository/incremental_indexer.py +275 -0
- velune/repository/index_state.py +96 -0
- velune/repository/indexer.py +243 -0
- velune/repository/module.py +17 -0
- velune/repository/parser.py +474 -0
- velune/repository/project_type.py +300 -0
- velune/repository/rename_journal.py +287 -0
- velune/repository/scanner.py +193 -0
- velune/repository/schemas.py +102 -0
- velune/repository/symbol_registry.py +365 -0
- velune/repository/tracker.py +252 -0
- velune/retrieval/__init__.py +27 -0
- velune/retrieval/cache.py +110 -0
- velune/retrieval/fast_path.py +391 -0
- velune/retrieval/graph.py +124 -0
- velune/retrieval/hybrid.py +271 -0
- velune/retrieval/keyword.py +131 -0
- velune/retrieval/module.py +26 -0
- velune/retrieval/pipeline.py +303 -0
- velune/retrieval/reranker.py +102 -0
- velune/retrieval/schemas.py +59 -0
- velune/retrieval/slow_path.py +364 -0
- velune/retrieval/vector.py +203 -0
- velune/telemetry/__init__.py +59 -0
- velune/telemetry/cognition.py +267 -0
- velune/telemetry/cost_estimator.py +92 -0
- velune/telemetry/debug.py +304 -0
- velune/telemetry/doctor.py +244 -0
- velune/telemetry/logging.py +286 -0
- velune/telemetry/spans.py +277 -0
- velune/telemetry/token_tracker.py +140 -0
- velune/telemetry/usage_tracker.py +340 -0
- velune/tools/__init__.py +41 -0
- velune/tools/base/registry.py +87 -0
- velune/tools/base/tool.py +63 -0
- velune/tools/code/navigate.py +116 -0
- velune/tools/code/search.py +123 -0
- velune/tools/filesystem/read.py +75 -0
- velune/tools/filesystem/search.py +136 -0
- velune/tools/filesystem/write.py +163 -0
- velune/tools/git/history.py +177 -0
- velune/tools/git/operations.py +122 -0
- velune/tools/git/state.py +121 -0
- velune/tools/module.py +81 -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 +122 -0
- velune_cli-0.9.0.dist-info/METADATA +518 -0
- velune_cli-0.9.0.dist-info/RECORD +279 -0
- velune_cli-0.9.0.dist-info/WHEEL +4 -0
- velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
- velune_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Plugin catalog registry tracking active plugin components and hook associations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from velune.plugins.hooks import PluginHookDispatcher
|
|
9
|
+
from velune.plugins.schemas import PluginManifest
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("velune.plugins.registry")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PluginRegistry:
|
|
15
|
+
"""Stores all loaded manifests and associated hook boundaries."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, hook_dispatcher: PluginHookDispatcher | None = None) -> None:
|
|
18
|
+
self.hook_dispatcher = hook_dispatcher or PluginHookDispatcher()
|
|
19
|
+
self._manifests: dict[str, PluginManifest] = {}
|
|
20
|
+
self._instances: dict[str, Any] = {}
|
|
21
|
+
|
|
22
|
+
def register_plugin(self, manifest: PluginManifest, instance: Any) -> None:
|
|
23
|
+
"""Saves a loaded plugin manifest and instantiates hook callbacks."""
|
|
24
|
+
self._manifests[manifest.name] = manifest
|
|
25
|
+
self._instances[manifest.name] = instance
|
|
26
|
+
|
|
27
|
+
# Search for callable hook methods based on manifest hooks list
|
|
28
|
+
for hook_name in manifest.hooks:
|
|
29
|
+
method_name = f"on_{hook_name}" if not hook_name.startswith("on_") else hook_name
|
|
30
|
+
# Strip 'on_' prefix for dispatcher hook names
|
|
31
|
+
clean_hook_name = hook_name
|
|
32
|
+
if clean_hook_name.startswith("on_"):
|
|
33
|
+
clean_hook_name = clean_hook_name[3:]
|
|
34
|
+
|
|
35
|
+
if hasattr(instance, method_name):
|
|
36
|
+
callback = getattr(instance, method_name)
|
|
37
|
+
if callable(callback):
|
|
38
|
+
self.hook_dispatcher.register_hook(clean_hook_name, callback)
|
|
39
|
+
logger.info(
|
|
40
|
+
"Associated plugin %s with hook: %s", manifest.name, clean_hook_name
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
logger.warning(
|
|
44
|
+
"Plugin %s declared hook %s but lacks method %s",
|
|
45
|
+
manifest.name,
|
|
46
|
+
hook_name,
|
|
47
|
+
method_name,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def get_plugin(self, name: str) -> Any | None:
|
|
51
|
+
"""Fetch active plugin instance."""
|
|
52
|
+
return self._instances.get(name)
|
|
53
|
+
|
|
54
|
+
def list_plugins(self) -> list[PluginManifest]:
|
|
55
|
+
"""Returns metadata list of loaded active plugins."""
|
|
56
|
+
return list(self._manifests.values())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Plugin manifest schemas and validation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PluginManifest(BaseModel):
|
|
11
|
+
"""Manifest data loaded from a plugin directory containing metadata and registration hooks."""
|
|
12
|
+
|
|
13
|
+
name: str = Field(..., description="Unique alphabetic slug of the plugin.")
|
|
14
|
+
version: str = Field("0.1.0", description="Plugin semantic version.")
|
|
15
|
+
description: str = Field("", description="Short plugin functional details.")
|
|
16
|
+
entry_point: str = Field(..., description="Relative python module path (e.g., 'plugin.py').")
|
|
17
|
+
hooks: list[str] = Field(
|
|
18
|
+
default_factory=list, description="List of Hook names subscribed (e.g., 'pre_execute')."
|
|
19
|
+
)
|
|
20
|
+
author: str | None = None
|
|
21
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Provider abstraction layer."""
|
|
2
|
+
|
|
3
|
+
# Provider adapters: import from velune.providers.adapters.*
|
|
4
|
+
# velune/providers/{name}/provider.py files are deprecated shims
|
|
5
|
+
|
|
6
|
+
from velune.providers.base import EmbeddingProvider, InferenceEngine, ModelProvider
|
|
7
|
+
from velune.providers.benchmarker import ProviderBenchmarker
|
|
8
|
+
from velune.providers.registry import ProviderRegistry
|
|
9
|
+
from velune.providers.router import ProviderRouter
|
|
10
|
+
from velune.providers.task_classifier import ComplexityLevel, TaskClassifier, TaskProfile, TaskType
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ModelProvider",
|
|
14
|
+
"InferenceEngine",
|
|
15
|
+
"EmbeddingProvider",
|
|
16
|
+
"ProviderRegistry",
|
|
17
|
+
"ProviderRouter",
|
|
18
|
+
"ProviderBenchmarker",
|
|
19
|
+
"TaskClassifier",
|
|
20
|
+
"TaskProfile",
|
|
21
|
+
"TaskType",
|
|
22
|
+
"ComplexityLevel",
|
|
23
|
+
]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Anthropic provider adapter implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import AsyncIterator
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from pydantic import SecretStr
|
|
11
|
+
|
|
12
|
+
from velune.core.errors.provider import (
|
|
13
|
+
InferenceError,
|
|
14
|
+
ProviderAuthenticationError,
|
|
15
|
+
)
|
|
16
|
+
from velune.core.types.inference import InferenceRequest, InferenceResponse, StreamChunk
|
|
17
|
+
from velune.core.types.model import CapabilityLevel, ModelDescriptor
|
|
18
|
+
from velune.core.types.provider import ProviderCapabilities, ProviderHealth
|
|
19
|
+
from velune.providers.base import ModelProvider
|
|
20
|
+
from velune.providers.keystore import get_key
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AnthropicProvider(ModelProvider):
|
|
24
|
+
"""Anthropic provider for Claude models."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self, api_key: str | SecretStr | None = None, base_url: str = "https://api.anthropic.com"
|
|
28
|
+
) -> None:
|
|
29
|
+
self._api_key = api_key or get_key("anthropic")
|
|
30
|
+
if hasattr(self._api_key, "get_secret_value"):
|
|
31
|
+
self._api_key = self._api_key.get_secret_value()
|
|
32
|
+
self._base_url = base_url
|
|
33
|
+
self.client: httpx.AsyncClient | None = None
|
|
34
|
+
self._capabilities = ProviderCapabilities(
|
|
35
|
+
supports_streaming=True,
|
|
36
|
+
supports_function_calling=True,
|
|
37
|
+
supports_embeddings=False,
|
|
38
|
+
max_context_window=200000,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def provider_id(self) -> str:
|
|
43
|
+
return "anthropic"
|
|
44
|
+
|
|
45
|
+
async def initialize(self) -> None:
|
|
46
|
+
"""Initialize HTTP client with Anthropic specific headers."""
|
|
47
|
+
if not self._api_key:
|
|
48
|
+
raise ProviderAuthenticationError(
|
|
49
|
+
"Anthropic API key not found in configuration or environment"
|
|
50
|
+
)
|
|
51
|
+
if not self.client:
|
|
52
|
+
headers = {
|
|
53
|
+
"x-api-key": self._api_key,
|
|
54
|
+
"anthropic-version": "2023-06-01",
|
|
55
|
+
"content-type": "application/json",
|
|
56
|
+
}
|
|
57
|
+
self.client = httpx.AsyncClient(base_url=self._base_url, headers=headers, timeout=300.0)
|
|
58
|
+
|
|
59
|
+
async def list_models(self) -> list[ModelDescriptor]:
|
|
60
|
+
"""List active Claude models."""
|
|
61
|
+
await self.initialize()
|
|
62
|
+
# Anthropic has static lists, or we can query their endpoints. Here we provide the standard suite.
|
|
63
|
+
return [
|
|
64
|
+
ModelDescriptor(
|
|
65
|
+
model_id="claude-opus-4-5",
|
|
66
|
+
display_name="Claude Opus 4.5",
|
|
67
|
+
provider_id="anthropic",
|
|
68
|
+
context_length=200000,
|
|
69
|
+
capabilities={
|
|
70
|
+
"coding": CapabilityLevel.EXPERT,
|
|
71
|
+
"reasoning": CapabilityLevel.EXPERT,
|
|
72
|
+
"planning": CapabilityLevel.EXPERT,
|
|
73
|
+
"summarization": CapabilityLevel.EXPERT,
|
|
74
|
+
"instruction_following": CapabilityLevel.EXPERT,
|
|
75
|
+
"tool_use": CapabilityLevel.EXPERT,
|
|
76
|
+
"long_context": CapabilityLevel.EXPERT,
|
|
77
|
+
},
|
|
78
|
+
is_local=False,
|
|
79
|
+
),
|
|
80
|
+
ModelDescriptor(
|
|
81
|
+
model_id="claude-sonnet-4-5",
|
|
82
|
+
display_name="Claude Sonnet 4.5",
|
|
83
|
+
provider_id="anthropic",
|
|
84
|
+
context_length=200000,
|
|
85
|
+
capabilities={
|
|
86
|
+
"coding": CapabilityLevel.ADVANCED,
|
|
87
|
+
"reasoning": CapabilityLevel.ADVANCED,
|
|
88
|
+
"planning": CapabilityLevel.ADVANCED,
|
|
89
|
+
"summarization": CapabilityLevel.ADVANCED,
|
|
90
|
+
"instruction_following": CapabilityLevel.ADVANCED,
|
|
91
|
+
"tool_use": CapabilityLevel.EXPERT,
|
|
92
|
+
"long_context": CapabilityLevel.ADVANCED,
|
|
93
|
+
},
|
|
94
|
+
is_local=False,
|
|
95
|
+
),
|
|
96
|
+
ModelDescriptor(
|
|
97
|
+
model_id="claude-haiku-4-5",
|
|
98
|
+
display_name="Claude Haiku 4.5",
|
|
99
|
+
provider_id="anthropic",
|
|
100
|
+
context_length=200000,
|
|
101
|
+
capabilities={
|
|
102
|
+
"coding": CapabilityLevel.INTERMEDIATE,
|
|
103
|
+
"reasoning": CapabilityLevel.INTERMEDIATE,
|
|
104
|
+
"planning": CapabilityLevel.INTERMEDIATE,
|
|
105
|
+
"summarization": CapabilityLevel.INTERMEDIATE,
|
|
106
|
+
"instruction_following": CapabilityLevel.ADVANCED,
|
|
107
|
+
"tool_use": CapabilityLevel.ADVANCED,
|
|
108
|
+
"long_context": CapabilityLevel.INTERMEDIATE,
|
|
109
|
+
},
|
|
110
|
+
is_local=False,
|
|
111
|
+
),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
async def infer(self, request: InferenceRequest) -> InferenceResponse:
|
|
115
|
+
"""Perform Claude inference."""
|
|
116
|
+
await self.initialize()
|
|
117
|
+
assert self.client is not None
|
|
118
|
+
start = time.perf_counter()
|
|
119
|
+
try:
|
|
120
|
+
# Map standard messages to Anthropic format (system role is parsed out if present)
|
|
121
|
+
system_prompt = ""
|
|
122
|
+
anth_messages = []
|
|
123
|
+
for msg in request.messages:
|
|
124
|
+
if msg.get("role") == "system":
|
|
125
|
+
system_prompt = msg.get("content", "")
|
|
126
|
+
else:
|
|
127
|
+
anth_messages.append({"role": msg.get("role"), "content": msg.get("content")})
|
|
128
|
+
|
|
129
|
+
payload = {
|
|
130
|
+
"model": request.model_id,
|
|
131
|
+
"messages": anth_messages,
|
|
132
|
+
"max_tokens": request.max_tokens or 4096,
|
|
133
|
+
"temperature": request.temperature,
|
|
134
|
+
"top_p": request.top_p,
|
|
135
|
+
}
|
|
136
|
+
if system_prompt:
|
|
137
|
+
payload["system"] = system_prompt
|
|
138
|
+
if request.stop_sequences:
|
|
139
|
+
payload["stop_sequences"] = request.stop_sequences
|
|
140
|
+
|
|
141
|
+
response = await self.client.post("/v1/messages", json=payload)
|
|
142
|
+
response.raise_for_status()
|
|
143
|
+
data = response.json()
|
|
144
|
+
latency = (time.perf_counter() - start) * 1000.0
|
|
145
|
+
|
|
146
|
+
content = ""
|
|
147
|
+
if data.get("content"):
|
|
148
|
+
content = data["content"][0].get("text", "")
|
|
149
|
+
|
|
150
|
+
# Record latency in health monitor if available
|
|
151
|
+
self._record_latency_to_monitor(latency)
|
|
152
|
+
|
|
153
|
+
return InferenceResponse(
|
|
154
|
+
content=content,
|
|
155
|
+
model_id=request.model_id,
|
|
156
|
+
finish_reason=data.get("stop_reason") or "end_turn",
|
|
157
|
+
tokens_used=data.get("usage", {}).get("total_tokens", 0),
|
|
158
|
+
latency_ms=latency,
|
|
159
|
+
)
|
|
160
|
+
except httpx.HTTPError as e:
|
|
161
|
+
raise InferenceError(f"Anthropic message completion failed: {e}")
|
|
162
|
+
|
|
163
|
+
async def stream(self, request: InferenceRequest) -> AsyncIterator[StreamChunk]:
|
|
164
|
+
"""Perform streaming completion."""
|
|
165
|
+
await self.initialize()
|
|
166
|
+
assert self.client is not None
|
|
167
|
+
start = time.perf_counter()
|
|
168
|
+
first_token = True
|
|
169
|
+
try:
|
|
170
|
+
system_prompt = ""
|
|
171
|
+
anth_messages = []
|
|
172
|
+
for msg in request.messages:
|
|
173
|
+
if msg.get("role") == "system":
|
|
174
|
+
system_prompt = msg.get("content", "")
|
|
175
|
+
else:
|
|
176
|
+
anth_messages.append({"role": msg.get("role"), "content": msg.get("content")})
|
|
177
|
+
|
|
178
|
+
payload = {
|
|
179
|
+
"model": request.model_id,
|
|
180
|
+
"messages": anth_messages,
|
|
181
|
+
"max_tokens": request.max_tokens or 4096,
|
|
182
|
+
"temperature": request.temperature,
|
|
183
|
+
"top_p": request.top_p,
|
|
184
|
+
"stream": True,
|
|
185
|
+
}
|
|
186
|
+
if system_prompt:
|
|
187
|
+
payload["system"] = system_prompt
|
|
188
|
+
if request.stop_sequences:
|
|
189
|
+
payload["stop_sequences"] = request.stop_sequences
|
|
190
|
+
|
|
191
|
+
async with self.client.stream("POST", "/v1/messages", json=payload) as response:
|
|
192
|
+
response.raise_for_status()
|
|
193
|
+
async for line in response.aiter_lines():
|
|
194
|
+
if line.startswith("data: "):
|
|
195
|
+
data_str = line[6:]
|
|
196
|
+
try:
|
|
197
|
+
data = json.loads(data_str)
|
|
198
|
+
d_type = data.get("type")
|
|
199
|
+
if d_type == "content_block_delta":
|
|
200
|
+
# Record latency at first token
|
|
201
|
+
if first_token:
|
|
202
|
+
latency = (time.perf_counter() - start) * 1000.0
|
|
203
|
+
self._record_latency_to_monitor(latency)
|
|
204
|
+
first_token = False
|
|
205
|
+
yield StreamChunk(
|
|
206
|
+
content=data["delta"].get("text", ""),
|
|
207
|
+
)
|
|
208
|
+
elif d_type == "message_delta":
|
|
209
|
+
yield StreamChunk(
|
|
210
|
+
content="",
|
|
211
|
+
finish_reason=data.get("delta", {}).get("stop_reason"),
|
|
212
|
+
)
|
|
213
|
+
except (json.JSONDecodeError, KeyError):
|
|
214
|
+
continue
|
|
215
|
+
except httpx.HTTPError as e:
|
|
216
|
+
raise InferenceError(f"Anthropic stream failed: {e}")
|
|
217
|
+
|
|
218
|
+
async def embed(self, texts: list[str], model_id: str) -> list[list[float]]:
|
|
219
|
+
raise NotImplementedError("Anthropic provider does not support embeddings.")
|
|
220
|
+
|
|
221
|
+
def _record_latency_to_monitor(self, latency_ms: float) -> None:
|
|
222
|
+
"""Record latency to health monitor if available."""
|
|
223
|
+
try:
|
|
224
|
+
from velune.kernel.registry import get_container
|
|
225
|
+
|
|
226
|
+
container = get_container()
|
|
227
|
+
if container.has("runtime.provider_health_monitor"):
|
|
228
|
+
monitor = container.get("runtime.provider_health_monitor")
|
|
229
|
+
monitor.record_latency(self.provider_id, int(latency_ms))
|
|
230
|
+
except (ImportError, AttributeError, KeyError):
|
|
231
|
+
pass # Health monitor not available, skip
|
|
232
|
+
|
|
233
|
+
async def health_check(self) -> ProviderHealth:
|
|
234
|
+
"""Simple validation verification."""
|
|
235
|
+
try:
|
|
236
|
+
await self.initialize()
|
|
237
|
+
assert self.client is not None
|
|
238
|
+
# Fetch a simple request with 1 token output
|
|
239
|
+
payload = {
|
|
240
|
+
"model": "claude-haiku-4-5",
|
|
241
|
+
"messages": [{"role": "user", "content": "ping"}],
|
|
242
|
+
"max_tokens": 1,
|
|
243
|
+
}
|
|
244
|
+
resp = await self.client.post("/v1/messages", json=payload)
|
|
245
|
+
if resp.status_code == 200:
|
|
246
|
+
return ProviderHealth.HEALTHY
|
|
247
|
+
return ProviderHealth.DEGRADED
|
|
248
|
+
except Exception:
|
|
249
|
+
return ProviderHealth.UNAVAILABLE
|
|
250
|
+
|
|
251
|
+
def get_capabilities(self) -> ProviderCapabilities:
|
|
252
|
+
return self._capabilities
|
|
253
|
+
|
|
254
|
+
async def shutdown(self) -> None:
|
|
255
|
+
if self.client:
|
|
256
|
+
await self.client.aclose()
|
|
257
|
+
self.client = None
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Fireworks.AI provider adapter — OpenAI-compatible REST API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from velune.core.types.model import CapabilityLevel, ModelDescriptor
|
|
6
|
+
from velune.core.types.provider import ProviderCapabilities
|
|
7
|
+
from velune.providers.adapters.openai import OpenAIProvider
|
|
8
|
+
from velune.providers.keystore import get_key
|
|
9
|
+
|
|
10
|
+
FIREWORKS_MODELS: list[ModelDescriptor] = [
|
|
11
|
+
ModelDescriptor(
|
|
12
|
+
model_id="accounts/fireworks/models/llama-v3p3-70b-instruct",
|
|
13
|
+
display_name="Llama 3.3 70B Instruct",
|
|
14
|
+
provider_id="fireworks",
|
|
15
|
+
context_length=131072,
|
|
16
|
+
capabilities={
|
|
17
|
+
"coding": CapabilityLevel.ADVANCED,
|
|
18
|
+
"reasoning": CapabilityLevel.ADVANCED,
|
|
19
|
+
"planning": CapabilityLevel.ADVANCED,
|
|
20
|
+
"summarization": CapabilityLevel.EXPERT,
|
|
21
|
+
"instruction_following": CapabilityLevel.EXPERT,
|
|
22
|
+
"tool_use": CapabilityLevel.ADVANCED,
|
|
23
|
+
"long_context": CapabilityLevel.ADVANCED,
|
|
24
|
+
},
|
|
25
|
+
speed_tier="fast",
|
|
26
|
+
is_local=False,
|
|
27
|
+
cost_per_1k_tokens=0.0009,
|
|
28
|
+
tags=["cloud", "fireworks", "llama"],
|
|
29
|
+
),
|
|
30
|
+
ModelDescriptor(
|
|
31
|
+
model_id="accounts/fireworks/models/deepseek-r1",
|
|
32
|
+
display_name="DeepSeek R1",
|
|
33
|
+
provider_id="fireworks",
|
|
34
|
+
context_length=163840,
|
|
35
|
+
capabilities={
|
|
36
|
+
"coding": CapabilityLevel.EXPERT,
|
|
37
|
+
"reasoning": CapabilityLevel.EXPERT,
|
|
38
|
+
"planning": CapabilityLevel.EXPERT,
|
|
39
|
+
"summarization": CapabilityLevel.ADVANCED,
|
|
40
|
+
"instruction_following": CapabilityLevel.EXPERT,
|
|
41
|
+
"tool_use": CapabilityLevel.ADVANCED,
|
|
42
|
+
"long_context": CapabilityLevel.ADVANCED,
|
|
43
|
+
},
|
|
44
|
+
speed_tier="slow",
|
|
45
|
+
is_local=False,
|
|
46
|
+
cost_per_1k_tokens=0.003,
|
|
47
|
+
tags=["cloud", "fireworks", "deepseek", "reasoning"],
|
|
48
|
+
),
|
|
49
|
+
ModelDescriptor(
|
|
50
|
+
model_id="accounts/fireworks/models/qwen2p5-coder-32b-instruct",
|
|
51
|
+
display_name="Qwen 2.5 Coder 32B Instruct",
|
|
52
|
+
provider_id="fireworks",
|
|
53
|
+
context_length=131072,
|
|
54
|
+
capabilities={
|
|
55
|
+
"coding": CapabilityLevel.EXPERT,
|
|
56
|
+
"reasoning": CapabilityLevel.ADVANCED,
|
|
57
|
+
"planning": CapabilityLevel.ADVANCED,
|
|
58
|
+
"summarization": CapabilityLevel.ADVANCED,
|
|
59
|
+
"instruction_following": CapabilityLevel.EXPERT,
|
|
60
|
+
"tool_use": CapabilityLevel.ADVANCED,
|
|
61
|
+
"long_context": CapabilityLevel.ADVANCED,
|
|
62
|
+
},
|
|
63
|
+
speed_tier="medium",
|
|
64
|
+
is_local=False,
|
|
65
|
+
cost_per_1k_tokens=0.0009,
|
|
66
|
+
tags=["cloud", "fireworks", "qwen", "coding"],
|
|
67
|
+
),
|
|
68
|
+
ModelDescriptor(
|
|
69
|
+
model_id="accounts/fireworks/models/mixtral-8x22b-instruct",
|
|
70
|
+
display_name="Mixtral 8x22B Instruct",
|
|
71
|
+
provider_id="fireworks",
|
|
72
|
+
context_length=65536,
|
|
73
|
+
capabilities={
|
|
74
|
+
"coding": CapabilityLevel.ADVANCED,
|
|
75
|
+
"reasoning": CapabilityLevel.ADVANCED,
|
|
76
|
+
"planning": CapabilityLevel.ADVANCED,
|
|
77
|
+
"summarization": CapabilityLevel.EXPERT,
|
|
78
|
+
"instruction_following": CapabilityLevel.ADVANCED,
|
|
79
|
+
"tool_use": CapabilityLevel.ADVANCED,
|
|
80
|
+
"long_context": CapabilityLevel.ADVANCED,
|
|
81
|
+
},
|
|
82
|
+
speed_tier="medium",
|
|
83
|
+
is_local=False,
|
|
84
|
+
cost_per_1k_tokens=0.0009,
|
|
85
|
+
tags=["cloud", "fireworks", "mixtral", "moe"],
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class FireworksProvider(OpenAIProvider):
|
|
91
|
+
"""Fireworks.AI — fast, cheap open-model inference."""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
api_key: str | None = None,
|
|
96
|
+
base_url: str = "https://api.fireworks.ai/inference/v1",
|
|
97
|
+
) -> None:
|
|
98
|
+
self._api_key = api_key or get_key("fireworks")
|
|
99
|
+
if hasattr(self._api_key, "get_secret_value"):
|
|
100
|
+
self._api_key = self._api_key.get_secret_value()
|
|
101
|
+
self._base_url = base_url
|
|
102
|
+
self.client = None
|
|
103
|
+
self._capabilities = ProviderCapabilities(
|
|
104
|
+
supports_streaming=True,
|
|
105
|
+
supports_function_calling=True,
|
|
106
|
+
supports_embeddings=False,
|
|
107
|
+
max_context_window=131072,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def provider_id(self) -> str:
|
|
112
|
+
return "fireworks"
|
|
113
|
+
|
|
114
|
+
async def list_models(self) -> list[ModelDescriptor]:
|
|
115
|
+
return list(FIREWORKS_MODELS)
|