feat: Implement logging for AI Agent task execution lifecycle

This commit is contained in:
Nawaz Dhandala
2025-12-28 12:33:46 +00:00
parent c256b03be6
commit 45a1748d50
4 changed files with 239 additions and 1 deletions

View File

@@ -1,5 +1,6 @@
import { ONEUPTIME_URL } from "../Config";
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
import AIAgentTaskLog from "../Utils/AIAgentTaskLog";
import URL from "Common/Types/API/URL";
import API from "Common/Utils/API";
import logger from "Common/Server/Utils/Logger";
@@ -86,6 +87,9 @@ const startTaskProcessingLoop: () => Promise<void> =
continue;
}
/* Send task started log */
await AIAgentTaskLog.sendTaskStartedLog(taskId);
/* Execute the task (empty function for now) */
await executeTask(task);
@@ -102,6 +106,8 @@ const startTaskProcessingLoop: () => Promise<void> =
if (!completedResult.isSuccess()) {
logger.error(`Failed to mark task ${taskId} as Completed`);
} else {
/* Send task completed log */
await AIAgentTaskLog.sendTaskCompletedLog(taskId);
logger.info(`Task completed successfully: ${taskId}`);
}
} catch (error) {
@@ -125,6 +131,9 @@ const startTaskProcessingLoop: () => Promise<void> =
);
}
/* Send task error log */
await AIAgentTaskLog.sendTaskErrorLog(taskId, errorMessage);
logger.error(`Task failed: ${taskId} - ${errorMessage}`);
logger.error(error);
}

View File

@@ -0,0 +1,79 @@
import { ONEUPTIME_URL } from "../Config";
import AIAgentAPIRequest from "./AIAgentAPIRequest";
import URL from "Common/Types/API/URL";
import API from "Common/Utils/API";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import { JSONObject } from "Common/Types/JSON";
import LogSeverity from "Common/Types/Log/LogSeverity";
import logger from "Common/Server/Utils/Logger";
export interface SendLogOptions {
taskId: string;
severity: LogSeverity;
message: string;
}
export default class AIAgentTaskLog {
private static createLogUrl: URL | null = null;
private static getCreateLogUrl(): URL {
if (!this.createLogUrl) {
this.createLogUrl = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
"/api/ai-agent-task-log/create-log",
);
}
return this.createLogUrl;
}
public static async sendLog(options: SendLogOptions): Promise<boolean> {
try {
const result: HTTPResponse<JSONObject> = await API.post({
url: this.getCreateLogUrl(),
data: {
...AIAgentAPIRequest.getDefaultRequestBody(),
taskId: options.taskId,
severity: options.severity,
message: options.message,
},
});
if (!result.isSuccess()) {
logger.error(`Failed to send log for task ${options.taskId}`);
return false;
}
return true;
} catch (error) {
logger.error(`Error sending log for task ${options.taskId}:`);
logger.error(error);
return false;
}
}
public static async sendTaskStartedLog(taskId: string): Promise<boolean> {
return this.sendLog({
taskId,
severity: LogSeverity.Information,
message: "Task execution started",
});
}
public static async sendTaskCompletedLog(taskId: string): Promise<boolean> {
return this.sendLog({
taskId,
severity: LogSeverity.Information,
message: "Task execution completed successfully",
});
}
public static async sendTaskErrorLog(
taskId: string,
errorMessage: string,
): Promise<boolean> {
return this.sendLog({
taskId,
severity: LogSeverity.Error,
message: `Task execution failed: ${errorMessage}`,
});
}
}

View File

@@ -1,8 +1,22 @@
import AIAgentTaskLogService, {
Service as AIAgentTaskLogServiceType,
} from "../Services/AIAgentTaskLogService";
import AIAgentService from "../Services/AIAgentService";
import AIAgentTaskService from "../Services/AIAgentTaskService";
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from "../Utils/Express";
import Response from "../Utils/Response";
import BaseAPI from "./BaseAPI";
import AIAgentTaskLog from "../../Models/DatabaseModels/AIAgentTaskLog";
import AIAgent from "../../Models/DatabaseModels/AIAgent";
import AIAgentTask from "../../Models/DatabaseModels/AIAgentTask";
import BadDataException from "../../Types/Exception/BadDataException";
import { JSONObject } from "../../Types/JSON";
import ObjectID from "../../Types/ObjectID";
import LogSeverity from "../../Types/Log/LogSeverity";
export default class AIAgentTaskLogAPI extends BaseAPI<
AIAgentTaskLog,
@@ -10,5 +24,141 @@ export default class AIAgentTaskLogAPI extends BaseAPI<
> {
public constructor() {
super(AIAgentTaskLog, AIAgentTaskLogService);
/*
* Create a log entry for an AI Agent task
* Validates aiAgentId and aiAgentKey before creating log
*/
this.router.post(
`${new this.entityType().getCrudApiPath()?.toString()}/create-log`,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const data: JSONObject = req.body;
/* Validate AI Agent credentials */
const aiAgent: AIAgent | null = await this.validateAIAgent(data);
if (!aiAgent) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid AI Agent ID or AI Agent Key"),
);
}
/* Validate required fields */
if (!data["taskId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("taskId is required"),
);
}
if (!data["severity"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("severity is required"),
);
}
if (!data["message"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("message is required"),
);
}
const taskId: ObjectID = new ObjectID(data["taskId"] as string);
const severity: LogSeverity = data["severity"] as LogSeverity;
const message: string = data["message"] as string;
/* Validate severity value */
const validSeverities: Array<LogSeverity> = Object.values(LogSeverity);
if (!validSeverities.includes(severity)) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
`Invalid severity. Must be one of: ${validSeverities.join(", ")}`,
),
);
}
/* Check if task exists and get project ID */
const existingTask: AIAgentTask | null =
await AIAgentTaskService.findOneById({
id: taskId,
select: {
_id: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!existingTask) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Task not found"),
);
}
/* Create the log entry */
const logEntry: AIAgentTaskLog = new AIAgentTaskLog();
logEntry.projectId = existingTask.projectId!;
logEntry.aiAgentTaskId = taskId;
logEntry.aiAgentId = aiAgent.id!;
logEntry.severity = severity;
logEntry.message = message;
await AIAgentTaskLogService.create({
data: logEntry,
props: {
isRoot: true,
},
});
return Response.sendJsonObjectResponse(req, res, {
taskId: taskId.toString(),
message: "Log entry created successfully",
});
} catch (err) {
next(err);
}
},
);
}
/*
* Validate AI Agent credentials from request body
* Returns AIAgent if valid, null otherwise
*/
private async validateAIAgent(data: JSONObject): Promise<AIAgent | null> {
if (!data["aiAgentId"] || !data["aiAgentKey"]) {
return null;
}
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(),
key: aiAgentKey,
},
select: {
_id: true,
},
props: {
isRoot: true,
},
});
return aiAgent;
}
}

View File

@@ -21,7 +21,7 @@ const AIAgentTaskViewLayout: FunctionComponent<
title="AI Agent Task"
modelType={AIAgentTask}
modelId={modelId}
modelNameField="_id"
modelNameField="name"
breadcrumbLinks={getAIAgentTasksBreadcrumbs(path)}
sideMenu={<SideMenu modelId={modelId} />}
>