All notable changes to zep-ingest are documented here. The project follows
Semantic Versioning; while at 0.x the public API may
still change between minor versions.
- Document grouping (
document_id). Text files, transcripts, and emails always get adocument_id(even as a single episode). Slack is ingested as one episode per message with a shareddocument_idper Slack thread, plus a second document of each channel's top-level messages in order (thread parents are ingested twice; payload identical exceptdocument_id). Not a Zepthread_id. The Slackgroupingoption is removed. JSON records omitdocument_id.TextChunkerpreservesdocument_idacross chunks. - Sequential
graph.addsendsdocument_idvia a thin API wrapper until the installedzep-cloudSDK exposes the field. Batch items pass it throughBatchAddItemextras. wait()polls one tail episode perdocument_idsaga (like multi-thread polling). Ingestion order within a document followscreated_at.- Unified live smoke suite:
ingestion/scripts/smoke.py(alsopytest -m smoke). Runs post-merge and post-release via.github/workflows/smoke-ingestion.ymlwithcontinue-on-error: true.
- Submit everything, then wait once. Multiple files or loaders destined for
the same graph are submitted together. Sequential vs batch only chooses the
submit API (
graph.addvs Batch API); neither waits for one file to finish processing before the next is sent. If you do not need to block, submit and return —wait()stays opt-in. wait()aligns with Check data ingestion status: Batch API paths poll the last batch viabatch.get; sequentialgraph.addpolls the last-submitted episode; sequentialthread.add_messagespolls the last message UUID per thread (regularadd_messagesreturns message UUIDs, nottask_id); nodes/triples poll every task id. Default timeout iswait_timeout_seconds(items_submitted)— 60s per item, minimum 120s. Passtimeout=Noneto wait without a deadline.IngestResult.from_batch_ids(...).wait()has no item count, so auto timeout does not invent a 120s cap. Do not mix Batch API and sequentialgraph.addon the same graph and expect onewait()to cover both.- File one-liners and loaders accept a sequence of paths/globs in caller order
(
ingest_json_records(client, [issues, prs, jira], graph_id=...)). ConcatLoaderconcatenates heterogeneous loaders into one submit stream.IngestResult.combine(...)merges poll handles for separate submits to the same graph (batch_ids,episode_uuids,task_ids); it does not mergenode_uuids/edge_uuids(zip alignment). Prefer separatewait()calls for seeding vs episode ingest.- Multi-thread sequential backfills poll the last message UUID per thread
(threads are independent sagas on the server). Regular
thread.add_messagesreturnsmessage_uuidsonly — nottask_id. - Production smoke script:
ingestion/scripts/release_smoke_prod.py(requiresZEP_API_KEY; run via KeyBankzep-prod).
- Batch submission now rolls over at 10,000 items by default
(
DEFAULT_ITEMS_PER_BATCH) instead of filling to the API's 50,000-item cap. Passmax_items_per_batch(up toMAX_ITEMS_PER_BATCH) to request a larger batch.
Breaking: Zep assigns node and fact UUIDs server-side. Matches the API change that rejects caller-supplied node identity and ignores caller-supplied fact identity.
NodeItemno longer accepts a clientuuid, andingest_nodesno longer hasrequire_uuids. Zep assigns node identities and returns them onIngestResult.node_uuids(parallel to the submitted nodes, withNonefor failed batches; also recovered from completed task params when resuming).FactTripleno longer acceptsfact_uuid; afterwait()/refresh(), assigned fact identities land onIngestResult.edge_uuidsfrom task params (parallel to submitted triples, withNonefor a terminal task that assigned none).source_node_uuid/target_node_uuidremain caller-supplied pins to existing nodes.- JSON row files that still include
uuid/fact_uuidraise a clear ConfigurationError naming the retired field. - Requires
zep-cloud>=3.27.0.
First release — everything upstream of the Zep API for getting unstructured and structured data into Context Graphs correctly.
- A one-liner per source:
ingest_slack_export,ingest_documents,ingest_transcripts,ingest_emails, andingest_json_records(CSV / JSONL / JSON array) parse the source, carry whatever timestamps the source itself supplies, and submit. - User-graph and explicit paths:
ingest_thread_messagesbackfills chat history into a user's graph via threads;ingest_fact_triplesasserts known relationships;ingest_nodesseeds canonical entities. - Composable pipeline:
Loader → Transforms → LimitGuard → Submitter, all lazy generators (a 500k-item export never sits in memory).preview()returns the transformed episodes and validation warnings with zero Zep API calls. - Transforms:
TextChunker(paragraph/sentence-aware, 500-char chunks),AliasCanonicalizer(entity-name canonicalization with a risky-word guard), and optionalLLMContextualizer— bring any LLM through a one-methodcomplete()protocol; OpenAI, Anthropic, and OpenAI-compatible adapters ship as optional extras. - Submission: the Batch API by default on every path — episodes and thread
messages alike — with transparent fallback to sequential
graph.add/thread.add_messageswhen the deployment has no batch endpoint to call (HTTP 404), plus rate-limit-aware pacing, retries, progress, and per-item error recording. Every ingest call returns as soon as the data is submitted;IngestResultexposeswait()/status/failed_items()and the resume handles (batch_ids/task_ids/episode_uuids). - Temporal correctness: loaders preserve source timestamps when the source
carries one — Slack
tsand an email'sDate:header are used automatically, while documents, transcripts, and JSON records stay undated until you supplycreated_at/use_file_mtime=True,meeting_start/default_start_time, orcreated_at_fieldrespectively. The pipeline warns about episodes missingcreated_atbefore submission, andsearch_when_readyabsorbs post-ingestion indexing lag. - Ingests into existing, configured destinations: create the graph and set
its ontology (
client.graph.set_ontology) yourself first — the package does not create graphs or users, nor set ontologies (ingest_thread_messagescreates only the backfill's own threads). - Eager, client-side validation: every documented API limit (episode/message size, metadata keys, UUIDs, RFC3339 timestamps, SCREAMING_SNAKE fact names, …) is checked before the first network call — a bad item is a clear Python error naming the field, not an HTTP 400 mid-run.
- Canonical Slack names: speakers,
@mentions, and DM labels resolve through the export roster preferringprofile.real_nameoverprofile.display_name, so a workspace handle ("morgan") does not split one person from the full name used in other sources ("Morgan Lee"). Authors with noreal_nameare reported inwarnings, andSlackMessage.user_idexposes the raw Slack id soformatter=can substitute names from your own directory. - Runnable examples and sample data for the Slack, document, email, JSON-record, thread-backfill, fact-triple, and user-graph paths, built around one coherent sample dataset.