-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
131 lines (115 loc) · 5.18 KB
/
Copy pathProgram.cs
File metadata and controls
131 lines (115 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Chat;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetryLangFuseRepro;
var builder = WebApplication.CreateBuilder(args);
var cfg = builder.Configuration;
const string aiTelemetrySource = "Microsoft.Extensions.AI";
const string agentTelemetrySource = "Microsoft.Agents.AI";
string apiKey = RequireConfig(cfg, "LlmProvider:ApiKey");
string endpoint = RequireConfig(cfg, "LlmProvider:Endpoint");
string modelId = RequireConfig(cfg, "LlmProvider:Model");
string lfHost = RequireConfig(cfg, "Langfuse:HostUrl");
string lfPub = RequireConfig(cfg, "Langfuse:PublicKey");
string lfSec = RequireConfig(cfg, "Langfuse:SecretKey");
string version = RequireConfig(cfg, "App:Version");
string release = RequireConfig(cfg, "App:Release");
builder.Services.AddHttpClient("MyLlmProvider");
builder.Services.AddOpenTelemetry().ConfigureResource(r => r
.AddService(serviceName: "langfuse-sandbox", serviceVersion: version))
.WithTracing(t => t
.AddSource(aiTelemetrySource)
.AddSource(agentTelemetrySource)
.AddAspNetCoreInstrumentation()
.AddProcessor(new LangfuseBaggageProcessor())
.AddConsoleExporter()
.AddOtlpExporter(o =>
{
o.Endpoint = new Uri($"{lfHost.TrimEnd('/')}/api/public/otel/v1/traces");
o.Protocol = OtlpExportProtocol.HttpProtobuf;
var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{lfPub}:{lfSec}"));
//o.Headers = $"Authorization=Basic {auth}";
o.HttpClientFactory = () =>
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
return client;
};
}));
// IChatClient: OpenAI-compatible client pointed, with gen_ai OpenTelemetry
builder.Services.AddSingleton<IChatClient>(sp =>
{
var http = sp.GetRequiredService<IHttpClientFactory>().CreateClient("MyLlmProvider");
var options = new OpenAIClientOptions
{
Endpoint = new Uri(endpoint),
Transport = new HttpClientPipelineTransport(http),
};
ChatClient chat = new OpenAIClient(new ApiKeyCredential(apiKey), options).GetChatClient(modelId);
IChatClient inner = chat.AsIChatClient();
return new ChatClientBuilder(inner)
.UseOpenTelemetry(sourceName: aiTelemetrySource, configure: c => c.EnableSensitiveData = true)
.Build();
});
// Simple C# coding agent, wrapped with the Agent Framework's OpenTelemetry instrumentation
builder.Services.AddSingleton<AIAgent>(sp =>
{
var chat = sp.GetRequiredService<IChatClient>();
var baseAgent = new ChatClientAgent(
chatClient: chat,
instructions: "You are a concise C# coding assistant. Give accurate, modern .NET 10 C# advice. Prefer minimal, idiomatic code. Use short code blocks when helpful.",
name: "CSharpHelper");
return new OpenTelemetryAgent(baseAgent, agentTelemetrySource) { EnableSensitiveData = true };
});
var app = builder.Build();
// POST /chat -> streams the agent reply as SSE
app.MapPost("/chat", async (HttpContext ctx, AIAgent agent) =>
{
var req = await ctx.Request.ReadFromJsonAsync<ChatRequest>(ctx.RequestAborted) ?? new ChatRequest("", null, null, null);
string userId = string.IsNullOrWhiteSpace(req.UserId) ? "anonymous" : req.UserId;
string sessionId = string.IsNullOrWhiteSpace(req.SessionId) ? Guid.NewGuid().ToString() : req.SessionId;
string[] tags = req.Tags is { Length: > 0 } t ? t : ["sandbox", "csharp-agent"];
var attrs = new Dictionary<string, object?>
{
["langfuse.user.id"] = userId,
["langfuse.session.id"] = sessionId,
["langfuse.version"] = version,
["langfuse.release"] = release,
["langfuse.trace.name"] = "chat",
["langfuse.trace.tags"] = tags,
["langfuse.trace.metadata.source"] = "aspnet-webapi",
};
if (Activity.Current is { } root)
foreach (var kv in attrs)
root.SetTag(kv.Key, kv.Value);
LangfuseContext.Current = attrs;
ctx.Response.ContentType = "text/event-stream";
ctx.Response.Headers["Cache-Control"] = "no-cache";
var ct = ctx.RequestAborted;
await foreach (var update in agent.RunStreamingAsync(req.Message).WithCancellation(ct))
{
var text = update.Text ?? "";
if (text.Length == 0) continue;
await ctx.Response.WriteAsync("data: ", ct);
await ctx.Response.WriteAsync(JsonSerializer.Serialize(text), ct);
await ctx.Response.WriteAsync("\n\n", ct);
await ctx.Response.Body.FlushAsync(ct);
}
await ctx.Response.WriteAsync("data: [DONE]\n\n", ct);
await ctx.Response.Body.FlushAsync(ct);
});
app.Run();
static string RequireConfig(IConfiguration cfg, string key) =>
cfg[key] is { Length: > 0 } s
? s
: throw new InvalidOperationException($"Missing required configuration value: '{key}'");
public sealed record ChatRequest(string Message, string? UserId, string? SessionId, string[]? Tags);