Scope knowledge and image libraries by brand
This commit is contained in:
@@ -61,6 +61,7 @@ type knowledgeParseJob struct {
|
||||
type knowledgeItemRecord struct {
|
||||
ID int64
|
||||
TenantID int64
|
||||
BrandID int64
|
||||
GroupID int64
|
||||
GroupName string
|
||||
Name string
|
||||
@@ -207,12 +208,16 @@ func (s *KnowledgeService) runtimeConfig() knowledgeRuntimeConfig {
|
||||
|
||||
func (s *KnowledgeService) ListGroups(ctx context.Context) ([]KnowledgeGroupResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT id, name, parent_id, sort_order, created_at
|
||||
FROM knowledge_groups
|
||||
WHERE tenant_id = $1 AND deleted_at IS NULL
|
||||
WHERE tenant_id = $1 AND brand_id = $2 AND deleted_at IS NULL
|
||||
ORDER BY sort_order, created_at, id
|
||||
`, actor.TenantID)
|
||||
`, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50050, "knowledge_groups_query_failed", "failed to list knowledge groups")
|
||||
}
|
||||
@@ -240,6 +245,7 @@ func (s *KnowledgeService) ListGroups(ctx context.Context) ([]KnowledgeGroupResp
|
||||
SELECT ki.group_id, COUNT(DISTINCT ki.id)::bigint
|
||||
FROM knowledge_items ki
|
||||
WHERE ki.tenant_id = $1
|
||||
AND ki.brand_id = $2
|
||||
AND ki.deleted_at IS NULL
|
||||
AND ki.status = 'completed'
|
||||
AND EXISTS (
|
||||
@@ -251,7 +257,7 @@ func (s *KnowledgeService) ListGroups(ctx context.Context) ([]KnowledgeGroupResp
|
||||
AND kcm.status = 'active'
|
||||
)
|
||||
GROUP BY ki.group_id
|
||||
`, actor.TenantID)
|
||||
`, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50050, "knowledge_group_counts_query_failed", "failed to list knowledge group counts")
|
||||
}
|
||||
@@ -310,6 +316,10 @@ func (s *KnowledgeService) ListGroups(ctx context.Context) ([]KnowledgeGroupResp
|
||||
|
||||
func (s *KnowledgeService) CreateGroup(ctx context.Context, req KnowledgeGroupRequest) (*KnowledgeGroupResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
@@ -318,7 +328,7 @@ func (s *KnowledgeService) CreateGroup(ctx context.Context, req KnowledgeGroupRe
|
||||
|
||||
var parentID *int64
|
||||
if req.ParentID != nil && *req.ParentID > 0 {
|
||||
if err := s.ensureRootGroup(ctx, actor.TenantID, *req.ParentID); err != nil {
|
||||
if err := s.ensureRootGroup(ctx, actor.TenantID, brandID, *req.ParentID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentID = req.ParentID
|
||||
@@ -333,11 +343,11 @@ func (s *KnowledgeService) CreateGroup(ctx context.Context, req KnowledgeGroupRe
|
||||
groupID int64
|
||||
createdAt time.Time
|
||||
)
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
INSERT INTO knowledge_groups (tenant_id, name, parent_id, sort_order)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
err = s.pool.QueryRow(ctx, `
|
||||
INSERT INTO knowledge_groups (tenant_id, brand_id, name, parent_id, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, created_at
|
||||
`, actor.TenantID, name, parentID, sortOrder).Scan(&groupID, &createdAt)
|
||||
`, actor.TenantID, brandID, name, parentID, sortOrder).Scan(&groupID, &createdAt)
|
||||
if err != nil {
|
||||
return nil, response.ErrConflict(40950, "knowledge_group_create_failed", "knowledge group already exists or cannot be created")
|
||||
}
|
||||
@@ -355,6 +365,10 @@ func (s *KnowledgeService) CreateGroup(ctx context.Context, req KnowledgeGroupRe
|
||||
|
||||
func (s *KnowledgeService) UpdateGroup(ctx context.Context, groupID int64, req KnowledgeGroupRequest) error {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
@@ -369,8 +383,8 @@ func (s *KnowledgeService) UpdateGroup(ctx context.Context, groupID int64, req K
|
||||
tag, err := s.pool.Exec(ctx, `
|
||||
UPDATE knowledge_groups
|
||||
SET name = $1, sort_order = $2, updated_at = NOW()
|
||||
WHERE id = $3 AND tenant_id = $4 AND deleted_at IS NULL
|
||||
`, name, sortOrder, groupID, actor.TenantID)
|
||||
WHERE id = $3 AND tenant_id = $4 AND brand_id = $5 AND deleted_at IS NULL
|
||||
`, name, sortOrder, groupID, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
return response.ErrConflict(40950, "knowledge_group_update_failed", "knowledge group already exists or cannot be updated")
|
||||
}
|
||||
@@ -382,6 +396,10 @@ func (s *KnowledgeService) UpdateGroup(ctx context.Context, groupID int64, req K
|
||||
|
||||
func (s *KnowledgeService) DeleteGroup(ctx context.Context, groupID int64) error {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
@@ -389,7 +407,7 @@ func (s *KnowledgeService) DeleteGroup(ctx context.Context, groupID int64) error
|
||||
}
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
|
||||
groupIDs, err := s.listGroupSubtreeIDs(ctx, tx, actor.TenantID, groupID)
|
||||
groupIDs, err := s.listGroupSubtreeIDs(ctx, tx, actor.TenantID, brandID, groupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -397,7 +415,7 @@ func (s *KnowledgeService) DeleteGroup(ctx context.Context, groupID int64) error
|
||||
return response.ErrNotFound(40451, "knowledge_group_not_found", "knowledge group not found")
|
||||
}
|
||||
|
||||
itemIDs, err := s.listActiveKnowledgeItemIDs(ctx, tx, actor.TenantID, groupIDs)
|
||||
itemIDs, err := s.listActiveKnowledgeItemIDs(ctx, tx, actor.TenantID, brandID, groupIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -407,9 +425,10 @@ func (s *KnowledgeService) DeleteGroup(ctx context.Context, groupID int64) error
|
||||
UPDATE knowledge_items
|
||||
SET deleted_at = NOW(), updated_at = NOW(), status = 'deleted'
|
||||
WHERE tenant_id = $1
|
||||
AND brand_id = $2
|
||||
AND deleted_at IS NULL
|
||||
AND id = ANY($2::bigint[])
|
||||
`, actor.TenantID, itemIDs); err != nil {
|
||||
AND id = ANY($3::bigint[])
|
||||
`, actor.TenantID, brandID, itemIDs); err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to delete knowledge items")
|
||||
}
|
||||
|
||||
@@ -448,9 +467,10 @@ func (s *KnowledgeService) DeleteGroup(ctx context.Context, groupID int64) error
|
||||
UPDATE knowledge_groups
|
||||
SET deleted_at = NOW(), updated_at = NOW()
|
||||
WHERE tenant_id = $1
|
||||
AND brand_id = $2
|
||||
AND deleted_at IS NULL
|
||||
AND id = ANY($2::bigint[])
|
||||
`, actor.TenantID, groupIDs); err != nil {
|
||||
AND id = ANY($3::bigint[])
|
||||
`, actor.TenantID, brandID, groupIDs); err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to delete knowledge groups")
|
||||
}
|
||||
|
||||
@@ -475,8 +495,12 @@ func (s *KnowledgeService) DeleteGroup(ctx context.Context, groupID int64) error
|
||||
|
||||
func (s *KnowledgeService) ListItems(ctx context.Context, groupID *int64) ([]KnowledgeItemResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if groupID != nil && *groupID > 0 {
|
||||
if err := s.ensureGroupAccessible(ctx, actor.TenantID, *groupID); err != nil {
|
||||
if err := s.ensureGroupAccessible(ctx, actor.TenantID, brandID, *groupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -485,23 +509,24 @@ func (s *KnowledgeService) ListItems(ctx context.Context, groupID *int64) ([]Kno
|
||||
SELECT ki.id, ki.group_id, kg.name, ki.name, ki.source_type, ki.source_uri, ki.status,
|
||||
ki.size_bytes, ki.error_message, ki.created_at, ki.updated_at
|
||||
FROM knowledge_items ki
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.deleted_at IS NULL
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.brand_id = ki.brand_id AND kg.deleted_at IS NULL
|
||||
WHERE ki.tenant_id = $1
|
||||
AND ki.brand_id = $2
|
||||
AND ki.deleted_at IS NULL
|
||||
`
|
||||
args := []any{actor.TenantID}
|
||||
args := []any{actor.TenantID, brandID}
|
||||
if groupID != nil && *groupID > 0 {
|
||||
query += `
|
||||
AND ki.group_id IN (
|
||||
WITH RECURSIVE selected_groups AS (
|
||||
SELECT id
|
||||
FROM knowledge_groups
|
||||
WHERE id = $2 AND tenant_id = $1 AND deleted_at IS NULL
|
||||
WHERE id = $3 AND tenant_id = $1 AND brand_id = $2 AND deleted_at IS NULL
|
||||
UNION ALL
|
||||
SELECT child.id
|
||||
FROM knowledge_groups child
|
||||
JOIN selected_groups parent ON child.parent_id = parent.id
|
||||
WHERE child.tenant_id = $1 AND child.deleted_at IS NULL
|
||||
WHERE child.tenant_id = $1 AND child.brand_id = $2 AND child.deleted_at IS NULL
|
||||
)
|
||||
SELECT id FROM selected_groups
|
||||
)
|
||||
@@ -551,6 +576,10 @@ func (s *KnowledgeService) ListItems(ctx context.Context, groupID *int64) ([]Kno
|
||||
|
||||
func (s *KnowledgeService) GetItemDetail(ctx context.Context, itemID int64) (*KnowledgeItemDetailResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
item KnowledgeItemDetailResponse
|
||||
@@ -560,13 +589,13 @@ func (s *KnowledgeService) GetItemDetail(ctx context.Context, itemID int64) (*Kn
|
||||
createdAt, updatedAt time.Time
|
||||
)
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
err = s.pool.QueryRow(ctx, `
|
||||
SELECT ki.id, ki.group_id, kg.name, ki.name, ki.source_type, ki.source_uri, ki.status,
|
||||
ki.size_bytes, ki.error_message, ki.markdown_content, ki.created_at, ki.updated_at
|
||||
FROM knowledge_items ki
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.deleted_at IS NULL
|
||||
WHERE ki.id = $1 AND ki.tenant_id = $2 AND ki.deleted_at IS NULL
|
||||
`, itemID, actor.TenantID).Scan(
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.brand_id = ki.brand_id AND kg.deleted_at IS NULL
|
||||
WHERE ki.id = $1 AND ki.tenant_id = $2 AND ki.brand_id = $3 AND ki.deleted_at IS NULL
|
||||
`, itemID, actor.TenantID, brandID).Scan(
|
||||
&item.ID,
|
||||
&item.GroupID,
|
||||
&item.GroupName,
|
||||
@@ -597,6 +626,10 @@ func (s *KnowledgeService) GetItemDetail(ctx context.Context, itemID int64) (*Kn
|
||||
|
||||
func (s *KnowledgeService) CreateTextItem(ctx context.Context, req KnowledgeTextItemRequest) (*KnowledgeItemResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.validateIngestionDependencies("text"); err != nil {
|
||||
return nil, err
|
||||
@@ -612,7 +645,7 @@ func (s *KnowledgeService) CreateTextItem(ctx context.Context, req KnowledgeText
|
||||
return nil, response.ErrBadRequest(40055, "knowledge_text_name_too_long", "knowledge text name must be at most 20 characters")
|
||||
}
|
||||
|
||||
return s.queueKnowledgeItem(ctx, actor.TenantID, knowledgeIngestInput{
|
||||
return s.queueKnowledgeItem(ctx, actor.TenantID, brandID, knowledgeIngestInput{
|
||||
GroupID: req.GroupID,
|
||||
Name: name,
|
||||
SourceType: "text",
|
||||
@@ -623,6 +656,10 @@ func (s *KnowledgeService) CreateTextItem(ctx context.Context, req KnowledgeText
|
||||
|
||||
func (s *KnowledgeService) CreateURLItem(ctx context.Context, req KnowledgeURLItemRequest) (*KnowledgeItemResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.validateIngestionDependencies("website"); err != nil {
|
||||
return nil, err
|
||||
@@ -633,7 +670,7 @@ func (s *KnowledgeService) CreateURLItem(ctx context.Context, req KnowledgeURLIt
|
||||
return nil, response.ErrBadRequest(40052, "knowledge_url_invalid", "knowledge url is invalid")
|
||||
}
|
||||
|
||||
return s.queueKnowledgeItem(ctx, actor.TenantID, knowledgeIngestInput{
|
||||
return s.queueKnowledgeItem(ctx, actor.TenantID, brandID, knowledgeIngestInput{
|
||||
GroupID: req.GroupID,
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
SourceType: "website",
|
||||
@@ -649,6 +686,10 @@ func (s *KnowledgeService) CreateFileItem(
|
||||
content []byte,
|
||||
) (*KnowledgeItemResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.validateIngestionDependencies("document"); err != nil {
|
||||
return nil, err
|
||||
@@ -664,7 +705,7 @@ func (s *KnowledgeService) CreateFileItem(
|
||||
return nil, response.ErrServiceUnavailable(50357, "knowledge_storage_unavailable", err.Error())
|
||||
}
|
||||
|
||||
return s.queueKnowledgeItem(ctx, actor.TenantID, knowledgeIngestInput{
|
||||
return s.queueKnowledgeItem(ctx, actor.TenantID, brandID, knowledgeIngestInput{
|
||||
GroupID: groupID,
|
||||
Name: displayName,
|
||||
SourceType: "document",
|
||||
@@ -675,6 +716,10 @@ func (s *KnowledgeService) CreateFileItem(
|
||||
|
||||
func (s *KnowledgeService) DeleteItem(ctx context.Context, itemID int64) error {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
@@ -685,8 +730,8 @@ func (s *KnowledgeService) DeleteItem(ctx context.Context, itemID int64) error {
|
||||
tag, err := tx.Exec(ctx, `
|
||||
UPDATE knowledge_items
|
||||
SET deleted_at = NOW(), updated_at = NOW(), status = 'deleted'
|
||||
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
|
||||
`, itemID, actor.TenantID)
|
||||
WHERE id = $1 AND tenant_id = $2 AND brand_id = $3 AND deleted_at IS NULL
|
||||
`, itemID, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_item_delete_failed", "failed to delete knowledge item")
|
||||
}
|
||||
@@ -734,14 +779,18 @@ func (s *KnowledgeService) DeleteItem(ctx context.Context, itemID int64) error {
|
||||
|
||||
func (s *KnowledgeService) RetryItem(ctx context.Context, itemID int64) (*KnowledgeItemResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
brandID, err := requireCurrentBrandID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sourceType string
|
||||
var status string
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
err = s.pool.QueryRow(ctx, `
|
||||
SELECT source_type, status
|
||||
FROM knowledge_items
|
||||
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
|
||||
`, itemID, actor.TenantID).Scan(&sourceType, &status)
|
||||
WHERE id = $1 AND tenant_id = $2 AND brand_id = $3 AND deleted_at IS NULL
|
||||
`, itemID, actor.TenantID, brandID).Scan(&sourceType, &status)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, response.ErrNotFound(40450, "knowledge_item_not_found", "knowledge item not found")
|
||||
@@ -793,9 +842,10 @@ func (s *KnowledgeService) RetryItem(ctx context.Context, itemID int64) (*Knowle
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
AND tenant_id = $2
|
||||
AND brand_id = $3
|
||||
AND deleted_at IS NULL
|
||||
AND status = 'failed'
|
||||
`, itemID, actor.TenantID)
|
||||
`, itemID, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50055, "knowledge_item_retry_failed", "failed to reset knowledge item")
|
||||
}
|
||||
@@ -816,7 +866,7 @@ func (s *KnowledgeService) RetryItem(ctx context.Context, itemID int64) (*Knowle
|
||||
return nil, response.ErrServiceUnavailable(50356, "knowledge_queue_unavailable", "knowledge parse queue is busy, please retry")
|
||||
}
|
||||
|
||||
items, err := s.listKnowledgeItemsByIDs(ctx, actor.TenantID, []int64{itemID})
|
||||
items, err := s.listKnowledgeItemsByIDs(ctx, actor.TenantID, brandID, []int64{itemID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -829,6 +879,7 @@ func (s *KnowledgeService) RetryItem(ctx context.Context, itemID int64) (*Knowle
|
||||
func (s *KnowledgeService) ResolveContext(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
groupIDs []int64,
|
||||
query string,
|
||||
) (*KnowledgeContext, error) {
|
||||
@@ -871,7 +922,7 @@ func (s *KnowledgeService) ResolveContext(
|
||||
return nil, response.ErrServiceUnavailable(50352, "qdrant_unavailable", err.Error())
|
||||
}
|
||||
|
||||
searchGroupIDs, groupNames, err := s.resolveKnowledgeSearchScope(ctx, tenantID, requestedGroupIDs)
|
||||
searchGroupIDs, groupNames, err := s.resolveKnowledgeSearchScope(ctx, tenantID, brandID, requestedGroupIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -883,7 +934,7 @@ func (s *KnowledgeService) ResolveContext(
|
||||
GroupIDs: searchGroupIDs,
|
||||
GroupNames: groupNames,
|
||||
Snippets: []KnowledgeSnippet{},
|
||||
PreciseFacts: s.collectKnowledgeContextPreciseFacts(ctx, tenantID, searchGroupIDs, nil),
|
||||
PreciseFacts: s.collectKnowledgeContextPreciseFacts(ctx, tenantID, brandID, searchGroupIDs, nil),
|
||||
}
|
||||
|
||||
vectors, err := s.provider.Embed(ctx, queries)
|
||||
@@ -903,6 +954,7 @@ func (s *KnowledgeService) ResolveContext(
|
||||
points, err := s.store.Search(ctx, retrieval.SearchRequest{
|
||||
Vector: vector,
|
||||
TenantID: tenantID,
|
||||
BrandID: brandID,
|
||||
GroupIDs: searchGroupIDs,
|
||||
Limit: runtimeCfg.RecallLimit,
|
||||
})
|
||||
@@ -922,7 +974,7 @@ func (s *KnowledgeService) ResolveContext(
|
||||
return baseContext, nil
|
||||
}
|
||||
|
||||
candidates, err = s.filterActiveKnowledgeSnippets(ctx, tenantID, candidates)
|
||||
candidates, err = s.filterActiveKnowledgeSnippets(ctx, tenantID, brandID, candidates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -939,17 +991,17 @@ func (s *KnowledgeService) ResolveContext(
|
||||
reranked, err := s.provider.Rerank(ctx, rerankQuery, documents, minInt(runtimeCfg.RerankTopN, len(documents)))
|
||||
if err != nil {
|
||||
selected := selectKnowledgeCandidateSnippets(candidates, runtimeCfg.RerankTopN)
|
||||
selected = s.enrichKnowledgePromptSnippets(ctx, tenantID, selected)
|
||||
selected = s.enrichKnowledgePromptSnippets(ctx, tenantID, brandID, selected)
|
||||
baseContext.Snippets = selected
|
||||
baseContext.PreciseFacts = s.collectKnowledgeContextPreciseFacts(ctx, tenantID, searchGroupIDs, selected)
|
||||
baseContext.PreciseFacts = s.collectKnowledgeContextPreciseFacts(ctx, tenantID, brandID, searchGroupIDs, selected)
|
||||
s.logKnowledgeResolve(baseContext, tenantID, rerankQuery, "rerank_failed_fallback", queryRewriteStage, queryRewriteModel, candidates, selected)
|
||||
return baseContext, nil
|
||||
}
|
||||
|
||||
selected := selectKnowledgeRerankedSnippets(candidates, reranked, runtimeCfg.RerankTopN)
|
||||
selected = s.enrichKnowledgePromptSnippets(ctx, tenantID, selected)
|
||||
selected = s.enrichKnowledgePromptSnippets(ctx, tenantID, brandID, selected)
|
||||
baseContext.Snippets = selected
|
||||
baseContext.PreciseFacts = s.collectKnowledgeContextPreciseFacts(ctx, tenantID, searchGroupIDs, selected)
|
||||
baseContext.PreciseFacts = s.collectKnowledgeContextPreciseFacts(ctx, tenantID, brandID, searchGroupIDs, selected)
|
||||
s.logKnowledgeResolve(baseContext, tenantID, rerankQuery, "resolved", queryRewriteStage, queryRewriteModel, candidates, selected)
|
||||
return baseContext, nil
|
||||
}
|
||||
@@ -1341,6 +1393,7 @@ type knowledgePromptItemRecord struct {
|
||||
func (s *KnowledgeService) enrichKnowledgePromptSnippets(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
snippets []KnowledgeSnippet,
|
||||
) []KnowledgeSnippet {
|
||||
if len(snippets) == 0 {
|
||||
@@ -1358,7 +1411,7 @@ func (s *KnowledgeService) enrichKnowledgePromptSnippets(
|
||||
return snippets
|
||||
}
|
||||
|
||||
items, err := s.loadKnowledgePromptItems(ctx, tenantID, itemIDs)
|
||||
items, err := s.loadKnowledgePromptItems(ctx, tenantID, brandID, itemIDs)
|
||||
if err != nil {
|
||||
if s.logger != nil {
|
||||
s.logger.Warn("load knowledge prompt items failed", zap.Error(err))
|
||||
@@ -1395,6 +1448,7 @@ func (s *KnowledgeService) enrichKnowledgePromptSnippets(
|
||||
func (s *KnowledgeService) loadKnowledgePromptItems(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
itemIDs []int64,
|
||||
) ([]knowledgePromptItemRecord, error) {
|
||||
if len(itemIDs) == 0 {
|
||||
@@ -1404,12 +1458,13 @@ func (s *KnowledgeService) loadKnowledgePromptItems(
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT ki.id, ki.group_id, kg.name, ki.name, ki.source_type, ki.source_uri, ki.content_text, ki.markdown_content
|
||||
FROM knowledge_items ki
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.deleted_at IS NULL
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.brand_id = ki.brand_id AND kg.deleted_at IS NULL
|
||||
WHERE ki.tenant_id = $1
|
||||
AND ki.id = ANY($2::bigint[])
|
||||
AND ki.brand_id = $2
|
||||
AND ki.id = ANY($3::bigint[])
|
||||
AND ki.status = 'completed'
|
||||
AND ki.deleted_at IS NULL
|
||||
`, tenantID, itemIDs)
|
||||
`, tenantID, brandID, itemIDs)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50051, "knowledge_items_query_failed", "failed to load knowledge prompt items")
|
||||
}
|
||||
@@ -1438,6 +1493,7 @@ func (s *KnowledgeService) loadKnowledgePromptItems(
|
||||
func (s *KnowledgeService) collectKnowledgeContextPreciseFacts(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
groupIDs []int64,
|
||||
snippets []KnowledgeSnippet,
|
||||
) []string {
|
||||
@@ -1454,7 +1510,7 @@ func (s *KnowledgeService) collectKnowledgeContextPreciseFacts(
|
||||
}
|
||||
matchedItemIDs = normalizeInt64IDs(matchedItemIDs)
|
||||
|
||||
items, err := s.loadKnowledgePromptItemsByGroupIDs(ctx, tenantID, groupIDs, matchedItemIDs, 24)
|
||||
items, err := s.loadKnowledgePromptItemsByGroupIDs(ctx, tenantID, brandID, groupIDs, matchedItemIDs, 24)
|
||||
if err != nil {
|
||||
if s.logger != nil {
|
||||
s.logger.Warn("collect knowledge context precise facts failed", zap.Error(err))
|
||||
@@ -1506,6 +1562,7 @@ func (s *KnowledgeService) collectKnowledgeContextPreciseFacts(
|
||||
func (s *KnowledgeService) loadKnowledgePromptItemsByGroupIDs(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
groupIDs []int64,
|
||||
prioritizedItemIDs []int64,
|
||||
limit int,
|
||||
@@ -1524,31 +1581,34 @@ func (s *KnowledgeService) loadKnowledgePromptItemsByGroupIDs(
|
||||
SELECT id
|
||||
FROM knowledge_groups
|
||||
WHERE tenant_id = $1
|
||||
AND id = ANY($2::bigint[])
|
||||
AND brand_id = $2
|
||||
AND id = ANY($3::bigint[])
|
||||
AND deleted_at IS NULL
|
||||
UNION ALL
|
||||
SELECT child.id
|
||||
FROM knowledge_groups child
|
||||
JOIN selected_groups parent ON child.parent_id = parent.id
|
||||
WHERE child.tenant_id = $1
|
||||
AND child.brand_id = $2
|
||||
AND child.deleted_at IS NULL
|
||||
)
|
||||
SELECT ki.id, ki.group_id, kg.name, ki.name, ki.source_type, ki.source_uri, ki.content_text, ki.markdown_content
|
||||
FROM knowledge_items ki
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.deleted_at IS NULL
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.brand_id = ki.brand_id AND kg.deleted_at IS NULL
|
||||
WHERE ki.tenant_id = $1
|
||||
AND ki.brand_id = $2
|
||||
AND ki.status = 'completed'
|
||||
AND ki.deleted_at IS NULL
|
||||
AND ki.group_id IN (SELECT id FROM selected_groups)
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN ki.id = ANY($3::bigint[]) THEN 0
|
||||
WHEN ki.id = ANY($4::bigint[]) THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
ki.updated_at DESC,
|
||||
ki.id DESC
|
||||
LIMIT $4
|
||||
`, tenantID, groupIDs, prioritizedItemIDs, limit)
|
||||
LIMIT $5
|
||||
`, tenantID, brandID, groupIDs, prioritizedItemIDs, limit)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50051, "knowledge_items_query_failed", "failed to load knowledge prompt items by groups")
|
||||
}
|
||||
@@ -1859,9 +1919,10 @@ func (s *KnowledgeService) validateIngestionDependencies(sourceType string) erro
|
||||
func (s *KnowledgeService) queueKnowledgeItem(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
input knowledgeIngestInput,
|
||||
) (*KnowledgeItemResponse, error) {
|
||||
resolvedGroupID, err := s.resolveKnowledgeStorageGroupID(ctx, tenantID, input.GroupID)
|
||||
resolvedGroupID, err := s.resolveKnowledgeStorageGroupID(ctx, tenantID, brandID, input.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1880,7 +1941,7 @@ func (s *KnowledgeService) queueKnowledgeItem(
|
||||
input.ContentText = &contentText
|
||||
}
|
||||
|
||||
itemID, parseTaskID, err := s.createPendingKnowledgeItem(ctx, tenantID, input)
|
||||
itemID, parseTaskID, err := s.createPendingKnowledgeItem(ctx, tenantID, brandID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1894,7 +1955,7 @@ func (s *KnowledgeService) queueKnowledgeItem(
|
||||
return nil, response.ErrServiceUnavailable(50356, "knowledge_queue_unavailable", "knowledge parse queue is busy, please retry")
|
||||
}
|
||||
|
||||
items, err := s.listKnowledgeItemsByIDs(ctx, tenantID, []int64{itemID})
|
||||
items, err := s.listKnowledgeItemsByIDs(ctx, tenantID, brandID, []int64{itemID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2064,6 +2125,7 @@ func (s *KnowledgeService) executeParseJob(ctx context.Context, job knowledgePar
|
||||
Vector: embeddings[index],
|
||||
Payload: map[string]any{
|
||||
"tenant_id": job.TenantID,
|
||||
"brand_id": item.BrandID,
|
||||
"group_id": item.GroupID,
|
||||
"group_name": item.GroupName,
|
||||
"item_id": item.ID,
|
||||
@@ -2105,14 +2167,15 @@ func (s *KnowledgeService) loadKnowledgeItemForParse(
|
||||
var contentText *string
|
||||
var markdownContent *string
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT ki.id, ki.tenant_id, ki.group_id, kg.name, ki.name, ki.source_type,
|
||||
SELECT ki.id, ki.tenant_id, ki.brand_id, ki.group_id, kg.name, ki.name, ki.source_type,
|
||||
ki.source_uri, ki.storage_key, ki.content_text, ki.markdown_content, ki.item_version
|
||||
FROM knowledge_items ki
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.brand_id = ki.brand_id
|
||||
WHERE ki.id = $1 AND ki.tenant_id = $2 AND ki.deleted_at IS NULL
|
||||
`, itemID, tenantID).Scan(
|
||||
&record.ID,
|
||||
&record.TenantID,
|
||||
&record.BrandID,
|
||||
&record.GroupID,
|
||||
&record.GroupName,
|
||||
&record.Name,
|
||||
@@ -2166,6 +2229,7 @@ type knowledgeChunkMetaInput struct {
|
||||
func (s *KnowledgeService) createPendingKnowledgeItem(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
input knowledgeIngestInput,
|
||||
) (int64, int64, error) {
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
@@ -2177,12 +2241,12 @@ func (s *KnowledgeService) createPendingKnowledgeItem(
|
||||
var itemID int64
|
||||
err = tx.QueryRow(ctx, `
|
||||
INSERT INTO knowledge_items (
|
||||
tenant_id, group_id, source_type, name, source_uri, storage_key,
|
||||
tenant_id, brand_id, group_id, source_type, name, source_uri, storage_key,
|
||||
content_text, markdown_content, status, size_bytes, item_version
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, NULL, 'pending', $8, 1)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NULL, 'pending', $9, 1)
|
||||
RETURNING id
|
||||
`, tenantID, input.GroupID, input.SourceType, input.Name, input.SourceURI, input.StorageKey, input.ContentText, input.SizeBytes).Scan(&itemID)
|
||||
`, tenantID, brandID, input.GroupID, input.SourceType, input.Name, input.SourceURI, input.StorageKey, input.ContentText, input.SizeBytes).Scan(&itemID)
|
||||
if err != nil {
|
||||
return 0, 0, response.ErrConflict(40951, "knowledge_item_create_failed", "failed to create knowledge item")
|
||||
}
|
||||
@@ -2434,15 +2498,15 @@ func buildKnowledgeSearchText(input knowledgeSearchTextInput) string {
|
||||
return strings.Join(metadata, "\n")
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) ensureGroupAccessible(ctx context.Context, tenantID, groupID int64) error {
|
||||
func (s *KnowledgeService) ensureGroupAccessible(ctx context.Context, tenantID, brandID, groupID int64) error {
|
||||
var exists bool
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM knowledge_groups
|
||||
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
|
||||
WHERE id = $1 AND tenant_id = $2 AND brand_id = $3 AND deleted_at IS NULL
|
||||
)
|
||||
`, groupID, tenantID).Scan(&exists)
|
||||
`, groupID, tenantID, brandID).Scan(&exists)
|
||||
if err != nil {
|
||||
return response.ErrInternal(50050, "knowledge_group_query_failed", "failed to query knowledge group")
|
||||
}
|
||||
@@ -2452,8 +2516,8 @@ func (s *KnowledgeService) ensureGroupAccessible(ctx context.Context, tenantID,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) ensureRootGroup(ctx context.Context, tenantID, groupID int64) error {
|
||||
if err := s.ensureGroupAccessible(ctx, tenantID, groupID); err != nil {
|
||||
func (s *KnowledgeService) ensureRootGroup(ctx context.Context, tenantID, brandID, groupID int64) error {
|
||||
if err := s.ensureGroupAccessible(ctx, tenantID, brandID, groupID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2464,10 +2528,11 @@ func (s *KnowledgeService) ensureRootGroup(ctx context.Context, tenantID, groupI
|
||||
FROM knowledge_groups
|
||||
WHERE id = $1
|
||||
AND tenant_id = $2
|
||||
AND brand_id = $3
|
||||
AND deleted_at IS NULL
|
||||
AND parent_id IS NULL
|
||||
)
|
||||
`, groupID, tenantID).Scan(&isRoot)
|
||||
`, groupID, tenantID, brandID).Scan(&isRoot)
|
||||
if err != nil {
|
||||
return response.ErrInternal(50050, "knowledge_group_query_failed", "failed to query knowledge group")
|
||||
}
|
||||
@@ -2477,8 +2542,8 @@ func (s *KnowledgeService) ensureRootGroup(ctx context.Context, tenantID, groupI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) ensureStorableGroup(ctx context.Context, tenantID, groupID int64) error {
|
||||
if err := s.ensureGroupAccessible(ctx, tenantID, groupID); err != nil {
|
||||
func (s *KnowledgeService) ensureStorableGroup(ctx context.Context, tenantID, brandID, groupID int64) error {
|
||||
if err := s.ensureGroupAccessible(ctx, tenantID, brandID, groupID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2490,13 +2555,15 @@ func (s *KnowledgeService) ensureStorableGroup(ctx context.Context, tenantID, gr
|
||||
JOIN knowledge_groups parent
|
||||
ON parent.id = child.parent_id
|
||||
AND parent.tenant_id = child.tenant_id
|
||||
AND parent.brand_id = child.brand_id
|
||||
AND parent.deleted_at IS NULL
|
||||
WHERE child.id = $1
|
||||
AND child.tenant_id = $2
|
||||
AND child.brand_id = $3
|
||||
AND child.deleted_at IS NULL
|
||||
AND parent.parent_id IS NULL
|
||||
)
|
||||
`, groupID, tenantID).Scan(&storable)
|
||||
`, groupID, tenantID, brandID).Scan(&storable)
|
||||
if err != nil {
|
||||
return response.ErrInternal(50050, "knowledge_group_query_failed", "failed to query knowledge group")
|
||||
}
|
||||
@@ -2506,14 +2573,14 @@ func (s *KnowledgeService) ensureStorableGroup(ctx context.Context, tenantID, gr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) resolveKnowledgeStorageGroupID(ctx context.Context, tenantID, groupID int64) (int64, error) {
|
||||
func (s *KnowledgeService) resolveKnowledgeStorageGroupID(ctx context.Context, tenantID, brandID, groupID int64) (int64, error) {
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return 0, response.ErrInternal(50055, "knowledge_group_resolve_failed", "failed to begin knowledge group transaction")
|
||||
}
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
|
||||
resolvedGroupID, err := s.resolveKnowledgeStorageGroupIDTx(ctx, tx, tenantID, groupID)
|
||||
resolvedGroupID, err := s.resolveKnowledgeStorageGroupIDTx(ctx, tx, tenantID, brandID, groupID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -2527,15 +2594,15 @@ func (s *KnowledgeService) resolveKnowledgeStorageGroupID(ctx context.Context, t
|
||||
func (s *KnowledgeService) resolveKnowledgeStorageGroupIDTx(
|
||||
ctx context.Context,
|
||||
tx pgx.Tx,
|
||||
tenantID, groupID int64,
|
||||
tenantID, brandID, groupID int64,
|
||||
) (int64, error) {
|
||||
var parentID *int64
|
||||
if err := tx.QueryRow(ctx, `
|
||||
SELECT parent_id
|
||||
FROM knowledge_groups
|
||||
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
|
||||
WHERE id = $1 AND tenant_id = $2 AND brand_id = $3 AND deleted_at IS NULL
|
||||
FOR UPDATE
|
||||
`, groupID, tenantID).Scan(&parentID); err != nil {
|
||||
`, groupID, tenantID, brandID).Scan(&parentID); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, response.ErrNotFound(40451, "knowledge_group_not_found", "knowledge group not found")
|
||||
}
|
||||
@@ -2547,8 +2614,8 @@ func (s *KnowledgeService) resolveKnowledgeStorageGroupIDTx(
|
||||
if err := tx.QueryRow(ctx, `
|
||||
SELECT parent_id
|
||||
FROM knowledge_groups
|
||||
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
|
||||
`, *parentID, tenantID).Scan(&grandParentID); err != nil {
|
||||
WHERE id = $1 AND tenant_id = $2 AND brand_id = $3 AND deleted_at IS NULL
|
||||
`, *parentID, tenantID, brandID).Scan(&grandParentID); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, response.ErrNotFound(40451, "knowledge_group_not_found", "knowledge group not found")
|
||||
}
|
||||
@@ -2565,22 +2632,23 @@ func (s *KnowledgeService) resolveKnowledgeStorageGroupIDTx(
|
||||
SELECT id
|
||||
FROM knowledge_groups
|
||||
WHERE tenant_id = $1
|
||||
AND parent_id = $2
|
||||
AND name = $3
|
||||
AND brand_id = $2
|
||||
AND parent_id = $3
|
||||
AND name = $4
|
||||
AND deleted_at IS NULL
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
`, tenantID, groupID, defaultKnowledgeChildGroupName).Scan(&childGroupID); err == nil {
|
||||
`, tenantID, brandID, groupID, defaultKnowledgeChildGroupName).Scan(&childGroupID); err == nil {
|
||||
return childGroupID, nil
|
||||
} else if !errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, response.ErrInternal(50050, "knowledge_group_query_failed", "failed to query knowledge group")
|
||||
}
|
||||
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO knowledge_groups (tenant_id, name, parent_id, sort_order)
|
||||
VALUES ($1, $2, $3, 0)
|
||||
INSERT INTO knowledge_groups (tenant_id, brand_id, name, parent_id, sort_order)
|
||||
VALUES ($1, $2, $3, $4, 0)
|
||||
RETURNING id
|
||||
`, tenantID, defaultKnowledgeChildGroupName, groupID).Scan(&childGroupID); err == nil {
|
||||
`, tenantID, brandID, defaultKnowledgeChildGroupName, groupID).Scan(&childGroupID); err == nil {
|
||||
return childGroupID, nil
|
||||
}
|
||||
|
||||
@@ -2588,12 +2656,13 @@ func (s *KnowledgeService) resolveKnowledgeStorageGroupIDTx(
|
||||
SELECT id
|
||||
FROM knowledge_groups
|
||||
WHERE tenant_id = $1
|
||||
AND parent_id = $2
|
||||
AND name = $3
|
||||
AND brand_id = $2
|
||||
AND parent_id = $3
|
||||
AND name = $4
|
||||
AND deleted_at IS NULL
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
`, tenantID, groupID, defaultKnowledgeChildGroupName).Scan(&childGroupID); err != nil {
|
||||
`, tenantID, brandID, groupID, defaultKnowledgeChildGroupName).Scan(&childGroupID); err != nil {
|
||||
return 0, response.ErrConflict(40950, "knowledge_group_create_failed", "failed to create default knowledge group")
|
||||
}
|
||||
return childGroupID, nil
|
||||
@@ -2602,22 +2671,22 @@ func (s *KnowledgeService) resolveKnowledgeStorageGroupIDTx(
|
||||
func (s *KnowledgeService) listGroupSubtreeIDs(
|
||||
ctx context.Context,
|
||||
tx pgx.Tx,
|
||||
tenantID, groupID int64,
|
||||
tenantID, brandID, groupID int64,
|
||||
) ([]int64, error) {
|
||||
rows, err := tx.Query(ctx, `
|
||||
WITH RECURSIVE selected_groups AS (
|
||||
SELECT id
|
||||
FROM knowledge_groups
|
||||
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
|
||||
WHERE id = $1 AND tenant_id = $2 AND brand_id = $3 AND deleted_at IS NULL
|
||||
UNION ALL
|
||||
SELECT child.id
|
||||
FROM knowledge_groups child
|
||||
JOIN selected_groups parent ON child.parent_id = parent.id
|
||||
WHERE child.tenant_id = $2 AND child.deleted_at IS NULL
|
||||
WHERE child.tenant_id = $2 AND child.brand_id = $3 AND child.deleted_at IS NULL
|
||||
)
|
||||
SELECT id
|
||||
FROM selected_groups
|
||||
`, groupID, tenantID)
|
||||
`, groupID, tenantID, brandID)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to query knowledge groups")
|
||||
}
|
||||
@@ -2638,6 +2707,7 @@ func (s *KnowledgeService) listActiveKnowledgeItemIDs(
|
||||
ctx context.Context,
|
||||
tx pgx.Tx,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
groupIDs []int64,
|
||||
) ([]int64, error) {
|
||||
if len(groupIDs) == 0 {
|
||||
@@ -2648,9 +2718,10 @@ func (s *KnowledgeService) listActiveKnowledgeItemIDs(
|
||||
SELECT id
|
||||
FROM knowledge_items
|
||||
WHERE tenant_id = $1
|
||||
AND brand_id = $2
|
||||
AND deleted_at IS NULL
|
||||
AND group_id = ANY($2::bigint[])
|
||||
`, tenantID, groupIDs)
|
||||
AND group_id = ANY($3::bigint[])
|
||||
`, tenantID, brandID, groupIDs)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to query knowledge items")
|
||||
}
|
||||
@@ -2714,6 +2785,7 @@ func (s *KnowledgeService) listKnowledgePointIDs(ctx context.Context, tenantID,
|
||||
func (s *KnowledgeService) filterActiveKnowledgeSnippets(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
brandID int64,
|
||||
candidates []KnowledgeSnippet,
|
||||
) ([]KnowledgeSnippet, error) {
|
||||
if len(candidates) == 0 {
|
||||
@@ -2735,10 +2807,11 @@ func (s *KnowledgeService) filterActiveKnowledgeSnippets(
|
||||
SELECT id
|
||||
FROM knowledge_items
|
||||
WHERE tenant_id = $1
|
||||
AND id = ANY($2::bigint[])
|
||||
AND brand_id = $2
|
||||
AND id = ANY($3::bigint[])
|
||||
AND status = 'completed'
|
||||
AND deleted_at IS NULL
|
||||
`, tenantID, itemIDs)
|
||||
`, tenantID, brandID, itemIDs)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50051, "knowledge_items_query_failed", "failed to filter deleted knowledge items")
|
||||
}
|
||||
@@ -2852,14 +2925,15 @@ func (s *KnowledgeService) loadKnowledgeItemForCleanup(
|
||||
var contentText *string
|
||||
var markdownContent *string
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT ki.id, ki.tenant_id, ki.group_id, kg.name, ki.name, ki.source_type,
|
||||
SELECT ki.id, ki.tenant_id, ki.brand_id, ki.group_id, kg.name, ki.name, ki.source_type,
|
||||
ki.source_uri, ki.storage_key, ki.content_text, ki.markdown_content, ki.item_version
|
||||
FROM knowledge_items ki
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.brand_id = ki.brand_id
|
||||
WHERE ki.id = $1 AND ki.tenant_id = $2 AND ki.deleted_at IS NOT NULL
|
||||
`, itemID, tenantID).Scan(
|
||||
&record.ID,
|
||||
&record.TenantID,
|
||||
&record.BrandID,
|
||||
&record.GroupID,
|
||||
&record.GroupName,
|
||||
&record.Name,
|
||||
@@ -2925,7 +2999,7 @@ func (s *KnowledgeService) clearKnowledgeCleanupError(ctx context.Context, tenan
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) listKnowledgeItemsByIDs(ctx context.Context, tenantID int64, itemIDs []int64) ([]KnowledgeItemResponse, error) {
|
||||
func (s *KnowledgeService) listKnowledgeItemsByIDs(ctx context.Context, tenantID, brandID int64, itemIDs []int64) ([]KnowledgeItemResponse, error) {
|
||||
if len(itemIDs) == 0 {
|
||||
return []KnowledgeItemResponse{}, nil
|
||||
}
|
||||
@@ -2933,10 +3007,10 @@ func (s *KnowledgeService) listKnowledgeItemsByIDs(ctx context.Context, tenantID
|
||||
SELECT ki.id, ki.group_id, kg.name, ki.name, ki.source_type, ki.source_uri, ki.status,
|
||||
ki.size_bytes, ki.error_message, ki.created_at, ki.updated_at
|
||||
FROM knowledge_items ki
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id
|
||||
WHERE ki.tenant_id = $1 AND ki.id = ANY($2::bigint[]) AND ki.deleted_at IS NULL
|
||||
JOIN knowledge_groups kg ON kg.id = ki.group_id AND kg.brand_id = ki.brand_id
|
||||
WHERE ki.tenant_id = $1 AND ki.brand_id = $2 AND ki.id = ANY($3::bigint[]) AND ki.deleted_at IS NULL
|
||||
ORDER BY ki.created_at DESC, ki.id DESC
|
||||
`, tenantID, itemIDs)
|
||||
`, tenantID, brandID, itemIDs)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50051, "knowledge_items_query_failed", "failed to list knowledge items")
|
||||
}
|
||||
@@ -2974,7 +3048,7 @@ func (s *KnowledgeService) listKnowledgeItemsByIDs(ctx context.Context, tenantID
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) resolveKnowledgeSearchScope(ctx context.Context, tenantID int64, groupIDs []int64) ([]int64, []string, error) {
|
||||
func (s *KnowledgeService) resolveKnowledgeSearchScope(ctx context.Context, tenantID, brandID int64, groupIDs []int64) ([]int64, []string, error) {
|
||||
groupIDs = normalizeInt64IDs(groupIDs)
|
||||
if len(groupIDs) == 0 {
|
||||
return []int64{}, []string{}, nil
|
||||
@@ -2985,19 +3059,21 @@ func (s *KnowledgeService) resolveKnowledgeSearchScope(ctx context.Context, tena
|
||||
SELECT id, name, parent_id, sort_order, created_at
|
||||
FROM knowledge_groups
|
||||
WHERE tenant_id = $1
|
||||
AND id = ANY($2::bigint[])
|
||||
AND brand_id = $2
|
||||
AND id = ANY($3::bigint[])
|
||||
AND deleted_at IS NULL
|
||||
UNION
|
||||
SELECT child.id, child.name, child.parent_id, child.sort_order, child.created_at
|
||||
FROM knowledge_groups child
|
||||
JOIN selected_groups parent ON child.parent_id = parent.id
|
||||
WHERE child.tenant_id = $1
|
||||
AND child.brand_id = $2
|
||||
AND child.deleted_at IS NULL
|
||||
)
|
||||
SELECT id, name
|
||||
FROM selected_groups
|
||||
ORDER BY sort_order, created_at, id
|
||||
`, tenantID, groupIDs)
|
||||
`, tenantID, brandID, groupIDs)
|
||||
if err != nil {
|
||||
return nil, nil, response.ErrInternal(50050, "knowledge_group_query_failed", "failed to query knowledge groups")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user