Commit 8d4b5d0b authored by 段廷瑞's avatar 段廷瑞

pref: Agent输出格式优化

parent 322b30f7
...@@ -73,3 +73,4 @@ bin ...@@ -73,3 +73,4 @@ bin
/cc /cc
/AppHome/Logs /AppHome/Logs
/AppHome/Package /AppHome/Package
/.workbuddy
No preview for this file type
This diff is collapsed.
This diff is collapsed.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Tools.MyAgent.Agent
{
/// <summary>回答类型(P3 双契约):知识问答走三段式,现场排查走六段式。</summary>
public enum AnswerKind
{
/// <summary>知识问答:原因 / 方案 / 引用</summary>
Knowledge = 0,
/// <summary>现场排查:已确认事实 / 证据 / 根因 / 待验证 / 最小验证 / 修复与回滚</summary>
Diagnostic = 1,
}
/// <summary>
/// 输出形态契约(P3):① 判定本轮问题属于知识问答还是现场排查;② 后置校验回答是否按契约输出。
/// <para>为什么是「双契约」:<c>PromptBuilder.BuildDiagnosticLayer</c> 已强制排查六段式并与产品上下文绑定,
/// 若对所有问题都下发单一三段式,两套互斥要求会同时落到模型头上(详见方案 §0.2)。</para>
/// <para>本类只做判定与校验,不生成提示词正文 —— 契约正文在 <c>prompts/answer_spec.md</c>(外置可定制)+ PromptBuilder 内置回退。</para>
/// </summary>
public static class AnswerSpec
{
/// <summary>诊断类工具(命中即判为排查类问题,与 BuildDiagnosticLayer 的规范一致)</summary>
private static readonly HashSet<string> DiagnosticTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"log_discover", "log_search", "config_read", "assembly_manifest", "diagnose_incident",
};
/// <summary>排查类问题关键词(报错/异常/失败这一类「现场故障」用语)</summary>
private static readonly string[] DiagnosticKeywords =
{
"报错", "报障", "异常", "失败", "闪退", "崩溃", "无法启动", "不能启动", "启动不了", "打不开",
"点不动", "超时", "错误码", "卡死", "死机", "没反应", "无响应", "不生效", "不走了", "乱码",
"堆栈", "调用链", "缺少", "取不到", "读不到", "写不进", "进不去", "看不到", "显示不全",
"数据不对", "对不上", "不一致",
"error", "exception", "failed", "timeout", "crash", "traceback", "nullreference",
};
/// <summary>
/// 指向具体模块/表/调用链的结构性线索:文件名(x.cs / x.sql / x.dll…)、类风格标识(U.Rule、frmXxx)、
/// 数据对象用语(表/字段/外键/存储过程/视图)。仅在绑定产品上下文时参与判定。
/// </summary>
private static readonly Regex StructuralHint = new Regex(
@"[\w\-]+\.(?:cs|sql|dll|xml|config|log|json|exe|csproj)\b"
+ @"|\b(?:frm|uc|w)[A-Z]\w+\b"
+ @"|\b[A-Z]\w+\.[A-Z]\w+\b"
+ @"|表|字段|外键|主键|存储过程|调用链|堆栈",
RegexOptions.Compiled);
private static readonly string[] DiagnosticSections =
{
"已确认事实", "证据", "根因", "待验证", "最小验证", "修复",
};
private static readonly string[] KnowledgeSections = { "原因", "方案", "引用" };
/// <summary>该工具是否属于诊断类(引擎在执行工具时记录,供回答类型判定)</summary>
public static bool IsDiagnosticTool(string toolName)
{
return !string.IsNullOrEmpty(toolName) && DiagnosticTools.Contains(toolName);
}
/// <summary>
/// 判定本轮回答类型。判定顺序:命中诊断类工具 → 报错类关键词 → (绑定产品上下文时)模块/表/调用链线索。
/// <para>拿不准时**偏向六段式**:信息更全,且与既有排查输出规范一致。</para>
/// </summary>
/// <param name="question">用户本轮问题</param>
/// <param name="diagnosticToolHit">本轮是否调用过诊断类工具</param>
/// <param name="productBound">是否绑定了产品上下文(未绑定时不做结构性线索判定,避免误判)</param>
public static AnswerKind Classify(string question, bool diagnosticToolHit, bool productBound)
{
if (diagnosticToolHit) return AnswerKind.Diagnostic;
string q = (question ?? "").Trim();
if (q.Length == 0) return AnswerKind.Knowledge;
string lower = q.ToLowerInvariant();
foreach (var k in DiagnosticKeywords)
if (lower.IndexOf(k, StringComparison.Ordinal) >= 0)
return AnswerKind.Diagnostic;
if (productBound)
{
try
{
if (StructuralHint.IsMatch(q)) return AnswerKind.Diagnostic;
}
catch { }
}
return AnswerKind.Knowledge;
}
/// <summary>
/// 契约校验(后置兜底):返回应加在最终回答顶部的一行提示;合规返回 null。
/// <para>知识问答:必须含「原因」「方案」两节(「引用」允许为空/省略 —— 未命中知识库时不得杜撰引用)。
/// 排查类:六节中至少命中 4 节(真实输出常把「修复与回滚」并作一节)。</para>
/// <para><b>双向放行</b>:判定为知识问答、但回答实际按排查六段式输出(≥4 节)时视为合规。
/// 分类靠关键词启发,必然有偏差;若不放行,用户会看到一条「未按三段式输出」的假警告,
/// 而这恰恰是双契约并存时最不能被接受的噪声。</para>
/// <para>只做提示、不重试:重试会把成本翻倍,而回答本身仍有价值。</para>
/// </summary>
public static string Validate(string content, AnswerKind kind)
{
if (string.IsNullOrWhiteSpace(content)) return null;
if (content.IndexOf("(模型未返回内容)", StringComparison.Ordinal) >= 0) return null;
if (kind == AnswerKind.Diagnostic)
return CountHits(content, DiagnosticSections) >= 4 ? null : "⚠ 未按排查模板输出(已原样保留)";
// 知识问答:模型改用了排查六段式 ⇒ 视为合规(放行,不出假警告)
if (CountHits(content, DiagnosticSections) >= 4) return null;
foreach (var s in KnowledgeSections)
{
if (string.Equals(s, "引用", StringComparison.Ordinal)) continue; // 引用节允许为空(不得杜撰来源)
if (!HasLineSection(content, s)) return "⚠ 未按三段式模板输出(已原样保留)";
}
return null;
}
/// <summary>命中给定小节标题的个数</summary>
private static int CountHits(string content, string[] sections)
{
int hit = 0;
foreach (var s in sections)
if (ContainsSection(content, s)) hit++;
return hit;
}
/// <summary>小节标题是否出现在行首(容忍 <c>###</c> / <c>**</c> / 全角冒号等写法)</summary>
public static bool HasLineSection(string content, string label)
{
if (string.IsNullOrEmpty(content) || string.IsNullOrEmpty(label)) return false;
try
{
return Regex.IsMatch(content,
@"(?:^|\n)[ \t]*(?:#{1,6}[ \t]*)?\**[ \t]*" + Regex.Escape(label) + @"[ \t]*\**[ \t]*[::]?",
RegexOptions.Multiline);
}
catch { return false; }
}
/// <summary>小节标题是否出现(不限定行首):仅用于排查六段式的宽松校验</summary>
public static bool ContainsSection(string content, string label)
{
return !string.IsNullOrEmpty(content) && !string.IsNullOrEmpty(label)
&& content.IndexOf(label, StringComparison.Ordinal) >= 0;
}
}
}
This diff is collapsed.
...@@ -29,6 +29,12 @@ namespace Tools.MyAgent.Agent ...@@ -29,6 +29,12 @@ namespace Tools.MyAgent.Agent
/// <summary>宿主注入的诊断根目录;只用于显示根目录 ID,不显示完整机器路径。</summary> /// <summary>宿主注入的诊断根目录;只用于显示根目录 ID,不显示完整机器路径。</summary>
public IReadOnlyList<DiagnosticRoot> DiagnosticRoots { get; set; } public IReadOnlyList<DiagnosticRoot> DiagnosticRoots { get; set; }
/// <summary>
/// P3 契约化输出开关(<c>AgentConfig.ConciseAnswer</c>);关闭时不注入输出形态契约层,
/// 回答形态回落到改造前的自由格式(与 <c>QuickAnswer</c> 相互独立,可单独回退)。
/// </summary>
public bool ConciseAnswerEnabled { get; set; } = true;
public string BuildSystemMessage(KbService kb) public string BuildSystemMessage(KbService kb)
{ {
// B2 提示词外置:静态层优先读 输出目录/prompts/*.md 模板(用户可定制), // B2 提示词外置:静态层优先读 输出目录/prompts/*.md 模板(用户可定制),
...@@ -60,6 +66,13 @@ namespace Tools.MyAgent.Agent ...@@ -60,6 +66,13 @@ namespace Tools.MyAgent.Agent
sb.AppendLine(LoadLayer(promptsDir, "security.md", SecurityLayer)); sb.AppendLine(LoadLayer(promptsDir, "security.md", SecurityLayer));
sb.AppendLine(); sb.AppendLine();
sb.AppendLine(LoadLayer(promptsDir, "tool_rules.md", ToolRules)); sb.AppendLine(LoadLayer(promptsDir, "tool_rules.md", ToolRules));
// P3 输出形态契约:放在最后一层(最贴近生成位置,权重最高);仅约束输出形态,
// 显式声明不得与安全层冲突,避免与 security.md 并列时被误读为同级规则。
if (ConciseAnswerEnabled)
{
sb.AppendLine();
sb.AppendLine(LoadLayer(promptsDir, "answer_spec.md", AnswerSpecLayer));
}
return sb.ToString(); return sb.ToString();
} }
...@@ -292,5 +305,38 @@ namespace Tools.MyAgent.Agent ...@@ -292,5 +305,38 @@ namespace Tools.MyAgent.Agent
- kb_write / kb_write_kb 一次只写一个文件;覆盖已有文件前先征得用户同意;追加请用 append=true。kb_write 写默认输出目录的 kb_write 子文件夹(Agent/output/kb_write/),kb_write_kb 仅在用户明确要求存入知识库时使用。 - kb_write / kb_write_kb 一次只写一个文件;覆盖已有文件前先征得用户同意;追加请用 append=true。kb_write 写默认输出目录的 kb_write 子文件夹(Agent/output/kb_write/),kb_write_kb 仅在用户明确要求存入知识库时使用。
- 检索结果过多时,先用 kb_list/kb_find 缩小范围,再精读。 - 检索结果过多时,先用 kb_list/kb_find 缩小范围,再精读。
- 工具返回为空或报错时,如实说明,不要假装成功。"; - 工具返回为空或报错时,如实说明,不要假装成功。";
/// <summary>
/// §P3 输出形态契约层(内置回退;外置同内容模板见 <c>Defaults/Agent/prompts/answer_spec.md</c>)。
/// <para>与外置文件**逐字一致**:文件缺失时 <see cref="LoadLayer"/> 会把本常量写盘成默认模板,
/// 二者若不一致,用户「重置模板」后会得到一个与内置回退不同的契约,故必须同步维护。</para>
/// </summary>
private static string AnswerSpecLayer =>
@"【输出形态契约(硬约束,仅约束输出形态,不得与安全层冲突)】
本节规定「最终回答长什么样」;安全层与事实纪律仍然最高优先级,二者冲突时以安全层与事实为准。
判断本轮属于哪一类(拿不准时按第二类即排查类输出,信息更全):
- 第一类 知识问答:查资料、查流程、问「是什么/怎么做/在哪配」等,且不涉及现场故障与代码/表/调用链定位。
- 第二类 现场排查:涉及报错、异常、失败、崩溃、超时、卡死、没反应,或需要定位到具体文件/类/表/调用链/日志。
一、知识问答类 → 只允许三节,顺序固定,节标题用加粗或三级标题,禁止增加其它章节与客套话:
- 原因:不超过 3 行,一句话点明根因或结论;不复述用户问题,不复述原文段落。
- 方案:不超过 5 步的有序列表,每步必须给出具体动作对象(菜单、按钮、字段、配置项、路径)。
- 引用:每行一条,格式固定:📎 <知识库相对路径> §<章节> L<行号> 匹配度 <0.xx>
章节、行号、匹配度三段可缺省(确实没有时省略),但**不得杜撰**路径、章节与行号。
只列本轮真正用到的来源,最多 3 条;未命中知识库时本节写「知识库中未找到相关内容」。
- 禁止出现「首先/其次/总的来说/希望对你有帮助」这类过渡与客套语。
二、现场排查类 → 沿用排查输出规范,六节顺序固定,每节不超过 3 行:
已确认事实 / 证据 / 最可能根因 / 待验证项 / 最小验证步骤 / 修复与回滚建议。
- 每一条「已确认事实」都必须能对应到某条「证据」(日志行、配置项、文件行号)。
- 证据不足时,在「待验证项」写明还缺什么,不要凭经验臆断根因。
- 不得仅凭单条异常就下定论;不得编造文件、表名、调用链或结论。
三、两类的通用禁止:
- 大段原文摘录(超过 3 行的原文引用一律改为摘要 + 引用行)。
- 超过 3 列的表格;代码块仅用于给出命令或配置片段。
- 结尾总结段、免责声明、与问题无关的扩展建议。
- 回答长度与问题复杂度匹配:简单问题给出简短三段式即可,不刻意展开。";
} }
} }
...@@ -110,6 +110,39 @@ ...@@ -110,6 +110,39 @@
/// <summary>是否启用图片提问(多模态,v2.2):启用后粘贴截图随提问以 image_url 发送,需视觉模型支持(如 DeepSeek-VL / qwen-vl);默认启用</summary> /// <summary>是否启用图片提问(多模态,v2.2):启用后粘贴截图随提问以 image_url 发送,需视觉模型支持(如 DeepSeek-VL / qwen-vl);默认启用</summary>
public bool EnableImageInput { get; set; } = true; public bool EnableImageInput { get; set; } = true;
// ════════════════ 高级项:回答形态(旋转指示 / 本地直答 / 契约化输出 / 附图) ════════════════
/// <summary>
/// 本地召回直答(回答形态改造 P3):本轮问题在知识库命中达阈值时,走「单轮直答」通道(不下发工具),
/// 不再进入多轮工具循环;召回失败/异常一律静默降级回原工具循环。关闭即完全回到改造前行为。
/// </summary>
public bool QuickAnswer { get; set; } = true;
/// <summary>
/// 契约化输出(由 prompts/answer_spec.md 约束 + 后置正则兜底):
/// 知识问答类走三段式(原因/方案/引用),排查类走压缩六段式。关闭则不追加契约层、不做兜底标注。
/// </summary>
public bool ConciseAnswer { get; set; } = true;
/// <summary>
/// 是否仍在问答区渲染思考文本。默认 false:思考只由右下角旋转指示表达,不再铺满问答区(也避免流式每帧重绘);
/// 置 true 恢复旧行为(调试/排查模型思维链时使用)。
/// </summary>
public bool ShowThinking { get; set; } = false;
/// <summary>回答附图(P5):本轮回答命中文档关联的图片以缩略图栏展示,点击放大(复用 wBitmapView)</summary>
public bool AnswerWithImages { get; set; } = true;
/// <summary>回答附图上限(张,1~8)</summary>
public int MaxAnswerImages { get; set; } = 4;
/// <summary>
/// 本地直答阈值(0.45~0.95):融合匹配度 ≥ 该值走单轮直答(mode=kb);
/// [0.45, 该值) 走「部分命中」(mode=mixed,证据 + 允许模型补充并标注);
/// 低于 0.45 视为未召回,**回退原工具循环**(保留原有检索/诊断能力,不因新通道削弱能力)。
/// </summary>
public double KbHitThreshold { get; set; } = 0.72;
// ════════════════ 高级项:策略参数(R12) ════════════════ // ════════════════ 高级项:策略参数(R12) ════════════════
/// <summary>工具调用轮次上限(默认 15;超限后进入收尾回答轮,基于已收集信息汇总作答)</summary> /// <summary>工具调用轮次上限(默认 15;超限后进入收尾回答轮,基于已收集信息汇总作答)</summary>
......
【输出形态契约(硬约束,仅约束输出形态,不得与安全层冲突)】
本节规定「最终回答长什么样」;安全层与事实纪律仍然最高优先级,二者冲突时以安全层与事实为准。
判断本轮属于哪一类(拿不准时按第二类即排查类输出,信息更全):
- 第一类 知识问答:查资料、查流程、问「是什么/怎么做/在哪配」等,且不涉及现场故障与代码/表/调用链定位。
- 第二类 现场排查:涉及报错、异常、失败、崩溃、超时、卡死、没反应,或需要定位到具体文件/类/表/调用链/日志。
一、知识问答类 → 只允许三节,顺序固定,节标题用加粗或三级标题,禁止增加其它章节与客套话:
- 原因:不超过 3 行,一句话点明根因或结论;不复述用户问题,不复述原文段落。
- 方案:不超过 5 步的有序列表,每步必须给出具体动作对象(菜单、按钮、字段、配置项、路径)。
- 引用:每行一条,格式固定:📎 <知识库相对路径> §<章节> L<行号> 匹配度 <0.xx>
章节、行号、匹配度三段可缺省(确实没有时省略),但**不得杜撰**路径、章节与行号。
只列本轮真正用到的来源,最多 3 条;未命中知识库时本节写「知识库中未找到相关内容」。
- 禁止出现「首先/其次/总的来说/希望对你有帮助」这类过渡与客套语。
二、现场排查类 → 沿用排查输出规范,六节顺序固定,每节不超过 3 行:
已确认事实 / 证据 / 最可能根因 / 待验证项 / 最小验证步骤 / 修复与回滚建议。
- 每一条「已确认事实」都必须能对应到某条「证据」(日志行、配置项、文件行号)。
- 证据不足时,在「待验证项」写明还缺什么,不要凭经验臆断根因。
- 不得仅凭单条异常就下定论;不得编造文件、表名、调用链或结论。
三、两类的通用禁止:
- 大段原文摘录(超过 3 行的原文引用一律改为摘要 + 引用行)。
- 超过 3 列的表格;代码块仅用于给出命令或配置片段。
- 结尾总结段、免责声明、与问题无关的扩展建议。
- 回答长度与问题复杂度匹配:简单问题给出简短三段式即可,不刻意展开。
...@@ -120,6 +120,7 @@ namespace Tools.MyAgent.Forms ...@@ -120,6 +120,7 @@ namespace Tools.MyAgent.Forms
InitializeComponent(); InitializeComponent();
BuildDbConnectionsTab(); // P0:预存数据库连接(db_query 按 ID 引用,聊天不回显密码) BuildDbConnectionsTab(); // P0:预存数据库连接(db_query 按 ID 引用,聊天不回显密码)
BuildToolPermissionUi(); // P3:工具级权限(按角色)输入区 BuildToolPermissionUi(); // P3:工具级权限(按角色)输入区
BuildAnswerShapeTab(); // 回答形态:旋转思考指示 / 本地直答 / 契约化输出 / 回答附图
BindConfig(); // 配置值回填控件(控件创建见 Designer.cs) BindConfig(); // 配置值回填控件(控件创建见 Designer.cs)
ApplyProviderMode(); // 布局联动:BaseUrl 行显隐 ApplyProviderMode(); // 布局联动:BaseUrl 行显隐
ApplyKbMode(); // 布局联动:共享 账号/密码/测试连接 行显隐 ApplyKbMode(); // 布局联动:共享 账号/密码/测试连接 行显隐
...@@ -235,6 +236,99 @@ namespace Tools.MyAgent.Forms ...@@ -235,6 +236,99 @@ namespace Tools.MyAgent.Forms
return rules; return rules;
} }
// ══ 回答形态:旋转思考指示 / 本地召回直答 / 契约化输出 / 回答附图 ══
private CheckBox chkQuickAnswer;
private CheckBox chkConciseAnswer;
private CheckBox chkShowThinking;
private CheckBox chkAnswerImages;
private NumericUpDown numMaxAnswerImages;
private NumericUpDown numKbHitThreshold;
/// <summary>
/// 新增「回答形态」选项卡(程序化创建,避免侵入设计器):
/// 思考呈现方式、本地召回直答、契约化输出、回答附图与直答阈值。
/// </summary>
private void BuildAnswerShapeTab()
{
var tabShape = new TabPage { Text = "回答形态", Padding = new Padding(3) };
var grp = new GroupBox
{
Text = "回答形态(思考指示 / 本地直答 / 契约化输出 / 附图)",
Location = new Point(8, 12),
Size = new Size(596, 324),
};
chkQuickAnswer = new CheckBox
{
Text = "本地召回直答:知识库命中达阈值时单轮直答(跳过工具循环);召回异常自动降级",
Location = new Point(16, 28),
AutoSize = true,
};
chkConciseAnswer = new CheckBox
{
Text = "契约化输出:知识问答三段式(原因/方案/引用),排查类六段式",
Location = new Point(16, 56),
AutoSize = true,
};
chkShowThinking = new CheckBox
{
Text = "仍在问答区渲染思考文本(默认关闭:仅右下旋转指示;调试用)",
Location = new Point(16, 84),
AutoSize = true,
};
chkAnswerImages = new CheckBox
{
Text = "回答附图:命中文档关联的图片以缩略图栏展示(点击放大)",
Location = new Point(16, 112),
AutoSize = true,
};
var lblThreshold = new Label { Text = "直答阈值", Location = new Point(16, 150), AutoSize = true };
numKbHitThreshold = new NumericUpDown
{
Location = new Point(96, 146),
Size = new Size(70, 23),
DecimalPlaces = 2,
Increment = 0.01M,
Minimum = 0.45M,
Maximum = 0.95M,
Value = 0.72M,
};
var lblMaxImages = new Label { Text = "附图上限", Location = new Point(196, 150), AutoSize = true };
numMaxAnswerImages = new NumericUpDown
{
Location = new Point(276, 146),
Size = new Size(70, 23),
Minimum = 1,
Maximum = 8,
Value = 4,
};
var hint = new Label
{
Text = "直答阈值:[阈值, 0.95] 直接以证据作答;[0.45, 阈值) 标注「部分命中」并允许模型补充;"
+ "低于 0.45 视为未召回,回退原多轮工具循环。\r\n"
+ "关闭「本地召回直答」后行为与改造前完全一致(多轮工具循环 + 原回答形态)。",
Location = new Point(16, 186),
AutoSize = false,
Size = new Size(564, 60),
ForeColor = Color.Gray,
};
grp.Controls.Add(chkQuickAnswer);
grp.Controls.Add(chkConciseAnswer);
grp.Controls.Add(chkShowThinking);
grp.Controls.Add(chkAnswerImages);
grp.Controls.Add(lblThreshold);
grp.Controls.Add(numKbHitThreshold);
grp.Controls.Add(lblMaxImages);
grp.Controls.Add(numMaxAnswerImages);
grp.Controls.Add(hint);
tabShape.Controls.Add(grp);
tabMain.TabPages.Add(tabShape);
}
// ══ 配置绑定:把 AgentConfig 值回填到控件(设计器仅设默认值) ══ // ══ 配置绑定:把 AgentConfig 值回填到控件(设计器仅设默认值) ══
private void BindConfig() private void BindConfig()
...@@ -296,6 +390,14 @@ namespace Tools.MyAgent.Forms ...@@ -296,6 +390,14 @@ namespace Tools.MyAgent.Forms
// 可选能力页:工具级权限(P3,角色白名单) // 可选能力页:工具级权限(P3,角色白名单)
txtToolRoles.Lines = ToolRoleRulesToLines(_config.ToolRoleAllow); txtToolRoles.Lines = ToolRoleRulesToLines(_config.ToolRoleAllow);
// 回答形态页:思考指示 / 本地直答 / 契约化输出 / 附图 / 阈值
chkQuickAnswer.Checked = _config.QuickAnswer;
chkConciseAnswer.Checked = _config.ConciseAnswer;
chkShowThinking.Checked = _config.ShowThinking;
chkAnswerImages.Checked = _config.AnswerWithImages;
numMaxAnswerImages.Value = ClampToControl(_config.MaxAnswerImages, numMaxAnswerImages, 4);
numKbHitThreshold.Value = ClampDecimalToControl(_config.KbHitThreshold, numKbHitThreshold, 0.72M);
// 数据库连接(P0):预存连接串(内存明文态)回填表格 // 数据库连接(P0):预存连接串(内存明文态)回填表格
dgvDbConn.Rows.Clear(); dgvDbConn.Rows.Clear();
if (_config.DbConnections != null) if (_config.DbConnections != null)
...@@ -751,6 +853,13 @@ namespace Tools.MyAgent.Forms ...@@ -751,6 +853,13 @@ namespace Tools.MyAgent.Forms
_config.EnableImageInput = chkImageInput.Checked; // v2.2 多模态:图片提问开关 _config.EnableImageInput = chkImageInput.Checked; // v2.2 多模态:图片提问开关
// 可选能力页:工具级权限(P3) // 可选能力页:工具级权限(P3)
_config.ToolRoleAllow = ParseToolRoleRules(txtToolRoles.Text); _config.ToolRoleAllow = ParseToolRoleRules(txtToolRoles.Text);
// 回答形态页:思考指示 / 本地直答 / 契约化输出 / 附图 / 阈值
_config.QuickAnswer = chkQuickAnswer.Checked;
_config.ConciseAnswer = chkConciseAnswer.Checked;
_config.ShowThinking = chkShowThinking.Checked;
_config.AnswerWithImages = chkAnswerImages.Checked;
_config.MaxAnswerImages = (int)numMaxAnswerImages.Value;
_config.KbHitThreshold = (double)numKbHitThreshold.Value;
// 数据库连接(P0):表格 → 字典(ID 大小写不敏感,空格清理;空 ID 行忽略) // 数据库连接(P0):表格 → 字典(ID 大小写不敏感,空格清理;空 ID 行忽略)
var conns = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var conns = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DataGridViewRow r in dgvDbConn.Rows) foreach (DataGridViewRow r in dgvDbConn.Rows)
......
using System;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
namespace Tools.MyAgent.Forms
{
/// <summary>
/// 文档行定位查看器(P4 引用行点击):只读展示文本类文件,并定位高亮到指定行。
/// <para>纯代码构造(无 Designer / resx 依赖,与 <see cref="wBitmapView"/> 同制);非文本格式(docx/xlsx/pdf/pptx)
/// 由调用方改走默认程序打开。</para>
/// <para>窗口为非模态(不阻塞继续提问),Esc 关闭;高亮用选区背景色实现,焦点移开仍可见。</para>
/// </summary>
public sealed class DocLineViewer : Form
{
/// <summary>可内置定位查看的文本类扩展名(其余交默认程序)</summary>
private static readonly string[] ViewableExtensions =
{
".md", ".markdown", ".txt", ".log", ".csv", ".tsv", ".json", ".xml", ".yml", ".yaml",
".cs", ".sql", ".config", ".ini", ".bat", ".cmd", ".ps1", ".js", ".ts", ".py",
".html", ".htm", ".css", ".vue", ".java", ".cpp", ".h",
};
/// <summary>超长文件的展示行上限(避免超大日志把 RichTextBox 拖死)</summary>
private const int MaxShowLines = 20000;
/// <summary>命中行高亮底色</summary>
private static readonly Color HighlightColor = Color.FromArgb(255, 244, 170);
private readonly string _file;
private readonly RichTextBox _box;
private readonly Label _hint;
private readonly int _totalLines;
private readonly bool _clipped;
/// <summary>是否支持内置定位查看(非文本格式返回 false,调用方应改走默认程序打开)</summary>
public static bool CanView(string file)
{
if (string.IsNullOrEmpty(file)) return false;
string ext;
try { ext = Path.GetExtension(file).ToLowerInvariant(); }
catch { return false; }
foreach (var e in ViewableExtensions)
if (string.Equals(e, ext, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
private DocLineViewer(string file, int requestedLine, string text)
{
_file = file;
this.Text = "文档定位 — " + SafeFileName(file);
this.FormBorderStyle = FormBorderStyle.SizableToolWindow;
this.ShowInTaskbar = false;
this.StartPosition = FormStartPosition.CenterParent;
this.ClientSize = new Size(920, 620);
this.MinimumSize = new Size(420, 260);
this.KeyPreview = true;
this.Font = new Font("微软雅黑", 9F);
var lines = (text ?? "").Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
_totalLines = lines.Length;
_clipped = lines.Length > MaxShowLines;
string shown = _clipped ? string.Join("\n", lines, 0, MaxShowLines) : (text ?? "");
_box = new RichTextBox
{
Dock = DockStyle.Fill,
ReadOnly = true,
BorderStyle = BorderStyle.None,
BackColor = Color.White,
Font = new Font("微软雅黑", 10F),
WordWrap = true,
ScrollBars = RichTextBoxScrollBars.Vertical,
DetectUrls = false,
HideSelection = false, // 焦点移开仍保持命中行高亮可见
TabStop = false,
};
_box.Text = shown;
_hint = new Label
{
Dock = DockStyle.Bottom,
Height = 26,
TextAlign = ContentAlignment.MiddleLeft,
Padding = new Padding(8, 0, 8, 0),
BackColor = Color.FromArgb(245, 245, 245),
ForeColor = Color.FromArgb(90, 90, 90),
AutoEllipsis = true,
// Show() 到 GotoLine() 之间会先渲染一帧:此处先给出目标行,避免底部空条闪一下
Text = requestedLine > 0 ? $"正在定位到第 {requestedLine} 行…" : "",
};
var tip = new Label
{
Dock = DockStyle.Top,
Height = 24,
TextAlign = ContentAlignment.MiddleLeft,
Padding = new Padding(8, 0, 8, 0),
BackColor = Color.FromArgb(250, 250, 252),
ForeColor = Color.FromArgb(120, 120, 120),
Text = "只读预览(Esc 关闭)· " + file,
AutoEllipsis = true,
};
this.Controls.Add(_box);
this.Controls.Add(_hint);
this.Controls.Add(tip);
this.KeyDown += (s, e) => { if (e.KeyCode == Keys.Escape) this.Close(); };
this.FormClosed += (s, e) =>
{
try { if (_box.Font != null) _box.Font.Dispose(); } catch { }
try { if (_box != null) _box.Dispose(); } catch { }
};
}
/// <summary>
/// 打开文件并定位到指定行(line ≤ 0 或超出范围时自动夹到首/末行)。
/// <para>读取失败不抛异常:窗口内显示失败原因,便于用户判断是「引用错了」还是「文件不可读」。</para>
/// </summary>
public static DocLineViewer ShowLine(string file, int line, Form owner = null)
{
string text = ReadAllTextSafe(file);
var v = new DocLineViewer(file, line, text);
if (owner != null && owner.IsHandleCreated && owner.Visible)
v.Owner = owner;
v.Show();
v.Activate();
v.GotoLine(line);
return v;
}
/// <summary>定位并高亮第 line 行(1 基),同时刷新底部提示</summary>
private void GotoLine(int line)
{
try
{
int total = _box.Lines.Length;
if (total <= 0) { _hint.Text = "(空文件)"; return; }
int target = line < 1 ? 1 : (line > total ? total : line);
int first = _box.GetFirstCharIndexFromLine(target - 1);
if (first < 0) first = 0;
_box.Select(first, 0);
_box.ScrollToCaret(); // 先滚到行首,避免长行末尾跳位
int len = 0;
try { len = _box.Lines[target - 1].Length; } catch { }
if (len > 0)
{
_box.Select(first, len);
_box.SelectionBackColor = HighlightColor;
}
_box.Select(first, 0);
_box.ScrollToCaret();
_hint.Text = _clipped
? $"已定位到第 {target} 行 · 共 {_totalLines} 行(仅展示前 {MaxShowLines} 行)"
: $"已定位到第 {target} 行 · 共 {_totalLines} 行";
}
catch (Exception ex)
{
_hint.Text = "定位失败:" + ex.Message;
}
}
/// <summary>读取文本(复用知识库的编码自动识别,兼容 GBK/UTF-8/UTF-16;失败返回提示文本而非抛异常)</summary>
private static string ReadAllTextSafe(string file)
{
try
{
if (string.IsNullOrEmpty(file) || !File.Exists(file))
return "(文件不存在或不可访问:" + file + ")";
string text = Tools.MyAgent.Kb.KbService.ReadTextFile(file);
if (text != null) return text;
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var sr = new StreamReader(fs, Encoding.UTF8, true))
return sr.ReadToEnd();
}
catch (Exception ex)
{
return "(读取失败:" + ex.Message + ")";
}
}
private static string SafeFileName(string file)
{
try { return Path.GetFileName(file); }
catch { return file ?? ""; }
}
}
}
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace Tools.MyAgent.Forms
{
/// <summary>
/// 思考旋转指示器(纯代码构建,无 Designer / resx 依赖)。
/// <para>24×24 的小控件,Timer(45ms) 驱动一段 240° 渐变弧旋转,用于替代「把思维链文本铺满问答区」的旧形态。</para>
/// <para>用法:<see cref="Thinking"/>=true 时显示并旋转;=false 时停止计时器并隐藏(不占布局、不耗帧)。</para>
/// </summary>
public sealed class ThinkingSpinner : Control
{
/// <summary>逻辑尺寸(未按 DPI 缩放前)</summary>
private const int BaseSize = 24;
/// <summary>动画帧间隔(毫秒)</summary>
private const int TickMs = 45;
/// <summary>弧线张角</summary>
private const int SweepDegrees = 240;
/// <summary>拖尾分段数(越多越平滑)</summary>
private const int Segments = 24;
/// <summary>每帧角度增量</summary>
private const int AngleStep = 12;
private readonly Timer _timer;
private int _angle;
private bool _thinking;
private float _scale = 1f;
public ThinkingSpinner()
{
SetStyle(ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint
| ControlStyles.OptimizedDoubleBuffer
| ControlStyles.ResizeRedraw, true);
// 与 panelChat / RichTextBox 一致的白底:避免半透明混色残影,也不依赖父容器重绘
BackColor = Color.White;
TabStop = false;
Visible = false;
Size = new Size(BaseSize, BaseSize);
_timer = new Timer { Interval = TickMs };
_timer.Tick += OnTick;
}
/// <summary>是否处于思考中(true:显示并旋转;false:隐藏并停止计时器)</summary>
public bool Thinking
{
get { return _thinking; }
set
{
if (_thinking == value) return;
_thinking = value;
if (value)
{
_angle = 0;
Visible = true;
_timer.Start();
}
else
{
_timer.Stop();
Visible = false;
}
Invalidate();
}
}
private void OnTick(object sender, EventArgs e)
{
_angle = (_angle + AngleStep) % 360;
Invalidate();
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ApplyScale();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (!_thinking) return;
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
float scale = _scale <= 0f ? 1f : _scale;
float penWidth = Math.Max(1.5f, 3f * scale);
float radius = (BaseSize - 8) * scale / 2f;
if (radius <= 1f) return;
float cx = ClientSize.Width / 2f;
float cy = ClientSize.Height / 2f;
var rect = new RectangleF(cx - radius, cy - radius, radius * 2f, radius * 2f);
// 拖尾渐隐:240° 均分 24 段,i=0 为弧头(最亮),随 i 增大透明度下降形成尾迹
float segSweep = (float)SweepDegrees / Segments;
for (int i = 0; i < Segments; i++)
{
int alpha = (int)(255 * (1.0 - (double)i / Segments) * 0.9) + 20;
if (alpha > 255) alpha = 255;
if (alpha < 12) alpha = 12;
using (var pen = new Pen(Color.FromArgb(alpha, 0, 120, 212), penWidth))
{
pen.StartCap = LineCap.Round;
pen.EndCap = LineCap.Round;
// 从弧头向后逐段绘制;+0.6° 交叠,避免 AntiAlias 下分段间出现细缝
float start = _angle + SweepDegrees - (i + 1) * segSweep;
g.DrawArc(pen, rect, start, segSweep + 0.6f);
}
}
}
/// <summary>按当前 DPI 计算缩放并同步控件尺寸(高 DPI 下弧线不糊、占位不偏)</summary>
private void ApplyScale()
{
try
{
using (var g = CreateGraphics())
_scale = g.DpiX > 0 ? g.DpiX / 96f : 1f;
}
catch { _scale = 1f; }
Size = new Size(Scaled(BaseSize), Scaled(BaseSize));
}
private int Scaled(int v)
{
float scale = _scale <= 0f ? 1f : _scale;
return (int)Math.Ceiling(v * scale);
}
protected override void Dispose(bool disposing)
{
if (disposing && _timer != null)
{
_timer.Stop();
_timer.Tick -= OnTick;
_timer.Dispose();
}
base.Dispose(disposing);
}
}
}
namespace Tools.MyAgent.Kb
{
/// <summary>
/// 结构化内容命中(P2 回答形态改造):携带匹配度与行号,供引擎侧「本地召回直答」通道打分与引用定位使用。
/// <para>与 kb_grep 的既有文本返回格式互不影响 —— 后者面向模型(格式即数据),本类型面向引擎(可计算、可定位)。</para>
/// </summary>
public sealed class ContentHit
{
/// <summary>知识库内相对路径('/' 分隔;与 kb_* 工具输出一致,便于引用行直接落地)</summary>
public string Rel = "";
/// <summary>命中文段(单行化,≤140 字)</summary>
public string Snippet = "";
/// <summary>命中行号(1 基;0 = 无法定位,如 pdf/pptx 等无行概念的格式)</summary>
public int Line;
/// <summary>
/// 该来源的证据基分(0~1):index 0.60 / scan 0.70 / synonym = index×0.85 / fuzzy 取模糊分原值。
/// <para>路由基分留在本层(只有本层知道命中的真实来源);上层据此叠加查询词覆盖率与多路共现加分。</para>
/// </summary>
public double Base;
/// <summary>来源:index / scan / synonym / fuzzy(供共现加分与轨迹展示)</summary>
public string Source = "";
}
}
This diff is collapsed.
...@@ -22,5 +22,17 @@ namespace Tools.MyAgent ...@@ -22,5 +22,17 @@ namespace Tools.MyAgent
/// <summary>回复末尾用量标注(展示用,不参与模型上下文/历史导出):如「本回复 1284+312 tokens(约 ¥0.42)」</summary> /// <summary>回复末尾用量标注(展示用,不参与模型上下文/历史导出):如「本回复 1284+312 tokens(约 ¥0.42)」</summary>
public string UsageSuffix { get; set; } public string UsageSuffix { get; set; }
/// <summary>
/// 回答附图(P5):本轮回答命中的知识库关联图片(本地绝对路径,已过沙箱校验)。
/// <para>仅助手消息使用;旧会话缺该字段 = <c>null</c>,渲染走默认分支(无需迁移代码)。</para>
/// </summary>
public List<string> AnswerImages { get; set; }
/// <summary>
/// 本轮回答形态(P5):<c>kb</c>(知识库直答)/ <c>mixed</c>(部分命中,含模型补充)。
/// <para>留空表示走原工具循环(形态未标注)。</para>
/// </summary>
public string AnswerMode { get; set; }
} }
} }
...@@ -78,9 +78,14 @@ namespace Tools.MyAgent ...@@ -78,9 +78,14 @@ namespace Tools.MyAgent
private static string MessageKey(ChatMessage m, string assistantName, string userName, bool detailsExpanded) private static string MessageKey(ChatMessage m, string assistantName, string userName, bool detailsExpanded)
{ {
// 思考文本仅在「仍渲染思考」(MarkdownRenderer.ShowThinking=true)时参与缓存键:
// 默认形态下思考不进问答区,若仍把它计入键,流式思考增量会每帧击穿缓存、整块重建 RTF(CPU 白耗)
string thinking = MarkdownRenderer.ShowThinking ? (m.ThinkingContent ?? "") : "";
return (m.Role ?? "") + "|" + m.Timestamp.Ticks + "|" return (m.Role ?? "") + "|" + m.Timestamp.Ticks + "|"
+ (m.Content ?? "") + "|" + (m.ThinkingContent ?? "") + "|" + m.IsStreaming + (m.Content ?? "") + "|" + thinking + "|" + m.IsStreaming
+ "|" + (m.UsageSuffix ?? "") // 用量标注:完成时附加,需纳入缓存键以触发重绘 + "|" + (m.UsageSuffix ?? "") // 用量标注:完成时附加,需纳入缓存键以触发重绘
+ "|" + (m.AnswerMode ?? "") // P5 回答形态标注:直答完成时附加,同上
+ "|" + (m.AnswerImages != null ? m.AnswerImages.Count : 0) // 形态标注里含图片数
+ "|" + assistantName + "|" + userName + "|" + detailsExpanded; + "|" + assistantName + "|" + userName + "|" + detailsExpanded;
} }
} }
......
...@@ -71,6 +71,13 @@ namespace Tools.MyAgent ...@@ -71,6 +71,13 @@ namespace Tools.MyAgent
"type ", "find ", "findstr ", "where ", "set ", "echo ", "cls " "type ", "find ", "findstr ", "where ", "set ", "echo ", "cls "
}; };
/// <summary>
/// 是否仍在问答区渲染思考文本(由宿主按配置注入,默认 false)。
/// <para>默认形态:思考只由右下旋转指示表达,不再把思维链整块铺进问答区(也避免流式每帧重绘)。
/// 置 true 恢复旧行为,便于调试/排查模型思维链。</para>
/// </summary>
public static bool ShowThinking { get; set; }
public static string BuildChatRtf(List<ChatMessage> messages, string welcomeText, string assistantName = "Claude", string userName = "You") public static string BuildChatRtf(List<ChatMessage> messages, string welcomeText, string assistantName = "Claude", string userName = "You")
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
...@@ -114,6 +121,25 @@ namespace Tools.MyAgent ...@@ -114,6 +121,25 @@ namespace Tools.MyAgent
return sb.ToString(); return sb.ToString();
} }
/// <summary>
/// P5 回答形态标注文案(展示用,不进入模型上下文/历史导出):
/// <c>kb</c> → 知识库直答;<c>mixed</c> → 知识库部分命中(含模型补充);
/// 未标注(走原工具循环)或未知取值返回空串。附带关联图片数,便于对上下方附图轨属于哪一轮。
/// </summary>
private static string AnswerModeBadge(ChatMessage msg)
{
string mode = msg.AnswerMode ?? "";
string head;
if (string.Equals(mode, "kb", StringComparison.OrdinalIgnoreCase))
head = "知识库直答";
else if (string.Equals(mode, "mixed", StringComparison.OrdinalIgnoreCase))
head = "知识库部分命中(含模型补充)";
else
return "";
int n = msg.AnswerImages != null ? msg.AnswerImages.Count : 0;
return n > 0 ? "📚 " + head + " · " + n + " 图" : "📚 " + head;
}
private static void RenderMessage(StringBuilder sb, ChatMessage msg, string assistantName, string userName, bool expandDetails = false) private static void RenderMessage(StringBuilder sb, ChatMessage msg, string assistantName, string userName, bool expandDetails = false)
{ {
bool isUser = msg.Role == "user"; bool isUser = msg.Role == "user";
...@@ -127,6 +153,16 @@ namespace Tools.MyAgent ...@@ -127,6 +153,16 @@ namespace Tools.MyAgent
sb.Append(ClTs); sb.Append(ClTs);
sb.Append(@"\fs16 "); sb.Append(@"\fs16 ");
sb.Append(msg.Timestamp.ToString("HH:mm:ss")); sb.Append(msg.Timestamp.ToString("HH:mm:ss"));
// P5 回答形态标注:让用户一眼分清「知识库直答」与「含模型补充」,
// 与回答正文里的引用行互为印证(未走直答的轮次不显示任何标注)
string badge = AnswerModeBadge(msg);
if (badge.Length > 0)
{
sb.Append(@"\cf"); sb.Append(ClBq);
sb.Append(@" · ");
sb.Append(Escape(badge));
sb.Append(@"\cf"); sb.Append(ClTs);
}
sb.Append(@"\par"); sb.Append(@"\par");
// Content // Content
...@@ -141,7 +177,8 @@ namespace Tools.MyAgent ...@@ -141,7 +177,8 @@ namespace Tools.MyAgent
} }
else else
{ {
if (!string.IsNullOrEmpty(msg.ThinkingContent)) // 思考文本:默认不渲染(改由 wMyAgent 右下旋转指示表达);ShowThinking=true 时恢复旧行为(兜底/调试开关)
if (ShowThinking && !string.IsNullOrEmpty(msg.ThinkingContent))
{ {
sb.Append(@"\cf"); sb.Append(ClThinking); sb.Append(@"\cf"); sb.Append(ClThinking);
sb.Append(@"\i\fs18 "); sb.Append(@"\i\fs18 ");
......
...@@ -101,6 +101,8 @@ ...@@ -101,6 +101,8 @@
<Compile Include="Agent\FailoverPolicy.cs" /> <Compile Include="Agent\FailoverPolicy.cs" />
<Compile Include="Agent\IOpenAiChatClient.cs" /> <Compile Include="Agent\IOpenAiChatClient.cs" />
<Compile Include="Agent\OpenAiCompatibleClient.cs" /> <Compile Include="Agent\OpenAiCompatibleClient.cs" />
<Compile Include="Agent\Answer\KbRecall.cs" />
<Compile Include="Agent\Answer\AnswerSpec.cs" />
<Compile Include="Agent\PromptBuilder.cs" /> <Compile Include="Agent\PromptBuilder.cs" />
<Compile Include="Agent\ToolSchema.cs" /> <Compile Include="Agent\ToolSchema.cs" />
<Compile Include="Agent\Tools\ITool.cs" /> <Compile Include="Agent\Tools\ITool.cs" />
...@@ -120,11 +122,28 @@ ...@@ -120,11 +122,28 @@
<Compile Include="Config\AgentConfig.cs" /> <Compile Include="Config\AgentConfig.cs" />
<Compile Include="Config\RoleTemplateStore.cs" /> <Compile Include="Config\RoleTemplateStore.cs" />
<Compile Include="Config\SecretProtector.cs" /> <Compile Include="Config\SecretProtector.cs" />
<Compile Include="Forms\AgentHistoryForm.cs"><SubType>Form</SubType></Compile> <Compile Include="Forms\AgentHistoryForm.cs">
<Compile Include="Forms\AgentHistoryForm.Designer.cs"><DependentUpon>AgentHistoryForm.cs</DependentUpon></Compile> <SubType>Form</SubType>
<Compile Include="Forms\AgentSettingsForm.cs"><SubType>Form</SubType></Compile> </Compile>
<Compile Include="Forms\AgentSettingsForm.Designer.cs"><DependentUpon>AgentSettingsForm.cs</DependentUpon></Compile> <Compile Include="Forms\AgentHistoryForm.Designer.cs">
<Compile Include="Forms\wBitmapView.cs"><SubType>Form</SubType></Compile> <DependentUpon>AgentHistoryForm.cs</DependentUpon>
</Compile>
<Compile Include="Forms\AgentSettingsForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\AgentSettingsForm.Designer.cs">
<DependentUpon>AgentSettingsForm.cs</DependentUpon>
</Compile>
<Compile Include="Forms\DocLineViewer.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\ThinkingSpinner.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Forms\wBitmapView.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Kb\ContentHit.cs" />
<Compile Include="Kb\DialectService.cs" /> <Compile Include="Kb\DialectService.cs" />
<Compile Include="Kb\EmbeddingClient.cs" /> <Compile Include="Kb\EmbeddingClient.cs" />
<Compile Include="Kb\FileParsers.cs" /> <Compile Include="Kb\FileParsers.cs" />
...@@ -142,15 +161,27 @@ ...@@ -142,15 +161,27 @@
<Compile Include="Support\CostEstimator.cs" /> <Compile Include="Support\CostEstimator.cs" />
<Compile Include="Support\ImageSearch.cs" /> <Compile Include="Support\ImageSearch.cs" />
<Compile Include="Support\MarkdownRenderer.cs" /> <Compile Include="Support\MarkdownRenderer.cs" />
<Compile Include="Support\NoCaretRichTextBox.cs"><SubType>Component</SubType></Compile> <Compile Include="Support\NoCaretRichTextBox.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Support\StorageCleanup.cs" /> <Compile Include="Support\StorageCleanup.cs" />
<Compile Include="Support\TextEncoding.cs" /> <Compile Include="Support\TextEncoding.cs" />
<Compile Include="Support\TextPatterns.cs" /> <Compile Include="Support\TextPatterns.cs" />
<Compile Include="wMyAgent.cs"><SubType>Form</SubType></Compile> <Compile Include="wMyAgent.cs">
<Compile Include="wMyAgent.Designer.cs"><DependentUpon>wMyAgent.cs</DependentUpon></Compile> <SubType>Form</SubType>
<EmbeddedResource Include="Forms\AgentHistoryForm.resx"><DependentUpon>AgentHistoryForm.cs</DependentUpon></EmbeddedResource> </Compile>
<EmbeddedResource Include="Forms\AgentSettingsForm.resx"><DependentUpon>AgentSettingsForm.cs</DependentUpon></EmbeddedResource> <Compile Include="wMyAgent.Designer.cs">
<EmbeddedResource Include="wMyAgent.resx"><DependentUpon>wMyAgent.cs</DependentUpon></EmbeddedResource> <DependentUpon>wMyAgent.cs</DependentUpon>
</Compile>
<EmbeddedResource Include="Forms\AgentHistoryForm.resx">
<DependentUpon>AgentHistoryForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\AgentSettingsForm.resx">
<DependentUpon>AgentSettingsForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="wMyAgent.resx">
<DependentUpon>wMyAgent.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Resources\cn_synonym.txt"> <EmbeddedResource Include="Resources\cn_synonym.txt">
<LogicalName>Tools.Agent.Resources.cn_synonym.txt</LogicalName> <LogicalName>Tools.Agent.Resources.cn_synonym.txt</LogicalName>
</EmbeddedResource> </EmbeddedResource>
...@@ -174,6 +205,21 @@ ...@@ -174,6 +205,21 @@
<None Include="THIRD-PARTY-NOTICES.txt" /> <None Include="THIRD-PARTY-NOTICES.txt" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Agent\dialect\erp\cases\" />
<Folder Include="Agent\dialect\mes\cases\" />
<Folder Include="Agent\dialect\pdm\cases\" />
<Folder Include="Agent\dialect\sql-core\" />
<Folder Include="Agent\dialect\troubleshoot-core\" />
<Folder Include="Agent\industry\鞋业\01-品类认知\" />
<Folder Include="Agent\industry\鞋业\02-生产工艺\" />
<Folder Include="Agent\industry\鞋业\03-材料与BOM\" />
<Folder Include="Agent\industry\鞋业\04-业务与贸易\" />
<Folder Include="Agent\industry\鞋业\05-质量管理\" />
<Folder Include="Agent\industry\鞋业\06-专业术语\" />
<Folder Include="Agent\industry\鞋业\07-常见问题\" />
<Folder Include="Agent\prompts\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\build\net45\Stub.System.Data.SQLite.Core.NetFramework.targets" Condition="Exists('..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\build\net45\Stub.System.Data.SQLite.Core.NetFramework.targets')" /> <Import Project="..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\build\net45\Stub.System.Data.SQLite.Core.NetFramework.targets" Condition="Exists('..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\build\net45\Stub.System.Data.SQLite.Core.NetFramework.targets')" />
</Project> </Project>
\ No newline at end of file
This diff is collapsed.
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