Files
Lore Engine Dev 6510e767c6 slice 4: 16 read tools + 3 write tools + Graph reverse indexes (92/92 tests; 341/341)
- Graph reverse indexes (slice 4.0): edges_by_object (O(1) reverse lookups)
  and entities_by_type (type-filtered queries) added to lore_engine_poc/tools.py
- responses.py (slice 4.1): shared edge_to_fact / entity_summary / envelope
  helpers — single source of truth for tool response shape
- read_tools.py (slice 4.2-4.6): 16 read tools across 5 groups
  - Group 1: lookup, entity_context
  - Group 2: true_during, entities_present, timeline (state_at deferred to 4.6+)
  - Group 3: list_lineage, list_offspring, ancestors_of, descendants_of,
    location_hierarchy
  - Group 4: event_chain, events_during
  - Group 5: lore_about (cite deferred to 4.7+)
- write_tools.py (slice 4.7): 3 minimal world-builder tools
  (add_entity, add_relation, add_lore_source) with allowlist + envelope
- scripts/02_demo.py: now exercises every read tool + write/read round-trip
- 92 new tests (7 graph_indexes + 7 responses + 10 group1 + 15 group2 +
  19 group3 + 11 group4 + 7 group5 + 16 write_tools). 341/341 green.

Excluded: state_at (composes 4 tools + consistency engine), summarize_chain
+ narrate_arc (LLM-required), cite (vector store), and Group 8 write tools
beyond the 3 minimal — all deferred per slice 4 plan.
2026-06-18 09:41:02 -04:00

89 lines
3.1 KiB
Python

"""Lore Engine POC — shared response helpers (slice 4.1).
The 16 read tools in slice 4 all return dicts with a small,
consistent shape. Schema drift across tools is the most common
tool-bug source (slice 4 plan risk #3), so the helpers below
centralise the contract:
* :func:`edge_to_fact` — flat dict for one fact. Every tool
that returns a single Edge (or a list of Edges) builds its
response through this helper, so ``valid_from``/``valid_until``,
``sources``, and ``confidence`` are always in the same place.
* :func:`entity_summary` — compact ``{name, type}`` for tools
that surface many entities (``lookup``, ``events_during``,
``entities_present``).
* :func:`envelope` — ``{"ok": ..., "data": ...}`` (or
``{"ok": False, "error": str}``) for tools that can fail
(the slice-4.7 write tools).
The slice-0 ``was_true_at`` tool predates this module and keeps
its inline response shape for backward compat. New read tools
should call these helpers instead.
"""
from __future__ import annotations
from typing import Any, Optional
from .tools import Edge
def edge_to_fact(edge: Edge) -> dict:
"""Convert an :class:`Edge` to the canonical fact dict.
The shape is the union of ``was_true_at``'s inline dict
and ``explain_violation``'s fact-shaped fields::
{
"subject": str,
"relation": str,
"object": str,
"valid_from": str | None,
"valid_until": str | None,
"sources": list[str],
"confidence": float,
}
``confidence`` is the edge's :pyattr:`Edge.aggregate_confidence`
— the worst-case per-source product of extraction and source
confidence across all sources. Read tools that want the
*best*-case confidence should consult the edge's
``extraction_confidences`` / ``source_confidences`` arrays
directly, but most callers want the floor.
"""
return {
"subject": edge.subject,
"relation": edge.relation,
"object": edge.object,
"valid_from": edge.valid_from,
"valid_until": edge.valid_until,
"sources": list(edge.sources),
"confidence": edge.aggregate_confidence,
}
def entity_summary(name: str, type_: Optional[str] = None) -> dict:
"""Compact ``{name, type}`` triple used by lookup-style tools.
``type_`` is optional because the in-memory graph doesn't
always carry type metadata (markdown-only entities without
structured-YAML types, for example). When ``None``, the
summary just carries the name.
"""
return {"name": name, "type": type_ or ""}
def envelope(ok: bool, data: Any = None, error: Optional[str] = None) -> dict:
"""Wrap a tool result in the standard ``{ok, data}`` envelope.
Used by the slice-4.7 write tools (which can fail) and by any
read tool that wants to surface an error rather than raising.
Successful reads don't need the envelope — they return plain
dicts / lists.
"""
if ok:
return {"ok": True, "data": data}
return {"ok": False, "error": error or "unknown error"}
__all__ = ["edge_to_fact", "entity_summary", "envelope"]