mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-01-11 19:56:44 +00:00
feat(ai-agent): Implement AI Agent service with configuration, registration, and health check functionality
This commit is contained in:
parent
51e9e2d95b
commit
1a2acbf12d
18 changed files with 651 additions and 5 deletions
40
AIAgent/Config.ts
Normal file
40
AIAgent/Config.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import URL from "Common/Types/API/URL";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Port from "Common/Types/Port";
|
||||
|
||||
if (!process.env["ONEUPTIME_URL"]) {
|
||||
logger.error("ONEUPTIME_URL is not set");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
export let ONEUPTIME_URL: URL = URL.fromString(
|
||||
process.env["ONEUPTIME_URL"] || "https://oneuptime.com",
|
||||
);
|
||||
|
||||
// If the URL does not have the ai-agent-ingest path, add it.
|
||||
if (
|
||||
!ONEUPTIME_URL.toString().endsWith("ai-agent-ingest") &&
|
||||
!ONEUPTIME_URL.toString().endsWith("ai-agent-ingest/")
|
||||
) {
|
||||
ONEUPTIME_URL = URL.fromString(
|
||||
ONEUPTIME_URL.addRoute("/ai-agent-ingest").toString(),
|
||||
);
|
||||
}
|
||||
|
||||
export const AI_AGENT_ID: ObjectID | null = process.env["AI_AGENT_ID"]
|
||||
? new ObjectID(process.env["AI_AGENT_ID"])
|
||||
: null;
|
||||
|
||||
if (!process.env["AI_AGENT_KEY"]) {
|
||||
logger.error("AI_AGENT_KEY is not set");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
export const AI_AGENT_KEY: string = process.env["AI_AGENT_KEY"];
|
||||
|
||||
export const HOSTNAME: string = process.env["HOSTNAME"] || "localhost";
|
||||
|
||||
export const PORT: Port = new Port(
|
||||
process.env["PORT"] ? parseInt(process.env["PORT"]) : 3875,
|
||||
);
|
||||
58
AIAgent/Index.ts
Normal file
58
AIAgent/Index.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { PORT } from "./Config";
|
||||
import AliveJob from "./Jobs/Alive";
|
||||
import Register from "./Services/Register";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import App from "Common/Server/Utils/StartServer";
|
||||
import Telemetry from "Common/Server/Utils/Telemetry";
|
||||
import "ejs";
|
||||
|
||||
const APP_NAME: string = "ai-agent";
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// Initialize telemetry
|
||||
Telemetry.init({
|
||||
serviceName: APP_NAME,
|
||||
});
|
||||
|
||||
logger.info("AI Agent Service - Starting...");
|
||||
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: PORT,
|
||||
isFrontendApp: false,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
|
||||
try {
|
||||
// Register this AI Agent.
|
||||
await Register.registerAIAgent();
|
||||
|
||||
logger.debug("AI Agent registered");
|
||||
|
||||
AliveJob();
|
||||
} catch (err) {
|
||||
logger.error("Register AI Agent failed");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("App Init Failed:");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
init().catch((err: Error) => {
|
||||
logger.error(err);
|
||||
logger.error("Exiting node process");
|
||||
process.exit(1);
|
||||
});
|
||||
56
AIAgent/Jobs/Alive.ts
Normal file
56
AIAgent/Jobs/Alive.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { ONEUPTIME_URL } from "../Config";
|
||||
import Register from "../Services/Register";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import BasicCron from "Common/Server/Utils/BasicCron";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
const InitJob: VoidFunction = (): void => {
|
||||
BasicCron({
|
||||
jobName: "AIAgent:Alive",
|
||||
options: {
|
||||
schedule: EVERY_MINUTE,
|
||||
runOnStartup: false,
|
||||
},
|
||||
runFunction: async () => {
|
||||
logger.debug("Checking if AI Agent is alive...");
|
||||
|
||||
const aiAgentId: string | undefined = LocalCache.getString(
|
||||
"AI_AGENT",
|
||||
"AI_AGENT_ID",
|
||||
);
|
||||
|
||||
if (!aiAgentId) {
|
||||
logger.warn(
|
||||
"AI Agent is not registered yet. Skipping alive check. Trying to register AI Agent again...",
|
||||
);
|
||||
await Register.registerAIAgent();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("AI Agent ID: " + aiAgentId.toString());
|
||||
|
||||
const aliveUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/alive");
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aliveUrl,
|
||||
data: AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
});
|
||||
|
||||
if (result.isSuccess()) {
|
||||
logger.debug("AI Agent update sent to server successfully.");
|
||||
} else {
|
||||
logger.error("Failed to send AI Agent update to server.");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default InitJob;
|
||||
75
AIAgent/Services/Register.ts
Normal file
75
AIAgent/Services/Register.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { ONEUPTIME_URL, AI_AGENT_ID, AI_AGENT_KEY } from "../Config";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import Sleep from "Common/Types/Sleep";
|
||||
import API from "Common/Utils/API";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
export default class Register {
|
||||
public static async registerAIAgent(): Promise<void> {
|
||||
// register AI agent with 10 retries and 30 second interval between each retry.
|
||||
|
||||
let currentRetry: number = 0;
|
||||
|
||||
const maxRetry: number = 10;
|
||||
|
||||
const retryIntervalInSeconds: number = 30;
|
||||
|
||||
while (currentRetry < maxRetry) {
|
||||
try {
|
||||
logger.debug(`Registering AI Agent. Attempt: ${currentRetry + 1}`);
|
||||
await Register._registerAIAgent();
|
||||
logger.debug(`AI Agent registered successfully.`);
|
||||
break;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to register AI Agent. Retrying after ${retryIntervalInSeconds} seconds...`,
|
||||
);
|
||||
logger.error(error);
|
||||
currentRetry++;
|
||||
await Sleep.sleep(retryIntervalInSeconds * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async _registerAIAgent(): Promise<void> {
|
||||
// Validate AI agent by sending alive request
|
||||
if (!AI_AGENT_ID) {
|
||||
logger.error("AI_AGENT_ID should be set");
|
||||
return process.exit();
|
||||
}
|
||||
|
||||
const aliveUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/alive");
|
||||
|
||||
logger.debug("Registering AI Agent...");
|
||||
logger.debug("Sending request to: " + aliveUrl.toString());
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aliveUrl,
|
||||
data: {
|
||||
aiAgentKey: AI_AGENT_KEY.toString(),
|
||||
aiAgentId: AI_AGENT_ID.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
if (result.isSuccess()) {
|
||||
LocalCache.setString(
|
||||
"AI_AGENT",
|
||||
"AI_AGENT_ID",
|
||||
AI_AGENT_ID.toString() as string,
|
||||
);
|
||||
logger.debug("AI Agent registered successfully");
|
||||
} else {
|
||||
throw new Error("Failed to register AI Agent: " + result.statusCode);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`AI Agent ID: ${LocalCache.getString("AI_AGENT", "AI_AGENT_ID") || "Unknown"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
17
AIAgent/Utils/AIAgent.ts
Normal file
17
AIAgent/Utils/AIAgent.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
|
||||
export default class AIAgentUtil {
|
||||
public static getAIAgentId(): ObjectID {
|
||||
const id: string | undefined =
|
||||
LocalCache.getString("AI_AGENT", "AI_AGENT_ID") ||
|
||||
process.env["AI_AGENT_ID"];
|
||||
|
||||
if (!id) {
|
||||
throw new BadDataException("AI Agent ID not found");
|
||||
}
|
||||
|
||||
return new ObjectID(id);
|
||||
}
|
||||
}
|
||||
12
AIAgent/Utils/AIAgentAPIRequest.ts
Normal file
12
AIAgent/Utils/AIAgentAPIRequest.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { AI_AGENT_KEY } from "../Config";
|
||||
import AIAgentUtil from "./AIAgent";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
export default class AIAgentAPIRequest {
|
||||
public static getDefaultRequestBody(): JSONObject {
|
||||
return {
|
||||
aiAgentKey: AI_AGENT_KEY,
|
||||
aiAgentId: AIAgentUtil.getAIAgentId().toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
11
AIAgent/nodemon.json
Normal file
11
AIAgent/nodemon.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"watch": [
|
||||
"./",
|
||||
"../Common"
|
||||
],
|
||||
"ext": "ts,tsx",
|
||||
"ignore": ["./node_modules/**", "./public/**", "./bin/**", "./build/**"],
|
||||
"watchOptions": {"useFsEvents": false, "interval": 500},
|
||||
"env": {"TS_NODE_TRANSPILE_ONLY": "1", "TS_NODE_FILES": "false"},
|
||||
"exec": "node -r ts-node/register/transpile-only Index.ts"
|
||||
}
|
||||
36
AIAgent/package.json
Normal file
36
AIAgent/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@oneuptime/ai-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "OneUptime AI Agent",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
"compile": "tsc",
|
||||
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
|
||||
"dev": "npx nodemon",
|
||||
"audit": "npm audit --audit-level=low",
|
||||
"dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true",
|
||||
"test": "jest --detectOpenHandles --passWithNoTests",
|
||||
"coverage": "jest --detectOpenHandles --coverage",
|
||||
"debug:test": "node --inspect node_modules/.bin/jest --runInBand ./Tests --detectOpenHandles"
|
||||
},
|
||||
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.13.1",
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.10",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^17.0.31",
|
||||
"jest": "^28.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-jest": "^28.0.2"
|
||||
}
|
||||
}
|
||||
45
AIAgent/tsconfig.json
Normal file
45
AIAgent/tsconfig.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"rootDir": "",
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"types": ["node", "jest"],
|
||||
"sourceMap": true,
|
||||
"outDir": "build/dist",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
82
App/FeatureSet/AIAgentIngest/API/AIAgentIngest.ts
Normal file
82
App/FeatureSet/AIAgentIngest/API/AIAgentIngest.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import AIAgentService from "Common/Server/Services/AIAgentService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import AIAgent from "Common/Models/DatabaseModels/AIAgent";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// Middleware to authorize AI Agent requests
|
||||
async function isAuthorizedAIAgentMiddleware(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> {
|
||||
const data: JSONObject = req.body;
|
||||
|
||||
if (!data["aiAgentId"] || !data["aiAgentKey"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("aiAgentId or aiAgentKey is missing"),
|
||||
);
|
||||
}
|
||||
|
||||
const aiAgentId: ObjectID = new ObjectID(data["aiAgentId"] as string);
|
||||
|
||||
const aiAgentKey: string = data["aiAgentKey"] as string;
|
||||
|
||||
const aiAgent: AIAgent | null = await AIAgentService.findOneBy({
|
||||
query: {
|
||||
_id: aiAgentId.toString(),
|
||||
secretKey: aiAgentKey,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!aiAgent) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid AI Agent ID or AI Agent Key"),
|
||||
);
|
||||
}
|
||||
|
||||
// Update last alive
|
||||
await AIAgentService.updateLastAlive(aiAgentId);
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
router.post(
|
||||
"/alive",
|
||||
isAuthorizedAIAgentMiddleware,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Update last alive in AI Agent and return success response.
|
||||
// The middleware already updates lastAlive, so we just return success.
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
16
App/FeatureSet/AIAgentIngest/Index.ts
Normal file
16
App/FeatureSet/AIAgentIngest/Index.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import AIAgentIngestAPI from "./API/AIAgentIngest";
|
||||
import FeatureSet from "Common/Server/Types/FeatureSet";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
|
||||
const AIAgentIngestFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
const APP_NAME: string = "ai-agent-ingest";
|
||||
|
||||
// Mount the AI Agent ingest API routes
|
||||
app.use([`/${APP_NAME}`, "/"], AIAgentIngestAPI);
|
||||
},
|
||||
};
|
||||
|
||||
export default AIAgentIngestFeatureSet;
|
||||
|
|
@ -2,6 +2,7 @@ import BaseAPIRoutes from "./FeatureSet/BaseAPI/Index";
|
|||
// import FeatureSets.
|
||||
import IdentityRoutes from "./FeatureSet/Identity/Index";
|
||||
import NotificationRoutes from "./FeatureSet/Notification/Index";
|
||||
import AIAgentIngestRoutes from "./FeatureSet/AIAgentIngest/Index";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import { ClickhouseAppInstance } from "Common/Server/Infrastructure/ClickhouseDatabase";
|
||||
import PostgresAppInstance from "Common/Server/Infrastructure/PostgresDatabase";
|
||||
|
|
@ -94,6 +95,7 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
|
|||
await IdentityRoutes.init();
|
||||
await NotificationRoutes.init();
|
||||
await BaseAPIRoutes.init();
|
||||
await AIAgentIngestRoutes.init();
|
||||
|
||||
// Add default routes to the app
|
||||
await App.addDefaultRoutes();
|
||||
|
|
|
|||
124
HelmChart/Public/oneuptime/templates/ai-agent.yaml
Normal file
124
HelmChart/Public/oneuptime/templates/ai-agent.yaml
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{{- if .Values.aiAgent.enabled }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ printf "%s-%s" $.Release.Name "ai-agent" }}
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
app: {{ printf "%s-%s" $.Release.Name "ai-agent" }}
|
||||
app.kubernetes.io/part-of: oneuptime
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
appname: oneuptime
|
||||
{{- if $.Values.deployment.includeTimestampLabel }}
|
||||
date: "{{ now | unixEpoch }}"
|
||||
{{- end }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ printf "%s-%s" $.Release.Name "ai-agent" }}
|
||||
{{- if and (ne $.Values.aiAgent.replicaCount nil) $.Values.aiAgent.disableAutoscaler }}
|
||||
replicas: {{ $.Values.aiAgent.replicaCount }}
|
||||
{{- else }}
|
||||
{{- if or (not $.Values.autoscaling.enabled) ($.Values.aiAgent.disableAutoscaler) }}
|
||||
replicas: {{ $.Values.deployment.replicaCount }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
strategy: {{- toYaml $.Values.deployment.updateStrategy | nindent 4 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ printf "%s-%s" $.Release.Name "ai-agent" }}
|
||||
{{- if $.Values.deployment.includeTimestampLabel }}
|
||||
date: "{{ now | unixEpoch }}"
|
||||
{{- end }}
|
||||
appname: oneuptime
|
||||
spec:
|
||||
{{- if $.Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml $.Values.imagePullSecrets | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if $.Values.aiAgent.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml $.Values.aiAgent.podSecurityContext | nindent 8 }}
|
||||
{{- else if $.Values.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml $.Values.podSecurityContext | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if $.Values.affinity }}
|
||||
affinity: {{- $.Values.affinity | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if $.Values.tolerations }}
|
||||
tolerations: {{- $.Values.tolerations | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if $.Values.aiAgent.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml $.Values.aiAgent.nodeSelector | nindent 8 }}
|
||||
{{- else if $.Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml $.Values.nodeSelector | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- image: {{ include "oneuptime.image" (dict "Values" $.Values "ServiceName" "ai-agent") }}
|
||||
name: {{ printf "%s-%s" $.Release.Name "ai-agent" }}
|
||||
{{- if $.Values.aiAgent.containerSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml $.Values.aiAgent.containerSecurityContext | nindent 12 }}
|
||||
{{- else if $.Values.containerSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml $.Values.containerSecurityContext | nindent 12 }}
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ $.Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: BILLING_ENABLED
|
||||
value: {{ $.Values.billing.enabled | squote }}
|
||||
- name: LOG_LEVEL
|
||||
value: {{ $.Values.logLevel }}
|
||||
- name: PORT
|
||||
value: {{ $.Values.aiAgent.ports.http | squote }}
|
||||
- name: OPENTELEMETRY_EXPORTER_OTLP_HEADERS
|
||||
value: {{ $.Values.openTelemetryExporter.headers }}
|
||||
- name: OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT
|
||||
value: {{ $.Values.openTelemetryExporter.endpoint }}
|
||||
- name: ONEUPTIME_URL
|
||||
value: http://{{ $.Release.Name }}-app.{{ $.Release.Namespace }}.svc.{{ $.Values.global.clusterDomain }}:{{ $.Values.app.ports.http }}
|
||||
- name: AI_AGENT_ID
|
||||
value: {{ $.Values.aiAgent.id | squote }}
|
||||
- name: AI_AGENT_KEY
|
||||
{{- if $.Values.aiAgent.key }}
|
||||
value: {{ $.Values.aiAgent.key }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ printf "%s-%s" $.Release.Name "secrets" }}
|
||||
key: ai-agent-key
|
||||
{{- end }}
|
||||
{{- if $.Values.aiAgent.disableTelemetryCollection }}
|
||||
- name: DISABLE_TELEMETRY
|
||||
value: {{ $.Values.aiAgent.disableTelemetryCollection | quote }}
|
||||
{{- end }}
|
||||
{{- include "oneuptime.env.runtime" (dict "Values" $.Values "Release" $.Release) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: {{ $.Values.aiAgent.ports.http }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
{{- if $.Values.aiAgent.resources }}
|
||||
resources:
|
||||
{{- toYaml $.Values.aiAgent.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
restartPolicy: {{ $.Values.image.restartPolicy }}
|
||||
---
|
||||
|
||||
# OneUptime AI Agent Service
|
||||
{{- $aiAgentPort := $.Values.aiAgent.ports.http }}
|
||||
{{- $aiAgentPorts := dict "port" $aiAgentPort -}}
|
||||
{{- $aiAgentServiceArgs := dict "ServiceName" "ai-agent" "Ports" $aiAgentPorts "Release" $.Release "Values" $.Values -}}
|
||||
{{- include "oneuptime.service" $aiAgentServiceArgs }}
|
||||
---
|
||||
|
||||
{{- if not $.Values.aiAgent.disableAutoscaler }}
|
||||
# OneUptime AI Agent autoscaler
|
||||
{{- $aiAgentAutoScalerArgs := dict "ServiceName" "ai-agent" "Release" $.Release "Values" $.Values -}}
|
||||
{{- include "oneuptime.autoscaler" $aiAgentAutoScalerArgs }}
|
||||
{{- end }}
|
||||
|
||||
{{- end }}
|
||||
|
|
@ -31,6 +31,14 @@ stringData:
|
|||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if and $.Values.aiAgent.enabled (not $.Values.aiAgent.key) }}
|
||||
{{- if (index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "ai-agent-key") }}
|
||||
ai-agent-key: {{ (index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "ai-agent-key" | b64dec) }}
|
||||
{{- else }}
|
||||
ai-agent-key: {{ randAlphaNum 32 | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{ else }} # install operation
|
||||
|
||||
{{- if .Values.oneuptimeSecret }}
|
||||
|
|
@ -48,6 +56,10 @@ stringData:
|
|||
{{printf "probe-%s" $key}}: {{ randAlphaNum 32 | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- if and $.Values.aiAgent.enabled (not $.Values.aiAgent.key) }}
|
||||
ai-agent-key: {{ randAlphaNum 32 | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -777,6 +777,25 @@ mcp:
|
|||
podSecurityContext: {}
|
||||
containerSecurityContext: {}
|
||||
|
||||
# AI Agent Configuration
|
||||
# Deploy this to run an AI Agent within your Kubernetes cluster
|
||||
# Note: This is disabled by default. To enable, set enabled to true and provide the AI Agent credentials
|
||||
aiAgent:
|
||||
enabled: false
|
||||
replicaCount: 1
|
||||
disableTelemetryCollection: false
|
||||
disableAutoscaler: false
|
||||
# AI Agent ID from OneUptime dashboard (required when enabled)
|
||||
id:
|
||||
# AI Agent Key from OneUptime dashboard (will be stored in secrets if not provided)
|
||||
key:
|
||||
ports:
|
||||
http: 3875
|
||||
resources:
|
||||
nodeSelector: {}
|
||||
podSecurityContext: {}
|
||||
containerSecurityContext: {}
|
||||
|
||||
serverMonitorIngest:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
|
|
|
|||
|
|
@ -299,6 +299,14 @@ LLM_SERVER_HUGGINGFACE_TOKEN=
|
|||
LLM_SERVER_HUGGINGFACE_MODEL_NAME=
|
||||
|
||||
|
||||
# AI Agent Configuration
|
||||
# Only set these if you want to run a self-hosted AI Agent
|
||||
# You can get the AI_AGENT_ID and AI_AGENT_KEY from the OneUptime Dashboard -> Project Settings -> AI Agents
|
||||
AI_AGENT_ID=
|
||||
AI_AGENT_KEY=
|
||||
AI_AGENT_ONEUPTIME_URL=http://localhost
|
||||
AI_AGENT_PORT=3876
|
||||
|
||||
# By default telemetry is disabled for all services in docker compose. If you want to enable telemetry for a service, then set the env var to false.
|
||||
DISABLE_TELEMETRY_FOR_ACCOUNTS=true
|
||||
DISABLE_TELEMETRY_FOR_APP=true
|
||||
|
|
@ -309,7 +317,7 @@ DISABLE_TELEMETRY_FOR_INCOMING_REQUEST_INGEST=true
|
|||
DISABLE_TELEMETRY_FOR_TEST_SERVER=true
|
||||
DISABLE_TELEMETRY_FOR_STATUS_PAGE=true
|
||||
DISABLE_TELEMETRY_FOR_DASHBOARD=true
|
||||
DISABLE_TELEMETRY_FOR_PROBE=true
|
||||
DISABLE_TELEMETRY_FOR_PROBE=true
|
||||
DISABLE_TELEMETRY_FOR_ADMIN_DASHBOARD=true
|
||||
DISABLE_TELEMETRY_FOR_OTEL_COLLECTOR=true
|
||||
DISABLE_TELEMETRY_FOR_ISOLATED_VM=true
|
||||
|
|
@ -317,6 +325,7 @@ DISABLE_TELEMETRY_FOR_INGRESS=true
|
|||
DISABLE_TELEMETRY_FOR_WORKER=true
|
||||
DISABLE_TELEMETRY_FOR_SERVER_MONITOR_INGEST=true
|
||||
DISABLE_TELEMETRY_FOR_MCP=true
|
||||
DISABLE_TELEMETRY_FOR_AI_AGENT=true
|
||||
|
||||
|
||||
# OPENTELEMETRY_COLLECTOR env vars
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ services:
|
|||
options:
|
||||
max-size: "1000m"
|
||||
|
||||
probe-2:
|
||||
probe-2:
|
||||
restart: always
|
||||
network_mode: host
|
||||
environment:
|
||||
|
|
@ -402,7 +402,20 @@ services:
|
|||
driver: "local"
|
||||
options:
|
||||
max-size: "1000m"
|
||||
|
||||
|
||||
ai-agent:
|
||||
restart: always
|
||||
network_mode: host
|
||||
environment:
|
||||
AI_AGENT_ID: ${AI_AGENT_ID}
|
||||
AI_AGENT_KEY: ${AI_AGENT_KEY}
|
||||
ONEUPTIME_URL: ${AI_AGENT_ONEUPTIME_URL}
|
||||
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_AI_AGENT}
|
||||
PORT: ${AI_AGENT_PORT}
|
||||
logging:
|
||||
driver: "local"
|
||||
options:
|
||||
max-size: "1000m"
|
||||
|
||||
otel-collector:
|
||||
networks:
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ services:
|
|||
context: .
|
||||
dockerfile: ./Probe/Dockerfile
|
||||
|
||||
probe-2:
|
||||
probe-2:
|
||||
volumes:
|
||||
- ./Probe:/usr/src/app:cached
|
||||
# Use node modules of the container and not host system.
|
||||
|
|
@ -292,7 +292,7 @@ services:
|
|||
|
||||
- /usr/src/Common/node_modules/
|
||||
|
||||
|
||||
|
||||
extends:
|
||||
file: ./docker-compose.base.yml
|
||||
service: probe-2
|
||||
|
|
@ -301,6 +301,25 @@ services:
|
|||
context: .
|
||||
dockerfile: ./Probe/Dockerfile
|
||||
|
||||
ai-agent:
|
||||
volumes:
|
||||
- ./AIAgent:/usr/src/app:cached
|
||||
# Use node modules of the container and not host system.
|
||||
# https://stackoverflow.com/questions/29181032/add-a-volume-to-docker-but-exclude-a-sub-folder
|
||||
- /usr/src/app/node_modules/
|
||||
- ./Common:/usr/src/Common:cached
|
||||
|
||||
- /usr/src/Common/node_modules/
|
||||
|
||||
|
||||
extends:
|
||||
file: ./docker-compose.base.yml
|
||||
service: ai-agent
|
||||
build:
|
||||
network: host
|
||||
context: .
|
||||
dockerfile: ./AIAgent/Dockerfile
|
||||
|
||||
isolated-vm:
|
||||
volumes:
|
||||
- ./IsolatedVM:/usr/src/app:cached
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue