模型优化

This commit is contained in:
2026-06-10 12:31:43 +08:00
parent fe2477dd97
commit 440f83f6a7
5 changed files with 110 additions and 5 deletions
+18 -1
View File
@@ -19,7 +19,8 @@ import (
const (
defaultActivationPrompt = `判断用户问题是否需要联网搜索。
当问题涉及实时信息、新闻、价格、当前版本、近期事件、政策、网页资料核验,或用户明确要求“查一下/搜索/联网/最新”时调用 search。
当问题涉及实时信息、新闻、价格、当前版本、近期事件、政策、网页资料核验,或用户明确要求“查一下/搜索/联网/最新”时调用 search。
当用户询问“历史上的今天”、某日期历史事件、需要按当前日期动态确定查询词的常识资料时,也应调用 search;如果联网无结果,主模型会回退到自身知识库回答并说明来源。
普通知识、闲聊、代码推理、已有上下文可回答的问题不要调用。`
defaultBaseURL = "https://api.duckduckgo.com/"
defaultTimeout = 10
@@ -287,6 +288,22 @@ func BuildErrorContext(query string, err error) string {
return fmt.Sprintf("工具路由尝试联网搜索但失败。用户问题:%s\n错误:%v\n请向用户说明联网搜索失败,不要编造搜索结果。", query, err)
}
func BuildFallbackContext(config ProfileConfig, query string, routeReason string, err error) string {
var b strings.Builder
fmt.Fprintf(&b, "工具路由尝试联网搜索,但没有可用的搜索结果。当前搜索源: %s(%s)。\n", config.Name, config.Provider)
fmt.Fprintf(&b, "搜索时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(&b, "搜索词: %s\n", query)
if strings.TrimSpace(routeReason) != "" {
fmt.Fprintf(&b, "调用原因: %s\n", strings.TrimSpace(routeReason))
}
if err != nil {
fmt.Fprintf(&b, "搜索结果状态: %v\n", err)
}
fmt.Fprintln(&b, "请改用模型自身知识库回答用户问题,并在回答开头或结尾明确说明:本次联网搜索未获得可用结果,以下内容来自模型训练数据/内置知识,可能不是最新或完整信息。")
fmt.Fprintln(&b, "不要伪造网页链接或声称已由搜索结果证实;涉及时效性、争议性或不确定细节时要提示用户核验。")
return b.String()
}
func defaultConfig() Config {
return Config{
Enabled: true,
+10
View File
@@ -2,6 +2,7 @@ package search
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
@@ -66,6 +67,15 @@ func TestBuildResultContext(t *testing.T) {
}
}
func TestBuildFallbackContext(t *testing.T) {
text := BuildFallbackContext(ProfileConfig{Name: "duckduckgo", Provider: "duckduckgo"}, "历史上的今天都发生了什么?", "需要查询当天历史事件", errors.New("未搜索到相关网页结果"))
for _, want := range []string{"没有可用的搜索结果", "历史上的今天", "需要查询当天历史事件", "模型训练数据/内置知识", "不要伪造网页链接"} {
if !strings.Contains(text, want) {
t.Fatalf("fallback context missing %q:\n%s", want, text)
}
}
}
func TestDuckDuckGoSearchParsesResults(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("q") != "golang" {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"time"
)
const ActivationPrompt = "提供当前日期、时间和常用时间范围。当用户问题包含今天、明天、昨天、本周、本月、本年、最近、日程安排等相对时间表达时,应先调用此工具;如果后续还需要查数据库,可继续调用 sql。"
const ActivationPrompt = "提供当前日期、时间和常用时间范围。当用户问题包含今天、今日、明天、昨天、本周、本月、本年、最近、历史上的今天、日程安排等相对时间表达时,应先调用此工具;如果后续还需要联网搜索或查数据库,可继续调用 search 或 sql。"
type Range struct {
Start time.Time
+46 -3
View File
@@ -42,7 +42,8 @@ const (
JSON 格式:{"tools":[{"name":"工具名称","reason":"..."}],"reason":"..."}
工具名称必须来自“可用工具”列表。
可以选择多个工具,工具会按配置顺序依次执行;后面的工具可以使用前面工具写入的上下文。
如果用户问题包含今天、明天、昨天、本周、本月、本年、最近等相对时间,且还需要查询数据库,请同时选择 time 和 sql
如果用户问题包含今天、今日、明天、昨天、本周、本月、本年、最近等相对时间,且还需要调用 search 或 sql,必须同时选择 time,并让 time 排在这些工具之前
例如“历史上的今天都发生了什么”应选择 time 和 search:先获取今天的绝对日期,再搜索当天历史事件;如果联网无结果,主模型会回退到自身知识库回答并说明来源。
例如“本月有什么日程安排”应选择 time 和 sql:先获取本月绝对日期范围,再查询日程表。
如果无需工具,返回 {"tools":[],"reason":"..."}。
只选择确实必要的工具。`
@@ -1104,8 +1105,8 @@ func runSearchTool(ctx context.Context, state *searchagent.State, messages []Cha
}
if len(results) == 0 {
err := errors.New("未搜索到相关网页结果")
emit(chatSSEFrame{Type: "trace", Tool: "search", Stage: "results", Status: "error", Message: err.Error()})
return prependHiddenContext(messages, searchagent.BuildErrorContext(query, err)), nil
emit(chatSSEFrame{Type: "trace", Tool: "search", Stage: "results", Status: "warning", Message: "未搜索到相关网页结果,将使用模型知识库回答"})
return prependHiddenContext(messages, searchagent.BuildFallbackContext(profile, query, routeReason, err)), nil
}
emit(chatSSEFrame{Type: "trace", Tool: "search", Stage: "results", Status: "success", Message: fmt.Sprintf("联网搜索完成,找到 %d 条结果", len(results)), Data: map[string]any{"provider": profile.Provider, "count": len(results)}})
return prependHiddenContext(messages, searchagent.BuildResultContext(profile, query, results, routeReason)), nil
@@ -1130,6 +1131,7 @@ func enrichMessagesWithRoutedTools(ctx context.Context, chatProfile *OpenAIProfi
return messages, err
}
selected := filterToolSelections(decision, tools, toolRouterState.cfg.Tools)
selected = ensureTimeSelectionForRelativeQuery(selected, tools, toolRouterState.cfg.Tools, latestUserQuery(messages))
if len(selected) == 0 {
emit(chatSSEFrame{Type: "trace", Tool: "tool_router", Stage: "route", Status: "success", Message: "工具路由结果:无需调用工具", Data: map[string]any{"reason": decision.Reason}})
return messages, nil
@@ -1239,6 +1241,47 @@ func filterToolSelections(decision ToolRoutingDecision, tools map[string]ChatToo
selected[item.Name] = item
}
}
return orderToolSelections(selected, order)
}
func ensureTimeSelectionForRelativeQuery(selected []ToolSelection, tools map[string]ChatTool, order []ToolRouteConfig, query string) []ToolSelection {
if !containsRelativeTime(query) || hasToolSelection(selected, "time") || (!hasToolSelection(selected, "search") && !hasToolSelection(selected, "sql")) {
return selected
}
if _, ok := tools["time"]; !ok {
return selected
}
withTime := make(map[string]ToolSelection, len(selected)+1)
for _, item := range selected {
withTime[item.Name] = item
}
withTime["time"] = ToolSelection{Name: "time", Reason: "问题包含相对日期,需要先获取当前日期"}
return orderToolSelections(withTime, order)
}
func containsRelativeTime(query string) bool {
query = strings.TrimSpace(query)
if query == "" {
return false
}
for _, keyword := range []string{"今天", "今日", "明天", "昨天", "本周", "这周", "本月", "这个月", "本年", "今年", "最近", "历史上的今天"} {
if strings.Contains(query, keyword) {
return true
}
}
return false
}
func hasToolSelection(selected []ToolSelection, name string) bool {
for _, item := range selected {
if item.Name == name {
return true
}
}
return false
}
func orderToolSelections(selected map[string]ToolSelection, order []ToolRouteConfig) []ToolSelection {
result := make([]ToolSelection, 0, len(selected))
for _, item := range order {
if selection, ok := selected[item.Name]; ok {
+35
View File
@@ -129,6 +129,41 @@ func TestFilterToolSelections(t *testing.T) {
}
}
func TestEnsureTimeSelectionForRelativeSearch(t *testing.T) {
tools := map[string]ChatTool{
"time": fakeChatTool{name: "time", enabled: true},
"search": fakeChatTool{name: "search", enabled: true},
}
selected := ensureTimeSelectionForRelativeQuery(
[]ToolSelection{{Name: "search", Reason: "查询历史事件"}},
tools,
[]ToolRouteConfig{{Name: "time"}, {Name: "search"}, {Name: "sql"}},
"历史上的今天都发生了什么?",
)
if len(selected) != 2 || selected[0].Name != "time" || selected[1].Name != "search" {
t.Fatalf("unexpected selected tools: %#v", selected)
}
if !strings.Contains(selected[0].Reason, "相对日期") {
t.Fatalf("unexpected time reason: %#v", selected[0])
}
}
func TestEnsureTimeSelectionSkipsOrdinarySearch(t *testing.T) {
tools := map[string]ChatTool{
"time": fakeChatTool{name: "time", enabled: true},
"search": fakeChatTool{name: "search", enabled: true},
}
selected := ensureTimeSelectionForRelativeQuery(
[]ToolSelection{{Name: "search", Reason: "查询资料"}},
tools,
[]ToolRouteConfig{{Name: "time"}, {Name: "search"}},
"查一下 Go 语言官网",
)
if len(selected) != 1 || selected[0].Name != "search" {
t.Fatalf("unexpected selected tools: %#v", selected)
}
}
func TestRunTimeToolAddsHiddenDateRanges(t *testing.T) {
messages := []ChatMessage{{Role: "user", Content: "本月有什么日程安排"}}
withTime, err := runTimeTool(context.Background(), messages, "需要日期范围", func(chatSSEFrame) {})