Да създадем първия си Agent с .NET
В предишната секция написахме Agent на ръка и видяхме колко ръчна работа е това. Сега ще изградим същото с библиотека, която поема тежката работа вместо нас.
В този урок ще създадеш първия си истински Agent: Alfred, който работи изцяло на твоя компютър и може да извиква C# методи, за да отговаря на въпроси.
Какво е Microsoft.Extensions.AI?
Секция „Какво е Microsoft.Extensions.AI?“Microsoft.Extensions.AI е библиотеката на Microsoft за работа с LLMs в .NET. Тя е малка, но покрива точно нещата, които в предишния урок писахме сами:
| В Dummy Agent го писахме сами | С Microsoft.Extensions.AI |
|---|---|
Ollama.cs: заявки към модела с HttpClient |
IChatClient: един интерфейс за всеки доставчик на LLM |
ChatMessage.cs: наш record за един message |
ChatMessage: готов тип със същото име |
| Описание на tool-а, написано на ръка в system prompt-а | AIFunctionFactory: прави tool от обикновен C# метод |
Tools.Run и while цикълът |
UseFunctionInvocation(): изпълнява целия цикъл автоматично |
Има и една разлика под повърхността. Нашият dummy Agent беше JSON Agent: молехме модела да пише Action: {...} в текста и сами го парсвахме. Библиотеката използва function calling (виж Actions): моделът връща извикването на tool като структурирани данни, така че няма формат, който да се обърка.
Да изградим Alfred
Секция „Да изградим Alfred“Ще ни трябват Ollama и моделът от предишния урок. Увери се, че Ollama работи, преди да започнеш.
1. Създай проекта
Секция „1. Създай проекта“Отвори терминал и изпълни:
dotnet new console -n FirstAgentcd FirstAgentdotnet add package Microsoft.Extensions.AIdotnet add package OllamaSharpПървите две команди създават проекта, а другите две добавят двата NuGet пакета. OllamaSharp е клиентът за Ollama. Той реализира интерфейса IChatClient, така че останалият код не зависи от Ollama. Ако някой ден смениш доставчика на модела, променяш един ред.
Отвори папката FirstAgent в редактора си. Ще работим с три файла.
2. Напиши tool-а
Секция „2. Напиши tool-а“Създай файл AlfredTools.cs. В него е единственият tool на Alfred: метод, който връща текущия час в дадена часова зона.
using System.ComponentModel;
public static class AlfredTools{ [Description("Gets the current local time in a given timezone.")] public static string GetCurrentTime( [Description("An IANA timezone name, for example 'Europe/Sofia'.")] string timezone) { try { TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(timezone); DateTime now = TimeZoneInfo.ConvertTime(DateTime.UtcNow, zone);
return $"The current time in {timezone} is {now:yyyy-MM-dd HH:mm:ss}"; } catch (TimeZoneNotFoundException) { return $"Unknown timezone '{timezone}'."; } }}Три неща правят един метод добър tool:
- Атрибут
[Description]върху метода и върху всеки параметър. Това е всичко, което моделът знае за tool-а (виж Tools). - Ясни типове на параметрите и на резултата.
- Грешките се връщат като текст. Ако моделът подаде невалидна часова зона, той ще прочете съобщението като Observation и може да опита отново.
3. Създай Agent-а
Секция „3. Създай Agent-а“Отвори Program.cs, изтрий съдържанието му и сложи следното:
using Microsoft.Extensions.AI;using OllamaSharp;
const string Model = "qwen3.5:9b";
const string SystemPrompt = "You are Alfred, a helpful and polite AI assistant. " + "Use your tools when they can help and never invent their results.";
IChatClient model = new OllamaApiClient(new Uri("http://localhost:11434"), Model);
IChatClient agent = new ChatClientBuilder(model) .UseFunctionInvocation() .Build();
var options = new ChatOptions{ Temperature = 0, Tools = [AIFunctionFactory.Create(AlfredTools.GetCurrentTime)],};
List<ChatMessage> history =[ new(ChatRole.System, SystemPrompt), new(ChatRole.User, "What time is it in Tokyo right now?"),];
ChatResponse response = await agent.GetResponseAsync(history, options);
AgentLog.PrintSteps(response);Console.WriteLine($"Alfred: {response.Text}");Кодът има четири части:
- Моделът.
OllamaApiClientе връзката с локалния модел, достъпна през интерфейсаIChatClient. Той заема мястото на нашияOllama.cs. - Agent-ът.
UseFunctionInvocation()обвива модела и добавя цикъла Thought → Action → Observation. Когато моделът поиска tool, библиотеката го извиква, връща резултата и пита модела отново, докато той не даде финален отговор. За да не се върти безкрайно, цикълът има лимит от 40 обиколки на заявка. - Tools.
AIFunctionFactory.Createчете името на метода, атрибутите[Description]и типовете на параметрите и ги подава на модела. - Разговорът.
historyе списъкът от messages: system prompt-ът и един въпрос.GetResponseAsyncго изпраща на Agent-а и връща отговора.
Редът AgentLog.PrintSteps(response) още не се компилира, защото този метод не съществува. Ще го напишем в следващата стъпка.
4. Покажи стъпките на цикъла
Секция „4. Покажи стъпките на цикъла“Библиотеката изпълнява целия цикъл вътре в GetResponseAsync. За да видим какво се е случило, ще отпечатаме стъпките, през които Agent-ът е минал. Създай файл AgentLog.cs:
using System.Text.Json;using Microsoft.Extensions.AI;
public static class AgentLog{ public static void PrintSteps(ChatResponse response) { foreach (AIContent content in response.Messages.SelectMany(m => m.Contents)) { switch (content) { case TextReasoningContent thought: Console.WriteLine($" Thought: {thought.Text}"); break; case FunctionCallContent call: string arguments = JsonSerializer.Serialize(call.Arguments); Console.WriteLine($" Action: {call.Name}({arguments})"); break; case FunctionResultContent result: Console.WriteLine($" Observation: {result.Result}"); break; } } }}response.Messages съдържа всичко, което Agent-ът е произвел по пътя към отговора. Всяка стъпка от цикъла идва като отделен .NET тип:
| Стъпка | Тип в Microsoft.Extensions.AI |
|---|---|
| Thought | TextReasoningContent |
| Action | FunctionCallContent |
| Observation | FunctionResultContent |
| Финален отговор | TextContent (достъпен и през response.Text) |
5. Стартирай програмата
Секция „5. Стартирай програмата“В терминала, в папката FirstAgent, изпълни:
dotnet runизход:
Thought: The user is asking for the current time in Tokyo. I need to use the GetCurrentTime function with the appropriate timezone for Tokyo. Tokyo is in the Asia/Tokyo timezone. Action: GetCurrentTime({"timezone":"Asia/Tokyo"}) Observation: The current time in Asia/Tokyo is 2026-11-12 20:32:31 Thought: The function returned the current time in Tokyo as 2026-11-12 20:32:31. I should present this information clearly to the user.Alfred: The current time in Tokyo is November 12, 2026 at 8:32:31 PM (20:32:31).При теб часът и точните думи ще са различни, но стъпките ще са същите. Ето какво се случи вътре в GetResponseAsync:
- Библиотеката изпрати на модела разговора заедно с описанието на
GetCurrentTime. - Моделът поиска tool-а с аргумент
Asia/Tokyo. Това е редътAction. - Библиотеката извика истинския метод
AlfredTools.GetCurrentTimeи върна резултата на модела. Това е редътObservation. - Моделът прочете резултата и написа финалния отговор.
Това е същият цикъл от Dummy Agent Library, но в нашия код няма нито stop, нито парсване, нито while.
6. Превърни го в разговор
Секция „6. Превърни го в разговор“Засега Alfred отговаря на един въпрос, записан в кода. В Program.cs замени всичко от реда List<ChatMessage> history надолу със следното:
List<ChatMessage> history = [new(ChatRole.System, SystemPrompt)];
Console.WriteLine("Alfred is ready. Ask a question (or press Enter to exit).");
while (true){ Console.Write("\nYou: "); string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input)) { break; }
history.Add(new ChatMessage(ChatRole.User, input));
ChatResponse response = await agent.GetResponseAsync(history, options); history.AddMessages(response);
AgentLog.PrintSteps(response); Console.WriteLine($"Alfred: {response.Text}");}Този while не е цикълът на Agent-а. Той само чете въпроси от конзолата. Важните редове са двата с history: моделът не помни нищо между заявките, затова добавяме в списъка всеки въпрос и всичко, което Agent-ът е отговорил.
Изпълни dotnet run и задай на Alfred три въпроса:
Alfred is ready. Ask a question (or press Enter to exit).
You: What time is it in Tokyo right now? Thought: The user is asking for the current time in Tokyo. I need to use the GetCurrentTime function with the appropriate timezone for Tokyo. Tokyo is in the Asia/Tokyo timezone. Action: GetCurrentTime({"timezone":"Asia/Tokyo"}) Observation: The current time in Asia/Tokyo is 2026-11-12 20:33:16 Thought: The function returned the current time in Tokyo. I should present this information clearly to the user.Alfred: The current time in Tokyo is 8:33 PM on November 12, 2026 (20:33:16).
You: And in Sofia? Thought: The user is asking for the current time in Sofia. I need to use the GetCurrentTime function with the timezone "Europe/Sofia" since Sofia is in Bulgaria which uses the Europe/Sofia timezone. Action: GetCurrentTime({"timezone":"Europe/Sofia"}) Observation: The current time in Europe/Sofia is 2026-11-12 13:33:43 Thought: The function returned the current time in Sofia as 2026-11-12 13:33:43. I should present this information clearly to the user, converting it to a more readable format if needed.Alfred: The current time in Sofia is 1:33 PM on November 12, 2026 (13:33:43).
You: Hello, who are you? Thought: The user is asking me who I am. I should introduce myself as Alfred, a helpful and polite AI assistant. This doesn't require any tool calls - it's just a conversational response.Alfred: Hello! I'm Alfred, your helpful and polite AI assistant. How can I help you today?Обърни внимание на две неща:
- Вторият въпрос е само „And in Sofia?“. Alfred го разбира, защото заедно с него получава целия разговор от
history. - За третия въпрос не е нужен tool, затова Alfred отговаря директно, без Action. Моделът сам преценява кога да използва tools.
Твой ред
Секция „Твой ред“Alfred има само един tool. Най-добрият начин да учиш е да му дадеш още. За начало добави в класа AlfredTools метода Calculator от секцията за Tools:
[Description("Multiply two integers.")]public static int Calculator(int a, int b) => a * b;и го регистрирай в Program.cs:
Tools =[ AIFunctionFactory.Create(AlfredTools.GetCurrentTime), AIFunctionFactory.Create(AlfredTools.Calculator),],Попитай „What is 123 times 456?“ и ще видиш Action: Calculator({"a":123,"b":456}), последван от Observation: 56088.
След това опитай със свои идеи: tool, който чете файл, търси в интернет с HttpClient или проверява времето през публичен API.
Поздравления, изгради първия си Agent! 🎉
Нормално е в началото той да е малко бавен или да греши. В следващите Units ще научим как да изграждаме по-добри Agents.
В следващата секция те очаква финалният quiz за Unit 1.