Repository navigation
Add Worker binding support for D1 checkpointer & langmem-vectorize & add new deepseek models - #53
Merged
Merged
Conversation
get_tuple/aget_tuple/list/alist read the checkpoints.metadata BLOB
column without base64-decoding it first, so every returned
CheckpointMetadata silently fell back to {"step": -2} (or an empty
dict in list/alist) instead of the real stored metadata. Fixed via a
shared decode_metadata_blob helper used by all four call sites across
CloudflareD1Saver, AsyncCloudflareD1Saver, since none of this had
integration test coverage before now.
Also fixes CloudflareD1Saver.list() (sync) building its own ad-hoc
WHERE clause that only supported exact-match filtering on
thread_id/checkpoint_id, silently dropping every other filter key and
never referencing the before parameter at all, unlike alist() (which
already delegated to search_where() correctly). Rewrote list() to use
search_where(), and fixed a related bug in search_where()'s metadata
predicate builder where the bool branch was checked after the int
branch and was therefore unreachable (bool is a subclass of int in
Python), so boolean filter values were passed through as raw
True/False instead of the 1/0 SQLite's json_extract() returns.
Adds REST API integration tests against live D1 (previously this
package had none, for either the sync or async saver).
Co-authored-by: Praveen Mittal <[email protected]>
Adds a checkpoint saver for use inside Cloudflare Python Workers, talking to D1 through the native Worker binding (env.DB) via sqlalchemy-cloudflare-d1's WorkerConnection instead of the REST API -- no network round-trip to the Cloudflare API, no API token. Installable via the new optional worker extra: pip install 'langgraph-checkpoint-cloudflare-d1[worker]' Implements both the native async methods (for graph.ainvoke/astream) and, mirroring sqlalchemy-cloudflare-d1's SyncWorkerConnection, synchronous methods bridged via pyodide.ffi.run_sync(), for calling the saver directly outside of a compiled graph. graph.invoke() (sync) itself still can't run inside a Worker with any checkpointer attached -- LangGraph's sync Pregel loop always submits checkpoint writes to a real ThreadPoolExecutor, which Workers/Pyodide can't create. Adds examples/workers/, a Python Worker example exercising the saver end to end, including a full StateGraph compiled with it as its checkpointer, and REST/Worker integration test coverage mirroring the split already used in sqlalchemy-cloudflare-d1 and langchain-cloudflare.
…th no query docs_and_scores was only assigned when namespace_prefix_str was truthy, so store.search(()) -- listing everything with no query, a documented valid LangGraph BaseStore usage -- raised UnboundLocalError instead of returning results. An empty prefix is a valid 'search everything' request since every namespace starts with the empty string; the dummy- query fetch now runs unconditionally when no query is given.
…m Worker support - add_documents/aadd_documents ignored explicit ids in some code paths, silently falling back to generated ones - _poll_mutation_status/_apoll_mutation_status had no timeout, so a single stuck API response could hang indefinitely - _index_is_ready checked fields the /info endpoint never returns, so create_index(wait=True) could never detect readiness - integration tests now poll for Vectorize's eventual-consistency window after writes instead of asserting immediately
…Worker integration tests Extends CloudflareVectorizeBaseStore with the same binding/Worker pattern already established in langchain-cloudflare and langgraph-checkpoint-cloudflare-d1: binding=/d1_binding= constructor params, real async implementations (aget/aput/adelete/asearch/abatch), and sync methods that bridge to them via pyodide.ffi.run_sync() inside a Worker. Also gives the package REST + Worker integration test coverage it didn't have before (examples/workers/, tests/integration_tests/, tests/worker_tests/), matching the other two libraries' test structure.
Reliability fixes found via full integration suite validation: - streamed chunk usage included a per-chunk "neurons" float that langchain_core's chunk merge can't combine across chunks, crashing any stream longer than one token - mistral-small-3.1 and gpt-oss don't support tool_choice reliably, so tool-calling-based structured output silently returned None; routed both through json_schema instead, with a max_tokens floor for reasoning models so the reasoning phase can't exhaust the budget before answering - CloudflareWorkersAIReranker's REST calls had no request timeout at all Also unifies AI Gateway routing with the standard Workers AI endpoint (chat_models.py, embeddings.py, rerankers.py): AI Gateway no longer uses a separate gateway.ai.cloudflare.com host, routing instead via a cf-aig-gateway-id header on the same endpoint everything else uses -- see https://blog.cloudflare.com/workers-ai-gateway-unification/
…eprecated kimi-k2.5 kimi-k2.5 is deprecated (planned_deprecation_date already past per the Workers AI models API) and superseded by kimi-k2.6, already in the model matrix and covering the same capabilities. gemma-4-26b-a4b-it is not deprecated, just intermittently slow (200s+ on some calls, passing in seconds on immediate retry) -- adds pytest-rerunfailures and wraps it with a rerun marker instead of letting the whole suite fail on model-specific, not code, flakiness.
langchain-cloudflare enforced ruff's E501 while langgraph-checkpoint-cloudflare-d1 and langmem-cloudflare-vectorize both ignored it, so the same lint gate meant different things per package. Standardized on enforcing it everywhere and rewrapped the resulting violations (docstrings, log/error f-strings, and SQL statement strings) without changing behavior.
langchain-cloudflare (0.3.6 -> 0.3.7): Vectorize/chat-model/reranker fixes and the AI Gateway unification. langmem-cloudflare-vectorize (0.1.1 -> 0.1.2): Worker binding support, the PR #51 empty-namespace search fix, and the with_cloudflare_embeddings index_name propagation fix. Backfilled a changelog section for this package, which previously had none.
Every docstring on this reranker (rerank(), arerank(), and _process_response() itself) promises results "sorted by relevance score (descending)", but the implementation just appended results in whatever order the API response listed them -- no sort() call anywhere. Nothing in the test suite exercised this at all; the only existing tests cover credential validation. Added the sort and two tests covering both an out-of-order response and an already-sorted one.
Adds @cf/deepseek-ai/deepseek-v4-pro-0813 and @cf/deepseek-ai/deepseek-v4-flash-0731 to the tested and example-supported model lists. Both are the first Workers AI models with a full 1,048,576-token context window and support thinking mode (reasoning_content) and function calling; deepseek-v4-pro additionally supports vision (image input), deepseek-v4-flash is text/audio only. Both require the Workers Paid plan or prepaid AI Gateway credits. Verified live before assuming standard reasoning-model behavior: tool_choice="required" returns clean tool_calls with empty content, and reasoning shows up under the existing reasoning_content field -- so both models use the existing _REASONING_BEHAVIOR entry rather than a bespoke one.
…red output) gpt-oss-120b intermittently produces runaway/degenerate output on structured-output requests -- a JSON field filled with thousands of repeated "-001" fragments (~20KB+), sometimes large/slow enough to trip a 500 instead of returning. Confirmed live by hitting the raw endpoint directly, repeatedly, outside pytest: 2 of 3 calls reproduced it, 1 returned a normal response. Not a code bug -- a live model-quality/stability issue on Cloudflare's platform for this model, same category as the existing gemma-4-26b-a4b-it handling. Wrapped with the existing _model_param()/FLAKY_MODELS rerun mechanism in both integration test files rather than adding a second one.
…s as flaky
llama-3.3-70b-instruct-fp8-fast intermittently returns zero chunks when
streaming a structured-output response ("Stream returned no chunks --
possible API issue" from langchain_tests itself), while the equivalent
non-streaming invoke() call succeeds. A live API/model reliability
issue, not a code bug -- confirmed pre-existing by running the same
tests against a clean checkout with none of this session's changes,
and intermittent (a different subset fails each run, never all).
This file has no existing FLAKY_MODELS/_model_param() infrastructure
to hook into (it's a fixed single-model LangChain conformance suite,
not a parametrized model list), so the affected inherited test methods
are overridden here just to attach pytest.mark.flaky(reruns=2,
reruns_delay=5), re-applying the base class's parametrize decorator
where present so all schema_type variants still run.
Verified live: test_structured_output[json_schema] and
test_structured_output_async[json_schema] both hit the failure and
were correctly retried to a pass by the new marker.
…ncy) CI failed across every lint job and the pre-commit job on first run, since none of this branch's code had ever actually gone through CI before this PR. Two distinct root causes, neither reproducible via the local venvs used earlier in this branch's history: 1. uv.lock is gitignored repo-wide, so both CI and any local machine re-resolve ruff fresh against PyPI on every sync with no pinning. Local venvs across all three packages had drifted to older ruff (0.14.10-0.15.18) than what CI resolves (0.16.x), which changed markdown-formatting and default-rule-selection behavior. Refreshed all three packages' environments to the current resolution and applied the resulting formatting fixes (langchain-cloudflare's and langmem-cloudflare-vectorize's READMEs, a langmem Worker test's line wrapping) plus two newly-caught pre-existing lint violations in root-level scripts (typing.Dict -> dict, an intentional blind except needing an explicit noqa). 2. langgraph-checkpoint-cloudflare-d1's worker.py imports sqlalchemy-cloudflare-d1, which was only declared in the `test` dependency group, not `dev` -- CI's lint job syncs `--group dev` only, so mypy couldn't resolve the import. Added it to `dev` too. Verified by simulating CI's exact per-job sync target (dev-only for lint, dev+test for test) in a clean venv, not just re-running the combined local dev environment.
uv.lock was gitignored repo-wide with no stated rationale (added as an aside in an unrelated commit). Without a committed lockfile, every `uv sync` -- local or CI -- re-resolves dependencies fresh against PyPI, so a local venv resolved months ago silently drifts from whatever CI gets on its next run. This is what caused PR #53's lint failures: local ruff was 3-4 releases behind what CI installed. Un-ignore and commit the lockfile for root and the three libs/* packages CI's _lint.yml/_test.yml actually run against, pinned to the versions already verified passing. Leave examples/workers/uv.lock gitignored -- those are pywrangler-managed Pyodide resolutions, never touched by CI, and already known to need manual re-vendoring per DEVELOPMENT.md, so pinning them doesn't prevent a real CI failure the way the package-level locks do.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
langgraph-checkpoint-cloudflare-d1
WorkerCloudflareD1Saverfor use inside Cloudflare Python Workers, matching the binding pattern already established elsewhere in this repo.list()'s filtering so it supportsbefore/metadata filters likealist()already does.langmem-cloudflare-vectorize
CloudflareVectorizeBaseStore(binding=/d1_binding=), with real async implementations and a Worker example.search()crashing on an empty namespace prefix with no query. Contributed by @mittalpk.langchain-cloudflare
@cf/deepseek-ai/deepseek-v4-pro-0813and@cf/deepseek-ai/deepseek-v4-flash-0731— first Workers AI models with a full 1M-token context window, thinking mode, and function calling;deepseek-v4-proalso supports vision.CloudflareWorkersAIRerankerresults not actually being sorted by relevance score, despite every docstring promising it. Contributed by @mittalpk.mistral/gpt-ossstructured output through a more reliable path, add a request timeout to the reranker, and unify AI Gateway routing with Cloudflare's endpoint consolidation.gemma-4-26b-a4b-it,gpt-oss-120b, andllama-3.3-70bstructured-output streaming) for automatic retry in integration tests; drop deprecatedkimi-k2.5.Versions
langchain-cloudflare→ 0.3.7langgraph-checkpoint-cloudflare-d1→ 0.1.6langmem-cloudflare-vectorize→ 0.1.2Test plan
pre-commit run --all-filespywrangler dev)