Commit 0fff0a7b authored by 段廷瑞's avatar 段廷瑞

pref: 智能体模型选择与交互优化

parent a3a8c203
......@@ -48,6 +48,61 @@ namespace Tools.MyAgent.Agent
public double Temperature { get; set; } = 0.3;
public int MaxTokens { get; set; } = 8192;
/// <summary>获取当前兼容接口公开的模型列表(不影响聊天请求)</summary>
public async Task<(bool Ok, string Message, List<string> Models)> FetchModelsAsync(CancellationToken ct)
{
try
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
cts.CancelAfter(15000);
string baseUrl = BaseUrl?.Trim().TrimEnd('/');
if (string.IsNullOrEmpty(baseUrl))
return (false, "BaseUrl 不能为空", null);
string modelsUrl = baseUrl.EndsWith("/v1", StringComparison.OrdinalIgnoreCase)
? baseUrl + "/models"
: baseUrl + "/v1/models";
using (var req = new HttpRequestMessage(HttpMethod.Get, modelsUrl))
{
if (!string.IsNullOrWhiteSpace(ApiKey))
req.Headers.TryAddWithoutValidation("Authorization", "Bearer " + ApiKey);
using (var resp = await Http.SendAsync(req, cts.Token).ConfigureAwait(false))
{
string json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!resp.IsSuccessStatusCode)
return (false, $"HTTP {(int)resp.StatusCode}:{TruncateModelResponse(json, 200)}", null);
var obj = JObject.Parse(json);
var data = obj["data"] as JArray;
var models = data == null ? new List<string>() : data
.OfType<JObject>()
.Select(x => x["id"]?.Value<string>())
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
.ToList();
return models.Count == 0
? (false, "接口未返回可用模型", null)
: (true, $"获取到 {models.Count} 个模型", models);
}
}
}
}
catch (OperationCanceledException)
{
return (false, "获取模型列表超时,请检查网络与 BaseUrl。", null);
}
catch (Exception ex)
{
return (false, ex.Message, null);
}
}
private static string TruncateModelResponse(string text, int max)
{
if (string.IsNullOrEmpty(text)) return "";
return text.Length <= max ? text : text.Substring(0, max) + "…";
}
/// <summary>是否使用系统代理(默认 true;公司内网常用)。
/// 优先读取 HTTPS_PROXY / HTTP_PROXY 环境变量,否则用系统 IE 代理设置。
/// 修复:原实现属性为死字段(handler 在构造时绑定,开关从不生效);
......@@ -321,10 +376,9 @@ namespace Tools.MyAgent.Agent
return messages.Count > 1;
}
/// <summary>发送一次请求并读取 SSE 流;非成功状态码抛 HttpRequestException(附 Data["HttpStatusCode"])</summary>
private async Task<ChatResult> SendOnceAsync(JObject body, CancellationToken ct)
{
var req = new HttpRequestMessage(HttpMethod.Post, BaseUrl.TrimEnd('/') + "/chat/completions");
var req = new HttpRequestMessage(HttpMethod.Post, ResolveChatEndpoint(BaseUrl));
req.Headers.TryAddWithoutValidation("Authorization", "Bearer " + ApiKey);
req.Content = new StringContent(body.ToString(Formatting.None), Encoding.UTF8, "application/json");
......@@ -372,7 +426,15 @@ namespace Tools.MyAgent.Agent
}
}
/// <summary>连接测试:发送最小补全请求,返回(是否成功,信息)</summary>
private static string ResolveChatEndpoint(string baseUrl)
{
string value = (baseUrl ?? "").Trim().TrimEnd('/');
if (value.EndsWith("/chat/completions", StringComparison.OrdinalIgnoreCase)) return value;
return value.EndsWith("/v1", StringComparison.OrdinalIgnoreCase)
? value + "/chat/completions"
: value + "/v1/chat/completions";
}
public async Task<(bool Ok, string Message)> TestConnectionAsync(CancellationToken ct)
{
try
......@@ -387,7 +449,7 @@ namespace Tools.MyAgent.Agent
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
cts.CancelAfter(15000);
var req = new HttpRequestMessage(HttpMethod.Post, BaseUrl.TrimEnd('/') + "/chat/completions");
var req = new HttpRequestMessage(HttpMethod.Post, ResolveChatEndpoint(BaseUrl));
req.Headers.TryAddWithoutValidation("Authorization", "Bearer " + ApiKey);
req.Content = new StringContent(body.ToString(Formatting.None), Encoding.UTF8, "application/json");
......@@ -396,6 +458,8 @@ namespace Tools.MyAgent.Agent
string json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!resp.IsSuccessStatusCode)
return (false, $"HTTP {(int)resp.StatusCode}:{Truncate(json, 200)}");
if (json.TrimStart().StartsWith("<", StringComparison.Ordinal))
return (false, "接口返回 HTML,请检查 BaseUrl 是否正确(通常应填写服务根地址或 /v1 地址)。");
var obj = JObject.Parse(json);
var content = obj["choices"]?[0]?["message"]?["content"]?.Value<string>();
......
......@@ -23,6 +23,9 @@
/// <summary>【核心】模型名称(可填写任意 OpenAI 兼容模型,如 Ollama 本地模型)</summary>
public string Model { get; set; } = "deepseek-v4-flash";
/// <summary>按提供商保存的模型列表(获取成功后持久化,兼容旧配置缺省为空)</summary>
public Dictionary<string, List<string>> ModelLists { get; set; } = new Dictionary<string, List<string>>();
/// <summary>【核心】知识库根目录(支持本地路径或网络共享 UNC 路径,如 \\服务器\共享名)</summary>
public string KnowledgeBasePath { get; set; } = "";
......
......@@ -36,6 +36,7 @@
this.lblApiKey = new System.Windows.Forms.Label();
this.txtApiKey = new System.Windows.Forms.TextBox();
this.btnTestApi = new System.Windows.Forms.Button();
this.btnFetchModels = new System.Windows.Forms.Button();
this.lblBaseUrl = new System.Windows.Forms.Label();
this.txtBaseUrl = new System.Windows.Forms.TextBox();
this.lblModel = new System.Windows.Forms.Label();
......@@ -165,6 +166,7 @@
this.grpModel.Controls.Add(this.txtBaseUrl);
this.grpModel.Controls.Add(this.lblModel);
this.grpModel.Controls.Add(this.cmbModel);
this.grpModel.Controls.Add(this.btnFetchModels);
this.grpModel.Location = new System.Drawing.Point(8, 12);
this.grpModel.Name = "grpModel";
this.grpModel.Size = new System.Drawing.Size(596, 164);
......@@ -260,9 +262,19 @@
"deepseek-r1"});
this.cmbModel.Location = new System.Drawing.Point(92, 124);
this.cmbModel.Name = "cmbModel";
this.cmbModel.Size = new System.Drawing.Size(486, 25);
this.cmbModel.Size = new System.Drawing.Size(306, 25);
this.cmbModel.TabIndex = 8;
//
// btnFetchModels
//
this.btnFetchModels.Location = new System.Drawing.Point(406, 123);
this.btnFetchModels.Name = "btnFetchModels";
this.btnFetchModels.Size = new System.Drawing.Size(172, 25);
this.btnFetchModels.TabIndex = 9;
this.btnFetchModels.Text = "获取模型列表";
this.btnFetchModels.UseVisualStyleBackColor = true;
this.btnFetchModels.Click += new System.EventHandler(this.OnFetchModels);
//
// grpKb
//
this.grpKb.Controls.Add(this.lblMode);
......@@ -1084,6 +1096,7 @@
private System.Windows.Forms.Label lblApiKey;
private System.Windows.Forms.TextBox txtApiKey;
private System.Windows.Forms.Button btnTestApi;
private System.Windows.Forms.Button btnFetchModels;
private System.Windows.Forms.Label lblBaseUrl;
private System.Windows.Forms.TextBox txtBaseUrl;
private System.Windows.Forms.Label lblModel;
......
This diff is collapsed.
......@@ -216,6 +216,7 @@ namespace Tools.MyAgent
BaseUrl = cfg.BaseUrl,
Model = cfg.Model,
Provider = cfg.Provider,
ModelLists = cfg.ModelLists ?? new Dictionary<string, List<string>>(),
KnowledgeBasePath = cfg.KnowledgeBasePath,
KbMode = cfg.KbMode,
ShareUser = cfg.ShareUser,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment