feat(server): add doc keyword search tool (#12837)

close AI-185

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a keyword-based document search tool, allowing users to
search for relevant documents within their workspace using keywords.
- Search results include document titles, summaries, and direct links,
enhancing document discovery and navigation.
- **Bug Fixes**
  - None.
- **Tests**
- Added new tests to verify document search by IDs and by keywords,
ensuring accurate and reliable search functionality.
- **Documentation**
  - None.
- **Chores**
- Updated configuration file organization for improved clarity; no
changes to functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->








#### PR Dependency Tree


* **PR #12867**
  * **PR #12863**
    * **PR #12837** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
This commit is contained in:
fengmk2
2025-06-20 18:50:34 +08:00
committed by GitHub
parent 3a124b67bd
commit e978147a16
7 changed files with 148 additions and 26 deletions
@@ -0,0 +1,64 @@
import { tool } from 'ai';
import { z } from 'zod';
import type { AccessController } from '../../../core/permission';
import type { IndexerService, SearchDoc } from '../../indexer';
import type { CopilotChatOptions } from '../providers';
export const buildDocKeywordSearchGetter = (
ac: AccessController,
indexerService: IndexerService
) => {
const searchDocs = async (options: CopilotChatOptions, query?: string) => {
if (!options || !query?.trim() || !options.user || !options.workspace) {
return undefined;
}
const canAccess = await ac
.user(options.user)
.workspace(options.workspace)
.can('Workspace.Read');
if (!canAccess) return undefined;
const docs = await indexerService.searchDocsByKeyword(
options.workspace,
query
);
// filter current user readable docs
const readableDocs = await ac
.user(options.user)
.workspace(options.workspace)
.docs(docs, 'Doc.Read');
return readableDocs;
};
return searchDocs;
};
export const createDocKeywordSearchTool = (
searchDocs: (query: string) => Promise<SearchDoc[] | undefined>
) => {
return tool({
description:
'Full-text search for relevant documents in the current workspace',
parameters: z.object({
query: z.string().describe('The query to search for'),
}),
execute: async ({ query }) => {
try {
const docs = await searchDocs(query);
if (!docs) {
return;
}
return docs.map(doc => ({
docId: doc.docId,
title: doc.title,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
createdByUser: doc.createdByUser,
updatedByUser: doc.updatedByUser,
}));
} catch {
return 'Failed to search documents.';
}
},
});
};
@@ -1,2 +1,3 @@
export * from './doc-keyword-search';
export * from './doc-semantic-search';
export * from './web-search';