Migrating Agentic Code Python -> C# Part 4

In the previous blog post we looked at the Blogger (orchestrator) code in C#. Let’s move on to some of the other agents.

The Blogger invokes the Researcher, so let’s go there next.

The Researcher class is created with an IChatClient (the principal object for llms), a set of options and the search tool (Tavily)

using System.Text.Json;
using Microsoft.Extensions.AI;

namespace BlogMigration;

/// <summary>Creates a researcher agent that uses Tavily search.</summary>
public class ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavilyTool) : IResearcherAgent
{

The main method is InvokeAsync which gets a copy of the query and uses Tavily to search the web using that query. The results are JSON, and the next step is to extract a JsonDocument object by parsing these results.

try
        {
            object? searchResult = await tavilyTool.InvokeAsync(
                new AIFunctionArguments { ["query"] = query });

            string searchJson = searchResult switch
            {
                JsonElement je => je.ValueKind == JsonValueKind.String ? je.GetString() ?? "{}" : je.GetRawText(),
                string s => s,
                _ => searchResult?.ToString() ?? "{}"
            };

            var formattedResults = new List<string>();

            using (JsonDocument document = JsonDocument.Parse(searchJson))
            {
                if (document.RootElement.TryGetProperty("results", out JsonElement results)
                    && results.ValueKind == JsonValueKind.Array)
                {
                    foreach (JsonElement result in results.EnumerateArray().Take(3))
                    {
                        string title = result.TryGetProperty("title", out JsonElement t) ? t.GetString() ?? "Untitled" : "Untitled";
                        string url = result.TryGetProperty("url", out JsonElement u) ? u.GetString() ?? "N/A" : "N/A";
                        string content = result.TryGetProperty("content", out JsonElement c) ? c.GetString() ?? "" : "";
                        string snippet = content.Length > 250 ? content[..250] : content;
                        formattedResults.Add($">>{title}\nSource: {url}\n{snippet}...\n");
                    }
                }
            }

            string rawOutput = formattedResults.Count > 0
                ? string.Join("\n", formattedResults)
                : "No results found";

We next instruct the llm using a system prompt, passing in the raw output we just created. We get back the summary of the findings and if that is not empty we return it.

           string summaryPrompt = $"""
                Based on these search results about '{query}',
                provide a concise summary of key findings:
                {rawOutput}
                """;

            ChatResponse summaryResponse = await llm.GetResponseAsync(summaryPrompt, chatOptions);
            string summary = summaryResponse.Text;

            return !string.IsNullOrEmpty(summary) ? summary : rawOutput;

Finally, we handle any exceptions raised

       catch (Exception e)
        {
            Console.WriteLine($"Research error: {e.Message}");
            return $"Research completed on: {query}. Key information has been gathered from web sources.";
        }
 

As we did with Blogger, we also create a Node, passing in the state and getting back the updated state.

   public async Task<ResearchState> ResearchNodeAsync(ResearchState state)
    {
        Console.WriteLine("\n>>>RESEARCHER");

        string subTask = !string.IsNullOrEmpty(state.CurrentSubTask) ? state.CurrentSubTask : state.MainTask;
        Console.WriteLine($"Researching: {subTask}");

        string findings;
        try
        {
            findings = await InvokeAsync(subTask);
            string preview = findings.Length > 100 ? findings[..100] : findings;
            Console.WriteLine($"Found: {preview}...");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Research error: {e.Message}");
            findings = $"Research on {subTask} - information gathered";
        }

        state.ResearchFindings.Add(findings);
        return state;
    }

In the next blog post we’ll look at the Author

Unknown's avatar

About Jesse Liberty

** Note ** Jesse is currently looking for a new position. You can learn more about him at https://jesseliberty.bio Thank you. Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET, is now available wherever you buy your books. Liberty was a Team Lead and Senior Software Engineer for various corporations, a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a 13 year Microsoft MVP.
This entry was posted in AI, C#, Programming. Bookmark the permalink.