forked from elizaOS/eliza
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request elizaOS#862 from ai16z-demirix/main
test: adding environment and knowledge tests
- Loading branch information
Showing
2 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'; | ||
import { validateEnv, validateCharacterConfig } from '../environment'; | ||
import { Clients, ModelProviderName } from '../types'; | ||
|
||
describe('Environment Configuration', () => { | ||
const originalEnv = process.env; | ||
|
||
beforeEach(() => { | ||
process.env = { | ||
...originalEnv, | ||
OPENAI_API_KEY: 'sk-test123', | ||
REDPILL_API_KEY: 'test-key', | ||
GROK_API_KEY: 'test-key', | ||
GROQ_API_KEY: 'gsk_test123', | ||
OPENROUTER_API_KEY: 'test-key', | ||
GOOGLE_GENERATIVE_AI_API_KEY: 'test-key', | ||
ELEVENLABS_XI_API_KEY: 'test-key', | ||
}; | ||
}); | ||
|
||
afterEach(() => { | ||
process.env = originalEnv; | ||
}); | ||
|
||
it('should validate correct environment variables', () => { | ||
expect(() => validateEnv()).not.toThrow(); | ||
}); | ||
|
||
it('should throw error for invalid OpenAI API key format', () => { | ||
process.env.OPENAI_API_KEY = 'invalid-key'; | ||
expect(() => validateEnv()).toThrow("OpenAI API key must start with 'sk-'"); | ||
}); | ||
|
||
it('should throw error for invalid GROQ API key format', () => { | ||
process.env.GROQ_API_KEY = 'invalid-key'; | ||
expect(() => validateEnv()).toThrow("GROQ API key must start with 'gsk_'"); | ||
}); | ||
|
||
it('should throw error for missing required keys', () => { | ||
delete process.env.REDPILL_API_KEY; | ||
expect(() => validateEnv()).toThrow('REDPILL_API_KEY: Required'); | ||
}); | ||
|
||
it('should throw error for multiple missing required keys', () => { | ||
delete process.env.REDPILL_API_KEY; | ||
delete process.env.GROK_API_KEY; | ||
delete process.env.OPENROUTER_API_KEY; | ||
expect(() => validateEnv()).toThrow( | ||
'Environment validation failed:\n' + | ||
'REDPILL_API_KEY: Required\n' + | ||
'GROK_API_KEY: Required\n' + | ||
'OPENROUTER_API_KEY: Required' | ||
); | ||
}); | ||
}); | ||
|
||
describe('Character Configuration', () => { | ||
const validCharacterConfig = { | ||
name: 'Test Character', | ||
modelProvider: ModelProviderName.OPENAI, | ||
bio: 'Test bio', | ||
lore: ['Test lore'], | ||
messageExamples: [[ | ||
{ | ||
user: 'user1', | ||
content: { | ||
text: 'Hello', | ||
} | ||
} | ||
]], | ||
postExamples: ['Test post'], | ||
topics: ['topic1'], | ||
adjectives: ['friendly'], | ||
clients: [Clients.DISCORD], | ||
plugins: ['test-plugin'], | ||
style: { | ||
all: ['style1'], | ||
chat: ['chat-style'], | ||
post: ['post-style'] | ||
} | ||
}; | ||
|
||
it('should validate correct character configuration', () => { | ||
expect(() => validateCharacterConfig(validCharacterConfig)).not.toThrow(); | ||
}); | ||
|
||
it('should validate configuration with optional fields', () => { | ||
const configWithOptionals = { | ||
...validCharacterConfig, | ||
id: '123e4567-e89b-12d3-a456-426614174000', | ||
system: 'Test system', | ||
templates: { | ||
greeting: 'Hello!' | ||
}, | ||
knowledge: ['fact1'], | ||
settings: { | ||
secrets: { | ||
key: 'value' | ||
}, | ||
voice: { | ||
model: 'test-model', | ||
url: 'http://example.com' | ||
} | ||
} | ||
}; | ||
expect(() => validateCharacterConfig(configWithOptionals)).not.toThrow(); | ||
}); | ||
|
||
it('should throw error for missing required fields', () => { | ||
const invalidConfig = { ...validCharacterConfig }; | ||
delete (invalidConfig as any).name; | ||
expect(() => validateCharacterConfig(invalidConfig)).toThrow(); | ||
}); | ||
|
||
it('should validate plugin objects in plugins array', () => { | ||
const configWithPluginObjects = { | ||
...validCharacterConfig, | ||
plugins: [{ | ||
name: 'test-plugin', | ||
description: 'Test description' | ||
}] | ||
}; | ||
expect(() => validateCharacterConfig(configWithPluginObjects)).not.toThrow(); | ||
}); | ||
|
||
it('should validate client-specific configurations', () => { | ||
const configWithClientConfig = { | ||
...validCharacterConfig, | ||
clientConfig: { | ||
discord: { | ||
shouldIgnoreBotMessages: true, | ||
shouldIgnoreDirectMessages: false | ||
}, | ||
telegram: { | ||
shouldIgnoreBotMessages: true, | ||
shouldIgnoreDirectMessages: true | ||
} | ||
} | ||
}; | ||
expect(() => validateCharacterConfig(configWithClientConfig)).not.toThrow(); | ||
}); | ||
|
||
it('should validate twitter profile configuration', () => { | ||
const configWithTwitter = { | ||
...validCharacterConfig, | ||
twitterProfile: { | ||
username: 'testuser', | ||
screenName: 'Test User', | ||
bio: 'Test bio', | ||
nicknames: ['test'] | ||
} | ||
}; | ||
expect(() => validateCharacterConfig(configWithTwitter)).not.toThrow(); | ||
}); | ||
|
||
it('should validate model endpoint override', () => { | ||
const configWithEndpoint = { | ||
...validCharacterConfig, | ||
modelEndpointOverride: 'custom-endpoint' | ||
}; | ||
expect(() => validateCharacterConfig(configWithEndpoint)).not.toThrow(); | ||
}); | ||
|
||
it('should validate message examples with additional properties', () => { | ||
const configWithComplexMessage = { | ||
...validCharacterConfig, | ||
messageExamples: [[{ | ||
user: 'user1', | ||
content: { | ||
text: 'Hello', | ||
action: 'wave', | ||
source: 'chat', | ||
url: 'http://example.com', | ||
inReplyTo: '123e4567-e89b-12d3-a456-426614174000', | ||
attachments: ['file1'], | ||
customField: 'value' | ||
} | ||
}]] | ||
}; | ||
expect(() => validateCharacterConfig(configWithComplexMessage)).not.toThrow(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
import knowledge from '../knowledge'; | ||
import { AgentRuntime } from '../runtime'; | ||
import { KnowledgeItem, Memory } from '../types'; | ||
import { getEmbeddingZeroVector } from '../embedding'; | ||
|
||
// Mock dependencies | ||
vi.mock('../embedding', () => ({ | ||
embed: vi.fn().mockResolvedValue(new Float32Array(1536).fill(0)), | ||
getEmbeddingZeroVector: vi.fn().mockReturnValue(new Float32Array(1536).fill(0)) | ||
})); | ||
|
||
vi.mock('../generation', () => ({ | ||
splitChunks: vi.fn().mockImplementation(async (text) => [text]) | ||
})); | ||
|
||
vi.mock('../uuid', () => ({ | ||
stringToUuid: vi.fn().mockImplementation((str) => str) | ||
})); | ||
|
||
describe('Knowledge Module', () => { | ||
describe('preprocess', () => { | ||
it('should handle invalid inputs', () => { | ||
expect(knowledge.preprocess(null)).toBe(''); | ||
expect(knowledge.preprocess(undefined)).toBe(''); | ||
expect(knowledge.preprocess('')).toBe(''); | ||
}); | ||
|
||
it('should remove code blocks and inline code', () => { | ||
const input = 'Here is some code: ```const x = 1;``` and `inline code`'; | ||
expect(knowledge.preprocess(input)).toBe('here is some code: and'); | ||
}); | ||
|
||
it('should handle markdown formatting', () => { | ||
const input = '# Header\n## Subheader\n[Link](http://example.com)\n![Image](image.jpg)'; | ||
expect(knowledge.preprocess(input)).toBe('header subheader link image'); | ||
}); | ||
|
||
it('should simplify URLs', () => { | ||
const input = 'Visit https://www.example.com/path?param=value'; | ||
expect(knowledge.preprocess(input)).toBe('visit example.com/path?param=value'); | ||
}); | ||
|
||
it('should remove Discord mentions and HTML tags', () => { | ||
const input = 'Hello <@123456789> and <div>HTML content</div>'; | ||
expect(knowledge.preprocess(input)).toBe('hello and html content'); | ||
}); | ||
|
||
it('should normalize whitespace and newlines', () => { | ||
const input = 'Multiple spaces\n\n\nand\nnewlines'; | ||
expect(knowledge.preprocess(input)).toBe('multiple spaces and newlines'); | ||
}); | ||
|
||
it('should remove comments', () => { | ||
const input = '/* Block comment */ Normal text // Line comment'; | ||
expect(knowledge.preprocess(input)).toBe('normal text'); | ||
}); | ||
}); | ||
|
||
describe('get and set', () => { | ||
let mockRuntime: AgentRuntime; | ||
|
||
beforeEach(() => { | ||
mockRuntime = { | ||
agentId: 'test-agent', | ||
knowledgeManager: { | ||
searchMemoriesByEmbedding: vi.fn().mockResolvedValue([ | ||
{ | ||
content: { text: 'test fragment', source: 'source1' }, | ||
similarity: 0.9 | ||
} | ||
]), | ||
createMemory: vi.fn().mockResolvedValue(undefined) | ||
}, | ||
documentsManager: { | ||
getMemoryById: vi.fn().mockResolvedValue({ | ||
id: 'source1', | ||
content: { text: 'test document' } | ||
}), | ||
createMemory: vi.fn().mockResolvedValue(undefined) | ||
} | ||
} as unknown as AgentRuntime; | ||
}); | ||
|
||
describe('get', () => { | ||
it('should handle invalid messages', async () => { | ||
const invalidMessage = {} as Memory; | ||
const result = await knowledge.get(mockRuntime, invalidMessage); | ||
expect(result).toEqual([]); | ||
}); | ||
|
||
it('should retrieve knowledge items based on message content', async () => { | ||
const message: Memory = { | ||
agentId: 'test-agent', | ||
content: { text: 'test query' } | ||
} as unknown as Memory; | ||
|
||
const result = await knowledge.get(mockRuntime, message); | ||
|
||
expect(result).toHaveLength(1); | ||
expect(result[0]).toEqual({ | ||
id: 'source1', | ||
content: { text: 'test document' } | ||
}); | ||
}); | ||
|
||
it('should handle empty processed text', async () => { | ||
const message: Memory = { | ||
agentId: 'test-agent', | ||
content: { text: '```code only```' } | ||
} as unknown as Memory; | ||
|
||
const result = await knowledge.get(mockRuntime, message); | ||
expect(result).toEqual([]); | ||
}); | ||
}); | ||
|
||
describe('set', () => { | ||
it('should store knowledge item and its fragments', async () => { | ||
const item: KnowledgeItem = { | ||
id: 'test-id-1234-5678-9101-112131415161', | ||
content: { text: 'test content' } | ||
}; | ||
|
||
await knowledge.set(mockRuntime, item); | ||
|
||
// Check if document was created | ||
expect(mockRuntime.documentsManager.createMemory).toHaveBeenCalledWith( | ||
expect.objectContaining({ | ||
id: item.id, | ||
content: item.content, | ||
embedding: getEmbeddingZeroVector() | ||
}) | ||
); | ||
|
||
// Check if fragment was created | ||
expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledWith( | ||
expect.objectContaining({ | ||
content: { | ||
source: item.id, | ||
text: expect.any(String) | ||
}, | ||
embedding: expect.any(Float32Array) | ||
}) | ||
); | ||
}); | ||
|
||
it('should use default chunk size and bleed', async () => { | ||
const item: KnowledgeItem = { | ||
id: 'test-id-1234-5678-9101-112131415161', | ||
content: { text: 'test content' } | ||
}; | ||
|
||
await knowledge.set(mockRuntime, item); | ||
|
||
// Verify default parameters were used | ||
expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); | ||
}); |