Skip to content

Commit

Permalink
Add sourceTag to PineconeConfiguration and additionalHeaders to…
Browse files Browse the repository at this point in the history
… data plane calls (#197)

## Problem
We want to provide the ability for consumers to include a `source_tag`
to identify the source of requests while using the client. Additionally,
there are debugging and development scenarios where being able to pass
additional headers with network requests would be convenient.

## Solution
- Add `sourceTag` to `PineconeConfiguration`. Update `buildUserAgent()`
to take in `PineconeConfiguration`, which should be available at all the
points we call to build the agent. If `sourceTag` is provided it will be
normalized, and inserted into the `User-Agent` header for all network
requests with this format: `source_tag=<normalizeSourceTag>`.
  - Normalization rules:
  - Lowercase
  - Limit to charset `[a-z0-9_ ]`
  - Trim left/right space
  - Condense all spaces to one space and replace with `_`
- Add `additionalHeaders` to the `Index` constructor. Custom headers can
now be provided when targeting a specific index. `Index` now passes
these headers to `VectorOperationsProvider` when setting up the API.
- Update `VectorOperationsProvider` to apply `additionalHeaders` to the
API `Configuration` when provided.

## Type of Change
New feature, but specific to developer / support usage.
- [X] New feature (non-breaking change which adds functionality)

## Test Plan
New unit test files:
- `indexOperationsBuilder.test.ts`
- `user-agent.test.ts`

New unit test for validating `additionalHeaders` is passed as expected,
a bit of refactoring in `index.test.ts` which appears messier than it
is.

Running locally with `PINECONE_DEBUG=true` to make sure the custom
headers / `sourceTag` are passed on requests when provided.

### Passing `integrationId`
```typescript
import { Pinecone } from '@pinecone-database/pinecone'

const pc = new Pinecone({ apiKey: 'your-api-key', sourceTag: 'test source tag' });
const index = pc.Index('my-index');

// Test control + data plane operations
await pc.listIndexes();
await index.upsert(...);
```

### Passing `additionalHeaders` to `Index`
```typescript
import { Pinecone } from '@pinecone-database/pinecone'

const pc = new Pinecone({ apiKey: 'your-api-key', sourceTag: 'test source tag' });
const index = pc.Index('my-index', undefined, { 'x-custom-header': 'header-value' });

// Make requests
await index.upsert(...);
```
  • Loading branch information
austin-denoble authored Mar 27, 2024
1 parent 90e72f9 commit b386099
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 146 deletions.
40 changes: 40 additions & 0 deletions src/control/__tests__/indexOperationsBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { indexOperationsBuilder } from '../indexOperationsBuilder';
import { Configuration } from '../../pinecone-generated-ts-fetch';

jest.mock('../../pinecone-generated-ts-fetch', () => ({
...jest.requireActual('../../pinecone-generated-ts-fetch'),
Configuration: jest.fn(),
}));

describe('indexOperationsBuilder', () => {
test('API Configuration basePath is set to api.pinecone.io by default', () => {
const config = { apiKey: 'test-api-key' };
indexOperationsBuilder(config);
expect(Configuration).toHaveBeenCalledWith(
expect.objectContaining({ basePath: 'https://api.pinecone.io' })
);
});

test('controllerHostUrl overwrites the basePath in API Configuration', () => {
const controllerHostUrl = 'https://test-controller-host-url.io';
const config = {
apiKey: 'test-api-key',
controllerHostUrl,
};
indexOperationsBuilder(config);
expect(Configuration).toHaveBeenCalledWith(
expect.objectContaining({ basePath: controllerHostUrl })
);
});

test('additionalHeaders are passed to the API Configuration', () => {
const additionalHeaders = { 'x-test-header': 'test-value' };
const config = { apiKey: 'test-api-key', additionalHeaders };
indexOperationsBuilder(config);
expect(Configuration).toHaveBeenCalledWith(
expect.objectContaining({
headers: expect.objectContaining(additionalHeaders),
})
);
});
});
2 changes: 1 addition & 1 deletion src/control/indexOperationsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const indexOperationsBuilder = (
apiKey,
queryParamsStringify,
headers: {
'User-Agent': buildUserAgent(),
'User-Agent': buildUserAgent(config),
...headers,
},
fetchApi: getFetch(config),
Expand Down
29 changes: 26 additions & 3 deletions src/data/__tests__/dataOperationsProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { DataOperationsProvider } from '../dataOperationsProvider';
import { IndexHostSingleton } from '../indexHostSingleton';
import { Configuration } from '../../pinecone-generated-ts-fetch';

jest.mock('../../pinecone-generated-ts-fetch', () => ({
...jest.requireActual('../../pinecone-generated-ts-fetch'),
Configuration: jest.fn(),
}));

describe('DataOperationsProvider', () => {
let real;
const config = {
apiKey: 'test-api-key',
};

beforeAll(() => {
real = IndexHostSingleton.getHostUrl;
Expand Down Expand Up @@ -41,9 +50,6 @@ describe('DataOperationsProvider', () => {
});

test('passing indexHostUrl skips hostUrl resolution', async () => {
const config = {
apiKey: 'test-api-key',
};
const indexHostUrl = 'http://index-host-url';
const provider = new DataOperationsProvider(
config,
Expand All @@ -58,4 +64,21 @@ describe('DataOperationsProvider', () => {
expect(IndexHostSingleton.getHostUrl).not.toHaveBeenCalled();
expect(provider.buildDataOperationsConfig).toHaveBeenCalled();
});

test('passing additionalHeaders applies them to the API Configuration', async () => {
const additionalHeaders = { 'x-custom-header': 'custom-value' };
const provider = new DataOperationsProvider(
config,
'index-name',
undefined,
additionalHeaders
);

await provider.provide();
expect(Configuration).toHaveBeenCalledWith(
expect.objectContaining({
headers: expect.objectContaining(additionalHeaders),
})
);
});
});
Loading

0 comments on commit b386099

Please sign in to comment.