@sweetoburrito/backstage-plugin-ai-assistant-backend 0.0.0-snapshot-20251030154701 → 0.0.0-snapshot-20251113134620

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.
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const DEFAULT_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_PAGE_SUMMARY_PROMPT = "Create a concise summary of the provided page content. Focus on the main topics, key information, and essential details. The summary should be informative and help users understand what the page contains without needing to read the full content. Return only the summary without any additional text or introductions.";
5
4
  const DEFAULT_IDENTITY_PROMPT = `
6
5
  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.
7
6
  `;
@@ -38,7 +37,6 @@ TOOL USAGE GUIDELINES:
38
37
 
39
38
  exports.DEFAULT_FORMATTING_PROMPT = DEFAULT_FORMATTING_PROMPT;
40
39
  exports.DEFAULT_IDENTITY_PROMPT = DEFAULT_IDENTITY_PROMPT;
41
- exports.DEFAULT_PAGE_SUMMARY_PROMPT = DEFAULT_PAGE_SUMMARY_PROMPT;
42
40
  exports.DEFAULT_SUMMARY_PROMPT = DEFAULT_SUMMARY_PROMPT;
43
41
  exports.DEFAULT_SYSTEM_PROMPT = DEFAULT_SYSTEM_PROMPT;
44
42
  exports.DEFAULT_TOOL_GUIDELINE = DEFAULT_TOOL_GUIDELINE;
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.cjs.js","sources":["../../src/constants/prompts.ts"],"sourcesContent":["export const DEFAULT_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_PAGE_SUMMARY_PROMPT =\n 'Create a concise summary of the provided page content. Focus on the main topics, key information, and essential details. The summary should be informative and help users understand what the page contains without needing to read the full content. Return only the summary without any additional text or introductions.';\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**: ![alt text](image-url)\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,sBAAA,GACX;AAEK,MAAM,2BAAA,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;;;;;;;;;"}
1
+ {"version":3,"file":"prompts.cjs.js","sources":["../../src/constants/prompts.ts"],"sourcesContent":["export const DEFAULT_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_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**: ![alt text](image-url)\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,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;;;;;;;;"}
@@ -11,7 +11,7 @@ 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
13
  var mcp = require('./services/mcp.cjs.js');
14
- var langfuse = require('./services/langfuse.cjs.js');
14
+ var callbacks = require('./services/callbacks.cjs.js');
15
15
 
16
16
  const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
17
17
  pluginId: "ai-assistant",
@@ -19,7 +19,7 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
19
19
  const ingestors = [];
20
20
  const models = [];
21
21
  const tools = [];
22
- const realtimeVoiceServices = [];
22
+ const callbacks$1 = [];
23
23
  let embeddingsProvider;
24
24
  env.registerExtensionPoint(backstagePluginAiAssistantNode.dataIngestorExtensionPoint, {
25
25
  registerIngestor: (ingestor) => {
@@ -55,9 +55,9 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
55
55
  tools.push(tool);
56
56
  }
57
57
  });
58
- env.registerExtensionPoint(backstagePluginAiAssistantNode.realtimeVoiceExtensionPoint, {
59
- register: (service) => {
60
- realtimeVoiceServices.push(service);
58
+ env.registerExtensionPoint(backstagePluginAiAssistantNode.callbackFactoryExtensionPoint, {
59
+ register: (callbackFactory) => {
60
+ callbacks$1.push(callbackFactory);
61
61
  }
62
62
  });
63
63
  env.registerInit({
@@ -75,11 +75,7 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
75
75
  auth: backendPluginApi.coreServices.auth
76
76
  },
77
77
  async init(options) {
78
- const { httpRouter, database, config, logger } = options;
79
- const { langfuseEnabled, langfuseClient } = langfuse.initLangfuse(
80
- config,
81
- logger
82
- );
78
+ const { httpRouter, database } = options;
83
79
  const client = await database.getClient();
84
80
  await migrations.applyDatabaseMigrations(client);
85
81
  const vectorStore = await pgVectorStore.PgVectorStore.fromConfig(options);
@@ -95,26 +91,17 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
95
91
  const mcp$1 = await mcp.createMcpService(options);
96
92
  const searchKnowledgeTool = searchKnowledge.createSearchKnowledgeTool({ vectorStore });
97
93
  tools.push(searchKnowledgeTool);
94
+ const callback = await callbacks.createCallbackService({
95
+ callbacks: callbacks$1
96
+ });
98
97
  const chat$1 = await chat.createChatService({
99
98
  ...options,
100
99
  models,
101
100
  tools,
102
101
  mcp: mcp$1,
103
- langfuseEnabled,
104
- langfuseClient
102
+ callback
105
103
  });
106
- for (const realtimeVoiceService of realtimeVoiceServices) {
107
- realtimeVoiceService.initialize({ tools });
108
- }
109
- httpRouter.use(
110
- await index.createRouter({
111
- ...options,
112
- chat: chat$1,
113
- models,
114
- langfuseEnabled,
115
- mcp: mcp$1
116
- })
117
- );
104
+ httpRouter.use(await index.createRouter({ ...options, chat: chat$1, mcp: mcp$1 }));
118
105
  dataIngestionPipeline.start();
119
106
  }
120
107
  });
@@ -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 realtimeVoiceExtensionPoint,\n RealtimeVoiceService,\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';\n\nimport { initLangfuse } from './services/langfuse';\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 realtimeVoiceServices: RealtimeVoiceService[] = [];\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(realtimeVoiceExtensionPoint, {\n register: service => {\n realtimeVoiceServices.push(service);\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, logger } = options;\n\n const { langfuseEnabled, langfuseClient } = initLangfuse(\n config,\n logger,\n );\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 chat = await createChatService({\n ...options,\n models,\n tools,\n mcp,\n langfuseEnabled,\n langfuseClient,\n });\n\n // Initialize realtime voice services with tools\n for (const realtimeVoiceService of realtimeVoiceServices) {\n realtimeVoiceService.initialize({ tools });\n }\n\n httpRouter.use(\n await createRouter({\n ...options,\n chat,\n models,\n langfuseEnabled,\n mcp,\n }),\n );\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","realtimeVoiceExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","initLangfuse","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","mcp","createMcpService","createSearchKnowledgeTool","chat","createChatService","createRouter"],"mappings":"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"}
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 callbackFactoryExtensionPoint,\n CallbackFactory,\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';\n\nimport { createCallbackService } from './services/callbacks';\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: CallbackFactory[] = [];\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(callbackFactoryExtensionPoint, {\n register: callbackFactory => {\n callbacks.push(callbackFactory);\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 } = 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 chat = await createChatService({\n ...options,\n models,\n tools,\n mcp,\n callback,\n });\n\n httpRouter.use(await createRouter({ ...options, chat, mcp }));\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","callbacks","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","callbackFactoryExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","mcp","createMcpService","createSearchKnowledgeTool","createCallbackService","chat","createChatService","createRouter"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const createCallbackService = async ({
4
+ callbacks
5
+ }) => {
6
+ const getAgentCallbackData = async (options) => {
7
+ const metadata = {};
8
+ const callbackHandlers = [];
9
+ for (const createCallback of callbacks) {
10
+ const { metadata: cbMetadata, callback } = await createCallback(
11
+ options
12
+ );
13
+ Object.assign(metadata, cbMetadata);
14
+ callbackHandlers.push(callback);
15
+ }
16
+ return {
17
+ metadata,
18
+ callbacks: callbackHandlers
19
+ };
20
+ };
21
+ return {
22
+ getAgentCallbackData
23
+ };
24
+ };
25
+
26
+ exports.createCallbackService = createCallbackService;
27
+ //# sourceMappingURL=callbacks.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callbacks.cjs.js","sources":["../../src/services/callbacks.ts"],"sourcesContent":["import {\n Callback,\n CallbackFactory,\n CallbackOptions,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nexport type CallbackService = {\n getAgentCallbackData: (options: CallbackOptions) => Promise<{\n metadata: Callback['metadata'];\n callbacks: Callback['callback'][];\n }>;\n};\n\nexport type CreateCallbackServiceOptions = {\n callbacks: CallbackFactory[];\n};\n\nexport const createCallbackService = async ({\n callbacks,\n}: CreateCallbackServiceOptions): Promise<CallbackService> => {\n const getAgentCallbackData: CallbackService['getAgentCallbackData'] =\n async options => {\n const metadata = {};\n const callbackHandlers: Callback['callback'][] = [];\n\n for (const createCallback of callbacks) {\n const { metadata: cbMetadata, callback } = await createCallback(\n options,\n );\n Object.assign(metadata, cbMetadata);\n callbackHandlers.push(callback);\n }\n\n return {\n metadata,\n callbacks: callbackHandlers,\n };\n };\n\n return {\n getAgentCallbackData,\n };\n};\n"],"names":[],"mappings":";;AAiBO,MAAM,wBAAwB,OAAO;AAAA,EAC1C;AACF,CAAA,KAA8D;AAC5D,EAAA,MAAM,oBAAA,GACJ,OAAM,OAAA,KAAW;AACf,IAAA,MAAM,WAAW,EAAC;AAClB,IAAA,MAAM,mBAA2C,EAAC;AAElD,IAAA,KAAA,MAAW,kBAAkB,SAAA,EAAW;AACtC,MAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAY,QAAA,KAAa,MAAM,cAAA;AAAA,QAC/C;AAAA,OACF;AACA,MAAA,MAAA,CAAO,MAAA,CAAO,UAAU,UAAU,CAAA;AAClC,MAAA,gBAAA,CAAiB,KAAK,QAAQ,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,SAAA,EAAW;AAAA,KACb;AAAA,EACF,CAAA;AAEF,EAAA,OAAO;AAAA,IACL;AAAA,GACF;AACF;;;;"}
@@ -6,7 +6,6 @@ var tools = require('@langchain/core/tools');
6
6
  var prebuilt = require('@langchain/langgraph/prebuilt');
7
7
  var prompts$1 = require('@langchain/core/prompts');
8
8
  var summarizer = require('./summarizer.cjs.js');
9
- var langchain = require('@langfuse/langchain');
10
9
  var uuid = require('uuid');
11
10
 
12
11
  const createChatService = async ({
@@ -21,8 +20,7 @@ const createChatService = async ({
21
20
  auth,
22
21
  mcp,
23
22
  userInfo,
24
- langfuseEnabled,
25
- langfuseClient
23
+ callback
26
24
  }) => {
27
25
  logger.info(`Available models: ${models.map((m) => m.id).join(", ")}`);
28
26
  logger.info(`Available tools: ${tools$1.map((t) => t.name).join(", ")}`);
@@ -39,8 +37,7 @@ ${contentPrompt}`;
39
37
  const summarizer$1 = await summarizer.createSummarizerService({
40
38
  config,
41
39
  models,
42
- langfuseEnabled,
43
- logger
40
+ callback
44
41
  });
45
42
  const systemPromptTemplate = prompts$1.SystemMessagePromptTemplate.fromTemplate(`
46
43
  PURPOSE:
@@ -144,11 +141,11 @@ ${contentPrompt}`;
144
141
  tools: agentTools,
145
142
  prompt: systemPrompt[0].text
146
143
  });
147
- const langfuseHandler = langfuseEnabled ? new langchain.CallbackHandler({
148
- sessionId: conversationId,
144
+ const { metadata: promptMetadata, callbacks } = await callback.getAgentCallbackData({
145
+ conversationId,
149
146
  userId: userEntityRef,
150
- tags: ["backstage-ai-assistant", "chat"]
151
- }) : void 0;
147
+ modelId
148
+ });
152
149
  const promptStream = await agent.stream(
153
150
  {
154
151
  messages: [...recentConversationMessages, ...messages]
@@ -156,12 +153,8 @@ ${contentPrompt}`;
156
153
  {
157
154
  streamMode: ["values"],
158
155
  runName: "ai-assistant-chat",
159
- metadata: {
160
- langfuseUserId: userEntityRef,
161
- langfuseSessionId: conversationId,
162
- langfuseTags: ["ai-assistant", "chat", modelId]
163
- },
164
- callbacks: langfuseHandler ? [langfuseHandler] : []
156
+ metadata: promptMetadata,
157
+ callbacks
165
158
  }
166
159
  );
167
160
  const responseMessages = [];
@@ -214,9 +207,8 @@ ${contentPrompt}`;
214
207
  }
215
208
  responseMessages.push(...newMessages);
216
209
  }
217
- const traceId = langfuseHandler?.last_trace_id ?? void 0;
218
210
  addMessages(
219
- responseMessages.map((m) => ({ ...m, id: uuid.v4(), traceId })),
211
+ responseMessages.map((m) => ({ ...m, id: uuid.v4() })),
220
212
  userEntityRef,
221
213
  conversationId,
222
214
  [...recentConversationMessages, ...messages]
@@ -248,20 +240,6 @@ ${contentPrompt}`;
248
240
  if (!message) {
249
241
  throw new Error(`Message with id ${messageId} not found`);
250
242
  }
251
- if (langfuseEnabled && message.traceId) {
252
- langfuseClient.score.create({
253
- traceId: message.traceId,
254
- name: "helpfulness",
255
- value: score
256
- });
257
- logger.info(
258
- `Scored message ${messageId} on Langfuse with trace ID ${message.traceId} - ${score} for helpfulness`
259
- );
260
- } else if (langfuseEnabled && !message.traceId) {
261
- logger.warn(
262
- `Message ${messageId} does not have a traceId, cannot score on Langfuse`
263
- );
264
- }
265
243
  const updatedMessage = {
266
244
  ...message,
267
245
  score
@@ -1 +1 @@
1
- {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { UserEntity } from '@backstage/catalog-model';\nimport {\n LoggerService,\n RootConfigService,\n DatabaseService,\n AuthService,\n} from '@backstage/backend-plugin-api';\nimport { ChatStore } from '../database/chat-store';\nimport {\n Conversation,\n Message,\n JsonObject,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { SignalsService } from '@backstage/plugin-signals-node';\nimport {\n DEFAULT_FORMATTING_PROMPT,\n DEFAULT_IDENTITY_PROMPT,\n DEFAULT_SYSTEM_PROMPT,\n DEFAULT_TOOL_GUIDELINE,\n} from '../constants/prompts';\nimport { Tool } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DynamicStructuredTool } from '@langchain/core/tools';\nimport { createReactAgent } from '@langchain/langgraph/prebuilt';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\nimport { createSummarizerService } from './summarizer';\nimport { CallbackHandler } from '@langfuse/langchain';\nimport { LangfuseClient } from '@langfuse/client';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { McpService } from './mcp';\n\nexport type ChatServiceOptions = {\n models: Model[];\n tools: Tool[];\n logger: LoggerService;\n config: RootConfigService;\n database: DatabaseService;\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n mcp: McpService;\n userInfo: UserInfoService;\n langfuseEnabled: boolean;\n langfuseClient?: LangfuseClient;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userCredentials: BackstageCredentials;\n};\n\ntype GetConversationOptions = {\n conversationId: string;\n userEntityRef: string;\n};\n\ntype GetConversationsOptions = {\n userEntityRef: string;\n};\n\n// Helper type for messages with required fields except traceId which remains optional\ntype MessageWithRequiredFields = Required<Omit<Message, 'traceId'>> &\n Pick<Message, 'traceId'>;\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<MessageWithRequiredFields[]>;\n getAvailableModels: () => Promise<string[]>;\n getConversation: (\n options: GetConversationOptions,\n ) => Promise<MessageWithRequiredFields[]>;\n getConversations: (\n options: GetConversationsOptions,\n ) => Promise<Conversation[]>;\n addMessages: (\n messages: Message[],\n userRef: string,\n conversationId: string,\n recentConversationMessages?: Message[],\n ) => Promise<void>;\n scoreMessage: (messageId: string, score: number) => Promise<void>;\n};\n\nexport const createChatService = async ({\n models,\n tools,\n logger,\n database,\n signals,\n config,\n catalog,\n cache,\n auth,\n mcp,\n userInfo,\n langfuseEnabled,\n langfuseClient,\n}: ChatServiceOptions): Promise<ChatService> => {\n logger.info(`Available models: ${models.map(m => m.id).join(', ')}`);\n logger.info(`Available tools: ${tools.map(t => t.name).join(', ')}`);\n\n const identityPrompt =\n config.getOptionalString('aiAssistant.prompt.identity') ||\n DEFAULT_IDENTITY_PROMPT;\n\n const formattingPrompt =\n config.getOptionalString('aiAssistant.prompt.formatting') ||\n DEFAULT_FORMATTING_PROMPT;\n\n const contentPrompt =\n config.getOptionalString('aiAssistant.prompt.content') ||\n DEFAULT_SYSTEM_PROMPT;\n\n const combinedBasePrompt = `${identityPrompt}\\n\\n${formattingPrompt}\\n\\n${contentPrompt}`;\n\n const toolGuideline =\n config.getOptionalString('aiAssistant.prompt.toolGuideline') ||\n DEFAULT_TOOL_GUIDELINE;\n\n const chatStore = await ChatStore.fromConfig({ database });\n const summarizer = await createSummarizerService({\n config,\n models,\n langfuseEnabled,\n logger,\n });\n\n const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {basePrompt}\n\n TOOL USAGE GUIDELINES:\n {toolGuideline}\n\n Available tools:\n {toolList}\n\n Calling User:\n {user}\n\n Context:\n {context}`);\n\n const addMessages: ChatService['addMessages'] = async (\n messages,\n userRef,\n conversationId,\n recentConversationMessages,\n ) => {\n // If we have recentConversationMessages, use them; otherwise, fetch the last 5 messages\n const recentMessages =\n recentConversationMessages ||\n (await chatStore.getChatMessages(conversationId, userRef, 5, ['tool']));\n\n const conversationSize = (recentMessages?.length ?? 0) + messages.length;\n\n if (recentMessages.length === 0) {\n const conversation: Conversation = {\n id: conversationId,\n title: 'New Conversation',\n userRef,\n };\n chatStore.createConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n return;\n }\n\n if (conversationSize < 5) {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const conversation = await chatStore.getConversation(\n conversationId,\n userRef,\n );\n\n if (conversation.title !== 'New Conversation') {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const summary = await summarizer.summarize(recentMessages, '25 characters');\n\n conversation.title = summary;\n\n chatStore.updateConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n };\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n modelId,\n stream = true,\n userCredentials,\n }: PromptOptions) => {\n const model = models.find(m => m.id === modelId)?.chatModel;\n\n if (!model) {\n throw new Error(`Model with id ${modelId} not found`);\n }\n\n const { userEntityRef } = await userInfo.getUserInfo(userCredentials);\n\n const streamFn = async () => {\n const recentConversationMessages = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n 10,\n ['tool'],\n );\n\n const credentials = await auth.getOwnServiceCredentials();\n const user = await getUser(cache, userEntityRef, credentials, catalog);\n\n const mcpTools = await mcp.getTools(userCredentials);\n\n const agentTools = tools\n .map(tool => new DynamicStructuredTool(tool))\n .concat(mcpTools.map(tool => new DynamicStructuredTool(tool)));\n\n addMessages(\n messages,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const systemPrompt = await systemPromptTemplate.formatMessages({\n basePrompt: combinedBasePrompt,\n toolGuideline,\n toolList: agentTools\n .map(tool => `- ${tool.name}: ${tool.description}`)\n .join('\\n'),\n context: `none`,\n user,\n });\n\n const agent = createReactAgent({\n llm: model,\n tools: agentTools,\n prompt: systemPrompt[0].text,\n });\n\n // Initialize Langfuse CallbackHandler for tracing if credentials are available\n const langfuseHandler = langfuseEnabled\n ? new CallbackHandler({\n sessionId: conversationId,\n userId: userEntityRef,\n tags: ['backstage-ai-assistant', 'chat'],\n })\n : undefined;\n\n const promptStream = await agent.stream(\n {\n messages: [...recentConversationMessages, ...messages],\n },\n {\n streamMode: ['values'],\n runName: 'ai-assistant-chat',\n metadata: {\n langfuseUserId: userEntityRef,\n langfuseSessionId: conversationId,\n langfuseTags: ['ai-assistant', 'chat', modelId],\n },\n callbacks: langfuseHandler ? [langfuseHandler] : [],\n },\n );\n\n const responseMessages: MessageWithRequiredFields[] = [];\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const newMessages: MessageWithRequiredFields[] = promptMessages\n .filter(m => responseMessages.findIndex(rm => rm.id === m.id) === -1)\n .filter(\n m =>\n recentConversationMessages.findIndex(rm => rm.id === m.id) === -1,\n )\n .filter(m => m.getType() !== 'human')\n .map(m => {\n const id = m.id ?? '';\n const role = m.getType();\n const content =\n typeof m.content === 'string'\n ? m.content\n : JSON.stringify(m.content);\n\n const metadata: JsonObject = {};\n\n if (role === 'ai') {\n const aiMessage = m as AIMessage;\n metadata.toolCalls = aiMessage.tool_calls || [];\n metadata.finishReason =\n aiMessage.response_metadata.finish_reason || undefined;\n metadata.modelName =\n aiMessage.response_metadata.model_name || undefined;\n }\n\n if (role === 'tool') {\n const toolMessage = m as ToolMessage;\n metadata.name = toolMessage.name || '';\n }\n\n return {\n id,\n role,\n content,\n metadata,\n score: 0,\n traceId: undefined,\n };\n });\n\n // Simulate streaming until langchain messages error is better understood\n for await (const m of newMessages) {\n const words = m.content.split(' ');\n const chunkSize = 5; // Send 5 words at a time\n let messageBuilder = '';\n\n for (let i = 0; i < words.length; i += chunkSize) {\n const wordChunk = words.slice(i, i + chunkSize).join(' ');\n messageBuilder = messageBuilder.concat(wordChunk).concat(' ');\n m.content = messageBuilder;\n\n await new Promise(resolve => setTimeout(resolve, 50));\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-stream:${conversationId}`,\n message: { messages: [m] },\n recipients: {\n type: 'user',\n entityRef: userEntityRef,\n },\n });\n }\n }\n\n responseMessages.push(...newMessages);\n }\n\n // Get the traceId from Langfuse if enabled\n const traceId = langfuseHandler?.last_trace_id ?? undefined;\n\n addMessages(\n responseMessages.map(m => ({ ...m, id: uuid(), traceId })),\n userEntityRef,\n conversationId,\n [...recentConversationMessages, ...messages],\n );\n\n return responseMessages;\n };\n\n const result = streamFn();\n\n return stream ? [] : result;\n };\n\n const getAvailableModels: ChatService['getAvailableModels'] = async () => {\n return models.map(x => x.id);\n };\n\n const getConversation: ChatService['getConversation'] = async (\n options: GetConversationOptions,\n ) => {\n const { conversationId, userEntityRef } = options;\n\n const conversation = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n );\n\n return conversation;\n };\n\n const getConversations: ChatService['getConversations'] = async ({\n userEntityRef,\n }: GetConversationsOptions) => {\n const conversations = await chatStore.getConversations(userEntityRef);\n\n return conversations;\n };\n\n const scoreMessage: ChatService['scoreMessage'] = async (\n messageId: string,\n score: number,\n ) => {\n const message = await chatStore.getMessageById(messageId);\n\n if (!message) {\n throw new Error(`Message with id ${messageId} not found`);\n }\n\n if (langfuseEnabled && message.traceId) {\n langfuseClient!.score.create({\n traceId: message.traceId,\n name: 'helpfulness',\n value: score,\n });\n logger.info(\n `Scored message ${messageId} on Langfuse with trace ID ${message.traceId} - ${score} for helpfulness`,\n );\n } else if (langfuseEnabled && !message.traceId) {\n logger.warn(\n `Message ${messageId} does not have a traceId, cannot score on Langfuse`,\n );\n }\n\n const updatedMessage: Required<Message> = {\n ...message,\n score,\n };\n\n await chatStore.updateMessage(updatedMessage);\n logger.info(`Message ${messageId} scored ${score}`);\n };\n\n return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\n scoreMessage,\n };\n};\n\nasync function getUser(\n cache: CacheService,\n userEntityRef: string,\n credentials: BackstageCredentials,\n catalog: CatalogService,\n) {\n const cached = await cache.get(userEntityRef);\n\n if (cached) {\n return JSON.parse(String(cached));\n }\n\n const user = (await catalog.getEntityByRef(userEntityRef, {\n credentials,\n })) as UserEntity | undefined;\n await cache.set(userEntityRef, JSON.stringify(user));\n\n return user;\n}\n"],"names":["tools","DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","chatStore","ChatStore","summarizer","createSummarizerService","SystemMessagePromptTemplate","conversation","DynamicStructuredTool","createReactAgent","CallbackHandler","uuid"],"mappings":"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"}
1
+ {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { UserEntity } from '@backstage/catalog-model';\nimport {\n LoggerService,\n RootConfigService,\n DatabaseService,\n AuthService,\n} from '@backstage/backend-plugin-api';\nimport { ChatStore } from '../database/chat-store';\nimport {\n Conversation,\n Message,\n JsonObject,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { SignalsService } from '@backstage/plugin-signals-node';\nimport {\n DEFAULT_FORMATTING_PROMPT,\n DEFAULT_IDENTITY_PROMPT,\n DEFAULT_SYSTEM_PROMPT,\n DEFAULT_TOOL_GUIDELINE,\n} from '../constants/prompts';\nimport { Tool } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DynamicStructuredTool } from '@langchain/core/tools';\nimport { createReactAgent } from '@langchain/langgraph/prebuilt';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\nimport { createSummarizerService } from './summarizer';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { McpService } from './mcp';\nimport { CallbackService } from './callbacks';\n\nexport type ChatServiceOptions = {\n models: Model[];\n tools: Tool[];\n logger: LoggerService;\n config: RootConfigService;\n database: DatabaseService;\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n mcp: McpService;\n userInfo: UserInfoService;\n callback: CallbackService;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userCredentials: BackstageCredentials;\n};\n\ntype GetConversationOptions = {\n conversationId: string;\n userEntityRef: string;\n};\n\ntype GetConversationsOptions = {\n userEntityRef: string;\n};\n\n// Helper type for messages with required fields except traceId which remains optional\ntype MessageWithRequiredFields = Required<Omit<Message, 'traceId'>> &\n Pick<Message, 'traceId'>;\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<MessageWithRequiredFields[]>;\n getAvailableModels: () => Promise<string[]>;\n getConversation: (\n options: GetConversationOptions,\n ) => Promise<MessageWithRequiredFields[]>;\n getConversations: (\n options: GetConversationsOptions,\n ) => Promise<Conversation[]>;\n addMessages: (\n messages: Message[],\n userRef: string,\n conversationId: string,\n recentConversationMessages?: Message[],\n ) => Promise<void>;\n scoreMessage: (messageId: string, score: number) => Promise<void>;\n};\n\nexport const createChatService = async ({\n models,\n tools,\n logger,\n database,\n signals,\n config,\n catalog,\n cache,\n auth,\n mcp,\n userInfo,\n callback,\n}: ChatServiceOptions): Promise<ChatService> => {\n logger.info(`Available models: ${models.map(m => m.id).join(', ')}`);\n logger.info(`Available tools: ${tools.map(t => t.name).join(', ')}`);\n\n const identityPrompt =\n config.getOptionalString('aiAssistant.prompt.identity') ||\n DEFAULT_IDENTITY_PROMPT;\n\n const formattingPrompt =\n config.getOptionalString('aiAssistant.prompt.formatting') ||\n DEFAULT_FORMATTING_PROMPT;\n\n const contentPrompt =\n config.getOptionalString('aiAssistant.prompt.content') ||\n DEFAULT_SYSTEM_PROMPT;\n\n const combinedBasePrompt = `${identityPrompt}\\n\\n${formattingPrompt}\\n\\n${contentPrompt}`;\n\n const toolGuideline =\n config.getOptionalString('aiAssistant.prompt.toolGuideline') ||\n DEFAULT_TOOL_GUIDELINE;\n\n const chatStore = await ChatStore.fromConfig({ database });\n const summarizer = await createSummarizerService({\n config,\n models,\n callback,\n });\n\n const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {basePrompt}\n\n TOOL USAGE GUIDELINES:\n {toolGuideline}\n\n Available tools:\n {toolList}\n\n Calling User:\n {user}\n\n Context:\n {context}`);\n\n const addMessages: ChatService['addMessages'] = async (\n messages,\n userRef,\n conversationId,\n recentConversationMessages,\n ) => {\n // If we have recentConversationMessages, use them; otherwise, fetch the last 5 messages\n const recentMessages =\n recentConversationMessages ||\n (await chatStore.getChatMessages(conversationId, userRef, 5, ['tool']));\n\n const conversationSize = (recentMessages?.length ?? 0) + messages.length;\n\n if (recentMessages.length === 0) {\n const conversation: Conversation = {\n id: conversationId,\n title: 'New Conversation',\n userRef,\n };\n chatStore.createConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n return;\n }\n\n if (conversationSize < 5) {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const conversation = await chatStore.getConversation(\n conversationId,\n userRef,\n );\n\n if (conversation.title !== 'New Conversation') {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const summary = await summarizer.summarize(recentMessages, '25 characters');\n\n conversation.title = summary;\n\n chatStore.updateConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n };\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n modelId,\n stream = true,\n userCredentials,\n }: PromptOptions) => {\n const model = models.find(m => m.id === modelId)?.chatModel;\n\n if (!model) {\n throw new Error(`Model with id ${modelId} not found`);\n }\n\n const { userEntityRef } = await userInfo.getUserInfo(userCredentials);\n\n const streamFn = async () => {\n const recentConversationMessages = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n 10,\n ['tool'],\n );\n\n const credentials = await auth.getOwnServiceCredentials();\n const user = await getUser(cache, userEntityRef, credentials, catalog);\n\n const mcpTools = await mcp.getTools(userCredentials);\n\n const agentTools = tools\n .map(tool => new DynamicStructuredTool(tool))\n .concat(mcpTools.map(tool => new DynamicStructuredTool(tool)));\n\n addMessages(\n messages,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const systemPrompt = await systemPromptTemplate.formatMessages({\n basePrompt: combinedBasePrompt,\n toolGuideline,\n toolList: agentTools\n .map(tool => `- ${tool.name}: ${tool.description}`)\n .join('\\n'),\n context: `none`,\n user,\n });\n\n const agent = createReactAgent({\n llm: model,\n tools: agentTools,\n prompt: systemPrompt[0].text,\n });\n\n const { metadata: promptMetadata, callbacks } =\n await callback.getAgentCallbackData({\n conversationId,\n userId: userEntityRef,\n modelId,\n });\n\n const promptStream = await agent.stream(\n {\n messages: [...recentConversationMessages, ...messages],\n },\n {\n streamMode: ['values'],\n runName: 'ai-assistant-chat',\n metadata: promptMetadata,\n callbacks,\n },\n );\n\n const responseMessages: MessageWithRequiredFields[] = [];\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const newMessages: MessageWithRequiredFields[] = promptMessages\n .filter(m => responseMessages.findIndex(rm => rm.id === m.id) === -1)\n .filter(\n m =>\n recentConversationMessages.findIndex(rm => rm.id === m.id) === -1,\n )\n .filter(m => m.getType() !== 'human')\n .map(m => {\n const id = m.id ?? '';\n const role = m.getType();\n const content =\n typeof m.content === 'string'\n ? m.content\n : JSON.stringify(m.content);\n\n const metadata: JsonObject = {};\n\n if (role === 'ai') {\n const aiMessage = m as AIMessage;\n metadata.toolCalls = aiMessage.tool_calls || [];\n metadata.finishReason =\n aiMessage.response_metadata.finish_reason || undefined;\n metadata.modelName =\n aiMessage.response_metadata.model_name || undefined;\n }\n\n if (role === 'tool') {\n const toolMessage = m as ToolMessage;\n metadata.name = toolMessage.name || '';\n }\n\n return {\n id,\n role,\n content,\n metadata,\n score: 0,\n traceId: undefined,\n };\n });\n\n // Simulate streaming until langchain messages error is better understood\n for await (const m of newMessages) {\n const words = m.content.split(' ');\n const chunkSize = 5; // Send 5 words at a time\n let messageBuilder = '';\n\n for (let i = 0; i < words.length; i += chunkSize) {\n const wordChunk = words.slice(i, i + chunkSize).join(' ');\n messageBuilder = messageBuilder.concat(wordChunk).concat(' ');\n m.content = messageBuilder;\n\n await new Promise(resolve => setTimeout(resolve, 50));\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-stream:${conversationId}`,\n message: { messages: [m] },\n recipients: {\n type: 'user',\n entityRef: userEntityRef,\n },\n });\n }\n }\n\n responseMessages.push(...newMessages);\n }\n\n addMessages(\n responseMessages.map(m => ({ ...m, id: uuid() })),\n userEntityRef,\n conversationId,\n [...recentConversationMessages, ...messages],\n );\n\n return responseMessages;\n };\n\n const result = streamFn();\n\n return stream ? [] : result;\n };\n\n const getAvailableModels: ChatService['getAvailableModels'] = async () => {\n return models.map(x => x.id);\n };\n\n const getConversation: ChatService['getConversation'] = async (\n options: GetConversationOptions,\n ) => {\n const { conversationId, userEntityRef } = options;\n\n const conversation = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n );\n\n return conversation;\n };\n\n const getConversations: ChatService['getConversations'] = async ({\n userEntityRef,\n }: GetConversationsOptions) => {\n const conversations = await chatStore.getConversations(userEntityRef);\n\n return conversations;\n };\n\n const scoreMessage: ChatService['scoreMessage'] = async (\n messageId: string,\n score: number,\n ) => {\n const message = await chatStore.getMessageById(messageId);\n\n if (!message) {\n throw new Error(`Message with id ${messageId} not found`);\n }\n\n const updatedMessage: Required<Message> = {\n ...message,\n score,\n };\n\n await chatStore.updateMessage(updatedMessage);\n logger.info(`Message ${messageId} scored ${score}`);\n };\n\n return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\n scoreMessage,\n };\n};\n\nasync function getUser(\n cache: CacheService,\n userEntityRef: string,\n credentials: BackstageCredentials,\n catalog: CatalogService,\n) {\n const cached = await cache.get(userEntityRef);\n\n if (cached) {\n return JSON.parse(String(cached));\n }\n\n const user = (await catalog.getEntityByRef(userEntityRef, {\n credentials,\n })) as UserEntity | undefined;\n await cache.set(userEntityRef, JSON.stringify(user));\n\n return user;\n}\n"],"names":["tools","DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","chatStore","ChatStore","summarizer","createSummarizerService","SystemMessagePromptTemplate","conversation","DynamicStructuredTool","createReactAgent","uuid"],"mappings":"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"}
@@ -4,7 +4,6 @@ var express = require('express');
4
4
  var Router = require('express-promise-router');
5
5
  var chat = require('./chat.cjs.js');
6
6
  var models = require('./models.cjs.js');
7
- var pageSummary = require('./page-summary.cjs.js');
8
7
  var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
9
8
  var mcp = require('./mcp.cjs.js');
10
9
 
@@ -19,7 +18,6 @@ async function createRouter(options) {
19
18
  router.use("/chat", await chat.createChatRouter(options));
20
19
  router.use("/models", await models.createModelRouter(options));
21
20
  router.use("/mcp", await mcp.createMcpRouter(options));
22
- router.use("/page-summary", await pageSummary.createPageSummaryRouter(options));
23
21
  const middleware = rootHttpRouter.MiddlewareFactory.create(options);
24
22
  router.use(middleware.error());
25
23
  return router;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../../src/services/router/index.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { createChatRouter, ChatRouterOptions } from './chat';\nimport { createModelRouter } from './models';\nimport { createPageSummaryRouter } from './page-summary';\nimport {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { createMcpRouter, McpRouterOptions } from './mcp';\nimport { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nexport type RouterOptions = ChatRouterOptions &\n McpRouterOptions & {\n config: RootConfigService;\n logger: LoggerService;\n models: Model[];\n langfuseEnabled: boolean;\n };\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n router.use('/chat', await createChatRouter(options));\n router.use('/models', await createModelRouter(options));\n router.use('/mcp', await createMcpRouter(options));\n router.use('/page-summary', await createPageSummaryRouter(options));\n\n const middleware = MiddlewareFactory.create(options);\n\n router.use(middleware.error());\n\n return router;\n}\n"],"names":["Router","express","createChatRouter","createModelRouter","createMcpRouter","createPageSummaryRouter","MiddlewareFactory"],"mappings":";;;;;;;;;;;;;;;AAqBA,eAAsB,aACpB,OAAA,EACyB;AACzB,EAAA,MAAM,SAASA,uBAAA,EAAO;AACtB,EAAA,MAAA,CAAO,GAAA,CAAIC,wBAAA,CAAQ,IAAA,EAAM,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,MAAMC,qBAAA,CAAiB,OAAO,CAAC,CAAA;AACnD,EAAA,MAAA,CAAO,GAAA,CAAI,SAAA,EAAW,MAAMC,wBAAA,CAAkB,OAAO,CAAC,CAAA;AACtD,EAAA,MAAA,CAAO,GAAA,CAAI,MAAA,EAAQ,MAAMC,mBAAA,CAAgB,OAAO,CAAC,CAAA;AACjD,EAAA,MAAA,CAAO,GAAA,CAAI,eAAA,EAAiB,MAAMC,mCAAA,CAAwB,OAAO,CAAC,CAAA;AAElE,EAAA,MAAM,UAAA,GAAaC,gCAAA,CAAkB,MAAA,CAAO,OAAO,CAAA;AAEnD,EAAA,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,KAAA,EAAO,CAAA;AAE7B,EAAA,OAAO,MAAA;AACT;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../src/services/router/index.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { createChatRouter, ChatRouterOptions } from './chat';\nimport { createModelRouter } from './models';\nimport {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { createMcpRouter, McpRouterOptions } from './mcp';\n\nexport type RouterOptions = ChatRouterOptions &\n McpRouterOptions & {\n config: RootConfigService;\n logger: LoggerService;\n };\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n router.use('/chat', await createChatRouter(options));\n router.use('/models', await createModelRouter(options));\n router.use('/mcp', await createMcpRouter(options));\n\n const middleware = MiddlewareFactory.create(options);\n\n router.use(middleware.error());\n\n return router;\n}\n"],"names":["Router","express","createChatRouter","createModelRouter","createMcpRouter","MiddlewareFactory"],"mappings":";;;;;;;;;;;;;;AAiBA,eAAsB,aACpB,OAAA,EACyB;AACzB,EAAA,MAAM,SAASA,uBAAA,EAAO;AACtB,EAAA,MAAA,CAAO,GAAA,CAAIC,wBAAA,CAAQ,IAAA,EAAM,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,MAAMC,qBAAA,CAAiB,OAAO,CAAC,CAAA;AACnD,EAAA,MAAA,CAAO,GAAA,CAAI,SAAA,EAAW,MAAMC,wBAAA,CAAkB,OAAO,CAAC,CAAA;AACtD,EAAA,MAAA,CAAO,GAAA,CAAI,MAAA,EAAQ,MAAMC,mBAAA,CAAgB,OAAO,CAAC,CAAA;AAEjD,EAAA,MAAM,UAAA,GAAaC,gCAAA,CAAkB,MAAA,CAAO,OAAO,CAAA;AAEnD,EAAA,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,KAAA,EAAO,CAAA;AAE7B,EAAA,OAAO,MAAA;AACT;;;;"}
@@ -2,58 +2,33 @@
2
2
 
3
3
  var prompts = require('../constants/prompts.cjs.js');
4
4
  var prompts$1 = require('@langchain/core/prompts');
5
- var contentCleaner = require('../utils/content-cleaner.cjs.js');
6
- var langchain = require('@langfuse/langchain');
7
5
 
8
6
  const createSummarizerService = async ({
9
7
  config,
10
8
  models,
11
- langfuseEnabled,
12
- logger
9
+ callback
13
10
  }) => {
14
11
  const summaryModelId = config.getOptionalString("aiAssistant.conversation.summaryModel") ?? models[0].id;
15
12
  const summaryPrompt = config.getOptionalString("aiAssistant.conversation.summaryPrompt") ?? prompts.DEFAULT_SUMMARY_PROMPT;
16
- const pageSummaryPrompt = config.getOptionalString("aiAssistant.page.summaryPrompt") ?? prompts.DEFAULT_PAGE_SUMMARY_PROMPT;
17
13
  const model = models.find((m) => m.id === summaryModelId);
18
14
  if (!model) {
19
15
  throw new Error(`Summary model with id ${summaryModelId} not found`);
20
16
  }
21
17
  const llm = model.chatModel;
22
- const langfuseHandler = langfuseEnabled ? new langchain.CallbackHandler({
23
- userId: "summarizer",
24
- tags: ["backstage-ai-assistant", "summarizer"]
25
- }) : void 0;
26
18
  const chatPromptTemplate = prompts$1.ChatPromptTemplate.fromMessages([
27
19
  prompts$1.SystemMessagePromptTemplate.fromTemplate(`
28
20
  PURPOSE:
29
21
  {summaryPrompt}
30
-
22
+
31
23
  Please summarize the following conversation in {summaryLength}.
32
24
  `),
33
25
  prompts$1.HumanMessagePromptTemplate.fromTemplate(`
34
26
  Conversation:
35
27
  {conversation}
36
-
28
+
37
29
  Please provide a summary of this conversation.
38
30
  `)
39
31
  ]);
40
- const pagePromptTemplate = prompts$1.ChatPromptTemplate.fromMessages([
41
- prompts$1.SystemMessagePromptTemplate.fromTemplate(`
42
- PURPOSE:
43
- {pageSummaryPrompt}
44
-
45
- Please summarize the following page content in {summaryLength}.
46
- `),
47
- prompts$1.HumanMessagePromptTemplate.fromTemplate(`
48
- Page Title: {pageTitle}
49
- Page URL: {pageUrl}
50
-
51
- Content:
52
- {pageContent}
53
-
54
- Please provide a summary of this page.
55
- `)
56
- ]);
57
32
  const summarize = async (messages, summaryLength = "as few words as possible") => {
58
33
  const conversationMessages = messages.filter(
59
34
  (msg) => msg.role === "ai" || msg.role === "human"
@@ -63,59 +38,19 @@ const createSummarizerService = async ({
63
38
  summaryLength,
64
39
  conversation: conversationMessages.map((msg) => `${msg.role}: ${msg.content}`).join("\n")
65
40
  });
66
- const invokeOptions = {
67
- runName: "conversation-summarizer",
68
- tags: ["summarizer"]
69
- };
70
- if (langfuseEnabled) {
71
- invokeOptions.callbacks = [langfuseHandler];
72
- }
73
- const { text } = await llm.invoke(prompt, invokeOptions);
74
- return text.trim();
75
- };
76
- const summarizePage = async (pageContent, pageUrl = "", pageTitle = "", summaryLength = "as few words as possible") => {
77
- logger.info(
78
- `[Page Summary] Starting summarization for content: ${pageContent.length} characters`
79
- );
80
- const cleanedContent = contentCleaner.cleanPageContent(pageContent);
81
- logger.info(
82
- `[Page Summary] Cleaned content: ${cleanedContent.length} characters`
83
- );
84
- const prompt = await pagePromptTemplate.formatMessages({
85
- pageSummaryPrompt,
86
- summaryLength,
87
- pageTitle,
88
- pageUrl,
89
- pageContent: cleanedContent
41
+ const { callbacks, metadata } = await callback.getAgentCallbackData({
42
+ conversationId: "summarizer",
43
+ userId: "system",
44
+ modelId: summaryModelId
45
+ });
46
+ const { text } = await llm.invoke(prompt, {
47
+ callbacks,
48
+ runName: "summarizer",
49
+ metadata
90
50
  });
91
- const invokeOptions = {
92
- runName: "page-summarizer",
93
- tags: ["summarizer", "page-summary"],
94
- timeout: 6e4
95
- // 60 second timeout at the LLM level
96
- };
97
- if (langfuseEnabled) {
98
- invokeOptions.callbacks = [langfuseHandler];
99
- }
100
- logger.info(`[Page Summary] Invoking LLM for summarization...`);
101
- const startTime = Date.now();
102
- try {
103
- const { text } = await llm.invoke(prompt, invokeOptions);
104
- const endTime = Date.now();
105
- logger.info(
106
- `[Page Summary] \u2705 Summarization completed in ${endTime - startTime}ms`
107
- );
108
- return text.trim();
109
- } catch (error) {
110
- const endTime = Date.now();
111
- console.error(
112
- `[Page Summary] \u274C Summarization failed after ${endTime - startTime}ms:`,
113
- error
114
- );
115
- throw error;
116
- }
51
+ return text.trim();
117
52
  };
118
- return { summarize, summarizePage };
53
+ return { summarize };
119
54
  };
120
55
 
121
56
  exports.createSummarizerService = createSummarizerService;
@@ -1 +1 @@
1
- {"version":3,"file":"summarizer.cjs.js","sources":["../../src/services/summarizer.ts"],"sourcesContent":["import {\n RootConfigService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport {\n DEFAULT_SUMMARY_PROMPT,\n DEFAULT_PAGE_SUMMARY_PROMPT,\n} from '../constants/prompts';\nimport {\n SystemMessagePromptTemplate,\n HumanMessagePromptTemplate,\n ChatPromptTemplate,\n} from '@langchain/core/prompts';\nimport { cleanPageContent } from '../utils/content-cleaner';\nimport { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { CallbackHandler } from '@langfuse/langchain';\n\ntype SummarizerService = {\n summarize: (\n conversationMessages: Message[],\n summaryLength?: string,\n ) => Promise<string>;\n summarizePage: (\n pageContent: string,\n pageUrl?: string,\n pageTitle?: string,\n summaryLength?: string,\n ) => Promise<string>;\n};\n\ntype SummarizerServiceOptions = {\n config: RootConfigService;\n models: Model[];\n langfuseEnabled: boolean;\n logger: LoggerService;\n};\n\nexport const createSummarizerService = async ({\n config,\n models,\n langfuseEnabled,\n logger,\n}: SummarizerServiceOptions): Promise<SummarizerService> => {\n const summaryModelId =\n config.getOptionalString('aiAssistant.conversation.summaryModel') ??\n models[0].id;\n\n const summaryPrompt =\n config.getOptionalString('aiAssistant.conversation.summaryPrompt') ??\n DEFAULT_SUMMARY_PROMPT;\n\n const pageSummaryPrompt =\n config.getOptionalString('aiAssistant.page.summaryPrompt') ??\n DEFAULT_PAGE_SUMMARY_PROMPT;\n\n const model = models.find(m => m.id === summaryModelId);\n\n if (!model) {\n throw new Error(`Summary model with id ${summaryModelId} not found`);\n }\n\n const llm = model.chatModel;\n\n // Initialize Langfuse CallbackHandler for tracing if credentials are available\n const langfuseHandler = langfuseEnabled\n ? new CallbackHandler({\n userId: 'summarizer',\n tags: ['backstage-ai-assistant', 'summarizer'],\n })\n : undefined;\n\n const chatPromptTemplate = ChatPromptTemplate.fromMessages([\n SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {summaryPrompt}\n \n Please summarize the following conversation in {summaryLength}.\n `),\n HumanMessagePromptTemplate.fromTemplate(`\n Conversation:\n {conversation}\n \n Please provide a summary of this conversation.\n `),\n ]);\n\n const pagePromptTemplate = ChatPromptTemplate.fromMessages([\n SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {pageSummaryPrompt}\n \n Please summarize the following page content in {summaryLength}.\n `),\n HumanMessagePromptTemplate.fromTemplate(`\n Page Title: {pageTitle}\n Page URL: {pageUrl}\n \n Content:\n {pageContent}\n \n Please provide a summary of this page.\n `),\n ]);\n\n const summarize: SummarizerService['summarize'] = async (\n messages,\n summaryLength = 'as few words as possible',\n ) => {\n const conversationMessages = messages.filter(\n msg => msg.role === 'ai' || msg.role === 'human',\n );\n\n const prompt = await chatPromptTemplate.formatMessages({\n summaryPrompt,\n summaryLength,\n conversation: conversationMessages\n .map(msg => `${msg.role}: ${msg.content}`)\n .join('\\n'),\n });\n\n const invokeOptions: any = {\n runName: 'conversation-summarizer',\n tags: ['summarizer'],\n };\n\n if (langfuseEnabled) {\n invokeOptions.callbacks = [langfuseHandler];\n }\n\n const { text } = await llm.invoke(prompt, invokeOptions);\n\n return text.trim();\n };\n\n const summarizePage: SummarizerService['summarizePage'] = async (\n pageContent,\n pageUrl = '',\n pageTitle = '',\n summaryLength = 'as few words as possible',\n ) => {\n logger.info(\n `[Page Summary] Starting summarization for content: ${pageContent.length} characters`,\n );\n\n // Clean the page content before summarization\n const cleanedContent = cleanPageContent(pageContent);\n logger.info(\n `[Page Summary] Cleaned content: ${cleanedContent.length} characters`,\n );\n\n const prompt = await pagePromptTemplate.formatMessages({\n pageSummaryPrompt,\n summaryLength,\n pageTitle,\n pageUrl,\n pageContent: cleanedContent,\n });\n\n const invokeOptions: any = {\n runName: 'page-summarizer',\n tags: ['summarizer', 'page-summary'],\n timeout: 60000, // 60 second timeout at the LLM level\n };\n\n if (langfuseEnabled) {\n invokeOptions.callbacks = [langfuseHandler];\n }\n\n logger.info(`[Page Summary] Invoking LLM for summarization...`);\n const startTime = Date.now();\n\n try {\n const { text } = await llm.invoke(prompt, invokeOptions);\n const endTime = Date.now();\n logger.info(\n `[Page Summary] ✅ Summarization completed in ${endTime - startTime}ms`,\n );\n\n return text.trim();\n } catch (error) {\n const endTime = Date.now();\n console.error(\n `[Page Summary] ❌ Summarization failed after ${\n endTime - startTime\n }ms:`,\n error,\n );\n throw error;\n }\n };\n\n return { summarize, summarizePage };\n};\n"],"names":["DEFAULT_SUMMARY_PROMPT","DEFAULT_PAGE_SUMMARY_PROMPT","CallbackHandler","ChatPromptTemplate","SystemMessagePromptTemplate","HumanMessagePromptTemplate","cleanPageContent"],"mappings":"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"}
1
+ {"version":3,"file":"summarizer.cjs.js","sources":["../../src/services/summarizer.ts"],"sourcesContent":["import { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DEFAULT_SUMMARY_PROMPT } from '../constants/prompts';\nimport {\n SystemMessagePromptTemplate,\n HumanMessagePromptTemplate,\n ChatPromptTemplate,\n} from '@langchain/core/prompts';\nimport { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { CallbackService } from './callbacks';\n\ntype SummarizerService = {\n summarize: (\n conversationMessages: Message[],\n summaryLength?: string,\n ) => Promise<string>;\n};\n\ntype SummarizerServiceOptions = {\n config: RootConfigService;\n models: Model[];\n callback: CallbackService;\n};\n\nexport const createSummarizerService = async ({\n config,\n models,\n callback,\n}: SummarizerServiceOptions): Promise<SummarizerService> => {\n const summaryModelId =\n config.getOptionalString('aiAssistant.conversation.summaryModel') ??\n models[0].id;\n\n const summaryPrompt =\n config.getOptionalString('aiAssistant.conversation.summaryPrompt') ??\n DEFAULT_SUMMARY_PROMPT;\n\n const model = models.find(m => m.id === summaryModelId);\n\n if (!model) {\n throw new Error(`Summary model with id ${summaryModelId} not found`);\n }\n\n const llm = model.chatModel;\n\n const chatPromptTemplate = ChatPromptTemplate.fromMessages([\n SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {summaryPrompt}\n\n Please summarize the following conversation in {summaryLength}.\n `),\n HumanMessagePromptTemplate.fromTemplate(`\n Conversation:\n {conversation}\n\n Please provide a summary of this conversation.\n `),\n ]);\n\n const summarize: SummarizerService['summarize'] = async (\n messages,\n summaryLength = 'as few words as possible',\n ) => {\n const conversationMessages = messages.filter(\n msg => msg.role === 'ai' || msg.role === 'human',\n );\n\n const prompt = await chatPromptTemplate.formatMessages({\n summaryPrompt,\n summaryLength,\n conversation: conversationMessages\n .map(msg => `${msg.role}: ${msg.content}`)\n .join('\\n'),\n });\n\n const { callbacks, metadata } = await callback.getAgentCallbackData({\n conversationId: 'summarizer',\n userId: 'system',\n modelId: summaryModelId,\n });\n\n const { text } = await llm.invoke(prompt, {\n callbacks,\n runName: 'summarizer',\n metadata,\n });\n\n return text.trim();\n };\n\n return { summarize };\n};\n"],"names":["DEFAULT_SUMMARY_PROMPT","ChatPromptTemplate","SystemMessagePromptTemplate","HumanMessagePromptTemplate"],"mappings":";;;;;AAwBO,MAAM,0BAA0B,OAAO;AAAA,EAC5C,MAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,KAA4D;AAC1D,EAAA,MAAM,iBACJ,MAAA,CAAO,iBAAA,CAAkB,uCAAuC,CAAA,IAChE,MAAA,CAAO,CAAC,CAAA,CAAE,EAAA;AAEZ,EAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,wCAAwC,CAAA,IACjEA,8BAAA;AAEF,EAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,cAAc,CAAA;AAEtD,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,cAAc,CAAA,UAAA,CAAY,CAAA;AAAA,EACrE;AAEA,EAAA,MAAM,MAAM,KAAA,CAAM,SAAA;AAElB,EAAA,MAAM,kBAAA,GAAqBC,6BAAmB,YAAA,CAAa;AAAA,IACzDC,sCAA4B,YAAA,CAAa;AAAA;AAAA;;AAAA;AAAA,IAAA,CAKxC,CAAA;AAAA,IACDC,qCAA2B,YAAA,CAAa;AAAA;AAAA;;AAAA;AAAA,IAAA,CAKvC;AAAA,GACF,CAAA;AAED,EAAA,MAAM,SAAA,GAA4C,OAChD,QAAA,EACA,aAAA,GAAgB,0BAAA,KACb;AACH,IAAA,MAAM,uBAAuB,QAAA,CAAS,MAAA;AAAA,MACpC,CAAA,GAAA,KAAO,GAAA,CAAI,IAAA,KAAS,IAAA,IAAQ,IAAI,IAAA,KAAS;AAAA,KAC3C;AAEA,IAAA,MAAM,MAAA,GAAS,MAAM,kBAAA,CAAmB,cAAA,CAAe;AAAA,MACrD,aAAA;AAAA,MACA,aAAA;AAAA,MACA,YAAA,EAAc,oBAAA,CACX,GAAA,CAAI,CAAA,GAAA,KAAO,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA,EAAA,EAAK,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA,CACxC,KAAK,IAAI;AAAA,KACb,CAAA;AAED,IAAA,MAAM,EAAE,SAAA,EAAW,QAAA,EAAS,GAAI,MAAM,SAAS,oBAAA,CAAqB;AAAA,MAClE,cAAA,EAAgB,YAAA;AAAA,MAChB,MAAA,EAAQ,QAAA;AAAA,MACR,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,GAAA,CAAI,OAAO,MAAA,EAAQ;AAAA,MACxC,SAAA;AAAA,MACA,OAAA,EAAS,YAAA;AAAA,MACT;AAAA,KACD,CAAA;AAED,IAAA,OAAO,KAAK,IAAA,EAAK;AAAA,EACnB,CAAA;AAEA,EAAA,OAAO,EAAE,SAAA,EAAU;AACrB;;;;"}
@@ -10,7 +10,7 @@ var z__default = /*#__PURE__*/_interopDefaultCompat(z);
10
10
  const createSearchKnowledgeTool = ({
11
11
  vectorStore
12
12
  }) => {
13
- return backstagePluginAiAssistantNode.createAssistantTool({
13
+ const knowledgeTool = backstagePluginAiAssistantNode.createAssistantTool({
14
14
  tool: {
15
15
  name: "search-knowledge-base",
16
16
  description: `Search the internal knowledge base containing company specific information.
@@ -40,6 +40,7 @@ Do NOT use for general knowledge that doesn't require company-specific informati
40
40
  }
41
41
  }
42
42
  });
43
+ return knowledgeTool;
43
44
  };
44
45
 
45
46
  exports.createSearchKnowledgeTool = createSearchKnowledgeTool;
@@ -1 +1 @@
1
- {"version":3,"file":"searchKnowledge.cjs.js","sources":["../../../src/services/tools/searchKnowledge.ts"],"sourcesContent":["import {\n createAssistantTool,\n Tool,\n VectorStore,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport z from 'zod';\n\ntype CreateSearchKnowledgeToolOptions = {\n vectorStore: VectorStore;\n};\n\nexport const createSearchKnowledgeTool = ({\n vectorStore,\n}: CreateSearchKnowledgeToolOptions): Tool => {\n return createAssistantTool({\n tool: {\n name: 'search-knowledge-base',\n description: `Search the internal knowledge base containing company specific information.\n\nUse this tool when users ask about:\n- General questions about the company or internal information\n\nDo NOT use for general knowledge that doesn't require company-specific information.`,\n schema: z.object({\n query: z.string().describe('The query to search for.'),\n filter: z\n .object({\n source: z.string().optional().describe('Source to filter by.'),\n id: z.string().optional().describe('ID to filter by.'),\n })\n .optional()\n .describe('Filters to apply to the search.'),\n amount: z\n .number()\n .min(1)\n .optional()\n .describe('The number of results to return.'),\n }),\n func: async ({ query, filter, amount }) => {\n const results = await vectorStore.similaritySearch(\n query,\n filter,\n amount,\n );\n if (results.length === 0) {\n return 'No relevant information found.';\n }\n return results.map(r => r.content).join('\\n---\\n');\n },\n },\n });\n};\n"],"names":["createAssistantTool","z"],"mappings":";;;;;;;;;AAWO,MAAM,4BAA4B,CAAC;AAAA,EACxC;AACF,CAAA,KAA8C;AAC5C,EAAA,OAAOA,kDAAA,CAAoB;AAAA,IACzB,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,uBAAA;AAAA,MACN,WAAA,EAAa,CAAA;;AAAA;AAAA;;AAAA,mFAAA,CAAA;AAAA,MAMb,MAAA,EAAQC,mBAAE,MAAA,CAAO;AAAA,QACf,KAAA,EAAOA,kBAAA,CAAE,MAAA,EAAO,CAAE,SAAS,0BAA0B,CAAA;AAAA,QACrD,MAAA,EAAQA,mBACL,MAAA,CAAO;AAAA,UACN,QAAQA,kBAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,SAAS,sBAAsB,CAAA;AAAA,UAC7D,IAAIA,kBAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,SAAS,kBAAkB;AAAA,SACtD,CAAA,CACA,QAAA,EAAS,CACT,SAAS,iCAAiC,CAAA;AAAA,QAC7C,MAAA,EAAQA,kBAAA,CACL,MAAA,EAAO,CACP,GAAA,CAAI,CAAC,CAAA,CACL,QAAA,EAAS,CACT,QAAA,CAAS,kCAAkC;AAAA,OAC/C,CAAA;AAAA,MACD,MAAM,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,QAAO,KAAM;AACzC,QAAA,MAAM,OAAA,GAAU,MAAM,WAAA,CAAY,gBAAA;AAAA,UAChC,KAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA,SACF;AACA,QAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,UAAA,OAAO,gCAAA;AAAA,QACT;AACA,QAAA,OAAO,QAAQ,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,OAAO,CAAA,CAAE,KAAK,SAAS,CAAA;AAAA,MACnD;AAAA;AACF,GACD,CAAA;AACH;;;;"}
1
+ {"version":3,"file":"searchKnowledge.cjs.js","sources":["../../../src/services/tools/searchKnowledge.ts"],"sourcesContent":["import {\n createAssistantTool,\n Tool,\n VectorStore,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport z from 'zod';\n\ntype CreateSearchKnowledgeToolOptions = {\n vectorStore: VectorStore;\n};\n\nexport const createSearchKnowledgeTool = ({\n vectorStore,\n}: CreateSearchKnowledgeToolOptions): Tool => {\n const knowledgeTool = createAssistantTool({\n tool: {\n name: 'search-knowledge-base',\n description: `Search the internal knowledge base containing company specific information.\n\nUse this tool when users ask about:\n- General questions about the company or internal information\n\nDo NOT use for general knowledge that doesn't require company-specific information.`,\n schema: z.object({\n query: z.string().describe('The query to search for.'),\n filter: z\n .object({\n source: z.string().optional().describe('Source to filter by.'),\n id: z.string().optional().describe('ID to filter by.'),\n })\n .optional()\n .describe('Filters to apply to the search.'),\n amount: z\n .number()\n .min(1)\n .optional()\n .describe('The number of results to return.'),\n }),\n func: async ({ query, filter, amount }) => {\n const results = await vectorStore.similaritySearch(\n query,\n filter,\n amount,\n );\n if (results.length === 0) {\n return 'No relevant information found.';\n }\n return results.map(r => r.content).join('\\n---\\n');\n },\n },\n });\n\n return knowledgeTool;\n};\n"],"names":["createAssistantTool","z"],"mappings":";;;;;;;;;AAWO,MAAM,4BAA4B,CAAC;AAAA,EACxC;AACF,CAAA,KAA8C;AAC5C,EAAA,MAAM,gBAAgBA,kDAAA,CAAoB;AAAA,IACxC,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,uBAAA;AAAA,MACN,WAAA,EAAa,CAAA;;AAAA;AAAA;;AAAA,mFAAA,CAAA;AAAA,MAMb,MAAA,EAAQC,mBAAE,MAAA,CAAO;AAAA,QACf,KAAA,EAAOA,kBAAA,CAAE,MAAA,EAAO,CAAE,SAAS,0BAA0B,CAAA;AAAA,QACrD,MAAA,EAAQA,mBACL,MAAA,CAAO;AAAA,UACN,QAAQA,kBAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,SAAS,sBAAsB,CAAA;AAAA,UAC7D,IAAIA,kBAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,SAAS,kBAAkB;AAAA,SACtD,CAAA,CACA,QAAA,EAAS,CACT,SAAS,iCAAiC,CAAA;AAAA,QAC7C,MAAA,EAAQA,kBAAA,CACL,MAAA,EAAO,CACP,GAAA,CAAI,CAAC,CAAA,CACL,QAAA,EAAS,CACT,QAAA,CAAS,kCAAkC;AAAA,OAC/C,CAAA;AAAA,MACD,MAAM,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,QAAO,KAAM;AACzC,QAAA,MAAM,OAAA,GAAU,MAAM,WAAA,CAAY,gBAAA;AAAA,UAChC,KAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA,SACF;AACA,QAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,UAAA,OAAO,gCAAA;AAAA,QACT;AACA,QAAA,OAAO,QAAQ,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,OAAO,CAAA,CAAE,KAAK,SAAS,CAAA;AAAA,MACnD;AAAA;AACF,GACD,CAAA;AAED,EAAA,OAAO,aAAA;AACT;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweetoburrito/backstage-plugin-ai-assistant-backend",
3
- "version": "0.0.0-snapshot-20251030154701",
3
+ "version": "0.0.0-snapshot-20251113134620",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -43,14 +43,8 @@
43
43
  "@langchain/langgraph": "^0.4.9",
44
44
  "@langchain/mcp-adapters": "^1.0.0",
45
45
  "@langchain/textsplitters": "^0.1.0",
46
- "@langfuse/client": "^4.3.0",
47
- "@langfuse/core": "^4.0.0",
48
- "@langfuse/langchain": "^4.0.0",
49
- "@langfuse/otel": "^4.3.0",
50
- "@opentelemetry/api": "^1.9.0",
51
- "@opentelemetry/sdk-node": "^0.207.0",
52
- "@sweetoburrito/backstage-plugin-ai-assistant-common": "0.0.0-snapshot-20251030154701",
53
- "@sweetoburrito/backstage-plugin-ai-assistant-node": "0.0.0-snapshot-20251030154701",
46
+ "@sweetoburrito/backstage-plugin-ai-assistant-common": "0.0.0-snapshot-20251113134620",
47
+ "@sweetoburrito/backstage-plugin-ai-assistant-node": "0.0.0-snapshot-20251113134620",
54
48
  "express": "^4.17.1",
55
49
  "express-promise-router": "^4.1.0",
56
50
  "knex": "^3.1.0",
@@ -66,6 +60,7 @@
66
60
  "@backstage/plugin-events-backend": "backstage:^",
67
61
  "@backstage/plugin-signals-backend": "backstage:^",
68
62
  "@backstage/types": "backstage:^",
63
+ "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-callback-provider-langfuse": "workspace:^",
69
64
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-embeddings-provider-azure-open-ai": "workspace:^",
70
65
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-embeddings-provider-ollama": "workspace:^",
71
66
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-azure-devops": "workspace:^",
@@ -74,7 +69,6 @@
74
69
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-azure-ai": "workspace:^",
75
70
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-google-vertex-ai": "workspace:^",
76
71
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-ollama": "workspace:^",
77
- "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-realtime-voice-azure": "workspace:^",
78
72
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-tool-provider-backstage": "workspace:^",
79
73
  "@types/express": "^4.0.0",
80
74
  "@types/supertest": "^2.0.12",
@@ -1,48 +0,0 @@
1
- 'use strict';
2
-
3
- var otel = require('@langfuse/otel');
4
- var sdkNode = require('@opentelemetry/sdk-node');
5
- var client = require('@langfuse/client');
6
-
7
- function initLangfuse(config, logger) {
8
- const langfuseSecret = config.getOptionalString(
9
- "aiAssistant.langfuse.secretKey"
10
- );
11
- const langfusePublic = config.getOptionalString(
12
- "aiAssistant.langfuse.publicKey"
13
- );
14
- const langfuseBaseUrl = config.getOptionalString(
15
- "aiAssistant.langfuse.baseUrl"
16
- );
17
- let langfuseSpanProcessor = void 0;
18
- if (langfuseSecret && langfusePublic && langfuseBaseUrl) {
19
- langfuseSpanProcessor = new otel.LangfuseSpanProcessor({
20
- secretKey: langfuseSecret,
21
- publicKey: langfusePublic,
22
- baseUrl: langfuseBaseUrl
23
- });
24
- const sdk = new sdkNode.NodeSDK({
25
- spanProcessors: [langfuseSpanProcessor]
26
- });
27
- const langfuseClient = new client.LangfuseClient({
28
- secretKey: langfuseSecret,
29
- publicKey: langfusePublic,
30
- baseUrl: langfuseBaseUrl
31
- });
32
- logger.info(
33
- "Langfuse: Starting OpenTelemetry SDK with LangfuseSpanProcessor"
34
- );
35
- sdk.start();
36
- return { langfuseEnabled: true, langfuseClient };
37
- }
38
- logger.info(
39
- "Langfuse: Skipping Langfuse initialization, credentials not found."
40
- );
41
- return {
42
- langfuseEnabled: false,
43
- langfuseClient: void 0
44
- };
45
- }
46
-
47
- exports.initLangfuse = initLangfuse;
48
- //# sourceMappingURL=langfuse.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"langfuse.cjs.js","sources":["../../src/services/langfuse.ts"],"sourcesContent":["import {\n RootConfigService,\n RootLoggerService,\n} from '@backstage/backend-plugin-api';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport { LangfuseClient } from '@langfuse/client';\n\nexport function initLangfuse(\n config: RootConfigService,\n logger: RootLoggerService,\n): { langfuseEnabled: boolean; langfuseClient: LangfuseClient | undefined } {\n const langfuseSecret = config.getOptionalString(\n 'aiAssistant.langfuse.secretKey',\n );\n const langfusePublic = config.getOptionalString(\n 'aiAssistant.langfuse.publicKey',\n );\n const langfuseBaseUrl = config.getOptionalString(\n 'aiAssistant.langfuse.baseUrl',\n );\n\n let langfuseSpanProcessor: LangfuseSpanProcessor | undefined = undefined;\n if (langfuseSecret && langfusePublic && langfuseBaseUrl) {\n langfuseSpanProcessor = new LangfuseSpanProcessor({\n secretKey: langfuseSecret,\n publicKey: langfusePublic,\n baseUrl: langfuseBaseUrl,\n });\n\n const sdk = new NodeSDK({\n spanProcessors: [langfuseSpanProcessor],\n });\n\n const langfuseClient = new LangfuseClient({\n secretKey: langfuseSecret,\n publicKey: langfusePublic,\n baseUrl: langfuseBaseUrl,\n });\n\n logger.info(\n 'Langfuse: Starting OpenTelemetry SDK with LangfuseSpanProcessor',\n );\n sdk.start();\n return { langfuseEnabled: true, langfuseClient };\n }\n logger.info(\n 'Langfuse: Skipping Langfuse initialization, credentials not found.',\n );\n return {\n langfuseEnabled: false,\n langfuseClient: undefined,\n };\n}\n"],"names":["LangfuseSpanProcessor","NodeSDK","LangfuseClient"],"mappings":";;;;;;AAQO,SAAS,YAAA,CACd,QACA,MAAA,EAC0E;AAC1E,EAAA,MAAM,iBAAiB,MAAA,CAAO,iBAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,iBAAiB,MAAA,CAAO,iBAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,kBAAkB,MAAA,CAAO,iBAAA;AAAA,IAC7B;AAAA,GACF;AAEA,EAAA,IAAI,qBAAA,GAA2D,MAAA;AAC/D,EAAA,IAAI,cAAA,IAAkB,kBAAkB,eAAA,EAAiB;AACvD,IAAA,qBAAA,GAAwB,IAAIA,0BAAA,CAAsB;AAAA,MAChD,SAAA,EAAW,cAAA;AAAA,MACX,SAAA,EAAW,cAAA;AAAA,MACX,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAM,GAAA,GAAM,IAAIC,eAAA,CAAQ;AAAA,MACtB,cAAA,EAAgB,CAAC,qBAAqB;AAAA,KACvC,CAAA;AAED,IAAA,MAAM,cAAA,GAAiB,IAAIC,qBAAA,CAAe;AAAA,MACxC,SAAA,EAAW,cAAA;AAAA,MACX,SAAA,EAAW,cAAA;AAAA,MACX,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAA,CAAO,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA,GAAA,CAAI,KAAA,EAAM;AACV,IAAA,OAAO,EAAE,eAAA,EAAiB,IAAA,EAAM,cAAA,EAAe;AAAA,EACjD;AACA,EAAA,MAAA,CAAO,IAAA;AAAA,IACL;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,eAAA,EAAiB,KAAA;AAAA,IACjB,cAAA,EAAgB;AAAA,GAClB;AACF;;;;"}
@@ -1,128 +0,0 @@
1
- 'use strict';
2
-
3
- var Router = require('express-promise-router');
4
- var z = require('zod');
5
- var validation = require('./middleware/validation.cjs.js');
6
- var summarizer = require('../summarizer.cjs.js');
7
-
8
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
-
10
- var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
11
- var z__default = /*#__PURE__*/_interopDefaultCompat(z);
12
-
13
- function preprocessContentForSummarization(content) {
14
- let processed = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
15
- processed = processed.replace(/[ \t]{2,}/g, " ");
16
- processed = processed.replace(/\n{3,}/g, "\n\n");
17
- if (processed.length > 4e4) {
18
- const lines = processed.split("\n");
19
- const importantLines = [];
20
- for (const line of lines) {
21
- const trimmed = line.trim();
22
- if (trimmed.length > 10 && !trimmed.match(/^[^\w]*$/) && !importantLines.some(
23
- (existing) => existing.toLowerCase().includes(trimmed.toLowerCase().substring(0, 50))
24
- )) {
25
- importantLines.push(line);
26
- }
27
- if (importantLines.join("\n").length > 35e3) {
28
- break;
29
- }
30
- }
31
- processed = importantLines.join("\n");
32
- }
33
- return processed;
34
- }
35
- async function createPageSummaryRouter(options) {
36
- const { httpAuth, userInfo, config, models, langfuseEnabled, logger } = options;
37
- const router = Router__default.default();
38
- const summarizer$1 = await summarizer.createSummarizerService({
39
- config,
40
- models,
41
- langfuseEnabled,
42
- logger
43
- });
44
- const pageSummarySchema = z__default.default.object({
45
- pageContent: z__default.default.string().describe("The HTML/text content of the page to summarize"),
46
- pageUrl: z__default.default.string().optional().describe("The URL of the page"),
47
- pageTitle: z__default.default.string().optional().describe("The title of the page"),
48
- summaryLength: z__default.default.string().optional().describe("Desired summary length instruction")
49
- });
50
- router.post(
51
- "/summarize",
52
- validation.validation(pageSummarySchema, "body"),
53
- async (req, res) => {
54
- try {
55
- const { pageContent, pageUrl, pageTitle, summaryLength } = req.body;
56
- req.setTimeout(12e4, () => {
57
- console.error("Request timeout for page summary");
58
- if (!res.headersSent) {
59
- res.status(504).json({
60
- success: false,
61
- error: "Request timeout - summarization took too long"
62
- });
63
- }
64
- });
65
- const credentials = await httpAuth.credentials(req);
66
- const { userEntityRef } = await userInfo.getUserInfo(credentials);
67
- logger.info(`Page summary request from user: ${userEntityRef}`);
68
- logger.info(`Page URL: ${pageUrl || "N/A"}`);
69
- logger.info(`Page Title: ${pageTitle || "N/A"}`);
70
- logger.info(`Content length: ${pageContent.length} characters`);
71
- const maxContentLength = 5e4;
72
- let processedContent = preprocessContentForSummarization(pageContent);
73
- if (processedContent.length > maxContentLength) {
74
- logger.info(
75
- `Content too large (${processedContent.length} chars), truncating to ${maxContentLength} chars`
76
- );
77
- processedContent = `${processedContent.substring(
78
- 0,
79
- maxContentLength
80
- )}
81
-
82
- [Content truncated due to length]`;
83
- }
84
- logger.info(
85
- `Content preprocessing: ${pageContent.length} -> ${processedContent.length} chars`
86
- );
87
- const summaryPromise = summarizer$1.summarizePage(
88
- processedContent,
89
- pageUrl,
90
- pageTitle,
91
- summaryLength || "in 2-3 sentences"
92
- );
93
- const timeoutPromise = new Promise((_, reject) => {
94
- setTimeout(() => reject(new Error("Summarization timeout")), 9e4);
95
- });
96
- const summary = await Promise.race([summaryPromise, timeoutPromise]);
97
- res.json({
98
- success: true,
99
- summary,
100
- originalContentLength: pageContent.length,
101
- processedContentLength: processedContent.length,
102
- contentTruncated: pageContent.length > maxContentLength,
103
- pageUrl,
104
- pageTitle
105
- });
106
- } catch (error) {
107
- console.error("Error generating page summary:", error);
108
- if (!res.headersSent) {
109
- if (error instanceof Error && error.message.includes("timeout")) {
110
- res.status(504).json({
111
- success: false,
112
- error: "Summarization request timed out. Please try with shorter content."
113
- });
114
- } else {
115
- res.status(500).json({
116
- success: false,
117
- error: error instanceof Error ? error.message : "Unknown error"
118
- });
119
- }
120
- }
121
- }
122
- }
123
- );
124
- return router;
125
- }
126
-
127
- exports.createPageSummaryRouter = createPageSummaryRouter;
128
- //# sourceMappingURL=page-summary.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"page-summary.cjs.js","sources":["../../../src/services/router/page-summary.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport z from 'zod';\nimport { validation } from './middleware/validation';\nimport {\n DatabaseService,\n HttpAuthService,\n UserInfoService,\n RootConfigService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { createSummarizerService } from '../summarizer';\nimport { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\n/**\n * Preprocess content for summarization to ensure optimal performance\n */\nfunction preprocessContentForSummarization(content: string): string {\n // Remove excessive whitespace and normalize line endings\n let processed = content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n // Remove repeated whitespace patterns\n processed = processed.replace(/[ \\t]{2,}/g, ' ');\n processed = processed.replace(/\\n{3,}/g, '\\n\\n');\n\n // If content is still very large, focus on the most important parts\n if (processed.length > 40000) {\n const lines = processed.split('\\n');\n const importantLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip very short lines, repeated content, or obvious noise\n if (\n trimmed.length > 10 &&\n !trimmed.match(/^[^\\w]*$/) &&\n !importantLines.some(existing =>\n existing\n .toLowerCase()\n .includes(trimmed.toLowerCase().substring(0, 50)),\n )\n ) {\n importantLines.push(line);\n }\n\n // Stop if we have enough content\n if (importantLines.join('\\n').length > 35000) {\n break;\n }\n }\n\n processed = importantLines.join('\\n');\n }\n\n return processed;\n}\n\nexport type PageSummaryRouterOptions = {\n database: DatabaseService;\n httpAuth: HttpAuthService;\n userInfo: UserInfoService;\n config: RootConfigService;\n logger: LoggerService;\n models: Model[];\n langfuseEnabled: boolean;\n};\n\nexport async function createPageSummaryRouter(\n options: PageSummaryRouterOptions,\n): Promise<express.Router> {\n const { httpAuth, userInfo, config, models, langfuseEnabled, logger } =\n options;\n const router = Router();\n\n const summarizer = await createSummarizerService({\n config,\n models,\n langfuseEnabled,\n logger,\n });\n\n const pageSummarySchema = z.object({\n pageContent: z\n .string()\n .describe('The HTML/text content of the page to summarize'),\n pageUrl: z.string().optional().describe('The URL of the page'),\n pageTitle: z.string().optional().describe('The title of the page'),\n summaryLength: z\n .string()\n .optional()\n .describe('Desired summary length instruction'),\n });\n\n router.post(\n '/summarize',\n validation(pageSummarySchema, 'body'),\n async (req, res) => {\n try {\n const { pageContent, pageUrl, pageTitle, summaryLength } = req.body;\n\n // Set a longer timeout for this endpoint (2 minutes)\n req.setTimeout(120000, () => {\n console.error('Request timeout for page summary');\n if (!res.headersSent) {\n res.status(504).json({\n success: false,\n error: 'Request timeout - summarization took too long',\n });\n }\n });\n\n // Authenticate the request\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n logger.info(`Page summary request from user: ${userEntityRef}`);\n logger.info(`Page URL: ${pageUrl || 'N/A'}`);\n logger.info(`Page Title: ${pageTitle || 'N/A'}`);\n logger.info(`Content length: ${pageContent.length} characters`);\n\n // Preprocess and check content length\n const maxContentLength = 50000; // 50k characters\n let processedContent = preprocessContentForSummarization(pageContent);\n\n if (processedContent.length > maxContentLength) {\n logger.info(\n `Content too large (${processedContent.length} chars), truncating to ${maxContentLength} chars`,\n );\n processedContent = `${processedContent.substring(\n 0,\n maxContentLength,\n )}\\n\\n[Content truncated due to length]`;\n }\n\n logger.info(\n `Content preprocessing: ${pageContent.length} -> ${processedContent.length} chars`,\n );\n\n // Add timeout protection for the summarization call\n const summaryPromise = summarizer.summarizePage(\n processedContent,\n pageUrl,\n pageTitle,\n summaryLength || 'in 2-3 sentences',\n );\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error('Summarization timeout')), 90000); // 90 seconds\n });\n\n const summary = await Promise.race([summaryPromise, timeoutPromise]);\n\n res.json({\n success: true,\n summary,\n originalContentLength: pageContent.length,\n processedContentLength: processedContent.length,\n contentTruncated: pageContent.length > maxContentLength,\n pageUrl,\n pageTitle,\n });\n } catch (error) {\n console.error('Error generating page summary:', error);\n\n if (!res.headersSent) {\n if (error instanceof Error && error.message.includes('timeout')) {\n res.status(504).json({\n success: false,\n error:\n 'Summarization request timed out. Please try with shorter content.',\n });\n } else {\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n }\n }\n }\n },\n );\n\n return router;\n}\n"],"names":["Router","summarizer","createSummarizerService","z","validation"],"mappings":"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"}
@@ -1,80 +0,0 @@
1
- 'use strict';
2
-
3
- function cleanPageContent(content) {
4
- if (content.length < 100) {
5
- return content.trim();
6
- }
7
- let cleanedContent = content;
8
- cleanedContent = cleanedContent.replace(
9
- /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
10
- ""
11
- );
12
- cleanedContent = cleanedContent.replace(
13
- /\s*on\w+\s*=\s*["'][^"']*["']/gi,
14
- ""
15
- );
16
- cleanedContent = cleanedContent.replace(/javascript:[^"'\s>]*/gi, "");
17
- cleanedContent = cleanedContent.replace(
18
- /<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi,
19
- ""
20
- );
21
- cleanedContent = cleanedContent.replace(
22
- /\s*style\s*=\s*["'][^"']*["']/gi,
23
- ""
24
- );
25
- cleanedContent = cleanedContent.replace(
26
- /\s*class\s*=\s*["'][^"']*["']/gi,
27
- ""
28
- );
29
- cleanedContent = cleanedContent.replace(
30
- /<(nav|header|footer|aside|script|style|noscript)[^>]*>[\s\S]*?<\/\1>/gi,
31
- ""
32
- );
33
- cleanedContent = convertHtmlToStructuredText(cleanedContent);
34
- cleanedContent = normalizeContentWhitespace(cleanedContent);
35
- const maxFinalLength = 3e4;
36
- if (cleanedContent.length > maxFinalLength) {
37
- cleanedContent = `${cleanedContent.substring(
38
- 0,
39
- maxFinalLength
40
- )}
41
-
42
- [Content truncated for processing efficiency]`;
43
- }
44
- return cleanedContent.trim();
45
- }
46
- function convertHtmlToStructuredText(html) {
47
- let text = html;
48
- text = text.replace(/<h([1-6])[^>]*>/gi, (_, level) => {
49
- const hashes = "#".repeat(parseInt(level, 10));
50
- return `
51
- ${hashes} `;
52
- });
53
- text = text.replace(/<\/h[1-6]>/gi, "\n\n");
54
- text = text.replace(/<p[^>]*>/gi, "\n");
55
- text = text.replace(/<\/p>/gi, "\n\n");
56
- text = text.replace(/<br[^>]*>/gi, "\n");
57
- text = text.replace(/<ul[^>]*>/gi, "\n");
58
- text = text.replace(/<\/ul>/gi, "\n");
59
- text = text.replace(/<ol[^>]*>/gi, "\n");
60
- text = text.replace(/<\/ol>/gi, "\n");
61
- text = text.replace(/<li[^>]*>/gi, "\n- ");
62
- text = text.replace(/<\/li>/gi, "");
63
- text = text.replace(/<(strong|b)[^>]*>/gi, "**");
64
- text = text.replace(/<\/(strong|b)>/gi, "**");
65
- text = text.replace(/<(em|i)[^>]*>/gi, "*");
66
- text = text.replace(/<\/(em|i)>/gi, "*");
67
- text = text.replace(/<a[^>]*href\s*=\s*["']([^"']*)["'][^>]*>/gi, "[");
68
- text = text.replace(/<\/a>/gi, "]");
69
- text = stripHtmlTags(text);
70
- return text;
71
- }
72
- function stripHtmlTags(html) {
73
- return html.replace(/<[^>]*>/g, "");
74
- }
75
- function normalizeContentWhitespace(text) {
76
- return text.replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").replace(/^[ \t]+|[ \t]+$/gm, "").replace(/^\s*$/gm, "");
77
- }
78
-
79
- exports.cleanPageContent = cleanPageContent;
80
- //# sourceMappingURL=content-cleaner.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"content-cleaner.cjs.js","sources":["../../src/utils/content-cleaner.ts"],"sourcesContent":["/**\n * Content cleaning utilities for processing page content before summarization\n */\n\n/**\n * Clean page content by removing HTML markup, styling, and scripts\n * while preserving meaningful text content and structure.\n */\nexport function cleanPageContent(content: string): string {\n // Early return for very small content\n if (content.length < 100) {\n return content.trim();\n }\n\n let cleanedContent = content;\n\n // Remove JavaScript blocks and inline scripts (optimized for performance)\n cleanedContent = cleanedContent.replace(\n /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n '',\n );\n cleanedContent = cleanedContent.replace(\n /\\s*on\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi,\n '',\n );\n cleanedContent = cleanedContent.replace(/javascript:[^\"'\\s>]*/gi, '');\n\n // Remove CSS style blocks and inline styles\n cleanedContent = cleanedContent.replace(\n /<style\\b[^<]*(?:(?!<\\/style>)<[^<]*)*<\\/style>/gi,\n '',\n );\n cleanedContent = cleanedContent.replace(\n /\\s*style\\s*=\\s*[\"'][^\"']*[\"']/gi,\n '',\n );\n cleanedContent = cleanedContent.replace(\n /\\s*class\\s*=\\s*[\"'][^\"']*[\"']/gi,\n '',\n );\n\n // Remove common noise elements early to reduce processing load\n cleanedContent = cleanedContent.replace(\n /<(nav|header|footer|aside|script|style|noscript)[^>]*>[\\s\\S]*?<\\/\\1>/gi,\n '',\n );\n\n // Convert HTML to structured text preserving important elements\n cleanedContent = convertHtmlToStructuredText(cleanedContent);\n\n // Normalize whitespace\n cleanedContent = normalizeContentWhitespace(cleanedContent);\n\n // Limit final content length to prevent excessive token usage\n const maxFinalLength = 30000; // 30k characters\n if (cleanedContent.length > maxFinalLength) {\n cleanedContent = `${cleanedContent.substring(\n 0,\n maxFinalLength,\n )}\\n\\n[Content truncated for processing efficiency]`;\n }\n\n return cleanedContent.trim();\n}\n\n/**\n * Convert HTML to structured text preserving headings, lists, and paragraphs\n */\nfunction convertHtmlToStructuredText(html: string): string {\n let text = html;\n\n // Convert headings\n text = text.replace(/<h([1-6])[^>]*>/gi, (_, level) => {\n const hashes = '#'.repeat(parseInt(level, 10));\n return `\\n${hashes} `;\n });\n text = text.replace(/<\\/h[1-6]>/gi, '\\n\\n');\n\n // Convert paragraphs\n text = text.replace(/<p[^>]*>/gi, '\\n');\n text = text.replace(/<\\/p>/gi, '\\n\\n');\n\n // Convert line breaks\n text = text.replace(/<br[^>]*>/gi, '\\n');\n\n // Convert lists\n text = text.replace(/<ul[^>]*>/gi, '\\n');\n text = text.replace(/<\\/ul>/gi, '\\n');\n text = text.replace(/<ol[^>]*>/gi, '\\n');\n text = text.replace(/<\\/ol>/gi, '\\n');\n text = text.replace(/<li[^>]*>/gi, '\\n- ');\n text = text.replace(/<\\/li>/gi, '');\n\n // Convert emphasis\n text = text.replace(/<(strong|b)[^>]*>/gi, '**');\n text = text.replace(/<\\/(strong|b)>/gi, '**');\n text = text.replace(/<(em|i)[^>]*>/gi, '*');\n text = text.replace(/<\\/(em|i)>/gi, '*');\n\n // Convert links (preserve URL in brackets)\n text = text.replace(/<a[^>]*href\\s*=\\s*[\"']([^\"']*)[\"'][^>]*>/gi, '[');\n text = text.replace(/<\\/a>/gi, ']');\n\n // Remove remaining HTML tags\n text = stripHtmlTags(text);\n\n return text;\n}\n\n/**\n * Simple HTML tag removal\n */\nfunction stripHtmlTags(html: string): string {\n return html.replace(/<[^>]*>/g, '');\n}\n\n/**\n * Normalize whitespace, removing excess spaces and line breaks\n */\nfunction normalizeContentWhitespace(text: string): string {\n return (\n text\n // Replace multiple spaces with single space\n .replace(/[ \\t]+/g, ' ')\n // Replace multiple line breaks with double line break (paragraph separation)\n .replace(/\\n{3,}/g, '\\n\\n')\n // Remove spaces at the beginning and end of lines\n .replace(/^[ \\t]+|[ \\t]+$/gm, '')\n // Remove empty lines that only contain whitespace\n .replace(/^\\s*$/gm, '')\n );\n}\n\n/**\n * Extract meaningful text content from HTML while removing noise\n */\nexport function extractTextContent(html: string): string {\n let text = html;\n\n // Remove comments\n text = text.replace(/<!--[\\s\\S]*?-->/g, '');\n\n // Remove common non-content elements by tag name\n text = text.replace(/<(nav|header|footer|aside)[^>]*>[\\s\\S]*?<\\/\\1>/gi, '');\n\n return cleanPageContent(text);\n}\n"],"names":[],"mappings":"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"}