feat(ts-sdk): add FastEmbed embedding provider (#5862)

Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
Rod Boev
2026-07-06 12:26:11 -04:00
committed by GitHub
parent 03b41ab00f
commit fec7cdf118
12 changed files with 680 additions and 7 deletions
+63 -2
View File
@@ -7,10 +7,18 @@ You can use FastEmbed to run embedding models locally in Mem0. FastEmbed is an O
### Installation
```bash
FastEmbed is an optional dependency, so install it alongside Mem0.
<CodeGroup>
```bash Python
pip install fastembed
```
```bash TypeScript
npm install fastembed
```
</CodeGroup>
### Usage
<CodeGroup>
@@ -38,13 +46,66 @@ messages = [
]
m.add(messages, user_id="john")
```
```typescript TypeScript
import { Memory } from "mem0ai/oss";
// FastEmbed needs no API key. Leave the embedder config empty to use the
// default model (fast-bge-small-en-v1.5), or set `model` to one of the
// supported models listed below.
const memory = new Memory({
embedder: {
provider: "fastembed",
config: {
model: "fast-bge-small-en-v1.5",
},
},
llm: {
provider: "openai",
config: { apiKey: process.env.OPENAI_API_KEY }, // For fact extraction
},
});
const messages = [
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
{ role: "assistant", content: "How about thriller movies? They can be quite engaging." },
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." },
];
await memory.add(messages, { userId: "john" });
```
</CodeGroup>
<Note>
**The Python and TypeScript SDKs default to different models.** Python defaults to `thenlper/gte-large` (1024 dimensions), while TypeScript defaults to `fast-bge-small-en-v1.5` (384 dimensions). The TypeScript package (`fastembed` on npm) ships a fixed set of ONNX models and does not include `thenlper/gte-large`. Because the two defaults produce vectors of different dimensions, do not point both SDKs at the same vector store collection unless you configure them to use the same model.
</Note>
The TypeScript SDK supports these FastEmbed models. Pass the exact string as `model`:
- `fast-bge-small-en-v1.5` (default)
- `fast-bge-small-en`
- `fast-bge-base-en`
- `fast-bge-base-en-v1.5`
- `fast-bge-small-zh-v1.5`
- `fast-all-MiniLM-L6-v2`
- `fast-multilingual-e5-large`
### Config
Here are the parameters available for configuring FastEmbed embedder:
Here are the parameters available for configuring the FastEmbed embedder:
<Tabs>
<Tab title="Python">
| Parameter | Description | Default Value |
| --- | --- | --- |
| `model` | The name of the FastEmbed model to use | `thenlper/gte-large` |
| `embedding_dims` | Dimensions of the embedding model (auto-derived from the model if not set) | `None` |
</Tab>
<Tab title="TypeScript">
| Parameter | Description | Default Value |
| --- | --- | --- |
| `model` | The FastEmbed model to use (see the supported list above) | `fast-bge-small-en-v1.5` |
The embedding dimension is detected automatically at startup, so you do not need to set it manually.
</Tab>
</Tabs>
+1 -1
View File
@@ -10,7 +10,7 @@ Mem0 offers support for various embedding models, allowing users to choose the o
See the list of supported embedders below.
<Note>
All embedders listed below are supported in the Python implementation. The TypeScript implementation supports: **OpenAI**, **Azure OpenAI**, **Google AI**, **Langchain**, **LM Studio**, **Ollama**, and **Together**.
All embedders listed below are supported in the Python implementation. The TypeScript implementation supports: **OpenAI**, **Azure OpenAI**, **FastEmbed**, **Google AI**, **Langchain**, **LM Studio**, **Ollama**, and **Together**.
</Note>
<CardGroup cols={4}>
+1
View File
@@ -122,6 +122,7 @@
"@types/pg": "8.11.0",
"better-sqlite3": "^12.6.2",
"cloudflare": "^4.2.0",
"fastembed": "^2.1.0",
"groq-sdk": "0.3.0",
"ollama": "^0.5.14",
"pg": "8.11.3",
+340
View File
@@ -80,6 +80,9 @@ importers:
compromise:
specifier: ^14.0.0
version: 14.15.1
fastembed:
specifier: ^2.1.0
version: 2.1.0
groq-sdk:
specifier: 0.3.0
version: 0.3.0
@@ -153,6 +156,27 @@ packages:
'@anthropic-ai/sdk@0.40.1':
resolution: {integrity: sha512-DJMWm8lTEM9Lk/MSFL+V+ugF7jKOn0M2Ujvb5fN8r2nY14aHbGPZ1k6sgjL+tpJ3VuOGJNG+4R83jEpOuYPv8w==}
'@anush008/tokenizers-darwin-universal@0.0.0':
resolution: {integrity: sha512-SACpWEooTjFX89dFKRVUhivMxxcZRtA3nJGVepdLyrwTkQ1TZQ8581B5JoXp0TcTMHfgnDaagifvVoBiFEdNCQ==}
engines: {node: '>= 10'}
os: [darwin]
'@anush008/tokenizers-linux-x64-gnu@0.0.0':
resolution: {integrity: sha512-TLjByOPWUEq51L3EJkS+slyH57HKJ7lAz/aBtEt7TIPq4QsE2owOPGovByOLIq1x5Wgh9b+a4q2JasrEFSDDhg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@anush008/tokenizers-win32-x64-msvc@0.0.0':
resolution: {integrity: sha512-/5kP0G96+Cr6947F0ZetXnmL31YCaN15dbNbh2NHg7TXXRwfqk95+JtPP5Q7v4jbR2xxAmuseBqB4H/V7zKWuw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@anush008/tokenizers@0.0.0':
resolution: {integrity: sha512-IQD9wkVReKAhsEAbDjh/0KrBGTEXelqZLpOBRDaIRvlzZ9sjmUP+gKbpvzyJnei2JHQiE8JAgj7YcNloINbGBw==}
engines: {node: '>= 10'}
'@aws-crypto/crc32@5.2.0':
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
engines: {node: '>=16.0.0'}
@@ -677,6 +701,20 @@ packages:
'@modelcontextprotocol/sdk':
optional: true
'@huggingface/blake3-jit@0.0.2':
resolution: {integrity: sha512-Bq7B5qabyjrJfhBsl85Jd2QBtf+HzRD7h7A9GfN2lzrrsABhOa5evVPgzoCTxR7Ub0QFj7YDK1YkYRWBU25+2w==}
'@huggingface/hub@2.13.2':
resolution: {integrity: sha512-I1Pbn9UcBHCt0M/SbZkKzYr3JVnbJp4YIFv74eVRH2Y22yT9Jpm6DtfM74tcjNLbC7SyFZ433znUw0szdFd4Fg==}
engines: {node: '>=18'}
hasBin: true
'@huggingface/tasks@0.21.21':
resolution: {integrity: sha512-6cHtkeMvgbudI6p4+gPP/+xvcAG2uumF+ntojCSNnHy5xcSzuHXCMJHzh+rlX0TSZ/QdDZ4uWkPLQikbphNyEw==}
'@huggingface/xetchunk-wasm@0.1.0':
resolution: {integrity: sha512-wWpp2qwPgf9kv1KLJjcDUk/OrpDOsFoQ3Qpz0U5LGn20csoymBf8eneOv6wm/GzPBzlWac1OYiR0aa1vT6aM2Q==}
'@iovalkey/commands@0.1.0':
resolution: {integrity: sha512-/B9W4qKSSITDii5nkBCHyPkIkAi+ealUtr1oqBJsLxjSRLka4pxun2VvMNSmcwgAMxgXtQfl0qRv7TE+udPJzg==}
@@ -684,6 +722,10 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
'@istanbuljs/load-nyc-config@1.1.0':
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
engines: {node: '>=8'}
@@ -1434,6 +1476,10 @@ packages:
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
boolean@3.2.0:
resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
bowser@2.14.1:
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
@@ -1535,6 +1581,14 @@ packages:
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
ci-info@3.9.0:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
@@ -1542,6 +1596,10 @@ packages:
cjs-module-lexer@1.4.3:
resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
cli-progress@3.12.0:
resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==}
engines: {node: '>=4'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -1648,10 +1706,18 @@ packages:
resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
engines: {node: '>=18'}
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
define-lazy-prop@3.0.0:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -1668,6 +1734,9 @@ packages:
resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
engines: {node: '>=8'}
detect-node@2.1.0:
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -1736,6 +1805,9 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
es6-error@4.1.1:
resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
esbuild@0.28.1:
resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
engines: {node: '>=18'}
@@ -1749,6 +1821,10 @@ packages:
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
engines: {node: '>=8'}
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
@@ -1790,6 +1866,9 @@ packages:
resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==}
hasBin: true
fastembed@2.1.0:
resolution: {integrity: sha512-oQkpcRHBppJ3+a3w9dU0uytSY0N1cnEa/iVMc8AXEd+tvT529GekOEFhNviJy89R3lvQXF6cdIMTXHj1Gi00xQ==}
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -1858,6 +1937,10 @@ packages:
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -1877,6 +1960,9 @@ packages:
resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
engines: {node: '>=18'}
gearhash-jit@1.0.2:
resolution: {integrity: sha512-UhzJL4KXSdqAKepy/tZwmi2Rcy0YMmtiC4DQS4SURCuIWdh8ECZtnXK2ePRMLigfB61hRKdLK/Vgg2bSw73izQ==}
generic-pool@3.9.0:
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
engines: {node: '>= 4'}
@@ -1921,6 +2007,14 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
engines: {node: '>=10.0'}
globalthis@1.0.4:
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
engines: {node: '>= 0.4'}
google-auth-library@10.7.0:
resolution: {integrity: sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==}
engines: {node: '>=18'}
@@ -1956,6 +2050,9 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
@@ -2267,6 +2364,9 @@ packages:
resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -2388,6 +2488,10 @@ packages:
makeerror@1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
matcher@3.0.0:
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
engines: {node: '>=10'}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -2443,13 +2547,34 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
minipass@7.1.3:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
minizlib@3.1.0:
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
engines: {node: '>= 18'}
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
mlly@1.8.2:
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
@@ -2569,6 +2694,10 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
object-keys@1.1.1:
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
engines: {node: '>= 0.4'}
obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
@@ -2582,6 +2711,13 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
onnxruntime-common@1.21.0:
resolution: {integrity: sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==}
onnxruntime-node@1.21.0:
resolution: {integrity: sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==}
os: [win32, darwin, linux]
open@10.2.0:
resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
engines: {node: '>=18'}
@@ -2807,6 +2943,10 @@ packages:
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@@ -2906,6 +3046,10 @@ packages:
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
hasBin: true
roarr@2.15.4:
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
engines: {node: '>=8.0'}
rollup@4.61.1:
resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -2925,6 +3069,9 @@ packages:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
semver-compare@1.0.0:
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -2934,6 +3081,10 @@ packages:
engines: {node: '>=10'}
hasBin: true
serialize-error@7.0.1:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
engines: {node: '>=10'}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -2999,6 +3150,9 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
stack-utils@2.0.6:
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
engines: {node: '>=10'}
@@ -3087,6 +3241,15 @@ packages:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
tar@7.5.19:
resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==}
engines: {node: '>=18'}
test-exclude@6.0.0:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'}
@@ -3200,6 +3363,10 @@ packages:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
type-fest@0.13.1:
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
engines: {node: '>=10'}
type-fest@0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
@@ -3353,6 +3520,10 @@ packages:
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yallist@5.0.0:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -3391,6 +3562,21 @@ snapshots:
transitivePeerDependencies:
- encoding
'@anush008/tokenizers-darwin-universal@0.0.0':
optional: true
'@anush008/tokenizers-linux-x64-gnu@0.0.0':
optional: true
'@anush008/tokenizers-win32-x64-msvc@0.0.0':
optional: true
'@anush008/tokenizers@0.0.0':
optionalDependencies:
'@anush008/tokenizers-darwin-universal': 0.0.0
'@anush008/tokenizers-linux-x64-gnu': 0.0.0
'@anush008/tokenizers-win32-x64-msvc': 0.0.0
'@aws-crypto/crc32@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
@@ -4162,6 +4348,22 @@ snapshots:
- supports-color
- utf-8-validate
'@huggingface/blake3-jit@0.0.2': {}
'@huggingface/hub@2.13.2':
dependencies:
'@huggingface/tasks': 0.21.21
'@huggingface/xetchunk-wasm': 0.1.0
optionalDependencies:
cli-progress: 3.12.0
'@huggingface/tasks@0.21.21': {}
'@huggingface/xetchunk-wasm@0.1.0':
dependencies:
'@huggingface/blake3-jit': 0.0.2
gearhash-jit: 1.0.2
'@iovalkey/commands@0.1.0': {}
'@isaacs/cliui@8.0.2':
@@ -4173,6 +4375,10 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.3
'@istanbuljs/load-nyc-config@1.1.0':
dependencies:
camelcase: 5.3.1
@@ -5035,6 +5241,8 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
boolean@3.2.0: {}
bowser@2.14.1: {}
brace-expansion@1.1.15:
@@ -5134,10 +5342,19 @@ snapshots:
chownr@1.1.4: {}
chownr@2.0.0: {}
chownr@3.0.0: {}
ci-info@3.9.0: {}
cjs-module-lexer@1.4.3: {}
cli-progress@3.12.0:
dependencies:
string-width: 4.2.3
optional: true
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -5238,8 +5455,20 @@ snapshots:
bundle-name: 4.1.0
default-browser-id: 5.0.1
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
es-errors: 1.3.0
gopd: 1.2.0
define-lazy-prop@3.0.0: {}
define-properties@1.2.1:
dependencies:
define-data-property: 1.1.4
has-property-descriptors: 1.0.2
object-keys: 1.1.1
delayed-stream@1.0.0: {}
denque@2.1.0: {}
@@ -5248,6 +5477,8 @@ snapshots:
detect-newline@3.1.0: {}
detect-node@2.1.0: {}
diff-sequences@29.6.3: {}
diff@4.0.4: {}
@@ -5306,6 +5537,8 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.4
es6-error@4.1.1: {}
esbuild@0.28.1:
optionalDependencies:
'@esbuild/aix-ppc64': 0.28.1
@@ -5339,6 +5572,8 @@ snapshots:
escape-string-regexp@2.0.0: {}
escape-string-regexp@4.0.0: {}
event-target-shim@5.0.1: {}
eventemitter3@4.0.7: {}
@@ -5385,6 +5620,14 @@ snapshots:
dependencies:
strnum: 2.4.1
fastembed@2.1.0:
dependencies:
'@anush008/tokenizers': 0.0.0
'@huggingface/hub': 2.13.2
onnxruntime-node: 1.21.0
progress: 2.0.3
tar: 6.2.1
fastq@1.20.1:
dependencies:
reusify: 1.1.0
@@ -5454,6 +5697,10 @@ snapshots:
fs-constants@1.0.0: {}
fs-minipass@2.1.0:
dependencies:
minipass: 3.3.6
fs.realpath@1.0.0: {}
fsevents@2.3.3:
@@ -5477,6 +5724,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
gearhash-jit@1.0.2: {}
generic-pool@3.9.0: {}
gensync@1.0.0-beta.2: {}
@@ -5529,6 +5778,20 @@ snapshots:
once: 1.4.0
path-is-absolute: 1.0.1
global-agent@3.0.0:
dependencies:
boolean: 3.2.0
es6-error: 4.1.1
matcher: 3.0.0
roarr: 2.15.4
semver: 7.8.4
serialize-error: 7.0.1
globalthis@1.0.4:
dependencies:
define-properties: 1.2.1
gopd: 1.2.0
google-auth-library@10.7.0:
dependencies:
base64-js: 1.5.1
@@ -5575,6 +5838,10 @@ snapshots:
has-flag@4.0.0: {}
has-property-descriptors@1.0.2:
dependencies:
es-define-property: 1.0.1
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
@@ -6070,6 +6337,8 @@ snapshots:
json-parse-even-better-errors@3.0.2: {}
json-stringify-safe@5.0.1: {}
json5@2.2.3: {}
jsonwebtoken@9.0.3:
@@ -6165,6 +6434,10 @@ snapshots:
dependencies:
tmpl: 1.0.5
matcher@3.0.0:
dependencies:
escape-string-regexp: 4.0.0
math-intrinsics@1.1.0: {}
md5@2.3.0:
@@ -6210,10 +6483,27 @@ snapshots:
minimist@1.2.8: {}
minipass@3.3.6:
dependencies:
yallist: 4.0.0
minipass@5.0.0: {}
minipass@7.1.3: {}
minizlib@2.1.2:
dependencies:
minipass: 3.3.6
yallist: 4.0.0
minizlib@3.1.0:
dependencies:
minipass: 7.1.3
mkdirp-classic@0.5.3: {}
mkdirp@1.0.4: {}
mlly@1.8.2:
dependencies:
acorn: 8.16.0
@@ -6344,6 +6634,8 @@ snapshots:
object-assign@4.1.1: {}
object-keys@1.1.1: {}
obuf@1.1.2: {}
ollama@0.5.18:
@@ -6358,6 +6650,14 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
onnxruntime-common@1.21.0: {}
onnxruntime-node@1.21.0:
dependencies:
global-agent: 3.0.0
onnxruntime-common: 1.21.0
tar: 7.5.19
open@10.2.0:
dependencies:
default-browser: 5.5.0
@@ -6574,6 +6874,8 @@ snapshots:
ansi-styles: 5.2.0
react-is: 18.3.1
progress@2.0.3: {}
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@@ -6688,6 +6990,15 @@ snapshots:
dependencies:
glob: 10.5.0
roarr@2.15.4:
dependencies:
boolean: 3.2.0
detect-node: 2.1.0
globalthis: 1.0.4
json-stringify-safe: 5.0.1
semver-compare: 1.0.0
sprintf-js: 1.1.3
rollup@4.61.1:
dependencies:
'@types/estree': 1.0.9
@@ -6729,10 +7040,16 @@ snapshots:
safe-stable-stringify@2.5.0: {}
semver-compare@1.0.0: {}
semver@6.3.1: {}
semver@7.8.4: {}
serialize-error@7.0.1:
dependencies:
type-fest: 0.13.1
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -6790,6 +7107,8 @@ snapshots:
split2@4.2.0: {}
sprintf-js@1.1.3: {}
stack-utils@2.0.6:
dependencies:
escape-string-regexp: 2.0.0
@@ -6882,6 +7201,23 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
tar@6.2.1:
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 5.0.0
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
tar@7.5.19:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
minipass: 7.1.3
minizlib: 3.1.0
yallist: 5.0.0
test-exclude@6.0.0:
dependencies:
'@istanbuljs/schema': 0.1.6
@@ -6995,6 +7331,8 @@ snapshots:
type-detect@4.0.8: {}
type-fest@0.13.1: {}
type-fest@0.21.3: {}
type-fest@3.13.1: {}
@@ -7108,6 +7446,8 @@ snapshots:
yallist@4.0.0: {}
yallist@5.0.0: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
+10 -4
View File
@@ -3,16 +3,22 @@ import { DEFAULT_MEMORY_CONFIG } from "./defaults";
export class ConfigManager {
static mergeConfig(userConfig: Partial<MemoryConfig> = {}): MemoryConfig {
const embedderProvider =
userConfig.embedder?.provider || DEFAULT_MEMORY_CONFIG.embedder.provider;
const embedderProviderKey = embedderProvider.toLowerCase();
const mergedConfig = {
version: userConfig.version || DEFAULT_MEMORY_CONFIG.version,
embedder: {
provider:
userConfig.embedder?.provider ||
DEFAULT_MEMORY_CONFIG.embedder.provider,
provider: embedderProvider,
config: (() => {
const defaultConf = DEFAULT_MEMORY_CONFIG.embedder.config;
const userConf = userConfig.embedder?.config;
let finalModel: string | any = defaultConf.model;
// The default embedder model (OpenAI's text-embedding-3-small) only
// makes sense for API-based providers. FastEmbed has its own fixed
// model set and default, so leave the model unset here and let
// FastEmbedEmbedder fall back to its own default.
let finalModel: string | any =
embedderProviderKey === "fastembed" ? undefined : defaultConf.model;
if (userConf?.model && typeof userConf.model === "object") {
finalModel = userConf.model;
@@ -0,0 +1,75 @@
import { EmbeddingModel, FlagEmbedding } from "fastembed";
import { Embedder } from "./base";
import { EmbeddingConfig } from "../types";
const DEFAULT_MODEL = EmbeddingModel.BGESmallENV15;
type FastEmbedModel = Exclude<EmbeddingModel, EmbeddingModel.CUSTOM>;
// FastEmbed only ships a fixed set of ONNX models. Keep the list handy so we can
// reject unknown model names up front with a clear message instead of letting
// FlagEmbedding.init fail later with an opaque download error.
const SUPPORTED_MODELS = Object.values(EmbeddingModel).filter(
(model) => model !== EmbeddingModel.CUSTOM,
) as FastEmbedModel[];
export class FastEmbedEmbedder implements Embedder {
private modelName: FastEmbedModel;
private embeddingModel?: Promise<FlagEmbedding>;
constructor(config: EmbeddingConfig) {
if (typeof config.model === "string" && config.model.length > 0) {
if (!SUPPORTED_MODELS.includes(config.model as FastEmbedModel)) {
throw new Error(
`Unsupported FastEmbed model "${config.model}". ` +
`Supported models: ${SUPPORTED_MODELS.join(", ")}.`,
);
}
this.modelName = config.model as FastEmbedModel;
} else {
this.modelName = DEFAULT_MODEL;
}
}
private getEmbeddingModel(): Promise<FlagEmbedding> {
if (!this.embeddingModel) {
this.embeddingModel = FlagEmbedding.init({
model: this.modelName,
}).catch((error) => {
this.embeddingModel = undefined;
throw error;
});
}
return this.embeddingModel;
}
private normalizeInput(text: string): string {
return text.replace(/\n/g, " ");
}
async embed(text: string): Promise<number[]> {
const normalizedText = this.normalizeInput(text);
const model = await this.getEmbeddingModel();
for await (const batch of model.embed([normalizedText])) {
const embedding = batch[0];
if (embedding !== undefined) {
return embedding;
}
}
throw new Error("FastEmbed embed() returned no embeddings");
}
async embedBatch(texts: string[]): Promise<number[][]> {
const normalizedTexts = texts.map((text) => this.normalizeInput(text));
const model = await this.getEmbeddingModel();
const embeddings: number[][] = [];
for await (const batch of model.embed(normalizedTexts)) {
embeddings.push(...batch);
}
return embeddings;
}
}
+1
View File
@@ -9,6 +9,7 @@ export * from "./embeddings/together";
export * from "./embeddings/google";
export * from "./embeddings/azure";
export * from "./embeddings/langchain";
export * from "./embeddings/fastembed";
export * from "./llms/base";
export * from "./llms/openai";
export * from "./llms/google";
+3
View File
@@ -36,6 +36,7 @@ import { GoogleEmbedder } from "../embeddings/google";
import { GoogleLLM } from "../llms/google";
import { AzureOpenAILLM } from "../llms/azure";
import { AzureOpenAIEmbedder } from "../embeddings/azure";
import { FastEmbedEmbedder } from "../embeddings/fastembed";
import { LangchainLLM } from "../llms/langchain";
import { LangchainEmbedder } from "../embeddings/langchain";
import { LangchainVectorStore } from "../vector_stores/langchain";
@@ -60,6 +61,8 @@ export class EmbedderFactory {
return new GoogleEmbedder(config);
case "azure_openai":
return new AzureOpenAIEmbedder(config);
case "fastembed":
return new FastEmbedEmbedder(config);
case "langchain":
return new LangchainEmbedder(config);
default:
@@ -298,6 +298,38 @@ describe("ConfigManager", () => {
});
});
describe("mergeConfig - FastEmbed defaults", () => {
const baseLlm = { provider: "openai", config: { apiKey: "k" } };
it("does not inject the OpenAI default embedder model into fastembed", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "fastembed",
config: {},
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.provider).toBe("fastembed");
expect(cfg.embedder.config.model).toBeUndefined();
});
it("treats FastEmbed provider casing the same way as the factory", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "FastEmbed",
config: {},
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.provider).toBe("FastEmbed");
expect(cfg.embedder.config.model).toBeUndefined();
});
});
describe("mergeConfig - LM Studio LLM config", () => {
const baseEmbedder = { provider: "openai", config: { apiKey: "k" } };
@@ -603,6 +635,30 @@ describe("Memory – LM Studio end-to-end flow", () => {
expect(result.results[0].memory).toBe("User likes hiking");
});
it("preserves the FastEmbed provider default model through the Memory config path", async () => {
const mem = new MemoryClass({
embedder: {
provider: "fastembed",
config: {},
},
vectorStore: { provider: "memory", config: { collectionName: "test" } },
llm: {
provider: "openai",
config: { apiKey: "test-key" },
},
disableHistory: true,
});
await mem.getAll({ filters: { user_id: "u1" } });
expect(mockEmbedderFactory.create).toHaveBeenCalledWith(
"fastembed",
expect.objectContaining({
model: undefined,
}),
);
});
it("add flow works with lmstudio LLM for fact extraction", async () => {
mockLlm.generateResponse.mockResolvedValueOnce(
'{"facts":["User loves sushi"]}',
@@ -20,6 +20,11 @@ jest.mock("../src/embeddings/google", () => ({
.fn()
.mockImplementation((config) => ({ type: "google-embedder", config })),
}));
jest.mock("../src/embeddings/fastembed", () => ({
FastEmbedEmbedder: jest
.fn()
.mockImplementation((config) => ({ type: "fastembed-embedder", config })),
}));
jest.mock("../src/embeddings/azure", () => ({
AzureOpenAIEmbedder: jest
.fn()
@@ -193,6 +198,7 @@ describe("EmbedderFactory", () => {
["google"],
["gemini"],
["azure_openai"],
["fastembed"],
["langchain"],
["lmstudio"],
["together"],
@@ -0,0 +1,123 @@
/// <reference types="jest" />
/**
* FastEmbed Embedder — unit tests (mocked FastEmbed client).
* Verifies async generator handling for embed and embedBatch.
*/
const mockInit = jest.fn();
const mockEmbed = jest.fn();
jest.mock("fastembed", () => {
return {
__esModule: true,
EmbeddingModel: {
BGESmallENV15: "fast-bge-small-en-v1.5",
},
FlagEmbedding: {
init: mockInit,
},
};
});
import { FastEmbedEmbedder } from "../src/embeddings/fastembed";
const mockVectorBatch = [
[0.1, 0.2],
[0.3, 0.4],
];
const mockEmbedding = [0.1, 0.2, 0.3];
const createEmbeddingGenerator = async function* (batches: number[][][]) {
for (const batch of batches) {
yield batch;
}
};
describe("FastEmbedEmbedder (unit)", () => {
beforeEach(() => {
mockInit.mockReset();
mockEmbed.mockReset();
mockInit.mockResolvedValue({
embed: mockEmbed,
});
});
it("embed() initializes lazily, normalizes newlines, and returns the first vector", async () => {
mockEmbed.mockReturnValue(
createEmbeddingGenerator([[mockEmbedding], mockVectorBatch]),
);
const embedder = new FastEmbedEmbedder({
model: "fast-bge-small-en-v1.5",
});
expect(mockInit).not.toHaveBeenCalled();
const result = await embedder.embed("Example\ntext");
expect(mockInit).toHaveBeenCalledTimes(1);
expect(mockInit).toHaveBeenCalledWith({
model: "fast-bge-small-en-v1.5",
});
expect(mockEmbed).toHaveBeenCalledTimes(1);
expect(mockEmbed.mock.calls[0][0]).toEqual(["Example text"]);
expect(result).toEqual(mockEmbedding);
});
it("embedBatch() consumes all async embedding batches and reuses the initialized model", async () => {
mockEmbed.mockReturnValueOnce(createEmbeddingGenerator([[mockEmbedding]]));
mockEmbed.mockReturnValueOnce(
createEmbeddingGenerator([[mockEmbedding], mockVectorBatch]),
);
const embedder = new FastEmbedEmbedder({});
await embedder.embed("warmup");
const result = await embedder.embedBatch([
"first text",
"second text",
"third text",
]);
expect(mockEmbed).toHaveBeenCalledWith([
"first text",
"second text",
"third text",
]);
expect(mockInit).toHaveBeenCalledTimes(1);
expect(result).toEqual([mockEmbedding, ...mockVectorBatch]);
});
it("embed() throws when FastEmbed yields no embeddings", async () => {
mockEmbed.mockReturnValue(createEmbeddingGenerator([]));
const embedder = new FastEmbedEmbedder({});
await expect(embedder.embed("missing")).rejects.toThrow(
"FastEmbed embed() returned no embeddings",
);
});
it("embed() retries initialization after a transient init failure", async () => {
mockInit.mockRejectedValueOnce(new Error("transient init failure"));
mockInit.mockResolvedValueOnce({
embed: mockEmbed,
});
mockEmbed.mockReturnValue(createEmbeddingGenerator([[mockEmbedding]]));
const embedder = new FastEmbedEmbedder({});
await expect(embedder.embed("first attempt")).rejects.toThrow(
"transient init failure",
);
const result = await embedder.embed("second attempt");
expect(mockInit).toHaveBeenCalledTimes(2);
expect(result).toEqual(mockEmbedding);
});
it("throws a clear error for an unsupported model instead of failing at init", () => {
expect(
() => new FastEmbedEmbedder({ model: "text-embedding-3-small" }),
).toThrow(/Unsupported FastEmbed model "text-embedding-3-small"/);
expect(mockInit).not.toHaveBeenCalled();
});
});
+1
View File
@@ -23,6 +23,7 @@ const external = [
"cloudflare",
"@cloudflare/workers-types",
"@langchain/core",
"fastembed",
"compromise",
"natural",
];