Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dclipca committed Dec 30, 2024
1 parent deef613 commit 4b1dbf7
Show file tree
Hide file tree
Showing 32 changed files with 1,906 additions and 4 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x
- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore

- name: Test (Unit Only)
run: dotnet test --no-build --verbosity normal --filter "Category!=Integration"
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ StyleCopReport.xml
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
Expand Down Expand Up @@ -397,4 +395,5 @@ FodyWeavers.xsd
*.msp

# JetBrains Rider
*.sln.iml
.idea/
*.sln.iml
67 changes: 67 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Contributing to LocalAI.NET

## Publishing to NuGet

### Prerequisites
1. Create a NuGet account at https://www.nuget.org
2. Generate an API key:
- Go to https://www.nuget.org/account/apikeys
- Click "Create"
- Name: "LocalAI.NET.KoboldCpp Publishing" (or your preferred name)
- Expiration: 365 days
- Select "Push new packages and package versions"
- Glob Pattern: "LocalAI.NET.KoboldCpp*"
- Save the generated key securely

### Publishing Process
1. Update version in `LocalAI.NET.KoboldCpp/LocalAI.NET.KoboldCpp.csproj`:
```xml
<Version>1.0.0</Version> <!-- Change this to new version -->
```

2. Clean and pack:
```bash
dotnet clean
dotnet pack -c Release
```

3. Push to NuGet:
```bash
dotnet nuget push .\LocalAI.NET.KoboldCpp\bin\Release\LocalAI.NET.KoboldCpp.1.0.0.nupkg --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json
```
Replace:
- `1.0.0` with your new version number
- `YOUR_API_KEY` with your NuGet API key

4. Wait 15-30 minutes for the package to appear on NuGet.org

### Version Guidelines
- Use [Semantic Versioning](https://semver.org/):
- MAJOR version for incompatible API changes
- MINOR version for backwards-compatible functionality
- PATCH version for backwards-compatible bug fixes

## Development Guidelines

### Code Style
- Use C# latest features and best practices
- Follow Microsoft's [C# Coding Conventions](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
- Use meaningful names for variables, methods, and classes
- Add XML documentation comments for public APIs

### Testing
1. Write unit tests for new features
2. Ensure all tests pass before submitting PR:
```bash
dotnet test
```

### Pull Request Process
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Update documentation and tests
5. Submit a pull request

## Questions?
Open an issue on GitHub if you have questions or need help.
3 changes: 3 additions & 0 deletions LocalAI.NET.Oobabooga.Tests/Common/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using Xunit;

[assembly: CollectionBehavior(DisableTestParallelization = true)]
46 changes: 46 additions & 0 deletions LocalAI.NET.Oobabooga.Tests/Common/IntegrationTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Xunit;
using Xunit.Abstractions;

namespace LocalAI.NET.Oobabooga.Tests.Common
{
public abstract class IntegrationTestBase : TestBase, IAsyncLifetime
{
protected bool ServerAvailable;

protected IntegrationTestBase(ITestOutputHelper output) : base(output)
{
}

public virtual async Task InitializeAsync()
{
try
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"{TestConfig.BaseApiUrl}/v1/models");
ServerAvailable = response.IsSuccessStatusCode;

if (ServerAvailable)
{
Output.WriteLine("API endpoint is available");
}
else
{
Output.WriteLine("API endpoint is not available");
throw new SkipTestException("API endpoint is not available");
}
}
catch (Exception ex) when (ex is not SkipTestException)
{
Output.WriteLine($"Failed to connect to API endpoint: {ex.Message}");
throw new SkipTestException("Failed to connect to API endpoint");
}
}

public virtual Task DisposeAsync() => Task.CompletedTask;

protected class SkipTestException : Exception
{
public SkipTestException(string message) : base(message) { }
}
}
}
74 changes: 74 additions & 0 deletions LocalAI.NET.Oobabooga.Tests/Common/OobaboogaTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using LocalAI.NET.Oobabooga.Providers.OpenAiCompatible;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using WireMock.Server;
using Xunit;
using Xunit.Abstractions;

namespace LocalAI.NET.Oobabooga.Tests.Common
{
public abstract class OobaboogaTestBase : IAsyncLifetime
{
protected readonly IOpenAiCompatibleProvider Provider;
protected readonly ITestOutputHelper Output;
protected readonly ILogger Logger;
protected bool ServerAvailable;

protected OobaboogaTestBase(ITestOutputHelper output)
{
Output = output;
Logger = LoggerFactory
.Create(builder => builder.AddXUnit(output))
.CreateLogger(GetType());

var httpClient = new HttpClient
{
BaseAddress = new Uri(TestConfig.BaseApiUrl),
Timeout = TimeSpan.FromSeconds(TestConfig.TimeoutSeconds)
};

var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};

Provider = new OpenAiCompatibleProvider(httpClient, logger: Logger, jsonSettings: jsonSettings);
}

public async Task InitializeAsync()
{
try
{
ServerAvailable = await Provider.IsAvailableAsync();
if (ServerAvailable)
{
Output.WriteLine("OpenAI API endpoint is available");
}
else
{
Output.WriteLine("OpenAI API endpoint is not available");
throw new SkipTestException("OpenAI API endpoint is not available");
}
}
catch (Exception ex) when (ex is not SkipTestException)
{
Output.WriteLine($"Failed to connect to OpenAI API endpoint: {ex.Message}");
throw new SkipTestException("Failed to connect to OpenAI API endpoint");
}
}

public Task DisposeAsync()
{
if (Provider is IDisposable disposable)
{
disposable.Dispose();
}
return Task.CompletedTask;
}

private class SkipTestException : Exception
{
public SkipTestException(string message) : base(message) { }
}
}
}
24 changes: 24 additions & 0 deletions LocalAI.NET.Oobabooga.Tests/Common/TestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

namespace LocalAI.NET.Oobabooga.Tests.Common
{
public abstract class TestBase : IDisposable
{
protected readonly ILogger Logger;
protected readonly ITestOutputHelper Output;

protected TestBase(ITestOutputHelper output)
{
Output = output;
Logger = LoggerFactory
.Create(builder => builder.AddXUnit(output))
.CreateLogger(GetType());
}

public virtual void Dispose()
{
// Cleanup if needed
}
}
}
12 changes: 12 additions & 0 deletions LocalAI.NET.Oobabooga.Tests/Common/TestConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace LocalAI.NET.Oobabooga.Tests.Common
{
public static class TestConfig
{
private const string DefaultHost = "http://127.0.0.1:5000";

public static string BaseApiUrl => Environment.GetEnvironmentVariable("OOBABOOGA_BASE_URL") ?? $"{DefaultHost}";

// Extended timeout for large models
public static int TimeoutSeconds => 120;
}
}
23 changes: 23 additions & 0 deletions LocalAI.NET.Oobabooga.Tests/Common/UnitTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using WireMock.Server;
using Xunit.Abstractions;

namespace LocalAI.NET.Oobabooga.Tests.Common
{
public abstract class UnitTestBase : TestBase
{
protected readonly WireMockServer Server;
protected readonly string BaseUrl;

protected UnitTestBase(ITestOutputHelper output) : base(output)
{
Server = WireMockServer.Start();
BaseUrl = Server.Urls[0];
}

public override void Dispose()
{
Server?.Dispose();
base.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using FluentAssertions;
using LocalAI.NET.Oobabooga.Client;
using LocalAI.NET.Oobabooga.Models;
using LocalAI.NET.Oobabooga.Models.Chat;
using LocalAI.NET.Oobabooga.Models.Common;
using LocalAI.NET.Oobabooga.Tests.Common;
using Xunit;
using Xunit.Abstractions;

namespace LocalAI.NET.Oobabooga.Tests.Integration
{
public class OobaboogaIntegrationTests : IntegrationTestBase
{
private readonly OobaboogaClient _client;

public OobaboogaIntegrationTests(ITestOutputHelper output) : base(output)
{
_client = new OobaboogaClient(new OobaboogaOptions
{
BaseUrl = TestConfig.BaseApiUrl
}, Logger);
}

[SkippableFact]
[Trait("Category", "Integration")]
public async Task Complete_WithSimplePrompt_ShouldWork()
{
Skip.If(!ServerAvailable, "API endpoint not available");

// Arrange & Act
var response = await _client.CompleteAsync(
"Once upon a time",
new OobaboogaCompletionOptions
{
MaxTokens = 20,
Temperature = 0.7f,
TopP = 0.9f
});

// Assert
response.Should().NotBeNullOrEmpty();
Output.WriteLine($"Completion response: {response}");
}

[Fact]
[Trait("Category", "Integration")]
public async Task ChatComplete_WithInstructTemplate_ShouldWork()
{
Skip.If(!ServerAvailable, "API endpoint not available");

// Arrange
var messages = new List<OobaboogaChatMessage>
{
new() { Role = "user", Content = "Write a short story" }
};

// Act
var response = await _client.ChatCompleteAsync(
messages,
new OobaboogaChatCompletionOptions
{
Mode = "instruct",
InstructionTemplate = "Alpaca",
MaxTokens = 100
});

// Assert
response.Should().NotBeNull();
response.Choices.Should().NotBeNull();
response.Choices.Should().NotBeEmpty("API should return at least one choice");
response.Choices.First().Message.Should().NotBeNull("Choice should contain a message");
response.Choices.First().Message.Content.Should().NotBeNullOrEmpty("Message should contain content");
}
}
}
Loading

0 comments on commit 4b1dbf7

Please sign in to comment.