@@ -310,10 +310,138 @@
310 310 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
data=locations,
311 311 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
query_type="filter",
312 312 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
raw_query=f"provenance({entity_name!r})",
313 313 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
explanation=f"Found {len(locations)} provenance records for '{entity_name}'",
314 314 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
)
315 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
316 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def shortest_path(self, start: str, end: str, max_depth: int = 6) -> QueryResult:
317 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Find the shortest path between two entities via BFS."""
318 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ start_entity = self.store.get_entity(start)
319 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ end_entity = self.store.get_entity(end)
320 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not start_entity:
321 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return QueryResult(
322 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data=[],
323 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query_type="filter",
324 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ raw_query=f"shortest_path({start!r}, {end!r})",
325 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ explanation=f"Entity '{start}' not found",
326 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
327 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not end_entity:
328 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return QueryResult(
329 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data=[],
330 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query_type="filter",
331 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ raw_query=f"shortest_path({start!r}, {end!r})",
332 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ explanation=f"Entity '{end}' not found",
333 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
334 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
335 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ all_rels = self.store.get_all_relationships()
336 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Build adjacency list
337 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ adj: dict[str, list[tuple[str, dict]]] = {}
338 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for rel in all_rels:
339 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ src_l = rel["source"].lower()
340 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tgt_l = rel["target"].lower()
341 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ adj.setdefault(src_l, []).append((tgt_l, rel))
342 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ adj.setdefault(tgt_l, []).append((src_l, rel))
343 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
344 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # BFS
345 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ start_l = start.lower()
346 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ end_l = end.lower()
347 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if start_l == end_l:
348 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return QueryResult(
349 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data=[start_entity],
350 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query_type="filter",
351 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ raw_query=f"shortest_path({start!r}, {end!r})",
352 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ explanation="Start and end are the same entity",
353 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
354 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
355 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from collections import deque
356 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
357 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ queue: deque[tuple[str, list[dict]]] = deque([(start_l, [])])
358 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ visited = {start_l}
359 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
360 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ while queue:
361 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ current, path = queue.popleft()
362 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(path) >= max_depth:
363 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ continue
364 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for neighbor, rel in adj.get(current, []):
365 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if neighbor in visited:
366 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ continue
367 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ new_path = path + [rel]
368 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if neighbor == end_l:
369 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Build result: entities + relationships along path
370 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path_entities = [start_entity]
371 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for r in new_path:
372 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path_entities.append(r)
373 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tgt_name = r["target"] if r["source"].lower() == current else r["source"]
374 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ e = self.store.get_entity(tgt_name)
375 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if e:
376 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path_entities.append(e)
377 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path_entities.append(end_entity)
378 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Deduplicate
379 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ seen = set()
380 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ deduped = []
381 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for item in path_entities:
382 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ key = str(item)
383 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if key not in seen:
384 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ seen.add(key)
385 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ deduped.append(item)
386 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return QueryResult(
387 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data=deduped,
388 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query_type="filter",
389 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ raw_query=f"shortest_path({start!r}, {end!r})",
390 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ explanation=f"Path found: {len(new_path)} hops",
391 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
392 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ visited.add(neighbor)
393 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ queue.append((neighbor, new_path))
394 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
395 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return QueryResult(
396 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data=[],
397 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query_type="filter",
398 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ raw_query=f"shortest_path({start!r}, {end!r})",
399 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ explanation=f"No path found between '{start}' and '{end}' within {max_depth} hops",
400 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
401 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
402 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def clusters(self) -> QueryResult:
403 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Find connected components (clusters) in the graph."""
404 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ all_entities = self.store.get_all_entities()
405 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ all_rels = self.store.get_all_relationships()
406 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
407 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Build adjacency
408 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ adj: dict[str, set[str]] = {}
409 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for e in all_entities:
410 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ adj.setdefault(e["name"].lower(), set())
411 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for r in all_rels:
412 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ adj.setdefault(r["source"].lower(), set()).add(r["target"].lower())
413 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ adj.setdefault(r["target"].lower(), set()).add(r["source"].lower())
414 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
415 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ visited: set[str] = set()
416 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ components: list[list[str]] = []
417 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
418 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for node in adj:
419 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if node in visited:
420 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ continue
421 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ component: list[str] = []
422 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ stack = [node]
423 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ while stack:
424 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ n = stack.pop()
425 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if n in visited:
426 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ continue
427 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ visited.add(n)
428 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ component.append(n)
429 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ stack.extend(adj.get(n, set()) - visited)
430 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ components.append(sorted(component))
431 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
432 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Sort by size descending
433 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ components.sort(key=len, reverse=True)
434 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
435 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result = [{"cluster_id": i, "size": len(c), "members": c} for i, c in enumerate(components)]
436 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
437 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return QueryResult(
438 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data=result,
439 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query_type="filter",
440 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ raw_query="clusters()",
441 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ explanation=f"Found {len(components)} clusters",
442 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
315 443 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
316 444 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
def sql(self, query: str) -> QueryResult:
317 445 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"""Execute a raw SQL query (SQLite only)."""
318 446 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
result = self.store.raw_query(query)
319 447 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return QueryResult(
@@ -350,10 +478,12 @@
350 478 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
f"Graph stats: {json.dumps(stats)}\n\n"
351 479 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"Available actions (pick exactly one):\n"
352 480 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
'- {{"action": "entities", "name": "...", "entity_type": "..."}}\n'
353 481 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
'- {{"action": "relationships", "source": "...", "target": "...", "rel_type": "..."}}\n'
354 482 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
'- {{"action": "neighbors", "entity_name": "...", "depth": 1}}\n'
483 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ '- {{"action": "shortest_path", "start": "...", "end": "..."}}\n'
484 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ '- {{"action": "clusters"}}\n'
355 485 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
'- {{"action": "stats"}}\n\n'
356 486 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
f"User question: {question}\n\n"
357 487 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"Return ONLY a JSON object with the action. Omit optional fields you don't need."
358 488 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
)
359 489 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
@@ -398,10 +528,17 @@
398 528 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
elif action == "neighbors":
399 529 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
result = self.neighbors(
400 530 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
entity_name=plan.get("entity_name", ""),
401 531 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
depth=plan.get("depth", 1),
402 532 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
)
533 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ elif action == "shortest_path":
534 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result = self.shortest_path(
535 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ start=plan.get("start", ""),
536 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ end=plan.get("end", ""),
537 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
538 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ elif action == "clusters":
539 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result = self.clusters()
403 540 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
elif action == "stats":
404 541 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
result = self.stats()
405 542 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
else:
406 543 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return QueryResult(
407 544 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
data=None,
408 545 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!