WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package im
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/agent/tools"
|
||||
"github.com/Tencent/WeKnora/internal/logger"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
)
|
||||
|
||||
const (
|
||||
searchMaxResults = 5
|
||||
searchContentMaxLen = 200 // runes shown per result
|
||||
)
|
||||
|
||||
// SearchCommand implements /search <query>.
|
||||
//
|
||||
// It runs a hybrid search (vector + keywords) against the user's selected
|
||||
// knowledge bases—or the bot-level defaults when no override is active—and
|
||||
// returns the raw matching passages without AI summarisation. This is useful
|
||||
// when the user needs to inspect source text directly.
|
||||
type SearchCommand struct {
|
||||
sessionService interfaces.SessionService
|
||||
kbService interfaces.KnowledgeBaseService
|
||||
}
|
||||
|
||||
func newSearchCommand(sessionService interfaces.SessionService, kbService interfaces.KnowledgeBaseService) *SearchCommand {
|
||||
return &SearchCommand{sessionService: sessionService, kbService: kbService}
|
||||
}
|
||||
|
||||
func (c *SearchCommand) Name() string { return "search" }
|
||||
func (c *SearchCommand) Description() string {
|
||||
return "直接检索知识库原文(不经 AI 总结),例如:/search 退款政策"
|
||||
}
|
||||
|
||||
func (c *SearchCommand) Execute(ctx context.Context, cmdCtx *CommandContext, args []string) (*CommandResult, error) {
|
||||
if len(args) == 0 {
|
||||
return &CommandResult{
|
||||
Content: "请输入搜索内容,例如:`/search 退款政策`",
|
||||
}, nil
|
||||
}
|
||||
|
||||
query := strings.Join(args, " ")
|
||||
|
||||
// Resolve which KBs to search, mirroring the logic in the QA pipeline's
|
||||
// resolveKnowledgeBasesFromAgent so that /search covers the same scope.
|
||||
var kbIDs []string
|
||||
if cmdCtx.CustomAgent != nil {
|
||||
switch cmdCtx.CustomAgent.Config.KBSelectionMode {
|
||||
case "all":
|
||||
allKBs, err := c.kbService.ListKnowledgeBases(ctx)
|
||||
if err == nil {
|
||||
// Same capability filter as the QA pipeline's
|
||||
// resolveKnowledgeBasesFromAgent (`all` branch) so `/search`
|
||||
// agrees with what the agent's tools can actually reach.
|
||||
// Agent-mode aware: quick-answer enforces RAG-only KBs.
|
||||
agentMode := cmdCtx.CustomAgent.Config.AgentMode
|
||||
allowed := cmdCtx.CustomAgent.Config.AllowedTools
|
||||
filter := tools.DeriveKBFilterForAgent(agentMode, allowed)
|
||||
skipped := 0
|
||||
for _, kb := range allKBs {
|
||||
if !filter.IsEmpty() &&
|
||||
!tools.KBSatisfiesAgentRequirements(kb.Capabilities(), agentMode, allowed) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
kbIDs = append(kbIDs, kb.ID)
|
||||
}
|
||||
if skipped > 0 {
|
||||
logger.Infof(ctx,
|
||||
"/search(agent=%s, mode=all): capability filter removed %d of %d KBs",
|
||||
cmdCtx.CustomAgent.ID, skipped, len(allKBs))
|
||||
}
|
||||
}
|
||||
case "none":
|
||||
// No knowledge bases configured — will return empty results.
|
||||
case "selected":
|
||||
kbIDs = cmdCtx.CustomAgent.Config.KnowledgeBases
|
||||
default:
|
||||
// Backward compatibility: fall back to configured list.
|
||||
kbIDs = cmdCtx.CustomAgent.Config.KnowledgeBases
|
||||
}
|
||||
}
|
||||
|
||||
results, err := c.sessionService.SearchKnowledge(ctx, kbIDs, nil, nil, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search knowledge: %w", err)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return &CommandResult{
|
||||
Content: fmt.Sprintf("未在知识库中找到与「%s」相关的内容。", query),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Cap the number of results shown in IM (wall of text is unhelpful).
|
||||
shown := results
|
||||
if len(shown) > searchMaxResults {
|
||||
shown = shown[:searchMaxResults]
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("🔍 **搜索「%s」** — 找到 %d 条结果\n\n", query, len(results)))
|
||||
|
||||
for i, r := range shown {
|
||||
// Trim content to a readable length.
|
||||
content := []rune(r.Content)
|
||||
suffix := ""
|
||||
if len(content) > searchContentMaxLen {
|
||||
content = content[:searchContentMaxLen]
|
||||
suffix = "…"
|
||||
}
|
||||
|
||||
// Source label: prefer title, fall back to filename.
|
||||
source := r.KnowledgeTitle
|
||||
if source == "" {
|
||||
source = r.KnowledgeFilename
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("**[%d]** %s\n> %s%s\n", i+1, source, string(content), suffix))
|
||||
|
||||
if r.Score > 0 {
|
||||
sb.WriteString(fmt.Sprintf("匹配度:%.0f%%\n", r.Score*100))
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
if len(results) > searchMaxResults {
|
||||
sb.WriteString(fmt.Sprintf("_(仅显示前 %d 条,共 %d 条)_", searchMaxResults, len(results)))
|
||||
}
|
||||
|
||||
return &CommandResult{Content: sb.String()}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user