async def agent_async(self, agent_configuration: AgentConfiguration, task: Task, llm_model: str = None):
        """
        Asynchronous version of the agent method.
        """
        original_task = task
        original_task.start_time = time.time()
        
        if llm_model is None:
            llm_model = agent_configuration.model

        copy_agent_configuration = copy.deepcopy(agent_configuration)
        copy_agent_configuration_json = copy_agent_configuration.model_dump_json(include={"job_title", "company_url", "company_objective", "name", "contact"})
        
        the_characterization_cache_key = f"characterization_{hashlib.sha256(copy_agent_configuration_json.encode()).hexdigest()}"

        if agent_configuration.system_prompt:
            the_characterization = agent_configuration.system_prompt
        elif llm_model and llm_model.startswith("ollama"):
            the_characterization = agent_configuration.system_prompt if agent_configuration.system_prompt else agent_configuration.name
        elif agent_configuration.caching:
            the_characterization = get_from_cache_with_expiry(the_characterization_cache_key)
            if the_characterization is None:
                the_characterization = await self.create_characterization_async(agent_configuration, llm_model, task.price_id)
                save_to_cache_with_expiry(the_characterization, the_characterization_cache_key, agent_configuration.cache_expiry)
        else:
            the_characterization = await self.create_characterization_async(agent_configuration, llm_model, task.price_id)

        knowledge_base = None
        if agent_configuration.knowledge_base:
            knowledge_base = agent_configuration.knowledge_base
        
        the_task = task
        is_it_sub_task = False
        shared_context = []

        if agent_configuration.sub_task:
            # Create a new agent configuration for sub-tasks with memory enabled and same retry setting
            sub_task_agent_config = copy.deepcopy(agent_configuration)
            sub_task_agent_config.agent_id_ = str(uuid.uuid4())  # Generate new agent ID for sub-tasks
            sub_task_agent_config.memory = True  # Enable memory for sub-tasks
            
            # Use the async version of multiple
            sub_tasks = await self.multiple_async(sub_task_agent_config, task, llm_model)
            is_it_sub_task = True
            the_task = sub_tasks

        if not isinstance(the_task, list):
            the_task = [the_task]

        for each in the_task:
            if not isinstance(each.context, list):
                each.context = [each.context]

        last_task = []
        for each in the_task:
            if isinstance(each.context, list):
                last_task.append(each)
        the_task = last_task

        for each in the_task:
            each.context.append(the_characterization)

        # Add knowledge base to the context for each task
        if knowledge_base:
            if isinstance(the_task, list):
                for each in the_task:
                    if each.context:
                        each.context.append(knowledge_base)
                    else:
                        each.context = [knowledge_base]

        if task.context:
            for each in the_task:
                each.context += task.context

        # Create copies of agent_configuration for all tasks except the last one
        task_specific_configs = []
        for i in range(len(the_task)):
            if i < len(the_task) - 1:
                # Create a copy and set reliability_layer to None for all except last task
                task_config = copy.deepcopy(sub_task_agent_config if agent_configuration.sub_task else agent_configuration)
                task_config.reliability_layer = None
                task_specific_configs.append(task_config)
            else:
                # Use original config for the last task
                task_specific_configs.append(agent_configuration)

        if agent_configuration.tools:
            if isinstance(the_task, list):
                for each in the_task:
                    each.tools = agent_configuration.tools

        results = []    
        if isinstance(the_task, list):
            for i, each in enumerate(the_task):
                if is_it_sub_task:
                    if shared_context:
                        each.context += shared_context

                # Use the async version of agent_
                result = await self.agent_async_(task_specific_configs[i], each, llm_model=llm_model)
                results += result

                # Collect tool calls from each subtask
                for tool_call in each.tool_calls:
                    original_task.add_tool_call(tool_call)

                if is_it_sub_task:
                    shared_context.append(OtherTask(task=each.description, result=each.response))

        original_task._response = the_task[-1].response
        
        total_time = 0
        for each in results:
            total_time += each["time"]

        total_input_tokens = 0
        total_output_tokens = 0
        for each in results:
            if "usage" in each and each["usage"] is not None:
                total_input_tokens += each["usage"].get("input_tokens", 0)
                total_output_tokens += each["usage"].get("output_tokens", 0)

        the_llm_model = llm_model
        if the_llm_model is None:
            the_llm_model = self.default_llm_model

        agent_total_cost(total_input_tokens, total_output_tokens, total_time, the_llm_model)

        if not original_task.not_main_task:
            print_price_id_summary(original_task.price_id, original_task)

        original_task.end_time = time.time()
        return original_task.response

        Asynchronous version of the agent method.
        
src/upsonic/client/level_two/agent.py