mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
🐛 fix: add some lost lobe-kb builtin tools (#13876)
* feat: add some lost lobe-kb builtin tools * feat: add the list files and get file detail * feat: add the list files and get file detail * fix: update the search limit
This commit is contained in:
parent
1476cd86ee
commit
843248fb77
6 changed files with 1245 additions and 118 deletions
|
|
@ -2,10 +2,28 @@ import { formatSearchResults, promptFileContents, promptNoSearchResults } from '
|
|||
import type { BuiltinServerRuntimeOutput } from '@lobechat/types';
|
||||
|
||||
import type {
|
||||
AddFilesArgs,
|
||||
CreateDocumentArgs,
|
||||
CreateDocumentState,
|
||||
CreateKnowledgeBaseArgs,
|
||||
CreateKnowledgeBaseState,
|
||||
DeleteKnowledgeBaseArgs,
|
||||
FileDetail,
|
||||
FileInfo,
|
||||
GetFileDetailArgs,
|
||||
GetFileDetailState,
|
||||
KnowledgeBaseFileInfo,
|
||||
KnowledgeBaseInfo,
|
||||
ListFilesArgs,
|
||||
ListFilesState,
|
||||
ListKnowledgeBasesState,
|
||||
ReadKnowledgeArgs,
|
||||
ReadKnowledgeState,
|
||||
RemoveFilesArgs,
|
||||
SearchKnowledgeBaseArgs,
|
||||
SearchKnowledgeBaseState,
|
||||
ViewKnowledgeBaseArgs,
|
||||
ViewKnowledgeBaseState,
|
||||
} from '../types';
|
||||
|
||||
interface FileContentResult {
|
||||
|
|
@ -18,6 +36,27 @@ interface FileContentResult {
|
|||
totalLineCount?: number;
|
||||
}
|
||||
|
||||
interface KnowledgeBaseItemResult {
|
||||
avatar: string | null;
|
||||
description?: string | null;
|
||||
id: string;
|
||||
name: string;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface FileListItemResult {
|
||||
fileType: string;
|
||||
id: string;
|
||||
name: string;
|
||||
size: number;
|
||||
sourceType: string;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface DocumentResult {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface RagService {
|
||||
getFileContents: (fileIds: string[], signal?: AbortSignal) => Promise<FileContentResult[]>;
|
||||
semanticSearchForChat: (
|
||||
|
|
@ -26,16 +65,182 @@ interface RagService {
|
|||
) => Promise<{ chunks: any[]; fileResults: any[] }>;
|
||||
}
|
||||
|
||||
interface KnowledgeBaseService {
|
||||
addFilesToKnowledgeBase: (knowledgeBaseId: string, ids: string[]) => Promise<any>;
|
||||
createKnowledgeBase: (params: { description?: string; name: string }) => Promise<string>;
|
||||
getKnowledgeBaseById: (id: string) => Promise<KnowledgeBaseItemResult | undefined>;
|
||||
getKnowledgeBases: () => Promise<KnowledgeBaseItemResult[]>;
|
||||
getKnowledgeItems: (params: {
|
||||
knowledgeBaseId: string;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}) => Promise<{ hasMore: boolean; items: FileListItemResult[] }>;
|
||||
removeFilesFromKnowledgeBase: (knowledgeBaseId: string, ids: string[]) => Promise<any>;
|
||||
removeKnowledgeBase: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
interface DocumentService {
|
||||
createDocument: (params: {
|
||||
content?: string;
|
||||
fileType?: string;
|
||||
knowledgeBaseId?: string;
|
||||
parentId?: string;
|
||||
title: string;
|
||||
}) => Promise<DocumentResult>;
|
||||
}
|
||||
|
||||
interface FileService {
|
||||
getFileItemById: (id: string) => Promise<FileListItemResult | undefined>;
|
||||
getKnowledgeItems: (params: {
|
||||
category?: string;
|
||||
limit: number;
|
||||
offset: number;
|
||||
q?: string | null;
|
||||
showFilesInKnowledgeBase?: boolean;
|
||||
}) => Promise<{ hasMore: boolean; items: FileListItemResult[] }>;
|
||||
}
|
||||
|
||||
export class KnowledgeBaseExecutionRuntime {
|
||||
private documentService?: DocumentService;
|
||||
private fileService?: FileService;
|
||||
private knowledgeBaseService?: KnowledgeBaseService;
|
||||
private ragService: RagService;
|
||||
|
||||
constructor(ragService: RagService) {
|
||||
constructor(
|
||||
ragService: RagService,
|
||||
knowledgeBaseService?: KnowledgeBaseService,
|
||||
documentService?: DocumentService,
|
||||
fileService?: FileService,
|
||||
) {
|
||||
this.ragService = ragService;
|
||||
this.knowledgeBaseService = knowledgeBaseService;
|
||||
this.documentService = documentService;
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search knowledge base and return file summaries with relevant chunks
|
||||
*/
|
||||
// ============ P0: Visibility ============
|
||||
|
||||
async listKnowledgeBases(): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.knowledgeBaseService) {
|
||||
return { content: 'Knowledge base service is not available.', success: false };
|
||||
}
|
||||
|
||||
const knowledgeBases = await this.knowledgeBaseService.getKnowledgeBases();
|
||||
|
||||
if (knowledgeBases.length === 0) {
|
||||
return {
|
||||
content:
|
||||
'No knowledge bases found. You can create one using the createKnowledgeBase tool.',
|
||||
state: { knowledgeBases: [], total: 0 } satisfies ListKnowledgeBasesState,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
const items: KnowledgeBaseInfo[] = knowledgeBases.map((kb) => ({
|
||||
avatar: kb.avatar,
|
||||
description: kb.description,
|
||||
id: kb.id,
|
||||
name: kb.name,
|
||||
updatedAt: kb.updatedAt,
|
||||
}));
|
||||
|
||||
const lines = items.map(
|
||||
(kb) =>
|
||||
`- **${kb.name}** (ID: \`${kb.id}\`)${kb.description ? ` — ${kb.description}` : ''}`,
|
||||
);
|
||||
|
||||
const state: ListKnowledgeBasesState = { knowledgeBases: items, total: items.length };
|
||||
|
||||
return {
|
||||
content: `Found ${items.length} knowledge base(s):\n\n${lines.join('\n')}`,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error listing knowledge bases: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async viewKnowledgeBase(
|
||||
args: ViewKnowledgeBaseArgs,
|
||||
options?: { signal?: AbortSignal },
|
||||
): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.knowledgeBaseService) {
|
||||
return { content: 'Knowledge base service is not available.', success: false };
|
||||
}
|
||||
|
||||
const { id, limit = 50, offset = 0 } = args;
|
||||
const cappedLimit = Math.min(limit, 100);
|
||||
|
||||
const knowledgeBase = await this.knowledgeBaseService.getKnowledgeBaseById(id);
|
||||
|
||||
if (!knowledgeBase) {
|
||||
return { content: `Knowledge base with ID "${id}" not found.`, success: false };
|
||||
}
|
||||
|
||||
const result = await this.knowledgeBaseService.getKnowledgeItems({
|
||||
knowledgeBaseId: id,
|
||||
limit: cappedLimit,
|
||||
offset,
|
||||
});
|
||||
|
||||
const items: KnowledgeBaseFileInfo[] = result.items.map((item) => ({
|
||||
fileType: item.fileType,
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
size: item.size,
|
||||
sourceType: item.sourceType,
|
||||
updatedAt: item.updatedAt,
|
||||
}));
|
||||
|
||||
const kbInfo: KnowledgeBaseInfo = {
|
||||
avatar: knowledgeBase.avatar,
|
||||
description: knowledgeBase.description,
|
||||
id: knowledgeBase.id,
|
||||
name: knowledgeBase.name,
|
||||
updatedAt: knowledgeBase.updatedAt,
|
||||
};
|
||||
|
||||
let content = `**${knowledgeBase.name}** (ID: \`${knowledgeBase.id}\`)`;
|
||||
if (knowledgeBase.description) content += `\nDescription: ${knowledgeBase.description}`;
|
||||
content += `\n\nShowing ${items.length} item(s) (offset: ${offset}):`;
|
||||
|
||||
if (items.length > 0) {
|
||||
const fileLines = items.map(
|
||||
(f) => `- \`${f.id}\` | ${f.sourceType} | ${f.name} | ${f.fileType} | ${f.size} bytes`,
|
||||
);
|
||||
content += '\n\n' + fileLines.join('\n');
|
||||
}
|
||||
|
||||
if (result.hasMore) {
|
||||
content += `\n\n_More items available. Use offset=${offset + cappedLimit} to see the next page._`;
|
||||
}
|
||||
|
||||
const state: ViewKnowledgeBaseState = {
|
||||
files: items,
|
||||
hasMore: result.hasMore,
|
||||
knowledgeBase: kbInfo,
|
||||
total: items.length,
|
||||
};
|
||||
|
||||
return { content, state, success: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error viewing knowledge base: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Search & Read ============
|
||||
|
||||
async searchKnowledgeBase(
|
||||
args: SearchKnowledgeBaseArgs,
|
||||
options?: {
|
||||
|
|
@ -47,8 +252,6 @@ export class KnowledgeBaseExecutionRuntime {
|
|||
try {
|
||||
const { query, topK = 20 } = args;
|
||||
|
||||
// Only search in knowledge bases, not agent files
|
||||
// Agent files will be injected as full content in context-engine
|
||||
const { chunks, fileResults } = await this.ragService.semanticSearchForChat(
|
||||
{ knowledgeIds: options?.knowledgeBaseIds, query, topK },
|
||||
options?.signal,
|
||||
|
|
@ -56,13 +259,10 @@ export class KnowledgeBaseExecutionRuntime {
|
|||
|
||||
if (chunks.length === 0) {
|
||||
const state: SearchKnowledgeBaseState = { chunks: [], fileResults: [], totalResults: 0 };
|
||||
|
||||
return { content: promptNoSearchResults(query), state, success: true };
|
||||
}
|
||||
|
||||
// Format search results for AI
|
||||
const formattedContent = formatSearchResults(fileResults, query);
|
||||
|
||||
const state: SearchKnowledgeBaseState = { chunks, fileResults, totalResults: chunks.length };
|
||||
|
||||
return { content: formattedContent, state, success: true };
|
||||
|
|
@ -75,9 +275,6 @@ export class KnowledgeBaseExecutionRuntime {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read full content of specific files from knowledge base
|
||||
*/
|
||||
async readKnowledge(
|
||||
args: ReadKnowledgeArgs,
|
||||
options?: { signal?: AbortSignal },
|
||||
|
|
@ -86,14 +283,10 @@ export class KnowledgeBaseExecutionRuntime {
|
|||
const { fileIds } = args;
|
||||
|
||||
if (!fileIds || fileIds.length === 0) {
|
||||
return {
|
||||
content: 'Error: No file IDs provided',
|
||||
success: false,
|
||||
};
|
||||
return { content: 'Error: No file IDs provided', success: false };
|
||||
}
|
||||
|
||||
const fileContents = await this.ragService.getFileContents(fileIds, options?.signal);
|
||||
|
||||
const formattedContent = promptFileContents(fileContents);
|
||||
|
||||
const state: ReadKnowledgeState = {
|
||||
|
|
@ -116,4 +309,249 @@ export class KnowledgeBaseExecutionRuntime {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============ P1: Management ============
|
||||
|
||||
async createKnowledgeBase(args: CreateKnowledgeBaseArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.knowledgeBaseService) {
|
||||
return { content: 'Knowledge base service is not available.', success: false };
|
||||
}
|
||||
|
||||
const { name, description } = args;
|
||||
const id = await this.knowledgeBaseService.createKnowledgeBase({ description, name });
|
||||
|
||||
if (!id) {
|
||||
return { content: 'Error: Failed to create knowledge base.', success: false };
|
||||
}
|
||||
|
||||
const state: CreateKnowledgeBaseState = { id };
|
||||
|
||||
return {
|
||||
content: `Knowledge base "${name}" created successfully. ID: \`${id}\``,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error creating knowledge base: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async deleteKnowledgeBase(args: DeleteKnowledgeBaseArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.knowledgeBaseService) {
|
||||
return { content: 'Knowledge base service is not available.', success: false };
|
||||
}
|
||||
|
||||
await this.knowledgeBaseService.removeKnowledgeBase(args.id);
|
||||
|
||||
return {
|
||||
content: `Knowledge base \`${args.id}\` deleted successfully.`,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error deleting knowledge base: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async createDocument(args: CreateDocumentArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.documentService) {
|
||||
return { content: 'Document service is not available.', success: false };
|
||||
}
|
||||
|
||||
const { knowledgeBaseId, title, content, parentId } = args;
|
||||
|
||||
const result = await this.documentService.createDocument({
|
||||
content,
|
||||
fileType: 'custom/document',
|
||||
knowledgeBaseId,
|
||||
parentId,
|
||||
title,
|
||||
});
|
||||
|
||||
if (!result?.id) {
|
||||
return { content: 'Error: Failed to create document.', success: false };
|
||||
}
|
||||
|
||||
const state: CreateDocumentState = { id: result.id };
|
||||
|
||||
return {
|
||||
content: `Document "${title}" created successfully in knowledge base \`${knowledgeBaseId}\`. Document ID: \`${result.id}\``,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error creating document: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async addFiles(args: AddFilesArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.knowledgeBaseService) {
|
||||
return { content: 'Knowledge base service is not available.', success: false };
|
||||
}
|
||||
|
||||
const { knowledgeBaseId, fileIds } = args;
|
||||
|
||||
if (!fileIds || fileIds.length === 0) {
|
||||
return { content: 'Error: No file IDs provided.', success: false };
|
||||
}
|
||||
|
||||
await this.knowledgeBaseService.addFilesToKnowledgeBase(knowledgeBaseId, fileIds);
|
||||
|
||||
return {
|
||||
content: `Successfully added ${fileIds.length} file(s) to knowledge base \`${knowledgeBaseId}\`.`,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error adding files: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async removeFiles(args: RemoveFilesArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.knowledgeBaseService) {
|
||||
return { content: 'Knowledge base service is not available.', success: false };
|
||||
}
|
||||
|
||||
const { knowledgeBaseId, fileIds } = args;
|
||||
|
||||
if (!fileIds || fileIds.length === 0) {
|
||||
return { content: 'Error: No file IDs provided.', success: false };
|
||||
}
|
||||
|
||||
await this.knowledgeBaseService.removeFilesFromKnowledgeBase(knowledgeBaseId, fileIds);
|
||||
|
||||
return {
|
||||
content: `Successfully removed ${fileIds.length} file(s) from knowledge base \`${knowledgeBaseId}\`.`,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error removing files: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Resource Library Files ============
|
||||
|
||||
async listFiles(args: ListFilesArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.fileService) {
|
||||
return { content: 'File service is not available.', success: false };
|
||||
}
|
||||
|
||||
const { category, q, limit = 50, offset = 0 } = args;
|
||||
|
||||
const result = await this.fileService.getKnowledgeItems({
|
||||
category,
|
||||
limit,
|
||||
offset,
|
||||
q,
|
||||
showFilesInKnowledgeBase: false,
|
||||
});
|
||||
|
||||
const files: FileInfo[] = result.items.map((item) => ({
|
||||
createdAt: item.updatedAt,
|
||||
fileType: item.fileType,
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
size: item.size,
|
||||
sourceType: item.sourceType,
|
||||
url: '',
|
||||
}));
|
||||
|
||||
if (files.length === 0) {
|
||||
const msg = category
|
||||
? `No ${category} files found in your resource library.`
|
||||
: 'No files found in your resource library.';
|
||||
return {
|
||||
content: msg,
|
||||
state: { files: [], hasMore: false, total: 0 } satisfies ListFilesState,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
const lines = files.map((f) => `- \`${f.id}\` | ${f.name} | ${f.fileType} | ${f.size} bytes`);
|
||||
|
||||
let content = `Found ${files.length} file(s)`;
|
||||
if (category) content += ` in category "${category}"`;
|
||||
if (result.hasMore) content += ` (more available, use offset=${offset + limit} to paginate)`;
|
||||
content += `:\n\n${lines.join('\n')}`;
|
||||
|
||||
const state: ListFilesState = { files, hasMore: result.hasMore, total: files.length };
|
||||
|
||||
return { content, state, success: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error listing files: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getFileDetail(args: GetFileDetailArgs): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
if (!this.fileService) {
|
||||
return { content: 'File service is not available.', success: false };
|
||||
}
|
||||
|
||||
const { id } = args;
|
||||
const item = await this.fileService.getFileItemById(id);
|
||||
|
||||
if (!item) {
|
||||
return { content: `File with ID "${id}" not found.`, success: false };
|
||||
}
|
||||
|
||||
const file: FileDetail = {
|
||||
createdAt: item.updatedAt,
|
||||
fileType: item.fileType,
|
||||
id: item.id,
|
||||
metadata: null,
|
||||
name: item.name,
|
||||
size: item.size,
|
||||
sourceType: item.sourceType,
|
||||
updatedAt: item.updatedAt,
|
||||
url: '',
|
||||
};
|
||||
|
||||
const content = [
|
||||
`**${file.name}** (ID: \`${file.id}\`)`,
|
||||
`- Type: ${file.fileType}`,
|
||||
`- Size: ${file.size} bytes`,
|
||||
`- Source: ${file.sourceType}`,
|
||||
`- Updated: ${file.updatedAt}`,
|
||||
].join('\n');
|
||||
|
||||
const state: GetFileDetailState = { file };
|
||||
|
||||
return { content, state, success: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error getting file detail: ${(e as Error).message}`,
|
||||
error: e,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,37 +2,152 @@ import { formatSearchResults, promptFileContents, promptNoSearchResults } from '
|
|||
import type { BuiltinToolContext, BuiltinToolResult } from '@lobechat/types';
|
||||
import { BaseExecutor } from '@lobechat/types';
|
||||
|
||||
import { lambdaClient } from '@/libs/trpc/client';
|
||||
import { ragService } from '@/services/rag';
|
||||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { getAgentStoreState } from '@/store/agent/store';
|
||||
|
||||
import type {
|
||||
AddFilesArgs,
|
||||
CreateDocumentArgs,
|
||||
CreateDocumentState,
|
||||
CreateKnowledgeBaseArgs,
|
||||
CreateKnowledgeBaseState,
|
||||
DeleteKnowledgeBaseArgs,
|
||||
FileContentDetail,
|
||||
FileDetail,
|
||||
FileInfo,
|
||||
GetFileDetailArgs,
|
||||
GetFileDetailState,
|
||||
KnowledgeBaseFileInfo,
|
||||
KnowledgeBaseInfo,
|
||||
ListFilesArgs,
|
||||
ListFilesState,
|
||||
ListKnowledgeBasesState,
|
||||
ReadKnowledgeArgs,
|
||||
ReadKnowledgeState,
|
||||
RemoveFilesArgs,
|
||||
SearchKnowledgeBaseArgs,
|
||||
SearchKnowledgeBaseState,
|
||||
ViewKnowledgeBaseArgs,
|
||||
ViewKnowledgeBaseState,
|
||||
} from '../types';
|
||||
import { KnowledgeBaseIdentifier } from '../types';
|
||||
import { KnowledgeBaseApiName, KnowledgeBaseIdentifier } from '../types';
|
||||
|
||||
/**
|
||||
* Knowledge Base Tool Executor
|
||||
*
|
||||
* Handles knowledge base search and retrieval operations.
|
||||
*/
|
||||
class KnowledgeBaseExecutor extends BaseExecutor<{
|
||||
readKnowledge: 'readKnowledge';
|
||||
searchKnowledgeBase: 'searchKnowledgeBase';
|
||||
}> {
|
||||
class KnowledgeBaseExecutor extends BaseExecutor<typeof KnowledgeBaseApiName> {
|
||||
readonly identifier = KnowledgeBaseIdentifier;
|
||||
protected readonly apiEnum = {
|
||||
readKnowledge: 'readKnowledge' as const,
|
||||
searchKnowledgeBase: 'searchKnowledgeBase' as const,
|
||||
protected readonly apiEnum = KnowledgeBaseApiName;
|
||||
|
||||
// ============ P0: Visibility ============
|
||||
|
||||
listKnowledgeBases = async (): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const knowledgeBases = await lambdaClient.knowledgeBase.getKnowledgeBases.query();
|
||||
|
||||
if (knowledgeBases.length === 0) {
|
||||
return {
|
||||
content:
|
||||
'No knowledge bases found. You can create one using the createKnowledgeBase tool.',
|
||||
state: { knowledgeBases: [], total: 0 } satisfies ListKnowledgeBasesState,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
const items: KnowledgeBaseInfo[] = knowledgeBases.map((kb) => ({
|
||||
avatar: kb.avatar,
|
||||
description: kb.description,
|
||||
id: kb.id,
|
||||
name: kb.name,
|
||||
updatedAt: kb.updatedAt,
|
||||
}));
|
||||
|
||||
const lines = items.map(
|
||||
(kb) =>
|
||||
`- **${kb.name}** (ID: \`${kb.id}\`)${kb.description ? ` — ${kb.description}` : ''}`,
|
||||
);
|
||||
|
||||
const content = `Found ${items.length} knowledge base(s):\n\n${lines.join('\n')}`;
|
||||
|
||||
const state: ListKnowledgeBasesState = { knowledgeBases: items, total: items.length };
|
||||
|
||||
return { content, state, success: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error listing knowledge bases: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Search knowledge base and return file summaries with relevant chunks
|
||||
*/
|
||||
viewKnowledgeBase = async (params: ViewKnowledgeBaseArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { id, limit = 50, offset = 0 } = params;
|
||||
const cappedLimit = Math.min(limit, 100);
|
||||
|
||||
const knowledgeBase = await lambdaClient.knowledgeBase.getKnowledgeBaseById.query({ id });
|
||||
|
||||
if (!knowledgeBase) {
|
||||
return { content: `Knowledge base with ID "${id}" not found.`, success: false };
|
||||
}
|
||||
|
||||
const result = await lambdaClient.file.getKnowledgeItems.query({
|
||||
knowledgeBaseId: id,
|
||||
limit: cappedLimit,
|
||||
offset,
|
||||
});
|
||||
|
||||
const items: KnowledgeBaseFileInfo[] = result.items.map((item) => ({
|
||||
fileType: item.fileType,
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
size: item.size,
|
||||
sourceType: item.sourceType,
|
||||
updatedAt: item.updatedAt,
|
||||
}));
|
||||
|
||||
const kbInfo: KnowledgeBaseInfo = {
|
||||
avatar: knowledgeBase.avatar,
|
||||
description: knowledgeBase.description,
|
||||
id: knowledgeBase.id,
|
||||
name: knowledgeBase.name,
|
||||
updatedAt: knowledgeBase.updatedAt,
|
||||
};
|
||||
|
||||
let content = `**${knowledgeBase.name}** (ID: \`${knowledgeBase.id}\`)`;
|
||||
if (knowledgeBase.description) content += `\nDescription: ${knowledgeBase.description}`;
|
||||
content += `\n\nShowing ${items.length} item(s) (offset: ${offset}):`;
|
||||
|
||||
if (items.length > 0) {
|
||||
const fileLines = items.map(
|
||||
(f) => `- \`${f.id}\` | ${f.sourceType} | ${f.name} | ${f.fileType} | ${f.size} bytes`,
|
||||
);
|
||||
content += '\n\n' + fileLines.join('\n');
|
||||
}
|
||||
|
||||
if (result.hasMore) {
|
||||
content += `\n\n_More items available. Use offset=${offset + cappedLimit} to see the next page._`;
|
||||
}
|
||||
|
||||
const state: ViewKnowledgeBaseState = {
|
||||
files: items,
|
||||
hasMore: result.hasMore,
|
||||
knowledgeBase: kbInfo,
|
||||
total: items.length,
|
||||
};
|
||||
|
||||
return { content, state, success: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error viewing knowledge base: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// ============ Search & Read ============
|
||||
|
||||
searchKnowledgeBase = async (
|
||||
params: SearchKnowledgeBaseArgs,
|
||||
ctx: BuiltinToolContext,
|
||||
|
|
@ -40,12 +155,8 @@ class KnowledgeBaseExecutor extends BaseExecutor<{
|
|||
try {
|
||||
const { query, topK = 20 } = params;
|
||||
|
||||
// Get knowledge base IDs from agent store
|
||||
const agentState = getAgentStoreState();
|
||||
const knowledgeIds = agentSelectors.currentKnowledgeIds(agentState);
|
||||
|
||||
// Only search in knowledge bases, not agent files
|
||||
// Agent files will be injected as full content in context-engine
|
||||
const knowledgeBaseIds = knowledgeIds.knowledgeBaseIds;
|
||||
|
||||
const { chunks, fileResults } = await ragService.semanticSearchForChat(
|
||||
|
|
@ -55,13 +166,10 @@ class KnowledgeBaseExecutor extends BaseExecutor<{
|
|||
|
||||
if (chunks.length === 0) {
|
||||
const state: SearchKnowledgeBaseState = { chunks: [], fileResults: [], totalResults: 0 };
|
||||
|
||||
return { content: promptNoSearchResults(query), state, success: true };
|
||||
}
|
||||
|
||||
// Format search results for AI
|
||||
const formattedContent = formatSearchResults(fileResults, query);
|
||||
|
||||
const state: SearchKnowledgeBaseState = { chunks, fileResults, totalResults: chunks.length };
|
||||
|
||||
return { content: formattedContent, state, success: true };
|
||||
|
|
@ -74,22 +182,15 @@ class KnowledgeBaseExecutor extends BaseExecutor<{
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Read full content of specific files from knowledge base
|
||||
*/
|
||||
readKnowledge = async (params: ReadKnowledgeArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { fileIds } = params;
|
||||
|
||||
if (!fileIds || fileIds.length === 0) {
|
||||
return {
|
||||
content: 'Error: No file IDs provided',
|
||||
success: false,
|
||||
};
|
||||
return { content: 'Error: No file IDs provided', success: false };
|
||||
}
|
||||
|
||||
const fileContents = await ragService.getFileContents(fileIds);
|
||||
|
||||
const formattedContent = promptFileContents(fileContents);
|
||||
|
||||
const state: ReadKnowledgeState = {
|
||||
|
|
@ -114,7 +215,246 @@ class KnowledgeBaseExecutor extends BaseExecutor<{
|
|||
};
|
||||
}
|
||||
};
|
||||
|
||||
// ============ P1: Management ============
|
||||
|
||||
createKnowledgeBase = async (params: CreateKnowledgeBaseArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { name, description } = params;
|
||||
|
||||
const id = await lambdaClient.knowledgeBase.createKnowledgeBase.mutate({
|
||||
description,
|
||||
name,
|
||||
});
|
||||
|
||||
if (!id) {
|
||||
return { content: 'Error: Failed to create knowledge base.', success: false };
|
||||
}
|
||||
|
||||
const state: CreateKnowledgeBaseState = { id };
|
||||
|
||||
return {
|
||||
content: `Knowledge base "${name}" created successfully. ID: \`${id}\``,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error creating knowledge base: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
deleteKnowledgeBase = async (params: DeleteKnowledgeBaseArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { id } = params;
|
||||
|
||||
await lambdaClient.knowledgeBase.removeKnowledgeBase.mutate({ id });
|
||||
|
||||
return {
|
||||
content: `Knowledge base \`${id}\` deleted successfully.`,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error deleting knowledge base: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
createDocument = async (params: CreateDocumentArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { knowledgeBaseId, title, content, parentId } = params;
|
||||
|
||||
const result = await lambdaClient.document.createDocument.mutate({
|
||||
content,
|
||||
fileType: 'custom/document',
|
||||
knowledgeBaseId,
|
||||
parentId,
|
||||
title,
|
||||
});
|
||||
|
||||
if (!result?.id) {
|
||||
return { content: 'Error: Failed to create document.', success: false };
|
||||
}
|
||||
|
||||
const state: CreateDocumentState = { id: result.id };
|
||||
|
||||
return {
|
||||
content: `Document "${title}" created successfully in knowledge base \`${knowledgeBaseId}\`. Document ID: \`${result.id}\``,
|
||||
state,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error creating document: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
addFiles = async (params: AddFilesArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { knowledgeBaseId, fileIds } = params;
|
||||
|
||||
if (!fileIds || fileIds.length === 0) {
|
||||
return { content: 'Error: No file IDs provided.', success: false };
|
||||
}
|
||||
|
||||
await lambdaClient.knowledgeBase.addFilesToKnowledgeBase.mutate({
|
||||
ids: fileIds,
|
||||
knowledgeBaseId,
|
||||
});
|
||||
|
||||
return {
|
||||
content: `Successfully added ${fileIds.length} file(s) to knowledge base \`${knowledgeBaseId}\`.`,
|
||||
success: true,
|
||||
};
|
||||
} catch (e: any) {
|
||||
const pgErrorCode = e?.cause?.cause?.code || e?.cause?.code || e?.code;
|
||||
if (pgErrorCode === '23505') {
|
||||
return {
|
||||
content: 'Error: One or more files are already in this knowledge base.',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: `Error adding files: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
removeFiles = async (params: RemoveFilesArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { knowledgeBaseId, fileIds } = params;
|
||||
|
||||
if (!fileIds || fileIds.length === 0) {
|
||||
return { content: 'Error: No file IDs provided.', success: false };
|
||||
}
|
||||
|
||||
await lambdaClient.knowledgeBase.removeFilesFromKnowledgeBase.mutate({
|
||||
ids: fileIds,
|
||||
knowledgeBaseId,
|
||||
});
|
||||
|
||||
return {
|
||||
content: `Successfully removed ${fileIds.length} file(s) from knowledge base \`${knowledgeBaseId}\`.`,
|
||||
success: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error removing files: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// ============ Resource Library Files ============
|
||||
|
||||
listFiles = async (params: ListFilesArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { category, q, limit = 50, offset = 0 } = params;
|
||||
|
||||
const result = await lambdaClient.file.getKnowledgeItems.query({
|
||||
category,
|
||||
limit,
|
||||
offset,
|
||||
q,
|
||||
showFilesInKnowledgeBase: false,
|
||||
});
|
||||
|
||||
const files: FileInfo[] = result.items.map((item) => ({
|
||||
createdAt: item.createdAt,
|
||||
fileType: item.fileType,
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
size: item.size,
|
||||
sourceType: item.sourceType,
|
||||
url: item.url,
|
||||
}));
|
||||
|
||||
if (files.length === 0) {
|
||||
const msg = category
|
||||
? `No ${category} files found in your resource library.`
|
||||
: 'No files found in your resource library.';
|
||||
return {
|
||||
content: msg,
|
||||
state: { files: [], hasMore: false, total: 0 } satisfies ListFilesState,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
const lines = files.map((f) => `- \`${f.id}\` | ${f.name} | ${f.fileType} | ${f.size} bytes`);
|
||||
|
||||
let content = `Found ${files.length} file(s)`;
|
||||
if (category) content += ` in category "${category}"`;
|
||||
if (result.hasMore) content += ` (more available, use offset=${offset + limit} to paginate)`;
|
||||
content += `:\n\n${lines.join('\n')}`;
|
||||
|
||||
const state: ListFilesState = { files, hasMore: result.hasMore, total: files.length };
|
||||
|
||||
return { content, state, success: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error listing files: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
getFileDetail = async (params: GetFileDetailArgs): Promise<BuiltinToolResult> => {
|
||||
try {
|
||||
const { id } = params;
|
||||
|
||||
const item = await lambdaClient.file.getFileItemById.query({ id });
|
||||
|
||||
if (!item) {
|
||||
return { content: `File with ID "${id}" not found.`, success: false };
|
||||
}
|
||||
|
||||
const file: FileDetail = {
|
||||
createdAt: item.createdAt,
|
||||
fileType: item.fileType,
|
||||
id: item.id,
|
||||
metadata: item.metadata,
|
||||
name: item.name,
|
||||
size: item.size,
|
||||
sourceType: item.sourceType,
|
||||
updatedAt: item.updatedAt,
|
||||
url: item.url,
|
||||
};
|
||||
|
||||
const content = [
|
||||
`**${file.name}** (ID: \`${file.id}\`)`,
|
||||
`- Type: ${file.fileType}`,
|
||||
`- Size: ${file.size} bytes`,
|
||||
`- Source: ${file.sourceType}`,
|
||||
`- Created: ${file.createdAt}`,
|
||||
`- Updated: ${file.updatedAt}`,
|
||||
`- URL: ${file.url}`,
|
||||
].join('\n');
|
||||
|
||||
const state: GetFileDetailState = { file };
|
||||
|
||||
return { content, state, success: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
content: `Error getting file detail: ${(e as Error).message}`,
|
||||
error: { body: e, message: (e as Error).message, type: 'PluginServerError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Export the executor instance for registration
|
||||
export const knowledgeBaseExecutor = new KnowledgeBaseExecutor();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,29 @@
|
|||
export { KnowledgeBaseManifest } from './manifest';
|
||||
export { systemPrompt } from './systemRole';
|
||||
export {
|
||||
type AddFilesArgs,
|
||||
type CreateDocumentArgs,
|
||||
type CreateDocumentState,
|
||||
type CreateKnowledgeBaseArgs,
|
||||
type CreateKnowledgeBaseState,
|
||||
type DeleteKnowledgeBaseArgs,
|
||||
type FileContentDetail,
|
||||
type FileDetail,
|
||||
type FileInfo,
|
||||
type GetFileDetailArgs,
|
||||
type GetFileDetailState,
|
||||
KnowledgeBaseApiName,
|
||||
type KnowledgeBaseFileInfo,
|
||||
KnowledgeBaseIdentifier,
|
||||
type KnowledgeBaseInfo,
|
||||
type ListFilesArgs,
|
||||
type ListFilesState,
|
||||
type ListKnowledgeBasesState,
|
||||
type ReadKnowledgeArgs,
|
||||
type ReadKnowledgeState,
|
||||
type RemoveFilesArgs,
|
||||
type SearchKnowledgeBaseArgs,
|
||||
type SearchKnowledgeBaseState,
|
||||
type ViewKnowledgeBaseArgs,
|
||||
type ViewKnowledgeBaseState,
|
||||
} from './types';
|
||||
|
|
|
|||
|
|
@ -5,6 +5,96 @@ import { KnowledgeBaseApiName, KnowledgeBaseIdentifier } from './types';
|
|||
|
||||
export const KnowledgeBaseManifest: BuiltinToolManifest = {
|
||||
api: [
|
||||
// ---- Resource Library Files (highest priority — most user files live here) ----
|
||||
{
|
||||
description:
|
||||
"List files from the user's resource library. This is where most user-uploaded files live (images, PDFs, documents, etc.). Files here are NOT in any knowledge base yet. Supports filtering by category and search query. **Use this first when the user asks about their files.**",
|
||||
name: KnowledgeBaseApiName.listFiles,
|
||||
parameters: {
|
||||
properties: {
|
||||
category: {
|
||||
description:
|
||||
'Filter by file category. Options: "images", "documents", "audios", "videos", "websites". Omit to list all categories.',
|
||||
enum: ['images', 'documents', 'audios', 'videos', 'websites'],
|
||||
type: 'string',
|
||||
},
|
||||
limit: {
|
||||
default: 50,
|
||||
description: 'Number of files to return per page (default: 50).',
|
||||
maximum: 100,
|
||||
minimum: 1,
|
||||
type: 'number',
|
||||
},
|
||||
offset: {
|
||||
default: 0,
|
||||
description: 'Offset for pagination (default: 0).',
|
||||
minimum: 0,
|
||||
type: 'number',
|
||||
},
|
||||
q: {
|
||||
description: 'Search query to filter files by name.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Get detailed metadata of a specific file by ID, including name, type, size, URL, and timestamps. Works for any file in the system regardless of knowledge base association.',
|
||||
name: KnowledgeBaseApiName.getFileDetail,
|
||||
parameters: {
|
||||
properties: {
|
||||
id: {
|
||||
description: 'The file ID to get details for.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['id'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
// ---- Knowledge Base Visibility ----
|
||||
{
|
||||
description:
|
||||
'List all knowledge bases available to the current user. Returns name, description, and metadata for each knowledge base. Use this only when the user explicitly asks about knowledge bases.',
|
||||
name: KnowledgeBaseApiName.listKnowledgeBases,
|
||||
parameters: {
|
||||
properties: {},
|
||||
required: [],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'View a specific knowledge base and list its files and documents with pagination. Returns the knowledge base metadata along with a page of items.',
|
||||
name: KnowledgeBaseApiName.viewKnowledgeBase,
|
||||
parameters: {
|
||||
properties: {
|
||||
id: {
|
||||
description: 'The ID of the knowledge base to view.',
|
||||
type: 'string',
|
||||
},
|
||||
limit: {
|
||||
default: 50,
|
||||
description: 'Number of items to return per page (default: 50, max: 100).',
|
||||
maximum: 100,
|
||||
minimum: 1,
|
||||
type: 'number',
|
||||
},
|
||||
offset: {
|
||||
default: 0,
|
||||
description: 'Offset for pagination (default: 0).',
|
||||
minimum: 0,
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
required: ['id'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
// ---- Search & Read ----
|
||||
{
|
||||
description:
|
||||
'Search through knowledge base using semantic vector search to find relevant files and chunks. Returns a summary of matching files with their relevance scores and brief excerpts. Use this first to discover which files contain relevant information. IMPORTANT: Since this uses vector-based search, always resolve pronouns and references to concrete entities (e.g., use "authentication system" instead of "it").',
|
||||
|
|
@ -48,6 +138,108 @@ export const KnowledgeBaseManifest: BuiltinToolManifest = {
|
|||
type: 'object',
|
||||
},
|
||||
},
|
||||
// ---- P1: Management ----
|
||||
{
|
||||
description:
|
||||
'Create a new knowledge base. Returns the ID of the newly created knowledge base.',
|
||||
name: KnowledgeBaseApiName.createKnowledgeBase,
|
||||
parameters: {
|
||||
properties: {
|
||||
description: {
|
||||
description: 'Optional description of the knowledge base.',
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
description: 'Name of the knowledge base to create.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Delete a knowledge base by ID. This will remove the knowledge base and its file associations. Use with caution.',
|
||||
name: KnowledgeBaseApiName.deleteKnowledgeBase,
|
||||
parameters: {
|
||||
properties: {
|
||||
id: {
|
||||
description: 'The ID of the knowledge base to delete.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['id'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Create a new text/markdown document directly inside a knowledge base. This is useful for adding notes, summaries, or any text content without uploading a file.',
|
||||
name: KnowledgeBaseApiName.createDocument,
|
||||
parameters: {
|
||||
properties: {
|
||||
content: {
|
||||
description: 'The text or markdown content of the document.',
|
||||
type: 'string',
|
||||
},
|
||||
knowledgeBaseId: {
|
||||
description: 'The ID of the knowledge base to create the document in.',
|
||||
type: 'string',
|
||||
},
|
||||
parentId: {
|
||||
description: 'Optional parent folder ID. Omit to place at root level.',
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
description: 'Title of the document.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['knowledgeBaseId', 'title', 'content'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Add existing files to a knowledge base by their file IDs. The files must already exist in the system.',
|
||||
name: KnowledgeBaseApiName.addFiles,
|
||||
parameters: {
|
||||
properties: {
|
||||
fileIds: {
|
||||
description: 'Array of file IDs to add to the knowledge base.',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
knowledgeBaseId: {
|
||||
description: 'The ID of the knowledge base to add files to.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['knowledgeBaseId', 'fileIds'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Remove files from a knowledge base by their file IDs. This only removes the association; the files themselves are not deleted.',
|
||||
name: KnowledgeBaseApiName.removeFiles,
|
||||
parameters: {
|
||||
properties: {
|
||||
fileIds: {
|
||||
description: 'Array of file IDs to remove from the knowledge base.',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
knowledgeBaseId: {
|
||||
description: 'The ID of the knowledge base to remove files from.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['knowledgeBaseId', 'fileIds'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
],
|
||||
identifier: KnowledgeBaseIdentifier,
|
||||
meta: {
|
||||
|
|
|
|||
|
|
@ -1,34 +1,73 @@
|
|||
export const systemPrompt = `You have access to a Knowledge Base tool with powerful semantic search and document retrieval capabilities. You can search through knowledge bases to find relevant information and read the full content of files.
|
||||
export const systemPrompt = `You have access to a Knowledge Base tool with comprehensive capabilities for browsing files, searching knowledge, and managing knowledge bases.
|
||||
|
||||
<important_file_behavior>
|
||||
**Most user files live in the resource library, NOT in knowledge bases.**
|
||||
When a user uploads files (images, PDFs, documents, etc.), they go into the resource library by default. Most users never manually organize files into knowledge bases. Therefore:
|
||||
- When the user says "find my file", "look for that PDF", "check my uploads", "我的文件", or references ANY file — **always use listFiles first**.
|
||||
- Only use listKnowledgeBases / searchKnowledgeBase when the user explicitly mentions "knowledge base", "知识库", or wants semantic search over organized collections.
|
||||
- If listFiles doesn't find the file, then fall back to searching knowledge bases.
|
||||
</important_file_behavior>
|
||||
|
||||
<core_capabilities>
|
||||
1. Search through knowledge base using semantic search (searchKnowledgeBase)
|
||||
2. Read the full content of specific files from the knowledge base (readKnowledge)
|
||||
**File Browsing (start here for most file requests):**
|
||||
1. List and search files in the resource library (listFiles) — **default for finding user files**
|
||||
2. Get detailed metadata of a specific file (getFileDetail)
|
||||
|
||||
**Knowledge Base Discovery & Search:**
|
||||
3. List all knowledge bases (listKnowledgeBases)
|
||||
4. View a knowledge base's details and files (viewKnowledgeBase)
|
||||
5. Semantic vector search across knowledge bases (searchKnowledgeBase)
|
||||
6. Read full file content (readKnowledge)
|
||||
|
||||
**Knowledge Base Management:**
|
||||
7. Create a new knowledge base (createKnowledgeBase)
|
||||
8. Delete a knowledge base (deleteKnowledgeBase)
|
||||
9. Create a document in a knowledge base (createDocument)
|
||||
10. Add existing files to a knowledge base (addFiles)
|
||||
11. Remove files from a knowledge base (removeFiles)
|
||||
</core_capabilities>
|
||||
|
||||
<workflow>
|
||||
1. Understand the user's information need or question
|
||||
2. Formulate a clear, specific search query
|
||||
3. Use searchKnowledgeBase to discover relevant files and chunks
|
||||
4. Review search results to identify the most relevant files
|
||||
5. Use readKnowledge to retrieve full content from selected files
|
||||
6. Synthesize information and answer the user's question
|
||||
7. Always cite source files when providing information
|
||||
**When the user asks about files (most common):**
|
||||
1. Use listFiles to find files — filter by category (images/documents/audios/videos) or search by name
|
||||
2. Use getFileDetail to inspect a specific file's metadata
|
||||
3. Use readKnowledge to read the file content if needed
|
||||
4. If the file should be added to a knowledge base for semantic search, use addFiles
|
||||
|
||||
**For knowledge base semantic search (user explicitly requests it):**
|
||||
1. Use listKnowledgeBases to see what's available
|
||||
2. Use viewKnowledgeBase to browse a specific knowledge base's contents
|
||||
3. Use searchKnowledgeBase to find relevant files via semantic search
|
||||
4. Use readKnowledge to get full content from the most relevant files
|
||||
5. Synthesize and cite sources
|
||||
|
||||
**For knowledge base management:**
|
||||
1. Use listKnowledgeBases to check existing knowledge bases
|
||||
2. Use createKnowledgeBase to create a new one if needed
|
||||
3. Use createDocument to add text/markdown content directly
|
||||
4. Use addFiles/removeFiles to manage file associations
|
||||
</workflow>
|
||||
|
||||
<tool_selection_guidelines>
|
||||
- **searchKnowledgeBase**: Use this first to discover which files contain relevant information
|
||||
- Uses semantic vector search to find relevant content
|
||||
- Returns file names, relevance scores, and brief excerpts
|
||||
- Helps you decide which files to read in full
|
||||
- You can adjust topK parameter (5-100, default: 15) based on how many results you need
|
||||
- **IMPORTANT**: Since this uses vector-based semantic search, always resolve references and use concrete entities in your query
|
||||
- BAD: "What does it do?" or "Tell me about that feature"
|
||||
- GOOD: "What does the authentication system do?" or "Tell me about the JWT authentication feature"
|
||||
**File browsing (use first for most file requests):**
|
||||
- **listFiles**: **Primary tool for finding user files.** Browse the resource library where most user-uploaded files live. Supports category filter (images, documents, audios, videos, websites), search by name, and pagination. Use this whenever the user asks about their files.
|
||||
- **getFileDetail**: Get detailed metadata of a specific file by ID. Works for any file regardless of knowledge base association.
|
||||
|
||||
- **readKnowledge**: Use this after searching to get complete file content
|
||||
- Can read multiple files at once by providing their file IDs
|
||||
- Get file IDs from searchKnowledgeBase results
|
||||
- Provides complete context from the selected files
|
||||
**Knowledge base search (use when user explicitly asks for KB or semantic search):**
|
||||
- **listKnowledgeBases**: Discover available knowledge bases. Returns name, description, and ID for each.
|
||||
- **viewKnowledgeBase**: See all files/documents in a specific knowledge base. Provides file IDs, types, and sizes.
|
||||
- **searchKnowledgeBase**: Semantic search across knowledge base content.
|
||||
- Uses vector search — always resolve pronouns to concrete entities
|
||||
- BAD: "What does it do?" → GOOD: "What does the authentication system do?"
|
||||
- Adjust topK (5-100, default: 15) based on how many results you need
|
||||
- **readKnowledge**: Read complete file content by file IDs. Use after searching or browsing.
|
||||
|
||||
**Knowledge base management:**
|
||||
- **createKnowledgeBase**: Create a new knowledge base with a name and optional description.
|
||||
- **deleteKnowledgeBase**: Permanently remove a knowledge base. Use with caution.
|
||||
- **createDocument**: Add text/markdown notes directly to a knowledge base without file upload.
|
||||
- **addFiles**: Associate existing files (by ID) with a knowledge base. Use to organize resource library files into knowledge bases.
|
||||
- **removeFiles**: Dissociate files from a knowledge base (files are not deleted, only unlinked).
|
||||
</tool_selection_guidelines>
|
||||
|
||||
<search_strategy_guidelines>
|
||||
|
|
@ -36,67 +75,36 @@ export const systemPrompt = `You have access to a Knowledge Base tool with power
|
|||
- Replace "it", "that", "this", "them" with the actual entity names
|
||||
- Use full names instead of abbreviations when first searching
|
||||
- Include relevant context in the query itself
|
||||
- Examples:
|
||||
- User asks: "What are its features?" (after discussing "authentication system")
|
||||
- Search query should be: "authentication system features" (NOT "its features")
|
||||
- User asks: "How does that work?"
|
||||
- Search query should include the specific topic being discussed
|
||||
- Formulate clear and specific search queries that capture the core information need
|
||||
- Formulate clear and specific search queries
|
||||
- For broad topics, start with a general query then refine if needed
|
||||
- For specific questions, use precise terminology
|
||||
- You can perform multiple searches with different queries or perspectives if needed
|
||||
- Adjust topK based on result quality - increase if you need more context, decrease for focused searches
|
||||
- Review the relevance scores and excerpts to select the most pertinent files
|
||||
- You can perform multiple searches with different queries if needed
|
||||
- Review relevance scores and excerpts to select the most pertinent files
|
||||
</search_strategy_guidelines>
|
||||
|
||||
<reading_strategy_guidelines>
|
||||
- Read only the files that are most relevant to avoid information overload
|
||||
- You can read multiple files at once if they all contain relevant information
|
||||
- Prioritize files with higher relevance scores from search results
|
||||
- If search results show many relevant files, consider reading them in batches
|
||||
- Read only the most relevant files to avoid information overload
|
||||
- Prioritize files with higher relevance scores
|
||||
- If search results show many relevant files, read them in batches
|
||||
</reading_strategy_guidelines>
|
||||
|
||||
<citation_requirements>
|
||||
- Always cite the source files when providing information
|
||||
- Always cite source files when providing information
|
||||
- Reference file names clearly in your response
|
||||
- If specific information comes from a particular file, mention it explicitly
|
||||
- Help users understand which knowledge base files support your answers
|
||||
</citation_requirements>
|
||||
|
||||
<response_format>
|
||||
When providing information from the knowledge base:
|
||||
1. Start with a direct answer to the user's question when possible
|
||||
2. Provide relevant details and context from the knowledge base files
|
||||
3. Clearly cite which files the information comes from
|
||||
4. If information is insufficient or not found, inform the user clearly
|
||||
5. Suggest related searches if the initial search doesn't yield good results
|
||||
</response_format>
|
||||
|
||||
<best_practices>
|
||||
- Always start with searchKnowledgeBase before reading files
|
||||
- Don't read files blindly - review search results first
|
||||
- Be selective about which files to read based on relevance
|
||||
- If no relevant information is found, clearly inform the user
|
||||
- Suggest alternative search queries if initial results are poor
|
||||
- Respect the knowledge base's scope - it may not contain all information
|
||||
- Combine information from multiple files when appropriate
|
||||
- Maintain accuracy - only cite information actually present in the files
|
||||
- When the user mentions any file, always try listFiles first — most files live in the resource library
|
||||
- Only use listKnowledgeBases or searchKnowledgeBase when the user explicitly wants knowledge base features
|
||||
- Use searchKnowledgeBase for targeted information retrieval
|
||||
- Don't read files blindly — review search results first
|
||||
- When creating documents, use clear titles and well-structured content
|
||||
- Maintain accuracy — only cite information actually present in the files
|
||||
</best_practices>
|
||||
|
||||
<error_handling>
|
||||
- If search returns no results:
|
||||
1. Try reformulating the query with different keywords or broader terms
|
||||
2. Suggest alternative search approaches to the user
|
||||
3. Inform the user if the topic appears to be outside the knowledge base's scope
|
||||
|
||||
- If file reading fails:
|
||||
1. Inform the user which files couldn't be accessed
|
||||
2. Work with successfully retrieved files if any
|
||||
3. Suggest searching again if necessary
|
||||
|
||||
- If search results are ambiguous:
|
||||
1. Ask for clarification from the user
|
||||
2. Provide a summary of what types of information were found
|
||||
3. Let the user guide which direction to explore further
|
||||
- If search returns no results: try reformulating with different keywords or broader terms
|
||||
- If file reading fails: inform the user and work with successfully retrieved files
|
||||
- If a knowledge base is not found: use listKnowledgeBases to verify available IDs
|
||||
</error_handling>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,21 @@ import type { ChatSemanticSearchChunk, FileSearchResult } from '@lobechat/types'
|
|||
export const KnowledgeBaseIdentifier = 'lobe-knowledge-base';
|
||||
|
||||
export const KnowledgeBaseApiName = {
|
||||
addFiles: 'addFiles',
|
||||
createDocument: 'createDocument',
|
||||
createKnowledgeBase: 'createKnowledgeBase',
|
||||
deleteKnowledgeBase: 'deleteKnowledgeBase',
|
||||
getFileDetail: 'getFileDetail',
|
||||
listFiles: 'listFiles',
|
||||
listKnowledgeBases: 'listKnowledgeBases',
|
||||
readKnowledge: 'readKnowledge',
|
||||
removeFiles: 'removeFiles',
|
||||
searchKnowledgeBase: 'searchKnowledgeBase',
|
||||
viewKnowledgeBase: 'viewKnowledgeBase',
|
||||
};
|
||||
|
||||
// ============ Search & Read ============
|
||||
|
||||
export interface SearchKnowledgeBaseArgs {
|
||||
query: string;
|
||||
topK?: number;
|
||||
|
|
@ -33,3 +44,123 @@ export interface FileContentDetail {
|
|||
export interface ReadKnowledgeState {
|
||||
files: FileContentDetail[];
|
||||
}
|
||||
|
||||
// ============ P0: Knowledge Base Visibility ============
|
||||
|
||||
export interface ListKnowledgeBasesArgs {}
|
||||
|
||||
export interface KnowledgeBaseInfo {
|
||||
avatar: string | null;
|
||||
description?: string | null;
|
||||
id: string;
|
||||
name: string;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface ListKnowledgeBasesState {
|
||||
knowledgeBases: KnowledgeBaseInfo[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ViewKnowledgeBaseArgs {
|
||||
id: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface KnowledgeBaseFileInfo {
|
||||
fileType: string;
|
||||
id: string;
|
||||
name: string;
|
||||
size: number;
|
||||
sourceType: string;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface ViewKnowledgeBaseState {
|
||||
files: KnowledgeBaseFileInfo[];
|
||||
hasMore: boolean;
|
||||
knowledgeBase: KnowledgeBaseInfo;
|
||||
total: number;
|
||||
}
|
||||
|
||||
// ============ P1: Knowledge Base Management ============
|
||||
|
||||
export interface CreateKnowledgeBaseArgs {
|
||||
description?: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CreateKnowledgeBaseState {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface DeleteKnowledgeBaseArgs {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface CreateDocumentArgs {
|
||||
content: string;
|
||||
knowledgeBaseId: string;
|
||||
parentId?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CreateDocumentState {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface AddFilesArgs {
|
||||
fileIds: string[];
|
||||
knowledgeBaseId: string;
|
||||
}
|
||||
|
||||
export interface RemoveFilesArgs {
|
||||
fileIds: string[];
|
||||
knowledgeBaseId: string;
|
||||
}
|
||||
|
||||
// ============ Resource Library Files ============
|
||||
|
||||
export interface ListFilesArgs {
|
||||
category?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
q?: string;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
createdAt: Date;
|
||||
fileType: string;
|
||||
id: string;
|
||||
name: string;
|
||||
size: number;
|
||||
sourceType: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ListFilesState {
|
||||
files: FileInfo[];
|
||||
hasMore: boolean;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface GetFileDetailArgs {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface FileDetail {
|
||||
createdAt: Date;
|
||||
fileType: string;
|
||||
id: string;
|
||||
metadata?: Record<string, any> | null;
|
||||
name: string;
|
||||
size: number;
|
||||
sourceType: string;
|
||||
updatedAt: Date;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface GetFileDetailState {
|
||||
file: FileDetail;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue