refactor: Update Llama app to use local model path instead of model ID

This commit is contained in:
Simon Larsen
2024-06-19 15:06:36 +01:00
parent 85edd12c2d
commit ccdcf2c679
11 changed files with 267 additions and 121 deletions

View File

@@ -3,6 +3,7 @@ import BaseAnalyticsAPI from "CommonServer/API/BaseAnalyticsAPI";
import BillingInvoiceAPI from "CommonServer/API/BillingInvoiceAPI";
import BillingPaymentMethodAPI from "CommonServer/API/BillingPaymentMethodAPI";
import CodeRepositoryAPI from "CommonServer/API/CodeRepositoryAPI";
import CopilotEventAPI from "CommonServer/API/CopilotEventAPI";
import FileAPI from "CommonServer/API/FileAPI";
import GlobalConfigAPI from "CommonServer/API/GlobalConfigAPI";
import MonitorGroupAPI from "CommonServer/API/MonitorGroupAPI";
@@ -30,9 +31,6 @@ import ApiKeyService, {
import CallLogService, {
Service as CallLogServiceType,
} from "CommonServer/Services/CallLogService";
import CopilotEventService, {
Service as CopilotEventServiceType,
} from "CommonServer/Services/CopilotEventService";
import DomainService, {
Service as DomainServiceType,
} from "CommonServer/Services/DomainService";
@@ -297,7 +295,6 @@ import Span from "Model/AnalyticsModels/Span";
import ApiKey from "Model/Models/ApiKey";
import ApiKeyPermission from "Model/Models/ApiKeyPermission";
import CallLog from "Model/Models/CallLog";
import CopilotEvent from "Model/Models/CopilotEvent";
import Domain from "Model/Models/Domain";
import EmailLog from "Model/Models/EmailLog";
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
@@ -504,14 +501,6 @@ const BaseAPIFeatureSet: FeatureSet = {
).getRouter(),
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new BaseAPI<CopilotEvent, CopilotEventServiceType>(
CopilotEvent,
CopilotEventService,
).getRouter(),
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new BaseAPI<ServiceCatalogOwnerUser, ServiceCatalogOwnerUserServiceType>(
@@ -1023,6 +1012,11 @@ const BaseAPIFeatureSet: FeatureSet = {
new CodeRepositoryAPI().getRouter(),
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new CopilotEventAPI().getRouter(),
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new UserNotificationLogTimelineAPI().getRouter(),

View File

@@ -1,8 +1,7 @@
import UserMiddleware from "../Middleware/UserAuthorization";
import CodeRepositoryAuthorization from "../Middleware/CodeRepositoryAuthorization";
import CodeRepositoryService, {
Service as CodeRepositoryServiceType,
} from "../Services/CodeRepositoryService";
import CopilotEventService from "../Services/CopilotEventService";
import ServiceRepositoryService from "../Services/ServiceRepositoryService";
import {
ExpressRequest,
@@ -15,7 +14,6 @@ import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
import BadDataException from "Common/Types/Exception/BadDataException";
import ObjectID from "Common/Types/ObjectID";
import CodeRepository from "Model/Models/CodeRepository";
import CopilotEvent from "Model/Models/CopilotEvent";
import ServiceRepository from "Model/Models/ServiceRepository";
export default class CodeRepositoryAPI extends BaseAPI<
@@ -29,7 +27,7 @@ export default class CodeRepositoryAPI extends BaseAPI<
`${new this.entityType()
.getCrudApiPath()
?.toString()}/get-code-repository/:secretkey`,
UserMiddleware.getUserMiddleware,
CodeRepositoryAuthorization.isAuthorizedRepository,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const secretkey: string = req.params["secretkey"]!;
@@ -98,84 +96,5 @@ export default class CodeRepositoryAPI extends BaseAPI<
}
},
);
this.router.get(
`${new this.entityType()
.getCrudApiPath()
?.toString()}/get-copilot-events-by-file/:secretkey`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const secretkey: string = req.params["secretkey"]!;
if (!secretkey) {
throw new BadDataException("Secret key is required");
}
const filePath: string = req.body["filePath"]!;
if (!filePath) {
throw new BadDataException("File path is required");
}
const serviceCatalogId: string = req.body["serviceCatalogId"]!;
if (!serviceCatalogId) {
throw new BadDataException("Service catalog id is required");
}
const codeRepository: CodeRepository | null =
await CodeRepositoryService.findOneBy({
query: {
secretToken: new ObjectID(secretkey),
},
select: {
_id: true,
},
props: {
isRoot: true,
},
});
if (!codeRepository) {
throw new BadDataException(
"Code repository not found. Secret key is invalid.",
);
}
const copilotEvents: Array<CopilotEvent> =
await CopilotEventService.findBy({
query: {
codeRepositoryId: codeRepository.id!,
filePath: filePath,
serviceCatalogId: new ObjectID(serviceCatalogId),
},
select: {
_id: true,
codeRepositoryId: true,
serviceCatalogId: true,
filePath: true,
copilotEventStatus: true,
copilotEventType: true,
createdAt: true,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
props: {
isRoot: true,
},
});
return Response.sendJsonObjectResponse(req, res, {
copilotEvents: CopilotEvent.toJSONArray(
copilotEvents,
CopilotEvent,
),
});
} catch (err) {
next(err);
}
},
);
}
}

View File

@@ -0,0 +1,105 @@
import CodeRepository from "Model/Models/CodeRepository";
import CopilotEventService, {
Service as CopilotEventServiceType,
} from "../Services/CopilotEventService";
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from "../Utils/Express";
import Response from "../Utils/Response";
import BaseAPI from "./BaseAPI";
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
import BadDataException from "Common/Types/Exception/BadDataException";
import ObjectID from "Common/Types/ObjectID";
import CopilotEvent from "Model/Models/CopilotEvent";
import CodeRepositoryService from "../Services/CodeRepositoryService";
import CodeRepositoryAuthorization from "../Middleware/CodeRepositoryAuthorization";
export default class CopilotEventAPI extends BaseAPI<
CopilotEvent,
CopilotEventServiceType
> {
public constructor() {
super(CopilotEvent, CopilotEventService);
this.router.get(
`${new this.entityType()
.getCrudApiPath()
?.toString()}/copilot-events-by-file/:secretkey`,
CodeRepositoryAuthorization.isAuthorizedRepository,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const secretkey: string = req.params["secretkey"]!;
if (!secretkey) {
throw new BadDataException("Secret key is required");
}
const filePath: string = req.body["filePath"]!;
if (!filePath) {
throw new BadDataException("File path is required");
}
const serviceCatalogId: string = req.body["serviceCatalogId"]!;
if (!serviceCatalogId) {
throw new BadDataException("Service catalog id is required");
}
const codeRepository: CodeRepository | null =
await CodeRepositoryService.findOneBy({
query: {
secretToken: new ObjectID(secretkey),
},
select: {
_id: true,
},
props: {
isRoot: true,
},
});
if (!codeRepository) {
throw new BadDataException(
"Code repository not found. Secret key is invalid.",
);
}
const copilotEvents: Array<CopilotEvent> =
await CopilotEventService.findBy({
query: {
codeRepositoryId: codeRepository.id!,
filePath: filePath,
serviceCatalogId: new ObjectID(serviceCatalogId),
},
select: {
_id: true,
codeRepositoryId: true,
serviceCatalogId: true,
filePath: true,
copilotEventStatus: true,
copilotEventType: true,
createdAt: true,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
props: {
isRoot: true,
},
});
return Response.sendJsonObjectResponse(req, res, {
copilotEvents: CopilotEvent.toJSONArray(
copilotEvents,
CopilotEvent,
),
});
} catch (err) {
next(err);
}
},
);
}
}

View File

@@ -0,0 +1,48 @@
import BadDataException from "Common/Types/Exception/BadDataException";
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from "../Utils/Express";
import CodeRepository from "Model/Models/CodeRepository";
import CodeRepositoryService from "../Services/CodeRepositoryService";
import ObjectID from "Common/Types/ObjectID";
export default class CodeRepositoryAuthorization {
public static async isAuthorizedRepository(
req: ExpressRequest,
_res: ExpressResponse,
next: NextFunction,
): Promise<void> {
try {
const secretkey: string = req.params["secretkey"]!;
if (!secretkey) {
throw new BadDataException("Secret key is required");
}
const codeRepository: CodeRepository | null =
await CodeRepositoryService.findOneBy({
query: {
secretToken: new ObjectID(secretkey),
},
select: {
_id: true,
},
props: {
isRoot: true,
},
});
if (!codeRepository) {
throw new BadDataException(
"Code repository not found. Secret key is invalid.",
);
}
next();
} catch (err) {
next(err);
}
}
}

View File

@@ -3,3 +3,6 @@ ONEUPTIME_REPOSITORY_SECRET_KEY=your-repository-secret-key
ONEUPTIME_LOCAL_REPOSITORY_PATH=/repository
GITHUB_TOKEN=
GITHUB_USERNAME=
# Optional. If this is left blank then this url will be ONEUPTIME_URL/llama
ONEUPTIME_LLAMA_SERVER_URL=
LLM_TYPE=llama

View File

@@ -29,3 +29,16 @@ export const GetGitHubUsername: GetStringOrNullFunction = (): string | null => {
const username: string | null = process.env["GITHUB_USERNAME"] || null;
return username;
};
export const GetLlamaServerUrl: GetURLFunction = () => {
return URL.fromString(
process.env["LLAMA_SERVER_URL"] ||
GetOneUptimeURL().addRoute("/llama").toString(),
);
};
type GetLlmTypeFunction = () => LlmType;
export const GetLlmType: GetLlmTypeFunction = (): LlmType => {
return (process.env["LLM_TYPE"] as LlmType) || LlmType.Llama;
};

View File

@@ -1,37 +1,14 @@
import CodeRepositoryUtil, {
CodeRepositoryResult,
} from "./Utils/CodeRepository";
import InitUtil from "./Utils/Init";
import ServiceRepositoryUtil from "./Utils/ServiceRepository";
import CodeRepositoryUtil from "./Utils/CodeRepository";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import Dictionary from "Common/Types/Dictionary";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import CodeRepositoryFile from "CommonServer/Utils/CodeRepository/CodeRepositoryFile";
import logger from "CommonServer/Utils/Logger";
import dotenv from "dotenv";
import Init from "./Init";
dotenv.config();
logger.info("OneUptime Copilot is starting...");
const init: PromiseVoidFunction = async (): Promise<void> => {
const codeRepositoryResult: CodeRepositoryResult = await InitUtil.init();
for (const serviceRepository of codeRepositoryResult.servicesRepository) {
const filesInService: Dictionary<CodeRepositoryFile> =
await ServiceRepositoryUtil.getFilesInServiceDirectory({
serviceRepository,
});
logger.info(
`Files found in ${serviceRepository.serviceCatalog?.name}: ${
Object.keys(filesInService).length
}`,
);
}
};
init()
Init()
.then(() => {
process.exit(0);
})

26
Copilot/Init.ts Normal file
View File

@@ -0,0 +1,26 @@
import { CodeRepositoryResult } from "./Utils/CodeRepository";
import InitUtil from "./Utils/Init";
import ServiceRepositoryUtil from "./Utils/ServiceRepository";
import Dictionary from "Common/Types/Dictionary";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import CodeRepositoryFile from "CommonServer/Utils/CodeRepository/CodeRepositoryFile";
import logger from "CommonServer/Utils/Logger";
const init: PromiseVoidFunction = async (): Promise<void> => {
const codeRepositoryResult: CodeRepositoryResult = await InitUtil.init();
for (const serviceRepository of codeRepositoryResult.servicesRepository) {
const filesInService: Dictionary<CodeRepositoryFile> =
await ServiceRepositoryUtil.getFilesInServiceDirectory({
serviceRepository,
});
logger.info(
`Files found in ${serviceRepository.serviceCatalog?.name}: ${
Object.keys(filesInService).length
}`,
);
}
};
export default init;

View File

@@ -0,0 +1,7 @@
import NotImplementedException from "Common/Types/Exception/NotImplementedException";
export default class LlmBase {
public static async getResponse(_data: { input: string }): Promise<string> {
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,48 @@
import URL from "Common/Types/API/URL";
import { GetLlamaServerUrl } from "../../Config";
import LlmBase from "./LLMBase";
import API from "Common/Utils/API";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import { JSONArray, JSONObject } from "Common/Types/JSON";
import BadRequestException from "Common/Types/Exception/BadRequestException";
export default class Llama extends LlmBase {
public static override async getResponse(data: {
input: string;
}): Promise<string> {
const serverUrl: URL = GetLlamaServerUrl();
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post(serverUrl.addRoute("/prompt"), {
prompt: data.input,
});
if (response instanceof HTTPErrorResponse) {
throw response;
}
const result: JSONObject = response.data;
if (
result["response"] &&
(result["response"] as JSONObject)["generated_text"]
) {
const arrayOfGeneratedText: JSONArray = (
result["response"] as JSONObject
)["generated_text"] as JSONArray;
// get last item
const lastItem: JSONObject = arrayOfGeneratedText[
arrayOfGeneratedText.length - 1
] as JSONObject;
if (lastItem["content"]) {
return lastItem["content"] as string;
}
}
throw new BadRequestException("Failed to get response from Llama server");
}
}

6
Copilot/Types/LlmType.ts Normal file
View File

@@ -0,0 +1,6 @@
enum LlmType {
Llama = "llama",
OpenAI = "openai",
}
export default LlmType;