Skip to content
Merged
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.2.22",
"version": "1.2.23",
"main": "index.ts",
"license": "BUSL-1.1",
"scripts": {
Expand Down Expand Up @@ -33,6 +33,7 @@
"typescript": "^4.7.4"
},
"dependencies": {
"@ai-sdk/openai": "^2.0.64",
"@amplitude/node": "^1.10.0",
"@graphql-tools/merge": "^8.3.1",
"@graphql-tools/schema": "^8.5.1",
Expand All @@ -52,9 +53,9 @@
"@types/mongodb": "^3.6.20",
"@types/morgan": "^1.9.10",
"@types/node": "^16.11.46",
"@types/node-fetch": "^2.5.4",
"@types/safe-regex": "^1.1.6",
"@types/uuid": "^8.3.4",
"ai": "^5.0.89",
"amqp-connection-manager": "^3.1.0",
"amqplib": "^0.5.5",
"apollo-server-express": "^3.10.0",
Expand Down Expand Up @@ -84,6 +85,7 @@
"prom-client": "^15.1.3",
"safe-regex": "^2.1.0",
"ts-node-dev": "^2.0.0",
"uuid": "^8.3.2"
"uuid": "^8.3.2",
"zod": "^3.25.76"
}
}
24 changes: 23 additions & 1 deletion src/models/eventsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const mongo = require('../mongo');
const Event = require('../models/event');
const { ObjectID } = require('mongodb');
const { composeEventPayloadByRepetition } = require('../utils/merge');
const { vercelAIApi } = require('../vercel-ai');

const MAX_DB_READ_BATCH_SIZE = Number(process.env.MAX_DB_READ_BATCH_SIZE);

Expand Down Expand Up @@ -178,6 +179,27 @@ class EventsFactory extends Factory {
return new Event(searchResult);
}

/**
* Generate AI suggestion for the event
*
* @param {string} projectId - event's project
* @param {string} eventId - event id
* @param {string} originalEventId - original event id
* @returns {Promise<string>} AI suggestion for the event
*/
async aiSuggestion(eventId, originalEventId) {
const repetition = await this.getEventRepetition(eventId, originalEventId);

if (!repetition) {
throw new Error('Repetition not found');
}

const payload = repetition.payload;
const solution = await vercelAIApi.generateSuggestion(payload);

return solution;
}

/**
* Returns events that grouped by day
*
Expand Down Expand Up @@ -623,7 +645,7 @@ class EventsFactory extends Factory {
/**
* If originalEventId equals repetitionId than user wants to get first repetition which is original event
*/
if (repetitionId === originalEventId) {
if (repetitionId.toString() === originalEventId.toString()) {
const originalEvent = await this.eventsDataLoader.load(originalEventId);

/**
Expand Down
14 changes: 14 additions & 0 deletions src/resolvers/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ module.exports = {
return factory.findChartData(days, timezoneOffset, groupHash);
},

/**
* Return AI suggestion for the event
*
* @param {string} projectId - event's project
* @param {string} eventId - event id
* @param {string} originalEventId - original event id
* @returns {Promise<string>} AI suggestion for the event
*/
async aiSuggestion({ projectId, _id: eventId, originalEventId }, _args, context) {
const factory = getEventsFactory(context, projectId);

return factory.aiSuggestion(eventId, originalEventId);
},

/**
* Return release data for the event
*
Expand Down
5 changes: 5 additions & 0 deletions src/typeDefs/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ type Event {
"""
repetitionsPortion(cursor: String = null, limit: Int = 10): RepetitionsPortion!

"""
AI suggestion for the event
"""
aiSuggestion: String!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems goofy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


"""
Array of users who visited event
"""
Expand Down
18 changes: 18 additions & 0 deletions src/types/vercel-ai.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
declare module 'ai' {
/**
* Minimal type for generateText used in server-side integration.
*/
export function generateText(input: {
model: any;
system?: string;
prompt: string;
}): Promise<{ text: string }>;
}

declare module '@ai-sdk/openai' {
/**
* Minimal types for OpenAI provider.
*/
export function createOpenAI(config?: { apiKey?: string }): (model: string) => any;
export const openai: (model: string) => any;
}
5 changes: 5 additions & 0 deletions src/types/zod-v4.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module 'zod/v4' {
export * from 'zod';
import z from 'zod';
export default z;
}
40 changes: 40 additions & 0 deletions src/vercel-ai/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { EventAddons, EventData } from '@hawk.so/types';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { eventSolvingInput } from './inputs/eventSolving';
import { ctoInstruction } from './instructions/cto';

/**
* Vercel AI API
*/
class VercelAIApi {
/**
* Model ID
*/
private readonly modelId: string;

constructor() {
/**
* @todo make it dynamic, get from project settings
*/
this.modelId = 'gpt-4o';
}

/**
* Generate AI suggestion for the event
*
* @param {EventData<EventAddons>} payload - event data
* @returns {Promise<string>} AI suggestion for the event
*/
public async generateSuggestion(payload: EventData<EventAddons>) {
const { text } = await generateText({
model: openai(this.modelId),
system: ctoInstruction,
prompt: eventSolvingInput(payload),
});

return text;
}
}

export const vercelAIApi = new VercelAIApi();
15 changes: 15 additions & 0 deletions src/vercel-ai/inputs/eventSolving.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { EventData, EventAddons } from '@hawk.so/types';

export const eventSolvingInput = (payload: EventData<EventAddons>) => `

Проанализируй ошибку и предложи решение

Payload: ${JSON.stringify(payload)}

Response:

{
"solution": "...",
"explanation": "..."
}
`;
1 change: 1 addition & 0 deletions src/vercel-ai/instructions/cto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ctoInstruction = 'Ты технический директор ИТ компании, тебе нужно пояснить ошибку и предложить решение';
9 changes: 6 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"zod/v4": ["src/types/zod-v4.d.ts"]
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"skipLibCheck": true
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

Expand Down
78 changes: 69 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,39 @@
# yarn lockfile v1


"@ai-sdk/[email protected]":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@ai-sdk/gateway/-/gateway-2.0.7.tgz#e3b77ef01658b47a19956313fc2a36b9e5a951f3"
integrity sha512-/AI5AKi4vOK9SEb8Z1dfXkhsJ5NAfWsoJQc96B/mzn2KIrjw5occOjIwD06scuhV9xWlghCoXJT1sQD9QH/tyg==
dependencies:
"@ai-sdk/provider" "2.0.0"
"@ai-sdk/provider-utils" "3.0.16"
"@vercel/oidc" "3.0.3"

"@ai-sdk/openai@^2.0.64":
version "2.0.64"
resolved "https://registry.yarnpkg.com/@ai-sdk/openai/-/openai-2.0.64.tgz#d8746bd341c277b440d2ed54179bfe1b43e7853c"
integrity sha512-+1mqxn42uB32DPZ6kurSyGAmL3MgCaDpkYU7zNDWI4NLy3Zg97RxTsI1jBCGIqkEVvRZKJlIMYtb89OvMnq3AQ==
dependencies:
"@ai-sdk/provider" "2.0.0"
"@ai-sdk/provider-utils" "3.0.16"

"@ai-sdk/[email protected]":
version "3.0.16"
resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-3.0.16.tgz#17b7170bf51a7a690bf0186490ce29a8ce50a961"
integrity sha512-lsWQY9aDXHitw7C1QRYIbVGmgwyT98TF3MfM8alNIXKpdJdi+W782Rzd9f1RyOfgRmZ08gJ2EYNDhWNK7RqpEA==
dependencies:
"@ai-sdk/provider" "2.0.0"
"@standard-schema/spec" "^1.0.0"
eventsource-parser "^3.0.6"

"@ai-sdk/[email protected]":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@ai-sdk/provider/-/provider-2.0.0.tgz#b853c739d523b33675bc74b6c506b2c690bc602b"
integrity sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==
dependencies:
json-schema "^0.4.0"

"@amplitude/identify@^1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@amplitude/identify/-/identify-1.10.0.tgz#d62b8b6785c29350c368810475a6fc7b04985210"
Expand Down Expand Up @@ -725,7 +758,7 @@
resolved "https://registry.yarnpkg.com/@n1ru4l/json-patch-plus/-/json-patch-plus-0.2.0.tgz#b8fa09fd980c3460dfdc109a7c4cc5590157aa6b"
integrity sha512-pLkJy83/rVfDTyQgDSC8GeXAHEdXNHGNJrB1b7wAyGQu0iv7tpMXntKVSqj0+XKNVQbco40SZffNfVALzIt0SQ==

"@opentelemetry/api@^1.4.0":
"@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.4.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
Expand Down Expand Up @@ -811,6 +844,11 @@
dependencies:
"@sinonjs/commons" "^1.7.0"

"@standard-schema/spec@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c"
integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==

"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
Expand Down Expand Up @@ -1149,14 +1187,6 @@
"@types/node" "*"
form-data "^4.0.0"

"@types/node-fetch@^2.5.4":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==
dependencies:
"@types/node" "*"
form-data "^3.0.0"

"@types/node@*":
version "18.6.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.2.tgz#ffc5f0f099d27887c8d9067b54e55090fcd54126"
Expand Down Expand Up @@ -1305,6 +1335,11 @@
semver "^7.3.2"
tsutils "^3.17.1"

"@vercel/[email protected]":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@vercel/oidc/-/oidc-3.0.3.tgz#82c2b6dd4d5c3b37dcb1189718cdeb9db402d052"
integrity sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==

abab@^2.0.3, abab@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
Expand Down Expand Up @@ -1363,6 +1398,16 @@ agent-base@6:
dependencies:
debug "4"

ai@^5.0.89:
version "5.0.89"
resolved "https://registry.yarnpkg.com/ai/-/ai-5.0.89.tgz#8929fbc18f247aa9e4442836a12aa84191edf2a4"
integrity sha512-8Nq+ZojGacQrupoJEQLrTDzT5VtR3gyp5AaqFSV3tzsAXlYQ9Igb7QE3yeoEdzOk5IRfDwWL7mDCUD+oBg1hDA==
dependencies:
"@ai-sdk/gateway" "2.0.7"
"@ai-sdk/provider" "2.0.0"
"@ai-sdk/provider-utils" "3.0.16"
"@opentelemetry/api" "1.9.0"

ajv@^6.10.0, ajv@^6.10.2:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
Expand Down Expand Up @@ -2928,6 +2973,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==

eventsource-parser@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90"
integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==

exec-sh@^0.3.2:
version "0.3.6"
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc"
Expand Down Expand Up @@ -4526,6 +4576,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==

json-schema@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==

json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
Expand Down Expand Up @@ -7154,3 +7209,8 @@ [email protected]:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==

zod@^3.25.76:
version "3.25.76"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34"
integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==
Loading