langchain-ai/langchain · error · ValueError
Length of keys must match length of group_ids
Error message
Length of keys must match length of group_ids
What it means
`ValueError` from `RecordManager.update` (and mirrored in `aupdate`): the `keys` sequence and the optional `group_ids` sequence have different lengths. Each key must be paired with exactly one group ID, so a mismatch means the caller's data is malformed and the upsert is rejected before any record is written.
Source
Thrown at libs/core/langchain_core/indexing/base.py:298
group_ids: A list of group IDs corresponding to the keys.
time_at_least: Optional timestamp. Implementation can use this
to optionally verify that the timestamp IS at least this time
in the system that stores.
E.g., use to validate that the time in the postgres database
is equal to or larger than the given timestamp, if not
raise an error.
This is meant to help prevent time-drift issues since
time may not be monotonically increasing!
Raises:
ValueError: If the length of keys doesn't match the length of group
ids.
ValueError: If time_at_least is in the future.
"""
if group_ids and len(keys) != len(group_ids):
msg = "Length of keys must match length of group_ids"
raise ValueError(msg)
for index, key in enumerate(keys):
group_id = group_ids[index] if group_ids else None
if time_at_least and time_at_least > self.get_time():
msg = "time_at_least must be in the past"
raise ValueError(msg)
self.records[key] = {"group_id": group_id, "updated_at": self.get_time()}
async def aupdate(
self,
keys: Sequence[str],
*,
group_ids: Sequence[str | None] | None = None,
time_at_least: float | None = None,
) -> None:
"""Async upsert records into the database.
Args:
keys: A list of record keys to upsert.View on GitHub (pinned to e32fa9a52e)
Solutions
- Make group IDs line up 1:1: `group_ids=[group_for(k) for k in keys]`.
- For a shared group across all keys: `group_ids=[group] * len(keys)`.
- Add a guard before the call: `if group_ids is not None: assert len(keys) == len(group_ids)`.
Example fix
# before record_manager.update(["k1", "k2", "k3"], group_ids=["g1", "g2"]) # after record_manager.update(["k1", "k2", "k3"], group_ids=["g1", "g2", "g3"])
Defensive patterns
Strategy: validation
Validate before calling
if group_ids is not None:
assert len(keys) == len(group_ids), f"{len(keys)} keys vs {len(group_ids)} group_ids" Type guard
null
Try / catch
null
Prevention
- Derive group_ids from keys with a comprehension so lengths match by construction
- Use [group] * len(keys) for a shared group
- Keep record-manager calls in one helper you can test
When it happens
Trigger: Calling `record_manager.update(keys, group_ids=group_ids)` where `len(keys) != len(group_ids)` — e.g. computing group IDs for a filtered subset of keys, or passing a single group ID string instead of a one-element list when updating one key.
Common situations: Custom RecordManager callers (the built-in indexer always passes matched lengths); passing `group_ids=["g"]` with `keys=["a","b"]`; list comprehensions where the group-ids comprehension filters or drops entries.
Related errors
- time_at_least must be in the past
- The first argument must be a string or a callable with a __n
- A pending deprecation cannot have a scheduled removal
- Cannot specify both alternative and alternative_import
- alternative_import must be a fully qualified module path. Go
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/a9bfdb828e8b2a37.
Report an issue: GitHub.