666ghj/MiroFish · error · RuntimeError

Multiple Zep batches match operation {operation_id}; refusin

Error message

Multiple Zep batches match operation {operation_id}; refusing ambiguity

What it means

RuntimeError in _find_batch_by_operation_id: the paginated batch list found two or more Zep batches whose metadata carries the same mirofish_operation_id (and graph_id). Because a batch-create call is not idempotent, a replayed create after an ambiguous timeout can produce a duplicate batch; rather than guessing which one holds the canonical data, the service refuses and asks for manual disambiguation.

Source

Thrown at backend/app/services/graph_builder.py:304

                    operation_name=f"reconcile batch create {operation_id}",
                )
                for batch in getattr(page, "batches", None) or []:
                    metadata = getattr(batch, "metadata", None) or {}
                    if (
                        metadata.get("mirofish_operation_id") == operation_id
                        and metadata.get("graph_id") == graph_id
                    ):
                        matches.append(batch)
                next_cursor = getattr(page, "next_cursor", None)
                if next_cursor is None:
                    break
                if next_cursor == cursor or next_cursor in seen_cursors:
                    raise RuntimeError("Zep batch list cursor did not advance")
                seen_cursors.add(next_cursor)
                cursor = next_cursor

            if len(matches) > 1:
                raise RuntimeError(
                    f"Multiple Zep batches match operation {operation_id}; refusing ambiguity"
                )
            if matches:
                return matches[0]
            if attempt < max_attempts:
                time.sleep(attempt)
        return None
    
    def set_ontology(self, graph_id: str, ontology: Dict[str, Any]):
        """设置图谱本体(公开方法)"""
        import warnings
        from typing import Optional
        from pydantic import Field
        from zep_cloud.external_clients.ontology import EntityModel, EntityText, EdgeModel
        
        # 抑制 Pydantic v2 关于 Field(default=None) 的警告
        # 这是 Zep SDK 要求的用法,警告来自动态类创建,可以安全忽略
        warnings.filterwarnings('ignore', category=UserWarning, module='pydantic')

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Inspect all batches with this operation_id in the Zep Cloud console (metadata mirofish_operation_id) and compare item counts/status.
  2. Delete the stale/draft duplicate batch in Zep, keep the processed one, then retry the build so the reconcile finds a single match.
  3. If both are partial drafts, delete both and restart the build fresh for that operation.
  4. Prevent recurrence: always clear project.zep_batch_id when abandoning a build, and never re-submit create for an operation whose batch may already exist.
  5. If concurrent submissions are possible, serialize build submission per project (the graph_lifecycle_lock pattern) so the same operation_id is never in flight twice.

Example fix

# before
if len(matches) > 1:
    raise RuntimeError(f"Multiple Zep batches match operation {operation_id}; refusing ambiguity")

# after - prefer the processed batch deterministically, still refuse true ties
if len(matches) > 1:
    processed = [b for b in matches if getattr(b, "status", None) not in {None, "draft"}]
    drafts = [b for b in matches if b not in processed]
    if len(processed) == 1:
        for d in drafts:
            self._safe_delete_batch(getattr(d, "batch_id", None))
        matches = processed
    else:
        raise RuntimeError(
            f"Multiple Zep batches match operation {operation_id} "
            f"(ids={[getattr(b, 'batch_id', None) for b in matches]}); refusing ambiguity"
        )
Defensive patterns

Strategy: validation

Validate before calling

matches = [b for b in list_all_batches() if (b.metadata or {}).get('mirofish_operation_id') == operation_id]
if len(matches) > 1:
    # operator must pick: prefer processed, delete drafts; do not auto-choose blindly
    raise AmbiguousBatchError([b.batch_id for b in matches])

Try / catch

try:
    submission = builder.submit_document_batch(graph_id, chunks, batch_size=bs)
except RuntimeError as e:
    if 'refusing ambiguity' in str(e):
        # inspect + clean duplicates in Zep console, then restart the build fresh
        logger.error('Duplicate Zep batches detected: %s', str(e))
        raise
    raise

Prevention

When it happens

Trigger: A retryable error on client.batch.create triggers _find_batch_by_operation_id, and the list now shows the original batch plus a duplicate created by an earlier replay; two concurrent build submissions computed the same operation_id; leftover batches from a previous failed run with identical content (same graph_id + chunks hash to the same operation_id).

Common situations: Network flakiness causing the create to be issued twice; resuming a build after a crash where the previous batch was never cleaned up; running the same document against the same graph twice and the old batch was not deleted.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/7b890e1605fe7616. Report an issue: GitHub.