@peleke.s/langchain-qortex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/dist/index.cjs +324 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +234 -0
- package/dist/index.d.ts +234 -0
- package/dist/index.js +320 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peleke Sengstacke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @peleke.s/langchain-qortex
|
|
2
|
+
|
|
3
|
+
LangChain.js VectorStore backed by [qortex](https://github.com/Peleke/qortex) knowledge graph. Graph-enhanced retrieval via MCP.
|
|
4
|
+
|
|
5
|
+
Drop-in replacement for MemoryVectorStore, Chroma, Pinecone, or any LangChain VectorStore. Same API. Same chains. Same retriever. Plus graph structure, rules, and feedback-driven learning.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @peleke.s/langchain-qortex @langchain/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { QortexVectorStore } from "@peleke.s/langchain-qortex";
|
|
17
|
+
import { OpenAIEmbeddings } from "@langchain/openai";
|
|
18
|
+
|
|
19
|
+
// Create store with any LangChain embeddings
|
|
20
|
+
const store = new QortexVectorStore(new OpenAIEmbeddings(), {
|
|
21
|
+
indexName: "my-docs",
|
|
22
|
+
domain: "engineering",
|
|
23
|
+
});
|
|
24
|
+
await store.connect();
|
|
25
|
+
|
|
26
|
+
// Add documents (standard LangChain)
|
|
27
|
+
await store.addDocuments([
|
|
28
|
+
{ pageContent: "OAuth2 authorization framework", metadata: { source: "rfc6749" } },
|
|
29
|
+
{ pageContent: "JWT token validation", metadata: { source: "rfc7519" } },
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
// Search (graph-enhanced: embedding + PPR + rules)
|
|
33
|
+
const docs = await store.similaritySearch("authentication", 5);
|
|
34
|
+
// docs[0].metadata.node_id -> graph node ID
|
|
35
|
+
// docs[0].metadata.rules -> linked rules from the knowledge graph
|
|
36
|
+
|
|
37
|
+
// Use as retriever in any LangChain chain
|
|
38
|
+
const retriever = store.asRetriever({ k: 10 });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Graph Extras
|
|
42
|
+
|
|
43
|
+
Beyond standard VectorStore operations, QortexVectorStore exposes qortex's graph capabilities:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Explore a concept's graph neighborhood
|
|
47
|
+
const neighborhood = await store.explore(docs[0].metadata.node_id);
|
|
48
|
+
// neighborhood.edges -> typed relationships (REQUIRES, EXTENDS, etc.)
|
|
49
|
+
// neighborhood.neighbors -> connected concepts
|
|
50
|
+
// neighborhood.rules -> linked rules
|
|
51
|
+
|
|
52
|
+
// Get projected rules
|
|
53
|
+
const rules = await store.getRules({ domains: ["engineering"] });
|
|
54
|
+
|
|
55
|
+
// Close the feedback loop (improves future retrieval)
|
|
56
|
+
await store.feedback({
|
|
57
|
+
[docs[0].id]: "accepted",
|
|
58
|
+
[docs[1].id]: "rejected",
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### `QortexVectorStore`
|
|
65
|
+
|
|
66
|
+
Extends `VectorStore` from `@langchain/core`.
|
|
67
|
+
|
|
68
|
+
| Method | Description |
|
|
69
|
+
|--------|-------------|
|
|
70
|
+
| `addDocuments(docs, options?)` | Embed and store documents |
|
|
71
|
+
| `addVectors(vectors, docs, options?)` | Store pre-computed vectors |
|
|
72
|
+
| `similaritySearch(query, k, filter?)` | Graph-enhanced text search (uses qortex_query) |
|
|
73
|
+
| `similaritySearchWithScore(query, k, filter?)` | Same, with scores |
|
|
74
|
+
| `similaritySearchVectorWithScore(vector, k, filter?)` | Raw vector search (uses qortex_vector_query) |
|
|
75
|
+
| `asRetriever(options?)` | Create a LangChain retriever |
|
|
76
|
+
| `explore(nodeId, depth?)` | Explore graph neighborhood |
|
|
77
|
+
| `getRules(options?)` | Get projected rules |
|
|
78
|
+
| `feedback(outcomes)` | Report feedback for learning |
|
|
79
|
+
| `connect()` / `disconnect()` | MCP lifecycle |
|
|
80
|
+
|
|
81
|
+
### `QortexEmbeddings`
|
|
82
|
+
|
|
83
|
+
Wraps a qortex-style embedding model (`.embed(texts)`) in LangChain's `Embeddings` interface.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { QortexEmbeddings } from "@peleke.s/langchain-qortex";
|
|
87
|
+
|
|
88
|
+
const embeddings = new QortexEmbeddings({ model: myQortexModel });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Configuration
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
interface QortexVectorStoreConfig {
|
|
95
|
+
serverCommand?: string; // Default: "uvx"
|
|
96
|
+
serverArgs?: string[]; // Default: ["qortex", "mcp-serve"]
|
|
97
|
+
serverEnv?: Record<string, string>;
|
|
98
|
+
mcpClient?: Client; // Pre-configured MCP client
|
|
99
|
+
indexName?: string; // Default: "default"
|
|
100
|
+
domain?: string; // Default: "default"
|
|
101
|
+
feedbackSource?: string; // Default: "langchain"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Architecture
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
LangChain App
|
|
109
|
+
|
|
|
110
|
+
v
|
|
111
|
+
QortexVectorStore (extends VectorStore)
|
|
112
|
+
|
|
|
113
|
+
v
|
|
114
|
+
QortexMcpClient (MCP SDK, stdio transport)
|
|
115
|
+
|
|
|
116
|
+
v
|
|
117
|
+
qortex MCP Server (Python, spawned via uvx)
|
|
118
|
+
|
|
|
119
|
+
v
|
|
120
|
+
Knowledge Graph + Vector Index
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Text-level search (`similaritySearch`) uses qortex's full pipeline: embedding + graph PPR + rules. Vector-level search (`similaritySearchVectorWithScore`) provides standard LangChain compatibility.
|
|
124
|
+
|
|
125
|
+
## Comparison with Python Version
|
|
126
|
+
|
|
127
|
+
| Feature | `langchain-qortex` (Python) | `@peleke.s/langchain-qortex` (TypeScript) |
|
|
128
|
+
|---------|----------------------------|----------------------------------------|
|
|
129
|
+
| Transport | Direct (LocalQortexClient) | MCP (stdio subprocess) |
|
|
130
|
+
| VectorStore | `langchain_core.vectorstores` | `@langchain/core/vectorstores` |
|
|
131
|
+
| Graph extras | `explore()`, `rules()`, `feedback()` | `explore()`, `getRules()`, `feedback()` |
|
|
132
|
+
| Embeddings | `QortexEmbeddings` | `QortexEmbeddings` |
|
|
133
|
+
| Retriever | `as_retriever()` | `asRetriever()` |
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm install
|
|
139
|
+
npm run build
|
|
140
|
+
npm test # Unit tests (mock MCP)
|
|
141
|
+
npm run test:e2e # E2E (requires uvx + qortex)
|
|
142
|
+
npm run test:dogfood # Dogfood (full import path test)
|
|
143
|
+
npm run lint # TypeScript type check
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var vectorstores = require('@langchain/core/vectorstores');
|
|
4
|
+
var documents = require('@langchain/core/documents');
|
|
5
|
+
var index_js = require('@modelcontextprotocol/sdk/client/index.js');
|
|
6
|
+
var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
7
|
+
var embeddings = require('@langchain/core/embeddings');
|
|
8
|
+
|
|
9
|
+
// src/vectorstore.ts
|
|
10
|
+
var QortexMcpClient = class {
|
|
11
|
+
client = null;
|
|
12
|
+
transport = null;
|
|
13
|
+
config;
|
|
14
|
+
_connected = false;
|
|
15
|
+
constructor(config = {}) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
if (config.mcpClient) {
|
|
18
|
+
this.client = config.mcpClient;
|
|
19
|
+
this._connected = true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
get connected() {
|
|
23
|
+
return this._connected;
|
|
24
|
+
}
|
|
25
|
+
async connect() {
|
|
26
|
+
if (this._connected) return;
|
|
27
|
+
const command = this.config.serverCommand ?? "uvx";
|
|
28
|
+
const args = this.config.serverArgs ?? ["qortex", "mcp-serve"];
|
|
29
|
+
const env = {};
|
|
30
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
31
|
+
if (v !== void 0) env[k] = v;
|
|
32
|
+
}
|
|
33
|
+
if (this.config.serverEnv) {
|
|
34
|
+
Object.assign(env, this.config.serverEnv);
|
|
35
|
+
}
|
|
36
|
+
this.transport = new stdio_js.StdioClientTransport({
|
|
37
|
+
command,
|
|
38
|
+
args,
|
|
39
|
+
env
|
|
40
|
+
});
|
|
41
|
+
this.client = new index_js.Client(
|
|
42
|
+
{ name: "langchain-qortex", version: "0.1.0" },
|
|
43
|
+
{ capabilities: {} }
|
|
44
|
+
);
|
|
45
|
+
await this.client.connect(this.transport);
|
|
46
|
+
this._connected = true;
|
|
47
|
+
}
|
|
48
|
+
async disconnect() {
|
|
49
|
+
if (this.transport) {
|
|
50
|
+
await this.transport.close();
|
|
51
|
+
this.transport = null;
|
|
52
|
+
}
|
|
53
|
+
this.client = null;
|
|
54
|
+
this._connected = false;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Call a qortex MCP tool and return the parsed result.
|
|
58
|
+
*
|
|
59
|
+
* @param name - Tool name (e.g. "qortex_vector_query")
|
|
60
|
+
* @param args - Tool arguments as a plain object
|
|
61
|
+
* @returns Parsed JSON result from the tool
|
|
62
|
+
*/
|
|
63
|
+
async callTool(name, args) {
|
|
64
|
+
if (!this._connected) {
|
|
65
|
+
await this.connect();
|
|
66
|
+
}
|
|
67
|
+
const result = await this.client.callTool({
|
|
68
|
+
name,
|
|
69
|
+
arguments: args
|
|
70
|
+
});
|
|
71
|
+
const content = result.content;
|
|
72
|
+
if (!content || content.length === 0) {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
const textBlock = content.find((c) => c.type === "text");
|
|
76
|
+
if (!textBlock?.text) {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(textBlock.text);
|
|
81
|
+
} catch {
|
|
82
|
+
if (result.isError) {
|
|
83
|
+
throw new Error(`MCP tool error: ${textBlock.text}`);
|
|
84
|
+
}
|
|
85
|
+
return { raw: textBlock.text };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/vectorstore.ts
|
|
91
|
+
var QortexVectorStore = class _QortexVectorStore extends vectorstores.VectorStore {
|
|
92
|
+
mcp;
|
|
93
|
+
indexName;
|
|
94
|
+
domain;
|
|
95
|
+
feedbackSource;
|
|
96
|
+
_lastQueryId = null;
|
|
97
|
+
constructor(embeddings, config = {}) {
|
|
98
|
+
super(embeddings, config);
|
|
99
|
+
this.mcp = new QortexMcpClient(config);
|
|
100
|
+
this.indexName = config.indexName ?? "default";
|
|
101
|
+
this.domain = config.domain ?? "default";
|
|
102
|
+
this.feedbackSource = config.feedbackSource ?? "langchain";
|
|
103
|
+
}
|
|
104
|
+
_vectorstoreType() {
|
|
105
|
+
return "qortex";
|
|
106
|
+
}
|
|
107
|
+
/** Ensure the MCP connection is established. */
|
|
108
|
+
async connect() {
|
|
109
|
+
await this.mcp.connect();
|
|
110
|
+
}
|
|
111
|
+
/** Disconnect from the MCP server. */
|
|
112
|
+
async disconnect() {
|
|
113
|
+
await this.mcp.disconnect();
|
|
114
|
+
}
|
|
115
|
+
/** The query_id from the most recent text-level search. */
|
|
116
|
+
get lastQueryId() {
|
|
117
|
+
return this._lastQueryId;
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Index management (MCP passthrough)
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
/** Create a vector index. Must be called before addVectors/addDocuments. */
|
|
123
|
+
async createIndex(params) {
|
|
124
|
+
const result = await this.mcp.callTool("qortex_vector_create_index", {
|
|
125
|
+
index_name: params.indexName ?? this.indexName,
|
|
126
|
+
dimension: params.dimension,
|
|
127
|
+
metric: params.metric ?? "cosine"
|
|
128
|
+
});
|
|
129
|
+
if (result.error) {
|
|
130
|
+
throw new Error(result.error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Delete a vector index. */
|
|
134
|
+
async deleteIndex(params) {
|
|
135
|
+
const result = await this.mcp.callTool("qortex_vector_delete_index", {
|
|
136
|
+
index_name: params?.indexName ?? this.indexName
|
|
137
|
+
});
|
|
138
|
+
if (result.error) {
|
|
139
|
+
throw new Error(result.error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** List all vector indexes. */
|
|
143
|
+
async listIndexes() {
|
|
144
|
+
const result = await this.mcp.callTool(
|
|
145
|
+
"qortex_vector_list_indexes",
|
|
146
|
+
{}
|
|
147
|
+
);
|
|
148
|
+
return result.indexes;
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Abstract method implementations (required by VectorStore)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
async addVectors(vectors, documents, options) {
|
|
154
|
+
const metadata = documents.map((doc) => ({
|
|
155
|
+
text: doc.pageContent,
|
|
156
|
+
...doc.metadata
|
|
157
|
+
}));
|
|
158
|
+
const ids = options?.ids ?? documents.map((doc) => doc.id).filter(Boolean);
|
|
159
|
+
const result = await this.mcp.callTool("qortex_vector_upsert", {
|
|
160
|
+
index_name: this.indexName,
|
|
161
|
+
vectors,
|
|
162
|
+
metadata,
|
|
163
|
+
ids: ids.length > 0 ? ids : void 0
|
|
164
|
+
});
|
|
165
|
+
if (result.error) {
|
|
166
|
+
throw new Error(result.error);
|
|
167
|
+
}
|
|
168
|
+
return result.ids;
|
|
169
|
+
}
|
|
170
|
+
async addDocuments(documents, options) {
|
|
171
|
+
const texts = documents.map((doc) => doc.pageContent);
|
|
172
|
+
const vectors = await this.embeddings.embedDocuments(texts);
|
|
173
|
+
return this.addVectors(vectors, documents, options);
|
|
174
|
+
}
|
|
175
|
+
async similaritySearchVectorWithScore(query, k, filter) {
|
|
176
|
+
const result = await this.mcp.callTool("qortex_vector_query", {
|
|
177
|
+
index_name: this.indexName,
|
|
178
|
+
query_vector: query,
|
|
179
|
+
top_k: k,
|
|
180
|
+
filter: filter ?? void 0,
|
|
181
|
+
include_vector: false
|
|
182
|
+
});
|
|
183
|
+
if (result.error) {
|
|
184
|
+
throw new Error(result.error);
|
|
185
|
+
}
|
|
186
|
+
return (result.results ?? []).map((item) => {
|
|
187
|
+
const { text, ...meta } = item.metadata ?? {};
|
|
188
|
+
const doc = new documents.Document({
|
|
189
|
+
pageContent: text ?? "",
|
|
190
|
+
metadata: { ...meta, score: item.score },
|
|
191
|
+
id: item.id
|
|
192
|
+
});
|
|
193
|
+
return [doc, item.score];
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Overridden methods: graph-enhanced text-level search
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
async similaritySearch(query, k = 4, filter) {
|
|
200
|
+
const docsAndScores = await this.similaritySearchWithScore(query, k, filter);
|
|
201
|
+
return docsAndScores.map(([doc]) => doc);
|
|
202
|
+
}
|
|
203
|
+
async similaritySearchWithScore(query, k = 4, filter) {
|
|
204
|
+
const domains = filter?.domains ? filter.domains : [this.domain];
|
|
205
|
+
const minConfidence = typeof filter?.min_confidence === "number" ? filter.min_confidence : 0;
|
|
206
|
+
const result = await this.mcp.callTool("qortex_query", {
|
|
207
|
+
context: query,
|
|
208
|
+
domains,
|
|
209
|
+
top_k: k,
|
|
210
|
+
min_confidence: minConfidence,
|
|
211
|
+
mode: "auto"
|
|
212
|
+
});
|
|
213
|
+
this._lastQueryId = result.query_id ?? null;
|
|
214
|
+
return (result.items ?? []).map((item) => {
|
|
215
|
+
const meta = {
|
|
216
|
+
score: item.score,
|
|
217
|
+
domain: item.domain,
|
|
218
|
+
node_id: item.node_id,
|
|
219
|
+
...item.metadata
|
|
220
|
+
};
|
|
221
|
+
if (result.rules?.length) {
|
|
222
|
+
const linkedRules = result.rules.filter((r) => r.source_concepts?.includes(item.node_id)).map((r) => ({
|
|
223
|
+
id: r.id,
|
|
224
|
+
text: r.text,
|
|
225
|
+
relevance: r.relevance
|
|
226
|
+
}));
|
|
227
|
+
if (linkedRules.length > 0) {
|
|
228
|
+
meta.rules = linkedRules;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const doc = new documents.Document({
|
|
232
|
+
pageContent: item.content,
|
|
233
|
+
metadata: meta,
|
|
234
|
+
id: item.id
|
|
235
|
+
});
|
|
236
|
+
return [doc, item.score];
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Qortex extras: graph exploration + rules + feedback
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
/**
|
|
243
|
+
* Explore a concept's graph neighborhood.
|
|
244
|
+
* Use node_id from search results metadata to navigate the graph.
|
|
245
|
+
*/
|
|
246
|
+
async explore(nodeId, depth = 1) {
|
|
247
|
+
const result = await this.mcp.callTool("qortex_explore", {
|
|
248
|
+
node_id: nodeId,
|
|
249
|
+
depth
|
|
250
|
+
});
|
|
251
|
+
if (result.node === null) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
/** Get projected rules from the knowledge graph. */
|
|
257
|
+
async getRules(options = {}) {
|
|
258
|
+
const result = await this.mcp.callTool("qortex_rules", {
|
|
259
|
+
domains: options.domains ?? void 0,
|
|
260
|
+
concept_ids: options.conceptIds ?? void 0,
|
|
261
|
+
categories: options.categories ?? void 0,
|
|
262
|
+
include_derived: options.includeDerived ?? true,
|
|
263
|
+
min_confidence: options.minConfidence ?? 0
|
|
264
|
+
});
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Report feedback for retrieved items to improve future retrieval.
|
|
269
|
+
* Accepted items get higher PPR teleportation probability; rejected lower.
|
|
270
|
+
*/
|
|
271
|
+
async feedback(outcomes) {
|
|
272
|
+
if (!this._lastQueryId) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
const result = await this.mcp.callTool("qortex_feedback", {
|
|
276
|
+
query_id: this._lastQueryId,
|
|
277
|
+
outcomes,
|
|
278
|
+
source: this.feedbackSource
|
|
279
|
+
});
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Static factory methods
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
static async fromTexts(texts, metadatas, embeddings, config = {}) {
|
|
286
|
+
const store = new _QortexVectorStore(embeddings, config);
|
|
287
|
+
await store.connect();
|
|
288
|
+
const metaArray = Array.isArray(metadatas) ? metadatas : texts.map(() => metadatas);
|
|
289
|
+
const docs = texts.map(
|
|
290
|
+
(text, i) => new documents.Document({
|
|
291
|
+
pageContent: text,
|
|
292
|
+
metadata: metaArray[i]
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
await store.addDocuments(docs);
|
|
296
|
+
return store;
|
|
297
|
+
}
|
|
298
|
+
static async fromDocuments(docs, embeddings, config = {}) {
|
|
299
|
+
const store = new _QortexVectorStore(embeddings, config);
|
|
300
|
+
await store.connect();
|
|
301
|
+
await store.addDocuments(docs);
|
|
302
|
+
return store;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
var QortexEmbeddings = class extends embeddings.Embeddings {
|
|
306
|
+
model;
|
|
307
|
+
constructor(params) {
|
|
308
|
+
super(params);
|
|
309
|
+
this.model = params.model;
|
|
310
|
+
}
|
|
311
|
+
async embedDocuments(texts) {
|
|
312
|
+
return await this.model.embed(texts);
|
|
313
|
+
}
|
|
314
|
+
async embedQuery(text) {
|
|
315
|
+
const result = await this.model.embed([text]);
|
|
316
|
+
return result[0];
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
exports.QortexEmbeddings = QortexEmbeddings;
|
|
321
|
+
exports.QortexMcpClient = QortexMcpClient;
|
|
322
|
+
exports.QortexVectorStore = QortexVectorStore;
|
|
323
|
+
//# sourceMappingURL=index.cjs.map
|
|
324
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/vectorstore.ts","../src/embeddings.ts"],"names":["StdioClientTransport","Client","VectorStore","Document","Embeddings"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * MCP client wrapper for communicating with the qortex MCP server.\n *\n * Handles connection lifecycle and tool invocation. The qortex server\n * is spawned as a subprocess (stdio transport) or connected to an\n * existing server.\n */\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\n\nexport interface QortexMcpClientConfig {\n /** Command to spawn the qortex MCP server (default: \"uvx\") */\n serverCommand?: string;\n /** Arguments for the server command (default: [\"qortex\", \"mcp-serve\"]) */\n serverArgs?: string[];\n /** Environment variables for the server process */\n serverEnv?: Record<string, string>;\n /** Pre-configured MCP client (skip spawning) */\n mcpClient?: Client;\n}\n\nexport class QortexMcpClient {\n private client: Client | null = null;\n private transport: StdioClientTransport | null = null;\n private config: QortexMcpClientConfig;\n private _connected = false;\n\n constructor(config: QortexMcpClientConfig = {}) {\n this.config = config;\n if (config.mcpClient) {\n this.client = config.mcpClient;\n this._connected = true;\n }\n }\n\n get connected(): boolean {\n return this._connected;\n }\n\n async connect(): Promise<void> {\n if (this._connected) return;\n\n const command = this.config.serverCommand ?? \"uvx\";\n const args = this.config.serverArgs ?? [\"qortex\", \"mcp-serve\"];\n const env: Record<string, string> = {};\n for (const [k, v] of Object.entries(process.env)) {\n if (v !== undefined) env[k] = v;\n }\n if (this.config.serverEnv) {\n Object.assign(env, this.config.serverEnv);\n }\n\n this.transport = new StdioClientTransport({\n command,\n args,\n env,\n });\n\n this.client = new Client(\n { name: \"langchain-qortex\", version: \"0.1.0\" },\n { capabilities: {} },\n );\n\n await this.client.connect(this.transport);\n this._connected = true;\n }\n\n async disconnect(): Promise<void> {\n if (this.transport) {\n await this.transport.close();\n this.transport = null;\n }\n this.client = null;\n this._connected = false;\n }\n\n /**\n * Call a qortex MCP tool and return the parsed result.\n *\n * @param name - Tool name (e.g. \"qortex_vector_query\")\n * @param args - Tool arguments as a plain object\n * @returns Parsed JSON result from the tool\n */\n async callTool(\n name: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (!this._connected) {\n await this.connect();\n }\n\n const result = await this.client!.callTool({\n name,\n arguments: args,\n });\n\n // MCP tool results come as content blocks\n const content = result.content as Array<{ type: string; text?: string }>;\n if (!content || content.length === 0) {\n return {};\n }\n\n const textBlock = content.find((c) => c.type === \"text\");\n if (!textBlock?.text) {\n return {};\n }\n\n // Handle MCP error responses that aren't valid JSON\n try {\n return JSON.parse(textBlock.text);\n } catch {\n if (result.isError) {\n throw new Error(`MCP tool error: ${textBlock.text}`);\n }\n return { raw: textBlock.text };\n }\n }\n}\n","/**\n * QortexVectorStore: LangChain.js VectorStore backed by qortex knowledge graph.\n *\n * Drop-in replacement for MemoryVectorStore, Chroma, Pinecone, or any\n * LangChain VectorStore. Implements the full VectorStore interface and\n * adds graph structure, rules, and feedback-driven learning.\n *\n * Text-level search (similaritySearch, similaritySearchWithScore) uses\n * qortex's full pipeline: embedding + graph PPR + rules. Vector-level\n * search (similaritySearchVectorWithScore) provides raw LangChain\n * compatibility via qortex_vector_query.\n *\n * Usage:\n * import { QortexVectorStore } from \"@peleke.s/langchain-qortex\";\n *\n * const store = await QortexVectorStore.fromTexts(\n * texts, metadatas, embeddings, { indexName: \"docs\" }\n * );\n * const docs = await store.similaritySearch(\"authentication\", 5);\n * const retriever = store.asRetriever({ k: 10 });\n */\n\nimport { VectorStore } from \"@langchain/core/vectorstores\";\nimport { Document } from \"@langchain/core/documents\";\nimport type { EmbeddingsInterface } from \"@langchain/core/embeddings\";\nimport type { DocumentInterface } from \"@langchain/core/documents\";\nimport { QortexMcpClient, type QortexMcpClientConfig } from \"./client.js\";\nimport type {\n ExploreResult,\n RulesResult,\n FeedbackOutcome,\n FeedbackResult,\n QortexQueryResult,\n QortexRule,\n} from \"./types.js\";\n\nexport interface QortexVectorStoreConfig extends QortexMcpClientConfig {\n /** Index name for vector operations (default: \"default\"). */\n indexName?: string;\n /** Default domain for text-level queries (default: \"default\"). */\n domain?: string;\n /** Source identifier for feedback events (default: \"langchain\"). */\n feedbackSource?: string;\n}\n\nexport class QortexVectorStore extends VectorStore {\n declare FilterType: Record<string, unknown>;\n\n private mcp: QortexMcpClient;\n private indexName: string;\n private domain: string;\n private feedbackSource: string;\n private _lastQueryId: string | null = null;\n\n constructor(\n embeddings: EmbeddingsInterface,\n config: QortexVectorStoreConfig = {},\n ) {\n super(embeddings, config);\n this.mcp = new QortexMcpClient(config);\n this.indexName = config.indexName ?? \"default\";\n this.domain = config.domain ?? \"default\";\n this.feedbackSource = config.feedbackSource ?? \"langchain\";\n }\n\n _vectorstoreType(): string {\n return \"qortex\";\n }\n\n /** Ensure the MCP connection is established. */\n async connect(): Promise<void> {\n await this.mcp.connect();\n }\n\n /** Disconnect from the MCP server. */\n async disconnect(): Promise<void> {\n await this.mcp.disconnect();\n }\n\n /** The query_id from the most recent text-level search. */\n get lastQueryId(): string | null {\n return this._lastQueryId;\n }\n\n // ---------------------------------------------------------------------------\n // Index management (MCP passthrough)\n // ---------------------------------------------------------------------------\n\n /** Create a vector index. Must be called before addVectors/addDocuments. */\n async createIndex(params: {\n indexName?: string;\n dimension: number;\n metric?: \"cosine\" | \"euclidean\" | \"dotproduct\";\n }): Promise<void> {\n const result = (await this.mcp.callTool(\"qortex_vector_create_index\", {\n index_name: params.indexName ?? this.indexName,\n dimension: params.dimension,\n metric: params.metric ?? \"cosine\",\n })) as Record<string, unknown>;\n\n if (result.error) {\n throw new Error(result.error as string);\n }\n }\n\n /** Delete a vector index. */\n async deleteIndex(params?: { indexName?: string }): Promise<void> {\n const result = (await this.mcp.callTool(\"qortex_vector_delete_index\", {\n index_name: params?.indexName ?? this.indexName,\n })) as Record<string, unknown>;\n\n if (result.error) {\n throw new Error(result.error as string);\n }\n }\n\n /** List all vector indexes. */\n async listIndexes(): Promise<string[]> {\n const result = (await this.mcp.callTool(\n \"qortex_vector_list_indexes\",\n {},\n )) as { indexes: string[] };\n return result.indexes;\n }\n\n // ---------------------------------------------------------------------------\n // Abstract method implementations (required by VectorStore)\n // ---------------------------------------------------------------------------\n\n async addVectors(\n vectors: number[][],\n documents: DocumentInterface[],\n options?: { ids?: string[] },\n ): Promise<string[] | void> {\n const metadata = documents.map((doc) => ({\n text: doc.pageContent,\n ...doc.metadata,\n }));\n const ids = options?.ids ?? documents.map((doc) => doc.id).filter(Boolean) as string[];\n\n const result = (await this.mcp.callTool(\"qortex_vector_upsert\", {\n index_name: this.indexName,\n vectors,\n metadata,\n ids: ids.length > 0 ? ids : undefined,\n })) as { ids?: string[]; error?: string };\n\n if (result.error) {\n throw new Error(result.error);\n }\n\n return result.ids;\n }\n\n async addDocuments(\n documents: DocumentInterface[],\n options?: { ids?: string[] },\n ): Promise<string[] | void> {\n const texts = documents.map((doc) => doc.pageContent);\n const vectors = await this.embeddings.embedDocuments(texts);\n return this.addVectors(vectors, documents, options);\n }\n\n async similaritySearchVectorWithScore(\n query: number[],\n k: number,\n filter?: this[\"FilterType\"],\n ): Promise<[DocumentInterface, number][]> {\n const result = (await this.mcp.callTool(\"qortex_vector_query\", {\n index_name: this.indexName,\n query_vector: query,\n top_k: k,\n filter: filter ?? undefined,\n include_vector: false,\n })) as {\n results?: Array<{\n id: string;\n score: number;\n metadata?: Record<string, unknown>;\n }>;\n error?: string;\n };\n\n if (result.error) {\n throw new Error(result.error);\n }\n\n return (result.results ?? []).map((item) => {\n const { text, ...meta } = (item.metadata ?? {}) as Record<string, unknown>;\n const doc = new Document({\n pageContent: (text as string) ?? \"\",\n metadata: { ...meta, score: item.score },\n id: item.id,\n });\n return [doc, item.score] as [DocumentInterface, number];\n });\n }\n\n // ---------------------------------------------------------------------------\n // Overridden methods: graph-enhanced text-level search\n // ---------------------------------------------------------------------------\n\n async similaritySearch(\n query: string,\n k: number = 4,\n filter?: this[\"FilterType\"],\n ): Promise<DocumentInterface[]> {\n const docsAndScores = await this.similaritySearchWithScore(query, k, filter);\n return docsAndScores.map(([doc]) => doc);\n }\n\n async similaritySearchWithScore(\n query: string,\n k: number = 4,\n filter?: this[\"FilterType\"],\n ): Promise<[DocumentInterface, number][]> {\n const domains = filter?.domains\n ? (filter.domains as string[])\n : [this.domain];\n const minConfidence =\n typeof filter?.min_confidence === \"number\" ? filter.min_confidence : 0.0;\n\n const result = (await this.mcp.callTool(\"qortex_query\", {\n context: query,\n domains,\n top_k: k,\n min_confidence: minConfidence,\n mode: \"auto\",\n })) as QortexQueryResult;\n\n this._lastQueryId = result.query_id ?? null;\n\n return (result.items ?? []).map((item) => {\n const meta: Record<string, unknown> = {\n score: item.score,\n domain: item.domain,\n node_id: item.node_id,\n ...item.metadata,\n };\n\n if (result.rules?.length) {\n const linkedRules = result.rules\n .filter((r: QortexRule) => r.source_concepts?.includes(item.node_id))\n .map((r: QortexRule) => ({\n id: r.id,\n text: r.text,\n relevance: r.relevance,\n }));\n if (linkedRules.length > 0) {\n meta.rules = linkedRules;\n }\n }\n\n const doc = new Document({\n pageContent: item.content,\n metadata: meta,\n id: item.id,\n });\n return [doc, item.score] as [DocumentInterface, number];\n });\n }\n\n // ---------------------------------------------------------------------------\n // Qortex extras: graph exploration + rules + feedback\n // ---------------------------------------------------------------------------\n\n /**\n * Explore a concept's graph neighborhood.\n * Use node_id from search results metadata to navigate the graph.\n */\n async explore(\n nodeId: string,\n depth: number = 1,\n ): Promise<ExploreResult | null> {\n const result = (await this.mcp.callTool(\"qortex_explore\", {\n node_id: nodeId,\n depth,\n })) as ExploreResult & { node: unknown };\n\n if (result.node === null) {\n return null;\n }\n\n return result;\n }\n\n /** Get projected rules from the knowledge graph. */\n async getRules(\n options: {\n domains?: string[];\n conceptIds?: string[];\n categories?: string[];\n includeDerived?: boolean;\n minConfidence?: number;\n } = {},\n ): Promise<RulesResult> {\n const result = (await this.mcp.callTool(\"qortex_rules\", {\n domains: options.domains ?? undefined,\n concept_ids: options.conceptIds ?? undefined,\n categories: options.categories ?? undefined,\n include_derived: options.includeDerived ?? true,\n min_confidence: options.minConfidence ?? 0.0,\n })) as RulesResult;\n\n return result;\n }\n\n /**\n * Report feedback for retrieved items to improve future retrieval.\n * Accepted items get higher PPR teleportation probability; rejected lower.\n */\n async feedback(\n outcomes: Record<string, FeedbackOutcome>,\n ): Promise<FeedbackResult | null> {\n if (!this._lastQueryId) {\n return null;\n }\n\n const result = (await this.mcp.callTool(\"qortex_feedback\", {\n query_id: this._lastQueryId,\n outcomes,\n source: this.feedbackSource,\n })) as FeedbackResult;\n\n return result;\n }\n\n // ---------------------------------------------------------------------------\n // Static factory methods\n // ---------------------------------------------------------------------------\n\n static async fromTexts(\n texts: string[],\n metadatas: object[] | object,\n embeddings: EmbeddingsInterface,\n config: QortexVectorStoreConfig = {},\n ): Promise<QortexVectorStore> {\n const store = new QortexVectorStore(embeddings, config);\n await store.connect();\n\n const metaArray = Array.isArray(metadatas)\n ? metadatas\n : texts.map(() => metadatas);\n\n const docs = texts.map(\n (text, i) =>\n new Document({\n pageContent: text,\n metadata: metaArray[i] as Record<string, unknown>,\n }),\n );\n\n await store.addDocuments(docs);\n return store;\n }\n\n static async fromDocuments(\n docs: DocumentInterface[],\n embeddings: EmbeddingsInterface,\n config: QortexVectorStoreConfig = {},\n ): Promise<QortexVectorStore> {\n const store = new QortexVectorStore(embeddings, config);\n await store.connect();\n await store.addDocuments(docs);\n return store;\n }\n}\n","/**\n * QortexEmbeddings: wrap a qortex-style embedding model in LangChain's\n * Embeddings interface.\n *\n * Any object with `.embed(texts) -> number[][]` works. This is a thin\n * utility -- most users will bring their own LangChain embeddings\n * (OpenAI, HuggingFace, etc.) and pass them to QortexVectorStore directly.\n */\n\nimport { Embeddings, type EmbeddingsParams } from \"@langchain/core/embeddings\";\n\nexport interface QortexEmbeddingsParams extends EmbeddingsParams {\n /** Any object with `.embed(texts: string[]) -> Promise<number[][]> | number[][]`. */\n model: { embed(texts: string[]): number[][] | Promise<number[][]> };\n}\n\nexport class QortexEmbeddings extends Embeddings {\n private model: { embed(texts: string[]): number[][] | Promise<number[][]> };\n\n constructor(params: QortexEmbeddingsParams) {\n super(params);\n this.model = params.model;\n }\n\n async embedDocuments(texts: string[]): Promise<number[][]> {\n return await this.model.embed(texts);\n }\n\n async embedQuery(text: string): Promise<number[]> {\n const result = await this.model.embed([text]);\n return result[0];\n }\n}\n"]}
|