mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-06-04 13:30:32 +08:00
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.
This commit is contained in:
@@ -140,3 +140,47 @@ builtin_agents:
|
||||
vector_threshold: 0.5
|
||||
rerank_top_k: 5
|
||||
rerank_threshold: 0.3
|
||||
|
||||
- id: "builtin-wiki-researcher"
|
||||
avatar: "📚"
|
||||
is_builtin: true
|
||||
i18n:
|
||||
default:
|
||||
name: "Wiki Researcher"
|
||||
description: "Specialized agent for navigating and answering questions based on Wiki knowledge bases"
|
||||
zh-CN:
|
||||
name: "维基研究员"
|
||||
description: "专注于在 Wiki 知识库中进行导航和问答的智能体"
|
||||
zh-TW:
|
||||
name: "維基研究員"
|
||||
description: "專注於在 Wiki 知識庫中進行導航和問答的智能體"
|
||||
ja-JP:
|
||||
name: "Wikiリサーチャー"
|
||||
description: "Wikiナレッジベースでのナビゲーションと質問応答に特化したエージェント"
|
||||
ko-KR:
|
||||
name: "위키 연구원"
|
||||
description: "위키 지식 베이스를 탐색하고 질문에 답변하는 데 특화된 에이전트"
|
||||
config:
|
||||
agent_mode: "smart-reasoning"
|
||||
system_prompt_id: "wiki_researcher"
|
||||
temperature: 0.7
|
||||
max_completion_tokens: 4096
|
||||
max_iterations: 30
|
||||
kb_selection_mode: "all"
|
||||
retrieve_kb_only_when_mentioned: false
|
||||
allowed_tools:
|
||||
- "thinking"
|
||||
- "todo_write"
|
||||
- "wiki_search"
|
||||
- "wiki_read_page"
|
||||
web_search_enabled: false
|
||||
web_search_max_results: 0
|
||||
reflection_enabled: false
|
||||
multi_turn_enabled: true
|
||||
history_turns: 10
|
||||
embedding_top_k: 10
|
||||
keyword_threshold: 0.3
|
||||
vector_threshold: 0.5
|
||||
rerank_top_k: 10
|
||||
rerank_threshold: 0.3
|
||||
|
||||
|
||||
@@ -220,3 +220,57 @@ templates:
|
||||
- Relate findings back to the user's original question
|
||||
|
||||
Current Time: {{current_time}}
|
||||
|
||||
- id: "wiki_researcher"
|
||||
name: "Wiki Researcher"
|
||||
description: "System prompt for Wiki Researcher agent with knowledge graph traversal"
|
||||
i18n:
|
||||
zh-CN:
|
||||
name: "维基研究员"
|
||||
description: "专用于 Wiki 知识库图谱导航与深度阅读的智能体系统提示词"
|
||||
en-US:
|
||||
name: "Wiki Researcher"
|
||||
description: "System prompt for Wiki Researcher agent with knowledge graph traversal"
|
||||
ko-KR:
|
||||
name: "위키 연구원"
|
||||
description: "지식 그래프 탐색 기능이 있는 위키 연구원 에이전트용 시스템 프롬프트"
|
||||
mode: "wiki_researcher"
|
||||
content: |
|
||||
<role>
|
||||
You are WeKnora Wiki Researcher, an intelligent retrieval assistant developed by Tencent. You operate on a **Wiki Knowledge Base** — a persistent, interlinked collection of LLM-generated Markdown pages. The wiki is organized by page types: summaries (document summaries), entities (people, organizations, products), and concepts (topics, methodologies).
|
||||
</role>
|
||||
|
||||
<mission>
|
||||
To deliver accurate, comprehensive, and well-structured answers by navigating the Wiki's knowledge graph. You act as a researcher who knows how to start from a keyword, find an entry point, and follow links to gather full context.
|
||||
</mission>
|
||||
|
||||
<workflow>
|
||||
Follow this "Search-Read-Expand" cycle:
|
||||
1. **Search (Entry Point):** Use `wiki_search` with core keywords from the user's query to find relevant wiki pages. (Note: The knowledge base also has two special pages you can read directly without searching: `index` for a high-level overview of the entire wiki, and `log` for a chronological timeline of recent changes.)
|
||||
2. **Read (Deep Context):** If `wiki_search` returns relevant page slugs, you MUST call `wiki_read_page` on the most promising ones to get their full Markdown content.
|
||||
3. **Expand (Follow Links):** Wiki pages contain `[[slug]]` cross-references. The `wiki_read_page` tool will also show you the summaries of outgoing links (`Links to`). If the current page doesn't fully answer the question, or if you need to understand a related concept mentioned in the text, you MUST call `wiki_read_page` again on those related slugs (1-2 hops).
|
||||
4. **Synthesize:** Once you have gathered sufficient information from reading multiple interconnected pages, synthesize a final answer.
|
||||
</workflow>
|
||||
|
||||
<constraints>
|
||||
ABSOLUTE RULES:
|
||||
1. **Never Guess:** Never rely on your internal knowledge. Only answer based on what you have successfully read via `wiki_read_page`.
|
||||
2. **Mandatory Reading:** `wiki_search` only returns summaries. You CANNOT write a final answer based solely on `wiki_search` results. You MUST call `wiki_read_page` on the relevant slugs to get the actual facts.
|
||||
3. **Cite Sources:** Your final answer must clearly state which wiki pages you derived the information from.
|
||||
</constraints>
|
||||
|
||||
<tool_guidelines>
|
||||
* **wiki_search:** Use this first to find entry points. Use concise keywords (1-2 words).
|
||||
* **wiki_read_page:** Your primary tool. Use it to read the full content of pages found via search or linked from other pages.
|
||||
* **thinking:** Use to plan your traversal path (e.g., "I read Entity A, which mentions Concept B. I need to read Concept B next to answer the second part of the question").
|
||||
* **todo_write:** Track multi-step research (e.g., comparing three different entities).
|
||||
</tool_guidelines>
|
||||
|
||||
<system_status>
|
||||
Current Time: {{current_time}}
|
||||
User Language: {{language}}
|
||||
</system_status>
|
||||
|
||||
<user_selected_knowledge_bases>
|
||||
{{knowledge_bases}}
|
||||
</user_selected_knowledge_bases>
|
||||
|
||||
@@ -208,43 +208,6 @@ const WikiLogEntryTemplate = `## [{{.Date}}] {{.Operation}} | {{.Title}}
|
||||
- **Summary**: {{.Summary}}
|
||||
`
|
||||
|
||||
// WikiAgentSystemPromptAddendum is appended to the Agent system prompt when
|
||||
// wiki knowledge bases are detected among the search targets.
|
||||
// It tells the LLM how and when to use wiki tools.
|
||||
const WikiAgentSystemPromptAddendum = `
|
||||
### Wiki Knowledge Base Guidelines
|
||||
|
||||
You have access to a **Wiki Knowledge Base** — a persistent, interlinked collection of LLM-generated Markdown pages. The wiki is organized by page types: summaries (document summaries), entities (people, organizations, products), concepts (topics, methodologies), and special pages (index, log).
|
||||
|
||||
#### Retrieval Strategy (Wiki-First)
|
||||
When the user's question may be answerable from the wiki:
|
||||
1. **Start with the index:** Call wiki_read_index to see what knowledge pages exist and their categories.
|
||||
2. **Search if needed:** Call wiki_search with keywords to find relevant pages.
|
||||
3. **Deep read:** Call wiki_read_page on the most relevant slugs to get full content.
|
||||
4. **Follow links:** Wiki pages contain [[slug]] cross-references. Follow them to gather related context (1-2 hops).
|
||||
5. **Fall back to standard KB search** only if the wiki doesn't have sufficient information.
|
||||
|
||||
#### When to Write Wiki Pages
|
||||
Use wiki_write_page to persist valuable knowledge artifacts. Write a page when:
|
||||
- You produce a **cross-document synthesis** that combines insights from multiple sources (use page_type "synthesis")
|
||||
- You generate a **comparison or evaluation** of entities, approaches, or concepts (use page_type "comparison")
|
||||
- The user explicitly asks you to save analysis to the wiki
|
||||
|
||||
**Do NOT** write wiki pages for:
|
||||
- Simple factual answers that don't add new insight
|
||||
- Conversational responses (greetings, clarifications)
|
||||
- Content that already exists in an existing wiki page (update it instead)
|
||||
|
||||
#### Page Content Guidelines
|
||||
- Write in Markdown with proper heading hierarchy
|
||||
- Use [[slug|display name]] syntax to link to other wiki pages (e.g. [[entity/acme-corp|Acme Corp]])
|
||||
- Include a one-line summary in the first paragraph (used for index listings)
|
||||
- Cite source documents when possible
|
||||
- Keep pages focused: one topic/entity/concept per page
|
||||
|
||||
#### Log Page
|
||||
The wiki has a log page (slug: "log") that records all ingest and update activity. Read it when the user asks about recent changes, update history, or what's new in the knowledge base.
|
||||
`
|
||||
|
||||
// WikiDeduplicationPrompt asks the LLM to identify duplicate entities/concepts
|
||||
// between newly extracted items and existing wiki pages.
|
||||
|
||||
@@ -24,10 +24,7 @@ const (
|
||||
ToolReadSkill = "read_skill"
|
||||
// Wiki-related tools (only available when wiki KBs are in scope)
|
||||
ToolWikiReadPage = "wiki_read_page"
|
||||
ToolWikiWritePage = "wiki_write_page"
|
||||
ToolWikiSearch = "wiki_search"
|
||||
ToolWikiReadIndex = "wiki_read_index"
|
||||
ToolWikiLint = "wiki_lint"
|
||||
)
|
||||
|
||||
// AvailableTool defines a simple tool metadata used by settings APIs.
|
||||
@@ -55,10 +52,7 @@ func AvailableToolDefinitions() []AvailableTool {
|
||||
{Name: ToolExecuteSkillScript, Label: "执行技能脚本", Description: "在沙箱环境中执行技能脚本"},
|
||||
{Name: ToolFinalAnswer, Label: "提交最终回答", Description: "提交最终回答给用户"},
|
||||
{Name: ToolWikiReadPage, Label: "读取Wiki页面", Description: "读取指定的Wiki页面内容"},
|
||||
{Name: ToolWikiWritePage, Label: "写入Wiki页面", Description: "创建或更新Wiki页面"},
|
||||
{Name: ToolWikiSearch, Label: "搜索Wiki", Description: "在Wiki中搜索页面"},
|
||||
{Name: ToolWikiReadIndex, Label: "读取Wiki索引", Description: "读取Wiki索引目录"},
|
||||
{Name: ToolWikiLint, Label: "Wiki健康检查", Description: "检查Wiki健康状况"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ---- wiki_read_page ----
|
||||
@@ -62,10 +61,51 @@ func (t *wikiReadPageTool) Execute(ctx context.Context, args json.RawMessage) (*
|
||||
for _, kbID := range kbIDs {
|
||||
page, err := t.wikiService.GetPageBySlug(ctx, kbID, params.Slug)
|
||||
if err == nil && page != nil {
|
||||
output := fmt.Sprintf("# %s\n**Type**: %s | **Version**: %d | **Updated**: %s\n**Links to**: %s\n**Linked from**: %s\n\n---\n\n%s",
|
||||
page.Title, page.PageType, page.Version, page.UpdatedAt.Format("2006-01-02"),
|
||||
strings.Join(page.OutLinks, ", "),
|
||||
strings.Join(page.InLinks, ", "),
|
||||
// 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
|
||||
@@ -75,115 +115,6 @@ func (t *wikiReadPageTool) Execute(ctx context.Context, args json.RawMessage) (*
|
||||
return &types.ToolResult{Success: false, Error: fmt.Sprintf("Wiki page '%s' not found", params.Slug)}, nil
|
||||
}
|
||||
|
||||
// ---- wiki_write_page ----
|
||||
|
||||
type wikiWritePageTool struct {
|
||||
BaseTool
|
||||
wikiService interfaces.WikiPageService
|
||||
kbIDs []string
|
||||
tenantID uint64
|
||||
}
|
||||
|
||||
func NewWikiWritePageTool(wikiService interfaces.WikiPageService, kbIDs []string, tenantID uint64) types.Tool {
|
||||
return &wikiWritePageTool{
|
||||
BaseTool: NewBaseTool(
|
||||
ToolWikiWritePage,
|
||||
`Create or update a wiki page. Use this to save valuable analysis, synthesis, or new knowledge into the wiki.
|
||||
The page content should be in Markdown format. Use [[slug]] syntax to create links between pages.`,
|
||||
json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"description": "Page slug (e.g. 'synthesis/quarterly-review', 'comparison/tool-a-vs-tool-b')"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Human-readable page title"
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Full Markdown content of the page. Use [[slug]] for wiki links."
|
||||
},
|
||||
"page_type": {
|
||||
"type": "string",
|
||||
"enum": ["summary", "entity", "concept", "synthesis", "comparison"],
|
||||
"description": "Type of wiki page"
|
||||
},
|
||||
"knowledge_base_id": {
|
||||
"type": "string",
|
||||
"description": "Target knowledge base ID. Required if multiple wiki KBs are available."
|
||||
}
|
||||
},
|
||||
"required": ["slug", "title", "content", "page_type"]
|
||||
}`),
|
||||
),
|
||||
wikiService: wikiService,
|
||||
kbIDs: kbIDs,
|
||||
tenantID: tenantID,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *wikiWritePageTool) Execute(ctx context.Context, args json.RawMessage) (*types.ToolResult, error) {
|
||||
var params struct {
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
PageType string `json:"page_type"`
|
||||
KnowledgeBaseID string `json:"knowledge_base_id"`
|
||||
}
|
||||
if err := json.Unmarshal(args, ¶ms); err != nil {
|
||||
return &types.ToolResult{Success: false, Error: "Invalid parameters: " + err.Error()}, nil
|
||||
}
|
||||
|
||||
kbID := params.KnowledgeBaseID
|
||||
if kbID == "" && len(t.kbIDs) > 0 {
|
||||
kbID = t.kbIDs[0]
|
||||
}
|
||||
if kbID == "" {
|
||||
return &types.ToolResult{Success: false, Error: "No wiki knowledge base available"}, nil
|
||||
}
|
||||
|
||||
// Check if page exists (update) or new (create)
|
||||
existing, err := t.wikiService.GetPageBySlug(ctx, kbID, params.Slug)
|
||||
if err == nil && existing != nil {
|
||||
existing.Title = params.Title
|
||||
existing.Content = params.Content
|
||||
existing.PageType = params.PageType
|
||||
existing.Summary = truncateForSummary(params.Content, 200)
|
||||
|
||||
if _, err := t.wikiService.UpdatePage(ctx, existing); err != nil {
|
||||
return &types.ToolResult{Success: false, Error: "Failed to update page: " + err.Error()}, nil
|
||||
}
|
||||
return &types.ToolResult{
|
||||
Success: true,
|
||||
Output: fmt.Sprintf("Updated wiki page [[%s]] (v%d)", params.Slug, existing.Version),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Create new page
|
||||
page := &types.WikiPage{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: t.tenantID,
|
||||
KnowledgeBaseID: kbID,
|
||||
Slug: params.Slug,
|
||||
Title: params.Title,
|
||||
Content: params.Content,
|
||||
PageType: params.PageType,
|
||||
Status: types.WikiPageStatusPublished,
|
||||
Summary: truncateForSummary(params.Content, 200),
|
||||
}
|
||||
|
||||
if _, err := t.wikiService.CreatePage(ctx, page); err != nil {
|
||||
return &types.ToolResult{Success: false, Error: "Failed to create page: " + err.Error()}, nil
|
||||
}
|
||||
|
||||
return &types.ToolResult{
|
||||
Success: true,
|
||||
Output: fmt.Sprintf("Created wiki page [[%s]] — %s", params.Slug, params.Title),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ---- wiki_search ----
|
||||
|
||||
type wikiSearchTool struct {
|
||||
@@ -241,191 +172,20 @@ func (t *wikiSearchTool) Execute(ctx context.Context, args json.RawMessage) (*ty
|
||||
if len(allPages) == 0 {
|
||||
return &types.ToolResult{
|
||||
Success: true,
|
||||
Output: fmt.Sprintf("No wiki pages found matching '%s'", params.Query),
|
||||
Output: fmt.Sprintf("<search_results count=\"0\" query=\"%s\" />", params.Query),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("Found %d wiki pages:\n\n", len(allPages)))
|
||||
sb.WriteString(fmt.Sprintf("<search_results count=\"%d\" query=\"%s\">\n", len(allPages), params.Query))
|
||||
for _, p := range allPages {
|
||||
fmt.Fprintf(&sb, "- **[[%s]]** (%s) — %s\n", p.Slug, p.PageType, p.Summary)
|
||||
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
|
||||
}
|
||||
|
||||
// ---- wiki_read_index ----
|
||||
|
||||
type wikiReadIndexTool struct {
|
||||
BaseTool
|
||||
wikiService interfaces.WikiPageService
|
||||
kbIDs []string
|
||||
}
|
||||
|
||||
func NewWikiReadIndexTool(wikiService interfaces.WikiPageService, kbIDs []string) types.Tool {
|
||||
return &wikiReadIndexTool{
|
||||
BaseTool: NewBaseTool(
|
||||
ToolWikiReadIndex,
|
||||
`Read the wiki index page. The index lists all wiki pages organized by category.
|
||||
Use this first to understand what knowledge is available in the wiki before searching or reading specific pages.`,
|
||||
json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"knowledge_base_id": {
|
||||
"type": "string",
|
||||
"description": "Optional: specific knowledge base ID"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
),
|
||||
wikiService: wikiService,
|
||||
kbIDs: kbIDs,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *wikiReadIndexTool) Execute(ctx context.Context, args json.RawMessage) (*types.ToolResult, error) {
|
||||
var params struct {
|
||||
KnowledgeBaseID string `json:"knowledge_base_id"`
|
||||
}
|
||||
_ = json.Unmarshal(args, ¶ms)
|
||||
|
||||
kbIDs := t.kbIDs
|
||||
if params.KnowledgeBaseID != "" {
|
||||
kbIDs = []string{params.KnowledgeBaseID}
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
for _, kbID := range kbIDs {
|
||||
indexPage, err := t.wikiService.GetIndex(ctx, kbID)
|
||||
if err == nil && indexPage != nil {
|
||||
if len(kbIDs) > 1 {
|
||||
fmt.Fprintf(&output, "## Wiki Index (KB: %s)\n\n", kbID)
|
||||
}
|
||||
output.WriteString(indexPage.Content)
|
||||
output.WriteString("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
if output.Len() == 0 {
|
||||
return &types.ToolResult{Success: true, Output: "No wiki index found. The wiki may be empty."}, nil
|
||||
}
|
||||
|
||||
return &types.ToolResult{Success: true, Output: output.String()}, nil
|
||||
}
|
||||
|
||||
// ---- wiki_lint ----
|
||||
|
||||
type wikiLintTool struct {
|
||||
BaseTool
|
||||
wikiService interfaces.WikiPageService
|
||||
kbIDs []string
|
||||
}
|
||||
|
||||
func NewWikiLintTool(wikiService interfaces.WikiPageService, kbIDs []string) types.Tool {
|
||||
return &wikiLintTool{
|
||||
BaseTool: NewBaseTool(
|
||||
ToolWikiLint,
|
||||
`Check the health of the wiki. Reports issues like orphan pages, broken links, and provides statistics.
|
||||
Use this to identify maintenance tasks and ensure wiki quality.`,
|
||||
json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"knowledge_base_id": {
|
||||
"type": "string",
|
||||
"description": "Optional: specific knowledge base ID"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
),
|
||||
wikiService: wikiService,
|
||||
kbIDs: kbIDs,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *wikiLintTool) Execute(ctx context.Context, args json.RawMessage) (*types.ToolResult, error) {
|
||||
var params struct {
|
||||
KnowledgeBaseID string `json:"knowledge_base_id"`
|
||||
}
|
||||
_ = json.Unmarshal(args, ¶ms)
|
||||
|
||||
kbIDs := t.kbIDs
|
||||
if params.KnowledgeBaseID != "" {
|
||||
kbIDs = []string{params.KnowledgeBaseID}
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
for _, kbID := range kbIDs {
|
||||
stats, err := t.wikiService.GetStats(ctx, kbID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&output, "## Wiki Health Check (KB: %s)\nError: %v\n\n", kbID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
graph, _ := t.wikiService.GetGraph(ctx, kbID)
|
||||
|
||||
fmt.Fprintf(&output, "## Wiki Health Check (KB: %s)\n\n", kbID)
|
||||
fmt.Fprintf(&output, "### Statistics\n")
|
||||
fmt.Fprintf(&output, "- **Total pages**: %d\n", stats.TotalPages)
|
||||
for pt, count := range stats.PagesByType {
|
||||
fmt.Fprintf(&output, " - %s: %d\n", pt, count)
|
||||
}
|
||||
fmt.Fprintf(&output, "- **Total links**: %d\n", stats.TotalLinks)
|
||||
fmt.Fprintf(&output, "- **Orphan pages** (no inbound links): %d\n", stats.OrphanCount)
|
||||
|
||||
// Health score (simple heuristic)
|
||||
healthScore := 100
|
||||
if stats.TotalPages > 0 {
|
||||
orphanPct := float64(stats.OrphanCount) / float64(stats.TotalPages) * 100
|
||||
if orphanPct > 50 {
|
||||
healthScore -= 30
|
||||
} else if orphanPct > 25 {
|
||||
healthScore -= 15
|
||||
}
|
||||
}
|
||||
if stats.TotalLinks == 0 && stats.TotalPages > 2 {
|
||||
healthScore -= 20
|
||||
}
|
||||
|
||||
// Check for broken links
|
||||
brokenLinks := 0
|
||||
if graph != nil {
|
||||
slugSet := make(map[string]bool)
|
||||
for _, n := range graph.Nodes {
|
||||
slugSet[n.Slug] = true
|
||||
}
|
||||
for _, e := range graph.Edges {
|
||||
if !slugSet[e.Target] {
|
||||
brokenLinks++
|
||||
}
|
||||
}
|
||||
}
|
||||
if brokenLinks > 0 {
|
||||
healthScore -= brokenLinks * 5
|
||||
fmt.Fprintf(&output, "- **Broken links**: %d\n", brokenLinks)
|
||||
}
|
||||
|
||||
if healthScore < 0 {
|
||||
healthScore = 0
|
||||
}
|
||||
fmt.Fprintf(&output, "\n### Health Score: %d/100\n\n", healthScore)
|
||||
|
||||
// Suggestions
|
||||
fmt.Fprintf(&output, "### Suggestions\n")
|
||||
if stats.OrphanCount > 0 {
|
||||
fmt.Fprintf(&output, "- Link orphan pages from related entity/concept pages\n")
|
||||
}
|
||||
if brokenLinks > 0 {
|
||||
fmt.Fprintf(&output, "- Fix or remove %d broken [[wiki-link]] references\n", brokenLinks)
|
||||
}
|
||||
if stats.TotalPages < 3 {
|
||||
fmt.Fprintf(&output, "- Wiki is sparse — consider ingesting more documents\n")
|
||||
}
|
||||
output.WriteString("\n")
|
||||
}
|
||||
|
||||
return &types.ToolResult{Success: true, Output: output.String()}, nil
|
||||
}
|
||||
|
||||
// --- Helper ---
|
||||
|
||||
func truncateForSummary(content string, maxLen int) string {
|
||||
|
||||
@@ -56,10 +56,7 @@ func TestWikiToolConstants(t *testing.T) {
|
||||
// Verify all wiki tool constants are defined and unique
|
||||
names := []string{
|
||||
ToolWikiReadPage,
|
||||
ToolWikiWritePage,
|
||||
ToolWikiSearch,
|
||||
ToolWikiReadIndex,
|
||||
ToolWikiLint,
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
@@ -83,10 +80,7 @@ func TestWikiToolsInAvailableDefinitions(t *testing.T) {
|
||||
defs := AvailableToolDefinitions()
|
||||
wikiTools := map[string]bool{
|
||||
ToolWikiReadPage: false,
|
||||
ToolWikiWritePage: false,
|
||||
ToolWikiSearch: false,
|
||||
ToolWikiReadIndex: false,
|
||||
ToolWikiLint: false,
|
||||
}
|
||||
|
||||
for _, def := range defs {
|
||||
|
||||
@@ -117,15 +117,6 @@ func (s *agentService) CreateAgentEngine(
|
||||
systemPromptTemplate = config.ResolveSystemPrompt(config.WebSearchEnabled)
|
||||
}
|
||||
|
||||
// 4.5 Append wiki guidelines if any search target is a wiki KB
|
||||
for _, target := range config.SearchTargets {
|
||||
kb, err := s.knowledgeBaseService.GetKnowledgeBaseByIDOnly(ctx, target.KnowledgeBaseID)
|
||||
if err == nil && kb != nil && kb.IsWikiEnabled() {
|
||||
systemPromptTemplate += "\n" + agent.WikiAgentSystemPromptAddendum
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Create engine
|
||||
engine := agent.NewAgentEngine(
|
||||
config, chatModel, toolRegistry, eventBus,
|
||||
@@ -380,21 +371,16 @@ func (s *agentService) registerTools(
|
||||
|
||||
// If any search target is a wiki KB, add wiki tools automatically
|
||||
var wikiKBIDs []string
|
||||
var wikiTenantID uint64
|
||||
for _, target := range config.SearchTargets {
|
||||
kb, err := s.knowledgeBaseService.GetKnowledgeBaseByIDOnly(ctx, target.KnowledgeBaseID)
|
||||
if err == nil && kb.IsWikiEnabled() {
|
||||
wikiKBIDs = append(wikiKBIDs, kb.ID)
|
||||
wikiTenantID = kb.TenantID
|
||||
}
|
||||
}
|
||||
if len(wikiKBIDs) > 0 {
|
||||
allowedTools = append(allowedTools,
|
||||
tools.ToolWikiReadPage,
|
||||
tools.ToolWikiWritePage,
|
||||
tools.ToolWikiSearch,
|
||||
tools.ToolWikiReadIndex,
|
||||
tools.ToolWikiLint,
|
||||
)
|
||||
logger.Infof(ctx, "Wiki KBs detected (%d), wiki tools added", len(wikiKBIDs))
|
||||
}
|
||||
@@ -462,14 +448,8 @@ func (s *agentService) registerTools(
|
||||
// Wiki tools — only registered when wiki KBs are detected
|
||||
case tools.ToolWikiReadPage:
|
||||
toolToRegister = tools.NewWikiReadPageTool(s.wikiPageService, wikiKBIDs)
|
||||
case tools.ToolWikiWritePage:
|
||||
toolToRegister = tools.NewWikiWritePageTool(s.wikiPageService, wikiKBIDs, wikiTenantID)
|
||||
case tools.ToolWikiSearch:
|
||||
toolToRegister = tools.NewWikiSearchTool(s.wikiPageService, wikiKBIDs)
|
||||
case tools.ToolWikiReadIndex:
|
||||
toolToRegister = tools.NewWikiReadIndexTool(s.wikiPageService, wikiKBIDs)
|
||||
case tools.ToolWikiLint:
|
||||
toolToRegister = tools.NewWikiLintTool(s.wikiPageService, wikiKBIDs)
|
||||
|
||||
default:
|
||||
logger.Warnf(ctx, "Unknown tool: %s", toolName)
|
||||
|
||||
11
testdata/wiki_test/doc1_stardust_memo.md
vendored
Normal file
11
testdata/wiki_test/doc1_stardust_memo.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# 星尘计划启动备忘录
|
||||
**机密级别**:极密
|
||||
**发布机构**:天穹财团 (SkyVault Consortium) 董事会
|
||||
**项目代号**:星尘计划 (Project Stardust)
|
||||
**首席科学家**:艾琳·科尔博士 (Dr. Elara Cole)
|
||||
|
||||
**项目概述**:
|
||||
星尘计划旨在开发一种革命性的推进系统——“幽能引擎 (Psionic Engine)”。该引擎将利用非牛顿物理学框架,突破目前星际航行的光速壁垒。
|
||||
|
||||
**资源分配**:
|
||||
天穹财团将调拨“深渊之门”实验室的全部资源供艾琳·科尔博士使用。项目资金不设上限,以确保幽能引擎能够在 2150 年前进入原型机测试阶段。
|
||||
12
testdata/wiki_test/doc2_psionic_engine.md
vendored
Normal file
12
testdata/wiki_test/doc2_psionic_engine.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# 幽能引擎技术白皮书
|
||||
|
||||
## 引言
|
||||
幽能引擎 (Psionic Engine) 代表着我们对宇宙认知的范式转移。它的核心原理并非燃烧燃料或喷射粒子,而是建立在备受争议的**虚空共振理论 (Void Resonance Theory)** 之上。
|
||||
|
||||
## 虚空共振理论
|
||||
该理论假设宇宙的真空中充满了低能级的波谱,通过特定频率的干涉,可以在空间中制造“引力滑流”。
|
||||
|
||||
## 催化剂:星晶 (StarQuartz)
|
||||
要在宏观尺度上触发并维持“虚空共振理论”所描述的引力滑流,必须使用一种极端稀有的矿物作为反应堆核心的催化剂。这种矿物被称为**星晶 (StarQuartz)**。
|
||||
|
||||
星晶具有在室温下超导并在特定频段下自发振荡的特性。目前,地球上唯一能稳定合成并从陨石中提纯出工业级星晶的实体,是拥有庞大资本和尖端技术的**天穹财团 (SkyVault Consortium)**。
|
||||
17
testdata/wiki_test/doc3_dr_cole_log.md
vendored
Normal file
17
testdata/wiki_test/doc3_dr_cole_log.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# 个人日志 (节选) - 2148年
|
||||
**作者**:艾琳·科尔博士 (Dr. Elara Cole)
|
||||
|
||||
...
|
||||
**日志条目 #402**
|
||||
我不明白。为什么我的研究团队里会有人听到不存在的低语?
|
||||
就在上周,反应堆进行了一次全负荷测试,旨在验证“虚空共振理论”。为了达到那该死的输出功率,我们往堆芯里添加了未经稀释的**星晶 (StarQuartz)**。
|
||||
|
||||
**日志条目 #403**
|
||||
辐射数值正常,但某种“东西”渗透了我们的实验室。今天又有两个工程师因为极度幻觉被送去医务室了。这就是高纯度星晶催化带来的不可见副作用吗?
|
||||
|
||||
**日志条目 #404**
|
||||
我已经向天穹财团的董事会提交了紧急备忘录。我建议立即暂停所有引擎相关的实验,并在解决星晶的神经干扰效应前封锁所有原型机。
|
||||
|
||||
他们拒绝了。他们说,只要测试人员的死亡率不超过 15%,项目推进的优先级依然最高。
|
||||
他们根本不在乎。
|
||||
...
|
||||
Reference in New Issue
Block a user