Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: Allow for factory callbacks in the process framework #10451

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

moonbox3
Copy link
Contributor

@moonbox3 moonbox3 commented Feb 7, 2025

Motivation and Context

In the current Python process framework, in runtimes like Dapr, there is no easy way to pass complex (unserializable) dependencies to a step -- this includes things like a ChatCompletion service or an agent of type ChatCompletionAgent.

Similar to how the kernel dependency was propagated to the step_actor or process_actor, we're introducing the ability to specify a factory callback that will be called as the step is instantiated. The factory is created, if specified via the optional kwarg when adding a step to the process builder like:

myBStep = process.add_step(step_type=BStep, factory_function=bstep_factory)

The bstep_factory looks like (along with its corresponding step)

async def bstep_factory():
    """Creates a BStep instance with ephemeral references like ChatCompletionAgent."""
    kernel = Kernel()
    kernel.add_service(AzureChatCompletion())

    agent = ChatCompletionAgent(kernel=kernel, name="echo", instructions="repeat the input back")
    step_instance = BStep()
    step_instance.agent = agent

    return step_instance

class BStep(KernelProcessStep):
    """A sample BStep that optionally holds a ChatCompletionAgent.

    By design, the agent is ephemeral (not stored in state).
    """

    # Ephemeral references won't be persisted to Dapr
    # because we do not place them in a step state model.
    # We'll set this in the factory function:
    agent: ChatCompletionAgent | None = None

    @kernel_function(name="do_it")
    async def do_it(self, context: KernelProcessStepContext):
        print("##### BStep ran (do_it).")
        await asyncio.sleep(2)

        if self.agent:
            history = ChatHistory()
            history.add_user_message("Hello from BStep!")
            async for msg in self.agent.invoke(history):
                print(f"BStep got agent response: {msg.content}")

        await context.emit_event(process_event="BStepDone", data="I did B")

Although this isn't explicitly necessary with the local runtime, the factory callback will also work, if desired.

Description

Adds the ability to specify a factory callback for a step in the process framework.

  • Adjusts the Dapr FastAPI demo sample to show how one can include a dependency like a ChatCompletionAgent and use the factory callback for BStep. Although the output from the agent isn't needed, it demonstrates the capability to handle these types of dependencies while running Dapr.
  • Adds unit tests
  • Closes Python: handle complex dependencies with Dapr runtime #10409

Contribution Checklist

@moonbox3 moonbox3 self-assigned this Feb 7, 2025
@moonbox3 moonbox3 requested a review from a team as a code owner February 7, 2025 10:26
@moonbox3 moonbox3 added python Pull requests for the Python Semantic Kernel processes labels Feb 7, 2025
@markwallace-microsoft
Copy link
Member

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
semantic_kernel/processes
   process_builder.py774443%60–73, 77–79, 85–95, 99–108, 112–118, 122–126, 131, 135–140
semantic_kernel/processes/dapr_runtime
   dapr_actor_registration.py291934%25–40, 48–53, 61–66
semantic_kernel/processes/dapr_runtime/actors
   process_actor.py24411354%72–76, 81, 84, 90, 93, 97, 103, 106, 122–125, 130, 143, 159–163, 168, 178, 183, 192–200, 204–206, 214–222, 229, 245–306, 311, 322, 336–344, 347–354, 358–386, 417–430
   step_actor.py25912950%81–85, 90, 93, 97–98, 110–112, 156–159, 173–175, 183, 187–188, 214, 222–223, 239–246, 253–254, 262–358, 362, 366–369, 373–389, 393–412, 416–434, 438–441, 445, 449–452
semantic_kernel/processes/kernel_process
   kernel_process.py24196%58
semantic_kernel/processes/local_runtime
   local_kernel_process_context.py32294%67–68
   local_process.py1385362%66, 103, 113, 131–142, 176–203, 207–212, 216, 220–226, 230–240, 244–245
   local_step.py17210638%64, 74, 83–171, 175, 179, 183–184, 189–259, 263–266, 270–273, 277–286, 292–295, 299–301
TOTAL18530216988% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
3174 4 💤 0 ❌ 0 🔥 1m 39s ⏱️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
processes python Pull requests for the Python Semantic Kernel
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

Python: handle complex dependencies with Dapr runtime
2 participants