Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
84 changes: 84 additions & 0 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: publish-nuget

on:
push:
branches: [ main ]
workflow_dispatch:
inputs:
version:
description: 'Override version (optional, e.g. 0.1.2)'
required: false
type: string

jobs:
pack-and-push:
runs-on: ubuntu-latest

env:
LIB_PROJ: PromptProvider/PromptProvider.csproj
TEST_PROJ: Tests/PromptProvider.Tests/PromptProvider.Tests.csproj

steps:
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

- name: Verify projects exist
shell: bash
run: |
set -euo pipefail
[[ -f "$LIB_PROJ" ]] || { echo "Library not found: $LIB_PROJ"; exit 1; }
echo "Library: $LIB_PROJ"
- name: Restore
shell: bash
run: |
dotnet restore "$TEST_PROJ"

- name: Build library
shell: bash
run: |
dotnet build "$LIB_PROJ" -c Release -p:ContinuousIntegrationBuild=true --no-restore
- name: Resolve version
id: ver
shell: bash
run: |
set -euo pipefail
if [ -n "${{ github.event.inputs.version }}" ]; then
PKGVER="${{ github.event.inputs.version }}"
else
PKGVER=$(grep -oPm1 '(?<=<Version>)[^<]+' "$LIB_PROJ" || true)
if [ -z "$PKGVER" ]; then
echo "<Version> not found in $LIB_PROJ" >&2
exit 1
fi
fi
echo "PKGVER=$PKGVER" >> $GITHUB_ENV
echo "Using version: $PKGVER"

- name: Pack
shell: bash
run: |
dotnet pack "$LIB_PROJ" -c Release \
-p:Version="${PKGVER}" \
-p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg \
-o ./artifacts \
--no-build

- name: Push to nuget.org
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
shell: bash
run: |
set -euo pipefail
if [ -z "${NUGET_API_KEY:-}" ]; then
echo "NUGET_API_KEY secret is not set." >&2
exit 1
fi
dotnet nuget push "./artifacts/*.nupkg" --skip-duplicate --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json
if ls ./artifacts/*.snupkg 1> /dev/null 2>&1; then
dotnet nuget push "./artifacts/*.snupkg" --skip-duplicate --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json
else
echo "No symbol packages (.snupkg) found to push."
fi
33 changes: 33 additions & 0 deletions DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using PromptProvider.Interfaces;
using PromptProvider.Options;
using PromptProvider.Services;

namespace PromptProvider;

public static class DependencyInjection
{
public static IServiceCollection AddPromptProvider(
this IServiceCollection services,
Action<LangfuseOptions>? configureLangfuse = null,
Action<PromptsOptions>? configurePrompts = null)
{
if (configureLangfuse is not null) services.Configure(configureLangfuse);
if (configurePrompts is not null) services.Configure(configurePrompts);

services.AddHttpClient<ILangfuseService, LangfuseService>((serviceProvider, client) =>
{
var options = serviceProvider.GetRequiredService<Microsoft.Extensions.Options.IOptions<LangfuseOptions>>().Value;
if (options.IsConfigured() && !string.IsNullOrWhiteSpace(options.BaseUrl))
{
client.BaseAddress = new Uri(options.BaseUrl);
}
});
services.AddScoped<IPromptService, PromptService>();

// Register default prompts provider (users may replace with custom implementation)
services.AddSingleton<IDefaultPromptsProvider, ConfigurationDefaultPromptsProvider>();

return services;
}
}
6 changes: 6 additions & 0 deletions Interfaces/IDefaultPromptsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace PromptProvider.Interfaces;

public interface IDefaultPromptsProvider
{
IReadOnlyDictionary<string, string> GetDefaults();
}
51 changes: 51 additions & 0 deletions Interfaces/ILangfuseService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using PromptProvider.Models;

namespace PromptProvider.Interfaces;

public interface ILangfuseService
{
/// <summary>
/// Get a prompt by name from Langfuse API.
/// </summary>
/// <param name="promptName">The name of the prompt</param>
/// <param name="version">Optional version of the prompt to retrieve</param>
/// <param name="label">Optional label of the prompt (defaults to "production" if no label or version is set)</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The Langfuse prompt model</returns>
Task<LangfusePromptModel?> GetPromptAsync(
string promptName,
int? version = null,
string? label = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Get all prompts from Langfuse API.
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of Langfuse prompt list items</returns>
Task<List<LangfusePromptListItem>> GetAllPromptsAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Create a new version for the prompt with the given name in Langfuse API.
/// </summary>
/// <param name="request">The prompt creation request</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The created prompt response</returns>
Task<CreateLangfusePromptResponse> CreatePromptAsync(
CreateLangfusePromptRequest request,
CancellationToken cancellationToken = default);

/// <summary>
/// Update labels for a specific prompt version in Langfuse API.
/// </summary>
/// <param name="promptName">The name of the prompt</param>
/// <param name="version">The version number to update</param>
/// <param name="request">The update request containing new labels</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The updated prompt model</returns>
Task<LangfusePromptModel> UpdatePromptLabelsAsync(
string promptName,
int version,
UpdatePromptLabelsRequest request,
CancellationToken cancellationToken = default);
}
35 changes: 35 additions & 0 deletions Interfaces/IPromptService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using PromptProvider.Models;

namespace PromptProvider.Interfaces;

public interface IPromptService
{
/// <summary>
/// Create a new prompt version in Langfuse
/// </summary>
Task<PromptResponse> CreatePromptAsync(CreatePromptRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Get a prompt by key with optional version or label. Falls back to local defaults if Langfuse is unavailable.
/// </summary>
/// <param name="promptKey">The prompt key/name</param>
/// <param name="version">Optional specific version number</param>
/// <param name="label">Optional label (e.g., "production", "latest")</param>
/// <param name="cancellationToken">Cancellation token</param>
Task<PromptResponse?> GetPromptAsync(string promptKey, int? version = null, string? label = null, CancellationToken cancellationToken = default);

/// <summary>
/// Get all prompts from Langfuse
/// </summary>
Task<List<LangfusePromptListItem>> GetAllPromptsAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Get multiple prompts by keys with optional label. Falls back to local defaults if Langfuse is unavailable.
/// </summary>
Task<List<PromptResponse>> GetPromptsAsync(IEnumerable<string> promptKeys, string? label = null, CancellationToken cancellationToken = default);

/// <summary>
/// Update labels for a specific prompt version in Langfuse
/// </summary>
Task<PromptResponse> UpdatePromptLabelsAsync(string promptKey, int version, UpdatePromptLabelsRequest request, CancellationToken cancellationToken = default);
}
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 ZioNet
Copyright (c) 2025 [copyright holder]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
26 changes: 26 additions & 0 deletions Models/CreateLangfusePromptRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Text.Json.Serialization;

namespace PromptProvider.Models;
public record CreateLangfusePromptRequest
{
[JsonPropertyName("name")]
public required string Name { get; set; }

[JsonPropertyName("prompt")]
public required string Prompt { get; set; }

[JsonPropertyName("type")]
public required string Type { get; set; }

[JsonPropertyName("commitMessage")]
public string? CommitMessage { get; set; }

[JsonPropertyName("config")]
public object? Config { get; set; }

[JsonPropertyName("labels")]
public string[]? Labels { get; set; }

[JsonPropertyName("tags")]
public string[]? Tags { get; set; }
}
33 changes: 33 additions & 0 deletions Models/CreateLangfusePromptResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Text.Json.Serialization;

namespace PromptProvider.Models;

public record CreateLangfusePromptResponse
{
[JsonPropertyName("name")]
public required string Name { get; set; }

[JsonPropertyName("prompt")]
public required string Prompt { get; set; }

[JsonPropertyName("type")]
public required string Type { get; set; }

[JsonPropertyName("version")]
public int Version { get; set; }

[JsonPropertyName("config")]
public object? Config { get; set; }

[JsonPropertyName("labels")]
public required string[] Labels { get; set; }

[JsonPropertyName("tags")]
public required string[] Tags { get; set; }

[JsonPropertyName("commitMessage")]
public string? CommitMessage { get; set; }

[JsonPropertyName("resolutionGraph")]
public object? ResolutionGraph { get; set; }
}
10 changes: 10 additions & 0 deletions Models/CreatePromptRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace PromptProvider.Models;

public record CreatePromptRequest
{
public required string PromptKey { get; set; }
public required string Content { get; set; }
public string? CommitMessage { get; set; }
public string[]? Labels { get; set; }
public string[]? Tags { get; set; }
}
6 changes: 6 additions & 0 deletions Models/GetPromptsBatchRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace PromptProvider.Models;

public sealed record GetPromptsBatchRequest
{
public required List<PromptConfiguration> Prompts { get; init; }
}
7 changes: 7 additions & 0 deletions Models/GetPromptsBatchResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace PromptProvider.Models;

public sealed record GetPromptsBatchResponse
{
public required List<PromptResponse> Prompts { get; init; }
public required List<string> NotFound { get; init; }
}
27 changes: 27 additions & 0 deletions Models/LangfusePromptListItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;

namespace PromptProvider.Models;

public record LangfusePromptListItem
{
[JsonPropertyName("name")]
public required string Name { get; set; }

[JsonPropertyName("type")]
public required string Type { get; set; }

[JsonPropertyName("tags")]
public required string[] Tags { get; set; }

[JsonPropertyName("labels")]
public required string[] Labels { get; set; }

[JsonPropertyName("lastUpdatedAt")]
public string? LastUpdatedAt { get; set; }

[JsonPropertyName("versions")]
public int[]? Versions { get; set; }

[JsonPropertyName("lastConfig")]
public object? LastConfig { get; set; }
}
39 changes: 39 additions & 0 deletions Models/LangfusePromptModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Text.Json.Serialization;

namespace PromptProvider.Models;

public record LangfusePromptModel
{
[JsonPropertyName("name")]
public required string Name { get; set; }

[JsonPropertyName("type")]
public required string Type { get; set; }

[JsonPropertyName("prompt")]
public required string Prompt { get; set; }

[JsonPropertyName("config")]
public LangfusePromptConfiguration? Config { get; set; }

[JsonPropertyName("version")]
public int Version { get; set; }

[JsonPropertyName("labels")]
public required string[] Labels { get; set; }

[JsonPropertyName("tags")]
public required string[] Tags { get; set; }
}

public record LangfusePromptConfiguration
{
[JsonPropertyName("model")]
public string? Model { get; set; }

[JsonPropertyName("temperature")]
public double? Temperature { get; set; }

[JsonPropertyName("supported_languages")]
public string[]? SupportedLanguages { get; set; }
}
Loading