Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ jobs:

# run build and test
- name: Restore dependencies
run: dotnet restore
run: dotnet restore Together.slnx

- name: Build
run: dotnet build --no-restore --configuration Release
run: dotnet build Together.slnx --no-restore --configuration Release

- name: Test and Collect Code Coverage
run: dotnet test --configuration Release -p:CollectCoverage=true -p:CoverletOutput=coverage/
run: dotnet test Together.slnx --no-build --configuration Release -p:CollectCoverage=true -p:CoverletOutput=coverage/

- name: Copy coverage files
run: |
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/monitor-upstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Monitor upstream SDKs

on:
schedule:
- cron: '0 6 * * *'
workflow_dispatch:

jobs:
check-updates:
runs-on: ubuntu-latest
permissions:
issues: write
contents: read

steps:
- name: Check upstream repositories
id: check
uses: actions/github-script@v7
with:
script: |
const repos = [
{ owner: 'togethercomputer', repo: 'together-python' },
{ owner: 'togethercomputer', repo: 'together-typescript' }
];
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000);
const results = [];
for (const target of repos) {
const { data } = await github.repos.listCommits({ owner: target.owner, repo: target.repo, per_page: 10 });
const recent = data.filter(c => new Date(c.commit.committer.date) > cutoff);
if (recent.length > 0) {
results.push({
owner: target.owner,
repo: target.repo,
commits: recent.map(c => ({
sha: c.sha.substring(0, 7),
message: c.commit.message.split('\n')[0],
url: c.html_url,
date: c.commit.committer.date
}))
});
}
}
core.setOutput('updates', JSON.stringify(results));
core.info(`Found ${results.length} repositories with updates.`);

- name: Create tracking issue
if: steps.check.outputs.updates != '[]'
uses: actions/github-script@v7
env:
UPDATES: ${{ steps.check.outputs.updates }}
with:
script: |
const updates = JSON.parse(process.env.UPDATES ?? '[]');
const today = new Date().toISOString().split('T')[0];
let body = 'Detected new upstream commits:\n\n';
for (const repo of updates) {
body += `### ${repo.owner}/${repo.repo}\n`;
for (const commit of repo.commits) {
body += `- [${commit.sha}](${commit.url}) ${commit.message} (_${commit.date}_)\n`;
}
body += '\n';
}
await github.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Upstream SDK updates ${today}`,
body,
labels: ['upstream-monitor']
});

- name: No updates found
if: steps.check.outputs.updates == '[]'
run: echo "No upstream changes detected in the last 24 hours."
12 changes: 7 additions & 5 deletions .github/workflows/nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name: nuget

on:
push:
branches: [ main ]
tags:
- 'v*'
workflow_dispatch:

jobs:
nuget-pack:
Expand All @@ -18,16 +20,16 @@ jobs:
dotnet-version: '9.0.x'

- name: Restore dependencies
run: dotnet restore
run: dotnet restore Together.slnx

- name: Build
run: dotnet build --configuration Release
run: dotnet build Together.slnx --configuration Release --no-restore

- name: Test
run: dotnet test --configuration Release
run: dotnet test Together.slnx --configuration Release --no-build

- name: Pack
run: dotnet pack --configuration Release -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg -o "packages"
run: dotnet pack Together.slnx --configuration Release --no-build -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg -o "packages"

- name: Push
run: dotnet nuget push "packages/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<LangVersion>13</LangVersion>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<Nullable>enable</Nullable>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<!--NuGet-->
Expand Down
14 changes: 14 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project>
<ItemGroup>
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.5.0" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.38.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Core" Version="1.38.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
<PackageVersion Include="Moq" Version="4.20.72" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Together.SemanticKernel/Together.SemanticKernel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="1.38.0" />
<PackageReference Include="Microsoft.SemanticKernel.Core" />
</ItemGroup>


Expand Down
205 changes: 205 additions & 0 deletions Together.Tests/Clients/NewResourceClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using Together.Clients;
using Together.Models.Audio;
using Together.Models.Batch;
using Together.Models.CodeInterpreter;
using Together.Models.Evaluations;
using Together.Models.Videos;

namespace Together.Tests.Clients;

public class NewResourceClientTests : TestBase
{
[Fact]
public async Task BatchClient_CreateAsync_ReturnsJob()
{
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(
"""{"job":{"id":"batch_1","input_file_id":"file_1","user_id":"user","file_size_bytes":10,"status":"IN_PROGRESS","job_deadline":"2024-01-01T00:00:00Z","created_at":"2024-01-01T00:00:00Z","endpoint":"/v1/completions","progress":0.5}}""")
};

var client = new BatchClient(CreateMockHttpClient(response));
var result = await client.CreateAsync(new BatchCreateRequest { InputFileId = "file_1", Endpoint = "/v1/completions" });

Assert.Equal("batch_1", result.Id);
Assert.Equal("file_1", result.InputFileId);
}

[Fact]
public async Task EndpointClient_ListAsync_ParsesResponse()
{
var json = """{"data":[{"id":"ep1","object":"endpoint","name":"ep","model":"model","type":"dedicated","owner":"user","state":"STARTED","created_at":"2024-01-01T00:00:00Z"}]}""";
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(json)
};

var client = new EndpointClient(CreateMockHttpClient(response));
var results = await client.ListAsync();

Assert.Single(results);
Assert.Equal("ep1", results[0].Id);
}

[Fact]
public async Task HardwareClient_ListAsync_ReturnsHardware()
{
var json = """{"object":"list","data":[{"object":"hardware","id":"hw1","pricing":{"cents_per_minute":1},"specs":{"gpu_type":"A100","gpu_link":"NVLINK","gpu_memory":80,"gpu_count":1},"updated_at":"2024-01-01T00:00:00Z"}]}""";
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(json)
};

var client = new HardwareClient(CreateMockHttpClient(response));
var result = await client.ListAsync();

Assert.Single(result.Data);
Assert.Equal("hw1", result.Data[0].Id);
}

[Fact]
public async Task JobClient_RetrieveAsync_ReturnsJob()
{
var json = """{"job_id":"job-1","args":{},"created_at":"2024-01-01","status":"Queued","status_updates":[],"type":"train","updated_at":"2024-01-01"}""";
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(json)
};

var client = new JobClient(CreateMockHttpClient(response));
var job = await client.RetrieveAsync("job-1");

Assert.Equal("job-1", job.JobId);
Assert.Equal("Queued", job.Status);
}

[Fact]
public async Task EvaluationClient_CreateAsync_ReturnsWorkflow()
{
var json = """{"workflow_id":"wf_1","status":"queued"}""";
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(json)
};

var client = new EvaluationClient(CreateMockHttpClient(response));
var result = await client.CreateAsync(new EvaluationCreateRequest
{
Type = "classify",
Judge = new JudgeModelConfig { Model = "judge", ModelSource = "serverless", SystemTemplate = "template" },
InputDataFilePath = "file.jsonl",
Labels = new List<string> { "yes" },
PassLabels = new List<string> { "yes" }
});

Assert.Equal("wf_1", result.WorkflowId);
Assert.Equal("queued", result.Status);
}

[Fact]
public async Task CodeInterpreterClient_RunAsync_ReturnsOutputs()
{
var json = """{"data":{"session_id":"sess","status":"completed","outputs":[{"type":"stdout","data":"hello"}]}}""";
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(json)
};

var client = new CodeInterpreterClient(CreateMockHttpClient(response));
var result = await client.RunAsync(new CodeInterpreterRequest { Code = "print('hi')" });

Assert.Equal("sess", result.Data.SessionId);
Assert.Single(result.Data.Outputs);
}

[Fact]
public async Task AudioClient_CreateSpeechAsync_ReturnsBytes()
{
var audioBytes = new byte[] { 1, 2, 3 };
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new ByteArrayContent(audioBytes)
};
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("audio/wav");

var client = new AudioClient(CreateMockHttpClient(response));
var result = await client.CreateSpeechAsync(new AudioSpeechRequest { Model = "model", Input = "hello" });

Assert.NotNull(result.Data);
Assert.Equal(audioBytes, result.Data);
}

[Fact]
public async Task AudioClient_CreateSpeechAsync_ParsesStream()
{
var payload = "data: {\"b64\":\"AQI=\"}\n" +
"data: [DONE]\n";
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(payload)
};

var client = new AudioClient(CreateMockHttpClient(response));
var request = new AudioSpeechRequest { Model = "model", Input = "hi", Stream = true };
var stream = await client.CreateSpeechAsync(request);

var chunks = new List<byte[]>();
await foreach (var chunk in stream.Stream!)
{
chunks.Add(chunk);
}

Assert.Single(chunks);
Assert.Equal(new byte[] { 1, 2 }, chunks[0]);
}

[Fact]
public async Task AudioClient_CreateTranscriptionAsync_ReturnsText()
{
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("""{"text":"Hello"}""")
};

var request = new AudioFileRequest
{
Content = new MemoryStream(Encoding.UTF8.GetBytes("data")),
FileName = "audio.wav",
Model = "whisper"
};

var client = new AudioClient(CreateMockHttpClient(response));
var result = await client.CreateTranscriptionAsync(request);

Assert.Equal("Hello", result.Response!.Text);
}

[Fact]
public async Task VideoClient_CreateAsync_ReturnsId()
{
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("""{"id":"video_1"}""")
};

var client = new VideoClient(CreateMockHttpClient(response));
var result = await client.CreateAsync(new CreateVideoRequest { Model = "video-model" });

Assert.Equal("video_1", result.Id);
}
}
Loading
Loading