pyinferencemanager 1.0.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.
Files changed (88) hide show
  1. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/Cargo.lock +87 -2
  2. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/Cargo.toml +1 -1
  3. pyinferencemanager-1.3.0/LICENSE +22 -0
  4. pyinferencemanager-1.3.0/PKG-INFO +162 -0
  5. pyinferencemanager-1.3.0/README.md +127 -0
  6. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/Cargo.toml +2 -0
  7. pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/backends/gemini_backend.rs +86 -0
  8. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/mod.rs +12 -1
  9. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/stub_backend.rs +9 -19
  10. pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/backends/vllm_backend.rs +155 -0
  11. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/cloud_client.rs +84 -8
  12. pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/engines/gemini_client.rs +250 -0
  13. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/mod.rs +4 -0
  14. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/ollama_client.rs +87 -1
  15. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/engines/openai_client.rs +83 -8
  16. pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/engines/provider_health.rs +483 -0
  17. pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/engines/vllm_client.rs +347 -0
  18. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/error.rs +3 -0
  19. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/budget_enforcer.rs +56 -10
  20. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/dynamic_router.rs +57 -6
  21. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/retry_strategy.rs +26 -0
  22. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/api_executor.rs +9 -7
  23. pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/orchestrator/mod.rs +841 -0
  24. pyinferencemanager-1.3.0/crates/pyinferencemanager-core/src/orchestrator/provider_executor.rs +267 -0
  25. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/provider_load_test.rs +1 -0
  26. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/real_load_tester.rs +1 -0
  27. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/router/multi_provider.rs +4 -1
  28. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/config.rs +5 -0
  29. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/dag.rs +64 -2
  30. pyinferencemanager-1.3.0/crates/pyinferencemanager-py/src/lib.rs +566 -0
  31. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/pyproject.toml +16 -2
  32. pyinferencemanager-1.3.0/src/pyinferencemanager/__init__.py +172 -0
  33. pyinferencemanager-1.3.0/src/pyinferencemanager/_mcp_connector.py +189 -0
  34. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0/src}/pyinferencemanager/_mcp_tools.py +216 -57
  35. pyinferencemanager-1.3.0/src/pyinferencemanager/py.typed +0 -0
  36. pyinferencemanager-1.0.0/PKG-INFO +0 -178
  37. pyinferencemanager-1.0.0/README.md +0 -142
  38. pyinferencemanager-1.0.0/crates/pyinferencemanager-core/src/engines/provider_health.rs +0 -303
  39. pyinferencemanager-1.0.0/crates/pyinferencemanager-core/src/orchestrator/mod.rs +0 -433
  40. pyinferencemanager-1.0.0/crates/pyinferencemanager-core/src/orchestrator/provider_executor.rs +0 -135
  41. pyinferencemanager-1.0.0/crates/pyinferencemanager-py/src/lib.rs +0 -381
  42. pyinferencemanager-1.0.0/pyinferencemanager/_mcp_connector.py +0 -105
  43. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/classifier.rs +0 -0
  44. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/complexity.rs +0 -0
  45. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/embedding_complexity.rs +0 -0
  46. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/analyzer/mod.rs +0 -0
  47. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/anthropic_backend.rs +0 -0
  48. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/colibri_backend.rs +0 -0
  49. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/ollama_backend.rs +0 -0
  50. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/backends/openai_backend.rs +0 -0
  51. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/embedding_key.rs +0 -0
  52. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/mod.rs +0 -0
  53. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/semantic_cache.rs +0 -0
  54. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/cache/sqlite_store.rs +0 -0
  55. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/error_classifier.rs +0 -0
  56. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/memory_map.rs +0 -0
  57. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/mod.rs +0 -0
  58. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/ollama_probe.rs +0 -0
  59. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/hardware/profiler.rs +0 -0
  60. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/lib.rs +0 -0
  61. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/memory/mod.rs +0 -0
  62. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/memory/resource_monitor.rs +0 -0
  63. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/jaeger_exporter.rs +0 -0
  64. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/logging_exporter.rs +0 -0
  65. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/mod.rs +0 -0
  66. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/exporters/prometheus_exporter.rs +0 -0
  67. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/logging.rs +0 -0
  68. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/metrics.rs +0 -0
  69. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/mod.rs +0 -0
  70. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/observability/tracer.rs +0 -0
  71. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/cost_estimator.rs +0 -0
  72. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/cost_tracker.rs +0 -0
  73. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/optimizer/mod.rs +0 -0
  74. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/executor.rs +0 -0
  75. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/load_tester.rs +0 -0
  76. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/orchestrator/scenarios.rs +0 -0
  77. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/dag_builder.rs +0 -0
  78. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/mod.rs +0 -0
  79. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/parallel.rs +0 -0
  80. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/planner/templates.rs +0 -0
  81. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/router/execution_router.rs +0 -0
  82. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/router/mod.rs +0 -0
  83. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/cache.rs +0 -0
  84. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/hardware.rs +0 -0
  85. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/mod.rs +0 -0
  86. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/plan.rs +0 -0
  87. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-core/src/types/task.rs +0 -0
  88. {pyinferencemanager-1.0.0 → pyinferencemanager-1.3.0}/crates/pyinferencemanager-py/Cargo.toml +0 -0
@@ -26,6 +26,16 @@ version = "1.0.104"
26
26
  source = "registry+https://github.com/rust-lang/crates.io-index"
27
27
  checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
28
28
 
29
+ [[package]]
30
+ name = "assert-json-diff"
31
+ version = "2.0.2"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
34
+ dependencies = [
35
+ "serde",
36
+ "serde_json",
37
+ ]
38
+
29
39
  [[package]]
30
40
  name = "async-trait"
31
41
  version = "0.1.91"
@@ -233,6 +243,24 @@ dependencies = [
233
243
  "typenum",
234
244
  ]
235
245
 
246
+ [[package]]
247
+ name = "deadpool"
248
+ version = "0.12.3"
249
+ source = "registry+https://github.com/rust-lang/crates.io-index"
250
+ checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b"
251
+ dependencies = [
252
+ "deadpool-runtime",
253
+ "lazy_static",
254
+ "num_cpus",
255
+ "tokio",
256
+ ]
257
+
258
+ [[package]]
259
+ name = "deadpool-runtime"
260
+ version = "0.1.4"
261
+ source = "registry+https://github.com/rust-lang/crates.io-index"
262
+ checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
263
+
236
264
  [[package]]
237
265
  name = "deranged"
238
266
  version = "0.5.8"
@@ -576,6 +604,12 @@ version = "1.10.1"
576
604
  source = "registry+https://github.com/rust-lang/crates.io-index"
577
605
  checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
578
606
 
607
+ [[package]]
608
+ name = "httpdate"
609
+ version = "1.0.3"
610
+ source = "registry+https://github.com/rust-lang/crates.io-index"
611
+ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
612
+
579
613
  [[package]]
580
614
  name = "hyper"
581
615
  version = "1.11.0"
@@ -590,6 +624,7 @@ dependencies = [
590
624
  "http",
591
625
  "http-body",
592
626
  "httparse",
627
+ "httpdate",
593
628
  "itoa",
594
629
  "pin-project-lite",
595
630
  "smallvec",
@@ -1304,7 +1339,7 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
1304
1339
 
1305
1340
  [[package]]
1306
1341
  name = "pyinferencemanager"
1307
- version = "1.0.0"
1342
+ version = "1.3.0"
1308
1343
  dependencies = [
1309
1344
  "chrono",
1310
1345
  "pyinferencemanager-core",
@@ -1315,7 +1350,7 @@ dependencies = [
1315
1350
 
1316
1351
  [[package]]
1317
1352
  name = "pyinferencemanager-core"
1318
- version = "1.0.0"
1353
+ version = "1.3.0"
1319
1354
  dependencies = [
1320
1355
  "anyhow",
1321
1356
  "async-trait",
@@ -1330,6 +1365,7 @@ dependencies = [
1330
1365
  "rusqlite",
1331
1366
  "serde",
1332
1367
  "serde_json",
1368
+ "serial_test",
1333
1369
  "sha2",
1334
1370
  "sqlite-vec",
1335
1371
  "tempfile",
@@ -1339,6 +1375,7 @@ dependencies = [
1339
1375
  "tracing-opentelemetry",
1340
1376
  "tracing-subscriber",
1341
1377
  "uuid",
1378
+ "wiremock",
1342
1379
  ]
1343
1380
 
1344
1381
  [[package]]
@@ -1856,6 +1893,31 @@ dependencies = [
1856
1893
  "zmij",
1857
1894
  ]
1858
1895
 
1896
+ [[package]]
1897
+ name = "serial_test"
1898
+ version = "3.5.0"
1899
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1900
+ checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
1901
+ dependencies = [
1902
+ "futures-executor",
1903
+ "futures-util",
1904
+ "log",
1905
+ "once_cell",
1906
+ "parking_lot",
1907
+ "serial_test_derive",
1908
+ ]
1909
+
1910
+ [[package]]
1911
+ name = "serial_test_derive"
1912
+ version = "3.5.0"
1913
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1914
+ checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
1915
+ dependencies = [
1916
+ "proc-macro2",
1917
+ "quote",
1918
+ "syn 2.0.119",
1919
+ ]
1920
+
1859
1921
  [[package]]
1860
1922
  name = "sha2"
1861
1923
  version = "0.10.9"
@@ -2726,6 +2788,29 @@ version = "0.52.6"
2726
2788
  source = "registry+https://github.com/rust-lang/crates.io-index"
2727
2789
  checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2728
2790
 
2791
+ [[package]]
2792
+ name = "wiremock"
2793
+ version = "0.6.5"
2794
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2795
+ checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031"
2796
+ dependencies = [
2797
+ "assert-json-diff",
2798
+ "base64",
2799
+ "deadpool",
2800
+ "futures",
2801
+ "http",
2802
+ "http-body-util",
2803
+ "hyper",
2804
+ "hyper-util",
2805
+ "log",
2806
+ "once_cell",
2807
+ "regex",
2808
+ "serde",
2809
+ "serde_json",
2810
+ "tokio",
2811
+ "url",
2812
+ ]
2813
+
2729
2814
  [[package]]
2730
2815
  name = "writeable"
2731
2816
  version = "0.6.3"
@@ -3,7 +3,7 @@ members = ["crates/pyinferencemanager-core", "crates/pyinferencemanager-py"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "1.0.0"
6
+ version = "1.3.0"
7
7
  edition = "2021"
8
8
  rust-version = "1.82"
9
9
  authors = ["Georgi Mammen Mullassery <mullassery@gmail.com>"]
@@ -0,0 +1,22 @@
1
+ Proprietary License — Free to use with explicit attribution
2
+
3
+ Copyright © 2026 Georgi Mammen Mullassery
4
+
5
+ Permission is granted to any person obtaining a copy of this software to use,
6
+ copy, modify, and distribute this software and its documentation, provided that:
7
+
8
+ 1. The above copyright notice and this permission notice appear in all copies
9
+ or substantial portions of the software.
10
+
11
+ 2. Explicit attribution to the original author (Georgi Mammen Mullassery) is
12
+ provided in any project documentation and significant code comments where
13
+ this software is used.
14
+
15
+ 3. The software is provided "AS IS" without warranty of any kind, express or
16
+ implied, including but not limited to the warranties of merchantability,
17
+ fitness for a particular purpose, or non-infringement.
18
+
19
+ 4. In no event shall the author be liable for any claim, damages, or other
20
+ liability arising from the use of this software.
21
+
22
+ For more information, visit: https://github.com/Mullassery
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyinferencemanager
3
+ Version: 1.3.0
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Environment :: Console
6
+ Classifier: Intended Audience :: Developers
7
+ Classifier: Intended Audience :: Science/Research
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Rust
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: pytest>=7.0 ; extra == 'dev'
19
+ Requires-Dist: pytest-asyncio>=0.21 ; extra == 'dev'
20
+ Requires-Dist: maturin>=1.0 ; extra == 'dev'
21
+ Requires-Dist: ruff>=0.5.0 ; extra == 'dev'
22
+ Provides-Extra: dev
23
+ License-File: LICENSE
24
+ Summary: Intelligent AI workload orchestrator. Multi-provider routing, cost optimization, semantic caching, dynamic routing with budget enforcement. Production observability included.
25
+ Keywords: ai,orchestrator,llm,ollama,anthropic,openai,local-first,cost-optimization,caching,multi-provider,routing,inference,workload-orchestration,hardware-aware,semantic-caching,distributed-tracing,observability,budget-enforcement
26
+ Author-email: Georgi Mammen Mullassery <mullassery@gmail.com>
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
29
+ Project-URL: Changelog, https://github.com/Mullassery/pyinferencemanager/releases
30
+ Project-URL: Documentation, https://github.com/Mullassery/pyinferencemanager/blob/main/README.md
31
+ Project-URL: Homepage, https://github.com/Mullassery/pyinferencemanager
32
+ Project-URL: Issues, https://github.com/Mullassery/pyinferencemanager/issues
33
+ Project-URL: Repository, https://github.com/Mullassery/pyinferencemanager.git
34
+
35
+ # PyInferenceManager
36
+
37
+ **A multi-provider LLM inference orchestrator with cost/latency-aware routing, real retry + budget guardrails, and local-first execution.**
38
+
39
+ Routes requests to Anthropic Claude, OpenAI GPT, Google Gemini, or a local Ollama model based on task complexity, privacy, and observed provider health — with real retry/backoff on failures and a real spend cap you configure.
40
+
41
+ [![PyPI](https://img.shields.io/pypi/v/pyinferencemanager)](https://pypi.org/project/pyinferencemanager)
42
+ [![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue)](https://www.python.org)
43
+ [![Tests](https://img.shields.io/github/actions/workflow/status/Mullassery/PyInferenceManager/tests.yml?label=tests)](https://github.com/Mullassery/PyInferenceManager/actions)
44
+ [![License: Proprietary](https://img.shields.io/badge/License-Proprietary-blue.svg)](./LICENSE)
45
+
46
+ ---
47
+
48
+ ## What this actually is
49
+
50
+ The core (Rust, ~11k lines, PyO3 bindings) is a workload orchestrator: it builds a small execution DAG for a task, profiles your local hardware, picks an engine (local Ollama model vs. a cloud provider) based on complexity/privacy/cost, executes with real retry and budget enforcement, and semantically caches results.
51
+
52
+ The Python API surface is a single `Orchestrator` class. There is no `Manager.chat()`, no streaming API, and no 11-provider marketplace — see [Providers](#providers) for the honest list.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install pyinferencemanager
58
+ ```
59
+
60
+ Requires Python 3.10+.
61
+
62
+ Set whichever provider keys you plan to use (none are required for local-only Ollama use):
63
+
64
+ ```bash
65
+ export ANTHROPIC_API_KEY=sk-ant-...
66
+ export OPENAI_API_KEY=sk-...
67
+ export GEMINI_API_KEY=... # or GOOGLE_API_KEY
68
+ ```
69
+
70
+ See [`.env.example`](.env.example).
71
+
72
+ ## Quick start
73
+
74
+ ```python
75
+ from pyinferencemanager import Orchestrator
76
+
77
+ orchestrator = Orchestrator(mode="local_first") # or "cloud_first"
78
+
79
+ result = orchestrator.run(task="question_answering", message="What is the capital of France?")
80
+ print(result.output)
81
+ print(f"Engines used: {result.engines_used}")
82
+ print(f"Cost: ${result.total_cost_usd:.4f} | Latency: {result.total_latency_ms}ms | Tokens: {result.total_tokens}")
83
+ ```
84
+
85
+ `mode="local_first"` runs on your local Ollama model when it's adequate for the task's complexity and escalates to a cloud provider otherwise. `mode="cloud_first"` prefers a cloud provider, falling back to local only for very low-complexity tasks. `privacy="high"` on any call always forces local execution regardless of mode.
86
+
87
+ If the selected backend is unreachable or unauthenticated, `run()` doesn't raise — it returns a result whose `output` says so (e.g. `"[cloud inference unavailable: ...]"`), so a single bad provider doesn't crash your pipeline.
88
+
89
+ ## Core API
90
+
91
+ ```python
92
+ orchestrator = Orchestrator(mode="local_first")
93
+
94
+ # Execute a task — real inference against Ollama or a cloud provider.
95
+ result = orchestrator.run(task="...", file=None, message=None, privacy="low")
96
+ # result.output, .total_tokens, .total_cost_usd, .total_latency_ms, .engines_used, .cache_hits
97
+
98
+ # Estimate cost/latency without executing anything.
99
+ plan = orchestrator.plan("Summarize this document")
100
+ # plan.stages, .estimated_cost_usd, .estimated_latency_ms, .local_first
101
+
102
+ # Real-time provider health, from actually completed cloud calls.
103
+ orchestrator.provider_ranking() # [(provider_key, health_score), ...]
104
+ orchestrator.provider_performance() # {provider_key: {success_rate, avg_latency_ms, ...}}
105
+
106
+ # Cost guardrails, enforced on every real cloud call.
107
+ orchestrator.configure_budget(max_cost_usd=10.0, max_requests=1000,
108
+ alert_threshold_percent=80.0, enforce_hard_limit=True)
109
+ orchestrator.budget_status()
110
+
111
+ # Retry/backoff policy for retryable errors (HTTP 429/408/5xx).
112
+ orchestrator.configure_retry(max_attempts=3, backoff="exponential",
113
+ initial_ms=100, max_ms=5000)
114
+
115
+ # Synthetic load test exercising the same budget + dynamic-routing logic
116
+ # run() uses, at a volume impractical against live APIs. Latencies/costs
117
+ # here are simulated, not real network calls.
118
+ orchestrator.run_load_test(num_requests=200, budget_usd=5.0)
119
+
120
+ # Local hardware profile (memory tier, Apple Silicon/Metal, Ollama models).
121
+ orchestrator.profile_hardware()
122
+ orchestrator.available_backends()
123
+ ```
124
+
125
+ See [`examples/`](examples/) for runnable scripts covering each of these.
126
+
127
+ ## Providers
128
+
129
+ Real HTTP calls, with retry/backoff and cost tracking:
130
+
131
+ | Provider | Env var | Notes |
132
+ |---|---|---|
133
+ | Anthropic Claude | `ANTHROPIC_API_KEY` | `claude-haiku-4-5`, `claude-opus-4-1` |
134
+ | OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` |
135
+ | Google Gemini | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `gemini-1.5-flash` |
136
+ | Ollama (local) | — | whatever models you've pulled locally |
137
+ | vLLM (local) | — | OpenAI-compatible endpoint, default `localhost:8000` |
138
+
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.
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.
142
+
143
+ ## MCP tools
144
+
145
+ `pyinferencemanager._mcp_tools.PyInferenceManagerMCPHandler` exposes 13 MCP-style tools (`list_available_models`, `execute_inference`, `estimate_inference_cost`, `get_provider_status`, etc.), all backed by a real `Orchestrator` instance — no hardcoded responses. See [`examples/mcp_pyinferencemanager.py`](examples/mcp_pyinferencemanager.py).
146
+
147
+ The network connector (`_mcp_connector.InferenceManager.start_mcp_connector()`) binds to `127.0.0.1` by default with scoped CORS and permissions; binding elsewhere requires passing `allow_remote=True` explicitly.
148
+
149
+ ## Testing
150
+
151
+ - Rust core: `cargo test --workspace` (350+ tests, including HTTP-mocked request/response tests for every cloud client via [`wiremock`](https://docs.rs/wiremock)).
152
+ - Python: `pytest tests/` — covers `Orchestrator.run/plan/provider_ranking`, the MCP tool handlers, budget/retry configuration, load testing, and MCP connector security defaults, all without requiring live API keys.
153
+
154
+ ## Known issues
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.
157
+ - No open GitHub issues and no `TODO`/`FIXME` markers in the codebase at the time of this writing.
158
+
159
+ ## License
160
+
161
+ Proprietary License — free to use with explicit attribution. See [LICENSE](LICENSE).
162
+
@@ -0,0 +1,127 @@
1
+ # PyInferenceManager
2
+
3
+ **A multi-provider LLM inference orchestrator with cost/latency-aware routing, real retry + budget guardrails, and local-first execution.**
4
+
5
+ Routes requests to Anthropic Claude, OpenAI GPT, Google Gemini, or a local Ollama model based on task complexity, privacy, and observed provider health — with real retry/backoff on failures and a real spend cap you configure.
6
+
7
+ [![PyPI](https://img.shields.io/pypi/v/pyinferencemanager)](https://pypi.org/project/pyinferencemanager)
8
+ [![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue)](https://www.python.org)
9
+ [![Tests](https://img.shields.io/github/actions/workflow/status/Mullassery/PyInferenceManager/tests.yml?label=tests)](https://github.com/Mullassery/PyInferenceManager/actions)
10
+ [![License: Proprietary](https://img.shields.io/badge/License-Proprietary-blue.svg)](./LICENSE)
11
+
12
+ ---
13
+
14
+ ## What this actually is
15
+
16
+ The core (Rust, ~11k lines, PyO3 bindings) is a workload orchestrator: it builds a small execution DAG for a task, profiles your local hardware, picks an engine (local Ollama model vs. a cloud provider) based on complexity/privacy/cost, executes with real retry and budget enforcement, and semantically caches results.
17
+
18
+ The Python API surface is a single `Orchestrator` class. There is no `Manager.chat()`, no streaming API, and no 11-provider marketplace — see [Providers](#providers) for the honest list.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install pyinferencemanager
24
+ ```
25
+
26
+ Requires Python 3.10+.
27
+
28
+ Set whichever provider keys you plan to use (none are required for local-only Ollama use):
29
+
30
+ ```bash
31
+ export ANTHROPIC_API_KEY=sk-ant-...
32
+ export OPENAI_API_KEY=sk-...
33
+ export GEMINI_API_KEY=... # or GOOGLE_API_KEY
34
+ ```
35
+
36
+ See [`.env.example`](.env.example).
37
+
38
+ ## Quick start
39
+
40
+ ```python
41
+ from pyinferencemanager import Orchestrator
42
+
43
+ orchestrator = Orchestrator(mode="local_first") # or "cloud_first"
44
+
45
+ result = orchestrator.run(task="question_answering", message="What is the capital of France?")
46
+ print(result.output)
47
+ print(f"Engines used: {result.engines_used}")
48
+ print(f"Cost: ${result.total_cost_usd:.4f} | Latency: {result.total_latency_ms}ms | Tokens: {result.total_tokens}")
49
+ ```
50
+
51
+ `mode="local_first"` runs on your local Ollama model when it's adequate for the task's complexity and escalates to a cloud provider otherwise. `mode="cloud_first"` prefers a cloud provider, falling back to local only for very low-complexity tasks. `privacy="high"` on any call always forces local execution regardless of mode.
52
+
53
+ If the selected backend is unreachable or unauthenticated, `run()` doesn't raise — it returns a result whose `output` says so (e.g. `"[cloud inference unavailable: ...]"`), so a single bad provider doesn't crash your pipeline.
54
+
55
+ ## Core API
56
+
57
+ ```python
58
+ orchestrator = Orchestrator(mode="local_first")
59
+
60
+ # Execute a task — real inference against Ollama or a cloud provider.
61
+ result = orchestrator.run(task="...", file=None, message=None, privacy="low")
62
+ # result.output, .total_tokens, .total_cost_usd, .total_latency_ms, .engines_used, .cache_hits
63
+
64
+ # Estimate cost/latency without executing anything.
65
+ plan = orchestrator.plan("Summarize this document")
66
+ # plan.stages, .estimated_cost_usd, .estimated_latency_ms, .local_first
67
+
68
+ # Real-time provider health, from actually completed cloud calls.
69
+ orchestrator.provider_ranking() # [(provider_key, health_score), ...]
70
+ orchestrator.provider_performance() # {provider_key: {success_rate, avg_latency_ms, ...}}
71
+
72
+ # Cost guardrails, enforced on every real cloud call.
73
+ orchestrator.configure_budget(max_cost_usd=10.0, max_requests=1000,
74
+ alert_threshold_percent=80.0, enforce_hard_limit=True)
75
+ orchestrator.budget_status()
76
+
77
+ # Retry/backoff policy for retryable errors (HTTP 429/408/5xx).
78
+ orchestrator.configure_retry(max_attempts=3, backoff="exponential",
79
+ initial_ms=100, max_ms=5000)
80
+
81
+ # Synthetic load test exercising the same budget + dynamic-routing logic
82
+ # run() uses, at a volume impractical against live APIs. Latencies/costs
83
+ # here are simulated, not real network calls.
84
+ orchestrator.run_load_test(num_requests=200, budget_usd=5.0)
85
+
86
+ # Local hardware profile (memory tier, Apple Silicon/Metal, Ollama models).
87
+ orchestrator.profile_hardware()
88
+ orchestrator.available_backends()
89
+ ```
90
+
91
+ See [`examples/`](examples/) for runnable scripts covering each of these.
92
+
93
+ ## Providers
94
+
95
+ Real HTTP calls, with retry/backoff and cost tracking:
96
+
97
+ | Provider | Env var | Notes |
98
+ |---|---|---|
99
+ | Anthropic Claude | `ANTHROPIC_API_KEY` | `claude-haiku-4-5`, `claude-opus-4-1` |
100
+ | OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` |
101
+ | Google Gemini | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `gemini-1.5-flash` |
102
+ | Ollama (local) | — | whatever models you've pulled locally |
103
+ | vLLM (local) | — | OpenAI-compatible endpoint, default `localhost:8000` |
104
+
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.
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.
108
+
109
+ ## MCP tools
110
+
111
+ `pyinferencemanager._mcp_tools.PyInferenceManagerMCPHandler` exposes 13 MCP-style tools (`list_available_models`, `execute_inference`, `estimate_inference_cost`, `get_provider_status`, etc.), all backed by a real `Orchestrator` instance — no hardcoded responses. See [`examples/mcp_pyinferencemanager.py`](examples/mcp_pyinferencemanager.py).
112
+
113
+ The network connector (`_mcp_connector.InferenceManager.start_mcp_connector()`) binds to `127.0.0.1` by default with scoped CORS and permissions; binding elsewhere requires passing `allow_remote=True` explicitly.
114
+
115
+ ## Testing
116
+
117
+ - Rust core: `cargo test --workspace` (350+ tests, including HTTP-mocked request/response tests for every cloud client via [`wiremock`](https://docs.rs/wiremock)).
118
+ - Python: `pytest tests/` — covers `Orchestrator.run/plan/provider_ranking`, the MCP tool handlers, budget/retry configuration, load testing, and MCP connector security defaults, all without requiring live API keys.
119
+
120
+ ## Known issues
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.
123
+ - No open GitHub issues and no `TODO`/`FIXME` markers in the codebase at the time of this writing.
124
+
125
+ ## License
126
+
127
+ Proprietary License — free to use with explicit attribution. See [LICENSE](LICENSE).
@@ -40,3 +40,5 @@ python = ["pyo3"]
40
40
  [dev-dependencies]
41
41
  tokio = { version = "1.53", features = ["full", "test-util"] }
42
42
  tempfile = "3"
43
+ wiremock = "0.6"
44
+ serial_test = "3"
@@ -0,0 +1,86 @@
1
+ use super::{BackendKind, InferenceRequest, InferenceResult, ModelProfile, RuntimeBackend};
2
+ use crate::engines::GeminiClient;
3
+
4
+ pub struct GeminiBackend {
5
+ api_key: String,
6
+ }
7
+
8
+ impl GeminiBackend {
9
+ pub fn new(api_key: String) -> Self {
10
+ GeminiBackend { api_key }
11
+ }
12
+ }
13
+
14
+ #[async_trait::async_trait]
15
+ impl RuntimeBackend for GeminiBackend {
16
+ async fn infer(&self, request: InferenceRequest) -> crate::Result<InferenceResult> {
17
+ let start = std::time::Instant::now();
18
+ let client = GeminiClient::new(self.api_key.clone(), request.model.clone());
19
+
20
+ let response = client.complete(&request.prompt, request.max_tokens).await?;
21
+
22
+ let latency_ms = start.elapsed().as_millis() as u64;
23
+
24
+ Ok(InferenceResult {
25
+ output: response.text,
26
+ tokens_used: response.tokens_used,
27
+ latency_ms,
28
+ })
29
+ }
30
+
31
+ fn estimate_cost(&self, profile: &ModelProfile) -> f32 {
32
+ // Gemini Flash-tier pricing is roughly in the same band as
33
+ // OpenAI's smaller models.
34
+ let size_gb = profile.size_gb();
35
+ if size_gb < 10.0 {
36
+ 0.0001
37
+ } else if size_gb < 30.0 {
38
+ 0.00025
39
+ } else {
40
+ 0.0005
41
+ }
42
+ }
43
+
44
+ fn estimate_latency(&self, profile: &ModelProfile) -> u64 {
45
+ let size_gb = profile.size_gb();
46
+ let base = (size_gb * 8.0) as u64;
47
+ base.clamp(80, 4000)
48
+ }
49
+
50
+ fn kind(&self) -> BackendKind {
51
+ BackendKind::Gemini
52
+ }
53
+ }
54
+
55
+ #[cfg(test)]
56
+ mod tests {
57
+ use super::*;
58
+
59
+ #[test]
60
+ fn test_gemini_backend_kind() {
61
+ let backend = GeminiBackend::new("test-key".to_string());
62
+ assert_eq!(backend.kind(), BackendKind::Gemini);
63
+ }
64
+
65
+ #[test]
66
+ fn test_gemini_estimate_cost() {
67
+ let backend = GeminiBackend::new("test-key".to_string());
68
+ let small_profile = ModelProfile::new(4 * 1_073_741_824, "gemini".to_string());
69
+ let large_profile = ModelProfile::new(50 * 1_073_741_824, "gemini".to_string());
70
+
71
+ let small_cost = backend.estimate_cost(&small_profile);
72
+ let large_cost = backend.estimate_cost(&large_profile);
73
+
74
+ assert!(small_cost < large_cost);
75
+ }
76
+
77
+ #[test]
78
+ fn test_gemini_estimate_latency() {
79
+ let backend = GeminiBackend::new("test-key".to_string());
80
+ let profile = ModelProfile::new(8 * 1_073_741_824, "gemini".to_string());
81
+ let latency = backend.estimate_latency(&profile);
82
+
83
+ assert!(latency >= 80);
84
+ assert!(latency <= 4000);
85
+ }
86
+ }
@@ -3,20 +3,25 @@ use std::collections::HashMap;
3
3
 
4
4
  pub mod anthropic_backend;
5
5
  pub mod colibri_backend;
6
+ pub mod gemini_backend;
6
7
  pub mod ollama_backend;
7
8
  pub mod openai_backend;
8
9
  pub mod stub_backend;
10
+ pub mod vllm_backend;
9
11
 
10
12
  pub use anthropic_backend::AnthropicBackend;
11
13
  pub use colibri_backend::ColibriBackend;
14
+ pub use gemini_backend::GeminiBackend;
12
15
  pub use ollama_backend::OllamaBackend;
13
16
  pub use openai_backend::OpenAiBackend;
14
17
  pub use stub_backend::StubBackend;
18
+ pub use vllm_backend::VLlmBackend;
15
19
 
16
20
  #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
17
21
  pub enum BackendKind {
18
22
  Anthropic,
19
23
  OpenAi,
24
+ Gemini,
20
25
  Ollama,
21
26
  VLlm,
22
27
  TensorRtLlm,
@@ -29,6 +34,7 @@ impl BackendKind {
29
34
  match self {
30
35
  BackendKind::Anthropic => "anthropic",
31
36
  BackendKind::OpenAi => "openai",
37
+ BackendKind::Gemini => "gemini",
32
38
  BackendKind::Ollama => "ollama",
33
39
  BackendKind::VLlm => "vllm",
34
40
  BackendKind::TensorRtLlm => "tensorrt_llm",
@@ -99,8 +105,9 @@ pub trait RuntimeBackend: Send + Sync {
99
105
  pub enum AnyBackend {
100
106
  Anthropic(AnthropicBackend),
101
107
  OpenAi(OpenAiBackend),
108
+ Gemini(GeminiBackend),
102
109
  Ollama(OllamaBackend),
103
- VLlm(StubBackend),
110
+ VLlm(VLlmBackend),
104
111
  TensorRtLlm(StubBackend),
105
112
  MlcLlm(StubBackend),
106
113
  Colibri(ColibriBackend),
@@ -111,6 +118,7 @@ impl AnyBackend {
111
118
  match self {
112
119
  AnyBackend::Anthropic(b) => b.infer(request).await,
113
120
  AnyBackend::OpenAi(b) => b.infer(request).await,
121
+ AnyBackend::Gemini(b) => b.infer(request).await,
114
122
  AnyBackend::Ollama(b) => b.infer(request).await,
115
123
  AnyBackend::VLlm(b) => b.infer(request).await,
116
124
  AnyBackend::TensorRtLlm(b) => b.infer(request).await,
@@ -123,6 +131,7 @@ impl AnyBackend {
123
131
  match self {
124
132
  AnyBackend::Anthropic(b) => b.estimate_cost(profile),
125
133
  AnyBackend::OpenAi(b) => b.estimate_cost(profile),
134
+ AnyBackend::Gemini(b) => b.estimate_cost(profile),
126
135
  AnyBackend::Ollama(b) => b.estimate_cost(profile),
127
136
  AnyBackend::VLlm(b) => b.estimate_cost(profile),
128
137
  AnyBackend::TensorRtLlm(b) => b.estimate_cost(profile),
@@ -135,6 +144,7 @@ impl AnyBackend {
135
144
  match self {
136
145
  AnyBackend::Anthropic(b) => b.estimate_latency(profile),
137
146
  AnyBackend::OpenAi(b) => b.estimate_latency(profile),
147
+ AnyBackend::Gemini(b) => b.estimate_latency(profile),
138
148
  AnyBackend::Ollama(b) => b.estimate_latency(profile),
139
149
  AnyBackend::VLlm(b) => b.estimate_latency(profile),
140
150
  AnyBackend::TensorRtLlm(b) => b.estimate_latency(profile),
@@ -147,6 +157,7 @@ impl AnyBackend {
147
157
  match self {
148
158
  AnyBackend::Anthropic(b) => b.kind(),
149
159
  AnyBackend::OpenAi(b) => b.kind(),
160
+ AnyBackend::Gemini(b) => b.kind(),
150
161
  AnyBackend::Ollama(b) => b.kind(),
151
162
  AnyBackend::VLlm(b) => b.kind(),
152
163
  AnyBackend::TensorRtLlm(b) => b.kind(),