Tasks lambda development

Here is tasks lambda development description.

More information about lambda types and differences of tasks and others is available at lambda types description.

Tasks lambda requirements

Tasks lambda has several requirements in addition to basic requirements:

  • Luna Faces available by credentials from Luna-Configurator

  • Luna Events available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Events usage

  • Luna Python Matcher available by credentials from Luna-Configurator

  • Luna Image Store available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Image-Store usage

  • Luna Remote SDK available by credentials from Luna-Configurator

  • Luna Handlers available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Handlers usage

  • Luna Tasks for interaction with lambda task (for task execution, tasks view and so on)

Usage and purpose of these services are described here.

Tasks lambda configuration

The tasks lambda required several settings from luna-configurator, which can be separated to several groups:

  • LUNA_LAMBDA_UNIT_LOGGER - lambda logger settings

  • luna-services addresses and timeouts settings (for example, LUNA_FACES_ADDRESS and LUNA_FACES_TIMEOUTS will be used by lambda to make requests to luna-faces service)

  • ADDITIONAL_SERVICES_USAGE setting will be used to determine which luna-services can be used by the lambda (the lambda will not check connection to disabled services and will raise an error if user tries to make request to such service). The luna_tasks of ADDITIONAL_SERVICES_USAGE must be enabled to use Luna-Tasks service and lambda task respectively

Tasks lambda request usage

The lambda tasks can only proceed service requests (such as configs (/config) and docs(/docs/spec)).

Task processing pipeline

Task processing contains of next stages:

  • preparation (if one of stages fails, the task will not be created)

    • user sends request to task router

    • task router validates task content from user request and sends it to task worker

    • task worker separates task content into one or more parts depending on internal logic and sends it to task router

      for content separation process and recommendations, see task content separation

    • user receive task id in reply from task router

    • task router execute task processing

  • task processing (if one of stages fails, the specific subtask will be marked as failed)

    • task router initialize processing of all subtasks

    • task worker processes each part of task content is treated as a separate subtask and saves result to Luna-Image-Store (if Luna-Image-Store is enabled)

      for subtasks processing description see subtasks processing

    • when the last subtask is done task router will execute task results collection

  • task results collection

    • task worker collects results of all subtasks, merges them and saves the task result

    • task router marks task as done and provides the user access to the task result

Task content separation process

Task processing assumes long running process whose processing time can take minutes or even hours.

For ease of presentation of the process and parallelization of task execution, it is recommended to divide each task into subtasks. User can make get task request to see how many parts of the task are finished and how many parts the task contains (count_task_parts_done and count_task_parts_done fields of reply respectively).

In case more than one task worker is available each subtask can be processed by different workers, whose will lead to faster task completion. (At this moment lambda task does not provide such an option, but it may be implemented in the future).

It is highly not recommended to use requests with pagination to load/match a large number of persons/events, as such queries get worse as the page number gets larger and this kind of sort is not stable (it is possible to receive the same objects on different pages). See the examples described here and below on how to correctly extract a large amount of data (preferably to use filtration using ids (face_id__gte/face_id__lte/etc) because this kind of sort is stable and received objects will be sorted by their ids).

Task content separation can be made in several ways, here are some examples of possible strategies:

  • separate data by list elements

    The task content includes list of N (3 for example) list ids

    task content from user request
     {"content": {"lambda_id": <task-lambda-id>, "lists": ["list_id_1", "list_id_1", "list_id_1"]}}
    

    Lambda task will split list by its elements

    lambda task content separation
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def splitTasksContent(self, content: dict) -> list[dict]:
             """Split task content to subtask contents"""
             return [{"lambda_id": content["content"]["lambda_id"], "list_id": row} for row in content["content"]["lists"]]
    

    Each subtask will process its own list (see comment for subtasks content examples)

    subtasks contents received by executeSubtask function
     class LambdaTask(BaseLambdaTask):
    
         async def processList(listId: str):
             ...
    
         async def executeSubtask(self, subtaskContent: dict) -> dict | list:
             """
             Execute current subtask processing
             Content of `subtaskContent` for subtask #1: {'list_id': 'list_id_1', 'lambda_id': <task-lambda-id>}
             Content of `subtaskContent` for subtask #2: {'list_id': 'list_id_2', 'lambda_id': <task-lambda-id>}
             Content of `subtaskContent` for subtask #3: {'list_id': 'list_id_3', 'lambda_id': <task-lambda-id>}
             """
             return await self.processList(subtaskContent["list_id"]
    
  • separate data by range

    The task content includes range of elements. For example, it must process all faces with id from “00000000-0000-4000-8000-000000000000” to “90000000-0000-4000-8000-000000000000”. Also, the create_time__gte filter can be included in content just like other filters.

    task content from user request
     {"content": {"lambda_id": <task-lambda-id>, "face_id__gte": "00000000-0000-4000-8000-000000000000",
     "face_id__lt": "90000000-0000-4000-8000-000000000000", "create_time__gte": "2020-10-10T10:10:10.123456+03:00"}}
    

    Lambda task will split specified range into several

    lambda task content separation
     class LambdaTask(BaseLambdaTask):
         ...
    
         def splitUUIDs(count: int, gteId: str | None = None, ltId: str | None = None):
             if count <= 1:
                 if gteId is None:
                     gteId = "00000000-0000-0000-0000-000000000000"
                 return [{"face_id__lt": ltId, "face_id__gte": gteId}]
    
             maxUUIDIntLength = 1 << 128
             if ltId is gteId is None:
                 rangeLength = maxUUIDIntLength
                 startValue = 0
             elif ltId is not None and gteId is not None:
                 rangeLength = UUID(ltId).int - UUID(gteId).int
                 startValue = UUID(gteId).int
             elif ltId is not None:
                 rangeLength = UUID(ltId).int
                 startValue = 0
             else:  # gteId is not None
                 rangeLength = maxUUIDIntLength - UUID(gteId).int
                 startValue = UUID(gteId).int
    
             stepValue = rangeLength // count
             pages = [{"face_id__lt": ltId, "face_id__gte": gteId}]
             for pageIndex in range(count - 1):
                 currentUuid = str(UUID(int=startValue + stepValue * (pageIndex + 1)))
                 pages[-1]["face_id__lt"] = currentUuid
                 pages.append({"face_id__gte": currentUuid, "face_id__lt": ltId})
             return pages
    
         async def splitTasksContent(self, content: dict) -> list[dict]:
             """Split task content to subtask contents"""
             lambdaId = content["content"]["lambda_id"]
             faceIdGte = content["face_id__gte"]
             faceIdLt = content["face_id__lt"]
             separated = splitUUIDs(count=5, gteId=faceIdGte, ltId=faceIdLt)
             return [{"lambda_id": lambdaId, "face_id__gte": row["face_id__gte"], "face_id__lt": row["face_id__lt"], "create_time__gte": "2020-10-10T10:10:10.123456+03:00"} for row in separated]
    

    Each subtask will process its own range of face ids (in the presented example range is separated into 5 parts)

    subtasks contents received by executeSubtask function
     class LambdaTask(BaseLambdaTask):
    
         async def processList(listId: str):
             ...
    
         async def executeSubtask(self, subtaskContent: dict) -> dict | list:
             """
             Execute current subtask processing
             Content of `subtaskContent` for subtask #1: {'face_id__gte': 00000000-0000-4000-8000-000000000000, 'face_id__lt': 1ccccccc-cccd-0ccd-4ccc-cccccccccccc, "create_time__gte": "2020-10-10T10:10:10.123456+03:00", 'lambda_id': <task-lambda-id>}
             Content of `subtaskContent` for subtask #2: {'face_id__gte': 1ccccccc-cccd-0ccd-4ccc-cccccccccccc, 'face_id__lt': 39999999-9999-d99a-1999-999999999998, "create_time__gte": "2020-10-10T10:10:10.123456+03:00", 'lambda_id': <task-lambda-id>}
             Content of `subtaskContent` for subtask #3: {'face_id__gte': 39999999-9999-d99a-1999-999999999998, 'face_id__lt': 56666666-6666-a666-e666-666666666664, "create_time__gte": "2020-10-10T10:10:10.123456+03:00", 'lambda_id': <task-lambda-id>}
             Content of `subtaskContent` for subtask #4: {'face_id__gte': 56666666-6666-a666-e666-666666666664, 'face_id__lt': 73333333-3333-7333-b333-333333333330, "create_time__gte": "2020-10-10T10:10:10.123456+03:00", 'lambda_id': <task-lambda-id>}
             Content of `subtaskContent` for subtask #5: {'face_id__gte': 73333333-3333-7333-b333-333333333330, 'face_id__lt': 90000000-0000-4000-8000-000000000000, "create_time__gte": "2020-10-10T10:10:10.123456+03:00", 'lambda_id': <task-lambda-id>}
             """
             faces = await self.clients.faces.getFaces(
               faceIdGte=subtaskContent["face_id__gte"],
               faceIdLt=subtaskContent["face_id__lt"],
               createTimeGte=subtaskContent["create_time__gte"],
             ).json["faces"]
             ...
    

However, if it is not possible to separate task content into several parts, in those cases the task will contain one subtask.

lambda task content separation to one subtask content
 class LambdaTask(BaseLambdaTask):
     ...

     async def splitTasksContent(self, content: dict) -> list[dict]:
         """Split task content to subtask contents"""
         return [content]

Subtask processing

The subtask processing purpose is to give away result of content processing by realizing executeSubtask function.

All features described in general development description is actual for lambda tasks with one exception: the luna services clients are available as BaseLambdaTask property.

For example, the standalone lambda clients usage is as follows:

lambda_main.py
from luna_lambda_tools import StandaloneLambdaRequest


async def main(request: StandaloneLambdaRequest) -> dict:
    versions = {
        "faces_address": request.clients.faces.getAddress(),
        "events_address": request.clients.events.getAddress(),
    }
    return versions

The lambda task luna services client’s usage as follows:

class LambdaTask(BaseLambdaTask):

    async def executeSubtask(self, subtaskContent: dict) -> dict | list:
        """Execute current subtask processing"""
        versions = {
            "faces_address": self.clients.faces.getAddress(),
            "events_address": self.clients.events.getAddress(),
        }
        ...

The result will be saved to Luna-Image-Store as subtask result.

Task error processing

During task preparation may cause many different errors with different consequences.

In general, any error that occurs during task content separation will lead to failure of task creation and if any error occurred during subtask processing, it will lead to subtask failure.

  • error processing basics

    The are two kinds of errors: expected and unexpected.

    Unexpected errors compared to expected ones will cause a trace in the logs.

    unexpected error example
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def splitTasksContent(self, content: dict) -> list[dict]:
             """Split task content to subtask contents"""
             data = {"abc": 123}
             data2 = data["qwe"]
    

    To make error expected it must be inherited from UserTaskLambdaException

    expected error example
     from luna_lambda_tools.public.exceptions import UserTaskLambdaException
    
     class NewException(UserTaskLambdaException):
         errorText = "exception description"
    
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def splitTasksContent(self, content: dict) -> list[dict]:
             """Split task content to subtask contents"""
             data = {"abc": 123}
             try:
                 data2 = data["qwe"]
             except KeyError:
                 raise NewException
             ...
    
  • Task separation errors

    example: wrong content

    task content from user request
     {"content": {"lambda_id": <task-lambda-id>, "lists": ["list_id_1", "list_id_1", "list_id_1"]}}
    
    lambda task content separation function
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def splitTasksContent(self, content: dict) -> list[dict]:
             """Split task content to subtask contents"""
             return [content["faces"]]
    

    consequence: user will receive the following type of answer:

    {
        "error_code": 42004,
        "desc": "Lambda exception",
        "detail": "Failed to split task content: <class 'KeyError'>: 'faces'",
        "link": "https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-42004"
    }
    

    possible way to improve usability - catch possible errors:

    lambda task content separation function
      class UserException(Exception):
    
          def __str__(self):
              return "exception representation"
    
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def splitTasksContent(self, content: dict) -> list[dict]:
             """Split task content to subtask contents"""
              try:
                  return [content["faces"]]
              except KeyError:
                  raise UserException
    

    consequence: user will receive the following type of answer:

    {
        "error_code": 42004,
        "desc": "Lambda exception",
        "detail": "Failed to split task content: <class 'UserException'>: 'exception representation'",
        "link": "https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-42004"
    }
    
  • subtask processing error

    example: unexpected error

    subtask content
     {"content": {"lambda_id": <task-lambda-id>, "list_id": "list_id_1"}}
    
    lambda subtask processing function
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def executeSubtask(self, subtaskContent: dict) -> dict | list:
             """Execute current subtask processing"""
             subtaskContent["faces"]
             ...
    

    consequence: the subtask will be marked as failed and user will receive the following type of answer in the subtask/task error in the result:

    {
        "error_id": 3,
        "task_id": 7,
        "subtask_id": 7,
        "error_code": 42004,
        "description": "Lambda exception",
        "detail": "<class 'KeyError'>: 'faces'",
        "additional_info": null,
        "error_time": "2023-12-07T17:25:15.588297+03:00",
        "link": "https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-42004"
    }
    

    possible way to improve usability - catch possible errors:

    lambda subtask processing function
      class UserException(Exception):
         ...
    
    
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def executeSubtask(self, subtaskContent: dict) -> dict | list:
             """Execute current subtask processing"""
              try:
                  subtaskContent["faces"]
              except KeyError:
                  raise UserException("expected `faces` field in subtask content")
             ...
    

    consequence: the subtask will be marked as failed and user will receive the following type of answer in the subtask/task error in the result:

    {
        "error_id": 3,
        "task_id": 7,
        "subtask_id": 7,
        "error_code": 42004,
        "description": "Lambda exception",
        "detail": "<class 'UserException'>: expected `faces` field in subtask content",
        "additional_info": null,
        "error_time": "2023-12-07T17:25:15.588297+03:00",
        "link": "https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-42004"
    }
    
  • example: luna3 client exceptions

    lambda subtask processing function (let’s imagine there is no face with id “00000000-0000-4000-a000-000003271373”)
     class LambdaTask(BaseLambdaTask):
         ...
    
         async def executeSubtask(self, subtaskContent: dict) -> dict | list:
             """Execute current subtask processing"""
             faceId = await self.clients.faces.getFace(faceId="00000000-0000-4000-a000-000003271373")
             ...
    

    consequence: the subtask will be marked as failed and user will receive the following type of answer in the subtask/task error in the result:

    {
        "error_id": 4,
        "task_id": 10,
        "subtask_id": 10,
        "error_code": 22002,
        "description": "Object not found",
        "detail": "Face with id '00000000-0000-4000-a000-000003271373' not found",
        "additional_info": null,
        "error_time": "2023-12-07T17:44:29.590114+03:00",
        "link": "https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-22002"
    }
    

Note

If one or more subtasks fail, the final task status will be marked as done, but the task result will contain errors collected from failed subtasks.

If some subtasks failed and some finished properly, the final task status will be also marked as done and the task result will contain errors from failed subtasks and also results from properly finished subtasks.

Tasks lambda examples

  • unlink faces from lists if faces do not similar to the specified face:
    • check each face is similar to the specified one (with matching threshold > 0.7)

    • save unlinked face ids as task result

    • in this case, task content will be separated into several subtasks equal count of received list ids.

    lambda_main.py
    import base64
    from uuid import UUID, uuid4
    
    import dpath
    from luna3.public.common import BinaryImage
    from luna3.public.matcher import Candidates, FaceFilters, SDKDescriptorReference
    from luna_lambda_tools.public.tasks import BaseLambdaTask
    
    
    class LambdaTask(BaseLambdaTask):
        """Lambda task"""
    
        async def splitTasksContent(self, content: dict) -> list[dict]:
            """
            Split task content to sub task contents
            Expected user request
            {
                "lambda_id": <lambda-id>,
                "photo": <image-base64>,
                "lists": [<list_id_1>, <list_id_2>]
            }
            """
            photo = content["photo"]
            lists = content["lists"]
            threshold = content.get("threshold", 0.7)
            return [{"content": {"photo": photo, "list_id": listId, "threshold": threshold}} for listId in lists]
    
        async def executeSubtask(self, subtaskContent: dict) -> dict | list:
            """Execute current sub task processing"""
            listId = subtaskContent["content"]["list_id"]
            binaryImage = BinaryImage(body=base64.b64decode(subtaskContent["content"]["photo"]), filename="raw_image")
            result = await self.clients.sdk.sdk(binaryImage, estimateFaceDescriptor=1)
            if (
                sdkDescriptor := dpath.get(
                    result.json,
                    "images_estimations/0/estimations/0/face/detection/attributes/descriptor/sdk_descriptor",
                    default=None,
                )
            ) is None:
                raise ValueError("failed to extract descriptor from specified image")
    
            threshold = subtaskContent["content"]["threshold"]
            unlinkedFaceIds = []
            faceIdGte = str(UUID(int=0))
            while True:
                result = await self.clients.matcher.matchFaces(
                    candidates=[
                        Candidates(
                            filters=FaceFilters(faceIdGte=faceIdGte, listId=listId),
                            targets=["face_id", "similarity"],
                            limit=100,
                        )
                    ],
                    references=[SDKDescriptorReference(referenceId=str(uuid4()), descriptor=sdkDescriptor)],
                )
                faceIdsToUnlink = [
                    row["face"]["face_id"]
                    for row in dpath.values({"result": result.json}, "result/*/matches/*/result/*")
                    if row["similarity"] < threshold
                ]
                if faceIdsToUnlink:
                    await self.clients.faces.unlinkFacesFromList(faceIds=faceIdsToUnlink, listId=listId)
                    # get next uuid
                    try:
                        faceIdGte = str(UUID(int=UUID(faceIdsToUnlink[-1]).int + 1))
                    except ValueError:
                        # max uuid has reached
                        break
                else:
                    break
                unlinkedFaceIds.extend([{"face_id": faceId} for faceId in faceIdsToUnlink])
            return unlinkedFaceIds
    
    task content example
    {
      "content": {
        "lambda_id": "94e37118-dfcc-4d15-869c-a9de53d20542",
        "lists": [
          "edffdea6-ea9c-4e33-b911-55097ccfbb50",
          "ee7e52c0-e5c0-4e9b-9d2e-5bd5558464dd"
        ],
        "photo": "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"
      }
    }
    
  • remove duplicates from list:
    • check each face has the similar one (similar face is face with threshold > 0.9 for this example) and remove all similar faces if exist

    • save sample ids of removed faces as task result

    • in this case, task content will be separated into several subtasks equal count of received list ids.

    lambda_main.py
    import uuid
    
    import dpath
    from luna3.public.matcher import Candidates, FaceFilters, Reference
    from luna_lambda_tools.public.tasks import BaseLambdaTask
    
    
    class LambdaTask(BaseLambdaTask):
        """Lambda task"""
    
        async def splitTasksContent(self, content: dict) -> list[dict]:
            """
            Split task content to sub task contents
            Expected user request
            {
                "lambda_id": <lambda-id>,
                "lists": [<list_id_1>, <list_id_2>],
                "threshold": 0.95
            }
            """
            lists = content["lists"]
            threshold = content.get("threshold", 0.9)
            return [{"content": {"list_id": listId, "threshold": threshold}} for listId in lists]
    
        async def removeFaceDuplicates(self, faceId: str, listId: str, threshold: float):
            """Remove duplicates of face"""
            sampleIds = []
            limit = 100
            while True:
                result = await self.clients.matcher.matchFaces(
                    candidates=[
                        Candidates(filters=FaceFilters(listId=listId), targets=["face_id", "similarity"], limit=limit)
                    ],
                    references=[Reference(referenceType="face", referenceId=faceId)],
                )
    
                faceIds = [
                    row["face"]["face_id"]
                    for row in dpath.values({"result": result.json}, "result/*/matches/*/result/*")
                    if row["similarity"] > threshold and faceId != row["face"]["face_id"]
                ]
                if not faceIds:
                    break
                for faceId in faceIds:
                    samples = (await self.clients.faces.getFaceAttributeSamples(faceId=faceId)).json["samples"]
                    sampleIds.extend(samples)
                await self.clients.faces.deleteFaces(faceIds=faceIds)
                if len(faceId) < limit:
                    break
            return sampleIds
    
        async def executeSubtask(self, subtaskContent: dict) -> dict | list:
            """Execute current sub task processing"""
            listId = subtaskContent["content"]["list_id"]
            threshold = subtaskContent["content"]["threshold"]
            resultMap = {}
            faceIdGte = str(uuid.UUID(int=0))
            while True:
                faces = (
                    await self.clients.faces.getFaces(listId=listId, pageSize=1, targets=["face_id"], faceIdGte=faceIdGte)
                ).json["faces"]
                if not faces:
                    break
                # get next uuid
                try:
                    faceIdGte = str(uuid.UUID(int=uuid.UUID(faces[0]["face_id"]).int + 1))
                except ValueError:
                    # max uuid has reached
                    resultMap[faces[0]["face_id"]] = await self.removeFaceDuplicates(
                        faceId=faces[0]["face_id"], listId=listId, threshold=threshold
                    )
                    break
    
                resultMap[faces[0]["face_id"]] = await self.removeFaceDuplicates(
                    faceId=faces[0]["face_id"], listId=listId, threshold=threshold
                )
            return resultMap
    
    task content example
    {
      "content": {
        "lambda_id": "94e37118-dfcc-4d15-869c-a9de53d20542",
        "lists": [
          "edffdea6-ea9c-4e33-b911-55097ccfbb50",
          "ee7e52c0-e5c0-4e9b-9d2e-5bd5558464dd"
        ]
      }
    }
    
  • recursively find events similar to the specified photo:
    • get top N (can be modified using limit) matches with specified photo

    • get top N matches for each received event M (can be modified using recursion_level parameter) times

    • in this case, task content will not be separated (only one subtask will be created anyway)

    lambda_main.py
    import base64
    from uuid import uuid4
    
    import dpath
    from luna3.public.common import BinaryImage
    from luna3.public.matcher import Candidates, EventFilters, Reference, SDKDescriptorReference
    from luna_lambda_tools.public.tasks import BaseLambdaTask
    
    DEFAULT_LIMIT = 10
    TARGETS = ["event_id", "similarity", "create_time"]
    
    
    class LambdaTask(BaseLambdaTask):
        """Lambda task"""
    
        async def splitTasksContent(self, content: dict) -> list[dict]:
            """
            Split task content to sub task contents
            """
            return [content]
    
        async def getSimilar(
            self, eventId: str, recursionLevel: int, limit: int, threshold: float, eventFilters: EventFilters
        ):
            """Get similar events"""
            matchReply = await self.clients.matcher.matchFaces(
                candidates=[Candidates(filters=eventFilters, targets=TARGETS, limit=limit)],
                references=[Reference(referenceId=eventId, referenceType="event")],
            )
            events = [
                {
                    "event_id": row["event"]["event_id"],
                    "create_time": row["event"]["create_time"],
                    "similarity": row["similarity"],
                }
                for row in dpath.values({"result": matchReply.json}, "result/*/matches/*/result/*")
                if row["similarity"] > threshold
            ]
            events.sort(key=lambda x: x["create_time"])
    
            if recursionLevel == 1:
                return events
            result = []
            for event in events:
                similarEvents = await self.getSimilar(
                    eventId=event["event_id"],
                    recursionLevel=recursionLevel - 1,
                    limit=limit,
                    threshold=threshold,
                    eventFilters=eventFilters,
                )
                result.append(
                    {
                        "event_id": event["event_id"],
                        "similarity": event["similarity"],
                        "create_time": event["create_time"],
                        "similar_events": similarEvents,
                    }
                )
            return result
    
        async def executeSubtask(self, subtaskContent: dict) -> dict | list:
            """Execute current sub task processing"""
            threshold = 0.01  # subtaskContent.get("threshold", 0.7)
            eventFilters = EventFilters(**subtaskContent.get("filters", {}))
            recursionLevel = subtaskContent.get("recursion_level", 3)
            if recursionLevel < 1 or recursionLevel > 10:
                raise ValueError("Expected recursion level greater 1 and lesser than 10")
            limit = subtaskContent.get("limit", DEFAULT_LIMIT)
            if limit > 100:
                raise ValueError(f"Max event limit is {DEFAULT_LIMIT}")
            binaryImage = BinaryImage(body=base64.b64decode(subtaskContent["photo"]), filename="raw_image")
    
            result = await self.clients.sdk.sdk(binaryImage, estimateFaceDescriptor=1)
            if (
                sdkDescriptor := dpath.get(
                    result.json,
                    "images_estimations/0/estimations/0/face/detection/attributes/descriptor/sdk_descriptor",
                    default=None,
                )
            ) is None:
                raise ValueError("failed to extract descriptor from specified image")
    
            matchReply = await self.clients.matcher.matchFaces(
                candidates=[Candidates(filters=eventFilters, targets=TARGETS, limit=limit)],
                references=[SDKDescriptorReference(referenceId=str(uuid4()), descriptor=sdkDescriptor)],
            )
            events = [
                {
                    "event_id": row["event"]["event_id"],
                    "create_time": row["event"]["create_time"],
                    "similarity": row["similarity"],
                }
                for row in dpath.values({"result": matchReply.json}, "result/*/matches/*/result/*")
                if row["similarity"] > threshold
            ]
            events.sort(key=lambda x: x["create_time"])
    
            result = []
            for event in events:
                similarEvents = await self.getSimilar(
                    eventId=event["event_id"],
                    recursionLevel=recursionLevel,
                    limit=limit,
                    threshold=threshold,
                    eventFilters=eventFilters,
                )
                result.append(
                    {
                        "event_id": event["event_id"],
                        "similarity": event["similarity"],
                        "create_time": event["create_time"],
                        "similar_events": similarEvents,
                    }
                )
    
            return result
    
    task content example
    {
      "content": {
        "lambda_id": "94e37118-dfcc-4d15-869c-a9de53d20542",
        "recursion_level": 3,
        "limit": 10,
        "photo": "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"
      }
    }