@inferagraph/core
v0.15.0
Published
AI-powered knowledge graph platform with WebGL visualization
Maintainers
Readme
@inferagraph/core
AI-powered knowledge graph platform with WebGL visualization. v0.9.1 — abstract base class renamed Datasource → DataSource for camel-case symmetry with sibling storage packages.
InferaGraph is a self-contained platform that holds graph data, performs AI reasoning via LLM, and renders interactive 3D visualizations. The consuming application is a thin shell that feeds data and displays results — it never invokes the LLM directly.
Features
- 3D force-directed graph visualization (WebGL/Three.js)
- Built-in graph store with query, filter, and search
- Domain-agnostic visibility predicate (uniform across all viz modes — graph, tree, future modes)
- Streaming chat-as-API with tool calls (
apply_filter,highlight,focus,annotate,set_inferred_visibility) auto-dispatched to the renderer - Hybrid retrieval — semantic (
embeddingStore.searchVector) + keyword + 1-hop graph expansion, weighted-sum merged - Cross-encoder rerank — per-candidate LLM relevance scoring with per-conversation cache, top-K kept
- Multi-turn conversations —
ConversationStoreinterface;InMemoryConversationStoreships in core. Pronoun resolution from prior turn'sretrievedNodeIds - Citation requirement — model is asked to cite every entity inline as
[[id]]; the host renders these as clickable links debugchat events — first-classChatEventmember; surfaced via the new<InferaGraph onDiagnostic>callback for ops-visibility badgesonToolCallOutcome— host callback fired after tool-call dispatch so UIs can render "applied / unknown" badgesGraphIndexer— reusable indexing engine; one call (embedAll,computeInferredEdges,recomputeInferredEdgesFor,reconcile) handles the whole RAG pipeline. Hosts wire it after data load; library handles the restembeddingText({contentKeys})— content body now drives the embedding's body (verbatim, nokey:prefix); attribute metadata becomes the header- Three-tier embedding progression — keyword search → cache-backed similarity → dedicated
EmbeddingStore(now with optionalsearchVectorfor vector-native stores) - Natural-language
queryprop — predicate compiled at runtime, ANDed with the explicitfilterprop - Inferred-edge overlay (RRF over LLM + embeddings + graph signals); toggleable via prop or
set_inferred_visibilitytool call - Drilldown + node detail —
+hover affordance, node-click handler,MemoryManagerLRU eviction - Pluggable LLM providers (Anthropic, OpenAI, Azure AI Foundry); host-blind core
- React entry point + a separate
dataentry for Next.js RSC contexts - CSS-themable overlays and controls
What's new in 0.12.1
AIEngine.buildCitationCandidatesno longer memoizes. 0.12.0 keyed the candidate list onstore.nodeCount, so an attribute-only edit (e.g. a slug rename with no add/remove) left a stale cache in place and the next chat turn cited the old value. Per-turn rebuild cost is single-digit ms at biblegraph scale (~hundreds to low thousands of nodes); not worth the freshness hazard. The candidate list is now rebuilt fresh on every turn.<ChatText>parses citations as a marked inline extension instead of pre-splitting. 0.12.0 split the text on[[token|matched-text]]first and ranmarked.parseInlineon each non-citation segment, which broke**[[slug|text]]**(marked saw orphan**markers on each side of the citation and dropped the emphasis). 0.12.1 registers acitationinline tokenizer on the per-instanceMarked, lexes the full text once, and walks the resulting token tree to React nodes — so emphasis, italic, and nested emphasis around a citation now render as<strong><a>...</a></strong>and friends. Codespans (`like this`) intentionally stay opaque: a citation token inside backticks renders as literal text in<code>. Public API is unchanged (text,renderCitation,className); raw HTML is still escaped per 0.10.3.
What's new in 0.12.0
- Citation wire format becomes
[[token|matched-text]]. Both segments are required. The engine now rewrites every entity-name occurrence (not just the first) into the new wire so the host renders one clickable link per mention with the model's exact casing preserved ("the Fall"stays lowercase article). Hard break: tokens without the|matched-textportion are NOT recognized; hosts upgrading from 0.11.x adopt the new shape on consumption. renderCitation: (token, matchedText) => ReactNode. Both arguments required. Hosts wire the anchor's text content frommatchedText(model's casing wins) and the URL/path fromtoken. The previous single-argument signature is gone — there is no back-compat path.- Citation scope = the whole graph. The injector consumes a
{ token, title }[]candidate list derived from every node in the store, not just the per-turn rerank top-K. Entities outside the relevant set (e.g. Seth in a turn focused on Adam) still cite when their title appears in the response. - System prompt softened. The
Citations:block now reads "Write naturally using each entity's name. The engine adds citation links automatically — you do not need to emit[[id]]tokens." The 0.11.0 alarmist "FORBIDDEN / CRITICAL ERROR" framing and the slug-shaped instructional examples are gone. Models write naturally; the engine guarantees the wire shape. - Idempotent injector. Running
injectCitationson its own output is a no-op: any pre-existing[[token|matched]]collapses to its matched text on the strip pass, then re-emerges identically on the scan pass. Bare[[slug]]model emissions are stripped entirely and treated as garbage. - Public surface change is hard-breaking but small. Hosts on 0.11.x update one regex (
[[token]]→[[token|matched-text]]) and one renderCitation signature; tests of the wire format follow.
What's new in 0.11.0
- Deterministic server-side citation injection. Production gpt-4o-class models routinely ignored the system prompt's
[[id]]requirement, leaving the host with uncited streaming text. Soft prompts are not a contract. After the model stream completes,AIEngine.chatnow scans the assistant text for first-occurrence titles of everyrelevantNodesentity and inserts[[citationKey]]directly after the matched span (whole-word, case-insensitive, prefix-aware soAdam Smithis never broken byAdam). When the corrected text differs from what streamed, the engine emits a newtext_replaceChatEvent so hosts replace the streamed-incremental bubble with the citation-complete final. Citations become a guaranteed property of the chat pipeline rather than something the model might forget. - New
ChatEventvarianttext_replace. Carries the FULL final text (no diff format).httpTransportreconstructs it from the SSE wire alongside the existing event types. Engines with no post-processing (nocitationKey) never emit it, so hosts that don't opt in see zero behavior change. - System prompt softened from "REQUIRED, NOT OPTIONAL / FORBIDDEN" to neutral wording. The post-processor handles the contract; the prompt is now belt-and-suspenders ("the host injects citations automatically if you skip them, but emitting them inline produces tighter output"). Existing
Cain [[cain]]example and tool-call/citation column guidance are unchanged. - Strictly additive on the public surface. Existing
ChatEventconsumers that exhaustivelyswitchshould addtext_replace(ordefault: assertNever(event)).
What's new in 0.10.3
<ChatText>dropsisomorphic-dompurify— escapes raw HTML viamarkedinstead. 0.10.2 sanitized parsed HTML by routing it throughDOMPurify.sanitize, butisomorphic-dompurifytransitively pulls injsdom→html-encoding-sniffer→@exodus/bytes/encoding-lite.js(ESM-only). Any process that imports the React entry server-side — Next.js SSR routes, Netlify functions, Vercel/Cloudflare workers running the Node runtime — crashed at module init withError [ERR_REQUIRE_ESM]: require() of ES Module @exodus/bytes/encoding-lite.js. The fix is to makemarkeditself the sanitizer: a per-instanceMarkedoverrides thehtmlrenderer so any raw<script>,<img onerror>, etc. is escaped to text rather than passed through. No DOM library is loaded at any point in the rendering pipeline. Public API is unchanged (ChatTextprops, citation behavior, default class,<strong>/<em>/<code>output for**bold**/*italic*/`code`); the change is purely an implementation swap. Removes theisomorphic-dompurifydependency from the package and from the consumer's installed tree (~33 transitive packages dropped includingjsdom,whatwg-encoding,html-encoding-sniffer,tough-cookie, etc.).
What's new in 0.10.2
<ChatText>React component — library now owns chat-text rendering. Hosts streaming assistant replies were each rolling their own markdown + citation parser (Bible Graph shipped one inChatBubble.tsxlast week), pullingmarked+isomorphic-dompurifyinto the host bundle and re-implementing the[[id]]token walk every time. The new component centralizes parse + sanitize + node assembly inside the library so the host's responsibility narrows to CSS styling and citation-link wiring. Passtext(the raw streamed string) plus an optionalrenderCitation(token)callback that returns the React node a[[token]]should become; the library splits on the citation regex, runs each non-citation segment throughmarked.parseInline()+ DOMPurify, and assembles the result. Default class is.ig-chat-text—themes/default.cssandthemes/dark.cssship<strong>/<em>/<code>typography rules so consumers get reasonable rendering out of the box; passclassNameto override. New runtime deps:marked^18.0.3 andisomorphic-dompurify^3.12.0 (already present in Bible Graph's bundle, so the migration is a net code reduction host-side).
What's new in 0.10.1
httpTransportnow reconstructsset_inferred_visibilityfrom the SSE wire. The event has been a member of theChatEventunion since Phase 5 (inferred-relationship overlay toggle), butreconstructChatEventnever grew a case for it, so a server-side route emitting{ type: 'set_inferred_visibility', visible: ... }was silently dropped on the client. Clients now receive bothvisible: trueandvisible: falsefaithfully; payloads with a non-booleanvisiblefield are rejected at the boundary just like the other event reconstructors. Pre-existing tech debt; no public API change.
What's new in 0.10.0
- Public host-driven dispatch surface (
useInferaGraphCommands,useInferaGraphChatContext). Until 0.9.5, hosts could only fire visual operations through chat —useInferaGraphChat()returned a chat iterator and nothing else. Hosts that wanted to reset highlights when a user clicks "Clear conversation" had to wrap the transport, mint a sentinel chat message, and synthesize an empty-idshighlightevent by hand. 0.10.0 promotes the renderer's dispatch sink to a public hook. The newuseInferaGraphCommands()returns a flat semantic facade (setHighlight,focusOn,applyFilter,setInferredVisibility,annotate,clearAnnotations,resetView,clearVisualState); reach foruseInferaGraphChatContext()only when you need to dispatch aChatEventvariant the facade doesn't expose. Both hooks throw with a clear message when used outside an<InferaGraph>subtree. - Three new parameterless
ChatEventvariants.clear_visual_state(the "fresh canvas" — drops highlights + annotations + filter, snaps the camera home),reset_view(camera-home only), andclear_annotations(drop every callout). They round-trip over the SSEhttpTransportwire too, so a server-side route can fire a reset just like a host UI button. Strictly additive — existingChatEventconsumers that exhaustivelyswitchshould adddefault: assertNever(event)(or accept the additions explicitly). SceneController.resetView()+clearVisualState(). New public methods on the controller that compose existing surfaces (setHighlight(empty),clearAnnotations(),setFilter(undefined),cameraController.resetRotation()). Call them directly from non-React hosts; the React layer maps the new ChatEvents to these methods.
What's new in 0.9.5
- Tool-call examples in the chat system prompt are now host-agnostic. The
Examples:block previously demonstratedhighlight()/focus()calls with literal Bible-Graph slugs (highlight(["garden-of-eden", "adam", "eve"]),focus("noah")). On UUID-keyed hosts those literals contradicted the catalog rows the model was actually reading and obscured the SHAPE of valid tool-call arguments. The examples now use<node-id-N>/<node-id>placeholders wrapped in angle brackets so a model reading the prompt understands the call shape without mistaking the placeholder for a real id. Strictly a prompt-content change; no public API surface moves. The slug-shaped citation example (Cain [[cain]]) under thecitationKey-set branch is unchanged — it is intentionally aligned with the catalog's last column per 0.9.4.
What's new in 0.9.4
citationKeyconfig aligns the catalog id with the cite-token example. 0.9.3's strengthened citation prompt instructed the model to cite using the catalog's first column, but the concrete example showed a slug-shaped token (Cain [[cain]]) while real catalog rows on UUID-keyed hosts (Bible Graph) carried UUID ids. Tool-use-trained models read the contradiction as "the rule does not match the data" and silently dropped citations for the whole turn. New optionalAIEngineConfig.citationKeynames a node attribute (e.g.,'slug') whose value is the citation token. When set, the catalog gains a trailing column carrying that value, and the system prompt instructs the model to cite using the LAST column — adding a note thathighlight()/focus()still take the FIRST (canonical) column. When unset, behavior matches 0.9.3 except the example uses generic[[node-id-N]]placeholders so it no longer contradicts UUID-shaped catalog ids. Strictly additive — hosts that don't setcitationKeysee zero behavior change beyond the example wording.
What's new in 0.9.3
- Citation requirement strengthened. The chat system-prompt's "cite every entity using
[[id]]" instruction is now framed asCITATIONS — REQUIRED, NOT OPTIONAL, with a concreteCain [[cain]]example and anUNCITED, FORBIDDENcounterexample. Tool-use-trained models read soft "cite" verbs as optional; the harder framing matches the pattern already used forhighlight()higher in the same prompt and stops models from skipping citations on multi-entity answers. SceneController.setHighlightandfocusOnnow return{ appliedIds, unknownIds }. When the LLM dispatches ahighlight()orfocus()tool call referencing an id the renderer hasn't seen (model hallucination or stale context), the React layer used to silently swallow the miss. The dispatch path now flows the partition throughonToolCallOutcome, so hosts can render "tried to highlightZbut it isn't in the graph" badges. Existing callers that ignored the void return are unaffected at the call site, butdispatchinInferaGraphChatContextis now typed(event) => ToolCallOutcome | undefined— host adapters that conformed to the previousvoidsignature should accept the new return value.
What's new in 0.9.1
Datasource→DataSourcerename (breaking). The abstract base class exported from@inferagraph/coreand@inferagraph/core/datais nowDataSource(camel-cased), matching the naming convention used by every sibling storage package (CosmosDataSource,GremlinDataSource,SqlDataSource,RedisDataSource,FileDataSource,LogAnalyticsDataSource). Behavior is unchanged. Hosts that subclass the base directly should renameextends Datasource→extends DataSourceand update their import.
What's new in 0.9.0
- Warmup test-noise silenced. Background
ensureEmbeddings()failures (typical with stub providers in unit tests) used to print[InferaGraph AIEngine] ensureEmbeddings failed: .../embed batch failed: ...toconsole.warn, polluting test output and forcing hosts to spy on the global console. The warmup path now routes failures through the chat diagnostic surface — the nextchat()call yields aChatEventof{type:'debug', phase:'warmup-failed', detail}and the buffer drains. Noconsole.warn, no test-runner clutter; failures still surface to hosts that consume the iterable. - Nominal-type collision between
@inferagraph/coreand@inferagraph/core/datafixed. Pre-0.9.0, the twotsupconfigs each produced an independentdeclare class AIEngine(and friends) in their.d.tsrollups. Because the class body carries private fields, TypeScript treated them as nominally distinct, soimport { AIEngine } from '@inferagraph/core'produced a value that was NOT assignable to a slot typed via@inferagraph/core/data— and vice versa. Folding all three entries into a single tsup config lets the DTS rollup share one chunk per shared class. OneAIEnginesymbol now flows from every entry; the assignability error disappears forAIEngine,GraphStore,QueryEngine,SearchEngine,GraphIndexer. No source-level import changes are required for consumers that already pick one entry per import — but if you previously worked around the collision with type assertions or aliased imports, you can now drop them. GraphIndexer.computeInferredEdgesbatched-prompt mode. New optionalinferredEdgeBatchSizeonGraphIndexerConfig. Default1(preserves the existing one-prompt-per-pair behavior). WithK > 1, the indexer asks the LLM for K relationship descriptions in a single JSON-array response per batch, droppingprovider.completecost from N calls toceil(N/K)on the happy path. Defensive parsing: a malformed batch response (non-JSON, missingdescriptionsarray, wrong array length, or call-level error) falls back to per-pair calls for THAT batch only — one bad batch never blocks the whole indexing pass.InMemoryCacheProviderships in core. NewinMemoryCacheProvider()factory +InMemoryCacheProviderclass give tests and short-lived dev workflows a Map-backed implementation with optional opt-in TTL and no size eviction.lruCache()remains the production default;@inferagraph/redis-cache-providercontinues to ship the persistent variant. Wire via the existingengine.setCache(...).CacheProviderwidened.setnow accepts an optional{ ttlSeconds }for per-call TTL override;delete(key)is added for targeted invalidation (distinct fromclear(), which still wipes everything). Existingset(key, value)callers are unaffected — the third argument is optional.InMemoryCacheProviderhonors both a construction-timettlSecondsdefault and per-call overrides via lazy expiry onget.lruCacheaccepts the per-callttlSecondsfor signature compatibility but ignores it (its TTL policy is fixed at construction); use construction-timettlconfig instead. External implementations such as@inferagraph/redis-cache-providerwill satisfy the wider interface in their next bump.
What's new in 0.8.1
httpTransportnow forwardsconversationIdfromChatOptionsinto the request body ({ message, emitToolCalls, conversationId? }). Omitted when undefined so server routes that treat "missing" as "generate a fresh id" still behave correctly.useInferaGraphChat().chat(message, { conversationId })accepts a per-callconversationId; the hook forwards it to the active transport so hosts no longer need to bypass the hook to thread conversation memory through their server route.
What's new in 0.8.0
LLMMessage/LLMRoletypes for structured-roles chat (already in 0.7.x; now stable surface)EmbeddingStore.searchVector?(queryEmbedding, {top, container})— additive optional method;InMemoryEmbeddingStoreimplements via linear-scan cosineChatEventgains{type:'debug', phase, detail?, counters?, conversationId?}AIEngineConfiggainsembeddingContentKeys,chatRerankEnabled,chatRerankCandidates,chatRerankTopK,priorTurnLimitAIEngine.setConversationStore(store)+chat(message, {conversationId})AIEngine.buildChatMessagesadds an Edges block, an Inferred-relationships block, a pronoun-resolution block (when applicable), and the citation requirementemitWithFallbacksrewritten as a pure reducer; empty-highlight substitution draws from per-query retrieval, zero-text synthesis grounds in the first retrieved node's content (no moreShowing X, Y, Zcatalog roll-up)<InferaGraph onDiagnostic onToolCallOutcome>props- Re-exports:
LLMMessage,LLMRole,ConversationStore,ConversationTurn,inMemoryConversationStore,SearchVectorHit,GraphIndexer,GraphIndexerConfig,IndexerProgress,DEFAULT_EMBEDDING_CONTENT_KEYS,EmbeddingTextOptions,ToolCallOutcome
Installation
pnpm add @inferagraph/coreTwo entry points:
@inferagraph/core/@inferagraph/core/react— React layer (client-side; touchesReact.createContext)@inferagraph/core/data— server-safe data layer (RSC, route handlers)
React component
import { InferaGraph } from '@inferagraph/core/react';
import { openaiProvider } from '@inferagraph/openai-provider';
<InferaGraph
data={data}
llm={openaiProvider({ apiKey: process.env.OPENAI_KEY! })}
query="people from the Patriarchs era"
onNodeClick={(id, node) => openDetailDialog(id, node)}
onExpandRequest={(id) => /* host can override; default expands neighbors */}
maxNodes={1000}
/>Selected props (see InferaGraphProps for the full list)
| Prop | Type | Notes |
|---|---|---|
| data | GraphData | Initial nodes + edges. |
| layout | LayoutMode | 'graph' (default) or 'tree'. |
| filter | (node) => boolean | Domain-agnostic visibility predicate. Same predicate applies in every viz mode. |
| query | string | NLQ; LLM compiles to predicate, ANDed with filter. Requires llm. |
| llm | LLMProvider | Pre-configured provider instance. Library is host-blind from this point. |
| cache | CacheProvider | Optional response cache. Default is no cache. |
| embeddingStore | EmbeddingStore | Optional Tier-3 vector store; default inMemoryEmbeddingStore() is exported. |
| transport | Transport | Override the default in-process chat transport (e.g., HTTP proxy). |
| showInferredEdges | boolean | Toggle the dashed inferred-edge overlay. Default false. Flipping on triggers an auto-fetch via the configured inferredEdgeStore. |
| inferredEdgeStore | InferredEdgeStore | Optional pluggable store; auto-fetch consumes this when showInferredEdges flips true. Default RemoteInferredEdgeStore('/api/inferred-edges') is the common browser shape. |
| onInferredEdgesLoadingChange | (loading) => void | Fires true when the auto-fetch starts, false on success / error / cancel. |
| onChat | (event) => void | Receives text + done events; tool calls dispatch silently to the renderer. |
| slugResolver | SlugResolver | Phase 6 — translates input slugs to canonical NodeIds for hooks. |
| maxNodes | number | Phase 6 — soft cap; MemoryManager LRU-evicts oldest non-protected nodes. |
| onNodeClick | (id, node) => void | Phase 6 — fires on node-body clicks (post-slug resolution). |
| onExpandRequest | (id) => void | Phase 6 — fires on + affordance clicks. Default handler calls useInferaGraphNeighbors().expand(id). |
| nodeColors / edgeColors | Record<string,string> | Type → color maps. Function variants via nodeColorFn / edgeColorFn. |
| incomingEdgeLabels / outgoingEdgeLabels | EdgeLabelMap | Tooltip relationship phrasing maps. |
Hooks (@inferagraph/core/react)
useInferaGraph()— store + AIEngine handlesuseInferaGraphChat()— streaming chat iteratoruseInferaGraphSearch()— keyword / similarity searchuseInferaGraphContent(idOrSlug)— fetch node detail content (usesslugResolverif configured)useInferaGraphNeighbors()— expand / collapse drilldownGraphProvider— context provider; needed only for advanced multi-instance setupscreateReactNodeRenderFn/createReactTooltipRenderFn— bridges for custom React node / tooltip components
DataAdapter contract
Every datasource plugin (and any custom one a host writes) implements seven methods:
interface DataAdapter {
getInitialView(config?): Promise<GraphData>;
getNode(id): Promise<NodeData | undefined>;
getNeighbors(id, depth?): Promise<GraphData>;
findPath(fromId, toId): Promise<GraphData>;
search(query, pagination?): Promise<PaginatedResult<NodeData>>;
filter(filter, pagination?): Promise<PaginatedResult<NodeData>>;
getContent(id): Promise<ContentData | undefined>;
}Inferred Relationships
The library ships an end-to-end overlay for edges the system believes exist between two nodes but that are NOT present as explicit edges in the store. Three pieces:
- Server-side compute + persistence.
AIEngine.computeInferredEdges()fuses graph signals (common neighbors, Jaccard, structural cosine), embedding similarity, and per-node LLM extraction via reciprocal-rank-fusion. Results land in the configuredInferredEdgeStore(inMemoryInferredEdgeStore()for self-contained apps;@inferagraph/cosmosdbshipsCosmosInferredEdgeStorefor production persistence). - HTTP surface.
createInferredEdgeRouteHandler(engine)builds a Web-Standard(Request) => Promise<Response>that returns the persisted edges as JSON. Lazy-computes on first request if the store is empty; concurrent first-requests share a single in-flight compute. - Client read.
RemoteInferredEdgeStore(url)implementsInferredEdgeStoreagainst the route handler. Hand it to<InferaGraph inferredEdgeStore={...}>; the auto-fetch effect kicks in whenshowInferredEdgesflips true and pipes the edges into the renderer.
Wiring — Next.js App Router
// app/api/inferred-edges/route.ts
import { createInferredEdgeRouteHandler } from '@inferagraph/core/server';
import { getServerEngine } from '@/lib/engine';
export const GET = createInferredEdgeRouteHandler(await getServerEngine());// app/page.tsx (or a client component)
import { InferaGraph, RemoteInferredEdgeStore } from '@inferagraph/core';
const inferredEdgeStore = new RemoteInferredEdgeStore('/api/inferred-edges');
<InferaGraph
data={data}
inferredEdgeStore={inferredEdgeStore}
showInferredEdges={enabled}
onInferredEdgesLoadingChange={setLoading}
/>;Wiring — Express (Node 20+ with global Request/Response)
const handler = createInferredEdgeRouteHandler(engine);
app.get('/api/inferred-edges', async (req, res) => {
const r = await handler(new Request(`http://x${req.originalUrl}`));
res.status(r.status);
r.headers.forEach((v, k) => res.setHeader(k, v));
res.send(await r.text());
});When to compute
The lazy first-request compute covers most cold-start cases, but a heavy LLM source can take longer than is friendly inside a route handler. Production deployments usually trigger compute out-of-band:
- Scheduled — a nightly cron / Azure Function / GitHub Actions workflow that calls
engine.computeInferredEdges()against the shared store. - On reindex — wire
computeInferredEdges()into yourGraphIndexer.reconcile()step so a content refresh that bumps embeddings also recomputes inferred edges. - On startup — kick a fire-and-forget
computeInferredEdges()after the server engine is constructed so the first chat / page load reads warm cache.
The route handler can sit alongside any of those — the lazyCompute: false option disables the inline trigger when an upstream pipeline already owns the work.
Per-tick positioning
Dashed inferred edges track node positions automatically while the force simulation runs. The renderer (SceneController.applyPositions → InferredEdgeMesh.updatePositions) rewrites the underlying Float32BufferAttribute in place on every frame — no buffer re-allocation, no rebuild. Callers push the edge set once (via the auto-fetch effect); the controller owns the rest.
Visualization follow-ups
InferredEdge carries score (the fused confidence) and reasoning (a human-readable rationale when the LLM source contributed). v1 stores these but doesn't yet surface them visually. Open follow-ups: opacity-by-confidence, hover tooltip showing the reasoning, per-source breakdown via InferredEdge.perSource.
LLM Providers
Hosts inject one provider instance at construction. The library never imports a provider SDK directly — the provider package owns its dependency.
pnpm add @inferagraph/anthropic-provider # Claude (+ optional Voyage embeddings)
pnpm add @inferagraph/openai-provider # OpenAI / Azure OpenAI / OpenRouter / GitHub Models
pnpm add @inferagraph/azure-foundry-provider # Azure AI Foundry catalogAnthropic has no native embeddings endpoint — pass an optional voyage config to its provider for embedding support, or mix-and-match (Anthropic for chat + a different provider's embed).
Cache providers
- Built-in
lruCache()— in-process, default(maxEntries: 500, ttl: '24h'). @inferagraph/redis-cache-provider— Redis-backed for shared / multi-process caches.
The cache prop also serves as Tier-2 embedding storage when the configured provider implements embed() and no dedicated embeddingStore is supplied.
License
MIT
