mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-20 17:08:01 +00:00
把原 websearch 示例改名为 deepsearch(目录/go.mod/plg.json/工具前缀/README 全量对齐, 工具名 websearch_* → deepsearch_*)。 新增 searxng.go:插件自己托管搜索后端 - 启动探 healthz:已在跑则直接接管(不重启),没跑就 docker compose up -d 并等就绪 - 关闭动作注册为 stop handler(幂等、限时 4s < 内核 5s 宽限期) - 配置 manage_searxng / searxng_dir / stop_searxng_on_exit - 崩溃/被 kill 时不关后端(下次启动接管):安全失败方向 - 可注入 cmdRunner + 时间预算,8 项单测离线覆盖接管/拉起/失败/幂等/保留 契约依据(internal/plugin/proc):停止插件 = plugin.stop → RunStopHandlers(LIFO、 幂等)→ Stop() → exit(0),宽限期 5s;stdin 关闭同路径。 验证:19 项单测(18 通过 + 1 联调跳过)、-race 干净、vet/gofmt 干净;内核 E2E 真调用 返回 58 条/37 条、58/58 带摘要;线上 daemon 实测 stop handler 与冷启动(约 3s)。
82 lines
2.5 KiB
Go
82 lines
2.5 KiB
Go
package main
|
||
|
||
import (
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// 真实后端联调(默认跳过,需显式指定地址):
|
||
//
|
||
// DEEPSEARCH_LIVE_SEARXNG=http://127.0.0.1:8888 go test -run TestLiveSearxng -v ./...
|
||
//
|
||
// 它跑的就是当初失败的场景(日志里那条「你的搜索能力好像不太行啊」对应的查询),
|
||
// 用来回答一个具体问题:换了后端之后,模型拿到的是不是「带摘要的相关结果」。
|
||
func TestLiveSearxng(t *testing.T) {
|
||
base := os.Getenv("DEEPSEARCH_LIVE_SEARXNG")
|
||
if base == "" {
|
||
t.Skip("未设置 DEEPSEARCH_LIVE_SEARXNG,跳过真实后端联调")
|
||
}
|
||
p := &Plugin{
|
||
name: "deepsearch",
|
||
searxURL: strings.TrimRight(base, "/"),
|
||
maxItems: 6,
|
||
language: "zh-CN",
|
||
fetchMax: 1200,
|
||
userAgent: defaultUA,
|
||
}
|
||
p.ensure()
|
||
|
||
// 1) 自检
|
||
st, err := p.handleStatus(map[string]interface{}{})
|
||
if err != nil {
|
||
t.Fatalf("status: %v", err)
|
||
}
|
||
t.Logf("status: %v", st)
|
||
|
||
// 2) 当初失败的那条查询
|
||
res, err := p.handleSearch(map[string]interface{}{"query": "深度科技 deepin 开发者 被开除"})
|
||
if err != nil {
|
||
t.Fatalf("search: %v", err)
|
||
}
|
||
txt := res.(map[string]interface{})["content"].(string)
|
||
t.Logf("检索结果:\n%s", txt)
|
||
if !strings.Contains(txt, "摘要:") {
|
||
t.Errorf("结果里应当有摘要(这正是原实现缺失的东西)")
|
||
}
|
||
if !strings.Contains(txt, "覆盖:") {
|
||
t.Errorf("应报告引擎覆盖度")
|
||
}
|
||
|
||
// 3) 正文抓取(取第一条结果的 URL)
|
||
var firstURL string
|
||
for _, line := range strings.Split(txt, "\n") {
|
||
l := strings.TrimSpace(line)
|
||
if strings.HasPrefix(l, "http") {
|
||
firstURL = l
|
||
break
|
||
}
|
||
}
|
||
if firstURL == "" {
|
||
t.Fatal("未从结果中解析出 URL")
|
||
}
|
||
page, err := p.handleFetch(map[string]interface{}{"url": firstURL, "max_chars": float64(600)})
|
||
if err != nil {
|
||
t.Logf("抓取 %s 失败(真实站点有反爬/需 JS 属正常):%v", firstURL, err)
|
||
} else {
|
||
body := page.(map[string]interface{})["content"].(string)
|
||
t.Logf("抓取 %s 正文前 400 字:%s", firstURL, oneLine(body, 400))
|
||
}
|
||
|
||
// 4) 深检索
|
||
deep, err := p.handleDeep(map[string]interface{}{"query": "统信 UOS 内核工程师 西装 事件", "top_k": float64(2)})
|
||
if err != nil {
|
||
t.Fatalf("deep: %v", err)
|
||
}
|
||
dTxt := deep.(map[string]interface{})["content"].(string)
|
||
if !strings.Contains(dTxt, "候选清单") || !strings.Contains(dTxt, "正文证据") {
|
||
t.Errorf("深检索输出结构不对")
|
||
}
|
||
t.Logf("深检索输出前 800 字:\n%s", oneLine(dTxt, 800))
|
||
}
|