diff --git a/assets/run.png b/assets/run.png new file mode 100644 index 0000000..b028f7f Binary files /dev/null and b/assets/run.png differ diff --git a/readme.md b/readme.md index 7150152..e82a7e4 100644 --- a/readme.md +++ b/readme.md @@ -1 +1,86 @@ -An opinionated meta-package for doing AI agents using Microsoft.Extensions.AI and MCP \ No newline at end of file +An opinionated meta-package for doing AI agents using Microsoft.Extensions.AI and MCP and dotnet run file. + +Examle Claude-based agent: + +![](https://raw.githubusercontent.com/devlooped/smith/main/assets/run.png) + +```csharp +#:package Smith@0.* + +var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets() + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddIniFile("appsettings.ini", optional: true, reloadOnChange: true) + .Build(); + +var services = new ServiceCollection(); + +services.AddHttpClient("ai").AddStandardResilienceHandler(); + +services.AddChatClient(services => new Anthropic.AnthropicClient( + configuration["Claude:Key"] ?? throw new InvalidOperationException("Missing Claude:Key configuration."), + services.GetRequiredService().CreateClient("ai"))) + .UseLogging() + .UseFunctionInvocation(); + +var provider = services.BuildServiceProvider(); +var history = new List { new ChatMessage(ChatRole.System, Prompts.System) }; +var chat = provider.GetRequiredService(); +var options = new ChatOptions +{ + ModelId = "claude-sonnet-4-20250514", + MaxOutputTokens = 1000, + Temperature = 0.7f, + Tools = [AIFunctionFactory.Create(() => DateTime.Now, "get_datetime", "Gets the current date and time on the user's local machine.")] +}; + +AnsiConsole.MarkupLine($":robot: Ready v{ThisAssembly.Info.Version}"); +AnsiConsole.Markup($":person_beard: "); +while (true) +{ + var input = Console.ReadLine()?.Trim(); + if (string.IsNullOrEmpty(input)) + continue; + + history.Add(new ChatMessage(ChatRole.User, input)); + + try + { + var response = await AnsiConsole.Status().StartAsync(":robot: Thinking...", + ctx => chat.GetResponseAsync(input, options)); + + history.AddRange(response.Messages); + // Try rendering as formatted markup + try + { + if (response.Text is { Length: > 0 }) + AnsiConsole.MarkupLine($":robot: {response.Text}"); + } + catch (Exception) + { + // Fallback to escaped markup text if rendering fails + AnsiConsole.MarkupInterpolated($":robot: {response.Text}"); + } + + AnsiConsole.WriteLine(); + AnsiConsole.Markup($":person_beard: "); + } + catch (Exception e) + { + AnsiConsole.WriteException(e); + } +} + +static class Prompts +{ + public const string System = + """ + Your responses will be rendered using Spectre.Console.AnsiConsole.Write(new Markup(string text))). + This means that you can use rich text formatting, colors, and styles in your responses, but you must + ensure that the text is valid markup syntax. + """; +} +``` + + \ No newline at end of file