Enhance R1 Interface API Specifications for Standard Compliance 63/14963/13
authorJuheeShin <odong3094@khu.ac.kr>
Mon, 22 Sep 2025 10:07:59 +0000 (10:07 +0000)
committerTaewan Kim <t25.kim@samsung.com>
Mon, 22 Sep 2025 10:19:04 +0000 (10:19 +0000)
Update the R1 interface API specifications and implementation to comply
with O-RAN standards and RFC 7807 guidelines. Several deviations from
O-RAN R1 specifications and API best practices were identified and fixed:

- Schema Consistency: Added missing fields (artifactVersion, owner) and
  ensured inputDataType/outputDataType are arrays with targetEnvironment included.
- Error Handling: Standardized error responses (400/404/500) to use
  RFC 7807 ProblemDetails format.
- Naming Consistency: Unified path parameters and schema naming to follow
  camelCase (e.g., id → modelId).

The artifactVersion and targetEnvironment fields are planned to be incorporated in a follow-up patch.

Issue-ID: AIMLFW-256

Change-Id: I01365edf62b25eab4b704c7c9c455802122b2ce2
Signed-off-by: JuheeShin <odong3094@khu.ac.kr>
API_docs/mme.yaml
apis/mmes_apis.go

index 7b6eb24..971fd1d 100644 (file)
@@ -1,8 +1,8 @@
 openapi: 3.0.0
 info:
   title: Model Management API
-  description: API for managing machine learning models (register, retrieve, update, delete, upload, and download).
-  version: 1.1.0
+  description: API for managing machine learning models (register, retrieve, update, delete, upload, and download)
+  version: 1.0.0
 
 servers:
   - url: http://11.0.0.4:32006
@@ -26,16 +26,37 @@ paths:
             schema:
               $ref: '#/components/schemas/ModelRelatedInformation'
       responses:
-        '200':
+        '201':
           description: Model registered successfully and returned object
+          headers:
+            Location:
+              description: 'Contains the URI of the newly created resource'
+              required: true
+              schema:
+                type: string
+                example: "/ai-ml-model-registration/v1/model-registrations/123e4567-e89b-12d3-a456-426614174000"
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ModelRelatedInformation'
         '400':
           description: Invalid request, bad input data
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
+        '409':
+          description: Conflict – model name and version combination already exists
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
         '500':
           description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
 
   /ai-ml-model-discovery/v1/models:
     get:
@@ -47,15 +68,6 @@ paths:
         - name: modelName
           in: query
           description: Model name to search
-          required: false
-          schema:
-            type: string
-        - name: modelVersion
-          in: query
-          description: Model version to search
-          required: false
-          schema:
-            type: string
       responses:
         '200':
           description: A list of models or filtered search results
@@ -67,20 +79,30 @@ paths:
                   $ref: '#/components/schemas/ModelRelatedInformation'
         '400':
           description: Invalid query parameters
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
         '500':
           description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
+
   /ai-ml-model-registration/v1/model-registrations/{modelRegistrationId}:
     get:
       tags:
         - Model Management
-      summary: Get model info by ID
+      summary: Get model info by modelRegistrationId
       operationId: getModelInfoById
       parameters:
-        - name: id
+        - name: modelRegistrationId
           in: path
           required: true
           schema:
             type: string
+            example: "123e4567-e89b-12d3-a456-426614174000"
       responses:
         '200':
           description: Model information
@@ -90,20 +112,29 @@ paths:
                 $ref: '#/components/schemas/ModelRelatedInformation'
         '404':
           description: Model not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
         '500':
           description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
 
     put:
       tags:
         - Model Management
-      summary: Update model info by ID
+      summary: Update model info by modelRegistrationId
       operationId: updateModel
       parameters:
-        - name: id
+        - name: modelRegistrationId
           in: path
           required: true
           schema:
             type: string
+            example: "123e4567-e89b-12d3-a456-426614174000"
       requestBody:
         required: true
         content:
@@ -113,29 +144,58 @@ paths:
       responses:
         '200':
           description: Model updated successfully
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ModelRelatedInformation'
         '400':
           description: Invalid request body
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
+        '404':
+          description: Model not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
         '500':
           description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
 
     delete:
       tags:
         - Model Management
-      summary: Delete a model by ID
+      summary: Delete a model by modelRegistrationId
       operationId: deleteModel
       parameters:
-        - name: id
+        - name: modelRegistrationId
           in: path
           required: true
           schema:
             type: string
+            example: "123e4567-e89b-12d3-a456-426614174000"
       responses:
-        '200':
+        '204':
           description: Model deleted successfully
+        '404':
+          description: Model not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
         '500':
           description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
 
-  /ai-ml-model-registration/v1/uploadModel/{modelName}/{modelVersion}/{artifactVersion}:
+  /ai-ml-model-registration/v1/uploadModel/{modelName}/{modelVersion}:
     post:
       tags:
         - Model Management
@@ -152,11 +212,6 @@ paths:
           required: true
           schema:
             type: string
-        - name: artifactVersion
-          in: path
-          required: true
-          schema:
-            type: string
       requestBody:
         required: true
         content:
@@ -167,8 +222,10 @@ paths:
                 file:
                   type: string
                   format: binary
+              required:
+                - file
       responses:
-        '200':
+        '201':
           description: Model uploaded successfully
           content:
             application/json:
@@ -177,14 +234,24 @@ paths:
                 properties:
                   code:
                     type: integer
-                    example: 200
+                    example: 201
                   message:
                     type: string
                     example: "Model uploaded successfully."
+        '400':
+          description: Invalid request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
         '500':
           description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
 
-  /ai-ml-model-registration/v1/downloadModel/{modelName}/{modelVersion}/{artifactVersion}/model.zip:
+  /ai-ml-model-registration/v1/downloadModel/{modelName}/{modelVersion}/model.zip:
     get:
       tags:
         - Model Management
@@ -201,11 +268,6 @@ paths:
           required: true
           schema:
             type: string
-        - name: artifactVersion
-          in: path
-          required: true
-          schema:
-            type: string
       responses:
         '200':
           description: Model downloaded successfully
@@ -214,27 +276,82 @@ paths:
               schema:
                 type: string
                 format: binary
+        '404':
+          description: Model not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
         '500':
           description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ProblemDetails'
 
 components:
   schemas:
+    ProblemDetails:
+      type: object
+      properties:
+        type:
+          type: string
+          format: uri
+          description: A URI reference that identifies the problem type
+          example: "https://o-ran.org/problems/invalid-model-format"
+          default: "about:blank"
+        title:
+          type: string
+          description: A short, human-readable summary of the problem
+          example: "Invalid Model Format"
+        detail:
+          type: string
+          description: A human-readable explanation specific to this occurrence of the problem
+          example: "The provided model file format is not supported. Expected formats: .pkl, .joblib, .h5"
+        status:
+          type: integer
+          description: The HTTP status code
+          example: 400
+          minimum: 100
+          maximum: 599
+        instance:
+          type: string
+          format: uri
+          description: A URI reference that identifies the specific occurrence of the problem
+          example: "/ai-ml-model-registration/v1/model-registrations/invalid-model-123"
+      required:
+        - type
+        - title
+
     Metadata:
       type: object
       properties:
         author:
           type: string
+          description: "Author of the AI/ML model"
           example: "John Doe"
+        owner:
+          type: string
+          description: "Owner information for model usage regulation"
+          example: "AI Research Team"
+      required:
+        - author
+        - owner
 
-    ModelID:
+    ModelId:
       type: object
       properties:
         modelName:
           type: string
+          description: "Name of the model"
           example: "example-model"
         modelVersion:
           type: string
+          description: "Version of the model"
           example: "v1.0"
+      required:
+        - modelName
+        - modelVersion
 
     ModelInformation:
       type: object
@@ -242,24 +359,46 @@ components:
         metadata:
           $ref: '#/components/schemas/Metadata'
         inputDataType:
-          type: string
-          description: 'Input data type for the model'
+          type: array
+          items:
+            type: string
+          description: 'Supported input data types for the model'
+          example: ["sensor_data", "network_metrics"]
+          minItems: 1
         outputDataType:
-          type: string
-          description: 'Output data type for the model'
-
+          type: array
+          items:
+            type: string
+          description: 'Expected output data types from the model'
+          example: ["prediction_result", "anomaly_score"]
+          minItems: 1
+      required:
+        - metadata
+        - inputDataType
+        - outputDataType
 
     ModelRelatedInformation:
       type: object
       properties:
-        id:
+        modelRegistrationId:
           type: string
+          description: "Unique identifier for the model registration"
           example: "123e4567-e89b-12d3-a456-426614174000"
         modelId:
-          $ref: '#/components/schemas/ModelID'
+          $ref: '#/components/schemas/ModelId'
         description:
           type: string
-          example: "This is an example model description."
+          description: "Description of the AI/ML model"
+          example: "This is an example model for network anomaly detection."
         modelInformation:
           $ref: '#/components/schemas/ModelInformation'
-
+        modelLocation:
+          type: string
+          format: uri
+          description: "Location where the model is stored in the runtime catalogue"
+          example: "https://model-registry.example.com/models/example-model/v1.0"
+      required:
+        - modelRegistrationId
+        - modelId
+        - description
+        - modelInformation
\ No newline at end of file
index 96f9c0d..af9bd21 100644 (file)
@@ -80,6 +80,7 @@ func (m *MmeApiHandler) RegisterModel(cont *gin.Context) {
                })
                return
        }
+
        // by default when a model is registered its artifact version is set to 0.0.0
        modelInfo.ModelId.ArtifactVersion = "0.0.0"
 
@@ -90,18 +91,17 @@ func (m *MmeApiHandler) RegisterModel(cont *gin.Context) {
                        if pqErr.Code == pgerrcode.UniqueViolation {
                                cont.JSON(http.StatusConflict, models.ProblemDetail{
                                        Status: http.StatusConflict,
-                                       Title:  "model name and version combination already present",
-                                       Detail: "The request json is not correct as",
-                               })
-                               return
-                       } else {
-                               cont.JSON(http.StatusInternalServerError, models.ProblemDetail{
-                                       Status: http.StatusInternalServerError,
-                                       Title:  "Bad Request",
-                                       Detail: "The request json is not correct as",
+                                       Title:  "Conflict",
+                                       Detail: "model name and version combination already present",
                                })
                                return
                        }
+                       cont.JSON(http.StatusInternalServerError, models.ProblemDetail{
+                               Status: http.StatusInternalServerError,
+                               Title:  "Internal Server Error",
+                               Detail: fmt.Sprintf("Database error: %s", err.Error()),
+                       })
+                       return
                }
        }
 
@@ -110,17 +110,15 @@ func (m *MmeApiHandler) RegisterModel(cont *gin.Context) {
        cont.JSON(http.StatusCreated, gin.H{
                "modelInfo": modelInfo,
        })
-
 }
 
 /*
 This API retrieves model info list managed in modelmgmtservice
 */
 func (m *MmeApiHandler) GetModelInfo(cont *gin.Context) {
-
        logging.INFO("Get model info ")
        queryParams := cont.Request.URL.Query()
-       //to check only modelName and modelVersion can be passed.
+       // to check only modelName and modelVersion can be passed.
        allowedParams := map[string]bool{
                MODELNAME:    true,
                MODELVERSION: true,
@@ -142,8 +140,7 @@ func (m *MmeApiHandler) GetModelInfo(cont *gin.Context) {
        modelVersion := cont.Query(MODELVERSION)
 
        if modelName == "" && modelVersion == "" {
-               //return all modelinfo stored
-
+               // return all modelinfo stored
                models, err := m.iDB.GetAll()
                if err != nil {
                        logging.ERROR("error:", err)
@@ -169,10 +166,8 @@ func (m *MmeApiHandler) GetModelInfo(cont *gin.Context) {
                                })
                                return
                        }
-
                        cont.JSON(http.StatusOK, modelInfos)
                        return
-
                } else {
                        // get all modelInfo by model name and version
                        modelInfo, err := m.iDB.GetModelInfoByNameAndVer(modelName, modelVersion)
@@ -190,13 +185,12 @@ func (m *MmeApiHandler) GetModelInfo(cont *gin.Context) {
                                statusCode := http.StatusNotFound
                                logging.ERROR("Record not found, send status code: ", statusCode)
                                cont.JSON(statusCode, models.ProblemDetail{
-                                       Status: http.StatusInternalServerError,
-                                       Title:  "Internal Server Error",
+                                       Status: http.StatusNotFound,
+                                       Title:  "Not Found",
                                        Detail: fmt.Sprintf("Record not found with modelName: %s and modelVersion: %s", modelName, modelVersion),
                                })
                                return
                        }
-
                        response := []models.ModelRelatedInformation{*modelInfo}
                        cont.JSON(http.StatusOK, response)
                        return
@@ -265,7 +259,7 @@ func (m *MmeApiHandler) UploadModel(cont *gin.Context) {
 
        modelKey := fmt.Sprintf("%s_%s_%s", modelName, modelVersion, artifactVersion)
        exportBucket := strings.ToLower(modelName)
-       //TODO convert multipart.FileHeader to []byted
+       //TODO convert multipart.FileHeader to []byte
        fileHeader, _ := cont.FormFile("file")
        //TODO : Accept only .zip file for trained model
        file, _ := fileHeader.Open()
@@ -309,7 +303,7 @@ func (m *MmeApiHandler) DownloadModel(cont *gin.Context) {
                })
                return
        }
-       //Return file in api reponse using byte slice
+       // Return file in api response using byte slice
        cont.Header("Content-Disposition", "attachment;"+fileName)
        cont.Header("Content-Type", "application/zip")
        cont.Data(http.StatusOK, "application/octet", fileByes)
@@ -342,12 +336,13 @@ func (m *MmeApiHandler) UpdateModel(c *gin.Context) {
                })
                return
        }
+
        if existingModelInfo.ModelId.ModelName != modelInfo.ModelId.ModelName || existingModelInfo.ModelId.ModelVersion != modelInfo.ModelId.ModelVersion {
                statusCode := http.StatusBadRequest
                logging.ERROR("Error occurred, send status code: ", statusCode)
                c.JSON(statusCode, gin.H{
                        "code":    statusCode,
-                       "message": fmt.Sprintf("model with id: %s have different modelname and modelversion than provided", id),
+                       "message": fmt.Sprintf("model with id: %s has different modelName and modelVersion than provided", id),
                })
                return
        }
@@ -380,7 +375,6 @@ func (m *MmeApiHandler) DeleteModel(cont *gin.Context) {
 
 func (m *MmeApiHandler) UpdateArtifact(cont *gin.Context) {
        logging.INFO("Update artifact version of model")
-       // var modelInfo *models.ModelRelatedInformation
        modelname := cont.Param("modelname")
        modelversion := cont.Param("modelversion")
        artifactversion := cont.Param("artifactversion")
@@ -395,7 +389,7 @@ func (m *MmeApiHandler) UpdateArtifact(cont *gin.Context) {
        }
        modelInfo.ModelId.ArtifactVersion = artifactversion
        if err := m.iDB.Update(*modelInfo); err != nil {
-               logging.ERROR("error in updated db", "error:", err)
+               logging.ERROR("error in update db", "error:", err)
                return
        }
        logging.INFO("model updated")