synor/sdk/csharp/SynorCompute/SynorComputeClient.cs
Gulshan Yadav 3aff77a799 feat(sdk): add consumer SDKs for Java, Kotlin, Swift, C, C++, C#, Ruby, and Rust
Expands SDK support to 8 additional languages/frameworks:
- Java SDK with Maven/OkHttp/Jackson
- Kotlin SDK with Gradle/Ktor/kotlinx.serialization
- Swift SDK with Swift Package Manager/async-await
- C SDK with CMake/libcurl
- C++ SDK with CMake/Modern C++20
- C# SDK with .NET 8.0/HttpClient
- Ruby SDK with Bundler/Faraday
- Rust SDK with Cargo/reqwest/tokio

All SDKs include:
- Tensor operations (matmul, conv2d, attention)
- LLM inference with streaming support
- Model registry, pricing, and usage APIs
- Builder patterns where idiomatic
- Full type safety
2026-01-11 17:46:22 +05:30

341 lines
10 KiB
C#

using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
namespace SynorCompute;
/// <summary>
/// Synor Compute SDK - C# Client
///
/// Access distributed heterogeneous compute resources (CPU, GPU, TPU, NPU, LPU, FPGA, DSP)
/// for AI/ML workloads at 90% cost reduction compared to traditional cloud.
/// </summary>
/// <example>
/// <code>
/// // Create client
/// using var client = new SynorComputeClient("your-api-key");
///
/// // Matrix multiplication on GPU
/// var a = Tensor.Rand(512, 512);
/// var b = Tensor.Rand(512, 512);
/// var result = await client.MatMulAsync(a, b, new MatMulOptions
/// {
/// Processor = ProcessorType.Gpu,
/// Precision = Precision.Fp16
/// });
///
/// if (result.IsSuccess)
/// {
/// Console.WriteLine($"Time: {result.ExecutionTimeMs}ms");
/// }
///
/// // LLM inference
/// var response = await client.InferenceAsync("llama-3-70b", "Explain quantum computing");
/// Console.WriteLine(response.Result);
///
/// // Streaming inference
/// await foreach (var token in client.InferenceStreamAsync("llama-3-70b", "Write a poem"))
/// {
/// Console.Write(token);
/// }
/// </code>
/// </example>
public sealed class SynorComputeClient : IDisposable
{
public const string Version = "0.1.0";
private readonly SynorConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
public SynorComputeClient(string apiKey)
: this(new SynorConfig { ApiKey = apiKey })
{
}
public SynorComputeClient(SynorConfig config)
{
_config = config;
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.BaseUrl),
Timeout = TimeSpan.FromMilliseconds(config.TimeoutMs)
};
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", $"csharp/{Version}");
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
PropertyNameCaseInsensitive = true
};
}
// ==================== Matrix Operations ====================
public Task<JobResult<Tensor>> MatMulAsync(Tensor a, Tensor b, CancellationToken ct = default)
=> MatMulAsync(a, b, new MatMulOptions(), ct);
public async Task<JobResult<Tensor>> MatMulAsync(
Tensor a,
Tensor b,
MatMulOptions options,
CancellationToken ct = default)
{
CheckDisposed();
var body = new
{
operation = "matmul",
a = TensorToDict(a),
b = TensorToDict(b),
precision = options.Precision.ToString().ToLower(),
processor = options.Processor.ToString().ToLower(),
priority = options.Priority.ToString().ToLower()
};
return await PostAsync<JobResult<Tensor>>("/compute", body, ct);
}
public async Task<JobResult<Tensor>> Conv2dAsync(
Tensor input,
Tensor kernel,
Conv2dOptions? options = null,
CancellationToken ct = default)
{
CheckDisposed();
options ??= new Conv2dOptions();
var body = new
{
operation = "conv2d",
input = TensorToDict(input),
kernel = TensorToDict(kernel),
stride = new[] { options.Stride.Item1, options.Stride.Item2 },
padding = new[] { options.Padding.Item1, options.Padding.Item2 },
precision = options.Precision.ToString().ToLower()
};
return await PostAsync<JobResult<Tensor>>("/compute", body, ct);
}
public async Task<JobResult<Tensor>> AttentionAsync(
Tensor query,
Tensor key,
Tensor value,
AttentionOptions? options = null,
CancellationToken ct = default)
{
CheckDisposed();
options ??= new AttentionOptions();
var body = new
{
operation = "attention",
query = TensorToDict(query),
key = TensorToDict(key),
value = TensorToDict(value),
num_heads = options.NumHeads,
flash = options.Flash,
precision = options.Precision.ToString().ToLower()
};
return await PostAsync<JobResult<Tensor>>("/compute", body, ct);
}
// ==================== LLM Inference ====================
public Task<JobResult<string>> InferenceAsync(string model, string prompt, CancellationToken ct = default)
=> InferenceAsync(model, prompt, new InferenceOptions(), ct);
public async Task<JobResult<string>> InferenceAsync(
string model,
string prompt,
InferenceOptions options,
CancellationToken ct = default)
{
CheckDisposed();
var body = new Dictionary<string, object>
{
["operation"] = "inference",
["model"] = model,
["prompt"] = prompt,
["max_tokens"] = options.MaxTokens,
["temperature"] = options.Temperature,
["top_p"] = options.TopP,
["top_k"] = options.TopK
};
if (options.Processor.HasValue)
{
body["processor"] = options.Processor.Value.ToString().ToLower();
}
return await PostAsync<JobResult<string>>("/inference", body, ct);
}
public async IAsyncEnumerable<string> InferenceStreamAsync(
string model,
string prompt,
InferenceOptions? options = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
CheckDisposed();
options ??= new InferenceOptions();
var body = new Dictionary<string, object>
{
["operation"] = "inference",
["model"] = model,
["prompt"] = prompt,
["max_tokens"] = options.MaxTokens,
["temperature"] = options.Temperature,
["stream"] = true
};
var request = new HttpRequestMessage(HttpMethod.Post, "/inference/stream")
{
Content = JsonContent.Create(body, options: _jsonOptions)
};
using var response = await _httpClient.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead,
ct);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(ct);
using var reader = new StreamReader(stream);
while (!reader.EndOfStream && !ct.IsCancellationRequested)
{
var line = await reader.ReadLineAsync(ct);
if (line == null) break;
if (line.StartsWith("data: "))
{
var data = line[6..];
if (data == "[DONE]") yield break;
try
{
var json = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(data, _jsonOptions);
if (json?.TryGetValue("token", out var token) == true)
{
yield return token.GetString() ?? "";
}
}
catch (JsonException)
{
// Skip malformed JSON
}
}
}
}
// ==================== Model Registry ====================
public async Task<List<ModelInfo>> ListModelsAsync(
ModelCategory? category = null,
CancellationToken ct = default)
{
CheckDisposed();
var url = category.HasValue
? $"/models?category={category.Value.ToString().ToLower()}"
: "/models";
var response = await GetAsync<JsonElement>(url, ct);
var models = response.GetProperty("models");
return models.Deserialize<List<ModelInfo>>(_jsonOptions) ?? new List<ModelInfo>();
}
public async Task<ModelInfo> GetModelAsync(string modelId, CancellationToken ct = default)
{
CheckDisposed();
return await GetAsync<ModelInfo>($"/models/{modelId}", ct);
}
public async Task<List<ModelInfo>> SearchModelsAsync(string query, CancellationToken ct = default)
{
CheckDisposed();
var response = await GetAsync<JsonElement>($"/models/search?q={Uri.EscapeDataString(query)}", ct);
var models = response.GetProperty("models");
return models.Deserialize<List<ModelInfo>>(_jsonOptions) ?? new List<ModelInfo>();
}
// ==================== Pricing & Usage ====================
public async Task<List<PricingInfo>> GetPricingAsync(CancellationToken ct = default)
{
CheckDisposed();
var response = await GetAsync<JsonElement>("/pricing", ct);
var pricing = response.GetProperty("pricing");
return pricing.Deserialize<List<PricingInfo>>(_jsonOptions) ?? new List<PricingInfo>();
}
public async Task<UsageStats> GetUsageAsync(CancellationToken ct = default)
{
CheckDisposed();
return await GetAsync<UsageStats>("/usage", ct);
}
// ==================== Health Check ====================
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try
{
var response = await GetAsync<JsonElement>("/health", ct);
return response.GetProperty("status").GetString() == "healthy";
}
catch
{
return false;
}
}
// ==================== Internal Methods ====================
private async Task<T> GetAsync<T>(string path, CancellationToken ct)
{
var response = await _httpClient.GetAsync(path, ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
?? throw new SynorException("Failed to deserialize response");
}
private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
{
var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
?? throw new SynorException("Failed to deserialize response");
}
private static object TensorToDict(Tensor tensor) => new
{
shape = tensor.Shape,
data = tensor.Data,
dtype = tensor.Dtype.ToString().ToLower()
};
private void CheckDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_httpClient.Dispose();
}
}
}