How Fuzzy and Semantic Search Work in the Understand Anything Dashboard: A Technical Deep Dive

The Understand Anything dashboard implements interchangeable search engines where fuzzy search uses Fuse.js for substring matching on node metadata, while semantic search computes cosine similarity between query embeddings and pre-computed node vectors, both managed through the useDashboardStore state container.

The Understand Anything dashboard provides developers with dual search capabilities to navigate complex knowledge graphs efficiently. This article explains how the system implements both fuzzy and semantic search in the Understand Anything dashboard using the SearchEngine and SemanticSearchEngine classes located in the core package, wired into the dashboard's reactive state management.

The Two Search Engines: Fuzzy vs. Semantic

The architecture centers on two distinct engines living in the core package that provide interchangeable search strategies. Both engines expose a uniform interface, allowing the UI to remain agnostic about which algorithm powers the search.

Fuzzy Search with Fuse.js

The fuzzy engine is implemented in packages/core/src/search.ts and leverages the Fuse.js library for approximate string matching. When the dashboard loads a graph, it instantiates a new SearchEngine with all graph nodes and predefined FUSE_OPTIONS.

The engine preprocesses queries into extended OR-style strings (e.g., converting "auth control" into auth | control) before passing them to Fuse. The scoring algorithm weights four specific node fields:

  • name – The node identifier
  • tags – Associated categorical labels
  • summary – Brief description text
  • languageNotes – Implementation-specific annotations

Results are returned as an array of {nodeId, score} objects, with lower scores indicating better matches.

Semantic Search with Vector Embeddings

The semantic engine resides in packages/core/src/embedding-search.ts and implements the SemanticSearchEngine class. This approach relies on pre-computed embedding vectors stored in a Map<string, number[]> where each key maps to a node identifier.

When a query string is converted into an embedding vector (typically via an external service like OpenAI), the engine calculates cosine similarity between the query vector and every node's stored vector using the cosineSimilarity function. The system converts similarity scores to distances using 1 - similarity, sorts results by increasing distance, and returns the top matches passing an optional threshold parameter.

Wiring the Engines to the Dashboard UI

The dashboard decouples search implementation from presentation through a centralized state management layer in packages/dashboard/src/store.ts.

State Management in the Dashboard Store

The useDashboardStore Zustand store maintains two critical properties for search management:

searchMode: "fuzzy" | "semantic";
searchEngine: SearchEngine | null;

When a graph loads via the setGraph method, the store automatically instantiates a fuzzy SearchEngine with the graph nodes:

// store.ts → setGraph
const searchEngine = new SearchEngine(graph.nodes);

Mode switching occurs through the setSearchMode action, which updates the state without recreating the engine. The actual query execution happens in setSearchQuery:

// store.ts → setSearchQuery
const engine = get().searchEngine;
const mode   = get().searchMode;
if (!engine || !query.trim()) { … }

 // both modes currently use the same fuzzy engine
 // when embeddings are available, "semantic" will use SemanticSearchEngine
void mode;                       // placeholder for future branch
const searchResults = engine.search(query);
set({ searchQuery: query, searchResults });

According to the comment on line 28 of store.ts, the current implementation falls back to the fuzzy engine for both modes, but the architecture is designed to seamlessly swap in SemanticSearchEngine once embeddings are available.

Unified Result Interface

The UI components consume search results through the searchResults property of the store. Because both engines return a standardized SearchResult[] shape, the rendering layer requires no logic to distinguish between fuzzy substring matches and semantic vector similarity. Changing searchMode automatically routes queries through the appropriate algorithm without requiring UI modifications.

Practical Implementation Examples

Switching to Semantic Mode

Once embeddings are loaded, instantiate the semantic engine and update the store:

import { useDashboardStore } from "./store";
import { SemanticSearchEngine } from "@understand-anything/core/embedding-search";

// Switch mode
useDashboardStore.getState().setSearchMode("semantic");

// Replace engine with semantic implementation
const { nodes } = useDashboardStore.getState().graph!;
const embeddings = await fetchEmbeddings(); // custom fetch
const semanticEngine = new SemanticSearchEngine(nodes, embeddings);
useDashboardStore.setState({ searchEngine: semanticEngine });

Execute a fuzzy query against node metadata:

import { useDashboardStore } from "./store";

useDashboardStore.getState().setSearchMode("fuzzy");
useDashboardStore.getState().setSearchQuery("auth token");
const results = useDashboardStore.getState().searchResults;

// results → [{ nodeId: "node-123", score: 0.06 }, …]

Computing Semantic Similarity

Search using vector embeddings with configurable thresholds:

import { SemanticSearchEngine } from "@understand-anything/core/embedding-search";
import { embedQuery } from "./my-embedding-client";

async function semanticSearch(text: string) {
  const queryEmbedding = await embedQuery(text);
  const engine = new SemanticSearchEngine(
    useDashboardStore.getState().graph!.nodes,
    useDashboardStore.getState().graph!.embeddings
  );
  const results = engine.search(queryEmbedding, { limit: 10, threshold: 0.2 });
  console.log(results);
}

Customizing Fuzzy Scoring Weights

Extend the base SearchEngine to modify Fuse.js configurations:

import Fuse from "fuse.js";
import { SearchEngine } from "@understand-anything/core/search";

const customOptions = {
  keys: [{ name: "name", weight: 0.5 }, { name: "summary", weight: 0.5 }],
  threshold: 0.3,
  includeScore: true,
};

class MySearchEngine extends SearchEngine {
  constructor(nodes) {
    super(nodes);
    this.fuse = new Fuse(nodes, customOptions); // replace Fuse instance
  }
}

Summary

  • Fuzzy search in packages/core/src/search.ts uses Fuse.js to match queries against the name, tags, summary, and languageNotes fields of graph nodes, preprocessing queries into OR-style patterns.
  • Semantic search in packages/core/src/embedding-search.ts implements SemanticSearchEngine to compute cosine similarity between query embeddings and pre-computed node vectors stored in a Map<string, number[]>.
  • The useDashboardStore in packages/dashboard/src/store.ts manages the active search mode and engine instance, allowing seamless switching between algorithms without UI changes.
  • Both engines return a uniform SearchResult[] interface, decoupling the search implementation from the dashboard presentation layer.
  • The architecture currently defaults to fuzzy matching for both modes but is designed to support full semantic search once embeddings are populated.

Frequently Asked Questions

What is the difference between fuzzy and semantic search in the Understand Anything dashboard?

Fuzzy search performs approximate string matching on node metadata using Fuse.js, scoring results based on text similarity in fields like name and summary. Semantic search converts queries and node content into vector embeddings, then ranks results by cosine similarity to find conceptually related content regardless of exact keyword matches.

How does the dashboard switch between search modes?

The useDashboardStore maintains a searchMode state that can be toggled between "fuzzy" and "semantic" using the setSearchMode method. The store's searchEngine property holds the active instance, and while the current implementation uses the fuzzy engine for both modes, the architecture supports swapping in SemanticSearchEngine when embeddings are available.

Where are the search embeddings stored in the Understand Anything architecture?

Pre-computed embeddings are stored in a Map<string, number[]> within the graph data structure, where each key corresponds to a node identifier. The SemanticSearchEngine in packages/core/src/embedding-search.ts consumes this map to perform vector similarity calculations against query embeddings.

Can I customize the fuzzy search scoring weights?

Yes, the SearchEngine class can be extended to override the default Fuse.js configuration. By creating a subclass and replacing the this.fuse instance with custom FUSE_OPTIONS, you can adjust field weights, threshold sensitivity, and other Fuse.js parameters to tune search relevance for your specific dataset.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →