Skip to content

Commit d676836

Browse files
author
AndrewMorgan1
committed
Added core project and integration for CLI to use core libraries
1 parent a9c8959 commit d676836

51 files changed

Lines changed: 1025 additions & 1062 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

TokenKit.sln

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
4-
VisualStudioVersion = 17.14.36518.9 d17.14
4+
VisualStudioVersion = 17.14.36518.9
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
77
EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{44399DF1-452C-412D-A5E4-8B13EB196561}"
99
EndProject
10-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TokenKit", "src\TokenKit\TokenKit.csproj", "{74BBAD68-4EFF-4453-B5EA-183659224542}"
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokenKit", "src\TokenKit\TokenKit.csproj", "{74BBAD68-4EFF-4453-B5EA-183659224542}"
1111
EndProject
12-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TokenKit.Tests", "tests\TokenKit.Tests\TokenKit.Tests.csproj", "{636A7797-0481-4825-89D1-1B294C132FD9}"
12+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokenKit.Tests", "tests\TokenKit.Tests\TokenKit.Tests.csproj", "{636A7797-0481-4825-89D1-1B294C132FD9}"
13+
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TokenKit.Core", "src\TokenKit.Core\TokenKit.Core.csproj", "{89781812-0C24-4F01-B670-F2AA668A6220}"
1315
EndProject
1416
Global
1517
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -25,13 +27,18 @@ Global
2527
{636A7797-0481-4825-89D1-1B294C132FD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
2628
{636A7797-0481-4825-89D1-1B294C132FD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
2729
{636A7797-0481-4825-89D1-1B294C132FD9}.Release|Any CPU.Build.0 = Release|Any CPU
30+
{89781812-0C24-4F01-B670-F2AA668A6220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{89781812-0C24-4F01-B670-F2AA668A6220}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{89781812-0C24-4F01-B670-F2AA668A6220}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{89781812-0C24-4F01-B670-F2AA668A6220}.Release|Any CPU.Build.0 = Release|Any CPU
2834
EndGlobalSection
2935
GlobalSection(SolutionProperties) = preSolution
3036
HideSolutionNode = FALSE
3137
EndGlobalSection
3238
GlobalSection(NestedProjects) = preSolution
3339
{74BBAD68-4EFF-4453-B5EA-183659224542} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
3440
{636A7797-0481-4825-89D1-1B294C132FD9} = {44399DF1-452C-412D-A5E4-8B13EB196561}
41+
{89781812-0C24-4F01-B670-F2AA668A6220} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
3542
EndGlobalSection
3643
GlobalSection(ExtensibilityGlobals) = postSolution
3744
SolutionGuid = {615E1888-FAAC-4D09-AF03-BA43CCD7E21E}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using TokenKit.Core.Implementations;
3+
using TokenKit.Core.Interfaces;
4+
5+
namespace TokenKit.Core.Extensions;
6+
7+
public static class ServiceCollectionExtensions
8+
{
9+
/// <summary>
10+
/// Registers TokenKit.Core services. If <paramref name="jsonPath"/> is null,
11+
/// the registry will look for models.data.json in AppContext.BaseDirectory.
12+
/// </summary>
13+
public static IServiceCollection AddTokenKitCore(this IServiceCollection services, string? jsonPath = null)
14+
{
15+
services.AddSingleton<IModelRegistry>(_ => new JsonModelRegistry(jsonPath));
16+
services.AddSingleton<ICostEstimator, BasicCostEstimator>();
17+
services.AddSingleton<ITokenKitCore, TokenKitCore>();
18+
return services;
19+
}
20+
}
21+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using TokenKit.Core.Interfaces;
2+
using TokenKit.Core.Models;
3+
4+
namespace TokenKit.Core.Implementations;
5+
6+
public sealed class BasicCostEstimator : ICostEstimator
7+
{
8+
public decimal EstimateTotal(ModelInfo model, int tokenCount)
9+
{
10+
var perToken = (model.InputPricePer1K + model.OutputPricePer1K) / 1000m;
11+
return Math.Round(tokenCount * perToken, 6);
12+
}
13+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Text.Json;
2+
using TokenKit.Core.Interfaces;
3+
using TokenKit.Core.Models;
4+
5+
namespace TokenKit.Core.Implementations;
6+
7+
public sealed class JsonModelRegistry : IModelRegistry
8+
{
9+
private readonly List<ModelInfo> _models;
10+
11+
/// <param name="path">
12+
/// Optional custom path to models.data.json.
13+
/// If null, resolves to: AppContext.BaseDirectory/models.data.json
14+
/// </param>
15+
public JsonModelRegistry(string? path = null)
16+
{
17+
var file = path ?? Path.Combine(AppContext.BaseDirectory, "models.data.json");
18+
19+
if (File.Exists(file))
20+
{
21+
try
22+
{
23+
var json = File.ReadAllText(file);
24+
_models = JsonSerializer.Deserialize<List<ModelInfo>>(json) ?? new();
25+
}
26+
catch (Exception ex) when (ex is JsonException || ex is IOException)
27+
{
28+
// Gracefully handle corrupt or unreadable JSON
29+
_models = new List<ModelInfo>();
30+
}
31+
}
32+
else
33+
{
34+
_models = new List<ModelInfo>();
35+
}
36+
}
37+
38+
public ModelInfo? Get(string id) =>
39+
_models.FirstOrDefault(m => string.Equals(m.Id, id, StringComparison.OrdinalIgnoreCase));
40+
41+
public IReadOnlyList<ModelInfo> GetAll(string? provider = null)
42+
{
43+
if (string.IsNullOrWhiteSpace(provider)) return _models;
44+
return _models
45+
.Where(m => string.Equals(m.Provider, provider, StringComparison.OrdinalIgnoreCase))
46+
.ToList();
47+
}
48+
}
49+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using TokenKit.Core.Interfaces;
2+
using TokenKit.Core.Models;
3+
4+
namespace TokenKit.Core.Implementations;
5+
6+
public sealed class SimpleTokenizerEngine : ITokenizerEngine
7+
{
8+
public string Name => "simple";
9+
10+
public int CountTokens(string text, ModelInfo model)
11+
=> string.IsNullOrWhiteSpace(text)
12+
? 0
13+
: text.Split((char[])[' ', '\n', '\r', '\t'], StringSplitOptions.RemoveEmptyEntries).Length;
14+
}
15+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using TokenKit.Core.Interfaces;
2+
using TokenKit.Core.Models;
3+
4+
namespace TokenKit.Core.Implementations;
5+
6+
public sealed class TokenKitCore : ITokenKitCore
7+
{
8+
private readonly IModelRegistry _registry;
9+
private readonly IDictionary<string, ITokenizerEngine> _engines;
10+
private readonly ICostEstimator _costs;
11+
12+
public TokenKitCore(IModelRegistry registry, IEnumerable<ITokenizerEngine> engines, ICostEstimator costs)
13+
{
14+
_registry = registry;
15+
_engines = engines.ToDictionary(e => e.Name, StringComparer.OrdinalIgnoreCase);
16+
_costs = costs;
17+
}
18+
19+
public Task<IReadOnlyList<ModelInfo>> GetModelsAsync(string? provider = null, CancellationToken ct = default)
20+
=> Task.FromResult(_registry.GetAll(provider));
21+
22+
public Task<AnalyzeResponse> AnalyzeAsync(AnalyzeRequest req, CancellationToken ct = default)
23+
{
24+
var model = _registry.Get(req.ModelId) ?? throw new ArgumentException($"Unknown model '{req.ModelId}'.");
25+
var engine = ResolveEngine(req.Engine);
26+
27+
var count = engine.CountTokens(req.Text, model);
28+
var cost = _costs.EstimateTotal(model, count);
29+
var valid = count <= model.MaxTokens;
30+
31+
return Task.FromResult(new AnalyzeResponse
32+
{
33+
Model = model.Id,
34+
Provider = model.Provider,
35+
Engine = engine.Name,
36+
TokenCount = count,
37+
EstimatedCost = cost,
38+
Valid = valid,
39+
Message = valid ? "OK" : $"Exceeds MaxTokens ({model.MaxTokens})."
40+
});
41+
}
42+
43+
public async Task<ValidateResponse> ValidateAsync(ValidateRequest req, CancellationToken ct = default)
44+
{
45+
var a = await AnalyzeAsync(new AnalyzeRequest
46+
{
47+
Text = req.Text,
48+
ModelId = req.ModelId,
49+
Engine = req.Engine
50+
}, ct);
51+
52+
return new ValidateResponse
53+
{
54+
IsValid = a.Valid,
55+
Message = a.Message,
56+
TokenCount = a.TokenCount,
57+
MaxTokens = _registry.Get(req.ModelId)!.MaxTokens
58+
};
59+
}
60+
61+
private ITokenizerEngine ResolveEngine(string? requested)
62+
{
63+
if (!string.IsNullOrWhiteSpace(requested) && _engines.TryGetValue(requested, out var eng))
64+
return eng;
65+
66+
// default to first registered engine
67+
return _engines.Values.First();
68+
}
69+
}
70+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using TokenKit.Core.Models;
2+
3+
namespace TokenKit.Core.Interfaces;
4+
5+
public interface ICostEstimator
6+
{
7+
decimal EstimateTotal(ModelInfo model, int tokenCount);
8+
}
9+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using TokenKit.Core.Models;
2+
3+
namespace TokenKit.Core.Interfaces;
4+
5+
public interface IModelRegistry
6+
{
7+
ModelInfo? Get(string id);
8+
IReadOnlyList<ModelInfo> GetAll(string? provider = null);
9+
}
10+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using TokenKit.Core.Models;
2+
3+
namespace TokenKit.Core.Interfaces;
4+
5+
public interface ITokenKitCore
6+
{
7+
Task<AnalyzeResponse> AnalyzeAsync(AnalyzeRequest req, CancellationToken ct = default);
8+
Task<ValidateResponse> ValidateAsync(ValidateRequest req, CancellationToken ct = default);
9+
Task<IReadOnlyList<ModelInfo>> GetModelsAsync(string? provider = null, CancellationToken ct = default);
10+
}
11+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using TokenKit.Core.Models;
2+
3+
namespace TokenKit.Core.Interfaces;
4+
5+
public interface ITokenizerEngine
6+
{
7+
string Name { get; }
8+
int CountTokens(string text, ModelInfo model);
9+
}
10+

0 commit comments

Comments
 (0)