gcharanteja commited on
Commit
b008b07
·
1 Parent(s): a964d2d

feat: add intelligent routing functionality with natural language processing for knowledge management

Browse files
Files changed (4) hide show
  1. server.js +5 -1
  2. services/curation.js +21 -0
  3. services/router.js +154 -0
  4. tools/routerTool.js +62 -0
server.js CHANGED
@@ -9,6 +9,7 @@ import {
9
  } from "@modelcontextprotocol/sdk/types.js";
10
  import { vectorTools, handleVectorTool } from "./tools/vectorTool.js";
11
  import { curateTools, handleCurateTool } from "./tools/curateTool.js";
 
12
 
13
  const PORT = Number(process.env.PORT || 3000);
14
  const HOST = process.env.HOST || "0.0.0.0";
@@ -45,12 +46,15 @@ function createMcpServer() {
45
  );
46
 
47
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
48
- tools: [...curateTools, ...vectorTools],
49
  }));
50
 
51
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
52
  const { name, arguments: args = {} } = request.params;
53
 
 
 
 
54
  const curateResult = await handleCurateTool(name, args, config);
55
  if (curateResult) return curateResult;
56
 
 
9
  } from "@modelcontextprotocol/sdk/types.js";
10
  import { vectorTools, handleVectorTool } from "./tools/vectorTool.js";
11
  import { curateTools, handleCurateTool } from "./tools/curateTool.js";
12
+ import { routerTools, handleRouterTool } from "./tools/routerTool.js";
13
 
14
  const PORT = Number(process.env.PORT || 3000);
15
  const HOST = process.env.HOST || "0.0.0.0";
 
46
  );
47
 
48
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
49
+ tools: [...routerTools, ...curateTools, ...vectorTools],
50
  }));
51
 
52
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
53
  const { name, arguments: args = {} } = request.params;
54
 
55
+ const routerResult = await handleRouterTool(name, args, config);
56
+ if (routerResult) return routerResult;
57
+
58
  const curateResult = await handleCurateTool(name, args, config);
59
  if (curateResult) return curateResult;
60
 
services/curation.js CHANGED
@@ -2,6 +2,7 @@ import fs from "fs-extra";
2
  import path from "path";
3
  import matter from "gray-matter";
4
  import { askOpenAI } from "./llm.js";
 
5
 
6
  export async function curateText(rawText, config) {
7
  const OUTPUT_DIR = config.OUTPUT_DIR || "/data/context-tree";
@@ -36,6 +37,26 @@ RETURN THIS JSON (no markdown, no explanation):
36
  OUTPUT_DIR,
37
  );
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  return {
40
  id,
41
  title: parsed.title,
 
2
  import path from "path";
3
  import matter from "gray-matter";
4
  import { askOpenAI } from "./llm.js";
5
+ import { addDocuments } from "./vector.js";
6
 
7
  export async function curateText(rawText, config) {
8
  const OUTPUT_DIR = config.OUTPUT_DIR || "/data/context-tree";
 
37
  OUTPUT_DIR,
38
  );
39
 
40
+ try {
41
+ await addDocuments({
42
+ documents: [
43
+ {
44
+ id,
45
+ text: `${parsed.title}\n\n${parsed.content}`,
46
+ metadata: {
47
+ title: parsed.title,
48
+ topic: parsed.topic,
49
+ type: parsed.type,
50
+ filePath,
51
+ summary: parsed.summary,
52
+ },
53
+ },
54
+ ],
55
+ });
56
+ } catch (err) {
57
+ console.error("Warning: Failed to add to ChromaDB:", err.message);
58
+ }
59
+
60
  return {
61
  id,
62
  title: parsed.title,
services/router.js ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { askOpenAI } from "./llm.js";
2
+ import { curateText } from "./curation.js";
3
+ import { queryCollectionDataEnhanced, queryCollectionData } from "./vector.js";
4
+ import { loadContextTreeDocuments } from "../utils.js";
5
+
6
+ export async function routeUserInput(input, config) {
7
+ try {
8
+ const intent = await classifyIntent(input, config);
9
+
10
+ switch (intent.type) {
11
+ case "search":
12
+ return await handleSearch(input, intent, config);
13
+
14
+ case "curate":
15
+ return await handleCurate(input, intent, config);
16
+
17
+ case "add":
18
+ return await handleAdd(input, intent, config);
19
+
20
+ case "list":
21
+ return await handleList(input, intent, config);
22
+
23
+ default:
24
+ return {
25
+ error: "Could not determine intent",
26
+ input,
27
+ intent: intent.type,
28
+ };
29
+ }
30
+ } catch (error) {
31
+ return {
32
+ error: `Routing failed: ${error.message}`,
33
+ input,
34
+ };
35
+ }
36
+ }
37
+
38
+ async function classifyIntent(input, config) {
39
+ const prompt = `Analyze this user input and classify the intent. Return ONLY valid JSON:
40
+
41
+ Input: "${input}"
42
+
43
+ Classify as one of these intents:
44
+ - "search": User is asking questions to find knowledge
45
+ - "curate": User is teaching/storing new knowledge
46
+ - "add": User is adding documents to the knowledge base
47
+ - "list": User wants to see all stored knowledge
48
+
49
+ Response format (valid JSON only):
50
+ {
51
+ "type": "search|curate|add|list",
52
+ "confidence": 0.0-1.0,
53
+ "reasoning": "brief explanation",
54
+ "hints": {
55
+ "enhanced": false,
56
+ "rerank": false,
57
+ "extractText": null
58
+ }
59
+ }`;
60
+
61
+ try {
62
+ const response = await askOpenAI(input, prompt);
63
+ const parsed = JSON.parse(response);
64
+ return parsed;
65
+ } catch (error) {
66
+ console.error("Intent classification failed:", error.message);
67
+ return { type: "search", confidence: 0.5, reasoning: "Fallback to search" };
68
+ }
69
+ }
70
+
71
+ async function handleSearch(input, intent, config) {
72
+ try {
73
+ const searchParams = {
74
+ query: input,
75
+ nResults: 5,
76
+ enhanced: intent.hints?.enhanced || false,
77
+ rerank: intent.hints?.rerank || false,
78
+ };
79
+
80
+ const results = searchParams.enhanced
81
+ ? await queryCollectionDataEnhanced(searchParams)
82
+ : await queryCollectionData(searchParams);
83
+
84
+ return {
85
+ intent: "search",
86
+ query: input,
87
+ searchStrategy: {
88
+ enhanced: searchParams.enhanced,
89
+ rerank: searchParams.rerank,
90
+ },
91
+ results,
92
+ };
93
+ } catch (error) {
94
+ return {
95
+ intent: "search",
96
+ error: `Search failed: ${error.message}`,
97
+ query: input,
98
+ };
99
+ }
100
+ }
101
+
102
+ async function handleCurate(input, intent, config) {
103
+ try {
104
+ const textToCurate = intent.hints?.extractText || input;
105
+
106
+ const result = await curateText(textToCurate, config);
107
+
108
+ return {
109
+ intent: "curate",
110
+ action: "extracted_and_stored",
111
+ result,
112
+ };
113
+ } catch (error) {
114
+ return {
115
+ intent: "curate",
116
+ error: `Curation failed: ${error.message}`,
117
+ input,
118
+ };
119
+ }
120
+ }
121
+
122
+ async function handleAdd(input, intent, config) {
123
+ return {
124
+ intent: "add",
125
+ message:
126
+ "To add documents, use the add_documents tool with an array of {id, text, metadata}",
127
+ guidance: input,
128
+ };
129
+ }
130
+
131
+ async function handleList(input, intent, config) {
132
+ try {
133
+ const dataDir = process.env.DATA_DIR || "/data";
134
+ const docs = await loadContextTreeDocuments(dataDir);
135
+
136
+ return {
137
+ intent: "list",
138
+ count: docs.length,
139
+ documents: docs.map((d) => ({
140
+ id: d.id,
141
+ title: d.title,
142
+ topic: d.topic,
143
+ type: d.type,
144
+ importance: d.importance,
145
+ filePath: d.filePath,
146
+ })),
147
+ };
148
+ } catch (error) {
149
+ return {
150
+ intent: "list",
151
+ error: `List failed: ${error.message}`,
152
+ };
153
+ }
154
+ }
tools/routerTool.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { routeUserInput } from "../services/router.js";
2
+
3
+ const intelligentRouterToolDefinition = {
4
+ name: "intelligent_router",
5
+ description:
6
+ "Natural language interface to the knowledge base. Ask questions, teach knowledge, or manage documents. LLM automatically understands intent and routes to the right operation (search, curate, add, or list).",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ input: {
11
+ type: "string",
12
+ description:
13
+ "Your natural language question, statement, or command about the knowledge base.",
14
+ },
15
+ },
16
+ required: ["input"],
17
+ additionalProperties: false,
18
+ },
19
+ };
20
+
21
+ export const routerTools = [intelligentRouterToolDefinition];
22
+
23
+ export async function handleRouterTool(name, args, config) {
24
+ if (name === "intelligent_router") {
25
+ try {
26
+ if (!args.input || typeof args.input !== "string") {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: "Error: 'input' field is required and must be a string",
32
+ },
33
+ ],
34
+ isError: true,
35
+ };
36
+ }
37
+
38
+ const result = await routeUserInput(args.input, config);
39
+
40
+ return {
41
+ content: [
42
+ {
43
+ type: "text",
44
+ text: JSON.stringify(result, null, 2),
45
+ },
46
+ ],
47
+ };
48
+ } catch (err) {
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: `Router error: ${err.message}`,
54
+ },
55
+ ],
56
+ isError: true,
57
+ };
58
+ }
59
+ }
60
+
61
+ return null;
62
+ }