Files

235 lines
8.0 KiB
C#
Raw Permalink Normal View History

2025-09-19 09:18:09 +08:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace WPFUI.Test.Web
{
/// <summary>
/// TODO
/// </summary>
internal class LLMClient
{
private readonly HttpClient _httpClient;
private readonly List<ChatMessage> _conversationHistory;
private bool _disposed;
/// <summary>
/// 创建LLM客户端实例
/// </summary>
/// <param name="apiKey">API密钥</param>
/// <param name="endpoint">API端点地址</param>
/// <param name="systemPrompt">可选的系统提示词</param>
public LLMClient(string apiKey, string endpoint, string systemPrompt = null)
{
if (string.IsNullOrEmpty(apiKey))
throw new ArgumentNullException(nameof(apiKey));
if (string.IsNullOrEmpty(endpoint))
throw new ArgumentNullException(nameof(endpoint));
_httpClient = new HttpClient
{
BaseAddress = new Uri(endpoint)
};
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiKey);
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// 初始化对话历史,可选添加系统提示
_conversationHistory = new List<ChatMessage>();
if (!string.IsNullOrEmpty(systemPrompt))
{
_conversationHistory.Add(new ChatMessage("system", systemPrompt));
}
}
/// <summary>
/// 获取当前对话历史
/// </summary>
public IReadOnlyList<ChatMessage> ConversationHistory => _conversationHistory.AsReadOnly();
/// <summary>
/// 清除对话历史(保留系统提示)
/// </summary>
public void ClearHistory()
{
if (_conversationHistory.Count > 0 && _conversationHistory[0].Role == "system")
{
var systemMessage = _conversationHistory[0];
_conversationHistory.Clear();
_conversationHistory.Add(systemMessage);
}
else
{
_conversationHistory.Clear();
}
}
/// <summary>
/// 发送非流式请求获取完整响应
/// </summary>
public async Task<string> SendMessageAsync(string userMessage, string model = "qwen-max")
{
// 添加用户消息到历史
_conversationHistory.Add(new ChatMessage("user", userMessage));
// 创建请求内容(包含完整对话历史)
var request = CreateRequestContent(_conversationHistory, model);
HttpResponseMessage response = await _httpClient.PostAsync("", request);
response.EnsureSuccessStatusCode();
string jsonResponse = await response.Content.ReadAsStringAsync();
// 解析响应并添加到对话历史
var responseObj = JsonConvert.DeserializeObject<dynamic>(jsonResponse);
string assistantReply = responseObj?.output?.choices?[0]?.message?.content;
if (!string.IsNullOrEmpty(assistantReply))
{
_conversationHistory.Add(new ChatMessage("assistant", assistantReply));
}
return assistantReply;
}
// 修正关键使用SendAsync替代PostAsync解决.NET Framework参数问题
public async Task SendMessageStreamAsync(string userMessage,
Action<string> onChunkReceived,
string model = "qwen-max",
CancellationToken cancellationToken = default)
{
_conversationHistory.Add(new ChatMessage("user", userMessage));
_conversationHistory.Add(new ChatMessage("assistant", "")); // 占位符
var request = CreateRequestContent(_conversationHistory, model, true);
// 创建HttpRequestMessage对象.NET Framework兼容方式
var httpRequest = new HttpRequestMessage(HttpMethod.Post, _httpClient.BaseAddress)
{
Content = request
};
// 使用SendAsync并指定HttpCompletionOption.NET Framework兼容
using (var response = await _httpClient.SendAsync(
httpRequest,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken))
{
response.EnsureSuccessStatusCode();
using (var stream = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string fullResponse = "";
while (!reader.EndOfStream)
{
string line = await reader.ReadLineAsync();
if (string.IsNullOrWhiteSpace(line)) continue;
// 修正正确处理阿里云SSE格式data: {...}
if (line.StartsWith("data: "))
{
string jsonData = line.Substring(6);
try
{
if (jsonData.Trim() == "[DONE]")
continue;
var json = JsonConvert.DeserializeObject<dynamic>(jsonData);
string content = json?.output?.choices?[0]?.delta?.content;
if (!string.IsNullOrEmpty(content))
{
fullResponse += content;
onChunkReceived?.Invoke(content);
}
}
catch
{
// 忽略无法解析的数据
}
}
}
// 更新助手消息的实际内容
if (_conversationHistory.Count > 0)
{
_conversationHistory[_conversationHistory.Count - 1] =
new ChatMessage("assistant", fullResponse);
}
}
}
}
private StringContent CreateRequestContent(List<ChatMessage> messages, string model, bool stream = false)
{
// 构建符合通义千问API的消息结构
var requestBody = new
{
model,
input = new
{
messages = messages.ConvertAll(m => new { role = m.Role, content = m.Content })
},
parameters = new
{
result_format = "message"
},
stream
};
string json = JsonConvert.SerializeObject(requestBody);
return new StringContent(json, Encoding.UTF8, "application/json");
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_httpClient?.Dispose();
}
_disposed = true;
}
}
}
/// <summary>
/// 表示对话中的单条消息
/// </summary>
public class ChatMessage
{
/// <summary>
/// 消息角色: user, assistant, system
/// </summary>
public string Role { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string Content { get; set; }
public ChatMessage(string role, string content)
{
Role = role;
Content = content;
}
}
}