feat: Introduce a pluggable LLM provider system with token extraction, pricing, and updated setup configuration.
This commit is contained in:
89
Providers/GenericTokenExtractor.cs
Normal file
89
Providers/GenericTokenExtractor.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AnchorCli.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// Generic token extractor for any OpenAI-compatible endpoint.
|
||||
/// Tries common header names and JSON body parsing.
|
||||
/// </summary>
|
||||
internal sealed class GenericTokenExtractor : ITokenExtractor
|
||||
{
|
||||
public string ProviderName => "Generic";
|
||||
|
||||
public (int inputTokens, int outputTokens)? ExtractTokens(HttpResponseHeaders headers, string? responseBody)
|
||||
{
|
||||
// Try various common header names
|
||||
var headerNames = new[] {
|
||||
"x-total-tokens",
|
||||
"x-ai-response-tokens",
|
||||
"x-tokens",
|
||||
"x-prompt-tokens",
|
||||
"x-completion-tokens"
|
||||
};
|
||||
|
||||
foreach (var headerName in headerNames)
|
||||
{
|
||||
if (headers.TryGetValues(headerName, out var values))
|
||||
{
|
||||
if (int.TryParse(values.FirstOrDefault(), out var tokens))
|
||||
{
|
||||
// Assume all tokens are output if we can't determine split
|
||||
return (0, tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try parsing from response body JSON
|
||||
if (!string.IsNullOrEmpty(responseBody))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(responseBody);
|
||||
var root = doc.RootElement;
|
||||
|
||||
// Try standard OpenAI format: usage.prompt_tokens, usage.completion_tokens
|
||||
if (root.TryGetProperty("usage", out var usage))
|
||||
{
|
||||
var prompt = usage.TryGetProperty("prompt_tokens", out var p) ? p.GetInt32() : 0;
|
||||
var completion = usage.TryGetProperty("completion_tokens", out var c) ? c.GetInt32() : 0;
|
||||
|
||||
if (prompt > 0 || completion > 0)
|
||||
{
|
||||
return (prompt, completion);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore parsing errors
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int? ExtractLatency(HttpResponseHeaders headers)
|
||||
{
|
||||
// Try various common latency headers
|
||||
var headerNames = new[] {
|
||||
"x-response-time",
|
||||
"x-response-timing",
|
||||
"x-latency-ms",
|
||||
"x-duration-ms"
|
||||
};
|
||||
|
||||
foreach (var headerName in headerNames)
|
||||
{
|
||||
if (headers.TryGetValues(headerName, out var values))
|
||||
{
|
||||
if (int.TryParse(values.FirstOrDefault(), out var latency))
|
||||
{
|
||||
return latency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user