WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// SearchTargetType represents the type of search target
|
||||
type SearchTargetType string
|
||||
|
||||
const (
|
||||
// SearchTargetTypeKnowledgeBase - search entire knowledge base
|
||||
SearchTargetTypeKnowledgeBase SearchTargetType = "knowledge_base"
|
||||
// SearchTargetTypeKnowledge - search specific knowledge files within a knowledge base
|
||||
SearchTargetTypeKnowledge SearchTargetType = "knowledge"
|
||||
)
|
||||
|
||||
// TagScope represents a tag-constrained retrieval scope inside one knowledge base.
|
||||
type TagScope struct {
|
||||
KnowledgeBaseID string `json:"knowledge_base_id"`
|
||||
TagIDs []string `json:"tag_ids"`
|
||||
}
|
||||
|
||||
// SearchTarget represents a unified search target
|
||||
// Either search an entire knowledge base, or specific knowledge files within a knowledge base
|
||||
type SearchTarget struct {
|
||||
// Type of search target
|
||||
Type SearchTargetType `json:"type"`
|
||||
// KnowledgeBaseID is the ID of the knowledge base to search
|
||||
KnowledgeBaseID string `json:"knowledge_base_id"`
|
||||
// TenantID is the tenant ID that owns this knowledge base
|
||||
// Required for cross-tenant shared KB queries
|
||||
TenantID uint64 `json:"tenant_id"`
|
||||
// KnowledgeIDs is the list of specific knowledge IDs to search within the knowledge base
|
||||
// Only used when Type is SearchTargetTypeKnowledge
|
||||
KnowledgeIDs []string `json:"knowledge_ids,omitempty"`
|
||||
// TagIDs limits retrieval to chunks/documents carrying any of these KB-local tags.
|
||||
TagIDs []string `json:"tag_ids,omitempty"`
|
||||
// ScopeTagIDs records the logical tag scope selected by the user. For
|
||||
// document KBs this is kept for tracing after the relation-table lookup has
|
||||
// been resolved to KnowledgeIDs; TagIDs remains the physical index filter.
|
||||
ScopeTagIDs []string `json:"scope_tag_ids,omitempty"`
|
||||
// DisableRecallThresholds keeps recall broad inside an already constrained,
|
||||
// user-selected scope. The reranker still orders candidates, but vector and
|
||||
// keyword thresholds cannot erase the whole explicit scope before reranking.
|
||||
DisableRecallThresholds bool `json:"disable_recall_thresholds,omitempty"`
|
||||
}
|
||||
|
||||
// SearchTargets is a list of search targets, pre-computed at request entry point
|
||||
type SearchTargets []*SearchTarget
|
||||
|
||||
// RecallThresholds returns the effective recall thresholds for this target.
|
||||
func (st *SearchTarget) RecallThresholds(vectorThreshold, keywordThreshold float64) (float64, float64) {
|
||||
if st != nil && st.DisableRecallThresholds {
|
||||
return 0, 0
|
||||
}
|
||||
return vectorThreshold, keywordThreshold
|
||||
}
|
||||
|
||||
// HasRecallThresholdOverride reports whether any target represents an
|
||||
// authoritative scope whose candidates must reach reranking before filtering.
|
||||
func (st SearchTargets) HasRecallThresholdOverride() bool {
|
||||
for _, target := range st {
|
||||
if target != nil && target.DisableRecallThresholds {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasKnowledgeRetrievalScope reports whether a request has any effective
|
||||
// knowledge retrieval scope. SearchTargets are the unified runtime form and
|
||||
// must be considered alongside the legacy/raw KB and knowledge ID fields so
|
||||
// tag-only mentions are not mistaken for pure chat.
|
||||
func HasKnowledgeRetrievalScope(
|
||||
searchTargets SearchTargets,
|
||||
knowledgeBaseIDs []string,
|
||||
knowledgeIDs []string,
|
||||
) bool {
|
||||
for _, id := range knowledgeBaseIDs {
|
||||
if id != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, id := range knowledgeIDs {
|
||||
if id != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, target := range searchTargets {
|
||||
if target == nil || target.KnowledgeBaseID == "" {
|
||||
continue
|
||||
}
|
||||
if target.Type == SearchTargetTypeKnowledgeBase || len(target.KnowledgeIDs) > 0 ||
|
||||
len(target.TagIDs) > 0 || len(target.ScopeTagIDs) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetAllKnowledgeBaseIDs returns all unique knowledge base IDs from the search targets
|
||||
func (st SearchTargets) GetAllKnowledgeBaseIDs() []string {
|
||||
seen := make(map[string]bool)
|
||||
var result []string
|
||||
for _, t := range st {
|
||||
if t == nil || t.KnowledgeBaseID == "" {
|
||||
continue
|
||||
}
|
||||
if !seen[t.KnowledgeBaseID] {
|
||||
seen[t.KnowledgeBaseID] = true
|
||||
result = append(result, t.KnowledgeBaseID)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetKBTenantMap returns a map from knowledge base ID to tenant ID
|
||||
func (st SearchTargets) GetKBTenantMap() map[string]uint64 {
|
||||
result := make(map[string]uint64)
|
||||
for _, t := range st {
|
||||
if t != nil && t.KnowledgeBaseID != "" {
|
||||
result[t.KnowledgeBaseID] = t.TenantID
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetTenantIDForKB returns the tenant ID for a given knowledge base ID
|
||||
// Returns 0 if not found
|
||||
func (st SearchTargets) GetTenantIDForKB(kbID string) uint64 {
|
||||
for _, t := range st {
|
||||
if t != nil && t.KnowledgeBaseID == kbID {
|
||||
return t.TenantID
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ContainsKB checks if the search targets contain a given knowledge base ID
|
||||
func (st SearchTargets) ContainsKB(kbID string) bool {
|
||||
for _, t := range st {
|
||||
if t != nil && t.KnowledgeBaseID == kbID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SearchResult represents the search result
|
||||
type SearchResult struct {
|
||||
// ID
|
||||
ID string `gorm:"column:id" json:"id"`
|
||||
// Content
|
||||
Content string `gorm:"column:content" json:"content"`
|
||||
// Knowledge ID
|
||||
KnowledgeID string `gorm:"column:knowledge_id" json:"knowledge_id"`
|
||||
// Chunk index
|
||||
ChunkIndex int `gorm:"column:chunk_index" json:"chunk_index"`
|
||||
// Knowledge title
|
||||
KnowledgeTitle string `gorm:"column:knowledge_title" json:"knowledge_title"`
|
||||
// Start at
|
||||
StartAt int `gorm:"column:start_at" json:"start_at"`
|
||||
// End at
|
||||
EndAt int `gorm:"column:end_at" json:"end_at"`
|
||||
// Seq
|
||||
Seq int `gorm:"column:seq" json:"seq"`
|
||||
// Score
|
||||
Score float64 ` json:"score"`
|
||||
// Match type
|
||||
MatchType MatchType ` json:"match_type"`
|
||||
// SubChunkIndex
|
||||
SubChunkID []string ` json:"sub_chunk_id"`
|
||||
// Metadata
|
||||
Metadata map[string]string ` json:"metadata"`
|
||||
|
||||
// Chunk 类型
|
||||
ChunkType string `json:"chunk_type"`
|
||||
// 父 Chunk ID
|
||||
ParentChunkID string `json:"parent_chunk_id"`
|
||||
// 图片信息 (JSON 格式)
|
||||
ImageInfo string `json:"image_info"`
|
||||
|
||||
// Knowledge file name
|
||||
// Used for file type knowledge, contains the original file name
|
||||
KnowledgeFilename string `json:"knowledge_filename"`
|
||||
|
||||
// Knowledge source
|
||||
// Used to indicate the source of the knowledge, such as "url"
|
||||
KnowledgeSource string `json:"knowledge_source"`
|
||||
|
||||
// KnowledgeChannel indicates through which channel the knowledge was ingested (web, api, wechat, etc.)
|
||||
KnowledgeChannel string `json:"knowledge_channel"`
|
||||
|
||||
// ChunkMetadata stores chunk-level metadata (e.g., generated questions)
|
||||
ChunkMetadata JSON `json:"chunk_metadata,omitempty"`
|
||||
|
||||
// MatchedContent is the actual content that was matched in vector search
|
||||
// For FAQ: this is the matched question text (standard or similar question)
|
||||
MatchedContent string `json:"matched_content,omitempty"`
|
||||
|
||||
// KnowledgeDescription is the description of the knowledge document
|
||||
KnowledgeDescription string `json:"knowledge_description,omitempty"`
|
||||
|
||||
// KnowledgeCustomMetadata is user-authored context safe to expose to models.
|
||||
KnowledgeCustomMetadata string `json:"knowledge_custom_metadata,omitempty"`
|
||||
|
||||
// KnowledgeBaseID is the ID of the knowledge base this result belongs to
|
||||
KnowledgeBaseID string `json:"knowledge_base_id,omitempty"`
|
||||
|
||||
// ContentRevision is the chunk edit revision at retrieval time.
|
||||
// Internal only: used by the merge pipeline to decide whether source
|
||||
// coordinates are still trustworthy.
|
||||
//
|
||||
// Zero means "never edited", so any construction path that forgets to carry
|
||||
// it over fails open to the position path. Results rebuilt from JSON (stored
|
||||
// message references) always land on zero because the field is not
|
||||
// serialized; the length invariant in chunkTrusted is the remaining guard
|
||||
// there. Keep the gorm column explicit so direct row scans populate it.
|
||||
ContentRevision int `gorm:"column:content_revision" json:"-"`
|
||||
|
||||
// ContentRewritten reports that the merge pipeline replaced Content
|
||||
// (parent or neighbor expansion) after retrieval, so StartAt/EndAt no
|
||||
// longer describe the body. Internal only: used by the merge pipeline,
|
||||
// never serialized.
|
||||
ContentRewritten bool `json:"-"`
|
||||
}
|
||||
|
||||
// SearchParams represents the search parameters
|
||||
type SearchParams struct {
|
||||
// QueryText is required unless query_embedding is provided, keyword matching is disabled,
|
||||
// and vector matching remains enabled.
|
||||
QueryText string `json:"query_text"`
|
||||
QueryEmbedding []float32 `json:"query_embedding,omitempty"`
|
||||
VectorThreshold float64 `json:"vector_threshold"`
|
||||
KeywordThreshold float64 `json:"keyword_threshold"`
|
||||
MatchCount int `json:"match_count"`
|
||||
DisableKeywordsMatch bool `json:"disable_keywords_match"`
|
||||
DisableVectorMatch bool `json:"disable_vector_match"`
|
||||
KnowledgeIDs []string `json:"knowledge_ids"`
|
||||
TagIDs []string `json:"tag_ids"` // Tag IDs for filtering (used for FAQ priority filtering)
|
||||
ScopeTagIDs []string `json:"scope_tag_ids,omitempty"`
|
||||
OnlyRecommended bool `json:"only_recommended"`
|
||||
// KnowledgeBaseIDs overrides the single KB ID passed to HybridSearch,
|
||||
// allowing a single retrieval call to span multiple KBs that share the
|
||||
// same embedding model. When empty, HybridSearch uses its own id parameter.
|
||||
KnowledgeBaseIDs []string `json:"knowledge_base_ids,omitempty"`
|
||||
// SkipContextEnrichment skips fetching parent, nearby, and relation chunks
|
||||
// in processSearchResults. Used by the chat pipeline where context assembly
|
||||
// is handled separately in the merge stage.
|
||||
SkipContextEnrichment bool `json:"skip_context_enrichment,omitempty"`
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface, used to convert SearchResult to database value
|
||||
func (c SearchResult) Value() (driver.Value, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface, used to convert database value to SearchResult
|
||||
func (c *SearchResult) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
b, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(b, c)
|
||||
}
|
||||
|
||||
// Pagination represents the pagination parameters
|
||||
type Pagination struct {
|
||||
// Page
|
||||
Page int `form:"page" json:"page" binding:"omitempty,min=1"`
|
||||
// Page size
|
||||
PageSize int `form:"page_size" json:"page_size" binding:"omitempty,min=1,max=1000"`
|
||||
}
|
||||
|
||||
// GetPage gets the page number, default is 1
|
||||
func (p *Pagination) GetPage() int {
|
||||
if p.Page < 1 {
|
||||
return 1
|
||||
}
|
||||
return p.Page
|
||||
}
|
||||
|
||||
// GetPageSize gets the page size, default is 20
|
||||
func (p *Pagination) GetPageSize() int {
|
||||
if p.PageSize < 1 {
|
||||
return 20
|
||||
}
|
||||
if p.PageSize > 1000 {
|
||||
return 1000
|
||||
}
|
||||
return p.PageSize
|
||||
}
|
||||
|
||||
// Offset gets the offset for database query
|
||||
func (p *Pagination) Offset() int {
|
||||
return (p.GetPage() - 1) * p.GetPageSize()
|
||||
}
|
||||
|
||||
// Limit gets the limit for database query
|
||||
func (p *Pagination) Limit() int {
|
||||
return p.GetPageSize()
|
||||
}
|
||||
|
||||
// PageResult represents the pagination query result
|
||||
type PageResult struct {
|
||||
Total int64 `json:"total"` // Total number of records
|
||||
Page int `json:"page"` // Current page number
|
||||
PageSize int `json:"page_size"` // Page size
|
||||
Data interface{} `json:"data"` // Data
|
||||
}
|
||||
|
||||
// NewPageResult creates a new pagination result
|
||||
func NewPageResult(total int64, page *Pagination, data interface{}) *PageResult {
|
||||
return &PageResult{
|
||||
Total: total,
|
||||
Page: page.GetPage(),
|
||||
PageSize: page.GetPageSize(),
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user