Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion AuthorAgent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Diagnostics;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;

namespace BlogWriter;

Expand All @@ -13,8 +15,16 @@ public class AuthorAgent : IAuthorAgent
{
private readonly ChatClientAgent _agent;

public AuthorAgent(IChatClient llm, ChatOptions chatOptions)
// Emits a span per draft creation/revision. Activated by the ActivityListener
// registered in Program.cs (or an OpenTelemetry TracerProvider).
private static readonly ActivitySource s_activitySource = new("BlogWriter.AuthorAgent");

private readonly ILogger<AuthorAgent> _logger;

public AuthorAgent(IChatClient llm, ChatOptions chatOptions, ILogger<AuthorAgent> logger)
{
_logger = logger;

_agent = new ChatClientAgent(llm, new ChatClientAgentOptions
{
Name = "Author",
Expand All @@ -25,10 +35,14 @@ public AuthorAgent(IChatClient llm, ChatOptions chatOptions)
MaxOutputTokens = chatOptions.MaxOutputTokens,
},
});
_logger.LogInformation("AuthorAgent initialized.");
}

public async Task<string> InvokeAsync(ResearchState state)
{
using Activity? activity = s_activitySource.StartActivity("Author.Invoke");
activity?.SetTag("blog.revision", state.RevisionNumber);

List<string> research = state.ResearchFindings;
string researchText = research.Count > 0 ? string.Join("\n\n", research) : "No research available.";

Expand Down
8 changes: 8 additions & 0 deletions BlogWorkflow.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;

namespace BlogWriter;
Expand All @@ -16,8 +17,15 @@ public class BlogWorkflow(
IAuthorAgent author,
IReviewerAgent reviewer) : IBlogWorkflow
{
// Emits the root span for a workflow run. Activated by the ActivityListener
// registered in Program.cs (or an OpenTelemetry TracerProvider).
private static readonly ActivitySource s_activitySource = new("BlogWriter.Workflow");

public async Task<ResearchState> RunAsync(ResearchState state)
{
using Activity? activity = s_activitySource.StartActivity("Workflow.Run");
activity?.SetTag("blog.topic", state.MainTask);

var bloggerExecutor = new BloggerExecutor(blogger);
var researcherExecutor = new ResearcherExecutor(researcher);
var authorExecutor = new AuthorExecutor(author);
Expand Down
2 changes: 2 additions & 0 deletions BlogWriter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
</PackageReference>
<PackageReference Include="Microsoft.Extensions.AI" Version="10.7.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.10" />
<PackageReference Include="OpenAI" Version="2.11.0" />
</ItemGroup>

Expand Down
16 changes: 15 additions & 1 deletion BloggerAgent.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Diagnostics;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;

namespace BlogWriter;

Expand All @@ -22,8 +24,16 @@ public class BloggerAgent : IBloggerAgent
// generated schema regardless of naming policy.
private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);

public BloggerAgent(IChatClient llm, ChatOptions chatOptions)
// Emits a span per Blogger decision. Activated by the ActivityListener
// registered in Program.cs (or an OpenTelemetry TracerProvider).
private static readonly ActivitySource s_activitySource = new("BlogWriter.BloggerAgent");

private readonly ILogger<BloggerAgent> _logger;

public BloggerAgent(IChatClient llm, ChatOptions chatOptions, ILogger<BloggerAgent> logger)
{
_logger = logger;

_agent = new ChatClientAgent(llm, new ChatClientAgentOptions
{
Name = "Blogger",
Expand All @@ -34,10 +44,14 @@ public BloggerAgent(IChatClient llm, ChatOptions chatOptions)
MaxOutputTokens = chatOptions.MaxOutputTokens,
},
});
_logger.LogInformation("BloggerAgent initialized.");
}

public async Task<BloggerDecision> InvokeAsync(ResearchState state)
{
using Activity? activity = s_activitySource.StartActivity("Blogger.Invoke");
activity?.SetTag("blog.revision", state.RevisionNumber);

List<string> research = state.ResearchFindings;
string researchText = research.Count > 0 ? string.Join("\n", research) : "No research yet.";
int revision = state.RevisionNumber;
Expand Down
34 changes: 29 additions & 5 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.ClientModel;
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using BlogWriter;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using OpenAI;

const string fileName = "config.json";
Expand Down Expand Up @@ -73,12 +75,29 @@
description: "A search engine optimized for comprehensive, accurate, and trusted results.");

// Creating a callable object
var bloggerAgent = new BloggerAgent(llm, chatOptions);
var researcherAgent = new ResearcherAgent(llm, chatOptions, tavilyTool);
var authorAgent = new AuthorAgent(llm, chatOptions);
var reviewerAgent = new ReviewerAgent(llm, chatOptions);
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());

var bloggerAgent = new BloggerAgent(llm, chatOptions, loggerFactory.CreateLogger<BloggerAgent>());
var researcherAgent = new ResearcherAgent(llm, chatOptions, tavilyTool, loggerFactory.CreateLogger<ResearcherAgent>());
var authorAgent = new AuthorAgent(llm, chatOptions, loggerFactory.CreateLogger<AuthorAgent>());
var reviewerAgent = new ReviewerAgent(llm, chatOptions, loggerFactory.CreateLogger<ReviewerAgent>());
var app = new BlogWorkflow(bloggerAgent, researcherAgent, authorAgent, reviewerAgent);

// Distributed tracing: an ActivityListener activates every "BlogWriter.*"
// ActivitySource in the app (agents + workflow) and writes span start/stop to
// the console. Swap this listener for OpenTelemetry's TracerProvider (and chain
// IChatClient.UseOpenTelemetry() above) to export the same spans instead.
var appActivitySource = new ActivitySource("BlogWriter.Program");

ActivitySource.AddActivityListener(new ActivityListener
{
ShouldListenTo = source => source.Name.StartsWith("BlogWriter", StringComparison.Ordinal),
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
ActivityStarted = activity => Console.WriteLine($"[trace] \u2192 {activity.DisplayName}"),
ActivityStopped = activity =>
Console.WriteLine($"[trace] \u2190 {activity.DisplayName} ({activity.Duration.TotalMilliseconds:F0} ms)")
});
Comment on lines +90 to +99

Console.Write("Enter your topic: ");
string topic = Console.ReadLine() ?? string.Empty;

Expand All @@ -88,7 +107,12 @@
MainTask = topic
};

ResearchState result = await app.RunAsync(initialState);
ResearchState result;
using (Activity? runActivity = appActivitySource.StartActivity("BlogWriter.Run"))
{
runActivity?.SetTag("blog.topic", topic);
result = await app.RunAsync(initialState);
}

Console.WriteLine("\n========== RESULTS ==========");
Console.WriteLine($"Task: {result.MainTask}");
Expand Down
17 changes: 16 additions & 1 deletion ResearcherAgent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Diagnostics;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;

namespace BlogWriter;

Expand All @@ -17,8 +19,16 @@ public class ResearcherAgent : IResearcherAgent
// per-call behaviour while gaining tool-calling for free.
private readonly ChatClientAgent _agent;

public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavilyTool)
// Emits a span per research turn. Activated by the ActivityListener
// registered in Program.cs (or an OpenTelemetry TracerProvider).
private static readonly ActivitySource s_activitySource = new("BlogWriter.ResearcherAgent");

private readonly ILogger<ResearcherAgent> _logger;

public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavilyTool, ILogger<ResearcherAgent> logger)
{
_logger = logger;

_agent = new ChatClientAgent(llm, new ChatClientAgentOptions
{
// Name surfaces in OpenTelemetry traces and agent logs.
Expand All @@ -35,11 +45,16 @@ public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavi
Tools = [tavilyTool],
},
});

_logger.LogInformation("ResearcherAgent initialized with Tavily tool: {ToolName}", tavilyTool.Name);
}

/// <summary>Execute research by letting the agent search and summarise.</summary>
public async Task<string> InvokeAsync(string query)
{
using Activity? activity = s_activitySource.StartActivity("Researcher.Invoke");
activity?.SetTag("blog.query", query);

try
{
// A single agent run: the model may call tavily_search one or more
Expand Down
16 changes: 15 additions & 1 deletion ReviewerAgent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Diagnostics;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;

namespace BlogWriter;

Expand All @@ -14,8 +16,16 @@ public class ReviewerAgent : IReviewerAgent
{
private readonly ChatClientAgent _agent;

public ReviewerAgent(IChatClient llm, ChatOptions chatOptions)
// Emits a span per review. Activated by the ActivityListener registered in
// Program.cs (or an OpenTelemetry TracerProvider).
private static readonly ActivitySource s_activitySource = new("BlogWriter.ReviewerAgent");

private readonly ILogger<ReviewerAgent> _logger;

public ReviewerAgent(IChatClient llm, ChatOptions chatOptions, ILogger<ReviewerAgent> logger)
{
_logger = logger;

_agent = new ChatClientAgent(llm, new ChatClientAgentOptions
{
Name = "Reviewer",
Expand All @@ -26,10 +36,14 @@ public ReviewerAgent(IChatClient llm, ChatOptions chatOptions)
MaxOutputTokens = chatOptions.MaxOutputTokens,
},
});
_logger.LogInformation("ReviewerAgent initialized.");
}

public async Task<string> InvokeAsync(ResearchState state)
{
using Activity? activity = s_activitySource.StartActivity("Reviewer.Invoke");
activity?.SetTag("blog.revision", state.RevisionNumber);

string draft = state.Draft;
int revisionNum = state.RevisionNumber;

Expand Down