Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions Examples/Examples/Agents/Skills/AgentWithCustomCodeSkillExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Data;
using System.Text.Json;
using Examples.Utils;
using MaIN.Core.Hub;
using MaIN.Domain.Entities.Skills;
using MaIN.Domain.Models;

namespace Examples.Agents.Skills;

/// <summary>
/// Demonstrates a custom code-based skill defined in the Examples project.
/// CalculatorSkill implements IAgentSkillProvider and is registered in Program.cs via
/// services.AddSingleton&lt;IAgentSkillProvider, CalculatorSkill&gt;(). The SkillRegistry
/// picks it up automatically at startup — no manual Register() call needed here.
/// This is the key difference from file-based skills: code skills can execute C# functions as tools.
/// </summary>
public class AgentWithCustomCodeSkillExample : IExample
{
public async Task Start()
{
Console.WriteLine("Agent with custom code-based skill (CalculatorSkill, OpenAi)");

OpenAiExample.Setup();

// CalculatorSkill is registered in Program.cs via DI (services.AddSingleton<IAgentSkillProvider, CalculatorSkill>();) and is already in the registry.
var context = await AIHub.Agent()
.WithModel(Models.OpenAi.Gpt4oMini)
.WithSkill("calculator")
.CreateAsync(interactiveResponse: true);

await context.ProcessAsync(
"A shop sells 3 items: apple $1.25, banana $0.80, cherry $3.40. " +
"I buy 4 apples, 7 bananas and 2 cherries. What is the total cost? " +
"Also, if I pay with $30, what is my change?");
}
}

/// <summary>
/// Custom code-based skill defined in the Examples project.
/// Gives the agent a "calculate" tool backed by a real C# function — something
/// .md file-based skills cannot do (they have no executable code).
/// </summary>
public class CalculatorSkill : IAgentSkillProvider
{
public AgentSkill GetSkill() => new()
{
Name = "calculator",
Description = "Gives the agent a precise calculation tool. Use when the prompt involves arithmetic.",
Version = "1.0.0",
Steps = ["ANSWER"],
StepPlacement = SkillStepPlacement.Before,
Priority = 20,
Tags = ["math", "tools", "calculator"],
Tools =
[
new SkillToolDefinition
{
Name = "calculate",
Description = "Evaluates a mathematical expression and returns the result. " +
"Supports +, -, *, /, %, ^ and parentheses.",
Parameters = new
{
type = "object",
properties = new
{
expression = new
{
type = "string",
description = "The math expression to evaluate, e.g. \"(12 + 8) * 3 / 4\""
}
},
required = new[] { "expression" }
},
Execute = async args =>
{
await Task.CompletedTask;
try
{
var doc = JsonDocument.Parse(args);
var expression = doc.RootElement.GetProperty("expression").GetString() ?? "";
var result = new DataTable().Compute(expression, null);
return $"{result}";
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
}
],
InstructionFragment =
"You have access to a precise calculator tool. " +
"For any arithmetic — no matter how simple — always call the calculate tool instead of computing mentally. " +
"Show the expression you used and the result."
};
}
30 changes: 30 additions & 0 deletions Examples/Examples/Agents/Skills/AgentWithFileSkillExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Examples.Utils;
using MaIN.Core.Hub;
using MaIN.Domain.Models;

namespace Examples.Agents.Skills;

/// <summary>
/// Demonstrates a skill loaded from a .md file in ./skills/ folder.
/// Drop any .md skill file there and it's auto-picked up on startup.
/// </summary>
public class AgentWithFileSkillExample : IExample
{
public async Task Start()
{
Console.WriteLine("Agent with file-based skill (OpenAi)");
Console.WriteLine("Looks for skills in ./skills/ directory...");

OpenAiExample.Setup();

// Skills from ./skills/*.md are auto-loaded if SkillsDirectory is configured,
// OR you can call AddSkillsFromDirectory() in startup.
var context = await AIHub.Agent()
.WithModel(Models.OpenAi.Gpt4oMini)
.WithSkill("web-search")
.WithSkill("file-journalist") // loaded from ./skills/file-journalist.md
.CreateAsync(interactiveResponse: true);

await context.ProcessAsync("Provide today's newsletter for poland");
}
}
38 changes: 38 additions & 0 deletions Examples/Examples/Agents/Skills/AgentWithFolderSkillExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Examples.Utils;
using MaIN.Core.Hub;
using MaIN.Domain.Models;

namespace Examples.Agents.Skills;

/// <summary>
/// Demonstrates a folder-based skill: skills/code-review/SKILL.md is the entrypoint,
/// prompts/ and examples/ subdirectories are loaded via the "includes" frontmatter key.
/// </summary>
public class AgentWithFolderSkillExample : IExample
{
public async Task Start()
{
Console.WriteLine("Agent with folder-based skill (code-review, OpenAi)");
Console.WriteLine("Skill loaded from: ./skills/code-review/SKILL.md");

OpenAiExample.Setup();

var context = await AIHub.Agent()
.WithModel(Models.OpenAi.Gpt4oMini)
.WithSkill("code-review") // name provided in name section in SKILL.md file
.CreateAsync(interactiveResponse: true);

await context.ProcessAsync("""
Review this code:
public List<string> GetNames(List<User> users)
{
List<string> names = new List<string>();
for (int i = 0; i < users.Count; i++)
{
names.Add(users[i].Name);
}
return names;
}
""");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Examples.Utils;
using MaIN.Core.Hub;
using MaIN.Domain.Models;

namespace Examples.Agents.Skills;

/// <summary>
/// Demonstrates a folder-based .md skill that wires up an MCP server.
/// The skill (skills/funfact-writer/SKILL.md) configures @modelcontextprotocol/server-filesystem
/// via its mcp: frontmatter — no C# required. The agent uses the MCP write_file tool
/// to create C:/Users/Public/funfacts/funfact.txt with a generated fun fact.
/// </summary>
public class AgentWithMcpFileWriterSkillExample : IExample
{
public async Task Start()
{
Console.WriteLine("Agent with MCP file-writer skill (.md, OpenAi)");
Console.WriteLine("Skill wires up @modelcontextprotocol/server-filesystem via SKILL.md frontmatter.");
Console.WriteLine("Output: C:/Users/Public/funfacts/funfact.txt");

OpenAiExample.Setup();

var context = await AIHub.Agent()
.WithModel(Models.OpenAi.Gpt4oMini)
.WithSkill("funfact-writer") // loaded from ./skills/funfact-writer/SKILL.md
.CreateAsync();

var result = await context.ProcessAsync("Generate a fun fact and save it to the file.");
Console.WriteLine(result.Message.Content);
}
}
27 changes: 27 additions & 0 deletions Examples/Examples/Agents/Skills/AgentWithSkillsExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Examples.Utils;
using MaIN.Core.Hub;
using MaIN.Domain.Models;

namespace Examples.Agents.Skills;

/// <summary>
/// Demonstrates skills applied via code — built-in "web-search" + "journalist".
/// Equivalent to AgentWithWebDataSourceOpenAiExample but with 2 lines instead of 15.
/// </summary>
public class AgentWithSkillsExample : IExample
{
public async Task Start()
{
Console.WriteLine("Agent with registered skills (code-based, OpenAi)");

OpenAiExample.Setup();

var context = await AIHub.Agent()
.WithModel(Models.OpenAi.Gpt4oMini)
.WithSkill("web-search") // FETCH_DATA step + BBC source
.WithSkill("journalist") // BECOME+Journalist + ANSWER steps + behaviour
.CreateAsync(interactiveResponse: true);

await context.ProcessAsync("Provide today's newsletter");
}
}
3 changes: 3 additions & 0 deletions Examples/Examples/Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,8 @@
<None Update="Files\Knowledge\people.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="skills\**\*.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
16 changes: 16 additions & 0 deletions Examples/Examples/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Examples;
using Examples.Agents;
using Examples.Agents.Flows;
using Examples.Agents.Skills;
using Examples.Chat;
using Examples.Mcp;
using MaIN.Core;
using MaIN.Domain.Entities.Skills;
using MaIN.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -30,6 +33,9 @@
services.AddSingleton<IConfiguration>(configuration);
services.AddMaIN(configuration);

services.AddSkillsFromDirectory("./skills");
services.AddSingleton<IAgentSkillProvider, CalculatorSkill>();

RegisterExamples(services);

var serviceProvider = services.BuildServiceProvider();
Expand Down Expand Up @@ -68,6 +74,11 @@ static void RegisterExamples(IServiceCollection services)
services.AddTransient<AgentsFlowLoadedExample>();
services.AddTransient<ChatExampleOpenAi>();
services.AddTransient<AgentWithWebDataSourceOpenAiExample>();
services.AddTransient<AgentWithSkillsExample>();
services.AddTransient<AgentWithFileSkillExample>();
services.AddTransient<AgentWithFolderSkillExample>();
services.AddTransient<AgentWithCustomCodeSkillExample>();
services.AddTransient<AgentWithMcpFileWriterSkillExample>();
services.AddTransient<ChatWithImageGenOpenAiExample>();
services.AddTransient<ChatExampleGemini>();
services.AddTransient<ChatGrammarExampleGemini>();
Expand Down Expand Up @@ -183,6 +194,10 @@ public class ExampleRegistry(IServiceProvider serviceProvider)
("\u25a0 OpenAi Chat", serviceProvider.GetRequiredService<ChatExampleOpenAi>()),
("\u25a0 OpenAi Chat with image", serviceProvider.GetRequiredService<ChatWithImageGenOpenAiExample>()),
("\u25a0 OpenAi Agent with Web Data Source", serviceProvider.GetRequiredService<AgentWithWebDataSourceOpenAiExample>()),
("\u25a0 Agent with Skills (file-based .md)", serviceProvider.GetRequiredService<AgentWithFileSkillExample>()),
("\u25a0 Agent with Skills (folder-based SKILL.md)", serviceProvider.GetRequiredService<AgentWithFolderSkillExample>()),
("\u25a0 Agent with Skills (custom C# skill)", serviceProvider.GetRequiredService<AgentWithCustomCodeSkillExample>()),
("\u25a0 Agent with Skills (MCP file writer)", serviceProvider.GetRequiredService<AgentWithMcpFileWriterSkillExample>()),
("\u25a0 Gemini Chat", serviceProvider.GetRequiredService<ChatExampleGemini>()),
("\u25a0 Gemini Chat with grammar", serviceProvider.GetRequiredService<ChatGrammarExampleGemini>()),
("\u25a0 Gemini Chat with image", serviceProvider.GetRequiredService<ChatWithImageGenGeminiExample>()),
Expand All @@ -195,6 +210,7 @@ public class ExampleRegistry(IServiceProvider serviceProvider)
("\u25a0 Ollama Chat", serviceProvider.GetRequiredService<ChatExampleOllama>()),
("\u25a0 McpClient example", serviceProvider.GetRequiredService<McpExample>()),
("\u25a0 McpAgent example", serviceProvider.GetRequiredService<McpAgentsExample>()),
("\u25a0 Mcp Anthropic example", serviceProvider.GetRequiredService<McpAnthropicExample>()),
("\u25a0 Chat with TTS example", serviceProvider.GetRequiredService<ChatWithTextToSpeechExample>()),
("\u25a0 McpAgent example", serviceProvider.GetRequiredService<McpAgentsExample>()),
("\u25a0 Chat with custom model ID", serviceProvider.GetRequiredService<ChatWithCustomModelIdExample>())
Expand Down
20 changes: 20 additions & 0 deletions Examples/Examples/skills/code-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: code-review
description: Reviews code for bugs, security issues, and improvements
version: 1.0.0
steps:
- ANSWER
placement: before
priority: 30
tags:
- code
- review
- quality
includes:
- prompts/review.md
- examples/bad.md
- examples/good.md
---

You are an expert code reviewer with deep knowledge of software engineering best practices.
Keep reviews concise and actionable.
12 changes: 12 additions & 0 deletions Examples/Examples/skills/code-review/examples/bad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Example: Problematic Code

```csharp
public string GetUser(int id) {
var conn = new SqlConnection("Server=prod;Password=admin123");
conn.Open();
var cmd = new SqlCommand("SELECT * FROM users WHERE id = " + id, conn);
return cmd.ExecuteScalar().ToString();
}
```

Expected issues: SQL injection, hardcoded connection string with password, no using/dispose, no null check on ExecuteScalar result, synchronous I/O.
14 changes: 14 additions & 0 deletions Examples/Examples/skills/code-review/examples/good.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Example: Well-Written Code

```csharp
public async Task<User?> GetUserAsync(int id, CancellationToken ct = default)
{
await using var conn = new SqlConnection(_connectionString);
return await conn.QueryFirstOrDefaultAsync<User>(
"SELECT * FROM users WHERE id = @id",
new { id },
cancellationToken: ct);
}
```

Why this is good: async, parameterized query prevents SQL injection, connection string from config, proper disposal via await using, nullable return type communicates that user may not exist.
14 changes: 14 additions & 0 deletions Examples/Examples/skills/code-review/prompts/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Review Guidelines

For every code snippet analyze:
- Bugs and logical errors
- Security vulnerabilities (injection, unvalidated input, hardcoded secrets)
- Performance (unnecessary allocations, missing async/await, N+1 queries)
- Readability (naming, complexity, magic numbers)
- Missing error handling and edge cases

Response format:

**Issues** — critical problems that must be fixed (numbered list)
**Suggestions** — improvements worth considering (numbered list)
**Verdict** — one sentence summary
21 changes: 21 additions & 0 deletions Examples/Examples/skills/file-journalist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: file-journalist
description: Journalist persona loaded from .md file — same as built-in journalist but defined in YAML
version: 1.0.0
steps:
- BECOME+Journalist
- ANSWER
placement: before
priority: 50
behaviours:
Journalist: "Based on data provided in chat, write a newsletter called MaIN_Letter. Be concise and factual."
tags:
- persona
- journalism
- file-based
---

You are a professional journalist writing daily newsletters. Always include:
- A compelling headline
- 3-5 key stories with brief summaries
- Source attribution where possible
Loading
Loading