@sweetoburrito/backstage-plugin-ai-assistant-backend 0.15.4 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var uuid = require('uuid');
4
+
5
+ const MESSAGE_ID_NAMESPACE = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
6
+ const createDeterministicUuid = (message) => {
7
+ if (message.id && uuid.validate(message.id)) {
8
+ return message.id;
9
+ }
10
+ if (message.id) {
11
+ return uuid.v5(message.id, MESSAGE_ID_NAMESPACE);
12
+ }
13
+ return uuid.v4();
14
+ };
15
+
16
+ exports.createDeterministicUuid = createDeterministicUuid;
17
+ //# sourceMappingURL=deterministic-uuid.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deterministic-uuid.cjs.js","sources":["../../../../src/services/agent/helpers/deterministic-uuid.ts"],"sourcesContent":["import { v5 as uuidv5, v4 as uuidv4, validate } from 'uuid';\n\nconst MESSAGE_ID_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';\n\nexport const createDeterministicUuid = (message: { id?: string }): string => {\n if (message.id && validate(message.id)) {\n return message.id;\n }\n if (message.id) {\n return uuidv5(message.id, MESSAGE_ID_NAMESPACE);\n }\n\n return uuidv4();\n};\n"],"names":["validate","uuidv5","uuidv4"],"mappings":";;;;AAEA,MAAM,oBAAA,GAAuB,sCAAA;AAEtB,MAAM,uBAAA,GAA0B,CAAC,OAAA,KAAqC;AAC3E,EAAA,IAAI,OAAA,CAAQ,EAAA,IAAMA,aAAA,CAAS,OAAA,CAAQ,EAAE,CAAA,EAAG;AACtC,IAAA,OAAO,OAAA,CAAQ,EAAA;AAAA,EACjB;AACA,EAAA,IAAI,QAAQ,EAAA,EAAI;AACd,IAAA,OAAOC,OAAA,CAAO,OAAA,CAAQ,EAAA,EAAI,oBAAoB,CAAA;AAAA,EAChD;AAEA,EAAA,OAAOC,OAAA,EAAO;AAChB;;;;"}
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var uuid = require('uuid');
3
+ var deterministicUuid = require('./deterministic-uuid.cjs.js');
4
4
 
5
5
  const parseLangchainMessage = (message, traceId) => {
6
- const id = uuid.validate(message.id) ? message.id : uuid.v4();
7
- const role = message.getType();
6
+ const id = deterministicUuid.createDeterministicUuid(message);
7
+ const role = message.type;
8
8
  const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
9
9
  const metadata = {};
10
10
  if (role === "ai") {
11
11
  const aiMessage = message;
12
12
  metadata.toolCalls = aiMessage.tool_calls || [];
13
- metadata.finishReason = aiMessage.response_metadata.finish_reason || void 0;
14
- metadata.modelName = aiMessage.response_metadata.model_name || void 0;
13
+ metadata.finishReason = aiMessage.response_metadata?.finish_reason || void 0;
14
+ metadata.modelName = aiMessage.response_metadata?.model_name || void 0;
15
15
  }
16
16
  if (role === "tool") {
17
17
  const toolMessage = message;
@@ -1 +1 @@
1
- {"version":3,"file":"message-parser.cjs.js","sources":["../../../../src/services/agent/helpers/message-parser.ts"],"sourcesContent":["import { AIMessage, ToolMessage, BaseMessage } from '@langchain/core/messages';\n\nimport {\n JsonObject,\n Message,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { v4 as uuid, validate } from 'uuid';\n\nexport const parseLangchainMessage = (\n message: BaseMessage,\n traceId: string,\n): Message => {\n const id = validate(message.id) ? message.id : uuid();\n const role = message.getType();\n const content =\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content);\n\n const metadata: JsonObject = {};\n\n if (role === 'ai') {\n const aiMessage = message as AIMessage;\n\n metadata.toolCalls = aiMessage.tool_calls || [];\n metadata.finishReason =\n aiMessage.response_metadata.finish_reason || undefined;\n metadata.modelName = aiMessage.response_metadata.model_name || undefined;\n }\n\n if (role === 'tool') {\n const toolMessage = message as ToolMessage;\n metadata.name = toolMessage.name || '';\n }\n\n return {\n id,\n role,\n content,\n metadata,\n score: 0,\n traceId,\n };\n};\n"],"names":["validate","uuid"],"mappings":";;;;AAQO,MAAM,qBAAA,GAAwB,CACnC,OAAA,EACA,OAAA,KACY;AACZ,EAAA,MAAM,KAAKA,aAAA,CAAS,OAAA,CAAQ,EAAE,CAAA,GAAI,OAAA,CAAQ,KAAKC,OAAA,EAAK;AACpD,EAAA,MAAM,IAAA,GAAO,QAAQ,OAAA,EAAQ;AAC7B,EAAA,MAAM,OAAA,GACJ,OAAO,OAAA,CAAQ,OAAA,KAAY,QAAA,GACvB,QAAQ,OAAA,GACR,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,OAAO,CAAA;AAEpC,EAAA,MAAM,WAAuB,EAAC;AAE9B,EAAA,IAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,SAAA,GAAY,OAAA;AAElB,IAAA,QAAA,CAAS,SAAA,GAAY,SAAA,CAAU,UAAA,IAAc,EAAC;AAC9C,IAAA,QAAA,CAAS,YAAA,GACP,SAAA,CAAU,iBAAA,CAAkB,aAAA,IAAiB,MAAA;AAC/C,IAAA,QAAA,CAAS,SAAA,GAAY,SAAA,CAAU,iBAAA,CAAkB,UAAA,IAAc,MAAA;AAAA,EACjE;AAEA,EAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,IAAA,MAAM,WAAA,GAAc,OAAA;AACpB,IAAA,QAAA,CAAS,IAAA,GAAO,YAAY,IAAA,IAAQ,EAAA;AAAA,EACtC;AAEA,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,IAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA,EAAO,CAAA;AAAA,IACP;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"message-parser.cjs.js","sources":["../../../../src/services/agent/helpers/message-parser.ts"],"sourcesContent":["import { AIMessage, ToolMessage, BaseMessage } from '@langchain/core/messages';\n\nimport {\n JsonObject,\n Message,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { createDeterministicUuid } from './deterministic-uuid';\n\nexport const parseLangchainMessage = (\n message: BaseMessage,\n traceId: string,\n): Message => {\n const id = createDeterministicUuid(message);\n const role = message.type as Message['role'];\n const content =\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content);\n\n const metadata: JsonObject = {};\n\n if (role === 'ai') {\n const aiMessage = message as AIMessage;\n\n metadata.toolCalls = aiMessage.tool_calls || [];\n\n metadata.finishReason =\n (aiMessage.response_metadata?.finish_reason as string) || undefined;\n metadata.modelName = aiMessage.response_metadata?.model_name || undefined;\n }\n\n if (role === 'tool') {\n const toolMessage = message as ToolMessage;\n metadata.name = toolMessage.name || '';\n }\n\n return {\n id,\n role,\n content,\n metadata,\n score: 0,\n traceId,\n };\n};\n"],"names":["createDeterministicUuid"],"mappings":";;;;AAQO,MAAM,qBAAA,GAAwB,CACnC,OAAA,EACA,OAAA,KACY;AACZ,EAAA,MAAM,EAAA,GAAKA,0CAAwB,OAAO,CAAA;AAC1C,EAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,EAAA,MAAM,OAAA,GACJ,OAAO,OAAA,CAAQ,OAAA,KAAY,QAAA,GACvB,QAAQ,OAAA,GACR,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,OAAO,CAAA;AAEpC,EAAA,MAAM,WAAuB,EAAC;AAE9B,EAAA,IAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,SAAA,GAAY,OAAA;AAElB,IAAA,QAAA,CAAS,SAAA,GAAY,SAAA,CAAU,UAAA,IAAc,EAAC;AAE9C,IAAA,QAAA,CAAS,YAAA,GACN,SAAA,CAAU,iBAAA,EAAmB,aAAA,IAA4B,MAAA;AAC5D,IAAA,QAAA,CAAS,SAAA,GAAY,SAAA,CAAU,iBAAA,EAAmB,UAAA,IAAc,MAAA;AAAA,EAClE;AAEA,EAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,IAAA,MAAM,WAAA,GAAc,OAAA;AACpB,IAAA,QAAA,CAAS,IAAA,GAAO,YAAY,IAAA,IAAQ,EAAA;AAAA,EACtC;AAEA,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,IAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA,EAAO,CAAA;AAAA,IACP;AAAA,GACF;AACF;;;;"}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
- var prebuilt = require('@langchain/langgraph/prebuilt');
4
+ var langchain = require('langchain');
5
5
  var toolFilter = require('./helpers/tool-filter.cjs.js');
6
6
  var prompts = require('../../constants/prompts.cjs.js');
7
7
  var prompts$1 = require('@langchain/core/prompts');
@@ -9,6 +9,7 @@ var callbacks = require('../callbacks.cjs.js');
9
9
  var model = require('../model.cjs.js');
10
10
  var tools = require('../tools.cjs.js');
11
11
  var messageParser = require('./helpers/message-parser.cjs.js');
12
+ var deterministicUuid = require('./helpers/deterministic-uuid.cjs.js');
12
13
 
13
14
  const createAgentService = ({
14
15
  model,
@@ -62,11 +63,6 @@ ${contentPrompt}`;
62
63
  context,
63
64
  systemPrompt
64
65
  });
65
- const agent = prebuilt.createReactAgent({
66
- llm,
67
- tools,
68
- prompt: agentPrompt[0].text
69
- });
70
66
  const { callbacks } = await callback.getChainCallbacks({
71
67
  userId,
72
68
  conversationId,
@@ -77,33 +73,49 @@ ${contentPrompt}`;
77
73
  conversationId,
78
74
  modelId: resolvedModelId
79
75
  });
80
- agent.config = {
81
- ...agent.config,
76
+ const agent = langchain.createAgent({
77
+ model: llm,
78
+ tools,
79
+ systemPrompt: agentPrompt[0].text
80
+ }).withConfig({
82
81
  callbacks,
83
82
  metadata,
84
83
  runId,
85
84
  runName
86
- };
85
+ });
87
86
  return agent;
88
87
  };
89
88
  const stream = async (options) => {
90
- const { messages, onStreamChunk } = options;
89
+ const { messages, onStreamChunk, onStreamEnd } = options;
91
90
  const agent = await createAgent(options);
92
91
  const promptStream = await agent.stream(
93
92
  {
94
93
  messages
95
94
  },
96
95
  {
97
- streamMode: ["values"]
96
+ streamMode: ["messages"]
98
97
  }
99
98
  );
100
- for await (const [, chunk] of promptStream) {
101
- const { messages: promptMessages } = chunk;
99
+ const promptMessages = [];
100
+ for await (const [, [chunk]] of promptStream) {
101
+ const messageChunk = chunk;
102
+ messageChunk.id = deterministicUuid.createDeterministicUuid(messageChunk);
103
+ const existingChunksIndex = promptMessages.findIndex(
104
+ (m) => m.id === messageChunk.id
105
+ );
106
+ if (existingChunksIndex === -1) {
107
+ promptMessages.push(messageChunk);
108
+ } else {
109
+ const existingChunk = promptMessages[existingChunksIndex];
110
+ existingChunk.concat(messageChunk);
111
+ promptMessages[existingChunksIndex] = existingChunk.concat(messageChunk);
112
+ }
102
113
  const parsedMessages = promptMessages.map(
103
114
  (m) => messageParser.parseLangchainMessage(m, options.metadata.runId)
104
115
  );
105
116
  onStreamChunk(parsedMessages);
106
117
  }
118
+ onStreamEnd?.();
107
119
  };
108
120
  const prompt = async (options) => {
109
121
  const {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../../src/services/agent/index.ts"],"sourcesContent":["import {\n BackstageCredentials,\n coreServices,\n createServiceFactory,\n createServiceRef,\n RootConfigService,\n ServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { createReactAgent } from '@langchain/langgraph/prebuilt';\nimport {\n EnabledTool,\n Message,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { toolFilter } from './helpers/tool-filter';\nimport {\n DEFAULT_FORMATTING_PROMPT,\n DEFAULT_IDENTITY_PROMPT,\n DEFAULT_SYSTEM_PROMPT,\n DEFAULT_TOOL_GUIDELINE,\n} from '../../constants/prompts';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\n\nimport { CallbackService, callbackServiceRef } from '../callbacks';\nimport { modelServiceRef, ModelService } from '../model';\nimport { toolsServiceRef, ToolsService } from '../tools';\n\nimport { BaseMessage } from '@langchain/core/messages';\nimport { parseLangchainMessage } from './helpers/message-parser';\n\ntype PromptOptions = {\n credentials: BackstageCredentials;\n metadata: {\n conversationId: string;\n userId: string;\n runName: string;\n runId: string;\n };\n messages: Message[];\n modelId?: string;\n tools?: EnabledTool[];\n systemPrompt?: string;\n context?: string;\n};\n\ntype StreamOptions = PromptOptions & {\n onStreamChunk: (messages: Message[]) => void;\n};\n\nexport type AgentService = {\n prompt: (options: PromptOptions) => Promise<Message[]>;\n stream: (options: StreamOptions) => Promise<void>;\n};\n\ntype AgentServiceOptions = {\n model: ModelService;\n config: RootConfigService;\n tool: ToolsService;\n callback: CallbackService;\n};\n\nconst createAgentService = ({\n model,\n tool,\n config,\n callback,\n}: AgentServiceOptions): AgentService => {\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 defaultBasePrompt = `${identityPrompt}\\n\\n${formattingPrompt}\\n\\n${contentPrompt}`;\n\n const toolGuideline =\n config.getOptionalString('aiAssistant.prompt.toolGuideline') ||\n DEFAULT_TOOL_GUIDELINE;\n\n const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {systemPrompt}\n\n TOOL USAGE GUIDELINES:\n {toolGuideline}\n\n Available tools:\n {toolList}\n\n Context:\n {context}`);\n\n const createAgent = async (\n options: PromptOptions,\n ): Promise<ReturnType<typeof createReactAgent>> => {\n const {\n modelId,\n credentials,\n tools: enabledTools,\n systemPrompt = defaultBasePrompt,\n context = 'none',\n metadata: { conversationId, userId, runId, runName },\n } = options;\n\n const { chatModel: llm, id: resolvedModelId } = model.getModel(\n modelId ?? 'default',\n );\n\n const tools = await tool.getPrincipalTools({\n credentials,\n filter: t => {\n return toolFilter(t, enabledTools);\n },\n });\n\n const toolList = tools.map(t => `- ${t.name}: ${t.description}`).join('\\n');\n\n const agentPrompt = await systemPromptTemplate.formatMessages({\n toolGuideline,\n toolList,\n context,\n systemPrompt,\n });\n\n const agent = createReactAgent({\n llm,\n tools,\n prompt: agentPrompt[0].text,\n });\n\n const { callbacks } = await callback.getChainCallbacks({\n userId,\n conversationId,\n modelId: resolvedModelId,\n });\n\n const { metadata } = await callback.getChainMetadata({\n userId,\n conversationId,\n modelId: resolvedModelId,\n });\n\n agent.config = {\n ...agent.config,\n callbacks,\n metadata,\n runId,\n runName,\n };\n\n return agent;\n };\n\n const stream: AgentService['stream'] = async options => {\n const { messages, onStreamChunk } = options;\n\n const agent = await createAgent(options);\n\n const promptStream = await agent.stream(\n {\n messages,\n },\n {\n streamMode: ['values'],\n },\n );\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const parsedMessages: Message[] = (promptMessages as BaseMessage[]).map(\n m => parseLangchainMessage(m, options.metadata.runId),\n );\n\n onStreamChunk(parsedMessages);\n }\n };\n\n const prompt: AgentService['prompt'] = async options => {\n const {\n messages,\n metadata: { runId },\n } = options;\n const agent = await createAgent(options);\n\n const result = await agent.invoke({\n messages,\n });\n\n return (result.messages as BaseMessage[]).map(m =>\n parseLangchainMessage(m, runId),\n );\n };\n\n return { prompt, stream };\n};\n\nexport const agentServiceRef: ServiceRef<AgentService, 'plugin', 'singleton'> =\n createServiceRef<AgentService>({\n id: 'ai-assistant.conversation-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n config: coreServices.rootConfig,\n model: modelServiceRef,\n tool: toolsServiceRef,\n callback: callbackServiceRef,\n },\n factory: async options => {\n return createAgentService(options);\n },\n }),\n });\n"],"names":["DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","SystemMessagePromptTemplate","toolFilter","createReactAgent","parseLangchainMessage","createServiceRef","createServiceFactory","coreServices","modelServiceRef","toolsServiceRef","callbackServiceRef"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../src/services/agent/index.ts"],"sourcesContent":["import {\n BackstageCredentials,\n coreServices,\n createServiceFactory,\n createServiceRef,\n RootConfigService,\n ServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { createAgent as createLangchainAgent } from 'langchain';\nimport {\n EnabledTool,\n Message,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { toolFilter } from './helpers/tool-filter';\nimport {\n DEFAULT_FORMATTING_PROMPT,\n DEFAULT_IDENTITY_PROMPT,\n DEFAULT_SYSTEM_PROMPT,\n DEFAULT_TOOL_GUIDELINE,\n} from '../../constants/prompts';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\n\nimport { CallbackService, callbackServiceRef } from '../callbacks';\nimport { modelServiceRef, ModelService } from '../model';\nimport { toolsServiceRef, ToolsService } from '../tools';\n\nimport { BaseMessage, BaseMessageChunk } from '@langchain/core/messages';\nimport { parseLangchainMessage } from './helpers/message-parser';\nimport { createDeterministicUuid } from './helpers/deterministic-uuid';\n\ntype PromptOptions = {\n credentials: BackstageCredentials;\n metadata: {\n conversationId: string;\n userId: string;\n runName: string;\n runId: string;\n };\n messages: Message[];\n modelId?: string;\n tools?: EnabledTool[];\n systemPrompt?: string;\n context?: string;\n};\n\ntype StreamOptions = PromptOptions & {\n onStreamChunk: (messages: Message[]) => void;\n onStreamEnd?: () => void;\n};\n\nexport type AgentService = {\n prompt: (options: PromptOptions) => Promise<Message[]>;\n stream: (options: StreamOptions) => Promise<void>;\n};\n\ntype AgentServiceOptions = {\n model: ModelService;\n config: RootConfigService;\n tool: ToolsService;\n callback: CallbackService;\n};\n\nconst createAgentService = ({\n model,\n tool,\n config,\n callback,\n}: AgentServiceOptions): AgentService => {\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 defaultBasePrompt = `${identityPrompt}\\n\\n${formattingPrompt}\\n\\n${contentPrompt}`;\n\n const toolGuideline =\n config.getOptionalString('aiAssistant.prompt.toolGuideline') ||\n DEFAULT_TOOL_GUIDELINE;\n\n const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {systemPrompt}\n\n TOOL USAGE GUIDELINES:\n {toolGuideline}\n\n Available tools:\n {toolList}\n\n Context:\n {context}`);\n\n const createAgent = async (\n options: PromptOptions,\n ): Promise<ReturnType<typeof createLangchainAgent>> => {\n const {\n modelId,\n credentials,\n tools: enabledTools,\n systemPrompt = defaultBasePrompt,\n context = 'none',\n metadata: { conversationId, userId, runId, runName },\n } = options;\n\n const { chatModel: llm, id: resolvedModelId } = model.getModel(\n modelId ?? 'default',\n );\n\n const tools = await tool.getPrincipalTools({\n credentials,\n filter: t => {\n return toolFilter(t, enabledTools);\n },\n });\n\n const toolList = tools.map(t => `- ${t.name}: ${t.description}`).join('\\n');\n\n const agentPrompt = await systemPromptTemplate.formatMessages({\n toolGuideline,\n toolList,\n context,\n systemPrompt,\n });\n\n const { callbacks } = await callback.getChainCallbacks({\n userId,\n conversationId,\n modelId: resolvedModelId,\n });\n\n const { metadata } = await callback.getChainMetadata({\n userId,\n conversationId,\n modelId: resolvedModelId,\n });\n\n const agent = createLangchainAgent({\n model: llm,\n tools,\n systemPrompt: agentPrompt[0].text,\n }).withConfig({\n callbacks,\n metadata,\n runId,\n runName,\n });\n\n return agent;\n };\n\n const stream: AgentService['stream'] = async options => {\n const { messages, onStreamChunk, onStreamEnd } = options;\n\n const agent = await createAgent(options);\n\n const promptStream = await agent.stream(\n {\n messages,\n },\n {\n streamMode: ['messages'],\n },\n );\n\n const promptMessages: BaseMessageChunk[] = [];\n\n for await (const [, [chunk]] of promptStream) {\n const messageChunk = chunk as BaseMessageChunk;\n\n messageChunk.id = createDeterministicUuid(messageChunk);\n\n const existingChunksIndex = promptMessages.findIndex(\n m => m.id === messageChunk.id,\n );\n\n if (existingChunksIndex === -1) {\n promptMessages.push(messageChunk);\n } else {\n const existingChunk = promptMessages[existingChunksIndex];\n\n existingChunk.concat(messageChunk);\n\n promptMessages[existingChunksIndex] =\n existingChunk.concat(messageChunk);\n }\n\n const parsedMessages: Message[] = promptMessages.map(m =>\n parseLangchainMessage(m, options.metadata.runId),\n );\n\n onStreamChunk(parsedMessages);\n }\n\n onStreamEnd?.();\n };\n\n const prompt: AgentService['prompt'] = async options => {\n const {\n messages,\n metadata: { runId },\n } = options;\n const agent = await createAgent(options);\n\n const result = await agent.invoke({\n messages,\n });\n\n return (result.messages as BaseMessage[]).map(m =>\n parseLangchainMessage(m, runId),\n );\n };\n\n return { prompt, stream };\n};\n\nexport const agentServiceRef: ServiceRef<AgentService, 'plugin', 'singleton'> =\n createServiceRef<AgentService>({\n id: 'ai-assistant.conversation-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n config: coreServices.rootConfig,\n model: modelServiceRef,\n tool: toolsServiceRef,\n callback: callbackServiceRef,\n },\n factory: async options => {\n return createAgentService(options);\n },\n }),\n });\n"],"names":["DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","SystemMessagePromptTemplate","toolFilter","createLangchainAgent","createDeterministicUuid","parseLangchainMessage","createServiceRef","createServiceFactory","coreServices","modelServiceRef","toolsServiceRef","callbackServiceRef"],"mappings":";;;;;;;;;;;;;AA8DA,MAAM,qBAAqB,CAAC;AAAA,EAC1B,KAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,KAAyC;AACvC,EAAA,MAAM,cAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,6BAA6B,CAAA,IACtDA,+BAAA;AAEF,EAAA,MAAM,gBAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,+BAA+B,CAAA,IACxDC,iCAAA;AAEF,EAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,4BAA4B,CAAA,IACrDC,6BAAA;AAEF,EAAA,MAAM,iBAAA,GAAoB,GAAG,cAAc;;AAAA,EAAO,gBAAgB;;AAAA,EAAO,aAAa,CAAA,CAAA;AAEtF,EAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,kCAAkC,CAAA,IAC3DC,8BAAA;AAEF,EAAA,MAAM,oBAAA,GAAuBC,sCAA4B,YAAA,CAAa;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA,aAAA,CAW1D,CAAA;AAEZ,EAAA,MAAM,WAAA,GAAc,OAClB,OAAA,KACqD;AACrD,IAAA,MAAM;AAAA,MACJ,OAAA;AAAA,MACA,WAAA;AAAA,MACA,KAAA,EAAO,YAAA;AAAA,MACP,YAAA,GAAe,iBAAA;AAAA,MACf,OAAA,GAAU,MAAA;AAAA,MACV,QAAA,EAAU,EAAE,cAAA,EAAgB,MAAA,EAAQ,OAAO,OAAA;AAAQ,KACrD,GAAI,OAAA;AAEJ,IAAA,MAAM,EAAE,SAAA,EAAW,GAAA,EAAK,EAAA,EAAI,eAAA,KAAoB,KAAA,CAAM,QAAA;AAAA,MACpD,OAAA,IAAW;AAAA,KACb;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,iBAAA,CAAkB;AAAA,MACzC,WAAA;AAAA,MACA,QAAQ,CAAA,CAAA,KAAK;AACX,QAAA,OAAOC,qBAAA,CAAW,GAAG,YAAY,CAAA;AAAA,MACnC;AAAA,KACD,CAAA;AAED,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,EAAA,EAAK,CAAA,CAAE,IAAI,CAAA,EAAA,EAAK,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAAE,KAAK,IAAI,CAAA;AAE1E,IAAA,MAAM,WAAA,GAAc,MAAM,oBAAA,CAAqB,cAAA,CAAe;AAAA,MAC5D,aAAA;AAAA,MACA,QAAA;AAAA,MACA,OAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,SAAS,iBAAA,CAAkB;AAAA,MACrD,MAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,SAAS,gBAAA,CAAiB;AAAA,MACnD,MAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAM,QAAQC,qBAAA,CAAqB;AAAA,MACjC,KAAA,EAAO,GAAA;AAAA,MACP,KAAA;AAAA,MACA,YAAA,EAAc,WAAA,CAAY,CAAC,CAAA,CAAE;AAAA,KAC9B,EAAE,UAAA,CAAW;AAAA,MACZ,SAAA;AAAA,MACA,QAAA;AAAA,MACA,KAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,MAAA,GAAiC,OAAM,OAAA,KAAW;AACtD,IAAA,MAAM,EAAE,QAAA,EAAU,aAAA,EAAe,WAAA,EAAY,GAAI,OAAA;AAEjD,IAAA,MAAM,KAAA,GAAQ,MAAM,WAAA,CAAY,OAAO,CAAA;AAEvC,IAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,MAAA;AAAA,MAC/B;AAAA,QACE;AAAA,OACF;AAAA,MACA;AAAA,QACE,UAAA,EAAY,CAAC,UAAU;AAAA;AACzB,KACF;AAEA,IAAA,MAAM,iBAAqC,EAAC;AAE5C,IAAA,WAAA,MAAiB,GAAG,CAAC,KAAK,CAAC,KAAK,YAAA,EAAc;AAC5C,MAAA,MAAM,YAAA,GAAe,KAAA;AAErB,MAAA,YAAA,CAAa,EAAA,GAAKC,0CAAwB,YAAY,CAAA;AAEtD,MAAA,MAAM,sBAAsB,cAAA,CAAe,SAAA;AAAA,QACzC,CAAA,CAAA,KAAK,CAAA,CAAE,EAAA,KAAO,YAAA,CAAa;AAAA,OAC7B;AAEA,MAAA,IAAI,wBAAwB,EAAA,EAAI;AAC9B,QAAA,cAAA,CAAe,KAAK,YAAY,CAAA;AAAA,MAClC,CAAA,MAAO;AACL,QAAA,MAAM,aAAA,GAAgB,eAAe,mBAAmB,CAAA;AAExD,QAAA,aAAA,CAAc,OAAO,YAAY,CAAA;AAEjC,QAAA,cAAA,CAAe,mBAAmB,CAAA,GAChC,aAAA,CAAc,MAAA,CAAO,YAAY,CAAA;AAAA,MACrC;AAEA,MAAA,MAAM,iBAA4B,cAAA,CAAe,GAAA;AAAA,QAAI,CAAA,CAAA,KACnDC,mCAAA,CAAsB,CAAA,EAAG,OAAA,CAAQ,SAAS,KAAK;AAAA,OACjD;AAEA,MAAA,aAAA,CAAc,cAAc,CAAA;AAAA,IAC9B;AAEA,IAAA,WAAA,IAAc;AAAA,EAChB,CAAA;AAEA,EAAA,MAAM,MAAA,GAAiC,OAAM,OAAA,KAAW;AACtD,IAAA,MAAM;AAAA,MACJ,QAAA;AAAA,MACA,QAAA,EAAU,EAAE,KAAA;AAAM,KACpB,GAAI,OAAA;AACJ,IAAA,MAAM,KAAA,GAAQ,MAAM,WAAA,CAAY,OAAO,CAAA;AAEvC,IAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,MAAA,CAAO;AAAA,MAChC;AAAA,KACD,CAAA;AAED,IAAA,OAAQ,OAAO,QAAA,CAA2B,GAAA;AAAA,MAAI,CAAA,CAAA,KAC5CA,mCAAA,CAAsB,CAAA,EAAG,KAAK;AAAA,KAChC;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC1B,CAAA;AAEO,MAAM,kBACXC,iCAAA,CAA+B;AAAA,EAC7B,EAAA,EAAI,mCAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAA,CAAqB;AAAA,IACnB,OAAA;AAAA,IACA,IAAA,EAAM;AAAA,MACJ,QAAQC,6BAAA,CAAa,UAAA;AAAA,MACrB,KAAA,EAAOC,qBAAA;AAAA,MACP,IAAA,EAAMC,qBAAA;AAAA,MACN,QAAA,EAAUC;AAAA,KACZ;AAAA,IACA,OAAA,EAAS,OAAM,OAAA,KAAW;AACxB,MAAA,OAAO,mBAAmB,OAAO,CAAA;AAAA,IACnC;AAAA,GACD;AACL,CAAC;;;;"}
@@ -51,6 +51,7 @@ const createChatService = async ({
51
51
  });
52
52
  const responseMessages = [];
53
53
  const conversationMessages = [...recentConversationMessages, ...messages];
54
+ const newMessages = [];
54
55
  agent.stream({
55
56
  credentials,
56
57
  messages: conversationMessages,
@@ -64,38 +65,38 @@ const createChatService = async ({
64
65
  },
65
66
  context: context[0].text,
66
67
  onStreamChunk: async (chunkMessages) => {
67
- const newMessages = chunkMessages.filter(
68
- (m) => conversationMessages.findIndex((cm) => cm.id === m.id) === -1
68
+ if (chunkMessages.length === 0) {
69
+ return;
70
+ }
71
+ const existingNewMessageIndex = newMessages.findIndex(
72
+ (cm) => cm.id === chunkMessages[0].id
69
73
  );
70
- if (newMessages.length !== 0) {
71
- conversation.addMessages(
72
- newMessages,
73
- userEntityRef,
74
- conversationId,
75
- conversationMessages
74
+ if (existingNewMessageIndex !== -1) {
75
+ newMessages.splice(
76
+ existingNewMessageIndex,
77
+ chunkMessages.length,
78
+ ...chunkMessages
76
79
  );
77
- conversationMessages.push(...newMessages);
78
- responseMessages.push(...newMessages);
79
- for await (const m of newMessages) {
80
- const words = m.content.split(" ");
81
- const chunkSize = 5;
82
- let messageBuilder = "";
83
- for (let i = 0; i < words.length; i += chunkSize) {
84
- const wordChunk = words.slice(i, i + chunkSize).join(" ");
85
- messageBuilder = messageBuilder.concat(wordChunk).concat(" ");
86
- m.content = messageBuilder;
87
- await new Promise((resolve) => setTimeout(resolve, 50));
88
- signals.publish({
89
- channel: `ai-assistant.chat.conversation-stream:${conversationId}`,
90
- message: { messages: [m] },
91
- recipients: {
92
- type: "user",
93
- entityRef: userEntityRef
94
- }
95
- });
96
- }
97
- }
80
+ } else {
81
+ newMessages.push(...chunkMessages);
98
82
  }
83
+ responseMessages.push(...chunkMessages);
84
+ signals.publish({
85
+ channel: `ai-assistant.chat.conversation-stream:${conversationId}`,
86
+ message: { messages: chunkMessages },
87
+ recipients: {
88
+ type: "user",
89
+ entityRef: userEntityRef
90
+ }
91
+ });
92
+ },
93
+ onStreamEnd: async () => {
94
+ conversation.addMessages(
95
+ newMessages,
96
+ userEntityRef,
97
+ conversationId,
98
+ conversationMessages
99
+ );
99
100
  }
100
101
  });
101
102
  return responseMessages;
@@ -1 +1 @@
1
- {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import {\n CatalogService,\n catalogServiceRef,\n} from '@backstage/plugin-catalog-node';\nimport {\n SignalsService,\n signalsServiceRef,\n} from '@backstage/plugin-signals-node';\n\nimport {\n Message,\n EnabledTool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { getUser } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n AuthService,\n ServiceRef,\n} from '@backstage/backend-plugin-api';\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { ConversationService, conversationServiceRef } from './conversation';\nimport { agentServiceRef, AgentService } from './agent';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\n\nexport type ChatServiceOptions = {\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n userInfo: UserInfoService;\n conversation: ConversationService;\n agent: AgentService;\n};\n\ntype PromptOptions = {\n credentials: BackstageCredentials;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n tools?: EnabledTool[];\n modelId?: string;\n};\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<Message[]>;\n};\n\nexport const createChatService = async ({\n signals,\n catalog,\n cache,\n auth,\n userInfo,\n conversation,\n agent,\n}: ChatServiceOptions): Promise<ChatService> => {\n const contextPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n Calling User:\n {user}`);\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n stream = true,\n credentials,\n tools: enabledTools,\n modelId,\n }: PromptOptions) => {\n const streamFn = async () => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n const recentConversationMessages =\n await conversation.getRecentConversationMessages({\n conversationId,\n userEntityRef,\n limit: 10,\n excludeRoles: ['tool'],\n });\n\n const user = await getUser(cache, userEntityRef, catalog, auth);\n\n const messagesWithoutSystem = messages.filter(m => m.role !== 'system');\n\n conversation.addMessages(\n messagesWithoutSystem,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const traceId = uuid();\n\n const context = await contextPromptTemplate.formatMessages({\n user,\n });\n\n const responseMessages: Message[] = [];\n\n const conversationMessages = [...recentConversationMessages, ...messages];\n\n agent.stream({\n credentials,\n messages: conversationMessages,\n tools: enabledTools,\n modelId,\n metadata: {\n conversationId,\n userId: userEntityRef,\n runName: 'ai-assistant-chat',\n runId: traceId,\n },\n context: context[0].text,\n onStreamChunk: async chunkMessages => {\n const newMessages: Message[] = chunkMessages.filter(\n m => conversationMessages.findIndex(cm => cm.id === m.id) === -1,\n );\n\n if (newMessages.length !== 0) {\n conversation.addMessages(\n newMessages,\n userEntityRef,\n conversationId,\n conversationMessages,\n );\n\n conversationMessages.push(...newMessages);\n responseMessages.push(...newMessages);\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 },\n });\n\n return responseMessages;\n };\n\n return stream ? await streamFn() : [];\n };\n\n return {\n prompt,\n };\n};\n\nexport const chatServiceRef: ServiceRef<ChatService, 'plugin', 'singleton'> =\n createServiceRef<ChatService>({\n id: 'ai-assistant.chat-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n cache: coreServices.cache,\n auth: coreServices.auth,\n userInfo: coreServices.userInfo,\n signals: signalsServiceRef,\n catalog: catalogServiceRef,\n conversation: conversationServiceRef,\n agent: agentServiceRef,\n },\n factory: async options => {\n return createChatService(options);\n },\n }),\n });\n"],"names":["SystemMessagePromptTemplate","getUser","uuid","createServiceRef","createServiceFactory","coreServices","signalsServiceRef","catalogServiceRef","conversationServiceRef","agentServiceRef"],"mappings":"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"}
1
+ {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import {\n CatalogService,\n catalogServiceRef,\n} from '@backstage/plugin-catalog-node';\nimport {\n SignalsService,\n signalsServiceRef,\n} from '@backstage/plugin-signals-node';\n\nimport {\n Message,\n EnabledTool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { getUser } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n AuthService,\n ServiceRef,\n} from '@backstage/backend-plugin-api';\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { ConversationService, conversationServiceRef } from './conversation';\nimport { agentServiceRef, AgentService } from './agent';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\n\nexport type ChatServiceOptions = {\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n userInfo: UserInfoService;\n conversation: ConversationService;\n agent: AgentService;\n};\n\ntype PromptOptions = {\n credentials: BackstageCredentials;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n tools?: EnabledTool[];\n modelId?: string;\n};\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<Message[]>;\n};\n\nexport const createChatService = async ({\n signals,\n catalog,\n cache,\n auth,\n userInfo,\n conversation,\n agent,\n}: ChatServiceOptions): Promise<ChatService> => {\n const contextPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n Calling User:\n {user}`);\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n stream = true,\n credentials,\n tools: enabledTools,\n modelId,\n }: PromptOptions) => {\n const streamFn = async () => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n const recentConversationMessages =\n await conversation.getRecentConversationMessages({\n conversationId,\n userEntityRef,\n limit: 10,\n excludeRoles: ['tool'],\n });\n\n const user = await getUser(cache, userEntityRef, catalog, auth);\n\n const messagesWithoutSystem = messages.filter(m => m.role !== 'system');\n\n conversation.addMessages(\n messagesWithoutSystem,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const traceId = uuid();\n\n const context = await contextPromptTemplate.formatMessages({\n user,\n });\n\n const responseMessages: Message[] = [];\n\n const conversationMessages = [...recentConversationMessages, ...messages];\n\n const newMessages: Message[] = [];\n\n agent.stream({\n credentials,\n messages: conversationMessages,\n tools: enabledTools,\n modelId,\n metadata: {\n conversationId,\n userId: userEntityRef,\n runName: 'ai-assistant-chat',\n runId: traceId,\n },\n context: context[0].text,\n onStreamChunk: async chunkMessages => {\n if (chunkMessages.length === 0) {\n return;\n }\n\n const existingNewMessageIndex = newMessages.findIndex(\n cm => cm.id === chunkMessages[0].id,\n );\n\n if (existingNewMessageIndex !== -1) {\n newMessages.splice(\n existingNewMessageIndex,\n chunkMessages.length,\n ...chunkMessages,\n );\n } else {\n newMessages.push(...chunkMessages);\n }\n\n responseMessages.push(...chunkMessages);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-stream:${conversationId}`,\n message: { messages: chunkMessages },\n recipients: {\n type: 'user',\n entityRef: userEntityRef,\n },\n });\n },\n onStreamEnd: async () => {\n conversation.addMessages(\n newMessages,\n userEntityRef,\n conversationId,\n conversationMessages,\n );\n },\n });\n\n return responseMessages;\n };\n\n return stream ? await streamFn() : [];\n };\n\n return {\n prompt,\n };\n};\n\nexport const chatServiceRef: ServiceRef<ChatService, 'plugin', 'singleton'> =\n createServiceRef<ChatService>({\n id: 'ai-assistant.chat-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n cache: coreServices.cache,\n auth: coreServices.auth,\n userInfo: coreServices.userInfo,\n signals: signalsServiceRef,\n catalog: catalogServiceRef,\n conversation: conversationServiceRef,\n agent: agentServiceRef,\n },\n factory: async options => {\n return createChatService(options);\n },\n }),\n });\n"],"names":["SystemMessagePromptTemplate","getUser","uuid","createServiceRef","createServiceFactory","coreServices","signalsServiceRef","catalogServiceRef","conversationServiceRef","agentServiceRef"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweetoburrito/backstage-plugin-ai-assistant-backend",
3
- "version": "0.15.4",
3
+ "version": "0.16.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -39,15 +39,15 @@
39
39
  "@backstage/errors": "backstage:^",
40
40
  "@backstage/plugin-catalog-node": "backstage:^",
41
41
  "@backstage/plugin-signals-node": "backstage:^",
42
- "@langchain/core": "^0.3.80",
43
- "@langchain/langgraph": "^0.4.9",
44
- "@langchain/mcp-adapters": "^1.0.0",
45
- "@langchain/textsplitters": "^0.1.0",
46
- "@sweetoburrito/backstage-plugin-ai-assistant-common": "^0.8.0",
47
- "@sweetoburrito/backstage-plugin-ai-assistant-node": "^0.10.0",
42
+ "@langchain/core": "^1.1.19",
43
+ "@langchain/mcp-adapters": "^1.1.2",
44
+ "@langchain/textsplitters": "^1.0.1",
45
+ "@sweetoburrito/backstage-plugin-ai-assistant-common": "^0.9.0",
46
+ "@sweetoburrito/backstage-plugin-ai-assistant-node": "^0.11.0",
48
47
  "express": "^4.17.1",
49
48
  "express-promise-router": "^4.1.0",
50
49
  "knex": "^3.1.0",
50
+ "langchain": "^1.2.17",
51
51
  "uuid": "^11.1.0",
52
52
  "zod": "^4.1.11"
53
53
  },
@@ -60,7 +60,7 @@
60
60
  "@backstage/plugin-events-backend": "backstage:^",
61
61
  "@backstage/plugin-signals-backend": "backstage:^",
62
62
  "@backstage/types": "backstage:^",
63
- "@drodil/backstage-plugin-qeta-backend": "^3.46.0",
63
+ "@drodil/backstage-plugin-qeta-backend": "^3.58.0",
64
64
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-callback-provider-langfuse": "workspace:^",
65
65
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-embeddings-provider-azure-open-ai": "workspace:^",
66
66
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-embeddings-provider-ollama": "workspace:^",