Skip to content

Commit 0f8dad9

Browse files
authored
Merge pull request #590 from codex-team/feat/openai
feat(ai): added ai integration
2 parents b295da1 + 6e92e18 commit 0f8dad9

File tree

13 files changed

+229
-16
lines changed

13 files changed

+229
-16
lines changed

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.2.25",
3+
"version": "1.2.26",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {
@@ -34,6 +34,7 @@
3434
"typescript": "^4.7.4"
3535
},
3636
"dependencies": {
37+
"@ai-sdk/openai": "^2.0.64",
3738
"@amplitude/node": "^1.10.0",
3839
"@graphql-tools/merge": "^8.3.1",
3940
"@graphql-tools/schema": "^8.5.1",
@@ -53,9 +54,9 @@
5354
"@types/mongodb": "^3.6.20",
5455
"@types/morgan": "^1.9.10",
5556
"@types/node": "^16.11.46",
56-
"@types/node-fetch": "^2.5.4",
5757
"@types/safe-regex": "^1.1.6",
5858
"@types/uuid": "^8.3.4",
59+
"ai": "^5.0.89",
5960
"amqp-connection-manager": "^3.1.0",
6061
"amqplib": "^0.5.5",
6162
"apollo-server-express": "^3.10.0",
@@ -86,6 +87,7 @@
8687
"redis": "^4.7.0",
8788
"safe-regex": "^2.1.0",
8889
"ts-node-dev": "^2.0.0",
89-
"uuid": "^8.3.2"
90+
"uuid": "^8.3.2",
91+
"zod": "^3.25.76"
9092
}
9193
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { EventAddons, EventData } from '@hawk.so/types';
2+
import { generateText } from 'ai';
3+
import { openai } from '@ai-sdk/openai';
4+
import { eventSolvingInput } from './inputs/eventSolving';
5+
import { ctoInstruction } from './instructions/cto';
6+
7+
/**
8+
* Interface for interacting with Vercel AI Gateway
9+
*/
10+
class VercelAIApi {
11+
/**
12+
* Model ID to use for generating suggestions
13+
*/
14+
private readonly modelId: string;
15+
16+
constructor() {
17+
/**
18+
* @todo make it dynamic, get from project settings
19+
*/
20+
this.modelId = 'gpt-4o';
21+
}
22+
23+
/**
24+
* Generate AI suggestion for the event
25+
*
26+
* @param {EventData<EventAddons>} payload - event data
27+
* @returns {Promise<string>} AI suggestion for the event
28+
* @todo add defence against invalid prompt injection
29+
*/
30+
public async generateSuggestion(payload: EventData<EventAddons>) {
31+
const { text } = await generateText({
32+
model: openai(this.modelId),
33+
system: ctoInstruction,
34+
prompt: eventSolvingInput(payload),
35+
});
36+
37+
return text;
38+
}
39+
}
40+
41+
export const vercelAIApi = new VercelAIApi();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { EventData, EventAddons } from '@hawk.so/types';
2+
3+
export const eventSolvingInput = (payload: EventData<EventAddons>) => `
4+
Payload: ${JSON.stringify(payload)}
5+
`;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const ctoInstruction = `Ты технический директор ИТ компании, тебе нужно пояснить ошибку и предложить решение.
2+
3+
Предоставь ответ в следующем формате:
4+
5+
1. Описание проблемы
6+
2. Решение проблемы
7+
3. Описание того, как можно предотвратить подобную ошибку в будущем
8+
9+
Ответь на русском языке.`;

src/models/eventsFactory.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ class EventsFactory extends Factory {
691691
/**
692692
* If originalEventId equals repetitionId than user wants to get first repetition which is original event
693693
*/
694-
if (repetitionId === originalEventId) {
694+
if (repetitionId.toString() === originalEventId.toString()) {
695695
const originalEvent = await this.eventsDataLoader.load(originalEventId);
696696

697697
/**

src/resolvers/event.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const getEventsFactory = require('./helpers/eventsFactory').default;
22
const sendPersonalNotification = require('../utils/personalNotifications').default;
3+
const { aiService } = require('../services/ai');
34

45
/**
56
* See all types and fields here {@see ../typeDefs/event.graphql}
@@ -89,6 +90,20 @@ module.exports = {
8990
return factory.getEventDailyChart(groupHash, days, timezoneOffset);
9091
},
9192

93+
/**
94+
* Return AI suggestion for the event
95+
*
96+
* @param {string} projectId - event's project
97+
* @param {string} eventId - event id
98+
* @param {string} originalEventId - original event id
99+
* @returns {Promise<string>} AI suggestion for the event
100+
*/
101+
async aiSuggestion({ projectId, _id: eventId, originalEventId }, _args, context) {
102+
const factory = getEventsFactory(context, projectId);
103+
104+
return aiService.generateSuggestion(factory, eventId, originalEventId);
105+
},
106+
92107
/**
93108
* Return release data for the event
94109
*

src/services/ai.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { vercelAIApi } from '../integrations/vercel-ai/';
2+
import { EventsFactoryInterface } from './types';
3+
4+
/**
5+
* Service for interacting with AI
6+
*/
7+
export class AIService {
8+
/**
9+
* Generate suggestion for the event
10+
*
11+
* @param eventsFactory - events factory
12+
* @param eventId - event id
13+
* @param originalEventId - original event id
14+
* @returns {Promise<string>} - suggestion
15+
*/
16+
public async generateSuggestion(eventsFactory: EventsFactoryInterface, eventId: string, originalEventId: string): Promise<string> {
17+
const event = await eventsFactory.getEventRepetition(eventId, originalEventId);
18+
19+
if (!event) {
20+
throw new Error('Event not found');
21+
}
22+
23+
return vercelAIApi.generateSuggestion(event.payload);
24+
}
25+
}
26+
27+
export const aiService = new AIService();

src/services/types.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { EventAddons, EventData } from '@hawk.so/types';
2+
3+
/**
4+
* Event type which is returned by events factory
5+
*/
6+
type Event = {
7+
_id: string;
8+
payload: EventData<EventAddons>;
9+
};
10+
11+
/**
12+
* Interface for interacting with events factory
13+
*/
14+
export interface EventsFactoryInterface {
15+
/**
16+
* Get event repetition
17+
*
18+
* @param repetitionId - repetition id
19+
* @param originalEventId - original event id
20+
* @returns {Promise<EventData<EventAddons>>} - event repetition
21+
*/
22+
getEventRepetition(repetitionId: string, originalEventId: string): Promise<Event>;
23+
}

src/typeDefs/event.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ type Event {
262262
"""
263263
repetitionsPortion(cursor: String = null, limit: Int = 10): RepetitionsPortion!
264264
265+
"""
266+
AI suggestion for the event
267+
"""
268+
aiSuggestion: String
269+
265270
"""
266271
Array of users who visited event
267272
"""

src/types/vercel-ai.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
declare module 'ai' {
2+
/**
3+
* Minimal type for generateText used in server-side integration.
4+
*/
5+
export function generateText(input: {
6+
model: any;
7+
system?: string;
8+
prompt: string;
9+
}): Promise<{ text: string }>;
10+
}
11+
12+
declare module '@ai-sdk/openai' {
13+
/**
14+
* Minimal types for OpenAI provider.
15+
*/
16+
export function createOpenAI(config?: { apiKey?: string }): (model: string) => any;
17+
export const openai: (model: string) => any;
18+
}

0 commit comments

Comments
 (0)