Skip to content

Python: Bug: a process step with a no-input function raises ProcessTargetFunctionNameMismatchException when a message fills only part of another function #14729

Open

Description

Description

A process step that has a function without inputs fails when a message fills only part of another function's inputs.

Take a step with add(a, b) and reset(). A message that fills a makes the step raise ProcessTargetFunctionNameMismatchException. The process stops and add never runs. The same step without reset() waits for b and then runs add, as it should.

The Dapr runtime has the same code and the same problem.

Reproduction

I ran this on current main (df1cb33), Python 3.13. The event go fills add.a right away. It reaches add.b one step later, through a relay step.

import asyncio

from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
from semantic_kernel.processes.local_runtime.local_event import KernelProcessEvent
from semantic_kernel.processes.local_runtime.local_kernel_process import start
from semantic_kernel.processes.process_builder import ProcessBuilder


class Relay(KernelProcessStep):
    @kernel_function
    async def pass_on(self, value: str) -> str:
        return value


class AddOnly(KernelProcessStep):
    @kernel_function
    async def add(self, a: str, b: str) -> str:
        print("  add called with", a, b)
        return a + b


class AddAndReset(KernelProcessStep):
    @kernel_function
    async def add(self, a: str, b: str) -> str:
        print("  add called with", a, b)
        return a + b

    @kernel_function
    async def reset(self) -> str:
        print("  reset called")
        return "reset"


async def run(step_type):
    builder = ProcessBuilder(name="fan_in")
    relay = builder.add_step(Relay)
    step = builder.add_step(step_type)
    builder.on_input_event("go").send_event_to(step, function_name="add", parameter_name="a")
    builder.on_input_event("go").send_event_to(relay, function_name="pass_on", parameter_name="value")
    relay.on_function_result("pass_on").send_event_to(step, function_name="add", parameter_name="b")
    await start(process=builder.build(), kernel=Kernel(), initial_event=KernelProcessEvent(id="go", data="x"))


for step_type in (AddOnly, AddAndReset):
    print(step_type.__name__)
    try:
        asyncio.run(run(step_type))
        print("  finished")
    except Exception as ex:
        print("  raised", type(ex).__name__ + ":", ex)

Output on main:

An error occurred while running the process: A message targeting function `add` has resulted in a different function `reset` becoming invocable. Check the function names..
AddOnly
  add called with x x
  finished
AddAndReset
  raised ProcessTargetFunctionNameMismatchException: A message targeting function `add` has resulted in a different function `reset` becoming invocable. Check the function names.

The step names and the event wiring are the same in both runs. Only the extra reset() function changes the result.

Cause

In LocalStep.handle_message (python/semantic_kernel/processes/local_runtime/local_step.py), the step stores the message values. Then it builds invocable_functions from every function whose inputs are all set.

A function with no required input has {} as its inputs, so it is always in that list. When the message targets add and add is not complete yet, target_function is None. Because reset is in the list, the code raises the mismatch error instead of waiting.

A function whose only parameter is KernelProcessStepContext is always in the list too, because that input is filled when the step starts.

StepActor.handle_message in python/semantic_kernel/processes/dapr_runtime/actors/step_actor.py has the same block.

Expected

A message can only change the function it targets. If that function still misses inputs, the step should log the missing keys and wait. This is what it already does for a step that has no other function.

Possible fix

I have a branch ready: https://github.com/VANDRANKI/semantic-kernel/tree/fix/process-step-partial-input-with-ready-function

It checks only the targeted function for missing inputs, in both LocalStep and StepActor. It adds 5 tests: 2 local step cases (a function with no inputs, and a function with only the step context), 1 Dapr step actor case, 1 end to end process like the script above, and 1 control that runs a no-input function when a message targets it. The 4 new tests that cover the bug fail on main and all 5 pass on the branch. The 239 existing process unit tests still pass.

I can't open a pull request from this account right now, so the compare link is main...VANDRANKI:semantic-kernel:fix/process-step-partial-input-with-ready-function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pythonPull requests for the Python Semantic Kerneltriage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions