Microsoft Agent Framework, OpenRouter, 그리고 gpt-oss 함께 사용하기

10월이 되자마자 새롭게 출시한 Microsoft Agent Framework는 익히 알려진대로 Microsoft Semantic Kernel과 Autogen을 결합한 것으로 이제 .NET에서도 Agent 개발을 좀 더 쉽고 간편하게 할 수 있게 된 점이 매력적입니다.

Microsoft.Extensions.AI의 IChatClient 인터페이스를 구현하는 주요 AI 클라이언트들과 모두 호환될 수 있게 디자인되어 있기 때문에, Azure와 OpenAI는 물론, Bedrock, Google Vertex 등 다른 모든 AI 클라이언트와도 상호 작용이 가능합니다.

개인적으로는 테스트 단계에서 비용을 지출하지 않는 것을 선호하기 때문에 OpenRouter의 무료 버전 모델을 사용하는 것을 무척 중요하게 생각하는데, 다행이 잘 지원됩니다. 아래는 간단한 예제 코드입니다.

var collections = new ServiceCollection();
collections.AddHttpClient("OpenRouter")
    .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler())
	.AddHttpMessageHandler(() => new OpenRouterHeaderManipHandler())
	.AddDefaultLogger();

var services = collections.BuildServiceProvider();
var clientFactory = services.GetRequiredService<IHttpClientFactory>();

var url = new Uri("https://openrouter.ai/api/v1/", UriKind.Absolute);
var credential = new ApiKeyCredential(Util.GetPassword("openai-testkey"));
var transport = new HttpClientPipelineTransport(clientFactory.CreateClient("OpenRouter"));
var chatClient = new OpenAIClient(credential, new OpenAIClientOptions
{
    Endpoint = url,
	Transport = transport,
});
var agent = new ChatClientAgent(
	chatClient.GetChatClient("openai/gpt-oss-20b:free").AsIChatClient(),
	instructions: "You are good at telling jokes.",
	name: "Joker");
var result = await agent.RunAsync();
foreach (var eachMessage in result.Messages)
	Console.WriteLine(eachMessage.ToString());

public sealed class OpenRouterHeaderManipHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var openaiBetaHeader = "OpenAI-Beta";

        if (request.Headers.Contains(openaiBetaHeader))
            request.Headers.Remove(openaiBetaHeader);

        return base.SendAsync(request, cancellationToken);
	}
}
 

위의 코드는 Microsoft.Agents.AI (Preview), Microsoft.Extensions.AI.OpenAI (Preview), Microsoft.Extensions.DependencyInjection, Microsoft.Extensions.Http 4개의 nuget 패키지가 필요합니다.

ps. 이 코드를 dotnet-fba-templates에 추가해보려 합니다.

2 Likes

Agent Framework를 사용하는 더 확실한 이유는 역시 Workflow일 것입니다. :smiley:

#!/usr/bin/env dotnet

#:package Microsoft.Agents.AI.Workflows@1.0.0-preview.251007.1
#:package Microsoft.Extensions.AI.OpenAI@9.9.1-preview.1.25474.6
#:package Microsoft.Extensions.DependencyInjection@9.0.*
#:package Microsoft.Extensions.Http@9.0.*

#:property PublishAot=False

using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using OpenAI;
using System.ClientModel;
using System.ClientModel.Primitives;

// https://devblogs.microsoft.com/dotnet/introducing-microsoft-agent-framework-preview/

var collections = new ServiceCollection();
collections.AddHttpClient("OpenRouter")
	.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler())
	.AddHttpMessageHandler(() => new OpenRouterHeaderManipHandler())
	.AddDefaultLogger();

var services = collections.BuildServiceProvider();
var clientFactory = services.GetRequiredService<IHttpClientFactory>();

var openRouterApiKey = Environment.GetEnvironmentVariable("OPENROUTER_APIKEY") ??
	throw new Exception("OPENROUTER_APIKEY is required.");
var openRouterModel = Environment.GetEnvironmentVariable("OPENROUTER_MODEL") ??
	"openai/gpt-oss-20b:free";
var url = new Uri("https://openrouter.ai/api/v1/", UriKind.Absolute);
var credential = new ApiKeyCredential(openRouterApiKey);
var transport = new HttpClientPipelineTransport(clientFactory.CreateClient("OpenRouter"));
var chatClient = new OpenAIClient(credential, new OpenAIClientOptions
{
	Endpoint = url,
	Transport = transport,
});
var writer = new ChatClientAgent(
	chatClient.GetChatClient(openRouterModel).AsIChatClient(),
	name: "Writer",
	instructions: "Write stories that are engaging and creative.");
var editor = new ChatClientAgent(
	chatClient.GetChatClient(openRouterModel).AsIChatClient(),
	instructions: "Make the story more engaging, fix grammar, and enhance the plot.",
	name: "Editor");

var workflow = AgentWorkflowBuilder.BuildSequential(writer, editor);
var workflowAgent = await workflow.AsAgentAsync(name: "StoryWriters");

var result = await workflowAgent.RunAsync("Write a short story about a haunted house.");
Console.WriteLine(result.Text);

public sealed class OpenRouterHeaderManipHandler : DelegatingHandler
{
	protected override Task<HttpResponseMessage> SendAsync(
		HttpRequestMessage request, CancellationToken cancellationToken)
	{
		var openaiBetaHeader = "OpenAI-Beta";

		if (request.Headers.Contains(openaiBetaHeader))
			request.Headers.Remove(openaiBetaHeader);

		return base.SendAsync(request, cancellationToken);
	}
}

1 Like