okf-agents 0.1.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.
- okf_agents-0.1.0/.gitignore +29 -0
- okf_agents-0.1.0/CHANGELOG.md +39 -0
- okf_agents-0.1.0/LICENSE +21 -0
- okf_agents-0.1.0/PKG-INFO +456 -0
- okf_agents-0.1.0/README.md +405 -0
- okf_agents-0.1.0/docs/api-reference.md +121 -0
- okf_agents-0.1.0/docs/concepts.md +127 -0
- okf_agents-0.1.0/docs/launch-checklist.md +71 -0
- okf_agents-0.1.0/docs/navigator-and-budgets.md +106 -0
- okf_agents-0.1.0/docs/testing.md +76 -0
- okf_agents-0.1.0/docs/vector-stores.md +108 -0
- okf_agents-0.1.0/okf_agents/__init__.py +39 -0
- okf_agents-0.1.0/okf_agents/_internal/__init__.py +1 -0
- okf_agents-0.1.0/okf_agents/_internal/graph_utils.py +86 -0
- okf_agents-0.1.0/okf_agents/_internal/parser.py +285 -0
- okf_agents-0.1.0/okf_agents/bundle.py +288 -0
- okf_agents-0.1.0/okf_agents/exceptions.py +66 -0
- okf_agents-0.1.0/okf_agents/indexing.py +172 -0
- okf_agents-0.1.0/okf_agents/models.py +106 -0
- okf_agents-0.1.0/okf_agents/navigator.py +407 -0
- okf_agents-0.1.0/okf_agents/py.typed +0 -0
- okf_agents-0.1.0/okf_agents/retriever.py +157 -0
- okf_agents-0.1.0/okf_agents/router.py +120 -0
- okf_agents-0.1.0/okf_agents/tools.py +228 -0
- okf_agents-0.1.0/pyproject.toml +127 -0
- okf_agents-0.1.0/tests/__init__.py +0 -0
- okf_agents-0.1.0/tests/conftest.py +23 -0
- okf_agents-0.1.0/tests/e2e/__init__.py +0 -0
- okf_agents-0.1.0/tests/e2e/conftest.py +25 -0
- okf_agents-0.1.0/tests/e2e/test_full_workflow.py +119 -0
- okf_agents-0.1.0/tests/fixtures/sample_bundle/concepts/customers.md +13 -0
- okf_agents-0.1.0/tests/fixtures/sample_bundle/concepts/index.md +4 -0
- okf_agents-0.1.0/tests/fixtures/sample_bundle/concepts/orders.md +22 -0
- okf_agents-0.1.0/tests/fixtures/sample_bundle/concepts/payments.md +13 -0
- okf_agents-0.1.0/tests/fixtures/sample_bundle/guides/getting-started.md +10 -0
- okf_agents-0.1.0/tests/fixtures/sample_bundle/index.md +7 -0
- okf_agents-0.1.0/tests/fixtures/sample_bundle/log.md +3 -0
- okf_agents-0.1.0/tests/integration/__init__.py +0 -0
- okf_agents-0.1.0/tests/integration/conftest.py +144 -0
- okf_agents-0.1.0/tests/integration/test_hybrid_retriever.py +188 -0
- okf_agents-0.1.0/tests/integration/test_navigator.py +107 -0
- okf_agents-0.1.0/tests/provider_support.py +85 -0
- okf_agents-0.1.0/tests/unit/__init__.py +0 -0
- okf_agents-0.1.0/tests/unit/test_bundle.py +272 -0
- okf_agents-0.1.0/tests/unit/test_exceptions.py +82 -0
- okf_agents-0.1.0/tests/unit/test_graph_utils.py +80 -0
- okf_agents-0.1.0/tests/unit/test_indexing.py +302 -0
- okf_agents-0.1.0/tests/unit/test_models.py +161 -0
- okf_agents-0.1.0/tests/unit/test_navigator.py +614 -0
- okf_agents-0.1.0/tests/unit/test_parser.py +392 -0
- okf_agents-0.1.0/tests/unit/test_retriever.py +298 -0
- okf_agents-0.1.0/tests/unit/test_router.py +249 -0
- okf_agents-0.1.0/tests/unit/test_tools.py +238 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Tooling caches
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
coverage.xml
|
|
19
|
+
htmlcov/
|
|
20
|
+
|
|
21
|
+
# Editors and OS
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
24
|
+
.DS_Store
|
|
25
|
+
|
|
26
|
+
# Local planning docs
|
|
27
|
+
docs/tasks/
|
|
28
|
+
langgraph-okf-spec.md
|
|
29
|
+
okf-agents-spec.md
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-07-14
|
|
11
|
+
|
|
12
|
+
Initial alpha release.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Deterministic OKF v0.1 bundle loading, parsing, link-graph construction,
|
|
17
|
+
and weighted lexical search (`OKFBundle`).
|
|
18
|
+
- Domain models for concepts, frontmatter, link edges, and the bundle
|
|
19
|
+
index (`Concept`, `ConceptFrontmatter`, `LinkEdge`, `BundleIndex`).
|
|
20
|
+
- Four deterministic LangChain agent tools over a loaded bundle
|
|
21
|
+
(`create_okf_tools`): `read_concept`, `search_concepts`, `list_links`,
|
|
22
|
+
`read_index`.
|
|
23
|
+
- A keyword `BaseRetriever` backed by bundle search (`OKFRetriever`) and a
|
|
24
|
+
shared concept-to-`Document` conversion helper.
|
|
25
|
+
- A query-routing LangGraph node that classifies queries as `bundle`,
|
|
26
|
+
`vector`, or `both`, with an offline heuristic and optional classifier
|
|
27
|
+
fallback (`create_okf_router`).
|
|
28
|
+
- A bounded LangGraph navigator subgraph that reads the bundle index,
|
|
29
|
+
plans traversal, expands linked concepts within hard budgets, and
|
|
30
|
+
returns a grounded, citation-validated answer (`create_okf_navigator`).
|
|
31
|
+
- Idempotent synchronization of bundles into LangChain vector stores
|
|
32
|
+
(`sync_bundle_to_vector_store`) and a graph-expanding retriever
|
|
33
|
+
(`OKFGraphRetriever`) that expands semantic hits through the link graph.
|
|
34
|
+
- Offline integration tests, opt-in provider-integration tests
|
|
35
|
+
(`RUN_INTEGRATION_TESTS=1`), and opt-in end-to-end tests
|
|
36
|
+
(`RUN_E2E_TESTS=1`).
|
|
37
|
+
|
|
38
|
+
[Unreleased]: https://github.com/RonCodes88/okf-agents/compare/v0.1.0...HEAD
|
|
39
|
+
[0.1.0]: https://github.com/RonCodes88/okf-agents/releases/tag/v0.1.0
|
okf_agents-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ronald Li
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: okf-agents
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Open Knowledge Format (OKF) agent tools, navigator, and graph-aware retrieval for LangGraph
|
|
5
|
+
Project-URL: Homepage, https://github.com/RonCodes88/langgraph-okf
|
|
6
|
+
Project-URL: Documentation, https://github.com/RonCodes88/langgraph-okf/tree/main/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/RonCodes88/langgraph-okf
|
|
8
|
+
Project-URL: Issue Tracker, https://github.com/RonCodes88/langgraph-okf/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/RonCodes88/langgraph-okf/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Ronald Li <ronaldliyh@gmail.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: ai-agents,knowledge,knowledge-graph,knowledge-management,langchain,langgraph,markdown,okf,open-knowledge-format,rag,retriever
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: langchain-core>=0.3
|
|
27
|
+
Requires-Dist: langgraph>=0.2
|
|
28
|
+
Requires-Dist: pydantic>=2.0
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
35
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
36
|
+
Provides-Extra: lint
|
|
37
|
+
Requires-Dist: ruff>=0.6; extra == 'lint'
|
|
38
|
+
Provides-Extra: release
|
|
39
|
+
Requires-Dist: build>=1.2; extra == 'release'
|
|
40
|
+
Requires-Dist: twine>=5.0; extra == 'release'
|
|
41
|
+
Provides-Extra: test
|
|
42
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'test'
|
|
43
|
+
Requires-Dist: pytest>=8.0; extra == 'test'
|
|
44
|
+
Provides-Extra: typecheck
|
|
45
|
+
Requires-Dist: mypy>=1.11; extra == 'typecheck'
|
|
46
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'typecheck'
|
|
47
|
+
Provides-Extra: vector-test
|
|
48
|
+
Requires-Dist: chromadb>=0.5; extra == 'vector-test'
|
|
49
|
+
Requires-Dist: langchain-chroma>=0.1; extra == 'vector-test'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
<div align="center">
|
|
53
|
+
|
|
54
|
+
# okf-agents
|
|
55
|
+
|
|
56
|
+
**Open Knowledge Format bundles as first-class LangGraph and LangChain building blocks.**
|
|
57
|
+
|
|
58
|
+
[](https://github.com/RonCodes88/okf-agents/actions/workflows/ci.yml)
|
|
59
|
+
[](LICENSE)
|
|
60
|
+
[](pyproject.toml)
|
|
61
|
+
|
|
62
|
+
[Installation](#installation) •
|
|
63
|
+
[Quick Start](#quick-start) •
|
|
64
|
+
[Documentation](docs/) •
|
|
65
|
+
[Examples](#examples) •
|
|
66
|
+
[Contributing](CONTRIBUTING.md) •
|
|
67
|
+
[Security](SECURITY.md) •
|
|
68
|
+
[Changelog](CHANGELOG.md)
|
|
69
|
+
|
|
70
|
+
*Not yet published to PyPI — see [the launch checklist](docs/launch-checklist.md).*
|
|
71
|
+
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
`okf-agents` turns an [Open Knowledge Format (OKF)](https://okf.md) bundle
|
|
77
|
+
— a directory of linked, frontmattered Markdown concepts — into deterministic
|
|
78
|
+
LangGraph and LangChain building blocks: bundle loading and weighted lexical
|
|
79
|
+
search, four ready-made agent tools, a keyword retriever, a bounded navigator
|
|
80
|
+
subgraph that reads and cites its sources, a query router, and idempotent
|
|
81
|
+
vector-store synchronization with graph-aware retrieval. It's for teams
|
|
82
|
+
building retrieval or agentic workflows over structured Markdown knowledge
|
|
83
|
+
bases who want small, typed, independently testable pieces rather than
|
|
84
|
+
another end-to-end RAG framework to learn.
|
|
85
|
+
|
|
86
|
+
Loading arbitrary Markdown gives you text; a vector store alone gives you
|
|
87
|
+
semantically similar chunks with no sense of how concepts relate to each
|
|
88
|
+
other. `okf-agents` keeps the bundle's link graph as a first-class
|
|
89
|
+
citizen: search results and semantic hits can be expanded through resolved
|
|
90
|
+
links (`OKFGraphRetriever`), the navigator subgraph follows links breadth
|
|
91
|
+
-first or on model guidance within hard token/hop budgets, and every
|
|
92
|
+
answer's citations are validated against concepts it actually read — so
|
|
93
|
+
retrieval reflects the bundle's real structure, not just embedding-space
|
|
94
|
+
proximity.
|
|
95
|
+
|
|
96
|
+
This library implements the [OKF specification](https://okf.md/spec/); it
|
|
97
|
+
is an integration layer for OKF, not the standard itself.
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install okf-agents
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Only `langgraph`, `langchain-core`, `pydantic`, and `pyyaml` are required.
|
|
106
|
+
Provider SDKs (`langchain-anthropic`, `langchain-openai`, ...) and
|
|
107
|
+
vector-store packages (`chromadb`, ...) are never installed as hard
|
|
108
|
+
dependencies — bring the ones you need.
|
|
109
|
+
|
|
110
|
+
For local development instead, see [Development](#development).
|
|
111
|
+
|
|
112
|
+
## Quick Start
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import tempfile
|
|
116
|
+
from pathlib import Path
|
|
117
|
+
from okf_agents import OKFBundle
|
|
118
|
+
|
|
119
|
+
tmp_dir = tempfile.mkdtemp()
|
|
120
|
+
concepts_dir = Path(tmp_dir) / "concepts"
|
|
121
|
+
concepts_dir.mkdir()
|
|
122
|
+
(concepts_dir / "orders.md").write_text(
|
|
123
|
+
"---\ntype: table\ntitle: Orders\ntags: [sales]\n---\n\n"
|
|
124
|
+
"# Orders\n\nEach order belongs to a [customer](customers.md).\n"
|
|
125
|
+
)
|
|
126
|
+
(concepts_dir / "customers.md").write_text(
|
|
127
|
+
"---\ntype: table\ntitle: Customers\ntags: [crm]\n---\n\n"
|
|
128
|
+
"# Customers\n\nCustomer accounts and contact details.\n"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
bundle = OKFBundle.load(tmp_dir)
|
|
132
|
+
print(bundle.concept_count, "concepts loaded")
|
|
133
|
+
print(sorted(concept.id for concept in bundle.all_concepts()))
|
|
134
|
+
# 2 concepts loaded
|
|
135
|
+
# ['concepts/customers', 'concepts/orders']
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
That's the whole contract: point `OKFBundle.load()` at a directory of
|
|
139
|
+
Markdown files with `type` frontmatter, and get back a typed, queryable
|
|
140
|
+
bundle. No index file is required — see [docs/concepts.md](docs/concepts.md).
|
|
141
|
+
|
|
142
|
+
## Why use okf-agents?
|
|
143
|
+
|
|
144
|
+
- **Deterministic by default.** Search, traversal, and routing heuristics
|
|
145
|
+
are dependency-free and offline; a real model is only involved where you
|
|
146
|
+
explicitly ask for one (the navigator, an optional router classifier).
|
|
147
|
+
- **The link graph is not thrown away.** Broken links are tolerated and
|
|
148
|
+
surfaced, not hidden; resolved links drive graph expansion and
|
|
149
|
+
traversal, not just chunk similarity.
|
|
150
|
+
- **Small, typed, independently useful pieces.** Use just the bundle
|
|
151
|
+
loader, just the retriever, just the tools — nothing requires the
|
|
152
|
+
navigator or a vector store.
|
|
153
|
+
- **Budgets are real limits.** The navigator's hop/concept/token budgets
|
|
154
|
+
are provable bounds on the graph, not soft guidelines a model can blow
|
|
155
|
+
through.
|
|
156
|
+
|
|
157
|
+
## When should I not use it?
|
|
158
|
+
|
|
159
|
+
- You need a general-purpose document loader for arbitrary file formats
|
|
160
|
+
(PDFs, HTML, docx) — `okf-agents` only reads OKF-shaped Markdown
|
|
161
|
+
bundles.
|
|
162
|
+
- You need semantic chunking, reranking, or multi-vector-store fan-out —
|
|
163
|
+
this library intentionally keeps vector-store integration minimal (see
|
|
164
|
+
[docs/vector-stores.md](docs/vector-stores.md)).
|
|
165
|
+
- You want a hosted, batteries-included RAG product — this is a library
|
|
166
|
+
of composable pieces for your own LangGraph app, not an application.
|
|
167
|
+
|
|
168
|
+
## Examples
|
|
169
|
+
|
|
170
|
+
Each example below continues from the `bundle` loaded in
|
|
171
|
+
[Quick Start](#quick-start).
|
|
172
|
+
|
|
173
|
+
### LangChain agent tools
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from okf_agents import create_okf_tools
|
|
177
|
+
|
|
178
|
+
tools = create_okf_tools(bundle)
|
|
179
|
+
search_tool = next(tool for tool in tools if tool.name == "search_concepts")
|
|
180
|
+
print(search_tool.invoke({"query": "customer"}))
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`create_okf_tools` also returns `read_concept`, `list_links`, and
|
|
184
|
+
`read_index` — deterministic, plain-text tools ready to bind to any
|
|
185
|
+
tool-calling agent.
|
|
186
|
+
|
|
187
|
+
### Keyword retriever
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from okf_agents import OKFRetriever
|
|
191
|
+
|
|
192
|
+
retriever = OKFRetriever(bundle=bundle, top_k=3)
|
|
193
|
+
for document in retriever.invoke("orders"):
|
|
194
|
+
print(document.metadata["concept_id"], "-", document.metadata["title"])
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Router
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from okf_agents import create_okf_router
|
|
201
|
+
|
|
202
|
+
router = create_okf_router(bundle)
|
|
203
|
+
print(router({"query": "Orders"})) # exact title match -> "bundle"
|
|
204
|
+
print(router({"query": "how do refunds work?"})) # vague, no vector store -> "bundle"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Pass `vector_store=` to route vague queries to `"vector"` instead, or
|
|
208
|
+
`classifier=` to let a model choose `"bundle"` / `"vector"` / `"both"`.
|
|
209
|
+
`create_okf_router` never performs retrieval itself — it only labels the
|
|
210
|
+
query for a downstream conditional edge.
|
|
211
|
+
|
|
212
|
+
### Navigator subgraph
|
|
213
|
+
|
|
214
|
+
The navigator needs a chat model. This example uses LangChain's
|
|
215
|
+
`FakeListChatModel` so it runs fully offline; swap in `ChatAnthropic`,
|
|
216
|
+
`ChatOpenAI`, or any other `BaseChatModel` in production.
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import json
|
|
220
|
+
from langchain_core.language_models.fake_chat_models import FakeListChatModel
|
|
221
|
+
from okf_agents import create_okf_navigator
|
|
222
|
+
|
|
223
|
+
model = FakeListChatModel(
|
|
224
|
+
responses=[
|
|
225
|
+
json.dumps({"concept_ids": ["concepts/orders"]}),
|
|
226
|
+
json.dumps({"sufficient": True}),
|
|
227
|
+
json.dumps({
|
|
228
|
+
"answer": "Orders belong to customers.",
|
|
229
|
+
"citations": ["concepts/orders"],
|
|
230
|
+
}),
|
|
231
|
+
]
|
|
232
|
+
)
|
|
233
|
+
navigator = create_okf_navigator(bundle, model, max_hops=2)
|
|
234
|
+
result = navigator.invoke({"question": "How do orders relate to customers?"})
|
|
235
|
+
print(result["answer"])
|
|
236
|
+
print(result["citations"])
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
See [docs/navigator-and-budgets.md](docs/navigator-and-budgets.md) for the
|
|
240
|
+
full traversal and budget contract.
|
|
241
|
+
|
|
242
|
+
### Vector-store sync and graph-aware retrieval (optional)
|
|
243
|
+
|
|
244
|
+
`sync_bundle_to_vector_store` and `OKFGraphRetriever` work with any
|
|
245
|
+
LangChain `VectorStore` that supports ID-based lookup and stable-ID
|
|
246
|
+
writes. This example uses a tiny in-process store and hashed-word
|
|
247
|
+
embeddings so it runs offline; swap in Chroma, pgvector, or another real
|
|
248
|
+
`VectorStore` with a real embeddings model in production.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
import hashlib
|
|
252
|
+
import math
|
|
253
|
+
import re
|
|
254
|
+
from typing import Any
|
|
255
|
+
|
|
256
|
+
from langchain_core.documents import Document
|
|
257
|
+
from langchain_core.embeddings import Embeddings
|
|
258
|
+
from langchain_core.vectorstores import VectorStore
|
|
259
|
+
from okf_agents import OKFGraphRetriever, sync_bundle_to_vector_store
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class DemoEmbeddings(Embeddings):
|
|
263
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
264
|
+
return [self._embed(text) for text in texts]
|
|
265
|
+
|
|
266
|
+
def embed_query(self, text: str) -> list[float]:
|
|
267
|
+
return self._embed(text)
|
|
268
|
+
|
|
269
|
+
def _embed(self, text: str, dimensions: int = 32) -> list[float]:
|
|
270
|
+
vector = [0.0] * dimensions
|
|
271
|
+
for token in re.findall(r"\w+", text.casefold()):
|
|
272
|
+
vector[int(hashlib.sha256(token.encode()).hexdigest(), 16) % dimensions] += 1.0
|
|
273
|
+
norm = math.sqrt(sum(v * v for v in vector)) or 1.0
|
|
274
|
+
return [v / norm for v in vector]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class DemoVectorStore(VectorStore):
|
|
278
|
+
def __init__(self, embedding: Embeddings) -> None:
|
|
279
|
+
self.embedding = embedding
|
|
280
|
+
self._docs: dict[str, Document] = {}
|
|
281
|
+
self._vectors: dict[str, list[float]] = {}
|
|
282
|
+
|
|
283
|
+
def add_documents(self, documents: list[Document], **kwargs: Any) -> list[str]:
|
|
284
|
+
ids = kwargs["ids"]
|
|
285
|
+
vectors = self.embedding.embed_documents([d.page_content for d in documents])
|
|
286
|
+
for doc_id, document, vector in zip(ids, documents, vectors, strict=True):
|
|
287
|
+
self._docs[doc_id] = document
|
|
288
|
+
self._vectors[doc_id] = vector
|
|
289
|
+
return list(ids)
|
|
290
|
+
|
|
291
|
+
def get_by_ids(self, ids: list[str], /) -> list[Document]:
|
|
292
|
+
return [self._docs[i] for i in ids if i in self._docs]
|
|
293
|
+
|
|
294
|
+
def similarity_search(self, query: str, k: int = 4, **kwargs: Any) -> list[Document]:
|
|
295
|
+
query_vector = self.embedding.embed_query(query)
|
|
296
|
+
ranked = sorted(
|
|
297
|
+
self._vectors,
|
|
298
|
+
key=lambda i: -sum(x * y for x, y in zip(query_vector, self._vectors[i], strict=True)),
|
|
299
|
+
)
|
|
300
|
+
return [self._docs[i] for i in ranked[:k]]
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def from_texts(cls, texts: Any, embedding: Any, metadatas: Any = None, **kwargs: Any) -> Any:
|
|
304
|
+
raise NotImplementedError
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
vector_store = DemoVectorStore(DemoEmbeddings())
|
|
308
|
+
sync_bundle_to_vector_store(bundle, vector_store)
|
|
309
|
+
|
|
310
|
+
graph_retriever = OKFGraphRetriever(
|
|
311
|
+
bundle=bundle, vector_store=vector_store, top_k=1, expand_hops=1
|
|
312
|
+
)
|
|
313
|
+
for document in graph_retriever.invoke("order belongs"):
|
|
314
|
+
print(document.metadata["concept_id"])
|
|
315
|
+
# concepts/orders
|
|
316
|
+
# concepts/customers (reached via the "customer" link, not just similarity)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
See [docs/vector-stores.md](docs/vector-stores.md) for the idempotency and
|
|
320
|
+
store-capability contract.
|
|
321
|
+
|
|
322
|
+
## Architecture overview
|
|
323
|
+
|
|
324
|
+
```text
|
|
325
|
+
OKF bundle (directory of Markdown)
|
|
326
|
+
│
|
|
327
|
+
▼
|
|
328
|
+
OKFBundle.load() deterministic parse + link graph + lexical search
|
|
329
|
+
│
|
|
330
|
+
├── create_okf_tools() four LangChain tools (no model required)
|
|
331
|
+
├── OKFRetriever keyword BaseRetriever
|
|
332
|
+
├── sync_bundle_to_vector_store + OKFGraphRetriever
|
|
333
|
+
│ idempotent sync + graph-aware semantic retrieval
|
|
334
|
+
├── create_okf_router() bundle / vector / both classification node
|
|
335
|
+
└── create_okf_navigator() bounded read → expand → cite subgraph
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Every arrow above is independently usable; nothing requires wiring the
|
|
339
|
+
whole diagram together.
|
|
340
|
+
|
|
341
|
+
## Feature status
|
|
342
|
+
|
|
343
|
+
| Feature | Status |
|
|
344
|
+
| ------------------------------------------ | ------ |
|
|
345
|
+
| Bundle loading, link graph, lexical search | Stable |
|
|
346
|
+
| LangChain agent tools | Stable |
|
|
347
|
+
| Keyword retriever | Stable |
|
|
348
|
+
| Router node | Stable |
|
|
349
|
+
| Navigator subgraph | Stable |
|
|
350
|
+
| Vector-store sync + graph-aware retriever | Stable |
|
|
351
|
+
| Async APIs | Not implemented (v0.1 is sync-only by design) |
|
|
352
|
+
| Multi-vector-store adapters | Out of scope — any LangChain `VectorStore` with ID lookup + stable-ID writes works |
|
|
353
|
+
|
|
354
|
+
"Stable" means covered by unit and offline integration tests and used
|
|
355
|
+
across the examples above — the package itself is still pre-1.0 alpha, so
|
|
356
|
+
APIs may change between minor versions.
|
|
357
|
+
|
|
358
|
+
## Public API map
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
from okf_agents import (
|
|
362
|
+
OKFBundle,
|
|
363
|
+
Concept, ConceptFrontmatter, LinkEdge, BundleIndex, SyncResult,
|
|
364
|
+
create_okf_tools,
|
|
365
|
+
OKFRetriever, OKFGraphRetriever,
|
|
366
|
+
create_okf_router,
|
|
367
|
+
create_okf_navigator,
|
|
368
|
+
sync_bundle_to_vector_store,
|
|
369
|
+
OKFError, BundleNotFoundError, BundleValidationError,
|
|
370
|
+
ConceptNotFoundError, LinkResolutionError,
|
|
371
|
+
)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Full signatures and behavior contracts: [docs/api-reference.md](docs/api-reference.md).
|
|
375
|
+
|
|
376
|
+
## Optional dependencies
|
|
377
|
+
|
|
378
|
+
| Extra | Installs | Needed for |
|
|
379
|
+
| ----------- | ------------------------------- | ------------------------------------ |
|
|
380
|
+
| `dev` | test + lint + typecheck tools | Local development |
|
|
381
|
+
| `test` | `pytest`, `pytest-cov` | Running the test suite |
|
|
382
|
+
| `lint` | `ruff` | Linting |
|
|
383
|
+
| `typecheck` | `mypy`, `types-PyYAML` | Strict type checking |
|
|
384
|
+
| `vector-test` | `chromadb`, `langchain-chroma` | Vector-store integration testing |
|
|
385
|
+
| `release` | `build`, `twine` | Building/publishing packages |
|
|
386
|
+
|
|
387
|
+
None of these are required to use `OKFBundle`, the tools, the retriever,
|
|
388
|
+
the router, or the navigator with your own chat model and vector store.
|
|
389
|
+
|
|
390
|
+
## Compatibility
|
|
391
|
+
|
|
392
|
+
- Python 3.11, 3.12, 3.13
|
|
393
|
+
- `langgraph` >= 0.2
|
|
394
|
+
- `langchain-core` >= 0.3
|
|
395
|
+
- `pydantic` >= 2.0
|
|
396
|
+
|
|
397
|
+
## Limitations
|
|
398
|
+
|
|
399
|
+
- v0.1 parses standard inline Markdown links only — reference-style
|
|
400
|
+
links, images, and autolinks are not graph edges (see
|
|
401
|
+
[docs/concepts.md](docs/concepts.md#links-and-resolution)).
|
|
402
|
+
- Lexical search is a weighted substring match, not TF-IDF or embeddings
|
|
403
|
+
— use `OKFGraphRetriever` with a real vector store for semantic recall.
|
|
404
|
+
- The navigator does not stream, checkpoint, or support human-in-the-loop
|
|
405
|
+
approval in v0.1.
|
|
406
|
+
|
|
407
|
+
## Development
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
git clone https://github.com/RonCodes88/okf-agents.git
|
|
411
|
+
cd okf-agents
|
|
412
|
+
python -m venv .venv && source .venv/bin/activate
|
|
413
|
+
pip install -e ".[dev]"
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Testing
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
pytest # unit tests + offline integration tests, no secrets required
|
|
420
|
+
pytest --cov # with coverage (>= 85% required)
|
|
421
|
+
ruff check .
|
|
422
|
+
mypy okf_agents tests
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Provider-integration and end-to-end tests are opt-in
|
|
426
|
+
(`RUN_INTEGRATION_TESTS=1`, `RUN_E2E_TESTS=1`) and skip cleanly without a
|
|
427
|
+
provider key. See [docs/testing.md](docs/testing.md).
|
|
428
|
+
|
|
429
|
+
## Contributing
|
|
430
|
+
|
|
431
|
+
Contributions are welcome — please read [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
432
|
+
for branch/commit conventions and local setup, and open an issue before
|
|
433
|
+
starting a large change.
|
|
434
|
+
|
|
435
|
+
## Security
|
|
436
|
+
|
|
437
|
+
See [SECURITY.md](SECURITY.md) for how to privately report a
|
|
438
|
+
vulnerability.
|
|
439
|
+
|
|
440
|
+
## License
|
|
441
|
+
|
|
442
|
+
[MIT](LICENSE) © Ronald Li
|
|
443
|
+
|
|
444
|
+
## Acknowledgements
|
|
445
|
+
|
|
446
|
+
Built against the [Open Knowledge Format specification](https://okf.md/spec/)
|
|
447
|
+
and the [LangGraph](https://github.com/langchain-ai/langgraph) /
|
|
448
|
+
[LangChain](https://github.com/langchain-ai/langchain) ecosystem.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
<div align="center">
|
|
453
|
+
|
|
454
|
+
If this project is useful to you, a ⭐ helps others find it.
|
|
455
|
+
|
|
456
|
+
</div>
|