Skip to content
Open
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
1 change: 1 addition & 0 deletions src/vouch/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"kb.status",
"kb.stats",
"kb.search",
"kb.neighbors",
"kb.context",
"kb.read_page",
"kb.read_claim",
Expand Down
31 changes: 31 additions & 0 deletions src/vouch/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,27 @@ def search(
click.echo(f"{k}/{i}\t{snip} ({used})")


@cli.command()
@click.argument("node_id")
@click.option("--depth", default=1, show_default=True, type=int)
@click.option("--rel-type", "rel_types", multiple=True,
help="Filter to relation types (repeatable).")
@click.option("--max-nodes", default=50, show_default=True, type=int)
def neighbors(node_id: str, depth: int, rel_types: tuple[str, ...],
max_nodes: int) -> None:
"""List graph neighbors of a claim, page, entity, or source."""
from .graph import find_neighbors

store = _load_store()
with _cli_errors():
result = find_neighbors(
store, node_id, depth=depth,
rel_types=list(rel_types) or None,
max_nodes=max_nodes,
)
_emit_json(result)


@cli.command()
@click.argument("task")
@click.option("--limit", default=10, show_default=True, type=int)
Expand All @@ -1317,6 +1338,10 @@ def search(
@click.option("--min-items", default=0, type=int)
@click.option("--project", default=None, help="Viewer project for scope filtering.")
@click.option("--agent", default=None, help="Viewer agent for scope filtering.")
@click.option("--expand-graph", is_flag=True,
help="Include 1-hop graph neighbors of search hits.")
@click.option("--graph-depth", default=1, show_default=True, type=int)
@click.option("--graph-limit", default=20, show_default=True, type=int)
def context(
task: str,
limit: int,
Expand All @@ -1325,6 +1350,9 @@ def context(
min_items: int,
project: str | None,
agent: str | None,
expand_graph: bool,
graph_depth: int,
graph_limit: int,
) -> None:
"""Build a ContextPack ready to inject into an agent prompt."""
store = _load_store()
Expand All @@ -1337,6 +1365,9 @@ def context(
require_citations=require_citations,
project=project,
agent=agent,
expand_graph=expand_graph,
graph_depth=graph_depth,
graph_limit=graph_limit,
)
_emit_json(pack)

Expand Down
72 changes: 71 additions & 1 deletion src/vouch/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import yaml

from . import index_db
from . import graph, index_db
from .models import ClaimStatus, ContextItem, ContextPack, ContextQuality
from .scoping import (
ViewerContext,
Expand Down Expand Up @@ -138,6 +138,65 @@ def _enrich_summary(store: KBStore, kind: str, artifact_id: str, summary: str) -
return summary


def _append_graph_neighbors(
store: KBStore,
items: list[ContextItem],
*,
depth: int,
limit: int,
rel_types: list[str] | None,
) -> list[str]:
"""Expand `items` with 1-hop (or deeper) graph neighbors. Returns warnings."""
warnings: list[str] = []
if not items:
return warnings
seed_scores = {it.id: it.score for it in items}
neighbors = graph.graph_neighbors_for_seeds(
store,
[it.id for it in items],
depth=depth,
rel_types=rel_types,
max_nodes=limit,
)
existing = {it.id for it in items}
added = 0
for node in neighbors:
nid = node["id"]
if nid in existing:
continue
kind = node["kind"]
cites: list[str] = []
if kind == "claim":
try:
claim = store.get_claim(nid)
except ArtifactNotFoundError:
continue
if claim.status in _RETRACTED_CLAIM_STATUSES:
continue
cites = list(claim.evidence)
via = node.get("via", "")
parent_score = seed_scores.get(via, 0.5)
distance = int(node.get("distance", 1))
score = parent_score * (0.8 ** distance)
Comment thread
jsdevninja marked this conversation as resolved.
summary = node.get("summary") or _enrich_summary(store, kind, nid, "")
items.append(
ContextItem(
id=nid,
type=cast(ContextItemKind, kind),
Comment thread
jsdevninja marked this conversation as resolved.
summary=summary,
score=score,
backend="graph",
citations=cites,
freshness="unknown",
)
)
existing.add(nid)
added += 1
if added:
warnings.append(f"graph expansion added {added} neighbor(s)")
Comment thread
jsdevninja marked this conversation as resolved.
return warnings


def build_context_pack(
store: KBStore,
*,
Expand All @@ -151,6 +210,10 @@ def build_context_pack(
explain: bool = False,
project: str | None = None,
agent: str | None = None,
expand_graph: bool = False,
graph_depth: int = 1,
graph_limit: int = 20,
graph_rel_types: list[str] | None = None,
) -> ContextPack | dict[str, Any]:
viewer = viewer_from(
config_path=store.config_path,
Expand Down Expand Up @@ -184,6 +247,13 @@ def build_context_pack(
)

warnings: list[str] = []
if expand_graph:
warnings.extend(
_append_graph_neighbors(
store, items, depth=graph_depth, limit=graph_limit,
rel_types=graph_rel_types,
)
)
failed: list[str] = []
uncited: list[str] = []
budget_truncated = False
Expand Down
Loading
Loading