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
4 changes: 4 additions & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step01_MeetYourClaw/Claw_Step01_MeetYourClaw.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData/Claw_Step02_WorkingWithData.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/Claw_Step03_ScalingCapabilities.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/ClawAgent.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent.Console/ClawAgent.Console.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent.Hosted/ClawAgent.Hosted.csproj" />
<Project Path="samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent.Evals/ClawAgent.Evals.csproj" />
<Project Path="samples/02-agents/Harness/ConsoleReactiveComponents/ConsoleReactiveComponents.csproj" />
<Project Path="samples/02-agents/Harness/ConsoleReactiveFramework/ConsoleReactiveFramework.csproj" />
<Project Path="samples/02-agents/Harness/Harness_Shared_Console/Harness_Shared_Console.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);OPENAI001;MAAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry" />
<PackageReference Include="OpenTelemetry.Exporter.Console" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ClawAgent\ClawAgent.csproj" />
<ProjectReference Include="..\..\..\Harness_Shared_Console\Harness_Shared_Console.csproj" />
<ProjectReference Include="..\..\..\Harness_Shared_Console_OpenAI\Harness_Shared_Console_OpenAI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Diagnostics.Metrics;
using ClawAgent;
using Harness.Shared.Console;
using Harness.Shared.Console.OpenAI;
using Harness.Shared.Console.ToolFormatters;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

const string ServiceName = "ClawAgent.Console";
var otlpEndpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT");
var telemetryEnabled = !string.IsNullOrWhiteSpace(otlpEndpoint);

// Export telemetry only when an OTLP endpoint is configured. We deliberately avoid the
// console exporter: this is an interactive app whose UI is rendered by
// HarnessConsole.RunAgentAsync, and streaming spans/metrics to stdout corrupts that UI.
var resourceBuilder = ResourceBuilder.CreateDefault().AddService(ServiceName, serviceVersion: "1.0.0");
using var tracerProvider = telemetryEnabled
? Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddSource(ClawAgentFactory.OpenTelemetrySourceName)
.AddHttpClientInstrumentation()
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint!))
.Build()
: null;

using var meterProvider = telemetryEnabled
? Sdk.CreateMeterProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddMeter(ClawAgentFactory.OpenTelemetrySourceName)
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint!))
.Build()
: null;

if (!telemetryEnabled)
{
Console.WriteLine("Telemetry export is off. Set OTEL_EXPORTER_OTLP_ENDPOINT to send traces/metrics to an OTLP collector.");
}

using var meter = new Meter(ClawAgentFactory.OpenTelemetrySourceName);
var sessionCounter = meter.CreateCounter<int>("claw_console_sessions_total", description: "Interactive claw console sessions started.");
sessionCounter.Add(1);

await using ClawAgentBuild build = await ClawAgentFactory.CreateAsync(new ClawAgentFactoryOptions
{
Log = Console.WriteLine,
});

await HarnessConsole.RunAgentAsync(
build.Agent,
userPrompt: "Ask me to value a stock, score your portfolio risk, research some tickers, or tidy your trade confirmations.",
new HarnessConsoleOptions
{
Observers =
[
new OpenAIResponsesWebSearchDisplayObserver(),
new OpenAIResponsesErrorObserver(),
.. HarnessConsoleOptions.BuildObserversWithPlanning(
build.Agent,
planModeName: "plan",
executionModeName: "execute",
toolFormatters: ToolCallFormatter.BuildDefaultToolFormatters()),
],
CommandHandlers = HarnessConsoleOptions.BuildDefaultCommandHandlers(build.Agent),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# ClawAgent.Console

Interactive local host for the production-ready claw. It uses the shared `ClawAgentFactory` and the Step 03 console experience (`HarnessConsole.RunAgentAsync`) with planning observers and OpenAI Responses display helpers.

## Run

```bash
cd dotnet
dotnet run --project samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent.Console
```

Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send traces/metrics to an OTLP collector (for example a local Aspire dashboard). When it is not set, telemetry is not exported — there is no console exporter, because streaming spans and metrics to stdout would corrupt the interactive UI rendered by `HarnessConsole.RunAgentAsync`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);OPENAI001;MAAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\ClawAgent\ClawAgent.csproj" />
<ProjectReference Include="..\..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.RegularExpressions;
using Azure.AI.Projects;
using Azure.Identity;
using ClawAgent;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI.Evaluation;
using FoundryEvals = Microsoft.Agents.AI.Foundry.FoundryEvals;

string[] queries =
[
"What's the capital of France?",
"Value MSFT for me.",
"How risky is my portfolio?",
];

await using ClawAgentBuild build = await ClawAgentFactory.CreateAsync(new ClawAgentFactoryOptions
{
// Evals run only the trusted skill scripts bundled with this sample. Auto-approve those scripts
// so evaluation receives the completed answer instead of an approval request.
AdditionalToolAutoApprovalRules = [AgentSkillsProvider.AllToolsAutoApprovalRule],
Log = Console.WriteLine,
});

Regex digitRegex = new(@"\d");
LocalEvaluator localEvaluator = new(
FunctionEvaluator.Create("off_topic_refusal_or_finance_steer", item =>
{
if (!item.Query.Contains("capital of France", StringComparison.OrdinalIgnoreCase))
{
return true;
}

return item.Response.Contains("finance", StringComparison.OrdinalIgnoreCase)
|| item.Response.Contains("invest", StringComparison.OrdinalIgnoreCase)
|| item.Response.Contains("portfolio", StringComparison.OrdinalIgnoreCase)
|| item.Response.Contains("outside", StringComparison.OrdinalIgnoreCase)
|| item.Response.Contains("can't", StringComparison.OrdinalIgnoreCase)
|| item.Response.Contains("cannot", StringComparison.OrdinalIgnoreCase);
}),
FunctionEvaluator.Create("numeric_valuation", item =>
!item.Query.Contains("Value MSFT", StringComparison.OrdinalIgnoreCase)
|| digitRegex.IsMatch(item.Response)),
FunctionEvaluator.Create("portfolio_risk_runs", item =>
!item.Query.Contains("portfolio", StringComparison.OrdinalIgnoreCase)
|| !string.IsNullOrWhiteSpace(item.Response)));

AgentEvaluationResults localResults = await build.Agent.EvaluateAsync(queries, localEvaluator, evalName: "ClawLocalFinanceEvals");
Comment thread
westey-m marked this conversation as resolved.
PrintResults("Local finance evals", localResults, queries);

string? endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT");
if (!string.IsNullOrWhiteSpace(endpoint))
{
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4";
AIProjectClient projectClient = new(new Uri(endpoint), new DefaultAzureCredential());
FoundryEvals foundryEvals = new(projectClient, deploymentName, FoundryEvals.Relevance, FoundryEvals.Coherence);
AgentEvaluationResults foundryResults = await build.Agent.EvaluateAsync(queries, foundryEvals, evalName: "ClawFoundryQualityEvals");
PrintResults("Foundry quality evals", foundryResults, queries);
}
else
{
Console.WriteLine("Skipping Foundry quality evals. Set FOUNDRY_PROJECT_ENDPOINT to enable them.");
}

static void PrintResults(string title, AgentEvaluationResults results, string[] queries)
{
Console.WriteLine($"=== {title} ===");
Console.WriteLine($"Provider: {results.ProviderName}");
Console.WriteLine($"Passed: {results.Passed}/{results.Total}");
if (results.ReportUrl is not null)
{
Console.WriteLine($"Report: {results.ReportUrl}");
}

Console.WriteLine();

for (int i = 0; i < results.Items.Count; i++)
{
Console.WriteLine($"Query: {(i < queries.Length ? queries[i] : "N/A")}");
Console.WriteLine($"Response: {(results.InputItems?[i].Response is { } response ? response[..Math.Min(80, response.Length)] : "N/A")}...");
foreach (var metric in results.Items[i].Metrics)
{
string value = metric.Value is NumericMetric numericMetric && numericMetric.Value.HasValue
? numericMetric.Value.Value.ToString("F1")
: metric.Value.Interpretation?.Failed == true ? "FAIL" : "PASS";
Console.WriteLine($" {metric.Key}: {value}");
}

Console.WriteLine();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ClawAgent.Evals

Evaluation host for the production-ready claw.

It builds the shared agent with `ClawAgentFactory`, runs local finance checks with `LocalEvaluator` and `FunctionEvaluator.Create(...)`, and prints `Passed`/`Total`. When `FOUNDRY_PROJECT_ENDPOINT` is available, it also runs Foundry quality evals (`FoundryEvals.Relevance` and `FoundryEvals.Coherence`).

The eval host auto-approves only Agent Skills tools so the trusted scripts bundled with this sample
can produce complete answers. Trades, shell commands, file writes, and unrelated tools remain subject
to their normal approval behavior.

## Run

```bash
cd dotnet
dotnet run --project samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent.Evals
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Files excluded from agent code deployment packaging.
# Uses .gitignore syntax.
# Note: only the root .agentignore is read; subdirectory files are not supported.
#
# To include a file that is excluded by default, use negation: !filename

# azd tooling files
agent.yaml
agent.manifest.yaml
azure.yaml
.agentignore

# Security / secrets
.env
.env.*
.azure/
.git/

# Python
__pycache__/
.venv/
venv/
*.pyc
*.pyo
.mypy_cache/
.pytest_cache/

# .NET
bin/
obj/
*.user
*.suo
.vs/

# Node
node_modules/

# Docker (not used in code deploy)
Dockerfile
.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.env
bin/
obj/
.vs/
.vscode/
*.user
.azure/
.checkpoints/
agent-file-memory/
*.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.azure
azure.yaml
Comment thread
moonbox3 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<NoWarn>$(NoWarn);OPENAI001;MAAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetEnv" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../ClawAgent/ClawAgent.csproj" />
<ProjectReference Include="../../../../../../src/Microsoft.Agents.AI.LocalCodeAct/Microsoft.Agents.AI.LocalCodeAct.csproj" />
<ProjectReference Include="../../../../../../src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="../../../../../../src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj" />
<ProjectReference Include="../../../../../04-hosting/FoundryHostedAgents/responses/Hosted_Shared_Contributor_Setup/Hosted_Shared_Contributor_Setup.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Dockerfile for the ClawAgent.Hosted Foundry Hosted Agent, built from the agent-framework repo source.
#
# This project uses ProjectReference to local repo sources (ClawAgent, Microsoft.Agents.AI.Foundry,
# Microsoft.Agents.AI.Foundry.Hosting, Microsoft.Agents.AI.LocalCodeAct) and to the repo's Central
# Package Management, so a standard in-container `dotnet restore`/`publish` cannot resolve everything
# from this folder alone. Instead, PRE-PUBLISH the app on your machine (inside the full repo, where the
# references and package versions resolve) and COPY the output into the image:
#
# # 1. Build and publish separately, targeting the container runtime (glibc x64):
# dotnet publish -c Release -f net10.0 -r linux-x64 --self-contained false -o out
#
# # 2. Then build the image from the pre-published output:
# docker build -t personal-finance-claw .
#
# # 3. (Optional) run it locally:
# docker run --rm -p 8088:8088 --env-file .env personal-finance-claw
#
# `azd deploy` performs step 2 for you (building remotely in Azure Container Registry) — but you must
# still run step 1 (publish to ./out) first. See README.md.
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app

# LocalCodeAct spawns Python to run and validate model-generated code, so the image needs python3.
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 \
&& rm -rf /var/lib/apt/lists/*

COPY out/ .
EXPOSE 8088
ENV ASPNETCORE_URLS=http://+:8088
ENV LOCAL_CODEACT_PYTHON=python3
# The container is non-interactive: never block on a console prompt for a missing setting.
ENV AF_DEMO_NONINTERACTIVE=1
ENTRYPOINT ["dotnet", "ClawAgent.Hosted.dll"]
Loading
Loading