@sweetoburrito/backstage-plugin-ai-assistant-backend 0.0.0-snapshot-20251029080430 → 0.0.0-snapshot-20251029145101

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,6 +1,6 @@
1
1
  'use strict';
2
2
 
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.";
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
4
  const DEFAULT_IDENTITY_PROMPT = `
5
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.
6
6
  `;
@@ -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.';\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;;;;;;;;"}
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;;;;;;;;"}
@@ -33,7 +33,9 @@ class ChatStore {
33
33
  role: row.role,
34
34
  content: row.content,
35
35
  id: row.id,
36
- metadata: row.metadata
36
+ metadata: row.metadata,
37
+ score: row.score,
38
+ traceId: row.trace_id
37
39
  }));
38
40
  return chatMessages;
39
41
  }
@@ -44,6 +46,7 @@ class ChatStore {
44
46
  role: msg.role,
45
47
  content: msg.content,
46
48
  metadata: msg.metadata,
49
+ trace_id: msg.traceId,
47
50
  userRef,
48
51
  created_at: this.client.fn.now()
49
52
  }));
@@ -53,7 +56,9 @@ class ChatStore {
53
56
  await this.messageTable().where({ id: message.id }).update({
54
57
  role: message.role,
55
58
  content: message.content,
56
- metadata: message.metadata
59
+ metadata: message.metadata,
60
+ score: message.score,
61
+ trace_id: message.traceId
57
62
  });
58
63
  }
59
64
  async getConversation(conversationId, userRef) {
@@ -90,6 +95,21 @@ class ChatStore {
90
95
  }));
91
96
  return conversations;
92
97
  }
98
+ async getMessageById(messageId) {
99
+ const row = await this.messageTable().where({ id: messageId }).first();
100
+ if (!row) {
101
+ return null;
102
+ }
103
+ const message = {
104
+ id: row.id,
105
+ role: row.role,
106
+ content: row.content,
107
+ metadata: row.metadata,
108
+ score: row.score,
109
+ traceId: row.trace_id
110
+ };
111
+ return message;
112
+ }
93
113
  }
94
114
 
95
115
  exports.ChatStore = ChatStore;
@@ -1 +1 @@
1
- {"version":3,"file":"chat-store.cjs.js","sources":["../../src/database/chat-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport {\n Message,\n Conversation,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { Knex } from 'knex';\n\nconst MESSAGE_TABLE_NAME = 'message';\nconst CONVERSATION_TABLE_NAME = 'conversation';\n\nexport type ChatStoreOptions = {\n database: DatabaseService;\n};\n\nexport class ChatStore {\n /**\n * Creates an instance of ChatStore.\n * @param client - The Knex client to interact with the PostgreSQL database.\n */\n constructor(private readonly client: Knex) {}\n\n static async fromConfig({ database }: ChatStoreOptions) {\n const client = await database.getClient();\n return new ChatStore(client);\n }\n\n messageTable() {\n return this.client(MESSAGE_TABLE_NAME);\n }\n\n conversationTable() {\n return this.client(CONVERSATION_TABLE_NAME);\n }\n\n async getChatMessages(\n conversationId: string,\n userRef: string,\n limit?: number,\n excludeRoles?: Message['role'][],\n ): Promise<Required<Message>[]> {\n let query = this.messageTable()\n .where({ conversation_id: conversationId, userRef })\n .select('*')\n .orderBy('created_at', 'asc');\n\n if (typeof limit === 'number') {\n query = query.limit(limit).orderBy('created_at', 'desc');\n }\n\n if (excludeRoles && excludeRoles.length > 0) {\n query = query.whereNotIn('role', excludeRoles);\n }\n\n const rows = await query;\n\n const chatMessages: Required<Message>[] = rows.map(row => ({\n role: row.role,\n content: row.content,\n id: row.id,\n metadata: row.metadata,\n }));\n\n return chatMessages;\n }\n\n async addChatMessage(\n messages: Message[],\n userRef: string,\n conversationId: string,\n ): Promise<void> {\n const rows = messages.map(msg => ({\n id: msg.id,\n conversation_id: conversationId,\n role: msg.role,\n content: msg.content,\n metadata: msg.metadata,\n userRef,\n created_at: this.client.fn.now(),\n }));\n\n await this.messageTable().insert(rows);\n }\n\n async updateMessage(message: Required<Message>) {\n await this.messageTable().where({ id: message.id }).update({\n role: message.role,\n content: message.content,\n metadata: message.metadata,\n });\n }\n\n async getConversation(\n conversationId: string,\n userRef: string,\n ): Promise<Conversation> {\n const row = await this.conversationTable()\n .where({ id: conversationId, userRef })\n .first();\n\n if (!row) {\n throw new Error('Conversation not found');\n }\n\n const conversation: Conversation = {\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n };\n\n return conversation;\n }\n\n async createConversation(conversation: Conversation) {\n await this.conversationTable().insert({\n id: conversation.id,\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async updateConversation(conversation: Conversation) {\n await this.conversationTable().where({ id: conversation.id }).update({\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async getConversations(userRef: string): Promise<Conversation[]> {\n const rows = await this.conversationTable()\n .where({ userRef })\n .select('*')\n .orderBy('created_at', 'desc');\n\n const conversations: Conversation[] = rows.map(row => ({\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n }));\n\n return conversations;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-store.cjs.js","sources":["../../src/database/chat-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport {\n Message,\n Conversation,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { Knex } from 'knex';\n\nconst MESSAGE_TABLE_NAME = 'message';\nconst CONVERSATION_TABLE_NAME = 'conversation';\n\nexport type ChatStoreOptions = {\n database: DatabaseService;\n};\n\nexport class ChatStore {\n /**\n * Creates an instance of ChatStore.\n * @param client - The Knex client to interact with the PostgreSQL database.\n */\n constructor(private readonly client: Knex) {}\n\n static async fromConfig({ database }: ChatStoreOptions) {\n const client = await database.getClient();\n return new ChatStore(client);\n }\n\n messageTable() {\n return this.client(MESSAGE_TABLE_NAME);\n }\n\n conversationTable() {\n return this.client(CONVERSATION_TABLE_NAME);\n }\n\n async getChatMessages(\n conversationId: string,\n userRef: string,\n limit?: number,\n excludeRoles?: Message['role'][],\n ): Promise<Required<Message>[]> {\n let query = this.messageTable()\n .where({ conversation_id: conversationId, userRef })\n .select('*')\n .orderBy('created_at', 'asc');\n\n if (typeof limit === 'number') {\n query = query.limit(limit).orderBy('created_at', 'desc');\n }\n\n if (excludeRoles && excludeRoles.length > 0) {\n query = query.whereNotIn('role', excludeRoles);\n }\n\n const rows = await query;\n\n const chatMessages: Required<Message>[] = rows.map(row => ({\n role: row.role,\n content: row.content,\n id: row.id,\n metadata: row.metadata,\n score: row.score,\n traceId: row.trace_id,\n }));\n\n return chatMessages;\n }\n\n async addChatMessage(\n messages: Message[],\n userRef: string,\n conversationId: string,\n ): Promise<void> {\n const rows = messages.map(msg => ({\n id: msg.id,\n conversation_id: conversationId,\n role: msg.role,\n content: msg.content,\n metadata: msg.metadata,\n trace_id: msg.traceId,\n userRef,\n created_at: this.client.fn.now(),\n }));\n\n await this.messageTable().insert(rows);\n }\n\n async updateMessage(message: Required<Message>) {\n await this.messageTable().where({ id: message.id }).update({\n role: message.role,\n content: message.content,\n metadata: message.metadata,\n score: message.score,\n trace_id: message.traceId,\n });\n }\n\n async getConversation(\n conversationId: string,\n userRef: string,\n ): Promise<Conversation> {\n const row = await this.conversationTable()\n .where({ id: conversationId, userRef })\n .first();\n\n if (!row) {\n throw new Error('Conversation not found');\n }\n\n const conversation: Conversation = {\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n };\n\n return conversation;\n }\n\n async createConversation(conversation: Conversation) {\n await this.conversationTable().insert({\n id: conversation.id,\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async updateConversation(conversation: Conversation) {\n await this.conversationTable().where({ id: conversation.id }).update({\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async getConversations(userRef: string): Promise<Conversation[]> {\n const rows = await this.conversationTable()\n .where({ userRef })\n .select('*')\n .orderBy('created_at', 'desc');\n\n const conversations: Conversation[] = rows.map(row => ({\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n }));\n\n return conversations;\n }\n\n async getMessageById(messageId: string): Promise<Required<Message> | null> {\n const row = await this.messageTable().where({ id: messageId }).first();\n\n if (!row) {\n return null;\n }\n\n const message: Required<Message> = {\n id: row.id,\n role: row.role,\n content: row.content,\n metadata: row.metadata,\n score: row.score,\n traceId: row.trace_id,\n };\n\n return message;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -69,7 +69,10 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
69
69
  },
70
70
  async init(options) {
71
71
  const { httpRouter, database, config, logger } = options;
72
- const langfuseEnabled = langfuse.initLangfuse(config, logger);
72
+ const { langfuseEnabled, langfuseClient } = langfuse.initLangfuse(
73
+ config,
74
+ logger
75
+ );
73
76
  const client = await database.getClient();
74
77
  await migrations.applyDatabaseMigrations(client);
75
78
  const vectorStore = await pgVectorStore.PgVectorStore.fromConfig(options);
@@ -88,7 +91,8 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
88
91
  ...options,
89
92
  models,
90
93
  tools,
91
- langfuseEnabled
94
+ langfuseEnabled,
95
+ langfuseClient
92
96
  });
93
97
  httpRouter.use(await index.createRouter({ ...options, chat: chat$1 }));
94
98
  dataIngestionPipeline.start();
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './services/router';\nimport {\n dataIngestorExtensionPoint,\n EmbeddingsProvider,\n embeddingsProviderExtensionPoint,\n Ingestor,\n Model,\n modelProviderExtensionPoint,\n Tool,\n toolExtensionPoint,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { createDataIngestionPipeline } from './services/ingestor';\nimport { createChatService } from './services/chat';\nimport { applyDatabaseMigrations } from './database/migrations';\nimport { PgVectorStore } from './database';\nimport { signalsServiceRef } from '@backstage/plugin-signals-node';\nimport { createSearchKnowledgeTool } from './services/tools/searchKnowledge';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\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\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.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 = initLangfuse(config, logger);\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 searchKnowledgeTool = createSearchKnowledgeTool({ vectorStore });\n tools.push(searchKnowledgeTool);\n\n const chat = await createChatService({\n ...options,\n models,\n tools,\n langfuseEnabled,\n });\n\n httpRouter.use(await createRouter({ ...options, chat }));\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","initLangfuse","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","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} 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 { 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\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.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 searchKnowledgeTool = createSearchKnowledgeTool({ vectorStore });\n tools.push(searchKnowledgeTool);\n\n const chat = await createChatService({\n ...options,\n models,\n tools,\n langfuseEnabled,\n langfuseClient,\n });\n\n httpRouter.use(await createRouter({ ...options, chat }));\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","initLangfuse","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","createSearchKnowledgeTool","chat","createChatService","createRouter"],"mappings":"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"}
@@ -19,7 +19,8 @@ const createChatService = async ({
19
19
  catalog,
20
20
  cache,
21
21
  auth,
22
- langfuseEnabled
22
+ langfuseEnabled,
23
+ langfuseClient
23
24
  }) => {
24
25
  logger.info(`Available models: ${models.map((m) => m.id).join(", ")}`);
25
26
  logger.info(`Available tools: ${tools$1.map((t) => t.name).join(", ")}`);
@@ -164,7 +165,7 @@ ${contentPrompt}`;
164
165
  const newMessages = promptMessages.filter((m) => responseMessages.findIndex((rm) => rm.id === m.id) === -1).filter(
165
166
  (m) => recentConversationMessages.findIndex((rm) => rm.id === m.id) === -1
166
167
  ).filter((m) => m.getType() !== "human").map((m) => {
167
- const id = m.id ?? "";
168
+ const id = uuid.v4();
168
169
  const role = m.getType();
169
170
  const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
170
171
  const metadata = {};
@@ -182,7 +183,9 @@ ${contentPrompt}`;
182
183
  id,
183
184
  role,
184
185
  content,
185
- metadata
186
+ metadata,
187
+ score: 0,
188
+ traceId: void 0
186
189
  };
187
190
  });
188
191
  for await (const m of newMessages) {
@@ -206,8 +209,9 @@ ${contentPrompt}`;
206
209
  }
207
210
  responseMessages.push(...newMessages);
208
211
  }
212
+ const traceId = langfuseHandler?.last_trace_id ?? void 0;
209
213
  addMessages(
210
- responseMessages.map((m) => ({ ...m, id: uuid.v4() })),
214
+ responseMessages.map((m) => ({ ...m, traceId })),
211
215
  userEntityRef,
212
216
  conversationId,
213
217
  [...recentConversationMessages, ...messages]
@@ -234,12 +238,39 @@ ${contentPrompt}`;
234
238
  const conversations = await chatStore$1.getConversations(userEntityRef);
235
239
  return conversations;
236
240
  };
241
+ const scoreMessage = async (messageId, score) => {
242
+ const message = await chatStore$1.getMessageById(messageId);
243
+ if (!message) {
244
+ throw new Error(`Message with id ${messageId} not found`);
245
+ }
246
+ if (langfuseEnabled && message.traceId) {
247
+ langfuseClient.score.create({
248
+ traceId: message.traceId,
249
+ name: "helpfulness",
250
+ value: score
251
+ });
252
+ logger.info(
253
+ `Scored message ${messageId} on Langfuse with trace ID ${message.traceId} - ${score} for helpfulness`
254
+ );
255
+ } else if (langfuseEnabled && !message.traceId) {
256
+ logger.warn(
257
+ `Message ${messageId} does not have a traceId, cannot score on Langfuse`
258
+ );
259
+ }
260
+ const updatedMessage = {
261
+ ...message,
262
+ score
263
+ };
264
+ await chatStore$1.updateMessage(updatedMessage);
265
+ logger.info(`Message ${messageId} scored ${score}`);
266
+ };
237
267
  return {
238
268
  prompt,
239
269
  getAvailableModels,
240
270
  getConversation,
241
271
  getConversations,
242
- addMessages
272
+ addMessages,
273
+ scoreMessage
243
274
  };
244
275
  };
245
276
  async function getUser(cache, userEntityRef, credentials, catalog) {
@@ -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 { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\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 langfuseEnabled: boolean;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userEntityRef: string;\n};\n\ntype GetConversationOptions = {\n conversationId: string;\n userEntityRef: string;\n};\n\ntype GetConversationsOptions = {\n userEntityRef: string;\n};\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<Required<Message>[]>;\n getAvailableModels: () => Promise<string[]>;\n getConversation: (\n options: GetConversationOptions,\n ) => Promise<Required<Message>[]>;\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};\n\nexport const createChatService = async ({\n models,\n tools,\n logger,\n database,\n signals,\n config,\n catalog,\n cache,\n auth,\n langfuseEnabled,\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 });\n\n const agentTools = tools.map(tool => new DynamicStructuredTool(tool));\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 userEntityRef,\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 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 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: Required<Message>[] = [];\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const newMessages: Required<Message>[] = 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 };\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 return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\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","DynamicStructuredTool","SystemMessagePromptTemplate","conversation","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 { CallbackHandler } from '@langfuse/langchain';\nimport { LangfuseClient } from '@langfuse/client';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\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 langfuseEnabled: boolean;\n langfuseClient?: LangfuseClient;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userEntityRef: string;\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 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 });\n\n const agentTools = tools.map(tool => new DynamicStructuredTool(tool));\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 userEntityRef,\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 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 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 = uuid(); // Generate UUID here instead of using LangChain's 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, 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","DynamicStructuredTool","SystemMessagePromptTemplate","conversation","createReactAgent","CallbackHandler","uuid"],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
 
3
3
  var otel = require('@langfuse/otel');
4
4
  var sdkNode = require('@opentelemetry/sdk-node');
5
+ var client = require('@langfuse/client');
5
6
 
6
7
  function initLangfuse(config, logger) {
7
8
  const langfuseSecret = config.getOptionalString(
@@ -23,16 +24,24 @@ function initLangfuse(config, logger) {
23
24
  const sdk = new sdkNode.NodeSDK({
24
25
  spanProcessors: [langfuseSpanProcessor]
25
26
  });
27
+ const langfuseClient = new client.LangfuseClient({
28
+ secretKey: langfuseSecret,
29
+ publicKey: langfusePublic,
30
+ baseUrl: langfuseBaseUrl
31
+ });
26
32
  logger.info(
27
33
  "Langfuse: Starting OpenTelemetry SDK with LangfuseSpanProcessor"
28
34
  );
29
35
  sdk.start();
30
- return true;
36
+ return { langfuseEnabled: true, langfuseClient };
31
37
  }
32
38
  logger.info(
33
39
  "Langfuse: Skipping Langfuse initialization, credentials not found."
34
40
  );
35
- return false;
41
+ return {
42
+ langfuseEnabled: false,
43
+ langfuseClient: void 0
44
+ };
36
45
  }
37
46
 
38
47
  exports.initLangfuse = initLangfuse;
@@ -1 +1 @@
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';\n\nexport function initLangfuse(\n config: RootConfigService,\n logger: RootLoggerService,\n): boolean {\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 logger.info(\n 'Langfuse: Starting OpenTelemetry SDK with LangfuseSpanProcessor',\n );\n sdk.start();\n return true;\n }\n logger.info(\n 'Langfuse: Skipping Langfuse initialization, credentials not found.',\n );\n return false;\n}\n"],"names":["LangfuseSpanProcessor","NodeSDK"],"mappings":";;;;;AAOO,SAAS,YAAA,CACd,QACA,MAAA,EACS;AACT,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,MAAA,CAAO,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA,GAAA,CAAI,KAAA,EAAM;AACV,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAA,CAAO,IAAA;AAAA,IACL;AAAA,GACF;AACA,EAAA,OAAO,KAAA;AACT;;;;"}
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;;;;"}
@@ -66,6 +66,27 @@ async function createChatRouter(options) {
66
66
  });
67
67
  res.json({ conversation });
68
68
  });
69
+ router.post(
70
+ "/message/:messageId/score",
71
+ validation.validation(
72
+ z__default.default.object({
73
+ messageId: z__default.default.string().uuid()
74
+ }),
75
+ "params"
76
+ ),
77
+ validation.validation(
78
+ z__default.default.object({
79
+ score: z__default.default.number()
80
+ }),
81
+ "body"
82
+ ),
83
+ async (req, res) => {
84
+ const { messageId } = req.params;
85
+ const { score } = req.body;
86
+ await chat.scoreMessage(messageId, score);
87
+ res.status(204).end();
88
+ }
89
+ );
69
90
  return router;
70
91
  }
71
92
 
@@ -1 +1 @@
1
- {"version":3,"file":"chat.cjs.js","sources":["../../../src/services/router/chat.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { ChatService } from '../chat';\nimport z from 'zod';\nimport { validation } from './middleware/validation';\nimport { v4 as uuid } from 'uuid';\nimport {\n DatabaseService,\n HttpAuthService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\n\nexport type ChatRouterOptions = {\n chat: ChatService;\n database: DatabaseService;\n httpAuth: HttpAuthService;\n userInfo: UserInfoService;\n};\n\nexport async function createChatRouter(\n options: ChatRouterOptions,\n): Promise<express.Router> {\n const { chat, httpAuth, userInfo } = options;\n\n const router = Router();\n\n const messageSchema = z.object({\n messages: z.array(\n z.object({\n id: z.string().uuid().optional().default(uuid),\n role: z.string(),\n content: z.string(),\n }),\n ),\n modelId: z.string(),\n conversationId: z.string().uuid().optional().default(uuid),\n stream: z.boolean().optional(),\n });\n\n router.post(\n '/message',\n validation(messageSchema, 'body'),\n async (req, res) => {\n const { messages, conversationId, modelId, stream } = req.body;\n\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const responseMessages = await chat.prompt({\n modelId,\n messages,\n conversationId,\n stream,\n userEntityRef,\n });\n\n res.json({\n messages: responseMessages,\n conversationId,\n });\n },\n );\n\n const chatSchema = z.object({\n id: z.string().uuid(),\n });\n\n router.get('/conversations', async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversations = await chat.getConversations({\n userEntityRef,\n });\n\n res.json({ conversations });\n });\n\n router.get('/:id', validation(chatSchema, 'params'), async (req, res) => {\n const { id } = req.params;\n\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversation = await chat.getConversation({\n conversationId: id,\n userEntityRef,\n });\n\n res.json({ conversation });\n });\n\n return router;\n}\n"],"names":["Router","z","uuid","validation"],"mappings":"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"}
1
+ {"version":3,"file":"chat.cjs.js","sources":["../../../src/services/router/chat.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { ChatService } from '../chat';\nimport z from 'zod';\nimport { validation } from './middleware/validation';\nimport { v4 as uuid } from 'uuid';\nimport {\n DatabaseService,\n HttpAuthService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\n\nexport type ChatRouterOptions = {\n chat: ChatService;\n database: DatabaseService;\n httpAuth: HttpAuthService;\n userInfo: UserInfoService;\n};\n\nexport async function createChatRouter(\n options: ChatRouterOptions,\n): Promise<express.Router> {\n const { chat, httpAuth, userInfo } = options;\n\n const router = Router();\n\n const messageSchema = z.object({\n messages: z.array(\n z.object({\n id: z.string().uuid().optional().default(uuid),\n role: z.string(),\n content: z.string(),\n }),\n ),\n modelId: z.string(),\n conversationId: z.string().uuid().optional().default(uuid),\n stream: z.boolean().optional(),\n });\n\n router.post(\n '/message',\n validation(messageSchema, 'body'),\n async (req, res) => {\n const { messages, conversationId, modelId, stream } = req.body;\n\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const responseMessages = await chat.prompt({\n modelId,\n messages,\n conversationId,\n stream,\n userEntityRef,\n });\n\n res.json({\n messages: responseMessages,\n conversationId,\n });\n },\n );\n\n const chatSchema = z.object({\n id: z.string().uuid(),\n });\n\n router.get('/conversations', async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversations = await chat.getConversations({\n userEntityRef,\n });\n\n res.json({ conversations });\n });\n\n router.get('/:id', validation(chatSchema, 'params'), async (req, res) => {\n const { id } = req.params;\n\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversation = await chat.getConversation({\n conversationId: id,\n userEntityRef,\n });\n\n res.json({ conversation });\n });\n\n router.post(\n '/message/:messageId/score',\n validation(\n z.object({\n messageId: z.string().uuid(),\n }),\n 'params',\n ),\n validation(\n z.object({\n score: z.number(),\n }),\n 'body',\n ),\n async (req, res) => {\n const { messageId } = req.params;\n const { score } = req.body;\n\n await chat.scoreMessage(messageId, score);\n\n res.status(204).end();\n },\n );\n\n return router;\n}\n"],"names":["Router","z","uuid","validation"],"mappings":"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"}
@@ -20,19 +20,25 @@ const createSummarizerService = async ({
20
20
  userId: "summarizer",
21
21
  tags: ["backstage-ai-assistant", "summarizer"]
22
22
  }) : void 0;
23
- const summaryPromptTemplate = prompts$1.SystemMessagePromptTemplate.fromTemplate(`
24
- PURPOSE:
25
- {summaryPrompt}
26
- Summarize the conversation in {summaryLength}
27
-
28
- Conversation:
29
- {conversation}
30
- `);
23
+ const chatPromptTemplate = prompts$1.ChatPromptTemplate.fromMessages([
24
+ prompts$1.SystemMessagePromptTemplate.fromTemplate(`
25
+ PURPOSE:
26
+ {summaryPrompt}
27
+
28
+ Please summarize the following conversation in {summaryLength}.
29
+ `),
30
+ prompts$1.HumanMessagePromptTemplate.fromTemplate(`
31
+ Conversation:
32
+ {conversation}
33
+
34
+ Please provide a summary of this conversation.
35
+ `)
36
+ ]);
31
37
  const summarize = async (messages, summaryLength = "as few words as possible") => {
32
38
  const conversationMessages = messages.filter(
33
39
  (msg) => msg.role === "ai" || msg.role === "human"
34
40
  );
35
- const prompt = await summaryPromptTemplate.formatMessages({
41
+ const prompt = await chatPromptTemplate.formatMessages({
36
42
  summaryPrompt,
37
43
  summaryLength,
38
44
  conversation: conversationMessages.map((msg) => `${msg.role}: ${msg.content}`).join("\n")
@@ -1 +1 @@
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 { SystemMessagePromptTemplate } from '@langchain/core/prompts';\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};\n\ntype SummarizerServiceOptions = {\n config: RootConfigService;\n models: Model[];\n langfuseEnabled: boolean;\n};\n\nexport const createSummarizerService = async ({\n config,\n models,\n langfuseEnabled,\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 // 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 summaryPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {summaryPrompt}\n Summarize the conversation in {summaryLength}\n\n Conversation:\n {conversation}\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 summaryPromptTemplate.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 return { summarize };\n};\n"],"names":["DEFAULT_SUMMARY_PROMPT","CallbackHandler","SystemMessagePromptTemplate"],"mappings":";;;;;;AAoBO,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;AAGlB,EAAA,MAAM,eAAA,GAAkB,eAAA,GACpB,IAAIC,yBAAA,CAAgB;AAAA,IAClB,MAAA,EAAQ,YAAA;AAAA,IACR,IAAA,EAAM,CAAC,wBAAA,EAA0B,YAAY;AAAA,GAC9C,CAAA,GACD,MAAA;AAEJ,EAAA,MAAM,qBAAA,GAAwBC,sCAA4B,YAAA,CAAa;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,EAAA,CAOtE,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,qBAAA,CAAsB,cAAA,CAAe;AAAA,MACxD,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,aAAA,GAAqB;AAAA,MACzB,OAAA,EAAS,yBAAA;AAAA,MACT,IAAA,EAAM,CAAC,YAAY;AAAA,KACrB;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,aAAA,CAAc,SAAA,GAAY,CAAC,eAAe,CAAA;AAAA,IAC5C;AAEA,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,GAAA,CAAI,MAAA,CAAO,QAAQ,aAAa,CAAA;AAEvD,IAAA,OAAO,KAAK,IAAA,EAAK;AAAA,EACnB,CAAA;AAEA,EAAA,OAAO,EAAE,SAAA,EAAU;AACrB;;;;"}
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 { CallbackHandler } from '@langfuse/langchain';\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 langfuseEnabled: boolean;\n};\n\nexport const createSummarizerService = async ({\n config,\n models,\n langfuseEnabled,\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 // 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 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 return { summarize };\n};\n"],"names":["DEFAULT_SUMMARY_PROMPT","CallbackHandler","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;AAGlB,EAAA,MAAM,eAAA,GAAkB,eAAA,GACpB,IAAIC,yBAAA,CAAgB;AAAA,IAClB,MAAA,EAAQ,YAAA;AAAA,IACR,IAAA,EAAM,CAAC,wBAAA,EAA0B,YAAY;AAAA,GAC9C,CAAA,GACD,MAAA;AAEJ,EAAA,MAAM,kBAAA,GAAqBC,6BAAmB,YAAA,CAAa;AAAA,IACzDC,sCAA4B,YAAA,CAAa;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAKxC,CAAA;AAAA,IACDC,qCAA2B,YAAA,CAAa;AAAA;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,aAAA,GAAqB;AAAA,MACzB,OAAA,EAAS,yBAAA;AAAA,MACT,IAAA,EAAM,CAAC,YAAY;AAAA,KACrB;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,aAAA,CAAc,SAAA,GAAY,CAAC,eAAe,CAAA;AAAA,IAC5C;AAEA,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,GAAA,CAAI,MAAA,CAAO,QAAQ,aAAa,CAAA;AAEvD,IAAA,OAAO,KAAK,IAAA,EAAK;AAAA,EACnB,CAAA;AAEA,EAAA,OAAO,EAAE,SAAA,EAAU;AACrB;;;;"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ *
3
+ * @param {import('knex').knex} knex
4
+ */
5
+
6
+ exports.down = async knex => {
7
+ const hasScoreColumn = await knex.schema.hasColumn('message', 'score');
8
+ const hasTraceIdColumn = await knex.schema.hasColumn('message', 'trace_id');
9
+
10
+ if (hasScoreColumn || hasTraceIdColumn) {
11
+ await knex.schema.alterTable('message', table => {
12
+ if (hasScoreColumn) {
13
+ table.dropColumn('score');
14
+ }
15
+ if (hasTraceIdColumn) {
16
+ table.dropColumn('trace_id');
17
+ }
18
+ });
19
+ }
20
+ };
21
+
22
+ /**
23
+ *
24
+ * @param {import('knex').knex} knex
25
+ */
26
+
27
+ exports.up = async knex => {
28
+ const hasScoreColumn = await knex.schema.hasColumn('message', 'score');
29
+ const hasTraceIdColumn = await knex.schema.hasColumn('message', 'trace_id');
30
+
31
+ if (!hasScoreColumn || !hasTraceIdColumn) {
32
+ await knex.schema.alterTable('message', table => {
33
+ if (!hasScoreColumn) {
34
+ table
35
+ .float('score')
36
+ .nullable()
37
+ .defaultTo(0)
38
+ .comment(
39
+ 'User feedback score for the message: 0 = no feedback, 1 = helpful, -1 = not helpful',
40
+ );
41
+ }
42
+ if (!hasTraceIdColumn) {
43
+ table
44
+ .string('trace_id')
45
+ .nullable()
46
+ .comment(
47
+ 'Langfuse trace ID for the message, used for scoring and observability',
48
+ );
49
+ }
50
+ });
51
+ }
52
+ };
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-20251029080430",
3
+ "version": "0.0.0-snapshot-20251029145101",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -42,12 +42,13 @@
42
42
  "@langchain/core": "^0.3.72",
43
43
  "@langchain/langgraph": "^0.4.9",
44
44
  "@langchain/textsplitters": "^0.1.0",
45
+ "@langfuse/client": "^4.3.0",
45
46
  "@langfuse/core": "^4.0.0",
46
47
  "@langfuse/langchain": "^4.0.0",
47
48
  "@langfuse/otel": "^4.3.0",
48
49
  "@opentelemetry/api": "^1.9.0",
49
50
  "@opentelemetry/sdk-node": "^0.207.0",
50
- "@sweetoburrito/backstage-plugin-ai-assistant-common": "^0.5.0",
51
+ "@sweetoburrito/backstage-plugin-ai-assistant-common": "0.0.0-snapshot-20251029145101",
51
52
  "@sweetoburrito/backstage-plugin-ai-assistant-node": "^0.5.1",
52
53
  "express": "^4.17.1",
53
54
  "express-promise-router": "^4.1.0",
@@ -70,6 +71,7 @@
70
71
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-catalog": "workspace:^",
71
72
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-github": "workspace:^",
72
73
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-azure-ai": "workspace:^",
74
+ "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-google-vertex-ai": "workspace:^",
73
75
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-ollama": "workspace:^",
74
76
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-tool-provider-backstage": "workspace:^",
75
77
  "@types/express": "^4.0.0",