|
| 1 | +using Microsoft.Extensions.AI; |
| 2 | + |
| 3 | +using System.Text.Json; |
| 4 | + |
| 5 | +namespace InvoicesBot.Console.Agent; |
| 6 | + |
| 7 | +/* |
| 8 | + * Notes: |
| 9 | + * For educational/learning purposes only. Not production grade. |
| 10 | + * This agent uses ToolCalls to interact with the InvoiceService. |
| 11 | + * InvoiceService is a simple in-memory service that simulates invoice data. Realworld applications would use a database or external service. |
| 12 | + * Prefer strongly typed models over objects for tool outputs. |
| 13 | + * Prefer serialized JSON over objects for tool outputs to save some tokens. |
| 14 | + * Always provide counts and sums in responses, AI is bad at counting/math. |
| 15 | + * Never return long lists of objects, always paginate or summarize to save tokens and avoid hallucinations. |
| 16 | + * This uses LINQ.Dynamic for dynamic filtering and grouping. Can also be done manually using expressions or reflection. |
| 17 | + * Do not add too many tools. Tools schema counts towards the token limit. |
| 18 | + * Use dynamic or RAG based tooling, Multi agents orchestration concepts if there are many tools or complex logic. |
| 19 | + * Chat history must be summarized beyond certain count. |
| 20 | + * Read more about prompt engineering and AI safety. |
| 21 | +*/ |
| 22 | +public interface IInvoicesAgent |
| 23 | +{ |
| 24 | + IAsyncEnumerable<string> GetResponseAsync(List<ChatMessage> messages, Guid threadId); |
| 25 | +} |
| 26 | +public class InvoicesAgent : IInvoicesAgent |
| 27 | +{ |
| 28 | + private readonly IChatClient chatClient; |
| 29 | + private readonly ToolBuilder toolBuilder; |
| 30 | + |
| 31 | + public InvoicesAgent([FromKeyedServices("OpenAI")] IChatClient chatClient, ToolBuilder toolBuilder) |
| 32 | + { |
| 33 | + this.chatClient=chatClient; |
| 34 | + this.toolBuilder=toolBuilder; |
| 35 | + } |
| 36 | + |
| 37 | + private ChatOptions GetChatOptions(string threadId) |
| 38 | + { |
| 39 | + return new ChatOptions |
| 40 | + { |
| 41 | + Temperature = 0.3f, |
| 42 | + Tools = [.. toolBuilder.Build()], |
| 43 | + MaxOutputTokens = 1024, |
| 44 | + ConversationId = threadId, |
| 45 | + AllowMultipleToolCalls = true, |
| 46 | + }; |
| 47 | + } |
| 48 | + |
| 49 | + |
| 50 | + public IAsyncEnumerable<string> GetResponseAsync(List<ChatMessage> messages, Guid threadId) |
| 51 | + { |
| 52 | + var systemMessage = new ChatMessage(ChatRole.System, InvoicesPromptBuilder.BuildSystemPrompt()); |
| 53 | + messages.Insert(0, systemMessage); |
| 54 | + //Console.WriteLine(systemMessage); |
| 55 | + var stream = chatClient.GetStreamingResponseAsync(messages, GetChatOptions(threadId.ToString())); |
| 56 | + return AIContentToStringAsync(stream); |
| 57 | + } |
| 58 | + //real world get messages history from db |
| 59 | + //private List<ChatMessage> GetMessages(string currentMessage) |
| 60 | + //{ |
| 61 | + // var chatHistory = new List<ChatMessage>(); |
| 62 | + // chatHistory.Add(new ChatMessage(ChatRole.System, SystemPromptBuilder.BuildSystemPrompt())); |
| 63 | + |
| 64 | + // //chatHistory.AddRange(previous.ToList().ConvertAll(pm => (ChatMessage)pm)); |
| 65 | + |
| 66 | + // chatHistory.Add(new ChatMessage(ChatRole.User, currentMessage)); |
| 67 | + // return chatHistory; |
| 68 | + //} |
| 69 | + private async IAsyncEnumerable<string> AIContentToStringAsync(IAsyncEnumerable<ChatResponseUpdate> stream) |
| 70 | + { |
| 71 | + await foreach(var update in stream) |
| 72 | + { |
| 73 | + foreach(var content in update.Contents) |
| 74 | + { |
| 75 | + switch(content) |
| 76 | + { |
| 77 | + case TextContent text: |
| 78 | + yield return text.Text; |
| 79 | + break; |
| 80 | + case FunctionCallContent toolCall: |
| 81 | + //yield return $"Tool call: {toolCall.Name} with arguments {JsonSerializer.Serialize(toolCall.Arguments)}"; |
| 82 | + break; |
| 83 | + default: |
| 84 | + break; |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
0 commit comments