@drodil/backstage-plugin-qeta-backend-module-openai 3.6.1 → 3.7.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.
@@ -1,13 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var openai = require('openai');
4
- var types = require('@backstage/types');
5
4
 
6
5
  class OpenAIHandler {
7
- constructor(logger, config, cache) {
6
+ constructor(logger, config) {
8
7
  this.logger = logger;
9
8
  this.config = config;
10
- this.cache = cache;
11
9
  this.model = this.config.getOptionalString("qeta.openai.model") ?? "gpt-3.5-turbo";
12
10
  this.maxTokens = this.config.getOptionalNumber("qeta.openai.maxTokens");
13
11
  this.temperature = this.config.getOptionalNumber("qeta.openai.temperature");
@@ -20,20 +18,6 @@ class OpenAIHandler {
20
18
  this.userPromptSuffix = this.config.getOptionalString(
21
19
  "qeta.openai.prompts.userSuffix"
22
20
  );
23
- const cacheTtlConfig = config.getOptional("qeta.openai.cacheTtl");
24
- let ttl;
25
- if (cacheTtlConfig !== void 0 && cacheTtlConfig !== null) {
26
- if (typeof cacheTtlConfig === "number") {
27
- ttl = cacheTtlConfig;
28
- } else if (typeof cacheTtlConfig === "object" && !Array.isArray(cacheTtlConfig)) {
29
- ttl = types.durationToMilliseconds(cacheTtlConfig);
30
- } else {
31
- throw new Error(
32
- `Invalid configuration qeta.openai.cacheTtl: ${cacheTtlConfig}, expected milliseconds number or HumanDuration object`
33
- );
34
- }
35
- }
36
- this.cacheTtl = ttl ?? types.durationToMilliseconds({ hours: 1 });
37
21
  }
38
22
  model;
39
23
  maxTokens;
@@ -41,7 +25,6 @@ class OpenAIHandler {
41
25
  systemPrompt;
42
26
  userPromptPrefix;
43
27
  userPromptSuffix;
44
- cacheTtl;
45
28
  async answerExistingQuestion(question, options) {
46
29
  const enabled = this.config.getOptionalBoolean(
47
30
  "qeta.openai.answer.existingQuestions"
@@ -49,12 +32,6 @@ class OpenAIHandler {
49
32
  if (enabled === false) {
50
33
  throw new Error("OpenAI is disabled for existing questions");
51
34
  }
52
- const cached = await this.cache?.get(
53
- `openai:question_${question.id}`
54
- );
55
- if (cached) {
56
- return JSON.parse(cached);
57
- }
58
35
  this.logger.info(`Answering question ${question.id} using OpenAI`);
59
36
  const prompt = `${question.title}
60
37
  ${question.content}`;
@@ -62,15 +39,7 @@ ${question.content}`;
62
39
  prompt,
63
40
  options?.credentials?.principal.userEntityRef
64
41
  );
65
- const ret = { answer: completion };
66
- await this.cache?.set(
67
- `openai:question_${question.id}`,
68
- JSON.stringify(ret),
69
- {
70
- ttl: this.cacheTtl
71
- }
72
- );
73
- return ret;
42
+ return { answer: completion };
74
43
  }
75
44
  async answerNewQuestion(title, content, options) {
76
45
  const enabled = this.config.getOptionalBoolean(
@@ -95,12 +64,6 @@ ${content}`;
95
64
  if (enabled === false) {
96
65
  throw new Error("OpenAI is disabled for article summaries");
97
66
  }
98
- const cached = await this.cache?.get(
99
- `openai:article_summary_${article.id}`
100
- );
101
- if (cached) {
102
- return JSON.parse(cached);
103
- }
104
67
  this.logger.info(`Summarizing article ${article.id} using OpenAI`);
105
68
  const prompt = `Can you summarize this article?
106
69
  Title: ${article.title}
@@ -109,15 +72,7 @@ Content: ${article.content}`;
109
72
  prompt,
110
73
  options?.credentials?.principal.userEntityRef
111
74
  );
112
- const ret = { answer: completion };
113
- await this.cache?.set(
114
- `openai:article_summary_${article.id}`,
115
- JSON.stringify(ret),
116
- {
117
- ttl: this.cacheTtl
118
- }
119
- );
120
- return ret;
75
+ return { answer: completion };
121
76
  }
122
77
  async getCompletion(prompt, user) {
123
78
  const client = this.getClient();
@@ -1 +1 @@
1
- {"version":3,"file":"OpenAIHandler.cjs.js","sources":["../src/OpenAIHandler.ts"],"sourcesContent":["/*\n * SPDX-FileCopyrightText: Copyright 2024 OP Financial Group (https://op.fi). All Rights Reserved.\n * SPDX-License-Identifier: LicenseRef-OpAllRightsReserved\n */\nimport { AIHandler } from '@drodil/backstage-plugin-qeta-node';\nimport {\n BackstageCredentials,\n BackstageUserPrincipal,\n CacheService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AIResponse,\n Article,\n Question,\n} from '@drodil/backstage-plugin-qeta-common';\nimport { Config } from '@backstage/config';\nimport { OpenAI } from 'openai';\nimport { durationToMilliseconds } from '@backstage/types';\n\nexport class OpenAIHandler implements AIHandler {\n private readonly model: string;\n private readonly maxTokens?: number;\n private readonly temperature?: number;\n private readonly systemPrompt?: string;\n private readonly userPromptPrefix?: string;\n private readonly userPromptSuffix?: string;\n private readonly cacheTtl?: number;\n\n constructor(\n private readonly logger: LoggerService,\n private readonly config: Config,\n private readonly cache?: CacheService,\n ) {\n this.model =\n this.config.getOptionalString('qeta.openai.model') ?? 'gpt-3.5-turbo';\n this.maxTokens = this.config.getOptionalNumber('qeta.openai.maxTokens');\n this.temperature = this.config.getOptionalNumber('qeta.openai.temperature');\n this.systemPrompt = this.config.getOptionalString(\n 'qeta.openai.prompts.system',\n );\n this.userPromptPrefix = this.config.getOptionalString(\n 'qeta.openai.prompts.userPrefix',\n );\n this.userPromptSuffix = this.config.getOptionalString(\n 'qeta.openai.prompts.userSuffix',\n );\n\n const cacheTtlConfig = config.getOptional('qeta.openai.cacheTtl');\n let ttl: number | undefined;\n if (cacheTtlConfig !== undefined && cacheTtlConfig !== null) {\n if (typeof cacheTtlConfig === 'number') {\n ttl = cacheTtlConfig;\n } else if (\n typeof cacheTtlConfig === 'object' &&\n !Array.isArray(cacheTtlConfig)\n ) {\n ttl = durationToMilliseconds(cacheTtlConfig);\n } else {\n throw new Error(\n `Invalid configuration qeta.openai.cacheTtl: ${cacheTtlConfig}, expected milliseconds number or HumanDuration object`,\n );\n }\n }\n this.cacheTtl = ttl ?? durationToMilliseconds({ hours: 1 });\n }\n\n async answerExistingQuestion(\n question: Question,\n options?: {\n credentials?: BackstageCredentials<BackstageUserPrincipal>;\n },\n ): Promise<AIResponse> {\n const enabled = this.config.getOptionalBoolean(\n 'qeta.openai.answer.existingQuestions',\n );\n if (enabled === false) {\n throw new Error('OpenAI is disabled for existing questions');\n }\n\n const cached = await this.cache?.get<string>(\n `openai:question_${question.id}`,\n );\n if (cached) {\n return JSON.parse(cached);\n }\n\n this.logger.info(`Answering question ${question.id} using OpenAI`);\n\n const prompt = `${question.title}\\n${question.content}`;\n const completion = await this.getCompletion(\n prompt,\n options?.credentials?.principal.userEntityRef,\n );\n\n const ret = { answer: completion };\n await this.cache?.set(\n `openai:question_${question.id}`,\n JSON.stringify(ret),\n {\n ttl: this.cacheTtl,\n },\n );\n return ret;\n }\n\n async answerNewQuestion(\n title: string,\n content: string,\n options?: { credentials?: BackstageCredentials<BackstageUserPrincipal> },\n ): Promise<AIResponse> {\n const enabled = this.config.getOptionalBoolean(\n 'qeta.openai.answer.newQuestions',\n );\n if (enabled === false) {\n throw new Error('OpenAI is disabled for new questions');\n }\n\n this.logger.info(`Answering question ${title} using OpenAI`);\n\n const prompt = `${title}\\n${content}`;\n const completion = await this.getCompletion(\n prompt,\n options?.credentials?.principal.userEntityRef,\n );\n return { answer: completion };\n }\n\n async summarizeArticle(\n article: Article,\n options?: { credentials?: BackstageCredentials<BackstageUserPrincipal> },\n ): Promise<AIResponse> {\n const enabled = this.config.getOptionalBoolean(\n 'qeta.openai.answer.articleSummary',\n );\n if (enabled === false) {\n throw new Error('OpenAI is disabled for article summaries');\n }\n\n const cached = await this.cache?.get<string>(\n `openai:article_summary_${article.id}`,\n );\n if (cached) {\n return JSON.parse(cached);\n }\n\n this.logger.info(`Summarizing article ${article.id} using OpenAI`);\n const prompt = `Can you summarize this article?\\nTitle: ${article.title}\\nContent: ${article.content}`;\n const completion = await this.getCompletion(\n prompt,\n options?.credentials?.principal.userEntityRef,\n );\n\n const ret = { answer: completion };\n await this.cache?.set(\n `openai:article_summary_${article.id}`,\n JSON.stringify(ret),\n {\n ttl: this.cacheTtl,\n },\n );\n return ret;\n }\n\n private async getCompletion(prompt: string, user?: string) {\n const client = this.getClient();\n\n let completion;\n try {\n const messages: Array<OpenAI.ChatCompletionMessageParam> =\n new Array<OpenAI.ChatCompletionMessageParam>();\n if (this.systemPrompt) {\n messages.push({ role: 'system', content: this.systemPrompt });\n }\n const userPrompt: OpenAI.ChatCompletionMessageParam = {\n role: 'user',\n content: `${this.userPromptPrefix ?? ''}${prompt}${\n this.userPromptSuffix ?? ''\n }`,\n };\n messages.push(userPrompt);\n\n completion = await client.chat.completions.create({\n model: this.model,\n messages: messages,\n stream: false,\n max_tokens: this.maxTokens,\n temperature: this.temperature,\n user,\n n: 1,\n });\n } catch (e) {\n // Hide the real error from users\n this.logger.error(`Error from OpenAI: ${e}`);\n throw new Error('Failed to get response from OpenAI');\n }\n\n if (\n completion.choices.length === 0 ||\n !completion.choices[0].message.content\n ) {\n throw new Error('No response from OpenAI');\n }\n return completion.choices[0].message.content;\n }\n\n private getClient() {\n const apiKey = this.config.getOptionalString('qeta.openai.apiKey');\n const organization = this.config.getOptionalString(\n 'qeta.openai.organization',\n );\n const endpoint = this.config.getOptionalString('qeta.openai.endpoint');\n const project = this.config.getOptionalString('qeta.openai.project');\n\n return new OpenAI({ baseURL: endpoint, apiKey, organization, project });\n }\n}\n"],"names":["durationToMilliseconds","OpenAI"],"mappings":"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"}
1
+ {"version":3,"file":"OpenAIHandler.cjs.js","sources":["../src/OpenAIHandler.ts"],"sourcesContent":["/*\n * SPDX-FileCopyrightText: Copyright 2024 OP Financial Group (https://op.fi). All Rights Reserved.\n * SPDX-License-Identifier: LicenseRef-OpAllRightsReserved\n */\nimport { AIHandler } from '@drodil/backstage-plugin-qeta-node';\nimport {\n BackstageCredentials,\n BackstageUserPrincipal,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AIResponse,\n Article,\n Question,\n} from '@drodil/backstage-plugin-qeta-common';\nimport { Config } from '@backstage/config';\nimport { OpenAI } from 'openai';\n\nexport class OpenAIHandler implements AIHandler {\n private readonly model: string;\n private readonly maxTokens?: number;\n private readonly temperature?: number;\n private readonly systemPrompt?: string;\n private readonly userPromptPrefix?: string;\n private readonly userPromptSuffix?: string;\n\n constructor(\n private readonly logger: LoggerService,\n private readonly config: Config,\n ) {\n this.model =\n this.config.getOptionalString('qeta.openai.model') ?? 'gpt-3.5-turbo';\n this.maxTokens = this.config.getOptionalNumber('qeta.openai.maxTokens');\n this.temperature = this.config.getOptionalNumber('qeta.openai.temperature');\n this.systemPrompt = this.config.getOptionalString(\n 'qeta.openai.prompts.system',\n );\n this.userPromptPrefix = this.config.getOptionalString(\n 'qeta.openai.prompts.userPrefix',\n );\n this.userPromptSuffix = this.config.getOptionalString(\n 'qeta.openai.prompts.userSuffix',\n );\n }\n\n async answerExistingQuestion(\n question: Question,\n options?: {\n credentials?: BackstageCredentials<BackstageUserPrincipal>;\n },\n ): Promise<AIResponse> {\n const enabled = this.config.getOptionalBoolean(\n 'qeta.openai.answer.existingQuestions',\n );\n if (enabled === false) {\n throw new Error('OpenAI is disabled for existing questions');\n }\n\n this.logger.info(`Answering question ${question.id} using OpenAI`);\n\n const prompt = `${question.title}\\n${question.content}`;\n const completion = await this.getCompletion(\n prompt,\n options?.credentials?.principal.userEntityRef,\n );\n\n return { answer: completion };\n }\n\n async answerNewQuestion(\n title: string,\n content: string,\n options?: { credentials?: BackstageCredentials<BackstageUserPrincipal> },\n ): Promise<AIResponse> {\n const enabled = this.config.getOptionalBoolean(\n 'qeta.openai.answer.newQuestions',\n );\n if (enabled === false) {\n throw new Error('OpenAI is disabled for new questions');\n }\n\n this.logger.info(`Answering question ${title} using OpenAI`);\n\n const prompt = `${title}\\n${content}`;\n const completion = await this.getCompletion(\n prompt,\n options?.credentials?.principal.userEntityRef,\n );\n return { answer: completion };\n }\n\n async summarizeArticle(\n article: Article,\n options?: { credentials?: BackstageCredentials<BackstageUserPrincipal> },\n ): Promise<AIResponse> {\n const enabled = this.config.getOptionalBoolean(\n 'qeta.openai.answer.articleSummary',\n );\n if (enabled === false) {\n throw new Error('OpenAI is disabled for article summaries');\n }\n\n this.logger.info(`Summarizing article ${article.id} using OpenAI`);\n const prompt = `Can you summarize this article?\\nTitle: ${article.title}\\nContent: ${article.content}`;\n const completion = await this.getCompletion(\n prompt,\n options?.credentials?.principal.userEntityRef,\n );\n\n return { answer: completion };\n }\n\n private async getCompletion(prompt: string, user?: string) {\n const client = this.getClient();\n\n let completion;\n try {\n const messages: Array<OpenAI.ChatCompletionMessageParam> =\n new Array<OpenAI.ChatCompletionMessageParam>();\n if (this.systemPrompt) {\n messages.push({ role: 'system', content: this.systemPrompt });\n }\n const userPrompt: OpenAI.ChatCompletionMessageParam = {\n role: 'user',\n content: `${this.userPromptPrefix ?? ''}${prompt}${\n this.userPromptSuffix ?? ''\n }`,\n };\n messages.push(userPrompt);\n\n completion = await client.chat.completions.create({\n model: this.model,\n messages: messages,\n stream: false,\n max_tokens: this.maxTokens,\n temperature: this.temperature,\n user,\n n: 1,\n });\n } catch (e) {\n // Hide the real error from users\n this.logger.error(`Error from OpenAI: ${e}`);\n throw new Error('Failed to get response from OpenAI');\n }\n\n if (\n completion.choices.length === 0 ||\n !completion.choices[0].message.content\n ) {\n throw new Error('No response from OpenAI');\n }\n return completion.choices[0].message.content;\n }\n\n private getClient() {\n const apiKey = this.config.getOptionalString('qeta.openai.apiKey');\n const organization = this.config.getOptionalString(\n 'qeta.openai.organization',\n );\n const endpoint = this.config.getOptionalString('qeta.openai.endpoint');\n const project = this.config.getOptionalString('qeta.openai.project');\n\n return new OpenAI({ baseURL: endpoint, apiKey, organization, project });\n }\n}\n"],"names":["OpenAI"],"mappings":"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"}
@@ -12,11 +12,10 @@ const qetaModuleOpenai = backendPluginApi.createBackendModule({
12
12
  deps: {
13
13
  logger: backendPluginApi.coreServices.logger,
14
14
  config: backendPluginApi.coreServices.rootConfig,
15
- ai: backstagePluginQetaNode.qetaAIExtensionPoint,
16
- cache: backendPluginApi.coreServices.cache
15
+ ai: backstagePluginQetaNode.qetaAIExtensionPoint
17
16
  },
18
- async init({ logger, config, ai, cache }) {
19
- const handler = new OpenAIHandler.OpenAIHandler(logger, config, cache);
17
+ async init({ logger, config, ai }) {
18
+ const handler = new OpenAIHandler.OpenAIHandler(logger, config);
20
19
  ai.setAIHandler(handler);
21
20
  }
22
21
  });
@@ -1 +1 @@
1
- {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * SPDX-FileCopyrightText: Copyright 2024 OP Financial Group (https://op.fi). All Rights Reserved.\n * SPDX-License-Identifier: LicenseRef-OpAllRightsReserved\n */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { qetaAIExtensionPoint } from '@drodil/backstage-plugin-qeta-node';\nimport { OpenAIHandler } from './OpenAIHandler';\n\nexport const qetaModuleOpenai = createBackendModule({\n pluginId: 'qeta',\n moduleId: 'openai',\n register(reg) {\n reg.registerInit({\n deps: {\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n ai: qetaAIExtensionPoint,\n cache: coreServices.cache,\n },\n async init({ logger, config, ai, cache }) {\n const handler = new OpenAIHandler(logger, config, cache);\n ai.setAIHandler(handler);\n },\n });\n },\n});\n"],"names":["createBackendModule","coreServices","qetaAIExtensionPoint","OpenAIHandler"],"mappings":";;;;;;AAWO,MAAM,mBAAmBA,oCAAoB,CAAA;AAAA,EAClD,QAAU,EAAA,MAAA;AAAA,EACV,QAAU,EAAA,QAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,MAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,EAAI,EAAAC,4CAAA;AAAA,QACJ,OAAOD,6BAAa,CAAA,KAAA;AAAA,OACtB;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,QAAQ,MAAQ,EAAA,EAAA,EAAI,OAAS,EAAA;AACxC,QAAA,MAAM,OAAU,GAAA,IAAIE,2BAAc,CAAA,MAAA,EAAQ,QAAQ,KAAK,CAAA,CAAA;AACvD,QAAA,EAAA,CAAG,aAAa,OAAO,CAAA,CAAA;AAAA,OACzB;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * SPDX-FileCopyrightText: Copyright 2024 OP Financial Group (https://op.fi). All Rights Reserved.\n * SPDX-License-Identifier: LicenseRef-OpAllRightsReserved\n */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { qetaAIExtensionPoint } from '@drodil/backstage-plugin-qeta-node';\nimport { OpenAIHandler } from './OpenAIHandler';\n\nexport const qetaModuleOpenai = createBackendModule({\n pluginId: 'qeta',\n moduleId: 'openai',\n register(reg) {\n reg.registerInit({\n deps: {\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n ai: qetaAIExtensionPoint,\n },\n async init({ logger, config, ai }) {\n const handler = new OpenAIHandler(logger, config);\n ai.setAIHandler(handler);\n },\n });\n },\n});\n"],"names":["createBackendModule","coreServices","qetaAIExtensionPoint","OpenAIHandler"],"mappings":";;;;;;AAWO,MAAM,mBAAmBA,oCAAoB,CAAA;AAAA,EAClD,QAAU,EAAA,MAAA;AAAA,EACV,QAAU,EAAA,QAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,MAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,EAAI,EAAAC,4CAAA;AAAA,OACN;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,MAAQ,EAAA,MAAA,EAAQ,IAAM,EAAA;AACjC,QAAA,MAAM,OAAU,GAAA,IAAIC,2BAAc,CAAA,MAAA,EAAQ,MAAM,CAAA,CAAA;AAChD,QAAA,EAAA,CAAG,aAAa,OAAO,CAAA,CAAA;AAAA,OACzB;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@drodil/backstage-plugin-qeta-backend-module-openai",
3
3
  "description": "The OpenAI backend module for the qeta plugin.",
4
- "version": "3.6.1",
4
+ "version": "3.7.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",
@@ -38,8 +38,8 @@
38
38
  "@backstage/backend-plugin-api": "^1.0.1",
39
39
  "@backstage/config": "^1.2.0",
40
40
  "@backstage/types": "^1.1.1",
41
- "@drodil/backstage-plugin-qeta-common": "^3.6.1",
42
- "@drodil/backstage-plugin-qeta-node": "^3.6.1",
41
+ "@drodil/backstage-plugin-qeta-common": "^3.7.0",
42
+ "@drodil/backstage-plugin-qeta-node": "^3.7.0",
43
43
  "openai": "^4.68.4"
44
44
  },
45
45
  "devDependencies": {