🐛 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:
LiJian 2026-04-16 17:08:22 +08:00 committed by GitHub
parent 1476cd86ee
commit 843248fb77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1245 additions and 118 deletions

View file

@ -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,
};
}
}
}

View file

@ -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();

View file

@ -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';

View file

@ -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: {

View file

@ -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>
`;

View file

@ -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;
}