Implement a hub-and-spoke orchestration

In my last post Advanced multi-agent orchestration architectures I wrote about Hub-and-Spoke, Hierarchical, and Supervisor patterns.  In this article, as the title makes clear I will get a bit deeper into hub-and-spoke.  But before that, as I was writing the hub and spoke code I learned a few hard lessons.  I share them in case you are experiencing the same issue, but I expect as the innovation and change continues to progress that some of these will go away and get resolved.

Firstly, I was combining V1 and V2 features of azure.ai.projects, this area is still fast evolving but I found some great information here which helped me progress.  I also provide Table 1 which describes the differences between V1 (Microsoft Foundry) and V2 (Microsoft Foundry Agents Service).  The example I found most were targeting V1, but the training I was taking was V2, or seemed also like a combination of both.

Table 1, V1 vs V2 azure-ai-projects


V1 V2
Threads Conversations
Runs Responses
Messages Input/Output Items
requires_action Explicit tool call loop
submit_tool_outputs Response continuation
Assistants Agents

The other important concept to understand is the difference between Prompt Agents and Hosted Agents.  The context for this article is Hosted Agents.  When you use Hosted Agents you do not need to worry so much about “containerization, web server setup, security, memory persistence, scaling, instrumentation, and version rollbacks” because the execution of the agent and its inference with the LLM happen on Microsoft Foundry.  It is common, during testing to run agents, by running I mean, the compute they consume is your personal workstation, and in that can you need to manage all the pre-mentioned dependencies.

The following illustrated, Figure 1, is generated by AI using a short prompt and the source code I wrote for hub-and-spoke-async.py.

image

Figure 1, hub and spoke advanced orchestration pattern

You can find the hub-and-spoke example I coded here: https://github.com/benperk/AI/blob/main/hub-and-spoke-async.py instead of pasting all code in this article. I’ll refer to the lines of code at that location, and share some snippets.

To implement the hub-and-spoke advanced multi-agent orchestration solution I utilized 2 different clients to perform the necessary V2 activities:

AIProjectClient(endpoint=endpoint, credential=credential) as project_client
project_client.get_openai_client(agent_name=hub_agent_name)

As you can see here on line 147 I used project_client to instantiate the AI Agent and again on line 185 to get the Azure Open AI Client.  Later on line 198 the Open AI client is used to instantiate the conversation then again on line 202 to create the response.

conversation = await hub_client.conversations.create(
        items=[{"type": "message", "role": "user", "content": user_query}],
    )
Listing 1, line 198
response = await hub_client.responses.create(conversation=conversation.id)
Listing 2, line 202

Figure 2 illustrates in detail how the pieces of the code fit together.

image

Figure 2, detailed hub and spoke advanced orchestration pattern

Figure 3 illustrates the output of the execution.

image

Figure 3, detailed hub and spoke advanced orchestration pattern output

I will add a few additional explanations for this implementation, through the different portions of the pattern from here. 

Define Spoke Agents and Tools

In this example there are 2 spoke agents one for marketing research and one for risk analysis.  The agents are created using the project_client.agents.create_version method.  These agents then get created in Microsoft Foundry as seen in Figure 4.

image

Figure 4, detailed hub and spoke advanced orchestration pattern hosted agents in Microsoft Foundry

Each agent is associated with a name and a instruction prompt, again using the project_client.  Later, project_client.get_openai_client(agent_name=hub_agent_name) is used to create the clients which are used in the SPOKE.

Create Hub Agent with Orchestration Loop

The hub is created in the same way as the spoke, the main difference is that the FunctionTools as passed as a property to the create_version() function.

The orchestration loop is where I like to say “the magic happens”.  I suppose to better understand how it works one would need to perform a deep dive into the responses.create method called from line 202.  The response returns numerous items, as you can seen in Figure 3, you can see reasoning, function_call, and message.  When there is a function_call the model is saying “I want somebody to execute the tool named invoke-market-analysis-agent.”  This will in-turn invoke the spoke passing either invoke-market-analysis-agent or invoke-risk-assessment-agent into the spoke method along with the clients for the associated market or risk agents.  The the agent is invoked, inferred, and returns the response.

The loop continues until all function_calls are executed, finally returning the results to the Hub for final rendition.

Here is some more great information about implementing hub-and-spoke pattern.