pyinferencemanager 1.2.0__tar.gz → 1.3.0__tar.gz
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.
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/Cargo.lock +2 -2
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/Cargo.toml +1 -1
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/PKG-INFO +4 -3
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/README.md +3 -2
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/mod.rs +3 -1
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/stub_backend.rs +9 -19
- pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/backends/vllm_backend.rs +155 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/mod.rs +2 -0
- pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/engines/provider_health.rs +483 -0
- pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/engines/vllm_client.rs +347 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/error.rs +3 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/retry_strategy.rs +26 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/mod.rs +180 -2
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/config.rs +5 -0
- pyinferencemanager-1.2.0/crates/pyinferencemanager-core/src/engines/provider_health.rs +0 -303
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/LICENSE +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/Cargo.toml +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/classifier.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/complexity.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/embedding_complexity.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/anthropic_backend.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/colibri_backend.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/gemini_backend.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/ollama_backend.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/openai_backend.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/embedding_key.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/semantic_cache.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/sqlite_store.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/cloud_client.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/gemini_client.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/ollama_client.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/openai_client.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/error_classifier.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/memory_map.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/ollama_probe.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/profiler.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/lib.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/memory/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/memory/resource_monitor.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/jaeger_exporter.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/logging_exporter.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/prometheus_exporter.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/logging.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/metrics.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/tracer.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/budget_enforcer.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/cost_estimator.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/cost_tracker.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/dynamic_router.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/api_executor.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/executor.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/load_tester.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/provider_executor.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/provider_load_test.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/real_load_tester.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/scenarios.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/dag_builder.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/parallel.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/templates.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/router/execution_router.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/router/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/router/multi_provider.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/cache.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/dag.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/hardware.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/mod.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/plan.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/task.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-py/Cargo.toml +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-py/src/lib.rs +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/pyproject.toml +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/src/pyinferencemanager/__init__.py +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/src/pyinferencemanager/_mcp_connector.py +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/src/pyinferencemanager/_mcp_tools.py +0 -0
- {pyinferencemanager-1.2.0 → pyinferencemanager-1.3.0}/src/pyinferencemanager/py.typed +0 -0
|
@@ -1339,7 +1339,7 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
|
|
|
1339
1339
|
|
|
1340
1340
|
[[package]]
|
|
1341
1341
|
name = "pyinferencemanager"
|
|
1342
|
-
version = "1.
|
|
1342
|
+
version = "1.3.0"
|
|
1343
1343
|
dependencies = [
|
|
1344
1344
|
"chrono",
|
|
1345
1345
|
"pyinferencemanager-core",
|
|
@@ -1350,7 +1350,7 @@ dependencies = [
|
|
|
1350
1350
|
|
|
1351
1351
|
[[package]]
|
|
1352
1352
|
name = "pyinferencemanager-core"
|
|
1353
|
-
version = "1.
|
|
1353
|
+
version = "1.3.0"
|
|
1354
1354
|
dependencies = [
|
|
1355
1355
|
"anyhow",
|
|
1356
1356
|
"async-trait",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyinferencemanager
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -134,8 +134,9 @@ Real HTTP calls, with retry/backoff and cost tracking:
|
|
|
134
134
|
| OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` |
|
|
135
135
|
| Google Gemini | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `gemini-1.5-flash` |
|
|
136
136
|
| Ollama (local) | — | whatever models you've pulled locally |
|
|
137
|
+
| vLLM (local) | — | OpenAI-compatible endpoint, default `localhost:8000` |
|
|
137
138
|
|
|
138
|
-
Additionally, `
|
|
139
|
+
Additionally, `tensorrt_llm`, `mlc_llm`, and `colibri` exist as **cost-estimator-only stubs** (`BackendKind`/`RuntimeBackend` trait implementations with real cost/latency estimation logic, but no live inference) — they're placeholders for self-hosted inference servers, not currently wired to make real calls. `orchestrator.available_backends()` lists all of these honestly, including the stubs.
|
|
139
140
|
|
|
140
141
|
Cloud execution dispatches through `BackendRegistry`/`RuntimeBackend` (not a hand-matched provider enum): `ProviderExecutor::execute` maps a `CloudProvider` to its `BackendKind`, registers the one backend it needs (reading the API key from the env vars above), and calls it through the same `RuntimeBackend::infer` trait object every backend implements — so adding a new cloud provider is a new `BackendKind`/backend + one dispatch arm, not edits scattered across every call site that used to match the old enum.
|
|
141
142
|
|
|
@@ -152,7 +153,7 @@ The network connector (`_mcp_connector.InferenceManager.start_mcp_connector()`)
|
|
|
152
153
|
|
|
153
154
|
## Known issues
|
|
154
155
|
|
|
155
|
-
- `
|
|
156
|
+
- `tensorrt_llm`, `mlc_llm`, and `colibri` backends are cost-estimator-only stubs (see [Providers](#providers)) — they do not make live inference calls yet. `vllm` was a stub too as of 1.2.0 but is now a real backend.
|
|
156
157
|
- No open GitHub issues and no `TODO`/`FIXME` markers in the codebase at the time of this writing.
|
|
157
158
|
|
|
158
159
|
## License
|
|
@@ -100,8 +100,9 @@ Real HTTP calls, with retry/backoff and cost tracking:
|
|
|
100
100
|
| OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` |
|
|
101
101
|
| Google Gemini | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `gemini-1.5-flash` |
|
|
102
102
|
| Ollama (local) | — | whatever models you've pulled locally |
|
|
103
|
+
| vLLM (local) | — | OpenAI-compatible endpoint, default `localhost:8000` |
|
|
103
104
|
|
|
104
|
-
Additionally, `
|
|
105
|
+
Additionally, `tensorrt_llm`, `mlc_llm`, and `colibri` exist as **cost-estimator-only stubs** (`BackendKind`/`RuntimeBackend` trait implementations with real cost/latency estimation logic, but no live inference) — they're placeholders for self-hosted inference servers, not currently wired to make real calls. `orchestrator.available_backends()` lists all of these honestly, including the stubs.
|
|
105
106
|
|
|
106
107
|
Cloud execution dispatches through `BackendRegistry`/`RuntimeBackend` (not a hand-matched provider enum): `ProviderExecutor::execute` maps a `CloudProvider` to its `BackendKind`, registers the one backend it needs (reading the API key from the env vars above), and calls it through the same `RuntimeBackend::infer` trait object every backend implements — so adding a new cloud provider is a new `BackendKind`/backend + one dispatch arm, not edits scattered across every call site that used to match the old enum.
|
|
107
108
|
|
|
@@ -118,7 +119,7 @@ The network connector (`_mcp_connector.InferenceManager.start_mcp_connector()`)
|
|
|
118
119
|
|
|
119
120
|
## Known issues
|
|
120
121
|
|
|
121
|
-
- `
|
|
122
|
+
- `tensorrt_llm`, `mlc_llm`, and `colibri` backends are cost-estimator-only stubs (see [Providers](#providers)) — they do not make live inference calls yet. `vllm` was a stub too as of 1.2.0 but is now a real backend.
|
|
122
123
|
- No open GitHub issues and no `TODO`/`FIXME` markers in the codebase at the time of this writing.
|
|
123
124
|
|
|
124
125
|
## License
|
|
@@ -7,6 +7,7 @@ pub mod gemini_backend;
|
|
|
7
7
|
pub mod ollama_backend;
|
|
8
8
|
pub mod openai_backend;
|
|
9
9
|
pub mod stub_backend;
|
|
10
|
+
pub mod vllm_backend;
|
|
10
11
|
|
|
11
12
|
pub use anthropic_backend::AnthropicBackend;
|
|
12
13
|
pub use colibri_backend::ColibriBackend;
|
|
@@ -14,6 +15,7 @@ pub use gemini_backend::GeminiBackend;
|
|
|
14
15
|
pub use ollama_backend::OllamaBackend;
|
|
15
16
|
pub use openai_backend::OpenAiBackend;
|
|
16
17
|
pub use stub_backend::StubBackend;
|
|
18
|
+
pub use vllm_backend::VLlmBackend;
|
|
17
19
|
|
|
18
20
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
19
21
|
pub enum BackendKind {
|
|
@@ -105,7 +107,7 @@ pub enum AnyBackend {
|
|
|
105
107
|
OpenAi(OpenAiBackend),
|
|
106
108
|
Gemini(GeminiBackend),
|
|
107
109
|
Ollama(OllamaBackend),
|
|
108
|
-
VLlm(
|
|
110
|
+
VLlm(VLlmBackend),
|
|
109
111
|
TensorRtLlm(StubBackend),
|
|
110
112
|
MlcLlm(StubBackend),
|
|
111
113
|
Colibri(ColibriBackend),
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
use super::{BackendKind, InferenceRequest, InferenceResult, ModelProfile, RuntimeBackend};
|
|
2
2
|
|
|
3
|
+
/// Placeholder `RuntimeBackend` for runtimes that don't have a real client
|
|
4
|
+
/// implementation yet. `BackendKind::VLlm` used to be one of these -- it now
|
|
5
|
+
/// has a real implementation in `vllm_backend::VLlmBackend` (see
|
|
6
|
+
/// `AnyBackend::VLlm` in `mod.rs`), so it's no longer constructed with this
|
|
7
|
+
/// stub. Only `TensorRtLlm` and `MlcLlm` remain genuinely unimplemented.
|
|
3
8
|
pub struct StubBackend {
|
|
4
9
|
kind: BackendKind,
|
|
5
10
|
}
|
|
@@ -22,15 +27,6 @@ impl RuntimeBackend for StubBackend {
|
|
|
22
27
|
fn estimate_cost(&self, profile: &ModelProfile) -> f32 {
|
|
23
28
|
let size_gb = profile.size_gb();
|
|
24
29
|
match self.kind {
|
|
25
|
-
BackendKind::VLlm => {
|
|
26
|
-
if size_gb < 20.0 {
|
|
27
|
-
0.0
|
|
28
|
-
} else if size_gb < 50.0 {
|
|
29
|
-
0.00001
|
|
30
|
-
} else {
|
|
31
|
-
0.00002
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
30
|
BackendKind::TensorRtLlm => {
|
|
35
31
|
if size_gb < 20.0 {
|
|
36
32
|
0.0
|
|
@@ -52,10 +48,6 @@ impl RuntimeBackend for StubBackend {
|
|
|
52
48
|
fn estimate_latency(&self, profile: &ModelProfile) -> u64 {
|
|
53
49
|
let size_gb = profile.size_gb();
|
|
54
50
|
match self.kind {
|
|
55
|
-
BackendKind::VLlm => {
|
|
56
|
-
let base = (size_gb * 30.0) as u64;
|
|
57
|
-
base.max(200).min(30000)
|
|
58
|
-
}
|
|
59
51
|
BackendKind::TensorRtLlm => {
|
|
60
52
|
let base = (size_gb * 20.0) as u64;
|
|
61
53
|
base.max(150).min(20000)
|
|
@@ -79,7 +71,7 @@ mod tests {
|
|
|
79
71
|
|
|
80
72
|
#[test]
|
|
81
73
|
fn test_stub_backend_infer_returns_error() {
|
|
82
|
-
let backend = StubBackend::new(BackendKind::
|
|
74
|
+
let backend = StubBackend::new(BackendKind::TensorRtLlm);
|
|
83
75
|
let request = InferenceRequest {
|
|
84
76
|
model: "model".to_string(),
|
|
85
77
|
prompt: "test".to_string(),
|
|
@@ -93,23 +85,21 @@ mod tests {
|
|
|
93
85
|
|
|
94
86
|
#[test]
|
|
95
87
|
fn test_stub_backend_kind() {
|
|
96
|
-
let vllm = StubBackend::new(BackendKind::VLlm);
|
|
97
88
|
let trt = StubBackend::new(BackendKind::TensorRtLlm);
|
|
98
89
|
let mlc = StubBackend::new(BackendKind::MlcLlm);
|
|
99
90
|
|
|
100
|
-
assert_eq!(vllm.kind(), BackendKind::VLlm);
|
|
101
91
|
assert_eq!(trt.kind(), BackendKind::TensorRtLlm);
|
|
102
92
|
assert_eq!(mlc.kind(), BackendKind::MlcLlm);
|
|
103
93
|
}
|
|
104
94
|
|
|
105
95
|
#[test]
|
|
106
96
|
fn test_stub_backend_estimate_cost() {
|
|
107
|
-
let
|
|
97
|
+
let trt = StubBackend::new(BackendKind::TensorRtLlm);
|
|
108
98
|
let small = ModelProfile::new(10 * 1_073_741_824, "model".to_string());
|
|
109
99
|
let large = ModelProfile::new(60 * 1_073_741_824, "model".to_string());
|
|
110
100
|
|
|
111
|
-
let small_cost =
|
|
112
|
-
let large_cost =
|
|
101
|
+
let small_cost = trt.estimate_cost(&small);
|
|
102
|
+
let large_cost = trt.estimate_cost(&large);
|
|
113
103
|
|
|
114
104
|
assert!(small_cost <= large_cost);
|
|
115
105
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
use super::{BackendKind, InferenceRequest, InferenceResult, ModelProfile, RuntimeBackend};
|
|
2
|
+
use crate::engines::VLlmClient;
|
|
3
|
+
|
|
4
|
+
/// Real `RuntimeBackend` for a self-hosted vLLM server, talking to its
|
|
5
|
+
/// OpenAI-compatible HTTP API (`POST /v1/completions`). Follows the same
|
|
6
|
+
/// shape as `OllamaBackend`: a thin adapter that owns just enough config
|
|
7
|
+
/// (here, the base URL and optional API key) to construct a fresh client
|
|
8
|
+
/// per call and translate `InferenceRequest`/`InferenceResult` to and from
|
|
9
|
+
/// the wire format.
|
|
10
|
+
pub struct VLlmBackend {
|
|
11
|
+
base_url: String,
|
|
12
|
+
api_key: Option<String>,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl VLlmBackend {
|
|
16
|
+
pub fn new(base_url: String) -> Self {
|
|
17
|
+
VLlmBackend {
|
|
18
|
+
base_url,
|
|
19
|
+
api_key: None,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn with_api_key(mut self, api_key: Option<String>) -> Self {
|
|
24
|
+
self.api_key = api_key;
|
|
25
|
+
self
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[async_trait::async_trait]
|
|
30
|
+
impl RuntimeBackend for VLlmBackend {
|
|
31
|
+
async fn infer(&self, request: InferenceRequest) -> crate::Result<InferenceResult> {
|
|
32
|
+
let start = std::time::Instant::now();
|
|
33
|
+
let client = VLlmClient::new(&self.base_url).with_api_key(self.api_key.clone());
|
|
34
|
+
|
|
35
|
+
let response = client
|
|
36
|
+
.complete(&request.model, &request.prompt, request.max_tokens)
|
|
37
|
+
.await?;
|
|
38
|
+
|
|
39
|
+
let latency_ms = start.elapsed().as_millis() as u64;
|
|
40
|
+
|
|
41
|
+
Ok(InferenceResult {
|
|
42
|
+
output: response.text,
|
|
43
|
+
tokens_used: response.tokens_used,
|
|
44
|
+
latency_ms,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// vLLM is a self-hosted runtime like Ollama, not a metered cloud API --
|
|
49
|
+
// there's no per-token bill from vLLM itself. What scales with model
|
|
50
|
+
// size is the GPU/compute cost of running it, so this mirrors the
|
|
51
|
+
// original stub's tiered estimate (kept identical to avoid silently
|
|
52
|
+
// changing cost-comparison behavior elsewhere, e.g. CostEstimator)
|
|
53
|
+
// rather than collapsing to a flat 0.0 like OllamaBackend, since vLLM is
|
|
54
|
+
// typically deployed for larger models where that compute cost is
|
|
55
|
+
// material.
|
|
56
|
+
fn estimate_cost(&self, profile: &ModelProfile) -> f32 {
|
|
57
|
+
let size_gb = profile.size_gb();
|
|
58
|
+
if size_gb < 20.0 {
|
|
59
|
+
0.0
|
|
60
|
+
} else if size_gb < 50.0 {
|
|
61
|
+
0.00001
|
|
62
|
+
} else {
|
|
63
|
+
0.00002
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn estimate_latency(&self, profile: &ModelProfile) -> u64 {
|
|
68
|
+
let size_gb = profile.size_gb();
|
|
69
|
+
let base = (size_gb * 30.0) as u64;
|
|
70
|
+
base.max(200).min(30000)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn kind(&self) -> BackendKind {
|
|
74
|
+
BackendKind::VLlm
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[cfg(test)]
|
|
79
|
+
mod tests {
|
|
80
|
+
use super::*;
|
|
81
|
+
|
|
82
|
+
#[test]
|
|
83
|
+
fn test_vllm_backend_kind() {
|
|
84
|
+
let backend = VLlmBackend::new("http://localhost:8000".to_string());
|
|
85
|
+
assert_eq!(backend.kind(), BackendKind::VLlm);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[test]
|
|
89
|
+
fn test_vllm_estimate_cost_tiers_by_model_size() {
|
|
90
|
+
let backend = VLlmBackend::new("http://localhost:8000".to_string());
|
|
91
|
+
let small = ModelProfile::new(10 * 1_073_741_824, "model".to_string());
|
|
92
|
+
let medium = ModelProfile::new(30 * 1_073_741_824, "model".to_string());
|
|
93
|
+
let large = ModelProfile::new(60 * 1_073_741_824, "model".to_string());
|
|
94
|
+
|
|
95
|
+
assert_eq!(backend.estimate_cost(&small), 0.0);
|
|
96
|
+
assert!(backend.estimate_cost(&medium) < backend.estimate_cost(&large));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[test]
|
|
100
|
+
fn test_vllm_estimate_latency_bounds() {
|
|
101
|
+
let backend = VLlmBackend::new("http://localhost:8000".to_string());
|
|
102
|
+
let small_profile = ModelProfile::new(7 * 1_073_741_824, "llama".to_string());
|
|
103
|
+
let large_profile = ModelProfile::new(70 * 1_073_741_824, "llama".to_string());
|
|
104
|
+
|
|
105
|
+
let small_latency = backend.estimate_latency(&small_profile);
|
|
106
|
+
let large_latency = backend.estimate_latency(&large_profile);
|
|
107
|
+
|
|
108
|
+
assert!(small_latency < large_latency);
|
|
109
|
+
assert!(small_latency >= 200);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
use wiremock::matchers::{method, path};
|
|
113
|
+
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
114
|
+
|
|
115
|
+
#[tokio::test]
|
|
116
|
+
async fn test_infer_real_http_dispatch_through_backend() {
|
|
117
|
+
let mock_server = MockServer::start().await;
|
|
118
|
+
|
|
119
|
+
Mock::given(method("POST"))
|
|
120
|
+
.and(path("/v1/completions"))
|
|
121
|
+
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
122
|
+
"choices": [{"text": "Hello from vLLM", "finish_reason": "stop"}],
|
|
123
|
+
"usage": {"prompt_tokens": 3, "completion_tokens": 4, "total_tokens": 7}
|
|
124
|
+
})))
|
|
125
|
+
.mount(&mock_server)
|
|
126
|
+
.await;
|
|
127
|
+
|
|
128
|
+
let backend = VLlmBackend::new(mock_server.uri());
|
|
129
|
+
let request = InferenceRequest {
|
|
130
|
+
model: "meta-llama/Llama-3-8b".to_string(),
|
|
131
|
+
prompt: "Hi".to_string(),
|
|
132
|
+
max_tokens: 50,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
let result = backend.infer(request).await.expect("mocked infer should succeed");
|
|
136
|
+
assert_eq!(result.output, "Hello from vLLM");
|
|
137
|
+
assert_eq!(result.tokens_used, 4);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[tokio::test]
|
|
141
|
+
async fn test_infer_propagates_backend_unreachable_error() {
|
|
142
|
+
// Nothing listening -- must return a real error, not silently
|
|
143
|
+
// succeed or hang, since this is meant to replace the old stub that
|
|
144
|
+
// always errored with "not configured".
|
|
145
|
+
let backend = VLlmBackend::new("http://127.0.0.1:1".to_string());
|
|
146
|
+
let request = InferenceRequest {
|
|
147
|
+
model: "model".to_string(),
|
|
148
|
+
prompt: "Hi".to_string(),
|
|
149
|
+
max_tokens: 10,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
let result = backend.infer(request).await;
|
|
153
|
+
assert!(result.is_err());
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -3,9 +3,11 @@ pub mod gemini_client;
|
|
|
3
3
|
pub mod ollama_client;
|
|
4
4
|
pub mod openai_client;
|
|
5
5
|
pub mod provider_health;
|
|
6
|
+
pub mod vllm_client;
|
|
6
7
|
|
|
7
8
|
pub use cloud_client::CloudClient;
|
|
8
9
|
pub use gemini_client::GeminiClient;
|
|
9
10
|
pub use ollama_client::OllamaClient;
|
|
10
11
|
pub use openai_client::OpenAIClient;
|
|
11
12
|
pub use provider_health::{ProviderHealth, ProviderHealthMetrics, ProviderStatus};
|
|
13
|
+
pub use vllm_client::VLlmClient;
|