修复窗口大小问题
This commit is contained in:
234
WPFUI.Test/Web/LLMClient.cs
Normal file
234
WPFUI.Test/Web/LLMClient.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user