Skip to content

feat(vector): auto-index observations into vector store | 向量索引自动集成#228

Open
mechanic-Q wants to merge 1 commit intorohitg00:mainfrom
mechanic-Q:feature/vector-auto-index
Open

feat(vector): auto-index observations into vector store | 向量索引自动集成#228
mechanic-Q wants to merge 1 commit intorohitg00:mainfrom
mechanic-Q:feature/vector-auto-index

Conversation

@mechanic-Q
Copy link
Copy Markdown

@mechanic-Q mechanic-Q commented May 2, 2026

Summary | 概述

Automatically add compressed observations to the VectorIndex during the observe lifecycle, enabling hybrid BM25+Vector search without manual index building.

在观察生命周期中自动将压缩记忆添加到向量索引,实现 BM25+向量混合搜索。

Motivation | 动机

The VectorIndex and EmbeddingProvider infrastructure exists but observations were only added to the BM25 index. The vector index remained empty unless manually populated. This meant the hybrid search (BM25 + Vector RRF fusion) couldn't leverage semantic similarity — only BM25 keyword matching was active. For non-English content (Chinese, multilingual), BM25 alone is insufficient because the tokenizer can't handle CJK text well. Vector search bridges this gap.

向量索引基础设施已存在,但观察结果仅被添加到 BM25 索引,向量索引保持为空。混合搜索无法利用语义相似度,仅靠 BM25 关键词匹配。对于中文等非英文内容,纯 BM25 效果差,向量搜索可以弥补这一差距。

Changes | 改动

  • src/functions/observe.ts: After synthetic compression and BM25 indexing, auto-embed the narrative and add to vector index
  • src/index.ts: Pass vectorIndex and embeddingProvider to registerObserveFunction
  • Auto-indexing runs within try-catch — embedding failures are logged but don't block observation capture
  • Only activates when an embedding provider is configured (opt-in by setting AGENTMEMORY_LOCAL_EMBEDDING_MODEL or cloud API keys)

Combined with PR #223 (configurable embedding)

# Enable multilingual hybrid search (BM25 + Vector)
AGENTMEMORY_LOCAL_EMBEDDING_MODEL=Xenova/bge-m3

This combination gives agentmemory full Chinese/multilingual semantic search capability — BM25 handles exact terms, vector handles meaning.

Backwards Compatibility | 向后兼容

New params are optional. When vectorIndex or embeddingProvider is null/undefined (default), the auto-indexing is skipped. No behavior change for existing deployments.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added optional vector indexing and embedding support for observations, enabling automatic metadata indexing when configured
    • Enhanced with external control over vector auto-indexing functionality
    • Improved resilience through graceful error handling that prevents vector operation failures from disrupting observation workflows

When an EmbeddingProvider is configured, automatically add compressed
observations to the VectorIndex during the observe lifecycle (synthetic
compression path). This enables hybrid BM25+Vector search without
requiring manual index building.

The auto-indexing runs after BM25 indexing within a try-catch so
embedding failures don't block observation capture. Combined with
configurable embedding model (AGENTMEMORY_LOCAL_EMBEDDING_MODEL),
users can enable multilingual vector search by setting the model
to bge-m3 or multilingual-e5.

Passes vectorIndex and embeddingProvider as optional params to
registerObserveFunction — fully backwards compatible.

当配置了嵌入模型时,自动将压缩后的观察结果添加到向量索引,
实现 BM25+向量混合搜索。嵌入失败不影响观察捕获。

配合可配置嵌入模型使用,设置 AGENTMEMORY_LOCAL_EMBEDDING_MODEL=bge-m3
即可启用中文等多语言语义搜索。
@vercel
Copy link
Copy Markdown

vercel Bot commented May 2, 2026

@mechanic-Q is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

📝 Walkthrough

Walkthrough

Updated registerObserveFunction to accept optional vectorIndex and embeddingProvider parameters. During non-LLM compression, the function now attempts to embed and index a combined "title + narrative" string, with embedding failures logged but not blocking the observation flow.

Changes

Vector Indexing Integration

Layer / File(s) Summary
Type Imports & Signature
src/functions/observe.ts (lines 2, 11, 39–43)
Added imports for EmbeddingProvider and VectorIndex. Updated registerObserveFunction signature to accept optional vectorIndex and embeddingProvider parameters.
Embedding & Indexing Logic
src/functions/observe.ts (lines 244–255)
After synthetic compression creation, conditionally embeds a combined "title + narrative" string and adds the vector to the index when both providers are available. Embedding/indexing errors are caught and logged without aborting the observation flow.
Initialization Wiring
src/index.ts (line 184)
Updated registerObserveFunction call to pass vectorIndex and embeddingProvider arguments during function registration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops through vectors bright, 🐰
Embedding titles in the night,
If indexing fails, we warn but stay,
Observations flow the rabbit's way! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(vector): auto-index observations into vector store' clearly and specifically describes the main change: adding automatic vector indexing of observations. It is concise, directly related to the primary feature being added, and follows conventional commit format.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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
Review rate limit: 4/8 reviews remaining, refill in 29 minutes and 56 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/functions/observe.ts`:
- Around line 244-255: The current code awaits embeddingProvider.embed(...)
inside the observe hook which blocks the response and stream triggers; change
this to a fire-and-forget pattern by removing the await and chaining a promise
handler: call embeddingProvider.embed(narrative).then(vec =>
vectorIndex.add(obsId, payload.sessionId, vec)).catch(err => logger.warn("Vector
auto-index failed for observation", { obsId, error: err instanceof Error ?
err.message : String(err) })); keep the surrounding conditional (vectorIndex &&
embeddingProvider) and do not reintroduce await so embedding work runs
asynchronously and does not block stream::set or the hook response.
🪄 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

Run ID: aad77881-b4cf-4f03-8c28-df9a87ed4403

📥 Commits

Reviewing files that changed from the base of the PR and between 94fc119 and e6a34d0.

📒 Files selected for processing (2)
  • src/functions/observe.ts
  • src/index.ts

Comment thread src/functions/observe.ts
Comment on lines +244 to +255
if (vectorIndex && embeddingProvider) {
try {
const narrative = (synthetic.title || "") + " " + (synthetic.narrative || "");
const vec = await embeddingProvider.embed(narrative);
vectorIndex.add(obsId, payload.sessionId, vec);
} catch (err) {
logger.warn("Vector auto-index failed for observation", {
obsId,
error: err instanceof Error ? err.message : String(err),
});
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

await embeddingProvider.embed() is on the critical path — blocks the hook response and all downstream stream triggers.

The observe hook returns only after the embedding completes (line 247). ML inference (e.g., Xenova/bge-m3) adds 100 ms–2 s to every tool-use observation for anyone who opts into an embedding provider. This also contradicts the PR's stated design ("failures are logged and do not prevent observation capture"), because successful embeddings still block the hook response and the stream::set triggers at lines 256–277.

The existing pattern for async ML work in this same function is fire-and-forget via sdk.triggerVoid (see the vision-embed dispatch at lines 146–152). The simplest fix consistent with that pattern is dropping the await and using a chained .catch() so embedding errors are still swallowed:

⚡ Proposed fix — fire-and-forget embedding (non-blocking)
-          if (vectorIndex && embeddingProvider) {
-            try {
-              const narrative = (synthetic.title || "") + " " + (synthetic.narrative || "");
-              const vec = await embeddingProvider.embed(narrative);
-              vectorIndex.add(obsId, payload.sessionId, vec);
-            } catch (err) {
-              logger.warn("Vector auto-index failed for observation", {
-                obsId,
-                error: err instanceof Error ? err.message : String(err),
-              });
-            }
-          }
+          if (vectorIndex && embeddingProvider) {
+            const narrative = (synthetic.title || "") + " " + (synthetic.narrative || "");
+            embeddingProvider.embed(narrative)
+              .then((vec) => vectorIndex.add(obsId, payload.sessionId, vec))
+              .catch((err) =>
+                logger.warn("Vector auto-index failed for observation", {
+                  obsId,
+                  error: err instanceof Error ? err.message : String(err),
+                }),
+              );
+          }
📝 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
if (vectorIndex && embeddingProvider) {
try {
const narrative = (synthetic.title || "") + " " + (synthetic.narrative || "");
const vec = await embeddingProvider.embed(narrative);
vectorIndex.add(obsId, payload.sessionId, vec);
} catch (err) {
logger.warn("Vector auto-index failed for observation", {
obsId,
error: err instanceof Error ? err.message : String(err),
});
}
}
if (vectorIndex && embeddingProvider) {
const narrative = (synthetic.title || "") + " " + (synthetic.narrative || "");
embeddingProvider.embed(narrative)
.then((vec) => vectorIndex.add(obsId, payload.sessionId, vec))
.catch((err) =>
logger.warn("Vector auto-index failed for observation", {
obsId,
error: err instanceof Error ? err.message : String(err),
}),
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/functions/observe.ts` around lines 244 - 255, The current code awaits
embeddingProvider.embed(...) inside the observe hook which blocks the response
and stream triggers; change this to a fire-and-forget pattern by removing the
await and chaining a promise handler: call
embeddingProvider.embed(narrative).then(vec => vectorIndex.add(obsId,
payload.sessionId, vec)).catch(err => logger.warn("Vector auto-index failed for
observation", { obsId, error: err instanceof Error ? err.message : String(err)
})); keep the surrounding conditional (vectorIndex && embeddingProvider) and do
not reintroduce await so embedding work runs asynchronously and does not block
stream::set or the hook response.

@Qodo-Free-For-OSS
Copy link
Copy Markdown

Hi, When AGENTMEMORY_AUTO_COMPRESS=true, mem::observe triggers mem::compress and does not run the new vectorIndex.add() code path, so the vector index stays empty even if an EmbeddingProvider is configured.

Severity: action required | Category: correctness

How to fix: Index vectors in mem::compress

Agent prompt to fix - you can give this to your LLM of choice:

Issue description

With AGENTMEMORY_AUTO_COMPRESS=true, mem::observe delegates compression to mem::compress, but vectors are only added during the synthetic compression path. This leaves the VectorIndex empty in the LLM compression mode.

Issue Context

  • Vector indexing currently happens only for buildSyntheticCompression(raw) output.
  • mem::compress persists the compressed observation and updates BM25 (getSearchIndex().add(compressed)), but does not embed/store vectors.

Fix Focus Areas

  • src/functions/observe.ts[222-256]
  • src/functions/compress.ts[170-178]

What to change

  • Add optional vectorIndex + embeddingProvider plumb-through to registerCompressFunction and embed compressed.title + ' ' + compressed.narrative (or similar) inside mem::compress after KV set/BM25 add.
  • Ensure failures are best-effort (log warn, do not fail compression).
  • Keep behavior consistent with synthetic path (same obsId/sessionId used).

Spotted by Qodo code review - free for open-source projects.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants