Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import pluginConfig from '@elizaos/config/eslint/eslint.config.plugin.js';

/**
* ESLint configuration for plugin-knowledge
* Extends the standard ElizaOS plugin configuration which includes:
* - @elizaos/structured-logging rule (enforces LOGGING_SPEC.md)
* - TypeScript support
* - Standard code quality rules
*/
export default [
...pluginConfig,
];
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@elizaos/plugin-knowledge",
"description": "Plugin for Knowledge",
"version": "1.5.15",
"version": "1.5.16",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
Expand Down Expand Up @@ -47,15 +47,20 @@
"zod": "3.25.76"
},
"devDependencies": {
"@elizaos/config": "^1.6.4",
"@eslint/js": "^9.17.0",
"@radix-ui/react-tabs": "^1.1.12",
"@tailwindcss/vite": "^4.1.0",
"@types/bun": "^1.2.22",
"@types/multer": "^2.0.0",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"@vitejs/plugin-react-swc": "^3.10.0",
"autoprefixer": "^10.4.19",
"esbuild-plugin-copy": "^2.1.1",
"eslint": "^9.17.0",
"postcss": "^8.5.3",
"prettier": "3.6.2",
"tailwindcss": "^4.1.0",
Expand All @@ -66,8 +71,9 @@
"scripts": {
"dev": "tsup --watch",
"build": "vite build && tsup",
"lint": "prettier --write ./src",
"test": "elizaos test",
"lint": "eslint ./src --fix && prettier --write ./src",
"lint:check": "eslint ./src",
"format": "prettier --write ./src",
"format:check": "prettier --check ./src",
"clean": "rm -rf dist .turbo node_modules .turbo-tsconfig.json tsconfig.tsbuildinfo"
Expand Down
6 changes: 3 additions & 3 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const processKnowledgeAction: Action = {
// Check if service is available
const service = runtime.getService(KnowledgeService.serviceType);
if (!service) {
logger.warn('Knowledge service not available for PROCESS_KNOWLEDGE action');
logger.warn({ src: 'plugin:knowledge:action:process' }, 'Knowledge service not available');
return false;
}

Expand Down Expand Up @@ -196,7 +196,7 @@ export const processKnowledgeAction: Action = {
await callback(response);
}
} catch (error) {
logger.error({ error }, 'Error in PROCESS_KNOWLEDGE action');
logger.error({ src: 'plugin:knowledge:action:process', error }, 'Error processing knowledge');

const errorResponse: Content = {
text: `I encountered an error while processing the knowledge: ${error instanceof Error ? error.message : 'Unknown error'}`,
Expand Down Expand Up @@ -326,7 +326,7 @@ export const searchKnowledgeAction: Action = {
await callback(response);
}
} catch (error) {
logger.error({ error }, 'Error in SEARCH_KNOWLEDGE action');
logger.error({ src: 'plugin:knowledge:action:search', error }, 'Error searching knowledge');

const errorResponse: Content = {
text: `I encountered an error while searching the knowledge base: ${error instanceof Error ? error.message : 'Unknown error'}`,
Expand Down
34 changes: 21 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export function validateModelConfig(runtime?: IAgentRuntime): ModelConfig {

// Log configuration once during validation (not per chunk)
logger.debug(
`[Document Processor] CTX_KNOWLEDGE_ENABLED: '${ctxKnowledgeEnabled} (runtime: ${!!runtime})`
{ src: 'plugin:knowledge', agentId: runtime?.agentId, ctxKnowledgeEnabled, hasRuntime: !!runtime },
'CTX_KNOWLEDGE_ENABLED configuration'
);

// If EMBEDDING_PROVIDER is not provided, assume we're using plugin-openai
Expand All @@ -41,11 +42,13 @@ export function validateModelConfig(runtime?: IAgentRuntime): ModelConfig {

if (openaiApiKey && openaiEmbeddingModel) {
logger.debug(
'[Document Processor] EMBEDDING_PROVIDER not specified, using configuration from plugin-openai'
{ src: 'plugin:knowledge', agentId: runtime?.agentId },
'EMBEDDING_PROVIDER not specified, using configuration from plugin-openai'
);
} else {
logger.debug(
'[Document Processor] EMBEDDING_PROVIDER not specified. Assuming embeddings are provided by another plugin (e.g., plugin-google-genai).'
{ src: 'plugin:knowledge', agentId: runtime?.agentId },
'EMBEDDING_PROVIDER not specified, assuming embeddings are provided by another plugin'
);
}
}
Expand Down Expand Up @@ -89,7 +92,7 @@ export function validateModelConfig(runtime?: IAgentRuntime): ModelConfig {
LOAD_DOCS_ON_STARTUP: parseBooleanEnv(getSetting('LOAD_DOCS_ON_STARTUP')),
CTX_KNOWLEDGE_ENABLED: ctxKnowledgeEnabled,
});
validateConfigRequirements(config, assumePluginOpenAI);
validateConfigRequirements(config, assumePluginOpenAI, runtime);
return config;
} catch (error) {
if (error instanceof z.ZodError) {
Expand All @@ -108,7 +111,7 @@ export function validateModelConfig(runtime?: IAgentRuntime): ModelConfig {
* @param assumePluginOpenAI Whether we're assuming plugin-openai is being used
* @throws Error if a required configuration value is missing
*/
function validateConfigRequirements(config: ModelConfig, assumePluginOpenAI: boolean): void {
function validateConfigRequirements(config: ModelConfig, assumePluginOpenAI: boolean, runtime?: IAgentRuntime): void {
// Only validate embedding requirements if EMBEDDING_PROVIDER is explicitly set
const embeddingProvider = config.EMBEDDING_PROVIDER;

Expand All @@ -123,7 +126,8 @@ function validateConfigRequirements(config: ModelConfig, assumePluginOpenAI: boo
// If no embedding provider is set, skip validation - let runtime handle it
if (!embeddingProvider) {
logger.debug(
'[Document Processor] No EMBEDDING_PROVIDER specified. Embeddings will be handled by the runtime.'
{ src: 'plugin:knowledge', agentId: runtime?.agentId },
'No EMBEDDING_PROVIDER specified. Embeddings will be handled by the runtime.'
);
}

Expand All @@ -136,7 +140,7 @@ function validateConfigRequirements(config: ModelConfig, assumePluginOpenAI: boo
// If Contextual Knowledge is enabled, we need additional validations
if (config.CTX_KNOWLEDGE_ENABLED) {
// Only log validation once during config init (not per document)
logger.debug('[Document Processor] CTX validation: Checking text generation settings...');
logger.debug({ src: 'plugin:knowledge', agentId: runtime?.agentId }, 'CTX validation: Checking text generation settings');

// Validate API keys based on the text provider
if (config.TEXT_PROVIDER === 'openai' && !config.OPENAI_API_KEY) {
Expand All @@ -157,21 +161,24 @@ function validateConfigRequirements(config: ModelConfig, assumePluginOpenAI: boo
const modelName = config.TEXT_MODEL?.toLowerCase() || '';
if (modelName.includes('claude') || modelName.includes('gemini')) {
logger.debug(
`[Document Processor] Using ${modelName} with OpenRouter. This configuration supports document caching for improved performance.`
{ src: 'plugin:knowledge', agentId: runtime?.agentId, modelName, provider: 'openrouter' },
'Using model with OpenRouter. This configuration supports document caching for improved performance.'
);
}
}
} else {
// Log appropriate message based on where embedding config came from
logger.info('[Document Processor] Contextual Knowledge is DISABLED!');
logger.info('[Document Processor] This means documents will NOT be enriched with context.');
logger.info({ src: 'plugin:knowledge', agentId: runtime?.agentId }, 'Contextual Knowledge is DISABLED');
logger.info({ src: 'plugin:knowledge', agentId: runtime?.agentId }, 'Documents will NOT be enriched with context');
if (assumePluginOpenAI) {
logger.info(
'[Document Processor] Embeddings will be handled by the runtime (e.g., plugin-openai, plugin-google-genai).'
{ src: 'plugin:knowledge', agentId: runtime?.agentId },
'Embeddings will be handled by the runtime (e.g., plugin-openai, plugin-google-genai)'
);
} else {
logger.info(
'[Document Processor] Using configured embedding provider for basic embeddings only.'
{ src: 'plugin:knowledge', agentId: runtime?.agentId },
'Using configured embedding provider for basic embeddings only'
);
}
}
Expand Down Expand Up @@ -204,7 +211,8 @@ export async function getProviderRateLimits(runtime?: IAgentRuntime): Promise<Pr
const primaryProvider = config.TEXT_PROVIDER || config.EMBEDDING_PROVIDER;

logger.debug(
`[Document Processor] Rate limiting for ${primaryProvider}: ${requestsPerMinute} RPM, ${tokensPerMinute} TPM, ${maxConcurrentRequests} concurrent`
{ src: 'plugin:knowledge', agentId: runtime?.agentId, provider: primaryProvider, requestsPerMinute, tokensPerMinute, maxConcurrentRequests },
'Rate limiting configuration'
);

// Provider-specific rate limits based on actual usage
Expand Down
10 changes: 4 additions & 6 deletions src/ctx-embeddings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* https://www.anthropic.com/news/contextual-retrieval
* https://github.com/anthropics/anthropic-cookbook/blob/main/skills/contextual-embeddings/guide.ipynb
*/
import { logger } from '@elizaos/core';

/**
* Default token size settings for chunking and context generation.
Expand Down Expand Up @@ -281,7 +282,7 @@ export function getContextualizationPrompt(
promptTemplate = CONTEXTUAL_CHUNK_ENRICHMENT_PROMPT_TEMPLATE
): string {
if (!docContent || !chunkContent) {
console.warn('Document content or chunk content is missing for contextualization.');
logger.warn({ src: 'plugin:knowledge' }, 'Document content or chunk content is missing for contextualization');
return 'Error: Document or chunk content missing.';
}

Expand Down Expand Up @@ -319,7 +320,7 @@ export function getCachingContextualizationPrompt(
maxTokens = CONTEXT_TARGETS.DEFAULT.MAX_TOKENS
): { prompt: string; systemPrompt: string } {
if (!chunkContent) {
console.warn('Chunk content is missing for contextualization.');
logger.warn({ src: 'plugin:knowledge' }, 'Chunk content is missing for contextualization');
return {
prompt: 'Error: Chunk content missing.',
systemPrompt: SYSTEM_PROMPTS.DEFAULT,
Expand Down Expand Up @@ -403,11 +404,9 @@ export function getPromptForMimeType(
minTokens = CONTEXT_TARGETS.MATH_PDF.MIN_TOKENS;
maxTokens = CONTEXT_TARGETS.MATH_PDF.MAX_TOKENS;
promptTemplate = MATH_PDF_PROMPT_TEMPLATE;
console.debug('Using mathematical PDF prompt template');
} else {
minTokens = CONTEXT_TARGETS.PDF.MIN_TOKENS;
maxTokens = CONTEXT_TARGETS.PDF.MAX_TOKENS;
console.debug('Using standard PDF settings');
}
} else if (
mimeType.includes('javascript') ||
Expand All @@ -420,7 +419,6 @@ export function getPromptForMimeType(
minTokens = CONTEXT_TARGETS.CODE.MIN_TOKENS;
maxTokens = CONTEXT_TARGETS.CODE.MAX_TOKENS;
promptTemplate = CODE_PROMPT_TEMPLATE;
console.debug('Using code prompt template');
} else if (
isTechnicalDocumentation(docContent) ||
mimeType.includes('markdown') ||
Expand Down Expand Up @@ -606,7 +604,7 @@ function isTechnicalDocumentation(content: string): boolean {
*/
export function getChunkWithContext(chunkContent: string, generatedContext: string): string {
if (!generatedContext || generatedContext.trim() === '') {
console.warn('Generated context is empty. Falling back to original chunk content.');
logger.warn({ src: 'plugin:knowledge' }, 'Generated context is empty. Falling back to original chunk content');
return chunkContent;
}

Expand Down
35 changes: 17 additions & 18 deletions src/docs-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ export function getKnowledgePath(runtimePath?: string): string {
const resolvedPath = path.resolve(knowledgePath);

if (!fs.existsSync(resolvedPath)) {
logger.warn(`Knowledge path does not exist: ${resolvedPath}`);
logger.warn({ src: 'plugin:knowledge', knowledgePath: resolvedPath }, 'Knowledge path does not exist');
if (runtimePath) {
logger.warn('Please create the directory or update KNOWLEDGE_PATH in agent settings');
logger.warn({ src: 'plugin:knowledge' }, 'Please create the directory or update KNOWLEDGE_PATH in agent settings');
} else if (process.env.KNOWLEDGE_PATH) {
logger.warn('Please create the directory or update KNOWLEDGE_PATH environment variable');
logger.warn({ src: 'plugin:knowledge' }, 'Please create the directory or update KNOWLEDGE_PATH environment variable');
} else {
logger.info('To use the knowledge plugin, either:');
logger.info('1. Create a "docs" folder in your project root');
logger.info('2. Set KNOWLEDGE_PATH in agent settings or environment variable');
logger.info({ src: 'plugin:knowledge' }, 'To use the knowledge plugin, create a docs folder or set KNOWLEDGE_PATH');
}
}

Expand All @@ -42,21 +40,21 @@ export async function loadDocsFromPath(
const docsPath = getKnowledgePath(knowledgePath);

if (!fs.existsSync(docsPath)) {
logger.warn(`Knowledge path does not exist: ${docsPath}`);
logger.warn({ src: 'plugin:knowledge', agentId: agentId, docsPath }, 'Knowledge path does not exist');
return { total: 0, successful: 0, failed: 0 };
}

logger.info(`Loading documents from: ${docsPath}`);
logger.info({ src: 'plugin:knowledge', agentId: agentId, docsPath }, 'Loading documents from path');

// Get all files recursively
const files = getAllFiles(docsPath);

if (files.length === 0) {
logger.info('No files found in knowledge path');
logger.info({ src: 'plugin:knowledge', agentId: agentId }, 'No files found in knowledge path');
return { total: 0, successful: 0, failed: 0 };
}

logger.info(`Found ${files.length} files to process`);
logger.info({ src: 'plugin:knowledge', agentId: agentId, filesCount: files.length }, 'Found files to process');

Comment on lines 42 to 58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Resolve ESLint object-shorthand on logging objects

ESLint is complaining about non-shorthand properties like agentId: agentId and docsPath: docsPath in this file. Using shorthand will fix it:

- logger.info({ src: 'plugin:knowledge', agentId: agentId, docsPath }, 'Loading documents from path');
+ logger.info({ src: 'plugin:knowledge', agentId, docsPath }, 'Loading documents from path');

Apply similar changes to the other logs noted in the static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!fs.existsSync(docsPath)) {
logger.warn(`Knowledge path does not exist: ${docsPath}`);
logger.warn({ src: 'plugin:knowledge', agentId: agentId, docsPath }, 'Knowledge path does not exist');
return { total: 0, successful: 0, failed: 0 };
}
logger.info(`Loading documents from: ${docsPath}`);
logger.info({ src: 'plugin:knowledge', agentId: agentId, docsPath }, 'Loading documents from path');
// Get all files recursively
const files = getAllFiles(docsPath);
if (files.length === 0) {
logger.info('No files found in knowledge path');
logger.info({ src: 'plugin:knowledge', agentId: agentId }, 'No files found in knowledge path');
return { total: 0, successful: 0, failed: 0 };
}
logger.info(`Found ${files.length} files to process`);
logger.info({ src: 'plugin:knowledge', agentId: agentId, filesCount: files.length }, 'Found files to process');
if (!fs.existsSync(docsPath)) {
logger.warn({ src: 'plugin:knowledge', agentId, docsPath }, 'Knowledge path does not exist');
return { total: 0, successful: 0, failed: 0 };
}
logger.info({ src: 'plugin:knowledge', agentId, docsPath }, 'Loading documents from path');
// Get all files recursively
const files = getAllFiles(docsPath);
if (files.length === 0) {
logger.info({ src: 'plugin:knowledge', agentId }, 'No files found in knowledge path');
return { total: 0, successful: 0, failed: 0 };
}
logger.info({ src: 'plugin:knowledge', agentId, filesCount: files.length }, 'Found files to process');
🧰 Tools
🪛 ESLint

[error] 43-43: Expected property shorthand.

(object-shorthand)


[error] 47-47: Expected property shorthand.

(object-shorthand)


[error] 53-53: Expected property shorthand.

(object-shorthand)


[error] 57-57: Expected property shorthand.

(object-shorthand)

🤖 Prompt for AI Agents
In src/docs-loader.ts around lines 42 to 58, the logger calls use full object
property syntax (e.g., agentId: agentId, docsPath: docsPath) which trips ESLint
object-shorthand; change those to use property shorthand (agentId, docsPath,
filesCount) in each logger call in this block and apply the same shorthand
refactor to other logging objects referenced in the static analysis hints
elsewhere in the file.

let successful = 0;
let failed = 0;
Expand All @@ -76,7 +74,7 @@ export async function loadDocsFromPath(

// Skip unsupported file types
if (!contentType) {
logger.debug(`Skipping unsupported file type: ${filePath}`);
logger.debug({ src: 'plugin:knowledge', agentId: agentId, filePath }, 'Skipping unsupported file type');
continue;
}

Expand All @@ -102,19 +100,20 @@ export async function loadDocsFromPath(
};

// Process the document
logger.debug(`Processing document: ${fileName}`);
logger.debug({ src: 'plugin:knowledge', agentId: agentId, filename: fileName }, 'Processing document');
const result = await service.addKnowledge(knowledgeOptions);

logger.info(`✅ "${fileName}": ${result.fragmentCount} fragments created`);
logger.info({ src: 'plugin:knowledge', agentId: agentId, filename: fileName, fragmentCount: result.fragmentCount }, 'Document processed');
successful++;
} catch (error) {
logger.error({ error }, `Failed to process file ${filePath}`);
} catch (error: any) {
logger.error({ src: 'plugin:knowledge', agentId: agentId, filePath, error: error.message }, 'Failed to process file');
failed++;
}
}

logger.info(
`Document loading complete: ${successful} successful, ${failed} failed out of ${files.length} total`
{ src: 'plugin:knowledge', agentId: agentId, successful, failed, total: files.length },
'Document loading complete'
);

return {
Expand Down Expand Up @@ -143,8 +142,8 @@ function getAllFiles(dirPath: string, files: string[] = []): string[] {
files.push(fullPath);
}
}
} catch (error) {
logger.error({ error }, `Error reading directory ${dirPath}`);
} catch (error: any) {
logger.error({ src: 'plugin:knowledge', dirPath, error: error.message }, 'Error reading directory');
}

return files;
Expand Down
Loading