@neural-technologies-indonesia/nl2sql 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -23,7 +23,7 @@ Core reasoning flow for text-to-SQL that can be exposed by any transport (Stream
23
23
  ## What lives here (and how it works)
24
24
  - Pure orchestrator in TypeScript (Bun-friendly) with clean interfaces for prompt composing, retrieval, LLM reasoning, query execution, and feedback policy.
25
25
  - Transport-agnostic: callers plug in their own input/output (UI, REST, MCP, etc.).
26
- - Defaults: prompt composer (`DefaultPromptComposer`), feedback policy (`SimpleFeedbackPolicy`), MCP executor (`MCPQueryExecutor`), MCP retriever (`MCPMilvusRetriever`), and OpenAI LLM wrapper (`OpenAILLM`).
26
+ - Defaults: prompt composer (`DefaultPromptComposer`), feedback policy (`SimpleFeedbackPolicy`), MCP executor (`MCPQueryExecutor`), Milvus retriever (`MilvusRetriever`), and OpenAI LLM wrapper (`OpenAILLM`).
27
27
  - Flow: retrieve context → compose prompt → ask LLM for SQL candidates → execute via MCP `run_query` → accept/retry via feedback policy.
28
28
 
29
29
  ## Example wiring
@@ -64,13 +64,69 @@ console.log(result);
64
64
 
65
65
  ## Env
66
66
  - Copy `.env.example` and set `OPENAI_API_KEY` (and optionally `OPENAI_BASE_URL`, `OPENAI_MODEL`).
67
+ - Milvus retriever defaults: `MILVUS_ADDRESS=milvus:19530`, `MILVUS_COLLECTION=rag_tables`, `MILVUS_VECTOR_FIELD=embedding`, `OPENAI_EMBEDDING_MODEL=text-embedding-3-small`.
68
+
69
+ ## Quick start (run-node)
70
+ - Make sure `../mcp-server` is available (defaults to `../mcp-server/index.ts`).
71
+ - Add `OPENAI_API_KEY` to `example/.env`.
72
+ - Run: `bun run example/run-node.ts`.
73
+
74
+ ```ts
75
+ import {
76
+ DefaultPromptComposer,
77
+ OpenAILLM,
78
+ ReasoningPipeline,
79
+ SimpleFeedbackPolicy,
80
+ createMcpUseAdapters,
81
+ } from "@neural-technologies-indonesia/nl2sql";
82
+ import dotenv from "dotenv";
83
+ import path from "node:path";
84
+ import { fileURLToPath } from "node:url";
85
+
86
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
87
+ dotenv.config({ path: path.join(__dirname, ".env") });
88
+
89
+ const serverCwd =
90
+ process.env.MCP_SERVER_CWD ??
91
+ path.resolve(__dirname, "..", "..", "mcp-server");
92
+ const serverEntry = path.join(serverCwd, "index.ts");
93
+
94
+ const { executor, retriever, listIndexes, close } = await createMcpUseAdapters({
95
+ serverCwd,
96
+ command: "bun",
97
+ args: ["run", serverEntry],
98
+ env: Object.fromEntries(
99
+ Object.entries(process.env).filter(
100
+ (entry): entry is [string, string] => typeof entry[1] === "string"
101
+ )
102
+ ),
103
+ });
104
+
105
+ const pipeline = new ReasoningPipeline({
106
+ composer: new DefaultPromptComposer(),
107
+ retriever,
108
+ llm: new OpenAILLM({ model: process.env.OPENAI_MODEL ?? "gpt-4o-mini" }),
109
+ executor,
110
+ indexLookup: { listIndexes },
111
+ policy: new SimpleFeedbackPolicy({ durationBudgetMs: 5000 }),
112
+ });
113
+
114
+ const result = await pipeline.run({
115
+ question:
116
+ "Show monthly entity counts per tier since 2020. For each tier and month, return total entities and how many match metadata tier. Ignore groups with total ≤ 1000, ordered by most recent month and highest totals.",
117
+ });
118
+
119
+ console.log(JSON.stringify(result, null, 2));
120
+
121
+ await close();
122
+ ```
67
123
 
68
124
  ## Examples
69
- - `example/bun-server.ts`: starts a Bun HTTP server exposing POST `/query` that runs the pipeline (OpenAI + MCP-backed executor/retriever). Run with `bun run example/bun-server.ts`.
125
+ - `example/bun-server.ts`: starts a Bun HTTP server exposing POST `/query` that runs the pipeline (OpenAI + Milvus retriever + MCP-backed executor). Run with `bun run example/bun-server.ts`.
70
126
  - `example/streamlit_app.py`: minimal Streamlit UI that POSTs to the Bun server. Configure `API_URL` in `.streamlit/secrets.toml` or edit the default (`http://localhost:8787/query`).
71
127
 
72
128
  ## Use the sibling MCP server (`../mcp-server`) via `mcp-use`
73
- - That folder ships a FastMCP server exposing `run_query` (Postgres latency) and `milvus_retrieve` (RAG search). Start it with your env: `DATABASE_URL=... OPENAI_API_KEY=... bun run start` from `../mcp-server`.
129
+ - That folder ships a FastMCP server exposing `run_query`/`run_query_explain`/`list_indexes` for Postgres. Start it with your env: `DATABASE_URL=... bun run start` from `../mcp-server`.
74
130
  - Install the client dependency here: `bun add mcp-use` (or `npm install mcp-use`).
75
131
  - If your MCP server lives somewhere else, point the helper there with env vars: `MCP_SERVER_CWD=/path/to/server`, optionally `MCP_SERVER_CMD=bun` and `MCP_SERVER_ARGS="run start"`. The example Bun server picks these up automatically.
76
132
  - Wire the pipeline to that server with the provided helper:
@@ -102,7 +158,7 @@ console.log(result);
102
158
  console.log(result);
103
159
  await close();
104
160
  ```
105
- - `createMcpUseAdapters` calls the MCP tools (no duplicated logic here) and returns the MCP-backed executor/retriever pair.
161
+ - `createMcpUseAdapters` calls the MCP tools for execution (no duplicated logic here) and wires the retriever directly to Milvus using the provided/env config.
106
162
 
107
163
  ## Next steps
108
164
  - Swap in real retriever and LLM clients.
@@ -0,0 +1,42 @@
1
+ import type { ContextChunk, PromptInput, Retriever } from "../types.js";
2
+ export type MilvusSearchResult = {
3
+ id?: string;
4
+ score?: number;
5
+ rag_text?: string;
6
+ metadata?: Record<string, unknown>;
7
+ };
8
+ export type MilvusRetrieverOptions = {
9
+ topK?: number;
10
+ collection?: string;
11
+ vectorField?: string;
12
+ milvusAddress?: string;
13
+ timeoutMs?: number;
14
+ loadRetries?: number;
15
+ openAIApiKey?: string;
16
+ openAIModel?: string;
17
+ openAIBaseUrl?: string;
18
+ };
19
+ export declare class MilvusRetriever implements Retriever {
20
+ private readonly topK;
21
+ private readonly collection;
22
+ private readonly vectorField;
23
+ private readonly milvusAddress;
24
+ private readonly timeoutMs;
25
+ private readonly loadRetries;
26
+ private readonly embeddingModel;
27
+ private readonly openAIBaseUrl?;
28
+ private readonly openAIApiKey?;
29
+ private milvusClient;
30
+ private openAIClient;
31
+ private collectionLoaded;
32
+ private collectionLoading;
33
+ constructor(options?: MilvusRetrieverOptions);
34
+ retrieve(input: PromptInput): Promise<ContextChunk[]>;
35
+ private getMilvusClient;
36
+ private getOpenAIClient;
37
+ private ensureCollectionLoaded;
38
+ private embedQuery;
39
+ private searchMilvus;
40
+ }
41
+ export { MilvusRetriever as MCPMilvusRetriever };
42
+ //# sourceMappingURL=milvusRetriever.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"milvusRetriever.d.ts","sourceRoot":"","sources":["../../src/adapters/milvusRetriever.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,qBAAa,eAAgB,YAAW,SAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IAEvC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,iBAAiB,CAA8B;gBAE3C,OAAO,CAAC,EAAE,sBAAsB;IAiBtC,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAY3D,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,eAAe;YAeT,sBAAsB;YA2BtB,UAAU;YAaV,YAAY;CA0B3B;AA0HD,OAAO,EAAE,eAAe,IAAI,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,230 @@
1
+ import { DataType, MilvusClient } from "@zilliz/milvus2-sdk-node";
2
+ import OpenAI from "openai";
3
+ export class MilvusRetriever {
4
+ topK;
5
+ collection;
6
+ vectorField;
7
+ milvusAddress;
8
+ timeoutMs;
9
+ loadRetries;
10
+ embeddingModel;
11
+ openAIBaseUrl;
12
+ openAIApiKey;
13
+ milvusClient = null;
14
+ openAIClient = null;
15
+ collectionLoaded = false;
16
+ collectionLoading = null;
17
+ constructor(options) {
18
+ this.topK = options?.topK ?? 5;
19
+ this.collection = options?.collection ?? process.env.MILVUS_COLLECTION ?? "rag_tables";
20
+ this.vectorField = options?.vectorField ?? process.env.MILVUS_VECTOR_FIELD ?? "embedding";
21
+ this.milvusAddress =
22
+ options?.milvusAddress ?? process.env.MILVUS_ADDRESS ?? "localhost:19530";
23
+ this.timeoutMs =
24
+ options?.timeoutMs ?? Number(process.env.MILVUS_TIMEOUT_MS ?? "120000");
25
+ this.loadRetries =
26
+ options?.loadRetries ?? Number(process.env.MILVUS_LOAD_RETRIES ?? "6");
27
+ this.embeddingModel =
28
+ options?.openAIModel ?? process.env.OPENAI_EMBEDDING_MODEL ?? "text-embedding-3-small";
29
+ this.openAIBaseUrl =
30
+ normalizeBaseUrl(options?.openAIBaseUrl ?? process.env.OPENAI_BASE_URL);
31
+ this.openAIApiKey = options?.openAIApiKey ?? process.env.OPENAI_API_KEY;
32
+ }
33
+ async retrieve(input) {
34
+ const query = buildSearchQuery(input);
35
+ if (!query)
36
+ return [];
37
+ try {
38
+ const results = await this.searchMilvus(query);
39
+ return normalizeResults(results);
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ }
45
+ getMilvusClient() {
46
+ if (this.milvusClient)
47
+ return this.milvusClient;
48
+ this.milvusClient = new MilvusClient({
49
+ address: this.milvusAddress,
50
+ timeout: this.timeoutMs,
51
+ });
52
+ return this.milvusClient;
53
+ }
54
+ getOpenAIClient() {
55
+ if (this.openAIClient)
56
+ return this.openAIClient;
57
+ if (!this.openAIApiKey) {
58
+ throw new Error("OPENAI_API_KEY is required for Milvus retrieval.");
59
+ }
60
+ this.openAIClient = new OpenAI({
61
+ apiKey: this.openAIApiKey,
62
+ baseURL: this.openAIBaseUrl,
63
+ });
64
+ return this.openAIClient;
65
+ }
66
+ async ensureCollectionLoaded(client) {
67
+ if (this.collectionLoaded)
68
+ return;
69
+ if (!this.collectionLoading) {
70
+ this.collectionLoading = (async () => {
71
+ for (let i = 0; i < this.loadRetries; i++) {
72
+ try {
73
+ await client.loadCollection({
74
+ collection_name: this.collection,
75
+ timeout: this.timeoutMs,
76
+ });
77
+ this.collectionLoaded = true;
78
+ return;
79
+ }
80
+ catch {
81
+ await sleep(Math.min(1000 * 2 ** i, 8000));
82
+ }
83
+ }
84
+ throw new Error(`Milvus load failed after ${this.loadRetries} attempts for collection '${this.collection}'.`);
85
+ })();
86
+ }
87
+ await this.collectionLoading;
88
+ }
89
+ async embedQuery(query) {
90
+ const client = this.getOpenAIClient();
91
+ const response = await client.embeddings.create({
92
+ model: this.embeddingModel,
93
+ input: query,
94
+ });
95
+ const embedding = response.data?.[0]?.embedding;
96
+ if (!embedding) {
97
+ throw new Error("Failed to generate embedding for query.");
98
+ }
99
+ return embedding;
100
+ }
101
+ async searchMilvus(text) {
102
+ const client = this.getMilvusClient();
103
+ await this.ensureCollectionLoaded(client);
104
+ const embedding = await this.embedQuery(text);
105
+ const searchResponse = await client.search({
106
+ collection_name: this.collection,
107
+ vectors: [embedding],
108
+ vector_type: DataType.FloatVector,
109
+ search_params: {
110
+ anns_field: this.vectorField,
111
+ topk: String(this.topK),
112
+ metric_type: "L2",
113
+ params: JSON.stringify({ nprobe: 16 }),
114
+ },
115
+ output_fields: ["id", "rag_text", "metadata"],
116
+ timeout: this.timeoutMs,
117
+ });
118
+ const results = Array.isArray(searchResponse.results)
119
+ ? searchResponse.results
120
+ : [];
121
+ return results;
122
+ }
123
+ }
124
+ // Combine user input fields into a single search string.
125
+ const buildSearchQuery = (input) => {
126
+ const queryParts = [input.question.trim()];
127
+ if (input.userContext?.trim()) {
128
+ queryParts.push(`Context: ${input.userContext.trim()}`);
129
+ }
130
+ if (input.constraints?.length) {
131
+ queryParts.push(`Constraints: ${input.constraints.filter(Boolean).join(" | ")}`);
132
+ }
133
+ return queryParts.filter(Boolean).join("\n").trim();
134
+ };
135
+ // Accepts any payload shape and converts it into ContextChunks.
136
+ const normalizeResults = (results) => {
137
+ if (typeof results === "string") {
138
+ return [{ source: "milvus", snippet: results }];
139
+ }
140
+ if (!Array.isArray(results)) {
141
+ return [{ source: "milvus", snippet: contentToSnippet(results) }];
142
+ }
143
+ return results.map((item, index) => {
144
+ const snippetParts = [item.rag_text ?? "(no rag_text)"];
145
+ if (item.metadata) {
146
+ snippetParts.push(buildMetadataSnippet(item.metadata));
147
+ }
148
+ return {
149
+ source: item.id ? String(item.id) : `milvus-${index + 1}`,
150
+ snippet: snippetParts.join("\n"),
151
+ score: item.score,
152
+ };
153
+ });
154
+ };
155
+ const contentToSnippet = (content) => {
156
+ if (typeof content === "string")
157
+ return content;
158
+ if (Array.isArray(content))
159
+ return content.map((item) => JSON.stringify(item)).join("\n");
160
+ if (content && typeof content === "object")
161
+ return JSON.stringify(content);
162
+ return String(content ?? "");
163
+ };
164
+ const normalizeTableReference = (schema, table) => {
165
+ if (!table.includes(".")) {
166
+ return { schema, table };
167
+ }
168
+ const parts = table.split(".").filter(Boolean);
169
+ if (!parts.length) {
170
+ return { schema, table };
171
+ }
172
+ if (parts.length >= 2 && !schema) {
173
+ return {
174
+ schema: parts[parts.length - 2],
175
+ table: parts[parts.length - 1],
176
+ };
177
+ }
178
+ return { schema, table: parts[parts.length - 1] };
179
+ };
180
+ // Formats metadata for the prompt while avoiding fully qualified names that can confuse the model.
181
+ const buildMetadataSnippet = (metadata) => {
182
+ const parts = [];
183
+ const schema = typeof metadata.schema === "string" ? metadata.schema : "";
184
+ const tableRaw = metadata.table ?? metadata.table_name ?? metadata.name;
185
+ const table = typeof tableRaw === "string" ? tableRaw : "";
186
+ const normalized = normalizeTableReference(schema, table);
187
+ const qualified = [normalized.schema, normalized.table].filter(Boolean).join(".");
188
+ if (qualified)
189
+ parts.push(`table=${qualified}`);
190
+ const tableType = typeof metadata.table_type === "string"
191
+ ? metadata.table_type
192
+ : typeof metadata.tableType === "string"
193
+ ? metadata.tableType
194
+ : "";
195
+ if (tableType)
196
+ parts.push(`type=${tableType}`);
197
+ const primaryKeys = Array.isArray(metadata.primary_keys) ? metadata.primary_keys : [];
198
+ if (primaryKeys.length)
199
+ parts.push(`primary_keys=${primaryKeys.map(String).join(",")}`);
200
+ const columns = Array.isArray(metadata.columns)
201
+ ? metadata.columns
202
+ .map((col) => col && typeof col === "object" && "name" in col ? String(col.name) : null)
203
+ .filter((name) => Boolean(name))
204
+ : [];
205
+ if (columns.length)
206
+ parts.push(`columns=${columns.join(", ")}`);
207
+ const description = typeof metadata.description === "string" ? metadata.description : "";
208
+ if (description)
209
+ parts.push(`desc=${description}`);
210
+ if (!parts.length) {
211
+ const sanitized = { ...metadata };
212
+ delete sanitized.fully_qualified_name;
213
+ delete sanitized.fullyQualifiedName;
214
+ return `metadata=${JSON.stringify(sanitized)}`;
215
+ }
216
+ return parts.join("; ");
217
+ };
218
+ const normalizeBaseUrl = (rawBaseUrl) => {
219
+ if (!rawBaseUrl)
220
+ return undefined;
221
+ const trimmed = rawBaseUrl.trim();
222
+ if (!trimmed)
223
+ return undefined;
224
+ if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://")) {
225
+ return `https://${trimmed}`;
226
+ }
227
+ return trimmed;
228
+ };
229
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
230
+ export { MilvusRetriever as MCPMilvusRetriever };
@@ -1,18 +1,20 @@
1
- import { MCPClient } from "mcp-use";
2
1
  import { MCPQueryExecutor } from "../adapters/mcpExecutor.js";
3
- import { MCPMilvusRetriever } from "../adapters/mcpRetriever.js";
4
- import type { MCPMilvusResult } from "../adapters/mcpRetriever.js";
2
+ import { MilvusRetriever } from "../adapters/milvusRetriever.js";
3
+ import type { MilvusRetrieverOptions } from "../adapters/milvusRetriever.js";
5
4
  export type MCPUseClientOptions = {
6
5
  serverName?: string;
7
6
  serverCwd?: string;
8
7
  command?: string;
9
8
  args?: string[];
10
9
  env?: Record<string, string>;
10
+ transportType?: "stdio" | "httpStream";
11
+ httpStreamUrl?: string;
12
+ milvus?: MilvusRetrieverOptions;
11
13
  };
12
14
  export declare const createMcpUseAdapters: (options?: MCPUseClientOptions) => Promise<{
13
15
  executor: MCPQueryExecutor;
14
- retriever: MCPMilvusRetriever;
15
- callTool: (name: "run_query" | "run_query_explain" | "list_indexes" | "milvus_retrieve", args: Record<string, unknown>) => Promise<string | MCPMilvusResult[] | Record<string, unknown>>;
16
+ retriever: MilvusRetriever;
17
+ callTool: (name: "run_query" | "run_query_explain" | "list_indexes", args: Record<string, unknown>) => Promise<string | Record<string, unknown>>;
16
18
  runQueryExplain: (sql: string) => Promise<{
17
19
  durationMs: number;
18
20
  explainLines: string[] | undefined;
@@ -33,7 +35,7 @@ export declare const createMcpUseAdapters: (options?: MCPUseClientOptions) => Pr
33
35
  tablename?: string;
34
36
  }) => Promise<string>;
35
37
  close: () => Promise<void>;
36
- client: MCPClient;
37
- session: import("mcp-use").MCPSession;
38
+ client: unknown;
39
+ session: unknown;
38
40
  }>;
39
41
  //# sourceMappingURL=mcpUseClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcpUseClient.d.ts","sourceRoot":"","sources":["../../src/clients/mcpUseClient.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAGnE,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,CAAC;AAwEF,eAAO,MAAM,oBAAoB,GAAU,UAAU,mBAAmB;;;qBA2C9D,WAAW,GAAG,mBAAmB,GAAG,cAAc,GAAG,iBAAiB,QACtE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;2BAuB5B,MAAM;;;;;;;;;;;;;;;4BAoDL;QACnC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;EA2BF,CAAC"}
1
+ {"version":3,"file":"mcpUseClient.d.ts","sourceRoot":"","sources":["../../src/clients/mcpUseClient.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG7E,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,GAAG,YAAY,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC,CAAC;AA8FF,eAAO,MAAM,oBAAoB,GAAU,UAAU,mBAAmB;;;qBAa5D,WAAW,GAAG,mBAAmB,GAAG,cAAc,QAClD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;2BAKV,MAAM;;;;;;;;;;;;;;;4BA4DL;QACnC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;iBAnEY,OAAO,CAAC,IAAI,CAAC;;;EAuK7B,CAAC"}
@@ -1,8 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { MCPClient } from "mcp-use";
4
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
6
  import { MCPQueryExecutor } from "../adapters/mcpExecutor.js";
5
- import { MCPMilvusRetriever } from "../adapters/mcpRetriever.js";
7
+ import { MilvusRetriever } from "../adapters/milvusRetriever.js";
6
8
  // Accepts a whitespace-delimited env var and turns it into an args array.
7
9
  const parseArgs = (value) => {
8
10
  if (!value)
@@ -12,6 +14,24 @@ const parseArgs = (value) => {
12
14
  .map((part) => part.trim())
13
15
  .filter(Boolean);
14
16
  };
17
+ const parseNumber = (value) => {
18
+ if (value === undefined)
19
+ return undefined;
20
+ const parsed = Number(value);
21
+ return Number.isNaN(parsed) ? undefined : parsed;
22
+ };
23
+ const resolveMilvusOptions = (options) => ({
24
+ ...options,
25
+ topK: options?.topK ?? parseNumber(process.env.MILVUS_TOP_K) ?? undefined,
26
+ collection: options?.collection ?? process.env.MILVUS_COLLECTION,
27
+ vectorField: options?.vectorField ?? process.env.MILVUS_VECTOR_FIELD,
28
+ milvusAddress: options?.milvusAddress ?? process.env.MILVUS_ADDRESS,
29
+ timeoutMs: options?.timeoutMs ?? parseNumber(process.env.MILVUS_TIMEOUT_MS),
30
+ loadRetries: options?.loadRetries ?? parseNumber(process.env.MILVUS_LOAD_RETRIES),
31
+ openAIApiKey: options?.openAIApiKey ?? process.env.OPENAI_API_KEY,
32
+ openAIBaseUrl: options?.openAIBaseUrl ?? process.env.OPENAI_BASE_URL,
33
+ openAIModel: options?.openAIModel ?? process.env.OPENAI_EMBEDDING_MODEL,
34
+ });
15
35
  // Convert relative file-like args to absolute paths so Bun can locate the entrypoint
16
36
  // even if the MCP client spawns from a different working directory.
17
37
  const absolutizeArgs = (args, baseDir) => {
@@ -69,6 +89,96 @@ const parseDurationMs = (text) => {
69
89
  // Starts mcp-use against the sibling MCP server and returns MCP-backed adapters.
70
90
  export const createMcpUseAdapters = async (options) => {
71
91
  const serverName = options?.serverName ?? "query-runner";
92
+ const transportType = options?.transportType ??
93
+ (process.env.MCP_SERVER_TRANSPORT === "http-stream"
94
+ ? "httpStream"
95
+ : process.env.MCP_SERVER_TRANSPORT);
96
+ const httpStreamUrl = options?.httpStreamUrl ?? process.env.MCP_SERVER_URL ?? undefined;
97
+ const retriever = new MilvusRetriever(resolveMilvusOptions(options?.milvus));
98
+ const buildAdapters = (callTool, close, client, session) => {
99
+ const runQueryExplain = async (sql) => {
100
+ const content = await callTool("run_query_explain", { raw_query: sql });
101
+ const text = contentToText(content);
102
+ const durationMs = parseDurationMs(text) ?? Number.NaN;
103
+ const jsonStart = text.search(/JSON:\s*/i);
104
+ const explainMarker = text.search(/EXPLAIN ANALYZE:\s*/i);
105
+ let parsed = null;
106
+ if (jsonStart !== -1) {
107
+ const jsonBlock = text.slice(jsonStart).replace(/^JSON:\s*/i, "");
108
+ const jsonText = explainMarker !== -1
109
+ ? jsonBlock.slice(0, explainMarker - jsonStart).trim()
110
+ : jsonBlock.trim();
111
+ try {
112
+ parsed = JSON.parse(jsonText);
113
+ }
114
+ catch {
115
+ parsed = null;
116
+ }
117
+ }
118
+ if (parsed) {
119
+ const explainLines = Array.isArray(parsed.explainLines)
120
+ ? parsed.explainLines.map((line) => String(line))
121
+ : undefined;
122
+ return {
123
+ durationMs: typeof parsed.durationMs === "number"
124
+ ? parsed.durationMs
125
+ : durationMs,
126
+ explainLines,
127
+ planningMs: typeof parsed.planningMs === "number"
128
+ ? parsed.planningMs
129
+ : undefined,
130
+ executionMs: typeof parsed.executionMs === "number"
131
+ ? parsed.executionMs
132
+ : undefined,
133
+ raw: text,
134
+ };
135
+ }
136
+ const explainSplit = text.split(/EXPLAIN ANALYZE:\s*/i)[1] ??
137
+ text.split(/EXPLAIN:\s*/i)[1] ??
138
+ "";
139
+ return { durationMs, explain: explainSplit.trim(), raw: text };
140
+ };
141
+ const executor = new MCPQueryExecutor({
142
+ runQuery: async (sql) => {
143
+ const content = await callTool("run_query", { raw_query: sql });
144
+ const text = contentToText(content);
145
+ const durationMs = parseDurationMs(text) ?? Number.NaN; // Keep numeric for typing even if parse fails.
146
+ return { durationMs };
147
+ },
148
+ runQueryExplain: async (sql) => runQueryExplain(sql),
149
+ });
150
+ const listIndexes = async (options) => {
151
+ const content = await callTool("list_indexes", {
152
+ schemaname: options?.schemaname,
153
+ tablename: options?.tablename,
154
+ });
155
+ return contentToText(content);
156
+ };
157
+ return {
158
+ executor,
159
+ retriever,
160
+ callTool,
161
+ runQueryExplain,
162
+ listIndexes,
163
+ close,
164
+ client,
165
+ session,
166
+ };
167
+ };
168
+ if (transportType === "httpStream" || httpStreamUrl) {
169
+ const url = httpStreamUrl ?? "http://localhost:8080/mcp";
170
+ const transport = new StreamableHTTPClientTransport(new URL(url));
171
+ const client = new Client({ name: serverName, version: "1.0.0" });
172
+ await client.connect(transport);
173
+ const callTool = async (name, args) => {
174
+ const result = await client.callTool({ name, arguments: args });
175
+ return toolResultPayload(result);
176
+ };
177
+ const close = async () => {
178
+ await transport.close();
179
+ };
180
+ return buildAdapters(callTool, close, client, null);
181
+ }
72
182
  console.log("mcp server cwd", process.env.MCP_SERVER_CWD);
73
183
  const serverCwd = options?.serverCwd ??
74
184
  process.env.MCP_SERVER_CWD ??
@@ -100,83 +210,11 @@ export const createMcpUseAdapters = async (options) => {
100
210
  const result = await session.callTool(name, args);
101
211
  return toolResultPayload(result);
102
212
  };
103
- const executor = new MCPQueryExecutor({
104
- runQuery: async (sql) => {
105
- const content = await callTool("run_query", { raw_query: sql });
106
- const text = contentToText(content);
107
- const durationMs = parseDurationMs(text) ?? Number.NaN; // Keep numeric for typing even if parse fails.
108
- return { durationMs };
109
- },
110
- runQueryExplain: async (sql) => runQueryExplain(sql),
111
- });
112
- const retriever = new MCPMilvusRetriever({
113
- milvusRetrieve: async (query, topK) => callTool("milvus_retrieve", { query, top_k: topK ?? 5 }),
114
- });
115
- const runQueryExplain = async (sql) => {
116
- const content = await callTool("run_query_explain", { raw_query: sql });
117
- const text = contentToText(content);
118
- const durationMs = parseDurationMs(text) ?? Number.NaN;
119
- const jsonStart = text.search(/JSON:\s*/i);
120
- const explainMarker = text.search(/EXPLAIN ANALYZE:\s*/i);
121
- let parsed = null;
122
- if (jsonStart !== -1) {
123
- const jsonBlock = text
124
- .slice(jsonStart)
125
- .replace(/^JSON:\s*/i, "");
126
- const jsonText = explainMarker !== -1
127
- ? jsonBlock.slice(0, explainMarker - jsonStart).trim()
128
- : jsonBlock.trim();
129
- try {
130
- parsed = JSON.parse(jsonText);
131
- }
132
- catch {
133
- parsed = null;
134
- }
135
- }
136
- if (parsed) {
137
- const explainLines = Array.isArray(parsed.explainLines)
138
- ? parsed.explainLines.map((line) => String(line))
139
- : undefined;
140
- return {
141
- durationMs: typeof parsed.durationMs === "number"
142
- ? parsed.durationMs
143
- : durationMs,
144
- explainLines,
145
- planningMs: typeof parsed.planningMs === "number"
146
- ? parsed.planningMs
147
- : undefined,
148
- executionMs: typeof parsed.executionMs === "number"
149
- ? parsed.executionMs
150
- : undefined,
151
- raw: text,
152
- };
153
- }
154
- const explainSplit = text.split(/EXPLAIN ANALYZE:\s*/i)[1] ??
155
- text.split(/EXPLAIN:\s*/i)[1] ??
156
- "";
157
- return { durationMs, explain: explainSplit.trim(), raw: text };
158
- };
159
- const listIndexes = async (options) => {
160
- const content = await callTool("list_indexes", {
161
- schemaname: options?.schemaname,
162
- tablename: options?.tablename,
163
- });
164
- return contentToText(content);
165
- };
166
213
  const close = async () => {
167
214
  if (typeof client
168
215
  .closeAllSessions === "function") {
169
216
  await client.closeAllSessions();
170
217
  }
171
218
  };
172
- return {
173
- executor,
174
- retriever,
175
- callTool,
176
- runQueryExplain,
177
- listIndexes,
178
- close,
179
- client,
180
- session,
181
- };
219
+ return buildAdapters(callTool, close, client, session);
182
220
  };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export * from "./types.js";
2
2
  export * from "./pipeline.js";
3
3
  export * from "./defaults/promptComposer.js";
4
4
  export * from "./adapters/mcpExecutor.js";
5
- export * from "./adapters/mcpRetriever.js";
5
+ export * from "./adapters/milvusRetriever.js";
6
6
  export * from "./adapters/openaiLLM.js";
7
7
  export * from "./clients/mcpUseClient.js";
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -2,6 +2,6 @@ export * from "./types.js";
2
2
  export * from "./pipeline.js";
3
3
  export * from "./defaults/promptComposer.js";
4
4
  export * from "./adapters/mcpExecutor.js";
5
- export * from "./adapters/mcpRetriever.js";
5
+ export * from "./adapters/milvusRetriever.js";
6
6
  export * from "./adapters/openaiLLM.js";
7
7
  export * from "./clients/mcpUseClient.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neural-technologies-indonesia/nl2sql",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Core reasoning pipeline for text-to-SQL.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -24,7 +24,9 @@
24
24
  "test": "echo \"no tests yet\""
25
25
  },
26
26
  "dependencies": {
27
+ "@zilliz/milvus2-sdk-node": "2.3.5",
27
28
  "langchain": "^1.2.1",
29
+ "@modelcontextprotocol/sdk": "^1.25.1",
28
30
  "mcp-use": "*",
29
31
  "openai": "^6.14.0"
30
32
  },