跪拜 Guibai
← All articles
LLM · Next.js · Agent

Vectorizing Long-Term Chat Memory So It Survives a Paraphrase

By 倾颜 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Most chat apps bolt on RAG and call it memory, but the real work is in the guardrails: which fields get indexed, which conversations trigger recall, and how the system degrades when the vector store is down. This implementation makes those boundaries explicit, offering a reference design for anyone adding semantic memory to a LangGraph agent without letting it become a single point of failure.

Summary

Keyword matching fails the moment a user rephrases a stored preference. AI Mind v0.4.6 adds a vector index to every active UserMemory, embedding `text` and `tags` via Doubao's embedding model into pgvector. On each eligible chat turn, the user's input is vectorized and run through an ANN search for the top-8 candidates, then squeezed through a six-layer filter chain that enforces a 0.32 cosine-similarity threshold, polarity conflict detection, and a hard budget of three memories totaling 900 characters.

The write path is deliberately narrow: only `active` memories get indexed, and only the `text` and `tags` fields are vectorized. The recall path is conservative by design, preferring to return nothing over injecting irrelevant context. A 1500ms timeout and two layers of try/catch ensure a failed embedding call or a dead pgvector query degrades to zero memories without blocking the chat stream.

The result is a retrieval system that finds "don't be too abstract" when the stored preference says "explain in plain language first," without a single LLM query-rewrite call.

Takeaways
— Only `active` UserMemory documents are vector-indexed; suppressed or inactive memories pass `false` to `store.put()` and stay out of the semantic candidate pool.
— Vector indexing is scoped to `['text', 'tags']` — other fields like `sourceConversationId` and `reason` are excluded to prevent irrelevant semantic matches.
— Semantic recall triggers only on `ordinary_chat` and `tool_assisted_ordinary_chat` paths; hydration, sidebar loads, and agent chains skip it to avoid wasted embedding API calls.
— User input is truncated to 800 characters (first 400 + last 400) and used as-is for vector search — no LLM query rewriting, keeping latency and cost down.
— PostgresStore's vector mode is used instead of hybrid search because hybrid mode would full-text search the entire JSON document, bypassing the field whitelist.
— Top-8 candidates from ANN search pass through six filter layers: eligibility check, score validity, a 0.32 cosine-similarity threshold, polarity conflict detection, stableKey deduplication, and conflict handling.
— The 0.32 threshold was calibrated from real score distributions of the Doubao embedding model on Chinese UserMemory data, not chosen arbitrarily.
— Final injection is capped at three memories and 900 total characters; each memory is clipped to 300 characters to keep token budgets predictable.
— A 1500ms Promise.race timeout and two nested try/catch blocks guarantee that any embedding or pgvector failure returns an empty array and lets the chat continue normally.
— Failure logs are sanitized: they record event name, provider type, error category, and duration, but never raw query text, memory text, or embedding vectors.
Conclusions

The decision to skip LLM query rewriting is a deliberate trade-off: it saves a round-trip and avoids rewrite-induced semantic drift, betting that the embedding model's own generalization is good enough for the recall task.

Scoping vector indexing to explicit fields rather than the whole JSON document is a security and relevance boundary that many RAG pipelines overlook, and it prevents metadata leakage into semantic search results.

The six-layer filter chain is effectively a cost-control mechanism disguised as quality assurance — each layer removes candidates that would waste context-window tokens without improving the answer.

Polarity conflict detection treats the current user utterance as higher authority than stored memory, which is correct for a chat assistant but would be wrong for a system that must enforce persistent rules.

Hard-coding the embedding model ID and version into persisted document metadata creates a future-proofing hook: when the model changes, old vectors are automatically distrusted rather than silently producing degraded results.

Concepts & terms
pgvector
A PostgreSQL extension that adds a vector data type and index methods (IVFFlat, HNSW) for approximate nearest neighbor (ANN) search, enabling semantic similarity queries directly in the database.
Cosine distance
A similarity metric that measures the cosine of the angle between two vectors. Values range from -1 to 1, with 1 meaning identical direction. It is widely used for text embeddings because it captures semantic relatedness independent of vector magnitude.
ANN (Approximate Nearest Neighbor)
A class of algorithms that trade a small amount of accuracy for much faster vector search compared to exact k-NN. pgvector uses ANN indexes so similarity search over millions of vectors completes in milliseconds.
Embedding model
A neural network that converts text into a fixed-length array of floating-point numbers (a vector). Texts with similar meanings produce vectors that are close together in the vector space, enabling semantic search without keyword overlap.
Hybrid search
A retrieval strategy that combines vector similarity search with traditional full-text (keyword) search, often merging results via reciprocal rank fusion. It can improve recall when exact term matches matter, but requires careful scoping to avoid searching irrelevant fields.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗