kntgraph 0.11.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.
- kntgraph/__init__.py +33 -0
- kntgraph/_optional.py +156 -0
- kntgraph/_version.py +24 -0
- kntgraph/agents/__init__.py +38 -0
- kntgraph/agents/config/__init__.py +20 -0
- kntgraph/agents/config/llm.py +282 -0
- kntgraph/agents/knowledge/__init__.py +19 -0
- kntgraph/agents/knowledge/solution_projector.py +363 -0
- kntgraph/agents/memory/__init__.py +76 -0
- kntgraph/agents/memory/solution_extractor.py +202 -0
- kntgraph/agents/memory/solution_lookup.py +505 -0
- kntgraph/agents/memory/solution_promoter.py +144 -0
- kntgraph/agents/memory/solution_review_publisher.py +124 -0
- kntgraph/agents/memory/solutions/__init__.py +139 -0
- kntgraph/agents/memory/solutions/_bus.py +60 -0
- kntgraph/agents/memory/solutions/_extractor.py +532 -0
- kntgraph/agents/memory/solutions/_extractor_helpers.py +80 -0
- kntgraph/agents/memory/solutions/_fingerprints.py +123 -0
- kntgraph/agents/memory/solutions/_promoter.py +300 -0
- kntgraph/agents/memory/solutions/_promoter_helpers.py +114 -0
- kntgraph/agents/memory/solutions/_values.py +197 -0
- kntgraph/agents/role_systems/__init__.py +224 -0
- kntgraph/agents/role_systems/_base.py +279 -0
- kntgraph/agents/role_systems/_prompts.py +289 -0
- kntgraph/agents/role_systems/_rule_based.py +336 -0
- kntgraph/agents/tools/__init__.py +122 -0
- kntgraph/agents/tools/arg_validation.py +186 -0
- kntgraph/agents/tools/capability.py +62 -0
- kntgraph/agents/tools/llm.py +732 -0
- kntgraph/agents/tools/pii/__init__.py +81 -0
- kntgraph/agents/tools/pii/_constants.py +24 -0
- kntgraph/agents/tools/pii/_level1.py +56 -0
- kntgraph/agents/tools/pii/_level2.py +128 -0
- kntgraph/agents/tools/pii/_patterns.py +104 -0
- kntgraph/agents/tools/pii/_result.py +29 -0
- kntgraph/agents/tools/pii/_tool.py +180 -0
- kntgraph/agents/tools/protocol.py +114 -0
- kntgraph/api/__init__.py +23 -0
- kntgraph/api/_auth/_dependencies.py +170 -0
- kntgraph/api/_auth/_errors.py +36 -0
- kntgraph/api/_auth/_helpers.py +43 -0
- kntgraph/api/_auth/_verifier.py +196 -0
- kntgraph/api/auth/__init__.py +77 -0
- kntgraph/api/intent_router/__init__.py +87 -0
- kntgraph/api/intent_router/app_factory.py +193 -0
- kntgraph/api/intent_router/helpers.py +188 -0
- kntgraph/api/intent_router/middleware_setup.py +76 -0
- kntgraph/api/intent_router/routes.py +370 -0
- kntgraph/api/middleware.py +115 -0
- kntgraph/api/schemas.py +184 -0
- kntgraph/cli/__init__.py +5 -0
- kntgraph/cli/_templates.py +65 -0
- kntgraph/cli/commands/__init__.py +5 -0
- kntgraph/cli/commands/init.py +156 -0
- kntgraph/cli/commands/keys.py +65 -0
- kntgraph/cli/commands/new.py +329 -0
- kntgraph/cli/main.py +47 -0
- kntgraph/cli/templates/agent.py.jinja +29 -0
- kntgraph/cli/templates/component.py.jinja +16 -0
- kntgraph/cli/templates/dispatcher.py.jinja +49 -0
- kntgraph/cli/templates/env.example.jinja +37 -0
- kntgraph/cli/templates/event.py.jinja +33 -0
- kntgraph/cli/templates/main.py.jinja +130 -0
- kntgraph/cli/templates/pyproject.toml.jinja +20 -0
- kntgraph/cli/templates/routing/__init__.py.jinja +4 -0
- kntgraph/cli/templates/routing/adapters/autonomous.py.jinja +8 -0
- kntgraph/cli/templates/routing/adapters/collaborate.py.jinja +11 -0
- kntgraph/cli/templates/routing/adapters/external.py.jinja +8 -0
- kntgraph/cli/templates/routing/components.py.jinja +24 -0
- kntgraph/cli/templates/routing/coordinator.py.jinja +11 -0
- kntgraph/cli/templates/routing/policy.py.jinja +10 -0
- kntgraph/cli/templates/routing/resolution.py.jinja +16 -0
- kntgraph/cli/templates/system.py.jinja +25 -0
- kntgraph/cli/templates/tool.py.jinja +27 -0
- kntgraph/core/__init__.py +134 -0
- kntgraph/core/_typing.py +223 -0
- kntgraph/core/agent_id.py +126 -0
- kntgraph/core/archetype.py +67 -0
- kntgraph/core/component.py +82 -0
- kntgraph/core/components/__init__.py +24 -0
- kntgraph/core/components/memory.py +123 -0
- kntgraph/core/event/__init__.py +84 -0
- kntgraph/core/event/codec.py +114 -0
- kntgraph/core/event/constants.py +57 -0
- kntgraph/core/event/correlation.py +186 -0
- kntgraph/core/event/event.py +337 -0
- kntgraph/core/event/id_helpers.py +55 -0
- kntgraph/core/event/operational.py +65 -0
- kntgraph/core/event/validators.py +91 -0
- kntgraph/core/lifecycle.py +73 -0
- kntgraph/core/long_poll.py +112 -0
- kntgraph/core/result/__init__.py +60 -0
- kntgraph/core/result/errors.py +81 -0
- kntgraph/core/result/result.py +320 -0
- kntgraph/core/storage.py +252 -0
- kntgraph/core/system.py +108 -0
- kntgraph/core/tool_event.py +162 -0
- kntgraph/core/world/__init__.py +71 -0
- kntgraph/core/world/components.py +201 -0
- kntgraph/core/world/projection.py +274 -0
- kntgraph/core/world/projection_memory.py +620 -0
- kntgraph/core/world/projection_tool_calls.py +503 -0
- kntgraph/core/world/query.py +159 -0
- kntgraph/core/world/view.py +108 -0
- kntgraph/core/world/world.py +172 -0
- kntgraph/events/__init__.py +35 -0
- kntgraph/events/dlq/__init__.py +65 -0
- kntgraph/events/dlq/actions.py +203 -0
- kntgraph/events/dlq/store.py +284 -0
- kntgraph/events/dlq/values.py +148 -0
- kntgraph/infra/__init__.py +28 -0
- kntgraph/infra/checkpoint.py +244 -0
- kntgraph/infra/config/__init__.py +235 -0
- kntgraph/infra/config/_base.py +149 -0
- kntgraph/infra/config/_embedding.py +70 -0
- kntgraph/infra/config/_falkordb.py +27 -0
- kntgraph/infra/config/_http.py +54 -0
- kntgraph/infra/config/_knowledge.py +57 -0
- kntgraph/infra/config/_llm.py +80 -0
- kntgraph/infra/config/_memory.py +44 -0
- kntgraph/infra/config/_pii.py +33 -0
- kntgraph/infra/config/_redis.py +31 -0
- kntgraph/infra/config/_resilience.py +28 -0
- kntgraph/infra/config/_runner.py +22 -0
- kntgraph/infra/config/_streams.py +25 -0
- kntgraph/infra/config/_timeouts.py +25 -0
- kntgraph/infra/graph/__init__.py +17 -0
- kntgraph/infra/graph/_adapter.py +122 -0
- kntgraph/infra/graph/_lite_pool.py +306 -0
- kntgraph/infra/graph/_pool.py +183 -0
- kntgraph/infra/hashing.py +65 -0
- kntgraph/infra/http/__init__.py +38 -0
- kntgraph/infra/http/_client.py +109 -0
- kntgraph/infra/redis/__init__.py +110 -0
- kntgraph/infra/redis/_auth/__init__.py +28 -0
- kntgraph/infra/redis/_auth/_adapter.py +74 -0
- kntgraph/infra/redis/_auth/_cache.py +233 -0
- kntgraph/infra/redis/_auth/_redis.py +100 -0
- kntgraph/infra/redis/_checkpoint/__init__.py +24 -0
- kntgraph/infra/redis/_checkpoint/_adapter.py +93 -0
- kntgraph/infra/redis/_checkpoint/_redis.py +155 -0
- kntgraph/infra/redis/_client.py +197 -0
- kntgraph/infra/redis/_codec.py +77 -0
- kntgraph/infra/redis/_dlq/__init__.py +45 -0
- kntgraph/infra/redis/_dlq/_adapter.py +134 -0
- kntgraph/infra/redis/_dlq/_redis.py +379 -0
- kntgraph/infra/redis/_errors.py +94 -0
- kntgraph/infra/redis/_event_log/__init__.py +40 -0
- kntgraph/infra/redis/_event_log/_adapter.py +223 -0
- kntgraph/infra/redis/_event_log/_idempotency.py +141 -0
- kntgraph/infra/redis/_event_log/_keys.py +69 -0
- kntgraph/infra/redis/_factory.py +233 -0
- kntgraph/infra/redis/_memory/__init__.py +41 -0
- kntgraph/infra/redis/_memory/_adapter.py +135 -0
- kntgraph/infra/redis/_memory/_continuity.py +128 -0
- kntgraph/infra/redis/_memory/_profile.py +132 -0
- kntgraph/infra/redis/_memory/_session.py +140 -0
- kntgraph/infra/redis/_memory/_solution.py +407 -0
- kntgraph/infra/redis/_pool.py +87 -0
- kntgraph/infra/redis/_world_checkpoint/__init__.py +29 -0
- kntgraph/infra/redis/_world_checkpoint/_adapter.py +61 -0
- kntgraph/infra/redis/_world_checkpoint/_redis.py +98 -0
- kntgraph/infra/world_checkpoint.py +222 -0
- kntgraph/knowledge/__init__.py +48 -0
- kntgraph/knowledge/embedding/_client.py +110 -0
- kntgraph/knowledge/embedding/_ollama.py +298 -0
- kntgraph/knowledge/embedding/_protocol.py +170 -0
- kntgraph/knowledge/embedding/provider.py +56 -0
- kntgraph/knowledge/extraction/__init__.py +248 -0
- kntgraph/knowledge/extraction/_slm_facades.py +345 -0
- kntgraph/knowledge/extraction/argument/__init__.py +87 -0
- kntgraph/knowledge/extraction/argument/_coerce.py +111 -0
- kntgraph/knowledge/extraction/argument/_extractor.py +196 -0
- kntgraph/knowledge/extraction/argument/_finder.py +119 -0
- kntgraph/knowledge/extraction/argument/_gliner_finder.py +397 -0
- kntgraph/knowledge/extraction/base.py +538 -0
- kntgraph/knowledge/extraction/gliner.py +338 -0
- kntgraph/knowledge/extraction/gliner_argument.py +193 -0
- kntgraph/knowledge/extraction/gliner_intent.py +441 -0
- kntgraph/knowledge/extraction/heuristic.py +261 -0
- kntgraph/knowledge/falkordb/_categorize.py +91 -0
- kntgraph/knowledge/falkordb/_params.py +96 -0
- kntgraph/knowledge/falkordb/adapter.py +277 -0
- kntgraph/knowledge/graph/__init__.py +35 -0
- kntgraph/knowledge/graph/_protocol.py +140 -0
- kntgraph/knowledge/graph/_sub/__init__.py +108 -0
- kntgraph/knowledge/graph/_sub/_agent.py +138 -0
- kntgraph/knowledge/graph/_sub/_document.py +213 -0
- kntgraph/knowledge/graph/_sub/_solution/__init__.py +31 -0
- kntgraph/knowledge/graph/_sub/_solution/_adapter.py +366 -0
- kntgraph/knowledge/graph/_sub/_solution/_read_filters.py +221 -0
- kntgraph/knowledge/graph/_sub/_solution/_row_helpers.py +47 -0
- kntgraph/knowledge/graph/_sub/_tool_call.py +133 -0
- kntgraph/knowledge/graphrag/retriever.py +336 -0
- kntgraph/memory/__init__.py +110 -0
- kntgraph/memory/base.py +315 -0
- kntgraph/memory/cache_warmer.py +198 -0
- kntgraph/memory/consolidation.py +429 -0
- kntgraph/memory/continuity/__init__.py +117 -0
- kntgraph/memory/continuity/cache_codec.py +201 -0
- kntgraph/memory/continuity/fold.py +211 -0
- kntgraph/memory/continuity/manager.py +460 -0
- kntgraph/memory/continuity/pii.py +76 -0
- kntgraph/memory/continuity/recorders/__init__.py +36 -0
- kntgraph/memory/continuity/recorders/category.py +53 -0
- kntgraph/memory/continuity/recorders/entity.py +69 -0
- kntgraph/memory/continuity/recorders/tool.py +71 -0
- kntgraph/memory/continuity/state.py +91 -0
- kntgraph/memory/profile.py +560 -0
- kntgraph/memory/session.py +589 -0
- kntgraph/resilience/__init__.py +122 -0
- kntgraph/resilience/bulkhead.py +324 -0
- kntgraph/resilience/circuit_breaker.py +446 -0
- kntgraph/resilience/edge.py +319 -0
- kntgraph/resilience/fallback.py +169 -0
- kntgraph/resilience/rate_limit.py +410 -0
- kntgraph/resilience/retry.py +300 -0
- kntgraph/resilience/timeout.py +474 -0
- kntgraph/runner/__init__.py +22 -0
- kntgraph/runner/reactive.py +647 -0
- kntgraph/runner/reactive_tool_projection.py +141 -0
- kntgraph/runner/runner.py +159 -0
- kntgraph/runner/tool_call_ttl_sweeper.py +272 -0
- kntgraph/security/__init__.py +220 -0
- kntgraph/security/keys/__init__.py +92 -0
- kntgraph/security/keys/_crypto.py +120 -0
- kntgraph/security/keys/_generate.py +84 -0
- kntgraph/security/keys/_metadata.py +53 -0
- kntgraph/security/keys/_registry.py +208 -0
- kntgraph/security/keys/_revocation.py +34 -0
- kntgraph/security/keys/_types.py +125 -0
- kntgraph/security/principal.py +405 -0
- kntgraph/security/signing/__init__.py +113 -0
- kntgraph/security/signing/_aggregate.py +145 -0
- kntgraph/security/signing/_canonical.py +50 -0
- kntgraph/security/signing/_crypto.py +84 -0
- kntgraph/security/signing/_errors.py +25 -0
- kntgraph/security/signing/_sign.py +84 -0
- kntgraph/security/signing/_types.py +200 -0
- kntgraph/security/signing/_verify.py +132 -0
- kntgraph/stream/__init__.py +32 -0
- kntgraph/stream/event_log/__init__.py +92 -0
- kntgraph/stream/event_log/codec.py +144 -0
- kntgraph/stream/event_log/dispatch.py +100 -0
- kntgraph/stream/event_log/store.py +425 -0
- kntgraph/stream/event_log/validation.py +167 -0
- kntgraph/stream/projection.py +82 -0
- kntgraph/testing/__init__.py +21 -0
- kntgraph/testing/embedding.py +75 -0
- kntgraph/tools/__init__.py +134 -0
- kntgraph/tools/acl.py +124 -0
- kntgraph/tools/descriptors.py +48 -0
- kntgraph/tools/llm_transport.py +200 -0
- kntgraph/tools/manager.py +303 -0
- kntgraph/tools/protocol.py +233 -0
- kntgraph/tools/registry.py +182 -0
- kntgraph/tools/router.py +65 -0
- kntgraph/tools/schema.py +179 -0
- kntgraph/tools/system.py +112 -0
- kntgraph/tools/worker.py +121 -0
- kntgraph-0.11.0.dist-info/METADATA +426 -0
- kntgraph-0.11.0.dist-info/RECORD +267 -0
- kntgraph-0.11.0.dist-info/WHEEL +5 -0
- kntgraph-0.11.0.dist-info/entry_points.txt +2 -0
- kntgraph-0.11.0.dist-info/licenses/LICENSE +201 -0
- kntgraph-0.11.0.dist-info/licenses/NOTICE +34 -0
- kntgraph-0.11.0.dist-info/top_level.txt +1 -0
kntgraph/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 kinetgraph
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
kntgraph -- public package surface.
|
|
7
|
+
|
|
8
|
+
The ``__version__`` attribute is derived from the
|
|
9
|
+
git tag by ``setuptools_scm`` (ADR-051). The import
|
|
10
|
+
is guarded so a source install without the build
|
|
11
|
+
step (e.g. a CI cache without a full git history)
|
|
12
|
+
returns the explicit ``"0.0.0+unknown"`` fallback
|
|
13
|
+
instead of raising ``AttributeError``.
|
|
14
|
+
|
|
15
|
+
The fallback uses PEP 440's local-version
|
|
16
|
+
convention (``+unknown``) so consumers can detect
|
|
17
|
+
the case programmatically via
|
|
18
|
+
``version.endswith("+unknown")``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
__all__ = ["__version__"]
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from kntgraph._version import __version__
|
|
27
|
+
except ImportError:
|
|
28
|
+
# Source install without ``setuptools_scm``
|
|
29
|
+
# having run (no git history, no tag). The
|
|
30
|
+
# version is unknown; downstream code that
|
|
31
|
+
# relies on a real version should fall back
|
|
32
|
+
# gracefully rather than crash.
|
|
33
|
+
__version__ = "0.0.0+unknown"
|
kntgraph/_optional.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 kinetgraph
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
_optional — single source of truth for lazy/guarded imports
|
|
7
|
+
of optional dependencies.
|
|
8
|
+
|
|
9
|
+
Background
|
|
10
|
+
----------
|
|
11
|
+
The `kntgraph` package declares several third-party packages
|
|
12
|
+
as optional extras (``falkordb``, ``ollama``, ``gliner2``,
|
|
13
|
+
``fastapi``, ``litellm``, …). The framework's own modules
|
|
14
|
+
should be **importable** even when none of those extras are
|
|
15
|
+
installed, so that:
|
|
16
|
+
|
|
17
|
+
- applications that only need the core ECS + EventLog +
|
|
18
|
+
Memory can install only ``kntgraph`` (no extras) and
|
|
19
|
+
still ``import kntgraph`` without errors;
|
|
20
|
+
- tests can stub the optional deps via sys.meta_path
|
|
21
|
+
blockers without ``ImportError`` at collection time;
|
|
22
|
+
- the ``agents`` sub-module does not have to
|
|
23
|
+
transitively pull in ``falkordb`` + ``ollama`` just
|
|
24
|
+
because someone imports ``kntgraph.agents.roles``.
|
|
25
|
+
|
|
26
|
+
Each module that uses an optional dep uses one of two
|
|
27
|
+
patterns:
|
|
28
|
+
|
|
29
|
+
1. **Lazy inside a method** (preferred for runtime-only
|
|
30
|
+
use, e.g. ``litellm`` in a Tool's ``invoke()``):
|
|
31
|
+
|
|
32
|
+
.. code-block:: python
|
|
33
|
+
|
|
34
|
+
async def invoke(self, **kwargs):
|
|
35
|
+
litellm = require_optional("litellm", "kntgraph[llm]")
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
2. **Top-level TYPE_CHECKING-only + eager runtime import
|
|
39
|
+
behind a guard** (for adapters that are importable but
|
|
40
|
+
not constructible without the extra, e.g.
|
|
41
|
+
``GlinerIntentAdapter``):
|
|
42
|
+
|
|
43
|
+
.. code-block:: python
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from gliner2 import GLiNER2 # noqa: F401
|
|
47
|
+
|
|
48
|
+
class GlinerIntentAdapter:
|
|
49
|
+
def __init__(self, ...):
|
|
50
|
+
GLiNER2 = require_optional(
|
|
51
|
+
"gliner2", "kntgraph[gliner]",
|
|
52
|
+
purpose="GlinerIntentAdapter",
|
|
53
|
+
)
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
The helper
|
|
57
|
+
----------
|
|
58
|
+
:func:`require_optional` raises ``ImportError`` with a
|
|
59
|
+
canonical message that always points the user at the
|
|
60
|
+
correct extra to install. Tests assert on the message
|
|
61
|
+
text so accidental rewording is caught.
|
|
62
|
+
|
|
63
|
+
:func:`try_import` is the non-raising variant — returns
|
|
64
|
+
``None`` instead. Useful for capability checks (e.g. "is
|
|
65
|
+
``fastapi`` available?" before exposing an HTTP route).
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
from __future__ import annotations
|
|
69
|
+
|
|
70
|
+
import importlib
|
|
71
|
+
from types import ModuleType
|
|
72
|
+
from typing import Optional
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _format_message(
|
|
76
|
+
package: str,
|
|
77
|
+
extra: str,
|
|
78
|
+
purpose: Optional[str],
|
|
79
|
+
) -> str:
|
|
80
|
+
"""
|
|
81
|
+
Build the canonical ImportError message. Kept in one
|
|
82
|
+
place so the wording is consistent across the
|
|
83
|
+
codebase and tests can pin it.
|
|
84
|
+
"""
|
|
85
|
+
where = purpose or "this feature"
|
|
86
|
+
return (
|
|
87
|
+
f"{where} requires the optional package "
|
|
88
|
+
f"`{package}`, which is not installed.\n"
|
|
89
|
+
f"Install it with one of:\n"
|
|
90
|
+
f" uv add {extra}\n"
|
|
91
|
+
f" pip install {extra}\n"
|
|
92
|
+
f"See the `[project.optional-dependencies]` table "
|
|
93
|
+
f"in the package's `pyproject.toml` for the full list "
|
|
94
|
+
f"of extras."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def require_optional(
|
|
99
|
+
package: str,
|
|
100
|
+
extra: str,
|
|
101
|
+
*,
|
|
102
|
+
purpose: Optional[str] = None,
|
|
103
|
+
) -> ModuleType:
|
|
104
|
+
"""
|
|
105
|
+
Import ``package`` or raise ``ImportError`` with a
|
|
106
|
+
message that points to the right ``extra``.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
package: the PyPI distribution name (the string
|
|
110
|
+
passed to ``importlib.import_module``).
|
|
111
|
+
extra: the install extra that provides it, e.g.
|
|
112
|
+
``"kntgraph[gliner]"`` or ``"kntgraph[llm]"``.
|
|
113
|
+
purpose: short human description of what was being
|
|
114
|
+
attempted, for the error message. Defaults to
|
|
115
|
+
"this feature".
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The imported module.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ImportError: with the canonical message.
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
return importlib.import_module(package)
|
|
125
|
+
except ImportError as e:
|
|
126
|
+
raise ImportError(_format_message(package, extra, purpose)) from e
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def try_import(
|
|
130
|
+
package: str,
|
|
131
|
+
extra: Optional[str] = None,
|
|
132
|
+
) -> Optional[ModuleType]:
|
|
133
|
+
"""
|
|
134
|
+
Best-effort import — returns ``None`` instead of
|
|
135
|
+
raising. Useful for capability checks where the
|
|
136
|
+
caller wants to branch on availability rather than
|
|
137
|
+
handle an exception.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
package: the PyPI distribution name.
|
|
141
|
+
extra: kept for API symmetry with
|
|
142
|
+
:func:`require_optional`; not used in the return
|
|
143
|
+
path because no error is raised. Provided so
|
|
144
|
+
callers that switch between the two helpers do
|
|
145
|
+
not have to drop the argument.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The imported module, or ``None`` if not installed.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
return importlib.import_module(package)
|
|
152
|
+
except ImportError:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
__all__ = ["require_optional", "try_import"]
|
kntgraph/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.11.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 11, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'g609830e8d'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 kinetgraph
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
kntgraph.agents — vertical agents sobre o framework FMH.
|
|
7
|
+
|
|
8
|
+
Esta package provê:
|
|
9
|
+
|
|
10
|
+
- tools/ : adapters concretos (LiteLLM, ...)
|
|
11
|
+
- roles/ : especializações semânticas que usam tools
|
|
12
|
+
(Planner, Summarizer, Classifier, ...)
|
|
13
|
+
- config/ : configuração carregada de env (modelos, budgets, ...)
|
|
14
|
+
- examples/ : scripts demonstrativos (estudo de APIs)
|
|
15
|
+
|
|
16
|
+
Convenções
|
|
17
|
+
----------
|
|
18
|
+
|
|
19
|
+
- **Tool** = 1 capability de I/O. Vive em `tools/`. Registrável
|
|
20
|
+
no `ToolRegistry`. Implementa o Protocol de
|
|
21
|
+
`kntgraph.agents.tools.protocol.Tool`.
|
|
22
|
+
- **Role** = especialização semântica. Vive em `roles/`. Não é
|
|
23
|
+
Tool — usa uma Tool por injeção. Conhece prompt do domínio e
|
|
24
|
+
schema de saída.
|
|
25
|
+
|
|
26
|
+
- **agent_id**: a aplicação define. Para NF, o número da NF. Para
|
|
27
|
+
sessão, "session:<id>". Para Empresa, CNPJ. O framework é
|
|
28
|
+
agnóstico.
|
|
29
|
+
|
|
30
|
+
- **idempotency_key**: toda Tool recebe
|
|
31
|
+
`idempotency_key=str(request.event_id)`. Roles devem
|
|
32
|
+
repassar essa chave (ou construir uma estável) ao chamar
|
|
33
|
+
a Tool.
|
|
34
|
+
|
|
35
|
+
Veja:
|
|
36
|
+
- ADR-006: separação Tool × Role
|
|
37
|
+
- ADR-007: LLM via LiteLLM
|
|
38
|
+
"""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 kinetgraph
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Configuration primitives for ``kntgraph.agents``.
|
|
6
|
+
|
|
7
|
+
The ``RateLimiter`` is re-exported from
|
|
8
|
+
``kntgraph.resilience.rate_limit`` (the shared
|
|
9
|
+
sliding-window primitive; was previously in the
|
|
10
|
+
standalone ``fmh_core`` package). The ``CostBudget``
|
|
11
|
+
is kntgraph.agents-specific (LLM spending semantics) and
|
|
12
|
+
lives in ``.llm``. ``LLMConfig`` is the main entry point
|
|
13
|
+
for LLM tool configuration.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from kntgraph.resilience.rate_limit import RateLimiter
|
|
17
|
+
|
|
18
|
+
from .llm import CostBudget, LLMConfig, load_env
|
|
19
|
+
|
|
20
|
+
__all__ = ["CostBudget", "LLMConfig", "RateLimiter", "load_env"]
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 kinetgraph
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
LLM configuration primitives.
|
|
7
|
+
|
|
8
|
+
`LLMConfig` carrega o setup do LLM a partir de env vars
|
|
9
|
+
ou dicionário explícito. Encapsula:
|
|
10
|
+
|
|
11
|
+
- `default_model`: o modelo primário (ex: "gpt-4o-mini")
|
|
12
|
+
- `fallback_models`: lista de modelos para tentar em sequência
|
|
13
|
+
se o primário falhar (rate limit, 5xx, ...)
|
|
14
|
+
- `rate_limit_rpm`: requests por minuto (None = sem limite)
|
|
15
|
+
- `cost_budget_per_hour_usd`: limite de gasto por hora
|
|
16
|
+
(None = sem limite)
|
|
17
|
+
- `timeout_s`: timeout por chamada
|
|
18
|
+
|
|
19
|
+
`RateLimiter` e `CostBudget` são wrappers async com janela
|
|
20
|
+
deslizante. São úteis para adapters que querem aplicar
|
|
21
|
+
limites em frente ao ``LiteLLMToolWorker`` (e.g. um
|
|
22
|
+
``LiteLLMTransportAdapter`` custom que consulta o budget
|
|
23
|
+
antes de encaminhar a chamada).
|
|
24
|
+
|
|
25
|
+
Uso típico:
|
|
26
|
+
|
|
27
|
+
from kntgraph.agents.config import LLMConfig
|
|
28
|
+
from kntgraph.agents.tools.llm import LiteLLMToolWorker
|
|
29
|
+
|
|
30
|
+
cfg = LLMConfig.from_env() # lê OPENAI_API_KEY etc
|
|
31
|
+
worker = LiteLLMToolWorker() # lê o config do env
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import asyncio
|
|
37
|
+
import os
|
|
38
|
+
import time
|
|
39
|
+
from collections import deque
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Optional
|
|
43
|
+
|
|
44
|
+
from kntgraph.infra.config import (
|
|
45
|
+
BaseSettings,
|
|
46
|
+
load_dotenv_files,
|
|
47
|
+
default_dotenv_candidates,
|
|
48
|
+
)
|
|
49
|
+
from kntgraph.resilience.rate_limit import (
|
|
50
|
+
RateLimiter,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_env(dotenv_path: Optional[Path] = None) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Load environment variables from a `.env` file. Returns
|
|
57
|
+
True if a file was found and loaded, False otherwise.
|
|
58
|
+
|
|
59
|
+
Lookup order:
|
|
60
|
+
1. `dotenv_path` argument (if given).
|
|
61
|
+
2. `default_dotenv_candidates()`: `<cwd>/.env`
|
|
62
|
+
then `~/.env`.
|
|
63
|
+
|
|
64
|
+
Variables already in `os.environ` are NOT overwritten
|
|
65
|
+
(the explicit env wins over the file — `override=False`
|
|
66
|
+
semantics in `python-dotenv`).
|
|
67
|
+
|
|
68
|
+
The implementation is now a thin wrapper around
|
|
69
|
+
`fmh_core.config.load_dotenv_files`, which is the
|
|
70
|
+
canonical env-loader for the whole workspace.
|
|
71
|
+
"""
|
|
72
|
+
if dotenv_path is not None:
|
|
73
|
+
return bool(load_dotenv_files(dotenv_path))
|
|
74
|
+
return bool(load_dotenv_files(*default_dotenv_candidates()))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# -----------------------------------------------------------------------------
|
|
78
|
+
# LLMConfig
|
|
79
|
+
# -----------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class _LLMSettings(BaseSettings):
|
|
83
|
+
"""
|
|
84
|
+
Internal Pydantic-settings wrapper that reads the
|
|
85
|
+
`KNT_LLM_*` env vars and coerces their types (int /
|
|
86
|
+
float / CSV tuple) before they land in the frozen
|
|
87
|
+
`LLMConfig` dataclass.
|
|
88
|
+
|
|
89
|
+
The `_env_prefix` is passed positionally at construction
|
|
90
|
+
by `LLMConfig.from_env(prefix=...)`; Pydantic settings
|
|
91
|
+
honour the `env_prefix` attribute on `model_config`.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
model_config = BaseSettings.model_config | {
|
|
95
|
+
"env_prefix": "KNT_LLM_",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
def __init__(self, *, _env_prefix: str = "KNT_LLM_", **data) -> None:
|
|
99
|
+
# `model_config` is class-level; per-instance
|
|
100
|
+
# overrides are not supported. We accept the prefix
|
|
101
|
+
# argument for API symmetry with
|
|
102
|
+
# `LLMConfig.from_env(prefix=...)` but the actual
|
|
103
|
+
# env-var lookup is governed by the class-level
|
|
104
|
+
# `env_prefix`. Non-default prefixes (e.g. in
|
|
105
|
+
# tests) are surfaced as a typed warning so
|
|
106
|
+
# operators don't get silent wrong-var lookups.
|
|
107
|
+
if _env_prefix != "KNT_LLM_":
|
|
108
|
+
import warnings
|
|
109
|
+
|
|
110
|
+
warnings.warn(
|
|
111
|
+
f"_LLMSettings only honours the KNT_LLM_ "
|
|
112
|
+
f"prefix; requested {_env_prefix!r} is "
|
|
113
|
+
f"ignored.",
|
|
114
|
+
UserWarning,
|
|
115
|
+
stacklevel=2,
|
|
116
|
+
)
|
|
117
|
+
super().__init__(**data)
|
|
118
|
+
|
|
119
|
+
default_model: Optional[str] = None
|
|
120
|
+
fallback_models: tuple[str, ...] = ()
|
|
121
|
+
rate_limit_rpm: Optional[int] = None
|
|
122
|
+
cost_budget_per_hour_usd: Optional[float] = None
|
|
123
|
+
timeout_s: float = 30.0
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(frozen=True)
|
|
127
|
+
class LLMConfig:
|
|
128
|
+
"""
|
|
129
|
+
Configuração imutável para o ``LiteLLMToolWorker`` (ou
|
|
130
|
+
qualquer outro ``@tool_worker`` que use o transporte
|
|
131
|
+
LiteLLM).
|
|
132
|
+
|
|
133
|
+
Carregue de env via `LLMConfig.from_env()` ou construa
|
|
134
|
+
explicitamente. O `__post_init__` valida invariantes
|
|
135
|
+
básicas (modelo não-vazio, fallback é lista, etc).
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
default_model: str = "gpt-4o-mini"
|
|
139
|
+
fallback_models: tuple[str, ...] = ()
|
|
140
|
+
rate_limit_rpm: Optional[int] = 60
|
|
141
|
+
cost_budget_per_hour_usd: Optional[float] = 2.0
|
|
142
|
+
timeout_s: float = 30.0
|
|
143
|
+
# LiteLLM drop_params=True: silently drop unsupported params
|
|
144
|
+
# (e.g. response_format para modelos que não suportam). Útil
|
|
145
|
+
# em multi-provider onde nem toda feature está disponível.
|
|
146
|
+
drop_unsupported_params: bool = True
|
|
147
|
+
|
|
148
|
+
def __post_init__(self) -> None:
|
|
149
|
+
if not self.default_model:
|
|
150
|
+
raise ValueError("default_model must be non-empty")
|
|
151
|
+
# Coerce fallback_models to tuple regardless of input
|
|
152
|
+
# (list, tuple, or None — all accepted at construction).
|
|
153
|
+
if not isinstance(self.fallback_models, tuple):
|
|
154
|
+
object.__setattr__(self, "fallback_models", tuple(self.fallback_models))
|
|
155
|
+
if self.rate_limit_rpm is not None and self.rate_limit_rpm <= 0:
|
|
156
|
+
raise ValueError(f"rate_limit_rpm must be > 0, got {self.rate_limit_rpm}")
|
|
157
|
+
if (
|
|
158
|
+
self.cost_budget_per_hour_usd is not None
|
|
159
|
+
and self.cost_budget_per_hour_usd <= 0
|
|
160
|
+
):
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"cost_budget_per_hour_usd must be > 0, "
|
|
163
|
+
f"got {self.cost_budget_per_hour_usd}"
|
|
164
|
+
)
|
|
165
|
+
if self.timeout_s <= 0:
|
|
166
|
+
raise ValueError(f"timeout_s must be > 0, got {self.timeout_s}")
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_env(cls, prefix: str = "KNT_LLM_") -> "LLMConfig":
|
|
170
|
+
"""
|
|
171
|
+
Carrega configuração de variáveis de ambiente.
|
|
172
|
+
|
|
173
|
+
Variáveis lidas (todas opcionais):
|
|
174
|
+
- <prefix>DEFAULT_MODEL
|
|
175
|
+
- <prefix>FALLBACK_MODELS (CSV)
|
|
176
|
+
- <prefix>RATE_LIMIT_RPM
|
|
177
|
+
- <prefix>COST_BUDGET_USD
|
|
178
|
+
- <prefix>TIMEOUT_S
|
|
179
|
+
|
|
180
|
+
Variáveis de provider (lidas mas não consumidas por
|
|
181
|
+
LLMConfig — propagadas para LiteLLM):
|
|
182
|
+
- OLLAMA_API_BASE
|
|
183
|
+
- OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.
|
|
184
|
+
|
|
185
|
+
Chamada típica: `LLMConfig.from_env()` no startup.
|
|
186
|
+
|
|
187
|
+
Internamente this delegates env-reading to
|
|
188
|
+
`LLMSettings` (a `BaseSettings` from
|
|
189
|
+
`kntgraph.infra.config`)
|
|
190
|
+
so the prefix is honoured and types are coerced
|
|
191
|
+
through Pydantic; the dataclass `LLMConfig` is the
|
|
192
|
+
frozen result. We deliberately keep two layers
|
|
193
|
+
because `LLMConfig` is a frozen dataclass used in
|
|
194
|
+
hot paths where allocating a Pydantic model would
|
|
195
|
+
be overkill.
|
|
196
|
+
"""
|
|
197
|
+
# Confirm provider endpoints are visible to
|
|
198
|
+
# LiteLLM (which reads them from os.environ). This
|
|
199
|
+
# is a no-op for the values themselves; it just
|
|
200
|
+
# documents the contract.
|
|
201
|
+
for provider_var in (
|
|
202
|
+
"OLLAMA_API_BASE",
|
|
203
|
+
"OPENAI_API_BASE",
|
|
204
|
+
"OPENAI_API_KEY",
|
|
205
|
+
"ANTHROPIC_API_KEY",
|
|
206
|
+
"GEMINI_API_KEY",
|
|
207
|
+
):
|
|
208
|
+
_ = os.environ.get(provider_var)
|
|
209
|
+
env = _LLMSettings(_env_prefix=prefix)
|
|
210
|
+
return cls(
|
|
211
|
+
default_model=env.default_model or "gpt-4o-mini",
|
|
212
|
+
fallback_models=tuple(env.fallback_models or ()),
|
|
213
|
+
rate_limit_rpm=env.rate_limit_rpm,
|
|
214
|
+
cost_budget_per_hour_usd=env.cost_budget_per_hour_usd,
|
|
215
|
+
timeout_s=env.timeout_s,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def rate_limiter(self) -> Optional["RateLimiter"]:
|
|
219
|
+
if self.rate_limit_rpm is None:
|
|
220
|
+
return None
|
|
221
|
+
return RateLimiter(rpm=self.rate_limit_rpm)
|
|
222
|
+
|
|
223
|
+
def cost_budget(self) -> Optional["CostBudget"]:
|
|
224
|
+
if self.cost_budget_per_hour_usd is None:
|
|
225
|
+
return None
|
|
226
|
+
return CostBudget(per_hour_usd=self.cost_budget_per_hour_usd)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# -----------------------------------------------------------------------------
|
|
230
|
+
# CostBudget
|
|
231
|
+
# -----------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class CostBudget:
|
|
235
|
+
"""
|
|
236
|
+
Budget de gasto (USD) por hora, sliding-window.
|
|
237
|
+
|
|
238
|
+
Mantém uma fila de (timestamp, cost_usd). Antes de cada
|
|
239
|
+
chamada, o caller pergunta `can_spend(estimated_cost)`.
|
|
240
|
+
Depois, chama `charge(actual_cost)` para debitar.
|
|
241
|
+
|
|
242
|
+
`estimated_cost` permite recusar uma chamada cara antes
|
|
243
|
+
de incorrer no gasto (ex: prompt muito longo).
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
def __init__(self, per_hour_usd: float) -> None:
|
|
247
|
+
if per_hour_usd <= 0:
|
|
248
|
+
raise ValueError(f"per_hour_usd must be > 0, got {per_hour_usd}")
|
|
249
|
+
self._per_hour = per_hour_usd
|
|
250
|
+
self._window_s = 3600.0
|
|
251
|
+
self._entries: deque[tuple[float, float]] = deque()
|
|
252
|
+
self._lock = asyncio.Lock()
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def per_hour_usd(self) -> float:
|
|
256
|
+
return self._per_hour
|
|
257
|
+
|
|
258
|
+
async def _spent_in_window(self) -> float:
|
|
259
|
+
now = time.monotonic()
|
|
260
|
+
while self._entries and (now - self._entries[0][0] > self._window_s):
|
|
261
|
+
self._entries.popleft()
|
|
262
|
+
return sum(c for _, c in self._entries)
|
|
263
|
+
|
|
264
|
+
async def can_spend(self, estimated_cost_usd: float) -> bool:
|
|
265
|
+
if estimated_cost_usd < 0:
|
|
266
|
+
raise ValueError(
|
|
267
|
+
f"estimated_cost_usd must be >= 0, got {estimated_cost_usd}"
|
|
268
|
+
)
|
|
269
|
+
async with self._lock:
|
|
270
|
+
spent = await self._spent_in_window()
|
|
271
|
+
return spent + estimated_cost_usd <= self._per_hour
|
|
272
|
+
|
|
273
|
+
async def charge(self, cost_usd: float) -> None:
|
|
274
|
+
if cost_usd < 0:
|
|
275
|
+
raise ValueError(f"cost_usd must be >= 0, got {cost_usd}")
|
|
276
|
+
async with self._lock:
|
|
277
|
+
self._entries.append((time.monotonic(), cost_usd))
|
|
278
|
+
|
|
279
|
+
async def remaining_usd(self) -> float:
|
|
280
|
+
async with self._lock:
|
|
281
|
+
spent = await self._spent_in_window()
|
|
282
|
+
return max(0.0, self._per_hour - spent)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 kinetgraph
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
kntgraph.agents.knowledge -- Vertical knowledge-graph adapters.
|
|
7
|
+
|
|
8
|
+
Re-exports the :class:`SolutionProjector` (the FalkorDB
|
|
9
|
+
adapter for the Solution sub-graph, ADR-010 §3). The
|
|
10
|
+
framework exposes only the generic knowledge primitives
|
|
11
|
+
(FalkorDB client, embedding provider Protocol); the
|
|
12
|
+
Solution-specific adapter lives here because the schema
|
|
13
|
+
is a vertical product choice.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from kntgraph.agents.knowledge.solution_projector import SolutionProjector
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["SolutionProjector"]
|