chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,38 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.processes.kernel_process.kernel_process_event import (
KernelProcessEvent,
KernelProcessEventVisibility,
)
from semantic_kernel.utils.feature_stage_decorator import experimental
@experimental
class LocalEvent(KernelBaseModel):
"""An event that is local to a namespace."""
namespace: str | None
inner_event: KernelProcessEvent
@property
def id(self) -> str:
"""The ID of the event."""
return f"{self.namespace}.{self.inner_event.id}" if self.namespace else self.inner_event.id
@property
def data(self) -> Any:
"""The data of the event."""
return self.inner_event.data
@property
def visibility(self) -> KernelProcessEventVisibility:
"""The visibility of the event."""
return self.inner_event.visibility
@classmethod
def from_kernel_process_event(cls, kernel_process_event: KernelProcessEvent, namespace: str) -> "LocalEvent":
"""Create a local event from a kernel process event."""
return cls(namespace=namespace, inner_event=kernel_process_event)
@@ -0,0 +1,52 @@
# Copyright (c) Microsoft. All rights reserved.
from enum import Enum
from typing import TYPE_CHECKING
from semantic_kernel.exceptions.process_exceptions import ProcessInvalidConfigurationException
from semantic_kernel.processes.local_runtime.local_event import KernelProcessEvent
from semantic_kernel.processes.local_runtime.local_kernel_process_context import LocalKernelProcessContext
from semantic_kernel.utils.feature_stage_decorator import experimental
if TYPE_CHECKING:
from semantic_kernel.kernel import Kernel
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
@experimental
async def start(
process: "KernelProcess",
kernel: "Kernel",
initial_event: KernelProcessEvent | str | Enum,
max_supersteps: int | None = None,
**kwargs,
) -> LocalKernelProcessContext:
"""Start the kernel process.
Args:
process: The kernel process to start.
kernel: The kernel instance.
initial_event: The initial event to start the process with.
max_supersteps: The maximum number of supersteps. This is the total number of times process steps will run.
Defaults to None, and thus the process will run its steps 100 times.
**kwargs: Additional keyword arguments.
"""
if process is None:
raise ProcessInvalidConfigurationException("process cannot be None")
if process.state is None or not process.state.name:
raise ProcessInvalidConfigurationException("process state name cannot be empty")
if kernel is None:
raise ProcessInvalidConfigurationException("kernel cannot be None")
if initial_event is None:
raise ProcessInvalidConfigurationException("initial_event cannot be None")
initial_event_str: str | KernelProcessEvent = (
initial_event.value if isinstance(initial_event, Enum) else initial_event
)
if isinstance(initial_event_str, str):
initial_event_str = KernelProcessEvent(id=initial_event_str, data=kwargs.get("data"))
process_context = LocalKernelProcessContext(process, kernel, max_supersteps)
await process_context.start_with_event(initial_event_str)
return process_context
@@ -0,0 +1,76 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import TYPE_CHECKING
from semantic_kernel.kernel import Kernel
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.processes.local_runtime.local_process import LocalProcess
from semantic_kernel.utils.feature_stage_decorator import experimental
if TYPE_CHECKING:
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
from semantic_kernel.processes.local_runtime.local_event import KernelProcessEvent
@experimental
class LocalKernelProcessContext(KernelBaseModel):
"""A local kernel process context."""
local_process: LocalProcess
def __init__(self, process: "KernelProcess", kernel: "Kernel", max_supersteps: int | None = None) -> None:
"""Initializes the local kernel process context.
Args:
process: The kernel process to start.
kernel: The kernel instance.
max_supersteps: The maximum number of supersteps. This is the total number of times process steps will run.
Defaults to None.
"""
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess # noqa: F401
LocalProcess.model_rebuild()
if not process or not process.state or not process.state.name.strip():
raise ValueError("Process and process state must be provided and have a valid name")
if not kernel:
raise ValueError("Kernel must be provided")
local_process = LocalProcess(
process=process,
kernel=kernel,
parent_process_id=None,
factories=process.factories,
max_supersteps=max_supersteps,
)
super().__init__(local_process=local_process) # type: ignore
async def start_with_event(self, initial_event: "KernelProcessEvent") -> None:
"""Starts the local process with an initial event."""
await self.local_process.run_once(initial_event)
async def send_event(self, process_event: "KernelProcessEvent") -> None:
"""Sends an event to the process."""
await self.local_process.send_message(process_event)
async def stop(self) -> None:
"""Stops the local process."""
await self.local_process.stop()
async def get_state(self) -> "KernelProcess":
"""Gets the current state of the process."""
return await self.local_process.get_process_info()
async def __aenter__(self):
"""Enters the async context (used for resource management)."""
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Exits the async context and disposes of resources."""
await self.dispose()
async def dispose(self) -> None:
"""Disposes of the resources used by the process."""
if self.local_process:
self.local_process.dispose()
@@ -0,0 +1,20 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from pydantic import Field
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.utils.feature_stage_decorator import experimental
@experimental
class LocalMessage(KernelBaseModel):
"""A message that is local to a namespace."""
source_id: str = Field(...)
destination_id: str = Field(...)
function_name: str = Field(...)
values: dict[str, Any | None] = Field(...)
target_event_id: str | None = Field(default=None)
target_event_data: Any | None = Field(default=None)
@@ -0,0 +1,30 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.processes.kernel_process.kernel_process_edge import KernelProcessEdge
from semantic_kernel.processes.local_runtime.local_message import LocalMessage
from semantic_kernel.utils.feature_stage_decorator import experimental
@experimental
class LocalMessageFactory:
"""Factory class to create LocalMessage instances."""
@staticmethod
def create_from_edge(edge: "KernelProcessEdge", data: Any | None = None) -> "LocalMessage":
"""Creates a new LocalMessage instance from a KernelProcessEdge and a data object."""
target = edge.output_target
parameter_value: dict[str, Any | None] = {}
if target.parameter_name:
parameter_value[target.parameter_name] = data
return LocalMessage(
source_id=edge.source_step_id,
destination_id=target.step_id,
function_name=target.function_name,
values=parameter_value,
target_event_id=target.target_event_id,
target_event_data=data,
)
@@ -0,0 +1,265 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import contextlib
import logging
import uuid
from collections.abc import Callable
from queue import Queue
from typing import TYPE_CHECKING, Any
from pydantic import Field
from semantic_kernel.exceptions import KernelException
from semantic_kernel.exceptions.process_exceptions import ProcessEventUndefinedException
from semantic_kernel.kernel import Kernel
from semantic_kernel.processes.const import END_PROCESS_ID
from semantic_kernel.processes.kernel_process.kernel_process_state import KernelProcessState
from semantic_kernel.processes.kernel_process.kernel_process_step_info import KernelProcessStepInfo
from semantic_kernel.processes.local_runtime.local_event import (
KernelProcessEvent,
KernelProcessEventVisibility,
LocalEvent,
)
from semantic_kernel.processes.local_runtime.local_message import LocalMessage
from semantic_kernel.processes.local_runtime.local_message_factory import LocalMessageFactory
from semantic_kernel.processes.local_runtime.local_step import LocalStep
from semantic_kernel.utils.feature_stage_decorator import experimental
if TYPE_CHECKING:
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
logger: logging.Logger = logging.getLogger(__name__)
@experimental
class LocalProcess(LocalStep):
"""A local process that contains a collection of steps."""
kernel: Kernel
steps: list[LocalStep] = Field(default_factory=list)
step_infos: list[KernelProcessStepInfo] = Field(default_factory=list)
process: "KernelProcess"
initialize_task: bool | None = False
external_event_queue: Queue = Field(default_factory=Queue)
process_task: asyncio.Task | None = None
factories: dict[str, Callable] = Field(default_factory=dict)
max_supersteps: int = Field(
default=100, ge=1, description="Maximum number of supersteps to execute before stopping the process."
)
def __init__(
self,
process: "KernelProcess",
kernel: Kernel,
factories: dict[str, Callable] | None = None,
parent_process_id: str | None = None,
max_supersteps: int | None = None,
):
"""Initializes the local process."""
args: dict[str, Any] = {
"step_info": process,
"kernel": kernel,
"parent_process_id": parent_process_id,
"step_infos": list(process.steps),
"process": process,
"initialize_task": False,
}
if max_supersteps is not None:
args["max_supersteps"] = max_supersteps
if factories:
args["factories"] = factories
super().__init__(**args)
def ensure_initialized(self):
"""Ensures the process is initialized lazily (synchronously)."""
if not self.initialize_task:
self.initialize_process()
self.initialize_task = True
async def start(self, keep_alive: bool = True) -> None:
"""Starts the process with an initial event.
Args:
keep_alive: Indicates if the process should wait for external events after it's finished processing.
"""
self.ensure_initialized()
self.process_task = asyncio.create_task(
self.internal_execute(max_supersteps=self.max_supersteps, keep_alive=keep_alive)
)
async def run_once(self, process_event: KernelProcessEvent):
"""Starts the process with an initial event and waits for it to finish.
Args:
process_event: The KernelProcessEvent to start the process with.
"""
if process_event is None:
raise ProcessEventUndefinedException("The process event must be specified.")
self.external_event_queue.put(process_event)
await self.start(keep_alive=False)
if self.process_task:
await self.process_task
async def stop(self):
"""Stops a running process."""
if not self.process_task or self.process_task.done():
return # Task is already finished or hasn't started
self.process_task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await self.process_task
async def initialize_step(self):
"""Initializes the step."""
# The process does not need any further initialization
pass
async def send_message(self, process_event: KernelProcessEvent):
"""Sends a message to the process."""
if process_event is None:
raise ProcessEventUndefinedException("The process event must be specified.")
self.external_event_queue.put(process_event)
async def get_process_info(self) -> "KernelProcess":
"""Gets the process information."""
return await self.to_kernel_process()
def initialize_process(self):
"""Initializes the input and output edges for the process and initializes the steps."""
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
# Initialize the input and output edges for the process
self.output_edges = {kvp[0]: list(kvp[1]) for kvp in self.process.edges.items()}
# Initialize the steps within this process
for step in self.step_infos:
local_step = None
# The current step should already have a name.
assert step.state and step.state.name is not None # nosec
if isinstance(step, KernelProcess):
# The process will only have an Id if it's already been executed.
if not step.state.id:
step.state.id = str(uuid.uuid4().hex)
# Create a new LocalProcess for the step
process = LocalProcess(
process=step,
kernel=self.kernel,
factories=self.factories,
parent_process_id=self.id,
)
local_step = process
else:
# The current step should already have an Id.
assert step.state and step.state.id is not None # nosec
# Create a LocalStep for the step
local_step = LocalStep( # type: ignore
step_info=step,
kernel=self.kernel,
factories=self.factories,
parent_process_id=self.id,
)
# Add the local step to the list of steps
self.steps.append(local_step)
async def handle_message(self, message: LocalMessage):
"""Handles a LocalMessage that has been sent to the process."""
if not message.target_event_id:
error_message = (
"Internal Process Error: The target event id must be specified when sending a message to a step."
)
logger.error(error_message)
raise KernelException(error_message)
event_id = message.target_event_id
if event_id in self.output_edges:
nested_event = KernelProcessEvent(
id=event_id, data=message.target_event_data, visibility=KernelProcessEventVisibility.Internal
)
await self.run_once(nested_event)
async def internal_execute(self, max_supersteps: int = 100, keep_alive: bool = True):
"""Internal execution logic for the process."""
message_channel: Queue[LocalMessage] = Queue()
logger.debug(f"Running process for {max_supersteps} supersteps.")
try:
for _ in range(max_supersteps):
self.enqueue_external_messages(message_channel)
for step in self.steps:
await self.enqueue_step_messages(step, message_channel)
messages_to_process: list[LocalMessage] = []
while not message_channel.empty():
messages_to_process.append(message_channel.get())
if not messages_to_process and (not keep_alive or self.external_event_queue.empty()):
break
message_tasks = []
for message in messages_to_process:
if message.destination_id == END_PROCESS_ID:
break
destination_step = next(step for step in self.steps if step.id == message.destination_id)
message_tasks.append(destination_step.handle_message(message))
await asyncio.gather(*message_tasks)
except Exception as ex:
logger.error(f"An error occurred while running the process: {ex}.")
raise
async def to_kernel_process(self) -> "KernelProcess":
"""Builds a KernelProcess from the current LocalProcess."""
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
process_state = KernelProcessState(name=self.name, version=self.step_state.version, id=self.id)
step_tasks = [step.to_kernel_process_step_info() for step in self.steps]
steps = await asyncio.gather(*step_tasks)
return KernelProcess(state=process_state, steps=steps, edges=self.output_edges)
async def to_kernel_process_step_info(self) -> "KernelProcessStepInfo":
"""Extracts the current state of the step and returns it as a KernelProcessStepInfo."""
return await self.to_kernel_process()
def enqueue_external_messages(self, message_channel: Queue[LocalMessage]):
"""Processes external events that have been sent to the process."""
while not self.external_event_queue.empty():
external_event: KernelProcessEvent = self.external_event_queue.get_nowait()
if external_event.id in self.output_edges:
edges = self.output_edges[external_event.id]
for edge in edges:
message = LocalMessageFactory.create_from_edge(edge, external_event.data)
message_channel.put(message)
async def enqueue_step_messages(self, step: LocalStep, message_channel: Queue[LocalMessage]):
"""Processes events emitted by the given step and enqueues them."""
all_step_events = step.get_all_events()
for step_event in all_step_events:
# must come first because emitting the step event modifies its namespace
for edge in step.get_edge_for_event(step_event.id):
message = LocalMessageFactory.create_from_edge(edge, step_event.data)
message_channel.put(message)
if step_event.visibility == KernelProcessEventVisibility.Public:
if isinstance(step_event, KernelProcessEvent):
await self.emit_event(step_event) # type: ignore
elif isinstance(step_event, LocalEvent):
await self.emit_local_event(step_event) # type: ignore
def dispose(self):
"""Clean up resources."""
if self.process_task:
self.process_task.cancel()
@@ -0,0 +1,303 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import logging
import uuid
from collections.abc import Callable
from inspect import isawaitable
from queue import Queue
from typing import Any
from pydantic import Field, model_validator
from semantic_kernel import Kernel
from semantic_kernel.exceptions import KernelException
from semantic_kernel.exceptions.process_exceptions import (
ProcessFunctionNotFoundException,
ProcessTargetFunctionNameMismatchException,
)
from semantic_kernel.functions import KernelFunction
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.processes.kernel_process.kernel_process_edge import KernelProcessEdge
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEvent
from semantic_kernel.processes.kernel_process.kernel_process_message_channel import KernelProcessMessageChannel
from semantic_kernel.processes.kernel_process.kernel_process_step_info import KernelProcessStepInfo
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
from semantic_kernel.processes.local_runtime.local_event import LocalEvent
from semantic_kernel.processes.local_runtime.local_message import LocalMessage
from semantic_kernel.processes.process_types import get_generic_state_type
from semantic_kernel.processes.step_utils import find_input_channels, get_fully_qualified_name
from semantic_kernel.utils.feature_stage_decorator import experimental
logger: logging.Logger = logging.getLogger(__name__)
@experimental
class LocalStep(KernelProcessMessageChannel, KernelBaseModel):
"""A local step that is part of a local process."""
kernel: "Kernel"
step_info: "KernelProcessStepInfo"
outgoing_event_queue: Queue[LocalEvent] = Field(default_factory=Queue)
initialize_task: bool | None = False
event_namespace: str
step_state: KernelProcessStepState
inputs: dict[str, dict[str, Any | None]] = Field(default_factory=dict)
initial_inputs: dict[str, dict[str, Any | None]] = Field(default_factory=dict)
functions: dict[str, KernelFunction] = Field(default_factory=dict)
output_edges: dict[str, list[KernelProcessEdge]] = Field(default_factory=dict)
parent_process_id: str | None = None
init_lock: asyncio.Lock = Field(default_factory=asyncio.Lock, exclude=True)
factories: dict[str, Callable]
@model_validator(mode="before")
@classmethod
def parse_initial_configuration(cls, data: Any) -> Any:
"""Parses the initial configuration of the step."""
if isinstance(data, dict):
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
step_info = data.get("step_info")
assert step_info is not None # nosec
if step_info and isinstance(step_info, KernelProcess) and step_info.state.id is None:
step_info.state.id = str(uuid.uuid4().hex)
data["step_state"] = step_info.state
data["output_edges"] = {k: v for k, v in step_info.edges.items()}
data["event_namespace"] = f"{step_info.state.name}_{step_info.state.id}"
return data
@property
def name(self) -> str:
"""Gets the name of the step."""
return self.step_info.state.name
@property
def id(self) -> str:
"""Gets the ID of the step."""
return self.step_info.state.id if self.step_info.state.id else ""
async def handle_message(self, message: LocalMessage):
"""Handles a LocalMessage that has been sent to the step."""
if message is None:
raise ValueError("The message is None.")
if not self.initialize_task:
async with self.init_lock:
# Second check to ensure that initialization happens only once
# This avoids a race condition where multiple coroutines might
# reach the first check at the same time before any of them acquire the lock.
if not self.initialize_task:
await self.initialize_step()
self.initialize_task = True
if self.functions is None or self.inputs is None or self.initial_inputs is None:
raise ValueError("The step has not been initialized.")
message_log_parameters = ", ".join(f"{k}: {v}" for k, v in message.values.items())
logger.info(
f"Received message from `{message.source_id}` targeting function "
f"`{message.function_name}` and parameters `{message_log_parameters}`."
)
# Add the message values to the inputs for the function
for k, v in message.values.items():
if self.inputs.get(message.function_name) and self.inputs[message.function_name].get(k):
logger.info(
f"Step {self.name} already has input for `{message.function_name}.{k}`, "
f"it is being overwritten with a message from Step named `{message.source_id}`."
)
if message.function_name not in self.inputs:
self.inputs[message.function_name] = {}
self.inputs[message.function_name][k] = v
invocable_functions = [
k
for k, v in self.inputs.items()
if v is not None and (v == {} or all(val is not None for val in v.values()))
]
missing_keys = [
f"{outer_key}.{inner_key}"
for outer_key, outer_value in self.inputs.items()
for inner_key, inner_value in outer_value.items()
if inner_value is None
]
if not invocable_functions:
logger.info(f"No invocable functions, missing keys: {', '.join(missing_keys)}")
return
target_function = next((name for name in invocable_functions if name == message.function_name), None)
if not target_function:
raise ProcessTargetFunctionNameMismatchException(
f"A message targeting function `{message.function_name}` has resulted in a different function "
f"`{invocable_functions[0]}` becoming invocable. Check the function names."
)
logger.info(
f"Step with Id '{self.id}' received all required input for function [{target_function}] and is executing."
)
# Concatenate all inputs and run the function
arguments = self.inputs[target_function]
function = self.functions.get(target_function)
if function is None:
raise ProcessFunctionNotFoundException(f"Function {target_function} not found in plugin {self.name}")
invoke_result = None
event_name: str = ""
event_value = None
try:
logger.info(
f"Invoking plugin `{function.plugin_name}` and function `{function.name}` with arguments: {arguments}"
)
invoke_result = await self.invoke_function(function, self.kernel, arguments)
if invoke_result is None:
raise KernelException(f"Function {target_function} returned None.")
event_name = f"{target_function}.OnResult"
event_value = invoke_result.value
except Exception as ex:
logger.error(f"Error in Step {self.name}: {ex!s}")
event_name = f"{target_function}.OnError"
event_value = str(ex)
finally:
await self.emit_event(KernelProcessEvent(id=event_name, data=event_value))
# Reset the inputs for the function that was just executed
self.inputs[target_function] = self.initial_inputs.get(target_function, {}).copy()
async def invoke_function(self, function: "KernelFunction", kernel: "Kernel", arguments: dict[str, Any]):
"""Invokes the function."""
return await kernel.invoke(function, **arguments)
async def emit_event(self, process_event: KernelProcessEvent):
"""Emits an event from the step."""
await self.emit_local_event(LocalEvent.from_kernel_process_event(process_event, self.event_namespace))
async def emit_local_event(self, local_event: "LocalEvent"):
"""Emits an event from the step."""
scoped_event = self.scoped_event(local_event)
self.outgoing_event_queue.put(scoped_event)
async def initialize_step(self):
"""Initializes the step."""
# Instantiate an instance of the inner step object
step_cls = self.step_info.inner_step_type
factory = (
self.factories.get(get_fully_qualified_name(self.step_info.inner_step_type)) if self.factories else None
)
if factory:
step_instance = factory()
if isawaitable(step_instance):
step_instance = await step_instance
step_cls = type(step_instance)
else:
step_instance = step_cls() # type: ignore
kernel_plugin = self.kernel.add_plugin(
step_instance, self.step_info.state.name if self.step_info.state else "default_name"
)
# Load the kernel functions
for name, f in kernel_plugin.functions.items():
self.functions[name] = f
# Initialize the input channels
self.initial_inputs = find_input_channels(channel=self, functions=self.functions)
self.inputs = {k: {kk: vv for kk, vv in v.items()} if v else {} for k, v in self.initial_inputs.items()}
# Use the existing state or create a new one if not provided
state_object = self.step_info.state
# Extract TState from inner_step_type
t_state = get_generic_state_type(step_cls)
if t_state is not None:
# Create state_type as KernelProcessStepState[TState]
state_type = KernelProcessStepState[t_state]
if state_object is None:
state_object = state_type(
name=step_cls.__name__,
id=step_cls.__name__,
state=None,
)
else:
# Make sure state_object is an instance of state_type
if not isinstance(state_object, KernelProcessStepState):
error_message = "State object is not of the expected type."
raise KernelException(error_message)
# Make sure that state_object.state is not None
if state_object.state is None:
try:
state_object.state = t_state()
except Exception as e:
error_message = f"Cannot instantiate state of type {t_state}: {e}"
raise KernelException(error_message)
else:
# The step has no user-defined state; use the base KernelProcessStepState
state_type = KernelProcessStepState
if state_object is None:
state_object = state_type(
name=step_cls.__name__,
id=step_cls.__name__,
state=None,
)
if state_object is None:
error_message = "The state object for the KernelProcessStep could not be created."
raise KernelException(error_message)
# Set the step state and activate the step with the state object
self.step_state = state_object
await step_instance.activate(state_object)
def get_all_events(self) -> list["LocalEvent"]:
"""Retrieves all events that have been emitted by this step in the previous superstep."""
all_events = []
while not self.outgoing_event_queue.empty():
all_events.append(self.outgoing_event_queue.get())
return all_events
def get_edge_for_event(self, event_id: str) -> list["KernelProcessEdge"]:
"""Retrieves all edges that are associated with the provided event Id."""
if not self.output_edges:
return []
return self.output_edges.get(event_id, [])
async def to_kernel_process_step_info(self) -> "KernelProcessStepInfo":
"""Extracts the current state of the step and returns it as a KernelProcessStepInfo."""
if not self.initialize_task:
async with self.init_lock:
# Second check to ensure that initialization happens only once
# This avoids a race condition where multiple coroutines might
# reach the first check at the same time before any of them acquire the lock.
if not self.initialize_task:
await self.initialize_step()
self.initialize_task = True
return KernelProcessStepInfo(
inner_step_type=self.step_info.inner_step_type, state=self.step_state, output_edges=self.output_edges
)
def scoped_event(self, local_event: "LocalEvent") -> "LocalEvent":
"""Generates a scoped event for the step."""
if local_event is None:
raise ValueError("The local event must be specified.")
local_event.namespace = f"{self.name}_{self.id}"
return local_event
def scoped_event_from_kernel_process(self, process_event: "KernelProcessEvent") -> "LocalEvent":
"""Generates a scoped event for the step from a KernelProcessEvent."""
if process_event is None:
raise ValueError("The process event must be specified.")
return LocalEvent.from_kernel_process_event(process_event, f"{self.name}_{self.id}")