Repository: HKUDS/LightRAG (python)

Issue:
[Bug]: Entity merge fails with Milvus due to missing `file_path` field

### Do you need to file an issue?

- [x] I have searched the existing issues and this bug is not already filed.
- [x] I believe this is a legitimate bug, not just a question or feature request.

### Describe the bug

When calling `amerge_entities` operation fails with:

```
DataNotMatchException: (code=1, message=Insert missed an field `file_path` to collection without set nullable==true or set default_value)
```

In `lightrag/utils_graph.py`, the `_merge_entities_impl` function retrieves `file_path` from Neo4j correctly, but when building data for Milvus upsert operations, it omits the `file_path` field:

### What i did to fix it

1. **Relations** (line ~1439): `edge_data` contains `file_path` but it's not copied to `relation_data_for_vdb` and should be added so it looks like this:
```python
relation_data_for_vdb = {
            relation_id: {
                "content": content,
                "src_id": normalized_src,
                "tgt_id": normalized_tgt,
                "source_id": source_id,
                "description": description,
                "keywords": keywords,
                "weight": weight,
                "file_path": edge_data.get("file_path", ""),  # <---Adding this solved it for me.
            }
        }
```
2. **Entities** (line ~1465): `merged_entity_data` contains `file_path` but it's not copied to `entity_data_for_vdb`

```python
entity_data_for_vdb = {
        entity_id: {
            "content": content,
            "entity_name": target_entity,
            "source_id": source_id,
            "description": description,
            "entity_type": entity_type,
            "file_path": merged_entity_data.get("file_path", ""),  # <---Adding this solved it for me.
        }
    }
```

P.S Im not a good programer so the solution might not be optimal.

### Steps to reproduce

1. Here is my docker-compose.yaml file
```yaml
services:
  neo4j: # (GRAPH_STORAGE)
    container_name: neo4j
    image: neo4j:2026.02.2
    ports:
      - 7687:7687
    networks:
      - lightrag-network
    volumes:
      - ./data/neo4j/data:/data
    environment:
      NEO4J_AUTH: "${NEO4J_USERNAME}/${NEO4J_PASSWORD}"
      NEO4J_PLUGINS: '["apoc"]'
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "neo4j", "status"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  redis: # (KV_STORAGE + DOC_STATUS_STORAGE)
    container_name: redis
    image: redis:latest
    ports:
      - 6379:6379
    networks:
      - lightrag-network
    volumes:
      - ./data/redis/data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  milvus: # (VECTOR_STORAGE)
    container_name: milvus
    image: milvusdb/milvus:v2.4.6
    command: milvus run standalone
    ports:
      - 19530:19530
      - 9091:9091
    networks:
      - lightrag-network
    volumes:
      - ./data/milvus:/var/lib/milvus
    environment:
      ETCD_USE_EMBED: "true"
      COMMON_STORAGETYPE: "local"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

networks:
  lightrag-network:
````

Here is my `.env` file with that could be used as example:

```ini
## Server Configuration
HOST=0.0.0.0
PORT=9621
WEBUI_TITLE='Codebase'
WEBUI_DESCRIPTION="Simple and Fast Graph Based RAG System"
WORKERS=2
TIMEOUT=720  # gunicorn worker timeout(as default LLM request timeout if LLM_TIMEOUT is not set)

## Document processing configuration
ENABLE_LLM_CACHE_FOR_EXTRACT=true

## Document processing output language: English, Chinese, French, German ...
SUMMARY_LANGUAGE=English

## Concurrency Configuration
MAX_ASYNC=2
MAX_PARALLEL_INSERT=2
EMBEDDING_FUNC_MAX_ASYNC=8
EMBEDDING_BATCH_NUM=10

# External LLM
LLM_TIMEOUT=720
LLM_BINDING=openai
LLM_BINDING_HOST=https://api.minimax.io/v1
LLM_BINDING_API_KEY=CHANGE_ME
LLM_MODEL=CHANGE_ME

## OpenAI compatible embedding
EMBEDDING_BINDING=openai
EMBEDDING_MODEL=text-embedding-qwen3-embedding-8b
EMBEDDING_DIM=4096
EMBEDDING_SEND_DIM=true
EMBEDDING_TOKEN_LIMIT=40960
#EMBEDDING_BINDING_HOST=http://host.docker.internal:1234/v1
EMBEDDING_BINDING_HOST=http://127.0.0.1:1234/v1
EMBEDDING_BINDING_API_KEY=CHANGE_ME
EMBEDDING_TIMEOUT=120

### Redis (KV_STORAGE + DOC_STATUS_STORAGE)
LIGHTRAG_KV_STORAGE=RedisKVStorage
LIGHTRAG_DOC_STATUS_STORAGE=RedisDocStatusStorage
REDIS_URI=redis://redis:6379
REDIS_SOCKET_TIMEOUT=30
REDIS_CONNECT_TIMEOUT=10
REDIS_MAX_CONNECTIONS=100
REDIS_RETRY_ATTEMPTS=3

### Neo4j (GRAPH_STORAGE)
LIGHTRAG_GRAPH_STORAGE=Neo4JStorage
NEO4J_URI=neo4j://neo4j:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=password
NEO4J_DATABASE=neo4j
# NEO4J_MAX_CONNECTION_POOL_SIZE=100
# NEO4J_CONNECTION_TIMEOUT=30
# NEO4J_CONNECTION_ACQUISITION_TIMEOUT=30
# NEO4J_MAX_TRANSACTION_RETRY_TIME=30
# NEO4J_MAX_CONNECTION_LIFETIME=300
# NEO4J_LIVENESS_CHECK_TIMEOUT=30
# NEO4J_KEEP_ALIVE=true

### Milvus (VECTOR_STORAGE)
LIGHTRAG_VECTOR_STORAGE=MilvusVectorDBStorage
MILVUS_URI=http://milvus:19530
MILVUS_DB_NAME=default
```

Install requirements:

```shell
python -m venv .venv && \
    source .venv/bin/active && \
    uv pip install -r requirements.txt
```

Run the example code that:

```python
import asyncio
import os
from functools import partial

from lightrag import LightRAG
from lightrag.llm.openai import openai_complete_if_cache, openai_embed
from lightrag.utils import EmbeddingFunc
from openai import OpenAI
from pydantic import BaseModel

# WorkingDir
WORKING_DIR = (
    "/Users/timlindholm/Code/KarolinskaSjukhuset/KIM/kar-dp-lightrag/embeding-dedup"
)
if not os.path.exists(WORKING_DIR):
    os.mkdir(WORKING_DIR)
print(f"WorkingDir: {WORKING_DIR}")

# redis
os.environ["REDIS_URI"] = "redis://localhost:6379"

# neo4j
BATCH_SIZE_NODES = 500
BATCH_SIZE_EDGES = 100
os.environ["NEO4J_URI"] = "neo4j://127.0.0.1:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "password"

# milvus
os.environ["MILVUS_URI"] = "http://localhost:19530"
os.environ["MILVUS_USER"] = "lightrag"
os.environ["MILVUS_PASSWORD"] = "lightrag"
os.environ["MILVUS_DB_NAME"] = "default"

# LLM
LLM_BINDING_HOST = "https://api.minimax.io/v1"
LLM_BINDING_API_KEY = os.getenv("LLM_BINDING_API_KEY", "lm-studio")
LLM_MODEL = "MiniMax-M2.7"
EMBEDDING_MODEL = "text-embedding-qwen3-embedding-8b"

# Required for openai_embed when using LM Studio (dummy value)
os.environ["OPENAI_API_KEY"] = "lm-studio"


async def llm_model_func(
    prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs
) -> str:
    return await openai_complete_if_cache(
        LLM_MODEL,
        prompt,
        system_prompt=system_prompt,
        history_messages=history_messages,
        api_key=LLM_BINDING_API_KEY,
        base_url=LLM_BINDING_HOST,
        **kwargs,
    )


embedding_func = EmbeddingFunc(
    embedding_dim=4096,
    max_token_size=16384,
    func=partial(
        openai_embed.func,
        model=EMBEDDING_MODEL,
        api_key="lm-studio",
        base_url="http://127.0.0.1:1234/v1",
    ),
)


async def initialize_rag():
    rag = LightRAG(
        working_dir=WORKING_DIR,
        llm_model_func=llm_model_func,
        summary_max_tokens=10000,
        embedding_func=embedding_func,
        chunk_token_size=512,
        chunk_overlap_token_size=256,
        kv_storage="RedisKVStorage",
        graph_storage="Neo4JStorage",
        vector_storage="MilvusVectorDBStorage",
        doc_status_storage="RedisDocStatusStorage",
    )
    await rag.initialize_storages()  # Auto-initializes pipeline_status
    return rag


class normalizedGraphEntity(BaseModel):
    name: str
    type: str
    description: str


async def main():
    # Initialize RAG instance
    rag = await initialize_rag()
    # Just grab two examples from the webui and try to merge them
    entity1 = "Name Of Team"
    entity2 = "name-of-team"
    # Merge the entities into a new one
    await rag.amerge_entities(
        source_entities=[entity1, entity2],
        target_entity=entity1,
    )


if __name__ == "__main__":
    llm = OpenAI(api_key=LLM_BINDING_API_KEY, base_url=LLM_BINDING_HOST)
    asyncio.run(main())
```

### Expected Behavior

It should have merged the entities

### LightRAG Config Used
```ini
## Server Configuration
HOST=0.0.0.0
PORT=9621
WEBUI_TITLE='Codebase'
WEBUI_DESCRIPTION="Simple and Fast Graph Based RAG System"
WORKERS=2
TIMEOUT=720  # gunicorn worker timeout(as default LLM request timeout if LLM_TIMEOUT is not set)

## Document processing configuration
ENABLE_LLM_CACHE_FOR_EXTRACT=true

SUMMARY_LANGUAGE=English

## Concurrency Configuration
MAX_ASYNC=2
MAX_PARALLEL_INSERT=2
EMBEDDING_FUNC_MAX_ASYNC=8
EMBEDDING_BATCH_NUM=10

# External LLM
LLM_TIMEOUT=720
LLM_BINDING=openai
LLM_BINDING_HOST=https://api.minimax.io/v1
LLM_BINDING_API_KEY=CHANGE_ME
LLM_MODEL=CHANGE_ME

## OpenAI compatible embedding
EMBEDDING_BINDING=openai
EMBEDDING_MODEL=text-embedding-qwen3-embedding-8b
EMBEDDING_DIM=4096
EMBEDDING_SEND_DIM=true
EMBEDDING_TOKEN_LIMIT=40960
#EMBEDDING_BINDING_HOST=http://host.docker.internal:1234/v1
EMBEDDING_BINDING_HOST=http://127.0.0.1:1234/v1
EMBEDDING_BINDING_API_KEY=CHANGE_ME
EMBEDDING_TIMEOUT=120

### Redis (KV_STORAGE + DOC_STATUS_STORAGE)
LIGHTRAG_KV_STORAGE=RedisKVStorage
LIGHTRAG_DOC_STATUS_STORAGE=RedisDocStatusStorage
REDIS_URI=redis://redis:6379
REDIS_SOCKET_TIMEOUT=30
REDIS_CONNECT_TIMEOUT=10
REDIS_MAX_CONNECTIONS=100
REDIS_RETRY_ATTEMPTS=3

### Neo4j (GRAPH_STORAGE)
LIGHTRAG_GRAPH_STORAGE=Neo4JStorage
NEO4J_URI=neo4j://neo4j:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=password
NEO4J_DATABASE=neo4j
# NEO4J_MAX_CONNECTION_POOL_SIZE=100
# NEO4J_CONNECTION_TIMEOUT=30
# NEO4J_CONNECTION_ACQUISITION_TIMEOUT=30
# NEO4J_MAX_TRANSACTION_RETRY_TIME=30
# NEO4J_MAX_CONNECTION_LIFETIME=300
# NEO4J_LIVENESS_CHECK_TIMEOUT=30
# NEO4J_KEEP_ALIVE=true

### Milvus (VECTOR_STORAGE)
LIGHTRAG_VECTOR_STORAGE=MilvusVectorDBStorage
MILVUS_URI=http://milvus:19530
MILVUS_DB_NAME=default
```

### Logs and screenshots

This is me running the example code

```shell
python main.py
WorkingDir: /tmp/lightrag-debug
INFO: Created shared Redis connection pool for redis://localhost:6379
INFO: [] Initialized Redis KV storage for llm_response_cache using shared connection pool
INFO: [] Initialized Redis KV storage for text_chunks using shared connection pool
INFO: [] Initialized Redis KV storage for full_docs using shared connection pool
INFO: [] Initialized Redis KV storage for full_entities using shared connection pool
INFO: [] Initialized Redis KV storage for full_relations using shared connection pool
INFO: [] Initialized Redis KV storage for entity_chunks using shared connection pool
INFO: [] Initialized Redis KV storage for relation_chunks using shared connection pool
INFO: [_] Initialized Redis doc status storage for doc_status using shared connection pool
INFO: [] Connected to Redis for namespace full_docs
INFO: [] Connected to Redis for namespace text_chunks
INFO: [] Connected to Redis for namespace full_entities
INFO: [] Connected to Redis for namespace full_relations
INFO: [] Connected to Redis for namespace entity_chunks
INFO: [] Connected to Redis for namespace relation_chunks
INFO: [] VectorDB collection 'entities' exists check: True
INFO: [] VectorDB Collection 'entities' compatibility validation passed
INFO: [] Milvus collection 'entities' initialized successfully
INFO: [] VectorDB collection 'relationships' exists check: True
INFO: [] VectorDB Collection 'relationships' compatibility validation passed
INFO: [] Milvus collection 'relationships' initialized successfully
INFO: [] VectorDB collection 'chunks' exists check: True
INFO: [] VectorDB Collection 'chunks' compatibility validation passed
INFO: [] Milvus collection 'chunks' initialized successfully
INFO: [base] Connected to neo4j at neo4j://127.0.0.1:7687
INFO: [base] Ensured B-Tree index on entity_id for base in neo4j
INFO: [base] Found existing index 'entity_id_fulltext_idx_base' with state: ONLINE
INFO: [base] Full-text index 'entity_id_fulltext_idx_base' already exists and is online. Skipping recreation.
INFO: [] Connected to Redis for namespace llm_response_cache
INFO: [_] Connected to Redis for doc status namespace doc_status
INFO: Entity Merge: Updated target 'Name Of Team'
INFO: Entity Merge: updatign 2 relations
INFO: Entity Merge: updating relation `Name Of Team`~`contact@example.se`
INFO: Entity Merge: updating relation `Name Of Team`~`my-app-dev`
INFO: [] Deleted 2 of 2 entries from relation_chunks
INFO: Entity Merge: 2 relation chunk tracking records updated
INFO: Embedding func: 8 new workers initialized (Timeouts: Func: 120s, Worker: 240s, Health Check: 255s)
2026-03-29 15:34:22,466 [ERROR][_log_rpc_error]: RPC error: [upsert_rows], <DataNotMatchException: (code=1, message=Insert missed an field `file_path` to collection without set nullable==true or set default_value)>, <elapsed:13.6ms>
Traceback:
Traceback (most recent call last):
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 404, in handler
    return func(*args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 451, in handler
    return func(self, *args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 342, in handler
    raise e from e
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 305, in handler
    return func(*args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 133, in handler
    raise e from e
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 118, in handler
    return func(self, collection_name, *args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/grpc_handler.py", line 1111, in upsert_rows
    request = self._prepare_row_upsert_request(
        collection_name, entities, partition_name, timeout, context=context, **kwargs
    )
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/grpc_handler.py", line 1086, in _prepare_row_upsert_request
    return Prepare.row_upsert_param(
           ~~~~~~~~~~~~~~~~~~~~~~~~^
        collection_name,
        ^^^^^^^^^^^^^^^^
    ...<6 lines>...
        partial_update=partial_update,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/prepare.py", line 1148, in row_upsert_param
    return cls._parse_upsert_row_request(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        request, fields_info, struct_fields_info, enable_dynamic, entities, partial_update
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/prepare.py", line 1017, in _parse_upsert_row_request
    raise DataNotMatchException(
        message=ExceptionsMessage.InsertMissedField % key
    )
pymilvus.exceptions.DataNotMatchException: <DataNotMatchException: (code=1, message=Insert missed an field `file_path` to collection without set nullable==true or set default_value)>
 (decorators.py:358)
ERROR: Error merging entities: <DataNotMatchException: (code=1, message=Insert missed an field `file_path` to collection without set nullable==true or set default_value)>
Traceback (most recent call last):
  File "/private/tmp/lightrag-debug/main.py", line 109, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.11/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.11/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.11/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/private/tmp/lightrag-debug/main.py", line 101, in main
    await rag.amerge_entities(
    ...<2 lines>...
    )
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/lightrag/lightrag.py", line 4392, in amerge_entities
    return await amerge_entities(
           ^^^^^^^^^^^^^^^^^^^^^^
    ...<9 lines>...
    )
    ^
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/lightrag/utils_graph.py", line 1599, in amerge_entities
    return await _merge_entities_impl(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<9 lines>...
    )
    ^
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/lightrag/utils_graph.py", line 1440, in _merge_entities_impl
    await relationships_vdb.upsert(relation_data_for_vdb)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/lightrag/kg/milvus_impl.py", line 1489, in upsert
    results = self._client.upsert(
        collection_name=self.final_namespace, data=list_data
    )
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/milvus_client/milvus_client.py", line 316, in upsert
    res = conn.upsert_rows(
        collection_name,
    ...<4 lines>...
        **kwargs,
    )
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 407, in handler
    raise e from e
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 404, in handler
    return func(*args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 451, in handler
    return func(self, *args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 342, in handler
    raise e from e
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 305, in handler
    return func(*args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 133, in handler
    raise e from e
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/decorators.py", line 118, in handler
    return func(self, collection_name, *args, **kwargs)
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/grpc_handler.py", line 1111, in upsert_rows
    request = self._prepare_row_upsert_request(
        collection_name, entities, partition_name, timeout, context=context, **kwargs
    )
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/grpc_handler.py", line 1086, in _prepare_row_upsert_request
    return Prepare.row_upsert_param(
           ~~~~~~~~~~~~~~~~~~~~~~~~^
        collection_name,
        ^^^^^^^^^^^^^^^^
    ...<6 lines>...
        partial_update=partial_update,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/prepare.py", line 1148, in row_upsert_param
    return cls._parse_upsert_row_request(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        request, fields_info, struct_fields_info, enable_dynamic, entities, partial_update
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/private/tmp/lightrag-debug/.venv/lib/python3.13/site-packages/pymilvus/client/prepare.py", line 1017, in _parse_upsert_row_request
    raise DataNotMatchException(
        message=ExceptionsMessage.InsertMissedField % key
    )
pymilvus.exceptions.DataNotMatchException: <DataNotMatchException: (code=1, message=Insert missed an field `file_path` to collection without set nullable==true or set default_value)>
```

### Additional Information

- LightRAG Version: 1.4.12 (lightrag-hku)
- Operating System: MacOS 26.3.1 (a)
- Python Version: 3.13.11
- Related Issues: Dont know



Patch:
diff --git a/lightrag/utils_graph.py b/lightrag/utils_graph.py
index c3e65a6a1e..16fc509297 100644
--- a/lightrag/utils_graph.py
+++ b/lightrag/utils_graph.py
@@ -1435,6 +1435,7 @@ async def _merge_entities_impl(
                 "description": description,
                 "keywords": keywords,
                 "weight": weight,
+                "file_path": edge_data.get("file_path", ""),
             }
         }
         await relationships_vdb.upsert(relation_data_for_vdb)
@@ -1458,6 +1459,7 @@ async def _merge_entities_impl(
             "source_id": source_id,
             "description": description,
             "entity_type": entity_type,
+            "file_path": merged_entity_data.get("file_path", ""),
         }
     }
     await entities_vdb.upsert(entity_data_for_vdb)


Test patch:
diff --git a/tests/test_description_api_validation.py b/tests/test_description_api_validation.py
index 95d82f2626..4beb44b457 100644
--- a/tests/test_description_api_validation.py
+++ b/tests/test_description_api_validation.py
@@ -1,6 +1,7 @@
 import pytest
 
 from lightrag.constants import SOURCE_IDS_LIMIT_METHOD_KEEP
+from lightrag.constants import GRAPH_FIELD_SEP
 from lightrag.operate import (
     _merge_nodes_then_upsert,
     _handle_single_relationship_extraction,
@@ -24,11 +25,15 @@ async def upsert_node(self, node_id, node_data):
 class DummyVectorStorage:
     def __init__(self):
         self.global_config = {"workspace": "test"}
+        self.upserts = []
+        self.deletes = []
 
     async def upsert(self, data):
+        self.upserts.append(data)
         return None
 
     async def delete(self, ids):
+        self.deletes.append(ids)
         return None
 
     async def get_by_id(self, id_):
@@ -46,6 +51,75 @@ async def __aexit__(self, exc_type, exc, tb):
         return False
 
 
+class DummyMergeGraphStorage:
+    def __init__(self):
+        self.nodes = {
+            "Canonical": {
+                "entity_id": "Canonical",
+                "description": "canonical desc",
+                "entity_type": "ORG",
+                "source_id": "chunk-1",
+                "file_path": "canonical.md",
+            },
+            "Alias": {
+                "entity_id": "Alias",
+                "description": "alias desc",
+                "entity_type": "ORG",
+                "source_id": "chunk-2",
+                "file_path": "alias.md",
+            },
+            "Neighbor": {
+                "entity_id": "Neighbor",
+                "description": "neighbor desc",
+                "entity_type": "ORG",
+                "source_id": "chunk-3",
+                "file_path": "neighbor.md",
+            },
+        }
+        self.edges = {
+            ("Alias", "Neighbor"): {
+                "description": "rel desc",
+                "keywords": "alias",
+                "source_id": "chunk-rel",
+                "weight": 1.0,
+                "file_path": "rel.md",
+            }
+        }
+
+    async def has_node(self, node_id):
+        return node_id in self.nodes
+
+    async def get_node(self, node_id):
+        return self.nodes[node_id]
+
+    async def upsert_node(self, node_id, node_data):
+        self.nodes[node_id] = dict(node_data)
+
+    async def get_node_edges(self, node_id):
+        results = []
+        for src, tgt in self.edges:
+            if src == node_id or tgt == node_id:
+                results.append((src, tgt))
+        return results
+
+    async def get_edge(self, src, tgt):
+        return self.edges.get((src, tgt)) or self.edges.get((tgt, src))
+
+    async def upsert_edge(self, src, tgt, edge_data):
+        self.edges[(src, tgt)] = dict(edge_data)
+
+    async def delete_node(self, node_id):
+        self.nodes.pop(node_id, None)
+        self.edges = {
+            (src, tgt): data
+            for (src, tgt), data in self.edges.items()
+            if src != node_id and tgt != node_id
+        }
+
+    async def index_done_callback(self):
+        return True
+
+
 @pytest.mark.asyncio
 async def test_merge_nodes_then_upsert_handles_missing_legacy_description():
     graph = DummyGraphStorage(node={"source_id": "chunk-1"})
@@ -145,3 +219,34 @@ def test_handle_single_relationship_extraction_ignores_empty_description():
     )
 
     assert relation is None
+
+
+@pytest.mark.asyncio
+async def test_merge_entities_preserves_file_path_in_vector_updates(monkeypatch):
+    graph = DummyMergeGraphStorage()
+    entities_vdb = DummyVectorStorage()
+    relationships_vdb = DummyVectorStorage()
+
+    async def fake_get_entity_info(*args, **kwargs):
+        return {"entity_name": "Canonical"}
+
+    monkeypatch.setattr(utils_graph, "get_entity_info", fake_get_entity_info)
+
+    await utils_graph._merge_entities_impl(
+        chunk_entity_relation_graph=graph,
+        entities_vdb=entities_vdb,
+        relationships_vdb=relationships_vdb,
+        source_entities=["Alias", "Canonical"],
+        target_entity="Canonical",
+    )
+
+    relationship_payload = relationships_vdb.upserts[-1]
+    entity_payload = entities_vdb.upserts[-1]
+
+    assert next(iter(relationship_payload.values()))["file_path"] == "rel.md"
+    assert set(
+        next(iter(entity_payload.values()))["file_path"].split(GRAPH_FIELD_SEP)
+    ) == {
+        "alias.md",
+        "canonical.md",
+    }