Skip to content

Commit

Permalink
feat: Add run creation to .NET SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
johnjcsmith committed Oct 28, 2024
1 parent ae8db6c commit fdde306
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 85 deletions.
31 changes: 31 additions & 0 deletions sdk-dotnet/src/API/APIClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,37 @@ async public Task CreateCallResult(string clusterId, string callId, CreateResult
response.EnsureSuccessStatusCode();
}

async public Task<CreateRunResult> CreateRun(string clusterId, CreateRunInput input)
{
string jsonData = JsonSerializer.Serialize(input);

HttpResponseMessage response = await _client.PostAsync(
$"/clusters/{clusterId}/runs",
new StringContent(jsonData, Encoding.UTF8, "application/json")
);

response.EnsureSuccessStatusCode();

string responseBody = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<CreateRunResult>(responseBody);

return result;
}

async public Task<GetRunResult> GetRun(string clusterId, string runId)
{
HttpResponseMessage response = await _client.GetAsync(
$"/clusters/{clusterId}/runs/{runId}"
);

response.EnsureSuccessStatusCode();

string responseBody = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<GetRunResult>(responseBody);

return result;
}

async public Task<(List<CallMessage>, int?)> ListCalls(string clusterId, string service)
{
HttpResponseMessage response = await _client.GetAsync(
Expand Down
86 changes: 86 additions & 0 deletions sdk-dotnet/src/API/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,90 @@ public struct FunctionConfig
public int? TimeoutSeconds { get; set; }
}

public struct CreateRunInput
{
[JsonPropertyName("message")]
public string? Message { get; set; }

[
JsonPropertyName("attachedFunctions"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)
]
public List<string>? AttachedFunctions { get; set; }

[
JsonPropertyName("metadata"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)
]
public Dictionary<string, string>? Metadata { get; set; }

[
JsonPropertyName("result"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)
]
public CreateRunResultInput? Result { get; set; }

[
JsonPropertyName("template"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)
]
public CreateRunTemplateInput? Template { get; set; }
}

public struct CreateRunTemplateInput
{
[JsonPropertyName("input")]
public Dictionary<string, object> Input { get; set; }

[JsonPropertyName("id")]
public string Id { get; set; }
}

public struct CreateRunResultInput
{
[JsonPropertyName("handler")]
public CreateRunResultHandlerInput? Handler { get; set; }

[JsonPropertyName("schema")]
public object? Schema { get; set; }
}

public struct CreateRunResultHandlerInput
{
[JsonPropertyName("service")]
public string? Service { get; set; }

[JsonPropertyName("function")]
public string? Function { get; set; }
}

public struct CreateRunResult
{
[JsonPropertyName("id")]
public string ID { get; set; }
}

public struct GetRunResult
{
[JsonPropertyName("id")]
public string ID { get; set; }

[JsonPropertyName("status")]
public string Status { get; set; }

[JsonPropertyName("failureReason")]
public string FailureReason { get; set; }

[JsonPropertyName("summary")]
public string Summary { get; set; }

[JsonPropertyName("result")]
public string Result { get; set; }

[JsonPropertyName("attachedFunctions")]
public List<string> AttachedFunctions { get; set; }

[JsonPropertyName("metadata")]
public Dictionary<string, string> Metadata { get; set; }
}
}
62 changes: 57 additions & 5 deletions sdk-dotnet/src/Inferable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,30 @@ public class InferableOptions
{
public string? BaseUrl { get; set; }
public string? ApiSecret { get; set; }
/// <summary>
/// PingInterval in seconds
/// </summary>
public int? PingInterval { get; set; }
public string? MachineId { get; set; }
public string? ClusterId { get; set; }
}


public struct PollRunOptions
{
public required TimeSpan MaxWaitTime { get; set; }
public required TimeSpan Delay { get; set; }
}

public class RunHandle
{
public required string ID { get; set; }
public required Func<PollRunOptions?, Task<GetRunResult?>> Poll { get; set; }
}

public class InferableClient
{
public static string DefaultBaseUrl = "https://api.inferable.ai/";

private readonly ApiClient _client;
private readonly ILogger<InferableClient> _logger;
private readonly string? _clusterId;

// Dictionary of service name to list of functions
private Dictionary<string, List<IFunctionRegistration>> _functionRegistry = new Dictionary<string, List<IFunctionRegistration>>();
Expand All @@ -46,6 +56,7 @@ public InferableClient(InferableOptions? options = null, ILogger<InferableClient
string? apiSecret = options?.ApiSecret ?? Environment.GetEnvironmentVariable("INFERABLE_API_SECRET");
string baseUrl = options?.BaseUrl ?? Environment.GetEnvironmentVariable("INFERABLE_API_ENDPOINT") ?? DefaultBaseUrl;
string machineId = options?.MachineId ?? Machine.GenerateMachineId();
this._clusterId = options?.ClusterId ?? Environment.GetEnvironmentVariable("INFERABLE_CLUSTER_ID");

if (apiSecret == null)
{
Expand All @@ -72,6 +83,36 @@ public RegisteredService RegisterService(string name)
return new RegisteredService(name, this);
}

async public Task<RunHandle> CreateRun(CreateRunInput input)
{
if (this._clusterId == null) {
throw new ArgumentException("Cluster ID must be provided to manage runs");
}

var result = await this._client.CreateRun(this._clusterId, input);

return new RunHandle {
ID = result.ID,
Poll = async (PollRunOptions? options) => {
var MaxWaitTime = options?.MaxWaitTime ?? TimeSpan.FromSeconds(60);
var Delay = options?.Delay ?? TimeSpan.FromMilliseconds(500);

var start = DateTime.Now;
while (DateTime.Now - start < MaxWaitTime) {
var pollResult = await this._client.GetRun(this._clusterId, result.ID);

var transientStates = new List<string> { "pending", "running" };
if (transientStates.Contains(pollResult.Status)) {
await Task.Delay(Delay);
}

return pollResult;
}
return null;
}
};
}

public IEnumerable<string> ActiveServices
{
get
Expand Down Expand Up @@ -138,6 +179,12 @@ internal async Task StopService(string name) {
}
}

public struct FunctionHandle
{
public required string Service { get; set; }
public required string Function { get; set; }
}

public struct RegisteredService
{
private string _name;
Expand All @@ -148,8 +195,13 @@ internal RegisteredService(string name, InferableClient inferable) {
this._inferable = inferable;
}

public void RegisterFunction<T>(FunctionRegistration<T> function) where T : struct {
public FunctionHandle RegisterFunction<T>(FunctionRegistration<T> function) where T : struct {
this._inferable.RegisterFunction<T>(this._name, function);

return new FunctionHandle {
Service = this._name,
Function = function.Name
};
}

async public Task Start() {
Expand Down
40 changes: 40 additions & 0 deletions sdk-dotnet/tests/Inferable.Tests/InferableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,46 @@ async public void Inferable_Can_Handle_Functions_Failure()
await inferable.Default.Stop();
}
}

[Fact]
async public void Inferable_Can_Trigger_Runs()
{
var inferable = CreateInferableClient();

var registration = new FunctionRegistration<TestInput>
{
Name = "successFunction",
Func = new Func<TestInput, string>((input) =>
{
Console.WriteLine("Executing successFunction");
return "This is a test response";
})
};

inferable.Default.RegisterFunction(registration);

try
{
await inferable.Default.Start();

var run = await inferable.CreateRun(new CreateRunInput
{
Message = "Call the successFunction",
AttachedFunctions = new List<string>
{
"default_successFunction"
}
});

var result = await run.Poll(null);

Assert.NotNull(result);
}
finally
{
await inferable.Default.Stop();
}
}
}

//TODO: Test transient /call failures
Expand Down
18 changes: 6 additions & 12 deletions sdk-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,30 @@ pnpm add inferable

### 1. Initializing Inferable

Create a file named i.ts which will be used to initialize Inferable. This file will export the Inferable instance.
Initialize the Inferable client with your API secret.

```typescript
// d.ts

import { Inferable } from "inferable";

// Initialize the Inferable client with your API secret.
// Get yours at https://console.inferable.ai.
export const d = new Inferable({
export const client = new Inferable({
apiSecret: "YOUR_API_SECRET",
});
```

### 2. Hello World Function

In a separate file, register a "sayHello" [function](https://docs.inferable.ai/pages/functions). This file will import the Inferable instance from `i.ts` and register the [function](https://docs.inferable.ai/pages/functions) with the [control-plane](https://docs.inferable.ai/pages/control-plane).
Register a "sayHello" [function](https://docs.inferable.ai/pages/functions). This file will import the Inferable instance from `i.ts` and register the [function](https://docs.inferable.ai/pages/functions) with the [control-plane](https://docs.inferable.ai/pages/control-plane).

```typescript
// service.ts

import { i } from "./i";

// Define a simple function that returns "Hello, World!"
const sayHello = async ({ to }: { to: string }) => {
return `Hello, ${to}!`;
};

// Register the service (using the 'default' service)
const sayHello = i.default.register({
const sayHello = client.default.register({
name: "sayHello",
func: sayHello,
schema: {
Expand All @@ -75,7 +69,7 @@ const sayHello = i.default.register({
});

// Start the 'default' service
i.default.start();
client.default.start();
```

### 3. Running the Service
Expand All @@ -96,7 +90,7 @@ The following code will create an [Inferable run](https://docs.inferable.ai/page
> - in the [CLI](https://www.npmjs.com/package/@inferable/cli) via `inf runs list`
```typescript
const run = await i.run({
const run = await client.run({
message: "Say hello to John",
functions: [sayHello],
// Alternatively, subscribe an Inferable function as a result handler which will be called when the run is complete.
Expand Down
Loading

0 comments on commit fdde306

Please sign in to comment.