jd-opensource/joyagent-jdgenie · error · RuntimeException

元数据为空!

Error message

元数据为空!

What it means

Parameter validation in QdrantService.upsertVector: the payload (metadata map) for the single point is null, so the upsert would create a point with no metadata. This is the last of the three sequential guards (collection name, vector, payload) that validate arguments before issuing the upsertAsync call; fires when callers omit the metadata map for the vector being stored.

Solutions

  1. Pass an empty ImmutableMap/HashMap instead of null when no metadata exists.
  2. Fix the metadata source so a payload map is always produced alongside the vector.
  3. Guard the call site: default null payloads to Collections.emptyMap() before invoking.
  4. Make the metadata builder return an empty map rather than null on missing data.

Example fix

// before
service.upsertVector(collection, vector, metadata); // metadata is null
// after
Map<String, JsonWithInt.Value> payload =
        metadata != null ? metadata : Collections.emptyMap();
service.upsertVector(collection, vector, payload);
Defensive patterns

Strategy: validation

Validate before calling

if (payload == null) {
    throw new IllegalArgumentException("payload map must not be null before upsertVector");
}

Type guard

static boolean hasPayload(Map<String, ?> payload) {
    return payload != null;
}

Try / catch

try {
    service.upsertVector(collection, vector, payload);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("元数据为空")) {
        log.error("Null payload for single upsert on collection {}", collection, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling upsertVector(collection, vector, null), e.g. when metadata construction was skipped, a lookup for the document's metadata returned null, or the payload field in the source record was never populated.

Common situations: Document metadata missing in the source system; join with a metadata table returning null; nullable DTO field passed directly; refactoring that removed a default-payload builder.

Related errors


AI-assisted analysis of jd-opensource/joyagent-jdgenie@2417e0b8b6 (2026-09-08). Data as JSON: /api/errors/fbd0606360abc408. Report an issue: GitHub.

Appendix: source

Thrown at genie-backend/src/main/java/com/jd/genie/service/QdrantService.java:243

                    .putAllPayload(maps.get(i))
                    .build();
            pointStructList.add(pointStruct);
        }

        return client.upsertAsync(collectionName, pointStructList).get();
    }

    public Points.UpdateResult upsertVector(String collectionName, List<Float> vector, Map<String, JsonWithInt.Value> payload) throws ExecutionException, InterruptedException {
        if (StringUtils.isBlank(collectionName)) {
            throw new RuntimeException("集合名为空!");
        }

        if (Objects.isNull(vector)) {
            throw new RuntimeException("向量为空!");
        }

        if (Objects.isNull(payload)) {
            throw new RuntimeException("元数据为空!");
        }


        List<Points.PointStruct> pointStructList = new ArrayList<>();

        Points.PointStruct pointStruct = Points.PointStruct.newBuilder()
                .setId(id(UUID.randomUUID()))
                .setVectors(vectors(vector))
                .putAllPayload(payload)
                .build();
        pointStructList.add(pointStruct);

        return client.upsertAsync(collectionName, pointStructList).get();
    }

    private List<Map<String, JsonWithInt.Value>> transPayloadMap(List<Map<String, Object>> payloads) {
        List<Map<String, JsonWithInt.Value>> mapList = new ArrayList<>();

View on GitHub (pinned to 2417e0b8b6)