February 10th, 2025

Guest Blog: Step-by-Step Guide to Building a Portfolio Manager: A Multi-Agent System with Microsoft Semantic Kernel and Azure OpenAI

Today the Semantic Kernel team is excited to welcome back a guest author, Akshay Kokane to share his recent Medium article using Semantic Kernel and Azure OpenAI, showcasing a step-by-step guide to building a Portfolio Manager. We’ll turn it over to him to dive into his work below.

In my previous blog, we went over how Semantic Kernel can be used to create a multi-agent system. Link.

However, agent collaboration was really challenging, as we were not able to control how agents collaborated. We could set the termination strategy to decide when to stop collaboration between agents, but not how agents would participate.

The new version of Semantic Kernel introduced selectionFunction, which solves this problem. How? Let’s dive deeper into it with an example and a step-by-step guide.

Selection Function in a Multi-Agent System Selection

The selection function in Semantic Kernel helps define the ground rules for agents to collaborate. In a multi-agent system, agents often enter incorrect loops, leading to inconsistent output. The selection function in Semantic Kernel solves this problem.

We will understand how the selection function works with the example of a ‘Portfolio Management Multi-Agent System.’ Let’s start by define 3 agents:

  1. Portfolio Optimizer Agent: This agent will have access to my personal financial position and goals and can help optimize my portfolio.
  2. Web Surfer Agent: This agent can browse the web for the latest news and information.
  3. Stock Analyzer Agent: This agent analyzes historical stock data, current news, and trends to evaluate stocks.

I created an app that will show me hosted agents, and I should be able to run agents on demand as well as on a schedule.

Step-by-Step Guide to Building a Portfolio Manager

On how to create Semantic Kernel, please refer to the official docs.

Step 1: Define Agents

   /// Step 1: Define Agents
            /// Creating individual agents for portfolio management, web surfing, and stock analysis.
            PortfolioManagerAgent = new()
            {
                Name = nameof(PortfolioManagerAgents),
                Instructions = @"Your are experieced Portfolio Manager.
                                You have access to user's porfolio.
                                You make recommendations based on latest news and stock analysis report.
                                You provide the portfolio to other participant if needed.
                                If you don't have latest news or stock analysis report, you ask for it to other participants. Never give general guidelines",
                Kernel = Kernel,
                Arguments = new KernelArguments(openAIPromptExecutionSettings)
            };

            WebAgent = new()
            {
                Name = nameof(WebSurferAgent),
                Instructions = "Your task is to retrieve and summarize the latest stock market news from reliable sources. " +
                   "You will monitor stock trends, economic events, and financial updates, providing real-time insights. " +
                   "to enhance investment recommendations. Ensure the news is current, relevant, and sourced from credible financial sources. Never provide general insights. You only provide news",
                Kernel = Kernel,
                Arguments = new KernelArguments(openAIPromptExecutionSettings)
            };

            /// Defining the Stock Analyzer Agent using OpenAI
            Agent = OpenAIAssistantAgent.CreateAsync(
                OpenAIClientProvider.ForAzureOpenAI(apiKeyCredential, new Uri("https://testmediumazureopenai.openai.azure.com")),
                new OpenAIAssistantDefinition("GPT4ov1")
                {
                    Name = nameof(StockAnalyzerAgent),
                    Instructions = "You are responsible for analyzing stock market data and sentiment analysis on news updates. " +
                   "Leverage historical stock data, technical indicators, and fundamental analysis to assess market trends. " +
                   "Perform sentiment analysis on news provided by the WebSurferAgent to gauge market sentiment.. Create charts in HTML",
                },
                Kernel);

Step 2: Define Selection Strategy

 /// Step 2: Define Selection Strategy
/// This function determines which agent should take the next turn in the collaboration.
private KernelFunction GetSelectionFunction()
{
    return AgentGroupChat.CreatePromptFunctionForStrategy(
        $$$"""
        Determine which participant takes the next turn in a conversation based on the most recent participant.
        State only the name of the participant to take the next turn.
        No participant should take more than one turn in a row.

        Choose only from these participants:
        - {{{nameof(PortfolioManagerAgents)}}}
        - {{{nameof(WebSurferAgent)}}}
        - {{{nameof(StockAnalyzerAgent)}}}

        Always follow these rules when selecting the next participant:
        - After {{{nameof(PortfolioManagerAgents)}}}, it is {{{nameof(WebSurferAgent)}}}'s turn.
        - After {{{nameof(WebSurferAgent)}}}, it is {{{nameof(StockAnalyzerAgent)}}}'s turn.
        - After {{{nameof(StockAnalyzerAgent)}}}, it is {{{nameof(PortfolioManagerAgents)}}}'s turn.
        
        History:
        {{$history}}
        """,
        safeParameterNames: "history");
}

Step 3: Define Termination Strategy

/// Step 3: Define Termination Strategy
/// This function ensures that the process stops once the Portfolio Manager makes a recommendation.
private KernelFunction GetTerminationStrategy()
{
    KernelFunction terminationFunction =
        AgentGroupChat.CreatePromptFunctionForStrategy(
            $$$"""
            Determine if the PortfolioManager is done with recommendations.
            If so, respond with a single word: done

            History:
            {{$history}}
            """,
            safeParameterNames: "history");

    return terminationFunction;
}

Step 4: Create Useful Tools

I am adding 2 tools

  1. NewsAPI for getting latest news (registered with WebSurfer agent)
  2. GetPortfolio for getting my upto date portfolio (registered with PortfolioManagement Agent)

I will not go into how to write tools for Semantic Kernel, as I have covered that in my previous blogs. Instead, I am sharing a small snippet on how to enable function calling for agents. For official docs, check here

// Define function/tools
[KernelFunction, Description("Get my latest portfolio information.")]
public string GetMyPortfolio(string user)
{
  // implmentation here
}

Kernel.Plugins.AddFromObject(portfolioTools);

OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
   FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

PortfolioManagerAgent = new()
            {
                ....
                Arguments = new KernelArguments(openAIPromptExecutionSettings)
                .....
            };

Step 5: Creating Multi-Agent Chat

/// Step 5: Putting It All Together
/// This function initializes the agent system and manages the execution workflow.
public async Task StartAsync(CancellationToken cancellationToken)
{
    StringBuilder interactions = new StringBuilder();

    try
    {
        var PAgent = new PortfolioManagerAgents().GetAgent();
        var WebAgent = new WebSurferAgent().GetAgent();
        var stockAgent = new StockAnalyzerAgent().GetAgent();
        
        // Define selection strategy
        KernelFunctionSelectionStrategy selectionStrategy =
            new(GetSelectionFunction(), Kernel)
            {
                InitialAgent = PAgent,
                HistoryVariableName = "history",
                HistoryReducer = new ChatHistoryTruncationReducer(10),
            };

        // Define termination strategy
        KernelFunctionTerminationStrategy terminationStrategy =
          new(GetTerminationStrategy(), Kernel)
          {
              Agents = [PAgent],
              ResultParser = (result) =>
                result.GetValue<string>()?.Contains("done", StringComparison.OrdinalIgnoreCase) ?? false,
              HistoryVariableName = "history",
              HistoryReducer = new ChatHistoryTruncationReducer(1),
              MaximumIterations = 10,
          };

        // Initialize the multi-agent system
        AgentGroupChat chat = new(PAgent, WebAgent, stockAgent)
        {
            ExecutionSettings = new()
            {
                TerminationStrategy = terminationStrategy,
                SelectionStrategy = selectionStrategy,
            }
        };

        chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, "start. Note today is Jan 15th 2025"));

        // Execute the multi-agent collaboration
        await foreach (var content in chat.InvokeAsync(cancellationToken))
        {
            string interaction = $"<b>#{content.Role}</b> - <i>{content.AuthorName ?? "*"}</i>: \"{content.Content}\"";
            Console.WriteLine(interaction);
            interactions.Append(interaction + "\n");
        }

        Console.WriteLine("PortfolioManagementAgentSystem has completed execution.");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    
    var prompt = @"Write an email body outlining the necessary actions for my portfolio. 
                  Provide the output in HTML format and keep it concise. 
                  Include graphs and charts in HTML format. 
                  Start directly with the email content without any additional text before or after.";
    
    var emailContent = await Kernel.InvokePromptAsync($"{prompt} {interactions}");
    
    EmailSender.SendEmail("Portfolio Agent Update", emailContent.ToString());
}

This is my portfolio that PortfolioManagerAgent has access too

Sample portfolio. Values are fake and not a real price of stocks or bonds

Here is the email I got once I run this multi-agent system. You can just output this in console log.

From the Semantic Kernel team, we’d like to thank Akshay for his time and all of his great work.  Please reach out if you have any questions or feedback through our Semantic Kernel GitHub Discussion Channel. We look forward to hearing from you! 

0 comments