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
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ rust-version = "1.85.0"
publish = false

[workspace.dependencies]
issundb = "0.1.0-alpha.5"
issundb = "0.1.0-alpha.6"

[profile.release]
strip = "debuginfo"
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Tests](https://img.shields.io/github/actions/workflow/status/IssunDB/example-apps/tests.yml?label=tests&style=flat&labelColor=282c34&logo=github)](https://github.com/IssunDB/example-apps/actions/workflows/tests.yml)
[![Code Coverage](https://img.shields.io/codecov/c/github/IssunDB/example-apps?label=coverage&style=flat&labelColor=282c34&logo=codecov)](https://codecov.io/gh/IssunDB/example-apps)
[![Python version](https://img.shields.io/badge/python-%3E=3.10-3776ab?style=flat&labelColor=282c34&logo=python)](https://github.com/IssunDB/example-apps)
[![License: MIT](https://img.shields.io/badge/license-MIT-ffd343?style=flat&labelColor=282c34&logo=open-source-initiative)](LICENSE)
[![License: MIT](https://img.shields.io/badge/license-MIT-3776ab?style=flat&labelColor=282c34&logo=open-source-initiative)](LICENSE)

This repository includes a collection of example applications that use the [IssunDB](https://github.com/IssunDB/issun-db) graph database.

Expand All @@ -13,12 +13,12 @@ This repository includes a collection of example applications that use the [Issu

Currently, the following table lists the included examples:

| # | Example | Language | Description |
|---|-------------------------------------------------------|----------|------------------------------------------------------------------------------------------|
| 1 | [GraphRAG Pipeline](rust/graphrag-agent) | Rust | Knowledge graph extraction and retrieval-augmented generation using an LLM. |
| 2 | [Codebase Explorer](rust/code-explorer) | Rust | Syntax dependency graph constructor and function ranking using PageRank. |
| 3 | [Fraud Detection System](python/fraud) | Python | Real-time financial transaction stream analyzer using Cypher queries. |
| 4 | [Social Recommendation System](python/recommendation) | Python | Hybrid friend and content recommender using collaborative filtering and semantic search. |
| # | Example | Language | Description |
|---|-------------------------------------------------------|----------|---------------------------------------------------------------------------------------------------|
| 1 | [GraphRAG Pipeline](rust/graphrag-agent) | Rust | Knowledge graph extraction and retrieval-augmented generation using an LLM. |
| 2 | [Codebase Explorer](rust/code-explorer) | Rust | Syntax dependency graph constructor and function ranking using PageRank. |
| 3 | [Fraud Detection System](python/fraud) | Python | Real-time financial transaction stream analyzer using Cypher queries. |
| 4 | [Social Recommendation System](python/recommendation) | Python | Hybrid friend and content recommender using graph traversal, vector search, and hybrid retrieval. |

---

Expand Down Expand Up @@ -51,7 +51,7 @@ To build and run the example applications, you need:

#### Running the Demos

You can run any of the demos using the provided `Makefile` targets:
You can run any of the demos using the provided [`Makefile`](Makefile) targets:

- Run the GraphRAG pipeline:
```bash
Expand Down
8 changes: 5 additions & 3 deletions python/fraud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ This example detects suspicious patterns and fraudulent behaviors in a real-time

### How It Works

1. Generates a continuous stream of financial transactions, accounts, and registration events.
2. Ingests the stream into IssunDB; map accounts, transactions, and devices as nodes and edges.
3. Executes Cypher queries concurrently over the database to identify circular transfers, shared devices, and stolen credentials.
1. Generates a deterministic, (seeded) stream of financial events (that includes account, device, and merchant registrations, transfers, payments, and
device logins information).
2. Ingests the stream into IssunDB; maps accounts, devices, and merchants as nodes and transfers, payments, and logins as edges.
3. Runs four Cypher-based detectors after every batch insert to flag things like circular transfer rings, shared devices, money-mule fan-in, and
velocity bursts.

More detailed workflow is shown below:

Expand Down
6 changes: 5 additions & 1 deletion python/fraud/src/fraud_detection_stream/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,15 @@ def main(argv: list[str] | None = None) -> int:
if not batch:
break
batch_no += 1
batch_first_ts = batch[0].ts
for event in batch:
store.apply(event)
last_ts = max(last_ts, event.ts)

since_ts = last_ts - args.window
# Look back `window` seconds, but never start later than the oldest
# event in this batch: a batch whose span exceeds the window would
# otherwise drop patterns injected early in it before they are scored.
since_ts = min(last_ts - args.window, batch_first_ts)
new_alerts: list[Alert] = []
for alert in run_all_detectors(db, since_ts):
key = (alert.rule, alert.subject)
Expand Down
10 changes: 6 additions & 4 deletions python/recommendation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ This example builds a social network graph and recommends content and friends to

### How It Works

1. Creates a graph of users, interests, followed tags, and published posts in IssunDB.
2. Computes text embeddings for posts and interests so we can run vector-based similarity checks on them.
3. Combines graph traversal (friend-of-a-friend collaborative filtering) and vector search (semantic similarity) to recommend relevant articles and
new connections.
1. Creates a graph of users, topics, and posts in IssunDB, with `FOLLOWS`, `POSTED`, `ABOUT`, and `LIKES` edges; each user has topic affinities
that drive their interest vector.
2. Computes interest-vector embeddings for users and posts and builds a full-text index over post text, so the graph supports both semantic and
keyword search.
3. Provides four recommendation features, including friend-of-friend suggestions through Cypher, kindred users and posts through vector search,
trending topics through Cypher aggregation over recent likes, and a hybrid discover feed that fuses vector, text, and one-hop graph expansion.

More detailed workflow is shown below:

Expand Down
6 changes: 4 additions & 2 deletions rust/code-explorer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ This example indexes Rust source code, builds a syntax dependency graph, and ran
### How It Works

1. Parses Rust source files into abstract syntax trees (using the `syn` library).
2. Stores syntax elements (like modules, functions, and structs) as nodes, and dependencies (like calls, imports, and definitions) as edges in IssunDB.
3. Computes the structural importance of functions using the PageRank algorithm on the constructed graph.
2. Stores files, functions, structs, enums, and traits as nodes, with `CONTAINS`, `CALLS`, `METHOD_OF`, and `IMPLEMENTS` edges between them.
3. Answers structural questions over the graph, including callers and callees through Cypher, dead-code candidates, transitive impact through native
adjacency traversal, the shortest call path between two functions, weakly connected components, and cycle detection.
4. Ranks functions by structural importance with PageRank over the code graph.

More detailed workflow is shown below:

Expand Down
13 changes: 9 additions & 4 deletions rust/code-explorer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ enum Command {
Dead,
/// Everything transitively affected if this function changes.
Impact { name: String },
/// Most structurally important functions (PageRank over the call graph).
/// Most structurally important functions (PageRank over the code graph).
Rank {
#[arg(long, default_value_t = 15)]
top: usize,
},
/// Connected components and recursion cycles in the call graph.
/// Connected components and reference cycles across the code graph.
Structure,
/// Shortest call path between two functions.
Path { from: String, to: String },
Expand Down Expand Up @@ -176,6 +176,9 @@ fn impact(graph: &Graph, name: &str) -> Result<()> {
}

fn rank(graph: &Graph, top: usize) -> Result<()> {
// PageRank runs over every edge type (CALLS, CONTAINS, METHOD_OF,
// IMPLEMENTS), so the score is whole-code-graph centrality, not pure
// call-graph importance; we then keep only the Function nodes.
let scores = graph.page_rank(30, 0.85)?;
let functions = graph.nodes_by_label("Function")?;
let mut ranked: Vec<(NodeId, f32)> = functions
Expand All @@ -184,7 +187,7 @@ fn rank(graph: &Graph, top: usize) -> Result<()> {
.collect();
ranked.sort_by(|a, b| b.1.total_cmp(&a.1));

println!("most depended-upon functions (PageRank over the call graph):");
println!("most central functions (PageRank over the code graph):");
for (node, score) in ranked.into_iter().take(top) {
println!(
" {score:.5} {:<36} {}",
Expand All @@ -208,8 +211,10 @@ fn structure(graph: &Graph) -> Result<()> {
sizes.len(),
&sizes[..sizes.len().min(8)]
);
// detect_cycle spans all edge types; in this schema a cycle is almost
// always CALLS recursion, but it is not restricted to it.
println!(
"recursion present: {}",
"cycle present: {}",
if graph.detect_cycle()? { "yes" } else { "no" }
);
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion rust/graphrag-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This example implements a knowledge graph extraction and question-answering pipe
### How It Works

1. Reads text documents, splits them into text chunks, extracts entities, and records entity co-occurrences.
2. Indexes chunks using a vector index (for semantic retrieval) and an BM25 text index (for keyword retrieval).
2. Indexes chunks using a vector index (for semantic retrieval) and a BM25 text index (for keyword retrieval).
3. Performs a hybrid search on both indexes and resolves the reciprocal-rank fusion of the results.
4. Traverses the graph structure to extract adjacent entities and document contexts.
5. Combines the retrieved context and queries an LLM to generate the final answer.
Expand Down
48 changes: 44 additions & 4 deletions rust/graphrag-agent/src/ingest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,10 @@ fn chunk(text: &str) -> Vec<String> {
current.push_str("\n\n");
}
current.push_str(para);
// Hard-split oversized paragraphs on word boundaries.
// Hard-split oversized paragraphs, cutting only at UTF-8 char
// boundaries so multi-byte text never panics the slice.
while current.len() > CHUNK_MAX {
let cut = current[..CHUNK_MAX]
.rfind(char::is_whitespace)
.unwrap_or(CHUNK_MAX);
let cut = split_point(&current);
let rest = current.split_off(cut);
chunks.push(std::mem::take(&mut current));
current = rest.trim_start().to_owned();
Expand All @@ -186,6 +185,32 @@ fn chunk(text: &str) -> Vec<String> {
chunks
}

/// Byte index at or below `CHUNK_MAX` at which to split a too-long chunk.
///
/// Prefers the last whitespace boundary within the limit and always lands on a
/// UTF-8 char boundary, so a chunk containing multi-byte characters cannot
/// trigger a panic when sliced. The result is at least 1, so the split loop
/// always makes progress.
fn split_point(s: &str) -> usize {
if let Some((idx, _)) = s
.char_indices()
.take_while(|(i, _)| *i <= CHUNK_MAX)
.filter(|(_, c)| c.is_whitespace())
.last()
{
if idx > 0 {
return idx;
}
}
// No usable whitespace in range: fall back to the largest char boundary
// at or below the limit.
let mut cut = CHUNK_MAX;
while cut > 1 && !s.is_char_boundary(cut) {
cut -= 1;
}
cut
}

/// Heuristic named-entity extraction: runs of capitalized words.
///
/// A real pipeline would use an NER model or an LLM here; runs of TitleCase
Expand Down Expand Up @@ -258,4 +283,19 @@ mod tests {
assert_eq!(chunks.len(), 1);
assert!(chunks[0].contains("para three"));
}

#[test]
fn chunking_splits_multibyte_text_without_panic() {
// A whitespace-free run of 3-byte characters: byte CHUNK_MAX lands
// mid-character, which a naive byte slice would panic on.
let text = "\u{4f60}".repeat(400); // 1200 bytes, 400 chars
let chunks = chunk(&text);
assert!(
chunks.len() > 1,
"expected a hard split, got {}",
chunks.len()
);
// No whitespace in the input, so the split is lossless.
assert_eq!(chunks.concat().chars().count(), 400);
}
}
10 changes: 5 additions & 5 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading