Repository navigation
Conversation
…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.
CollierKing
added a commit
that referenced
this pull request
Aug 18, 2026
…add new deepseek models (#53) * fix: decode base64 metadata blobs and fix sync list() filtering 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 <pkmittal28@gmail.com> * feat: add WorkerCloudflareD1Saver for Cloudflare Python Workers 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. * fix(langmem-vectorize): search() crashes on empty namespace prefix with 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. * fix(langchain-cloudflare): Vectorize/index bugs found building langmem 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 * feat(langmem-cloudflare-vectorize): add Python Worker support + REST/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. * fix(langchain-cloudflare): harden REST client layer 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/ * test(langchain-cloudflare): mark known-flaky models for retry, drop deprecated 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. * chore: enforce E501 line-length lint consistently across all packages 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. * chore: bump versions and update changelog for this session's changes 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. * fix(reranker): _process_response never actually sorts results by score 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. * feat(langchain-cloudflare): add DeepSeek V4 Pro and Flash models 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. * test(langchain-cloudflare): mark gpt-oss-120b as known-flaky (structured 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. * test(langchain-cloudflare): mark llama-3.3-70b structured-output tests 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. * fix: CI lint failures (ruff version drift + missing dev-group dependency) 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. * fix: stop gitignoring uv.lock for the 4 CI-relevant packages 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. --------- Co-authored-by: Collier King <collier@example.com> Co-authored-by: Praveen Mittal <pkmittal28@gmail.com>
Collaborator
|
Thank you for this — This fix has been incorporated into #53, which just merged into main (commit 9437e4c). Closing this in favor of that — appreciate you tracking it down! |
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.
CloudflareVectorizeBaseStore.search()only assignsdocs_and_scoreswhenqueryis truthy, or whenqueryis falsy andnamespace_prefix_stris truthy. If both are empty —store.search(()), listing everything with no query, a documented valid LangGraphBaseStoreusage — neither branch runs, and the code falls through tofor doc, score in docs_and_scores:, raisingUnboundLocalError.An empty namespace prefix is a valid "search everything" request (every namespace string starts with
""), so the dummy-query fetch now runs unconditionally whenever noqueryis given, and the existing namespace-prefix filtering loop below handles the rest.Added
tests/unit_tests/test_search_empty_namespace.py. Confirmed the crash reproduces on unpatched code (git stash) with the exactUnboundLocalError, and passes after the fix.ruff checkandmypyclean.