fix(openai): preserve custom provider id through memory embedding adapter#81170
fix(openai): preserve custom provider id through memory embedding adapter#81170sholomsbs33 wants to merge 1 commit into
Conversation
…pter When `memorySearch.provider` points at a custom OpenAI-compatible entry (e.g. `bailian-embedding` with its own `baseUrl` and `apiKey`), the runtime adapter lookup eventually picks up `extensions/openai`'s memory adapter via the `openai-completions` API binding. But the adapter then unconditionally overwrites the caller-supplied provider id with `"openai"` in two places — `openAiMemoryEmbeddingProviderAdapter.create` and the internal `resolveOpenAiEmbeddingClient` — so the downstream remote-client lookups (`models.providers["openai"].baseUrl/apiKey/headers`) never see the user's custom provider config. The result is memory_search calling `api.openai.com` with default OpenAI auth instead of the configured Bailian/DashScope endpoint, which surfaces as the `fetch failed` / "embedding/provider error" the reporter saw in openclaw#47884. Preserve `options.provider` when it is set, fall back to `"openai"` only when the caller did not specify one. The adapter id (`openAiMemory… Adapter.id`, runtime.id) and the auto-select wiring stay `"openai"` — only the *provider-config lookup key* and the `cacheKeyData.provider` are now honoured, which is the value the downstream remote bearer client reads from `models.providers[<id>]` for endpoint, auth, and headers. Tests in `memory-embedding-adapter.test.ts` cover the forwarding and the cache-key propagation, plus a fall-through that the default stays `"openai"`. `embedding-provider.test.ts` adds matching cases on the internal client-resolution path so the second call site can't silently revert. Fixes openclaw#47884.
|
Codex review: needs maintainer review before merge. Reviewed May 28, 2026, 12:55 AM ET / 04:55 UTC. Summary PR surface: Source +16, Tests +70. Total +86 across 4 files. Reproducibility: yes. Source inspection shows current main and v2026.5.26 force the OpenAI provider id despite memorySearch.provider carrying a custom OpenAI-compatible id, and the PR body includes before/after adapter proof with captured endpoint and Authorization header. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land this PR after maintainer acceptance of the provider/auth routing change and required checks pass, then let it close #47884. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main and v2026.5.26 force the OpenAI provider id despite memorySearch.provider carrying a custom OpenAI-compatible id, and the PR body includes before/after adapter proof with captured endpoint and Authorization header. Is this the best way to solve the issue? Yes. Preserving options.provider at the adapter and resolver is the narrowest fix for the documented custom-provider contract while keeping the default OpenAI path unchanged. AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against f7c32fc8befd. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +16, Tests +70. Total +86 across 4 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
This pull request has been automatically marked as stale due to inactivity. |
|
ClawSweeper PR egg: ✨ hatched 🥚 common Mossy Signal Puff. Rarity: 🥚 common. Trait: guards the happy path. DetailsShare on X: post this hatch
About:
|
|
Heads up: this PR needs to be updated against current |
Preserve custom OpenAI-compatible memory embedding provider ids from #81170. Fixes #47884. Fixes #49524. Refs #56532. Co-authored-by: adone0 <[email protected]>
|
Thanks @adone0. I folded this fix into the broader memory availability patch that landed on main: Your provider-id preservation change is included there, with regression coverage for both OpenAI embedding boundaries, and the commit preserves your contribution with a Co-authored-by trailer. I am closing this PR as superseded by the landed maintainer commit. |
Preserve custom OpenAI-compatible memory embedding provider ids from openclaw#81170. Fixes openclaw#47884. Fixes openclaw#49524. Refs openclaw#56532. Co-authored-by: adone0 <[email protected]>
Preserve custom OpenAI-compatible memory embedding provider ids from openclaw#81170. Fixes openclaw#47884. Fixes openclaw#49524. Refs openclaw#56532. Co-authored-by: adone0 <[email protected]>
Preserve custom OpenAI-compatible memory embedding provider ids from openclaw#81170. Fixes openclaw#47884. Fixes openclaw#49524. Refs openclaw#56532. Co-authored-by: adone0 <[email protected]>
Summary
memorySearch.providerpoints at a custom OpenAI-compatible provider (e.g.bailian-embeddingwith its ownbaseUrlandapiKey), the runtime adapter lookup atsrc/plugins/memory-embedding-provider-runtime.ts:53correctly walks[customProviderId, "openai-completions"]and lands on this extension'sopenAiMemoryEmbeddingProviderAdaptervia theopenai-completionsAPI binding. But the adapter then unconditionally overwrites the caller-supplied provider id with"openai"in two places —openAiMemoryEmbeddingProviderAdapter.create(extensions/openai/memory-embedding-adapter.ts:21) and the internalresolveOpenAiEmbeddingClient(extensions/openai/embedding-provider.ts:89). The downstream remote-client lookups (models.providers["openai"].baseUrl / apiKey / headers) therefore never see the user's custom provider config; memory_search callsapi.openai.comwith default OpenAI auth, and the reporter seesfetch failed/ "embedding/provider error" against a perfectly-configured DashScope/Bailian endpoint ([Bug]: memory_search tool fails with "fetch failed" despite embedding provider configured #47884).options.providerat both sites and fall back to"openai"only when the caller did not specify one. The adapter id (openAiMemoryEmbeddingProviderAdapter.id,runtime.id,authProviderId) stays"openai"so auto-select and adapter routing are unchanged; only the provider-config lookup key (read by the downstream remote bearer client to resolvemodels.providers[<id>]) and thecacheKeyData.provider(so per-provider cache entries do not collide) now honour the caller's id.memory-embedding-adapter.test.tscover the forwarding intocreateOpenAiEmbeddingProvider, the cache-key propagation, and a fall-through asserting the default stays"openai".embedding-provider.test.tsadds matching cases on the internalresolveRemoteEmbeddingClientcall so the second call site cannot silently revert.Related Issues
Closes #47884.
Type of Change
Real behavior proof
Behavior or issue addressed: Reporter
AllenSupermanxiaod(#47884, 2026-03-16) configured a custom OpenAI-compatible embedding provider entrybailian-embedding(withapi: "openai-completions",baseUrl: https://dashscope.aliyuncs.com/compatible-mode/v1, and a Bailian-issuedapiKey) and saw memory_search return{"error": "fetch failed", "warning": "Memory search is unavailable due to an embedding/provider error.", "disabled": true, "unavailable": true}. ClawSweeper's source-level review confirmed the runtime lookup correctly walks to the OpenAI memory adapter via theopenai-completionsAPI binding, then the adapter overwrites the custom provider id with"openai"so the remote bearer client cannot readmodels.providers["bailian-embedding"].baseUrl/apiKey/headers. Recommended action: "Preserve the requested custom provider id while selecting the correct OpenAI-compatible memory adapter." After this patch, the same reporter config now resolves throughmodels.providers["bailian-embedding"]and reaches the DashScope endpoint with the right auth.Real environment tested: Local Linux checkout of
openclaw/openclawatupstream/main178a50e017, branched intofix/memory-search-preserve-custom-provider-id-47884. Verified by source-level walk against ClawSweeper's reproduction recipe:src/plugins/memory-embedding-provider-runtime.ts:53is unchanged and still lands onopenAiMemoryEmbeddingProviderAdapterforopenai-completions-bound custom providers; the patched adapter atextensions/openai/memory-embedding-adapter.ts:21-26now resolvesoptions.provider ?? "openai"and forwards it both tocreateOpenAiEmbeddingProviderand tocacheKeyData.provider.resolveOpenAiEmbeddingClientatextensions/openai/embedding-provider.ts:89-99does the same, soresolveRemoteEmbeddingClient(packages/memory-host-sdk/src/host/embeddings-remote-client.ts:30) now reads the correctmodels.providers[customId]row for endpoint, headers, and API key.Exact steps or command run after this patch:
I invoked the patched OpenAI memory embedding adapter directly against a local HTTP listener that captures the actual HTTP request the runtime makes. The listener binds to
127.0.0.1on an ephemeral port and is wired as thebaseUrlfor a custom provider entry that mimics the reporter'sbailian-embeddingconfig — so the captured request, auth header, and body are exactly what the OpenClaw runtime would send to a real DashScope endpoint at production time. Both the pre-patch and post-patch runs use the same invocation script; the only change between them is whetherextensions/openai/memory-embedding-adapter.tsandextensions/openai/embedding-provider.tsareupstream/mainor this PR's branch.Output captured 2026-05-12 on Node v22.22.2:
Pre-patch (
upstream/main178a50e017, no PR applied) —embedQuerythrows because the runtime forcesprovider: "openai"despite the caller'sbailian-embeddingrequest, so the request goes toapi.openai.comwith the placeholder OpenAI key from the test config and OpenAI returns a real 401:The local capture server is never hit. This is the exact failure shape the reporter describes ("
fetch failed... memory_search is unavailable"): thebailian-embeddingconfig is on disk, but the adapter overwrites it with the OpenAI default before the remote client reads it.Post-patch (this PR branch
409e780560applied to the sameupstream/main):Three behaviour deltas the runtime observably emits:
runtime.cacheKeyData.provideris now"bailian-embedding"instead of"openai", so per-provider cache entries no longer collide across users who configure multiple OpenAI-compatible custom providers.baseUrl(127.0.0.1:42857/v1) instead ofapi.openai.com. The reporter's DashScope config (https://dashscope.aliyuncs.com/compatible-mode/v1) would now be hit for real.Authorizationheader isBearer sk-bailian-test-abc123, i.e. the custom provider's API key, not the OpenAI default. The reporter's Bailian-issued key would now be used for real.The request body (
model: "text-embedding-v3",input: ["hello"]) is unchanged — the only difference between the two runs is whichmodels.providers[<id>]row the remote client read, which is exactly the bug clawsweeper diagnosed atextensions/openai/memory-embedding-adapter.ts:21andextensions/openai/embedding-provider.ts:89.Evidence after fix:
extensions/openai/memory-embedding-adapter.ts:21:const resolvedProviderId = options.provider ?? "openai";is computed once, then passed tocreateOpenAiEmbeddingProvider({ ...options, provider: resolvedProviderId, fallback: "none" })and propagated intoruntime.cacheKeyData.provider. For the reporter'sbailian-embeddingconfig,resolvedProviderId === "bailian-embedding", the downstreamresolveRemoteEmbeddingClientreadsmodels.providers["bailian-embedding"](correct DashScopebaseUrl+ BailianapiKey), and the embeddings request reacheshttps://dashscope.aliyuncs.com/compatible-mode/v1/embeddingsinstead ofhttps://api.openai.com/v1/embeddings.extensions/openai/embedding-provider.ts:92-97: the internalresolveOpenAiEmbeddingClientnow passesprovider: options.provider ?? "openai"toresolveRemoteEmbeddingClient, which is the second call site the bot called out. With both sites fixed, no path through the OpenAI memory adapter can silently revert to OpenAI-routed lookups.extensions/openai/memory-embedding-adapter.test.ts: three new cases (#47884tag) — (a)preserves the caller's custom provider id when creating the embedding clientassertscreateOpenAiEmbeddingProviderwas called withprovider: "bailian-embedding"; (b)propagates the custom provider id into the embedding runtime cache keyassertsruntime.cacheKeyData.provider === "bailian-embedding"; (c)defaults the lookup id to 'openai' when no provider is suppliedasserts the fall-through default. The existingsends document input_typecase is unchanged and continues to pass (it explicitly setsprovider: "openai", which the new code preserves identically).extensions/openai/embedding-provider.test.ts: two new cases (#47884tag) — (a)forwards a custom provider id through resolveRemoteEmbeddingClientasserts the args at the client-resolver boundary; (b)falls back to 'openai' when no provider id is suppliedcovers the default path through the internal resolver.Observed result after fix: A
memory_searchinvocation against the reporter's exact config now:openAiMemoryEmbeddingProviderAdapter.create({ provider: "bailian-embedding", ... }).resolvedProviderId = "bailian-embedding"(instead of the prior overwrite to"openai").createOpenAiEmbeddingProvider({ provider: "bailian-embedding", ... }).resolveOpenAiEmbeddingClientcallsresolveRemoteEmbeddingClient({ provider: "bailian-embedding", ... }).models.providers["bailian-embedding"]→ correctbaseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1), correct API key, correct headers.fetch failed.When
memorySearch.provideris left unset (or set to literal"openai"), the new code resolvesresolvedProviderId = "openai"and the behaviour is byte-identical to current main — covered by the unchangedsends document input_type in OpenAI batch embedding requestscase and the new fall-through tests.What was not tested: I did not live-call DashScope or any other commercial OpenAI-compatible endpoint — that would require reporter-specific Bailian credentials. The local listener captures the exact HTTP request the runtime would send to such an endpoint (URL path,
Authorizationheader, JSON body), which is enough to prove the provider-id preservation contract end-to-end without needing live credentials.Checklist
src/gateway/protocol/schema/**untouched, sochecks-fast-protocolstays green.authProviderId: "openai"andruntime.id: "openai"left as-is — only the provider-config lookup key changes. The auto-select-priority wiring and the "OpenAI plugin ownsmemoryEmbeddingProviders: ["openai"]" manifest contract are unaffected.AGENTS.md: contributors do not edit it; maintainer adds at landing).main.