Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
Ftt_Common
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Ftt_Common
Commits
06b9d21a
Commit
06b9d21a
authored
Oct 09, 2026
by
段廷瑞
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 支持远程知识库(飞天兔大脑)
parent
12f2ed0d
Changes
11
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
916 additions
and
22 deletions
+916
-22
.gitignore
.gitignore
+1
-0
Tools.Agent.dll
AppHome/Tools.Agent.dll
+0
-0
Tools.Agent.xml
AppHome/Tools.Agent.xml
+123
-2
AgentEngine.cs
Tools.Agent/Agent/AgentEngine.cs
+13
-0
KbRecall.cs
Tools.Agent/Agent/Answer/KbRecall.cs
+102
-4
OpenAiCompatibleClient.cs
Tools.Agent/Agent/OpenAiCompatibleClient.cs
+9
-8
AgentConfig.cs
Tools.Agent/Config/AgentConfig.cs
+16
-2
AgentSettingsForm.cs
Tools.Agent/Forms/AgentSettingsForm.cs
+94
-3
RemoteEntryKb.cs
Tools.Agent/Kb/Remote/RemoteEntryKb.cs
+487
-0
Tools.Agent.csproj
Tools.Agent/Tools.Agent.csproj
+1
-0
wMyAgent.cs
Tools.Agent/wMyAgent.cs
+70
-3
No files found.
.gitignore
View file @
06b9d21a
...
@@ -74,3 +74,4 @@ bin
...
@@ -74,3 +74,4 @@ bin
/AppHome/Logs
/AppHome/Logs
/AppHome/Package
/AppHome/Package
/.workbuddy
/.workbuddy
/temp
AppHome/Tools.Agent.dll
View file @
06b9d21a
No preview for this file type
AppHome/Tools.Agent.xml
View file @
06b9d21a
This diff is collapsed.
Click to expand it.
Tools.Agent/Agent/AgentEngine.cs
View file @
06b9d21a
...
@@ -301,6 +301,7 @@ namespace Tools.MyAgent.Agent
...
@@ -301,6 +301,7 @@ namespace Tools.MyAgent.Agent
:
0.72
;
:
0.72
;
_recall
.
ImagesEnabled
=
cfg
.
AnswerWithImages
;
// 关掉附图即不采图(省掉目录扫描)
_recall
.
ImagesEnabled
=
cfg
.
AnswerWithImages
;
// 关掉附图即不采图(省掉目录扫描)
_recall
.
MaxImages
=
cfg
.
MaxAnswerImages
>
0
?
Math
.
Min
(
cfg
.
MaxAnswerImages
,
16
)
:
16
;
_recall
.
MaxImages
=
cfg
.
MaxAnswerImages
>
0
?
Math
.
Min
(
cfg
.
MaxAnswerImages
,
16
)
:
16
;
_recall
.
ConfigureRemote
(
cfg
.
RemoteKbEntries
);
// v4.1:远程知识库条目接口模板(空 = 关闭)
CostEstimator
.
Configure
(
cfg
.
CustomPrices
);
// R6 费用估算:应用自定义价格表覆盖
CostEstimator
.
Configure
(
cfg
.
CustomPrices
);
// R6 费用估算:应用自定义价格表覆盖
AgentTelemetry
.
Configure
(
cfg
.
TelemetryEnabled
,
cfg
.
ObservabilityLevel
);
// 可观测性开关与隐私级别
AgentTelemetry
.
Configure
(
cfg
.
TelemetryEnabled
,
cfg
.
ObservabilityLevel
);
// 可观测性开关与隐私级别
...
@@ -542,6 +543,18 @@ namespace Tools.MyAgent.Agent
...
@@ -542,6 +543,18 @@ namespace Tools.MyAgent.Agent
OnThinkingDelta
?.
Invoke
(
result
.
ReasoningContent
);
OnThinkingDelta
?.
Invoke
(
result
.
ReasoningContent
);
await
FinishAnswerAsync
(
messages
,
result
,
finalAnswer
,
ct
).
ConfigureAwait
(
false
);
await
FinishAnswerAsync
(
messages
,
result
,
finalAnswer
,
ct
).
ConfigureAwait
(
false
);
// v4.5b 空回答回退:直答偶发空内容(长目录证据噎住模型 / 自定义端点流异常中途断流,
// 真机实证 gpt-6-luna 直答 2.7s 断流 tokens=-1+-1)时,不能拿「(模型未返回内容)」占位收场 ——
// 回退工具循环重试。finalAnswer 里的占位必须清掉,否则工具循环的回答会接在占位后面。
if
(
finalAnswer
.
ToString
().
IndexOf
(
"(模型未返回内容)"
,
StringComparison
.
Ordinal
)
>=
0
)
{
finalAnswer
.
Clear
();
while
(
messages
.
Count
>
mark
)
messages
.
RemoveAt
(
messages
.
Count
-
1
);
// 回滚证据消息
AgentLogger
.
Log
(
"直答返回空内容,回退工具循环重试"
);
return
false
;
}
RaiseAnswerAttachments
(
recall
);
// P5:把命中图片交给宿主渲染附图轨(无图则不发)
RaiseAnswerAttachments
(
recall
);
// P5:把命中图片交给宿主渲染附图轨(无图则不发)
return
true
;
return
true
;
}
}
...
...
Tools.Agent/Agent/Answer/KbRecall.cs
View file @
06b9d21a
...
@@ -5,6 +5,7 @@ using System.Linq;
...
@@ -5,6 +5,7 @@ using System.Linq;
using
System.Text
;
using
System.Text
;
using
System.Text.RegularExpressions
;
using
System.Text.RegularExpressions
;
using
Tools.MyAgent.Kb
;
using
Tools.MyAgent.Kb
;
using
Tools.MyAgent.Kb.Remote
;
namespace
Tools.MyAgent.Agent
namespace
Tools.MyAgent.Agent
{
{
...
@@ -87,6 +88,22 @@ namespace Tools.MyAgent.Agent
...
@@ -87,6 +88,22 @@ namespace Tools.MyAgent.Agent
/// <summary>关联图片上限(内部默认 16;引擎 Configure 按 AgentConfig.MaxAnswerImages 注入)</summary>
/// <summary>关联图片上限(内部默认 16;引擎 Configure 按 AgentConfig.MaxAnswerImages 注入)</summary>
public
int
MaxImages
=
16
;
public
int
MaxImages
=
16
;
// ── 远程知识库条目补充(v4.1) ──
/// <summary>远程知识库源(解析自 AgentConfig.RemoteKbEntries;null/空 = 关闭)</summary>
private
List
<
RemoteEntryKb
.
Source
>
_remoteSources
;
/// <summary>注入远程知识库条目接口 URL 模板(引擎 Configure 期调用;无效模板静默丢弃)</summary>
public
void
ConfigureRemote
(
IEnumerable
<
string
>
urlTemplates
)
{
_remoteSources
=
RemoteEntryKb
.
ParseTemplates
(
urlTemplates
);
}
/// <summary>列表意图:命中则走远程「目录模式」(返回全部条目标题清单),而非逐词检索</summary>
private
static
readonly
Regex
RemoteListIntent
=
new
Regex
(
@"列出|列表|清单|目录|都有什么|有哪些|有什么内容|多少条|全部条目"
,
RegexOptions
.
Compiled
);
/// <summary>纳入证据链的命中上限(主命中 + 其余)</summary>
/// <summary>纳入证据链的命中上限(主命中 + 其余)</summary>
private
const
int
MaxHits
=
3
;
private
const
int
MaxHits
=
3
;
...
@@ -213,7 +230,7 @@ namespace Tools.MyAgent.Agent
...
@@ -213,7 +230,7 @@ namespace Tools.MyAgent.Agent
}
}
}
}
if
(
acc
.
Count
==
0
)
if
(
acc
.
Count
==
0
)
return
new
KbRecallResult
{
Mode
=
"model"
,
Query
=
question
,
Keywords
=
keywords
.
Take
(
used
).
ToList
()
}
;
return
Finalize
(
new
KbRecallResult
{
Mode
=
"model"
,
Query
=
question
,
Keywords
=
keywords
.
Take
(
used
).
ToList
()
},
keywords
,
question
,
ct
)
;
}
}
// ── 融合分:来源基分 + 0.25×查询词覆盖率 + 0.05×额外来源数(上限 0.95) ──
// ── 融合分:来源基分 + 0.25×查询词覆盖率 + 0.05×额外来源数(上限 0.95) ──
...
@@ -234,7 +251,7 @@ namespace Tools.MyAgent.Agent
...
@@ -234,7 +251,7 @@ namespace Tools.MyAgent.Agent
.
ThenBy
(
h
=>
h
.
RelPath
,
StringComparer
.
OrdinalIgnoreCase
)
.
ThenBy
(
h
=>
h
.
RelPath
,
StringComparer
.
OrdinalIgnoreCase
)
.
ToList
();
.
ToList
();
if
(
ranked
.
Count
==
0
)
if
(
ranked
.
Count
==
0
)
return
new
KbRecallResult
{
Mode
=
"model"
,
Query
=
question
,
Keywords
=
keywords
.
Take
(
used
).
ToList
()
}
;
return
Finalize
(
new
KbRecallResult
{
Mode
=
"model"
,
Query
=
question
,
Keywords
=
keywords
.
Take
(
used
).
ToList
()
},
keywords
,
question
,
ct
)
;
var
primary
=
ranked
[
0
];
var
primary
=
ranked
[
0
];
var
result
=
new
KbRecallResult
var
result
=
new
KbRecallResult
...
@@ -253,18 +270,99 @@ namespace Tools.MyAgent.Agent
...
@@ -253,18 +270,99 @@ namespace Tools.MyAgent.Agent
}
}
else
else
{
{
// 低于下界:
不注入证据,回退原工具循环(保留原有检索/诊断能力,避免新通道削弱能力
)
// 低于下界:
本地证据不入链,回退前仍尝试远程兜底(Finalize 内统一处理
)
result
.
Mode
=
"model"
;
result
.
Mode
=
"model"
;
return
result
;
return
Finalize
(
result
,
keywords
,
question
,
ct
)
;
}
}
// ── 只对进入证据链的命中补齐 行号/章节/正文/图片(读文件的成本只花在少量命中上) ──
// ── 只对进入证据链的命中补齐 行号/章节/正文/图片(读文件的成本只花在少量命中上) ──
Enrich
(
primary
,
MaxImages
);
Enrich
(
primary
,
MaxImages
);
for
(
int
i
=
0
;
i
<
result
.
Others
.
Count
;
i
++)
for
(
int
i
=
0
;
i
<
result
.
Others
.
Count
;
i
++)
Enrich
(
result
.
Others
[
i
],
0
);
// 次要命中不采图,避免附图轨被无关图片占满
Enrich
(
result
.
Others
[
i
],
0
);
// 次要命中不采图,避免附图轨被无关图片占满
// ── 远程知识库条目补充(v4.1):失败静默、不改变本地命中自身的分数 ──
return
Finalize
(
result
,
keywords
,
question
,
ct
);
}
/// <summary>统一出口:本地结果 + 远程条目补充(所有 return 路径都经过,保证 model 时远程兜底同样生效)。</summary>
private
KbRecallResult
Finalize
(
KbRecallResult
result
,
List
<
string
>
keywords
,
string
question
,
System
.
Threading
.
CancellationToken
ct
)
{
try
{
AppendRemoteEvidence
(
result
,
keywords
,
question
,
ct
);
}
catch
(
OperationCanceledException
)
{
throw
;
}
// 宿主取消:透传
catch
{
/* 静默:远程补充绝不破坏本地召回结果 */
}
return
result
;
return
result
;
}
}
/// <summary>
/// 远程知识库条目补充(v4.1):本地召回完成后,按本地关键词逐词并行查询远端条目接口。
/// <para>· 本地已召回(kb/mixed)→ 远程命中并入 <see cref="KbRecallResult.Others"/>;</para>
/// <para>· 本地未召回(model)→ 远程命中(含"最新条目"目录兜底)作为证据走 mixed(Reason 标注来源),
/// 让"远程知识库都有什么 / 本地没有的问题"也能得到回答;</para>
/// <para>· 纪律:失败静默;远程命中不参与本地融合打分;AbsPath 留空 —— 不 Enrich、不进图片轨,
/// 引用行走 AnswerRefPattern 兜底分支(零正则改动)。</para>
/// </summary>
private
void
AppendRemoteEvidence
(
KbRecallResult
result
,
List
<
string
>
keywords
,
string
question
,
System
.
Threading
.
CancellationToken
ct
)
{
if
(
_remoteSources
==
null
||
_remoteSources
.
Count
==
0
)
return
;
bool
localMiss
=
result
.
Primary
==
null
||
result
.
Mode
==
"model"
;
// 列表意图("列出/都有什么/有哪些…"):走目录模式,返回全部条目标题清单(而非逐词检索的零散命中)
if
(
localMiss
&&
!
string
.
IsNullOrEmpty
(
question
)
&&
RemoteListIntent
.
IsMatch
(
question
))
{
RemoteEntryKb
.
Hit
dir
;
try
{
dir
=
RemoteEntryKb
.
ListAll
(
_remoteSources
,
ct
);
}
catch
(
OperationCanceledException
)
{
throw
;
}
catch
{
dir
=
null
;
}
if
(
dir
!=
null
)
{
result
.
Primary
=
ToKbHit
(
dir
);
result
.
Mode
=
"mixed"
;
result
.
Reason
=
"本地知识库未命中,以下为远程知识库条目目录。"
;
return
;
}
// 目录失败:继续走常规逐词/兜底路径
}
List
<
RemoteEntryKb
.
Hit
>
hits
;
try
{
hits
=
RemoteEntryKb
.
Search
(
_remoteSources
,
keywords
,
ct
);
}
catch
(
OperationCanceledException
)
{
throw
;
}
// 宿主取消:透传(直答通道既有取消语义)
catch
{
return
;
}
// 双保险:Search 承诺除取消外不抛
if
(
hits
==
null
||
hits
.
Count
==
0
)
return
;
if
(!
localMiss
)
if
(!
localMiss
)
{
foreach
(
var
h
in
hits
)
result
.
Others
.
Add
(
ToKbHit
(
h
));
return
;
}
// 本地未召回:远程条目升级为 mixed 证据(首条 Primary,其余 Others)。
// Others 必须先清空 —— 此处 result.Others 可能还残留本地低分命中(<0.45,未达证据线),
// 它们已随 Enrich 采集了关联图片;混入会让"远程回答"挂上无关的本地图片(真机反馈 2026-10-08)。
var
remote
=
hits
.
Select
(
ToKbHit
).
ToList
();
result
.
Primary
=
remote
[
0
];
result
.
Others
.
Clear
();
result
.
Others
.
AddRange
(
remote
.
Skip
(
1
));
result
.
Mode
=
"mixed"
;
result
.
Reason
=
"本地知识库未命中,以下引用来自远程知识库条目(可能不完全匹配,请结合判断)。"
;
}
private
static
KbHit
ToKbHit
(
RemoteEntryKb
.
Hit
h
)
{
// Id=0 为目录模式 Hit:引用名显示「条目目录」(此前显示「条目#0·最新」有误导性)
string
label
=
h
.
Id
==
0
?
"远程·"
+
h
.
SourceHost
+
"·条目目录"
:
"远程·"
+
h
.
SourceHost
+
"·条目#"
+
h
.
Id
+
(
h
.
IsFallback
?
"·最新"
:
""
);
return
new
KbHit
{
RelPath
=
label
,
Snippet
=
""
,
Paragraph
=
h
.
Text
,
Score
=
h
.
Score
,
HitKeywords
=
string
.
Join
(
","
,
h
.
HitWords
),
};
}
/// <summary>命中 → 证据消息(作为一条 user 消息追加在问题之后,契约要求模型只据证据作答)</summary>
/// <summary>命中 → 证据消息(作为一条 user 消息追加在问题之后,契约要求模型只据证据作答)</summary>
public
string
BuildEvidenceMessage
(
KbRecallResult
r
)
public
string
BuildEvidenceMessage
(
KbRecallResult
r
)
{
{
...
...
Tools.Agent/Agent/OpenAiCompatibleClient.cs
View file @
06b9d21a
...
@@ -570,6 +570,15 @@ namespace Tools.MyAgent.Agent
...
@@ -570,6 +570,15 @@ namespace Tools.MyAgent.Agent
try
{
obj
=
JObject
.
Parse
(
data
);
}
try
{
obj
=
JObject
.
Parse
(
data
);
}
catch
{
continue
;
}
catch
{
continue
;
}
// 用量(v4.4 修复):SSE 末帧 choices 为空数组、仅含 usage —— 必须在 choices 空判**之前**解析,
// 否则末帧被下方 continue 跳过,用量(含费用尾注)永远采集不到(真机反馈:回答尾部无用量)。
var
usage
=
obj
[
"usage"
]
as
JObject
;
if
(
usage
!=
null
)
{
result
.
PromptTokens
=
usage
[
"prompt_tokens"
]?.
Value
<
int
>()
??
-
1
;
result
.
CompletionTokens
=
usage
[
"completion_tokens"
]?.
Value
<
int
>()
??
-
1
;
}
var
choices
=
obj
[
"choices"
]
as
JArray
;
var
choices
=
obj
[
"choices"
]
as
JArray
;
if
(
choices
==
null
||
choices
.
Count
==
0
)
continue
;
if
(
choices
==
null
||
choices
.
Count
==
0
)
continue
;
var
first
=
choices
[
0
]
as
JObject
;
var
first
=
choices
[
0
]
as
JObject
;
...
@@ -578,14 +587,6 @@ namespace Tools.MyAgent.Agent
...
@@ -578,14 +587,6 @@ namespace Tools.MyAgent.Agent
var
fr
=
first
[
"finish_reason"
]?.
Value
<
string
>();
var
fr
=
first
[
"finish_reason"
]?.
Value
<
string
>();
if
(!
string
.
IsNullOrEmpty
(
fr
))
finishReason
=
fr
;
if
(!
string
.
IsNullOrEmpty
(
fr
))
finishReason
=
fr
;
// 用量(DeepSeek 在 SSE 末帧返回 usage)
var
usage
=
obj
[
"usage"
]
as
JObject
;
if
(
usage
!=
null
)
{
result
.
PromptTokens
=
usage
[
"prompt_tokens"
]?.
Value
<
int
>()
??
-
1
;
result
.
CompletionTokens
=
usage
[
"completion_tokens"
]?.
Value
<
int
>()
??
-
1
;
}
var
delta
=
first
[
"delta"
]
as
JObject
;
var
delta
=
first
[
"delta"
]
as
JObject
;
if
(
delta
==
null
)
continue
;
if
(
delta
==
null
)
continue
;
...
...
Tools.Agent/Config/AgentConfig.cs
View file @
06b9d21a
...
@@ -55,6 +55,17 @@
...
@@ -55,6 +55,17 @@
/// <summary>共享文件夹访问密码(可选;落盘时经 DPAPI 加密(见 SecretProtector),内存中为明文)</summary>
/// <summary>共享文件夹访问密码(可选;落盘时经 DPAPI 加密(见 SecretProtector),内存中为明文)</summary>
public
string
SharePassword
{
get
;
set
;
}
=
""
;
public
string
SharePassword
{
get
;
set
;
}
=
""
;
/// <summary>
/// 远程知识库条目接口 URL 模板数组(v4.1 外挂知识库)。
/// <para>每条模板必须含 <c>{q}</c> 占位符;可选 <c>{filter}</c>(服务端 source 等值过滤)/<c>{page}</c>/<c>{n}</c>。</para>
/// <para>**默认内置超级大脑条目接口**(开箱即用);设置页清空并保存 = 永久关闭。
/// 检索策略固定为「逐词并行 + 本地合并打分」(见 next_pref_plan.md §3),其余参数为代码内常量。</para>
/// </summary>
public
List
<
string
>
RemoteKbEntries
{
get
;
set
;
}
=
new
List
<
string
>
{
"http://192.168.1.108:8718/api/knowledge/entries?search={q}&page_size={n}&page={page}&partition={filter}"
,
};
// ════════════════ 高级项:生成参数 ════════════════
// ════════════════ 高级项:生成参数 ════════════════
/// <summary>采样温度</summary>
/// <summary>采样温度</summary>
...
@@ -189,8 +200,11 @@
...
@@ -189,8 +200,11 @@
/// <summary>runs_*.jsonl 保留天数(默认 14)。</summary>
/// <summary>runs_*.jsonl 保留天数(默认 14)。</summary>
public
int
RunsKeepDays
{
get
;
set
;
}
=
14
;
public
int
RunsKeepDays
{
get
;
set
;
}
=
14
;
/// <summary>回复尾注是否带本轮摘要(耗时/工具数/通道);默认关闭,避免打扰。</summary>
/// <summary>
public
bool
ShowRunSummary
{
get
;
set
;
}
=
false
;
/// 回复尾注是否带本轮摘要(耗时/工具数/通道);默认开启(真机反馈:回答结尾应有用量信息)。
/// <para>v4.4 由 ShowRunSummary 改名而来:旧配置文件中的 false 值不再生效(一次性摆脱旧默认)。</para>
/// </summary>
public
bool
RunSummaryEnabled
{
get
;
set
;
}
=
true
;
// ── Admin 看板 ──
// ── Admin 看板 ──
...
...
Tools.Agent/Forms/AgentSettingsForm.cs
View file @
06b9d21a
...
@@ -121,12 +121,49 @@ namespace Tools.MyAgent.Forms
...
@@ -121,12 +121,49 @@ namespace Tools.MyAgent.Forms
BuildDbConnectionsTab
();
// P0:预存数据库连接(db_query 按 ID 引用,聊天不回显密码)
BuildDbConnectionsTab
();
// P0:预存数据库连接(db_query 按 ID 引用,聊天不回显密码)
BuildToolPermissionUi
();
// P3:工具级权限(按角色)输入区
BuildToolPermissionUi
();
// P3:工具级权限(按角色)输入区
BuildDashboardTab
();
// 可观测性:统计看板入口(替代隐晦的 /admin)
BuildDashboardTab
();
// 可观测性:统计看板入口(替代隐晦的 /admin)
BuildRemoteKbUi
();
// v4.1:远程知识库条目接口(高级 → 语义检索页底部)
// 回答形态不再提供设置页(v1.2):全部内部默认(见 AgentConfig 注释),降低设置页认知负担
// 回答形态不再提供设置页(v1.2):全部内部默认(见 AgentConfig 注释),降低设置页认知负担
BindConfig
();
// 配置值回填控件(控件创建见 Designer.cs)
BindConfig
();
// 配置值回填控件(控件创建见 Designer.cs)
ApplyProviderMode
();
// 布局联动:BaseUrl 行显隐
ApplyProviderMode
();
// 布局联动:BaseUrl 行显隐
ApplyKbMode
();
// 布局联动:共享 账号/密码/测试连接 行显隐
ApplyKbMode
();
// 布局联动:共享 账号/密码/测试连接 行显隐
}
}
// ══ v4.1:远程知识库条目接口(程序化创建,避免侵入设计器) ══
private
TextBox
txtRemoteKb
;
/// <summary>
/// 远程知识库条目接口(next_pref_plan v4.1):追加到「高级 → 语义检索」二级页底部。
/// 多行文本框,每行一条 URL 模板(须含 {q} 占位符);留空 = 功能关闭。
/// </summary>
private
void
BuildRemoteKbUi
()
{
var
grp
=
new
GroupBox
{
Text
=
"远程知识库(HTTP 外挂 · 条目接口)"
,
Location
=
new
Point
(
4
,
142
),
Size
=
new
Size
(
590
,
118
),
TabIndex
=
2
,
};
var
lbl
=
new
Label
{
Text
=
"每行一条条目接口 URL 模板(须含 {q},可选 {filter}/{page}/{n})。\r\n默认已内置超级大脑接口;清空全部行并保存 = 关闭。服务不可达时自动冷却 60s。"
,
Location
=
new
Point
(
10
,
22
),
Size
=
new
Size
(
570
,
34
),
};
txtRemoteKb
=
new
TextBox
{
Multiline
=
true
,
ScrollBars
=
ScrollBars
.
Vertical
,
Location
=
new
Point
(
10
,
58
),
Size
=
new
Size
(
570
,
52
),
TabIndex
=
0
,
};
grp
.
Controls
.
Add
(
lbl
);
grp
.
Controls
.
Add
(
txtRemoteKb
);
tabSubSem
.
Controls
.
Add
(
grp
);
}
// ══ P0:预存数据库连接选项卡(程序化创建,避免侵入设计器) ══
// ══ P0:预存数据库连接选项卡(程序化创建,避免侵入设计器) ══
private
DataGridView
dgvDbConn
;
private
DataGridView
dgvDbConn
;
...
@@ -225,7 +262,7 @@ namespace Tools.MyAgent.Forms
...
@@ -225,7 +262,7 @@ namespace Tools.MyAgent.Forms
var
grpKpi
=
new
GroupBox
var
grpKpi
=
new
GroupBox
{
{
Text
=
"运行统计(
本进程实时累计
)"
,
Text
=
"运行统计(
进程实时 + 近 7 天落盘,重启不丢
)"
,
Location
=
new
Point
(
8
,
8
),
Location
=
new
Point
(
8
,
8
),
Size
=
new
Size
(
596
,
172
),
Size
=
new
Size
(
596
,
172
),
};
};
...
@@ -306,7 +343,7 @@ namespace Tools.MyAgent.Forms
...
@@ -306,7 +343,7 @@ namespace Tools.MyAgent.Forms
{
{
var
st
=
Tools
.
MyAgent
.
AgentTelemetry
.
Snapshot
();
var
st
=
Tools
.
MyAgent
.
AgentTelemetry
.
Snapshot
();
var
sb
=
new
System
.
Text
.
StringBuilder
();
var
sb
=
new
System
.
Text
.
StringBuilder
();
sb
.
Append
(
"本
轮进程累计
:\n"
);
sb
.
Append
(
"本
进程累计(实时)
:\n"
);
sb
.
Append
(
string
.
Format
(
" 轮数 {0}(成功 {1} · 失败 {2} · 取消 {3})\n"
,
sb
.
Append
(
string
.
Format
(
" 轮数 {0}(成功 {1} · 失败 {2} · 取消 {3})\n"
,
st
.
Runs
,
st
.
Success
,
st
.
Errors
,
st
.
Cancels
));
st
.
Runs
,
st
.
Success
,
st
.
Errors
,
st
.
Cancels
));
if
(
st
.
Duration
.
Count
>
0
)
if
(
st
.
Duration
.
Count
>
0
)
...
@@ -319,7 +356,43 @@ namespace Tools.MyAgent.Forms
...
@@ -319,7 +356,43 @@ namespace Tools.MyAgent.Forms
if
(
tokens
>
0
)
sb
.
Append
(
string
.
Format
(
" Token {0} + {1}\n"
,
st
.
PromptTokens
,
st
.
CompletionTokens
));
if
(
tokens
>
0
)
sb
.
Append
(
string
.
Format
(
" Token {0} + {1}\n"
,
st
.
PromptTokens
,
st
.
CompletionTokens
));
if
(
st
.
Cost
>
0
)
sb
.
Append
(
" 费用 "
+
Tools
.
MyAgent
.
CostEstimator
.
Format
(
st
.
Cost
)
+
"\n"
);
if
(
st
.
Cost
>
0
)
sb
.
Append
(
" 费用 "
+
Tools
.
MyAgent
.
CostEstimator
.
Format
(
st
.
Cost
)
+
"\n"
);
if
(
st
.
Dropped
>
0
)
sb
.
Append
(
" ⚠ 丢弃 "
+
st
.
Dropped
+
" 条(采集跟不上)\n"
);
if
(
st
.
Dropped
>
0
)
sb
.
Append
(
" ⚠ 丢弃 "
+
st
.
Dropped
+
" 条(采集跟不上)\n"
);
if
(
st
.
Runs
==
0
)
sb
.
Append
(
" (本进程尚未完成任何一轮回答;多日历史见「打开完整看板」)\n"
);
// 近 7 天历史(runs_*.jsonl 落盘,重启不丢;与 /admin 完整看板同源)
sb
.
Append
(
"\n近 7 天历史(落盘持久化):\n"
);
try
{
var
rows
=
new
Tools
.
MyAgent
.
TelemetryStore
().
LoadRuns
();
if
(
rows
.
Length
==
0
)
sb
.
Append
(
" (暂无落盘记录;完成问答后写入 Logs\\runs_*.jsonl)\n"
);
else
{
int
ok
=
0
,
err
=
0
,
cancel
=
0
;
long
tok
=
0
;
double
cost
=
0
;
var
ms
=
new
List
<
long
>();
foreach
(
var
r
in
rows
)
{
if
(
r
.
Outcome
==
"success"
)
ok
++;
else
if
(
r
.
Outcome
==
"error"
)
err
++;
else
if
(
r
.
Outcome
==
"cancel"
)
cancel
++;
tok
+=
r
.
PromptTokens
+
r
.
CompletionTokens
;
cost
+=
r
.
Cost
;
if
(
r
.
TotalMs
>
0
)
ms
.
Add
(
r
.
TotalMs
);
}
ms
.
Sort
();
sb
.
Append
(
string
.
Format
(
" 轮数 {0}(成功 {1} · 失败 {2} · 取消 {3})\n"
,
rows
.
Length
,
ok
,
err
,
cancel
));
if
(
ms
.
Count
>
0
)
sb
.
Append
(
string
.
Format
(
" 耗时 P50 {0}ms · P95 {1}ms · 最大 {2}ms\n"
,
Pct
(
ms
,
0.5
),
Pct
(
ms
,
0.95
),
ms
[
ms
.
Count
-
1
]));
if
(
tok
>
0
)
sb
.
Append
(
" Token "
+
tok
+
"\n"
);
if
(
cost
>
0
)
sb
.
Append
(
" 费用 "
+
Tools
.
MyAgent
.
CostEstimator
.
Format
(
cost
)
+
"\n"
);
if
(
rows
.
Length
>
0
)
sb
.
Append
(
" 最新:"
+
rows
[
0
].
StartedAt
.
ToString
(
"MM-dd HH:mm"
)
+
"("
+
rows
[
0
].
Outcome
+
")\n"
);
}
}
catch
{
sb
.
Append
(
" (历史记录读取失败)\n"
);
}
if
(
st
.
Runs
==
0
)
sb
.
Append
(
"\n(本进程尚未完成任何一轮回答;多日历史见「打开完整看板」)\n"
);
_lblDashKpi
.
Text
=
sb
.
ToString
();
_lblDashKpi
.
Text
=
sb
.
ToString
();
}
}
catch
catch
...
@@ -328,6 +401,16 @@ namespace Tools.MyAgent.Forms
...
@@ -328,6 +401,16 @@ namespace Tools.MyAgent.Forms
}
}
}
}
/// <summary>已排序列表取分位数(线性下标,与直方图口径近似)。</summary>
private
static
long
Pct
(
List
<
long
>
sorted
,
double
p
)
{
if
(
sorted
==
null
||
sorted
.
Count
==
0
)
return
0
;
int
idx
=
(
int
)
Math
.
Ceiling
(
sorted
.
Count
*
p
)
-
1
;
if
(
idx
<
0
)
idx
=
0
;
if
(
idx
>=
sorted
.
Count
)
idx
=
sorted
.
Count
-
1
;
return
sorted
[
idx
];
}
/// <summary>角色规则字典 → 编辑文本(排序保证稳定回显)</summary>
/// <summary>角色规则字典 → 编辑文本(排序保证稳定回显)</summary>
private
static
string
[]
ToolRoleRulesToLines
(
Dictionary
<
string
,
string
>
rules
)
private
static
string
[]
ToolRoleRulesToLines
(
Dictionary
<
string
,
string
>
rules
)
{
{
...
@@ -406,6 +489,9 @@ namespace Tools.MyAgent.Forms
...
@@ -406,6 +489,9 @@ namespace Tools.MyAgent.Forms
cmbEmbModel
.
Text
=
_config
.
EmbeddingModel
;
cmbEmbModel
.
Text
=
_config
.
EmbeddingModel
;
txtEmbKey
.
Text
=
_config
.
EmbeddingApiKey
;
txtEmbKey
.
Text
=
_config
.
EmbeddingApiKey
;
// 远程知识库(v4.1):URL 模板逐行回填
txtRemoteKb
.
Text
=
string
.
Join
(
Environment
.
NewLine
,
_config
.
RemoteKbEntries
??
new
List
<
string
>());
// 提示词:启用开关 + 角色模板回填 + 自定义提示词
// 提示词:启用开关 + 角色模板回填 + 自定义提示词
chkPromptEnabled
.
Checked
=
_config
.
SystemPromptEnabled
;
chkPromptEnabled
.
Checked
=
_config
.
SystemPromptEnabled
;
PopulateRoles
();
PopulateRoles
();
...
@@ -865,6 +951,11 @@ namespace Tools.MyAgent.Forms
...
@@ -865,6 +951,11 @@ namespace Tools.MyAgent.Forms
_config
.
EmbeddingBaseUrl
=
txtEmbUrl
.
Text
.
Trim
();
_config
.
EmbeddingBaseUrl
=
txtEmbUrl
.
Text
.
Trim
();
_config
.
EmbeddingModel
=
string
.
IsNullOrEmpty
(
cmbEmbModel
.
Text
.
Trim
())
?
"bge-m3"
:
cmbEmbModel
.
Text
.
Trim
();
_config
.
EmbeddingModel
=
string
.
IsNullOrEmpty
(
cmbEmbModel
.
Text
.
Trim
())
?
"bge-m3"
:
cmbEmbModel
.
Text
.
Trim
();
_config
.
EmbeddingApiKey
=
txtEmbKey
.
Text
.
Trim
();
_config
.
EmbeddingApiKey
=
txtEmbKey
.
Text
.
Trim
();
// 远程知识库(v4.1):每行一条 URL 模板,空行忽略
_config
.
RemoteKbEntries
=
(
txtRemoteKb
!=
null
?
txtRemoteKb
.
Lines
:
new
string
[
0
])
.
Select
(
l
=>
(
l
??
""
).
Trim
())
.
Where
(
l
=>
l
.
Length
>
0
)
.
ToList
();
// 提示词:启用开关 + 角色模板 + 自定义系统提示词
// 提示词:启用开关 + 角色模板 + 自定义系统提示词
_config
.
SystemPromptEnabled
=
chkPromptEnabled
.
Checked
;
_config
.
SystemPromptEnabled
=
chkPromptEnabled
.
Checked
;
_config
.
RoleTemplateId
=
(
cmbRole
.
SelectedItem
as
RoleItem
)?.
Id
??
""
;
_config
.
RoleTemplateId
=
(
cmbRole
.
SelectedItem
as
RoleItem
)?.
Id
??
""
;
...
...
Tools.Agent/Kb/Remote/RemoteEntryKb.cs
0 → 100644
View file @
06b9d21a
This diff is collapsed.
Click to expand it.
Tools.Agent/Tools.Agent.csproj
View file @
06b9d21a
...
@@ -156,6 +156,7 @@
...
@@ -156,6 +156,7 @@
<Compile
Include=
"Kb\KbConfig.cs"
/>
<Compile
Include=
"Kb\KbConfig.cs"
/>
<Compile
Include=
"Kb\KbIndex.cs"
/>
<Compile
Include=
"Kb\KbIndex.cs"
/>
<Compile
Include=
"Kb\KbService.cs"
/>
<Compile
Include=
"Kb\KbService.cs"
/>
<Compile
Include=
"Kb\Remote\RemoteEntryKb.cs"
/>
<Compile
Include=
"Kb\SynonymIndex.cs"
/>
<Compile
Include=
"Kb\SynonymIndex.cs"
/>
<Compile
Include=
"ShareHelper.cs"
/>
<Compile
Include=
"ShareHelper.cs"
/>
<Compile
Include=
"Support\AgentLogger.cs"
/>
<Compile
Include=
"Support\AgentLogger.cs"
/>
...
...
Tools.Agent/wMyAgent.cs
View file @
06b9d21a
...
@@ -109,6 +109,15 @@ namespace Tools.MyAgent
...
@@ -109,6 +109,15 @@ namespace Tools.MyAgent
public
int
Line
;
public
int
Line
;
}
}
/// <summary>
/// 远程知识库引用行(v4.3):形如「远程·<host>·条目#40」「远程·<host>·条目目录」。
/// 不带文件扩展名,<see cref="MarkdownRenderer.AnswerRefPattern"/> 不匹配 ⇒ 走独立正则扫描登记,
/// 点击跳转对应知识库网页(OpenRemoteKbWeb)。
/// </summary>
private
static
readonly
Regex
RemoteKbRefRegex
=
new
Regex
(
@"远程·(?<host>[^·\s]+)·条目(#(?<id>\d+)|目录)"
,
RegexOptions
.
Compiled
);
/// <summary>
/// <summary>
/// 当前渲染文本中的引用行表。每次 <see cref="RefreshChatRtf"/> 后清空重建(字符位置随重绘失效);
/// 当前渲染文本中的引用行表。每次 <see cref="RefreshChatRtf"/> 后清空重建(字符位置随重绘失效);
/// 流式期间不扫描(完成时算一次),避免每帧全文正则扫描的 O(n) 白耗。
/// 流式期间不扫描(完成时算一次),避免每帧全文正则扫描的 O(n) 白耗。
...
@@ -380,12 +389,14 @@ namespace Tools.MyAgent
...
@@ -380,12 +389,14 @@ namespace Tools.MyAgent
CustomPrices
=
cfg
.
CustomPrices
??
new
Dictionary
<
string
,
string
>(),
CustomPrices
=
cfg
.
CustomPrices
??
new
Dictionary
<
string
,
string
>(),
DbConnections
=
protectedConns
,
DbConnections
=
protectedConns
,
ToolRoleAllow
=
cfg
.
ToolRoleAllow
??
new
Dictionary
<
string
,
string
>(),
ToolRoleAllow
=
cfg
.
ToolRoleAllow
??
new
Dictionary
<
string
,
string
>(),
// 远程知识库条目接口(v4.1;漏登记 ⇒ 设置页保存一次即被静默清空)
RemoteKbEntries
=
cfg
.
RemoteKbEntries
??
new
List
<
string
>(),
// 可观测性 + Admin 看板(漏登记 ⇒ 设置页保存一次即被静默清空)
// 可观测性 + Admin 看板(漏登记 ⇒ 设置页保存一次即被静默清空)
TelemetryEnabled
=
cfg
.
TelemetryEnabled
,
TelemetryEnabled
=
cfg
.
TelemetryEnabled
,
ObservabilityLevel
=
cfg
.
ObservabilityLevel
,
ObservabilityLevel
=
cfg
.
ObservabilityLevel
,
HealthCheckOnStart
=
cfg
.
HealthCheckOnStart
,
HealthCheckOnStart
=
cfg
.
HealthCheckOnStart
,
RunsKeepDays
=
cfg
.
RunsKeepDays
,
RunsKeepDays
=
cfg
.
RunsKeepDays
,
ShowRunSummary
=
cfg
.
ShowRunSummary
,
RunSummaryEnabled
=
cfg
.
RunSummaryEnabled
,
// v4.4 改名:旧配置的 ShowRunSummary=false 不再生效
AdminEnabled
=
cfg
.
AdminEnabled
,
AdminEnabled
=
cfg
.
AdminEnabled
,
AdminRoles
=
cfg
.
AdminRoles
,
AdminRoles
=
cfg
.
AdminRoles
,
DashboardDays
=
cfg
.
DashboardDays
,
DashboardDays
=
cfg
.
DashboardDays
,
...
@@ -673,6 +684,10 @@ namespace Tools.MyAgent
...
@@ -673,6 +684,10 @@ namespace Tools.MyAgent
Line
=
line
,
Line
=
line
,
});
});
}
}
// 远程知识库引用行(v4.3):AnswerRefPattern 因无扩展名不匹配,独立扫描登记(点击跳知识库网页)
foreach
(
Match
m
in
RemoteKbRefRegex
.
Matches
(
text
))
_answerRefs
.
Add
(
new
AnswerRef
{
Start
=
m
.
Index
,
Length
=
m
.
Length
,
Rel
=
m
.
Value
});
}
}
catch
{
_answerRefs
.
Clear
();
}
catch
{
_answerRefs
.
Clear
();
}
}
}
...
@@ -797,6 +812,14 @@ namespace Tools.MyAgent
...
@@ -797,6 +812,14 @@ namespace Tools.MyAgent
private
void
OpenAnswerRef
(
AnswerRef
r
)
private
void
OpenAnswerRef
(
AnswerRef
r
)
{
{
if
(
r
==
null
||
string
.
IsNullOrEmpty
(
r
.
Rel
))
return
;
if
(
r
==
null
||
string
.
IsNullOrEmpty
(
r
.
Rel
))
return
;
// 远程知识库引用行(v4.3):点击跳转对应知识库网页(origin 取自配置的接口模板)
if
(
r
.
Rel
.
StartsWith
(
"远程·"
,
StringComparison
.
Ordinal
))
{
OpenRemoteKbWeb
(
r
.
Rel
);
return
;
}
string
abs
=
null
;
string
abs
=
null
;
bool
dialect
=
false
;
bool
dialect
=
false
;
try
try
...
@@ -851,6 +874,50 @@ namespace Tools.MyAgent
...
@@ -851,6 +874,50 @@ namespace Tools.MyAgent
OpenLinkTarget
(
abs
);
OpenLinkTarget
(
abs
);
}
}
/// <summary>
/// 远程知识库引用行点击(v4.3):跳转对应知识库网页。
/// 网页地址 = 接口模板的 origin(scheme://host:port,从设置页 RemoteKbEntries 按 host 匹配解析);
/// 找不到匹配模板(配置已删源)时提示。
/// </summary>
private
void
OpenRemoteKbWeb
(
string
rel
)
{
string
host
=
null
;
var
m
=
RemoteKbRefRegex
.
Match
(
rel
??
""
);
if
(
m
.
Success
)
host
=
m
.
Groups
[
"host"
].
Value
;
string
origin
=
ResolveRemoteKbOrigin
(
host
);
if
(
string
.
IsNullOrEmpty
(
origin
))
{
AgentUtils
.
Toast
(
"未找到该远程知识库的网页地址(请检查设置页「远程知识库」的接口模板)"
,
4000
);
return
;
}
try
{
System
.
Diagnostics
.
Process
.
Start
(
origin
);
}
catch
(
Exception
ex
)
{
AgentUtils
.
Toast
(
"打开浏览器失败:"
+
ex
.
Message
,
4000
);
}
}
/// <summary>按 host 匹配配置中的远程知识库模板,返回其 origin(如 http://192.168.1.108:8718/)。</summary>
private
string
ResolveRemoteKbOrigin
(
string
host
)
{
if
(
string
.
IsNullOrEmpty
(
host
))
return
null
;
try
{
var
entries
=
_config
!=
null
?
_config
.
RemoteKbEntries
:
null
;
if
(
entries
!=
null
)
foreach
(
var
t
in
entries
)
{
var
s
=
(
t
??
""
).
Trim
();
if
(
s
.
Length
==
0
)
continue
;
Uri
u
;
try
{
u
=
new
Uri
(
s
.
Replace
(
"{q}"
,
"x"
));
}
catch
{
continue
;
}
if
(
u
.
Scheme
!=
Uri
.
UriSchemeHttp
&&
u
.
Scheme
!=
Uri
.
UriSchemeHttps
)
continue
;
if
(
string
.
Equals
(
u
.
Host
,
host
,
StringComparison
.
OrdinalIgnoreCase
))
return
u
.
GetLeftPart
(
UriPartial
.
Authority
)
+
"/"
;
}
}
catch
{
}
return
null
;
}
/// <summary>指定消息的工具轨迹(<details>)是否展开</summary>
/// <summary>指定消息的工具轨迹(<details>)是否展开</summary>
private
bool
IsDetailsExpanded
(
int
messageIndex
)
=>
_expandedDetails
.
Contains
(
messageIndex
);
private
bool
IsDetailsExpanded
(
int
messageIndex
)
=>
_expandedDetails
.
Contains
(
messageIndex
);
...
@@ -1066,8 +1133,8 @@ namespace Tools.MyAgent
...
@@ -1066,8 +1133,8 @@ namespace Tools.MyAgent
string
text
=
$"本回复
{
prompt
}
+
{
completion
}
tokens"
;
string
text
=
$"本回复
{
prompt
}
+
{
completion
}
tokens"
;
double
cost
=
_engine
.
TotalCost
-
_replyCostBase
;
double
cost
=
_engine
.
TotalCost
-
_replyCostBase
;
if
(
cost
>
0
)
text
+=
$"(约
{
CostEstimator
.
Format
(
cost
)}
)"
;
if
(
cost
>
0
)
text
+=
$"(约
{
CostEstimator
.
Format
(
cost
)}
)"
;
// M4:本轮摘要(耗时/工具数/通道)——
默认关闭,避免打扰(AgentConfig.ShowRunSummary
)
// M4:本轮摘要(耗时/工具数/通道)——
v4.4 起默认开启(AgentConfig.RunSummaryEnabled
)
if
(
_config
.
ShowRunSummary
&&
_lastRunSummary
.
Length
>
0
)
text
+=
" · "
+
_lastRunSummary
;
if
(
_config
.
RunSummaryEnabled
&&
_lastRunSummary
.
Length
>
0
)
text
+=
" · "
+
_lastRunSummary
;
return
text
;
return
text
;
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment