Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
3631794
Add UI test
chengbiao-jin Apr 15, 2026
dc1807b
Merge branch 'main' into release_1.4.0
chengbiao-jin Apr 22, 2026
d22fca4
Update version number
chengbiao-jin Apr 22, 2026
51aeeb0
feat: Add Trace Logs UI for agent execution observability
prins-agivant Apr 14, 2026
d5b1ed2
fix: avoid intermediate variable in agent.py return
prins-agivant Apr 14, 2026
025f0b9
feat: capture node input/output in agent_steps and display in Trace Logs
prins-agivant Apr 14, 2026
c741963
fix: move _safe_serialize above loop, remove unused LuClock import
prins-agivant Apr 14, 2026
0d1cef7
feat: role-gated View Trace, remove truncation, fix routing labels
prins-agivant Apr 17, 2026
043591d
feat: add trace_logs volume mount in docker-compose
prins-agivant Apr 17, 2026
de18fbb
feat: add trace log save/fetch endpoints in ui.py and nginx /trace route
prins-agivant Apr 17, 2026
7d48f0e
fix: update trace route to /trace/:messageId in main.tsx
prins-agivant Apr 17, 2026
71bb924
fix: show all durations in seconds in TraceLogs
prins-agivant Apr 21, 2026
96f5770
feat: add LLM token usage tracking per node and Token Overview tab
prins-agivant Apr 27, 2026
41abc99
fix: deduplicate LLM caller names in Token Overview table
prins-agivant Apr 27, 2026
b2210ff
fix: add auth to trace endpoint, fix browser auth dialog, async file …
prins-agivant Apr 29, 2026
7f54a80
feat(GML-2086): add Excel and CSV extraction support with UI warning
prins-agivant Apr 30, 2026
352d58a
fix(GML-2086): preserve all rows for headerless Excel sheets
prins-agivant Apr 30, 2026
c823080
fix(GML-2086): align supported_extensions dict with get_supported_ext…
prins-agivant Apr 30, 2026
f75a9d2
fix(GML-2086): handle non-UTF-8 encodings in CSV extraction
prins-agivant Apr 30, 2026
58b6d32
fix: remove chunk text from citations in trace logs UI and backend JSON
prins-agivant May 4, 2026
ae872a5
v1.4.0: schema-aware ingest, prompt centralization, faster startup
chengbiao-jin May 2, 2026
6da2c97
feat(GML-2086): show unsupported file type warning in upload UI
prins-agivant May 5, 2026
b561896
feat: move Citations tab first, add 30-day trace log cleanup
prins-agivant May 6, 2026
cb6363e
GML-2072 Better Trace Log and related UI (#33)
chengbiao-jin May 6, 2026
e3f95c3
GML-2086 add Excel and CSV extraction support with UI warning (#35)
chengbiao-jin May 6, 2026
a7bf04c
Add UI test
chengbiao-jin Apr 15, 2026
222d680
Update version number
chengbiao-jin Apr 22, 2026
4d6918f
feat: Add Trace Logs UI for agent execution observability
prins-agivant Apr 14, 2026
2b33762
fix: avoid intermediate variable in agent.py return
prins-agivant Apr 14, 2026
3e260c7
feat: capture node input/output in agent_steps and display in Trace Logs
prins-agivant Apr 14, 2026
200895d
fix: move _safe_serialize above loop, remove unused LuClock import
prins-agivant Apr 14, 2026
e6c98ab
feat: role-gated View Trace, remove truncation, fix routing labels
prins-agivant Apr 17, 2026
722d115
feat: add trace_logs volume mount in docker-compose
prins-agivant Apr 17, 2026
7250ddd
feat: add trace log save/fetch endpoints in ui.py and nginx /trace route
prins-agivant Apr 17, 2026
e359f98
fix: update trace route to /trace/:messageId in main.tsx
prins-agivant Apr 17, 2026
39c1263
fix: show all durations in seconds in TraceLogs
prins-agivant Apr 21, 2026
ef0dae0
feat: add LLM token usage tracking per node and Token Overview tab
prins-agivant Apr 27, 2026
2696382
fix: deduplicate LLM caller names in Token Overview table
prins-agivant Apr 27, 2026
ad307d9
fix: add auth to trace endpoint, fix browser auth dialog, async file …
prins-agivant Apr 29, 2026
02a326d
fix: remove chunk text from citations in trace logs UI and backend JSON
prins-agivant May 4, 2026
51bbb65
feat: move Citations tab first, add 30-day trace log cleanup
prins-agivant May 6, 2026
ade9289
feat(GML-2086): add Excel and CSV extraction support with UI warning
prins-agivant Apr 30, 2026
698724b
fix(GML-2086): preserve all rows for headerless Excel sheets
prins-agivant Apr 30, 2026
62ea52e
fix(GML-2086): align supported_extensions dict with get_supported_ext…
prins-agivant Apr 30, 2026
f8ccdd6
fix(GML-2086): handle non-UTF-8 encodings in CSV extraction
prins-agivant Apr 30, 2026
6a2bda4
feat(GML-2086): show unsupported file type warning in upload UI
prins-agivant May 5, 2026
934edd7
feat(GML-2076): add Auto retrieval method selection (Phase 1)
chengbiao-jin May 6, 2026
3051fc3
feat(GML-2076): observability + out-of-corpus short-circuit (Phase 1.5)
chengbiao-jin May 6, 2026
532836d
Merge branch 'main' into release_1.4.0
chengbiao-jin May 6, 2026
37d9af2
Merge branch 'release_1.4.0' into GML-2076-Router-Improvement
chengbiao-jin May 6, 2026
830aede
refactor(GML-2076): simplify retriever badge to icon + label
chengbiao-jin May 7, 2026
fa9b5b1
fix: trace log path traversal + superuser auth, usage collector reset…
prins-agivant May 7, 2026
2a570a8
fix(TraceLogs): show not-found state when trace data missing
prins-agivant May 7, 2026
681be2c
Route schema-aware community and hybrid retrieval through domain vert…
chengbiao-jin May 7, 2026
c6e38c5
feat(GML-2076): cross-lane and in-lane retrieval fallbacks
chengbiao-jin May 7, 2026
7bebe25
fix(GML-2076): treat empty generate_function result as a retry trigger
chengbiao-jin May 7, 2026
659f04e
feat(GML-2076): add enable_router_fallback config toggle
chengbiao-jin May 7, 2026
04dfe3d
feat(GML-2076): expose enable_router_fallback in GraphRAG config UI
chengbiao-jin May 8, 2026
cec4153
fix(trace): per-user ownership check on /ui/trace/{message_id}
prins-agivant May 8, 2026
7c63758
Merge remote-tracking branch 'origin/GML-2076-Router-Improvement' int…
chengbiao-jin May 8, 2026
bf1aab6
GML-2076 router improvement (#37)
chengbiao-jin May 8, 2026
f1c2ac9
Merge branch 'release_1.4.0' of github.com:tigergraph/graphrag into G…
chengbiao-jin May 8, 2026
819072b
Address review feedback on schema-aware ingest and config reload
chengbiao-jin May 9, 2026
7681705
GML-2004 integrate schema (#38)
chengbiao-jin May 9, 2026
c071ad7
Prepare 1.4.0: CHANGELOG backfill, per-graph embedding store, pnpm pin
chengbiao-jin May 9, 2026
92507be
Harden schema apply against TG validator and reserved-word collisions
chengbiao-jin May 9, 2026
93d8e5d
Resolve schema-extraction sample budget from LLM context window
chengbiao-jin May 9, 2026
3e6bdee
Make graph initialization async-job + harden init UX
chengbiao-jin May 10, 2026
6bc3847
Structured hints for schema extraction and surrounding UX
chengbiao-jin May 11, 2026
8efa084
Add Query Guidance prompt, image versioning, and ECC rebuild fix
chengbiao-jin May 11, 2026
bfd5500
Tighten rebuild observability, sync graph picker, harden chat handshake
chengbiao-jin May 12, 2026
6e9d64f
Multi-edge support, anti-hallucination, init UX hardening
chengbiao-jin May 14, 2026
c8736b2
Keep upload session alive across long file conversion
chengbiao-jin May 14, 2026
be29673
Add alert dialog hook missed in prior commit
chengbiao-jin May 14, 2026
a78a710
Always fetch trace data on tab open
chengbiao-jin May 14, 2026
8822936
Inline trace dialog and lock chat UI during streaming
chengbiao-jin May 15, 2026
6850082
Drop final response from trace dialog and add accessibility labels
chengbiao-jin May 15, 2026
5f7987c
Address v1.4.0 code-review feedback
chengbiao-jin May 16, 2026
fd844f2
Mark v1.4.0 released in README and drop stale roadmap note
chengbiao-jin May 16, 2026
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
88 changes: 88 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
---

## Releases
* **5/16/2026**: GraphRAG v1.4.0 released. Added schema-aware knowledge graphs, auto retrieval method selection, and a Trace Logs UI, along with many other improvements and bug fixes. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.4.0) for details.
* **4/10/2026**: GraphRAG v1.3.0 released. Added an admin configuration UI with role-based access and per-graph chatbot LLM override, along with many other improvements and bug fixes. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.3.0) for details.
* **2/28/2026**: GraphRAG v1.2.0 released. Added Admin UI for graph initialization, document ingestion, and knowledge graph rebuild, along with many other improvements and bug fixes. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.2.0) for details.
* **9/22/2025**: GraphRAG is available now officially v1.1 (v1.1.0). AWS Bedrock support is completed with BDA integration for multimodal document ingestion. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.1.0) for details.
Expand Down Expand Up @@ -478,6 +479,11 @@ Copy the below code into `configs/server_config.json`. You shouldn’t need to c
| `chat_history_api` | string | `"http://chat-history:8002"` | URL of the chat history service. No change needed when using the provided Docker Compose file. |
| `chunker` | string | `"semantic"` | Default document chunker. Options: `semantic`, `character`, `regex`, `markdown`, `html`, `recursive`. |
| `extractor` | string | `"llm"` | Entity extraction method. Options: `llm`, `graphrag`. |
| `strict_mode` | bool | `false` | Dynamic-schema enforcement during extraction. When `true`, entities and relationships that don't match the domain schema are dropped. When `false` (default), unmatched nodes fall back to generic `Entity` vertices. |
| `retrieval_include_entity` | bool \| null | `null` (auto) | Whether retriever queries include the generic `Entity` vertex alongside domain types. When unset, the server uses `false` if a domain schema exists and `true` otherwise. Set explicitly to override. |
| `schema_max_sample_files` | int | `5` | Maximum number of sample documents accepted by the *Generate from sample documents* path on the *Initialize Knowledge Graph* dialog. |
| `schema_max_total_mb` | int | `50` | Combined upload cap (MB) across all sample files for schema extraction. Bounds the content sent to the LLM. A single file may use the full budget; no separate per-file cap. |
| `enable_router_fallback` | bool | `true` | When the function-call or Cypher path fails after 3 retries, fall back to vector search instead of failing the query. |
| `chunker_config` | object | `{}` | Chunker-specific settings (see sub-parameters below). All settings are saved regardless of which chunker is selected as default. |
| ↳ `chunk_size` | int | `2048` | Maximum number of characters per chunk. Used by `character`, `markdown`, `html`, and `recursive` chunkers. Larger values produce fewer, bigger chunks; smaller values produce more, finer-grained chunks. |
| ↳ `overlap_size` | int | 1/8 of `chunk_size` | Number of overlapping characters between consecutive chunks. Used by `character`, `markdown`, `html`, and `recursive` chunkers. More overlap preserves cross-chunk context but increases total chunk count. Set to `0` for no overlap. |
Expand Down Expand Up @@ -926,7 +932,9 @@ Today's primary lever is the **entity-extraction prompt**:
- **Add 1–2 short domain examples** in the prompt. Even one well-chosen exemplar (an extracted entity with type and definition) dramatically improves consistency across chunks.
- **List the canonical edge verbs you want.** Encourage `PUBLISHES`, `OWNS`, `ISSUES`, `MANAGES`, `REPORTS_ON` in the relationship-extraction prompt rather than letting the LLM emit ad-hoc nominal phrases.

If extraction quality is still poor after iterating on the prompt, the next-best option today is to clear the graph's domain types and re-ingest with the improved prompt — schema growth is currently driven entirely by what extraction produces. (A schema-aware initialization flow that lets you supply a curated schema up front is on the roadmap.)
If extraction quality is still poor after iterating on the prompt, declare a domain schema up front via the *Initialize Knowledge Graph* dialog (paste GSQL, or generate a draft from sample documents) so extraction populates the types you actually want instead of growing them organically from what the LLM happens to emit. See the Configuration table above for `strict_mode` and `retrieval_include_entity` for the schema-aware behavior knobs.

**Note on LLM faithfulness.** Entity, relationship, and attribute extraction is best-effort and may include occasional errors, especially for well-known entities. For high-stakes applications, validate critical extracted values against your source documents before relying on them.

### 4. Retrieval — match context size to the question

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.3.1
1.4.0
164 changes: 160 additions & 4 deletions common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,17 @@ def resolve_llm_services(llm_cfg: dict) -> dict:
if svc_key in cfg and "region_name" not in cfg[svc_key]:
cfg[svc_key]["region_name"] = top_region

# Inject top-level prompt_path into LLM-prompted service configs
# if missing. The UI never lets users set per-service prompt_paths;
# in practice they are always identical to completion's.
# ``embedding_service`` is excluded — embedding models never load
# prompt files (their class hierarchy has no prompt-property machinery).
top_prompt_path = cfg.get("prompt_path")
if top_prompt_path:
for svc_key in ["completion_service", "multimodal_service", "chat_service"]:
if svc_key in cfg and "prompt_path" not in cfg[svc_key]:
cfg[svc_key]["prompt_path"] = top_prompt_path

completion = cfg.get("completion_service", {})

# Resolve embedding: inherit provider-level config from completion
Expand Down Expand Up @@ -366,6 +377,15 @@ def get_graphrag_config(graphname=None):
if svc_key in llm_config and "region_name" not in llm_config[svc_key]:
llm_config[svc_key]["region_name"] = llm_config["region_name"]

# Inject top-level prompt_path into LLM-prompted service configs if
# missing. Embedding service is excluded — embedding models never load
# prompt files. Per-service entries on disk are accepted for backward
# compat but never written by the UI.
if "prompt_path" in llm_config:
for svc_key in ["completion_service", "multimodal_service", "chat_service"]:
if svc_key in llm_config and "prompt_path" not in llm_config[svc_key]:
llm_config[svc_key]["prompt_path"] = llm_config["prompt_path"]

_comp = llm_config.get("completion_service")
if _comp is None:
raise Exception("completion_service is not found in llm_config")
Expand Down Expand Up @@ -414,6 +434,8 @@ def get_graphrag_config(graphname=None):
graphrag_config["chunker"] = "semantic"
if "extractor" not in graphrag_config:
graphrag_config["extractor"] = "llm"
# ``retrieval_include_entity`` is resolved at install time
# (see ``common.db.retriever_render.resolve_include_entity``).

reuse_embedding = graphrag_config.get("reuse_embedding", True)
doc_process_switch = graphrag_config.get("doc_process_switch", True)
Expand Down Expand Up @@ -441,6 +463,15 @@ def get_graphrag_config(graphname=None):
else:
raise Exception("Embedding service not implemented")

def get_embedding_service():
"""Return the current embedding service instance.

Use this instead of importing ``embedding_service`` directly so
consumers always read the latest instance after a config reload.
"""
return embedding_service


def get_llm_service(service_config: dict) -> LLM_Model:
"""
Instantiate an LLM provider from a flat service config dict.
Expand Down Expand Up @@ -474,25 +505,134 @@ def get_llm_service(service_config: dict) -> LLM_Model:
raise Exception(f"LLM service '{service_name}' not supported")


if os.getenv("INIT_EMBED_STORE", "true") == "true":
# Module-level ``embedding_store`` is the back-compat default for
# direct importers (``from common.config import embedding_store``).
# It's populated by the background init thread below.
#
# ``_embedding_stores`` is the per-graph cache used by chatbot
# retrievers via ``get_embedding_store(graphname=...)``. Each entry
# has its own ``TigerGraphConnection`` bound to that graphname for
# its lifetime — no in-place ``set_graphname`` mutation — so
# concurrent chat across different graphs can't race over a shared
# connection.
embedding_store = None
_embedding_store_ready = threading.Event()
_embedding_stores: dict = {}
_embedding_stores_lock = threading.Lock()
service_status["embedding_store"] = {
"status": "initializing",
"error": "Embedding store is still initializing",
}


def _build_embedding_store(graphname: str = "") -> TigerGraphEmbeddingStore:
"""Construct a fresh ``TigerGraphEmbeddingStore`` bound to *graphname*.

Uses the live globals (``db_config`` for the connection and
``embedding_service`` for the model) so the result reflects the
current config.
"""
conn = TigerGraphConnection(
host=db_config.get("hostname", "http://tigergraph"),
username=db_config.get("username", "tigergraph"),
password=db_config.get("password", "tigergraph"),
gsPort=db_config.get("gsPort", "14240"),
restppPort=db_config.get("restppPort", "9000"),
graphname=db_config.get("graphname", ""),
graphname=graphname or db_config.get("graphname", ""),
apiToken=db_config.get("apiToken", ""),
)
if not db_config.get("apiToken") and db_config.get("getToken"):
conn.getToken()

embedding_store = TigerGraphEmbeddingStore(
store = TigerGraphEmbeddingStore(
conn,
embedding_service,
support_ai_instance=True,
)
service_status["embedding_store"] = {"status": "ok", "error": None}
if graphname:
# Runs the GDS check and per-graph vector-query install.
store.set_graphname(graphname)
return store


def _init_embedding_store():
"""Background thread target. Builds the default embedding store
without blocking module import — TigerGraph may be slow on first
connect, and we don't want app startup to wait on it.
"""
global embedding_store
try:
embedding_store = _build_embedding_store()
service_status["embedding_store"] = {"status": "ok", "error": None}
except Exception as e:
service_status["embedding_store"] = {"status": "error", "error": str(e)}
logger.error(f"Failed to initialize embedding store: {e}")
finally:
_embedding_store_ready.set()


def get_embedding_store(graphname: str | None = None, timeout: float = 0):
"""Return an embedding store.

Args:
graphname: When supplied, returns a per-graph instance built
and cached on first request (each cache entry has its own
connection bound to *graphname* for its lifetime).
timeout: Seconds to wait for the default-store init when
*graphname* is not supplied. Default 0 (non-blocking —
raises immediately if still initializing).

Raises:
RuntimeError: if not yet ready, timed out, or initialization failed.
"""
if graphname:
with _embedding_stores_lock:
cached = _embedding_stores.get(graphname)
if cached is not None:
return cached
# Build outside the lock so first-time setup for one graph
# doesn't serialize first-time setup for another.
store = _build_embedding_store(graphname)
with _embedding_stores_lock:
existing = _embedding_stores.get(graphname)
if existing is not None:
return existing # racing thread won
_embedding_stores[graphname] = store
return store

if not _embedding_store_ready.wait(timeout=timeout):
raise RuntimeError(
"Embedding store is still initializing. Please try again shortly."
)
if embedding_store is None:
error = service_status.get("embedding_store", {}).get("error", "Unknown error")
raise RuntimeError(f"Embedding store failed to initialize: {error}")
return embedding_store


def reset_embedding_store() -> None:
"""Drop the per-graph cache and the default store, then re-run the
background init so a config reload picks up the new
``embedding_service`` and ``db_config``. Callers should swap the
inputs before calling. No-op when ``INIT_EMBED_STORE`` is disabled
(e.g. ECC).
"""
global embedding_store
if os.getenv("INIT_EMBED_STORE", "true") != "true":
return
with _embedding_stores_lock:
_embedding_stores.clear()
embedding_store = None
_embedding_store_ready.clear()
service_status["embedding_store"] = {
"status": "initializing",
"error": "Embedding store is still initializing",
}
threading.Thread(target=_init_embedding_store, daemon=True).start()


if os.getenv("INIT_EMBED_STORE", "true") == "true":
threading.Thread(target=_init_embedding_store, daemon=True).start()


def reload_llm_config(new_llm_config: dict = None):
Expand Down Expand Up @@ -550,6 +690,14 @@ def reload_llm_config(new_llm_config: dict = None):
if svc_key in new_llm_config and "region_name" not in new_llm_config[svc_key]:
new_llm_config[svc_key]["region_name"] = new_llm_config["region_name"]

# Inject top-level prompt_path into LLM-prompted service configs
# if missing. Embedding service is excluded — embedding models
# never load prompt files.
if "prompt_path" in new_llm_config:
for svc_key in ["completion_service", "multimodal_service", "chat_service"]:
if svc_key in new_llm_config and "prompt_path" not in new_llm_config[svc_key]:
new_llm_config[svc_key]["prompt_path"] = new_llm_config["prompt_path"]

new_completion_config = new_llm_config.get("completion_service")
new_embedding_config = new_llm_config.get("embedding_service")

Expand Down Expand Up @@ -595,6 +743,10 @@ def reload_llm_config(new_llm_config: dict = None):
else:
raise Exception("Embedding service not implemented")

# Clear per-graph cache + rebuild the default so callers don't
# keep references to the old embedding service.
reset_embedding_store()

return {
"status": "success",
"message": "LLM configuration reloaded successfully"
Expand Down Expand Up @@ -645,6 +797,10 @@ def reload_db_config(new_db_config: dict = None):
del db_config[k]
db_config.update(new_db_config)

# Clear per-graph cache + rebuild the default so callers don't
# keep connections bound to the old credentials.
reset_embedding_store()

return {
"status": "success",
"message": "DB configuration reloaded successfully"
Expand Down
Loading
Loading