Skip to content

feat: unify rerank/embed degrade-logging; default scaffold to Gitee embed+rerank#251

Merged
helebest merged 4 commits into
mainfrom
worktree-feat+rerank-embed-degrade-logging
Jun 28, 2026
Merged

feat: unify rerank/embed degrade-logging; default scaffold to Gitee embed+rerank#251
helebest merged 4 commits into
mainfrom
worktree-feat+rerank-embed-degrade-logging

Conversation

@helebest

@helebest helebest commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

What

Unifies rerank/embedding failure semantics on the retrieval read + write paths into one observable, never-silently-broken contract, and flips the dikw init default scaffold to a single-key Gitee embed + rerank profile.

Degrade / log contract

situation behavior log
not configured (rerank unset / no embedder wired) degrade, don't block WARNING
configured + transient failure (5xx/408/429/timeout/parse) degrade, don't block ERROR
configured + permanent failure (401/403/404, bad key/model) fail fast (read → 500, write → ingest aborts) ERROR (propagated)

Concretely:

  • Read path — a transient query-embedding failure degrades the hybrid query to FTS-only (vec leg dropped) instead of 500-ing; single-leg vector/bm25 ablations re-raise. A transient rerank failure degrades to the fused order. Both now log at ERROR. An enabled-but-unconfigured reranker logs one WARNING per retrieve.
  • Write path — an exhausted-retry embed-batch skip now logs ERROR (in-progress retries stay WARNING); a write that produces chunks with no embedder / version drift logs one WARNING that embedding was deferred (ingest / wisdom / synth) rather than silently shipping a vector-less doc.
  • Fail-fast invariant preserved — permanent provider errors still propagate on both paths.

Default scaffold → Gitee (one GITEE_API_KEY drives both legs)

dikw init now defaults the embedder to Gitee bge-m3 (dim 1024, batch 16 — Gitee caps the input array at 25) and ships a Gitee bge-reranker-v2-m3 reranker, so a fresh base reranks out of the box (OpenAI has no /rerank endpoint). LLM default stays Anthropic. The field default factory (_default_provider_config) and the scaffold are now a single source of truth.

Why

Before, a transient model blip 500'd the read path while a misconfig and an unconfigured leg were indistinguishable in logs; the embed write path classified transient-vs-permanent but the "configured-but-failed" and "not-configured" cases were silent. This makes every degrade observable at the right level while keeping the deliberate fail-fast-on-misconfig invariant.

Notable review catches (fixed)

  • rerank model id must be the Gitee-served bare bge-reranker-v2-m3 — the HuggingFace-prefixed BAAI/... 404s on Gitee → permanent error → 500 on every fresh-base retrieve.
  • embedding_batch_size=16 in the default — Gitee's 25-item cap would 400 a fresh ingest at the inherited OpenAI-tuned 64.
  • synth drift — synth now resolves the active text version via the shared drift-aware helper (like wisdom/lint apply), so a cfg-identity drift defers inline embed instead of embedding under the wrong model and deactivating the page.
  • eval purity — eval keeps fail-loud on a transient query-embed blip (degrade_query_embed_on_transient=False) so the --against gate isn't false-flagged.

Eval gate

no-baseline-needed: under healthy providers the change is ranking-neutral — the degrade fires only on injected failure, the rest is log levels + a default-config swap (eval uses its own dataset config, not default_config). The recall@100 invariant is structurally preserved.

Delivery receipt

Verify (step 3):

leg status detail
floor tools/check.py PASS ruff + mypy clean; 2397 passed, 138 skipped
retrieval det. (test_search / test_retrieval_quality) PASS incl. new degrade / log-level / eval-purity / unconfigured-warn cases
postgres contract PASS (real PG) local docker pgvector/pgvector:pg18; storage + task-store contract, 186 passed, 0 PG-skips
config / scaffold smoke PASS dikw init → Gitee bge-m3 + bare bge-reranker-v2-m3 + batch 16; validator OK
retrieval real-data ablation N/A no-baseline-needed (ranking-neutral)
K-layer synth-output N/A synth change is version-resolution + a warning; no authoring/output change

Codex review (step 4): 1 round — TP: Gitee batch size (fixed) + synth deferred-embed warning false-positive (gated).

Fresh-review (step 5): PASS — no blocking findings (fail-fast preserved, mode-gating correct, layering clean).

/code-review xhigh (step 5, 31 agents): 6 findings triaged vs source — 4 CONFIRMED + 1 PLAUSIBLE fixed (rerank id, provider-default divergence, README/getting-started drift, synth drift, eval purity); 1 nit rejected (synth-without-any-embedder is an intentional LLM-only mode).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Fresh setups now ship with Gitee-based embedding and reranking configured by default.
    • Documentation examples were updated to reflect the new default embedding setup and API key.
  • Bug Fixes

    • Hybrid search now falls back more gracefully when query embedding fails temporarily.
    • Retrieval and ingestion now surface more helpful warnings and errors for missing or deferred embedding/reranking.
    • Rerank failures now degrade search results without interrupting the request when safe to do so.
  • Documentation

    • Expanded setup and provider docs with the latest default configuration and failure-handling behavior.

helebestus and others added 3 commits June 28, 2026 15:04
… to Gitee embed+rerank

Read-path resilience + observability, fail-fast invariants preserved:
- transient query-embedding failure degrades the hybrid query to FTS-only
  (vec leg dropped) instead of 500; hybrid-only, single-leg vector/bm25
  ablations re-raise (eval purity); permanent query-embed still 500s.
- transient rerank-degrade + exhausted embed-batch-skip now log at ERROR
  (a configured leg that failed; was WARNING). In-progress retries stay WARNING.
- enabled-but-unconfigured reranker logs one WARNING per retrieve; a write path
  that defers embedding (no embedder wired / version drift) warns instead of
  silently shipping a vector-less doc (ingest/wisdom/synth).
- permanent provider errors (401/403/404, bad key/model) still fail fast on both
  read (->500) and write (ingest aborts) paths — invariant unchanged.

default_config(): embedder -> Gitee bge-m3 (dim 1024) AND ships a Gitee
BAAI/bge-reranker-v2-m3 reranker, both keyed by one GITEE_API_KEY, so a fresh
dikw init reranks out of the box (OpenAI has no /rerank endpoint). LLM default
stays Anthropic. Tests pin the historical openai identity + clear rerank via
init_test_base so the suite is insulated from the shipping default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gate synth deferred-embed warning

Address codex review of the prior commit:
- default_config(): set embedding_batch_size=16. The default embedder is now
  Gitee (bge-m3), and Gitee's /v1/embeddings caps the input array at 25 (HTTP
  400 above) — the inherited OpenAI-tuned 64 would fail a fresh ingest out of
  the box (docs/providers.md gotcha #2). The 64 field default still applies to
  bases that omit the field.
- api_synth: gate the "embedder wired but no active text version" warning on
  there being sources to synth, so a no-op synth on an empty base stays quiet
  (codex minor false-positive).
- tests/fakes.init_test_base: pin embedding_batch_size=64 (historical test
  value) so the suite is insulated from the new Gitee scaffold default — the
  streaming tests string-replace `embedding_batch_size: 64` to force tiny
  batches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…der default, synth drift guard, eval-purity flag

- rerank_model: use the Gitee-served BARE `bge-reranker-v2-m3` (not the
  HuggingFace-prefixed `BAAI/...`, which 404s on Gitee → permanent error → 500
  on every fresh-base retrieve). The eval-verified baseline + cookbook + wiring
  test all use the bare id. Fixed default + test + docs + the Gitee-citing
  validator/docstring examples.
- DRY the two diverging provider defaults: `default_config()` (dikw init) now
  reuses `_default_provider_config()` (the DikwConfig.provider field factory),
  which is updated to the Gitee profile — one source of truth so a bare
  DikwConfig() and an init'd base no longer ship different embedders.
- synth: resolve the active text version via the shared drift-aware
  `_resolve_active_text_version_for_inline_embed` (same as wisdom / lint apply)
  instead of a raw get_active_embed_version, so a cfg-identity drift defers
  inline embed instead of embedding under the wrong model/space (which would
  deactivate the freshly-synthesized page). Honors the documented invariant.
- eval purity: add `degrade_query_embed_on_transient` (default True for the
  production read path); eval/runner passes False so a transient query-embed
  blip fails the eval loud rather than silently degrading a hybrid query and
  biasing the --against gate.
- docs: quickstart `export OPENAI_API_KEY` → `GITEE_API_KEY`; README config
  block → Gitee, matching the new scaffold.

Tests: +test_default_provider_factory_matches_scaffold,
+test_synth_defers_inline_embed_on_version_drift,
+test_query_embed_transient_propagates_when_degrade_disabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@helebest helebest added the no-baseline-needed Bypass Eval gate: change is genuinely non-functional w.r.t. retrieval/synth metrics label Jun 28, 2026
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@helebest, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 14 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f25124b-0ec0-4b8b-955a-e87e75faba32

📥 Commits

Reviewing files that changed from the base of the PR and between b7148fc and 9dcf7b3.

📒 Files selected for processing (2)
  • tests/test_embed_resilient.py
  • tests/test_search.py
📝 Walkthrough

Walkthrough

Switches the default dikw init scaffold from OpenAI to Gitee bge-m3 embedder (1024-dim) plus bge-reranker-v2-m3 reranker. Adds transient query-embedding degradation in HybridSearcher (hybrid-only; eval runner opts out). Raises retry-exhausted embed and rerank failure log levels to ERROR. Adds WARNING logging for enabled-but-unconfigured reranker and deferred embedding across ingest, synth, and wisdom write paths.

Changes

Gitee defaults, read/write-path resilience, and observability

Layer / File(s) Summary
Default scaffold switched to Gitee embedder + reranker
src/dikw_core/config.py, src/dikw_core/providers/rerank.py, tests/test_config.py
_default_provider_config() now returns bge-m3 (Gitee, 1024-dim, GITEE_API_KEY) and bge-reranker-v2-m3 reranker; default_config() delegates to this factory; model name references in docstrings and error messages updated; two new config tests pin Gitee identity and consistency.
HybridSearcher transient query-embed degradation
src/dikw_core/domains/info/search.py, src/dikw_core/eval/runner.py
HybridSearcher.__init__ and from_config gain degrade_query_embed_on_transient: bool = True; search() catches TransientProviderError on text/multimodal vector generation and nulls the vector leg in hybrid mode; transient rerank failure log raised to ERROR; eval runner sets flag to False.
Embed retry-exhaustion log level raised to ERROR
src/dikw_core/domains/info/embed.py
_run_batch_with_retry and embed_assets change "retries exhausted → batch skipped" log calls from warning to error.
Write-path deferred-embed WARNING logging
src/dikw_core/api_ingest.py, src/dikw_core/api_synth.py, src/dikw_core/api_wisdom.py
api_ingest logs WARNING when chunks indexed but no embedding performed; api_synth uses _resolve_active_text_version_for_inline_embed and logs WARNING on drift-triggered deferral; api_wisdom logs WARNING when embedding deferred due to missing active version.
Retrieve warns when reranker enabled but unconfigured
src/dikw_core/api_retrieve.py
_retrieve_inner logs a WARNING when rerank_enabled is true but reranker is None, then continues without the rerank leg.
Test infrastructure
tests/fakes.py
RaisingEmbedder dataclass added and exported; init_test_base now pins OpenAI-compat embedding identity and clears rerank config fields to isolate tests from changed defaults.
Tests
tests/test_search.py, tests/test_embed_resilient.py, tests/test_rerank_api_wiring.py, tests/test_synthesize_pipeline.py, tests/test_write_path_embed_warnings.py
Tests cover: query-embed hybrid degrade and propagation modes (4 new), rerank transient ERROR log, embed batch-skip ERROR log, enabled-but-unconfigured reranker WARNING, synth drift WARNING, and ingest no-embedder WARNING.
Docs and changelog
CHANGELOG.md, CLAUDE.md, README.md, docs/getting-started.md, docs/providers.md
Updated to document Gitee defaults, batch size change, rerank resilience semantics, and new log-level behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • OpenDIKW/dikw-core#136: Introduced TransientProviderError and the per-batch retry-then-skip embed pipeline that this PR's ERROR log-level change and degradation logic build on.
  • OpenDIKW/dikw-core#239: Introduced the optional rerank stage and HybridSearcher._apply_rerank plumbing that this PR extends with Gitee defaults, log-level changes, and enabled-but-unconfigured warnings.
  • OpenDIKW/dikw-core#179: Modified the same api_synth.synthesize flow that this PR changes to add drift-aware embedding deferral and WARNING logging.

Poem

🐇 Hop hop, the defaults have changed today,
From OpenAI's key to Gitee's gateway!
When embeddings fail, we degrade with grace,
Log an ERROR and keep up the pace.
No silent failures — a WARNING rings clear,
The rabbit's pipeline grows wiser this year! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: unified failure handling and a Gitee-based default scaffold.
Description check ✅ Passed The description covers the motivation, behavior changes, testing, and notes, though it doesn't follow the exact template headings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-feat+rerank-embed-degrade-logging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/dikw_core/api_synth.py`:
- Around line 144-158: The warning in api_synth.py is gated on sources, but
sources still includes items skipped by the already logic, so it can fire even
when no synth work happens. Move the warning gate to the post-skip candidate set
used by the synth path, or trigger it only after a source actually enters the
rewrite flow in the same function where embed_deferred and already are
evaluated. Keep the message and logger.warning call, but ensure it reflects only
documents that will באמת be synthesized without inline embeddings.

In `@src/dikw_core/domains/info/search.py`:
- Around line 460-463: The fallback log in the text-embedding failure path is
misleading because the query may still use multimodal or graph signals, so it
should not be described as FTS-only. Update the error message in the search flow
around the text embedding failure handling in the relevant search method to use
neutral wording that reflects partial degradation without implying only FTS
remains, while keeping the existing exception logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e1a80e0-bde1-4878-b63c-d0961de00730

📥 Commits

Reviewing files that changed from the base of the PR and between c9762e4 and b7148fc.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • docs/getting-started.md
  • docs/providers.md
  • src/dikw_core/api_ingest.py
  • src/dikw_core/api_retrieve.py
  • src/dikw_core/api_synth.py
  • src/dikw_core/api_wisdom.py
  • src/dikw_core/config.py
  • src/dikw_core/domains/info/embed.py
  • src/dikw_core/domains/info/search.py
  • src/dikw_core/eval/runner.py
  • src/dikw_core/providers/rerank.py
  • tests/fakes.py
  • tests/test_config.py
  • tests/test_embed_resilient.py
  • tests/test_rerank_api_wiring.py
  • tests/test_search.py
  • tests/test_synthesize_pipeline.py
  • tests/test_write_path_embed_warnings.py

Comment on lines 144 to +158
sources = list(await storage.list_documents(layer=Layer.SOURCE, active=True))
# Heads-up only when there's real work: an embedder was wired but no
# usable active text version (none yet, or cfg drifted from the active
# identity), so any pages synthesized below land without inline vectors
# (a future ingest's resume scan reconciles them) — warn rather than
# silently shipping vector-less K pages. Gated on ``sources`` so a no-op
# synth on an empty base stays quiet.
if embed_deferred and sources:
logger.warning(
"synth: embedder wired but no usable active text version (none "
"yet, or cfg embedding identity drifted from the active one); "
"%d source(s) will be synthesized without inline embeddings (a "
"future ingest will reconcile their vectors)",
len(sources),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Gate this warning on actual synth work.

sources includes documents that the existing already logic will skip. On a default rerun of an up-to-date base, this still logs that sources "will be synthesized without inline embeddings" even though no page is rewritten. Please key the warning off the post-skip candidate set, or emit it only once a source actually enters the synth path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dikw_core/api_synth.py` around lines 144 - 158, The warning in
api_synth.py is gated on sources, but sources still includes items skipped by
the already logic, so it can fire even when no synth work happens. Move the
warning gate to the post-skip candidate set used by the synth path, or trigger
it only after a source actually enters the rewrite flow in the same function
where embed_deferred and already are evaluated. Keep the message and
logger.warning call, but ensure it reflects only documents that will באמת be
synthesized without inline embeddings.

Comment on lines +460 to +463
logger.error(
"query text embedding failed transiently; degrading to "
"FTS-only for this query",
exc_info=True,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid calling this fallback FTS-only.

If multimodal retrieval or the graph leg is active, this path still has non-FTS signals after the text-vector leg drops out. The current message will mislead debugging.

Suggested wording
-                    logger.error(
-                        "query text embedding failed transiently; degrading to "
-                        "FTS-only for this query",
-                        exc_info=True,
-                    )
+                    logger.error(
+                        "query text embedding failed transiently; running this "
+                        "query without the text-vector leg",
+                        exc_info=True,
+                    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.error(
"query text embedding failed transiently; degrading to "
"FTS-only for this query",
exc_info=True,
logger.error(
"query text embedding failed transiently; running this "
"query without the text-vector leg",
exc_info=True,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dikw_core/domains/info/search.py` around lines 460 - 463, The fallback
log in the text-embedding failure path is misleading because the query may still
use multimodal or graph signals, so it should not be described as FTS-only.
Update the error message in the search flow around the text embedding failure
handling in the relevant search method to use neutral wording that reflects
partial degradation without implying only FTS remains, while keeping the
existing exception logging.

codecov/patch flagged 6 new lines without coverage. Add tests for the
read-path multimodal (asset-leg) query-embed transient degrade —
both the hybrid degrade-to-FTS path and the eval-opt-out propagate
path (search.py) — and the asset embed-batch exhausted-retry ERROR
log (embed.py). Mirrors the existing text-leg / chunk-leg cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@helebest helebest merged commit 91b530a into main Jun 28, 2026
21 of 23 checks passed
@helebest helebest deleted the worktree-feat+rerank-embed-degrade-logging branch June 28, 2026 08:46
helebest added a commit that referenced this pull request Jun 28, 2026
Cut the 0.6.5 release and bump every user-facing version reference ahead
of tagging. Since 0.6.4 the engine gained three changes, all already
merged and CI-green on main:

- #251 (feat) — default `dikw init` scaffold now ships a Gitee `bge-m3`
  embedder + `bge-reranker-v2-m3` reranker keyed by one `GITEE_API_KEY`,
  and rerank/embed degrade-logging is unified (transient leg failures log
  at ERROR, enabled-but-unconfigured / deferred-embed log at WARNING;
  permanent provider errors still fail fast).
- #250 (fix) — eval snapshot cache key now includes the ingest-time
  `cjk_tokenizer` and reads every search-time retrieval knob from the live
  config, so retrieval ablations on a shared `cache_root` under the default
  `--cache read_write` stop silently reusing the prior run's config.
- #249 (feat) — eval per-query + negative rows surface `top1_score` and
  `top1_vec_cosine` (reranker-independent, via an eval-internal probe) so
  expect_none / OOD robustness is measurable from absolute relevance.

Mechanics (mirrors the prepare-0.6.4 PR #247):
- Version bump `0.6.4 -> 0.6.5` (`pyproject.toml` + `uv.lock` self-entry).
- CHANGELOG: rename `## Unreleased` -> `## 0.6.5 — …` (Added/Changed/Fixed),
  open a fresh empty `## Unreleased`. The 0.6.5 heading is awk-extractable
  by `release.yml`'s notes extractor.
- Docs/examples: bump the GHCR image tags (`docs/deployment-docker.md`), the
  pip-install pin (`docs/getting-started.md`), the compose `.env` default +
  error-message example (`examples/docker/.env.example`,
  `docker-compose.yml`), and the bug-report version placeholder.
- `examples/docker/Dockerfile` intentionally NOT bumped — `release.yml`'s
  `sync-dockerfile` job auto-bumps it after the tag publishes to PyPI; its
  current 0.6.4 is published, so the Dockerfile guard stays green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-baseline-needed Bypass Eval gate: change is genuinely non-functional w.r.t. retrieval/synth metrics

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants