Skip to content

About

No embeddings. No vector DB. Your knowledge becomes a criteria tree — an LLM decides its way down, and every hit comes with a readable path. Pure Java 8, zero dependencies.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

简体中文 | English

SemQuestTree 问义树

License: MIT JDK Dependencies

Search without embeddings. Your knowledge becomes a tree of criteria; a decision model walks content down the tree layer by layer — add files it into leaves, quest retrieves by natural language. Every hit carries its full decision path: which criteria fired, at what probability, at what token cost.

  • Explainable — a readable decision path with P(yes) trace, not a black-box similarity score
  • Multi-label — one utterance lands in several leaves at once (noul mode); single-path descent also available (choice mode)
  • Human-steerable — criteria are configuration: edit them in the UI, per-subtree rule overrides, effective on save
  • Zero infrastructure — no embedding model, no vector DB, no indexer; JDK 8+ and it runs
  • Visual — built-in server: expand trees, click nodes, edit criteria, hot-swap models, query console
  • Self-growing (opt-in) — a quest that lands on fallbacks means the tree says "don't know": the generation model (qwen/qwen3.7-flash in our runs; decision calls on microsoft/microsoft-decision-1) writes new branch criteria on the spot, each kept only if the triggering query now hits it (rolled back otherwise) — the hit happens within the same request; switch on with growth.enabled

Good fit: FAQ / policy lookup, ticket triage, intent routing — any small-to-medium index a human must read and control.

Self-growth as measured: microsoft-decision-1 for decisions, qwen3.7-flash for criteria generation — 50 docs batch-imported, the tree grows to depth 4 on its own

Full write-up of the run above (tree building / self-growth / capacity & latency / cost): docs/SELF-GROWING.zh-CN.md

What a table looks like

One table = one criteria tree. A demo table, built with a handful of HTTP API POSTs:

ROOT
├── Fare     ── surge / lowball / unpaid
├── Cargo    ── damaged / loading
├── Schedule ── late / cancel
└── Service  ── complaint / praise / contact

Every node carries a yes criterion (an event-level atomic assertion); leaves hold the placed docs; every layer grows an X_OTHER fallback leaf. add and quest descend the same tree — the decision model answers one batched noul question per layer, and anything below the gate falls to that layer's fallback:

content / query descends via lands in
This Fare is a pure lowball for 40 km, please raise it. Fare → lowball lowball
The ride is done but the Fare is still unpaid, I need that money. Fare → unpaid unpaid
driver says the Fare is unpaid and the Cargo arrived damaged (query) Fare→unpaid ∥ Cargo→damaged unpaid + damaged

Retrieval score = number of hit leaves; every hit ships with the full per-layer probability trace.

Up and running in 60 seconds (no credentials)

git clone https://github.com/LEERYx/SemQuestTree.git
cd SemQuestTree
javac -encoding UTF-8 -d target/classes $(find src/main/java -name "*.java")   # Windows: use Git Bash / WSL
java -cp target/classes org.example.SemQuestServer
  • No credentials configured → the server falls back to the built-in mock backend (zero tokens, zero network). Open http://localhost:8787 — the demo table 司机意图 migrates automatically; open its 控制台 → 检索 and try 找运费价格相关:要求涨价、讨要运费的话术
  • English demo table (Driver Intents): java -cp target/classes org.example.SemQuestClient seed
  • Real models: copy semquest.properties.example → semquest.properties, see Configuration

Running the visualization server

From the SemQuestTree directory, compile + start:

mkdir -p target/classes && javac -encoding UTF-8 -d target/classes $(find src/main/java -name "*.java") \
     && java -cp target/classes org.example.SemQuestServer

Or start directly if already compiled:

java -cp target/classes org.example.SemQuestServer

Stop the server (kills every local SemQuestServer process):

pkill -f org.example.SemQuestServer

If upstream decision endpoints are region-blocked, append -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=7890 (adjust the port) to the java command; more in docs/TROUBLESHOOTING.md.

At http://localhost:8787 :

  • Start page: all tables as cards — 新建表 (name + branch mode noul / choice + note, fixed at creation), 改名 / 备注 / 删表
  • Left-click a node to expand one layer; 全部展开 pulls the whole tree; click a node → the right panel shows path, criteria and placed docs (internal nodes add the subtree's docs, 10 at a time)
  • Right-click → 增加判断节点 / 增加选项: creating a branch re-judges the parent's existing docs into it; 修改节点 edits name / criteria with zero model calls; 删除节点 removes the subtree and its placements (root and fallback leaves protected)
  • Header model bar (all tables): pick backend jev / openrouter / 百炼 / mock, then a model — selecting hot-swaps instantly; ⚡ = decision-endpoint models, ·免费 = free
  • 控制台 tab, two modes (Enter runs, Shift+Enter newlines): 落位 add places one line of content (paste several → batch), 检索 search returns hit leaves, ranked docs, decision trace and token bill; repeating the exact same query returns the cached result (⚡, zero model calls)
  • Top-right parameter box: 门控 (noul gate) / 采纳线 (choice acceptance line) — runtime-adjustable, persisted per table, never re-ranks placed data
  • EN / 中文 switches the UI language

Each table persists at tables/<id>.json (structure, criteria, gates, placements, growth / edit records); restart reads the snapshot with zero model calls. --reseed rebuilds only demo tables from the schema (calls the model):

java -cp target/classes org.example.SemQuestServer --reseed

Console demo (optional)

java -cp target/classes org.example.Demo
java -cp target/classes org.example.Demo mock        # pick a backend (mock | jev | openrouter | bailian)
java -cp target/classes org.example.Demo jev 0.7     # backend + gate

Prints add placement traces, quest hits and the token bill.

HTTP API & Java client

The visualization server is itself a full REST API — every endpoint documented in docs/API.md. A zero-dependency Java client ships in org.example.SemQuestClient: copy it with Json.java into your backend and call any endpoint; credentials stay server-side:

java -cp target/classes org.example.SemQuestClient                    # demo: create table → branches → docs → retrieve → drop; ~6 real decision calls, switch to mock for zero cost
java -cp target/classes org.example.SemQuestClient http://localhost:8790   # custom address

Configuration

All configuration lives in semquest.properties (start by copying semquest.properties.example; the real credentials file is excluded by .gitignore). Priority: -Dsemquest.KEY=... > config file > code defaults.

Two providers' API keys are currently supported: jev and OpenRouter — apply on their platforms and fill the keys in the config file. Bailian has no decision model available yet; it will be adapted once one ships (its config entries are kept for then). For a no-credential experience use backend=mock.

Key Meaning
backend mock | jev | openrouter | bailian (jev without a key falls back to openrouter, then to mock; mock always works with no credentials)
noulGate noul gate, default 0.7 (initial value for new noul tables; adjustable per table from the header)
choiceGate choice acceptance line, default 0.6 (initial value for new choice tables; adjustable per table from the header)
prompt.* decision prompts (server-composed wording), add (placement) and search (retrieval) each with their own set: prompt.rule.add / prompt.rule.search (state rule; default for new tables — a hot reload swaps only tables still carrying the previous default text, customized ones are untouched), prompt.noul.add.instructions ({name} = branch name), prompt.noul.search.criterion ({yes} = raw criterion, {name} = branch name; wraps the criterion text in a retrieval frame before judging so generic queries like "all sad utterances" can pass mechanism-description criteria — the add side is a hard-coded identity {yes}, no config key), prompt.noul.search.query ({query} = the raw query; wraps the retrieval request in a role frame too, anchoring "the query" for the criterion frame so meta-descriptive queries are not judged as content themselves — default "用户想检索:{query}", set {query} for identity), prompt.choice.add.root / prompt.choice.add.node ({node} = node name, ROOT layer uses the root key), prompt.choice.other (fallback option description); optional divergence keys prompt.noul.search.instructions / prompt.choice.search.root / prompt.choice.search.node inherit the add-side value when absent or blank; delete the line or leave it blank for the built-in default
questCacheSize query-result cache capacity (entries), default 256; 0 = off; identical queries return with zero model calls; hot-reloaded on config save
growth.* search-as-computation synchronous growth (off by default): growth.enabled switch / growth.model generation model (default free Chinese xingchen-agi/xing4.0-29b-a4b:free, key reuses openrouter.apikey) / growth.url endpoint override (optional) / growth.maxBranches per-layer cap guard (default 12) — a fallback quest synchronously generates ≤2 new branches; acceptance re-runs the triggering query and rolls back misses, all-fail returns the original fallback result; hot-reloaded
schema demo table migration source, default driver-intent-schema.json
timeoutMs per-request timeout in milliseconds
server.port / server.tables / server.data / server.viz server port / tables dir / legacy data file (migration) / page file (restart required)
jev.* / openrouter.* the two decision-model providers' url / model / apikey (bring your own keys)
bailian.* Bailian url / model / apikey (no decision model available yet; adapted once it ships)
mock.model display id of the mock backend model, default keyword-match
jev.models / openrouter.models / bailian.models manual model lists (comma-separated; when set they take priority; empty = fetch live from upstream)

Hot reload: the server polls semquest.properties every 2 s — saving the file applies it at once: noulGate / choiceGate / questCacheSize / growth.* (growth switch / generation model / guard) / prompt.* (prompt changes also clear the query caches; prompt.rule.* swaps only tables still carrying the previous default text), plus timeout, credentials, per-backend url / model and model lists (changed credentials rebuild the backend, zero inference). Restart required: server.*, schema, initial backend.

Conventions

  • All-Chinese, four layers deep: rules (ruleAdd / ruleSearch), inputs, options and true-false criteria; criteria are event-level atomic assertions; empty no auto-generates the negation side; subtrees may override the rules; English backup: driver-intent-schema.en.json
  • noul tables: one batched question per layer over all real criteria; every branch with P(yes) ≥ gate descends (multi-label); fallback leaves (X_OTHER) are excluded from decision requests — zero passes at a layer falls to that layer's fallback; retrieval mirrors the same condition (a hit node with data never returns empty); score = number of hit leaves
  • Query cache: the exact same query returns the cached result with zero model calls (⚡); LFU eviction; capacity questCacheSize (default 256, 0 = off); any table change clears it; memory-only
  • choice tables: one question per layer; options = children (description doubles as criterion, defaulting to the name) + the explicit fallback 以上都不符合; a win below choiceGate falls to the fallback; single path; score = product of path probabilities — fallback or below-line ends with no hit
  • Fallback grows with the layer: the first branch at a layer auto-creates its fallback leaf; deleting a layer's last branch removes an empty fallback (refused while it holds placements)
  • Mock backend: deterministic keyword substring match (content contains the node name → P = 0.95); for demos / tests only, not real judgment
  • Chinese test corpus: corpus-zh-test.md (20 utterances with expected-hit annotations)
  • respan-family models (free): state uses the {input,output} message protocol and only noul is accepted (jev-style three-part criteria dialect); choice tables fail fast with a clear error — switch to a jev / elephant family model

License

MIT

About

No embeddings. No vector DB. Your knowledge becomes a criteria tree — an LLM decides its way down, and every hit comes with a readable path. Pure Java 8, zero dependencies.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages