vulcan-core 1.2.1__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.
- vulcan_core/__init__.py +45 -0
- vulcan_core/actions.py +31 -0
- vulcan_core/ast_utils.py +506 -0
- vulcan_core/conditions.py +432 -0
- vulcan_core/engine.py +287 -0
- vulcan_core/models.py +271 -0
- vulcan_core/reporting.py +595 -0
- vulcan_core/util.py +127 -0
- vulcan_core-1.2.1.dist-info/METADATA +88 -0
- vulcan_core-1.2.1.dist-info/RECORD +11 -0
- vulcan_core-1.2.1.dist-info/WHEEL +4 -0
vulcan_core/util.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2025 Latchfield Technologies http://latchfield.com
|
|
3
|
+
|
|
4
|
+
import functools
|
|
5
|
+
from collections.abc import Callable, Iterator
|
|
6
|
+
from contextlib import AbstractContextManager
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from typing import Any, NoReturn
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class WithContext:
|
|
14
|
+
"""Applies a context manager as a decorator.
|
|
15
|
+
|
|
16
|
+
@WithContext(suppress(Exception))
|
|
17
|
+
def foo():
|
|
18
|
+
raise Exception("Some Exception")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
context: AbstractContextManager
|
|
22
|
+
|
|
23
|
+
def __call__(self, func):
|
|
24
|
+
@wraps(func)
|
|
25
|
+
def wrapper(*args, **kwargs):
|
|
26
|
+
with self.context:
|
|
27
|
+
return func(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
return wrapper
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def not_implemented(func) -> Callable:
|
|
33
|
+
@functools.wraps(func)
|
|
34
|
+
def wrapper(*args, **kwargs) -> NoReturn:
|
|
35
|
+
msg = f"{func.__name__} is not implemented."
|
|
36
|
+
raise NotImplementedError(msg)
|
|
37
|
+
|
|
38
|
+
return wrapper
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_private(key: str) -> bool:
|
|
42
|
+
return key.startswith("_")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AttrDict(dict):
|
|
46
|
+
def validate(self, key: str) -> str:
|
|
47
|
+
if is_private(key):
|
|
48
|
+
msg = f"Access denied to private attribute: {key}"
|
|
49
|
+
raise KeyError(msg)
|
|
50
|
+
|
|
51
|
+
if key not in self.__annotations__:
|
|
52
|
+
raise KeyError(key)
|
|
53
|
+
|
|
54
|
+
return key
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
if type(self) is AttrDict:
|
|
58
|
+
msg = f"{AttrDict.__name__} is an abstract class that can not be directly instantiated."
|
|
59
|
+
raise TypeError(msg)
|
|
60
|
+
|
|
61
|
+
def __getitem__(self, key: str) -> Any:
|
|
62
|
+
try:
|
|
63
|
+
return getattr(self, self.validate(key))
|
|
64
|
+
except KeyError:
|
|
65
|
+
if hasattr(self, "__missing__"):
|
|
66
|
+
return self.__missing__(key) # type: ignore
|
|
67
|
+
else:
|
|
68
|
+
raise
|
|
69
|
+
|
|
70
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
71
|
+
setattr(self, self.validate(key), value)
|
|
72
|
+
|
|
73
|
+
def __iter__(self) -> Iterator[str]:
|
|
74
|
+
return (key for key in self.__annotations__ if not is_private(key))
|
|
75
|
+
|
|
76
|
+
def __reversed__(self) -> Iterator[str]:
|
|
77
|
+
return reversed(list(self))
|
|
78
|
+
|
|
79
|
+
def __len__(self) -> int:
|
|
80
|
+
return sum(1 for _ in self)
|
|
81
|
+
|
|
82
|
+
def __contains__(self, key: str) -> bool:
|
|
83
|
+
return hasattr(self, self.validate(key))
|
|
84
|
+
|
|
85
|
+
def __or__(self, other: dict) -> dict:
|
|
86
|
+
return dict(self) | other
|
|
87
|
+
|
|
88
|
+
def __repr__(self) -> str:
|
|
89
|
+
return repr(dict(self))
|
|
90
|
+
|
|
91
|
+
def keys(self) -> list[str]:
|
|
92
|
+
return list(self)
|
|
93
|
+
|
|
94
|
+
def values(self) -> list[Any]:
|
|
95
|
+
return [getattr(self, key) for key in self]
|
|
96
|
+
|
|
97
|
+
def items(self) -> list[tuple[str, Any]]:
|
|
98
|
+
return [(key, getattr(self, key)) for key in self]
|
|
99
|
+
|
|
100
|
+
def get(self, key: str, default: Any = None):
|
|
101
|
+
return getattr(self, self.validate(key), default)
|
|
102
|
+
|
|
103
|
+
def setdefault(self, key: str, default: Any = None) -> Any:
|
|
104
|
+
if key not in self:
|
|
105
|
+
self[key] = default
|
|
106
|
+
return self[key]
|
|
107
|
+
|
|
108
|
+
@not_implemented
|
|
109
|
+
def __delitem__(self, key: str) -> NoReturn: ...
|
|
110
|
+
|
|
111
|
+
@not_implemented
|
|
112
|
+
def __ior__(self, other: dict[str, Any]) -> NoReturn: ...
|
|
113
|
+
|
|
114
|
+
@not_implemented
|
|
115
|
+
def clear(self) -> NoReturn: ...
|
|
116
|
+
|
|
117
|
+
@not_implemented
|
|
118
|
+
def copy(self) -> NoReturn: ...
|
|
119
|
+
|
|
120
|
+
@not_implemented
|
|
121
|
+
def pop(self, key: str, defaul: Any = None) -> NoReturn: ...
|
|
122
|
+
|
|
123
|
+
@not_implemented
|
|
124
|
+
def popitem(self) -> NoReturn: ...
|
|
125
|
+
|
|
126
|
+
@not_implemented
|
|
127
|
+
def update(self, *args, **kwargs) -> NoReturn: ...
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vulcan-core
|
|
3
|
+
Version: 1.2.1
|
|
4
|
+
Summary: AI-Hybrid Rules Engine for Logical Reasoning.
|
|
5
|
+
Keywords: rules,logic,reasoning,ai,artificial intelligence,RAG,LLM
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
8
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Dist: pyyaml>=6.0.2,<7.0.0
|
|
11
|
+
Requires-Dist: pydantic>=2.11.7,<3.0.0
|
|
12
|
+
Requires-Dist: langchain==0.3.*
|
|
13
|
+
Requires-Dist: langchain-openai==0.3.* ; extra == 'openai'
|
|
14
|
+
Requires-Python: >=3.12, <4.0
|
|
15
|
+
Project-URL: homepage, https://latchfield.com/vulcan
|
|
16
|
+
Project-URL: repository, https://github.com/latchfield/vulcan-core
|
|
17
|
+
Project-URL: documentation, https://latchfield.com/vulcan/docs
|
|
18
|
+
Provides-Extra: openai
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
22
|
+
<!-- Copyright 2026 Latchfield Technologies http://latchfield.com -->
|
|
23
|
+
<img alt="Vulcan Logo" src="https://latchfield.com/vulcan/assets/images/vulcan-logo.svg" height="100px">
|
|
24
|
+
|
|
25
|
+
# AI-Hybrid Rules Engine for Logical Reasoning
|
|
26
|
+
[](https://pypi.org/project/vulcan-core/)
|
|
27
|
+
|
|
28
|
+
Vulcan is an AI-hybrid rules engine designed for advanced automated reasoning. It combines the power of rule-based decision systems with LLMs (Large Language Models) for improved consistency and explainability in AI-powered systems.
|
|
29
|
+
|
|
30
|
+
Learn more about Vulcan at [https://latchfield.com/vulcan](https://latchfield.com/vulcan), or jump in with:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv add vulcan-core
|
|
34
|
+
# or
|
|
35
|
+
poetry add vulcan-core
|
|
36
|
+
# or
|
|
37
|
+
pip install vulcan-core
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
To gain your bearings, read the documentation for guides and API reference: [https://latchfield.com/vulcan/docs](https://latchfield.com/vulcan/docs).
|
|
41
|
+
|
|
42
|
+
## Why use Vulcan?
|
|
43
|
+
Vulcan strives to improve AI reliability and explainability by explicitly separating computational logic from LLM prediction through declarative rules and microprompting. Vulcan provides developers with a toolkit to create, manage, and execute rules with seamless integration with LLMs and vector databases.
|
|
44
|
+
|
|
45
|
+
### Features:
|
|
46
|
+
* **AI-Hybrid Rules** - Combine deterministic logic with LLMs and vector databases
|
|
47
|
+
* **Transparent Decision-Making** - Full explainability of how decisions are made
|
|
48
|
+
* **Developer-Friendly API** - Intuitive interfaces for rule creation and management
|
|
49
|
+
* **Platform Flexibility** - Works across various environments and integrates with existing tools
|
|
50
|
+
|
|
51
|
+
### Simple Example:
|
|
52
|
+
Turn your lengthy unpredictable prompts:
|
|
53
|
+
|
|
54
|
+
> As a bakery, I want to buy 10 apples if I have less than 10 in inventory, but only if my supplier has apples used for baking in stock. Given I have 9 apples, and my supplier has "Honeycrisp", how many apples should I order?
|
|
55
|
+
|
|
56
|
+
Into repeatable, consistent, and explainable rules:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# Use natural language for prediction and data retrieval:
|
|
60
|
+
engine.rule(
|
|
61
|
+
when=condition(f"Are {Apple.kind} considered good for baking?"),
|
|
62
|
+
then=action(Apple(baking=True)),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Use computed logic for operations that must be correct:
|
|
66
|
+
engine.rule(
|
|
67
|
+
when=condition(lambda: Apple.baking and Inventory.apples < 10),
|
|
68
|
+
then=action(Order(apples=10)),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Intelligent on-demand rule evaluation:
|
|
72
|
+
engine.fact(Inventory(apples=9))
|
|
73
|
+
engine.fact(Apple(kind="Honeycrisp"))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Get Involved!
|
|
77
|
+
We welcome contributions from the community to help make Vulcan even better:
|
|
78
|
+
|
|
79
|
+
* **Contribute Code** - Check out the [contribution guidelines](https://github.com/latchfield/vulcan/blob/main/CONTRIBUTING.md) for information on how to submit pull requests
|
|
80
|
+
* **Report Issues** - Found a bug or have a feature request? Open an issue on our [GitHub repository](https://github.com/latchfield/vulcan-core/issues/)
|
|
81
|
+
* **Join the Community** - Connect with other Vulcan users and developers on [GitHub Discussions](https://github.com/latchfield/vulcan-core/discussions)
|
|
82
|
+
|
|
83
|
+
## Additional Resources
|
|
84
|
+
Learn more about Vulcan:
|
|
85
|
+
|
|
86
|
+
* [Core Concepts](https://latchfield.com/vulcan/docs/concepts) - Understand the fundamental principles of Vulcan
|
|
87
|
+
* [Guides & Tutorials](https://latchfield.com/vulcan/docs/guides/quick-start/) - Step-by-step instructions for common use cases
|
|
88
|
+
* [API Reference](https://latchfield.com/vulcan/docs/) - Detailed information about the Vulcan API
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
vulcan_core/__init__.py,sha256=pjCnmbMjrsp672WXRQeOV0aSKUEoA_mj0o7q_ouMWs8,1057
|
|
2
|
+
vulcan_core/actions.py,sha256=JeX71MOsNww234vFFJAPTY0kCz-1AhVVZFyrVArKwno,1009
|
|
3
|
+
vulcan_core/ast_utils.py,sha256=bxeouX1_KXZyODKTca49ATLwykswOOwHc0scHPnmUrs,21073
|
|
4
|
+
vulcan_core/conditions.py,sha256=jGr83f3ve6hesltWbkMRHQoeg7wxx_GOyMNYxjomRho,18794
|
|
5
|
+
vulcan_core/engine.py,sha256=W2ki0zR5NuCcxKrM8ii_1uABFAXczIM5sWUNxTJu6dY,11386
|
|
6
|
+
vulcan_core/models.py,sha256=XzeKih2WzKB6Ql_EvAeuVqulrBOmK_Of-0JivATCXaI,8972
|
|
7
|
+
vulcan_core/reporting.py,sha256=p7s5YaGchXdpOHmFLlCpdGppjvWHME5r7iW9DJvPaMQ,22660
|
|
8
|
+
vulcan_core/util.py,sha256=Uq5uWhrfWd8fNv6IeeTFZRGeLBAECPZUx63UjbbSMrA,3420
|
|
9
|
+
vulcan_core-1.2.1.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
10
|
+
vulcan_core-1.2.1.dist-info/METADATA,sha256=dCQdeOwCJCb9BIYBjRdCSKFb6bNXqkGT7OBuQzoZxbc,4273
|
|
11
|
+
vulcan_core-1.2.1.dist-info/RECORD,,
|