Files
ragflow/internal/utility/embedding_lru.go
Jin Hai 70e9743ef1 RAGFlow go API server (#13240)
# RAGFlow Go Implementation Plan 🚀

This repository tracks the progress of porting RAGFlow to Go. We'll
implement core features and provide performance comparisons between
Python and Go versions.

## Implementation Checklist

- [x] User Management APIs
- [x] Dataset Management Operations
- [x] Retrieval Test
- [x] Chat Management Operations
- [x] Infinity Go SDK

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
Co-authored-by: Yingfeng Zhang <yingfeng.zhang@gmail.com>
2026-03-04 19:17:16 +08:00

142 lines
3.9 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package utility
import (
"container/list"
"sync"
)
// EmbeddingLRU is a thread-safe LRU cache for embeddings.
// The key is a combination of question and embedding ID.
type EmbeddingLRU struct {
capacity int
cache map[string]*list.Element
list *list.List
mu sync.RWMutex
}
// entry holds the key and value in the LRU cache.
type entry struct {
key string
value []float64
}
// NewEmbeddingLRU creates a new EmbeddingLRU with the given capacity.
func NewEmbeddingLRU(capacity int) *EmbeddingLRU {
return &EmbeddingLRU{
capacity: capacity,
cache: make(map[string]*list.Element),
list: list.New(),
}
}
// buildKey creates a composite key from question and embedding ID.
func buildKey(question, embeddingID string) string {
// Use a delimiter that is unlikely to appear in the strings.
// If needed, a more robust key generation can be implemented.
return question + "::" + embeddingID
}
// Get retrieves the embedding for the given question and embedding ID.
// Returns the embedding and true if found, otherwise nil and false.
func (lru *EmbeddingLRU) Get(question, embeddingID string) ([]float64, bool) {
key := buildKey(question, embeddingID)
lru.mu.RLock()
defer lru.mu.RUnlock()
if elem, ok := lru.cache[key]; ok {
// Move to front (most recently used)
lru.list.MoveToFront(elem)
ent := elem.Value.(*entry)
// Return a copy to prevent external modification of cached slice
embedding := make([]float64, len(ent.value))
copy(embedding, ent.value)
return embedding, true
}
return nil, false
}
// Put stores an embedding for the given question and embedding ID.
// If the key already exists, its value is updated and moved to front.
// If the cache is at capacity, the least recently used item is evicted.
func (lru *EmbeddingLRU) Put(question, embeddingID string, embedding []float64) {
key := buildKey(question, embeddingID)
lru.mu.Lock()
defer lru.mu.Unlock()
// If key exists, update value and move to front
if elem, ok := lru.cache[key]; ok {
lru.list.MoveToFront(elem)
ent := elem.Value.(*entry)
// Replace the embedding slice
ent.value = make([]float64, len(embedding))
copy(ent.value, embedding)
return
}
// Add new entry
ent := &entry{key: key, value: make([]float64, len(embedding))}
copy(ent.value, embedding)
elem := lru.list.PushFront(ent)
lru.cache[key] = elem
// Evict if capacity exceeded
if lru.list.Len() > lru.capacity {
lru.evictOldest()
}
}
// evictOldest removes the least recently used item from the cache.
// Must be called with lock held.
func (lru *EmbeddingLRU) evictOldest() {
elem := lru.list.Back()
if elem != nil {
lru.list.Remove(elem)
ent := elem.Value.(*entry)
delete(lru.cache, ent.key)
}
}
// Remove removes the embedding for the given question and embedding ID.
func (lru *EmbeddingLRU) Remove(question, embeddingID string) {
key := buildKey(question, embeddingID)
lru.mu.Lock()
defer lru.mu.Unlock()
if elem, ok := lru.cache[key]; ok {
lru.list.Remove(elem)
delete(lru.cache, key)
}
}
// Clear removes all items from the cache.
func (lru *EmbeddingLRU) Clear() {
lru.mu.Lock()
defer lru.mu.Unlock()
lru.cache = make(map[string]*list.Element)
lru.list.Init()
}
// Len returns the number of items in the cache.
func (lru *EmbeddingLRU) Len() int {
lru.mu.RLock()
defer lru.mu.RUnlock()
return lru.list.Len()
}