Skip to content

Commit 1e8246e

Browse files
committed
Merge branch 'main' into feature/infra-page-update
2 parents 5688c30 + b198073 commit 1e8246e

File tree

13 files changed

+209
-253
lines changed

13 files changed

+209
-253
lines changed

Examples/Examples/Chat/ChatExample.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public async Task Start()
1212
// Using strongly-typed model
1313
await AIHub.Chat()
1414
.WithModel<Gemma2_2b>()
15+
.EnsureModelDownloaded()
1516
.WithMessage("Where do hedgehogs goes at night?")
1617
.CompleteAsync(interactive: true);
1718
}

MaIN.Core.IntegrationTests/ChatTests.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using MaIN.Core.Hub;
1+
using FuzzySharp;
2+
using MaIN.Core.Hub;
3+
using MaIN.Core.IntegrationTests.Helpers;
24
using MaIN.Domain.Entities;
35
using MaIN.Domain.Models.Abstract;
46
using MaIN.Domain.Models.Concrete;
@@ -67,7 +69,7 @@ public async Task Should_AnswerGameFromImage_ChatWithVision()
6769

6870
var result = await AIHub.Chat()
6971
.WithModel<Llama3_2_3b>()
70-
.WithMessage("What is the title of game?")
72+
.WithMessage("What is the title of the game? Answer only this question.")
7173
.WithMemoryParams(new MemoryParams
7274
{
7375
AnswerTokens = 1000
@@ -78,13 +80,20 @@ public async Task Should_AnswerGameFromImage_ChatWithVision()
7880
Assert.True(result.Done);
7981
Assert.NotNull(result.Message);
8082
Assert.NotEmpty(result.Message.Content);
81-
Assert.Contains("call of duty", result.Message.Content.ToLower());
83+
var ratio = Fuzz.PartialRatio("call of duty", result.Message.Content.ToLowerInvariant());
84+
Assert.True(ratio > 50,
85+
$"""
86+
Fuzzy match failed!
87+
Expected > 50, but got {ratio}.
88+
Expexted: 'call of duty'
89+
Actual: '{result.Message.Content}'
90+
""");
8291
}
8392

8493
[Fact(Skip = "Require powerful GPU")]
8594
public async Task Should_GenerateImage_BasedOnPrompt()
8695
{
87-
Assert.True(PingHost("127.0.0.1", 5003, 5), "Please make sure ImageGen service is running on port 5003");
96+
Assert.True(NetworkHelper.PingHost("127.0.0.1", 5003, 5), "Please make sure ImageGen service is running on port 5003");
8897

8998
const string extension = "png";
9099

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Net.Sockets;
3+
4+
namespace MaIN.Core.IntegrationTests.Helpers;
5+
6+
public static class NetworkHelper
7+
{
8+
public static bool PingHost(string host, int port, int timeout)
9+
{
10+
try
11+
{
12+
using var client = new TcpClient();
13+
var result = client.BeginConnect(host, port, null, null);
14+
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));
15+
16+
if (!success)
17+
{
18+
return false;
19+
}
20+
21+
client.EndConnect(result);
22+
return true;
23+
}
24+
catch
25+
{
26+
return false;
27+
}
28+
}
29+
}

MaIN.Core.IntegrationTests/IntegrationTestBase.cs

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,62 +10,32 @@ public class IntegrationTestBase : IDisposable
1010
protected readonly IHost _host;
1111
protected readonly IServiceProvider _services;
1212

13-
public IntegrationTestBase()
13+
protected IntegrationTestBase()
1414
{
15-
_host = CreateHost();
15+
_host = Host.CreateDefaultBuilder()
16+
.ConfigureServices((context, services) =>
17+
{
18+
services.AddMaIN(context.Configuration);
19+
ConfigureServices(services);
20+
})
21+
.Build();
22+
23+
_host.Services.UseMaIN();
1624
_host.Start();
17-
25+
1826
_services = _host.Services;
1927
}
2028

21-
private IHost CreateHost()
29+
// Allow derived classes to add additional services or override existing ones
30+
protected virtual void ConfigureServices(IServiceCollection services)
2231
{
23-
var hostBuilder = Host.CreateDefaultBuilder()
24-
.ConfigureWebHostDefaults(webBuilder =>
25-
{
26-
webBuilder
27-
.UseUrls("http://localhost:0") // Random available port
28-
.ConfigureServices((context, services) =>
29-
{
30-
services.AddMaIN(context.Configuration);
31-
32-
var provider = services.BuildServiceProvider();
33-
provider.UseMaIN();
34-
});
35-
});
36-
37-
return hostBuilder.Build();
3832
}
3933

4034
protected T GetService<T>() where T : notnull
4135
{
4236
return _services.GetRequiredService<T>();
4337
}
4438

45-
protected static bool PingHost(string host, int port, int timeout)
46-
{
47-
try
48-
{
49-
using (var client = new TcpClient())
50-
{
51-
var result = client.BeginConnect(host, port, null, null);
52-
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));
53-
54-
if (!success)
55-
{
56-
return false;
57-
}
58-
59-
client.EndConnect(result);
60-
return true;
61-
}
62-
}
63-
catch
64-
{
65-
return false;
66-
}
67-
}
68-
6939
public void Dispose()
7040
{
7141
_host?.Dispose();

MaIN.Core.IntegrationTests/MaIN.Core.IntegrationTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11+
<PackageReference Include="FuzzySharp" Version="2.0.2" />
1112
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
1213
<PackageReference Include="xunit" Version="2.9.3" />
1314
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">

src/MaIN.Core/Hub/Contexts/AgentContext.cs

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
using MaIN.Core.Hub.Contexts.Interfaces.AgentContext;
2+
using MaIN.Core.Hub.Utils;
23
using MaIN.Domain.Configuration;
34
using MaIN.Domain.Entities;
45
using MaIN.Domain.Entities.Agents;
56
using MaIN.Domain.Entities.Agents.AgentSource;
6-
using MaIN.Domain.Models;
7-
using MaIN.Services.Services.Abstract;
8-
using MaIN.Services.Services.Models;
9-
using MaIN.Core.Hub.Utils;
107
using MaIN.Domain.Entities.Agents.Knowledge;
118
using MaIN.Domain.Entities.Tools;
129
using MaIN.Domain.Exceptions.Agents;
10+
using MaIN.Domain.Models;
11+
using MaIN.Domain.Models.Abstract;
1312
using MaIN.Services.Constants;
13+
using MaIN.Services.Services.Abstract;
14+
using MaIN.Services.Services.Models;
1415

1516
namespace MaIN.Core.Hub.Contexts;
1617

@@ -20,6 +21,7 @@ public sealed class AgentContext : IAgentBuilderEntryPoint, IAgentConfigurationB
2021
private InferenceParams? _inferenceParams;
2122
private MemoryParams? _memoryParams;
2223
private bool _disableCache;
24+
private bool _ensureModelDownloaded;
2325
private readonly Agent _agent;
2426
internal Knowledge? _knowledge;
2527

@@ -60,8 +62,8 @@ internal AgentContext(IAgentService agentService, Agent existingAgent)
6062
public async Task<Agent?> GetAgentById(string id) => await _agentService.GetAgentById(id);
6163
public async Task Delete() => await _agentService.DeleteAgent(_agent.Id);
6264
public async Task<bool> Exists() => await _agentService.AgentExists(_agent.Id);
63-
64-
65+
66+
6567
public IAgentConfigurationBuilder WithModel(string model)
6668
{
6769
_agent.Model = model;
@@ -82,18 +84,18 @@ public async Task<IAgentContextExecutor> FromExisting(string agentId)
8284
{
8385
throw new AgentNotFoundException(agentId);
8486
}
85-
87+
8688
var context = new AgentContext(_agentService, existingAgent);
8789
context.LoadExistingKnowledgeIfExists();
8890
return context;
8991
}
90-
92+
9193
public IAgentConfigurationBuilder WithInitialPrompt(string prompt)
9294
{
9395
_agent.Context.Instruction = prompt;
9496
return this;
9597
}
96-
98+
9799
public IAgentConfigurationBuilder WithId(string id)
98100
{
99101
_agent.Id = id;
@@ -112,6 +114,12 @@ public IAgentConfigurationBuilder DisableCache()
112114
return this;
113115
}
114116

117+
public IAgentConfigurationBuilder EnsureModelDownloaded()
118+
{
119+
_ensureModelDownloaded = true;
120+
return this;
121+
}
122+
115123
public IAgentConfigurationBuilder WithSource(IAgentSource source, AgentSourceType type)
116124
{
117125
_agent.Context.Source = new AgentSource()
@@ -121,7 +129,7 @@ public IAgentConfigurationBuilder WithSource(IAgentSource source, AgentSourceTyp
121129
};
122130
return this;
123131
}
124-
132+
125133
public IAgentConfigurationBuilder WithName(string name)
126134
{
127135
_agent.Name = name;
@@ -143,7 +151,7 @@ public IAgentConfigurationBuilder WithMcpConfig(Mcp mcpConfig)
143151
_agent.Context.McpConfig = mcpConfig;
144152
return this;
145153
}
146-
154+
147155
public IAgentConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams)
148156
{
149157
_inferenceParams = inferenceParams;
@@ -174,7 +182,7 @@ public IAgentConfigurationBuilder WithKnowledge(KnowledgeBuilder knowledge)
174182
_knowledge = knowledge.ForAgent(_agent).Build();
175183
return this;
176184
}
177-
185+
178186
public IAgentConfigurationBuilder WithKnowledge(Knowledge knowledge)
179187
{
180188
_knowledge = knowledge;
@@ -189,7 +197,7 @@ public IAgentConfigurationBuilder WithInMemoryKnowledge(Func<KnowledgeBuilder, K
189197
_knowledge = knowledgeConfig(builder).Build();
190198
return this;
191199
}
192-
200+
193201
public IAgentConfigurationBuilder WithBehaviour(string name, string instruction)
194202
{
195203
_agent.Behaviours ??= new Dictionary<string, string>();
@@ -200,10 +208,15 @@ public IAgentConfigurationBuilder WithBehaviour(string name, string instruction)
200208

201209
public async Task<IAgentContextExecutor> CreateAsync(bool flow = false, bool interactiveResponse = false)
202210
{
211+
if (_ensureModelDownloaded && !string.IsNullOrWhiteSpace(_agent.Model))
212+
{
213+
await AIHub.Model().EnsureDownloadedAsync(_agent.Model);
214+
}
215+
203216
await _agentService.CreateAgent(_agent, flow, interactiveResponse, _inferenceParams, _memoryParams, _disableCache);
204217
return this;
205218
}
206-
219+
207220
public IAgentContextExecutor Create(bool flow = false, bool interactiveResponse = false)
208221
{
209222
_ = _agentService.CreateAgent(_agent, flow, interactiveResponse, _inferenceParams, _memoryParams, _disableCache).Result;
@@ -215,7 +228,7 @@ public IAgentConfigurationBuilder WithTools(ToolsConfiguration toolsConfiguratio
215228
_agent.ToolsConfiguration = toolsConfiguration;
216229
return this;
217230
}
218-
231+
219232
internal void LoadExistingKnowledgeIfExists()
220233
{
221234
_knowledge ??= new Knowledge(_agent);
@@ -229,7 +242,7 @@ internal void LoadExistingKnowledgeIfExists()
229242
Console.WriteLine("Knowledge cannot be loaded - new one will be created");
230243
}
231244
}
232-
245+
233246
public async Task<ChatResult> ProcessAsync(Chat chat, bool translate = false)
234247
{
235248
if (_knowledge == null)
@@ -247,7 +260,7 @@ public async Task<ChatResult> ProcessAsync(Chat chat, bool translate = false)
247260
CreatedAt = DateTime.Now
248261
};
249262
}
250-
263+
251264
public async Task<ChatResult> ProcessAsync(
252265
string message,
253266
bool translate = false,
@@ -276,8 +289,8 @@ public async Task<ChatResult> ProcessAsync(
276289
CreatedAt = DateTime.Now
277290
};
278291
}
279-
280-
public async Task<ChatResult> ProcessAsync(Message message,
292+
293+
public async Task<ChatResult> ProcessAsync(Message message,
281294
bool translate = false,
282295
Func<LLMTokenValue, Task>? tokenCallback = null,
283296
Func<ToolInvocation, Task>? toolCallback = null)
@@ -288,7 +301,7 @@ public async Task<ChatResult> ProcessAsync(Message message,
288301
}
289302
var chat = await _agentService.GetChatByAgent(_agent.Id);
290303
chat.Messages.Add(message);
291-
var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback);;
304+
var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback);
292305
var messageResult = result.Messages.LastOrDefault()!;
293306
return new ChatResult()
294307
{
@@ -298,7 +311,7 @@ public async Task<ChatResult> ProcessAsync(Message message,
298311
CreatedAt = DateTime.Now
299312
};
300313
}
301-
314+
302315
public async Task<ChatResult> ProcessAsync(
303316
IEnumerable<Message> messages,
304317
bool translate = false,
@@ -317,7 +330,7 @@ public async Task<ChatResult> ProcessAsync(
317330
chat.Messages.Add(systemMsg);
318331
}
319332
chat.Messages.AddRange(messages);
320-
var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback);;
333+
var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback);
321334
var messageResult = result.Messages.LastOrDefault()!;
322335
return new ChatResult()
323336
{
@@ -335,7 +348,7 @@ public static async Task<AgentContext> FromExisting(IAgentService agentService,
335348
{
336349
throw new AgentNotFoundException(agentId);
337350
}
338-
351+
339352
var context = new AgentContext(agentService, existingAgent);
340353
context.LoadExistingKnowledgeIfExists();
341354
return context;
@@ -345,8 +358,8 @@ public static async Task<AgentContext> FromExisting(IAgentService agentService,
345358
public static class AgentExtensions
346359
{
347360
public static async Task<ChatResult> ProcessAsync(
348-
this Task<AgentContext> agentTask,
349-
string message,
361+
this Task<AgentContext> agentTask,
362+
string message,
350363
bool translate = false)
351364
{
352365
var agent = await agentTask;

0 commit comments

Comments
 (0)