Skip to content

inference: add embedding provider foundation#69903

Open
ChangRui-Ryan wants to merge 12 commits into
pingcap:masterfrom
ChangRui-Ryan:changrui_embedded_p1
Open

inference: add embedding provider foundation#69903
ChangRui-Ryan wants to merge 12 commits into
pingcap:masterfrom
ChangRui-Ryan:changrui_embedded_p1

Conversation

@ChangRui-Ryan

@ChangRui-Ryan ChangRui-Ryan commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: ref #67765

Problem Summary:

The auto-embedding feature needs a reusable, provider-independent foundation for calling external embedding services. Without a common abstraction, each provider would need to duplicate request batching, cancellation, timeout handling, response validation, and credential-safe error logging.

What changed and how does it work?

This PR introduces the embedding provider foundation used by follow-up auto-embedding changes:

  • Adds a common Embedder interface and helpers for JSON options, float32 vector decoding, HTTP timeouts, and credential-safe error logging.
  • Adds a batch embedder that groups requests with the same provider, model, and options, then distributes the returned embeddings to the original callers. The underlying request is canceled only when all callers have canceled.
  • Adds a deterministic mock provider for tests.
  • Adds an OpenAI-compatible provider with Base URL normalization, request/response handling, timeout protection, and response index validation.

The new packages are not registered with the TiDB runtime and do not introduce any SQL, system variable, configuration, or Domain behavior changes in this PR.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Please refer to Release Notes Language Style Guide to write a quality release note.

None

Summary by CodeRabbit

  • New Features
    • Added an embedding provider interface for generating vector representations.
    • Added OpenAI embedding support, including configurable endpoints, authentication, request options, timeouts, and response validation.
    • Added concurrent request batching to improve embedding throughput while supporting multiple models and providers.
    • Added a deterministic mock embedding provider for testing and development.
  • Security
    • Sensitive credentials and token-like values are redacted from logged error responses.
  • Reliability
    • Added handling for canceled requests, provider errors, malformed responses, and oversized responses.

@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-tests-checked release-note-none Denotes a PR that doesn't merit a release note. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed do-not-merge/needs-tests-checked labels Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

1 similar comment
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds shared embedding contracts and utilities, a concurrent batching embedder, deterministic mock and OpenAI providers, extensive tests, Bazel targets, and a bindinfo test-sharding adjustment.

Changes

Embedding subsystem

Layer / File(s) Summary
Base embedding contract and utilities
pkg/inference/embedding/base/*
Defines the provider interface, float32 decoding, option merging, sensitive-error sanitization, tests, and Bazel targets.
Request batching implementation
pkg/inference/embedding/batcher/*
Adds provider registration, model/option-based batching, batch windows, size limits, cancellation coordination, result fan-out, tests, and Bazel targets.
Mock embedder for testing
pkg/inference/embedding/mock/*
Adds a deterministic embedder that parses JSON vectors, validates options, applies offsets, supports delays, and is wired into Bazel.
OpenAI embedder implementation
pkg/inference/embedding/openai/*
Adds OpenAI request/response types, HTTP request handling, authentication, response limits, validation, base64 decoding, error mapping, tests, and Bazel targets.

Bazel test sharding adjustment

Layer / File(s) Summary
Bindinfo test sharding
pkg/bindinfo/tests/BUILD.bazel
Changes the bindinfo test target from 25 to 26 shards.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Batch
  participant Provider
  Caller->>Batch: CreateEmbeddings(provider/model, texts, opts)
  Batch->>Batch: Group compatible requests
  Batch->>Provider: CreateEmbeddings(merged texts, opts)
  Provider-->>Batch: embeddings or error
  Batch-->>Caller: request-specific embeddings or error
Loading
sequenceDiagram
  participant Caller
  participant OpenAIEmbedder
  participant OpenAIAPI
  Caller->>OpenAIEmbedder: CreateEmbeddings(model, texts, opts)
  OpenAIEmbedder->>OpenAIAPI: POST /embeddings
  OpenAIAPI-->>OpenAIEmbedder: Response or ErrorResponse
  OpenAIEmbedder-->>Caller: decoded embeddings or error
Loading

Poem

A rabbit hops through vectors bright,
Batching requests by day and night.
Keys are masked, secrets hide,
OpenAI answers flow inside.
Tests shard wide, the builds now sing—
Carrots for every embedding!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.65% 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 clearly summarizes the main change: adding a provider foundation for embedding.
Description check ✅ Passed The description follows the template and includes the issue reference, problem summary, changes, tests, side effects, docs, and release note.
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

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.

@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: 5

🧹 Nitpick comments (3)
pkg/inference/embedding/mock/mock.go (1)

74-78: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Prevent timer leak by using time.NewTimer.

When ctx.Done() is selected before the duration elapses, time.After leaks the underlying timer until the duration fires (which is 1 hour in one of your tests). It is a good practice to use time.NewTimer and explicitly stop it to prevent accumulating lingering timers in the test environment.

♻️ Proposed refactor
-				select {
-				case <-time.After(dur):
-				case <-ctx.Done():
-					return nil, ctx.Err()
-				}
+				timer := time.NewTimer(dur)
+				select {
+				case <-timer.C:
+				case <-ctx.Done():
+					timer.Stop()
+					return nil, ctx.Err()
+				}
+				timer.Stop()
🤖 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 `@pkg/inference/embedding/mock/mock.go` around lines 74 - 78, Update the delay
select in the mock embedding flow to use a timer created with time.NewTimer
instead of time.After, and stop the timer when the context cancellation branch
wins. Preserve the existing duration completion and ctx.Err() return behavior.
pkg/inference/embedding/batcher/batcher_test.go (1)

203-247: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the new test targets deterministic instead of relying on flaky retries.

  • pkg/inference/embedding/batcher/batcher_test.go#L203-L247: synchronize all calls entering the batch before triggering processing.
  • pkg/inference/embedding/batcher/batcher_test.go#L578-L630: replace elapsed-time upper bounds with an explicit provider-start signal.
  • pkg/inference/embedding/batcher/BUILD.bazel#L16-L16: remove flaky = True after fixing the timing assumptions.
  • pkg/inference/embedding/base/BUILD.bazel#L15-L15: remove flaky = True; these utility tests are already deterministic.

As per coding guidelines, test changes should remain deterministic.

🤖 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 `@pkg/inference/embedding/batcher/batcher_test.go` around lines 203 - 247, Make
the batching tests deterministic by synchronizing all CreateEmbeddings calls in
TestBatchEmbedder_BatchingSameModel before allowing batch processing; at
pkg/inference/embedding/batcher/batcher_test.go:203-247, use an explicit barrier
or equivalent coordination. At
pkg/inference/embedding/batcher/batcher_test.go:578-630, replace elapsed-time
upper-bound assertions with an explicit signal that the provider has started.
Remove flaky = True from pkg/inference/embedding/batcher/BUILD.bazel:16 and
pkg/inference/embedding/base/BUILD.bazel:15.

Source: Coding guidelines

pkg/inference/embedding/openai/openai_test.go (1)

69-88: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid require inside the httptest.Server handlers

require calls FailNow, which must run on the test goroutine. Capture request data in the handler and assert in the test body, or use assert for in-handler checks.

🤖 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 `@pkg/inference/embedding/openai/openai_test.go` around lines 69 - 88, Update
the httptest.Server handler in the OpenAI embedding test to avoid all require
calls, since FailNow must run on the test goroutine. Capture the request path,
method, headers, body, and any read error in the handler, then perform the
corresponding require assertions after the request completes in the test body.

Source: Coding guidelines

🤖 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 `@pkg/inference/embedding/base/base.go`:
- Around line 62-77: Update JSONFieldsWithOptions so fixed fields always take
precedence over provider options when keys collide; preserve all non-conflicting
options. Ensure MarshalJSONWithOptions uses this corrected merge behavior,
preventing options such as input, model, or encoding_format from changing the
actual request payload.

In `@pkg/inference/embedding/batcher/batcher.go`:
- Around line 101-118: Update RegisterEmbedder and MustRegisterEmbedder to
reject nil base.Embedder values before storing them, using a shared registration
path where practical. Preserve the existing provider normalization and
duplicate/format validation, and ensure neither method inserts a nil embedder
into b.embedders.
- Around line 254-281: Update the batching flow around thisBatch.calls and
startIndices to exclude calls whose contexts are already canceled before
building allTexts, while preserving offsets for the remaining active callers. If
every call is canceled, skip embedder.CreateEmbeddings entirely and return the
corresponding canceled results; otherwise dispatch only the active texts and
retain the existing cancellation watcher behavior for them.

In `@pkg/inference/embedding/openai/openai.go`:
- Around line 115-118: Update the response-reading logic in the OpenAI embedding
request flow around io.ReadAll to enforce a configurable maximum body size for
both successful and error responses. Use a bounded reader, reject responses
exceeding the limit, and preserve the existing error propagation behavior.
- Around line 156-159: Update the error handling in the embedding decode flow
around base.DecodeFloat32ArrayBytes to wrap and preserve the returned err while
retaining the item index context. Ensure callers can inspect the underlying
decoding failure, including malformed base64 errors.

---

Nitpick comments:
In `@pkg/inference/embedding/batcher/batcher_test.go`:
- Around line 203-247: Make the batching tests deterministic by synchronizing
all CreateEmbeddings calls in TestBatchEmbedder_BatchingSameModel before
allowing batch processing; at
pkg/inference/embedding/batcher/batcher_test.go:203-247, use an explicit barrier
or equivalent coordination. At
pkg/inference/embedding/batcher/batcher_test.go:578-630, replace elapsed-time
upper-bound assertions with an explicit signal that the provider has started.
Remove flaky = True from pkg/inference/embedding/batcher/BUILD.bazel:16 and
pkg/inference/embedding/base/BUILD.bazel:15.

In `@pkg/inference/embedding/mock/mock.go`:
- Around line 74-78: Update the delay select in the mock embedding flow to use a
timer created with time.NewTimer instead of time.After, and stop the timer when
the context cancellation branch wins. Preserve the existing duration completion
and ctx.Err() return behavior.

In `@pkg/inference/embedding/openai/openai_test.go`:
- Around line 69-88: Update the httptest.Server handler in the OpenAI embedding
test to avoid all require calls, since FailNow must run on the test goroutine.
Capture the request path, method, headers, body, and any read error in the
handler, then perform the corresponding require assertions after the request
completes in the test body.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e606833c-58e5-4420-a40c-559951d09559

📥 Commits

Reviewing files that changed from the base of the PR and between 9fed09d and e2c4165.

📒 Files selected for processing (13)
  • pkg/inference/embedding/base/BUILD.bazel
  • pkg/inference/embedding/base/base.go
  • pkg/inference/embedding/base/base_test.go
  • pkg/inference/embedding/batcher/BUILD.bazel
  • pkg/inference/embedding/batcher/batcher.go
  • pkg/inference/embedding/batcher/batcher_test.go
  • pkg/inference/embedding/mock/BUILD.bazel
  • pkg/inference/embedding/mock/mock.go
  • pkg/inference/embedding/mock/mock_test.go
  • pkg/inference/embedding/openai/BUILD.bazel
  • pkg/inference/embedding/openai/openai.go
  • pkg/inference/embedding/openai/openai_test.go
  • pkg/inference/embedding/openai/protocol.go

Comment thread pkg/inference/embedding/base/base.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/openai/openai.go Outdated
Comment thread pkg/inference/embedding/openai/openai.go
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.9091%. Comparing base (57b5f22) to head (6372fc9).
⚠️ Report is 8 commits behind head on master.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #69903        +/-   ##
================================================
- Coverage   76.3198%   73.9091%   -2.4108%     
================================================
  Files          2041       2058        +17     
  Lines        559955     579713     +19758     
================================================
+ Hits         427357     428461      +1104     
- Misses       131697     150767     +19070     
+ Partials        901        485       -416     
Flag Coverage Δ
integration 40.6988% <ø> (+0.9936%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
dumpling 60.4471% <ø> (ø)
parser ∅ <ø> (∅)
br 47.4060% <ø> (-15.3154%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ti-chi-bot ti-chi-bot Bot added the sig/planner SIG: Planner label Jul 17, 2026
@ChangRui-Ryan

Copy link
Copy Markdown
Contributor Author

/retest

Comment thread pkg/inference/embedding/base/base.go Outdated
Comment thread pkg/inference/embedding/base/base.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go
Comment thread pkg/inference/embedding/mock/mock_test.go Outdated
@ChangRui-Ryan

Copy link
Copy Markdown
Contributor Author

/retest

@ChangRui-Ryan

Copy link
Copy Markdown
Contributor Author

/retest

Comment thread pkg/inference/embedding/batcher/batcher.go
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/openai/openai.go
Comment thread pkg/inference/embedding/openai/openai.go Outdated
Comment thread pkg/inference/embedding/openai/openai.go Outdated
Comment thread pkg/inference/embedding/openai/openai.go
Comment on lines +186 to +189
if message != "" {
return nil, fmt.Errorf("OpenAI: %s", message)
}
return nil, fmt.Errorf("OpenAI: status code %d", resp.StatusCode)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe merge into one OpenAI: status code %d, message: %s

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This would be reasonable for a new error contract, but it changes the externally visible error text that existing downstream code already receives.

The current implementation always records the HTTP status in the structured internal log, returns the sanitized provider message when one is available, and falls back to a status-code error otherwise. We prefer to preserve that behavior in this compatibility-focused PR. A structured provider error can be designed together with retry semantics in a separate change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

who is "existing downstream code"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

By "existing downstream code" I mean the deployed EMBED_TEXT() integration from tidbcloud/tidb-cse#1686 and its follow-up tidbcloud/tidb-cse#2015.

The concrete call path is pkg/expression/builtin_inference.go -> domainadaptor.GetEmbedFn(sctx).EmbedWithContext(...) -> the batcher -> the OpenAI embedder. EvalEmbedTextArgsToDatum returns the provider error directly, so this text is surfaced to the TiDB user as the SQL error from EMBED_TEXT(). The provider initialization in pkg/inference/sqlembed.go also supplies the user-facing 401 error.

Therefore changing all failures to OpenAI: status code %d, message: %s would change SQL-visible error text already used by that deployment. For this compatibility-focused split PR, I prefer preserving the current text. A later typed provider error can carry status/retry metadata without requiring a user-visible text change.

I should have identified this downstream explicitly in the previous reply.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there is no need to compatible with tidb-cse, I think we can simplify it directly

return nil, err
}

jsonData, err := json.Marshal(base.JSONFieldsWithOptions(map[string]any{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does open AI have SDK for accessing its resource? so we can avoid write raw http req/resp

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OpenAI Go SDKs are available, but this adapter deliberately targets OpenAI-compatible endpoints rather than only the hosted OpenAI service. It needs configurable base URLs and transparent pass-through of provider-specific options.

The raw HTTP surface here is small, has explicit timeout/body-size/error handling, and avoids introducing SDK-specific request types, retry behavior, or version coupling. For this foundation layer, we prefer to keep the minimal HTTP adapter.

Comment thread pkg/inference/embedding/openai/protocol.go
}
}

func readResponseBody(reader io.Reader, maxBytes int64) ([]byte, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could simplify this by letting a single bounded io.ReadAll include the overflow probe:

func readResponseBody(reader io.Reader, maxBytes int64) ([]byte, error) {
	body, err := io.ReadAll(io.LimitReader(reader, maxBytes+1))
	if err != nil {
		return nil, err
	}
	if int64(len(body)) > maxBytes {
		return nil, fmt.Errorf("response body exceeds maximum size of %d bytes", maxBytes)
	}
	return body, nil
}

For an arbitrary io.Reader, consuming one extra byte is still necessary to distinguish an exactly-at-limit body from an oversized one, but this avoids the separate io.ReadFull step and states the intent more directly. Content-Length could provide an early rejection, but cannot replace the bounded read because it may be unknown or inaccurate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, the proposed implementation is valid and is slightly more compact. The current implementation has the same bound and semantics: it reads at most maxBytes, then probes exactly one additional byte to distinguish an exactly-at-limit response from an oversized response.

Since the existing implementation is covered by exact-limit and overflow tests and there is no correctness difference, we prefer to avoid a non-functional rewrite in this compatibility-focused PR.

@D3Hunter D3Hunter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary

  • Total findings: 21
  • Inline comments: 21
  • Summary-only findings (no inline anchor): 0
Findings (highest risk first)

⚠️ [Major] (11)

  1. Embedder contract omits the output-to-input ordering invariant (pkg/inference/embedding/base/base.go:42; pkg/inference/embedding/batcher/batcher.go:368; pkg/inference/embedding/openai/openai.go:196)
  2. Register and MustRegister do not expose distinct registration semantics (pkg/inference/embedding/batcher/batcher.go:105)
  3. SanitizeErrorText overstates a best-effort credential redactor (pkg/inference/embedding/base/base.go:34; pkg/inference/embedding/base/base.go:72; pkg/inference/embedding/openai/openai.go:170)
  4. Batch CreateEmbeddings hides its required provider-qualified model syntax (pkg/inference/embedding/batcher/batcher.go:224; pkg/inference/embedding/base/base.go:44; pkg/inference/embedding/openai/openai.go:110)
  5. Canceled batch owner can mutate options used by another caller (pkg/inference/embedding/batcher/batcher.go:257 (consumed at line 364))
  6. Batched callers receive aliased result slices (pkg/inference/embedding/batcher/batcher.go:392)
  7. Full batches can fan out an unbounded number of provider requests (pkg/inference/embedding/batcher/batcher.go:287)
  8. A panic in asynchronous batch processing can terminate the TiDB process (pkg/inference/embedding/batcher/batcher.go:259)
  9. OpenAI failures do not preserve a retryable error classification (pkg/inference/embedding/openai/openai.go:166)
  10. Caller ownership is encoded in parallel slices during batch fan-out (pkg/inference/embedding/batcher/batcher.go:317 and pkg/inference/embedding/batcher/batcher.go:389)
  11. Cancellation isolation test can pass before the watcher observes cancellation (pkg/inference/embedding/batcher/batcher_test.go:506)

🟡 [Minor] (8)

  1. OpenAI protocol types are exported without consumers or canonical use (pkg/inference/embedding/openai/protocol.go:17; pkg/inference/embedding/openai/openai.go:139)
  2. Constructor names and comments refer to nonexistent type names (pkg/inference/embedding/openai/openai.go:47; pkg/inference/embedding/openai/openai.go:60; pkg/inference/embedding/mock/mock.go:33)
  3. DecodeFloat32ArrayBytes does not signal byte order in its name (pkg/inference/embedding/base/base.go:48; pkg/inference/embedding/base/base_test.go:25)
  4. Unknown-provider errors enumerate providers nondeterministically (pkg/inference/embedding/batcher/batcher.go:141)
  5. A later chunk failure discards successful results for earlier callers (pkg/inference/embedding/batcher/batcher.go:361)
  6. Provider-specific implementation helpers are exposed from the base contract package (pkg/inference/embedding/base/base.go:28 through pkg/inference/embedding/base/base.go:88)
  7. The public mock provider has no consumer and duplicates local test scaffolding (pkg/inference/embedding/mock/BUILD.bazel:3, pkg/inference/embedding/mock/mock.go:26, and pkg/inference/embedding/batcher/batcher_test.go:31)
  8. Bindinfo test sharding is outside the embedding foundation scope (pkg/bindinfo/tests/BUILD.bazel:14)

🧹 [Nit] (2)

  1. Numeric cancellation test names hide the scenarios under test (pkg/inference/embedding/batcher/batcher_test.go:749; pkg/inference/embedding/batcher/batcher_test.go:773)
  2. Narration-only comments repeat the surrounding function names (pkg/inference/embedding/batcher/batcher.go:265; pkg/inference/embedding/batcher/batcher.go:301; pkg/inference/embedding/batcher/batcher.go:332; pkg/inference/embedding/batcher/batcher.go:379)

)

// Embedder is an interface for embedding providers.
type Embedder interface {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [Major] Embedder contract omits the output-to-input ordering invariant

Why
Embedder is the shared provider contract, but its only method comment does not say that it must return exactly one embedding per input in the same logical order. The batcher and the OpenAI adapter both rely on that invariant to associate vectors with the original texts.

Scope
pkg/inference/embedding/base/base.go:42; pkg/inference/embedding/batcher/batcher.go:368; pkg/inference/embedding/openai/openai.go:196

Risk if unchanged
A new provider can satisfy the interface while returning missing, extra, or provider-ordered vectors; callers may then receive an error late in batching or, worse, associate a valid vector with the wrong text.

Evidence
The interface comment only says CreateEmbeddings generates embeddings. processBatch requires len(chunkEmbeddings) == len(texts) and slices the aggregate by caller boundaries, while the OpenAI implementation explicitly restores request order from response indices.

Change request
Please add a precise contract stating that result length must equal len(texts) and result element i must correspond to texts[i], including the empty-input behavior expected from implementations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, this is the semantic invariant used by the current implementations. The OpenAI adapter validates cardinality, rejects duplicate/out-of-range indices, and restores request order; the batcher also rejects a provider result whose length does not match the submitted chunk before fan-out.

We agree that a stronger interface-level description could improve discoverability. For this PR, however, we are preserving the already-consumed base interface and relying on the existing runtime validation rather than broadening the public contract. No behavior change is needed for the current providers or batcher.

}
}

// Register registers an embedder with the given provider name as the prefix.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [Major] Register and MustRegister do not expose distinct registration semantics

Why
The non-Must method still panics for nil embedders, accepts invalid provider names that MustRegister rejects, and silently replaces an existing provider. The method names and comments therefore do not tell callers whether registration validates, overwrites, returns an error, or panics.

Scope
pkg/inference/embedding/batcher/batcher.go:105

Risk if unchanged
Configuration mistakes can either crash the process or silently reroute embedding requests to a replacement provider; a provider containing / can also be accepted even though the provider/model parser can never select it.

Evidence
Register calls panicIfNilEmbedder and assigns directly to b.embedders[provider]. Only MustRegister checks empty names, /, and duplicates before delegating back to Register, and neither comment documents Register's panic or replacement behavior.

Change request
Can we make Register the checked, non-panicking operation that returns an error for invalid, nil, and duplicate registrations, and make MustRegister only the panic-on-error wrapper? If replacement is intentional, expose it as a separately named operation such as Replace and document the side effect.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We agree that a clean-slate API could make Register return an error and implement MustRegister as a panic-on-error wrapper.

In this case, however, changing Register to return an error and changing its replacement behavior would be a source- and behavior-breaking change for the established downstream implementation. Registration is performed during initialization, before concurrent requests, and production initialization uses the stricter MustRegister path. We therefore prefer to preserve the existing registration API in this compatibility-focused PR rather than introduce a second registration contract.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please comment why not changed
and who is downstream?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pushing on this. I rechecked the actual downstream and my previous reply was inaccurate: production initialization uses the corresponding RegisterEmbedder path, not MustRegisterEmbedder.

The downstream is the deployed EMBED_TEXT() implementation from tidbcloud/tidb-cse#1686 / tidbcloud/tidb-cse#2015, specifically pkg/inference/sqlembed.go. Its NewEmbedFn initialization registers a fixed set of valid, unique providers such as openai, jina_ai, and cohere; it does not rely on invalid provider names or silent replacement.

Based on that check, I changed this in commit 6372fc9592: Register now returns an error for invalid names, nil/typed-nil embedders, and duplicate normalized providers, and it never silently replaces an existing provider. MustRegister is now only the panic-on-error wrapper. I did not add Replace because there is no current replacement use case.

This changes only the Go registration helper; the TiDB SQL/config behavior and provider wire protocols remain unchanged.


maxSanitizedErrorTextBytes = 4096
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [Major] SanitizeErrorText overstates a best-effort credential redactor

Why
The exported name reads as a general guarantee that arbitrary provider error text is safe to log or return, while the implementation only recognizes a small set of field names and token shapes plus exact secrets supplied by the caller.

Scope
pkg/inference/embedding/base/base.go:34; pkg/inference/embedding/base/base.go:72; pkg/inference/embedding/openai/openai.go:170

Risk if unchanged
Future providers can call this broad helper and log text containing credentials or private input under the mistaken assumption that it has been fully sanitized.

Evidence
The patterns cover authorization, API-key/token variants, credentials, bearer tokens, and sk- keys, but not other common sensitive fields such as passwords, cookies, client secrets, or arbitrary user content. The OpenAI path treats the returned value as loggable provider text.

Change request
Prefer a name that states the bounded behavior, such as RedactKnownCredentialPatternsAndTruncate, and document that it is best effort and that callers must pass every exact secret. Better yet, keep provider-specific redaction internal and avoid logging untrusted provider messages when that guarantee cannot be made.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The concern is valid, but the current comment intentionally says that the helper redacts “common credential patterns” and exact secrets supplied by the caller; it is not intended to claim arbitrary untrusted text is universally safe.

The OpenAI path also does not log the raw response body. It parses only the provider's error message, passes the exact API key for redaction, truncates the result, and records the HTTP status separately. Renaming or moving this exported helper would change the established API, so we prefer to keep it here and retain the bounded, best-effort semantics.

}
}

// CreateEmbeddings batches requests with the same model/opts combination within the batch window.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [Major] Batch CreateEmbeddings hides its required provider-qualified model syntax

Why
Batch advertises the same CreateEmbeddings interface as a provider embedder, but its model argument has a different routing contract: it must be provider/model. The public method comment mentions batching only and does not state that grammar or the provider-name normalization rules.

Scope
pkg/inference/embedding/batcher/batcher.go:224; pkg/inference/embedding/base/base.go:44; pkg/inference/embedding/openai/openai.go:110

Risk if unchanged
Code written against base.Embedder can substitute the batcher and pass an ordinary model name, then fail at runtime with a format error even though the method name and interface contract appear identical.

Evidence
The OpenAI implementation accepts a bare model such as text-embedding-3-small, whereas Batch.parseModelWithProvider rejects any value without / and interprets the first segment as a registered provider.

Change request
Please make the routing contract explicit at the public boundary. Prefer a named provider-qualified model concept or a router-specific method; at minimum, document the exact provider/model grammar, normalization, and examples on Batch.CreateEmbeddings so the same parameter does not carry two undisclosed meanings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Batch is intentionally a routing implementation of base.Embedder, so it must use the same method signature with one model string. For this implementation, the first path segment is the registered provider selector and the remaining value is passed as the provider-local model.

The downstream SQL integration already uses the established provider/model representation, and invalid bare model names fail with an explicit format error. Introducing separate parameters or a router-specific public method would change the shared interface and existing call sites, so we are keeping the current routing contract in this PR.

Comment thread pkg/inference/embedding/batcher/batcher.go Outdated
Comment thread pkg/inference/embedding/base/base.go
Comment thread pkg/inference/embedding/mock/BUILD.bazel
Comment thread pkg/bindinfo/tests/BUILD.bazel
Comment thread pkg/inference/embedding/batcher/batcher_test.go
Comment thread pkg/inference/embedding/batcher/batcher.go
@ChangRui-Ryan

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. We addressed the concrete isolation, panic-safety, determinism, and test-synchronization issues in f1a9786dca:

  • batch-owned recursive option snapshots;
  • isolated outer result slices;
  • recovery around asynchronous batch processing;
  • deterministic unknown-provider errors;
  • deterministic cancellation-watcher synchronization in tests.

The earlier provider terminology, protocol-source, configurable-error documentation, and malformed-error-response logging comments were addressed in 5716c8b985.

For the remaining API and policy suggestions, we have replied in the individual threads. The implementation is already consumed downstream, so this split keeps the established public interfaces and call semantics stable. Broader topics such as provider concurrency limits, typed retryable errors, and partial-success semantics require explicit owner-level policies and are better handled separately rather than introduced implicitly in this foundation PR.

@ChangRui-Ryan

Copy link
Copy Markdown
Contributor Author

/retest

@ti-chi-bot

ti-chi-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ailinkid, bb7133 for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Comment on lines +171 to +175
value := reflect.ValueOf(embedder)
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
return value.IsNil()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

when embedder == nil is not enough? can you give some example?

@D3Hunter D3Hunter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rest lgtm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note-none Denotes a PR that doesn't merit a release note. sig/planner SIG: Planner size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants