Skip to content

Release v0.5.0

Choose a tag to compare

@github-actions github-actions released this 27 Oct 03:57
d91316f

Added

  • CockroachDBStore and AsyncCockroachDBStore, LangGraph long-term memory stores on CockroachDB. They implement LangGraph's BaseStore with the API of langgraph-checkpoint-postgres's PostgresStore (from_conn_string with pipeline and pool_config, setup(), index and ttl), so they work with create_agent(store=...), StateGraph.compile(store=...) and Deep Agents' StoreBackend. from_conn_string also takes retry_policy, connect_timeout, prepare_threshold and connect_kwargs, as the checkpointers do. The two stores, CockroachDBIndexConfig, CockroachDBTTLConfig and PoolConfig are exported from langchain_cockroachdb.store, and all of them but PoolConfig from langchain_cockroachdb too.
  • Semantic search on CockroachDB's VECTOR type, with a C-SPANN index that has the namespace as a prefix column. index takes LangGraph's dims, embed and fields, plus distance_type (cosine, l2 or inner_product), cspann partition sizes, flat to skip the index, and beam_size, which is set for each search. A search under a namespace first lists the namespaces at or under it, then ranks with the index within them, and runs a second, exact query when a filter or expired items leave its page short, unless a count shows that the index returned every vector of those namespaces. A search of the whole store, every search of a flat store, and a search over more than 1000 namespaces (MAX_SEARCH_NAMESPACES) rank every match exactly instead. So a filter or expired items never leave a page short while more matches exist.
  • Expiry through CockroachDB row-level TTL. With a ttl configuration, setup() turns on the TTL job for the items and vectors tables, on a schedule from sweep_interval_minutes or ttl_job_cron (@hourly by default), so no sweeper thread is needed. Vectors carry their item's expiry. Expired items are left out of reads, searches and listings by default, even by a store built without ttl; ttl={"omit_expired": False} turns that off. With refresh_on_read, a read pushes back the expiry of the items it returns, in the same statement. sweep_ttl(), start_ttl_sweeper() and stop_ttl_sweeper() work as in PostgresStore. A store built without ttl applies no default expiry, never refreshes one, and its setup() leaves an existing TTL setting alone. On CockroachDB v25.4, disable_ttl(), adisable_ttl() and the TTL step of setup() retry while another process changes the same table.
  • delete_namespace(namespace_prefix) and adelete_namespace, which delete every item and vector in a namespace and its descendants in one transaction and return the number of items deleted.
  • The sync methods of AsyncCockroachDBStore (batch, get, put, search, delete and list_namespaces) raise RuntimeError instead of hanging when the store's event loop is closed or not running, or when called from a forked child, as those of AsyncCockroachDBSaver do. From the loop's own thread they raise asyncio.InvalidStateError.
  • Differences from PostgresStore: the puts and deletes of one batch commit in one transaction; every call retries transient errors under the store's RetryPolicy; a CockroachDBStore on a pool runs batches concurrently; putting an item again or deleting it removes its old vectors; a filter that compares with a number matches numeric values only (PostgresStore compares text), nested dictionaries match nested keys, and $ne matches items without the key; results with the same score or updated_at are ordered by namespace and key, so paging is stable; table_name and schema configure the tables (defaults store, store_vectors and store_migrations in public); a query on a store without index logs a WARNING and runs a plain search; after setup(), a changed dims or distance_type raises, and other partition sizes, or flat with an index present, log a WARNING; disable_ttl() and adisable_ttl() remove the table TTL setting, and drop_vector_index() and adrop_vector_index() drop the vector index so that setup() can build it again, for example with another distance; and there is no deserializer= argument, because psycopg decodes JSONB before the store sees it.
  • examples/long_term_memory.py, a LangGraph agent that saves a memory in one thread and recalls it in another, and a guide, an example page and an API page for the store in the documentation.

Fixed

  • enable_ttl and disable_ttl (and aenable_ttl and adisable_ttl) on the checkpointers no longer fail when several processes run them at once on CockroachDB v25.4. That version refuses to change a table's TTL settings while another schema change on it runs, with SQLSTATE 0A000, which was not retried. These TTL changes now retry that error under the saver's retry policy, even if its retry_on would refuse it; max_attempts=1 turns this off. The code and phrase are exported as SCHEMA_CHANGE_BUSY_SQLSTATE and SCHEMA_CHANGE_BUSY_MESSAGES, with is_schema_change_busy() and schema_change_policy().
  • A checkpointer or store on one connection in pipeline mode (pipe=, or from_conn_string(..., pipeline=True)) could hang when a large write met a slow link, such as TLS to a distant cluster. psycopg's pipeline sync can leave the last bytes of a write unsent, so the server waited for them while the client waited for the server. The library now sends them before it waits. CockroachDBSaver and AsyncCockroachDBSaver had this since 0.2.0; CockroachDBStore and AsyncCockroachDBStore have the fix from the start. Pools, and single connections without a pipeline, were not affected.

What's Changed

Full Changelog: v0.4.1...v0.5.0