0d3cb498a3
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
783 lines
22 KiB
TypeScript
783 lines
22 KiB
TypeScript
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import logger from '../../../src/logger';
|
|
import { AwsBedrockKnowledgeBaseProvider } from '../../../src/providers/bedrock/knowledgeBase';
|
|
import { sha256 } from '../../../src/util/createHash';
|
|
import { createEmptyTokenUsage } from '../../../src/util/tokenUsageUtils';
|
|
import { mockProcessEnv } from '../../util/utils';
|
|
|
|
const mockSend = vi.fn();
|
|
const mockBedrockClient = {
|
|
send: mockSend,
|
|
};
|
|
|
|
vi.mock('@aws-sdk/client-bedrock-agent-runtime', async (importOriginal) => {
|
|
return {
|
|
...(await importOriginal()),
|
|
|
|
BedrockAgentRuntimeClient: vi.fn().mockImplementation(function () {
|
|
return mockBedrockClient;
|
|
}),
|
|
|
|
RetrieveAndGenerateCommand: vi.fn().mockImplementation(function (params) {
|
|
return params;
|
|
}),
|
|
};
|
|
});
|
|
|
|
// Module imports - loaded in beforeAll
|
|
let BedrockAgentRuntimeClient: typeof import('@aws-sdk/client-bedrock-agent-runtime').BedrockAgentRuntimeClient;
|
|
let RetrieveAndGenerateCommand: typeof import('@aws-sdk/client-bedrock-agent-runtime').RetrieveAndGenerateCommand;
|
|
const NodeHttpHandlerMock = vi.mocked(NodeHttpHandler);
|
|
|
|
// Mock @smithy/node-http-handler with ESM-compatible exports
|
|
vi.mock('@smithy/node-http-handler', () => ({
|
|
__esModule: true,
|
|
NodeHttpHandler: vi.fn().mockImplementation(function () {
|
|
return {
|
|
handle: vi.fn(),
|
|
};
|
|
}),
|
|
default: vi.fn().mockImplementation(function () {
|
|
return {
|
|
handle: vi.fn(),
|
|
};
|
|
}),
|
|
}));
|
|
|
|
// Mock proxy-agent with ESM-compatible exports
|
|
vi.mock('proxy-agent', () => ({
|
|
__esModule: true,
|
|
ProxyAgent: vi.fn(function ProxyAgentMock() {}),
|
|
default: vi.fn(function ProxyAgentMock() {}),
|
|
}));
|
|
|
|
vi.mock('../../../src/logger', () => ({
|
|
default: {
|
|
debug: vi.fn(),
|
|
error: vi.fn(),
|
|
warn: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
const mockGet = vi.hoisted(() => vi.fn());
|
|
|
|
const mockSet = vi.hoisted(() => vi.fn());
|
|
|
|
const mockIsCacheEnabled = vi.fn().mockReturnValue(false);
|
|
|
|
function buildKnowledgeBaseCacheKey({
|
|
knowledgeBaseId,
|
|
modelArn,
|
|
modelName,
|
|
prompt,
|
|
region,
|
|
kbConfig,
|
|
}: {
|
|
knowledgeBaseId: string;
|
|
modelArn?: string;
|
|
modelName: string;
|
|
prompt: string;
|
|
region: string;
|
|
kbConfig: Record<string, unknown>;
|
|
}) {
|
|
const cacheConfig = {
|
|
region,
|
|
modelName,
|
|
...Object.fromEntries(
|
|
Object.entries(kbConfig).filter(
|
|
([key]) => !['accessKeyId', 'secretAccessKey', 'sessionToken'].includes(key),
|
|
),
|
|
),
|
|
};
|
|
const configStr = JSON.stringify(cacheConfig, Object.keys(cacheConfig).sort());
|
|
|
|
return `bedrock-kb:${knowledgeBaseId}:${modelArn}:${region}:${sha256(
|
|
JSON.stringify({
|
|
configStr,
|
|
prompt,
|
|
}),
|
|
)}`;
|
|
}
|
|
|
|
vi.mock('../../../src/cache', async (importOriginal) => {
|
|
return {
|
|
...(await importOriginal()),
|
|
|
|
getCache: vi.fn().mockImplementation(function () {
|
|
return {
|
|
get: mockGet,
|
|
set: mockSet,
|
|
};
|
|
}),
|
|
|
|
isCacheEnabled: () => mockIsCacheEnabled(),
|
|
};
|
|
});
|
|
|
|
describe('AwsBedrockKnowledgeBaseProvider', () => {
|
|
beforeAll(async () => {
|
|
const bedrockModule = await import('@aws-sdk/client-bedrock-agent-runtime');
|
|
BedrockAgentRuntimeClient = bedrockModule.BedrockAgentRuntimeClient;
|
|
RetrieveAndGenerateCommand = bedrockModule.RetrieveAndGenerateCommand;
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockGet.mockReset();
|
|
mockSet.mockReset();
|
|
mockIsCacheEnabled.mockReset().mockReturnValue(false);
|
|
mockProcessEnv({ AWS_BEDROCK_MAX_RETRIES: undefined });
|
|
mockProcessEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined });
|
|
mockProcessEnv({ HTTPS_PROXY: undefined });
|
|
mockProcessEnv({ https_proxy: undefined });
|
|
mockProcessEnv({ HTTP_PROXY: undefined });
|
|
mockProcessEnv({ http_proxy: undefined });
|
|
mockProcessEnv({ npm_config_https_proxy: undefined });
|
|
mockProcessEnv({ npm_config_http_proxy: undefined });
|
|
mockProcessEnv({ npm_config_proxy: undefined });
|
|
mockProcessEnv({ all_proxy: undefined });
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
mockProcessEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined });
|
|
mockProcessEnv({ HTTPS_PROXY: undefined });
|
|
mockProcessEnv({ https_proxy: undefined });
|
|
mockProcessEnv({ HTTP_PROXY: undefined });
|
|
mockProcessEnv({ http_proxy: undefined });
|
|
mockProcessEnv({ npm_config_https_proxy: undefined });
|
|
mockProcessEnv({ npm_config_http_proxy: undefined });
|
|
mockProcessEnv({ npm_config_proxy: undefined });
|
|
mockProcessEnv({ all_proxy: undefined });
|
|
});
|
|
|
|
it('should throw an error if knowledgeBaseId is not provided', () => {
|
|
expect(() => {
|
|
new AwsBedrockKnowledgeBaseProvider('us.anthropic.claude-3-7-sonnet-20241022-v2:0', {
|
|
config: {} as any,
|
|
});
|
|
}).toThrow('Knowledge Base ID is required');
|
|
});
|
|
|
|
it('should create provider with required options', () => {
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(provider).toBeDefined();
|
|
expect(provider.kbConfig.knowledgeBaseId).toBe('kb-123');
|
|
expect(provider.getRegion()).toBe('us-east-1');
|
|
});
|
|
|
|
it('should create knowledge base client without proxy settings', async () => {
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.getKnowledgeBaseClient();
|
|
|
|
// client-bedrock-agent-runtime already defaults to HTTP/1.1,
|
|
// so no custom handler is needed without proxy or apiKey
|
|
expect(NodeHttpHandlerMock).not.toHaveBeenCalled();
|
|
expect(BedrockAgentRuntimeClient).toHaveBeenCalledWith({
|
|
region: 'us-east-1',
|
|
retryMode: 'adaptive',
|
|
maxAttempts: 10,
|
|
});
|
|
});
|
|
|
|
it('should create knowledge base client with credentials', async () => {
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
accessKeyId: 'test-access-key',
|
|
secretAccessKey: 'test-secret-key',
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.getKnowledgeBaseClient();
|
|
|
|
expect(NodeHttpHandlerMock).not.toHaveBeenCalled();
|
|
expect(BedrockAgentRuntimeClient).toHaveBeenCalledWith({
|
|
region: 'us-east-1',
|
|
retryMode: 'adaptive',
|
|
maxAttempts: 10,
|
|
credentials: {
|
|
accessKeyId: 'test-access-key',
|
|
secretAccessKey: 'test-secret-key',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should respect AWS_BEDROCK_MAX_RETRIES environment variable', async () => {
|
|
mockProcessEnv({ AWS_BEDROCK_MAX_RETRIES: '5' });
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.getKnowledgeBaseClient();
|
|
|
|
expect(NodeHttpHandlerMock).not.toHaveBeenCalled();
|
|
expect(BedrockAgentRuntimeClient).toHaveBeenCalledWith({
|
|
region: 'us-east-1',
|
|
retryMode: 'adaptive',
|
|
maxAttempts: 5,
|
|
});
|
|
});
|
|
|
|
it('should call the knowledge base API with correct parameters', async () => {
|
|
const mockResponse = {
|
|
output: {
|
|
text: 'This is the response from the knowledge base',
|
|
},
|
|
citations: [
|
|
{
|
|
retrievedReferences: [
|
|
{
|
|
content: {
|
|
text: 'This is a citation',
|
|
},
|
|
location: {
|
|
type: 's3',
|
|
s3Location: {
|
|
uri: 's3://bucket/key',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
generatedResponsePart: {
|
|
textResponsePart: {
|
|
text: 'part of the response',
|
|
span: {
|
|
start: 0,
|
|
end: 10,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
mockSend.mockResolvedValueOnce(mockResponse);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
const result = await provider.callApi('What is the capital of France?');
|
|
|
|
const expectedCommand = {
|
|
input: { text: 'What is the capital of France?' },
|
|
retrieveAndGenerateConfiguration: {
|
|
type: 'KNOWLEDGE_BASE',
|
|
knowledgeBaseConfiguration: {
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(RetrieveAndGenerateCommand).toHaveBeenCalledWith(expectedCommand);
|
|
expect(mockSend).toHaveBeenCalledWith(expectedCommand);
|
|
expect(result).toEqual({
|
|
output: 'This is the response from the knowledge base',
|
|
metadata: { citations: mockResponse.citations },
|
|
tokenUsage: createEmptyTokenUsage(),
|
|
});
|
|
});
|
|
|
|
it('should handle API errors gracefully', async () => {
|
|
mockSend.mockRejectedValueOnce(new Error('API error'));
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
const result = await provider.callApi('What is the capital of France?');
|
|
|
|
expect(result).toEqual({
|
|
error: 'Bedrock Knowledge Base API error: Error: API error',
|
|
});
|
|
});
|
|
|
|
it('should use custom modelArn if provided', async () => {
|
|
const mockResponse = {
|
|
output: {
|
|
text: 'This is the response from the knowledge base',
|
|
},
|
|
citations: [],
|
|
};
|
|
|
|
mockSend.mockResolvedValueOnce(mockResponse);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider('amazon.nova-lite-v1:0', {
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
modelArn: 'custom:model:arn',
|
|
},
|
|
});
|
|
|
|
await provider.callApi('What is the capital of France?');
|
|
|
|
const expectedCommand = {
|
|
input: { text: 'What is the capital of France?' },
|
|
retrieveAndGenerateConfiguration: {
|
|
type: 'KNOWLEDGE_BASE',
|
|
knowledgeBaseConfiguration: {
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'custom:model:arn',
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(RetrieveAndGenerateCommand).toHaveBeenCalledWith(expectedCommand);
|
|
});
|
|
|
|
it('should pass along config parameters but not create generationConfiguration', async () => {
|
|
const mockResponse = {
|
|
output: {
|
|
text: 'This is the response from the knowledge base',
|
|
},
|
|
citations: [],
|
|
};
|
|
|
|
mockSend.mockResolvedValueOnce(mockResponse);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider('amazon.nova-lite-v1:0', {
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
} as any,
|
|
});
|
|
|
|
await provider.callApi('What is the capital of France?');
|
|
|
|
const expectedCommand = {
|
|
input: { text: 'What is the capital of France?' },
|
|
retrieveAndGenerateConfiguration: {
|
|
type: 'KNOWLEDGE_BASE',
|
|
knowledgeBaseConfiguration: {
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-lite-v1:0',
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(RetrieveAndGenerateCommand).toHaveBeenCalledWith(expectedCommand);
|
|
});
|
|
|
|
it('should not include retrievalConfiguration when numberOfResults is not provided', async () => {
|
|
const mockResponse = {
|
|
output: {
|
|
text: 'This is the response from the knowledge base',
|
|
},
|
|
citations: [],
|
|
};
|
|
|
|
mockSend.mockResolvedValueOnce(mockResponse);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.callApi('What is the capital of France?');
|
|
|
|
const expectedCommand = {
|
|
input: { text: 'What is the capital of France?' },
|
|
retrieveAndGenerateConfiguration: {
|
|
type: 'KNOWLEDGE_BASE',
|
|
knowledgeBaseConfiguration: {
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(RetrieveAndGenerateCommand).toHaveBeenCalledWith(expectedCommand);
|
|
});
|
|
|
|
it('should use custom numberOfResults when provided', async () => {
|
|
const mockResponse = {
|
|
output: {
|
|
text: 'This is the response from the knowledge base',
|
|
},
|
|
citations: [],
|
|
};
|
|
|
|
mockSend.mockResolvedValueOnce(mockResponse);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
numberOfResults: 10,
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.callApi('What is the capital of France?');
|
|
|
|
const expectedCommand = {
|
|
input: { text: 'What is the capital of France?' },
|
|
retrieveAndGenerateConfiguration: {
|
|
type: 'KNOWLEDGE_BASE',
|
|
knowledgeBaseConfiguration: {
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
retrievalConfiguration: {
|
|
vectorSearchConfiguration: {
|
|
numberOfResults: 10,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(RetrieveAndGenerateCommand).toHaveBeenCalledWith(expectedCommand);
|
|
});
|
|
|
|
it('should retrieve citations from cache when available', async () => {
|
|
mockIsCacheEnabled.mockReturnValue(true);
|
|
|
|
const cachedResponse = JSON.stringify({
|
|
output: 'Cached response from knowledge base',
|
|
citations: [
|
|
{
|
|
retrievedReferences: [
|
|
{
|
|
content: { text: 'Citation from cache' },
|
|
location: { s3Location: { uri: 'https://example.com/cached' } },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockGet.mockResolvedValueOnce(cachedResponse);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
const result = await provider.callApi('What is the capital of France?');
|
|
|
|
const cacheKey = mockGet.mock.calls[0][0];
|
|
|
|
expect(cacheKey).toBe(
|
|
buildKnowledgeBaseCacheKey({
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
modelName: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
prompt: 'What is the capital of France?',
|
|
region: 'us-east-1',
|
|
kbConfig: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
}),
|
|
);
|
|
expect(cacheKey).not.toContain('What is the capital of France?');
|
|
const cacheHitLog = vi
|
|
.mocked(logger.debug)
|
|
.mock.calls.find(
|
|
([message]) => message === 'Returning cached Bedrock Knowledge Base response',
|
|
);
|
|
expect(cacheHitLog).toBeDefined();
|
|
expect(JSON.stringify(cacheHitLog)).not.toContain('What is the capital of France?');
|
|
|
|
expect(result).toEqual({
|
|
output: 'Cached response from knowledge base',
|
|
metadata: {
|
|
citations: [
|
|
{
|
|
retrievedReferences: [
|
|
{
|
|
content: { text: 'Citation from cache' },
|
|
location: { s3Location: { uri: 'https://example.com/cached' } },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
tokenUsage: createEmptyTokenUsage(),
|
|
cached: true,
|
|
});
|
|
|
|
mockIsCacheEnabled.mockReturnValue(false);
|
|
});
|
|
|
|
it('should use custom modelArn in cache key when provided', async () => {
|
|
mockIsCacheEnabled.mockReturnValue(true);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider('amazon.nova-lite-v1:0', {
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
modelArn: 'custom:model:arn',
|
|
},
|
|
});
|
|
|
|
mockGet.mockResolvedValueOnce(null);
|
|
|
|
const mockResponse = {
|
|
output: {
|
|
text: 'Response with custom model ARN',
|
|
},
|
|
citations: [],
|
|
};
|
|
mockSend.mockResolvedValueOnce(mockResponse);
|
|
|
|
await provider.callApi('What is the capital of France?');
|
|
|
|
const cacheKey = mockGet.mock.calls[0][0];
|
|
|
|
expect(cacheKey).toBe(
|
|
buildKnowledgeBaseCacheKey({
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'custom:model:arn',
|
|
modelName: 'amazon.nova-lite-v1:0',
|
|
prompt: 'What is the capital of France?',
|
|
region: 'us-east-1',
|
|
kbConfig: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
modelArn: 'custom:model:arn',
|
|
},
|
|
}),
|
|
);
|
|
expect(cacheKey).not.toContain('What is the capital of France?');
|
|
|
|
expect(mockSet).toHaveBeenCalledWith(cacheKey, expect.any(String));
|
|
|
|
mockIsCacheEnabled.mockReturnValue(false);
|
|
});
|
|
|
|
it('should include numberOfResults in cache key when provided', async () => {
|
|
mockIsCacheEnabled.mockReturnValue(true);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
numberOfResults: 10,
|
|
},
|
|
},
|
|
);
|
|
|
|
mockGet.mockResolvedValueOnce(null);
|
|
|
|
const mockResponse = {
|
|
output: {
|
|
text: 'Response with custom numberOfResults',
|
|
},
|
|
citations: [],
|
|
};
|
|
mockSend.mockResolvedValueOnce(mockResponse);
|
|
|
|
await provider.callApi('What is the capital of France?');
|
|
|
|
const cacheKey = mockGet.mock.calls[0][0];
|
|
|
|
expect(cacheKey).toBe(
|
|
buildKnowledgeBaseCacheKey({
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
modelName: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
prompt: 'What is the capital of France?',
|
|
region: 'us-east-1',
|
|
kbConfig: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
numberOfResults: 10,
|
|
},
|
|
}),
|
|
);
|
|
expect(cacheKey).not.toContain('What is the capital of France?');
|
|
|
|
expect(mockSet).toHaveBeenCalledWith(cacheKey, expect.any(String));
|
|
|
|
mockIsCacheEnabled.mockReturnValue(false);
|
|
});
|
|
|
|
it('should hash prompt and config values in the cache key', async () => {
|
|
mockIsCacheEnabled.mockReturnValue(true);
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
apiKey: 'SECRET_API_KEY',
|
|
modelArn: 'custom:model:arn',
|
|
},
|
|
},
|
|
);
|
|
|
|
mockGet.mockResolvedValueOnce(null);
|
|
mockSend.mockResolvedValueOnce({
|
|
output: {
|
|
text: 'SECRET_RESPONSE_VALUE',
|
|
},
|
|
citations: [{ retrievedReferences: [{ content: { text: 'SECRET_CITATION_VALUE' } }] }],
|
|
});
|
|
|
|
await provider.callApi('SECRET_PROMPT_VALUE');
|
|
|
|
const cacheKey = mockGet.mock.calls[0][0];
|
|
const debugLogs = JSON.stringify(vi.mocked(logger.debug).mock.calls);
|
|
|
|
expect(cacheKey).not.toContain('SECRET_PROMPT_VALUE');
|
|
expect(cacheKey).not.toContain('SECRET_API_KEY');
|
|
expect(debugLogs).not.toContain('SECRET_PROMPT_VALUE');
|
|
expect(debugLogs).not.toContain('SECRET_RESPONSE_VALUE');
|
|
expect(debugLogs).not.toContain('SECRET_CITATION_VALUE');
|
|
expect(cacheKey).toBe(
|
|
buildKnowledgeBaseCacheKey({
|
|
knowledgeBaseId: 'kb-123',
|
|
modelArn: 'custom:model:arn',
|
|
modelName: 'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
prompt: 'SECRET_PROMPT_VALUE',
|
|
region: 'us-east-1',
|
|
kbConfig: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
apiKey: 'SECRET_API_KEY',
|
|
modelArn: 'custom:model:arn',
|
|
},
|
|
}),
|
|
);
|
|
|
|
mockIsCacheEnabled.mockReturnValue(false);
|
|
});
|
|
|
|
it('should create knowledge base client with API key authentication from config', async () => {
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
apiKey: 'test-api-key',
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.getKnowledgeBaseClient();
|
|
|
|
expect(BedrockAgentRuntimeClient).toHaveBeenCalledWith({
|
|
region: 'us-east-1',
|
|
retryMode: 'adaptive',
|
|
maxAttempts: 10,
|
|
requestHandler: expect.any(Object),
|
|
});
|
|
});
|
|
|
|
it('should create knowledge base client with API key authentication from environment', async () => {
|
|
mockProcessEnv({ AWS_BEARER_TOKEN_BEDROCK: 'test-env-api-key' });
|
|
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.getKnowledgeBaseClient();
|
|
|
|
expect(BedrockAgentRuntimeClient).toHaveBeenCalledWith({
|
|
region: 'us-east-1',
|
|
retryMode: 'adaptive',
|
|
maxAttempts: 10,
|
|
requestHandler: expect.any(Object),
|
|
});
|
|
|
|
mockProcessEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined });
|
|
});
|
|
|
|
it('should prioritize explicit credentials over API key for knowledge base', async () => {
|
|
const provider = new AwsBedrockKnowledgeBaseProvider(
|
|
'us.anthropic.claude-3-7-sonnet-20241022-v2:0',
|
|
{
|
|
config: {
|
|
knowledgeBaseId: 'kb-123',
|
|
region: 'us-east-1',
|
|
apiKey: 'test-api-key',
|
|
accessKeyId: 'test-access-key',
|
|
secretAccessKey: 'test-secret-key',
|
|
},
|
|
},
|
|
);
|
|
|
|
await provider.getKnowledgeBaseClient();
|
|
|
|
// Should use explicit credentials (highest priority) instead of API key
|
|
expect(BedrockAgentRuntimeClient).toHaveBeenCalledWith({
|
|
region: 'us-east-1',
|
|
retryMode: 'adaptive',
|
|
maxAttempts: 10,
|
|
credentials: {
|
|
accessKeyId: 'test-access-key',
|
|
secretAccessKey: 'test-secret-key',
|
|
sessionToken: undefined,
|
|
},
|
|
requestHandler: expect.any(Object), // Still has handler for API key scenario
|
|
});
|
|
});
|
|
});
|