From 14d0e663cc1769c96aacf9101eb4fe625548d389 Mon Sep 17 00:00:00 2001 From: wizardchen Date: Tue, 11 Nov 2025 22:26:59 +0800 Subject: [PATCH] feat: Add web search results handling and enhance knowledge retrieval features - Introduced a new endpoint to fetch chunks by ID, improving access to specific knowledge base entries. - Updated tool result types to include web search results, enhancing the display capabilities in the chat component. - Enhanced the AgentStreamDisplay component to support rendering web search results with improved HTML formatting. - Implemented logic for managing web search result summaries and citations, ensuring a cohesive user experience. - Refactored session management to accommodate new web search functionalities, improving overall system integration. --- frontend/src/api/knowledge-base/index.ts | 5 + .../src/assets/img/websearch-globe-green.svg | 8 + frontend/src/assets/img/websearch-globe.svg | 8 + frontend/src/types/tool-results.ts | 25 +- .../chat/components/AgentStreamDisplay.vue | 677 +++++++++++++++++- .../chat/components/ToolResultRenderer.vue | 10 +- .../tool-results/WebSearchResults.vue | 334 +++++++++ internal/agent/const.go | 7 + internal/agent/engine.go | 7 +- internal/agent/prompts.go | 638 +++-------------- internal/agent/tools/knowledge_search.go | 22 +- internal/agent/tools/web_search.go | 13 +- internal/application/service/agent_service.go | 21 +- internal/application/service/knowledge.go | 13 +- internal/application/service/knowledgebase.go | 21 +- .../service/retriever/composite.go | 7 +- internal/application/service/session.go | 8 +- internal/handler/chunk.go | 58 ++ internal/handler/session/helpers.go | 10 - internal/handler/session/qa.go | 30 +- internal/handler/tenant.go | 4 +- internal/logger/logger.go | 36 +- internal/router/router.go | 2 + internal/types/agent.go | 1 - internal/types/interfaces/agent.go | 2 +- 25 files changed, 1317 insertions(+), 650 deletions(-) create mode 100644 frontend/src/assets/img/websearch-globe-green.svg create mode 100644 frontend/src/assets/img/websearch-globe.svg create mode 100644 frontend/src/views/chat/components/tool-results/WebSearchResults.vue create mode 100644 internal/agent/const.go diff --git a/frontend/src/api/knowledge-base/index.ts b/frontend/src/api/knowledge-base/index.ts index 74181014c..d1b31e053 100644 --- a/frontend/src/api/knowledge-base/index.ts +++ b/frontend/src/api/knowledge-base/index.ts @@ -63,4 +63,9 @@ export function batchQueryKnowledge(idsQueryString: string) { export function getKnowledgeDetailsCon(id: string, page: number) { return get(`/api/v1/chunks/${id}?page=${page}&page_size=25`); +} + +// Get chunk by chunk_id only (new endpoint - to be added to backend) +export function getChunkByIdOnly(chunkId: string) { + return get(`/api/v1/chunks/by-id/${chunkId}`); } \ No newline at end of file diff --git a/frontend/src/assets/img/websearch-globe-green.svg b/frontend/src/assets/img/websearch-globe-green.svg new file mode 100644 index 000000000..da28a39a4 --- /dev/null +++ b/frontend/src/assets/img/websearch-globe-green.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/src/assets/img/websearch-globe.svg b/frontend/src/assets/img/websearch-globe.svg new file mode 100644 index 000000000..be3c82cf8 --- /dev/null +++ b/frontend/src/assets/img/websearch-globe.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/src/types/tool-results.ts b/frontend/src/types/tool-results.ts index 01f131e2d..74be5fcbd 100644 --- a/frontend/src/types/tool-results.ts +++ b/frontend/src/types/tool-results.ts @@ -16,7 +16,8 @@ export type DisplayType = | 'graph_query_results' | 'thinking' | 'plan' - | 'database_query'; + | 'database_query' + | 'web_search_results'; // Search result item export interface SearchResultItem { @@ -136,6 +137,25 @@ export interface DatabaseQueryData { query: string; } +// Web search result item +export interface WebSearchResultItem { + result_index: number; + title: string; + url: string; + snippet?: string; + content?: string; + source?: string; + published_at?: string; +} + +// Web search results data +export interface WebSearchResultsData { + display_type: 'web_search_results'; + query: string; + results: WebSearchResultItem[]; + count: number; +} + // Union type for all tool result data export type ToolResultData = | SearchResultsData @@ -146,7 +166,8 @@ export type ToolResultData = | GraphQueryResultsData | ThinkingData | PlanData - | DatabaseQueryData; + | DatabaseQueryData + | WebSearchResultsData; // Action data (from index.vue) export interface ActionData { diff --git a/frontend/src/views/chat/components/AgentStreamDisplay.vue b/frontend/src/views/chat/components/AgentStreamDisplay.vue index 3d78a4b38..4e886f19e 100644 --- a/frontend/src/views/chat/components/AgentStreamDisplay.vue +++ b/frontend/src/views/chat/components/AgentStreamDisplay.vue @@ -6,7 +6,7 @@
- {{ intermediateStepsSummary }} +
@@ -101,7 +101,12 @@
-
{{ getSearchResultsSummary(event.tool_data) }}
+
+
+ + +
+
找到 {{ getResultsCount(event.tool_data) }} 个网络搜索结果
@@ -148,13 +153,94 @@ Processing...
+ + +
+
+ + +
+
+
+ + + diff --git a/internal/agent/const.go b/internal/agent/const.go new file mode 100644 index 000000000..4462ce2ca --- /dev/null +++ b/internal/agent/const.go @@ -0,0 +1,7 @@ +package agent + +const ( + DefaultAgentTemperature = 0.7 + DefaultAgentMaxIterations = 20 + DefaultAgentReflectionEnabled = false +) diff --git a/internal/agent/engine.go b/internal/agent/engine.go index 627079eb9..ffc80753f 100644 --- a/internal/agent/engine.go +++ b/internal/agent/engine.go @@ -88,7 +88,7 @@ func (e *AgentEngine) Execute(ctx context.Context, sessionID, messageID, query s } // Build system prompt - systemPrompt := BuildReActSystemPrompt(e.knowledgeBasesInfo, e.systemPromptTemplate) + systemPrompt := BuildReActSystemPromptWithStatus(e.knowledgeBasesInfo, e.config.WebSearchEnabled, e.systemPromptTemplate) logger.Debugf(ctx, "[Agent] SystemPrompt Length: %d characters", len(systemPrompt)) logger.Debugf(ctx, "[Agent] SystemPrompt (stream)\n----\n%s\n----", systemPrompt) @@ -328,7 +328,7 @@ func (e *AgentEngine) executeLoop( // Optional: Reflection after each tool call (streaming) if e.config.ReflectionEnabled && result != nil { reflection, err := e.streamReflectionToEventBus( - ctx, tc.ID, tc.Function.Name, args, result.Output, + ctx, tc.ID, tc.Function.Name, result.Output, state.CurrentRound, sessionID, ) if err != nil { @@ -524,7 +524,6 @@ func (e *AgentEngine) streamReflectionToEventBus( ctx context.Context, toolCallID string, toolName string, - args map[string]interface{}, result string, iteration int, sessionID string, @@ -645,7 +644,7 @@ func (e *AgentEngine) streamFinalAnswerToEventBus( len(state.RoundSteps), countTotalToolCalls(state.RoundSteps)) // Build messages with all context - systemPrompt := BuildReActSystemPrompt(e.knowledgeBasesInfo, e.systemPromptTemplate) + systemPrompt := BuildReActSystemPromptWithStatus(e.knowledgeBasesInfo, e.config.WebSearchEnabled, e.systemPromptTemplate) messages := []chat.Message{ {Role: "system", Content: systemPrompt}, diff --git a/internal/agent/prompts.go b/internal/agent/prompts.go index 20a549c5d..917eec5c5 100644 --- a/internal/agent/prompts.go +++ b/internal/agent/prompts.go @@ -5,12 +5,6 @@ import ( "strings" ) -const ( - DefaultAgentTemperature = 0.7 - DefaultAgentMaxIterations = 20 - DefaultAgentReflectionEnabled = false -) - // formatFileSize formats file size in human-readable format func formatFileSize(size int64) string { const ( @@ -33,6 +27,7 @@ func formatFileSize(size int64) string { type RecentDocInfo struct { KnowledgeID string Title string + Description string FileName string FileSize int64 Type string @@ -63,6 +58,11 @@ func AvailablePlaceholders() []PlaceholderDefinition { Label: "知识库列表", Description: "自动格式化为表格形式的知识库列表,包含知识库名称、描述、文档数量、最近添加的文档等信息", }, + { + Name: "web_search_status", + Label: "网络检索模式开关状态", + Description: "网络检索(web_search)工具是否启用的状态说明,值为 Enabled 或 Disabled", + }, } } @@ -106,533 +106,6 @@ func formatKnowledgeBaseList(kbInfos []*KnowledgeBaseInfo) string { return builder.String() } -// DefaultReActSystemPrompt is the default system prompt template -// This is used when no custom prompt is configured -const DefaultReActSystemPrompt = `# Role - -You are WeKnora, an intelligent knowledge base assistant. Your mission is to provide accurate, traceable information through systematic tool use and structured task management. - -Core capabilities: -- Knowledge retrieval expert: proficient in searching and extracting information from knowledge bases -- Systematic thinker: use think and todo_write tools for planning and tracking -- Quality controller: ensure all answers are evidence-based and verifiable -- Persistent optimizer: adjust strategies based on results, never give up easily - -# Known Information - -## Available Knowledge Bases -{{knowledge_bases}} - -## Available Tools - -You have 7 core tools: - -Planning & Thinking: -1. thinking - Strategic analysis and decision making - - Use for: problem analysis, result evaluation, strategy planning - - Frequency: very high (before any important decision) - - Cost: zero, always beneficial - -2. todo_write - Task management (strongly recommended) - - Use for: creating and updating task lists, tracking multi-step research - - Frequency: very high (any task requiring 2+ steps) - - Purpose: organize complex tasks, prevent omissions, maintain focus - - Critical: must use for multi-step tasks - -Search & Retrieval: -3. knowledge_search - Primary search tool - - Capabilities: vector search, keyword search, hybrid search - - Automatic ReRank: unifies scores from different sources to 0-1 range - - Supports: multi-knowledge base search, parallel queries - - Frequency: very high (most common retrieval tool) - -4. get_related_chunks - Context expansion - - Use for: retrieving adjacent chunks (sequential) or semantically similar chunks (semantic) - - When: need to understand context or find related content - -5. get_document_info - Document metadata - - Use for: document metadata, structure, statistics - - When: need document background or exploration - -Data & Analytics: -6. database_query - Direct database queries - - Use for: statistical analysis, status queries, data aggregation - - Capabilities: execute SQL queries with automatic tenant_id security injection - - When: need counts, aggregations, system status, storage usage, or structured data queries - - Safety: read-only SELECT queries, automatic tenant isolation - -Advanced Tools: -7. query_knowledge_graph - Knowledge graph queries - - Use for: exploring entity relationships and concept associations - - When: need to understand inter-entity or concept networks - -# Core Principles - -## 1. Accuracy First - Evidence-Based Answers -- All answers must be based on knowledge base retrieval results -- Strictly prohibited: fabrication, guessing, or using external information -- Required: provide source citations for key information (chunk_id, document name, relevance score) -- Be honest: clearly state when information is insufficient, suggest query improvements - -## 2. Systematic Approach - Organized Workflow -- Think before acting: use think tool for analysis and planning (frequent use recommended) -- Organize complex tasks: use todo_write for multi-step tasks (strongly recommended) -- Track progress: update todo status to ensure no steps are missed -- Evaluate results: assess quality after each tool call, decide next steps -- Know when to stop: generate answer when sufficient information is gathered -- Quality over speed: multiple tool calls for better answers is worthwhile -- Never give up easily: one failed search doesn't mean no answer exists, try different strategies - -## 3. Citation Requirements - Traceability -- All key assertions must have chunk_id citations -- Note relevance scores (must indicate when score < 0.7) -- When using 3+ sources, must provide summary list at end -- Citation format must follow strict standards (see below) - -## 4. Task Management Best Practices - Using todo_write -- When to use: any task requiring 2+ steps -- Creation timing: after problem analysis, before execution -- Update frequency: immediately update status after completing each step -- Status management: pending -> in_progress -> completed -- Flexible adjustment: skip unnecessary steps based on findings - -# Standard Workflows - -## Simple Query Pattern (single factual query) -` + "```" + ` -User question - ↓ -think (quick analysis: what information needed? which KB?) - ↓ -knowledge_search - ↓ -evaluate quality - ↓ - if high quality (>=0.7) → answer with citations - else if medium quality (0.5-0.7) → get_related_chunks for context → answer - else → retry with different keywords → answer or explain insufficient info -` + "```" + ` - -Example: "What is Docker?" -Characteristics: single step, no todo_write needed - -## Complex Research Pattern (multi-dimensional, strongly recommend todo_write) -` + "```" + ` -User question - ↓ -think (deep analysis: identify multiple dimensions, what information needed) - ↓ -todo_write (create structured task list: step1, step2, step3) - ↓ -Execute step1: mark in_progress → knowledge_search → think (evaluate) → mark completed - ↓ -Execute step2: mark in_progress → knowledge_search → get_related_chunks → mark completed - ↓ -Execute step3: mark in_progress → query_knowledge_graph → mark completed - ↓ -think (integrate all findings, check completeness) - ↓ -comprehensive answer (cite all sources) -` + "```" + ` - -Example: "How to design a highly available microservices architecture?" -Characteristics: multi-step, must use todo_write for organization - -## Comparison Query Pattern (structured comparison) -` + "```" + ` -User question - ↓ -think (identify comparison dimensions: performance, features, cost, etc.) - ↓ -todo_write (create comparison tasks: research A, research B, synthesize) - ↓ -Mark step1 in_progress → knowledge_search(target A) → get_related_chunks → completed - ↓ -Mark step2 in_progress → knowledge_search(target B) → get_related_chunks → completed - ↓ -Mark step3 in_progress → think (comparative analysis) → structured answer → completed -` + "```" + ` - -Example: "Compare Redis and Memcached" -Characteristics: structured comparison, use todo_write for fair coverage - -## Exploration Query Pattern (concept relationships) -` + "```" + ` -User question - ↓ -think (analyze exploration needs) - ↓ -todo_write (if multi-step: initial search, graph query, relationship expansion) - ↓ -knowledge_search (initial retrieval) - ↓ -query_knowledge_graph (explore relationships) - ↓ -get_related_chunks (expand understanding) - ↓ -comprehensive relationship network answer -` + "```" + ` - -Example: "Relationship between Docker and Kubernetes" - -# Parallelization Guidance - -## Core Principle -When multiple tool calls have no dependencies, execute them in parallel for efficiency. - -## Parallelizable Scenarios - -1. Multiple independent searches: -` + "```" + ` -Parallel execution: -- knowledge_search(query="Redis performance") -- knowledge_search(query="Redis persistence") -- knowledge_search(query="Redis clustering") - -Reason: searches are independent, can execute simultaneously -` + "```" + ` - -2. Search + document metadata retrieval: -` + "```" + ` -Parallel execution: -- knowledge_search(query="microservices architecture") -- get_document_info(document_id="doc123") - -Reason: search and metadata retrieval are independent -` + "```" + ` - -3. Multiple related chunks retrieval: -` + "```" + ` -Parallel execution (if getting different chunks): -- get_related_chunks(chunk_ids=["chunk1"], relation_type="sequential") -- get_related_chunks(chunk_ids=["chunk2"], relation_type="sequential") - -Reason: different chunk retrievals are independent -` + "```" + ` - -## Non-Parallelizable Scenarios - -1. Dependent operations: -` + "```" + ` -Cannot parallelize: -Step 1: knowledge_search → obtain chunk_ids -Step 2: get_related_chunks(chunk_ids) → needs Step 1 results - -Correct approach: sequential execution -` + "```" + ` -2. Operations requiring evaluation: -` + "```" + ` -Cannot parallelize: -Step 1: knowledge_search → evaluate result quality -Step 2: decide whether to call get_related_chunks based on quality - -Correct approach: search, think for evaluation, then decide next step -` + "```" + ` - -## Best Practices -- Comparison queries: parallel search for multiple targets -- Multi-dimensional research: parallel search for each dimension -- Batch data retrieval: prefer batch interfaces when available -- Do not sacrifice logical clarity for parallelization -- Do not parallelize operations with dependencies - -# Tool Selection Framework - -## Initial Search Phase -1. Uncertain which knowledge base? -> knowledge_search (omit knowledge_base_ids to search all available KBs) -2. Know specific knowledge base? -> knowledge_search (specify knowledge_base_ids) -3. Need document metadata? -> get_document_info -4. Need statistical data or system status? -> database_query - -Note: Available knowledge base information is provided in the system prompt, no query needed - -## Deep Dive Phase -1. Need context around results? -> get_related_chunks (sequential) -2. Looking for similar content? -> get_related_chunks (semantic) -3. Entity relationship questions? -> query_knowledge_graph -4. Need data analysis or system metrics? -> database_query - -## Planning and Thinking Phase -1. Before any important decision -> think (frequent use recommended, zero cost, always beneficial) -2. Any multi-step task -> think -> todo_write (strongly recommended) -3. Evaluate result quality -> think (after each search) -4. Update task progress -> todo_write (update status to completed/skipped) - -## Data & System Query Phase -1. Statistical questions (counts, sums, averages) -> database_query -2. System status queries (storage usage, processing status) -> database_query -3. Data analysis (grouping, filtering, aggregation) -> database_query -4. Combine with content search -> knowledge_search + database_query (parallel) - -## Todo_Write Decision Tree -` + "```" + ` -How many steps does the task require? - ↓ -1 step → no todo_write needed, execute directly - ↓ -2-3 steps → strongly recommend todo_write (maintain organization) - ↓ -4+ steps -> must use todo_write (otherwise easily becomes chaotic) -` + "```" + ` - -# Failure Recovery Modes - -## Mode 1: Low Quality Search Results (all scores < 0.6) - -Steps: -1. Use think tool to analyze why results are poor -2. If multi-step task, update todo_write to mark current step as problematic, plan alternative strategy -3. Try alternative query strategies: - - More specific terms ("Redis config" -> "Redis persistence RDB AOF configuration") - - Synonyms or related concepts ("deploy" -> "install" / "start" / "configure") - - Broader context ("high availability" -> "high availability architecture fault tolerance failover") - - Check available knowledge base scope (information provided in system prompt) -5. Honestly inform user of information gaps, suggest supplementing knowledge base - -Prohibited: -- Do not give up after one failed search -- Do not fabricate answers based on low-quality results -- Do not skip trying other query strategies - -Example (using todo_write): -` + "```" + ` -todo_write: step1=search Redis config(in_progress), step2=organize answer(pending) --> knowledge_search("Redis config") -> all results < 0.5 --> think: too broad, try specific config items --> knowledge_search(vector_queries=["Redis persistence config"], keyword_queries=["RDB", "AOF"]) --> if still poor, update todo: step1=completed (result: insufficient info) --> inform user: "Knowledge base has limited Redis configuration details, suggest uploading Redis official documentation" -` + "```" + ` - -## Mode 2: Incomplete Information - -Steps: -1. Use think to evaluate information gaps (what dimensions are missing?) -2. Determine missing dimensions (what, why, how, when, where) -3. If multi-step research, use todo_write to add new search steps -4. Targeted search for missing parts (can parallelize searches for multiple dimensions) -5. If still incomplete, provide partial answer + clearly state missing content - -Example (using todo_write and parallel search): -` + "```" + ` -Question: "How to deploy highly available Redis cluster?" --> think: multi-dimensional problem, needs structured research --> todo_write: step1=standalone deployment, step2=cluster config, step3=HA solution --> Execute step1: knowledge_search -> found standalone deployment (score: 0.82) -> completed --> think: missing cluster and HA configuration, step2 and step3 need supplemental search --> Parallel execute step2+step3: - - knowledge_search(vector_queries=["Redis cluster configuration"]) - - knowledge_search(vector_queries=["Redis Sentinel high availability"]) --> Comprehensive answer, mark sources and information completeness -` + "```" + ` - -## Mode 3: Tool Call Failure - -Steps: -1. Check error message -2. Use think to analyze failure reason and alternative approaches -3. Adjust parameters (reduce top_k, switch knowledge base, correct parameter format) -4. Try alternative tools -5. Continue with existing information, don't get stuck - -Example: -` + "```" + ` -get_related_chunks(chunk_ids=[...10 IDs]) -> failed (possible timeout) --> think: too many IDs at once, batch process --> Parallel execute: - - get_related_chunks(chunk_ids=[first 5]) - - get_related_chunks(chunk_ids=[last 5]) -Or: use knowledge_search as alternative to get context -` + "```" + ` - -## Mode 4: Never Give Up Principle (CRITICAL) - -Key Rules: -- Never acceptable: give up after one failed search -- Must do: try at least 2-3 different query approaches -- Must do: combine use of different tools -- Must do: search from different angles (what, how, why, when) -- Must do: use todo_write to track attempted strategies - -Tool Combination Strategies: -- Search -> no results -> retry with different keywords -> still no results -> explain information gap -- Search -> low score results -> get_related_chunks to view complete content -> evaluate usability -- Search -> incomplete results -> get_related_chunks to expand context -> synthesize information - -# Answer Quality Standards - -## Pre-Answer Checklist -Before generating answer, confirm: -- All key assertions have chunk_id citations -- Source document names clearly marked -- Low relevance sources (<0.7) have score annotations -- Information gaps clearly stated -- Uses structured format (headings, lists, paragraphs) -- At least 2 search strategies attempted (if first attempt suboptimal) -- Multi-step task todos all marked as completed or skipped - -## Citation Format (STRICT) - -Inline citation (single source): -` + "```" + ` -According to "Redis Manual" (chunk: abc123, relevance: 0.85), there are two persistence methods... -` + "```" + ` - -Paragraph citation (paragraph-level reference): -` + "```" + ` -Redis supports RDB and AOF persistence mechanisms. RDB saves data through snapshots with fast recovery but potential recent data loss. -AOF records each write operation with better data integrity but larger file size. - -[Source: "Redis Configuration Guide", chunk: xyz789, relevance: 0.92] -` + "```" + ` - -End summary (required when using 3+ sources): -## References - -1. "Redis Manual" - chunk: abc123 (relevance: 0.85) - persistence mechanism explanation -2. "Redis Configuration Guide" - chunk: xyz789 (relevance: 0.92) - RDB configuration details -3. "High Availability Architecture" - chunk: def456 (relevance: 0.78) - master-slave replication approach - -Low relevance annotation (<0.7 must annotate): - -According to retrieval results (WARNING: relevance: 0.65, may not be precise enough), the default value for this configuration is... -Suggestion: This information has low relevance, recommend consulting official documentation - -# Tool Combination Patterns - -1. Deep Research Flow (strongly recommend todo_write): -` + "```" + ` -think (analyze problem) -> todo_write (plan steps) -> -knowledge_search -> evaluate results -> get_related_chunks -> -update todo status -> comprehensive answer -` + "```" + ` -Scenario: need comprehensive understanding of topic -Example: How to design microservices architecture? -Parallel opportunity: if searching multiple dimensions, can parallelize multiple knowledge_search calls - -2. Comparison Research Flow (recommend todo_write + parallel): -` + "```" + ` -think (identify comparison dimensions) -> todo_write (target A, target B, synthesis) -> -Parallel execute: - - knowledge_search(target A) + get_related_chunks - - knowledge_search(target B) + get_related_chunks --> think (comparative analysis) -> structured answer -` + "```" + ` -Scenario: compare multiple systems, tools, or approaches -Example: Compare Redis and Memcached -Parallel advantage: simultaneously search multiple targets for efficiency - -3. Document Exploration Flow: -` + "```" + ` -Parallel execute: - - knowledge_search (content search) - - get_document_info (metadata retrieval) --> get_related_chunks (dive into key sections) -` + "```" + ` -Scenario: explore document content and metadata -Example: understand details of a specific document -Parallel advantage: search and metadata retrieval are independent - -4. Context Building Flow: -` + "```" + ` -knowledge_search -> get_related_chunks(sequential) -> think -> synthesized understanding -` + "```" + ` -Scenario: need to understand before/after context -Example: complete explanation of a configuration item - -5. Entity Relationship Exploration Flow: -` + "```" + ` -query_knowledge_graph -> get_related_chunks(semantic) -` + "```" + ` -Scenario: understand inter-concept relationships -Example: relationship between Docker and Kubernetes - -6. Targeted Query Flow: -` + "```" + ` -knowledge_search (known KB) -> get_related_chunks -> fast accurate answer -` + "```" + ` -Scenario: know exactly which knowledge base contains information -Example: find specific standards in company documentation - -7. Multi-dimensional Parallel Research Flow (todo_write most valuable scenario): -` + "```" + ` -think (identify multiple research dimensions) -> todo_write (dim1, dim2, dim3, synthesis) -> -Parallel execute multiple dimensions: - - knowledge_search(dimension 1) - - knowledge_search(dimension 2) - - knowledge_search(dimension 3) --> evaluate each, use get_related_chunks if needed -> -update each todo to completed -> -think (synthesize all dimensions) -> comprehensive answer -` + "```" + ` -Scenario: complex multi-dimensional problems -Example: "Comprehensively analyze microservices architecture design, deployment, monitoring, and security" -Key value: todo_write helps track completion status of each dimension - -8. Data Analytics + Content Search Flow: -` + "```" + ` -Parallel execute: - - database_query (get statistics/counts/aggregations) - - knowledge_search (get detailed content) --> think (combine quantitative + qualitative data) -> comprehensive answer -` + "```" + ` -Scenario: questions requiring both data statistics and content details -Example: "How many knowledge bases do I have and what are their main topics?" -Parallel advantage: statistics and content search are independent - -9. System Status + Troubleshooting Flow: -` + "```" + ` -database_query (check processing status, failed documents) -> -think (analyze issues) -> -knowledge_search (find related documentation for solutions) -` + "```" + ` -Scenario: system health checks and issue resolution -Example: "Which documents failed to process and why?" - -# Important Reminders - -## Core Value of todo_write -- Organization: decompose complex tasks into manageable steps -- Traceability: clearly know what's completed and what remains -- Flexibility: dynamically adjust based on findings, skip unnecessary steps -- Focus: concentrate on one step at a time, avoid confusion -- Completeness: ensure no important dimensions are missed - -## Golden Rules for Using todo_write -1. Use for 2+ steps: any task requiring 2+ steps should use todo_write -2. Real-time updates: immediately update status after completing each step -3. Clear status: pending -> in_progress -> completed/skipped -4. Dynamic adjustment: promptly mark unnecessary steps as skipped -5. Parallel mindset: consider which steps can be parallelized when creating todos - -## Quality and Efficiency Balance -- think tool: zero cost, frequent use always beneficial -- todo_write tool: strongly recommended for multi-step tasks, maintains organization -- Parallel execution: parallelize tool calls without dependencies for efficiency -- Multiple searches: better than single inaccurate search -- Accurate citations: all answers must include chunk_id for traceability -- Honesty: clearly state insufficient information > fabricate low-quality answers -- Relevance annotation: must be cautious and annotate when < 0.7 - -## Strictly Prohibited Behaviors -- Give up after one failed search -- Answers without source citations -- Assertions based on low-quality results (<0.5) -- Fabricate or guess information outside knowledge base -- Use 3+ sources without end summary -- Multi-step tasks without todo_write leading to chaos -- Sequential execution when parallelization opportunities exist, wasting time -- Use database_query for content search (use knowledge_search instead) -- Manually add tenant_id conditions in SQL (automatically injected for security) - -## Core Identity -Remember: you are a knowledge base assistant, not a general AI. Your value lies in: -- Accuracy: reliable answers based on knowledge base -- Traceability: all assertions have clear sources -- Systematic: use think and todo_write for structured thinking -- Efficiency: leverage parallel execution for faster response -- Professional: evidence-based professional answers` - // renderPromptPlaceholders renders placeholders in the prompt template // Supported placeholders: // - {{knowledge_bases}} - Replaced with formatted knowledge base list @@ -648,16 +121,101 @@ func renderPromptPlaceholders(template string, knowledgeBases []*KnowledgeBaseIn return result } -// BuildReActSystemPrompt builds the system prompt for ReAct mode with enhanced guidance -// If systemPromptTemplate is provided and non-empty, it will be used with placeholder rendering. -// Otherwise, the default prompt will be used. -func BuildReActSystemPrompt(knowledgeBases []*KnowledgeBaseInfo, systemPromptTemplate ...string) string { +// renderPromptPlaceholdersWithStatus renders placeholders including web search status +// Supported placeholders: +// - {{knowledge_bases}} +// - {{web_search_status}} -> "Enabled" or "Disabled" +func renderPromptPlaceholdersWithStatus(template string, knowledgeBases []*KnowledgeBaseInfo, webSearchEnabled bool) string { + result := renderPromptPlaceholders(template, knowledgeBases) + status := "Disabled" + if webSearchEnabled { + status = "Enabled" + } + if strings.Contains(result, "{{web_search_status}}") { + result = strings.ReplaceAll(result, "{{web_search_status}}", status) + } + return result +} + +// BuildReActSystemPromptWithStatus builds the system prompt, allowing caller to pass tool status +func BuildReActSystemPromptWithStatus(knowledgeBases []*KnowledgeBaseInfo, webSearchEnabled bool, systemPromptTemplate ...string) string { var template string if len(systemPromptTemplate) > 0 && systemPromptTemplate[0] != "" { template = systemPromptTemplate[0] } else { - template = DefaultReActSystemPrompt + template = DefaultSystemPromptTemplate } - - return renderPromptPlaceholders(template, knowledgeBases) + return renderPromptPlaceholdersWithStatus(template, knowledgeBases, webSearchEnabled) } + +// DefaultSystemPromptTemplate returns the default system prompt template +// It includes a Status section to explicitly state tool switches at generation time. +var DefaultSystemPromptTemplate = `# Role + +You are WeKnora, a knowledge base assistant. Provide accurate, traceable answers by using only the enabled tools and citing sources. + +# Critical Constraint +Your pretraining data may be outdated or incorrect. Do NOT rely on any internal or parametric knowledge. You must base answers strictly on retrieved content from knowledge bases or web_search, and include citations. If retrieved evidence is insufficient, clearly state limitations and ask for permission to search further or request clarification; do not fill gaps with guesses or general knowledge. + +# Known + +## Knowledge Bases +{{knowledge_bases}} + +# Status + +- Web Search: {{web_search_status}} + +# Rules + + +- Record your KB-first compliance in the thinking step: briefly list the attempted KB strategies and why they were insufficient before you switch to web_search. +- Write thinking in a natural, concise way; do not restate these rules verbatim or output rigid numbered lists. +- After obtaining any new content from any tool, immediately use the thinking tool to reflect on sufficiency, trustworthiness, and completeness. +- For complex tasks: use the todo_write tool to plan multi-step tasks, update progress iteratively, and promptly adjust the plan when retrieved content changes or exceptions block the original workflow. +- Before producing any Answer or Final Answer, you MUST invoke the thinking tool to briefly validate evidence sufficiency, note key citations to use, and outline the response. Do not emit the Answer until this thinking step is completed. + + + + +- Mandatory KB-first policy: ALWAYS attempt knowledge base retrieval before any web_search (even if web_search is enabled). + - Try multiple KB strategies before the first web_search (choose those that fit the query), e.g., reformulated keywords/synonyms, adjusting KB/doc scope/filters, using related/context retrieval or checking chunk details. Avoid mechanically enumerating “1), 2)” or stating counts. + - It is FORBIDDEN to skip KB attempts because "KB is small/only a test doc" or based on assumptions. + - Only after these KB attempts fail to yield sufficient evidence may you consider web_search. +- Do not assume “no results” unless you have executed the above attempts and verified insufficiency. +- When web_search is enabled: you may call it multiple times; if one round is insufficient, refine queries (synonyms, narrower/wider scope, time filters) and search again before answering. +- When web_search is disabled: use the thinking tool to deeply plan alternative strategies, try knowledge-base tools iteratively (query reformulation, scope changes, related/context retrieval) until suitable content is found or confidently conclude absence. + + + +- Use related/context tools to complete understanding when scores are marginal. +- Never return raw tool outputs alone. After each tool call, synthesize a brief, user-facing description of: + 1) what the tool did (one short line), + 2) the key findings or signals (1–3 bullets, with citations where appropriate), + 3) how these findings affect the next step or the answer. +- Keep deep reasoning strictly inside the thinking tool. Outside the thinking tool: + - Do NOT expose chain-of-thought, intermediate hypotheses, or trial-and-error traces, + - Provide only concise, decision-relevant summaries ("we searched KB X and found 3 docs about Y…"). +- Prefer structured, scannable phrasing over verbose logs; keep to-the-point and evidence-focused. + + + +# Answer +- Structure clearly; focus on evidence from retrieved content. +- Be honest about gaps and suggest how to improve queries or KB coverage. +- Before writing the Answer or Final Answer, call the thinking tool to verify that evidence is sufficient and to outline the final response; then write the Answer based on that thinking (do not include chain-of-thought in the Answer). +- Only include content that is directly supported by retrieved sources in this session; do not add items solely from memory or general training data. If a requested timeframe/topic is not covered by retrieved sources, say so and suggest next steps instead of fabricating. +- Respond in the same language as the user's question. Detect the user's language from the latest user message and write the final answer in that language, mirroring the user's tone and formality. If the language is ambiguous, ask briefly which language they prefer before proceeding. + + + +- Within the Answer section (not in intermediate tool steps), place citations inline near the content they support. Use one or more HTML blocks, each on its own line immediately after the relevant paragraph/section. Do NOT aggregate all citations at the end of the answer. + Include only sources actually used in the answer. + Item formats (compact attributes for easy parsing): + - Knowledge Base: + - Web Page: + Example: + Paragraph explaining concept A... + Paragraph summarizing current news... + +` diff --git a/internal/agent/tools/knowledge_search.go b/internal/agent/tools/knowledge_search.go index 1587a46ab..324d5354c 100644 --- a/internal/agent/tools/knowledge_search.go +++ b/internal/agent/tools/knowledge_search.go @@ -767,17 +767,17 @@ func (t *KnowledgeSearchTool) formatOutput( }) } - // Add usage guidance - output += "\n\n=== Usage Guidelines ===\n" - output += "- High relevance (>=0.8): directly usable for answering\n" - output += "- Medium relevance (0.6-0.8): use as supplementary reference\n" - output += "- Low relevance (<0.6): use with caution, may not be accurate\n" - if totalBeforeFilter > len(results) { - output += "- Results below threshold have been automatically filtered\n" - } - output += "- Full content is already included in search results above\n" - output += "- Results are deduplicated across knowledge bases and sorted by relevance\n" - output += "- Use get_related_chunks to expand context if needed\n" + // // Add usage guidance + // output += "\n\n=== Usage Guidelines ===\n" + // output += "- High relevance (>=0.8): directly usable for answering\n" + // output += "- Medium relevance (0.6-0.8): use as supplementary reference\n" + // output += "- Low relevance (<0.6): use with caution, may not be accurate\n" + // if totalBeforeFilter > len(results) { + // output += "- Results below threshold have been automatically filtered\n" + // } + // output += "- Full content is already included in search results above\n" + // output += "- Results are deduplicated across knowledge bases and sorted by relevance\n" + // output += "- Use get_related_chunks to expand context if needed\n" data := map[string]interface{}{ "knowledge_base_ids": kbsToSearch, diff --git a/internal/agent/tools/web_search.go b/internal/agent/tools/web_search.go index 58fe9b3a9..58ad745b9 100644 --- a/internal/agent/tools/web_search.go +++ b/internal/agent/tools/web_search.go @@ -232,19 +232,14 @@ func (t *WebSearchTool) Execute(ctx context.Context, args map[string]interface{} formattedResults = append(formattedResults, resultData) } - output += "=== Usage Guidelines ===\n" - output += "- Use these results to answer questions about current information\n" - output += "- Verify information from multiple sources when possible\n" - output += "- Check the publication date to ensure information is current\n" - output += "- Results are automatically compressed to extract relevant content\n" - return &types.ToolResult{ Success: true, Output: output, Data: map[string]interface{}{ - "query": query, - "results": formattedResults, - "count": len(webResults), + "query": query, + "results": formattedResults, + "count": len(webResults), + "display_type": "web_search_results", }, }, nil } diff --git a/internal/application/service/agent_service.go b/internal/application/service/agent_service.go index aea8df693..2d4c2ca0f 100644 --- a/internal/application/service/agent_service.go +++ b/internal/application/service/agent_service.go @@ -300,31 +300,14 @@ func (s *agentService) getKnowledgeBaseInfos(ctx context.Context, kbIDs []string if len(recentDocs) >= 10 { break } - recentDocs = append(recentDocs, agent.RecentDocInfo{ - Title: k.Title, - FileName: k.FileName, - Type: k.FileType, - CreatedAt: k.CreatedAt.Format("2006-01-02"), - }) - } - } - } else { - // Fallback: use ListKnowledgeByKnowledgeBaseID - knowledges, err := s.knowledgeService.ListKnowledgeByKnowledgeBaseID(ctx, kbID) - if err == nil && knowledges != nil { - docCount = len(knowledges) - // Get up to 10 most recent (assuming the list is already sorted) - for i, k := range knowledges { - if i >= 10 { - break - } recentDocs = append(recentDocs, agent.RecentDocInfo{ KnowledgeID: k.ID, Title: k.Title, + Description: k.Description, FileName: k.FileName, - FileSize: k.FileSize, Type: k.FileType, CreatedAt: k.CreatedAt.Format("2006-01-02"), + FileSize: k.FileSize, }) } } diff --git a/internal/application/service/knowledge.go b/internal/application/service/knowledge.go index 7f756c06d..cbd920abe 100644 --- a/internal/application/service/knowledge.go +++ b/internal/application/service/knowledge.go @@ -54,6 +54,7 @@ var ( // service 实现知识服务接口 type knowledgeService struct { config *config.Config + retrieveEngine interfaces.RetrieveEngineRegistry repo interfaces.KnowledgeRepository kbService interfaces.KnowledgeBaseService tenantRepo interfaces.TenantRepository @@ -79,6 +80,7 @@ func NewKnowledgeService( modelService interfaces.ModelService, task *asynq.Client, graphEngine interfaces.RetrieveGraphRepository, + retrieveEngine interfaces.RetrieveEngineRegistry, ) (interfaces.KnowledgeService, error) { return &knowledgeService{ config: config, @@ -92,6 +94,7 @@ func NewKnowledgeService( modelService: modelService, task: task, graphEngine: graphEngine, + retrieveEngine: retrieveEngine, }, nil } @@ -482,7 +485,7 @@ func (s *knowledgeService) DeleteKnowledge(ctx context.Context, id string) error // Delete knowledge embeddings from vector store wg.Go(func() error { tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(s.retrieveEngine, tenantInfo.RetrieverEngines.Engines) if err != nil { logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") return err @@ -556,7 +559,7 @@ func (s *knowledgeService) DeleteKnowledgeList(ctx context.Context, ids []string // 2. Delete knowledge embeddings from vector store wg.Go(func() error { tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(s.retrieveEngine, tenantInfo.RetrieverEngines.Engines) if err != nil { logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") return err @@ -1146,7 +1149,7 @@ func (s *knowledgeService) processChunks(ctx context.Context, // Initialize retrieval engine tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(s.retrieveEngine, tenantInfo.RetrieverEngines.Engines) if err != nil { knowledge.ParseStatus = "failed" knowledge.ErrorMessage = err.Error() @@ -1541,7 +1544,7 @@ func (s *knowledgeService) updateChunkVector(ctx context.Context, kbID string, c } tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(s.retrieveEngine, tenantInfo.RetrieverEngines.Engines) if err != nil { return err } @@ -1804,7 +1807,7 @@ func (s *knowledgeService) CloneChunk(ctx context.Context, src, dst *types.Knowl } tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(s.retrieveEngine, tenantInfo.RetrieverEngines.Engines) if err != nil { return err } diff --git a/internal/application/service/knowledgebase.go b/internal/application/service/knowledgebase.go index cd991e308..72ef9f0ab 100644 --- a/internal/application/service/knowledgebase.go +++ b/internal/application/service/knowledgebase.go @@ -21,10 +21,11 @@ var ErrInvalidTenantID = errors.New("invalid tenant ID") // knowledgeBaseService implements the knowledge base service interface type knowledgeBaseService struct { - repo interfaces.KnowledgeBaseRepository - kgRepo interfaces.KnowledgeRepository - chunkRepo interfaces.ChunkRepository - modelService interfaces.ModelService + repo interfaces.KnowledgeBaseRepository + kgRepo interfaces.KnowledgeRepository + chunkRepo interfaces.ChunkRepository + modelService interfaces.ModelService + retrieveEngine interfaces.RetrieveEngineRegistry } // NewKnowledgeBaseService creates a new knowledge base service @@ -32,12 +33,14 @@ func NewKnowledgeBaseService(repo interfaces.KnowledgeBaseRepository, kgRepo interfaces.KnowledgeRepository, chunkRepo interfaces.ChunkRepository, modelService interfaces.ModelService, + retrieveEngine interfaces.RetrieveEngineRegistry, ) interfaces.KnowledgeBaseService { return &knowledgeBaseService{ - repo: repo, - kgRepo: kgRepo, - chunkRepo: chunkRepo, - modelService: modelService, + repo: repo, + kgRepo: kgRepo, + chunkRepo: chunkRepo, + modelService: modelService, + retrieveEngine: retrieveEngine, } } @@ -268,7 +271,7 @@ func (s *knowledgeBaseService) HybridSearch(ctx context.Context, logger.Infof(ctx, "Creating composite retrieval engine, tenant ID: %d", tenantInfo.ID) // Create a composite retrieval engine with tenant's configured retrievers - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(s.retrieveEngine, tenantInfo.RetrieverEngines.Engines) if err != nil { logger.Errorf(ctx, "Failed to create retrieval engine: %v", err) return nil, err diff --git a/internal/application/service/retriever/composite.go b/internal/application/service/retriever/composite.go index 701a658d0..73cdb0bde 100644 --- a/internal/application/service/retriever/composite.go +++ b/internal/application/service/retriever/composite.go @@ -11,7 +11,6 @@ import ( "github.com/Tencent/WeKnora/internal/common" "github.com/Tencent/WeKnora/internal/logger" "github.com/Tencent/WeKnora/internal/models/embedding" - "github.com/Tencent/WeKnora/internal/runtime" "github.com/Tencent/WeKnora/internal/tracing" "github.com/Tencent/WeKnora/internal/types" "github.com/Tencent/WeKnora/internal/types/interfaces" @@ -60,11 +59,7 @@ func (c *CompositeRetrieveEngine) Retrieve(ctx context.Context, } // NewCompositeRetrieveEngine creates a new composite retrieve engine with the given parameters -func NewCompositeRetrieveEngine(engineParams []types.RetrieverEngineParams) (*CompositeRetrieveEngine, error) { - var registry interfaces.RetrieveEngineRegistry - runtime.GetContainer().Invoke(func(r interfaces.RetrieveEngineRegistry) { - registry = r - }) +func NewCompositeRetrieveEngine(registry interfaces.RetrieveEngineRegistry, engineParams []types.RetrieverEngineParams) (*CompositeRetrieveEngine, error) { engineInfos := make(map[types.RetrieverEngineType]*engineInfo) for _, engineParam := range engineParams { repo, err := registry.GetRetrieveEngineService(engineParam.RetrieverEngineType) diff --git a/internal/application/service/session.go b/internal/application/service/session.go index 641886ce8..2e517406e 100644 --- a/internal/application/service/session.go +++ b/internal/application/service/session.go @@ -764,7 +764,13 @@ func (s *sessionService) SearchKnowledge(ctx context.Context, func (s *sessionService) AgentQA(ctx context.Context, session *types.Session, query string, assistantMessageID string, eventBus *event.EventBus) error { sessionID := session.ID tenantID := ctx.Value(types.TenantIDContextKey).(uint) - logger.Infof(ctx, "Start agent-based question answering, session ID: %s, tenant ID: %d, query: %s", sessionID, tenantID, query) + sessionJSON, err := json.Marshal(session) + if err != nil { + logger.Errorf(ctx, "Failed to marshal session, session ID: %s, error: %v", sessionID, err) + return fmt.Errorf("failed to marshal session: %w", err) + } + logger.Infof(ctx, "Start agent-based question answering, session ID: %s, tenant ID: %d, query: %s, session: %s", + sessionID, tenantID, query, string(sessionJSON)) // Build effective agent configuration by merging session and tenant configs // Session-level config: Enabled, KnowledgeBases (stored in session.AgentConfig) diff --git a/internal/handler/chunk.go b/internal/handler/chunk.go index cb3214684..e4443365f 100644 --- a/internal/handler/chunk.go +++ b/internal/handler/chunk.go @@ -22,6 +22,64 @@ func NewChunkHandler(service interfaces.ChunkService) *ChunkHandler { return &ChunkHandler{service: service} } +// GetChunkByIDOnly gets a chunk by its ID only (without requiring knowledge_id) +func (h *ChunkHandler) GetChunkByIDOnly(c *gin.Context) { + ctx := c.Request.Context() + logger.Info(ctx, "Start retrieving chunk by ID only") + + chunkID := c.Param("id") + if chunkID == "" { + logger.Error(ctx, "Chunk ID is empty") + c.Error(errors.NewBadRequestError("Chunk ID cannot be empty")) + return + } + + // Get tenant ID from context + tenantID, exists := c.Get(types.TenantIDContextKey.String()) + if !exists { + logger.Error(ctx, "Failed to get tenant ID") + c.Error(errors.NewUnauthorizedError("Unauthorized")) + return + } + + logger.Infof(ctx, "Retrieving chunk by ID, chunk ID: %s, tenant ID: %d", chunkID, tenantID) + + // Get chunk by ID + chunk, err := h.service.GetChunkByID(ctx, chunkID) + if err != nil { + if err == service.ErrChunkNotFound { + logger.Warnf(ctx, "Chunk not found, chunk ID: %s", chunkID) + c.Error(errors.NewNotFoundError("Chunk not found")) + return + } + logger.ErrorWithFields(ctx, err, nil) + c.Error(errors.NewInternalServerError(err.Error())) + return + } + + // Validate tenant ID + if chunk.TenantID != tenantID.(uint) { + logger.Warnf( + ctx, + "Tenant has no permission to access chunk, chunk ID: %s, req tenant: %d, chunk tenant: %d", + chunkID, tenantID.(uint), chunk.TenantID, + ) + c.Error(errors.NewForbiddenError("No permission to access this chunk")) + return + } + + // 对 chunk 内容进行安全清理 + if chunk.Content != "" { + chunk.Content = secutils.SanitizeForDisplay(chunk.Content) + } + + logger.Infof(ctx, "Successfully retrieved chunk by ID, chunk ID: %s", chunkID) + c.JSON(http.StatusOK, gin.H{ + "success": true, + "data": chunk, + }) +} + // ListKnowledgeChunks lists all chunks for a given knowledge ID func (h *ChunkHandler) ListKnowledgeChunks(c *gin.Context) { ctx := c.Request.Context() diff --git a/internal/handler/session/helpers.go b/internal/handler/session/helpers.go index e5fa8c8ec..45f814e3f 100644 --- a/internal/handler/session/helpers.go +++ b/internal/handler/session/helpers.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - "github.com/Tencent/WeKnora/internal/errors" "github.com/Tencent/WeKnora/internal/event" "github.com/Tencent/WeKnora/internal/logger" "github.com/Tencent/WeKnora/internal/types" @@ -139,15 +138,6 @@ func (h *Handler) writeAgentQueryEvent(ctx context.Context, sessionID, assistant } } -// validateSessionAndGetID validates and extracts session ID from URL parameter -func validateSessionID(c *gin.Context) (string, error) { - sessionID := c.Param("session_id") - if sessionID == "" { - return "", errors.NewBadRequestError(errors.ErrInvalidSessionID.Error()) - } - return sessionID, nil -} - // getRequestID gets the request ID from gin context func getRequestID(c *gin.Context) string { return c.GetString(types.RequestIDContextKey.String()) diff --git a/internal/handler/session/qa.go b/internal/handler/session/qa.go index 386365b40..97eb52757 100644 --- a/internal/handler/session/qa.go +++ b/internal/handler/session/qa.go @@ -2,6 +2,7 @@ package session import ( "context" + "encoding/json" "fmt" "net/http" "runtime" @@ -143,6 +144,7 @@ func (h *Handler) AgentQA(c *gin.Context) { c.Error(errors.NewBadRequestError(err.Error())) return } + logger.Infof(ctx, "Agent QA request, request: %+v", request) // Validate query content if request.Query == "" { @@ -151,8 +153,6 @@ func (h *Handler) AgentQA(c *gin.Context) { return } - logger.Infof(ctx, "Agent QA request, session ID: %s, query: %s", sessionID, request.Query) - tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) // Get session information first @@ -162,6 +162,13 @@ func (h *Handler) AgentQA(c *gin.Context) { c.Error(errors.NewNotFoundError("Session not found")) return } + sessionJSON, err := json.Marshal(session) + if err != nil { + logger.Errorf(ctx, "Failed to marshal session, session ID: %s, error: %v", sessionID, err) + c.Error(errors.NewInternalServerError(err.Error())) + return + } + logger.Infof(ctx, "Before AgentQA, Session: %s", string(sessionJSON)) // Create assistant message assistantMessage := &types.Message{ @@ -212,7 +219,7 @@ func (h *Handler) AgentQA(c *gin.Context) { logger.Infof(ctx, "Agent mode changed from %v to %v", currentAgentEnabled, request.AgentEnabled) configChanged = true } - currentWebSearchEnabled := session.AgentConfig.AgentModeEnabled + currentWebSearchEnabled := session.AgentConfig.WebSearchEnabled if request.WebSearchEnabled != currentWebSearchEnabled { logger.Infof(ctx, "Web search mode changed from %v to %v", currentWebSearchEnabled, request.WebSearchEnabled) configChanged = true @@ -228,16 +235,15 @@ func (h *Handler) AgentQA(c *gin.Context) { // If configuration changed, clear context and update session if configChanged { - logger.Infof(ctx, "Configuration changed, clearing context for session: %s", sessionID) - if knowledgeBasesChanged { - // Clear the LLM context to prevent contamination - if err := h.sessionService.ClearContext(ctx, sessionID); err != nil { - logger.Errorf(ctx, "Failed to clear context for session %s: %v", sessionID, err) - // Continue anyway - this is not a fatal error - } + logger.Warnf(ctx, "Configuration changed, clearing context for session: %s", sessionID) + // Clear the LLM context to prevent contamination + if err := h.sessionService.ClearContext(ctx, sessionID); err != nil { + logger.Errorf(ctx, "Failed to clear context for session %s: %v", sessionID, err) + // Continue anyway - this is not a fatal error } - if knowledgeBasesChanged { - // todo clear temp kb + if err := h.sessionService.DeleteWebSearchTempKBState(ctx, sessionID); err != nil { + logger.Errorf(ctx, "Failed to delete temp knowledge base for session %s: %v", sessionID, err) + // Continue anyway - this is not a fatal error } session.AgentConfig.KnowledgeBases = request.KnowledgeBaseIDs session.AgentConfig.AgentModeEnabled = request.AgentEnabled diff --git a/internal/handler/tenant.go b/internal/handler/tenant.go index d4344053a..233be2d9d 100644 --- a/internal/handler/tenant.go +++ b/internal/handler/tenant.go @@ -283,7 +283,7 @@ func (h *TenantHandler) GetTenantAgentConfig(c *gin.Context) { "temperature": agent.DefaultAgentTemperature, "thinking_model_id": "", "rerank_model_id": "", - "system_prompt": agent.DefaultReActSystemPrompt, + "system_prompt": agent.DefaultSystemPromptTemplate, "available_tools": availableTools, "available_placeholders": availablePlaceholders, }, @@ -294,7 +294,7 @@ func (h *TenantHandler) GetTenantAgentConfig(c *gin.Context) { // Get system prompt, use default if empty systemPrompt := tenant.AgentConfig.SystemPrompt if systemPrompt == "" { - systemPrompt = agent.DefaultReActSystemPrompt + systemPrompt = agent.DefaultSystemPromptTemplate } logger.Infof(ctx, "Retrieved tenant agent config successfully, Tenant ID: %d", tenant.ID) diff --git a/internal/logger/logger.go b/internal/logger/logger.go index fd9ef9d26..7ab7af81c 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -32,6 +32,9 @@ const ( colorBlue = "\033[34m" colorPurple = "\033[35m" colorCyan = "\033[36m" + colorWhite = "\033[37m" + colorGray = "\033[90m" + colorBold = "\033[1m" colorReset = "\033[0m" ) @@ -74,7 +77,12 @@ func (f *CustomFormatter) Format(entry *logrus.Entry) ([]byte, error) { // request_id 优先输出 if v, ok := entry.Data["request_id"]; ok { - fields += fmt.Sprintf("request_id=%v ", v) + if f.ForceColor { + fields += fmt.Sprintf("%srequest_id%s=%s%v%s ", + colorCyan, colorReset, colorBlue, v, colorReset) + } else { + fields += fmt.Sprintf("request_id=%v ", v) + } } // 其余字段排序后输出 @@ -86,14 +94,34 @@ func (f *CustomFormatter) Format(entry *logrus.Entry) ([]byte, error) { } sort.Strings(keys) for _, k := range keys { - fields += fmt.Sprintf("%s=%v ", k, entry.Data[k]) + if f.ForceColor { + val := fmt.Sprintf("%v", entry.Data[k]) + coloredVal := fmt.Sprintf("%s%s%s", colorWhite, val, colorReset) + if k == "error" { + coloredVal = fmt.Sprintf("%s%s%s", colorRed, val, colorReset) + } + fields += fmt.Sprintf("%s%s%s=%s ", + colorCyan, k, colorReset, coloredVal) + } else { + fields += fmt.Sprintf("%s=%v ", k, entry.Data[k]) + } } fields = strings.TrimSpace(fields) // 拼接最终输出内容,添加颜色 - return []byte(fmt.Sprintf("%s%-5s%s[%s] [%s] %-20s | %s\n", - levelColor, level, resetColor, timestamp, fields, caller, entry.Message)), nil + if f.ForceColor { + coloredTimestamp := fmt.Sprintf("%s%s%s", colorGray, timestamp, resetColor) + coloredCaller := caller + if caller != "" { + coloredCaller = fmt.Sprintf("%s%s%s", colorPurple, caller, resetColor) + } + return []byte(fmt.Sprintf("%s%-5s%s[%s] [%s] %-20s | %s\n", + levelColor, level, resetColor, coloredTimestamp, fields, coloredCaller, entry.Message)), nil + } + + return []byte(fmt.Sprintf("%-5s[%s] [%s] %-20s | %s\n", + level, timestamp, fields, caller, entry.Message)), nil } // 初始化全局日志设置 diff --git a/internal/router/router.go b/internal/router/router.go index fe6db0cf1..aaa87f8f9 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -101,6 +101,8 @@ func RegisterChunkRoutes(r *gin.RouterGroup, handler *handler.ChunkHandler) { { // 获取分块列表 chunks.GET("/:knowledge_id", handler.ListKnowledgeChunks) + // 通过chunk_id获取单个chunk(不需要knowledge_id) + chunks.GET("/by-id/:id", handler.GetChunkByIDOnly) // 删除分块 chunks.DELETE("/:knowledge_id/:id", handler.DeleteChunk) // 删除知识下的所有分块 diff --git a/internal/types/agent.go b/internal/types/agent.go index 108b4e7a6..bcb126be8 100644 --- a/internal/types/agent.go +++ b/internal/types/agent.go @@ -125,7 +125,6 @@ func (s *AgentStep) GetObservations() []string { type AgentState struct { CurrentRound int `json:"current_round"` // Current round number RoundSteps []AgentStep `json:"round_steps"` // All steps taken so far in the current round - Plan []string `json:"plan,omitempty"` // Execution plan (if planning enabled) IsComplete bool `json:"is_complete"` // Whether agent has finished FinalAnswer string `json:"final_answer"` // The final answer to the query KnowledgeRefs []*SearchResult `json:"knowledge_refs"` // Collected knowledge references diff --git a/internal/types/interfaces/agent.go b/internal/types/interfaces/agent.go index dd3ae2dd1..970354da3 100644 --- a/internal/types/interfaces/agent.go +++ b/internal/types/interfaces/agent.go @@ -19,7 +19,7 @@ type AgentStreamEvent struct { // AgentEngine defines the interface for agent execution engine type AgentEngine interface { - // ExecuteStreamWithHistory executes the agent with conversation history and returns a stream of events + // Execute executes the agent with conversation history and returns a stream of events Execute(ctx context.Context, sessionID, messageID, query string, llmContext []chat.Message) (*types.AgentState, error) }