Files
WeKnora/internal/agent/tools/wiki_tools.go
wizardchen 4715b10642 feat: Add Wiki Researcher agent and system prompt for enhanced knowledge retrieval
- Introduced a new built-in agent, "Wiki Researcher," designed for navigating and answering questions based on Wiki knowledge bases, complete with multilingual support.
- Added a corresponding system prompt that outlines the agent's role, mission, and workflow for effective knowledge graph traversal.
- Updated the agent configuration to include specific tools and parameters tailored for the Wiki Researcher, enhancing its functionality and user interaction.
- Removed deprecated wiki tools from the agent service to streamline the toolset and improve performance.

These changes significantly enhance the capabilities of the agent system, providing users with a specialized tool for in-depth research and information retrieval from Wiki sources.
2026-04-22 21:17:35 +08:00

203 lines
5.6 KiB
Go

package tools
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
)
// ---- wiki_read_page ----
type wikiReadPageTool struct {
BaseTool
wikiService interfaces.WikiPageService
kbIDs []string
}
func NewWikiReadPageTool(wikiService interfaces.WikiPageService, kbIDs []string) types.Tool {
return &wikiReadPageTool{
BaseTool: NewBaseTool(
ToolWikiReadPage,
`Read a wiki page by its slug. Returns the full markdown content, metadata, and links.
Use this to read specific wiki pages when you know their slug (e.g. "entity/acme-corp", "concept/rag", "summary/document-title").`,
json.RawMessage(`{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The wiki page slug to read (e.g. 'entity/acme-corp', 'concept/rag')"
},
"knowledge_base_id": {
"type": "string",
"description": "Optional: specific knowledge base ID. If omitted, searches all wiki KBs."
}
},
"required": ["slug"]
}`),
),
wikiService: wikiService,
kbIDs: kbIDs,
}
}
func (t *wikiReadPageTool) Execute(ctx context.Context, args json.RawMessage) (*types.ToolResult, error) {
var params struct {
Slug string `json:"slug"`
KnowledgeBaseID string `json:"knowledge_base_id"`
}
if err := json.Unmarshal(args, &params); err != nil {
return &types.ToolResult{Success: false, Error: "Invalid parameters: " + err.Error()}, nil
}
kbIDs := t.kbIDs
if params.KnowledgeBaseID != "" {
kbIDs = []string{params.KnowledgeBaseID}
}
for _, kbID := range kbIDs {
page, err := t.wikiService.GetPageBySlug(ctx, kbID, params.Slug)
if err == nil && page != nil {
// Resolve OutLinks summaries to provide 1-hop context
var outLinksDesc []string
if len(page.OutLinks) > 0 {
for _, outSlug := range page.OutLinks {
if linkPage, err := t.wikiService.GetPageBySlug(ctx, kbID, outSlug); err == nil && linkPage != nil {
outLinksDesc = append(outLinksDesc, fmt.Sprintf("[[%s]] (%s)", outSlug, linkPage.Summary))
} else {
outLinksDesc = append(outLinksDesc, fmt.Sprintf("[[%s]]", outSlug))
}
}
} else {
outLinksDesc = []string{"(none)"}
}
// Resolve InLinks summaries to provide reverse 1-hop context
var inLinksDesc []string
if len(page.InLinks) > 0 {
for _, inSlug := range page.InLinks {
if linkPage, err := t.wikiService.GetPageBySlug(ctx, kbID, inSlug); err == nil && linkPage != nil {
inLinksDesc = append(inLinksDesc, fmt.Sprintf("[[%s]] (%s)", inSlug, linkPage.Summary))
} else {
inLinksDesc = append(inLinksDesc, fmt.Sprintf("[[%s]]", inSlug))
}
}
} else {
inLinksDesc = []string{"(none)"}
}
output := fmt.Sprintf(`<wiki_page>
<metadata>
<title>%s</title>
<slug>%s</slug>
<type>%s</type>
</metadata>
<relationships>
<links_to>%s</links_to>
<linked_from>%s</linked_from>
</relationships>
<content>
%s
</content>
</wiki_page>`,
page.Title, page.Slug, page.PageType,
strings.Join(outLinksDesc, ", "),
strings.Join(inLinksDesc, ", "),
page.Content,
)
return &types.ToolResult{Success: true, Output: output}, nil
}
}
return &types.ToolResult{Success: false, Error: fmt.Sprintf("Wiki page '%s' not found", params.Slug)}, nil
}
// ---- wiki_search ----
type wikiSearchTool struct {
BaseTool
wikiService interfaces.WikiPageService
kbIDs []string
}
func NewWikiSearchTool(wikiService interfaces.WikiPageService, kbIDs []string) types.Tool {
return &wikiSearchTool{
BaseTool: NewBaseTool(
ToolWikiSearch,
`Search wiki pages by keyword. Returns matching pages with titles, slugs, and summaries.
Use this to find relevant wiki pages when you don't know the exact slug.`,
json.RawMessage(`{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (keywords or phrase)"
},
"limit": {
"type": "integer",
"description": "Max results to return (default 10)"
}
},
"required": ["query"]
}`),
),
wikiService: wikiService,
kbIDs: kbIDs,
}
}
func (t *wikiSearchTool) Execute(ctx context.Context, args json.RawMessage) (*types.ToolResult, error) {
var params struct {
Query string `json:"query"`
Limit int `json:"limit"`
}
if err := json.Unmarshal(args, &params); err != nil {
return &types.ToolResult{Success: false, Error: "Invalid parameters: " + err.Error()}, nil
}
if params.Limit <= 0 {
params.Limit = 10
}
var allPages []*types.WikiPage
for _, kbID := range t.kbIDs {
pages, err := t.wikiService.SearchPages(ctx, kbID, params.Query, params.Limit)
if err == nil {
allPages = append(allPages, pages...)
}
}
if len(allPages) == 0 {
return &types.ToolResult{
Success: true,
Output: fmt.Sprintf("<search_results count=\"0\" query=\"%s\" />", params.Query),
}, nil
}
var sb strings.Builder
sb.WriteString(fmt.Sprintf("<search_results count=\"%d\" query=\"%s\">\n", len(allPages), params.Query))
for _, p := range allPages {
fmt.Fprintf(&sb, "<page>\n<title>%s</title>\n<slug>%s</slug>\n<type>%s</type>\n<summary>%s</summary>\n</page>\n", p.Title, p.Slug, p.PageType, p.Summary)
}
sb.WriteString("</search_results>")
return &types.ToolResult{Success: true, Output: sb.String()}, nil
}
// --- Helper ---
func truncateForSummary(content string, maxLen int) string {
// Take first paragraph or first maxLen chars
lines := strings.SplitN(content, "\n\n", 2)
summary := strings.TrimSpace(lines[0])
summary = strings.TrimPrefix(summary, "# ")
summary = strings.TrimPrefix(summary, "## ")
runes := []rune(summary)
if len(runes) > maxLen {
return string(runes[:maxLen]) + "..."
}
return summary
}