Skip to content

Commit 58dfe1b

Browse files
committed
Address PR review feedback (#5771)
- Treat parsed-empty tag inputs as absent - Add regression coverage for query and document updates
1 parent 7942b36 commit 58dfe1b

2 files changed

Lines changed: 108 additions & 14 deletions

File tree

apps/sim/lib/copilot/tools/server/knowledge/knowledge-base.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,66 @@ describe('knowledge base Copilot operations', () => {
263263
)
264264
})
265265

266+
it.each([
267+
['an empty array', []],
268+
['null', null],
269+
['only blank entries', [{ tagName: ' ', tagValue: ' ' }]],
270+
])('applies other document updates when documentTags contains %s', async (_label, tags) => {
271+
mockCheckDocumentWriteAccess.mockResolvedValue({ hasAccess: true } as never)
272+
mockUpdateDocument.mockResolvedValue({} as never)
273+
274+
const result = await knowledgeBaseServerTool.execute(
275+
{
276+
operation: 'update_document',
277+
args: {
278+
knowledgeBaseId: 'knowledge-base-1',
279+
documentId: 'document-1',
280+
filename: 'renamed.txt',
281+
enabled: false,
282+
documentTags: tags,
283+
},
284+
},
285+
{ userId: 'user-1', workspaceId: 'workspace-paid' }
286+
)
287+
288+
expect(result).toMatchObject({
289+
success: true,
290+
data: {
291+
documentId: 'document-1',
292+
filename: 'renamed.txt',
293+
enabled: false,
294+
},
295+
})
296+
expect(mockGetDocumentTagDefinitions).not.toHaveBeenCalled()
297+
expect(mockUpdateDocument).toHaveBeenCalledWith(
298+
'document-1',
299+
{ filename: 'renamed.txt', enabled: false },
300+
expect.any(String)
301+
)
302+
})
303+
304+
it('rejects update_document when empty documentTags is the only supplied update', async () => {
305+
mockCheckDocumentWriteAccess.mockResolvedValue({ hasAccess: true } as never)
306+
307+
const result = await knowledgeBaseServerTool.execute(
308+
{
309+
operation: 'update_document',
310+
args: {
311+
knowledgeBaseId: 'knowledge-base-1',
312+
documentId: 'document-1',
313+
documentTags: [],
314+
},
315+
},
316+
{ userId: 'user-1', workspaceId: 'workspace-paid' }
317+
)
318+
319+
expect(result).toEqual({
320+
success: false,
321+
message: 'At least one of filename, enabled, or documentTags is required for update_document',
322+
})
323+
expect(mockUpdateDocument).not.toHaveBeenCalled()
324+
})
325+
266326
it('applies tag filters to semantic queries', async () => {
267327
mockCheckKnowledgeBaseAccess.mockResolvedValue({ hasAccess: true } as never)
268328
mockGetKnowledgeBaseById.mockResolvedValue({
@@ -333,6 +393,54 @@ describe('knowledge base Copilot operations', () => {
333393
expect(mockHandleVectorOnlySearch).not.toHaveBeenCalled()
334394
})
335395

396+
it.each([
397+
['an empty array', []],
398+
['null', null],
399+
['only blank entries', [{ tagName: ' ', tagValue: ' ' }]],
400+
])('uses vector-only search when tagFilters contains %s', async (_label, filters) => {
401+
mockCheckKnowledgeBaseAccess.mockResolvedValue({ hasAccess: true } as never)
402+
mockGetKnowledgeBaseById.mockResolvedValue({
403+
id: 'knowledge-base-1',
404+
name: 'User Memory',
405+
workspaceId: 'workspace-paid',
406+
embeddingModel: 'text-embedding-3-small',
407+
} as never)
408+
mockCheckAttributedUsageLimits.mockResolvedValue({ isExceeded: false } as never)
409+
mockGenerateSearchEmbedding.mockResolvedValue({
410+
embedding: [0.1, 0.2],
411+
isBYOK: false,
412+
} as never)
413+
mockGetQueryStrategy.mockReturnValue({ distanceThreshold: 1 } as never)
414+
mockHandleVectorOnlySearch.mockResolvedValue([])
415+
mockRecordSearchEmbeddingUsage.mockResolvedValue(undefined)
416+
417+
const result = await knowledgeBaseServerTool.execute(
418+
{
419+
operation: 'query',
420+
args: {
421+
knowledgeBaseId: 'knowledge-base-1',
422+
query: 'memory',
423+
tagFilters: filters,
424+
},
425+
},
426+
{
427+
userId: 'external-admin',
428+
workspaceId: 'workspace-paid',
429+
billingAttribution: BILLING_ATTRIBUTION,
430+
}
431+
)
432+
433+
expect(result.success).toBe(true)
434+
expect(mockGetDocumentTagDefinitions).not.toHaveBeenCalled()
435+
expect(mockHandleTagAndVectorSearch).not.toHaveBeenCalled()
436+
expect(mockHandleVectorOnlySearch).toHaveBeenCalledWith({
437+
knowledgeBaseIds: ['knowledge-base-1'],
438+
topK: 5,
439+
queryVector: JSON.stringify([0.1, 0.2]),
440+
distanceThreshold: 1,
441+
})
442+
})
443+
336444
it('wraps tag definitions in an object-shaped result payload', async () => {
337445
mockCheckKnowledgeBaseAccess.mockResolvedValue({ hasAccess: true } as never)
338446
mockGetDocumentTagDefinitions.mockResolvedValue([

apps/sim/lib/copilot/tools/server/knowledge/knowledge-base.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,6 @@ export const knowledgeBaseServerTool: BaseServerTool<KnowledgeBaseArgs, Knowledg
253253
}
254254

255255
const parsedTagFilters = parseTagFilters(args.tagFilters)
256-
if (args.tagFilters !== undefined && parsedTagFilters.length === 0) {
257-
return {
258-
success: false,
259-
message: 'tagFilters must contain at least one tagName and tagValue',
260-
}
261-
}
262-
263256
const tagDefinitions =
264257
parsedTagFilters.length > 0 ? await getDocumentTagDefinitions(args.knowledgeBaseId) : []
265258
const structuredFilters = resolveStructuredTagFilters(parsedTagFilters, tagDefinitions)
@@ -629,13 +622,6 @@ export const knowledgeBaseServerTool: BaseServerTool<KnowledgeBaseArgs, Knowledg
629622
updateData.enabled = args.enabled
630623
}
631624
const parsedDocumentTags = parseDocumentTags(args.documentTags)
632-
if (args.documentTags !== undefined && parsedDocumentTags.length === 0) {
633-
return {
634-
success: false,
635-
message: 'documentTags must contain at least one tagName and tagValue',
636-
}
637-
}
638-
639625
const updatedTags: Record<string, string> = {}
640626
if (parsedDocumentTags.length > 0) {
641627
const tagDefinitions = await getDocumentTagDefinitions(args.knowledgeBaseId)

0 commit comments

Comments
 (0)