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.
Files changed (267) hide show
  1. kntgraph/__init__.py +33 -0
  2. kntgraph/_optional.py +156 -0
  3. kntgraph/_version.py +24 -0
  4. kntgraph/agents/__init__.py +38 -0
  5. kntgraph/agents/config/__init__.py +20 -0
  6. kntgraph/agents/config/llm.py +282 -0
  7. kntgraph/agents/knowledge/__init__.py +19 -0
  8. kntgraph/agents/knowledge/solution_projector.py +363 -0
  9. kntgraph/agents/memory/__init__.py +76 -0
  10. kntgraph/agents/memory/solution_extractor.py +202 -0
  11. kntgraph/agents/memory/solution_lookup.py +505 -0
  12. kntgraph/agents/memory/solution_promoter.py +144 -0
  13. kntgraph/agents/memory/solution_review_publisher.py +124 -0
  14. kntgraph/agents/memory/solutions/__init__.py +139 -0
  15. kntgraph/agents/memory/solutions/_bus.py +60 -0
  16. kntgraph/agents/memory/solutions/_extractor.py +532 -0
  17. kntgraph/agents/memory/solutions/_extractor_helpers.py +80 -0
  18. kntgraph/agents/memory/solutions/_fingerprints.py +123 -0
  19. kntgraph/agents/memory/solutions/_promoter.py +300 -0
  20. kntgraph/agents/memory/solutions/_promoter_helpers.py +114 -0
  21. kntgraph/agents/memory/solutions/_values.py +197 -0
  22. kntgraph/agents/role_systems/__init__.py +224 -0
  23. kntgraph/agents/role_systems/_base.py +279 -0
  24. kntgraph/agents/role_systems/_prompts.py +289 -0
  25. kntgraph/agents/role_systems/_rule_based.py +336 -0
  26. kntgraph/agents/tools/__init__.py +122 -0
  27. kntgraph/agents/tools/arg_validation.py +186 -0
  28. kntgraph/agents/tools/capability.py +62 -0
  29. kntgraph/agents/tools/llm.py +732 -0
  30. kntgraph/agents/tools/pii/__init__.py +81 -0
  31. kntgraph/agents/tools/pii/_constants.py +24 -0
  32. kntgraph/agents/tools/pii/_level1.py +56 -0
  33. kntgraph/agents/tools/pii/_level2.py +128 -0
  34. kntgraph/agents/tools/pii/_patterns.py +104 -0
  35. kntgraph/agents/tools/pii/_result.py +29 -0
  36. kntgraph/agents/tools/pii/_tool.py +180 -0
  37. kntgraph/agents/tools/protocol.py +114 -0
  38. kntgraph/api/__init__.py +23 -0
  39. kntgraph/api/_auth/_dependencies.py +170 -0
  40. kntgraph/api/_auth/_errors.py +36 -0
  41. kntgraph/api/_auth/_helpers.py +43 -0
  42. kntgraph/api/_auth/_verifier.py +196 -0
  43. kntgraph/api/auth/__init__.py +77 -0
  44. kntgraph/api/intent_router/__init__.py +87 -0
  45. kntgraph/api/intent_router/app_factory.py +193 -0
  46. kntgraph/api/intent_router/helpers.py +188 -0
  47. kntgraph/api/intent_router/middleware_setup.py +76 -0
  48. kntgraph/api/intent_router/routes.py +370 -0
  49. kntgraph/api/middleware.py +115 -0
  50. kntgraph/api/schemas.py +184 -0
  51. kntgraph/cli/__init__.py +5 -0
  52. kntgraph/cli/_templates.py +65 -0
  53. kntgraph/cli/commands/__init__.py +5 -0
  54. kntgraph/cli/commands/init.py +156 -0
  55. kntgraph/cli/commands/keys.py +65 -0
  56. kntgraph/cli/commands/new.py +329 -0
  57. kntgraph/cli/main.py +47 -0
  58. kntgraph/cli/templates/agent.py.jinja +29 -0
  59. kntgraph/cli/templates/component.py.jinja +16 -0
  60. kntgraph/cli/templates/dispatcher.py.jinja +49 -0
  61. kntgraph/cli/templates/env.example.jinja +37 -0
  62. kntgraph/cli/templates/event.py.jinja +33 -0
  63. kntgraph/cli/templates/main.py.jinja +130 -0
  64. kntgraph/cli/templates/pyproject.toml.jinja +20 -0
  65. kntgraph/cli/templates/routing/__init__.py.jinja +4 -0
  66. kntgraph/cli/templates/routing/adapters/autonomous.py.jinja +8 -0
  67. kntgraph/cli/templates/routing/adapters/collaborate.py.jinja +11 -0
  68. kntgraph/cli/templates/routing/adapters/external.py.jinja +8 -0
  69. kntgraph/cli/templates/routing/components.py.jinja +24 -0
  70. kntgraph/cli/templates/routing/coordinator.py.jinja +11 -0
  71. kntgraph/cli/templates/routing/policy.py.jinja +10 -0
  72. kntgraph/cli/templates/routing/resolution.py.jinja +16 -0
  73. kntgraph/cli/templates/system.py.jinja +25 -0
  74. kntgraph/cli/templates/tool.py.jinja +27 -0
  75. kntgraph/core/__init__.py +134 -0
  76. kntgraph/core/_typing.py +223 -0
  77. kntgraph/core/agent_id.py +126 -0
  78. kntgraph/core/archetype.py +67 -0
  79. kntgraph/core/component.py +82 -0
  80. kntgraph/core/components/__init__.py +24 -0
  81. kntgraph/core/components/memory.py +123 -0
  82. kntgraph/core/event/__init__.py +84 -0
  83. kntgraph/core/event/codec.py +114 -0
  84. kntgraph/core/event/constants.py +57 -0
  85. kntgraph/core/event/correlation.py +186 -0
  86. kntgraph/core/event/event.py +337 -0
  87. kntgraph/core/event/id_helpers.py +55 -0
  88. kntgraph/core/event/operational.py +65 -0
  89. kntgraph/core/event/validators.py +91 -0
  90. kntgraph/core/lifecycle.py +73 -0
  91. kntgraph/core/long_poll.py +112 -0
  92. kntgraph/core/result/__init__.py +60 -0
  93. kntgraph/core/result/errors.py +81 -0
  94. kntgraph/core/result/result.py +320 -0
  95. kntgraph/core/storage.py +252 -0
  96. kntgraph/core/system.py +108 -0
  97. kntgraph/core/tool_event.py +162 -0
  98. kntgraph/core/world/__init__.py +71 -0
  99. kntgraph/core/world/components.py +201 -0
  100. kntgraph/core/world/projection.py +274 -0
  101. kntgraph/core/world/projection_memory.py +620 -0
  102. kntgraph/core/world/projection_tool_calls.py +503 -0
  103. kntgraph/core/world/query.py +159 -0
  104. kntgraph/core/world/view.py +108 -0
  105. kntgraph/core/world/world.py +172 -0
  106. kntgraph/events/__init__.py +35 -0
  107. kntgraph/events/dlq/__init__.py +65 -0
  108. kntgraph/events/dlq/actions.py +203 -0
  109. kntgraph/events/dlq/store.py +284 -0
  110. kntgraph/events/dlq/values.py +148 -0
  111. kntgraph/infra/__init__.py +28 -0
  112. kntgraph/infra/checkpoint.py +244 -0
  113. kntgraph/infra/config/__init__.py +235 -0
  114. kntgraph/infra/config/_base.py +149 -0
  115. kntgraph/infra/config/_embedding.py +70 -0
  116. kntgraph/infra/config/_falkordb.py +27 -0
  117. kntgraph/infra/config/_http.py +54 -0
  118. kntgraph/infra/config/_knowledge.py +57 -0
  119. kntgraph/infra/config/_llm.py +80 -0
  120. kntgraph/infra/config/_memory.py +44 -0
  121. kntgraph/infra/config/_pii.py +33 -0
  122. kntgraph/infra/config/_redis.py +31 -0
  123. kntgraph/infra/config/_resilience.py +28 -0
  124. kntgraph/infra/config/_runner.py +22 -0
  125. kntgraph/infra/config/_streams.py +25 -0
  126. kntgraph/infra/config/_timeouts.py +25 -0
  127. kntgraph/infra/graph/__init__.py +17 -0
  128. kntgraph/infra/graph/_adapter.py +122 -0
  129. kntgraph/infra/graph/_lite_pool.py +306 -0
  130. kntgraph/infra/graph/_pool.py +183 -0
  131. kntgraph/infra/hashing.py +65 -0
  132. kntgraph/infra/http/__init__.py +38 -0
  133. kntgraph/infra/http/_client.py +109 -0
  134. kntgraph/infra/redis/__init__.py +110 -0
  135. kntgraph/infra/redis/_auth/__init__.py +28 -0
  136. kntgraph/infra/redis/_auth/_adapter.py +74 -0
  137. kntgraph/infra/redis/_auth/_cache.py +233 -0
  138. kntgraph/infra/redis/_auth/_redis.py +100 -0
  139. kntgraph/infra/redis/_checkpoint/__init__.py +24 -0
  140. kntgraph/infra/redis/_checkpoint/_adapter.py +93 -0
  141. kntgraph/infra/redis/_checkpoint/_redis.py +155 -0
  142. kntgraph/infra/redis/_client.py +197 -0
  143. kntgraph/infra/redis/_codec.py +77 -0
  144. kntgraph/infra/redis/_dlq/__init__.py +45 -0
  145. kntgraph/infra/redis/_dlq/_adapter.py +134 -0
  146. kntgraph/infra/redis/_dlq/_redis.py +379 -0
  147. kntgraph/infra/redis/_errors.py +94 -0
  148. kntgraph/infra/redis/_event_log/__init__.py +40 -0
  149. kntgraph/infra/redis/_event_log/_adapter.py +223 -0
  150. kntgraph/infra/redis/_event_log/_idempotency.py +141 -0
  151. kntgraph/infra/redis/_event_log/_keys.py +69 -0
  152. kntgraph/infra/redis/_factory.py +233 -0
  153. kntgraph/infra/redis/_memory/__init__.py +41 -0
  154. kntgraph/infra/redis/_memory/_adapter.py +135 -0
  155. kntgraph/infra/redis/_memory/_continuity.py +128 -0
  156. kntgraph/infra/redis/_memory/_profile.py +132 -0
  157. kntgraph/infra/redis/_memory/_session.py +140 -0
  158. kntgraph/infra/redis/_memory/_solution.py +407 -0
  159. kntgraph/infra/redis/_pool.py +87 -0
  160. kntgraph/infra/redis/_world_checkpoint/__init__.py +29 -0
  161. kntgraph/infra/redis/_world_checkpoint/_adapter.py +61 -0
  162. kntgraph/infra/redis/_world_checkpoint/_redis.py +98 -0
  163. kntgraph/infra/world_checkpoint.py +222 -0
  164. kntgraph/knowledge/__init__.py +48 -0
  165. kntgraph/knowledge/embedding/_client.py +110 -0
  166. kntgraph/knowledge/embedding/_ollama.py +298 -0
  167. kntgraph/knowledge/embedding/_protocol.py +170 -0
  168. kntgraph/knowledge/embedding/provider.py +56 -0
  169. kntgraph/knowledge/extraction/__init__.py +248 -0
  170. kntgraph/knowledge/extraction/_slm_facades.py +345 -0
  171. kntgraph/knowledge/extraction/argument/__init__.py +87 -0
  172. kntgraph/knowledge/extraction/argument/_coerce.py +111 -0
  173. kntgraph/knowledge/extraction/argument/_extractor.py +196 -0
  174. kntgraph/knowledge/extraction/argument/_finder.py +119 -0
  175. kntgraph/knowledge/extraction/argument/_gliner_finder.py +397 -0
  176. kntgraph/knowledge/extraction/base.py +538 -0
  177. kntgraph/knowledge/extraction/gliner.py +338 -0
  178. kntgraph/knowledge/extraction/gliner_argument.py +193 -0
  179. kntgraph/knowledge/extraction/gliner_intent.py +441 -0
  180. kntgraph/knowledge/extraction/heuristic.py +261 -0
  181. kntgraph/knowledge/falkordb/_categorize.py +91 -0
  182. kntgraph/knowledge/falkordb/_params.py +96 -0
  183. kntgraph/knowledge/falkordb/adapter.py +277 -0
  184. kntgraph/knowledge/graph/__init__.py +35 -0
  185. kntgraph/knowledge/graph/_protocol.py +140 -0
  186. kntgraph/knowledge/graph/_sub/__init__.py +108 -0
  187. kntgraph/knowledge/graph/_sub/_agent.py +138 -0
  188. kntgraph/knowledge/graph/_sub/_document.py +213 -0
  189. kntgraph/knowledge/graph/_sub/_solution/__init__.py +31 -0
  190. kntgraph/knowledge/graph/_sub/_solution/_adapter.py +366 -0
  191. kntgraph/knowledge/graph/_sub/_solution/_read_filters.py +221 -0
  192. kntgraph/knowledge/graph/_sub/_solution/_row_helpers.py +47 -0
  193. kntgraph/knowledge/graph/_sub/_tool_call.py +133 -0
  194. kntgraph/knowledge/graphrag/retriever.py +336 -0
  195. kntgraph/memory/__init__.py +110 -0
  196. kntgraph/memory/base.py +315 -0
  197. kntgraph/memory/cache_warmer.py +198 -0
  198. kntgraph/memory/consolidation.py +429 -0
  199. kntgraph/memory/continuity/__init__.py +117 -0
  200. kntgraph/memory/continuity/cache_codec.py +201 -0
  201. kntgraph/memory/continuity/fold.py +211 -0
  202. kntgraph/memory/continuity/manager.py +460 -0
  203. kntgraph/memory/continuity/pii.py +76 -0
  204. kntgraph/memory/continuity/recorders/__init__.py +36 -0
  205. kntgraph/memory/continuity/recorders/category.py +53 -0
  206. kntgraph/memory/continuity/recorders/entity.py +69 -0
  207. kntgraph/memory/continuity/recorders/tool.py +71 -0
  208. kntgraph/memory/continuity/state.py +91 -0
  209. kntgraph/memory/profile.py +560 -0
  210. kntgraph/memory/session.py +589 -0
  211. kntgraph/resilience/__init__.py +122 -0
  212. kntgraph/resilience/bulkhead.py +324 -0
  213. kntgraph/resilience/circuit_breaker.py +446 -0
  214. kntgraph/resilience/edge.py +319 -0
  215. kntgraph/resilience/fallback.py +169 -0
  216. kntgraph/resilience/rate_limit.py +410 -0
  217. kntgraph/resilience/retry.py +300 -0
  218. kntgraph/resilience/timeout.py +474 -0
  219. kntgraph/runner/__init__.py +22 -0
  220. kntgraph/runner/reactive.py +647 -0
  221. kntgraph/runner/reactive_tool_projection.py +141 -0
  222. kntgraph/runner/runner.py +159 -0
  223. kntgraph/runner/tool_call_ttl_sweeper.py +272 -0
  224. kntgraph/security/__init__.py +220 -0
  225. kntgraph/security/keys/__init__.py +92 -0
  226. kntgraph/security/keys/_crypto.py +120 -0
  227. kntgraph/security/keys/_generate.py +84 -0
  228. kntgraph/security/keys/_metadata.py +53 -0
  229. kntgraph/security/keys/_registry.py +208 -0
  230. kntgraph/security/keys/_revocation.py +34 -0
  231. kntgraph/security/keys/_types.py +125 -0
  232. kntgraph/security/principal.py +405 -0
  233. kntgraph/security/signing/__init__.py +113 -0
  234. kntgraph/security/signing/_aggregate.py +145 -0
  235. kntgraph/security/signing/_canonical.py +50 -0
  236. kntgraph/security/signing/_crypto.py +84 -0
  237. kntgraph/security/signing/_errors.py +25 -0
  238. kntgraph/security/signing/_sign.py +84 -0
  239. kntgraph/security/signing/_types.py +200 -0
  240. kntgraph/security/signing/_verify.py +132 -0
  241. kntgraph/stream/__init__.py +32 -0
  242. kntgraph/stream/event_log/__init__.py +92 -0
  243. kntgraph/stream/event_log/codec.py +144 -0
  244. kntgraph/stream/event_log/dispatch.py +100 -0
  245. kntgraph/stream/event_log/store.py +425 -0
  246. kntgraph/stream/event_log/validation.py +167 -0
  247. kntgraph/stream/projection.py +82 -0
  248. kntgraph/testing/__init__.py +21 -0
  249. kntgraph/testing/embedding.py +75 -0
  250. kntgraph/tools/__init__.py +134 -0
  251. kntgraph/tools/acl.py +124 -0
  252. kntgraph/tools/descriptors.py +48 -0
  253. kntgraph/tools/llm_transport.py +200 -0
  254. kntgraph/tools/manager.py +303 -0
  255. kntgraph/tools/protocol.py +233 -0
  256. kntgraph/tools/registry.py +182 -0
  257. kntgraph/tools/router.py +65 -0
  258. kntgraph/tools/schema.py +179 -0
  259. kntgraph/tools/system.py +112 -0
  260. kntgraph/tools/worker.py +121 -0
  261. kntgraph-0.11.0.dist-info/METADATA +426 -0
  262. kntgraph-0.11.0.dist-info/RECORD +267 -0
  263. kntgraph-0.11.0.dist-info/WHEEL +5 -0
  264. kntgraph-0.11.0.dist-info/entry_points.txt +2 -0
  265. kntgraph-0.11.0.dist-info/licenses/LICENSE +201 -0
  266. kntgraph-0.11.0.dist-info/licenses/NOTICE +34 -0
  267. 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"]