初始提交
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
using System.ClientModel;
|
||||
using Azure.AI.OpenAI;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace MAF1.Utils;
|
||||
|
||||
public sealed class AgentFactory
|
||||
{
|
||||
private readonly ChatClient _chatClient;
|
||||
|
||||
public AgentFactory(LlmOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(options.ApiKey))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"""
|
||||
还没有配置模型。请在 appsettings.json 的 Llm 节点,或环境变量中设置:
|
||||
OPENAI_API_KEY / Llm:ApiKey
|
||||
OPENAI_ENDPOINT / Llm:Endpoint
|
||||
OPENAI_CHAT_MODEL / Llm:Model
|
||||
""");
|
||||
}
|
||||
|
||||
string model = string.IsNullOrWhiteSpace(options.Model)
|
||||
? DefaultModelFor(options.Endpoint)
|
||||
: options.Model;
|
||||
|
||||
if (LooksLikeAzureOpenAI(options.Endpoint))
|
||||
{
|
||||
Console.Error.WriteLine($"使用 Azure OpenAI: {options.Endpoint} 部署: {model}");
|
||||
_chatClient = new AzureOpenAIClient(new Uri(options.Endpoint), new ApiKeyCredential(options.ApiKey))
|
||||
.GetChatClient(model);
|
||||
return;
|
||||
}
|
||||
|
||||
OpenAIClientOptions clientOptions = new();
|
||||
if (!string.IsNullOrWhiteSpace(options.Endpoint))
|
||||
{
|
||||
clientOptions.Endpoint = ToOpenAICompatibleEndpoint(options.Endpoint);
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"使用 OpenAI 兼容接口: {clientOptions.Endpoint?.ToString() ?? "https://api.openai.com/v1"} 模型: {model}");
|
||||
_chatClient = new OpenAIClient(new ApiKeyCredential(options.ApiKey), clientOptions)
|
||||
.GetChatClient(model);
|
||||
}
|
||||
|
||||
public AIAgent CreateAgent(string name, string instructions, IList<AITool>? tools = null)
|
||||
{
|
||||
return _chatClient.AsAIAgent(instructions: instructions, name: name, tools: tools);
|
||||
}
|
||||
|
||||
public static LlmOptions Load(IConfiguration config)
|
||||
{
|
||||
LlmOptions options = config.GetSection("Llm").Get<LlmOptions>() ?? new LlmOptions();
|
||||
options.ApiKey = FirstNonEmpty(options.ApiKey, Env("OPENAI_API_KEY"), Env("AZURE_OPENAI_API_KEY"));
|
||||
options.Endpoint = FirstNonEmpty(options.Endpoint, Env("OPENAI_ENDPOINT"), Env("OPENAI_BASE_URL"), Env("AZURE_OPENAI_ENDPOINT"));
|
||||
options.Model = FirstNonEmpty(options.Model, Env("OPENAI_CHAT_MODEL"), Env("AZURE_OPENAI_DEPLOYMENT_NAME"));
|
||||
return options;
|
||||
}
|
||||
|
||||
private static string DefaultModelFor(string? endpoint)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(endpoint) && endpoint.Contains("deepseek", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "deepseek-chat";
|
||||
}
|
||||
|
||||
return "gpt-4o-mini";
|
||||
}
|
||||
|
||||
private static bool LooksLikeAzureOpenAI(string? endpoint)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(endpoint) || !Uri.TryCreate(endpoint, UriKind.Absolute, out Uri? uri))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string host = uri.Host;
|
||||
return host.Contains("openai.azure.com", StringComparison.OrdinalIgnoreCase)
|
||||
|| host.Contains("cognitiveservices.azure.com", StringComparison.OrdinalIgnoreCase)
|
||||
|| host.Contains("services.ai.azure.com", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static Uri ToOpenAICompatibleEndpoint(string endpoint)
|
||||
{
|
||||
string trimmed = endpoint.TrimEnd('/');
|
||||
if (!trimmed.EndsWith("/v1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
trimmed += "/v1";
|
||||
}
|
||||
|
||||
return new Uri(trimmed);
|
||||
}
|
||||
|
||||
private static string? Env(string name) => Environment.GetEnvironmentVariable(name);
|
||||
|
||||
private static string FirstNonEmpty(params string?[] values)
|
||||
{
|
||||
foreach (string? value in values)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace MAF1.Utils;
|
||||
|
||||
public static class JsonText
|
||||
{
|
||||
public static string UnwrapObject(string text)
|
||||
{
|
||||
string trimmed = text.Trim();
|
||||
if (trimmed.StartsWith("```", StringComparison.Ordinal))
|
||||
{
|
||||
int firstNewLine = trimmed.IndexOf('\n');
|
||||
int lastFence = trimmed.LastIndexOf("```", StringComparison.Ordinal);
|
||||
if (firstNewLine >= 0 && lastFence > firstNewLine)
|
||||
{
|
||||
trimmed = trimmed[(firstNewLine + 1)..lastFence].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
int objectStart = trimmed.IndexOf('{');
|
||||
int objectEnd = trimmed.LastIndexOf('}');
|
||||
if (objectStart >= 0 && objectEnd > objectStart)
|
||||
{
|
||||
return trimmed[objectStart..(objectEnd + 1)];
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MAF1.Utils;
|
||||
|
||||
public sealed class LlmOptions
|
||||
{
|
||||
public string ApiKey { get; set; } = "";
|
||||
public string Endpoint { get; set; } = "";
|
||||
public string Model { get; set; } = "";
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace MAF1.Utils;
|
||||
|
||||
public static class WindowsConsole
|
||||
{
|
||||
private const uint Utf8CodePage = 65001;
|
||||
private const int StdOutputHandle = -11;
|
||||
private const uint EnableVirtualTerminalProcessing = 0x0004;
|
||||
|
||||
public static void EnableUtf8()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
Console.OutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
Console.InputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
return;
|
||||
}
|
||||
|
||||
SetConsoleOutputCP(Utf8CodePage);
|
||||
SetConsoleCP(Utf8CodePage);
|
||||
|
||||
UTF8Encoding utf8 = new(encoderShouldEmitUTF8Identifier: false);
|
||||
Console.OutputEncoding = utf8;
|
||||
Console.InputEncoding = utf8;
|
||||
|
||||
nint stdout = GetStdHandle(StdOutputHandle);
|
||||
if (stdout != nint.Zero && GetConsoleMode(stdout, out uint mode))
|
||||
{
|
||||
SetConsoleMode(stdout, mode | EnableVirtualTerminalProcessing);
|
||||
}
|
||||
|
||||
StreamWriter writer = new(Console.OpenStandardOutput(), utf8)
|
||||
{
|
||||
AutoFlush = true,
|
||||
};
|
||||
Console.SetOut(writer);
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetConsoleOutputCP(uint wCodePageID);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetConsoleCP(uint wCodePageID);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern nint GetStdHandle(int nStdHandle);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool GetConsoleMode(nint hConsoleHandle, out uint lpMode);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetConsoleMode(nint hConsoleHandle, uint dwMode);
|
||||
}
|
||||
Reference in New Issue
Block a user