Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ The following diagram shows where the Knowledge Mapper operates within the Knowl

![architecture diagram](./docs/img/architecture.png)

## Getting Started with Examples

The easiest way to learn the Knowledge Mapper is by exploring the [examples](./examples/README.md). They demonstrate key features like creating knowledge bases, defining knowledge interactions, using binding models, dependency injection, and testing. Each example includes inline comments and can be run locally with a Knowledge Engine instance. See the [examples README](./examples/README.md) for setup instructions and an overview of all available examples.

## How do I use it?

1. Install `knowledge_mapper` in a Python environment with `pip`:
Expand Down
12 changes: 11 additions & 1 deletion examples/basic.py → examples/01-basic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"""Basic example: register a simple ANSWER KI and handle incoming requests.

This is the smallest runnable example and a good starting point to understand the
KnowledgeBase lifecycle: connect -> register -> unregister.
"""

from shared import get_example_logger

from src.kb.knowledge_base import KnowledgeBase
from knowledge_mapper import KnowledgeBase

EXAMPLE_NAME = "basic"
logger = get_example_logger(EXAMPLE_NAME)
Expand All @@ -13,6 +19,8 @@
)


# Decorate a function as an ANSWER KI handler.
# The incoming bindings match the graph pattern variables.
@kb.answer_ki(
name="example-answer-ki",
graph_pattern="""
Expand All @@ -23,10 +31,12 @@
)
def example_answer_ki(binding_set, info):
logger.info("Handling a call to the example answer KI.")
# Echo incoming bindings to demonstrate a minimal handler.
return binding_set


if __name__ == "__main__":
# Connect to the KE, then register and unregister this KB.
kb.connect()
kb.register()
logger.info("Registered a Knowledge Base in the basic example!")
Expand Down
15 changes: 13 additions & 2 deletions examples/binding_models.py → examples/02-binding_models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
"""Binding models example: compare typed vs raw binding handling.

This example registers two ANSWER KIs with the same pattern:
1. A typed handler using a BindingModel class.
2. A raw handler using plain BindingSet dictionaries.
"""

from datetime import datetime

from rdflib import URIRef
from shared import get_example_logger

from src.kb.knowledge_base import KnowledgeBase
from src.ke.models import (
from knowledge_mapper import (
BindingModel,
BindingSet,
KnowledgeBase,
KnowledgeInteractionInfo,
Literal,
Uri,
Expand All @@ -23,6 +30,7 @@
)


# Define strongly-typed bindings for variables in the graph pattern.
class CurrentTemperatureBinding(BindingModel):
measurement: Uri
value: Literal[float]
Expand All @@ -47,6 +55,7 @@ def binding_models_answer_ki(
f"Handling a call to the binding models answer KI with incoming bindings: "
f"{binding_set}"
)
# Return a single current measurement using typed values.
return [
CurrentTemperatureBinding(
measurement=URIRef(
Expand Down Expand Up @@ -76,6 +85,7 @@ def binding_models_raw_answer_ki(
f"Handling a call to the binding models raw answer KI with incoming bindings: "
f"{binding_set}"
)
# Return the same shape as above, but manually encoded as raw strings.
return [
{
"measurement": "<http://example.org/knowledge-mapper/binding-models#currentTemp>",
Expand All @@ -87,6 +97,7 @@ def binding_models_raw_answer_ki(


if __name__ == "__main__":
# Register both KIs, then cleanly unregister.
kb.connect()
kb.register()
logger.info("Registered the binding models example KB!")
Expand Down
12 changes: 10 additions & 2 deletions examples/ask_interaction.py → examples/03-ask_interaction.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""ASK interaction example.

Registers an ASK KI and executes it from this script to show how query bindings
and typed results work end-to-end.
"""

from shared import get_example_logger

from src import KnowledgeBase
from src.ke.models import BindingModel, Literal, Uri
from knowledge_mapper import BindingModel, KnowledgeBase, Literal, Uri

EXAMPLE_NAME = "ask-interaction"
logger = get_example_logger(EXAMPLE_NAME)
Expand All @@ -14,12 +19,14 @@
)


# Binding model for variables used in the ASK graph pattern.
class PersonBinding(BindingModel):
person: Uri
name: Literal[str]
age: Literal[int]


# Register an ASK KI that can be called via kb.ask(...).
kb.ask_ki(
name="ask-ki",
graph_pattern="""
Expand All @@ -32,6 +39,7 @@ class PersonBinding(BindingModel):
)

if __name__ == "__main__":
# Register this KB, execute one ASK request, and then unregister.
kb.register()
logger.info("KB registered.")
result = kb.ask(
Expand Down
19 changes: 12 additions & 7 deletions examples/post_measurement.py → examples/04-post_measurement.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
"""POST interaction example for publishing a measurement.

This script registers a POST KI, then posts one measurement binding set and logs
the result bindings returned by the KE.
"""

import time
from datetime import datetime
from uuid import uuid4

from rdflib import URIRef
from shared import get_example_logger

from src.kb.knowledge_base import KnowledgeBase
from src.ke.models import (
BindingModel,
Literal,
Uri,
)
from knowledge_mapper import BindingModel, KnowledgeBase, Literal, Uri

EXAMPLE_NAME = "post-measurement"
logger = get_example_logger(EXAMPLE_NAME)
Expand All @@ -24,18 +25,21 @@
)


# Argument bindings for the POST interaction input pattern.
class MeasurementBinding(BindingModel):
measurement: Uri
value: Literal[float]
unit: Uri
time: Literal[datetime]


# Result bindings for the POST interaction result pattern.
class ResultBinding(BindingModel):
measurement: Uri
kb: Uri


# Register a POST KI with separate argument and result graph patterns.
kb.post_ki(
name="post-measurement-ki",
argument_graph_pattern="""
Expand All @@ -46,7 +50,7 @@ class ResultBinding(BindingModel):
""",
result_graph_pattern="""
?measurement a ex:Measurement ;
ex:storedBy ?kb ;
ex:storedBy ?kb .
""",
prefixes={"ex": "http://example.org/knowledge-mapper/post-measurement#"},
result_binding_model=ResultBinding,
Expand All @@ -55,6 +59,7 @@ class ResultBinding(BindingModel):


if __name__ == "__main__":
# Register KB, wait briefly for manual testing, then execute one POST.
kb.register()
logger.info("KB registered.")
time.sleep(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@
from pydantic_settings import CliSettingsSource, SettingsConfigDict
from shared import get_example_logger

from src import KnowledgeBase, KnowledgeBaseSettings
from src.ke.models import BindingSet, KnowledgeInteractionInfo
from knowledge_mapper import (
BindingSet,
KnowledgeBase,
KnowledgeBaseSettings,
KnowledgeInteractionInfo,
)

EXAMPLE_NAME = "custom-settings"
logger = get_example_logger(EXAMPLE_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@

from shared import get_example_logger

from src import Depends, KnowledgeBase
from src.ke.models import BindingModel, KnowledgeInteractionInfo, Literal, Uri
from knowledge_mapper import (
BindingModel,
Depends,
KnowledgeBase,
KnowledgeInteractionInfo,
Literal,
Uri,
)

EXAMPLE_NAME = "dependency-injection"
logger = get_example_logger(EXAMPLE_NAME)
Expand Down
15 changes: 7 additions & 8 deletions examples/testing/kb.py → examples/07-testing/kb.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@

from shared import get_example_logger

from src import KnowledgeBase
from src.ke.models import BindingModel, Literal, Uri
from knowledge_mapper import BindingModel, KnowledgeBase, Literal, Uri

EXAMPLE_NAME = "testing"
logger = get_example_logger(EXAMPLE_NAME)
Expand All @@ -32,7 +31,7 @@
)


class TestBinding(BindingModel):
class ExampleBinding(BindingModel):
s: Uri
value: Literal[str]

Expand All @@ -43,15 +42,15 @@ class TestBinding(BindingModel):
?s a ex:TestSubject ;
ex:hasValue ?value .
""",
binding_model=TestBinding,
binding_model=ExampleBinding,
prefixes={"ex": "http://example.org/knowledge-mapper/testing#"},
)


def ask_for_values_of_subject(subject_name: str) -> list[str]:
result_binding_set: list[TestBinding] = kb.ask(
result_binding_set: list[ExampleBinding] = kb.ask(
[
TestBinding(
ExampleBinding(
s=URIRef(f"http://example.org/knowledge-mapper/testing#{subject_name}"),
value=None,
)
Expand Down Expand Up @@ -80,7 +79,7 @@ class ResultBinding(BindingModel):
?s a ex:TestSubject ;
ex:storedBy ?other .
""",
argument_binding_model=TestBinding,
argument_binding_model=ExampleBinding,
result_binding_model=ResultBinding,
prefixes={"ex": "http://example.org/knowledge-mapper/testing#"},
)
Expand All @@ -92,7 +91,7 @@ def repeat_value_post(value: str, iterations: int) -> list[URIRef]:
result_binding_set.extend(
kb.post(
[
TestBinding(
ExampleBinding(
s=URIRef(
f"http://example.org/knowledge-mapper/testing#Subject-{i}"
),
Expand Down
19 changes: 9 additions & 10 deletions examples/testing/test_kb.py → examples/07-testing/test_kb.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import pytest
from rdflib import URIRef

from src.ke.testing import TestClient
from knowledge_mapper.testing import TestClient

# Import the Knowledge Base that you would like to test, along with any relevant binding
# models.
from .kb import TestBinding, ask_for_values_of_subject, kb, repeat_value_post
from .kb import ExampleBinding, ask_for_values_of_subject, kb, repeat_value_post

# In your tests you likely want to use the TestClient to mock results from the KE.
# A Knowledge Base instance is initialized with a real Client that makes HTTP requests
Expand Down Expand Up @@ -40,45 +40,44 @@ def test_ask_ki_with_result(client: TestClient):
[
{
"s": "<http://example.org/knowledge-mapper/testing#Subject>",
"value": "test value",
"value": '"test value"',
}
],
)
result_binding_set = kb.ask([], "ask-ki-no-binding-model")
assert result_binding_set == [
{
"s": "<http://example.org/knowledge-mapper/testing#Subject>",
"value": "test value",
"value": '"test value"',
}
]


# This is a little more useful when you have a binding model, testing the correctness of
# the binding model according to the graph pattern. One test per interaction like this
# per interaction is probably a good idea, to isolate issues with the binding model from
# other issues.
# is probably a good idea, to isolate issues with the binding model from other issues.
def test_ask_ki_with_binding_model(client: TestClient):
client.mock_result_binding_set(
"ask-ki-with-binding-model",
[
{
"s": "<http://example.org/knowledge-mapper/testing#Subject>",
"value": "test value",
"value": '"test value"',
}
],
)

result_binding_set = kb.ask(
[
TestBinding(
ExampleBinding(
s=URIRef("http://example.org/knowledge-mapper/testing#Subject"),
value=None,
)
],
"ask-ki-with-binding-model",
)
assert result_binding_set == [
TestBinding(
ExampleBinding(
s=URIRef("http://example.org/knowledge-mapper/testing#Subject"),
value="test value",
)
Expand All @@ -91,7 +90,7 @@ def test_function_containing_ask(client: TestClient):
client.mock_result_binding_set(
ki_name="ask-ki-with-binding-model",
binding_set=[
TestBinding(
ExampleBinding(
s=URIRef("http://example.org/knowledge-mapper/testing#Subject"),
value="test value",
).model_dump(),
Expand Down
Loading
Loading