@sweetoburrito/backstage-plugin-ai-assistant-backend 0.9.0 → 0.10.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/config.d.ts +8 -0
- package/dist/constants/prompts.cjs.js +3 -1
- package/dist/constants/prompts.cjs.js.map +1 -1
- package/dist/database/chat-store.cjs.js +22 -2
- package/dist/database/chat-store.cjs.js.map +1 -1
- package/dist/database/mcp-store.cjs.js +46 -0
- package/dist/database/mcp-store.cjs.js.map +1 -0
- package/dist/plugin.cjs.js +26 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/services/callbacks.cjs.js +49 -0
- package/dist/services/callbacks.cjs.js.map +1 -0
- package/dist/services/chat.cjs.js +73 -34
- package/dist/services/chat.cjs.js.map +1 -1
- package/dist/services/mcp.cjs.js +119 -0
- package/dist/services/mcp.cjs.js.map +1 -0
- package/dist/services/router/chat.cjs.js +23 -3
- package/dist/services/router/chat.cjs.js.map +1 -1
- package/dist/services/router/index.cjs.js +4 -0
- package/dist/services/router/index.cjs.js.map +1 -1
- package/dist/services/router/mcp.cjs.js +81 -0
- package/dist/services/router/mcp.cjs.js.map +1 -0
- package/dist/services/router/summary.cjs.js +56 -0
- package/dist/services/router/summary.cjs.js.map +1 -0
- package/dist/services/summarizer.cjs.js +38 -14
- package/dist/services/summarizer.cjs.js.map +1 -1
- package/migrations/20251029_mcp.js +42 -0
- package/migrations/20251029_score.js +52 -0
- package/package.json +6 -3
package/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HumanDuration } from '@backstage/types';
|
|
2
2
|
import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
|
|
3
|
+
import { McpServerConfig } from '@sweetoburrito/backstage-plugin-ai-assistant-common';
|
|
3
4
|
|
|
4
5
|
export interface Config {
|
|
5
6
|
aiAssistant: {
|
|
@@ -28,5 +29,12 @@ export interface Config {
|
|
|
28
29
|
ingestion?: {
|
|
29
30
|
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
|
|
30
31
|
};
|
|
32
|
+
mcp: {
|
|
33
|
+
/**
|
|
34
|
+
* @visibility secret
|
|
35
|
+
*/
|
|
36
|
+
encryptionKey: string;
|
|
37
|
+
servers?: Array<McpServerConfig>;
|
|
38
|
+
};
|
|
31
39
|
};
|
|
32
40
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const DEFAULT_CONVERSATION_SUMMARY_PROMPT = "Summarize this conversation in a concise manner. The summary should capture the main points. Return the summary only, without any additional text. Do not include any introductions or other part of the conversation that doesn't contribute to the summary or form part of the overall conversation as part of the summary.";
|
|
4
|
+
const DEFAULT_SUMMARY_PROMPT = "Summarize the following content in a concise manner. The summary should capture the main points. Return the summary only, without any additional text. Do not include any introductions or other part of the content that doesn't contribute to the summary.";
|
|
4
5
|
const DEFAULT_IDENTITY_PROMPT = `
|
|
5
6
|
You are a helpful assistant that answers questions based on provided context from various documents. The context may come from sources such as internal wikis, code repositories, technical documentation, or other structured or unstructured data.
|
|
6
7
|
`;
|
|
@@ -35,6 +36,7 @@ TOOL USAGE GUIDELINES:
|
|
|
35
36
|
- If a tool fails, try an alternative approach before using another tool
|
|
36
37
|
`;
|
|
37
38
|
|
|
39
|
+
exports.DEFAULT_CONVERSATION_SUMMARY_PROMPT = DEFAULT_CONVERSATION_SUMMARY_PROMPT;
|
|
38
40
|
exports.DEFAULT_FORMATTING_PROMPT = DEFAULT_FORMATTING_PROMPT;
|
|
39
41
|
exports.DEFAULT_IDENTITY_PROMPT = DEFAULT_IDENTITY_PROMPT;
|
|
40
42
|
exports.DEFAULT_SUMMARY_PROMPT = DEFAULT_SUMMARY_PROMPT;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.cjs.js","sources":["../../src/constants/prompts.ts"],"sourcesContent":["export const
|
|
1
|
+
{"version":3,"file":"prompts.cjs.js","sources":["../../src/constants/prompts.ts"],"sourcesContent":["export const DEFAULT_CONVERSATION_SUMMARY_PROMPT =\n \"Summarize this conversation in a concise manner. The summary should capture the main points. Return the summary only, without any additional text. Do not include any introductions or other part of the conversation that doesn't contribute to the summary or form part of the overall conversation as part of the summary.\";\n\nexport const DEFAULT_SUMMARY_PROMPT =\n \"Summarize the following content in a concise manner. The summary should capture the main points. Return the summary only, without any additional text. Do not include any introductions or other part of the content that doesn't contribute to the summary.\";\n\nexport const DEFAULT_IDENTITY_PROMPT = `\nYou are a helpful assistant that answers questions based on provided context from various documents. The context may come from sources such as internal wikis, code repositories, technical documentation, or other structured or unstructured data.\n`;\n\nexport const DEFAULT_FORMATTING_PROMPT = `\nCRITICAL FORMATTING RULES - MUST ALWAYS FOLLOW:\n1. **ALWAYS use proper markdown formatting in ALL responses**\n2. **NEVER output plain URLs** - ALWAYS convert them to clickable markdown links using [description](url) syntax\n3. **For images, ALWAYS use markdown image syntax**: \n4. **For all URLs, ALWAYS format as**: [descriptive text](url) - never just paste the raw URL\n5. Use headings (##, ###), bullet points, numbered lists, and **bold**/*italic* text appropriately\n6. Format code with backticks: \\`inline code\\` or \\`\\`\\`language for code blocks\n7. Structure responses clearly with proper spacing and organization\n`;\n\nexport const DEFAULT_SYSTEM_PROMPT = `\nContent Rules:\n1. Always base your answers on the provided context. Do not make up information.\n2. When relevant, cite or reference the source information provided in the context.\n3. Maintain a professional, friendly, and helpful tone.\n4. Return only the relevant information without any filler or unnecessary details.\n5. If you don't know the answer, admit it and suggest ways to find the information.\n6. **Actively use available tools** to enhance your responses\n7. Adapt your approach based on the specific tools and capabilities available in the current session\n8. When you do not have the information needed to answer, use the tools provided to gather more context before responding.\n`;\n\nexport const DEFAULT_TOOL_GUIDELINE = `\nTOOL USAGE GUIDELINES:\n- Only use tools when explicitly needed to answer the user's question\n- Read tool descriptions carefully before using them\n- If you can answer without tools, do so\n- IMPORTANT: When using tools, always explain why you're using each tool\n- Use tools in logical sequence, not randomly\n- If a tool fails, try an alternative approach before using another tool\n`;\n"],"names":[],"mappings":";;AAAO,MAAM,mCAAA,GACX;AAEK,MAAM,sBAAA,GACX;AAEK,MAAM,uBAAA,GAA0B;AAAA;AAAA;AAIhC,MAAM,yBAAA,GAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWlC,MAAM,qBAAA,GAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAY9B,MAAM,sBAAA,GAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;;;;"}
|
|
@@ -33,7 +33,9 @@ class ChatStore {
|
|
|
33
33
|
role: row.role,
|
|
34
34
|
content: row.content,
|
|
35
35
|
id: row.id,
|
|
36
|
-
metadata: row.metadata
|
|
36
|
+
metadata: row.metadata,
|
|
37
|
+
score: row.score,
|
|
38
|
+
traceId: row.trace_id
|
|
37
39
|
}));
|
|
38
40
|
return chatMessages;
|
|
39
41
|
}
|
|
@@ -44,6 +46,7 @@ class ChatStore {
|
|
|
44
46
|
role: msg.role,
|
|
45
47
|
content: msg.content,
|
|
46
48
|
metadata: msg.metadata,
|
|
49
|
+
trace_id: msg.traceId,
|
|
47
50
|
userRef,
|
|
48
51
|
created_at: this.client.fn.now()
|
|
49
52
|
}));
|
|
@@ -53,7 +56,9 @@ class ChatStore {
|
|
|
53
56
|
await this.messageTable().where({ id: message.id }).update({
|
|
54
57
|
role: message.role,
|
|
55
58
|
content: message.content,
|
|
56
|
-
metadata: message.metadata
|
|
59
|
+
metadata: message.metadata,
|
|
60
|
+
score: message.score,
|
|
61
|
+
trace_id: message.traceId
|
|
57
62
|
});
|
|
58
63
|
}
|
|
59
64
|
async getConversation(conversationId, userRef) {
|
|
@@ -90,6 +95,21 @@ class ChatStore {
|
|
|
90
95
|
}));
|
|
91
96
|
return conversations;
|
|
92
97
|
}
|
|
98
|
+
async getMessageById(messageId) {
|
|
99
|
+
const row = await this.messageTable().where({ id: messageId }).first();
|
|
100
|
+
if (!row) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const message = {
|
|
104
|
+
id: row.id,
|
|
105
|
+
role: row.role,
|
|
106
|
+
content: row.content,
|
|
107
|
+
metadata: row.metadata,
|
|
108
|
+
score: row.score,
|
|
109
|
+
traceId: row.trace_id
|
|
110
|
+
};
|
|
111
|
+
return message;
|
|
112
|
+
}
|
|
93
113
|
}
|
|
94
114
|
|
|
95
115
|
exports.ChatStore = ChatStore;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-store.cjs.js","sources":["../../src/database/chat-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport {\n Message,\n Conversation,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { Knex } from 'knex';\n\nconst MESSAGE_TABLE_NAME = 'message';\nconst CONVERSATION_TABLE_NAME = 'conversation';\n\nexport type ChatStoreOptions = {\n database: DatabaseService;\n};\n\nexport class ChatStore {\n /**\n * Creates an instance of ChatStore.\n * @param client - The Knex client to interact with the PostgreSQL database.\n */\n constructor(private readonly client: Knex) {}\n\n static async fromConfig({ database }: ChatStoreOptions) {\n const client = await database.getClient();\n return new ChatStore(client);\n }\n\n messageTable() {\n return this.client(MESSAGE_TABLE_NAME);\n }\n\n conversationTable() {\n return this.client(CONVERSATION_TABLE_NAME);\n }\n\n async getChatMessages(\n conversationId: string,\n userRef: string,\n limit?: number,\n excludeRoles?: Message['role'][],\n ): Promise<Required<Message>[]> {\n let query = this.messageTable()\n .where({ conversation_id: conversationId, userRef })\n .select('*')\n .orderBy('created_at', 'asc');\n\n if (typeof limit === 'number') {\n query = query.limit(limit).orderBy('created_at', 'desc');\n }\n\n if (excludeRoles && excludeRoles.length > 0) {\n query = query.whereNotIn('role', excludeRoles);\n }\n\n const rows = await query;\n\n const chatMessages: Required<Message>[] = rows.map(row => ({\n role: row.role,\n content: row.content,\n id: row.id,\n metadata: row.metadata,\n }));\n\n return chatMessages;\n }\n\n async addChatMessage(\n messages: Message[],\n userRef: string,\n conversationId: string,\n ): Promise<void> {\n const rows = messages.map(msg => ({\n id: msg.id,\n conversation_id: conversationId,\n role: msg.role,\n content: msg.content,\n metadata: msg.metadata,\n userRef,\n created_at: this.client.fn.now(),\n }));\n\n await this.messageTable().insert(rows);\n }\n\n async updateMessage(message: Required<Message>) {\n await this.messageTable().where({ id: message.id }).update({\n role: message.role,\n content: message.content,\n metadata: message.metadata,\n });\n }\n\n async getConversation(\n conversationId: string,\n userRef: string,\n ): Promise<Conversation> {\n const row = await this.conversationTable()\n .where({ id: conversationId, userRef })\n .first();\n\n if (!row) {\n throw new Error('Conversation not found');\n }\n\n const conversation: Conversation = {\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n };\n\n return conversation;\n }\n\n async createConversation(conversation: Conversation) {\n await this.conversationTable().insert({\n id: conversation.id,\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async updateConversation(conversation: Conversation) {\n await this.conversationTable().where({ id: conversation.id }).update({\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async getConversations(userRef: string): Promise<Conversation[]> {\n const rows = await this.conversationTable()\n .where({ userRef })\n .select('*')\n .orderBy('created_at', 'desc');\n\n const conversations: Conversation[] = rows.map(row => ({\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n }));\n\n return conversations;\n }\n}\n"],"names":[],"mappings":";;AAQA,MAAM,kBAAA,GAAqB,SAAA;AAC3B,MAAM,uBAAA,GAA0B,cAAA;AAMzB,MAAM,SAAA,CAAU;AAAA;AAAA;AAAA;AAAA;AAAA,EAKrB,YAA6B,MAAA,EAAc;AAAd,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAe;AAAA,EAE5C,aAAa,UAAA,CAAW,EAAE,QAAA,EAAS,EAAqB;AACtD,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,SAAA,EAAU;AACxC,IAAA,OAAO,IAAI,UAAU,MAAM,CAAA;AAAA,EAC7B;AAAA,EAEA,YAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,OAAO,kBAAkB,CAAA;AAAA,EACvC;AAAA,EAEA,iBAAA,GAAoB;AAClB,IAAA,OAAO,IAAA,CAAK,OAAO,uBAAuB,CAAA;AAAA,EAC5C;AAAA,EAEA,MAAM,eAAA,CACJ,cAAA,EACA,OAAA,EACA,OACA,YAAA,EAC8B;AAC9B,IAAA,IAAI,QAAQ,IAAA,CAAK,YAAA,EAAa,CAC3B,KAAA,CAAM,EAAE,eAAA,EAAiB,cAAA,EAAgB,OAAA,EAAS,EAClD,MAAA,CAAO,GAAG,CAAA,CACV,OAAA,CAAQ,cAAc,KAAK,CAAA;AAE9B,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,KAAA,GAAQ,MAAM,KAAA,CAAM,KAAK,CAAA,CAAE,OAAA,CAAQ,cAAc,MAAM,CAAA;AAAA,IACzD;AAEA,IAAA,IAAI,YAAA,IAAgB,YAAA,CAAa,MAAA,GAAS,CAAA,EAAG;AAC3C,MAAA,KAAA,GAAQ,KAAA,CAAM,UAAA,CAAW,MAAA,EAAQ,YAAY,CAAA;AAAA,IAC/C;AAEA,IAAA,MAAM,OAAO,MAAM,KAAA;AAEnB,IAAA,MAAM,YAAA,GAAoC,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,MAAQ;AAAA,MACzD,MAAM,GAAA,CAAI,IAAA;AAAA,MACV,SAAS,GAAA,CAAI,OAAA;AAAA,MACb,IAAI,GAAA,CAAI,EAAA;AAAA,MACR,UAAU,GAAA,CAAI;AAAA,
|
|
1
|
+
{"version":3,"file":"chat-store.cjs.js","sources":["../../src/database/chat-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport {\n Message,\n Conversation,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { Knex } from 'knex';\n\nconst MESSAGE_TABLE_NAME = 'message';\nconst CONVERSATION_TABLE_NAME = 'conversation';\n\nexport type ChatStoreOptions = {\n database: DatabaseService;\n};\n\nexport class ChatStore {\n /**\n * Creates an instance of ChatStore.\n * @param client - The Knex client to interact with the PostgreSQL database.\n */\n constructor(private readonly client: Knex) {}\n\n static async fromConfig({ database }: ChatStoreOptions) {\n const client = await database.getClient();\n return new ChatStore(client);\n }\n\n messageTable() {\n return this.client(MESSAGE_TABLE_NAME);\n }\n\n conversationTable() {\n return this.client(CONVERSATION_TABLE_NAME);\n }\n\n async getChatMessages(\n conversationId: string,\n userRef: string,\n limit?: number,\n excludeRoles?: Message['role'][],\n ): Promise<Required<Message>[]> {\n let query = this.messageTable()\n .where({ conversation_id: conversationId, userRef })\n .select('*')\n .orderBy('created_at', 'asc');\n\n if (typeof limit === 'number') {\n query = query.limit(limit).orderBy('created_at', 'desc');\n }\n\n if (excludeRoles && excludeRoles.length > 0) {\n query = query.whereNotIn('role', excludeRoles);\n }\n\n const rows = await query;\n\n const chatMessages: Required<Message>[] = rows.map(row => ({\n role: row.role,\n content: row.content,\n id: row.id,\n metadata: row.metadata,\n score: row.score,\n traceId: row.trace_id,\n }));\n\n return chatMessages;\n }\n\n async addChatMessage(\n messages: Message[],\n userRef: string,\n conversationId: string,\n ): Promise<void> {\n const rows = messages.map(msg => ({\n id: msg.id,\n conversation_id: conversationId,\n role: msg.role,\n content: msg.content,\n metadata: msg.metadata,\n trace_id: msg.traceId,\n userRef,\n created_at: this.client.fn.now(),\n }));\n\n await this.messageTable().insert(rows);\n }\n\n async updateMessage(message: Required<Message>) {\n await this.messageTable().where({ id: message.id }).update({\n role: message.role,\n content: message.content,\n metadata: message.metadata,\n score: message.score,\n trace_id: message.traceId,\n });\n }\n\n async getConversation(\n conversationId: string,\n userRef: string,\n ): Promise<Conversation> {\n const row = await this.conversationTable()\n .where({ id: conversationId, userRef })\n .first();\n\n if (!row) {\n throw new Error('Conversation not found');\n }\n\n const conversation: Conversation = {\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n };\n\n return conversation;\n }\n\n async createConversation(conversation: Conversation) {\n await this.conversationTable().insert({\n id: conversation.id,\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async updateConversation(conversation: Conversation) {\n await this.conversationTable().where({ id: conversation.id }).update({\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async getConversations(userRef: string): Promise<Conversation[]> {\n const rows = await this.conversationTable()\n .where({ userRef })\n .select('*')\n .orderBy('created_at', 'desc');\n\n const conversations: Conversation[] = rows.map(row => ({\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n }));\n\n return conversations;\n }\n\n async getMessageById(messageId: string): Promise<Required<Message> | null> {\n const row = await this.messageTable().where({ id: messageId }).first();\n\n if (!row) {\n return null;\n }\n\n const message: Required<Message> = {\n id: row.id,\n role: row.role,\n content: row.content,\n metadata: row.metadata,\n score: row.score,\n traceId: row.trace_id,\n };\n\n return message;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MCP_TABLE_NAME = "user_mcp_config";
|
|
4
|
+
class McpStore {
|
|
5
|
+
/**
|
|
6
|
+
* Creates an instance of ChatStore.
|
|
7
|
+
* @param client - The Knex client to interact with the PostgreSQL database.
|
|
8
|
+
*/
|
|
9
|
+
constructor(client) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
}
|
|
12
|
+
static async fromConfig({ database }) {
|
|
13
|
+
const client = await database.getClient();
|
|
14
|
+
return new McpStore(client);
|
|
15
|
+
}
|
|
16
|
+
mcpTable() {
|
|
17
|
+
return this.client(MCP_TABLE_NAME);
|
|
18
|
+
}
|
|
19
|
+
async getUserUserMcpConfigNames(userRef) {
|
|
20
|
+
const rows = await this.mcpTable().where({ userRef }).select("name");
|
|
21
|
+
return rows.map((row) => row.name);
|
|
22
|
+
}
|
|
23
|
+
async getUserMcpConfigs(userRef) {
|
|
24
|
+
const rows = await this.mcpTable().where({ userRef }).select();
|
|
25
|
+
return rows.map((row) => ({
|
|
26
|
+
name: row.name,
|
|
27
|
+
encryptedOptions: row.encryptedOptions
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
async createUserMcpConfig(userRef, name, encryptedOptions) {
|
|
31
|
+
await this.mcpTable().insert({
|
|
32
|
+
userRef,
|
|
33
|
+
name,
|
|
34
|
+
encryptedOptions
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async updateUserMcpConfig(userRef, name, encryptedOptions) {
|
|
38
|
+
await this.mcpTable().where({ userRef, name }).update({ encryptedOptions });
|
|
39
|
+
}
|
|
40
|
+
async deleteUserMcpConfig(userRef, name) {
|
|
41
|
+
await this.mcpTable().where({ userRef, name }).delete();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
exports.McpStore = McpStore;
|
|
46
|
+
//# sourceMappingURL=mcp-store.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-store.cjs.js","sources":["../../src/database/mcp-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\n\nimport { Knex } from 'knex';\n\nconst MCP_TABLE_NAME = 'user_mcp_config';\n\nexport type McpStoreOptions = {\n database: DatabaseService;\n};\n\nexport class McpStore {\n /**\n * Creates an instance of ChatStore.\n * @param client - The Knex client to interact with the PostgreSQL database.\n */\n constructor(private readonly client: Knex) {}\n\n static async fromConfig({ database }: McpStoreOptions) {\n const client = await database.getClient();\n return new McpStore(client);\n }\n\n mcpTable() {\n return this.client(MCP_TABLE_NAME);\n }\n\n async getUserUserMcpConfigNames(userRef: string): Promise<string[]> {\n const rows = await this.mcpTable().where({ userRef }).select('name');\n return rows.map(row => row.name);\n }\n\n async getUserMcpConfigs(\n userRef: string,\n ): Promise<Array<{ name: string; encryptedOptions: string }>> {\n const rows = await this.mcpTable().where({ userRef }).select();\n return rows.map(row => ({\n name: row.name,\n encryptedOptions: row.encryptedOptions,\n }));\n }\n\n async createUserMcpConfig(\n userRef: string,\n name: string,\n encryptedOptions: string,\n ): Promise<void> {\n await this.mcpTable().insert({\n userRef,\n name,\n encryptedOptions,\n });\n }\n\n async updateUserMcpConfig(\n userRef: string,\n name: string,\n encryptedOptions: string,\n ): Promise<void> {\n await this.mcpTable().where({ userRef, name }).update({ encryptedOptions });\n }\n\n async deleteUserMcpConfig(userRef: string, name: string): Promise<void> {\n await this.mcpTable().where({ userRef, name }).delete();\n }\n}\n"],"names":[],"mappings":";;AAIA,MAAM,cAAA,GAAiB,iBAAA;AAMhB,MAAM,QAAA,CAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,YAA6B,MAAA,EAAc;AAAd,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAe;AAAA,EAE5C,aAAa,UAAA,CAAW,EAAE,QAAA,EAAS,EAAoB;AACrD,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,SAAA,EAAU;AACxC,IAAA,OAAO,IAAI,SAAS,MAAM,CAAA;AAAA,EAC5B;AAAA,EAEA,QAAA,GAAW;AACT,IAAA,OAAO,IAAA,CAAK,OAAO,cAAc,CAAA;AAAA,EACnC;AAAA,EAEA,MAAM,0BAA0B,OAAA,EAAoC;AAClE,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,CAAA,CAAE,MAAA,CAAO,MAAM,CAAA;AACnE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,KAAO,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA,EAEA,MAAM,kBACJ,OAAA,EAC4D;AAC5D,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,MAAM,EAAE,OAAA,EAAS,CAAA,CAAE,MAAA,EAAO;AAC7D,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,GAAA,MAAQ;AAAA,MACtB,MAAM,GAAA,CAAI,IAAA;AAAA,MACV,kBAAkB,GAAA,CAAI;AAAA,KACxB,CAAE,CAAA;AAAA,EACJ;AAAA,EAEA,MAAM,mBAAA,CACJ,OAAA,EACA,IAAA,EACA,gBAAA,EACe;AACf,IAAA,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,MAAA,CAAO;AAAA,MAC3B,OAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,mBAAA,CACJ,OAAA,EACA,IAAA,EACA,gBAAA,EACe;AACf,IAAA,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAA,CAAO,EAAE,gBAAA,EAAkB,CAAA;AAAA,EAC5E;AAAA,EAEA,MAAM,mBAAA,CAAoB,OAAA,EAAiB,IAAA,EAA6B;AACtE,IAAA,MAAM,IAAA,CAAK,UAAS,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO;AAAA,EACxD;AACF;;;;"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -10,6 +10,9 @@ var pgVectorStore = require('./database/pg-vector-store.cjs.js');
|
|
|
10
10
|
var pluginSignalsNode = require('@backstage/plugin-signals-node');
|
|
11
11
|
var searchKnowledge = require('./services/tools/searchKnowledge.cjs.js');
|
|
12
12
|
var pluginCatalogNode = require('@backstage/plugin-catalog-node');
|
|
13
|
+
var mcp = require('./services/mcp.cjs.js');
|
|
14
|
+
var callbacks = require('./services/callbacks.cjs.js');
|
|
15
|
+
var summarizer = require('./services/summarizer.cjs.js');
|
|
13
16
|
|
|
14
17
|
const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
|
|
15
18
|
pluginId: "ai-assistant",
|
|
@@ -17,6 +20,7 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
|
|
|
17
20
|
const ingestors = [];
|
|
18
21
|
const models = [];
|
|
19
22
|
const tools = [];
|
|
23
|
+
const callbacks$1 = [];
|
|
20
24
|
let embeddingsProvider;
|
|
21
25
|
env.registerExtensionPoint(backstagePluginAiAssistantNode.dataIngestorExtensionPoint, {
|
|
22
26
|
registerIngestor: (ingestor) => {
|
|
@@ -52,6 +56,11 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
|
|
|
52
56
|
tools.push(tool);
|
|
53
57
|
}
|
|
54
58
|
});
|
|
59
|
+
env.registerExtensionPoint(backstagePluginAiAssistantNode.callbackProviderExtensionPoint, {
|
|
60
|
+
register: (callbackProvider) => {
|
|
61
|
+
callbacks$1.push(callbackProvider);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
55
64
|
env.registerInit({
|
|
56
65
|
deps: {
|
|
57
66
|
httpRouter: backendPluginApi.coreServices.httpRouter,
|
|
@@ -67,7 +76,7 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
|
|
|
67
76
|
auth: backendPluginApi.coreServices.auth
|
|
68
77
|
},
|
|
69
78
|
async init(options) {
|
|
70
|
-
const { httpRouter, database } = options;
|
|
79
|
+
const { httpRouter, database, config } = options;
|
|
71
80
|
const client = await database.getClient();
|
|
72
81
|
await migrations.applyDatabaseMigrations(client);
|
|
73
82
|
const vectorStore = await pgVectorStore.PgVectorStore.fromConfig(options);
|
|
@@ -80,14 +89,28 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
|
|
|
80
89
|
vectorStore,
|
|
81
90
|
ingestors
|
|
82
91
|
});
|
|
92
|
+
const mcp$1 = await mcp.createMcpService(options);
|
|
83
93
|
const searchKnowledgeTool = searchKnowledge.createSearchKnowledgeTool({ vectorStore });
|
|
84
94
|
tools.push(searchKnowledgeTool);
|
|
95
|
+
const callback = await callbacks.createCallbackService({
|
|
96
|
+
callbacks: callbacks$1
|
|
97
|
+
});
|
|
98
|
+
const summarizer$1 = await summarizer.createSummarizerService({
|
|
99
|
+
config,
|
|
100
|
+
models,
|
|
101
|
+
callback
|
|
102
|
+
});
|
|
85
103
|
const chat$1 = await chat.createChatService({
|
|
86
104
|
...options,
|
|
87
105
|
models,
|
|
88
|
-
tools
|
|
106
|
+
tools,
|
|
107
|
+
mcp: mcp$1,
|
|
108
|
+
callback,
|
|
109
|
+
summarizer: summarizer$1
|
|
89
110
|
});
|
|
90
|
-
httpRouter.use(
|
|
111
|
+
httpRouter.use(
|
|
112
|
+
await index.createRouter({ ...options, chat: chat$1, mcp: mcp$1, summarizer: summarizer$1 })
|
|
113
|
+
);
|
|
91
114
|
dataIngestionPipeline.start();
|
|
92
115
|
}
|
|
93
116
|
});
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './services/router';\nimport {\n dataIngestorExtensionPoint,\n EmbeddingsProvider,\n embeddingsProviderExtensionPoint,\n Ingestor,\n Model,\n modelProviderExtensionPoint,\n Tool,\n toolExtensionPoint,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { createDataIngestionPipeline } from './services/ingestor';\nimport { createChatService } from './services/chat';\nimport { applyDatabaseMigrations } from './database/migrations';\nimport { PgVectorStore } from './database';\nimport { signalsServiceRef } from '@backstage/plugin-signals-node';\nimport { createSearchKnowledgeTool } from './services/tools/searchKnowledge';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\n
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './services/router';\nimport {\n dataIngestorExtensionPoint,\n EmbeddingsProvider,\n embeddingsProviderExtensionPoint,\n Ingestor,\n Model,\n modelProviderExtensionPoint,\n Tool,\n toolExtensionPoint,\n callbackProviderExtensionPoint,\n CallbackProvider,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { createDataIngestionPipeline } from './services/ingestor';\nimport { createChatService } from './services/chat';\nimport { applyDatabaseMigrations } from './database/migrations';\nimport { PgVectorStore } from './database';\nimport { signalsServiceRef } from '@backstage/plugin-signals-node';\nimport { createSearchKnowledgeTool } from './services/tools/searchKnowledge';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createMcpService } from './services/mcp';\nimport { createCallbackService } from './services/callbacks';\nimport { createSummarizerService } from './services/summarizer';\n/**\n * aiAssistantPlugin backend plugin\n *\n * @public\n */\n\nexport const aiAssistantPlugin = createBackendPlugin({\n pluginId: 'ai-assistant',\n register(env) {\n const ingestors: Ingestor[] = [];\n const models: Model[] = [];\n const tools: Tool[] = [];\n const callbacks: CallbackProvider[] = [];\n\n let embeddingsProvider: EmbeddingsProvider;\n\n env.registerExtensionPoint(dataIngestorExtensionPoint, {\n registerIngestor: ingestor => {\n const existingIngestor = ingestors.find(i => i.id === ingestor.id);\n if (existingIngestor) {\n throw new Error(\n `Ingestor with id ${ingestor.id} is already registered.`,\n );\n }\n ingestors.push(ingestor);\n },\n });\n\n env.registerExtensionPoint(embeddingsProviderExtensionPoint, {\n register: provider => {\n embeddingsProvider = provider;\n },\n });\n\n env.registerExtensionPoint(modelProviderExtensionPoint, {\n register: model => {\n const existingModel = models.find(m => m.id === model.id);\n if (existingModel) {\n throw new Error(`Model with id ${model.id} is already registered.`);\n }\n models.push(model);\n },\n });\n\n env.registerExtensionPoint(toolExtensionPoint, {\n register: tool => {\n const existingTool = tools.find(t => t.name === tool.name);\n if (existingTool) {\n throw new Error(`Tool with name ${tool.name} is already registered.`);\n }\n tools.push(tool);\n },\n });\n\n env.registerExtensionPoint(callbackProviderExtensionPoint, {\n register: callbackProvider => {\n callbacks.push(callbackProvider);\n },\n });\n\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n database: coreServices.database,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n scheduler: coreServices.scheduler,\n httpAuth: coreServices.httpAuth,\n userInfo: coreServices.userInfo,\n signals: signalsServiceRef,\n catalog: catalogServiceRef,\n cache: coreServices.cache,\n auth: coreServices.auth,\n },\n\n async init(options) {\n const { httpRouter, database, config } = options;\n\n const client = await database.getClient();\n\n await applyDatabaseMigrations(client);\n\n const vectorStore = await PgVectorStore.fromConfig(options);\n\n if (!embeddingsProvider) {\n throw new Error('No Embeddings Provider was registered.');\n }\n\n vectorStore.connectEmbeddings(await embeddingsProvider.getEmbeddings());\n\n const dataIngestionPipeline = createDataIngestionPipeline({\n ...options,\n vectorStore,\n ingestors,\n });\n\n const mcp = await createMcpService(options);\n\n const searchKnowledgeTool = createSearchKnowledgeTool({ vectorStore });\n tools.push(searchKnowledgeTool);\n\n const callback = await createCallbackService({\n callbacks,\n });\n\n const summarizer = await createSummarizerService({\n config,\n models,\n callback,\n });\n\n const chat = await createChatService({\n ...options,\n models,\n tools,\n mcp,\n callback,\n summarizer,\n });\n\n httpRouter.use(\n await createRouter({ ...options, chat, mcp, summarizer }),\n );\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","callbacks","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","callbackProviderExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","mcp","createMcpService","createSearchKnowledgeTool","createCallbackService","summarizer","createSummarizerService","chat","createChatService","createRouter"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const createCallbackService = async ({
|
|
4
|
+
callbacks
|
|
5
|
+
}) => {
|
|
6
|
+
const getChainCallbacks = async (options) => {
|
|
7
|
+
const callbackHandlers = [];
|
|
8
|
+
for (const { chainCallback } of callbacks) {
|
|
9
|
+
if (!chainCallback) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const callbackHandler = await chainCallback(options);
|
|
13
|
+
callbackHandlers.push(callbackHandler);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
callbacks: callbackHandlers
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const getChainMetadata = async (options) => {
|
|
20
|
+
const metadata = {};
|
|
21
|
+
for (const { chainMetadataCallback } of callbacks) {
|
|
22
|
+
if (!chainMetadataCallback) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const callbackData = await chainMetadataCallback(options);
|
|
26
|
+
Object.assign(metadata, callbackData.metadata);
|
|
27
|
+
}
|
|
28
|
+
return { metadata };
|
|
29
|
+
};
|
|
30
|
+
const handleScoreCallbacks = async ({
|
|
31
|
+
name,
|
|
32
|
+
message
|
|
33
|
+
}) => {
|
|
34
|
+
callbacks.forEach(async ({ scoreCallback }) => {
|
|
35
|
+
if (!scoreCallback) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
scoreCallback({ name, message });
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
getChainCallbacks,
|
|
43
|
+
getChainMetadata,
|
|
44
|
+
handleScoreCallbacks
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
exports.createCallbackService = createCallbackService;
|
|
49
|
+
//# sourceMappingURL=callbacks.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callbacks.cjs.js","sources":["../../src/services/callbacks.ts"],"sourcesContent":["import {\n CallbackProvider,\n ChainMetadata,\n ChainCallbackOptions,\n ChainCallback,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nexport type CallbackService = {\n getChainCallbacks: (options: ChainCallbackOptions) => Promise<{\n callbacks: ReturnType<ChainCallback>[];\n }>;\n\n getChainMetadata: (\n options: ChainCallbackOptions,\n ) => Promise<{ metadata: ChainMetadata }>;\n\n handleScoreCallbacks: NonNullable<CallbackProvider['scoreCallback']>;\n};\n\nexport type CreateCallbackServiceOptions = {\n callbacks: CallbackProvider[];\n};\n\nexport const createCallbackService = async ({\n callbacks,\n}: CreateCallbackServiceOptions): Promise<CallbackService> => {\n const getChainCallbacks: CallbackService['getChainCallbacks'] =\n async options => {\n const callbackHandlers: ReturnType<ChainCallback>[] = [];\n\n for (const { chainCallback } of callbacks) {\n if (!chainCallback) {\n continue;\n }\n\n const callbackHandler = await chainCallback(options);\n\n callbackHandlers.push(callbackHandler);\n }\n\n return {\n callbacks: callbackHandlers,\n };\n };\n\n const getChainMetadata: CallbackService['getChainMetadata'] =\n async options => {\n const metadata: ChainMetadata = {};\n\n for (const { chainMetadataCallback } of callbacks) {\n if (!chainMetadataCallback) {\n continue;\n }\n\n const callbackData = await chainMetadataCallback(options);\n\n Object.assign(metadata, callbackData.metadata);\n }\n\n return { metadata };\n };\n\n const handleScoreCallbacks: CallbackService['handleScoreCallbacks'] = async ({\n name,\n message,\n }) => {\n callbacks.forEach(async ({ scoreCallback }) => {\n if (!scoreCallback) {\n return;\n }\n\n scoreCallback({ name, message });\n });\n };\n\n return {\n getChainCallbacks,\n getChainMetadata,\n handleScoreCallbacks,\n };\n};\n"],"names":[],"mappings":";;AAuBO,MAAM,wBAAwB,OAAO;AAAA,EAC1C;AACF,CAAA,KAA8D;AAC5D,EAAA,MAAM,iBAAA,GACJ,OAAM,OAAA,KAAW;AACf,IAAA,MAAM,mBAAgD,EAAC;AAEvD,IAAA,KAAA,MAAW,EAAE,aAAA,EAAc,IAAK,SAAA,EAAW;AACzC,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,eAAA,GAAkB,MAAM,aAAA,CAAc,OAAO,CAAA;AAEnD,MAAA,gBAAA,CAAiB,KAAK,eAAe,CAAA;AAAA,IACvC;AAEA,IAAA,OAAO;AAAA,MACL,SAAA,EAAW;AAAA,KACb;AAAA,EACF,CAAA;AAEF,EAAA,MAAM,gBAAA,GACJ,OAAM,OAAA,KAAW;AACf,IAAA,MAAM,WAA0B,EAAC;AAEjC,IAAA,KAAA,MAAW,EAAE,qBAAA,EAAsB,IAAK,SAAA,EAAW;AACjD,MAAA,IAAI,CAAC,qBAAA,EAAuB;AAC1B,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,YAAA,GAAe,MAAM,qBAAA,CAAsB,OAAO,CAAA;AAExD,MAAA,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,YAAA,CAAa,QAAQ,CAAA;AAAA,IAC/C;AAEA,IAAA,OAAO,EAAE,QAAA,EAAS;AAAA,EACpB,CAAA;AAEF,EAAA,MAAM,uBAAgE,OAAO;AAAA,IAC3E,IAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,SAAA,CAAU,OAAA,CAAQ,OAAO,EAAE,aAAA,EAAc,KAAM;AAC7C,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA;AAAA,MACF;AAEA,MAAA,aAAA,CAAc,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AAAA,IACjC,CAAC,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var chatStore = require('../database/chat-store.cjs.js');
|
|
4
4
|
var prompts = require('../constants/prompts.cjs.js');
|
|
5
|
+
var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
|
|
5
6
|
var tools = require('@langchain/core/tools');
|
|
6
7
|
var prebuilt = require('@langchain/langgraph/prebuilt');
|
|
7
8
|
var prompts$1 = require('@langchain/core/prompts');
|
|
8
|
-
var summarizer = require('./summarizer.cjs.js');
|
|
9
9
|
var uuid = require('uuid');
|
|
10
10
|
|
|
11
11
|
const createChatService = async ({
|
|
@@ -17,7 +17,11 @@ const createChatService = async ({
|
|
|
17
17
|
config,
|
|
18
18
|
catalog,
|
|
19
19
|
cache,
|
|
20
|
-
auth
|
|
20
|
+
auth,
|
|
21
|
+
mcp,
|
|
22
|
+
userInfo,
|
|
23
|
+
callback,
|
|
24
|
+
summarizer
|
|
21
25
|
}) => {
|
|
22
26
|
logger.info(`Available models: ${models.map((m) => m.id).join(", ")}`);
|
|
23
27
|
logger.info(`Available tools: ${tools$1.map((t) => t.name).join(", ")}`);
|
|
@@ -31,8 +35,6 @@ ${formattingPrompt}
|
|
|
31
35
|
${contentPrompt}`;
|
|
32
36
|
const toolGuideline = config.getOptionalString("aiAssistant.prompt.toolGuideline") || prompts.DEFAULT_TOOL_GUIDELINE;
|
|
33
37
|
const chatStore$1 = await chatStore.ChatStore.fromConfig({ database });
|
|
34
|
-
const summarizer$1 = await summarizer.createSummarizerService({ config, models });
|
|
35
|
-
const agentTools = tools$1.map((tool) => new tools.DynamicStructuredTool(tool));
|
|
36
38
|
const systemPromptTemplate = prompts$1.SystemMessagePromptTemplate.fromTemplate(`
|
|
37
39
|
PURPOSE:
|
|
38
40
|
{basePrompt}
|
|
@@ -81,7 +83,10 @@ ${contentPrompt}`;
|
|
|
81
83
|
chatStore$1.addChatMessage(messages, userRef, conversationId);
|
|
82
84
|
return;
|
|
83
85
|
}
|
|
84
|
-
const summary = await summarizer
|
|
86
|
+
const summary = await summarizer.summarizeConversation({
|
|
87
|
+
messages: recentMessages,
|
|
88
|
+
length: "25 characters"
|
|
89
|
+
});
|
|
85
90
|
conversation.title = summary;
|
|
86
91
|
chatStore$1.updateConversation(conversation);
|
|
87
92
|
chatStore$1.addChatMessage(messages, userRef, conversationId);
|
|
@@ -99,12 +104,13 @@ ${contentPrompt}`;
|
|
|
99
104
|
messages,
|
|
100
105
|
modelId,
|
|
101
106
|
stream = true,
|
|
102
|
-
|
|
107
|
+
userCredentials
|
|
103
108
|
}) => {
|
|
104
109
|
const model = models.find((m) => m.id === modelId)?.chatModel;
|
|
105
110
|
if (!model) {
|
|
106
111
|
throw new Error(`Model with id ${modelId} not found`);
|
|
107
112
|
}
|
|
113
|
+
const { userEntityRef } = await userInfo.getUserInfo(userCredentials);
|
|
108
114
|
const streamFn = async () => {
|
|
109
115
|
const recentConversationMessages = await chatStore$1.getChatMessages(
|
|
110
116
|
conversationId,
|
|
@@ -113,9 +119,12 @@ ${contentPrompt}`;
|
|
|
113
119
|
["tool"]
|
|
114
120
|
);
|
|
115
121
|
const credentials = await auth.getOwnServiceCredentials();
|
|
116
|
-
const user = await getUser(cache, userEntityRef, credentials, catalog);
|
|
122
|
+
const user = await backstagePluginAiAssistantNode.getUser(cache, userEntityRef, credentials, catalog);
|
|
123
|
+
const mcpTools = await mcp.getTools(userCredentials);
|
|
124
|
+
const agentTools = tools$1.map((tool) => new tools.DynamicStructuredTool(tool)).concat(mcpTools.map((tool) => new tools.DynamicStructuredTool(tool)));
|
|
125
|
+
const messagesWithoutSystem = messages.filter((m) => m.role !== "system");
|
|
117
126
|
addMessages(
|
|
118
|
-
|
|
127
|
+
messagesWithoutSystem,
|
|
119
128
|
userEntityRef,
|
|
120
129
|
conversationId,
|
|
121
130
|
recentConversationMessages
|
|
@@ -132,22 +141,45 @@ ${contentPrompt}`;
|
|
|
132
141
|
tools: agentTools,
|
|
133
142
|
prompt: systemPrompt[0].text
|
|
134
143
|
});
|
|
144
|
+
const { callbacks } = await callback.getChainCallbacks({
|
|
145
|
+
conversationId,
|
|
146
|
+
userId: userEntityRef,
|
|
147
|
+
modelId
|
|
148
|
+
});
|
|
149
|
+
const { metadata: promptMetadata } = await callback.getChainMetadata({
|
|
150
|
+
conversationId,
|
|
151
|
+
userId: userEntityRef,
|
|
152
|
+
modelId
|
|
153
|
+
});
|
|
154
|
+
const traceId = uuid.v4();
|
|
135
155
|
const promptStream = await agent.stream(
|
|
136
156
|
{
|
|
137
157
|
messages: [...recentConversationMessages, ...messages]
|
|
138
158
|
},
|
|
139
|
-
{
|
|
159
|
+
{
|
|
160
|
+
streamMode: ["values"],
|
|
161
|
+
runName: "ai-assistant-chat",
|
|
162
|
+
runId: traceId,
|
|
163
|
+
metadata: promptMetadata,
|
|
164
|
+
callbacks
|
|
165
|
+
}
|
|
140
166
|
);
|
|
141
167
|
const responseMessages = [];
|
|
142
168
|
for await (const [, chunk] of promptStream) {
|
|
143
169
|
const { messages: promptMessages } = chunk;
|
|
144
|
-
const newMessages = promptMessages.filter(
|
|
170
|
+
const newMessages = promptMessages.filter(
|
|
171
|
+
(m) => responseMessages.findIndex(
|
|
172
|
+
(rm) => m.id === rm.metadata.langGraphId
|
|
173
|
+
) === -1
|
|
174
|
+
).filter(
|
|
145
175
|
(m) => recentConversationMessages.findIndex((rm) => rm.id === m.id) === -1
|
|
146
176
|
).filter((m) => m.getType() !== "human").map((m) => {
|
|
147
|
-
const id =
|
|
177
|
+
const id = uuid.v4();
|
|
148
178
|
const role = m.getType();
|
|
149
179
|
const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
150
|
-
const metadata = {
|
|
180
|
+
const metadata = {
|
|
181
|
+
langGraphId: m.id ?? ""
|
|
182
|
+
};
|
|
151
183
|
if (role === "ai") {
|
|
152
184
|
const aiMessage = m;
|
|
153
185
|
metadata.toolCalls = aiMessage.tool_calls || [];
|
|
@@ -162,14 +194,18 @@ ${contentPrompt}`;
|
|
|
162
194
|
id,
|
|
163
195
|
role,
|
|
164
196
|
content,
|
|
165
|
-
metadata
|
|
197
|
+
metadata,
|
|
198
|
+
score: 0,
|
|
199
|
+
traceId
|
|
166
200
|
};
|
|
167
201
|
});
|
|
168
202
|
for await (const m of newMessages) {
|
|
169
|
-
const
|
|
203
|
+
const words = m.content.split(" ");
|
|
204
|
+
const chunkSize = 5;
|
|
170
205
|
let messageBuilder = "";
|
|
171
|
-
for
|
|
172
|
-
|
|
206
|
+
for (let i = 0; i < words.length; i += chunkSize) {
|
|
207
|
+
const wordChunk = words.slice(i, i + chunkSize).join(" ");
|
|
208
|
+
messageBuilder = messageBuilder.concat(wordChunk).concat(" ");
|
|
173
209
|
m.content = messageBuilder;
|
|
174
210
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
175
211
|
signals.publish({
|
|
@@ -184,12 +220,10 @@ ${contentPrompt}`;
|
|
|
184
220
|
}
|
|
185
221
|
responseMessages.push(...newMessages);
|
|
186
222
|
}
|
|
187
|
-
addMessages(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
[...recentConversationMessages, ...messages]
|
|
192
|
-
);
|
|
223
|
+
addMessages(responseMessages, userEntityRef, conversationId, [
|
|
224
|
+
...recentConversationMessages,
|
|
225
|
+
...messages
|
|
226
|
+
]);
|
|
193
227
|
return responseMessages;
|
|
194
228
|
};
|
|
195
229
|
const result = streamFn();
|
|
@@ -212,25 +246,30 @@ ${contentPrompt}`;
|
|
|
212
246
|
const conversations = await chatStore$1.getConversations(userEntityRef);
|
|
213
247
|
return conversations;
|
|
214
248
|
};
|
|
249
|
+
const scoreMessage = async (messageId, score) => {
|
|
250
|
+
const message = await chatStore$1.getMessageById(messageId);
|
|
251
|
+
if (!message) {
|
|
252
|
+
throw new Error(`Message with id ${messageId} not found`);
|
|
253
|
+
}
|
|
254
|
+
const updatedMessage = {
|
|
255
|
+
...message,
|
|
256
|
+
score
|
|
257
|
+
};
|
|
258
|
+
chatStore$1.updateMessage(updatedMessage);
|
|
259
|
+
callback.handleScoreCallbacks({
|
|
260
|
+
name: "helpfulness",
|
|
261
|
+
message: updatedMessage
|
|
262
|
+
});
|
|
263
|
+
};
|
|
215
264
|
return {
|
|
216
265
|
prompt,
|
|
217
266
|
getAvailableModels,
|
|
218
267
|
getConversation,
|
|
219
268
|
getConversations,
|
|
220
|
-
addMessages
|
|
269
|
+
addMessages,
|
|
270
|
+
scoreMessage
|
|
221
271
|
};
|
|
222
272
|
};
|
|
223
|
-
async function getUser(cache, userEntityRef, credentials, catalog) {
|
|
224
|
-
const cached = await cache.get(userEntityRef);
|
|
225
|
-
if (cached) {
|
|
226
|
-
return JSON.parse(String(cached));
|
|
227
|
-
}
|
|
228
|
-
const user = await catalog.getEntityByRef(userEntityRef, {
|
|
229
|
-
credentials
|
|
230
|
-
});
|
|
231
|
-
await cache.set(userEntityRef, JSON.stringify(user));
|
|
232
|
-
return user;
|
|
233
|
-
}
|
|
234
273
|
|
|
235
274
|
exports.createChatService = createChatService;
|
|
236
275
|
//# sourceMappingURL=chat.cjs.js.map
|